diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..96d1a74 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this crate are documented here. The format +follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and +this project adheres to [Semantic Versioning](https://semver.org/). + +## 0.1.0 — 2026-04-13 + +Initial release. The DongLoRa wire-protocol types and COBS framing, +extracted from the firmware so the firmware, `client-rs`, `mux-rs`, +and `bridge` can share one definition. + +### Added + +- Wire types: `Command`, `Response`, `RadioConfig`, `Bandwidth`, + `ErrorCode`, plus the `MAX_PAYLOAD`, `RADIO_CONFIG_SIZE`, + `TX_POWER_MAX`, and `PREAMBLE_DEFAULT` constants. Fixed-width + little-endian on the wire — see the firmware repo's `PROTOCOL.md`. +- `framing` module: streaming `CobsDecoder` plus `cobs_encode_response`, + with a `MAX_FRAME` constant sized to fit any valid `Response` after + COBS overhead. +- Cargo features: `defmt` (default, derives `defmt::Format` on public + enums) and `std` (off, opt-in for host-side `Debug` niceties). +- Test surface: 23 proptest properties (~1.13M cases per run), 4 + cargo-fuzz targets, 16 canonical wire-byte vectors anchored to the + spec, 6 mutation-killers. Mutation testing reaches 89/89 viable + mutants under both feature configurations (3 provably-equivalent + mutants documented and excluded). Miri runs clean on the library + and the canonical-vector tests. +- `forbid(unsafe_code)` at the crate root. +- GitHub Actions CI (test/clippy/fmt/doc/deny/miri/fuzz/mutants). diff --git a/Cargo.toml b/Cargo.toml index 0360d05..5e588fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,20 @@ license = "MIT" description = "DongLoRa wire protocol types and COBS framing — shared between firmware and host crates" repository = "https://github.com/donglora/protocol" homepage = "https://github.com/donglora/protocol" +documentation = "https://docs.rs/donglora-protocol" +readme = "README.md" keywords = ["lora", "radio", "protocol", "no-std"] categories = ["embedded", "no-std", "encoding"] -exclude = ["fuzz", ".jj"] +exclude = [ + ".github", + ".jj", + "deny.toml", + "fuzz", + "justfile", + "mise.toml", + "mutants.out", + "mutants.out.old", +] [dependencies] heapless = "0.9" diff --git a/README.md b/README.md index bf7f505..ae1db6e 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,57 @@ # donglora-protocol -Wire protocol types and COBS framing for DongLoRa, shared between firmware -and host crates (`client-rs`, `mux-rs`, `bridge`). +[![CI](https://github.com/donglora/protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/donglora/protocol/actions/workflows/ci.yml) +[![Crates.io](https://img.shields.io/crates/v/donglora-protocol.svg)](https://crates.io/crates/donglora-protocol) +[![Docs.rs](https://docs.rs/donglora-protocol/badge.svg)](https://docs.rs/donglora-protocol) -`no_std` by default. `defmt::Format` derives are opt-in via the default -`defmt` feature. Host consumers disable default features: +Wire protocol types and COBS framing for [DongLoRa], shared between the +firmware and host crates (`client-rs`, `mux-rs`, `bridge`). `no_std`, +zero-alloc, fixed-width little-endian — see the firmware repo's +[`PROTOCOL.md`][protocol-md] for the full spec. + +[DongLoRa]: https://github.com/donglora +[protocol-md]: https://github.com/donglora/firmware/blob/main/PROTOCOL.md + +## Install ```toml +# Embedded / firmware (defmt::Format derives on by default): +donglora-protocol = "0.1" + +# Host (no defmt): donglora-protocol = { version = "0.1", default-features = false } ``` -## Status - -Pre-1.0. Currently consumed by path dep; will publish to crates.io once -the surface area settles. - ## Layout -- `src/lib.rs` — `Command`, `Response`, `RadioConfig`, `ErrorCode`, etc. -- `src/framing.rs` — `CobsDecoder` + `cobs_encode_response` for USB/UART - transports. -- `tests/` — proptest suite (roundtrips, bounded-capacity invariants, - regional-regs, duty-cycle math). -- `fuzz/` — cargo-fuzz targets on the parser and framer. +- `src/lib.rs` — `Command`, `Response`, `RadioConfig`, `Bandwidth`, + `ErrorCode` plus constants and validation/resolve helpers. +- `src/framing.rs` — `CobsDecoder` (streaming) and `cobs_encode_response`. + +## Features + +| Feature | Default | What it does | +|---------|---------|--------------| +| `defmt` | yes | Derive `defmt::Format` on public enums. | +| `std` | no | Enable `Debug`-based niceties for host-side fuzz / proptest. | + +## Testing + +The crate ships its own quality gate via `just test`: + +| Gate | What runs | +|------|-----------| +| `test-unit` | nextest across all feature permutations + doc tests | +| `test-clippy` | `-D warnings` across all feature permutations | +| `test-fmt` | `cargo fmt -- --check` | +| `test-doc` | `cargo doc --no-deps --all-features` | +| `test-deny` | `cargo deny check` | +| `test-fuzz` | 60 s on each of 7 cargo-fuzz targets (nightly) | +| `test-miri` | miri on lib + vector tests, both feature shapes (nightly) | +| `test-mutants` | cargo-mutants under both feature configurations | + +The proptest suite alone exercises ~1.13M test cases per run. + +## License + +MIT — see [`LICENSE`](LICENSE). diff --git a/src/framing.rs b/src/framing.rs index 0f4f2ac..d10efeb 100644 --- a/src/framing.rs +++ b/src/framing.rs @@ -55,9 +55,10 @@ impl CobsDecoder { if self.len > 0 { let mut decode_buf = [0u8; MAX_FRAME]; if let Some(decoded_len) = ucobs::decode(&self.buf[..self.len], &mut decode_buf) - && let Some(cmd) = Command::from_bytes(&decode_buf[..decoded_len]) { - on_command(cmd); + if let Some(cmd) = Command::from_bytes(&decode_buf[..decoded_len]) { + on_command(cmd); + } } } self.len = 0;