use async_trait::async_trait;
use std::fmt;
#[allow(unused_imports)]
use tracing::{debug, info, instrument, trace};
use crate::{
transport::{device::Device, Channel},
webauthn::Error,
};
use super::channel::NfcChannel;
#[cfg(feature = "nfc-backend-libnfc")]
use super::libnfc;
#[cfg(feature = "nfc-backend-pcsc")]
use super::pcsc;
use super::{Context, Nfc};
#[derive(Clone, Debug)]
enum DeviceInfo {
#[cfg(feature = "nfc-backend-libnfc")]
LibNfc(libnfc::Info),
#[cfg(feature = "nfc-backend-pcsc")]
Pcsc(pcsc::Info),
}
#[derive(Clone, Debug)]
pub struct NfcDevice {
info: DeviceInfo,
}
impl fmt::Display for DeviceInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
#[cfg(feature = "nfc-backend-libnfc")]
DeviceInfo::LibNfc(info) => write!(f, "{}", info),
#[cfg(feature = "nfc-backend-pcsc")]
DeviceInfo::Pcsc(info) => write!(f, "{}", info),
}
}
}
impl fmt::Display for NfcDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.info)
}
}
impl NfcDevice {
#[cfg(feature = "nfc-backend-libnfc")]
pub fn new_libnfc(info: libnfc::Info) -> Self {
NfcDevice {
info: DeviceInfo::LibNfc(info),
}
}
#[cfg(feature = "nfc-backend-pcsc")]
pub fn new_pcsc(info: pcsc::Info) -> Self {
NfcDevice {
info: DeviceInfo::Pcsc(info),
}
}
async fn channel_sync(&self) -> Result<NfcChannel<Context>, Error> {
trace!("nfc channel {:?}", self);
let mut channel: NfcChannel<Context> = match &self.info {
#[cfg(feature = "nfc-backend-libnfc")]
DeviceInfo::LibNfc(info) => info.channel(),
#[cfg(feature = "nfc-backend-pcsc")]
DeviceInfo::Pcsc(info) => info.channel(),
}?;
channel.select_fido2().await?;
Ok(channel)
}
}
#[async_trait]
impl<'d> Device<'d, Nfc, NfcChannel<Context>> for NfcDevice {
async fn channel(&'d mut self) -> Result<NfcChannel<Context>, Error> {
self.channel_sync().await
}
}
async fn is_fido(device: &NfcDevice) -> bool {
async fn inner(device: &NfcDevice) -> Result<bool, Error> {
let chan = device.channel_sync().await?;
let protocols = chan.supported_protocols().await?;
Ok(protocols.fido2 || protocols.u2f)
}
inner(device).await.is_ok()
}
#[instrument]
pub async fn get_nfc_device() -> Result<Option<NfcDevice>, Error> {
let list_devices_fns = [
#[cfg(feature = "nfc-backend-libnfc")]
libnfc::list_devices,
#[cfg(feature = "nfc-backend-pcsc")]
pcsc::list_devices,
];
for list_devices in list_devices_fns {
for device in list_devices()? {
if is_fido(&device).await {
return Ok(Some(device));
}
}
}
Ok(None)
}
#[instrument]
pub fn is_nfc_available() -> bool {
let mut available = false;
#[cfg(feature = "nfc-backend-libnfc")]
{
available |= libnfc::is_nfc_available();
}
#[cfg(feature = "nfc-backend-pcsc")]
{
available |= pcsc::is_nfc_available();
}
available
}