use alloc::boxed::Box;
use alloc::vec::Vec;
use wasefire_applet_api::fingerprint::matcher as api;
use wasefire_common::ptr::SharedPtr;
use wasefire_error::Code;
use wasefire_sync::{Lazy, Mutex};
use crate::{Error, convert, convert_bool, convert_unit};
pub fn is_supported() -> bool {
convert_bool(unsafe { api::is_supported() }).unwrap_or(false)
}
fn template_length() -> usize {
static TEMPLATE_LENGTH: Lazy<usize> =
Lazy::new(|| convert(unsafe { api::template_length() }).unwrap() as usize);
*TEMPLATE_LENGTH
}
pub struct Enroll {
state: SharedPtr<Mutex<EnrollState>>,
dropped: bool,
}
impl Drop for Enroll {
fn drop(&mut self) {
if !self.dropped {
self.drop_state().unwrap();
}
}
}
#[derive(Clone, Copy)]
pub struct EnrollProgress {
pub detected: usize,
pub remaining: Option<usize>,
}
enum EnrollState {
Running(EnrollProgress),
Done { template: Box<[u8]> },
Failed { error: Error },
}
const INIT_ENROLL_STATE: EnrollState =
EnrollState::Running(EnrollProgress { detected: 0, remaining: None });
impl Enroll {
pub fn new() -> Result<Self, Error> {
let handler_step_func = Self::step;
let handler_func = Self::done;
let state = Box::into_raw(Box::new(Mutex::new(INIT_ENROLL_STATE)));
let handler_step_data = state as *const u8;
let handler_data = state as *const u8;
let params = api::enroll::Params {
handler_step_func,
handler_step_data,
handler_func,
handler_data,
};
convert_unit(unsafe { api::enroll(params) })?;
Ok(Enroll { state: SharedPtr(state), dropped: false })
}
pub fn progress(&self) -> Option<EnrollProgress> {
match *Self::state(self.state.0 as *const u8).lock() {
EnrollState::Running(x) => Some(x),
EnrollState::Done { .. } | EnrollState::Failed { .. } => None,
}
}
pub fn result(mut self) -> Result<Box<[u8]>, Error> {
match Self::into_state(&mut self) {
EnrollState::Running(_) => {
let _ = self.abort();
Err(Error::user(Code::InvalidState))
}
EnrollState::Done { template } => Ok(template),
EnrollState::Failed { error } => Err(error),
}
}
pub fn abort(mut self) -> Result<(), Error> {
self.drop_state()
}
fn drop_state(&mut self) -> Result<(), Error> {
if matches!(Self::into_state(self), EnrollState::Running(_)) {
convert_unit(unsafe { api::abort_enroll() })?;
}
Ok(())
}
extern "C" fn step(data: *const u8, remaining: usize) {
match *Self::state(data).lock() {
EnrollState::Running(ref mut progress) => {
progress.detected += 1;
progress.remaining = Some(remaining);
}
EnrollState::Done { .. } | EnrollState::Failed { .. } => (),
}
}
extern "C" fn done(data: *const u8, result: isize, template: *mut u8) {
*Self::state(data).lock() = match convert_unit(result) {
Ok(()) => EnrollState::Done { template: new_template(template) },
Err(error) => EnrollState::Failed { error },
}
}
fn state<'a>(data: *const u8) -> &'a Mutex<EnrollState> {
unsafe { &*(data as *const Mutex<EnrollState>) }
}
#[allow(clippy::wrong_self_convention)]
fn into_state(&mut self) -> EnrollState {
assert!(!self.dropped);
self.dropped = true;
unsafe { Box::from_raw(self.state.0 as *mut Mutex<EnrollState>) }.into_inner()
}
}
pub struct Identify {
state: SharedPtr<Mutex<IdentifyState>>,
dropped: bool,
}
impl Drop for Identify {
fn drop(&mut self) {
if !self.dropped {
self.drop_state().unwrap();
}
}
}
enum IdentifyState {
Started,
Done { template: Option<Box<[u8]>> },
Failed { error: Error },
}
pub enum IdentifyResult {
NoMatch,
Match {
template: Box<[u8]>,
},
}
impl Identify {
pub fn new(template: Option<&[u8]>) -> Result<Self, Error> {
let template = opt_template(template)?;
let handler_func = Self::call;
let state = Box::into_raw(Box::new(Mutex::new(IdentifyState::Started)));
let handler_data = state as *const u8;
let params = api::identify::Params { template, handler_func, handler_data };
convert_unit(unsafe { api::identify(params) })?;
Ok(Identify { state: SharedPtr(state), dropped: false })
}
pub fn is_done(&self) -> bool {
match *Self::state(self.state.0 as *const u8).lock() {
IdentifyState::Started => false,
IdentifyState::Done { .. } | IdentifyState::Failed { .. } => true,
}
}
pub fn result(mut self) -> Result<IdentifyResult, Error> {
match Self::into_state(&mut self) {
IdentifyState::Started => {
let _ = self.abort();
Err(Error::user(Code::InvalidState))
}
IdentifyState::Done { template: None } => Ok(IdentifyResult::NoMatch),
IdentifyState::Done { template: Some(template) } => {
Ok(IdentifyResult::Match { template })
}
IdentifyState::Failed { error } => Err(error),
}
}
pub fn abort(mut self) -> Result<(), Error> {
self.drop_state()
}
fn drop_state(&mut self) -> Result<(), Error> {
if matches!(Self::into_state(self), IdentifyState::Started) {
convert_unit(unsafe { api::abort_identify() })?;
}
Ok(())
}
extern "C" fn call(data: *const u8, result: isize, template: *mut u8) {
*Self::state(data).lock() = match convert_bool(result) {
Ok(false) => IdentifyState::Done { template: None },
Ok(true) => IdentifyState::Done { template: Some(new_template(template)) },
Err(error) => IdentifyState::Failed { error },
}
}
fn state<'a>(data: *const u8) -> &'a Mutex<IdentifyState> {
unsafe { &*(data as *const Mutex<IdentifyState>) }
}
#[allow(clippy::wrong_self_convention)]
fn into_state(&mut self) -> IdentifyState {
assert!(!self.dropped);
self.dropped = true;
unsafe { Box::from_raw(self.state.0 as *mut Mutex<IdentifyState>) }.into_inner()
}
}
pub fn delete_template(template: Option<&[u8]>) -> Result<(), Error> {
let template = opt_template(template)?;
let params = api::delete_template::Params { template };
convert_unit(unsafe { api::delete_template(params) })
}
pub fn list_templates() -> Result<Templates, Error> {
let mut ptr = core::ptr::null_mut();
let params = api::list_templates::Params { templates: &mut ptr };
let count = convert(unsafe { api::list_templates(params) })?;
let len = count * template_length();
let data = if count == 0 { Vec::new() } else { unsafe { Vec::from_raw_parts(ptr, len, len) } };
Ok(Templates { data })
}
pub struct Templates {
data: Vec<u8>,
}
impl Templates {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
self.data.len() / template_length()
}
pub fn iter(&self) -> impl Iterator<Item = &[u8]> {
self.data.chunks(template_length())
}
}
fn new_template(template: *mut u8) -> Box<[u8]> {
let len = template_length();
let ptr = core::ptr::slice_from_raw_parts_mut(template, len);
unsafe { Box::from_raw(ptr) }
}
fn opt_template(template: Option<&[u8]>) -> Result<*const u8, Error> {
match template {
None => Ok(core::ptr::null()),
Some(x) if x.len() == template_length() => Ok(x.as_ptr()),
Some(_) => Err(Error::user(Code::InvalidLength)),
}
}