# ADR-002: Protocol Base: the Vendored fast-socks5 Typestate Subset ## Status Accepted (2026-09-20, Phase 1; the extracted-and-forked posture is OQ-SK-04's resolution: the extraction mechanics in [ADR-011](011-inline-vendor.md), the ownership posture in [ADR-013](013-targeted-fork.md)) ## Context Phase 0 evaluated the protocol-base candidates: fast-socks5 (the full-fidelity RFC 1928 implementation, explicit typestate server API, generic over `T`), iroh-socks5 (structurally the closest sibling, but iroh-coupled and an RFC subset), and a hand-rolled codec (rejected as anti-prior-art; alknet's client hand-rolls, and it is the known weakness). POC #1 confirmed the load-bearing property: fast-socks5's `Socks5ServerProtocol` accepts a channels `BiStream` directly (`T: AsyncRead + AsyncWrite + Unpin`), and POC #4 measured the wasm gating at ~120 cfg lines with the wasm-clean subset functionally complete (a full RFC 1928 CONNECT conversation passes `--no-default-features`). ## Decision - **The protocol layer is built on fast-socks5's explicit typestate API**: `Socks5ServerProtocol` (negotiate/finish_auth/ read_command/reply_success/reply_error), the client side `Socks5Stream::use_stream` / the associate session, and the pure codecs (`new_udp_header`/`parse_udp_request`). - **Only the explicit API is used.** The legacy `Socks5Server`/`Socks5Socket`/`Incoming` bind-based API is deprecated upstream and is out; never forked into the default path. - **Base = v1.0.0, extracted in-tree and forked** (ADR-011 for the extraction mechanics and the two carried changes; ADR-013 for the targeted-fork ownership posture). `/workspace/fast-socks5` remains the read-only reference for differential testing of RFC edge cases; the fork point is noted in the module docs and the bundled license notice. - **Refusals map faithfully per RFC 1928**: `ReplyError` codes (`as_u8`/`from_u8`) are the error vocabulary at the command plane; refusals are never collapsed into generic errors (AGENTS.md convention 2). POC #1 validated the mapping (dial refusal → 0x05). ## Consequences - The crate owns its protocol code in-tree (the fork), so BIND and the `new_reply` publication ride the same tree; no upstream waits. - The typestate's interception points are the layering seams: command-read (the dial/accept dispatch), `transfer` (the raw two-pump), and, where the default handlers are unusable (UDP ASSOCIATE's unconditional bind), direct typestate driving. The interception-point map is spec'd in [producer.md](../producer.md). - iroh-socks5's design ideas (the `{addr, data}` frame, control-EOF association lifetime, FRAG≠0 refusal) were adopted at the design level, not the code level (`docs/research/iroh-socks5-eval.md`). ## References - OQ-SK-04 (resolved: the extracted-and-forked base), OQ-SK-03 (the `run_udp_proxy_custom` caveat) - `docs/research/iroh-socks5-eval.md` (the base comparison), POC #1 + POC #4 (`poc-connect-wasm-findings.md`) - Sibling precedent: alktunnels ADR-002 (one-ALPN option A)