1use anyhow::{anyhow, bail, Context, Result};
23use hidapi::{HidApi, HidDevice};
24
25use atecc608b::{AteccErrorKind, ChipError};
26use hsm_usb_protocol::{Frame, HID_REPORT_SIZE, USB_PID, USB_VID};
27use hsm_usb_protocol::responses::ResponseStatus;
28
29pub(crate) fn open() -> Result<HidDevice>
31{
32 let api = HidApi::new().context("failed to initialise hidapi")?;
33 let device = api
34 .open(USB_VID, USB_PID)
35 .with_context(|| format!
36 (
37 "failed to open HID device {:04X}:{:04X}. Is the dongle plugged in?",
38 USB_VID, USB_PID
39 ))?;
40 Ok(device)
41}
42
43pub(crate) fn enumerate() -> Result<()>
45{
46 let api = HidApi::new().context("failed to initialise hidapi")?;
47 let mut matches = 0;
48 for info in api.device_list()
49 {
50 let is_mini_hsm = info.vendor_id() == USB_VID && info.product_id() == USB_PID;
51 if is_mini_hsm
52 {
53 matches += 1;
54 }
55 let marker = if is_mini_hsm { "<-- mini-HSM" } else { "" };
56 println!
57 (
58 "{:04x}:{:04x} {} / {} {}",
59 info.vendor_id(),
60 info.product_id(),
61 info.manufacturer_string().unwrap_or("(no manufacturer)"),
62 info.product_string().unwrap_or("(no product)"),
63 marker,
64 );
65 }
66 println!();
67 println!("{matches} mini-HSM device(s) found.");
68 Ok(())
69}
70
71pub(crate) fn send_command
78(
79 device: &HidDevice,
80 opcode: u8,
81 payload: &[u8],
82) -> Result<Vec<u8>>
83{
84 let request = Frame::to_report(opcode, payload).map_err
85 (|e|
86 anyhow!("failed to encode request frame: {:?}", e)
87 )?;
88
89 let mut wire = [0u8; HID_REPORT_SIZE + 1];
92 wire[0] = 0;
93 wire[1..].copy_from_slice(&request);
94 device.write(&wire).context("HID write failed")?;
95
96 let mut response = [0u8; HID_REPORT_SIZE];
97 let n = device
98 .read_timeout(&mut response, 60_000)
99 .context("HID read failed")?;
100 if n != HID_REPORT_SIZE
101 {
102 bail!("short HID read: got {n} bytes, expected {HID_REPORT_SIZE}");
103 }
104
105 let frame = Frame::parse(&response)
106 .map_err(|e| anyhow!("malformed response frame: {:?}", e))?;
107
108 if frame.opcode != ResponseStatus::Ok.as_u8()
109 {
110 let status_name = describe_status(frame.opcode);
111
112 if frame.opcode == ResponseStatus::WrongPin.as_u8() && frame.payload.len() == 1
113 {
114 bail!(
115 "chip returned WrongPin: {} attempt(s) remaining before block",
116 frame.payload[0],
117 );
118 }
119 if frame.opcode == ResponseStatus::WrongPuk.as_u8() && frame.payload.len() == 1
120 {
121 bail!(
122 "chip returned WrongPuk: {} attempt(s) remaining before brick",
123 frame.payload[0],
124 );
125 }
126 if frame.opcode == ResponseStatus::EmergencyResetNotPermitted.as_u8()
127 && frame.payload.len() == 2
128 {
129 bail!(
130 "chip refused EmergencyReset: {} PIN attempt(s) and {} PUK attempt(s) \
131 still remain. Use `verify-pin` / `unblock-pin` to recover instead.",
132 frame.payload[0],
133 frame.payload[1],
134 );
135 }
136 if frame.opcode == ResponseStatus::AteccChipError.as_u8() && frame.payload.len() == 1
137 {
138 let raw = frame.payload[0];
139 let chip = ChipError::from_status_byte(raw);
140 bail!(
141 "chip returned chip-level error: {} (raw status byte 0x{:02x})",
142 describe_chip_error(chip),
143 raw,
144 );
145 }
146 if frame.opcode == ResponseStatus::AteccCommunicationError.as_u8()
147 && frame.payload.len() == 1
148 {
149 let sub = frame.payload[0];
150 let kind = AteccErrorKind::from_sub_code(sub);
151 bail!(
152 "chip returned communication error: {} (sub-code 0x{:02x})",
153 describe_atecc_kind(kind),
154 sub,
155 );
156 }
157 if frame.payload.is_empty()
158 {
159 bail!("chip returned status 0x{:02x} ({status_name})", frame.opcode);
160 }
161 else
162 {
163 bail!
164 (
165 "chip returned status 0x{:02x} ({status_name}), data: {}",
166 frame.opcode,
167 hex::encode(frame.payload),
168 );
169 }
170 }
171
172 Ok(frame.payload.to_vec())
173}
174
175fn describe_status(byte: u8) -> &'static str
176{
177 match ResponseStatus::from_byte(byte)
181 {
182 Some(ResponseStatus::Ok) => "Ok",
183 Some(ResponseStatus::InvalidCommand) => "InvalidCommand",
184 Some(ResponseStatus::InvalidPayload) => "InvalidPayload",
185 Some(ResponseStatus::InvalidSlot) => "InvalidSlot",
186 Some(ResponseStatus::AteccCommunicationError) => "AteccCommunicationError",
187 Some(ResponseStatus::AteccChipError) => "AteccChipError",
188 Some(ResponseStatus::TouchTimeout) => "TouchTimeout",
189 Some(ResponseStatus::NotProvisioned) => "NotProvisioned",
190 Some(ResponseStatus::LockMagicMismatch) => "LockMagicMismatch",
191 Some(ResponseStatus::LockCrcMismatch) => "LockCrcMismatch",
192 Some(ResponseStatus::Busy) => "Busy",
193 Some(ResponseStatus::WrongPin) => "WrongPin",
194 Some(ResponseStatus::PinRequired) => "PinRequired",
195 Some(ResponseStatus::PinBlocked) => "PinBlocked",
196 Some(ResponseStatus::WrongPuk) => "WrongPuk",
197 Some(ResponseStatus::Bricked) => "Bricked",
198 Some(ResponseStatus::EmergencyResetNotPermitted) => "EmergencyResetNotPermitted",
199 None => "Unknown",
200 }
201}
202
203fn describe_chip_error(err: Option<ChipError>) -> &'static str
206{
207 match err
208 {
209 Some(ChipError::CheckMacOrVerifyFailed) => "CheckMacOrVerifyFailed",
210 Some(ChipError::ParseError) => "ParseError",
211 Some(ChipError::EccFault) => "EccFault",
212 Some(ChipError::SelfTestFailed) => "SelfTestFailed",
213 Some(ChipError::HealthTestFailed) => "HealthTestFailed",
214 Some(ChipError::ExecutionError) => "ExecutionError",
215 Some(ChipError::WatchdogAboutToExpire) => "WatchdogAboutToExpire",
216 Some(ChipError::CommandCrcError) => "CommandCrcError",
217 Some(ChipError::Unknown(_)) => "Unknown(...)",
218 None => "Success(0x00, unexpected here)",
219 }
220}
221
222fn describe_atecc_kind(kind: Option<AteccErrorKind>) -> &'static str
225{
226 match kind
227 {
228 Some(AteccErrorKind::Hal) => "Hal (I2C nack / GPIO failure)",
229 Some(AteccErrorKind::WakeFailed) => "WakeFailed (wrong wake pattern)",
230 Some(AteccErrorKind::SelfTestFailure) => "SelfTestFailure (chip self-test failed at wake)",
231 Some(AteccErrorKind::BadCrc) => "BadCrc (response CRC mismatch)",
232 Some(AteccErrorKind::Timeout) => "Timeout (chip polling timed out)",
233 Some(AteccErrorKind::MalformedResponse) => "MalformedResponse (inconsistent length byte)",
234 Some(AteccErrorKind::BufferTooSmall) => "BufferTooSmall (driver buffer too small)",
235 Some(AteccErrorKind::Chip(_)) => "Chip(_) (unexpected here, see AteccChipError)",
236 None => "Unknown sub-code",
237 }
238}