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

6.1 KiB
Raw Blame History

status, last_updated
status last_updated
draft 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) 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 §The Open Op and ADR-001), 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) — 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

{
  "$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 (the handle swap + the frame), ADR-001 (the JSON open-op shape — outside BAST scope), ADR-011 (the vendored codecs)
  • 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)