tests/vendor-differential.rs pins the vendored extraction's RFC edge behavior as explicit bytes, per vendor.md §Maintenance Posture (the differential-testing contract; ADR-011): - negotiation: empty/zero method lists -> 0xff, server-order method preference, 0x04 greeting refusal before the methods byte, EOF mid-handshake typed error, password sub-protocol EmptyUsername/ EmptyPassword + the 0x01/0xff-then-error reject path - command reads: unknown command -> 0x07 reply, RSV != 0 tolerance (upstream reads it unchecked), ATYP 0x02/0x05 -> 0x08, the 255-byte domain wire maximum, truncated v4/domain address errors - reply forms (new_reply regression guard): 10/22-byte shapes, constant VER/RSV across codes, zero-addr error replies, caller sock_addr verbatim on success - client session: per-code ReplyError mapping (incl. the 0x06 double map), domain request encoding, UDP-ASSOCIATE zero-target fallback form, skip_auth emits no greeting - UDP codec: GarbageInReserved, FRAG passthrough (the parser returns it; refusing is the caller's job), v6 round-trip, payload split - transfer/wait_on_tcp: pump both directions to completion, EOF on clean close, UnexpectedUdpControlGarbage carries the byte Every non-obvious expectation names the upstream behavior it pins (file + line in /workspace/fast-socks5, v1.0.0); the reference is consulted, never linked — no build-time dependency. Duplex pairs only (no TcpListener/UdpSocket/tokio::net in test code). Verification: cargo test (37 unit + 32 differential), cargo test --all-features (identical), cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo check --target wasm32-unknown-unknown — all clean. Task: tasks/implementation/vendor-differential-tests.md (status -> completed).
8.1 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | ||
|---|---|---|---|---|---|---|---|---|---|
| implementation/vendor-differential-tests | Vendor — the differential RFC edge-case test suite | completed |
|
moderate | medium | component | 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):
- 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). - 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 byto_be_bytes), truncated address fields (EOF mid-address → typed error). - Reply-form edge cases (the
new_replycarried change's regression guard): v4 reply = 10 bytes, v6 reply = 22 bytes, VER=0x05/RSV=0x00 in every reply, error replies carry0.0.0.0:0-equivalent (ATYP=1, zero addr+port), success replies carry the caller'ssock_addrverbatim. - Client-session edge cases: reply#1 with a non-zero reply code →
the mapped
ReplyError(thefrom_u8table), domain-target request encoding (ATYP=3, len-prefixed), the UDP-ASSOCIATE zero-target fallback (upstreamrequest_header'starget_addr: Nonebranch — pin the 10-byte zero form),skip_authbehavior (no method negotiation bytes on the wire). - UDP codec edge cases:
parse_udp_requestwith garbage RSV →GarbageInReserved, FRAG ≠ 0 passthrough (the parser returns frag; the caller refuses — pin the parser's contract),new_udp_header/parse_udp_requestround-trip for v4/v6/domain. transfer/wait_on_tcpsemantics:transferpumps both directions to completion over duplexes (including half-close propagation);wait_on_tcpreturnsEOFon clean close,UnexpectedUdpControlGarbageon 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
- The six coverage groups above each have at least one test;
all pass in
cargo test - Tests use only duplex/in-memory streams — no
TcpListener/UdpSocket/tokio::netin test code (the suite runs in the default-feature build) - Every non-obvious expectation carries a doc-comment note naming
the upstream behavior it pins (file + line in
/workspace/fast-socks5) cargo test(default features) green;cargo test --all-featuresgreen (no feature-gated vendor code exists, so both must pass identically)cargo check --target wasm32-unknown-unknownstill clean (tests are dev-only; the crate surface unchanged)cargo clippy --all-targets -- -D warningsandcargo fmt --checkclean
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; itssrc/lib.rstest module andsrc/server.rstest 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-914for method-order preference,src/client.rs:277-285for the UDP-ASSOCIATE zero fallback,src/server.rs:1236-1242for 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'starget_addr: Nonezero fallback; thetarget_addrfield reset the in-module tests use). The integration suite pins the wire form byte-for-byte (the 10-byte zero request, verified decodable to0.0.0.0:0via the vendoredread_address); the code-path forms of those tests already live insrc/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 thetransferhalf-close-propagation group is driven with drops (full EOF) rather thanshutdown()-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_replypublished — group 3; the typedMissingTargetAddrreplacing upstream'sanyhow::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).