Packages
macula
5.2.1
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/deliver_packet/macula_deliver_packet.erl
%%%-------------------------------------------------------------------
%%% @doc Ingress delivery for macula-net packets.
%%%
%%% Receives a CBOR-encoded macula-net envelope (typically from
%%% macula_net_transport_quic), decodes it via {@link macula_cbor_nif},
%%% validates against spec, and writes the inner IPv6 packet to the
%%% local TUN device when the destination is one of the locally-hosted
%%% addresses.
%%%
%%% Phase 1 simplification (PLAN_MACULA_NET.md §5.2): single-hop only.
%%% This station receives a packet whose `dst' is expected to be local;
%%% if not, we drop and log. Multi-hop forwarding (decrement TTL,
%%% re-route) lands in Phase 2 alongside the DHT.
%%%
%%% Decoupled from {@link macula_tun} via a TunWriter callback so this
%%% slice has no compile-time dependency on the kernel-side NIF.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_deliver_packet).
-export([
configure/1,
handle_envelope/1
]).
-export_type([
tun_writer/0,
config/0
]).
-type tun_writer() :: fun((IPv6Packet :: binary()) -> ok | {error, term()}).
-type config() :: #{
local_addresses := [<<_:128>>],
tun_writer := tun_writer()
}.
-define(TABLE, macula_deliver_packet_table).
-define(SUPPORTED_VSN, 1).
-define(LOCAL_ADDRS_KEY, local_addresses).
-define(TUN_WRITER_KEY, tun_writer).
%% =============================================================================
%% Public API
%% =============================================================================
%% @doc Install the local-addresses set + TUN writer callback.
%% Calling configure/1 again replaces the previous config.
-spec configure(config()) -> ok.
configure(#{local_addresses := Addrs, tun_writer := Writer})
when is_list(Addrs), is_function(Writer, 1) ->
ensure_table(),
true = ets:insert(?TABLE, {?LOCAL_ADDRS_KEY, sets:from_list(Addrs)}),
true = ets:insert(?TABLE, {?TUN_WRITER_KEY, Writer}),
ok.
%% @doc Handle a single inbound CBOR-encoded macula-net envelope.
%%
%% Returns:
%% <ul>
%% <li>`ok' — payload delivered to local TUN, OR a non-data control
%% message accepted but not handled (Phase 1 stub for ctrl/gossip)</li>
%% <li>`{error, version_unsupported}' — packet's `v' field unknown</li>
%% <li>`{error, decode_failed}' — CBOR malformed / missing fields</li>
%% <li>`{error, no_route}' — `dst' not in local-addresses set</li>
%% <li>`{error, not_configured}' — configure/1 not called yet</li>
%% <li>`{error, tun_write_failed}' — kernel write returned an error</li>
%% </ul>
-spec handle_envelope(binary()) ->
ok | {error, version_unsupported | decode_failed | no_route
| not_configured | tun_write_failed | term()}.
handle_envelope(Cbor) when is_binary(Cbor) ->
T0 = erlang:monotonic_time(microsecond),
Result = handle_with_config(lookup_config(), Cbor),
emit_ingress_telemetry(Result, T0),
Result.
emit_ingress_telemetry(ok, T0) ->
Latency = erlang:monotonic_time(microsecond) - T0,
telemetry:execute([macula, net, ingress, delivered],
#{latency_us => Latency}, #{kind => <<"data">>});
emit_ingress_telemetry({error, Reason}, _T0) ->
telemetry:execute([macula, net, ingress, dropped],
#{count => 1}, #{reason => reason_bin(Reason)}).
reason_bin(R) when is_atom(R) -> atom_to_binary(R, utf8);
reason_bin(R) when is_binary(R) -> R;
reason_bin(_) -> <<"unknown">>.
%% =============================================================================
%% Internals
%% =============================================================================
handle_with_config({error, _} = Err, _Cbor) ->
Err;
handle_with_config({ok, LocalAddrs, Writer}, Cbor) ->
handle_decoded(macula_cbor_nif:unpack(Cbor), LocalAddrs, Writer).
handle_decoded({error, _Reason}, _LocalAddrs, _Writer) ->
{error, decode_failed};
handle_decoded({ok, #{<<"v">> := V}}, _LocalAddrs, _Writer)
when V =/= ?SUPPORTED_VSN ->
{error, version_unsupported};
handle_decoded({ok, #{<<"type">> := <<"data">>,
<<"dst">> := Dst,
<<"payload">> := Payload}}, LocalAddrs, Writer) ->
deliver_local(sets:is_element(Dst, LocalAddrs), Payload, Writer);
handle_decoded({ok, #{<<"type">> := _Other}}, _LocalAddrs, _Writer) ->
%% ctrl/gossip handlers land in Phase 1.5+. Treat as
%% unhandled-but-not-error for now.
ok;
handle_decoded({ok, _Other}, _LocalAddrs, _Writer) ->
{error, decode_failed}.
deliver_local(false, _Payload, _Writer) ->
{error, no_route};
deliver_local(true, Payload, Writer) ->
deliver_via(Writer(Payload)).
deliver_via(ok) -> ok;
deliver_via({error, _}) -> {error, tun_write_failed}.
lookup_config() ->
case ets:info(?TABLE) of
undefined -> {error, not_configured};
_ -> lookup_config_entries(ets:lookup(?TABLE, ?LOCAL_ADDRS_KEY),
ets:lookup(?TABLE, ?TUN_WRITER_KEY))
end.
lookup_config_entries([{_, Addrs}], [{_, Writer}]) ->
{ok, Addrs, Writer};
lookup_config_entries(_, _) ->
{error, not_configured}.
ensure_table() ->
case ets:info(?TABLE) of
undefined ->
_ = ets:new(?TABLE, [named_table, public, set,
{read_concurrency, true}]),
ok;
_ ->
ok
end.