localauthentication/
la_error.rs1use core::ffi::c_char;
4use core::fmt;
5
6use libc::free;
7
8use crate::ffi;
9
10pub const LA_ERROR_DOMAIN: &str = "com.apple.LocalAuthentication";
12
13pub type Result<T, E = LAError> = std::result::Result<T, E>;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18#[non_exhaustive]
19pub enum LAError {
20 InvalidArgument(String),
22 TimedOut(String),
24 BridgeFailed(String),
26 AuthenticationFailed(String),
28 UserCancel(String),
30 UserFallback(String),
32 SystemCancel(String),
34 PasscodeNotSet(String),
36 BiometryNotAvailable(String),
38 BiometryNotEnrolled(String),
40 BiometryLockout(String),
42 AppCancel(String),
44 InvalidContext(String),
46 NotInteractive(String),
48 CompanionNotAvailable(String),
50 BiometryNotPaired(String),
52 BiometryDisconnected(String),
54 InvalidDimensions(String),
56 Other { code: i32, message: String },
58}
59
60pub type LocalAuthenticationError = LAError;
62
63impl LAError {
64 #[must_use]
66 pub const fn code(&self) -> i32 {
67 match self {
68 Self::InvalidArgument(_) => ffi::status::INVALID_ARGUMENT,
69 Self::TimedOut(_) => ffi::status::TIMED_OUT,
70 Self::BridgeFailed(_) => ffi::status::BRIDGE_FAILED,
71 Self::AuthenticationFailed(_) => ffi::la_error::AUTHENTICATION_FAILED,
72 Self::UserCancel(_) => ffi::la_error::USER_CANCEL,
73 Self::UserFallback(_) => ffi::la_error::USER_FALLBACK,
74 Self::SystemCancel(_) => ffi::la_error::SYSTEM_CANCEL,
75 Self::PasscodeNotSet(_) => ffi::la_error::PASSCODE_NOT_SET,
76 Self::BiometryNotAvailable(_) => ffi::la_error::BIOMETRY_NOT_AVAILABLE,
77 Self::BiometryNotEnrolled(_) => ffi::la_error::BIOMETRY_NOT_ENROLLED,
78 Self::BiometryLockout(_) => ffi::la_error::BIOMETRY_LOCKOUT,
79 Self::AppCancel(_) => ffi::la_error::APP_CANCEL,
80 Self::InvalidContext(_) => ffi::la_error::INVALID_CONTEXT,
81 Self::NotInteractive(_) => ffi::la_error::NOT_INTERACTIVE,
82 Self::CompanionNotAvailable(_) => ffi::la_error::COMPANION_NOT_AVAILABLE,
83 Self::BiometryNotPaired(_) => ffi::la_error::BIOMETRY_NOT_PAIRED,
84 Self::BiometryDisconnected(_) => ffi::la_error::BIOMETRY_DISCONNECTED,
85 Self::InvalidDimensions(_) => ffi::la_error::INVALID_DIMENSIONS,
86 Self::Other { code, .. } => *code,
87 }
88 }
89
90 #[must_use]
92 pub fn message(&self) -> &str {
93 match self {
94 Self::InvalidArgument(message)
95 | Self::TimedOut(message)
96 | Self::BridgeFailed(message)
97 | Self::AuthenticationFailed(message)
98 | Self::UserCancel(message)
99 | Self::UserFallback(message)
100 | Self::SystemCancel(message)
101 | Self::PasscodeNotSet(message)
102 | Self::BiometryNotAvailable(message)
103 | Self::BiometryNotEnrolled(message)
104 | Self::BiometryLockout(message)
105 | Self::AppCancel(message)
106 | Self::InvalidContext(message)
107 | Self::NotInteractive(message)
108 | Self::CompanionNotAvailable(message)
109 | Self::BiometryNotPaired(message)
110 | Self::BiometryDisconnected(message)
111 | Self::InvalidDimensions(message)
112 | Self::Other { message, .. } => message,
113 }
114 }
115
116 #[must_use]
118 pub const fn domain() -> &'static str {
119 LA_ERROR_DOMAIN
120 }
121
122 #[must_use]
124 pub fn from_code_message(code: i32, message: impl Into<String>) -> Self {
125 from_status_message(code, message.into())
126 }
127}
128
129impl fmt::Display for LAError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "{} (code {})", self.message(), self.code())
132 }
133}
134
135impl std::error::Error for LAError {}
136
137pub(crate) fn take_owned_c_string(ptr: *mut c_char) -> String {
139 if ptr.is_null() {
140 return String::new();
141 }
142
143 let string = unsafe { core::ffi::CStr::from_ptr(ptr) }
144 .to_string_lossy()
145 .into_owned();
146 unsafe { free(ptr.cast()) };
147 string
148}
149
150pub(crate) fn take_owned_buffer(ptr: *mut u8, len: usize) -> Vec<u8> {
152 if ptr.is_null() || len == 0 {
153 if !ptr.is_null() {
154 unsafe { free(ptr.cast()) };
155 }
156 return Vec::new();
157 }
158
159 let bytes = unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec();
160 unsafe { free(ptr.cast()) };
161 bytes
162}
163
164pub(crate) fn from_status(status: i32, error_str: *mut c_char) -> LAError {
166 let message = take_owned_c_string(error_str);
167 from_status_message(status, message)
168}
169
170pub(crate) const fn from_status_message(status: i32, message: String) -> LAError {
172 match status {
173 ffi::status::INVALID_ARGUMENT => LAError::InvalidArgument(message),
174 ffi::status::TIMED_OUT => LAError::TimedOut(message),
175 ffi::status::BRIDGE_FAILED => LAError::BridgeFailed(message),
176 ffi::la_error::AUTHENTICATION_FAILED => LAError::AuthenticationFailed(message),
177 ffi::la_error::USER_CANCEL => LAError::UserCancel(message),
178 ffi::la_error::USER_FALLBACK => LAError::UserFallback(message),
179 ffi::la_error::SYSTEM_CANCEL => LAError::SystemCancel(message),
180 ffi::la_error::PASSCODE_NOT_SET => LAError::PasscodeNotSet(message),
181 ffi::la_error::BIOMETRY_NOT_AVAILABLE => LAError::BiometryNotAvailable(message),
182 ffi::la_error::BIOMETRY_NOT_ENROLLED => LAError::BiometryNotEnrolled(message),
183 ffi::la_error::BIOMETRY_LOCKOUT => LAError::BiometryLockout(message),
184 ffi::la_error::APP_CANCEL => LAError::AppCancel(message),
185 ffi::la_error::INVALID_CONTEXT => LAError::InvalidContext(message),
186 ffi::la_error::NOT_INTERACTIVE => LAError::NotInteractive(message),
187 ffi::la_error::COMPANION_NOT_AVAILABLE => LAError::CompanionNotAvailable(message),
188 ffi::la_error::BIOMETRY_NOT_PAIRED => LAError::BiometryNotPaired(message),
189 ffi::la_error::BIOMETRY_DISCONNECTED => LAError::BiometryDisconnected(message),
190 ffi::la_error::INVALID_DIMENSIONS => LAError::InvalidDimensions(message),
191 code => LAError::Other { code, message },
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::LAError;
198 use crate::ffi;
199
200 #[test]
201 fn maps_common_la_error_codes() {
202 let error = LAError::from_code_message(
203 ffi::la_error::BIOMETRY_LOCKOUT,
204 "biometry is locked".to_owned(),
205 );
206 assert!(matches!(
207 error,
208 LAError::BiometryLockout(message)
209 if message == "biometry is locked"
210 ));
211 }
212
213 #[test]
214 fn maps_bridge_status_codes() {
215 let error =
216 LAError::from_code_message(ffi::status::TIMED_OUT, "operation timed out".to_owned());
217 assert!(matches!(
218 error,
219 LAError::TimedOut(message)
220 if message == "operation timed out"
221 ));
222 }
223}