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