hsm_usb_protocol/lib.rs
1// Copyright (c) 2026 Tuloup Simon
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! USB-HID protocol used between the mini-HSM dongle and the host.
17//!
18//! This crate is `no_std` by default so the firmware can depend on it. The
19//! `std` feature toggles a handful of niceties (currently nothing, kept as
20//! a future extension point).
21//!
22//! # Wire format
23//!
24//! Every exchange consists of fixed-size HID reports of [`HID_REPORT_SIZE`]
25//! bytes. The same 3-byte header is used in both directions:
26//!
27//! ```text
28//! Byte 0 : Opcode (command opcode for IN reports, status byte for OUT)
29//! Byte 1..3 : Payload length, little-endian u16
30//! Byte 3..3+len : Payload
31//! Byte 3+len..end : Zero padding (sent zero, ignored on receive)
32//! ```
33//!
34//! `len` is the host-meaningful payload size, not the wire size. The wire
35//! size is always [`HID_REPORT_SIZE`] regardless. `len` must not exceed
36//! [`MAX_PAYLOAD_SIZE`].
37//!
38//! # Layering
39//!
40//! [`Frame`] encodes/decodes the byte layout. It is opcode-agnostic and
41//! deals only with `(u8, &[u8])`. Both the firmware and `hsm-host` build
42//! one [`Frame`] per HID report, then interpret the payload with helpers
43//! defined in [`commands`] and [`responses`]. There is no big `enum
44//! Command { Info, Sign { slot, digest }, ... }`: that pattern scales
45//! poorly with size and pays runtime cost for static information.
46
47#![cfg_attr(not(feature = "std"), no_std)]
48#![deny(missing_docs)]
49#![deny(unsafe_code)]
50#![warn(clippy::pedantic)]
51#![allow(rustdoc::private_intra_doc_links)]
52
53pub mod commands;
54pub mod frame;
55pub(crate) mod hid_descriptor;
56pub mod responses;
57
58pub use commands::CommandOpcode;
59pub use frame::{Frame, FrameParseError};
60pub use hid_descriptor::HID_REPORT_DESCRIPTOR;
61pub use responses::ResponseStatus;
62
63/// USB vendor identifier for this project. `0xCAFE` is a community
64/// convention for open-source / hobby devices (no USB-IF assignment).
65pub const USB_VID: u16 = 0xCAFE;
66
67/// USB product identifier for this project.
68pub const USB_PID: u16 = 0x1312;
69
70/// Fixed size of every HID report in either direction.
71///
72/// 128 bytes is enough to carry a single ECDSA P-256 signature (64 bytes)
73/// or a single public key (64 bytes) in one report with room to spare for
74/// the 3-byte header and any prefix bytes (slot id, block index, etc).
75/// Larger transfers like the 128-byte config zone are split across multiple
76/// reports by the caller.
77pub const HID_REPORT_SIZE: usize = 128;
78
79/// Size of the 3-byte header (`opcode | len_lo | len_hi`).
80pub(crate) const HEADER_SIZE: usize = 3;
81
82/// Maximum payload size in a report (`HID_REPORT_SIZE` - [`HEADER_SIZE`]).
83pub(crate) const MAX_PAYLOAD_SIZE: usize = HID_REPORT_SIZE - HEADER_SIZE;