--- id: tunnels/wire-codec name: Data-plane codec (frame_datagram / DatagramReader) + sentinel layering tests status: pending depends_on: [tunnels/crate-init, tunnels/params] scope: narrow risk: medium impact: component level: implementation tags: [wire, codec, udp] --- ## Description Implement `src/wire.rs` per ADR-003 + wire.md §The Data Plane + bast.md: the mandatory UDP length-framing codec. This is a direct port of the forward POC's `/workspace/alktunnels-udp-poc/src/wire.rs` (17 tests rode it; the shape is settled) with the POC-to-crate generalizations the ADRs pin. ### API ```rust pub const MAX_DATAGRAM_LEN: usize = u16::MAX as usize; // 65535 pub struct DatagramCodecError { .. } // thiserror: Oversize(usize), InvalidLength(...) pub fn frame_datagram(payload: &[u8]) -> Result // [len: u16 BE][payload]; len=0 is a legal empty datagram; >65535 = Oversize at frame time pub struct DatagramReader { /* incremental decoder state */ } impl DatagramReader { pub fn new() -> Self pub fn feed(&mut self, chunk: &[u8]) -> Result, DatagramCodecError> // zero or more complete datagrams per chunk; buffers partial frames // across chunk boundaries } ``` ### The invariants the tests MUST pin (all POC-validated; they are the spec's executable form) 1. Single datagram round-trip (frame → feed → exact bytes out). 2. Empty datagram (`len=0`): survives as a real datagram — NEVER confused with EOF (the F-2 invariant; the codec layer never emits a zero-length read). 3. Split across chunks (feed a frame in awkward 7-byte chunks; exact reassembly). 4. Two datagrams batched in one chunk (feed once, two out, in order). 5. Partial header at a chunk boundary (1-byte `len` prefix split). 6. Mid-datagram state observable (incremental decode correctness). 7. Oversize rejected at frame time (never a wire overflow). 8. Truncation fail-loud (OQ-TN-13): the adapter-level receive that would truncate surfaces as an error — test at the codec boundary by asserting `frame_datagram` + reader round-trip with a buffer smaller than a full datagram is not silently accepted (the concrete API shape lands with `tunnels/local-socket-halves`; here, pin the codec-side invariant: a truncated stream yields an error, not a partial datagram). No `stream_type` byte, no 5-byte header — a tunnel has one data stream per direction (ADR-003). No EOF sentinel in the codec — EOF is the channels-level `length=0` chunk, a different layer (the two coexist; the POC proved no collision). ## Acceptance Criteria - [ ] All 8 test families above pass (the POC's 7 + the truncation pin) - [ ] `frame_datagram` returns `Bytes` (zero-copy handoff to the mux) - [ ] `DatagramReader` state is incremental across arbitrary chunk boundaries - [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check` clean - [ ] wasm32 check passes (the codec is pure byte work — it must be wasm-clean) ## References - docs/architecture/decisions/003-codec-and-udp-framing.md - docs/architecture/bast.md (the binary contract) - docs/architecture/wire.md §The Data Plane (normative byte diagrams) - POC reference: `/workspace/alktunnels-udp-poc/src/wire.rs` (port with the crate's doc-comment style) ## Notes > Agent fills during implementation. ## Summary > Agent fills this on completion.