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.
This commit is contained in:
1 parent
76717f3944
commit
776844e2fb
4 files changed
+870
-16
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: implementation/core-consumer-bind
|
||||
name: "Core — consumer BIND: BindSession (reply#2 reader) + e2e tests"
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [implementation/core-consumer-connect, implementation/core-producer-bind]
|
||||
scope: narrow
|
||||
risk: medium
|
||||
@@ -45,15 +45,15 @@ adapted to the crate's error surface):
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `BindSession` with `bind`/`await_inbound`/data-plane accessors
|
||||
- [x] `BindSession` with `bind`/`await_inbound`/data-plane accessors
|
||||
per the POC shape
|
||||
- [ ] Reply#2 read via the forked `read_address` codec (no second
|
||||
- [x] Reply#2 read via the forked `read_address` codec (no second
|
||||
codec)
|
||||
- [ ] Typed error surface (reply-code-carrying session errors)
|
||||
- [ ] Teardown ownership on Drop; no `Clone` (compile-asserted)
|
||||
- [ ] E2E tests through the harness (round-trip, gate refusal,
|
||||
- [x] Typed error surface (reply-code-carrying session errors)
|
||||
- [x] Teardown ownership on Drop; no `Clone` (compile-asserted)
|
||||
- [x] E2E tests through the harness (round-trip, gate refusal,
|
||||
failure mappings, regression guard)
|
||||
- [ ] `cargo test` green; clippy/fmt clean; both wasm checks clean
|
||||
- [x] `cargo test` green; clippy/fmt clean; both wasm checks clean
|
||||
|
||||
## References
|
||||
|
||||
@@ -65,8 +65,74 @@ adapted to the crate's error surface):
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
> Filled by the implementation agent (2026-09-24):
|
||||
|
||||
- The POC's `BindReply1` handle is absorbed into the teardown-owning
|
||||
session (the alktunnels ADR-005 posture the CONNECT session
|
||||
pinned): `BindSession` captures reply#1's BND.ADDR at `bind` (the
|
||||
forked client `request()` returns it once) and exposes the phase
|
||||
through borrows — `reply1()` yields the `BindReply1` handle
|
||||
(`bind_addr` + `raw_mut`), `await_inbound(&mut self)` reads reply#2
|
||||
and returns the borrowed `BindDataPlane` (`Ok(None)` on a spent
|
||||
session). The session stays the single teardown owner for every
|
||||
shape; the POC's `(session, channel_id)` tuple and the
|
||||
`BindDataPlane` ownership split collapse into one owner.
|
||||
- `read_reply2` is the POC's port: 4-byte head → version pin →
|
||||
`ReplyError::from_u8` → the forked public `read_address` codec (no
|
||||
second codec, ADR-011). Errors ride `SocksSessionError` (the
|
||||
`SocksError` conversions routed through the forked vocabulary, per
|
||||
the crate's error-surface convention — `AddrError`/`ReplyError` are
|
||||
wrapped into `SocksError` first because the session error type
|
||||
exposes `From<SocksError>`, not the RFC inner enums directly).
|
||||
- `BindDataPlane<'s>` borrows the session's stream (the CONNECT
|
||||
session's `SplitPipe` shape, generalized): `into_inner()` yields
|
||||
the `&mut Socks5Stream<BiStream>` (the teardown owner rides the
|
||||
session), `get_socket_mut()` is the forked client's socket accessor
|
||||
(the POC shape), and `AsyncRead`/`AsyncWrite` are forwarded so the
|
||||
plane is a plain pipe (`pump_bidi`-shaped consumers work without
|
||||
the accessors). `BiStream` is not `Sync`, so the plane pins `Send`
|
||||
(runtime probe) — same shape the forked client type has.
|
||||
- Teardown: Drop = sync reap + structural EOF (the CONNECT posture);
|
||||
`close()` = half-close (the EOF sentinel reaches the producer's
|
||||
pump) then reap. Teardown-at-birth on the in-stream refusal path
|
||||
(command-gate 0x02, factory-failure reply#1): the adopted channel
|
||||
is reaped before the error returns. Post-reply#1 failures
|
||||
(accept-stage, quiet-wait garbage) keep the channel — reply#1
|
||||
succeeded, the listener existed; `close`/`Drop` reaps.
|
||||
- The quiet-wait garbage path surfaces at `await_inbound` as an
|
||||
`Io`-shaped error (the producer ends the session with no reply#2;
|
||||
the consumer's reply#2 read EOFs) — the client-side mirror of the
|
||||
producer's `UnexpectedUdpControlGarbage` exit, pinned by test.
|
||||
- Harness reuse: no harness changes — the consumer-side topology's
|
||||
`wire_consumer_side_with` + the producer-bind task's
|
||||
`echo_accept`/`refusing_accept*` closures cover every test; the
|
||||
parked-accept factory for the quiet-wait garbage test is
|
||||
test-local (deterministic garbage-vs-accept branch).
|
||||
|
||||
## Summary
|
||||
|
||||
> To be filled on completion
|
||||
Implemented the BIND consumer session in `src/consumer.rs` (POC #5's
|
||||
shape, adapted to the crate's teardown-owning session posture):
|
||||
`BindSession::bind` (open → no-auth → BIND → reply#1, capturing
|
||||
BND.ADDR), `reply1()` (the `BindReply1` handle — the app server's
|
||||
out-of-band dial address + `raw_mut`), `await_inbound` (the POC's
|
||||
`read_reply2` — hand-read with the forked `read_address` codec, no
|
||||
second codec) returning the `BindDataPlane` (`into_inner`/
|
||||
`get_socket_mut` + `AsyncRead`/`AsyncWrite` forwards), and the
|
||||
CONNECT session's teardown ownership (Drop sync-reap + structural
|
||||
EOF, `close()` graceful, teardown-at-birth on in-stream refusals, no
|
||||
`Clone` — `compile_fail`-asserted). Typed error surface throughout:
|
||||
open failures are `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`. E2E suite (`tests/consumer-bind.rs`, 9 tests over
|
||||
the harness): the round-trip (reply#1 BND.ADDR → reply#2 peer →
|
||||
pass-through echo both directions), the data-plane accessors
|
||||
(`into_inner`/`get_socket_mut`/`raw_mut` on one pipe), the
|
||||
command-gate refusal (typed 0x02, no phantom channel), the
|
||||
factory-failure (reply#1 code) and accept-stage (reply#2 code)
|
||||
mappings, quiet-wait garbage ending the session, drop teardown +
|
||||
connection health, the CONNECT+BIND regression guard on one
|
||||
registration, and the `Send` compile-shape probe. Verification:
|
||||
`cargo test` (259 incl. the doctest), clippy `-D warnings` (all
|
||||
targets), fmt, and both wasm checks clean.
|
||||
Reference in new issue
Block a user