Skip to content

relay: routing-envelope wrapper type — marshal, unmarshal, tests #1

@ilmoniemi

Description

@ilmoniemi

User Story

As a developer building the relay's routing logic, I want a typed Go wrapper for the relay↔binary routing envelope ({conn_id, frame}), with marshal/unmarshal helpers and exhaustive tests, so that every future routing-layer ticket can use a single canonical type instead of poking at JSON ad-hoc.

Context

The relay forwards WS frames between phones and a pyrycode binary, prepending/stripping a small routing wrapper that tells the binary which phone connection a frame came from. The wire shape is defined in pyrycode/pyrycode/docs/protocol-mobile.md § Routing envelope:

{
  "conn_id": "c-7f3a...",
  "frame": { /* the actual envelope */ }
}

conn_id is a relay-assigned, per-phone-connection opaque string. frame is the inner envelope; the relay treats it as opaque bytes and MUST NOT deserialise it.

This ticket establishes the canonical Go vocabulary (type + helpers) the rest of the routing layer will use. Networking, header validation, and the connection registry are separate, later tickets.

Acceptance Criteria

  • A typed wrapper for the routing envelope exists in internal/relay/, exporting:
    • A struct with two fields matching the wire shape: a conn_id string and a frame raw-JSON payload (kept as opaque bytes — see opacity invariant below).
    • A Marshal helper that takes a connection id and an inner frame []byte and returns the JSON-encoded envelope. Returns an error on empty connection id and on inner-frame bytes that are not valid JSON.
    • An Unmarshal helper that parses a JSON-encoded envelope into the struct. Returns an error on malformed JSON, missing conn_id, or missing frame.
  • Opacity invariant: a frame containing arbitrary nested JSON round-trips through Marshal → Unmarshal → access bytewise (modulo whitespace). The relay never parses inner-frame contents.
  • Exhaustive tests covering, at minimum:
    • Round-trip with a non-trivial nested-JSON frame, asserting bytewise opacity.
    • Marshal rejects empty connection id.
    • Marshal rejects invalid-JSON frame bytes.
    • Unmarshal rejects malformed JSON.
    • Unmarshal rejects envelopes missing conn_id.
    • Unmarshal rejects envelopes missing frame.
  • Each exported symbol carries a doc comment describing inputs, return values, and the opacity invariant.
  • make vet clean; make test green; make build still produces a working binary (no behaviour change to cmd/pyrycode-relay/main.go).

Technical Notes

  • Stdlib only (encoding/json); json.RawMessage is the natural fit for the opaque frame field but the architect owns the final type choice.
  • The wrapper is the boundary between "relay thinks about routing" and "relay never thinks about message content." All future routing-layer code should use these helpers, never inline JSON.

Out of Scope

  • WebSocket upgrade / frame forwarding.
  • Connection-pool data structure mapping server-id ↔ connections.
  • Auth header validation.
  • The conn_id generation scheme (relay-assigned, opaque — later ticket).

Size Estimate

XS — single file, pure functions, ~50 LOC + ~50 LOC tests.

Not security-sensitive: pure JSON marshalling, no auth, no networking, no internet exposure.

Metadata

Metadata

Assignees

No one assigned

    Labels

    size:xsTiny ticket: <30 lines production code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions