Packages
macula
5.0.0
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
src/peering/macula_peering.erl
%% @doc Macula peering — connection state machine API.
%%
%% Each peer connection is one `macula_peering_conn' gen_statem under the
%% `macula_peering_conn_sup' simple_one_for_one supervisor.
%%
%% Two entry points:
%% <ul>
%% <li>`connect/1' — outbound dial; worker drives the QUIC connect.</li>
%% <li>`accept/2' — inbound; caller transfers ownership of an
%% already-established `macula_quic' connection (a `reference()')
%% to a new worker.</li>
%% </ul>
%%
%% The caller passes a `controlling_pid' in opts; that pid receives
%% peering events as messages:
%% <ul>
%% <li>`{macula_peering, connected, ConnPid, PeerNodeId}'</li>
%% <li>`{macula_peering, frame, ConnPid, Frame}' (post-handshake)</li>
%% <li>`{macula_peering, disconnected, ConnPid, Reason}'</li>
%% </ul>
%%
%% An optional `accept_owner' pid in opts receives a single
%% `{macula_peering, handshake_complete, ConnPid, PeerNodeId}'
%% message the moment the worker transitions from `handshaking' to
%% `connected'. Used by accept-side listeners that (a) cap concurrent
%% handshaking workers separately from healthy connected peers, and
%% (b) dedupe duplicate dials from the same peer identity by closing
%% prior workers for the same `PeerNodeId'.
-module(macula_peering).
-export([
connect/1,
accept/2,
close/1, close/2,
send_frame/2,
peer_capabilities/1
]).
%% Capability bit asserting the peer is a relay-station (i.e. it
%% advertises on behalf of others via gossip). Daemons MUST leave this
%% unset. Stations set this on outbound dial and inbound accept so the
%% counterpart can tell direct daemon ADVERTISEs apart from station
%% gossip relays.
-define(CAP_STATION, 16#0000_0000_0000_0001).
-type opts() :: macula_peering_conn:opts().
-export_type([opts/0]).
%%------------------------------------------------------------------
%% Public API
%%------------------------------------------------------------------
%% @doc Outbound connect. Spawns a worker that opens a QUIC connection to
%% `target' and runs the CONNECT/HELLO handshake.
-spec connect(opts()) -> {ok, pid()} | {error, term()}.
connect(Opts) ->
macula_peering_conn_sup:start_conn(Opts#{role => client}).
%% @doc Inbound accept. Caller currently owns `Conn' (e.g. it's the listener
%% owner that just received `{quic, new_conn, Conn, _}'). The transfer of
%% ownership and the handshake start are sequenced atomically.
-spec accept(reference(), opts()) ->
{ok, pid()} | {error, term()}.
accept(Conn, Opts) ->
start_server_worker(Conn, Opts#{role => server, quic_conn => Conn}).
start_server_worker(Conn, Opts) ->
handle_started(macula_peering_conn_sup:start_conn(Opts), Conn).
handle_started({ok, Pid}, Conn) ->
ok = macula_quic:controlling_process(Conn, Pid),
ok = gen_statem:cast(Pid, start_handshake),
{ok, Pid};
handle_started(Err, _Conn) ->
Err.
%% @doc Initiate a graceful close (sends GOODBYE, drains 5s, terminates).
-spec close(pid()) -> ok.
close(Pid) ->
close(Pid, operator_stop).
-spec close(pid(), atom()) -> ok.
close(Pid, Reason) ->
gen_statem:cast(Pid, {close, Reason}).
%% @doc Send a frame through the peer connection. Signs the frame with
%% the local identity if it isn't already signed. Fire-and-forget.
-spec send_frame(pid(), macula_frame:frame()) -> ok.
send_frame(Pid, Frame) when is_map(Frame) ->
gen_statem:cast(Pid, {send_frame, Frame}).
%% @doc Read the peer's capabilities bitmask as observed in their
%% CONNECT/HELLO frame. Returns `{ok, NegotiatedCaps}' once the
%% handshake has completed and `{error, not_connected}' otherwise.
%%
%% Used by relays to tell direct daemon ADVERTISEs from station-to-
%% station gossip relays at frame-dispatch time (see `?CAP_STATION').
%% Daemons send `0'; relay stations OR-in `?CAP_STATION'. Pre-version
%% peers that don't set the bit are treated as daemons by callers,
%% which matches their actual role.
-spec peer_capabilities(pid()) ->
{ok, non_neg_integer()} | {error, not_connected}.
peer_capabilities(Pid) when is_pid(Pid) ->
try gen_statem:call(Pid, peer_capabilities, 1_000) of
{ok, _Caps} = Ok -> Ok;
not_connected -> {error, not_connected}
catch _:_ -> {error, not_connected}
end.