Files
alksocks/docs/architecture/bast.md
T
glm-5.3-flash 9587670138 docs: Phase 1 architecture — specs, ADRs 001-012, open-questions tracker
- docs/architecture/: README index, overview, wire, producer,
  consumer, vendor, bast specs + open-questions.md (the OQ-SK-01..09
  ledger promoted; new questions as OQ-SK2-06..)
- ADRs 001-012: ALPN/op/params, protocol base, the handle swap,
  the dial-callback shape, auth split by the door, access control
  (open gate + BIND command gate), BIND in base, provider defaults,
  feature gates, channel capacity, the inline vendor, target policy
- deferred OQs (SK2-06, SK2-07) with tracker tasks in
  tasks/architecture/ per the sdd_process deferred-OQ protocol
- review pass applied: frame-length joint bound (65766 corner),
  accept_no_auth vs skip_auth corrected against fast-socks5 source,
  composed-feature wasm posture, cross-reference fixes

Verified: cross-references (all ADR/OQ refs resolve), doc sizes
under split threshold, Phase 0 fidelity spot-checks
2026-09-20 18:41:56 +00:00

138 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
vendored fast-socks5 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 vendored
fast-socks5 codecs ([vendor.md](vendor.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 vendored 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; len == 0 is a legal empty datagram, NOT EOF" }
]
}
}
}
```
(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 vendored
(`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 frame
with `len == 0`, never EOF; alktunnels F-2's invariant, met
structurally).
### 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 vendored 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)