Documentation
¶
Overview ¶
Package relaywire defines the framing that crosses the daemon↔relay socket.
It is the one place the relay's byte layout is written down for Go; the Cloudflare Worker and the web client implement the same layout and are held to it by the shared fixtures in testdata/relay/frames.json. Nothing here knows about WebSockets, Noise, or the wire protocol carried inside — it is framing only, so all three implementations can be compared byte for byte.
Two layers stack:
[4-byte big-endian channel][payload] the daemon↔relay socket [1-byte kind][wire bytes] inside a decrypted channel payload
See spec/relay-protocol.md for the protocol these frames carry.
Index ¶
- Constants
- Variables
- func DecodeControl(b []byte) (any, error)
- func DecodePlain(b []byte) (text bool, data []byte, err error)
- func Encode(f Frame) []byte
- func EncodeControl(msg any) ([]byte, error)
- func EncodePlain(text bool, data []byte) []byte
- type Close
- type Closed
- type Frame
- type Open
- type Pair
- type PairResult
Constants ¶
const ( Ping = "flue-ping" Pong = "flue-pong" )
Ping and Pong are the keepalive text frames. They are never channel-framed: either leg may send Ping, the edge answers Pong through the Durable Object's auto-response, and a receiver drops Pong silently.
const ControlChannel uint32 = 0
ControlChannel is the channel id reserved for the JSON control messages in control.go. Every other channel carries one browser's Noise session.
Variables ¶
var ErrEmptyPayload = errors.New("relaywire: plain payload has no kind byte")
ErrEmptyPayload is returned by DecodePlain for a payload with no kind byte.
var ErrShortFrame = errors.New("relaywire: frame shorter than the channel header")
ErrShortFrame is returned by Decode for a frame with no room for the header.
Functions ¶
func DecodeControl ¶
DecodeControl parses a control message into its concrete type, returning one of *Open, *Closed, *Close, *Pair or *PairResult. An unknown discriminator is an error rather than a silent drop: on this channel every message is one the two ends agreed on, so an unrecognised one means the peers disagree about the protocol.
func DecodePlain ¶
DecodePlain splits a decrypted channel payload into its kind and its bytes. The returned data aliases b, with its capacity clamped as Decode's is. An empty payload, or a kind byte other than 0 or 1, is a protocol error: the peer is not speaking this protocol, and guessing would hand the layer above a frame of the wrong sort.
func Encode ¶
Encode lays a frame out as [4-byte big-endian channel][payload]. The result is freshly allocated, so a caller may keep mutating the payload it passed.
func EncodeControl ¶
EncodeControl marshals a control message, setting "type" from the concrete Go type. Both values and pointers are accepted, so a message DecodeControl returned can be handed straight back, and whatever the caller left in the Type field is overwritten: the concrete type is the authority.
Unlike internal/wire's encoder this marshals the struct directly rather than round-tripping through a map, which keeps the field order the declarations give — "type" first — so the bytes match what the TypeScript side writes by hand and the shared fixtures can pin them.
A nil pointer is an error rather than a panic: this runs on the daemon's relay writer, where a nil message is a bug to report, not one to crash on.
func EncodePlain ¶
EncodePlain prefixes the kind byte that survives the trip through Noise.
The wire protocol distinguishes text frames (JSON control) from binary ones (terminal data), a distinction the WebSocket gives us locally and encryption erases: through the relay every frame is one binary WebSocket message of ciphertext. This byte carries it, so the layer above the relay reads the same (text, data) pair it reads locally.
Types ¶
type Frame ¶
Frame is one message on the daemon↔relay socket: a channel id and the bytes carried on it. On channel 0 the payload is a control message; on any other channel it is a Noise handshake message or transport ciphertext, which this package never inspects.
func Decode ¶
Decode parses a framed message. The returned payload aliases b, so a caller that retains it past the read buffer's lifetime must copy it. Its capacity is clamped to its length, so appending to it allocates rather than writing into the spare capacity of the caller's read buffer.
A frame that is exactly a header is well formed and carries no payload; only a frame too short to hold the header is an error.
type Open ¶
type Open struct {
Type string `json:"type"` // "open"
Channel uint32 `json:"channel"`
Origin string `json:"origin"`
}
Open tells the daemon a browser connected and was assigned Channel. Origin is the Worker's own origin, which the daemon checks against the relay it dialled.
relay -> daemon.
type Pair ¶
type Pair struct {
Type string `json:"type"` // "pair"
ID uint64 `json:"id"`
Origin string `json:"origin"`
Body json.RawMessage `json:"body"`
}
Pair carries an HTTP POST /api/pair the Worker received. Body is the client's JSON as it arrived — the daemon's pairing handler parses these bytes itself, so the relay must not reshape them. Decoding preserves them exactly; re-encoding a Pair in Go escapes <, > and & to their \u00xx forms, which every JSON parser reads back identically but which is not byte-for-byte what a Worker's JSON.stringify produced. Only Go re-encodes a Pair, and only in tests: on the wire this message is written once, by the Worker.
ID correlates the answer, and is the relay's own: it means nothing across a reconnect.
relay -> daemon.
type PairResult ¶
type PairResult struct {
Type string `json:"type"` // "pairResult"
ID uint64 `json:"id"`
Status int `json:"status"`
Body json.RawMessage `json:"body"`
}
PairResult answers a Pair with the HTTP status and response body the Worker should write back to the browser.
Body must be a JSON value: the relay writes it as an application/json response, and EncodeControl refuses anything else rather than shipping a malformed frame. A refusal therefore travels as JSON — {"error":"pairing refused"} — not as the bare text the daemon's own HTTP handler writes.
daemon -> relay.