Skip to content

feat: wire packet tap for sniffer/debug consumers#10

Open
rinchen wants to merge 1 commit into
ratspeak:mainfrom
rinchen:feat/packet-tap
Open

feat: wire packet tap for sniffer/debug consumers#10
rinchen wants to merge 1 commit into
ratspeak:mainfrom
rinchen:feat/packet-tap

Conversation

@rinchen

@rinchen rinchen commented Jul 2, 2026

Copy link
Copy Markdown

Summary

  • Add PacketTapEvent / PacketTapDirection and TransportMessage::SetPacketTap
  • Emit RX taps on inbound and TX taps on interface send
  • Expose ReticulumHandle::register_packet_tap for embedders (e.g. mesh-client sidecar wire sniffer)

Consumer

Colorado-Mesh/mesh-client#596 — Reticulum Stats/Sniffer panel (wire_packet events, GET /api/v1/packets)

Test plan

  • cargo test -p rns-transport -p rns-runtime
  • mesh-client cargo test --features rns-stack with this branch as sibling

Add PacketTapEvent/PacketTapDirection, TransportMessage::SetPacketTap,
RX/TX emission in the transport actor, and ReticulumHandle::register_packet_tap
for embedders that need wire-level observation (e.g. mesh-client sidecar sniffer).
rinchen added a commit to Colorado-Mesh/mesh-client that referenced this pull request Jul 2, 2026
- Add reticulum-sidecar/patches/rsReticulum-packet-tap.patch (base
  6d2b284) exposing PacketTapEvent and register_packet_tap for the
  wire sniffer; upstream PR ratspeak/rsReticulum#10.
- Pin RS_RETICULUM_REF and apply the patch in build-rns-stack and
  build-windows-arm64-rns-stack workflow jobs before cargo build.
- Add scripts/apply-rsReticulum-packet-tap.sh and document the overlay
  in reticulum-sidecar/README.md and patches/README.md.

CI was failing E0432/E0425/E0599 because live.rs calls packet-tap APIs
that upstream rsReticulum main does not ship yet; the vendored overlay
unblocks all four rns-stack matrix jobs without removing sniffer wiring.
rinchen added a commit to Colorado-Mesh/mesh-client that referenced this pull request Jul 2, 2026
…, RNode flasher/BLE/Wi-Fi, triple-protocol BLE, peer graph overhaul & repo-wide i18n (Fixes #593) (#596)

feat: add Reticulum as third protocol tab alongside Meshtastic and MeshCore

Completes #593 — adds a headless Rust mesh-client-reticulum sidecar (AGPL,
axum HTTP + WebSocket), Electron reticulum:* IPC, and full React UI with
Ratspeak/rsReticulum/rsLXMF wire interop as primary compatibility target.

469 files changed, 68,834 insertions(+), 4,849 deletions(-)

── Rust sidecar (reticulum-sidecar/) ──────────────────────────────────

Binary: mesh-client-reticulum — axum HTTP + WebSocket event bridge.

API modules: status, app info, interfaces CRUD, contacts, LXMF
send/resource/reaction/delete, config import/export/audit/repair,
diagnostics, factory reset, propagation sync, nomad list/favorite/page/file,
topology edges, packet tap ring buffer (GET/DELETE /api/v1/packets,
wire_packet WS events), voice/games status stubs, BLE scan/availability,
serial port enumeration.

Cargo features: optional rns-stack; rns-ble (BLE Peer + ble:// RNode);
rns-rnode-tcp (Wi-Fi tcp:// RNode). Dev auto-build enables rns-stack,rns-ble
when Ratspeak siblings present at ../../rsReticulum, ../../rsLXMF.

Live stack bridge: async reads (no blocking_read panics); timed transport
control queries (8 s cap) with cache fallback; LinkDeliveryManager for async
LXMF (Direct vs Propagated); Nomad page/file via LinkClient::query with
identity-hash resolution; LXMF attachment send/receive; propagation bridge
for live sync.

Packet tap: register_packet_tap from rsReticulum → wire_packet WS + sniffer
buffer. Vendored patch reticulum-sidecar/patches/rsReticulum-packet-tap.patch
(upstream ratspeak/rsReticulum#10) applied in CI before rns-stack builds.

Config: validated atomic writes, extra_sections round-trip, 4 MiB body limit,
mnemonic stripped on disk, localhost CORS, loopback bind guard.

Interface health: merge config rows with live GetInterfaceStats so failed
RNode/KISS interfaces remain visible; preset expansion/repair on bootstrap;
coordinated RNode RF profiles; enabled preserved when live stats omit
offline BLE RNodes.

CI: dual matrix — stub builds (no siblings) on all platforms + rns-stack on
linux x64, macOS arm64, Windows x64+arm64. libdbus-1-dev for Ubuntu rns-ble.
cargo test in workflow. electron-builder extraResources.

Dev: pnpm run reticulum:sidecar:build; auto-rebuild when Rust sources newer
than binary; sidecar survives dev HMR remounts.

── Electron main process + preload ────────────────────────────────────

ReticulumSidecarManager — spawn, health poll, serialized start (coalesce
concurrent reticulum:start), HTTP proxy GET/POST/PUT/DELETE, WS event bridge
to renderer.

reticulum:* IPC — proxy routes with path allowlist (/api/v1/),
transport-aware timeouts (30 s for peers/interfaces/topology/packets; Nomad
page/file uses max(egress, RF) up to 180 s), config file read/import dialog.

SQLite IPC — Reticulum message search (FTS5 schema v41), delete, attachment
save/show-in-folder (path jail), peer/contact delete, db:clearReticulumMessages,
chat outbox attempt_count persistence.

ble-coexistence-coordinator.ts — peripheral MAC registry, scan-only mutex
(noble / reticulum / webbt); Noble pause/resume for Reticulum picker scans
without dropping GATT; rejects same-MAC conflicts.

mqtt-client-teardown.ts — absorbs late mqtt.js connack/keepalive errors on
force-disconnect (fixes uncaught main-process exceptions).

Security hardening (manual audit): validateIpcSender on reticulum/vault/DB/BLE
handlers; attachment path jail; proxy body/config read caps; vault 8-char
passcode + unlock rate limiting; bounded readUtf8FileBounded (TOCTOU fix).

── Renderer — Reticulum protocol integration ─────────────────────────

Registered in meshProtocol, ProtocolCapabilities, theme (amber),
useReticulumRuntime, useReticulumSession, SQLite migrations (v39+),
hydrateIdentityStoresFromDb, useReticulumPanelActions.

Capability-gated UI — hasReticulum*, hasLxmfDeliveryStatus, etc.;
check-protocol-string-gates extended; no raw protocol === 'reticulum' checks
in new code.

Canonical stores — reticulumIngest → messageStore; nodeStore canonical for
Reticulum UI; reticulumPeerStore for favorites/names; deduplicated sidecar
reads (reticulumSidecarReads), peer display names, chat last-read migration,
unread counting.

Runtime — WS subscriptions for wire_packet, LXMF delivery, peers_updated,
propagation sync_progress, interface.state; autostart/reconnect on unexpected
stop; debounced event refreshes (2 s) during Nomad browsing.

Header — green "configured" status when stack running; identity display name
from sidecar (not uint32 fallback); ConnectIcon during connecting.

── Tab layout & panel architecture ───────────────────────────────────

Connection tab  → ReticulumStackPanel — stack start/stop, auto-start,
                   Disconnect & quit; Interfaces section (CRUD, device
                   pickers, offline alerts) — collapsed by default
Network tab     → ReticulumNetworkPanel — identity gen/import/export/switch,
                   announce interval, propagation mode, config import/export,
                   stack settings
Admin tab       → ReticulumAdminPanel — RNode firmware flasher, Wi-Fi
                   provisioning (WifiConfig.tsx), factory reset
Peers tab       → ReticulumPeerListPanel — Peers | Contacts sub-tabs,
                   search, sort, virtual scroll, path/probe/favorite/chat,
                   detail modal
Chat tab        → shared ChatPanel — DM-only LXMF; contact tabs; transport
                   badges; outbox; search; attachments; voice clips; reactions
Topology tab    → ReticulumTopologyPanel — hierarchical graph: self →
                   interface hubs → peers; distant-peer filters
Nomad tab       → NomadNetworkPanel — Favourites/Announces, Micron HTML
                   browser, file downloads
Diagnostics tab → shared + ReticulumDiagnosticEngine — Reticulum-native rows
                   (no LoRa hop-goblin semantics); config audit hints;
                   unified ping panel
Sniffer/Stats   → shared RawPacketLogPanel — Reticulum wire_packet variants;
                   Graph tab hidden for Reticulum

Meshtastic/MeshCore Radio, Map, Modules, and Graph tabs restored after
Reticulum capability gating (appTabMappings OR rule fix).

── Connection tab — interface management ──────────────────────────────

CRUD: add, edit (inline form: name, TCP host/port, RNode serial/preset/
callsign/RF params, BLE seed addresses, Wi-Fi host/port), enable/disable,
delete with ConfirmModal.

Device pickers: ReticulumInterfaceDevicePickerModal for serial, BLE Peer,
BLE RNode; Electron serial picker with known/last-used port labels.

Transports: USB serial, ble:// RNode, tcp://host[:7633] Wi-Fi RNode (not
TCP Client 4242), BLE Peer mesh, Auto, TCP/UDP, KISS, Pipe, I2P,
RNode Multi.

Health UI: reticulumLocalInterfaceHealth classifier (stale_port vs
enabled_down); Connection alert banner with Open Network / Refresh ports;
cyan "connecting" grace window (30 s post-start) for BLE RNode link settle;
adaptive 5 s/30 s polling; stack restart on serial CRUD or RF patch changes.

Config audit/repair: sidecar GET /config/audit, POST /config/repair;
diagnostics merge; Connection hints with repair/disable/deep-link edit via
reticulumUiStore.

RNode RF edit: frequency, bandwidth, spreading factor, coding rate, TX power;
coordinated preset defaults; stack restart on RF changes.

── LXMF chat (DM-only) ───────────────────────────────────────────────

DM tabs from LXMF contacts even without prior message history; auto-select
first contact; block send without recipient.

Transport badges: RF / TCP / NET in and out on messages (received_via /
sent_via columns, sidecar egress classification).

Delivery status: outbound "Sending" until lxmf_outbound_status /
LinkDeliveryManager events; propagation-node badge and toast when offline
without PN.

Threading: ratspeak.chat.v2 reply hashes; dismiss closed DM tabs across
restarts; clear DM history via App danger zone (db:clearReticulumMessages).

Attachments: live LXMF file send/receive; [file:...] rendering;
save/show-in-folder IPC; chunked upload.

Voice clips: record in ChatComposer, playback/cache.

Reactions: live LXMF reactions on rns-stack path.

Outbox: persist attempt_count; drain on announce, path updates, MQTT
reconnect (chatOutboxDrain).

Search: FTS5 message search in SearchModal.

Payload limit: 4096 bytes via ProtocolCapabilities.lxmfPayloadLimit.

── Peers, contacts & identity ────────────────────────────────────────

Dedicated Peers tab (not NodeListPanel fork): path, probe, favorite, open DM,
copy hash (Electron clipboard IPC).

reticulumPeerStore + SQLite destination persistence; peers_updated WS after
path/probe; footer peer count synced from path table.

Identity: generate, import (ratspeak.identity.v2), export (passphrase
required), multi-identity switcher, announce interval, clear announces,
profile icons from LXMF FIELD_ICON_APPEARANCE, blocked contacts, identity
activity store.

Peer detail modal: avatar colors; voice-call button removed (LXST not
implemented).

── Nomad Network browser ─────────────────────────────────────────────

Sidecar ingests nomadnetwork.node announces; persists nodes with
identity_hash for LinkClient::query.

UI: split list + browser pane; Micron HTML via micron-parser +
NomadMicronPageView (DOMPurify); source toggle; in-page and cross-node
navigation.

Downloads: /file/ fetches with base64 IPC round-trip; transport-aware
timeouts (TCP 45 s, RF hop-scaled up to 180 s).

Resilience: 5 s TTL cache for interfaces GET; 60 s egress cache; skip
caching network egress when interfaces list empty; proxy timeout uses
max(egress, RF) so RF page fetches are not aborted at 47 s.

── Network topology graph (Reticulum) ────────────────────────────────

MeshChat-style hierarchical layout: local identity center → configured
interface hubs (human-readable names) → peers hung off matching interface.

Show all configured interfaces as spokes even when idle; green/red
online/offline styling.

Distant-peer filters (show distant peers, max hops); enrich labels from
peers, contacts, Nomad announces.

buildReticulumViaHashEdges for hub-to-leaf edges when API returns nodes but
empty edges array.

Force-directed fallback replaced by structured layout; full-height mount
without nested scroll.

── Wire packet sniffer & Stats (all protocols) ───────────────────────

Reticulum: sidecar packet tap → wire_packet WS → rawPackets store;
clearRawPackets IPC.

Pause-to-inspect: Wireshark-style Pause/Resume with pending count; snapshot
packets while paused; jump-to-latest when unpinned.

Batch ingestion: wire_packet appends batched per animation frame.

Scroll pinning: align with ChatPanel contract (anchorTo: 'end',
followOnAppend); unpin on scroll-up/wheel/expand; preserve anchor when
reading history.

Virtualizer keys: rawPacketContentKey / getItemKey — no duplicate React keys
for identical captures.

Duplicate sniffer rows fixed (removed dual handleSidecarEvent subscription
in ReticulumStackPanel).

Stats/Sniffer panels enabled for Reticulum with RawPacketLog Reticulum
variants; Graph tab removed for Reticulum
▣  Build · Big Pickle · interrupted
feat: add Reticulum as third protocol tab alongside Meshtastic and MeshCore

Completes #593: Rust sidecar (axum HTTP+WS), Electron reticulum:* IPC, and
full React UI with Ratspeak/rsReticulum/rsLXMF wire interop. 469 files,
68,834 insertions.

── Sidecar (reticulum-sidecar/) ──
Binary mesh-client-reticulum with optional rns-stack, rns-ble, rns-rnode-tcp.
Async live-stack bridge (no blocking_read panics), LinkDeliveryManager for
async LXMF, Nomad page/file via LinkClient::query, register_packet_tap →
wire_packet WS + sniffer buffer. Vendored rsReticulum patch for packet tap.
Config atomic writes, 4 MiB body limit, localhost CORS. CI dual matrix (stub
+ rns-stack). pnpm run reticulum:sidecar:build; survives HMR remounts.

── Main / preload ──
ReticulumSidecarManager (spawn, health poll, serialized start, HTTP proxy,
WS bridge). reticulum:* IPC with path allowlist, transport-aware timeouts
(30 s general, 180 s Nomad RF). SQLite FTS5 message search (v41), attachment
path jail. ble-coexistence-coordinator — triple-protocol BLE MAC registry +
scan mutex without dropping GATT. mqtt-client-teardown absorbs late connack
errors. Security: validateIpcSender, vault rate limiting, bounded file reads.

── Renderer integration ──
Registered in meshProtocol, ProtocolCapabilities, theme (amber),
useReticulumRuntime, useReticulumSession, SQLite migrations v39+.
Capability-gated UI (hasReticulum*, hasLxmfDeliveryStatus); no raw
protocol === 'reticulum'. reticulumIngest → messageStore;
reticulumPeerStore for favorites/names. WS subs for wire_packet, LXMF
delivery, peers_updated, propagation, interface.state. Autostart/reconnect
on unexpected stop. Header shows identity display name, not uint32 fallback.

── Tab layout ──
Connection   → ReticulumStackPanel — start/stop, auto-start, interface CRUD
Network      → ReticulumNetworkPanel — identity gen/import/export/switch,
               announce, propagation, config import/export
Admin        → ReticulumAdminPanel — RNode nRF52 DFU + ESP32 flasher, Wi-Fi
               provisioning, factory reset
Peers        → ReticulumPeerListPanel — Peers | Contacts, search, sort,
               virtual scroll, path/probe/favorite/chat, detail modal
Chat         → shared ChatPanel — DM-only LXMF, transport badges, outbox,
               search, attachments, voice clips, reactions
Topology     → ReticulumTopologyPanel — hierarchical graph: self → hubs →
               peers, distant-peer filters
Nomad        → NomadNetworkPanel — Favourites/Announces, Micron HTML
               browser, file downloads
Diagnostics  → shared + ReticulumDiagnosticEngine — native rows, config
               audit hints, unified ping panel
Sniffer      → shared RawPacketLogPanel — wire_packet variants

Meshtastic/MeshCore Radio, Map, Modules, Graph tabs restored after
capability OR fix.

── Interfaces (Connection tab) ──
CRUD inline form: name, TCP host/port, RNode serial/preset/RF, BLE seed
addresses, Wi-Fi host/port. Device pickers for serial, BLE Peer, BLE RNode.
Transports: Auto, TCP/UDP, KISS, Pipe, I2P, RNode (USB/ble:///tcp:// Wi-Fi),
RNode Multi, BLE Peer. Health classifier (stale_port vs enabled_down);
cyan 30 s grace for BLE RNode settle; adaptive polling. Config audit/repair
from sidecar; deep-link edit via reticulumUiStore. RNode RF profiles with
coordinated presets; stack restart on RF changes.

── LXMF chat ──
DM tabs from contacts without prior history. Transport badges (RF/TCP/NET).
Delivery status with LinkDeliveryManager events; propagation-node badge.
ratspeak.chat.v2 reply hashes. Attachments: live send/receive, chunked
upload. Voice clips, reactions. Outbox with attempt_count persistence; drain
on announce/path/MQTT reconnect. FTS5 search. 4096 B payload limit.

── Peers & identity ──
Peers tab with path/probe/favorite/chat/copy hash. reticulumPeerStore +
SQLite persistence. peers_updated WS. Identity gen, ratspeak.identity.v2
import, passphrase-encrypted export, multi-identity switcher, announce
interval, profile icons from FIELD_ICON_APPEARANCE, blocked contacts.

── Nomad Network ──
Sidecar ingests nomadnetwork.node announces; identity_hash for
LinkClient::query. Split list + Micron HTML browser (DOMPurify). File
downloads via base64 IPC. Transport-aware timeouts (TCP 45 s, RF up to
180 s). 5 s TTL on interfaces GET; 60 s egress cache.

── Topology graph ──
Hierarchical: self → interface hubs → peers. Configured interfaces shown as
spokes even when idle. buildReticulumViaHashEdges for hub-to-leaf edges.
Distant-peer filters. No force-directed fallback.

── Sniffer (all protocols) ──
Wireshark-style Pause/Resume with pending count. Batch ingestion per
animation frame. Scroll pinning aligned with ChatPanel contract. Fixed
virtualizer duplicate keys. Removed dual handleSidecarEvent subscription.

── RNode flasher (Admin) ──
nRF52 DFU (SLIP/CRC16), ESP32 esptool-js 0.4.5, EEPROM provision, firmware
hash. Unified step UX (flash → provision → set-hash). ESP32 hex parse fix
(boot loop). Wi-Fi CMD_WIFI_* → add tcp://host:7633 on Connection tab.

── BLE — triple-protocol coexistence ──
BleCoexistenceCoordinator: per-MAC ownership, scan-only mutex, Noble
pause/resume without GATT teardown. Same MAC rejected with i18n errors.
rns-ble sidecar feature; BLE Peer mesh with seed addresses. ConnectionPanel
defers secondary auto-connect; meshtasticEverConfiguredRef gates reconnect.

── Peer graph (Meshtastic/MeshCore) ──
buildMeshPeerTopologyGraph with Reticulum-style layout: self → relay hubs →
peers. 90-node budget, relay cap at 20, MQTT-only demoted to compact dots.
SVG pan/zoom (useSvgPanZoom), hover tooltips, density-aware sizing. Pointer
events fixed (no capture-on-mousedown). Default 2 hops.

── i18n audit ──
118 unused keys pruned (2834→2716), CI enforcement. ~304 new keys across all
three protocols. Hardcoded-English sweep in Connection, Radio, Modules,
Security, Chat, Map, Log, Diagnostics, App, Rooms, Repeaters.
meshcoreMessageI18n helper, translateReticulumDiagnosticCause,
diagnosticsLabels for routing ports. check-i18n-quality.mjs Reticulum rules.

── Dev/CI tooling ──
check:environment (scripts/check-environment.mjs). Flatpak offline pnpm
retry. CodeQL pinned refs. Prettier ignores reticulum-sidecar/.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant