Elixir implementation of Reticulum protocol
Core roadmap phases are implemented through runtime shell, UDP transport, announce/path discovery, messaging API, and proof/receipt handling.
Runtime node tables (destinations, paths, packet-hash cache, interfaces, local destinations, and message handlers) are ETS-only and intentionally cold-start on every restart.
alias Reticulum.Destination
alias Reticulum.Identity
alias Reticulum.Node
{:ok, _pid} =
Node.start_link(
name: Reticulum.Node.Example,
storage_path: Path.join(System.tmp_dir!(), "reticulum-example"),
receipt_timeout_seconds: 3
)
{:ok, _iface} =
Node.start_udp_interface(Reticulum.Node.Example,
name: :udp,
listen_ip: {127, 0, 0, 1},
listen_port: 43_001,
default_peer_ip: {127, 0, 0, 1},
default_peer_port: 43_002
)
identity = Identity.new()
{:ok, destination} = Destination.new(:in, :single, "example", identity, ["inbox"])
:ok =
Node.register_local_announce_destination(Reticulum.Node.Example, destination, self(),
callback: fn event ->
IO.inspect({:inbound_payload, event.packet.data})
end
)Use Reticulum.Node.start_from_config/2 to start a node and its interfaces from TOML:
alias Reticulum.Node
{:ok, _pid} =
Node.start_from_config("config/reticulum.example.toml",
name: Reticulum.Node.ConfigExample
)The file format uses [node] and [interfaces.<name>] sections. See
config/reticulum.example.toml for a runnable reference.
[node] supports startup_mode = "cold" | "warm_restore".
coldruns cold-start lifecycle hooks.warm_restoreruns warm-restore lifecycle hooks.
[node] also supports use_implicit_proof = true | false.
truesends implicit proofs (signature only, reference-aligned default).falsesends explicit proofs (packet hash + signature).
[node] also supports ratchet_expiry_seconds.
- ratchet announcements are cached in memory and expire after this TTL.
- persistence is currently memory-only; disk persistence lands with general runtime persistence.
[node] also supports routing-core controls.
routing_max_hopscaps transit forwarding, announce rebroadcasts, and forwarded path requests.announce_forwarding = true | falseenables or disables rebroadcasting remote announces onto other interfaces.path_request_forwarding = true | falseenables or disables forwarding remote path requests onto other interfaces.path_request_timeout_secondscontrols how long locally-originated path requests stay pending before they are dropped.path_request_retry_count,path_request_retry_base_seconds, andpath_request_retry_backoff_factortune local path request retries.path_request_min_interval_secondssuppresses duplicate local requests on the same interface while one is still fresh.path_request_duplicate_ttl_secondssuppresses duplicate forwarded path requests that loop back through the topology.path_request_fanoutcaps how many healthy non-ingress interfaces a forwarded path request is rebroadcast onto.
[node] also supports interface-runtime defaults.
interface_queue_limitbounds per-interface outbound queue depth.interface_backpressure = "reject" | "drop_newest" | "drop_oldest"controls queue overflow behavior:rejectrefuses new frames with an error,drop_newestaccepts and silently drops them,drop_oldestevicts the oldest queued frame. Sends are accepted into the queue immediately, so a throttled interface never blocks callers.interface_rate_limit_bytes_per_secondandinterface_rate_limit_packets_per_secondbound sustained egress.interface_rate_limit_burst_bytesandinterface_rate_limit_burst_packetscontrol token-bucket burst capacity.
Route selection now prefers higher interface health scores first, then lower hop counts, then fresher path updates. Health scoring considers adapter status, queue pressure, and recent throttling/send failures.
[interfaces.<name>] supports adapter selection, optional IFAC auth settings, and per-interface runtime overrides.
-
type = "udp"uses a built-in adapter. -
module = "Reticulum.MyCustomInterface"loads a custom adapter module instead of a built-in alias. Custom adapters implement theReticulum.Interfacebehaviour; see its module docs for the contract. -
queue_limit,backpressure, andrate_limit_*override the node-level interface defaults for one interface. -
ifac_netnameand/orifac_netkeyderive the shared IFAC identity for that interface. -
ifac_size_bitscontrols truncated IFAC size in bits and must be a multiple of 8. -
auth-enabled interfaces require IFAC-authenticated inbound frames and transmit authenticated frames by default.
For imperative startup, built-ins still have helpers like Reticulum.Node.start_udp_interface/2, and custom adapters can be started with Reticulum.Node.start_interface/3.
For imperative startup, pass startup_lifecycle: YourModule to
Reticulum.Node.start_link/1. Lifecycle modules implement the
Reticulum.Node.StartupLifecycle callbacks.
Current runtime tables are still ETS-only, so warm restore currently behaves as a no-op restore hook contract (persistence lands in the next phase).
alias Reticulum.Node
destination_hash = <<0::128>>
public_key = :crypto.strong_rand_bytes(64)
:ok = Node.put_destination(Reticulum.Node.Example, destination_hash, public_key, nil)
{:ok, receipt_hash} =
Node.send_data(Reticulum.Node.Example, :udp, destination_hash, "hello", track_receipt: true)
case Node.receipt(Reticulum.Node.Example, receipt_hash) do
{:ok, receipt} -> IO.inspect(receipt.status)
:error -> :not_found
endInbound destinations only return proofs when destination.proof_strategy is set to
:all or :app.
:none(default) never sends a proof:allalways sends a proof:appcallsproof_requested_callback.(event)and sends a proof only ontrue
Reticulum.Node.send_data/5 encrypts payloads for destination: :single and destination: :group
when context is active data transport. To send unencrypted payloads, use destination: :plain.
When both interfaces share IFAC auth config, request authenticated transport with ifac: :auth.
:ok =
Node.send_data(Reticulum.Node.Example, :udp, destination_hash, "plain-payload",
destination: :plain
)
:ok =
Node.send_data(Reticulum.Node.Example, :udp, destination_hash, "auth-payload",
ifac: :auth
)Reticulum.Transport emits runtime hooks through Reticulum.Observability:
- telemetry event names start with
[:reticulum, ...] - proof events include
[:reticulum, :transport, :proof, :sent]and[:reticulum, :transport, :proof, :invalid] - receipt lifecycle events include
[:reticulum, :transport, :receipt, :tracked],[:reticulum, :transport, :receipt, :delivered], and[:reticulum, :transport, :receipt, :timed_out]
Attach telemetry handlers in your application to collect and export metrics.