CyberKey's crate split is designed so that all business logic is testable on a laptop without hardware.
The rule: if code does not touch a hardware peripheral, it belongs in a no_std portable crate with unit tests. Firmware (firmware/) is a thin integration layer that wires portable crates to hardware drivers.
cargo test --package cyberkey-core- TOTP known-vector tests (RFC 6238 Appendix B — SHA-1 at T=0, T=1, etc.)
- Config validation (duplicate
finger_id, label length, secret length) - Config serialization/deserialization roundtrip
- BCD encoding/decoding for RTC timestamps
The crate uses #![cfg_attr(not(test), no_std)]. Tests compile with std enabled, firmware compilation uses no_std. Same code, different feature sets.
cargo test --package fingerprint2-rs~28 unit tests covering:
- Packet framing (serialize/deserialize)
- Checksum computation for known packet sequences
- Command encoding (enroll, identify, LED color)
- Error parsing (sensor error codes →
FingerprintErrorvariants) - Wakeup packet detection (
is_wakeup_packetfor the exact 12-byte sequence) - Mock UART round-trips (send command, fake sensor response, check parsed result)
The driver is generic over embedded_hal_nb::serial::{Read, Write}. Tests use a MockUart that is two Vec<u8> buffers (RX and TX). No hardware needed.
cargo test --package cyberkey-hid- All printable ASCII bytes (0x20–0x7E) map to a non-zero keycode
- Uppercase letters have the Shift modifier set
- Digits and symbols match the USB HID usage table
- Numpad keycodes cover 0–9 without gaps
The table is const, so tests catch misaligned entries at compile time.
cargo test --package cyberkey-cliLight coverage:
- JSON command serialization matches expected wire format
- JSON response deserialization handles
ok:falsewith error field - Secret masking (the first N characters of a secret are hidden in
list_entriesoutput)
End-to-end CLI tests (against a real device) are manual — see below.
# From workspace root
cargo test --exclude firmwareThis runs all tests for all crates except the firmware crate, which requires the Xtensa toolchain and cannot be cross-compiled to your host target.
The firmware crate has no unit tests. Testing is manual smoke testing on a physical device.
# One-time: install the Xtensa toolchain
cargo install espup
espup install
source ~/.espup/export-esp.shcd firmware
cargo run --release # builds, flashes, and opens serial monitorBoot:
- Device powers on, status bar shows time + battery
- If no bonds in NVS: LCD shows pairing screen with passkey
- If bonds exist: LCD shows "Connecting..." then "Connected" (or "Waiting..." if host is off)
Buttons:
- Button B short press: opens BLE pairing window for 60 s
- Button A long-press (1.5 s): prompts to confirm bond clear
- Button A long-press × 2 within 10 s: erases bonds, reboots
BLE Pairing:
- Host OS detects CyberKey in Bluetooth settings
- Host prompts for passkey; enter the code shown on LCD
- Bond completes; LCD shows "Connected"
- Reboot device; host reconnects automatically (no re-pairing)
Fingerprint + TOTP:
- CLI: add an entry (
add_entry, label = "Test", secret = valid base32) - Complete 3-step enrollment on device
- Place enrolled finger on sensor → service label and TOTP code appear on the display, code is typed into the focused text field
- Place wrong finger → red LED flash, no typing
CLI:
-
cyberkey-cliauto-detects port, sendssync_clock, shows menu -
list_entriesshows enrolled entries with masked secrets -
remove_entryremoves an entry; subsequent finger match returns no-match -
factory_reset(type "RESET" to confirm) wipes all entries and reboots
Clock sync:
- After
sync_clock, LCD shows correct local time - After reboot without CLI session, LCD still shows correct local time (RTC persisted)
- TOTP codes match a reference authenticator app
| Area | Gap | Notes |
|---|---|---|
| CLI end-to-end | No automated serial loopback tests | Would require a hardware-in-the-loop setup |
| BLE pairing | Manual only | No emulated BLE host |
| NVS encryption | Not directly tested | Tested implicitly by add/list/remove on a flashed device |
| Power management | Manual only | Current draw requires a multimeter to verify |
| Long-press timing | Not unit-tested | Threshold (1.5 s) is a constant; test would be a tautology |