fc6571425acea2cc656ed0a65169e01398ea02c2
83
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fc6571425a | version: bump 0.3.1 -> 0.3.2 (the alktunnels 0.5.0 dependency-patch ride, pre-release) | ||
|
|
fcacedb21e |
dep: alktunnels 0.4.1 -> 0.5.0 (the correlation-gate release)
- Additive upstream API only (the forwarded-op establisher variant, register_tunnel_forwarded_openable_with_establisher + ForwardedGate); no alkcall bump (0.10.1 rides on), no wire change, and no change on this crate's paths: our composed accept keeps the base register_tunnel_forwarded_openable byte-for-byte. Verification: cargo test (default + composed: 145/148 lib tests, all suites green), cargo clippy --all-targets -D warnings (default + composed), cargo fmt --check, cargo check/clippy wasm32-unknown-unknown. |
||
|
|
db051dd068 |
release: v0.3.1 — the dependency-patch (alkcall 0.10.1 / alktunnels 0.4.1)
- alkcall 0.10.0 -> 0.10.1: the adoption-window ordering fix (review 013, routed from alkgit's wave-2 gate) — install+park-drain is one atomic section in the channels demux; the fix surface is private upstream, no change on this crate's paths (relevant in principle to the bind-first adopt flow) - alktunnels 0.4.0 -> 0.4.1: the dependency-patch ride, no API/wire change - fuzz/shared harness alkcall pin bumped in lockstep - doc pins: AGENTS.md convention 11, README (rides paragraph), overview.md dependency tree + feature table; CHANGELOG 0.3.1 entry Verification: cargo test (145 passed), cargo test --all-features (638 passed), fuzz corpus replay green, clippy (all-targets + wasm32-unknown-unknown) -D warnings clean, cargo fmt --check clean, wasm check green, cargo doc clean, publish dry-run cleanv0.3.1 |
||
|
|
b548a47449 | docs: AGENTS.md — the release steps pinned (annotated tags, the v0.3.0 flow) | ||
|
|
f56b44589c |
release: v0.3.0 — the dependency-major (alkcall 0.10.0 / alktunnels 0.4.0)
- alkcall 0.9.0 -> 0.10.0 (the gateway ownership-seam release): the GatewayDispatch ownership-provider threading + the loud unwired-spine default are not on this crate's paths (no gateway module use; registry dispatch on the native wire untouched). Zero API/wire change here. - alktunnels 0.3.0 -> 0.4.0 (its dependency-major ride): no alktunnels API/wire change; the composed providers see none of it. - fuzz/shared: the harness's own alkcall pin bumped in lockstep (its ChannelOpenError construction makes a drifting pin a duplicate-type compile failure, not silent drift) — the only bump contact. - Docs: CHANGELOG 0.3.0 entry, README version blurb (alktunnels style), AGENTS.md pin update, deps tree in overview.md. - Minor-not-patch for the established reason: semver-incompatible dependency bumps + this crate's public ChannelOpenError re-exports (SocksOpenError::Open/open_ref) are semver-visible downstream. Verification: cargo test (default 145 unit + suites green), --features local, --all-features (171 unit + all suites), clippy --all-targets --all-features -D warnings, fmt --check, wasm32-unknown-unknown check + clippy, fuzz corpus replay 3/3, publish --dry-run clean.v0.3.0 |
||
|
|
9ed3e8c83d |
release: v0.2.0 — the dependency-major (alkcall 0.9.0 / alktunnels 0.3.0)
- Cargo.toml: alkcall 0.8.1 -> 0.9.0 (crates.io, the stub-sweep release) and alktunnels 0.2.1 -> 0.3.0 (its dependency-major ride); 0.1.0 -> 0.2.0 (semver-incompatible dep bumps + public alkcall re-exports -> minor, matching the alktunnels 0.2.1 -> 0.3.0 ride) - fuzz/shared/Cargo.toml: alkcall 0.9.0 (one alkcall version across the path graph) - No code change: zero contact with the 0.9.0 breaking surface (no marked-pub registration, no control-handle subscription, no resource enumerator use, no generic op/register announcements; Establishment::default() is the only Establishment contact, in the session gate) - CHANGELOG: 0.2.0 section + compare links - README: the 0.2.0 note - docs/architecture/overview.md: dep tree + feature table alkcall 0.9.0 / alktunnels 0.3.0 - AGENTS.md: alkcall refs — v0.8.x/0.8.0 pin -> v0.9.x/0.9.0 Verification: cargo test (145 default / 171 all-features), fuzz/shared corpus replay (3/3), clippy --all-targets -D warnings (native + wasm32-unknown-unknown), fmt --check, wasm32 check, cargo doc, and cargo publish --dry-run --allow-dirty all greenv0.2.0 |
||
|
|
3127e0231a |
release: v0.1.0 — the initial release
- CHANGELOG: the 0.1.0 entry finalized for publish — the alkcall 0.8.1 / alktunnels 0.2.1 bumps and the fuzzing adoption folded in (the crate is unpublished, so no separate hardening cut — the alktunnels 0.2.1 precedent only applies post-publish); the fuzz/ workspace bullet added (three targets, 129 seeds, corpus-replay gate, campaigns clean) - README: the Version 0.1 note aligned (alkcall 0.8.1 — the duplicate-adopt fix is directly relevant to the consumer open/adopt paths; alktunnels 0.2.1; the fuzz posture noted) - docs/architecture/overview.md: dep tree 0.8.1 / 0.2.1 Verification: cargo test (376 default / 638 all-features), fuzz/shared corpus replay (3 pass), clippy --all-targets -D warnings (default, all-features, wasm32-unknown-unknown), fmt --check, doc, wasm32 check, and cargo publish --dry-run --allow-dirty all green (64 packaged files — fuzz/, docs/research|reviews|plans/, tasks/, .opencode/, AGENTS.md all excluded)v0.1.0 |
||
|
|
08a3694464 |
fuzz: adopt fuzzing (wave 1) + alkcall 0.8.1 / alktunnels 0.2.1 bumps
Adopt the alk*/fuzzing posture (alkcall 0.8.1 + alktunnels 0.2.1 precedent) before the v0.1 publish — the cheapest moment this crate will ever have for it: - alkcall 0.8.1 + alktunnels 0.2.1: the duplicate-adopt/open fix is directly relevant to this crate's consumer open/adopt paths - fuzz/ workspace (the sibling layout): nightly-pinned subtree, stable fuzz/shared library (corpus replay = the fuzz gate), detached runner, JSON dict, deterministic seed generator; 129 committed seeds - three wave-1 targets over already-pub APIs: datagram_codec (the ASSOCIATE frame codec — the alkcall-fuzz-doc-assigned downstream target), rfc1928_codec (the forked RFC 1928 core's handshake state machines, both protocol directions), params_parse (the wire-stable open-op surface) - one diagnostic: DatagramReader::retained_bytes() (the decoder-state bound's assertion target, the alktunnels retained_bytes() posture) - docs/research/fuzzing.md: the scoped plan + implementation log; AGENTS.md gains the corpus-replay verification line - campaigns: 3 × 10-min detached runs exited 0 (datagram ~1.9M execs, rfc1928 ~2.0M, params ~3.1M; oom/timeout/crash 0/0/0 on all 33 fork jobs per target; coverage saturated on both codec targets). No crate bugs; three harness-model defects found + fixed (2 pre-campaign by corpus replay, 2 campaign-found, crash inputs promoted as seeds 900/ 901): the len==0 well-framed decode partition, the fieldless-struct serde-seq form, the BadAtyp re-frame arm, the short-domain count-walk gate - wave 2 (session_pumps stateful target) deferred to post-0.1 — the byte-level targets carry the attacker-reachable load for the publish Verification: cargo test (15 suites ok, default + all-features), clippy (all-targets + wasm + fuzz/shared), fmt, cargo doc, publish --dry-run, corpus replay 129 seeds green, cargo fuzz build clean. Refs: alkcall docs/research/fuzzing.md (§7.9 posture), alktunnels docs/research/fuzzing.md; docs/research/fuzzing.md (this crate's copy) |
||
|
|
8af23eeee0 |
docs: OQ-SK2-09 (the UDP door's peer-pin posture) + OQ-SK2-06's trigger-ownership check
- OQ-SK2-09 recorded: option 2 (the same-IP lock + reply-port
refresh + the RFC drop rule) landed in 0.1.0 (
|
||
|
|
31fb11cf83 |
door: the peer-pin fix — the UDP front door's same-IP lock + reply-port refresh (OQ-SK2-09 option 2)
- serve_on pins the client's source IP (parsed datagrams only — protocol garbage can never move the pin) and sends replies to the latest-seen port from that IP: a NAT port rebind under a stable IP keeps receiving (the review 003 S-1 residual, now fixed) - the RFC 1928 MUST implemented: datagrams from a source IP other than the pinned one are dropped (previously every parsed datagram was forwarded regardless of source); the spoof primitive shrinks from any-source to same-IP off-path - the residual (a full client IP change mid-association) stays the RFC's expected-peer posture — revisitable per OQ-SK2-09 (the control-conn-derived expected IP is the complete fix, ADR-shaped) - two tests pin the new behavior: the NAT port-rebind follow (the old port stops receiving, the new one takes over) and the foreign-IP drop (no reply to the spoofer, the association healthy) Verification: cargo test --features local (15 suites green, door-udp 38 tests), clippy --all-targets --all-features -D warnings clean, fmt clean. |
||
|
|
086aeb4135 |
local: the non-blocking resolver — tokio lookup_host replaces the blocking to_socket_addrs
- resolve_target + local_tcp_dial resolve via tokio::net::lookup_host (the blocking std resolver parked the pump on resolver latency — the review 003 S-1-adjacent residue); the module doc's blocking caveat retired - semantics unchanged: the dial keeps its try-all-addresses loop, the egress keeps first-address (upstream parity) Verification: cargo test --features local (15 suites green), clippy --all-targets --all-features -D warnings clean, fmt clean. |
||
|
|
4d9796be64 |
publish-prep: the v0.1 pre-publish round — the README, the CHANGELOG, the license files, the readme field
- README.md: the alktunnels-shape quick start (producer registration, the three consumer sessions, both doors, the producer-side listener), the architecture table, the feature matrix, the verification commands — self-contained, since docs/architecture/ rides the package but docs/reviews|research|plans do not - CHANGELOG.md: the Keep-a-Changelog 0.1.0 entry (the sibling shape: Unreleased section + tag links) - LICENSE-MIT / LICENSE-APACHE: copied from the siblings (crates.io license-file convention; the crate license is dual from Cargo.toml) - Cargo.toml: `readme = "README.md"` (the sibling pattern; the packaged crate showed `readme = false`) Verification: cargo test + cargo test --all-features (15 suites each), clippy --all-targets --all-features -D warnings, fmt --check, doc --no-deps (0 warnings), wasm32-unknown-unknown check (default + composed), publish --dry-run (64 files, 278.3 KiB). |
||
|
|
13c6209b07 | review-003: the residue round's dispositions recorded — the review resolved | ||
|
|
b43cbd24ba | review-003 N-8: the worthwhile residue probed — the BIND Domain-reply wire helper + table, the refusal-recode unit pins, the serve() wrappers, the len/is_empty accessors, the bridge later-batch garbage | ||
|
|
b43bf5462b | review-003 D-2: the composed-egress failure matrix pinned (eviction→association-end, relay exits, oversize guard) | ||
|
|
59e8fb1649 | review-003 D-1/D-3: the associate_with_token probe + the io-kind mapping tables table-tested | ||
|
|
87afe706de | review-003 doc items: S-1/N-1/N-2/N-3 — the accepted-residual posture notes | ||
|
|
d51ea8a262 | review-003: fix the frontmatter's invalid YAML — the plain-scalar colons broke the parse | ||
|
|
2db05172ef |
review-003: the assembly-layer full review — F-1/F-2 fixed, the residue dispositioned
- Review 003 written (docs/reviews/003-assembly-layer-implementation- review.md): the batch-2 assembly layer post-landing, the review-002 template — correctness/security/smells/coverage over the five batch-2 modules + the batch-2 consumer/producer diff surface. Baseline: 375 default / 620 all-features green, clippy/fmt/doc clean, both wasm checks clean, publish dry-run completes, llvm-cov 93.68% lines / 91.01% functions. - F-1 (major, fixed): the cred-bearing door swallowed every in-band refusal's mapped reply code (the CONNECT/BIND session-open errors and the accept-stage Err were bare closes) — a vanilla client got no reply where ADR-007 pins a reply-code-shaped refusal. Fixed with a write_refusal_reply helper (the door's boundary codec, RFC placeholder BND.ADDR) at all three sites, pinned by cred_door_in_band_refusal_surfaces_the_reply_code. - F-2 (major, fixed): UdpFrontDoor::serve_on returned Ok(()) on a fatal relay-socket I/O failure against its own doc contract — the Io exit now surfaces as Err; the idle/association/session exits stay clean. - Findings recorded, not fixed (the release-polish round's residue): D-1 (associate_with_token 0% covered), D-2 (the composed-egress failure matrix), D-3 (the io-kind refusal-table arms), S-1 (the UDP door's learned client address), N-1..N-8 (doc lines, posture notes, coverage residue). Verification: cargo test (375) + --all-features (620) green, clippy --all-targets --all-features -D warnings clean, fmt --check clean, doc --no-deps clean, both wasm checks clean, publish dry-run completes. |
||
|
|
04c76e3a41 |
review: the batch-2 gate closed — the assembly layer verified, R-1/R-2 fixed
Review checkpoint closing batch 2 (the assembly layer). The gate opens: the never-binds posture, seam contracts, door fidelity, the bind-first graduation, feature-gate hygiene, and POC fidelity all verify clean; two findings were corrected in the review change. Findings corrected: - R-1 (major): the UDP front door's in-band decode created a fresh DatagramReader per call and kept only the first frame of a batch — POC #2's first-cut bug (reader state must persist across calls; batched datagrams were silently dropped, mid-frame EOF was silent). Fix: an owning InbandBridge (persistent reader + pending queue + fail-loud mid-frame EOF, the AssociateSession shape) threaded through the channel->socket pump; pinned by three bridge unit tests (which fail against the per-call reader) and an end-to-end doubled-echo door-udp test. - R-2 (convention 2): seven non-test expects -> typed errors — DoorBuildError (both door builders' .build() is now Result), ProducerDoorBuildError/ProducerDoorError (the bind decision is serve's typed error), the composed table invariant as an egress error, register_forwarded_serving -> Result (the alkcall diagnostic surfaced), TokenClientError (wrap on a spent client). New error types re-exported; test call sites updated. - R-3/R-4 (doc residues): producer.md's local-backend section described a channel pipe — the landed ProducerDoor serves serve_socks5_over on the accepted socket directly; associate_with_token's doc (the TCP door refuses ASSOCIATE, never calls it); SessionOwner's doc (the accept uses SyncHalves/ OwnedWrite). Verification: cargo test (default) green; cargo test --all-features green; cargo clippy --all-targets [--all-features] -- -D warnings clean; cargo fmt --check clean; both wasm checks clean (default + composed); local-on-wasm fails at mio as designed; cargo doc --no-deps clean. Task: tasks/implementation/review-assembly-layer.md (status: completed; the batch-2 gate opens release polish). |
||
|
|
1addedfdd0 |
assembly: the cross-cutting integration suite + docs sync (batch 2 closes)
- tests/assembly-integration.rs (7 tests, local + composed): the full -D story (vanilla cred-bearing client -> TCP door + UDP door -> producer w/ local providers -> real echo targets; CONNECT + BIND + ASSOCIATE live, one-command-per-connection, teardown + fresh sessions), the composed story (direct_egress + bindfirst_accept + real TCP dial; all three commands; the lazy-table dial-count witness), mixed assembly (channels-native + door clients on one registration; composed seams beside native sessions), the assembly teardown matrix (UDP-door idle bound; channel reaped; fresh association after), the producer door driving all three commands, and the -D domain-target pin (resolution at the producer's seam, ADR-012). - Fix the drift the suite surfaced: ComposedEgress::next_reply ended fresh (never-opened-table) associations at birth — the ended-relay Err now fires only once a tunnel existed and every relay ended; the fresh table parks (the readiness contract). Regression-pinned in tests/composed-egress.rs (fresh_association_parks_before_any_ tunnel_is_opened). - Harness: the composed-accept machinery single-sourced into tests/harness.rs (the composed suites' shapes + the five-gate composed identity); composed-accept.rs consumes the shared shapes. - Docs sync: README Current State (the assembly layer landed; both suites named), lib.rs crate docs (the assembly-layer paragraph), consumer.md (the TokenClient door-only contract + the one-command-per-connection posture reconciled). - Task assembly-integration-suite: completed; Notes + Summary filled (the local-on-wasm failure observed: mio's unsupported-wasm error). Verification: cargo test (375), cargo test --all-features (615), clippy --all-targets -D warnings (default + all-features), fmt --check, doc --no-deps (0 warnings), both wasm checks (default + composed), local-on-wasm fails as designed. |
||
|
|
4e1673eefc |
composed: the bind-first accept — the listener on a listen resource
Lands the composed accept (ADR-007 variant 2 / ADR-008): the BIND accept whose listener lives on an alktunnels 0.2.0 bind-first listen resource, at the graduation's vanilla fidelity. - `composed::bindfirst_accept` — the BindAcceptFactory: opens the listen channel (reply#1 advertises the open-op reply's real `bound`, the 0.8.0 reply projection — no expected-addr stand-in), inserts the minted `listen` token into the correlation registry, and returns the accept future parked on the token's oneshot. - `composed::register_forwarded_serving` — the forwarded-op serving half on the SOCKS5 producer's connection: the shared handler correlates by token and owns the middle pump (either-EOF completion; a three-party per-direction shutdown cascade would deadlock). - The Sync-bound wrinkle: `SyncHalves`/`OwnedWrite` (mutex Sync-proof, no unsafe) over the forwarded halves; the owner (`ForwardedOwner`, the TunnelListener) rides inside the write payload — dropping the handle drops the session, halves, and listener together (teardown structural, compiler-enforced). - `tests/composed-accept.rs`: the full story (real bound, real peer, raw pass-through round trip), client-gone teardown drop-witness, distinct tokens for concurrent BINDs, per-accept isolation of a FORBIDDEN forwarded open, late-forwarded correlation miss. - The producer-side adopted forwarded entry rides the per-adopter contract (transport-EOF reap), as upstream's bind-first suites concede; the no-leak probe reflects that shape. Verification: cargo test (default + --all-features), wasm check/clippy (hard gate), clippy --all-targets --all-features -D warnings, fmt --check — all clean. |
||
|
|
3128d2bd4f |
composed: the direct-op egress — the lazy destination→tunnel table
The `composed` feature's ASSOCIATE egress (ADR-008's composed variant, ADR-009's gate): `composed::direct_egress` — each destination dials through an alktunnels 0.2.0 direct-op channel (`TunnelSession::open_direct`, `Substrate::Udp`), per-target ACL for free, the POC #2 `ComposedEgress` shape ported to the landed seam types. - `Cargo.toml` — `composed = ["dep:alktunnels"]` (alktunnels pinned "0.2.0"; rides `composed` alone, `cargo tree --edges normal` spot-checked); dev-dep for the composed suite. - `src/composed/mod.rs` — the lazy destination→tunnel table (LRU-capped, `DEFAULT_MAX_TUNNELS` = 256), per-tunnel relay tasks draining a shared inbound queue, `SessionOwner` (the owner wrapper shared with the composed accept — the batch-1 review's Sync-bound wrinkle), the readiness contract (real async park; ended relays → association-end `Err`, never a hot `Ok(None)`), framing-once, domain pass-through unresolved (ADR-012), the backpressure bound documented on the constructor. - Two deliberate deviations from the POC shape, documented in the task's Notes: the closed-queue condition is a live-relay count (the egress holds its own `Sender`, so the POC's queue-close arm was unreachable through this surface); the tunnel codec is alktunnels' (`[len]` + payload), not the RFC-framed reader. - `tests/composed-egress.rs` — round-trips per destination → one tunnel each, table reuse, LRU eviction/re-dial, the readiness park, the closed-relay-set `Err`, framing-once through the wire layer, domain pass-through — over a `wire_tunnel_direct` harness topology (the consumer-side shape with the direct op on the producer's serving registry). Verification: `cargo test` + `--all-features` pass (13 suites); `cargo check --features composed --target wasm32-unknown-unknown` clean (the hard gate); clippy `--all-targets --all-features -- -D warnings` clean; `cargo fmt --check` clean; `cargo doc --no-deps` clean. |
||
|
|
29569d9afd |
local: the producer-side local backend — the producer's front door
- ProducerDoor (feature local, src/local/producer_door.rs): a real TcpListener accept loop handing each accepted connection to the landed Socks5Producer::serve_socks5_over — the NoAuth in-stream RFC conversation with the local seam providers dialing/egressing/accepting; no new protocol-layer code. - The accept path owns ADR-014's two knobs: the idle-first-byte peek (non-consuming) and the max-concurrent-sessions cap (try_acquire — an accept beyond the bound is refused door-side). - ProducerDoorGuard: the listener's lifetime is the caller's — graceful close() awaits the loop's end; Drop aborts best-effort (the alktunnels TunnelListener teardown posture). In-flight sessions are never force-closed. - Per-connection failures are logged + isolated (the loop continues). - The BIND gate on this path is the builder's bind_policy (no open-op identity here; the pump handler's identity-derived gate does not apply) — pinned by the 0x02-refusal test. - tests/local-backend-producer.rs (9): vanilla CONNECT round-trip on real sockets, faithful-RFC BIND (real reply#1/reply#2 addresses), UDP ASSOCIATE through the real egress, domain resolution at the seam, idle timeout, session cap, failure isolation, bind_policy(false). Verification: cargo test + --all-features green; clippy clean (default, all-features, wasm32 targets); cargo fmt --check clean; wasm checks green (backend is local-gated). |
||
|
|
2413646b6a |
door: the UDP front door — the RFC codec boundary (ADR-003)
- src/door.rs (feature local): UdpFrontDoor — a sibling of the TCP
door (one relay UDP socket = one association; no shared accept
loop). Inbound datagrams decode via parse_udp_request at the door's
codec boundary and ride in-band as {addr, data} frames; replies
encode via new_udp_header back to the client's learned source
address. FRAG != 0 refused (dropped; association healthy). The
association's lifetime is the channel's — the relay socket closes
with the pump and the teardown reaps the adopted channel. The
client idle bound (ADR-014) is constructor config.
- Pump shape: the association's stream is split (one pump owns the
in-band write half, the other the read half) — a shared-session
mutex deadlocks (the channel read parks holding it while the socket
side's send needs it); the pumps join via select!.
- tests/door-udp.rs: the POC #2 test-8 shape end to end (real UDP
echo target + local::udp_egress + vanilla RFC-header'd datagram),
the empty-datagram invariant (F-2), FRAG refusal + health, the idle
bound + fresh-association health.
Verification: cargo test green (default + all-features + local),
clippy -D warnings clean (default, local, wasm32 target), fmt clean,
cargo doc clean.
|
||
|
|
b8fd75b9f3 |
door: the TCP front door — NoAuth raw pipe + RFC 1929 cred-bearing + BIND
The consumer's TCP front door (feature `local`, ADR-005/ADR-014): the ssh `-D` local exposure vanilla clients point at. - `src/door.rs`: `FrontDoor::builder().credentials(...).bind(...)` → `serve`/`serve_on`. The NoAuth door is the literal raw pipe (accept → channel BiStream via copy_bidirectional, zero protocol translation). The cred-bearing door terminates RFC 1929 locally (the forked accept_password_auth shape) and re-originates in-band via the token-carrying consumer sessions — the vanilla replies are re-encoded at the door (the RFC codec boundary, ADR-003), the password never crosses the channel as a SOCKS password. The BIND door rides BindSession with reply#1 fidelity (the advertised address passes through unchanged). UDP ASSOCIATE at the TCP door refuses RFC-faithfully (0x07 — the UDP door's task). The ADR-014 accept-path defense: the idle-first-byte window (peek-based, door-side close) + the max-sessions cap (semaphore) as constructor config; per-accept failure isolation (the loop continues). - The identity mechanism (decided + recorded in the task Notes): the per-credential identity rides the open-op `auth_token` payload (CF-005 precedence (1)) — the token is the mapped identity's id; the producer resolves it through ServingConfig.identity_provider. No per-credential ChannelClient. - `src/consumer.rs`: `TokenClient` (the token-carrying open surface over the taken CallConnection + shared manager) and the `*_with_token` twins of the three session constructors through the open_with_token_and_negotiate prologue (teardown-at-birth preserved); `Socks5ChannelSession::reply_addr` (the CONNECT reply's BND.ADDR for the door's re-encoding). Existing surfaces untouched. - `tests/door-tcp.rs`: 10 integration tests — the NoAuth raw-pipe round-trip (the POC #1 curl shape), good/bad credentials (the establisher witness sees the mapped identity, never the password; a rejection opens no channel), the never-pipes-raw-post-auth pin, the BIND door's vanilla conversation, the idle timeout, the session cap, failure isolation, and the native-session/BIND-token regression guards. Harness: wire_consumer_side_with_provider (the producer's identity provider configurable). Verification: cargo test (375) + --all-features (423) green; clippy -D warnings clean (default + all-features); fmt --check clean; wasm check + wasm clippy green; default graph stays net-free. |
||
|
|
f3c371310c | tasks: the batch-2 decomposition — the remaining assembly-layer tasks | ||
|
|
a867f0cb81 |
local: the socket-backed seam providers — tcp_dial / udp_egress / bind_accept
The `local` feature's first-party providers (ADR-008's local variants, ADR-009's gate): the three seams' faithful-RFC assembly-layer constructors over the landed seam types; the protocol layer never sees them. - Cargo.toml: `local = ["tokio/net"]` (no socket2 — the POCs ran tokio-only); the default graph stays net/mio/socket2-free (cargo tree spot-checked both ways) - src/local/: `tcp_dial()` (real TcpStream per CONNECT, domain resolution at the provider — the ssh -D semantic; the resolved address set iterated, faithful SocksDialError mapping, NotAllowed never synthesized), `udp_egress()` (one real UdpSocket per association bound 127.0.0.1:0 — the explicit bind decision; the POC #2 LocalEgress readiness shape: park on readable(), try_recv_from, WouldBlock -> spurious Ok(None); inner bytes to_be_bytes() + data, framing-once; resolve at the egress), `bind_accept(bind_host)` (real TcpListener per BIND session, ADR-007 variant 1: requested port honored when non-zero, local_addr as reply#1's BND.ADDR, the accept future owning the listener end-to-end, peer byte-exact; two-stage failure mapping; the bind host is the caller's explicit choice) - lib.rs: cfg-gated module + re-exports; crate doc updated - tests: 12 unit (real-socket round-trips, the readiness contract's park-then-datagram, the drop-closes-the-listener witness, the refusal mappings) + 6 integration (tests/local-providers.rs, feature-gated: the three commands end to end through the producer's real dispatch path with real targets, connect-refused -> 0x05 through the producer's reply, the readiness contract re-pinned through the datagram stage, client-gone -> the advertised port frees) - task status -> completed (acceptance criteria checked; Notes + Summary filled) Verification: cargo test (157 green) + --all-features green, clippy -D warnings clean (default + all-features), fmt --check clean, both wasm checks clean, the dependency-graph spot-checks green. |
||
|
|
431920ddef |
review: the fix-round gate closed — every review-002 finding verified, batch 2 opens
The closing checkpoint (tasks/implementation/review-fix-round-002.md): each review-002 finding verified closed against the landed tree, the full verification sweep green, conventions re-swept, and the gate condition (ADR-014 accepted, no major finding) holds. Per-finding verification (all nine checklist points): - C-1: the take-guard verified — a second await_inbound returns the spent shape Ok(None), the re-read structurally impossible (the plane owns the stream); pinned by the double-read + reply1-twice tests. D-1's one-reader decision recorded (option (a), BindReply1 cut). - C-3 closed: the builder's explicit bind_policy is the substrate-free gate; the presence reading is only the backward-compat default; the pump handler's identity-derived gate byte-identical. - D-2 closed: the full builder chain through .register(...) serves all three commands end to end (RegisterMode::ProducerBuilder + the integration test, identity witness asserted). - N-1/N-4 coherent: the cut list gone (zero references), keep-notes current, the Refused vocabulary honest at all five sites + the harness's minimal producer, wire untouched, provenance extended. - D-3 resolved (declare side): the channel:open_failed ErrorDefinition declared, schema pinned drift-free against the typed parse table. - N-8 re-run: 96.30% regions / 97.28% lines; every named line covered; the residual attributed (accept_bi log arm, fork accepted-uncovered, test panic-guards) — no new load-bearing gaps. - N-5/N-6 verified: the shared module compiles into both crates via the crate-root mount + extern-crate-self alias; open_and_negotiate single-sources the teardown-at-birth prologue. - C-2 decided: ADR-014 accepted (option 1 — teardown-governed phases), the spec lines carry the posture. - Cross-cutting sweep green: test 375 green (+ all-features, local, composed), clippy -D warnings clean, fmt clean, doc clean, both wasm checks clean, publish dry-run completes; no unwrap/expect/panic in non-test code (mechanically attributed across src/); no local/ composed code exists (the batch boundary holds). One gate-round finding, F-1 (minor), fixed in this change: the review-numbering collision — "review 002 R-1/R-2/R-4" cited the batch-1 gate review's findings (tasks/implementation/ review-protocol-core.md) under a name that now means the general review; reworded to "the gate review's R-N" at all six sites (producer.rs, consumer.rs, egress.rs, the review doc). Review 002 marked resolved; the next general review doc is 003-*. Verification: cargo test (375 green), --all-features + --features local/composed green, clippy --all-targets -D warnings clean, fmt --check clean, doc clean, both wasm checks clean, publish dry-run completes, llvm-cov 96.30% regions / 97.28% lines attributed. |
||
|
|
07b8a74241 |
tests: consolidate the duplicated harness shapes (review-002 N-5)
- new tests/common/mod.rs — the single-sourced shapes, compiled into the lib test crate (#[cfg(test)] #[path] mod in src/lib.rs + extern crate self as alksocks) and the integration suite (tests/harness.rs declares it and re-exports under the consumed names; the per-test use-harness surface is unchanged) - single-sourced: the echo dial (plain + witnessing), the refusing dial, the queue egress + echo factory (the harness ChanEgress / producer QueueEgress mutex-wrapper divergence collapsed), the refusing egress, the raw RFC 1928 client trio (generic over the stream; harness RawRfcClient delegates), the datagram frame codec helpers (three copies to one), the parked accept factory - deliberately left test-local: HotNoneEgress / PureHotNoneEgress (R-1's two shapes), Witness / FailingSendWitness (drop flags), core-integration's HotNoneEgress (concurrency-load variant), the typed accept-refusal factories - zero behavior change: same test population (145 lib / 38 associate / 41 bind / 40 connect / 41 integration / 30 harness / 37 differential / 3 doc-tests), no tests folded, net -307 lines Verification: cargo test + --all-features green (same counts), clippy -D warnings clean (native + wasm), fmt clean, doc clean, both wasm checks clean, publish dry-run completes. |
||
|
|
8dbe73d49b |
tests: the review-002 coverage residue — stage exits, splits, spent arms (N-8)
Eighteen new tests, zero production-code changes; every line the review attributed in §N-8 is now covered (verified by a `cargo llvm-cov --show-missing-lines` before/after diff). - producer: the datagram stage's failure exits pinned as direct unit tests on `run_datagram_stage` (the serve layer discards StageExit, so the unit shape is the only place the exact matrix is assertable) — `ReplyFrameRejected` (a joint-bound-violating reply), `EgressEnded` via `next_reply`'s Err, `Io` via a failing write and a failing read; the `send`-failure arm got the serve-layer drop-witness treatment (full ASSOCIATE conversation, EOF after the refusal, the drop flag flips — teardown-at-birth on the failure path) - producer: the quiet wait's `Err(_)` arm (a read *error* ends the wait `Ok(())`, the accept future drops with its owned state — the client-gone contract's error-shaped twin; driven directly on `bind_quiet_wait_and_serve`) and the BIND reply#2 Domain-peer fallback (`TargetAddr::Domain(_, port)` → `0.0.0.0:port`, the data plane live end to end) - producer: the establisher's `None`-witness arm (the registration's optional witness seam) added to the existing unit test - consumer: the two front-door-bridge `split`s tested on live `Socks5Stream<BiStream>` streams via `build_skip_auth_bi_stream` (two duplexes joined the `open_and_negotiate` shape) — raw framed bytes through both halves; the BIND split leaves the take-guard untouched (`await_inbound` still reads the reply#2 phase) - consumer: the single-arm residue — spent `send_datagram`'s `Io` arm (+ the spent-`recv` `Ok(None)` shape asserted alongside), the mid-batch later-frame `Err` dropping the pending `Ok`s (the fail-loud-drops-pending semantics), ASSOCIATE `into_inner`'s `Ok` pairing, `BindDataPlane`'s `poll_flush`/`poll_shutdown` passthroughs (the BIND door's half-close), `read_reply2`'s version pin (a foreign producer's non-5 reply#2), the ASSOCIATE/BIND session `Debug` bodies, the spent-`close` no-op - egress/error: the three seam-handle `Debug` renders (TargetHandle / BindTargetHandle / BindAccept) and `SocksEstablishmentReason`'s `Display` (each variant renders as its canonical wire code) Coverage: 96.31% regions / 97.22% lines; no new accepted-uncovered entries (the residual uncovered lines are the pre-existing out-of-scope set + producer.rs 188-189's untestable `accept_bi` substrate-failure log arm). One test-only select-shape discovery recorded in the task Notes: `tokio::select!`'s random branch order can drop a not-yet-polled accept future before its owned state exists — the quiet-wait test parks once before erroring so both branches are guaranteed started. Verification: `cargo test` + `--all-features` green (375 tests), clippy `-D warnings` clean (native + wasm), fmt clean, `cargo doc` clean, both wasm checks clean. |
||
|
|
986bd13ce7 |
adr-014: the idle-timeout posture — teardown-governed phases, no protocol-layer idle bound
- ADR-014 accepted (review 002 C-2): the pre-command handshake, the BIND quiet wait, and the association lifetime are all teardown-governed; the protocol layer never times out (option 1) - the BIND relationship recorded as same regime (ADR-007 decided one phase's ends by designing BIND; ADR-014 records the inherited resolution — one principle, not two) - the doors' accept-path obligations recorded pre-batch-2 (accept-stage idle bound, per-credential/per-peer caps, accept-queue bounds); revisitable additively (an optional producer-side idle seam would be config, not wire) - spec lines carry the decided posture: wire.md (association-lifetime bullet, no-in-band-control bullet, preamble, frontmatter); producer.md (the open handler, the local-backend section, decision table, frontmatter); overview.md ADR index - one upstream-fidelity correction: upstream's dial timeout maps to 0x06 (TTL expired), not 0x01 as the review sketch suggested - open-questions.md unchanged (C-2 was never tracked there) Verification: cargo test green (357 passed), clippy -D warnings clean, fmt clean, cargo doc clean, wasm check + clippy (wasm32) clean |
||
|
|
ff3295422c |
params: the error-schema declaration + the wire hygiene residue (review-002 D-3, N-3, N-9)
- D-3: socks5_open_spec declares the channel:open_failed ErrorDefinition (alktty-ported schema, adapted) — reason enum is the full ADR-049 §3 five-code vocabulary matching SocksEstablishmentReason's parse table; description carries this crate's gate classes (FORBIDDEN / INVALID_INPUT / channel:too_many_channels) + the no-phantom-channel property. Pinned by open_spec_declares_the_open_failed_error_definition, which round-trips every enum value through the typed vocabulary — schema and typed view cannot drift. Module doc updated from claim to fact. - N-3: DatagramReader::feed's structurally-unreachable let-else restructured — the header read folds into the let-else's else arm (live-else shape); zero behavior change, wire suite green unchanged. - N-9: is_mid_frame kept; doc names the anticipated batch-2 consumer (the front-door bridges) so dead-surface sweeps don't flag it. Verification: - cargo test: 357 passed, 0 failed (all suites) - cargo clippy --all-targets -- -D warnings: clean - cargo fmt --check: clean - cargo doc --no-deps: clean - cargo check --target wasm32-unknown-unknown: clean - cargo clippy --target wasm32-unknown-unknown -- -D warnings: clean Task: tasks/implementation/fix-hygiene-residue.md (completed) |
||
|
|
68d5be36a0 |
rfc1928: the keep-or-cut sweep + the honest refusal vocabulary (review-002 N-1, N-4)
- N-1 cut: auth_method_enums! (macro + generated StandardAuthentication/ StandardAuthenticationStarted/allow_no_auth), skip_auth_this_is_not_ rfc_compliant, CheckResult's Option/Result impls (bool stays) — the producer-replaced machinery; ADR-013's no-speculative-surface posture - N-1 keep-notes made current: ConnectError (hand-port companion; the fired trigger + keep disposition recorded in stream.rs), transfer/wait_on_tcp (differential-suite-consumed), Bug (upstream- verbatim, display intact) - N-4: new SocksServerError::Refused(&'static str) — the honest log vocabulary for the five in-stream policy refusal sites (the BIND command gate + dial/egress/bind-factory/bind-accept seams) and the harness's minimal producer; wire bytes untouched (Bug keeps its display; AuthenticationRejected stays the typestate's auth-reject) - Provenance extended, not replaced (convention 17): server.rs module doc's owned-changes paragraph + rfc1928.md's owned-code post-fork changes list + last_updated - Differential :103 comment reworded to the RFC's ordering rationale; AuthMethod doc updated for the macro cut - Tests: refusal-site session-error asserts moved to Refused, the mapping-table loop grew the variant, a display/shape test pins it Verified: cargo test (356 green incl. --all-features), clippy/fmt/doc clean, both wasm checks clean. |
||
|
|
ff81f2d27b |
producer: the explicit bind-policy gate + the builder-register pin (review-002 C-3, D-2, S-1, N-2)
- C-3: Socks5ProducerBuilder.bind_policy(bool) — the substrate-free entry's explicit BIND gate, defaulting to the accept seam's presence; seam presence is never the sole gate - D-2: RegisterMode::ProducerBuilder + an integration test wiring the full builder through .register(...) serving all three commands - S-1: run_datagram_stage's doc names the hot-Ok(None) residual - N-2: wire.md §UDP ASSOCIATE carries the inline-send backpressure line Verified: cargo test (352 green), clippy/fmt/doc clean, wasm checks clean. |
||
|
|
53739b20cb |
consumer: the BIND reply#2 re-entry guard + session hygiene (review-002 C-1, D-1, N-6, S-2)
- C-1: BindSession::await_inbound is take-guarded — the first read takes the session's stream; the returned BindDataPlane owns it (the TakenInner pairing posture; the session stays the teardown owner), so a second call returns the family's spent shape Ok(None) and can never re-read the live data plane. A failed read restores the inner (the phase is only consumed on success; the producer ends the session on every post-reply#1 failure exit, so a re-call EOFs — never a live data-plane re-read either way). Pinned by the double-await_inbound integration test (the spent shape + the data-plane bytes asserted untouched) and the spent-BIND unit test. - D-1: one normative reply#2 reader (option a) — the session's await_inbound; BindReply1::await_inbound is cut and reply1() is documented as a view (bind_addr + raw_mut), matching its actual usage; the reply1()-twice re-entry path is closed (pinned by the view test). - N-6: the three session constructors' prologues collapsed into one private open_and_negotiate helper (teardown-at-birth in one place); zero behavior change. - S-2: the EOF-propagation doc line on all three reap sites (the dropped teardown_channel handle is deliberate — the graceful shape; clear_all aborts instead). - BindDataPlane is un-lifetimed (owns the stream); into_inner returns the owned Socks5Stream<BiStream>; docs synced (module + session docs, consumer.md §The BIND client). Verification: cargo test 352 green (all-features too), clippy -D warnings clean, fmt clean, cargo doc clean, both wasm checks clean. |
||
|
|
6d14d35a54 |
tasks: decompose the review-002 fix round — eight dependency-ordered tasks
The general review (docs/reviews/002-protocol-core-implementation-review.md) decomposed into the fix-round group, grouped so no session carries both a code-fix vertical and the test sweep: - fix-bind-reply2-reentry: C-1 (the BIND reply#2 re-entry guard) + D-1 (the reader decision) + N-6 (the constructor prologue helper) + S-2 (the reap doc line) — one consumer session - fix-producer-gate-policy: C-3 (the explicit BIND gate input) + D-2 (the register/builder coverage) + S-1/N-2 (the stage doc lines) — one producer session - fix-rfc1928-posture: N-1 (the keep-or-cut pass) + N-4 (the honest refusal vocabulary) — one fork posture session - fix-hygiene-residue: D-3 (the ErrorDefinition declaration) + N-3 (the unreachable let-else) + N-9 (the is_mid_frame note) - fix-coverage-residue: N-8's attributed line list — tests only, after the code fixes (final shapes) - fix-test-helper-consolidation: N-5 — after the coverage round - architecture/adr-014-idle-timeout-posture: C-2 — the ADR-sized decision, independent, before the doors exist - review-fix-round-002: the closing checkpoint; the batch-2 gate The review doc's disposition table now names the landed tasks. N-7 is recorded-only (the review's own recommendation). |
||
|
|
d612d7f7f2 |
review 003: the protocol-core implementation reviewed — correctness/security/smells/coverage
General review of the landed batch-1 protocol core (docs/reviews/ 002-protocol-core-implementation-review.md), complementing the narrow fidelity review: correctness hazards, security posture, dead surface, and cargo-llvm-cov attribution, cross-checked against alkcall 0.8.0 and the sibling crates. Two majors: - C-1: BindSession::await_inbound (and BindReply1::await_inbound) is re-entrant with no spent-marker — a second call re-reads the live data plane as reply#2, silently corrupting the pipe. Fix sketched: inner.take() (the session's own None-arm posture) + tests. - C-2: no timeout surface on the pre-command RFC handshake or the association (bounded memory, unbounded time; the alkcall establishment bound covers only the open op). A decision to make before batch 2's doors route vanilla clients through the same phases; sketched as an ADR-level choice, not a core defect. Minors: C-3 (the substrate-free can_bind gate conflates factory-wired with allowed), D-1 (BindReply1::await_inbound is spec'd, 0% covered, never called — the two-reader ambiguity), D-2 (Socks5Producer::register + the builder's egress/accept setters unexercised — the spec'd assembly shape has no test), D-3 (error_schemas declared vec![] while params.rs's doc claims the ADR-049/ADR-016 contract; alktty declares it), S-1 (the stage's Ok(None) defense is livelock-safe but rate-unbounded — posture note), N-1 (review 001's keep-or-cut triggers fired: StandardAuthentication/skip_auth_this_is_not_rfc_compliant/ CheckResult impls are cut candidates; ConnectError/transfer/ wait_on_tcp stay as hand-port-map companions; Bug is now consumed). Coverage (94.52% regions / 95.55% lines): the load-bearing gaps attributed in N-8 — the two untested split surfaces, ASSOCIATE into_inner's Ok arm, the datagram stage's three failure exits (ReplyFrameRejected/EgressEnded/Io), read_reply2's version arm, plus single-arm cosmetics. Verified: cargo test 348 green; --all-features green; clippy --all-targets -D warnings clean; fmt --check clean; cargo doc --no-deps clean; both wasm checks clean; cargo publish --dry-run completes; cargo llvm-cov 94.52/94.67/95.55 (regions/functions/ lines). No code changed in this review — findings documented for the fix decomposition. |
||
|
|
16b21e3447 |
review 002: the protocol core reviewed — readiness-contract hardening + two convention fixes
The batch-1 gate review (tasks/implementation/review-protocol-core.md): all eight criteria examined (seam contracts, wire fidelity, access control, teardown/lifecycle, conventions, wasm, POC fidelity, docs sync); the gate to batch 2 opens. Findings R-1/R-2 were corrected in the review change; R-3..R-5 are doc residues fixed in the same change; R-6 is accepted with rationale. - R-1 (major): the datagram stage's Ok(None) re-park is unsafe against a zero-yield egress — a next_reply that is a pure instantly-ready Ok(None) with no await wins every select! poll and livelocks the stage on a current-thread runtime (empirically reproduced; the landed unit test's mutex masked the hazard at its lock's yield boundary; multi-thread survives). Fix: the Ok(None) handler yields before re-parking; a pinning unit test drives the strictest zero-yield violator. - R-2: the two into_inner expect()s (consumer sessions) become typed Result<_, SpentSessionError> — convention 2's no-panic rule; spent- session unit tests pin the shape; test call sites updated. - R-3/R-4/R-5: doc residues — stale task-time phrasings in egress.rs/producer.rs, producer.md's register() sketch reconciled to the landed signature, the establishment-bound test doc reworded to what the substrate-free probe actually pins. - R-6 (accepted): the wasm gate rides a transitive getrandom/wasm_js feature unification (alkcall -> jsonschema/referencing -> ahash) — verified compiling clean today; noted for OQ-SK2-06. Verified: cargo test 348 green; --all-features green; clippy --all-targets -D warnings clean; fmt --check clean; both wasm checks clean (from a clean wasm target dir); cargo doc --no-deps clean. The bind-only-identity reconciliation from the integration session was independently verified as correct (ADR-006 as landed; the suite pins it). Batch boundary holds: no local/composed code exists. |
||
|
|
1ad8d9d489 |
core: the cross-cutting integration suite + docs sync
The batch-1 closing task: nine cross-cutting integration tests
(tests/core-integration.rs) pinning the whole protocol core end to
end over the channels harness, plus the architecture-sync docs pass.
Tests:
- One registration, all three commands: CONNECT + ASSOCIATE + BIND
interleaved live on the one channels/socks5/sub op; a teardown
ends only its own session (mux/demux isolation) — the POC #2/#5
regression guard, complete.
- Concurrency: nine concurrent mixed-command sessions (raw topology)
and six through the typed consumer sessions (consumer-side
topology) — the mux/demux pinned in both channel-id allocation
directions; fresh round-trips after the burst; no channel leaks.
- Teardown matrix (wire.md §Sentinels and lifecycle): CONNECT
half-close, ASSOCIATE EOF, BIND client-gone (a parked accept so
the quiet-wait exit is deterministic) — each followed by fresh
sessions of every command (no connection poisoning).
- Identity/scope matrix (ADR-006 end to end): open-only (CONNECT +
ASSOCIATE serve, BIND refused 0x02 at the command gate), bind-only
fail-closed at the open gate for all commands (the spec drift the
task's phrasing carried, reconciled — ADR-006's open gate requires
socks5:open unconditionally), both-scopes (everything, granted
explicitly); the CF-006 witness asserted per row.
- The readiness contract re-pinned under concurrent load: a
hot-Ok(None) egress across concurrent associations does not starve
the datagram stage's channel branch.
Docs sync: README Current State ("the protocol core is implemented"
+ the landed inventory; the decomposition residue gone); consumer.md
§Wiring reconciled to the landed session signatures; ADR-006
§Consequences states the grant asymmetry explicitly; lib.rs crate
docs describe the landed surface.
Verification: cargo test 302 green; --all-features green; clippy
--all-targets -D warnings clean; fmt --check clean; both wasm checks
clean; cargo doc --no-deps clean. Batch boundary holds: no
local/composed code, the default crate is protocol-only.
|
||
|
|
c57451545d |
consumer: AssociateSession — the framed datagram stage + e2e suite
The UDP ASSOCIATE consumer session (POC #2's shape, consumer.md §The associate session, ADR-003): - `AssociateSession::associate` — open → no-auth → ASSOCIATE → reply; the sentinel BND.ADDR is received (`relay_addr()`, RFC fidelity) and never used for addressing (the handle is the channel). The ASSOCIATE DST.ADDR is the vanilla-mirror unspecified address. - `send_datagram` — frames exactly once via the crate's wire `frame()` (the framing-once mirror; the u16 joint bound enforced at frame time). Takes `impl ToTargetAddr` (ADR-012 posture); a `ToTargetAddr for &TargetAddr` impl was added to the forked codec so a `TargetAddr` value survives across sends. - `recv_datagram` — the incremental `DatagramReader` + batched- datagram pending queue, state persisting across calls (the POC's reader-state finding); `Ok(None)` on channel EOF; fail-loud on a stream end mid-frame and on undecodable frames (`WireError` → the forked `AddrError`/`UdpHeaderError` RFC classes — the consumer mirror of the producer stage's `UndecodableFrame` exit). - `shutdown` — the half-close; the mux's implicit-EOF sentinel reaches the producer's stage as EOF (association lifetime = channel lifetime). `close()`/`Drop` own the teardown (sync reap + structural EOF, the CONNECT/BIND posture); teardown-at-birth on in-stream refusals; no `Clone` (compile-fail asserted). - Typed error surface: open failures are `SocksSessionError::Open` (ADR-049); the missing-egress 0x07 and factory-failure codes surface from `associate` as `Rfc`. E2E suite (`tests/consumer-associate.rs`, 8 tests over the harness): round-trips (multiple in-order datagrams, one association, sentinel relay address asserted), the split-datagram persistence test (a 60 KiB datagram reassembling across the call boundary + the pending- queue drain — both halves of the persistence invariant), the empty- payload round-trip (F-2, never EOF), shutdown → a fresh associate succeeds immediately (the POC's test 4), Drop teardown + connection health, the missing-egress refusal (typed 0x07, no phantom channel), the egress-factory failure mapping, and the CONNECT+ASSOCIATE regression guard (interleaved on one registration). Unit tests (4) pin the reader internals over a duplex (`skip_auth`'s handshake-free path). Verification: cargo test 305 (7 suites, all green), clippy --all-targets -D warnings, fmt, cargo doc --no-deps, and both wasm32-unknown-unknown checks clean. |
||
|
|
776844e2fb |
consumer: BindSession — the reply#2 reader + e2e suite
- BindSession::bind (open → no-auth → BIND → reply#1, capturing BND.ADDR) + reply1() (the BindReply1 handle) + await_inbound (the POC #5 read_reply2 — hand-read with the forked read_address codec, no second codec, ADR-011) returning BindDataPlane (into_inner / get_socket_mut + AsyncRead/AsyncWrite forwards) - Teardown ownership (the CONNECT session's posture): Drop sync-reap + structural EOF, close() graceful, teardown-at-birth on in-stream refusals, no Clone (compile_fail-asserted) - Typed error surface: open failures ride SocksSessionError::Open (ADR-049 reasons); the command gate's 0x02 and factory-failure reply#1 codes surface from bind as Rfc; accept-stage reply#2 codes surface at await_inbound - tests/consumer-bind.rs: 9 e2e tests over the harness (round-trip, data-plane accessors, gate refusal, both failure mappings, quiet-wait garbage, drop teardown + health, the CONNECT+BIND regression guard, the Send probe) Verification: cargo test (259 incl. the doctest), clippy -D warnings (native + wasm), fmt, both wasm checks, cargo doc clean. |
||
|
|
76717f3944 |
consumer: Socks5ChannelSession (CONNECT) — teardown ownership + e2e suite
- Socks5ChannelSession::connect: open_channel(OP_SOCKS5_OPEN, {},
SOCKS5_ALPN) then the forked client typestate's use_stream path
over the channel BiStream (POC #1's fit); post-reply the session
is the raw byte pipe (0 B framing)
- Target construction via ToTargetAddr: IP-string forms parse to IP
targets, domain form passes through unresolved (ADR-012)
- split() borrowed halves (SplitPipe) / into_inner() owned pipe
(TakenInner — the session rides along as the teardown owner);
channel_id() for correlation
- Teardown ownership (alktunnels ADR-005 posture): Drop sync-reaps
the adopted entry (teardown_channel is sync; Drop cannot await) +
the pipe drop EOFs the producer pump structurally; close() is the
graceful path (write-half shutdown, then reap); the in-stream
refusal tears the channel down in connect (teardown-at-birth —
no phantom channel); no Clone (compile_fail doctest)
- Error surface: open failures are the typed SocksOpenError reasons
(ADR-049 §4); a refused target is the RFC reply code through
SocksSessionError::Rfc (consumer.md §Error surface)
- Harness: the consumer-side topology (wire_consumer_side[_with] —
the consumer dials with a real ChannelClient; the producer accepts
with the real registration) + witnessing_echo_dial; the
allow(dead_code) attr rides the alktunnels harness posture
- tests/consumer-connect.rs: 9 e2e tests — echo round-trip + CF-006
witness, target forms at the dial seam (ADR-012), refused dial →
typed RFC error + no phantom channel, FORBIDDEN open → typed open
error + no session, drop teardown + connection health, half-close
EOF semantics, into_inner pairing, Send shape
Verified: cargo test (219 green incl. the compile_fail doctest),
clippy --all-targets -D warnings, fmt --check,
check/clippy --target wasm32-unknown-unknown
|
||
|
|
df772ccbeb |
producer: the UDP ASSOCIATE datagram stage — the select!-driven relay (ADR-003)
- serve_udp_associate: sentinel reply (127.0.0.1:0 — the handle-swap surface) then the datagram stage; missing egress refuses RFC-faithfully (CommandNotSupported, ADR-008); egress-factory failure maps to reply#1's code - run_datagram_stage: select! over channel reads ∥ egress replies (the concurrent full-duplex shape), typed StageExit diagnostics (EOF = the clean exit; undecodable frames fail loud; the egress readiness contract tolerated as spurious wakeups) - wire: frame_inner — the reply-path half of framing-once (identical wire to frame(), pinned); egress inner bytes never re-prefixed - tests: the readiness-contract test (instant-Ok(None) egress does not starve the stage), empty-payload datagram legal (never EOF), undecodable-frame loud fail, EOF teardown + fresh associate, the egress drop witness (stage owns it), missing-egress refusal + CONNECT guard — in-process (duplex) and over channels (harness); the three-command regression guard drives all commands serving Verified: cargo test 174/174; clippy --all-targets -D warnings; fmt --check; wasm check + clippy (wasm32-unknown-unknown); doc. |
||
|
|
048493fefa |
producer: the BIND phase model + the socks5:bind command gate (ADR-006/ADR-007)
The TCP BIND dispatch arm replaces its refusal with the full phase model (POC #5-validated scope), plus the command gate at dispatch: - The command gate: TCP BIND requires socks5:bind from the per-call opener identity (CF-005/CF-006) — the pump handler resolves it from the open-op AuthContext; serve_socks5_over<T> takes the resolved can_bind boolean explicitly. A scope-less identity gets the RFC-faithful ConnectionNotAllowed (0x02) with no factory call (the gate precedes the seam) while CONNECT still serves it. - The phase model (wire.md §BIND): the BindAcceptFactory at the command-read interception point → reply#1 carrying BND.ADDR (the factory's bind_addr) → the quiet wait (single-shot select!, exactly two exits: the one accept, or the control stream's EOF/garbage/ error; any byte = protocol garbage → session ends, no data plane; the accept future's drop IS the client-gone teardown) → reply#2 carrying the peer address (via the fork's published new_reply) → pump_bidi inline (the CONNECT shape, R-02). - Two-stage RFC-faithful failure mapping: factory failure → reply#1's mapped code; accept-stage failure → reply#2's mapped code, no data plane. No phantom listeners, no phantom data planes. - The no-factory default is the wrapper-aware in-band variant (ADR-007 variant 3, documented): sentinel reply#1 + sentinel reply#2 (0.0.0.0:0), the full phase model preserved — the channel is the connection. - The accept wait lives in the teardown-governed pump phase, never under the establishment bound (ADR-007). Tests: 16 unit tests (raw RFC client over duplex) + 7 harness integration tests over channels — happy path, the gate (0x02 while CONNECT serves, both scopes), quiet-wait garbage, client-gone teardown (owner-flag witness), both failure stages over channels, no-factory variant 3, and the three-command regression guard (CONNECT serves, BIND serves under its gate, ASSOCIATE still refuses). Verification: cargo test green (96 lib + 26 harness + 37 differential); clippy -D warnings clean (all targets, native + wasm32-unknown-unknown); cargo fmt clean; wasm check clean. Task: tasks/implementation/core-producer-bind.md (status → completed) |
||
|
|
cfacc8a968 |
producer: the session-gate establisher + the command-dispatch pump + CONNECT
The crate's core task (implementation/core-producer-connect): the producer half of src/producer.rs per producer.md, adapted to the crate's seam types and the alkcall 0.8.0 establishment surface. - The session-gate establisher (`socks5_session_establisher`): validates/records the opener identity (the CF-005/CF-006 seam, ADR-005) via the optional `identity_witness` probe (the alktunnels posture); returns `Establishment::default()` — no plan payload (the dial happens post-establishment; POC #1's wire note). - The pump handler (`make_socks5_pump_handler`): `accept_bi()` → the forked typestate over the `BiStream` (`accept_no_auth → read_command`) → dispatch. CONNECT: the dial seam (`DialFn`, ADR-004) at the command-read interception point; success: `reply_success(sentinel)` then `pump_bidi` awaited inline (R-02 — the JoinHandle tracks the data-plane lifetime). Refusal: `reply_error` with the mapped RFC code — no phantom data plane. - The ASSOCIATE/BIND arms land as RFC-faithful refusals (`CommandNotSupported`) — the pre-vertical posture (ADR-008); the dispatch skeleton + registration signature carry the seam parameters from day one so the associate/bind tasks swap the arms without touching the registration shape. - `register_socks5_openable`: spec + establisher + pump via `register_openable_with_establisher` (`timeout: None` = the 10s default), scope-gated `socks5:open`. Missing callbacks = those commands refuse RFC-faithfully, never a panic. - `serve_socks5_over<T>`: the substrate-free protocol entry (the POC shape), directly testable without channels; the builder (`Socks5Producer::builder()`) is the spec'd assembly shape. - Tests: 11 in-module (substrate-free duplex-driven — round-trip + half-close teardown, refusal mapping per variant, missing-dial refusal, ASSOCIATE/BIND arms, dispatch guard, establisher witness, builder shape) + 5 harness integration (end-to-end CONNECT echo with the identity witness, dial refusal 0x05 with no phantom data plane, scope-class refusal 0x02, ASSOCIATE/BIND refusals with CONNECT still serving, repeated CONNECTs on one registration). The harness retargeted to the real producer (`RegisterMode:: Producer`); the minimal stand-in is a harness-internal helper. Verification: cargo test 87+19+37 green; clippy --all-targets -D warnings clean; fmt clean; wasm check + wasm clippy clean; cargo doc clean; --all-features green. |
||
|
|
f8773521b9 |
harness: the producer/consumer integration test harness over duplex
tests/harness.rs — the producer<->consumer wiring over one tokio::io::duplex carrying the channels 8-byte chunk format, the alkcall 0.8.0 establishment surface consumed for the first time by this crate (register_openable_with_establisher + the call/adopt dance) — the first-consumption risk retired. - Identities: consumer (socks5:open) / bind (socks5:bind only) / both-scopes (explicit, ADR-006) + TestIdProvider (token path). - In-process seams, no sockets, no `local`: echo_dial / refusing_dial (variant-preserving), echo_egress (readiness contract verified — next_reply parks Pending when idle) / refusing_egress, echo_accept / refusing_accept_factory / refusing_accept (two-stage BIND refusal shapes, mapped through to_reply_error()). - Producer-side registration helper: register_socks5_openable_minimal + socks5_session_establisher + RegisterMode — the single place the producer wiring lives; the CF-006 identity witness rides it and is asserted end-to-end. - RawRfcClient: the hand-rolled RFC 1928 client helper over any AsyncRead + AsyncWrite, round-tripped against a byte-level hand-rolled server (success + refusal replies). - Smoke test: harness up -> producer registered -> consumer open + adopt -> raw RFC CONNECT -> echo round-trip through pump_bidi. Fail-closed probe: scope-less identity -> FORBIDDEN, no channel anywhere (the load-bearing POC #1 set_identity wiring requirement, explicit in the install hook and tested). 14 harness tests. Verification: cargo test (127 green), clippy (all-targets + wasm32) clean, fmt clean, doc zero warnings, test --all-features green, both wasm checks clean (harness is dev-only). |
||
|
|
ac361f7610 |
seams: the three seam types — dial / egress / accept (ADR-004)
- egress.rs: the I/O-plane inversion point, function-not-trait at the three factories; Egress is the one trait (the per-association returned object) - dial: TargetHandle (boxed halves, +Sync per the ChannelPlan bound), DialFn over the typed TargetAddr (the POCs' JSON stand-in retired), SocksDialError with the five-variant RFC-faithful mapping - egress: Egress (send payload-only; next_reply returns inner bytes; the readiness contract — Pending when nothing is ready — documented on the trait), EgressFactory, EgressError, the egress_factory sync-lift constructor - accept: BindAccept (the accept future owns its resources end-to-end; the quiet wait's drop IS the client-gone teardown), BindTargetHandle (ownership structural; the composed keep-alive owner rides inside the halves provider-side), BindAcceptFactory, the two-stage error pair (factory → reply#1; accept-stage → reply#2) - no substrate type named anywhere in the module; ownership rides the handle structurally (no _ bindings) - 9 new tests: the four error→reply-code tables (all variants, byte-compared), the sync-lift Ok/Err paths, the closure compile shapes, the Send/Sync/Unpin assertions; lib.rs re-exports the surface Verified: cargo test (113 green), --all-features green, clippy -D warnings clean (native + wasm32), fmt clean, cargo doc zero warnings, wasm32 check clean. |
||
|
|
1ccd5bbe0c |
params/error: the wire-stable open-op surface + the typed open-error vocabulary
- params: the ADR-001/ADR-006 constants (`channels/socks5/sub`,
`alk/socks5`, `socks5:open`, `socks5:bind`), `socks5_open_spec()`
(Sub/External/channel-open marker; trivial empty-object input schema
with loud `additionalProperties: false`; reply
`{channel_id: integer, minimum 1}` required; `socks5:open` scope),
`Socks5Params {}` (deny_unknown_fields — empty stays valid forever),
the `establishment_reason` helper re-export.
- error: `SocksOpenError` (the ADR-049 §4 typed surface, shaped on
alktunnels' `TunnelOpenError`), the typed
`SocksEstablishmentReason` vocabulary (the five reason codes,
parse/as_str; unknown codes never aliased), `SocksOpenError::reason()`
as the typed view, and `SocksSessionError` (open vs in-stream `Rfc`
vs `Io` — a refused target is an RFC reply code through the session
type, consumer.md §Error surface). `SocksError`/`SocksServerError`/
`ReplyError` re-exported from the forked core.
- lib.rs re-exports the new public surface (convention 15).
- 9 new tests: spec shape, constants, serde round-trip +
unknown-field rejection, the reason mapping table, the
non-establishment classes (`FORBIDDEN`/`INVALID_INPUT`/
`too_many_channels`), future-reason non-aliasing, the three-plane
session surface.
- task `implementation/core-params-errors`: status → completed;
Notes + Summary filled.
Verification: cargo test (104 green), clippy --all-targets -D
warnings clean, fmt --check clean, doc --no-deps zero warnings,
both wasm checks (check + clippy on wasm32-unknown-unknown) clean.
|
||
|
|
d530eae5c4 |
wire: the ASSOCIATE datagram frame codec — frame + DatagramReader (ADR-003)
- frame(addr, payload): [len: u16 BE][addr + data] over TargetAddr::to_be_bytes() + payload; the joint bound enforced at frame time (checked_add; 65507 + 255-char domain = 65766 > 65535 → Oversize) with addr_encoding_len exposed (7 v4 / 19 v6 / 259 max domain) and MAX_INNER_LEN pinned - DatagramReader: incremental reassembly across chunk boundaries (split frames, batched frames, partial headers), infallible, returning per-frame Results; the addr sub-layout decoded by hand (ATYP-keyed, UTF-8-validated) with typed decode errors — BadAtyp / ShortAddress / DomainNotUtf8 / EmptyFrame — so the producer stage fails the association loud on protocol garbage (udp_undecodable_frame_ends_association, POC #2) - module docs state the framing-once invariant (the egress speaks inner bytes; this layer owns the single length prefix — the double-framing bug class POC #2 caught), the F-2 invariant at payload level (a zero-length payload is a legal empty datagram, never EOF; wire len == 0 is unreachable garbage), and the EOF layering (the channels-level sentinel, outside the codec) - doc-sync: wire.md + bast.md residue corrected — "len == 0 is a legal empty datagram" replaced with the payload-level F-2 statement (len == 0 unreachable since addr >= 7 bytes) - lib.rs re-exports frame, DatagramReader, addr_encoding_len, MAX_INNER_LEN, WireError (convention 15) Verification: cargo test (58 lib + 37 differential), clippy --all-targets -- -D warnings, fmt --check, doc --no-deps, and both wasm checks (check + clippy on wasm32-unknown-unknown) — all clean. |