No description
  • Rust 94.6%
  • Shell 5.4%
Find a file
faicel fcd4342a49
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
Merge pull request 'fix/audit-security' (#5) from fix/audit-security into dev
Reviewed-on: #5
2026-09-05 13:39:27 +00:00
.cargo Update dependencies, enhance compression error handling, and improve CI workflows. Added CHANGELOG.md to version control and refined README.md for clarity. 2026-04-05 08:30:14 +02:00
.claude Update dependencies, enhance compression error handling, and improve CI workflows. Added CHANGELOG.md to version control and refined README.md for clarity. 2026-04-05 08:30:14 +02:00
.forgejo Add codeOwners 2026-09-05 15:38:41 +02:00
scripts Make the default feature set empty and run CI gates with --all-features 2026-09-04 19:44:57 +02:00
src fix(register): reject out-of-range ST-MEMS register addresses in all build modes 2026-09-04 21:43:55 +02:00
.gitignore chore: bootstrap changelog unreleased section and fix config hygiene 2026-09-04 16:27:03 +02:00
Cargo.lock update version 2026-09-04 21:50:30 +02:00
Cargo.toml update version 2026-09-04 21:50:30 +02:00
CHANGELOG.md Add codeOwners 2026-09-05 15:38:41 +02:00
deny.toml Drop the unused heapless feature from chacha20poly1305 2026-09-04 19:38:43 +02:00
LICENSE init 2026-03-07 21:58:43 +01:00
README.md fix(register): reject out-of-range ST-MEMS register addresses in all build modes 2026-09-04 21:43:55 +02:00

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::Mac is 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 a heapless::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_hex convert text forms to/from the wire form.
  • Encryption: in-place slice API — encryption::controller::encrypt(buffer, msg_len, &key, &nonce, aad) encrypts buffer[..msg_len] and overwrites it with the wire frame nonce (12) || ciphertext || tag (16), returning the frame length; encryption::controller::decrypt(buffer, data_len, &key, aad) decrypts buffer[..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 return Result<usize, EncryptionError> instead of panicking.
  • Random: implement random::Entropy::get_bit for your hardware entropy source and rand::<N, _>(&mut delay) -> Result<[u8; N], random::EntropyError> comes for free: raw bits are von Neumann-debiased in pairs (01 → 0, 10 → 1, 00/11 discarded), assembled least significant bit first. Two fail-closed health checks guard the call: 640+ consecutive identical raw samples return EntropyError::Degenerate (stuck source), and a per-call cap of 256 × N raw samples returns EntropyError::Exhausted instead 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 return Result<usize, compression::CompressionError>. compress returns NoSizeReduction when the compressed length is not strictly smaller than the input.
  • RGB: RgbPwm::new(set_rgb, delay, max_duty) builds an active-high controller and RgbPwm::new_active_low(set_rgb, delay, max_duty) an active-low one, where set_rgb: FnMut(u32, u32, u32) sets the red/green/blue duty cycles at once and delay: 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) and register::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-hal 1.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.