Search documentation of macula

# macula_frame (macula v4.4.1)

View Source

CBOR-encoded wire frames for Macula V2 (Part 6 §3 canonical wire).

A wire frame is a length-prefixed deterministic CBOR map:

        <<Length:32/big, Cbor/binary>>

where `Cbor` is the RFC 8949 §4.2.1 deterministic encoding of a single map. The map carries the common header fields (`Part 6 §3`) plus type-specific fields and an Ed25519 signature.

PLAN_WIRE_CBOR.md migrated this codec from BERT to CBOR so hecate-station and the macula 3.x SDK share a wire format. Frame schemas (atom-keyed maps in process memory) are unchanged; the codec transparently round-trips atom keys/values to text strings on the wire and reconstitutes them via `binary_to_existing_atom` on decode.

Phase 1 covers CONNECT / HELLO / GOODBYE. Phase 2 adds SWIM. Phase 3 (Session 3.4) adds the DHT operation frames from Part 6 §7: PING / PONG, FIND_NODE / NODES, FIND_VALUE / VALUE, STORE / STORE_ACK, REPLICATE / REPLICATE_ACK. Phase 4 (Session 4.1) adds the CALL / RESULT / ERROR frames from Part 6 §5 plus the BOLT#4 error taxonomy in `hecate_bolt4`. PUBLISH frames land later.

Signatures are Ed25519 over `"macula-v2-frame\0" ++ canonical_cbor(unsigned)` where `canonical_cbor` is `macula_record_cbor:encode/1` (RFC 8949 §4.2.1 deterministic).

# Summary

## Types

advertise_spec/0
block_spec/0
call_error_spec/0
call_id/0
call_spec/0
cancel_spec/0
connect_spec/0
country/0
delivery_channel/0
event_spec/0
find_node_spec/0
find_value_spec/0
frame/0
frame_type/0
have_entry/0
have_spec/0
hello_spec/0
hyparview_disconnect_spec/0
hyparview_forward_join_spec/0
hyparview_join_spec/0
hyparview_neighbor_spec/0
hyparview_shuffle_reply_spec/0
hyparview_shuffle_spec/0
id256/0
manifest_req_spec/0
manifest_res_spec/0
mcid/0
member_state/0
msg_id/0
neighbor_priority/0
nodes_spec/0
nonce128/0
ping_spec/0
plumtree_gossip_spec/0
plumtree_graft_spec/0
plumtree_ihave_spec/0
plumtree_prune_spec/0
pong_spec/0
publish_spec/0
replicate_ack_spec/0
replicate_spec/0
result_spec/0
station_ref/0
station_ref_spec/0
store_ack_spec/0
store_spec/0
stream_data_spec/0
stream_encoding/0
stream_end_spec/0
stream_error_spec/0
stream_id/0
stream_mode/0
stream_open_spec/0
stream_reply_spec/0
stream_role/0
subscribe_spec/0
swim_ack_spec/0
swim_ping_spec/0
swim_suspect_spec/0
swim_update/0
swim_update_spec/0
tier/0
unadvertise_spec/0
unsubscribe_spec/0
value_spec/0
want_entry/0
want_priority/0
want_spec/0

## Functions

advertise(Spec)
block(_)
call(Spec)
call_error(Spec)
cancel(_)
connect(Spec)
decode(Buf)

Decode a single length-prefixed frame from the head of a buffer. Returns `{ok, Frame, RestBuffer}`, `{more, BytesNeeded}` if the buffer is short, or `{error, Reason}` if the framing is malformed.

encode(Frame)
event(Spec)
find_node(_)
find_value(_)
frame_id(_)
frame_type(_)
goodbye(Reason, Detail)
goodbye(Reason, Detail, Caps)
have(_)
hello(Spec)
hyparview_disconnect(_)
hyparview_forward_join(_)
hyparview_join(_)
hyparview_neighbor(_)
hyparview_shuffle(_)
hyparview_shuffle_reply(_)
manifest_req(_)
manifest_res(_)
nodes(_)
parse_stream(Buf)

Drain all complete frames from a buffer. Returns the list of frames (in order) and the remaining (incomplete) buffer.

ping(_)
plumtree_gossip(_)
plumtree_graft(_)
plumtree_ihave(_)
plumtree_prune(_)
pong(_)
publish(Spec)
replicate(_)
replicate_ack(_)
result(Spec)
sent_at_ms(_)
sign(Frame, Identity)
sign_publisher(Frame, Identity)

Add `publisher_sig` to a PUBLISH or EVENT frame: the publisher's Ed25519 signature over (topic, realm, publisher, seq, payload). `Identity` must be the key pair / private key of the pubkey in the frame's `publisher` field.

sign_swim_update(Update, Identity)
signature(_)
station_ref(Spec)
store(_)
store_ack(Spec)
stream_data(_)
stream_end(_)
stream_error(_)
stream_open(Spec)
stream_reply(_)
subscribe(Spec)
swim_ack(Spec)
swim_confirm(Spec)
swim_ping(Spec)
swim_suspect(Spec)
swim_update(_)
unadvertise(_)
unsubscribe(_)
value(_)
verify(Frame, Pub)
verify_publisher(Frame)

Verify a frame's `publisher_sig` against its `publisher` field. Returns `{ok, Frame}` on success, `{error, Reason}` otherwise (`no_publisher_sig` when the field is absent — callers that want it MUST treat absence as a verification failure, not as "trusted").

verify_swim_update(Update)
version(_)
want(_)

# Types

# advertise_spec/0

    -type advertise_spec() ::
              #{realm := id256(),
                procedure := binary(),
                advertiser := macula_identity:pubkey(),
                options => map()}.

# block_spec/0

    -type block_spec() :: #{mcid := mcid(), payload := binary()}.

# call_error_spec/0

    -type call_error_spec() ::
              #{call_id := call_id(),
                code := macula_bolt4:code(),
                reported_by := macula_identity:pubkey(),
                detail => binary() | undefined,
                offending_hop => macula_identity:pubkey() | undefined,
                source_route_partial => binary()}.

# call_id/0

    -type call_id() :: <<_:128>>.

# call_spec/0

    -type call_spec() ::
              #{call_id := call_id(),
                procedure := binary(),
                realm := id256(),
                payload := term(),
                deadline_ms := integer(),
                caller := macula_identity:pubkey(),
                source_route => binary(),
                retry_budget => non_neg_integer()}.

# cancel_spec/0

    -type cancel_spec() :: #{blocks := [mcid()]}.

# connect_spec/0

    -type connect_spec() ::
              #{node_id := macula_identity:pubkey(),
                station_id := macula_identity:pubkey(),
                realms := [macula_identity:pubkey()],
                capabilities := non_neg_integer(),
                puzzle_evidence := <<_:256>>,
                addresses => [map()],
                site => map() | undefined,
                endorsements => [map()]}.

# country/0

    -type country() :: <<_:16>>.

# delivery_channel/0

    -type delivery_channel() :: plumtree | dht | direct.

# event_spec/0

    -type event_spec() ::
              #{topic := binary(),
                realm := id256(),
                publisher := macula_identity:pubkey(),
                seq := non_neg_integer(),
                payload := term(),
                delivered_via := delivery_channel(),
                publisher_sig => binary()}.

# find_node_spec/0

    -type find_node_spec() ::
              #{key := id256(), origin := macula_identity:pubkey(), depth := non_neg_integer()}.

# find_value_spec/0

    -type find_value_spec() :: #{key := id256(), origin := macula_identity:pubkey()}.

# frame/0

    -type frame() :: map().

# frame_type/0

    -type frame_type() ::
              connect | hello | goodbye | swim_ping | swim_ack | swim_suspect | swim_confirm | ping | pong |
              find_node | nodes | find_value | value | store | store_ack | replicate | replicate_ack |
              call | result | error | hyparview_join | hyparview_forward_join | hyparview_neighbor |
              hyparview_disconnect | hyparview_shuffle | hyparview_shuffle_reply | plumtree_gossip |
              plumtree_ihave | plumtree_graft | plumtree_prune | publish | subscribe | unsubscribe | event |
              advertise | unadvertise | stream_open | stream_data | stream_end | stream_error |
              stream_reply | want | have | block | manifest_req | manifest_res | cancel.

# have_entry/0

    -type have_entry() :: #{mcid := mcid(), size := non_neg_integer()}.

# have_spec/0

    -type have_spec() :: #{blocks := [have_entry()]}.

# hello_spec/0

    -type hello_spec() ::
              #{node_id := macula_identity:pubkey(),
                station_id := macula_identity:pubkey(),
                realms := [macula_identity:pubkey()],
                capabilities := non_neg_integer(),
                accepted := boolean(),
                negotiated_capabilities := non_neg_integer(),
                addresses => [map()],
                site => map() | undefined,
                refusal_code => non_neg_integer() | undefined}.

# hyparview_disconnect_spec/0

    -type hyparview_disconnect_spec() :: #{realm := id256()}.

# hyparview_forward_join_spec/0

    -type hyparview_forward_join_spec() ::
              #{realm := id256(),
                new_member := macula_identity:pubkey(),
                ttl := non_neg_integer(),
                arwl := non_neg_integer(),
                prwl := non_neg_integer()}.

# hyparview_join_spec/0

    -type hyparview_join_spec() :: #{realm := id256(), new_member := macula_identity:pubkey()}.

# hyparview_neighbor_spec/0

    -type hyparview_neighbor_spec() :: #{realm := id256(), priority := neighbor_priority()}.

# hyparview_shuffle_reply_spec/0

    -type hyparview_shuffle_reply_spec() :: #{realm := id256(), peer_sample := [macula_identity:pubkey()]}.

# hyparview_shuffle_spec/0

    -type hyparview_shuffle_spec() ::
              #{realm := id256(),
                origin := macula_identity:pubkey(),
                ttl := non_neg_integer(),
                peer_sample := [macula_identity:pubkey()]}.

# id256/0

    -type id256() :: <<_:256>>.

# manifest_req_spec/0

    -type manifest_req_spec() :: #{mcid := mcid()}.

# manifest_res_spec/0

    -type manifest_res_spec() :: #{mcid := mcid(), manifest := map() | not_found}.

# mcid/0

    -type mcid() :: <<_:272>>.

# member_state/0

    -type member_state() :: alive | suspect | confirmed_failed.

# msg_id/0

    -type msg_id() :: <<_:128>>.

# neighbor_priority/0

    -type neighbor_priority() :: high | low.

# nodes_spec/0

    -type nodes_spec() :: #{key := id256(), nodes := [station_ref()]}.

# nonce128/0

    -type nonce128() :: <<_:128>>.

# ping_spec/0

    -type ping_spec() :: #{nonce := nonce128()}.

# plumtree_gossip_spec/0

    -type plumtree_gossip_spec() ::
              #{realm := id256(), msg_id := msg_id(), round := non_neg_integer(), payload := term()}.

# plumtree_graft_spec/0

    -type plumtree_graft_spec() :: #{realm := id256(), msg_id := msg_id(), round := non_neg_integer()}.

# plumtree_ihave_spec/0

    -type plumtree_ihave_spec() :: #{realm := id256(), msg_id := msg_id(), round := non_neg_integer()}.

# plumtree_prune_spec/0

    -type plumtree_prune_spec() :: #{realm := id256()}.

# pong_spec/0

    -type pong_spec() :: #{nonce := nonce128()}.

# publish_spec/0

    -type publish_spec() ::
              #{topic := binary(),
                realm := id256(),
                publisher := macula_identity:pubkey(),
                seq := non_neg_integer(),
                payload := term(),
                published_at_ms := non_neg_integer(),
                ttl_ms => non_neg_integer() | undefined,
                publisher_sig => binary()}.

# replicate_ack_spec/0

    -type replicate_ack_spec() :: #{key := id256(), accepted := boolean()}.

# replicate_spec/0

    -type replicate_spec() :: #{record := macula_record:record(), new_custodian := boolean()}.

# result_spec/0

    -type result_spec() ::
              #{call_id := call_id(),
                payload := term(),
                responded_by := macula_identity:pubkey(),
                source_route_reverse => binary()}.

# station_ref/0

    -type station_ref() ::
              #{node_id := macula_identity:pubkey(),
                station_id := macula_identity:pubkey(),
                addresses := [map()],
                tier := tier(),
                asn := non_neg_integer() | undefined,
                country := country(),
                last_seen_at := pos_integer()}.

# station_ref_spec/0

    -type station_ref_spec() ::
              #{node_id := macula_identity:pubkey(),
                station_id := macula_identity:pubkey(),
                addresses => [map()],
                tier := tier(),
                asn => non_neg_integer() | undefined,
                country := country(),
                last_seen_at := pos_integer()}.

# store_ack_spec/0

    -type store_ack_spec() :: #{key := id256(), stored := boolean(), reason => atom() | undefined}.

# store_spec/0

    -type store_spec() :: #{record := macula_record:record()}.

# stream_data_spec/0

    -type stream_data_spec() ::
              #{stream_id := stream_id(),
                seq := non_neg_integer(),
                encoding := stream_encoding(),
                body := term()}.

# stream_encoding/0

    -type stream_encoding() :: raw | msgpack.

# stream_end_spec/0

    -type stream_end_spec() :: #{stream_id := stream_id(), role := stream_role()}.

# stream_error_spec/0

    -type stream_error_spec() :: #{stream_id := stream_id(), code := binary(), message := binary()}.

# stream_id/0

    -type stream_id() :: <<_:128>>.

# stream_mode/0

    -type stream_mode() :: server_stream | client_stream | bidi.

# stream_open_spec/0

    -type stream_open_spec() ::
              #{stream_id := stream_id(),
                procedure := binary(),
                realm := id256(),
                mode := stream_mode(),
                args := term(),
                deadline_ms := integer(),
                caller := macula_identity:pubkey(),
                source_route => binary(),
                retry_budget => non_neg_integer()}.

# stream_reply_spec/0

    -type stream_reply_spec() ::
              #{stream_id := stream_id(), payload := term(), responded_by := macula_identity:pubkey()}.

# stream_role/0

    -type stream_role() :: send | both.

# subscribe_spec/0

    -type subscribe_spec() ::
              #{topic := binary(),
                realm := id256(),
                subscriber := macula_identity:pubkey(),
                filter => term() | undefined,
                options => map()}.

# swim_ack_spec/0

    -type swim_ack_spec() ::
              #{round := non_neg_integer(),
                responder := macula_identity:pubkey(),
                incarnation := non_neg_integer(),
                piggyback => [swim_update()]}.

# swim_ping_spec/0

    -type swim_ping_spec() ::
              #{round := non_neg_integer(), incarnation := non_neg_integer(), piggyback => [swim_update()]}.

# swim_suspect_spec/0

    -type swim_suspect_spec() ::
              #{target := macula_identity:pubkey(),
                target_incarnation := non_neg_integer(),
                suspected_by := macula_identity:pubkey(),
                ttl := non_neg_integer()}.

# swim_update/0

    -type swim_update() ::
              #{target := macula_identity:pubkey(),
                state := member_state(),
                incarnation := non_neg_integer(),
                observed_at := pos_integer(),
                by := macula_identity:pubkey(),
                signature => <<_:512>>}.

# swim_update_spec/0

    -type swim_update_spec() ::
              #{target := macula_identity:pubkey(),
                state := member_state(),
                incarnation := non_neg_integer(),
                observed_at := pos_integer(),
                by := macula_identity:pubkey()}.

# tier/0

    -type tier() :: 0..4.

# unadvertise_spec/0

    -type unadvertise_spec() ::
              #{realm := id256(), procedure := binary(), advertiser := macula_identity:pubkey()}.

# unsubscribe_spec/0

    -type unsubscribe_spec() ::
              #{topic := binary(), realm := id256(), subscriber := macula_identity:pubkey()}.

# value_spec/0

    -type value_spec() :: #{key := id256(), records := [macula_record:record()]}.

# want_entry/0

    -type want_entry() :: #{mcid := mcid(), priority => want_priority()}.

# want_priority/0

    -type want_priority() :: 0..255.

# want_spec/0

    -type want_spec() :: #{blocks := [want_entry()]}.

# Functions

# advertise(Spec)

    -spec advertise(advertise_spec()) -> frame().

# block(_)

    -spec block(block_spec()) -> frame().

# call(Spec)

    -spec call(call_spec()) -> frame().

# call_error(Spec)

    -spec call_error(call_error_spec()) -> frame().

# cancel(_)

    -spec cancel(cancel_spec()) -> frame().

# connect(Spec)

    -spec connect(connect_spec()) -> frame().

# decode(Buf)

    -spec decode(binary()) -> {ok, frame(), binary()} | {more, pos_integer()} | {error, term()}.

Decode a single length-prefixed frame from the head of a buffer. Returns `{ok, Frame, RestBuffer}`, `{more, BytesNeeded}` if the buffer is short, or `{error, Reason}` if the framing is malformed.

# encode(Frame)

    -spec encode(frame()) -> binary().

# event(Spec)

    -spec event(event_spec()) -> frame().

# find_node(_)

    -spec find_node(find_node_spec()) -> frame().

# find_value(_)

    -spec find_value(find_value_spec()) -> frame().

# frame_id(_)

# frame_type(_)

# goodbye(Reason, Detail)

    -spec goodbye(atom(), binary() | undefined) -> frame().

# goodbye(Reason, Detail, Caps)

    -spec goodbye(atom(), binary() | undefined, non_neg_integer()) -> frame().

# have(_)

    -spec have(have_spec()) -> frame().

# hello(Spec)

    -spec hello(hello_spec()) -> frame().

# hyparview_disconnect(_)

    -spec hyparview_disconnect(hyparview_disconnect_spec()) -> frame().

# hyparview_forward_join(_)

    -spec hyparview_forward_join(hyparview_forward_join_spec()) -> frame().

# hyparview_join(_)

    -spec hyparview_join(hyparview_join_spec()) -> frame().

# hyparview_neighbor(_)

    -spec hyparview_neighbor(hyparview_neighbor_spec()) -> frame().

# hyparview_shuffle(_)

    -spec hyparview_shuffle(hyparview_shuffle_spec()) -> frame().

# hyparview_shuffle_reply(_)

    -spec hyparview_shuffle_reply(hyparview_shuffle_reply_spec()) -> frame().

# manifest_req(_)

    -spec manifest_req(manifest_req_spec()) -> frame().

# manifest_res(_)

    -spec manifest_res(manifest_res_spec()) -> frame().

# nodes(_)

    -spec nodes(nodes_spec()) -> frame().

# parse_stream(Buf)

    -spec parse_stream(binary()) -> {[frame()], binary()}.

Drain all complete frames from a buffer. Returns the list of frames (in order) and the remaining (incomplete) buffer.

# ping(_)

    -spec ping(ping_spec()) -> frame().

# plumtree_gossip(_)

    -spec plumtree_gossip(plumtree_gossip_spec()) -> frame().

# plumtree_graft(_)

    -spec plumtree_graft(plumtree_graft_spec()) -> frame().

# plumtree_ihave(_)

    -spec plumtree_ihave(plumtree_ihave_spec()) -> frame().

# plumtree_prune(_)

    -spec plumtree_prune(plumtree_prune_spec()) -> frame().

# pong(_)

    -spec pong(pong_spec()) -> frame().

# publish(Spec)

    -spec publish(publish_spec()) -> frame().

# replicate(_)

    -spec replicate(replicate_spec()) -> frame().

# replicate_ack(_)

    -spec replicate_ack(replicate_ack_spec()) -> frame().

# result(Spec)

    -spec result(result_spec()) -> frame().

# sent_at_ms(_)

# sign(Frame, Identity)

    -spec sign(frame(), macula_identity:key_pair() | macula_identity:privkey()) -> frame().

# sign_publisher(Frame, Identity)

    -spec sign_publisher(frame(), macula_identity:key_pair() | macula_identity:privkey()) -> frame().

Add `publisher_sig` to a PUBLISH or EVENT frame: the publisher's Ed25519 signature over (topic, realm, publisher, seq, payload). `Identity` must be the key pair / private key of the pubkey in the frame's `publisher` field.

# sign_swim_update(Update, Identity)

    -spec sign_swim_update(swim_update(), macula_identity:key_pair() | macula_identity:privkey()) ->
                              swim_update().

# signature(_)

# station_ref(Spec)

    -spec station_ref(station_ref_spec()) -> station_ref().

# store(_)

    -spec store(store_spec()) -> frame().

# store_ack(Spec)

    -spec store_ack(store_ack_spec()) -> frame().

# stream_data(_)

    -spec stream_data(stream_data_spec()) -> frame().

# stream_end(_)

    -spec stream_end(stream_end_spec()) -> frame().

# stream_error(_)

    -spec stream_error(stream_error_spec()) -> frame().

# stream_open(Spec)

    -spec stream_open(stream_open_spec()) -> frame().

# stream_reply(_)

    -spec stream_reply(stream_reply_spec()) -> frame().

# subscribe(Spec)

    -spec subscribe(subscribe_spec()) -> frame().

# swim_ack(Spec)

    -spec swim_ack(swim_ack_spec()) -> frame().

# swim_confirm(Spec)

    -spec swim_confirm(swim_suspect_spec()) -> frame().

# swim_ping(Spec)

    -spec swim_ping(swim_ping_spec()) -> frame().

# swim_suspect(Spec)

    -spec swim_suspect(swim_suspect_spec()) -> frame().

# swim_update(_)

    -spec swim_update(swim_update_spec()) -> swim_update().

# unadvertise(_)

    -spec unadvertise(unadvertise_spec()) -> frame().

# unsubscribe(_)

    -spec unsubscribe(unsubscribe_spec()) -> frame().

# value(_)

    -spec value(value_spec()) -> frame().

# verify(Frame, Pub)

    -spec verify(frame(), macula_identity:pubkey()) -> {ok, frame()} | {error, term()}.

# verify_publisher(Frame)

    -spec verify_publisher(frame()) -> {ok, frame()} | {error, term()}.

Verify a frame's `publisher_sig` against its `publisher` field. Returns `{ok, Frame}` on success, `{error, Reason}` otherwise (`no_publisher_sig` when the field is absent — callers that want it MUST treat absence as a verification failure, not as "trusted").

# verify_swim_update(Update)

    -spec verify_swim_update(swim_update()) -> {ok, swim_update()} | {error, term()}.

# version(_)

# want(_)

    -spec want(want_spec()) -> frame().