Skip to content

feat(capture): live-proxy pump pipeline (#56, part 1)#63

Merged
sepehr-safari merged 1 commit into
mainfrom
feat/capture-proxy
Jul 11, 2026
Merged

feat(capture): live-proxy pump pipeline (#56, part 1)#63
sepehr-safari merged 1 commit into
mainfrom
feat/capture-proxy

Conversation

@sepehr-safari

Copy link
Copy Markdown
Member

Part 1 of #56 (the flagship proxy). This lands the socket-free pump
pipeline
; a follow-up wires it to real std.Io.net sockets with io.async
bidirectional concurrency to close the issue.

Why split

The pipeline (relay + decode + record + detect) is pure data-flow and worth
verifying in isolation, deterministically. The transport layer (accept/dial +
structured concurrency over Zig 0.16's new async std.Io.net) is a separable,
novel-API concern. Keeping them apart gives each a green, reviewable checkpoint.

What's here — src/capture/proxy.zig

  • pumpDirection drives one direction over a generic Io.Reader/Io.Writer:
    it relays every frame verbatim (Studio is transparent) and taps a copy
    to decode. Verbatim relay is correct because masking polarity is preserved hop
    for hop — CP→CSMS stays client→server (masked), CSMS→CP stays server→client
    (unmasked) — so raw bytes forward untouched; the tap decodes a copy (decode
    unmasks in place, never the relayed bytes).
  • Sink accumulates tapped events, appends a JSONL recording embedding
    the exact OCPP-J bytes with explicit direction/timestamp (so it re-parses
    offline identically), reassembles fragmented messages via ws.Assembler, and
    runs detection on demand (detect — the streaming/session-end surface).
  • Control frames (ping/pong/close) are relayed so the real peers handle them; a
    relayed close ends the direction.

Hardening

Per-frame cap (1 MiB) → oversize closes the session; per-session event cap bounds
retained memory; a frame that fails to decode is counted (dropped) and skipped,
never fatal.

Tests (3 new, 147 total)

  • Verbatim relay is byte-exact and both messages are tapped with the
    socket-known direction.
  • A fragmented message reassembles; a frame after a close is not tapped.
  • Parity: streaming detection over the live events equals a cold offline
    detectFailures over the recording (a FAILED_AUTHORIZATION case).

Verification

  • native test -Dplatform=null147/147.
  • native build (ReleaseFast) → clean.
  • zig fmt --check clean.

Next (part 2, closes #56)

Real IpAddress.listen/accept/connect, the MITM handshake relay (reusing
#54's both-halves codec), io.async/Group concurrency guarded by a
std.Io.Mutex, and an in-process netSocketCreatePair CP ↔ proxy ↔ CSMS
integration test — plus ADR-0009 recording the transport/concurrency decision.

The socket-free heart of the MITM proxy. `pumpDirection` drives one direction
over a generic Io.Reader/Io.Writer: it relays every WebSocket frame verbatim
(Studio is transparent — masking polarity is preserved hop for hop) and taps a
copy to decode into canonical events. A `Sink` accumulates the tapped events,
appends a JSONL recording (embedding the exact OCPP-J bytes, with explicit
direction/timestamp so it re-parses offline identically), reassembles fragmented
messages, and runs detection on demand.

Operating over generic readers/writers keeps the whole pipeline unit-testable in
memory (no sockets): verbatim relay, fragmented reassembly + stop-on-close, and a
parity test proving streaming detection over the live events equals a cold
offline pass over the recording.

The real-socket accept/dial/connect wiring and the io.async bidirectional
concurrency build on this to close #56 (part 2).

Part of #56.
@sepehr-safari sepehr-safari merged commit dfe8c2c into main Jul 11, 2026
3 checks passed
@sepehr-safari sepehr-safari deleted the feat/capture-proxy branch July 11, 2026 23:34
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): MITM proxy — streaming detection + record-to-trace

1 participant