1#[cfg(feature = "std")]
4use std::fmt;
5
6#[cfg(not(feature = "std"))]
7use core::fmt;
8
9use alloc::string::String;
10
11#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq)]
14pub enum Error {
15 Success,
17 DoesAlreadyExist,
19 DoesNotExist,
21 KeyStoreFull,
23 OutOfMemory,
25 Timeout,
27 Other,
29 InitializationFailed,
31 InvalidCallbackResult,
33 CborCommandFailed(i32),
35 InvalidClientDataHash,
37 NoCredentials,
43 PinAuthRequired,
45 UnauthorizedPermission,
50 InvalidRpIdHash,
54 PinTokenExpired,
56 InvalidSubcommand,
58 CtapError(u8),
60 IoError(String),
62 InvalidPinLength,
64}
65
66impl fmt::Display for Error {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Error::Success => write!(f, "Success"),
70 Error::DoesAlreadyExist => write!(f, "Value already exists"),
71 Error::DoesNotExist => write!(f, "Value does not exist"),
72 Error::KeyStoreFull => write!(f, "Key store is full"),
73 Error::OutOfMemory => write!(f, "Out of memory"),
74 Error::Timeout => write!(f, "Operation timed out"),
75 Error::Other => write!(f, "Unspecified error"),
76 Error::InitializationFailed => write!(f, "Initialization failed"),
77 Error::InvalidCallbackResult => write!(f, "Invalid callback result"),
78 Error::CborCommandFailed(code) => {
79 write!(f, "CBOR command failed with code {}", code)
80 }
81 Error::InvalidClientDataHash => {
82 write!(f, "Invalid client data hash (must be 32 bytes)")
83 }
84 Error::NoCredentials => write!(f, "No credentials found"),
85 Error::PinAuthRequired => write!(f, "PIN/UV authentication required"),
86 Error::UnauthorizedPermission => write!(f, "Insufficient permissions"),
87 Error::InvalidRpIdHash => write!(f, "Invalid RP ID hash (must be 32 bytes)"),
88 Error::PinTokenExpired => write!(f, "PIN/UV auth token expired"),
89 Error::InvalidSubcommand => write!(f, "Invalid subcommand"),
90 Error::CtapError(code) => write!(f, "CTAP error: 0x{:02X}", code),
91 Error::IoError(msg) => write!(f, "IO error: {}", msg),
92 Error::InvalidPinLength => write!(f, "Invalid PIN length (must be 4-63 characters)"),
93 }
94 }
95}
96
97#[cfg(feature = "std")]
98impl std::error::Error for Error {}
99
100impl From<i32> for Error {
101 fn from(value: i32) -> Self {
102 match value {
103 0 => Error::Success,
104 -1 => Error::DoesAlreadyExist,
105 -2 => Error::DoesNotExist,
106 -3 => Error::KeyStoreFull,
107 -4 => Error::OutOfMemory,
108 -5 => Error::Timeout,
109 -6 => Error::Other,
110 _ => Error::CborCommandFailed(value),
111 }
112 }
113}
114
115impl From<soft_fido2_ctap::StatusCode> for Error {
116 fn from(status: soft_fido2_ctap::StatusCode) -> Self {
117 use soft_fido2_ctap::StatusCode;
118
119 match status {
120 StatusCode::Success => Error::Success,
121 StatusCode::Timeout | StatusCode::UserActionTimeout | StatusCode::ActionTimeout => {
122 Error::Timeout
123 }
124 StatusCode::KeyStoreFull => Error::KeyStoreFull,
125 StatusCode::NoCredentials => Error::NoCredentials,
126 StatusCode::Other => Error::Other,
127 _ => Error::CtapError(status.to_u8()),
128 }
129 }
130}
131
132impl From<Error> for soft_fido2_ctap::StatusCode {
133 fn from(error: Error) -> Self {
134 use soft_fido2_ctap::StatusCode;
135
136 match error {
137 Error::Success => StatusCode::Success,
138 Error::DoesNotExist | Error::NoCredentials => StatusCode::NoCredentials,
139 Error::KeyStoreFull => StatusCode::KeyStoreFull,
140 Error::Timeout => StatusCode::Timeout,
141 Error::Other => StatusCode::Other,
142 Error::CtapError(code) => StatusCode::from_u8(code),
143 Error::InvalidPinLength => StatusCode::PinPolicyViolation,
144 Error::PinAuthRequired => StatusCode::PuatRequired,
145 Error::UnauthorizedPermission => StatusCode::UnauthorizedPermission,
146 Error::InvalidRpIdHash => StatusCode::InvalidParameter,
147 Error::PinTokenExpired => StatusCode::PinAuthInvalid,
148 Error::InvalidSubcommand => StatusCode::InvalidSubcommand,
149 _ => StatusCode::Other,
150 }
151 }
152}
153
154#[cfg(feature = "std")]
156impl From<std::io::Error> for Error {
157 fn from(error: std::io::Error) -> Self {
158 Error::IoError(error.to_string())
159 }
160}
161
162impl Error {
163 pub fn parse_ctap_response(data: &[u8]) -> Result<&[u8]> {
171 if data.is_empty() {
172 return Err(Error::Other);
173 }
174
175 let status_byte = data[0];
176 if status_byte == 0x00 {
177 Ok(&data[1..])
179 } else {
180 Err(soft_fido2_ctap::StatusCode::from(status_byte).into())
182 }
183 }
184}
185
186#[cfg(feature = "std")]
188pub type Result<T> = std::result::Result<T, Error>;
189
190#[cfg(not(feature = "std"))]
191pub type Result<T> = core::result::Result<T, Error>;
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use soft_fido2_ctap::StatusCode;
197
198 #[test]
199 fn status_to_error_uses_the_status_registry() {
200 assert_eq!(
201 Error::from(StatusCode::PuatRequired),
202 Error::CtapError(0x36)
203 );
204 assert_eq!(Error::from(StatusCode::UpRequired), Error::CtapError(0x3b));
205 assert_eq!(
206 Error::from(StatusCode::UnauthorizedPermission),
207 Error::CtapError(0x40)
208 );
209 }
210
211 #[test]
212 fn ctap_error_round_trips_through_the_status_registry() {
213 assert_eq!(
214 StatusCode::from(Error::CtapError(0x36)),
215 StatusCode::PuatRequired
216 );
217 assert_eq!(
218 StatusCode::from(Error::CtapError(0x3b)),
219 StatusCode::UpRequired
220 );
221 assert_eq!(StatusCode::from(Error::CtapError(0x38)), StatusCode::Other);
222 assert_eq!(StatusCode::from(Error::CtapError(0x41)), StatusCode::Other);
223 }
224}