# ExReticulum
Elixir NIF wrapper for [Reticulum-rs](https://github.com/BeechatNetworkSystemsLtd/Reticulum-rs), the Rust implementation of the [Reticulum](https://reticulum.network/) cryptographic mesh networking stack.
ExReticulum lets Elixir applications participate as full nodes in Reticulum networks — creating identities, announcing destinations, establishing encrypted links, and routing packets over any physical layer.
## Prerequisites
- **Elixir** >= 1.17
- **Rust** >= 1.70 (with `cargo`)
- **protoc** (Protocol Buffers compiler — required by the Reticulum-rs gRPC layer)
### Installing protoc
```bash
# macOS
brew install protobuf
# Ubuntu/Debian
apt install protobuf-compiler
# Arch
pacman -S protobuf
```
## Setup
ExReticulum's NIF crate pulls the `reticulum` Rust crate directly from the [Reticulum-rs GitHub repo](https://github.com/BeechatNetworkSystemsLtd/Reticulum-rs). No need to clone it separately.
```bash
git clone https://github.com/jtippett/ex_reticulum
cd ex_reticulum
# Fetch dependencies and compile (first build compiles the Rust NIF — ~60-90s)
mix deps.get
mix compile
```
## Quick Start
```elixir
# Generate a cryptographic identity
{:ok, identity} = ExReticulum.Identity.generate()
# Start a transport node
{:ok, transport} = ExReticulum.Transport.start_link(
identity: identity,
name: "my_node"
)
# Connect to the network
{:ok, :added} = ExReticulum.Transport.add_interface(
transport, :tcp_client, address: "127.0.0.1:4242"
)
# Create and announce a destination
{:ok, dest} = ExReticulum.Transport.add_destination(
transport, identity, "myapp", "service"
)
{:ok, :announced} = ExReticulum.Transport.announce(transport, dest)
```
## Core Concepts
### Identity
Ed25519/X25519 key pairs used for signing, verification, and key exchange.
```elixir
# Generate a random identity
{:ok, id} = ExReticulum.Identity.generate()
# Deterministic identity from a name (same name = same keys)
{:ok, id} = ExReticulum.Identity.from_name("my_node")
# Serialize/deserialize for persistence
{:ok, hex} = ExReticulum.Identity.to_hex(id)
{:ok, restored} = ExReticulum.Identity.from_hex(hex)
# Sign and verify data
{:ok, signature} = ExReticulum.Identity.sign(id, "hello")
{:ok, :verified} = ExReticulum.Identity.verify(id, "hello", signature)
```
All functions return `{:ok, result}` or `{:error, reason}`. Bang variants (`generate!`, `sign!`, etc.) are available and raise `ExReticulum.Error` on failure.
### Transport
The central GenServer managing network participation. Wraps the Rust Transport via NIF.
```elixir
{:ok, transport} = ExReticulum.Transport.start_link(
identity: identity,
name: "my_node",
broadcast: true, # forward packets (default: true)
retransmit: true, # retransmit announces (default: true)
reroute_eager: false, # eager path rerouting (default: false)
restart_outlinks: false, # restart failed outbound links (default: false)
announce_forever: false # keep retransmitting announces (default: false)
)
```
### Interfaces
Connect to the network over TCP or UDP. Note that `add_interface/3` returns `{:ok, :added}` once the interface worker is spawned — the actual bind/connect happens asynchronously and retries every 5 seconds on failure.
```elixir
# TCP server (listen for incoming connections)
{:ok, :added} = ExReticulum.Transport.add_interface(
transport, :tcp_server, address: "0.0.0.0:4242"
)
# TCP client (connect to a peer)
{:ok, :added} = ExReticulum.Transport.add_interface(
transport, :tcp_client, address: "192.168.1.10:4242"
)
# UDP (with optional forwarding address)
{:ok, :added} = ExReticulum.Transport.add_interface(
transport, :udp, bind_address: "0.0.0.0:5555", forward_address: "192.168.1.255:5555"
)
```
### Destinations and Announces
Destinations are addressable endpoints. Announce them so other nodes can discover and reach you.
```elixir
# Create a destination
{:ok, dest} = ExReticulum.Transport.add_destination(
transport, identity, "myapp", "chat"
)
# Announce with optional app data
{:ok, :announced} = ExReticulum.Transport.announce(transport, dest, "status: online")
# Check if a destination exists locally
{:ok, true} = ExReticulum.Transport.has_destination(transport, dest.address_hash)
```
### Subscribing to Events
Subscribe any process to receive network events as messages.
```elixir
# Subscribe to announces, link events, or data
{:ok, :subscribed} = ExReticulum.Transport.subscribe(transport, :announces)
{:ok, :subscribed} = ExReticulum.Transport.subscribe(transport, :link_events)
{:ok, :subscribed} = ExReticulum.Transport.subscribe(transport, :data)
# Events arrive as {:ex_reticulum, struct} messages
receive do
{:ex_reticulum, %ExReticulum.Announce{} = announce} ->
IO.puts("Discovered: #{Base.encode16(announce.address_hash)}")
IO.puts("App data: #{announce.app_data}")
{:ex_reticulum, %ExReticulum.LinkEvent{event: :activated, link_id: id}} ->
IO.puts("Link activated: #{Base.encode16(id)}")
{:ex_reticulum, %ExReticulum.LinkEvent{event: {:data, payload}}} ->
IO.puts("Received: #{payload}")
{:ex_reticulum, %ExReticulum.ReceivedData{data: data}} ->
IO.puts("Data: #{data}")
end
```
Subscriptions are automatically cleaned up when the subscribing process exits.
### Links
Bidirectional encrypted channels for sustained communication between two nodes.
```elixir
# After receiving an announce, create a link to that destination
{:ok, :subscribed} = ExReticulum.Transport.subscribe(transport, :announces)
{:ok, :subscribed} = ExReticulum.Transport.subscribe(transport, :link_events)
receive do
{:ex_reticulum, %ExReticulum.Announce{} = announce} ->
# Create a link using the announce data
{:ok, link} = ExReticulum.Transport.link(transport, %{
address_hash: announce.address_hash,
identity: announce.identity,
name_hash: announce.name_hash
})
# Wait for the link to activate (handshake completes)
receive do
{:ex_reticulum, %ExReticulum.LinkEvent{event: :activated}} ->
# Send data over the encrypted link
{:ok, :sent} = ExReticulum.Transport.link_send(transport, link, "hello!")
end
end
# Query link state
{:ok, :active} = ExReticulum.Link.status(link)
{:ok, rtt_ms} = ExReticulum.Link.rtt(link)
# Close when done
{:ok, :closed} = ExReticulum.Transport.link_close(transport, link)
```
## Supervision
Add transports to your application's supervision tree:
```elixir
defmodule MyApp.Application do
use Application
def start(_type, _args) do
{:ok, identity} = ExReticulum.Identity.from_name("my_app")
children = [
{ExReticulum.Transport,
identity: identity,
name: "my_app",
process_name: MyApp.Transport}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
```
Then reference it by registered name:
```elixir
ExReticulum.Transport.add_interface(MyApp.Transport, :tcp_client, address: "peer:4242")
```
## Architecture
ExReticulum uses [Rustler](https://github.com/rustler-project/rustler) to bridge Elixir and Rust:
- A single **Tokio runtime** handles all async Rust operations
- Rust objects (Transport, Destinations, Links) are held as opaque **ResourceArc** references
- Events flow from Rust to Elixir via **OwnedEnv::send()** — no polling
- Crypto operations run on BEAM **dirty CPU schedulers**; network I/O on **dirty I/O schedulers**
- All Mutex locks handle poisoning gracefully (return `{:error, :lock_error}`, never panic)
## Testing
```bash
mix test # Run all tests (including E2E)
mix test --exclude e2e # Skip end-to-end tests
```
The E2E tests use TCP loopback to verify two-node announce discovery and link data exchange.
## License
MIT