--- id: implementation/vendor-differential-tests name: Vendor — the differential RFC edge-case test suite status: completed depends_on: [implementation/vendor-server-typestate, implementation/vendor-client-session] scope: moderate risk: medium impact: component level: implementation --- ## Description Build the crate's RFC edge-case test suite for the extracted vendor — the differential-testing posture vendor.md pins: "the crate's test suite covers the edge cases the extracted code must keep passing; when in doubt, diff behavior against the reference checkout." The per-task unit tests (wire vocabulary, typestate, client session) cover the happy paths; this task covers the RFC edge cases and the extraction-fidelity checks, using `/workspace/fast-socks5` as the behavioral reference (read-only — tests never depend on it at build time; the reference is consulted while writing the tests, and the assertions pin the expected bytes). Test infrastructure: tokio duplex streams (`tokio::io::duplex`) — no sockets, no `local` feature, runs in the default wasm-clean crate's test suite (tests are native-only via dev-deps; the crate itself stays wasm-clean). A first-party test harness drives the vendored typestate server against the vendored client session over duplex pairs. **Edge-case coverage** (each pinned with explicit expected bytes where the wire form is the point): 1. **Negotiation edge cases**: method-list parsing (empty method list, zero methods offered → 0xff), method-order preference (server picks its first match, not the client's first offer), version mismatch (0x04 greeting → refusal), truncated greeting (EOF mid-handshake → typed error, no hang), password-auth sub-protocol (empty username → `EmptyUsername`, empty password → `EmptyPassword`, reject path → 0x01/0xff reply then error). 2. **Command-read edge cases**: unknown command byte (0x04+ → `UnknownCommand` → 0x07 reply), reserved-byte tolerance (RSV ≠ 0 — upstream reads it without checking; pin the upstream behavior), address-type edge cases (ATYP 0x02/0x05+ → `IncorrectAddressType` → 0x08 reply), domain-length boundary (255-byte domain OK, 256+ rejected at encode time by `to_be_bytes`), truncated address fields (EOF mid-address → typed error). 3. **Reply-form edge cases** (the `new_reply` carried change's regression guard): v4 reply = 10 bytes, v6 reply = 22 bytes, VER=0x05/RSV=0x00 in every reply, error replies carry `0.0.0.0:0`-equivalent (ATYP=1, zero addr+port), success replies carry the caller's `sock_addr` verbatim. 4. **Client-session edge cases**: reply#1 with a non-zero reply code → the mapped `ReplyError` (the `from_u8` table), domain-target request encoding (ATYP=3, len-prefixed), the UDP-ASSOCIATE zero-target fallback (upstream `request_header`'s `target_addr: None` branch — pin the 10-byte zero form), `skip_auth` behavior (no method negotiation bytes on the wire). 5. **UDP codec edge cases**: `parse_udp_request` with garbage RSV → `GarbageInReserved`, FRAG ≠ 0 passthrough (the parser returns frag; the *caller* refuses — pin the parser's contract), `new_udp_header`/`parse_udp_request` round-trip for v4/v6/domain. 6. **`transfer`/`wait_on_tcp` semantics**: `transfer` pumps both directions to completion over duplexes (including half-close propagation); `wait_on_tcp` returns `EOF` on clean close, `UnexpectedUdpControlGarbage` on a control byte. **Differential method**: for each behavior above, consult the reference checkout's behavior (its own tests + code reading) and pin the *same* expectation in-tree, noting the upstream source line in the test's doc comment where the expectation is non-obvious. Where the extraction deliberately diverges (the pruned error variants, the published `new_reply`), the test pins the *in-tree* contract and the divergence is already recorded in vendor.md — no new divergence may be introduced by this task. ## Acceptance Criteria - [x] The six coverage groups above each have at least one test; all pass in `cargo test` - [x] Tests use only duplex/in-memory streams — no `TcpListener`/ `UdpSocket`/`tokio::net` in test code (the suite runs in the default-feature build) - [x] Every non-obvious expectation carries a doc-comment note naming the upstream behavior it pins (file + line in `/workspace/fast-socks5`) - [x] `cargo test` (default features) green; `cargo test --all-features` green (no feature-gated vendor code exists, so both must pass identically) - [x] `cargo check --target wasm32-unknown-unknown` still clean (tests are dev-only; the crate surface unchanged) - [x] `cargo clippy --all-targets -- -D warnings` and `cargo fmt --check` clean ## References - docs/architecture/vendor.md §Maintenance Posture (the differential-testing contract) - docs/architecture/decisions/011-inline-vendor.md (differential testing guards the RFC edge cases) - docs/architecture/decisions/002-protocol-base.md (faithful reply-code mapping) - Reference checkout: `/workspace/fast-socks5` (v1.0.0 — behavior source for the pinned expectations; its `src/lib.rs` test module and `src/server.rs` test module are the upstream coverage to mirror where extractable) - RFC 1928 (the wire form's authority) ## Notes - **Suite location**: `tests/vendor-differential.rs` (an integration test — dev-deps only, no crate-surface change). 32 tests over the six coverage groups; tokio duplex pairs throughout, no sockets. - **Differential method followed**: each non-obvious expectation's doc comment names the upstream file + line it pins (e.g. `src/server.rs:903-914` for method-order preference, `src/client.rs:277-285` for the UDP-ASSOCIATE zero fallback, `src/server.rs:1236-1242` for the parser/caller FRAG split). The reference checkout was read, not linked — assertions pin bytes. - **Client-session private-API edge cases**: two wire behaviors are only reachable through private methods (`request_header`'s `target_addr: None` zero fallback; the `target_addr` field reset the in-module tests use). The integration suite pins the wire form byte-for-byte (the 10-byte zero request, verified decodable to `0.0.0.0:0` via the vendored `read_address`); the code-path forms of those tests already live in `src/vendor/client.rs`'s in-module tests (privacy-scoped), written by the client-session task. No new divergence introduced — the pin notes cross-reference both. - **Duplex half-close shape**: `DuplexStream`'s shutdown closes the whole pipe's write side, so the `transfer` half-close-propagation group is driven with drops (full EOF) rather than `shutdown()`-per-direction; the test's doc comment records why. - **First-party-only divergence check**: the only in-tree contracts the tests pin beyond upstream behavior are the two recorded carried changes/adjustments (`new_reply` published — group 3; the typed `MissingTargetAddr` replacing upstream's `anyhow::Error` — group 4 note). No new divergence introduced. ## Summary Built the vendor's differential RFC edge-case suite per vendor.md §Maintenance Posture: `tests/vendor-differential.rs`, 32 tests covering the six groups — negotiation edge cases (empty/zero method lists → 0xff, server-order preference, 0x04 greeting refusal, EOF mid-handshake typed error, password sub-protocol empty username/password + the 0x01/0xff-then-error reject path); command reads (unknown command → 0x07, RSV≠0 tolerance, ATYP 0x02/0x05 → 0x08, the 255-byte domain boundary, truncated v4/domain address errors); reply forms (10/22-byte shapes, constant VER/RSV, zero-addr error replies, caller-`sock_addr`-verbatim success replies); client sessions (per-code `ReplyError` mapping incl. the 0x06 double map, domain request encoding, UDP-ASSOCIATE zero fallback, `skip_auth`'s no-greeting wire shape); UDP codec (garbage RSV, FRAG passthrough contract, v6 round-trip, payload split); and `transfer`/`wait_on_tcp` (pump-to-completion, EOF on clean close, `UnexpectedUdpControlGarbage` carrying the byte). All expectations byte-pinned with upstream file+line references; verification green (`cargo test` 37+32, `--all-features` identical, clippy/fmt/wasm clean).