diff --git a/src/lib.rs b/src/lib.rs index 37fd0c2..35f2a22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,6 +156,7 @@ mod metadata; pub mod mgm; #[cfg(feature = "untested")] pub mod msroots; +pub mod readers; #[cfg(feature = "untested")] mod serialization; #[cfg(feature = "untested")] @@ -164,7 +165,7 @@ mod transaction; pub mod yubikey; #[cfg(feature = "untested")] -pub use self::{key::Key, mgm::MgmKey}; +pub use self::{key::Key, mgm::MgmKey, readers::Readers}; pub use yubikey::YubiKey; /// Object identifiers diff --git a/src/readers.rs b/src/readers.rs new file mode 100644 index 0000000..b3391d3 --- /dev/null +++ b/src/readers.rs @@ -0,0 +1,83 @@ +//! Support for enumerating available readers + +use crate::error::Error; +use std::{ + borrow::Cow, + ffi::CStr, + sync::{Arc, Mutex}, +}; + +/// Iterator over connected readers +pub type Iter<'ctx> = std::vec::IntoIter>; + +/// Enumeration support for available readers +pub struct Readers { + /// PC/SC context + ctx: Arc>, + + /// Buffer for storing reader names + reader_names: Vec, +} + +impl Readers { + /// Open a PC/SC context + 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, Error> { + let Self { ctx, reader_names } = self; + + let reader_cstrs: Vec<_> = { + let c = ctx.lock().unwrap(); + + // ensure PC/SC context is valid + c.is_valid()?; + + c.list_readers(reader_names)?.collect() + }; + + let readers: Vec<_> = reader_cstrs + .iter() + .map(|name| Reader::new(name, Arc::clone(ctx))) + .collect(); + + Ok(readers.into_iter()) + } +} + +/// An individual connected 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 the given reader + // TODO(tarcieri): return a `YubiKey` here rather than a `pcsc::Card` + pub fn open(&self) -> Result { + let ctx = self.ctx.lock().unwrap(); + Ok(ctx.connect(self.name, pcsc::ShareMode::Shared, pcsc::Protocols::T1)?) + } +}