1use core::fmt;
7use core::str::FromStr;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14#[repr(u8)]
15pub enum StatusCode {
16 Success = 0x00,
18
19 InvalidCommand = 0x01,
21
22 InvalidParameter = 0x02,
24
25 InvalidLength = 0x03,
27
28 InvalidSeq = 0x04,
30
31 Timeout = 0x05,
33
34 ChannelBusy = 0x06,
36
37 LockRequired = 0x0A,
39
40 InvalidChannel = 0x0B,
42
43 CborUnexpectedType = 0x11,
45
46 InvalidCbor = 0x12,
48
49 MissingParameter = 0x14,
51
52 LimitExceeded = 0x15,
54
55 UnsupportedExtension = 0x16,
57
58 CredentialExcluded = 0x19,
60
61 Processing = 0x21,
63
64 InvalidCredential = 0x22,
66
67 UserActionPending = 0x23,
69
70 OperationPending = 0x24,
72
73 NoOperations = 0x25,
75
76 UnsupportedAlgorithm = 0x26,
78
79 OperationDenied = 0x27,
81
82 KeyStoreFull = 0x28,
84
85 NotBusy = 0x29,
87
88 NoOperationPending = 0x2A,
90
91 UnsupportedOption = 0x2B,
93
94 InvalidOption = 0x2C,
96
97 KeepaliveCancel = 0x2D,
99
100 NoCredentials = 0x2E,
102
103 UserActionTimeout = 0x2F,
105
106 NotAllowed = 0x30,
108
109 PinInvalid = 0x31,
111
112 PinBlocked = 0x32,
114
115 PinAuthInvalid = 0x33,
117
118 PinAuthBlocked = 0x34,
120
121 PinNotSet = 0x35,
123
124 PinRequired = 0x36,
126
127 PinPolicyViolation = 0x37,
129
130 PinTokenExpired = 0x38,
132
133 RequestTooLarge = 0x39,
135
136 ActionTimeout = 0x3A,
138
139 UpRequired = 0x3B,
141
142 UvBlocked = 0x3C,
144
145 IntegrityFailure = 0x3D,
147
148 InvalidSubcommand = 0x3E,
150
151 UvInvalid = 0x3F,
153
154 UnauthorizedPermission = 0x40,
156
157 PuatRequired = 0x41,
159
160 Other = 0x7F,
162}
163
164impl fmt::Display for StatusCode {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 let msg = match self {
167 Self::Success => "Success",
168 Self::InvalidCommand => "Invalid command",
169 Self::InvalidParameter => "Invalid parameter",
170 Self::InvalidLength => "Invalid length",
171 Self::InvalidSeq => "Invalid sequence",
172 Self::Timeout => "Timeout",
173 Self::ChannelBusy => "Channel busy",
174 Self::LockRequired => "Lock required",
175 Self::InvalidChannel => "Invalid channel",
176 Self::CborUnexpectedType => "CBOR unexpected type",
177 Self::InvalidCbor => "Invalid CBOR",
178 Self::MissingParameter => "Missing parameter",
179 Self::LimitExceeded => "Limit exceeded",
180 Self::UnsupportedExtension => "Unsupported extension",
181 Self::CredentialExcluded => "Credential excluded",
182 Self::Processing => "Processing",
183 Self::InvalidCredential => "Invalid credential",
184 Self::UserActionPending => "User action pending",
185 Self::OperationPending => "Operation pending",
186 Self::NoOperations => "No operations",
187 Self::UnsupportedAlgorithm => "Unsupported algorithm",
188 Self::OperationDenied => "Operation denied",
189 Self::KeyStoreFull => "Key store full",
190 Self::NotBusy => "Not busy",
191 Self::NoOperationPending => "No operation pending",
192 Self::UnsupportedOption => "Unsupported option",
193 Self::InvalidOption => "Invalid option",
194 Self::KeepaliveCancel => "Keepalive cancel",
195 Self::NoCredentials => "No credentials",
196 Self::UserActionTimeout => "User action timeout",
197 Self::NotAllowed => "Not allowed",
198 Self::PinInvalid => "PIN invalid",
199 Self::PinBlocked => "PIN blocked",
200 Self::PinAuthInvalid => "PIN auth invalid",
201 Self::PinAuthBlocked => "PIN auth blocked",
202 Self::PinNotSet => "PIN not set",
203 Self::PinRequired => "PIN required",
204 Self::PinPolicyViolation => "PIN policy violation",
205 Self::PinTokenExpired => "PIN token expired",
206 Self::RequestTooLarge => "Request too large",
207 Self::ActionTimeout => "Action timeout",
208 Self::UpRequired => "UP required",
209 Self::UvBlocked => "UV blocked",
210 Self::IntegrityFailure => "Integrity failure",
211 Self::InvalidSubcommand => "Invalid subcommand",
212 Self::UvInvalid => "UV invalid",
213 Self::UnauthorizedPermission => "Unauthorized permission",
214 Self::PuatRequired => "PIN/UV auth token required",
215 Self::Other => "Other error",
216 };
217 write!(f, "{}", msg)
218 }
219}
220
221#[cfg(feature = "std")]
223impl std::error::Error for StatusCode {}
224
225impl StatusCode {
226 pub fn to_u8(self) -> u8 {
228 self as u8
229 }
230
231 pub fn from_u8(value: u8) -> Self {
233 match value {
234 0x00 => Self::Success,
235 0x01 => Self::InvalidCommand,
236 0x02 => Self::InvalidParameter,
237 0x03 => Self::InvalidLength,
238 0x04 => Self::InvalidSeq,
239 0x05 => Self::Timeout,
240 0x06 => Self::ChannelBusy,
241 0x0A => Self::LockRequired,
242 0x0B => Self::InvalidChannel,
243 0x11 => Self::CborUnexpectedType,
244 0x12 => Self::InvalidCbor,
245 0x14 => Self::MissingParameter,
246 0x15 => Self::LimitExceeded,
247 0x16 => Self::UnsupportedExtension,
248 0x19 => Self::CredentialExcluded,
249 0x21 => Self::Processing,
250 0x22 => Self::InvalidCredential,
251 0x23 => Self::UserActionPending,
252 0x24 => Self::OperationPending,
253 0x25 => Self::NoOperations,
254 0x26 => Self::UnsupportedAlgorithm,
255 0x27 => Self::OperationDenied,
256 0x28 => Self::KeyStoreFull,
257 0x29 => Self::NotBusy,
258 0x2A => Self::NoOperationPending,
259 0x2B => Self::UnsupportedOption,
260 0x2C => Self::InvalidOption,
261 0x2D => Self::KeepaliveCancel,
262 0x2E => Self::NoCredentials,
263 0x2F => Self::UserActionTimeout,
264 0x30 => Self::NotAllowed,
265 0x31 => Self::PinInvalid,
266 0x32 => Self::PinBlocked,
267 0x33 => Self::PinAuthInvalid,
268 0x34 => Self::PinAuthBlocked,
269 0x35 => Self::PinNotSet,
270 0x36 => Self::PinRequired,
271 0x37 => Self::PinPolicyViolation,
272 0x38 => Self::PinTokenExpired,
273 0x39 => Self::RequestTooLarge,
274 0x3A => Self::ActionTimeout,
275 0x3B => Self::UpRequired,
276 0x3C => Self::UvBlocked,
277 0x3D => Self::IntegrityFailure,
278 0x3E => Self::InvalidSubcommand,
279 0x3F => Self::UvInvalid,
280 0x40 => Self::UnauthorizedPermission,
281 0x41 => Self::PuatRequired,
282 _ => Self::Other,
283 }
284 }
285
286 pub fn is_success(self) -> bool {
288 self == Self::Success
289 }
290}
291
292impl From<StatusCode> for u8 {
293 fn from(status: StatusCode) -> u8 {
294 status.to_u8()
295 }
296}
297
298impl From<u8> for StatusCode {
299 fn from(value: u8) -> Self {
300 Self::from_u8(value)
301 }
302}
303
304impl From<soft_fido2_crypto::CryptoError> for StatusCode {
305 fn from(err: soft_fido2_crypto::CryptoError) -> Self {
306 match err {
307 soft_fido2_crypto::CryptoError::InvalidPublicKey => Self::InvalidParameter,
308 soft_fido2_crypto::CryptoError::InvalidPrivateKey => Self::InvalidParameter,
309 soft_fido2_crypto::CryptoError::InvalidSignature => Self::InvalidParameter,
310 soft_fido2_crypto::CryptoError::DecryptionFailed => Self::PinAuthInvalid,
311 soft_fido2_crypto::CryptoError::EncryptionFailed => Self::Other,
312 soft_fido2_crypto::CryptoError::InvalidKeyLength { .. } => Self::InvalidParameter,
313 soft_fido2_crypto::CryptoError::KeyAgreementFailed => Self::Other,
314 soft_fido2_crypto::CryptoError::InvalidCoseKey => Self::InvalidParameter,
315 _ => Self::Other,
316 }
317 }
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub struct ParseStatusCodeError;
323
324impl fmt::Display for ParseStatusCodeError {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 write!(f, "invalid status code string")
327 }
328}
329
330impl FromStr for StatusCode {
331 type Err = ParseStatusCodeError;
332
333 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
334 match s {
335 "Success" => Ok(Self::Success),
336 "InvalidCommand" => Ok(Self::InvalidCommand),
337 "InvalidParameter" => Ok(Self::InvalidParameter),
338 "InvalidLength" => Ok(Self::InvalidLength),
339 "InvalidSeq" => Ok(Self::InvalidSeq),
340 "Timeout" => Ok(Self::Timeout),
341 "ChannelBusy" => Ok(Self::ChannelBusy),
342 "LockRequired" => Ok(Self::LockRequired),
343 "InvalidChannel" => Ok(Self::InvalidChannel),
344 "CborUnexpectedType" => Ok(Self::CborUnexpectedType),
345 "InvalidCbor" => Ok(Self::InvalidCbor),
346 "MissingParameter" => Ok(Self::MissingParameter),
347 "LimitExceeded" => Ok(Self::LimitExceeded),
348 "UnsupportedExtension" => Ok(Self::UnsupportedExtension),
349 "CredentialExcluded" => Ok(Self::CredentialExcluded),
350 "Processing" => Ok(Self::Processing),
351 "InvalidCredential" => Ok(Self::InvalidCredential),
352 "UserActionPending" => Ok(Self::UserActionPending),
353 "OperationPending" => Ok(Self::OperationPending),
354 "NoOperations" => Ok(Self::NoOperations),
355 "UnsupportedAlgorithm" => Ok(Self::UnsupportedAlgorithm),
356 "OperationDenied" => Ok(Self::OperationDenied),
357 "KeyStoreFull" => Ok(Self::KeyStoreFull),
358 "NotBusy" => Ok(Self::NotBusy),
359 "NoOperationPending" => Ok(Self::NoOperationPending),
360 "UnsupportedOption" => Ok(Self::UnsupportedOption),
361 "InvalidOption" => Ok(Self::InvalidOption),
362 "KeepaliveCancel" => Ok(Self::KeepaliveCancel),
363 "NoCredentials" => Ok(Self::NoCredentials),
364 "UserActionTimeout" => Ok(Self::UserActionTimeout),
365 "NotAllowed" => Ok(Self::NotAllowed),
366 "PinInvalid" => Ok(Self::PinInvalid),
367 "PinBlocked" => Ok(Self::PinBlocked),
368 "PinAuthInvalid" => Ok(Self::PinAuthInvalid),
369 "PinAuthBlocked" => Ok(Self::PinAuthBlocked),
370 "PinNotSet" => Ok(Self::PinNotSet),
371 "PinRequired" => Ok(Self::PinRequired),
372 "PinPolicyViolation" => Ok(Self::PinPolicyViolation),
373 "PinTokenExpired" => Ok(Self::PinTokenExpired),
374 "RequestTooLarge" => Ok(Self::RequestTooLarge),
375 "ActionTimeout" => Ok(Self::ActionTimeout),
376 "UpRequired" => Ok(Self::UpRequired),
377 "UvBlocked" => Ok(Self::UvBlocked),
378 "IntegrityFailure" => Ok(Self::IntegrityFailure),
379 "InvalidSubcommand" => Ok(Self::InvalidSubcommand),
380 "UvInvalid" => Ok(Self::UvInvalid),
381 "UnauthorizedPermission" => Ok(Self::UnauthorizedPermission),
382 "PuatRequired" => Ok(Self::PuatRequired),
383 "Other" => Ok(Self::Other),
384 _ => Err(ParseStatusCodeError),
385 }
386 }
387}
388
389pub type Result<T> = core::result::Result<T, StatusCode>;
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_status_code_round_trip() {
398 let codes = vec![
399 StatusCode::Success,
400 StatusCode::InvalidCommand,
401 StatusCode::PinInvalid,
402 StatusCode::OperationDenied,
403 ];
404
405 for code in codes {
406 let byte = code.to_u8();
407 let recovered = StatusCode::from_u8(byte);
408 assert_eq!(code, recovered);
409 }
410 }
411
412 #[test]
413 fn test_unknown_status_code() {
414 let unknown = StatusCode::from_u8(0xFF);
415 assert_eq!(unknown, StatusCode::Other);
416 }
417
418 #[test]
419 fn test_is_success() {
420 assert!(StatusCode::Success.is_success());
421 assert!(!StatusCode::InvalidCommand.is_success());
422 }
423
424 #[test]
425 fn test_from_crypto_error() {
426 let status: StatusCode = soft_fido2_crypto::CryptoError::InvalidPublicKey.into();
427 assert_eq!(status, StatusCode::InvalidParameter);
428
429 let status: StatusCode = soft_fido2_crypto::CryptoError::DecryptionFailed.into();
430 assert_eq!(status, StatusCode::PinAuthInvalid);
431 }
432
433 #[test]
434 fn test_from_str() {
435 assert_eq!("Success".parse::<StatusCode>(), Ok(StatusCode::Success));
436 assert_eq!(
437 "InvalidCommand".parse::<StatusCode>(),
438 Ok(StatusCode::InvalidCommand)
439 );
440 assert_eq!(
441 "PinInvalid".parse::<StatusCode>(),
442 Ok(StatusCode::PinInvalid)
443 );
444 assert!("InvalidString".parse::<StatusCode>().is_err());
445 }
446}