-
Notifications
You must be signed in to change notification settings - Fork 0
Mesh Protocol
The watchdog talks to whyknot.dev over a single persistent WebSocket. The wire shape has versioned forward/backward like an HTTP API does — the watchdog can talk to any server back to v1 (legacy) and any server up to its own client version.
Subprotocol: whyknot-v3 literal in the WebSocket upgrade. If the server (or an intermediate proxy) doesn't echo it back, the client silently falls back to v2 behavior. The user-visible console doesn't change either way.
Compression: RFC 7692 permessage-deflate offered on the upgrade. Server can refuse — the connection is uncompressed-but-functional in that case. When negotiated, every frame is compressed before it leaves the server's egress, so the welcome and resolve_log streams are roughly half their v2 size on the wire.
Welcome cache: The server's welcome frame is mostly unchanging across reconnects (engine list, feature list, version strings). v3 adds a welcome_hash field — an opaque server-side fingerprint. The client persists it per-node and offers it back on the next reconnect via a small client_hello frame:
[upgrade] Sec-WebSocket-Protocol: whyknot-v3
client → server (FIRST FRAME):
{"action":"client_hello","welcome_hash":"<cached-hash or null>","client_id":"<process-guid>"}
server → client:
EITHER (cache hit, small):
{"action":"welcome_cached","protocol_version":3,"node":"node1","warp_active":true}
OR (cache miss, full welcome with new hash to cache):
{"action":"welcome", "protocol_version":3, "node":..., "engines":[...],
"features":[..., "v3_compression", "welcome_hash_ack"], "yt_dlp_version":...,
"server_version":..., "welcome_hash":"<new-fingerprint>"}
After the welcome, the resolve hot path is byte-exact identical to v2 — resolve request → resolved / fallback_native response. v3 only changes the handshake.
%LOCALAPPDATA%Low\WKVRCProxy\v3_welcome_cache.json — same LocalLow state-root as clean_exit.flag, codec-state.json, etc. (see Logs and Diagnostics).
Per-node keying: apex-302 routes between node1 and node2, and their welcome contents differ (different yt-dlp versions, different node labels, different warp_active state). Cache is keyed on the hostname so node-rotation doesn't thrash.
Atomic write via <file>.new → File.Move(overwrite:true). A crash mid-write leaves either the old or new file intact, never half-written.
Asymmetric format negotiation. Client → server stays JSON over WebSocket Text frames; server → client post-welcome picks JSON-Text or MessagePack-Binary based on what the client offered. Control frames (welcome / welcome_cached / client_hello) stay JSON either way for debuggability + first-byte simplicity.
The choice rides on the existing client_hello frame:
client → server (FIRST FRAME, JSON-Text):
{
"action": "client_hello",
"welcome_hash": "<cached or null>",
"client_id": "<persistent guid>",
"accept_formats": ["msgpack", "json"] ← v3.1 NEW (preference-ordered)
}
server → client (welcome, JSON-Text):
{
"action": "welcome", ...,
"negotiated_format": "msgpack" ← v3.1 NEW. "json" or "msgpack".
}
Server picks the first format from accept_formats it supports. Older servers (v2 / v3.0) ignore the unknown field and the response defaults to json — universal fallback baseline. Choice is fixed for the connection's lifetime; no per-frame negotiation.
After the welcome, the hot path (resolved / fallback_native / resolve_log) arrives as MessagePack Binary frames when the server picked msgpack. Wire savings measured live: 60-72% smaller than JSON, ~67% faster to decode. Wrapper still receives JSON over the local pipe — the watchdog transcodes msgpack → JSON before the pipe write so the wrapper stays simple and small.
When the server doesn't accept whyknot-v3:
- v2 servers don't recognize the subprotocol → echo it back as null/empty.
- Cloudflare in some configs strips unrecognized WS subprotocol headers → server never sees it.
- Older server builds before v3.0 deployed.
In all three cases:
- Client's
ShouldSendClientHelloreturns false based on the echoed subprotocol. - No
client_hellois sent. - Existing v2 path runs unchanged: server emits its plain welcome, client deserializes it, hot path operates as it has since v2.
- File-only
[mesh][v3] negotiated subprotocol=<none> v3=falselog line inwatchdog-<utc>.logso the diagnostic is available without any console churn.
- Doesn't change the resolve hot path.
resolverequest andresolved/fallback_nativeresponse shapes are identical to v2. - Doesn't introduce a local HTTP proxy server (the parked Part E adapter design — separate, larger work).
- Doesn't change the wrapper (
Tools/yt-dlp.exe). v3 is a watchdog ↔ mesh-server concern only.
- Engineering Standards — wire-protocol contributor rules.
-
src/WKVRCProxy.Shared/Protocol.cs— DTO definitions, byte-exact with whyknot.dev'sMeshResolveProtocol.cs. -
src/WKVRCProxy/MeshClient.cs— connection lifecycle + dispatch. -
src/WKVRCProxy/WelcomeCache.cs— cache persistence. -
src/WKVRCProxy/MeshJsonContext.cs— source-gen JSON metadata for the v3 DTOs.
Start here
For users
Reference
For maintainers