Skip to content

relay: frame forwarding loop — wrap/unwrap routing envelope, route by server-id and conn_id #6

@ilmoniemi

Description

@ilmoniemi

User Story

As the relay, I want per-connection forwarding goroutines that read frames from each registered phone and binary, wrap them in (or strip from) the routing envelope, and write them to the addressed peer, so that phone-to-binary and binary-to-phone traffic actually flows after the WS upgrades complete.

Context

The actual routing layer. After the WS upgrades (#4, #5) register connections in the registry (#3), this ticket wires the data path: every phone frame is wrapped in the routing envelope (#1) with the phone's conn_id, and forwarded to the binary; every binary frame is unwrapped, the embedded conn_id is looked up, and the inner frame is sent to the right phone. See pyrycode/pyrycode/docs/protocol-mobile.md § Routing envelope.

Acceptance Criteria

  • New file internal/relay/forward.go exporting:
    • func StartPhoneForwarder(ctx context.Context, r *Registry, serverID string, conn Conn, logger *slog.Logger) — starts a goroutine that reads phone frames in a loop. For each frame: wrap with MarshalRoutingEnvelope(conn.ConnID(), frame) (uses relay: routing-envelope wrapper type — marshal, unmarshal, tests #1's helper), look up registry.BinaryFor(serverID), and call binary.Send(wrapped). Errors on Send → log + close phone conn.
    • func StartBinaryForwarder(ctx context.Context, r *Registry, serverID string, conn Conn, logger *slog.Logger) — starts a goroutine that reads binary frames in a loop. For each frame: UnmarshalRoutingEnvelope(frame), look up the phone in registry.PhonesFor(serverID) matching env.ConnID, call phone.Send(env.Frame). If conn-id not found → log + drop frame (don't close binary; phones come and go).
    • Both forwarders exit when the context is cancelled OR the source conn returns a Read error.
  • Wired into the WS upgrade handlers from relay: WS upgrade for /v1/server — accept binary connection, validate headers, claim server-id #4 and relay: WS upgrade for /v1/client — accept phone connection, look up server-id, register #5: after a successful registration, the handler calls StartPhoneForwarder (or StartBinaryForwarder) on the new conn. The handler's HTTP response goroutine doesn't return until the forwarder exits.
  • Tests in internal/relay/forward_test.go:
    • Phone-to-binary: mock phone+binary conns wired to a registry; phone sends 3 frames; assert binary receives 3 wrapped envelopes with correct conn_id.
    • Binary-to-phone: mock binary sends an envelope with a known conn_id; assert the matching phone receives the inner frame; assert other phones do NOT receive it.
    • Multi-phone: 2 phones registered; binary sends envelopes addressed to each; each receives only its own.
    • Unknown conn_id: binary sends to nonexistent conn_id → log warning, no panic, frame dropped, other connections unaffected.
    • Phone disconnects mid-stream: forwarder exits cleanly, registry no longer lists the phone.
    • Binary disconnects: registry releases server-id; in-flight phone forwarders' next Send fails (no binary) → they exit cleanly.
  • go test -race -count=20 clean for the test suite.

Technical Notes

Size Estimate

M — two forwarders + integration with handlers + multi-conn test scenarios. ~150 LOC + ~200 LOC tests.

Depends on

Metadata

Metadata

Assignees

No one assigned

    Labels

    security-sensitiveTouches auth, crypto, or internet-exposed input paths

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions