Skip to content

feat(capture): WebSocket transport — hardened RFC 6455 subset (ADR-0008)#61

Merged
sepehr-safari merged 1 commit into
mainfrom
feat/ws-transport
Jul 11, 2026
Merged

feat(capture): WebSocket transport — hardened RFC 6455 subset (ADR-0008)#61
sepehr-safari merged 1 commit into
mainfrom
feat/ws-transport

Conversation

@sepehr-safari

Copy link
Copy Markdown
Member

Closes #54. First issue of S5 — Live capture.

Summary

The transport foundation for the live-capture proxy. Zig's std has no WebSocket
implementation, and the zero-config build (ADR-0004) has no dependency mechanism
without ejecting the runner — so ADR-0008 records the decision to hand-roll a
bounded RFC 6455 subset that Studio owns. That keeps the untrusted-input
hardening at the rawest entry point in the app (raw socket bytes from an
arbitrary peer) fully in our control.

What's here

  • ADR-0008 — WebSocket transport (hand-rolled subset; scope in/out; TLS and
    permessage-deflate explicitly deferred to post-0.5, behind this same
    boundary).
  • src/capture/ws.zig — a pure codec (byte buffers, no sockets), so the
    whole thing is unit-testable without a network:
    • Both handshake halves — server-accept (Sec-WebSocket-Accept via
      SHA-1 + base64) and client-request — since MITM plays both roles.
    • Base framing at every payload-length form (7-bit / 16-bit / 64-bit),
      encode + decode.
    • Fragmentation reassembly (Assembler); close-frame build/parse.
  • src/capture/capture.zig aggregate root, wired into the root test block.
  • ADR index caught up with 0006–0008 (0006/0007 were added in S3 but never
    indexed).

Hardening (untrusted input)

  • Masking enforced per role: client→server frames MUST be masked,
    server→client MUST NOT — violations rejected (RFC 6455 §5.1).
  • Per-frame (16 MiB) and reassembled-message (32 MiB) size bounds; fragment-count
    cap (1024); oversize payloads rejected before the payload is read.
  • Reserved bits, unknown opcodes, and malformed/fragmented control frames
    rejected. Truncated buffers return null (read more), never over-read.

Tests

15 new codec tests (138 total, up from 123), including the RFC 6455 §1.3 accept
example, round-trips at each length form (masked + unmasked), and every rejection
path above.

Verification

  • native test -Dplatform=null138/138.
  • native build (ReleaseFast) → clean (the lazy-analysis double-check).
  • zig fmt --check clean.

Not here (later S5 issues)

Socket I/O and the proxy loop (#56); OCPP-J decode (#55). The codec is
socket-free by design.

The transport foundation for live capture. Zig's std has no WebSocket, and the
zero-config build (ADR-0004) has no dependency mechanism without ejecting, so
ADR-0008 records the decision to hand-roll a bounded RFC 6455 subset that Studio
owns — keeping the untrusted-input hardening at the entry point where it matters
most.

src/capture/ws.zig is a pure codec (byte buffers, no sockets), fully
unit-testable: both handshake halves (server-accept + client-request), base
framing at every length form, fragmentation reassembly, and control frames.
Hardening: masking enforced per role (client->server MUST be masked,
server->client MUST NOT), per-frame and per-message size bounds, a fragment-count
cap, and rejection of reserved bits, unknown opcodes, and malformed control
frames.

Adds the src/capture/ aggregate root, wires it into the root test block, and
catches up the ADR index with 0006-0008.

Closes #54.
@sepehr-safari
sepehr-safari merged commit cf37fec into main Jul 11, 2026
3 checks passed
@sepehr-safari
sepehr-safari deleted the feat/ws-transport branch July 11, 2026 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(capture): WebSocket transport — RFC 6455 subset (ADR-0008)

1 participant