use core::ffi::c_char;
use core::fmt;
use libc::free;
use crate::ffi;
pub const LA_ERROR_DOMAIN: &str = "com.apple.LocalAuthentication";
pub type Result<T, E = LAError> = std::result::Result<T, E>;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum LAError {
InvalidArgument(String),
TimedOut(String),
BridgeFailed(String),
AuthenticationFailed(String),
UserCancel(String),
UserFallback(String),
SystemCancel(String),
PasscodeNotSet(String),
BiometryNotAvailable(String),
BiometryNotEnrolled(String),
BiometryLockout(String),
AppCancel(String),
InvalidContext(String),
NotInteractive(String),
CompanionNotAvailable(String),
BiometryNotPaired(String),
BiometryDisconnected(String),
InvalidDimensions(String),
Other { code: i32, message: String },
}
pub type LocalAuthenticationError = LAError;
impl LAError {
#[must_use]
pub const fn code(&self) -> i32 {
match self {
Self::InvalidArgument(_) => ffi::status::INVALID_ARGUMENT,
Self::TimedOut(_) => ffi::status::TIMED_OUT,
Self::BridgeFailed(_) => ffi::status::BRIDGE_FAILED,
Self::AuthenticationFailed(_) => ffi::la_error::AUTHENTICATION_FAILED,
Self::UserCancel(_) => ffi::la_error::USER_CANCEL,
Self::UserFallback(_) => ffi::la_error::USER_FALLBACK,
Self::SystemCancel(_) => ffi::la_error::SYSTEM_CANCEL,
Self::PasscodeNotSet(_) => ffi::la_error::PASSCODE_NOT_SET,
Self::BiometryNotAvailable(_) => ffi::la_error::BIOMETRY_NOT_AVAILABLE,
Self::BiometryNotEnrolled(_) => ffi::la_error::BIOMETRY_NOT_ENROLLED,
Self::BiometryLockout(_) => ffi::la_error::BIOMETRY_LOCKOUT,
Self::AppCancel(_) => ffi::la_error::APP_CANCEL,
Self::InvalidContext(_) => ffi::la_error::INVALID_CONTEXT,
Self::NotInteractive(_) => ffi::la_error::NOT_INTERACTIVE,
Self::CompanionNotAvailable(_) => ffi::la_error::COMPANION_NOT_AVAILABLE,
Self::BiometryNotPaired(_) => ffi::la_error::BIOMETRY_NOT_PAIRED,
Self::BiometryDisconnected(_) => ffi::la_error::BIOMETRY_DISCONNECTED,
Self::InvalidDimensions(_) => ffi::la_error::INVALID_DIMENSIONS,
Self::Other { code, .. } => *code,
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::InvalidArgument(message)
| Self::TimedOut(message)
| Self::BridgeFailed(message)
| Self::AuthenticationFailed(message)
| Self::UserCancel(message)
| Self::UserFallback(message)
| Self::SystemCancel(message)
| Self::PasscodeNotSet(message)
| Self::BiometryNotAvailable(message)
| Self::BiometryNotEnrolled(message)
| Self::BiometryLockout(message)
| Self::AppCancel(message)
| Self::InvalidContext(message)
| Self::NotInteractive(message)
| Self::CompanionNotAvailable(message)
| Self::BiometryNotPaired(message)
| Self::BiometryDisconnected(message)
| Self::InvalidDimensions(message)
| Self::Other { message, .. } => message,
}
}
#[must_use]
pub const fn domain() -> &'static str {
LA_ERROR_DOMAIN
}
#[must_use]
pub fn from_code_message(code: i32, message: impl Into<String>) -> Self {
from_status_message(code, message.into())
}
}
impl fmt::Display for LAError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (code {})", self.message(), self.code())
}
}
impl std::error::Error for LAError {}
pub(crate) fn take_owned_c_string(ptr: *mut c_char) -> String {
if ptr.is_null() {
return String::new();
}
let string = unsafe { core::ffi::CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned();
unsafe { free(ptr.cast()) };
string
}
pub(crate) fn take_owned_buffer(ptr: *mut u8, len: usize) -> Vec<u8> {
if ptr.is_null() || len == 0 {
if !ptr.is_null() {
unsafe { free(ptr.cast()) };
}
return Vec::new();
}
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec();
unsafe { free(ptr.cast()) };
bytes
}
pub(crate) fn from_status(status: i32, error_str: *mut c_char) -> LAError {
let message = take_owned_c_string(error_str);
from_status_message(status, message)
}
pub(crate) const fn from_status_message(status: i32, message: String) -> LAError {
match status {
ffi::status::INVALID_ARGUMENT => LAError::InvalidArgument(message),
ffi::status::TIMED_OUT => LAError::TimedOut(message),
ffi::status::BRIDGE_FAILED => LAError::BridgeFailed(message),
ffi::la_error::AUTHENTICATION_FAILED => LAError::AuthenticationFailed(message),
ffi::la_error::USER_CANCEL => LAError::UserCancel(message),
ffi::la_error::USER_FALLBACK => LAError::UserFallback(message),
ffi::la_error::SYSTEM_CANCEL => LAError::SystemCancel(message),
ffi::la_error::PASSCODE_NOT_SET => LAError::PasscodeNotSet(message),
ffi::la_error::BIOMETRY_NOT_AVAILABLE => LAError::BiometryNotAvailable(message),
ffi::la_error::BIOMETRY_NOT_ENROLLED => LAError::BiometryNotEnrolled(message),
ffi::la_error::BIOMETRY_LOCKOUT => LAError::BiometryLockout(message),
ffi::la_error::APP_CANCEL => LAError::AppCancel(message),
ffi::la_error::INVALID_CONTEXT => LAError::InvalidContext(message),
ffi::la_error::NOT_INTERACTIVE => LAError::NotInteractive(message),
ffi::la_error::COMPANION_NOT_AVAILABLE => LAError::CompanionNotAvailable(message),
ffi::la_error::BIOMETRY_NOT_PAIRED => LAError::BiometryNotPaired(message),
ffi::la_error::BIOMETRY_DISCONNECTED => LAError::BiometryDisconnected(message),
ffi::la_error::INVALID_DIMENSIONS => LAError::InvalidDimensions(message),
code => LAError::Other { code, message },
}
}
#[cfg(test)]
mod tests {
use super::LAError;
use crate::ffi;
#[test]
fn maps_common_la_error_codes() {
let error = LAError::from_code_message(
ffi::la_error::BIOMETRY_LOCKOUT,
"biometry is locked".to_owned(),
);
assert!(matches!(
error,
LAError::BiometryLockout(message)
if message == "biometry is locked"
));
}
#[test]
fn maps_bridge_status_codes() {
let error =
LAError::from_code_message(ffi::status::TIMED_OUT, "operation timed out".to_owned());
assert!(matches!(
error,
LAError::TimedOut(message)
if message == "operation timed out"
));
}
}