2 Commits
Author SHA1 Message Date
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
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
7 changed files with 88 additions and 85 deletions

No files matched your search

+14 -3
View File
@@ -1,11 +1,22 @@
# Changelog
## [0.2.3] - 2026-04-06
## [0.3.0] - 2026-04-07
### Changed
- README: clarify that the bridge relays raw LoRa packets and works with any
protocol (Meshtastic, MeshCore, etc.) simultaneously.
- Upgraded to `donglora-client` 0.2: all connections now auto-validate via
ping-on-connect, rejecting non-DongLoRa serial devices within 200ms.
- Simplified connection logic — removed manual mux/serial fallback and manual
ping; `donglora_client::connect()` handles the full fallback chain and
validation internally.
- Config negotiation detects mux vs serial via transport type instead of
string comparison.
### Fixed
- Repository and homepage URLs now point to `github.com/donglora/bridge`
(was `swaits/donglora-bridge`).
- README links updated to `donglora` GitHub org.
## [0.2.2] - 2026-04-06
Generated
+3 -3
View File
@@ -906,7 +906,7 @@ dependencies = [
[[package]]
name = "donglora-bridge"
version = "0.2.2"
version = "0.3.0"
dependencies = [
"anyhow",
"blake3",
@@ -938,9 +938,9 @@ dependencies = [
[[package]]
name = "donglora-client"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162468289ac189a798c4ecd961ed92c5dde2d9c5709d779ae324e0bb9a5ac4dc"
checksum = "1936935d31ab56a8cd70ceecfd6d447f2628a73a123e1cfe37ee4987eab0fa70"
dependencies = [
"anyhow",
"serialport",
+4 -4
View File
@@ -1,12 +1,12 @@
[package]
name = "donglora-bridge"
version = "0.2.3"
version = "0.3.0"
edition = "2024"
authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
description = "Peer-to-peer LoRa bridge using iroh and DongLoRa"
repository = "https://github.com/swaits/donglora-bridge"
homepage = "https://github.com/swaits/donglora-bridge"
repository = "https://github.com/donglora/bridge"
homepage = "https://github.com/donglora/bridge"
keywords = ["lora", "bridge", "gossip", "p2p", "iot"]
categories = ["network-programming", "command-line-utilities"]
readme = "README.md"
@@ -14,7 +14,7 @@ rust-version = "1.94.1"
exclude = [".jj/", ".github/"]
[dependencies]
donglora-client = "0.1"
donglora-client = "0.2"
iroh = "0.97"
iroh-gossip = "0.97"
mainline = { version = "6", features = ["async"] }
+12 -15
View File
@@ -1,22 +1,19 @@
# donglora-bridge
[![CI](https://github.com/swaits/donglora-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/swaits/donglora-bridge/actions/workflows/ci.yml)
[![CI](https://github.com/donglora/bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/donglora/bridge/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
![Rust](https://img.shields.io/badge/rust-1.94.1%2B-orange)
**donglora-bridge** relays raw LoRa packets over the internet. Every packet your
radio hears is forwarded to every other bridge running the same passphrase, and
their radios retransmit it locally. Because it operates on raw packets, it works
with any protocol built on LoRa --
[Meshtastic](https://meshtastic.org/),
[MeshCore](https://github.com/rocketgithub/meshcore-firmware),
or anything else -- simultaneously and without configuration. If your radio can
hear it, the bridge will relay it.
**donglora-bridge** connects LoRa radios to the internet so packets heard by one
radio can be retransmitted by another radio anywhere in the world.
[LoRa](https://en.wikipedia.org/wiki/LoRa) is a long-range, low-power wireless
modulation used by off-grid mesh networks. A single radio might cover a few
kilometers. This bridge removes that limit, extending a local LoRa network
across a larger geographic area.
protocol used by off-grid mesh networks like
[MeshCore](https://github.com/rocketgithub/meshcore-firmware). A single LoRa
radio might cover a few kilometers. This bridge removes that limit: any packet
your radio hears gets relayed over the internet to every other bridge running the
same passphrase, and their radios retransmit it locally. The result is a single
mesh network across a larger geographic area.
There is no central server. Bridges discover each other automatically using the
[Mainline DHT](https://en.wikipedia.org/wiki/Mainline_DHT) and communicate
@@ -28,12 +25,12 @@ through an encrypted peer-to-peer gossip swarm.
### 1. A DongLoRa radio
[DongLoRa](https://github.com/swaits/donglora) is open-source firmware that
[DongLoRa](https://github.com/donglora/firmware) is open-source firmware that
turns a supported LoRa development board (such as a Heltec WiFi LoRa 32 or
similar ESP32+SX1262 board) into a USB LoRa dongle. You will need to:
1. **Get a board** -- any ESP32 board with an SX1262 LoRa radio that DongLoRa
supports. See the [DongLoRa README](https://github.com/swaits/donglora) for
supports. See the [DongLoRa README](https://github.com/donglora/firmware) for
the full list.
2. **Flash the DongLoRa firmware** onto the board. Instructions are in the
DongLoRa repository.
@@ -71,7 +68,7 @@ mise use -g cargo:donglora-bridge
Or build from source:
```sh
git clone https://github.com/swaits/donglora-bridge.git
git clone https://github.com/donglora/bridge.git
cd donglora-bridge
cargo build --release
```
+44 -35
View File
@@ -1,60 +1,69 @@
# Default: run all checks
set shell := ["bash", "-c"]
default: build
# Run all checks (fmt, clippy, test)
[private]
_ensure_tools:
@mise trust --yes . 2>/dev/null; mise install --quiet
# Run the bridge (TUI mode)
run *args: _ensure_tools
cargo run --release -- {{args}}
# Run in headless mode
run-headless *args: _ensure_tools
cargo run --release -- --log-only {{args}}
# Run the config wizard
config: _ensure_tools
cargo run --release -- config
# Run all checks (fmt, clippy, test, audit)
check: fmt-check clippy test audit
# Format code
fmt:
fmt: _ensure_tools
cargo fmt
# Check formatting without changing files
fmt-check:
fmt-check: _ensure_tools
cargo fmt --check
# Lint with clippy (pedantic + nursery)
clippy:
# Lint with clippy
clippy: _ensure_tools
cargo clippy --all-targets -- -D warnings
# Build release binary
build:
cargo build --release
# Generate docs
doc:
cargo doc --no-deps --open
# Run the bridge (TUI mode)
run *ARGS:
cargo run --release -- {{ARGS}}
# Run in headless mode
run-headless *ARGS:
cargo run --release -- --log-only {{ARGS}}
# Run the config wizard
config:
cargo run --release -- config
# Security + license audit
audit:
cargo deny check
# Run all tests
test: test-unit test-fuzz test-mutants
test: test-unit
# Normal unit tests
test-unit:
test-unit: _ensure_tools
cargo nextest run
# Mutation testing
test-mutants *ARGS:
cargo mutants {{ARGS}}
test-mutants *args: _ensure_tools
cargo mutants {{args}}
# Fuzz a target (requires nightly)
test-fuzz TARGET="fuzz_gossip_frame_decode" DURATION="60":
test-fuzz TARGET="fuzz_gossip_frame_decode" DURATION="60": _ensure_tools
cargo +nightly fuzz run {{TARGET}} -- -max_total_time={{DURATION}}
# Security + license audit
audit: _ensure_tools
cargo deny check
# Build release binary
build: _ensure_tools
cargo build --release
# Generate docs
doc: _ensure_tools
cargo doc --no-deps --open
# Check for outdated dependencies
outdated: _ensure_tools
cargo outdated
# Clean build artifacts
clean:
cargo clean
+3
View File
@@ -0,0 +1,3 @@
[tools]
rust = "1.94"
just = "latest"
+8 -25
View File
@@ -6,7 +6,7 @@
use std::time::Duration;
use anyhow::{Context, Result};
use donglora_client::{RadioConfig, Response, Transport};
use donglora_client::{AnyTransport, RadioConfig, Response, Transport};
use tokio::sync::mpsc;
use tracing::{debug, error, info, warn};
@@ -93,30 +93,13 @@ fn connect_and_run(
let timeout = Duration::from_secs(10);
info!("connecting to dongle...");
let (mut client, device) = if let Some(port) = port {
let c = donglora_client::connect(Some(port), timeout)?;
(c, shorten_path(port))
} else if let Ok(c) = donglora_client::connect_mux_auto(timeout) {
(c, "mux".to_string())
} else {
let port_path = donglora_client::find_port().ok_or_else(|| anyhow::anyhow!("no mux or USB dongle found"))?;
info!("no mux found, connecting to serial port {port_path}");
let c = donglora_client::connect(Some(&port_path), timeout)?;
(c, shorten_path(&port_path))
};
info!("[radio] transport connected: {device}");
info!("[radio] sending ping...");
match client.ping() {
Ok(()) => info!("[radio] ping OK"),
Err(e) => {
info!("[radio] ping FAILED: {e:#}");
return Err(e);
}
}
let mut client = donglora_client::connect(port, timeout)?;
let is_mux = matches!(client.transport(), AnyTransport::Mux(_));
let device = if is_mux { "mux".to_string() } else { port.map_or_else(|| "usb".to_string(), shorten_path) };
info!("[radio] connected and validated: {device}");
info!("[radio] negotiating config...");
let (active_config, config_source) = match negotiate_config(&mut client, config, &device) {
let (active_config, config_source) = match negotiate_config(&mut client, config, is_mux) {
Ok(result) => {
info!("[radio] config negotiated: source={:?}, config={}", result.1, format_radio_config(&result.0));
result
@@ -215,7 +198,7 @@ fn connect_and_run(
fn negotiate_config<T: Transport>(
client: &mut donglora_client::Client<T>,
desired: &RadioConfig,
device: &str,
is_mux: bool,
) -> Result<(RadioConfig, ConfigSource)> {
info!("[negotiate] get_config...");
match client.get_config() {
@@ -223,7 +206,7 @@ fn negotiate_config<T: Transport>(
if configs_match(&cfg, desired) {
info!("[negotiate] get_config OK (matches desired): {}", format_radio_config(&cfg));
Ok((cfg, ConfigSource::Ours))
} else if device == "mux" {
} else if is_mux {
info!("[negotiate] get_config OK (mux config differs, accepting): {}", format_radio_config(&cfg));
Ok((cfg, ConfigSource::Mux))
} else {