20 Commits
Author SHA1 Message Date
swaits fcf2f5826c release 3.0.0: iroh 1.0-rc.0, gossip 0.99, rendezvous 0.2 2026-05-11 16:02:23 -06:00
swaits 6d0f8cd50d chore: release 2.0.1 v2.0.1 2026-04-25 20:52:41 -06:00
swaits b8e957fe33 fix(tui): drop stale "updating DHT" overlay, bound shutdown wait
Pre-upgrade, `Gossip::shutdown()` synchronously published a "remove
self" entry to mainline DHT — that's where the TUI's "updating
DHT..." overlay came from, and it was genuinely slow. The
iroh-gossip-rendezvous 0.1 migration dropped that publish (entries
age out instead), but the shutdown flow didn't change: the overlay
still drew before shutdown ran, and the terminal was restored after.
So `q` would land the user on the misleading screen for as long as
`endpoint.close()` took to flush.

Restore the terminal the instant `q` is pressed, remove the overlay,
and bound `swarm.shutdown()` with a 2s timeout so a slow endpoint
close can't stall the process.
2026-04-25 20:50:50 -06:00
swaits b4cc826e74 chore: release 2.0.0 v2.0.0 2026-04-22 23:21:07 -06:00
swaits 63b59482fe feat!: factor DHT rendezvous into iroh-gossip-rendezvous 0.1
The partition-healing / DHT-rendezvous algorithm that used to live in
`src/gossip.rs` is now its own crate, published as
`iroh-gossip-rendezvous` 0.1.0. Bridge depends on it for swarm
discovery; the 760-line gossip module shrinks to ~75 lines of glue.

`dht-partition-healing.md` is reduced to a pointer at the new
crate's `PROTOCOL.md` — the normative spec moved with the code.

### Dependency changes

- **New:** `iroh-gossip-rendezvous = "0.1"` (path + version for local
  dev; publish strips the path).
- **Bumped:** `iroh` and `iroh-gossip` 0.97 → 0.98 (major).
- **Removed:** direct deps on `mainline`, `sha2`, `zeroize` — all
  now transitive via `iroh-gossip-rendezvous`.
- `donglora-client` spec loosened from `"1.0.0"` to `"1"` so patch
  and minor releases of the client propagate automatically.

BREAKING CHANGE: any direct users of `bridge::gossip` internals
relied on the in-bridge partition-healing types that have moved.
Import from `iroh_gossip_rendezvous::…` instead.
2026-04-22 23:20:24 -06:00
swaits 43b30952f8 chore: release 1.0.1 v1.0.1 2026-04-22 10:47:49 -06:00
swaits 599cfd14dc chore: bump rand past RUSTSEC-2026-0097
cargo update -p rand@0.9.2 and cargo update -p rand@0.8.5 take the two pinned rand versions past the RUSTSEC-2026-0097 fix windows:


* rand 0.9.2 -> 0.9.4 (fix >= 0.9.3).

* rand 0.8.5 -> 0.8.6 (fix >= 0.8.6).


The advisory is reachable only with a custom logger that calls rand::rng() at trace/warn level and hits the 64 KB reseed path. We are not known to do that, but unblocking CI is reason enough.
2026-04-22 10:34:26 -06:00
swaits 436ab155e7 chore: release 1.0.0 v1.0.0 2026-04-22 09:48:32 -06:00
swaits 0b9775c310 feat!: bridge on DongLoRa Protocol v2 and async client
The 1.0 bridge running on the async Dongle API from donglora-client 1.0.0 and the Protocol v2 wire format.

* Radio loop is now a tokio task, not a dedicated std::thread. Everything is async end to end.

* [radio] config schema: tx_power renamed to tx_power_dbm (plain i8, no "max" keyword), cad field removed (CAD is a per-TX flag in v1.0), default sync_word changed 0x3444 to 0x1424 to match the SX126x register encoding of MeshCore private sync byte.

* TX retry with backoff (new [tx] config section). Every TX runs through Dongle::tx_with_retry, retrying TX_DONE(CHANNEL_BUSY) and ERR(EBUSY) per PROTOCOL.md §6.10 / §C.5.5. Defaults: 3 attempts, 20-100 ms randomized backoff, doubling to 500 ms cap, 5 s per-attempt timeout. Configurable via max_attempts, backoff_min_ms, backoff_max_ms, backoff_cap_ms, backoff_multiplier, per_attempt_timeout_secs.

* TUI packet log shows retry state: in-flight retries render with a cycle marker in yellow and an N/M attempt counter, terminal TX failures render in red, post-retry success shows final attempt count.

* Stats gain tx_retries and tx_failures counters surfaced in StatsSnapshot.

* Measured airtime from TX_DONE replaces the theoretical estimate, feeding the rate limiter with the real on-air duration.

* The explicit 2 s liveness ping loop is gone. The Dongle runs a 500 ms internal keepalive, matching spec §3.4 inactivity window with 2x margin.
2026-04-22 09:48:01 -06:00
swaits 030280791e fix: sticky mux reconnect prevents USB port theft
Once the bridge connects via mux, all subsequent reconnects use mux-only
mode (connect_mux_auto). Previously, each reconnect re-ran the full
mux→USB fallback chain, which could steal the serial port from the mux
during a brief disconnect.
v0.3.1
2026-04-07 18:05:56 -06:00
swaits 222802cf13 feat: upgrade to donglora-client 0.2, simplify connection, fix org URLs
- Upgrade donglora-client from 0.1 to 0.2 (ping-on-connect validation)
- Remove manual mux/serial fallback and manual ping — handled by client now
- Fix repository/homepage URLs to github.com/donglora/bridge
- Fix README links to donglora GitHub org
v0.3.0
2026-04-07 16:11:08 -06:00
swaits 394acaee6e chore: add consistent mise.toml, justfile, and tooling config 2026-04-07 14:43:26 -06:00
swaits 943f2615f3 fix: reduce TUI minimum width from 86 to 80 columns 2026-04-06 17:08:56 -06:00
swaits ae284d571a fix: remove stale sibling-clone CI step (donglora-client is on crates.io) 2026-04-06 17:05:22 -06:00
swaits ca26368136 feat: auto-detect connection — fall back to direct USB serial when mux is unavailable v0.2.0 2026-04-06 16:17:39 -06:00
swaits 6bae65c464 feat: publish-ready overhaul — lints, tests, docs, CI, fuzzing, mutant testing
- Enable clippy pedantic + nursery lints; fix all warnings
- Add config validation (SF, CR, frequency range checks)
- Zeroize passphrase intermediates after key derivation
- Convert to lib+bin crate for fuzz target accessibility
- Replace Vec with VecDeque for O(1) log/bucket rotation
- Add TUI minimum terminal size guard

Testing:
- 145 unit + property tests (was 15)
- 6 proptest groups (frame roundtrip, hash, SNR, air time, peer list, padding)
- 2 cargo-fuzz targets (gossip frame decode, peer list decode)
- cargo-mutants: 0 missed, 241 caught, 54 unviable

Documentation:
- README with prerequisites, quick start, config reference, architecture
- Module-level and item-level rustdoc on all public items
- LICENSE (MIT), rustfmt.toml, .gitignore

Infrastructure:
- GitHub Actions CI (fmt, clippy, test, audit)
- cargo-mutants config (.cargo/mutants.toml)
- justfile for common tasks
2026-04-06 14:52:18 -06:00
swaits b3bd92c5f8 fix: replace expect() with match in SIGTERM handler to satisfy clippy 2026-04-06 07:03:17 -06:00
swaits b4026ace34 feat: overhaul radio reconnection, TUI layout, DHT strategy, and signal handling
Radio reconnection:
- Add connect_mux_auto() to donglora-client — mux-only connect that never
  silently falls back to USB serial (matches Python client's sticky mux)
- Fix codec.rs read_frame() to handle WouldBlock alongside TimedOut
- Add liveness ping (2s) to detect dead mux connections
- Treat transmit errors as fatal (trigger reconnect)
- Faster backoff: 250ms initial, 5s max, resets after stable connection

TUI improvements:
- Redesign packet log: RF+Net columns show full packet path (RX→TX),
  hash column with dedup ellipsis, fixed-width columns throughout
- Move activity charts to right column (fills remaining width)
- 1-second sparkline buckets instead of 10-second
- Radio panel shows "(disconnected)" with red border when mux is down
- Header: status pills left, clock with TZ right-justified, Ctrl-C quits
- Local time via jiff crate with proper timezone abbreviation

DHT strategy:
- Alone: read every 3s, merge-publish every 30s (preserves existing peers)
- Connected: publish authoritative [self + neighbors], purging stale peers
- Publish immediately on first neighbor join

Signal handling:
- SIGINT and SIGTERM both trigger graceful shutdown via cancellation token
- All paths go through swarm.shutdown() to update DHT before exit
2026-04-05 21:43:36 -06:00
swaits 4508f317be feat: replace direct P2P networking with iroh-gossip swarm and DHT peer discovery
Replace manually configured peer connections with a passphrase-based gossip
swarm. Peers sharing the same passphrase automatically discover each other
via mainline DHT and join an iroh-gossip topic.

Key changes:
- Add gossip.rs: iroh-gossip swarm with DHT bootstrap, heartbeat, and
  graceful shutdown that publishes neighbor list
- Add setup.rs: interactive config wizard replacing generate-config
- Add rate_limit.rs: token-bucket TX rate limiter derived from LoRa air time
- Remove network.rs: old direct P2P connection management
- Config: remove [[peers]] section, add passphrase to [bridge], update
  radio defaults to MeshCore US/Canada recommended values
- Packet: rename Envelope to GossipFrame, simplify wire format (drop
  version byte and seq number)
- Router: unify dedup into single content-hash cache, add rate limiting
- Identity: use ephemeral keypair per launch instead of persistent secret key
- Radio: improve config negotiation (GetConfig-first strategy), add device
  info tracking
- TUI: complete redesign with network/swarm panel, sparkline activity
  charts, color-coded RSSI/SNR, and shutdown overlay
2026-04-05 19:00:26 -06:00
swaits 4387549e7f feat: initial working P2P LoRa bridge
iroh QUIC datagrams for peer-to-peer networking with NAT traversal,
donglora-client for radio access (direct USB or mux), ratatui TUI
with four-panel dashboard (peers, radio config, stats, packet log).

- Packet envelope with origin tracking, sequence numbers, RSSI/SNR
- Dual dedup: iroh-level (origin+seq) and radio echo (blake3 hash)
- SNR filtering per PROTOCOL.md (forward GOOD+MARGINAL, drop rest)
- Auto-generate config + identity on first run
- Graceful mux config fallback (adopts locked config if SetConfig rejected)
- WouldBlock workaround for donglora-client socket timeout bug
- Reconnect with exponential backoff on dongle disconnect
- CLI: show-id, generate-config subcommands, --log-only headless mode
2026-04-05 11:43:00 -06:00