Reticulum-Go/docs/en/development-and-testing.md
2026-08-29 20:32:40 -05:00

24 KiB

Development and testing

See CONTRIBUTING.md for commit format, git hooks, pull request checklist, and CI overview.

Development environment

Requirements:

  • Go 1.26.6 or later (see scripts/ci/dev-tools.env and mise.toml)
  • Make and/or Task (either works for common workflows)
  • revive and staticcheck (make bootstrap or task bootstrap)
  • shellcheck and yamllint for git hooks (optional system packages)
  • Python 3 for crossref vector generation (optional)

Clone the repository. Dependencies are in vendor/. No network fetch is needed for ordinary builds.

Setup (Make, Task, or both):

make bootstrap          # or: task bootstrap
make doctor             # or: task doctor
make hooks-install      # or: task hooks:install

On some Linux distributions the Task binary is named go-task. Add alias task='go-task' if needed. Run task --list or make help for available targets.

Optional: mise (mise install) or the Dev Container (.devcontainer/).

Build automation reference

Makefile and Taskfile both set GOFLAGS=-mod=vendor, GOPROXY=off, and GOSUMDB=off. Prefer Make, Task, or plain go as you like. Equivalents for the common targets:

Make Task Manual / notes
make / make all / make build task build mkdir -p bin && CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/reticulum-go ./cmd/reticulum-go
make install task install Installs binary, legacy tool symlinks, and man pages under PREFIX (supports DESTDIR)
make install-man Man pages only
make install-service INIT=auto|systemd|openrc|runit|dinit|all. Sample config under /var/lib/reticulum-go
make uninstall Removes installed binary, symlinks, and man pages
make package-deb / package-rpm / package-arch nfpm packages into dist/
make clean task clean go clean and remove build artifacts
make test task test go test -v ./... (project wrapper may use testsummary)
make test-short task test-short go test -short -v ./...
make test-race CGO_ENABLED=1 go test -race -v ./...
make test-services scripts/ci/test-services-docker.sh
make test-install-script scripts/ci/test-install.sh
make test-self-check scripts/ci/run-self-check.sh
make test-self-check-{386,arm,riscv64,ppc64le,ppc64} qemu-user self-check (qemu-user-static)
make coverage coverage profile and HTML report
make bench task bench go test -run=^$ -bench=. -benchmem ./...
make fmt task fmt go fmt ./...
make vet task vet go vet ./...
make lint task lint revive with revive.toml
make staticcheck task staticcheck staticcheck on core packages
make vulncheck task vulncheck govulncheck
make check task check fmt/vet/lint/staticcheck/short tests/vulncheck (gosec via Make)
make prepush task prepush fmt-check, vet, lint, test-short
make deps task deps module download/verify (clears offline proxy for the fetch)
make run task run go run ./cmd/reticulum-go
make debug go build -o bin/reticulum-go ./cmd/reticulum-go
make build-linux / build-windows / build-darwin / build-all matching task build-* Cross-compile release targets
make build-windows-legacy task build-windows-legacy go-legacy-win7 (GO_LEGACY_WIN7)
make build-windows-xp task build-windows-xp go-legacy-winxp (GO_LEGACY_WINXP)
make build-librns task build-librns CGO_ENABLED=1 go build -buildmode=c-shared -o bin/librns.so ./cmd/librns
make test-wasm task test-wasm js/wasm package tests (Node exec helper)
task build-wasm GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o bin/reticulum-go.wasm ./cmd/reticulum-wasm
make microvm-up / microvm-stop matching microvm tasks Firecracker guest. See microvm
make tree-rsm-verify / tree-rsm-sign matching tasks Source tree .rsm inventory. See SECURITY.md
make hooks-install task hooks:install Tracked git hooks
make doctor / bootstrap / changelog-preview matching tasks Dev tool pins and CHANGELOG preview

Code quality commands

make fmt && make vet && make lint && make check
# or
task fmt
task vet
task lint
task staticcheck
task test-short
task vulncheck
task prepush      # fast path before git push
task check        # full local check suite
task ci           # same static checks as CI lint job

make check runs fmt, vet, lint, staticcheck, test-short, vulncheck, and gosec.

Linting

revive with project config:

revive -config revive.toml -formatter friendly ./pkg/* ./cmd/* ./internal/*

staticcheck:

staticcheck -tests=false ./pkg/... ./cmd/... ./internal/... ./tests/...

Changelog preview

Preview unreleased notes from conventional commits:

task changelog-preview

Uses cliff.toml and git-cliff (installed via go run when not on PATH).

Race detector

make test-race

Transport and interface packages include dedicated race and deadlock regression tests (interface_stress_race_test.go, pipe_race_test.go, pipe_deadlock_test.go).

Coverage

make coverage

Opens HTML coverage from coverage.out.

Benchmarks

make bench
task test-bench-gate

Loopback link throughput smoke (RNS Speedtest-style liveness floor):

task test-link-speed
reticulum-go speedtest

Nightly sim-heavy also runs test-link-speed.

Project layout for contributors

Path Purpose
pkg/ Public library code
cmd/ Binaries and e2e tests
internal/ Daemon-only code
tests/crossref/ Python vector parity
tests/interop/ Live Go/Python tests
scripts/ci/ CI install and release scripts
.github/workflows/ GitHub Actions

Follow existing naming, error wrapping, and SPDX headers in each file.

Testing layers

Layer How to run What it covers
Unit task test / task test-short Package *_test.go
Property task test-property *_pbt_test.go and embedded quad4/pbt / testing/quick
Mutation task test-mutation gomutant on cryptography, packet, announce, destination, identity, ifac, backbone, interfaces
Chaos task test-chaos / task test-soak / task test-soak-protect TestSimChaos* / TestLinkChaos* / TestIfaceChaos* plus soak, including dos_protection flood soak
Oracle task test-oracle Crossref vectors, health TransportOracle deltas, adversarial corpus
Smoke task test-binary-smoke, binding smokes Binary --version/--help, CLI dump via Main, librns smoke examples
Acceptance task test-acceptance librns SCAFFOLD minimum, control API acceptance
E2E task test-e2e Daemon reload, UDP path e2e, transport TestE2E_*
Black box task test-blackbox CLI Main / rgodump surface, control API HTTP acceptance
Interop RUN_LIVE_INTEROP=1 go test ./tests/interop/... Live Go↔Python (optional locally), including dos_protection live suites

Unit tests

Standard go test in each package. Run all:

go test -v ./...

Short mode skips long tests:

go test -short -v ./...

Property-based tests

Files named *_pbt_test.go use quad4/pbt for generative testing (cryptography, packet, buffer, rate, resource, announce, link, identity). Some properties still live beside unit tests in the same package.

task test-property

Mutation tests

In-repo tools/gomutant flips same-width operators and re-runs package tests.

task test-mutation

Default packages: cryptography, packet, announce, destination, identity, ifac, backbone, interfaces. Override with MUTATION_PACKAGES / MUTATION_THRESHOLD.

Fuzz tests

Files named *_fuzz_test.go cover packet, link, ifac, blackhole, discovery, health counters (pkg/health), pipe HDLC framing (pkg/interfaces/pipe_fuzz_test.go), and librns (pkg/librns).

Chaos and fault injection

Seeded loss, reorder, corruption, and flap tests across layers:

Prefix Package Focus
TestSimChaos* pkg/transport Multi-node path and announce under delay/loss/corrupt/reorder/flap (health oracle deltas on corrupt)
TestLinkChaos* pkg/link Establish, packet under loss/reorder, resource under capped drop, mid-session flap, goroutine budget
TestIfaceChaos* pkg/interfaces TCP HDLC corrupt/reorder, Local corrupt resync, Pipe respawn, Backbone reconnect
task test-chaos
task test-soak
task test-soak-protect

Chaos suites are in-process Go only (sim pipes / HDLC fixtures). Live Go↔Python HDLC loss, reorder, corrupt, and mid-session flap live under tests/interop/ (TestLiveInteropHDLC*, RUN_LIVE_INTEROP=1). Unpack hop-gate, HT2 truncation, oversize, and Pack/Unpack byte-identity vs Python RNS.Packet live in unpack_live_test.go. Healthy-path cross-stack coverage also lives under tests/interop/.

dos_protection tests

pkg/protect and interface hooks cover false positives, false negatives, auto learn/persist/relearn, and live sockets.

Suite Location Focus
Unit / FP-FN pkg/protect/false_positive_test.go Quiet traffic, bursty legit, multi-iface isolation, oscillation, poisoned warmup, drift without false block
Snapshot / bitrate / priority pkg/protect/snapshot_test.go, bitrate_test.go, packetclass.go Status snapshot, scaled floors, prefer-keep shedding
Harm / FP oracles pkg/protect/harm_oracle_test.go Quiet mesh, slow radio, bulk link after quiet learn, path requests during announce floods, massive discovery bursts, peer isolation vs resource streams, auto-learning flood contract
Transport handler shed pkg/transport/protect_test.go Semaphore-full always sheds in detect, prevent, and auto learning
Slow findings pkg/rnsutil/slow_protect_test.go dos_armed_trips and cool-down findings from RPC protect block
Sandbox soft-fail pkg/sandbox/warn_test.go Rate-limited stdout when Landlock or seccomp soft-fails
Replay traces pkg/protect/replay_trace_test.go Mesh-like timelines, flood inject and recovery, auto learn then block
Property / fuzz pkg/protect/property_test.go, fuzz_test.go Detect never blocks, prevent after threshold, mode round-trip
Soak task test-soak-protect Bounded flood heap and goroutine budgets
Live UDP/TCP pkg/interfaces/protect_*_live_test.go Real loopback sockets, optional non-loopback NIC, TCP accept storms
Live interop tests/interop/dos_protect_live_test.go RUN_LIVE_INTEROP=1: quiet budget, UDP flood shed, auto learn on live UDP, transport path, public mesh peer dials
go test ./pkg/protect -short
go test ./pkg/interfaces -run 'LiveUDP|LiveTCP|Protect'
task test-soak-protect
RUN_LIVE_INTEROP=1 go test ./tests/interop -run 'DoSProtect|DoSProtection'

FreeBSD, OpenBSD, and Haiku CI jobs run go test -short ./pkg/protect/ and transport protect tests in addition to pkg/sandbox and self-check. The test-extra soak matrix runs task test-soak-protect.

Config and threat scope: Configuration, Security.

Test oracles

Oracle Location
Python crossref vectors tests/crossref/
Health counter deltas pkg/health.TransportOracle / OracleSnapshot.Delta
Handshake / adversarial frames pkg/packet/testdata/
IFAC goldens pkg/ifac unit tests
Sim path/hop asserts pkg/transport/sim_assertions_test.go
task test-oracle

Smoke, acceptance, e2e, black box

task test-binary-smoke
task test-acceptance
task test-e2e
task test-blackbox

Notable additions: pkg/cli dump smoke via Main, pkg/librns.TestAcceptanceScaffoldMinimum, pkg/node.TestUDPPathE2E.

Crossref tests

Location: tests/crossref/

Purpose: byte-level parity with Python reference output.

Workflow:

./tests/crossref/run_crossref.sh generate   # requires Python reference
./tests/crossref/run_crossref.sh test
./tests/crossref/run_crossref.sh all

Vectors are JSON with format version 5 (generate_vectors.py). The reference tree is fetched via rngit in run_crossref.sh.

Coverage includes identity, HKDF, HMAC, packet wire, announces, encryption, links, resources, channel envelopes, buffers, path requests.

Handshake decode trees for porters also live in pkg/packet/testdata/handshake_vectors.json (see packet-debug.md).

Packet debug tools

  • Wireshark Lua dissector: tools/wireshark/rns.lua
  • reticulum-go dump / rgodump for hex and pcap
  • reticulum-go snapshot / rgosnap for path and health JSON
  • Timeline convention: interop-timeline.md
go test ./pkg/packet/ -run 'TestHandshakeVector|TestTshark|TestDecode|TestPCAP'
go test ./pkg/health/ -run TestDrop
go test ./pkg/cli/ -run TestRunDump

Interop tests

Location: tests/interop/

Live tests pair a Go process with Python helpers under tests/interop/py/. HDLC burst (TestLiveInteropHDLCBurst*) and Unpack hop-gate (TestLiveInteropUnpackOracleMatchesPython) cover stream coalescing and refuse-invalid parity.

Enable:

RUN_LIVE_INTEROP=1 go test -v ./tests/interop/...

Optional Python interpreter (prefer a venv or pipx install with rns==1.5.2):

sh scripts/ci/setup-venv-pip.sh 'rns==1.5.2'
PYTHON_INTEROP=.venv/bin/python RUN_LIVE_INTEROP=1 go test -v ./tests/interop/...

With pipx (pipx install rns), the interop harness and self-check also auto-detect ~/.local/share/pipx/venvs/rns/bin/python when PYTHON_INTEROP is unset.

Debug harness

NomadNet relay, pageserver, and shared helpers use tests/interop/harness/.

Variable Behavior
INTEROP_ARTIFACTS=1 Always keep artifact dirs (default: only when the test fails)
INTEROP_EVENTS=1 Force event logging (also on when artifacts are enabled)
INTEROP_ARTIFACT_ROOT Parent directory for durable artifact folders

On failure (or with INTEROP_ARTIFACTS=1) the test logs the artifact path and the last events. Typical files:

File Contents
events.jsonl One JSON object per line (ts, src, event, kind, detail)
stderr.txt Captured Python stderr (human RNS logs plus INTEROP_EVENT lines)
env.json Selected interop env keys

Python peers emit INTEROP_EVENT {...} on stderr via tests/interop/py/interop_events.py. Stdout tokens such as READY and REQUEST_OK are unchanged.

Example:

INTEROP_ARTIFACTS=1 INTEROP_ARTIFACT_ROOT=/tmp/rns-interop \
  RUN_LIVE_INTEROP=1 go test -v ./tests/interop/ -run 'TestLiveNomadNetLinkThroughGoRelay|TestLiveInteropPythonPageServerLargePageRequest'
Test file Topic
link_live_test.go Link sessions, resources, binary burst echo, Go→Python request
channel_buffer_live_test.go Channel messages and buffer streams
rncp_blackhole_live_test.go rncp file transfer and blackhole LINKIDENTIFY
auto_live_test.go AutoInterface
ifac_live_test.go IFAC
transport_path_live_test.go Path requests
transport_relay_live_test.go Transport relay
backbone_live_test.go Backbone
quic_live_test.go QUIC Go-Go echo (no Python peer)
pipe_live_test.go PipeInterface with Python echo
modem73_live_test.go Modem73Interface (fake KISS/control, optional modem73 binary audio loop via MODEM73_BIN + pactl)
sdr_live_test.go SDRInterface mock exchange (RUN_LIVE_SDR=1)
shared_rpc_live_test.go Shared-instance RPC (TCP and Unix on Linux)
pageserver_live_test.go Pageserver example
nomadnet_crawl_live_test.go Nomadnet crawl
nomadnet_relay_live_test.go NomadNet through Go mesh relay
path_cp_live_test.go path and rgocp utilities
i2p_live_test.go I2PInterface Go and Python (needs SAM)
directory_outgoing_live_test.go Public directory clearnet TCP peers

Package-specific live tests

Package Env var
pkg/i2p, pkg/interfaces I2P RUN_LIVE_I2P=1 (optional I2P_SAM_ADDRESS, I2P_DIRECTORY_URL)
pkg/blackhole RUN_PY_INTEROP=1
pkg/discovery RUN_PY_INTEROP=1
pkg/lxstamper RUN_PY_INTEROP=1 (LXStamper)

End-to-end daemon tests

cmd/reticulum-go/ contains controlapi_e2e_test.go, reload_e2e_test.go, and related tests.

Host self-check

reticulum-go self-check is a host OS preflight. It validates that platform features work on the machine under test (crypto, identity file backend, sandbox, securemem, loopback interfaces, daemon with sandbox, shared-instance RPC, and on Unix a SIGHUP config/interface reload).

make test-self-check
# or
task test-self-check
# or
./bin/reticulum-go self-check --json --full

Flags:

Flag Behavior
--json Machine-readable report
--quick Core and platform only (no loopback or daemon)
--full Also probe QUIC, HTTPS, VSOCK, Pipe, and Serial
--interop Optional external tools (crossref vectors, Python RNS, binding CLIs)
--strict Treat warnings as failures
--binary PATH Binary used for CLI and daemon checks

Environment:

Variable Behavior
RETICULUM_SELF_CHECK=1 Used by CI wrappers that invoke the same checklist
RETICULUM_SELF_CHECK_INTEROP=1 Enables the interop tier
RETICULUM_TEST_KEYRING=1 Require Linux keyring round-trip (fail if unavailable)

Exit code is non-zero on any fail result. With --strict, warnings also fail.

Daemon checks include Control API health with sandbox enabled, shared-instance GetInterfaceStats RPC, and (except Windows and FreeBSD CapEnter) SIGHUP reload of a UDP interface.

CI runs self-check on Linux (amd64 and arm64), macOS, Windows, FreeBSD, and OpenBSD. Extra Linux arches (386, arm GOARM=6, riscv64, ppc64le, ppc64) run via qemu-user-static (task test-self-check-386, test-self-check-arm, test-self-check-riscv64, test-self-check-ppc64le, test-self-check-ppc64). Android emulator self-check is a separate workflow (selfcheck-android.yml) on schedule or workflow_dispatch.

NetBSD is not in CI. Run reticulum-go self-check manually on that host.

Static footgun scan

reticulum-go zen scans Go sources (and optional Python with -python) for path and link anti-patterns: RequestPath or HasPath loops, Establish before AwaitPath, link use without callbacks, announce bursts, and legacy 15 second timeouts. It is a developer tool, not a runtime check. No daemon is required.

make build
./bin/reticulum-go zen ./...
./bin/reticulum-go zen -list-rules
./bin/reticulum-go zen -fix ./pkg/myapp/...

-fix applies only safe edits (checking RequestPath errors in functions that return error). Warnings without -fix exit non-zero so CI can gate on the scan.

Package tests live in pkg/zenfix/. Full flag and rule reference: CLI utilities.

Vendoring

Third-party source is committed under vendor/. Ordinary builds and tests use that tree with GOFLAGS=-mod=vendor and GOPROXY=off (set by the Makefile and Taskfile). That keeps air-gapped builds reliable, makes dependency upgrades reviewable as diffs, and matches CI.

Versions and checksums remain in go.mod / go.sum. Scripts that install standalone CLI tools (revive, gosec, and similar) temporarily clear those env flags to fetch the tool binary. Project code itself always compiles from vendor/.

Refresh after dependency changes:

task vendor-sync
# or
make deps

task vendor-sync requires LIBS_ROOT pointing at the Reticulum-Go-Projects sibling tree for replace directives. Commit go.mod, go.sum, and vendor/ after refresh.

Day-to-day clones only need vendor/ to build offline. Sibling checkouts are only required when re-vendoring first-party libraries. examples/wasm and examples/pageserver keep their own go.mod / vendor/ trees. Docker configs under docker/ copy those folders for offline image builds.

CI overview

GitHub Actions workflows in .github/workflows/:

Workflow Role
ci.yml Build, test, reproducibility, OS self-check (Linux amd64 v1+v3, i686/386, arm64, arm, riscv64, ppc64le, ppc64, mips, s390x, plus Windows, macOS, *BSD, Solaris, illumos, AIX, Android arm64, wasm, and qemu-user self-check for 386/arm/riscv64/ppc64le/ppc64)
selfcheck-android.yml Android emulator self-check (nightly / manual)
security.yml Gosec, govulncheck, Trivy, SBOM dispatch
codeql.yml CodeQL for Go, JS/TS, Python, and Actions workflows
dependency-review.yml PR dependency and advisory gate
publish.yml Tagged releases, cosign attestations

CI uses Go 1.26.5 via actions/setup-go in .github/actions/setup-ci with GOTOOLCHAIN=local and vendored modules. Actions are SHA-pinned. Dependabot opens weekly PRs for Action bumps (.github/dependabot.yml).

Cross-compilation

make build-linux
make build-windows
make build-darwin
make build-all

make build-linux always emits linux-amd64 v1 and v3 together, plus linux-386 and linux-i686. make build-all covers every CGO-free GOOS/GOARCH pair that compiles (see scripts/build-release-targets.sh).

Legacy Windows uses go-legacy-win7 (make build-windows-legacy).

WebAssembly development

task build-wasm
make test-wasm
# or
task test-wasm

Manual WASM binary:

mkdir -p bin
GOOS=js GOARCH=wasm go build -ldflags="-s -w" -o bin/reticulum-go.wasm ./cmd/reticulum-wasm

See Embedding and WebAssembly.

librns shared library

make build-librns
# or: task build-librns
make -C bindings/c/examples/smoke
./bindings/c/examples/smoke/librns-smoke

Needs a C toolchain and CGO. Daemon builds stay CGO_ENABLED=0. See librns.

Odin bindings

task build-librns
task test-odin

Requires the Odin compiler on PATH (CI installs a pinned monthly release via scripts/ci/setup-odin.sh, job Odin bindings). Package lives under bindings/odin. See librns.

Zig bindings

task build-librns
task test-zig

Requires Zig 0.16.0 or later on PATH (CI installs a pinned release via scripts/ci/setup-zig.sh, job Zig bindings). Package lives under bindings/zig. See librns.

C++ bindings

task build-librns
task test-cpp

Requires CMake and a C++17 compiler on PATH (CI job C++ bindings). Package lives under bindings/cpp. See librns.

Dart bindings

task build-librns
task test-dart

Requires the Dart SDK on PATH (CI pins 3.11.4, job Dart bindings) and CGO for librns.so FFI smoke tests. Package and examples live under bindings/dart. See librns Dart FFI and Control API.

Rust bindings

task build-librns
task test-rust

Requires cargo on PATH (CI job Rust bindings). Package and examples live under bindings/rust.

Python bindings

task build-librns
task test-python

Requires python3 on PATH (CI job Python bindings). Package and examples live under bindings/python.

Lua bindings

task build-librns
task test-lua

Requires LuaJIT on PATH (CI job Lua bindings). Package and examples live under bindings/lua.

Swift bindings

task build-librns
task test-swift

Requires swift on PATH (CI pins 6.0.3 via scripts/ci/setup-swift.sh, job Swift bindings). Package and examples live under bindings/swift.

Java bindings

task build-librns
task test-java

Requires javac on PATH (CI uses Temurin 17, job Java bindings). Package and examples live under bindings/java. JNA is fetched on first build.

Kotlin bindings

task build-librns
task test-kotlin

Requires kotlinc and javac on PATH (CI pins Kotlin 2.1.10 via scripts/ci/setup-kotlin.sh, job Kotlin bindings). Package and examples live under bindings/kotlin and depend on bindings/java.

C ABI examples

task build-librns
task test-c

Builds and runs bindings/c/examples (smoke plus page-fetch/pageserver compile). Binding CI jobs also run make -C bindings/ examples.

Adding a change safely

  1. Write or extend unit tests in the affected package
  2. If wire format changes, update crossref vectors and Python reference together
  3. Run make check locally
  4. For protocol behavior, add or extend interop test when feasible
  5. Update Compatibility and COMPATIBILITY.md if parity status changes