- 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
140 lines
6.3 KiB
Markdown
140 lines
6.3 KiB
Markdown
---
|
||
status: draft
|
||
last_updated: 2026-09-20 (Phase 1 initial draft)
|
||
---
|
||
|
||
# alksocks: BAST Document for the `alk/socks5` Wire Format
|
||
|
||
This document is the **BAST (Binary Abstract Syntax Tree)**
|
||
specification for the binary portions of the `alk/socks5` wire
|
||
format, per the alk* family BAST convention (alktunnels AGENTS.md
|
||
convention 12, carried from alktty; alktunnels `bast.md` is the
|
||
precedent), the mandatory ASSOCIATE
|
||
datagram framing ([ADR-003](decisions/003-udp-handle-swap.md)) is
|
||
binary framing beyond pass-through, so the BAST document exists even
|
||
though the format is small. It is a normative JSON document
|
||
conforming to the BAST meta-schema at `https://alk.dev/bast/v1/schema`
|
||
(JSON Schema Draft 2020-12); any Draft 2020-12 validator can check
|
||
well-formedness.
|
||
|
||
**alksocks does not depend on alktype.** The runtime codec is the
|
||
forked RFC 1928 address encoding + the length-framing codec
|
||
(shaped on alktunnels' `frame_datagram`/`DatagramReader`, ADR-003);
|
||
this BAST document is the human-readable contract describing what
|
||
those round-trip. If runtime validation against the BAST becomes
|
||
desirable later, alktype becomes an optional dep and this document
|
||
feeds it.
|
||
|
||
## Scope
|
||
|
||
The `alk/socks5` wire format is: a trivial JSON open op on the
|
||
channels call plane (a UTF-8 JSON object, `{}`; specified in
|
||
[wire.md](wire.md) §The Open Op and
|
||
[ADR-001](decisions/001-alpn-and-open-op.md)), and a data plane
|
||
inside the channel `BiStream` that is *mostly* RFC 1928 wire rules
|
||
plus raw pass-through.
|
||
|
||
Per the BAST format spec, a BAST document describes **binary data
|
||
layouts**, not JSON payloads. The RFC 1928 conversation itself
|
||
(negotiation, command, replies, the RFC UDP datagram header at front
|
||
doors) is specified by RFC 1928 and implemented by the forked
|
||
RFC 1928 codecs ([rfc1928.md](rfc1928.md)); it is not re-specified
|
||
here. This document covers exactly the alksocks-specific binary
|
||
framing:
|
||
|
||
1. **The ASSOCIATE datagram frame** (`[len: u16 BE][addr + data]`),
|
||
the one framed stage (ADR-003). CONNECT's pass-through and BIND's
|
||
pass-through have no framing for BAST to describe.
|
||
|
||
What this BAST deliberately does **not** cover:
|
||
|
||
- **The RFC 1928 wire rules**: method negotiation, command/reply
|
||
encoding, the `addr` sub-layout (ATYP + addr + port), the RFC UDP
|
||
datagram header (RSV/FRAG/ATYP/addr/port, front doors only). The
|
||
RFC is normative; the forked codecs implement it.
|
||
- **The open-op params JSON shape** (`{}`): UTF-8 JSON on the call
|
||
plane; ADR-001.
|
||
- **The channels 8-byte chunk header**: the channels layer's own
|
||
framing (alknet ADR-071/093); stripped transparently before the
|
||
alksocks data plane sees bytes. Specified upstream, not here.
|
||
- **The EOF sentinel**: the channels-level zero-length chunk
|
||
(upstream); the alksocks codecs never emit it (the F-2 invariant).
|
||
|
||
## The BAST document
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://alk.dev/bast/v1/schema",
|
||
"$defs": {
|
||
"AssociateFrame": {
|
||
"kind": "struct",
|
||
"endian": "big",
|
||
"fields": [
|
||
{ "name": "len", "kind": "uint16" },
|
||
{ "name": "addr", "kind": "bytes", "encoding": "length-prefixed",
|
||
"maxLength": 259, "description": "RFC 1928 address encoding: ATYP + addr + port (TargetAddr::to_be_bytes); v4 = 1 + 4 + 2 = 7, v6 = 1 + 16 + 2 = 19, domain = 1 + 1 + 255 + 2 = 259 (the length byte of the domain form counts)" },
|
||
{ "name": "data", "kind": "bytes", "encoding": "length-prefixed",
|
||
"maxLength": 65535, "description": "the datagram payload; a zero-length payload is a legal empty datagram, NOT EOF (the F-2 invariant at payload level — len == 0, zero inner bytes, is unreachable from a compliant peer since addr is always >= 7 bytes)" }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
(Note: `addr + data` together are exactly `len` bytes; the two
|
||
`encoding: length-prefixed` fields are the *decoder's* internal
|
||
split of one frame's payload; on the wire `len` covers both, and
|
||
`data`'s length is `len − len(addr)`, decodable because the ATYP
|
||
determines the address length. The two-field split documents the
|
||
decoder's contract, not extra wire bytes. **Joint constraint:** a
|
||
frame is legal only when `len(addr) + len(data) ≤ 65535`; the
|
||
per-field maxima are not independent (a 65507-byte datagram to a
|
||
255-char domain would need 65766 frame bytes and must be rejected
|
||
`Oversize` at frame time; wire.md §The Data Plane carries the
|
||
arithmetic).)
|
||
|
||
## Annotations
|
||
|
||
### `AssociateFrame`: binary, exact
|
||
|
||
Maps 1:1 to the on-wire bytes: `len` as `uint16` big-endian (2
|
||
bytes), then exactly `len` bytes = `addr + data`. The `addr`
|
||
sub-layout is the RFC 1928 address encoding as forked
|
||
(`TargetAddr::to_be_bytes()`: ATYP `0x01` IPv4 / `0x03` domain /
|
||
`0x04` IPv6, then the address, then the u16 BE port). RSV/FRAG do
|
||
not ride the tunnel wire (ADR-003); FRAG ≠ 0 is refused at front
|
||
doors.
|
||
|
||
One frame per datagram, in both directions of the channel
|
||
`BiStream`, for the whole datagram stage (association lifetime =
|
||
channel lifetime). Frames are self-delimiting on the byte stream; a
|
||
decoder is an incremental state machine across chunk boundaries
|
||
(boundary preservation is mandatory; an empty datagram is a legal
|
||
zero-length payload, never EOF; alktunnels F-2's invariant, met
|
||
structurally and stated at payload level: `len == 0`, zero inner
|
||
bytes, is unreachable from a compliant peer, since `addr` is always
|
||
≥ 7 bytes).
|
||
|
||
### Layering note
|
||
|
||
The frame codec sits in the wire layer, applied **exactly once**:
|
||
`Egress::next_reply` returns *inner* bytes (`addr + data`); the
|
||
producer's frame writer adds the single length prefix (ADR-003's
|
||
framing-once invariant; the double-framing bug class POC #2
|
||
caught). The consumer side mirrors it: the session's send frames
|
||
once, its reader state persists across receives. The BAST describes
|
||
the wire inside the `BiStream` only; `pump_bidi` (ADR-050) is
|
||
upstream's, not re-specified here.
|
||
|
||
## References
|
||
|
||
- [ADR-003](decisions/003-udp-handle-swap.md) (the handle swap + the
|
||
frame), [ADR-001](decisions/001-alpn-and-open-op.md) (the JSON
|
||
open-op shape, outside BAST scope),
|
||
[ADR-011](decisions/011-inline-vendor.md) (the forked codecs)
|
||
- [wire.md](wire.md) (the normative prose; §The Data Plane)
|
||
- alktunnels `bast.md` (the BAST precedent: same meta-schema, same
|
||
"binary framing only" scope rule; the length codec's original
|
||
document) and alktunnels ADR-003 (the codec alksocks reuses)
|
||
- POC #2 `poc-udp-associate-findings.md` (the frame's validation
|
||
matrix: empty datagrams, splits/batches, garbage-ATYP refusal) |