README.md

# 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