- Rust 94.6%
- Shell 5.4%
|
All checks were successful
Create tag on dev / rust-crate-checks (push) Successful in 20s
Create tag on dev / checks (push) Successful in 0s
Create tag on dev / tag_prerelease (push) Successful in 9s
Publish release on tag / rust-crate-checks (push) Successful in 17s
Create tag on dev / tag (push) Successful in 0s
Publish release on tag / publish (push) Successful in 0s
Publish release on tag / checks (push) Successful in 0s
Publish release on tag / release_on_tag (push) Successful in 11s
Reviewed-on: #5 |
||
|---|---|---|
| .cargo | ||
| .claude | ||
| .forgejo | ||
| scripts | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| deny.toml | ||
| LICENSE | ||
| README.md | ||
tools
Important
The canonical repository for this project lives on Forgejo: https://code.bhk-itsolutions.com/homeiot/tools.git. This GitHub repository is only a mirror and is not the primary git remote.
no_std embedded utilities: encryption, MAC address generation, entropy/random, SPI register I/O, LZSS compression, and PWM RGB LED control.
Features
Every module sits behind its own cargo feature. No features are enabled by
default: build and test with --all-features to exercise the whole crate
(CI runs its gates with --all-features). Consumers should set
default-features = false and enable only the features they use — each
feature pulls only its own dependencies.
| Feature | Description |
|---|---|
encryption |
ChaCha20-Poly1305 in-place AEAD (slice API, nonce-prefixed wire frames). |
mac |
Mac identity class: hardware UID to MAC display string and PeerId conversions. |
random |
Entropy trait: byte generation from a raw hardware entropy source. |
register |
SPI register protocols with chip-select and delay (msb_flag, st_mems). |
compression |
LZSS compress/decompress using stack-based buffers. |
rgb |
PWM-driven RGB LED controller (duty-cycle setter + delay, active-high/low). |
Recommended consumer setup — explicit features, from the Forgejo registry:
[dependencies]
tools = { version = "1.1", default-features = false, registry = "forgejo", features = ["encryption", "mac", "compression"] }
Configure the Forgejo registry in your project (e.g. .cargo/config.toml):
[registries.forgejo]
index = "sparse+https://code.bhk-itsolutions.com/api/packages/homeiot/cargo/"
Use your instance URL and index path if different. A token (e.g. CARGO_REGISTRIES_FORGEJO_TOKEN) is required for private registries.
Project structure
src/
├── lib.rs # Crate docs and feature-gated module declarations
├── register/ # SPI register I/O (embedded-hal)
│ ├── bus.rs # Chip-select timing, transfers, BusError, 32-byte frame cap
│ ├── msb_flag.rs # Bit-7 read/write flag protocol (e.g. SX1278 radio)
│ └── st_mems.rs # STMicro MEMS SPI framing (LIS2DH12, LIS3DH)
├── encryption/ # ChaCha20-Poly1305 (feature "encryption")
│ └── controller.rs
├── mac/ # Mac identity class (feature "mac")
├── random/ # Entropy trait (feature "random")
├── compression/ # LZSS (feature "compression")
└── rgb/ # PWM RGB LED (feature "rgb")
Each module directory carries its unit tests in a tests/mod.rs subdirectory.
Usage examples
- MAC:
tools::Macis the single source of truth for identity conversions:Mac::new(uid)/Mac::from_serial_str(serial)build the identity from the 16-byte hardware UID;mac_string()returns the colon-separated display MAC ("XX:XX:XX:XX:XX:XX", uppercase hex) as aheapless::String<17>;peer_id()returns the 17-byte wire PeerId;Mac::peer_id_from_mac_str/Mac::peer_id_from_hex/Mac::peer_id_to_hexconvert text forms to/from the wire form. - Encryption: in-place slice API —
encryption::controller::encrypt(buffer, msg_len, &key, &nonce, aad)encryptsbuffer[..msg_len]and overwrites it with the wire framenonce (12) || ciphertext || tag (16), returning the frame length;encryption::controller::decrypt(buffer, data_len, &key, aad)decryptsbuffer[..data_len]in place and returns the plaintext length. The buffer must have room for the message plus the fixed 28-byte frame overhead (buffer.len() >= msg_len + 28, i.e. 12-byte nonce + 16-byte tag); both returnResult<usize, EncryptionError>instead of panicking. - Random: implement
random::Entropy::get_bitfor your hardware entropy source andrand::<N, _>(&mut delay) -> Result<[u8; N], random::EntropyError>comes for free: raw bits are von Neumann-debiased in pairs (01→0,10→1,00/11discarded), assembled least significant bit first. Two fail-closed health checks guard the call: 640+ consecutive identical raw samples returnEntropyError::Degenerate(stuck source), and a per-call cap of256 × Nraw samples returnsEntropyError::Exhaustedinstead of blocking forever on a marginal source. Each raw sample costs a 10 µs settling delay: nominally ≈ 320 µs per byte (≈ 10.24 ms per 32-byte nonce) at p = 0.5, ≈ 28.4 ms worst in-range (p = 0.9), hard cap ≈ 81.9 ms per 32-byte nonce. The checks are a best-effort sanity net (stuck sources and long runs — not periodic or short-correlation sequences), not a statistical certification: characterize your hardware source. - Compression:
compression::compress(msg, &mut out)/compression::decompress(&input, &mut out)— both returnResult<usize, compression::CompressionError>.compressreturnsNoSizeReductionwhen the compressed length is not strictly smaller than the input. - RGB:
RgbPwm::new(set_rgb, delay, max_duty)builds an active-high controller andRgbPwm::new_active_low(set_rgb, delay, max_duty)an active-low one, whereset_rgb: FnMut(u32, u32, u32)sets the red/green/blue duty cycles at once anddelay: FnMut(u32)sleeps for the given milliseconds; then call.set_color(Color::Green)etc., or.set_raw(r, g, b)for custom per-channel duties. - Register: two first-class protocols, both fully fallible —
register::msb_flag::{read_register, write_register}(bit-7 read/write flag framing, as required by the SX1278 radio) andregister::st_mems::{read_register, write_register, read_registers}(STMicro MEMS sensors). Example:
use tools::register::{msb_flag, st_mems};
// MSB-flag protocol (SX1278): read one register, write a payload.
let status: u8 = msb_flag::read_register(&mut spi, &mut cs, 0x42, &mut delay)?;
msb_flag::write_register(&mut spi, &mut cs, 0x42, &[0x0A, 0x0B], &mut delay)?;
// STMicro MEMS (LIS2DH12): burst-read six bytes with auto-increment.
let mut data = [0u8; 6];
st_mems::read_registers(&mut spi, &mut cs, 0x28, &mut data, &mut delay)?;
st_mems::write_register(&mut spi, &mut cs, 0x20, &[0x17], &mut delay)?;
Every register function returns Result<_, register::bus::BusError<SPI::Error, CS::Error>>: BusError::Spi(S) for a failed SPI transfer, BusError::Cs(C) for a failed chip-select transition, BusError::PayloadTooLong when the payload exceeds the 32-byte frame cap (register::bus::MAX_PAYLOAD) — oversized requests are rejected instead of being truncated — and BusError::InvalidRegister when a ST-MEMS register address exceeds the 6-bit AD field (reg > 0x3F): out-of-range addresses are rejected before any bus access in every build mode, instead of being silently truncated into a different valid address.
Requirements
- Rust (edition 2021),
no_std. - Dependencies (all optional, pulled per feature):
embedded-hal1.x (random,register),chacha20poly1305(encryption),lzss(compression),heapless(mac).
Tests
Full coverage runs with all features enabled (none are on by default, so a
plain cargo test --locked exercises no features):
cargo test --locked --all-features
The full release gate (version sync, format, clippy, docs, tests):
bash scripts/test.sh
Status
Work in progress. API may change.
License
Personal Use Only — Non-Commercial. See LICENSE.