Files
alksocks/tasks/implementation/core-consumer-connect.md
T
glm-5.3-flash 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
2026-09-23 23:50:01 +00:00

5.9 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
implementation/core-consumer-connect Core — consumer half: Socks5ChannelSession (CONNECT) + e2e tests completed
implementation/core-producer-connect
narrow low component implementation

Description

Implement the CONNECT consumer session in src/consumer.rs per consumer.md §The Channels-Native Sessions: Socks5ChannelSession — the typed client that opens channels/socks5/sub and drives the client side of the RFC conversation in-stream (POC #1's use_stream shape).

Deliverables (port from /workspace/alksocks-connect-poc/src/ consumer.rs, adapted to the crate's params/error surface):

  1. Socks5ChannelSession::connect(client, target): opens the channel via ChannelClient::open_channel(OP_SOCKS5_OPEN, params, SOCKS5_ALPN) (trivial params — ADR-001), then drives the forked client typestate's use_stream path over the channel's BiStream (generic over the backing stream — the POC #1 fit: Socks5Stream impls AsyncRead + AsyncWrite itself, so the session over a BiStream is the intended use).
  2. Target construction via ToTargetAddr (IP-string forms parse to IP targets; domain form passes through unresolved — ADR-012).
  3. Post-reply payload pipe: after the reply, the session is a raw byte pipe — expose split() (read/write halves) and into_inner() (the underlying Socks5Stream<BiStream> for advanced use). 0 B framing (CONNECT).
  4. Teardown ownership (alktunnels ADR-005 posture, inherited): the session owns the channel; on Drop it closes/joins. No Clone (compile-asserted — aliasing a session would alias its teardown; the alktunnels compile_fail doctest shape).
  5. Error surface: open failures are the typed SocksOpenError reasons (ADR-049 §4 — a refused session never yields a phantom channel); a refused target is an RFC reply code surfaced through the session type (consumer.md §Error surface).
  6. Tests (end-to-end through the harness): connect → echo round-trip (payload bytes both directions); refused dial → the RFC reply code surfaced as a typed session error; FORBIDDEN open → typed open error, no session; half-close semantics (dropping the write half EOFs the producer's pump cleanly); domain-form target passes through unresolved (assert the wire bytes, the echo dial ignores the target).

Acceptance Criteria

  • Socks5ChannelSession with connect/split/into_inner per the POC shape
  • Teardown ownership on Drop; no Clone (compile-asserted)
  • Typed error surface (open errors vs reply-code refusals)
  • E2E tests through the harness (round-trip, refusals, half-close, domain pass-through)
  • cargo test green; clippy/fmt clean; both wasm checks clean

References

  • docs/architecture/consumer.md §The Channels-Native Sessions
  • docs/architecture/decisions/003-udp-handle-swap.md (the framing mirror — CONNECT is the 0 B case)
  • POC source: /workspace/alksocks-connect-poc/src/consumer.rs
  • alktunnels template: /workspace/@alkdev/alktunnels/src/consumer.rs (the teardown-ownership + no-Clone posture)

Notes

Filled by the implementation agent (2026-09-23):

  • The session shape is exactly the POC's, adapted to the crate surface: Socks5Stream::use_stream(bidi, None, Config::default()) over BiStream::from_joined(recv, send), then request(TCPConnect, target). Config::default() is the no-auth posture (the skip_auth knob stays internal to the fork; the identity seam is the open op's, CF-005/CF-006 — no RFC-facing auth on this path).
  • split() returns borrowed halves (tokio::io::split over a &mut Socks5Stream<BiStream>; the SplitPipe type alias), not owned ones: the session keeps teardown ownership for every shape, including the split-driven one. The owned path is into_inner() (TakenInner { session, inner } — the session rides along as the teardown owner).
  • Refusal teardown: an in-stream refusal (Rfc error) tears the adopted channel down inside connect before the error returns (teardown-at-birth; the phantom-channel property, tested both sides). The producer's own handler-exit teardown races the consumer's — the leak-check probe polls past that race.
  • Drop is the sync-reap posture (teardown_channel is sync; Drop cannot await) + the structural EOF of the dropped pipe; the graceful path is close() (write-half shutdown first, then reap).
  • Harness: added the consumer-side topology (wire_consumer_side[_with] — the consumer dials with a real ChannelClient; the producer accepts with the real registration) plus witnessing_echo_dial (the dial-witness target-form probe). The allow(dead_code) attr rides the alktunnels harness posture (each test binary compiles the harness and uses a different subset).
  • No Clone is compile-asserted by the doctest; a runtime probe pins Send (the Clone bound itself is the doctest's job).

Summary

Implemented Socks5ChannelSession (CONNECT) in src/consumer.rs — connect/channel_id/split/into_inner/close + Drop teardown ownership and the SplitPipe/TakenInner companions — with the typed error surface (SocksOpenError for opens, the RFC reply code through SocksSessionError::Rfc for refused targets) and teardown-at-birth on the in-stream refusal path. E2E suite (tests/consumer-connect.rs, 9 tests over the new consumer-side harness topology): echo round-trip + CF-006 witness, target forms (IP-string parses / domain passes through — ADR-012, wire-observed at the dial), 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, and the no-Clone/Send compile shape (the compile_fail doctest pins !Clone). Verification: cargo test (219 incl. the doctest), clippy -D warnings (all targets), fmt, and both wasm checks clean.