Reticulum-Go/docs/en/api-reference.md
2026-08-13 17:50:20 -05:00

436 lines
18 KiB
Markdown

# API reference
This is the application-facing API guide for Reticulum-Go. It is not a dump of every exported symbol. It is organized the way you build programs: choose an integration path, follow a recipe, then look up types and methods.
Wire behavior matches the [Python RNS API reference](https://reticulum.network/manual/reference.html). Package layout, concurrency rules, and embedder lifecycle are Go-specific and documented here because the Python manual does not cover them.
For generated signatures, use `go doc` on the import path or browse the module on pkg.go.dev. For package file maps, see [Package map](package-map.md).
## How this differs from the Python reference
| Python RNS manual | This document |
|-------------------|---------------|
| Class catalog (`RNS.Reticulum`, Identity, Destination, …) | Task-first recipes, then API tables |
| One process model (`RNS.Reticulum(...)`) | Four integration paths with trade-offs |
| Little concurrency guidance | Explicit callback and locking rules |
| No C / WASM / control-plane docs in the same place | Links to Control API, librns, WASM |
| Examples live elsewhere | Recipes point at `examples/` |
## Choose an integration path
```text
Need Reticulum in my app
|
v
Go process?
/ \
yes no
| |
v v
pkg/node Same machine as daemon?
in-process |
-------+-------
/ | \
yes C/FFI browser
| | |
v v v
Control API librns pkg/wasm
HTTP/WS .so
\ | /
\ | /
v v v
destination + link
```
| Path | Package / surface | Use when |
|------|-------------------|----------|
| In-process Go | `pkg/node` | Default for Go services and tools |
| Daemon + JSON | [Control API](control-api.md) | Python, Rust, scripts, multi-language hosts |
| In-process C | [librns](librns.md) | Native hosts that cannot embed Go source |
| In-process Odin | [librns](librns.md#odin-bindings) | `bindings/odin` over `librns.so` |
| Out-of-process Dart / Flutter | [Control API](control-api.md#dart-and-flutter) | `bindings/dart` (rns_control) |
| In-process Dart / Flutter FFI | [librns Dart FFI](librns.md#dart-ffi-bindings) | `bindings/dart` (`ffi.dart`), Linux / Android / Windows |
| Out-of-process any language | [Control API](control-api.md) | HTTP and WebSocket |
| Browser | `pkg/wasm` | WebSocket gateway clients |
Most of this page describes the **`pkg/node` happy path**. Other paths expose the same concepts with different bindings.
## Mental model
1. **Config** loads interfaces and storage paths (`pkg/reticulumconfig`, `pkg/common`).
2. **Node** starts transport, interfaces, and optional shared instance (`pkg/node`).
3. **Identity** holds X25519 + Ed25519 keys (`pkg/identity`).
4. **Destination** is an app endpoint named `app.aspect…` (`pkg/destination`).
5. **Announce** publishes reachability. Peers learn paths.
6. **Path** is a cached route (`Transport.HasPath` / RequestPath).
7. **Link** is an encrypted session to a destination (`pkg/link`).
8. **Request / resource** move structured replies and large payloads (`Link.Request`, `pkg/resource`).
Packet MTU remains **500 bytes** on the wire (`pkg/packet.MTU`), same as Python.
## Quick start recipe (Go)
```go
package main
import (
"log"
"os"
"os/signal"
"syscall"
"quad4/reticulum-go/pkg/destination"
"quad4/reticulum-go/pkg/identity"
"quad4/reticulum-go/pkg/node"
"quad4/reticulum-go/pkg/reticulumconfig"
)
const appName = "example_utilities"
func main() {
cfg, err := reticulumconfig.InitConfig()
if err != nil {
log.Fatal(err)
}
identity.InitKnownDestinationsPersistence(cfg.ConfigPath, cfg.InMemoryKnownDestinations)
n, err := node.New(cfg)
if err != nil {
log.Fatal(err)
}
if err := n.Start(); err != nil {
log.Fatal(err)
}
defer n.Stop()
id, err := identity.New()
if err != nil {
log.Fatal(err)
}
dest, err := destination.New(id, destination.In|destination.Out, destination.Single,
appName, n.Transport(), "minimal")
if err != nil {
log.Fatal(err)
}
if err := dest.Announce(false, nil, nil); err != nil {
log.Fatal(err)
}
log.Printf("listening on %x", dest.GetHash())
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
}
```
For a guide on complete runnable examples, see [Examples](examples.md).
## Recipe: inbound link and request handler
```go
dest.AcceptsLinks(true)
dest.SetLinkEstablishedCallback(func(v any) {
l := v.(*link.Link) // import pkg/link
_ = l.SetResourceStrategy(link.AcceptAll)
l.SetPacketCallback(func(data []byte, _ *packet.Packet) {
log.Printf("data: %q", data)
})
})
_ = dest.RegisterRequestHandler("/echo",
func(_ string, data []byte, _ []byte, _ []byte, _ *identity.Identity, _ int64) []byte {
return data
},
destination.AllowAll, nil)
```
## Recipe: outbound link and request
```go
remoteID, err := identity.Recall(peerDestHash)
if err != nil {
log.Fatal(err)
}
out, err := destination.FromHash(peerDestHash, remoteID, destination.Single, n.Transport())
if err != nil {
log.Fatal(err)
}
if !n.Transport().HasPath(peerDestHash) {
_ = n.Transport().RequestPath(peerDestHash, "", nil, false)
ctx, cancel := context.WithTimeout(context.Background(), rnsutil.PathResponseWindow(n.Transport(), peerDestHash))
defer cancel()
if err := rnsutil.WaitPath(ctx, n.Transport(), peerDestHash); err != nil {
log.Fatal(err)
}
}
l := link.NewLink(out, n.Transport(), nil, nil, nil)
if err := l.Establish(); err != nil {
log.Fatal(err)
}
receipt, err := l.Request("/echo", []byte("ping"), 15*time.Second)
if err != nil {
log.Fatal(err)
}
// poll receipt.Concluded() or set receipt.SetResponseCallback
```
## Recipe: send a file resource
```go
res, err := resource.New(fileBytes, true)
if err != nil {
log.Fatal(err)
}
_ = res.SetMetadata(map[string]any{"name": []byte("report.bin")})
if err := l.SendResource(res); err != nil {
log.Fatal(err)
}
```
On the receiver, set AcceptAll or AcceptApp and handle `link.IncomingResource` (or plain `[]byte` when no metadata). CLI equivalent: rgocp in [CLI utilities](utilities.md).
## Recipe: network sleep and wake
```go
n.SetPauseMode(node.PauseModeDisable)
_ = n.OnNetworkLost() // pause links, disable interfaces
_ = n.OnNetworkAvailable()
_ = n.RefreshPaths() // re-request watched destinations
```
Optional: `n.EnableLinkAutoReconnect(node.LinkReconnectOptions{MaxAttempts: 5, Backoff: time.Second})` and `n.RegisterLink(l)`.
## Core types
### Node (`pkg/node`)
Orchestrates transport, interfaces, shared instance, and lifecycle. Prefer this over constructing `transport.Transport` by hand.
| Symbol | Role |
|--------|------|
| `New(cfg) (*Node, error)` | Build without starting |
| `Start() error` | Transport, path handler, shared instance, interfaces |
| `Stop() error` | Tear down in reverse order |
| `Transport() *transport.Transport` | Pass to destinations and links |
| `Config() *common.ReticulumConfig` | Active config |
| `Interfaces() []interfaces.Interface` | Configured interfaces |
| `OnNetworkAvailable() error` | Resume after outage |
| `OnNetworkLost() error` | Pause for sleep / NIC down |
| `SetPauseMode(PauseMode)` | PauseModeDisable or PauseModeStop |
| `WatchDestination(hash)` | Include hash in wake refreshes |
| `RefreshPaths(dests...)` | Force path refresh |
| `ReloadInterfaces(newCfg)` | Hot-reload interface blocks |
| `EnableLinkAutoReconnect(opts)` | Re-establish registered links |
| `RegisterLink(l)` | Track link for reconnect |
| `StartInterfaceDiscovery()` | rnstransport discovery listen + InterfaceAnnouncer when discoverable |
### Identity (`pkg/identity`)
| Symbol | Role |
|--------|------|
| `New() (*Identity, error)` | Generate software identity (preferred) |
| `NewIdentity()` | Alternate generator |
| FromFile / ToFile | Persist via identity_backend (file, Secret Service, or Linux kernel keyring + RSSI marker) |
| FromBytes / FromPublicKey | Load from bytes |
| `LoadIdentityFile(path, signer)` | Software or RHB1 hardware-bound (also resolves RSSI markers) |
| `NewIdentityWithSigner(...)` | External Ed25519 signer (HSM) |
| SetIdentityBackend / ApplyIdentityBackendFromConfig | Select file or secretservice |
| Close / Wipe | Zero locked private key buffers |
| `Hash() []byte` | 16-byte truncated hash |
| `GetPublicKey() []byte` | 64-byte combined public key |
| Sign / Verify | Ed25519 |
| Encrypt / Decrypt | Identity tokens with optional ratchets |
| RememberRatchet / GetRatchet / CurrentRatchetID | Announced peer ratchet public keys |
| `Recall(destHash)` | Public identity from known destinations |
| Remember / ValidateAnnounce | Announce storage |
| LoadOrCreateTransportIdentity | Daemon transport identity |
| RotateRatchet / GetRatchets / GetCurrentRatchetKey | Explicit identity-level keys only. Does not auto-generate. On-wire SINGLE ratchets use Destination.EnableRatchets |
Constants: KeySize (bits), TruncatedHashLength (bits). Hex destination or identity hashes are **32 characters**.
Private key material uses `pkg/securemem` (best-effort mlock, wipe on Close). See [Identity and destinations](identity-and-destinations.md).
### Destination (`pkg/destination`)
| Constant | Meaning |
|----------|---------|
| In / Out | Direction bit flags (`In\|Out` for both) |
| Single / Group / Plain | Destination types |
| ProveNone / ProveAll / ProveApp | Proof strategy |
| AllowNone / AllowAll / AllowList | Request handler ACL |
| Symbol | Role |
|--------|------|
| `New(id, direction, type, app, transport, aspects...)` | Create and optionally auto-register (In) |
| `FromHash(hash, id, type, transport)` | Outbound destination for a known peer |
| `Hash(id, app, aspects...)` | Compute destination hash |
| ParseName / ExpandAppName | Dotted name helpers |
| `Announce(pathResponse, tag, iface)` | Publish reachability |
| `AcceptsLinks(bool)` | Accept link requests |
| Encrypt / Decrypt / Sign | Destination crypto |
| CreateKeys / LoadPrivateKey / GetPrivateKey | GROUP Token PSK (64-byte AES-256 default) |
| SetPacketCallback | Single-packet inbound data |
| SetLinkEstablishedCallback | Inbound link ready (`func(any)`) |
| RegisterRequestHandler / RegisterRequestHandlerAny | Link request paths |
| EnableRatchets(path) | Enable SINGLE ratchets and persist private keys at path |
| EnableRatchetsInMemory | Same, RAM only (no ratchet file) |
| EnforceRatchets | Reject identity-key ciphertext (opt-in, same as Python) |
| SetRetainedRatchets / SetRatchetInterval | Retention count and rotation interval |
| RotateRatchets / CurrentRatchetPublic / LatestRatchetID / RatchetsEnabled | Local rotation and announce public key |
### Link (`pkg/link`)
| Status | Value | Meaning on Link |
|--------|-------|-------------------|
| StatusPending | `0x00` | Not established |
| StatusHandshake | `0x01` | Handshake |
| StatusActive | `0x02` | Ready |
| StatusStale | `0x03` | Stale |
| StatusClosed | `0x04` | Closed |
| StatusFailed | `0x05` | Failed |
| Symbol | Role |
|--------|------|
| `NewLink(dest, transport, iface, onEst, onClose)` | Outbound link object |
| `Establish() error` | Initiator handshake |
| `EstablishmentTimeout()` | Handshake wait used by the link watchdog |
| `Teardown()` | Close |
| `Identify(id)` | Prove local identity to peer |
| Send / SendPacket / SendPacketWithContext | Encrypted data |
| `Request(path, data, timeout)` | Msgpack request (auto resource if large) |
| `SendResource(res)` | Outbound resource transfer |
| `GetChannel()` | Reliable channel over the link |
| SetResourceStrategy | AcceptNone / AcceptAll / AcceptApp |
| SetResourceConcludedCallback | `[]byte` or IncomingResource |
| GetRTT / idle timers / PHY stats | Link health |
#### RequestReceipt
| Method | Role |
|--------|------|
| `Concluded()` | Finished (success or failure) |
| `GetStatus()` | **StatusActive means response OK**, StatusFailed means timeout or error |
| `GetResponse()` / `GetResponseValue()` | Bytes or decoded msgpack |
| `GetMetadata()` | Resource response metadata |
| `Progress()` | Bytes received / total for resource replies |
| SetResponseCallback / SetFailedCallback | Async completion |
Do not confuse `RequestReceipt.GetStatus()` with `Link.GetStatus()`. Both reuse status byte constants with different meanings.
### Resource (`pkg/resource`)
| Symbol | Role |
|--------|------|
| `New(data, autoCompress)` | `[]byte` or seekable file |
| `SetMetadata(map)` | Prepended msgpack metadata (Python-compatible) |
| GetProgress / GetStatus / GetHash | Transfer state |
| PrepareOutboundForLink | Called by `Link.SendResource` |
Statuses: StatusPending, StatusActive, StatusComplete, StatusFailed, StatusCancelled.
### Transport (via `Node.Transport()`)
| Method | Role |
|--------|------|
| `HasPath(hash)` | Cached route present |
| `RequestPath(hash, iface, tag, recursive)` | Path request (throttled) |
| HopsTo / NextHop / NextHopInterface | Route inspection |
| `FirstHopTimeout(hash)` | Next-hop airtime plus 6s (Python `get_first_hop_timeout`) |
| `PathResponseWindow(hash)` | Cold path wait from slowest online bitrate |
| `SlowestOnlineBitrate()` | Lowest advertised bitrate of an online interface |
| ExpirePath / PrepareFreshPathRequest | Drop or refresh cache |
| RegisterInterface / GetInterfaces | Interface table |
| RegisterDestination | Usually automatic for In destinations |
| SendPacket / HandlePacket | Low-level inject (advanced) |
| RegisterAnnounceHandler | Observe announces |
Avoid `transport.Destination` and `transport.Link` placeholder types. Use `destination.Destination` and `link.Link`.
### Packet (`pkg/packet`)
| Symbol | Role |
|--------|------|
| `MTU` | 500 |
| NewPacket / Pack / Unpack | Wire encode/decode |
| PacketReceipt | Delivery proofs for data packets |
| Context constants | ContextRequest, ContextResource, link contexts, … |
### Config (`pkg/reticulumconfig`, `pkg/common`)
| Function | Role |
|----------|------|
| `InitConfig()` | Load or create `~/.reticulum-go/config` |
| `LoadConfig(path)` | Parse INI (unknown keys ignored) |
| SaveConfig / DefaultConfig / CreateDefaultConfig | Persist defaults |
Important ReticulumConfig fields: EnableTransport, ShareInstance, SharedInstanceType, ports, RPCKey, Interfaces, EnableControlAPI, InMemoryPathTable, InMemoryStorage, WatchInterfaces, DiscoverInterfaces, BackboneIO.
Default config directory is **`~/.reticulum-go`**, not `~/.reticulum`.
## Python to Go map
| Python | Go |
|--------|-----|
| `RNS.Reticulum(configdir=...)` | `reticulumconfig.LoadConfig` + `node.New` + Start |
| `RNS.Identity()` | `identity.New()` |
| `Identity.from_file` / to_file | FromFile / ToFile |
| `Identity.recall(hash)` | `identity.Recall(hash)` |
| `Destination(identity, IN, SINGLE, app, *aspects)` | `destination.New(id, destination.In, destination.Single, app, tr, aspects...)` |
| `Destination(..., OUT, ...)` | `destination.Out` or FromHash for known peers |
| `destination.announce()` | `dest.Announce(false, nil, nil)` |
| `destination.set_link_established_callback` | SetLinkEstablishedCallback (`func(any)`) |
| `destination.register_request_handler` | RegisterRequestHandler / RegisterRequestHandlerAny |
| `RNS.Link(destination)` | `link.NewLink` + Establish |
| `link.establishment_timeout` | `l.EstablishmentTimeout()` |
| `link.identify(identity)` | `l.Identify(id)` |
| `link.request(path, data=...)` | `l.Request(path, data, timeout)` |
| `RNS.Resource(data, link, metadata=...)` | `resource.New` + SetMetadata + `l.SendResource` |
| `RNS.Transport.has_path` / request_path | `tr.HasPath` / `tr.RequestPath` |
| `RNS.Reticulum.get_first_hop_timeout` | `tr.FirstHopTimeout` (use `rnsutil.FirstHopTimeout` when attached to a shared instance) |
| Shared instance master | First `share_instance = yes` process (daemon or Node) |
| `~/.reticulum` | `~/.reticulum-go` |
## Concurrency and callbacks
| Component | Rule |
|-----------|------|
| Transport / interfaces | Packet handlers run on interface or transport goroutines |
| Destination / link callbacks | May fire concurrently. Return quickly. Do heavy work in your own goroutine |
| `Link.Request` receipts | Timeout and response callbacks run in separate goroutines |
| Same Link | Do not call Establish, Teardown, and Request concurrently without external locking |
| `Node.ReloadInterfaces` / network hooks | Serialized by an internal mutex |
| Identities / destinations | Internally mutex-protected. Still treat callbacks as re-entrant |
Python RNS is largely single-threaded asyncio. Go is multi-threaded by default. Design for that.
## Errors and empty results
| Situation | Typical signal |
|-----------|----------------|
| No path yet | HasPath false. Call RequestPath and wait |
| Link not ready | Establish error or `GetStatus() != StatusActive` |
| Request timeout | RequestReceipt status StatusFailed |
| Recall before announce | `identity.Recall` error. Wait for announce or seed known destinations |
| Shared instance auth failure | RPC dial / auth error. Align rpc_key or transport identity |
| Hardware-bound identity without signer | ErrHardwareBoundSignerRequired |
## Other API surfaces
| Surface | Document |
|---------|----------|
| Localhost JSON and WebSocket | [Control API](control-api.md) |
| C ABI (`include/rns.h`) | [librns](librns.md) |
| Odin bindings (`bindings/odin`) | [librns](librns.md#odin-bindings) |
| Dart FFI and Control API (`bindings/dart`) | [librns](librns.md#dart-ffi-bindings), [Control API](control-api.md#dart-and-flutter) |
| Browser JS bridge | [Embedding and WebAssembly](embedding-and-wasm.md) |
| CLI tools | [CLI utilities](utilities.md) |
| Crypto details | [Cryptography](cryptography.md) |
| Interface types | [Interfaces](interfaces.md) |
## Related documents
- [Examples](examples.md)
- [Package map](package-map.md)
- [Embedding and WebAssembly](embedding-and-wasm.md)
- [Compatibility](compatibility.md)
- [Python RNS API reference](https://reticulum.network/manual/reference.html) (wire and semantic authority)