Skip to main content

Module frame

Module frame 

Source
Expand description

The macula application-frame envelope: construction, Ed25519 signing/verification, and the length-prefixed wire codec. Ported from src/peering/macula_frame.erl (macula-io/macula).

A wire frame is <<Length:32/big, Cbor/binary>> where Cbor is the deterministic encoding of a single map (see crate::cbor). Every frame carries a common envelope — version, frame_type, frame_id (UUIDv7), sent_at_ms, capabilities, plus realm/call_id/ source_route set to null unless the specific frame type populates them — and every frame is Ed25519-signed over its own canonical bytes with signature/publisher_sig stripped first.

This module’s correctness is checked against a real reference frame: tests::connect_frame_matches_the_reference_byte_for_byte builds the exact same CONNECT frame macula_frame:connect/1 + macula_frame:sign/2 produced in a live rebar3 shell — same identity, same fixed frame_id/sent_at_ms (injected explicitly, since the reference randomizes both per call and non-determinism would make an exact byte comparison meaningless) — and asserts the encoded bytes, including the Ed25519 signature itself, match exactly. That’s the strongest test available short of dialing a real station: it proves the canonical-CBOR encoding, the field set, and the signing domain are all bit-for-bit compatible at once.

Structs§

AdvertiseSpec
Fields for an ADVERTISE frame.
CallErrorSpec
Fields for an ERROR frame. name is derived from code automatically (matching macula_frame:call_error/1’s own macula_bolt4:name/1 lookup), not a caller-supplied field.
CallInfo
The fields a provider needs from an inbound CALL — the counterpart to CallResponse for the receiving side. Still doesn’t carry source_route/retry_budget: nothing in the provider role built so far acts on either. ucan_token IS carried — added for crate::connection::Session::serve_one_call_gated’s policy check, which runs before a handler ever sees the call.
CallSpec
Fields for a CALL frame — see plans/PLAN_WIRE_PROTOCOL.md §6.4.
ConnectSpec
Fields for a CONNECT frame — see plans/PLAN_WIRE_PROTOCOL.md §5.
EventInfo
What a subscriber actually receives — parsed fields of an EVENT frame.
HelloInfo
The fields of a HELLO frame actually needed to drive the handshake state machine (plans/PLAN_WIRE_PROTOCOL.md §3).
PublishSpec
Fields for a PUBLISH frame.
ResultSpec
Fields for a RESULT frame.
StreamDataSpec
Fields for a STREAM_DATA frame — one chunk. body’s shape follows encoding: Value::Bytes for StreamEncoding::Raw, any structured Value for StreamEncoding::Msgpack (see this section’s module-level note on why that’s still a plain CBOR value, not a second codec).
StreamEndSpec
Fields for a STREAM_END frame — a half-close (role: Send) or full close (role: Both) of one direction. See StreamDataSpec::signer’s doc — same field, same reasoning.
StreamErrorSpec
Fields for a STREAM_ERROR frame — the explicit abort a well-behaved peer sends instead of just dropping the stream on any non-normal termination (plans/PLAN_WIRE_PROTOCOL.md §13.1, point 4). code here is a free-form label (is_binary(Code) in the reference), NOT a BOLT#4 numeric code like an ERROR (§6.4) frame’s code — streaming aborts and unary-call errors use unrelated error vocabularies. signer: see StreamDataSpec::signer’s doc — same field, same reasoning.
StreamOpenInfo
The fields a provider needs from an inbound STREAM_OPEN — the first frame on a freshly-accepted dedicated stream (§13.2). Doesn’t carry source_route/retry_budget: nothing in the provider role built so far acts on either.
StreamOpenSpec
Fields for a STREAM_OPEN frame. Mirrors CALL’s auth/routing shape — deadline_ms/caller/source_route/retry_budget — plus the stream-specific stream_id/mode/args.
StreamReplySpec
Fields for a STREAM_REPLY frame — the terminal result of a client_stream/bidi exchange, sent once by the provider after it has fully consumed and verified whatever the caller streamed.
SubscribeSpec
Fields for a SUBSCRIBE frame.
UnadvertiseSpec
Fields for an UNADVERTISE frame.
UnsubscribeSpec
Fields for an UNSUBSCRIBE frame.

Enums§

CallResponse
Parsed fields of a RESULT or ERROR response to a CALL, correlated by call_id. Returned by crate::connection::Session::call.
DecodeFrameError
Decoded
Result of attempting to decode one frame from the head of a buffer — mirrors the reference decoder’s three-way {ok,_,_} / {more,_} / {error,_} contract, adapted to return a consumed-byte count instead of a remainder slice (equally usable, more idiomatic here).
EncodeFrameError
ParseCallError
ParseCallResponseError
ParseEventError
ParseHelloError
ParseStreamEventError
ParseStreamOpenError
StreamEncoding
encoding on a STREAM_DATA — a hint for how to interpret body, not a second wire codec. See this section’s module-level note.
StreamEvent
What a stream consumer actually receives — one parsed STREAM_DATA/STREAM_END/STREAM_ERROR/STREAM_REPLY frame.
StreamMode
mode on a STREAM_OPEN — who’s expected to push data. Matches macula_stream:mode().
StreamRole
role on a STREAM_END — which direction(s) are closing.
VerifyError
VerifyPublisherError

Constants§

EVENT_PUBLISHER_DOMAIN
MAX_FRAME_BYTES
16 MiB minus one byte — matches ?MAX_FRAME_BYTES (16#FFFFFF) exactly.
PROTOCOL_VERSION
SIG_DOMAIN
Domain separator for the per-frame Ed25519 signature (every frame’s own signature field). Distinct from the SWIM-update and publisher-end-to-end domains documented in plans/PLAN_WIRE_PROTOCOL.md §4 — neither of those is implemented here yet.

Functions§

advertise
Build an ADVERTISE frame with a fresh frame_id/sent_at_ms.
call
Build a CALL frame with a fresh frame_id/sent_at_ms. Unsigned — pass the result to sign before sending.
call_error
Build an ERROR frame with a fresh frame_id/sent_at_ms.
connect
Build a CONNECT frame with a fresh frame_id/sent_at_ms. Unsigned — pass the result to sign before sending.
decode
Decode one length-prefixed frame from the head of buf.
encode
Encode frame as <<Length:32/big, Cbor/binary>>.
frame_call_id
Extract this frame’s call_id, regardless of frame type — used to correlate a RESULT/ERROR back to the CALL that requested it. 16 bytes, matching call_id() :: <<_:128>> — NOT 32; caught only by re-checking against the spec, since the original test for this function made the identical size mistake and so didn’t catch it.
frame_stream_id
Extract this frame’s stream_id, regardless of frame type — used to correlate STREAM_DATA/STREAM_END/STREAM_ERROR/STREAM_REPLY frames back to the STREAM_OPEN that started the exchange. 16 bytes, matching stream_id() :: <<_:128>>.
goodbye
Build a GOODBYE frame. reason is a short machine-readable code (e.g. "normal"); detail is an optional human-readable string.
parse_call
Parse a decoded frame as a CALL — the provider-side counterpart to parse_call_response.
parse_call_response
Parse a decoded frame as a RESULT or ERROR response to a CALL.
parse_event
Parse a decoded frame as an EVENT.
parse_hello
Parse a decoded frame as a HELLO, checking frame_type first.
parse_stream_event
Parse a decoded frame as one of STREAM_DATA/STREAM_END/STREAM_ERROR/ STREAM_REPLY.
parse_stream_open
Parse a decoded frame as a STREAM_OPEN.
publish
Build a PUBLISH frame with a fresh frame_id/sent_at_ms. Does not set publisher_sig (the separate end-to-end publisher signature, §4/§6.8 of the spec) — not implemented by this crate yet.
result
Build a RESULT frame with a fresh frame_id/sent_at_ms.
sign
Sign frame with identity, over SIG_DOMAIN || canonical_cbor(frame minus signature/publisher_sig), and return the frame with its signature field set (64 bytes).
sign_publisher
Add publisher_sig to a PUBLISH or EVENT frame: identity’s Ed25519 signature over (topic, realm, publisher, seq, payload). identity must be the key pair for the pubkey already in the frame’s publisher field – this is not checked here (callers build frames with their own identity’s pubkey as publisher by construction).
stream_data
Build a STREAM_DATA frame with a fresh frame_id/sent_at_ms.
stream_end
Build a STREAM_END frame with a fresh frame_id/sent_at_ms.
stream_error
Build a STREAM_ERROR frame with a fresh frame_id/sent_at_ms.
stream_open
Build a STREAM_OPEN frame with a fresh frame_id/sent_at_ms. Unsigned — pass the result to sign before sending.
stream_reply
Build a STREAM_REPLY frame with a fresh frame_id/sent_at_ms.
subscribe
Build a SUBSCRIBE frame with a fresh frame_id/sent_at_ms. No filter, no options — the plainest possible subscription.
unadvertise
Build an UNADVERTISE frame with a fresh frame_id/sent_at_ms.
unsubscribe
Build an UNSUBSCRIBE frame with a fresh frame_id/sent_at_ms.
verify
Verify frame’s signature field against pubkey, over the same domain-separated bytes sign produces.
verify_publisher
Verify frame’s publisher_sig against its OWN publisher field – unlike verify (the per-hop signature), there is no separate pubkey parameter: publisher_sig’s whole point is proving “the pubkey named in this frame produced it”, independent of which connection it arrived on.