//! Support for enumerating available PC/SC card readers. use crate::{Error, Result, YubiKey}; use std::{ borrow::Cow, ffi::CStr, fmt, sync::{Arc, Mutex}, }; /// Iterator over connected readers pub type Iter<'ctx> = std::vec::IntoIter>; /// PC/SC reader context: used to enumerate available PC/SC [`Reader`]s. pub struct Context { /// PC/SC context ctx: Arc>, /// Buffer for storing reader names reader_names: Vec, } impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Context").finish_non_exhaustive() } } impl Context { /// Open a PC/SC context, which can be used to enumerate available PC/SC /// readers (which can be used to connect to YubiKeys). pub fn open() -> Result { let ctx = pcsc::Context::establish(pcsc::Scope::System)?; let reader_names = vec![0u8; ctx.list_readers_len()?]; Ok(Self { ctx: Arc::new(Mutex::new(ctx)), reader_names, }) } /// Iterate over the available readers. pub fn iter(&mut self) -> Result> { let Self { ctx, reader_names } = self; let reader_cstrs: Vec<_> = { // TODO(tarcieri): better error? let c = ctx.lock().map_err(|_| Error::GenericError)?; // ensure PC/SC context is valid c.is_valid()?; c.list_readers(reader_names)?.collect() }; #[allow(clippy::needless_collect)] let readers: Vec<_> = reader_cstrs .iter() .map(|name| Reader::new(name, Arc::clone(ctx))) .collect(); Ok(readers.into_iter()) } } /// An individual connected PC/SC card reader. pub struct Reader<'ctx> { /// Name of this reader name: &'ctx CStr, /// PC/SC context ctx: Arc>, } impl<'ctx> Reader<'ctx> { /// Create a new reader from its name and context. fn new(name: &'ctx CStr, ctx: Arc>) -> Self { // TODO(tarcieri): open devices, determine they're YubiKeys, get serial? Self { name, ctx } } /// Get this reader's name. pub fn name(&self) -> Cow<'_, str> { // TODO(tarcieri): is lossy ok here? try to avoid lossiness? self.name.to_string_lossy() } /// Open a connection to this reader, returning a `YubiKey` if successful. pub fn open(&self) -> Result { self.try_into() } /// Connect to this reader, returning its `pcsc::Card`. pub(crate) fn connect(&self) -> Result { // TODO(tarcieri): better error? let ctx = self.ctx.lock().map_err(|_| Error::GenericError)?; Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?) } }