Files
alksocks/tasks/implementation/vendor-differential-tests.md
T
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- Rewrote em dash prose across the whole tree outside docs/reviews/
  (the historical reviews stay verbatim, matching the alktunnels and
  alktty precedent): src doc comments and inline comments (587),
  docs/architecture (319), docs/research + sdd_process (484), tests
  (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77),
  AGENTS.md (85), and the .opencode/agents prompts (109)
- Fenced content follows the precedent: comments, ASCII-diagram label
  separators, and template placeholders rewritten; wire data, JSON/BAST
  documents, byte diagrams, and transcripts survive verbatim; assert,
  expect, panic, error-display, and format message literals rewritten
  as prose; no code, fixture, or test data change
- src/rfc1928/LICENSE: the two dashes in alksocks' own provenance
  header rewritten; the MIT legal text untouched
- AGENTS.md adds Project Convention 18: no em dashes in prose; varied
  restructure, no spaced-hyphen substitutes, fenced prose rule, en
  dash numeric ranges exempt, docs/reviews/ exempt from the greps
- tasks/*.md frontmatter: the name values rewritten by the sweep are
  now quoted (a raw colon inside an unquoted YAML scalar parsed as
  null); taskgraph validate reports 37 tasks, no issues

Verification: cargo test; cargo clippy --all-targets -D warnings;
cargo fmt --check; cargo doc --no-deps; fuzz corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/ and the bast.md BAST
JSON data fence
2026-11-03 19:31:53 +00:00

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
implementation/vendor-server-typestate
implementation/vendor-client-session
moderate medium component implementation

Description

Build the crate's RFC edge-case test suite for the extracted vendor, per 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, since 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) with 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, so pin that 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, the 10-byte zero form being pinned), 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, and 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, so 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, with no TcpListener/ UdpSocket/tokio::net in 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-features green (no feature-gated vendor code exists, so both must pass identically)
  • cargo check --target wasm32-unknown-unknown still clean (tests are dev-only; the crate surface unchanged)
  • 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, the 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, with 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, so 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, in group 3; the typed MissingTargetAddr replacing upstream's anyhow::Error in group 4's 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).