use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ops::Index;
use std::ptr::NonNull;
use foreign_types::{ForeignType, ForeignTypeRef};
use zeroize::Zeroizing;
use crate::credentials::{Credential, CredentialRef};
use crate::device::Device;
use crate::error::Result;
use crate::utils::{allocation_error, check};
pub struct CredentialManagement<'a> {
pub(crate) ptr: NonNull<ffi::fido_credman_metadata_t>,
dev: &'a Device,
pin: Option<Zeroizing<CString>>,
}
impl<'a> CredentialManagement<'a> {
pub(crate) fn new(
ptr: NonNull<ffi::fido_credman_metadata_t>,
device: &'a Device,
pin: Option<Zeroizing<CString>>,
) -> CredentialManagement<'a> {
CredentialManagement {
ptr,
dev: device,
pin,
}
}
pub fn count(&self) -> usize {
unsafe { ffi::fido_credman_rk_existing(self.ptr.as_ptr()) as usize }
}
pub fn remaining(&self) -> usize {
unsafe { ffi::fido_credman_rk_remaining(self.ptr.as_ptr()) as usize }
}
pub fn get_rp(&self) -> Result<CredManRP> {
let pin_ptr = match &self.pin {
Some(p) => p.as_ptr(),
None => std::ptr::null(),
};
unsafe {
let p = ffi::fido_credman_rp_new();
let p = NonNull::new(p).ok_or_else(allocation_error)?;
if let Err(err) = check(ffi::fido_credman_get_dev_rp(
self.dev.ptr.as_ptr(),
p.as_ptr(),
pin_ptr,
)) {
let mut raw = p.as_ptr();
ffi::fido_credman_rp_free(&mut raw);
return Err(err.into());
}
Ok(CredManRP {
ptr: p,
_phantom: Default::default(),
})
}
}
pub fn get_rk<'i, I: Into<Cow<'i, CStr>>>(&self, rp: I) -> Result<CredManRK> {
let rp = rp.into();
let pin_ptr = match &self.pin {
Some(p) => p.as_ptr(),
None => std::ptr::null(),
};
unsafe {
let rk = ffi::fido_credman_rk_new();
let rk = NonNull::new(rk).ok_or_else(allocation_error)?;
if let Err(err) = check(ffi::fido_credman_get_dev_rk(
self.dev.ptr.as_ptr(),
rp.as_ptr(),
rk.as_ptr(),
pin_ptr,
)) {
let mut raw = rk.as_ptr();
ffi::fido_credman_rk_free(&mut raw);
return Err(err.into());
}
Ok(CredManRK {
ptr: rk,
_phantom: PhantomData,
})
}
}
pub fn delete_rk(&self, cred_id: &[u8]) -> Result<()> {
let pin_ptr = match &self.pin {
Some(p) => p.as_ptr(),
None => std::ptr::null(),
};
unsafe {
check(ffi::fido_credman_del_dev_rk(
self.dev.ptr.as_ptr(),
cred_id.as_ptr(),
cred_id.len(),
pin_ptr,
))?;
Ok(())
}
}
pub fn set_rk(&self, cred: &Credential) -> Result<()> {
let pin_ptr = match &self.pin {
Some(p) => p.as_ptr(),
None => std::ptr::null(),
};
unsafe {
check(ffi::fido_credman_set_dev_rk(
self.dev.ptr.as_ptr(),
cred.as_ptr(),
pin_ptr,
))?;
Ok(())
}
}
}
impl<'a> Drop for CredentialManagement<'a> {
fn drop(&mut self) {
unsafe {
let mut ptr = self.ptr.as_ptr();
ffi::fido_credman_metadata_free(&mut ptr);
}
}
}
pub struct CredManRK {
ptr: NonNull<ffi::fido_credman_rk_t>,
_phantom: PhantomData<ffi::fido_credman_rk_t>,
}
impl CredManRK {
pub fn count(&self) -> usize {
unsafe { ffi::fido_credman_rk_count(self.ptr.as_ptr()) }
}
pub fn iter(&self) -> IterRK<'_> {
let total = self.count();
IterRK {
idx: 0,
total,
rk: self.ptr,
_phantom: Default::default(),
}
}
}
impl Drop for CredManRK {
fn drop(&mut self) {
unsafe {
let mut ptr = self.ptr.as_ptr();
ffi::fido_credman_rk_free(&mut ptr);
}
}
}
impl Index<usize> for CredManRK {
type Output = CredentialRef;
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.count(), "credential index out of bounds");
unsafe {
let ptr = ffi::fido_credman_rk(self.ptr.as_ptr(), index);
assert!(!ptr.is_null(), "libfido2 returned NULL for credential");
CredentialRef::from_ptr(ptr as *mut ffi::fido_cred_t)
}
}
}
pub struct IterRK<'a> {
idx: usize,
total: usize,
rk: NonNull<ffi::fido_credman_rk_t>,
_phantom: PhantomData<&'a CredManRK>,
}
impl<'a> Iterator for IterRK<'a> {
type Item = &'a CredentialRef;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.total {
return None;
}
let ptr = unsafe { ffi::fido_credman_rk(self.rk.as_ptr(), self.idx) };
assert!(!ptr.is_null(), "libfido2 returned NULL for credential");
self.idx += 1;
let credential_ref = unsafe { CredentialRef::from_ptr(ptr as *mut ffi::fido_cred_t) };
Some(credential_ref)
}
}
impl ExactSizeIterator for IterRK<'_> {
fn len(&self) -> usize {
self.total - self.idx
}
}
#[derive(Copy, Clone, Debug)]
pub struct RelyingParty<'a> {
pub id: &'a CStr,
pub name: Option<&'a CStr>,
}
pub struct CredManRP {
ptr: NonNull<ffi::fido_credman_rp_t>,
_phantom: PhantomData<ffi::fido_credman_rp_t>,
}
impl CredManRP {
pub fn count(&self) -> usize {
unsafe { ffi::fido_credman_rp_count(self.ptr.as_ptr()) }
}
pub fn iter(&self) -> IterRP<'_> {
let total = self.count();
IterRP {
idx: 0,
total,
rp: self.ptr,
_phantom: Default::default(),
}
}
}
impl Drop for CredManRP {
fn drop(&mut self) {
unsafe {
let mut ptr = self.ptr.as_ptr();
ffi::fido_credman_rp_free(&mut ptr);
}
}
}
pub struct IterRP<'a> {
idx: usize,
total: usize,
rp: NonNull<ffi::fido_credman_rp_t>,
_phantom: PhantomData<&'a CredManRP>,
}
impl<'a> Iterator for IterRP<'a> {
type Item = RelyingParty<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.total {
return None;
}
let id = unsafe {
let id = ffi::fido_credman_rp_id(self.rp.as_ptr(), self.idx);
assert!(!id.is_null(), "libfido2 returned NULL for relying party id");
CStr::from_ptr(id)
};
let name = unsafe {
let name = ffi::fido_credman_rp_name(self.rp.as_ptr(), self.idx);
if !name.is_null() {
Some(CStr::from_ptr(name))
} else {
None
}
};
self.idx += 1;
Some(RelyingParty { id, name })
}
}
impl ExactSizeIterator for IterRP<'_> {
fn len(&self) -> usize {
self.total - self.idx
}
}