pcsc-2.7.0/.cargo_vcs_info.json0000644000000001420000000000100117670ustar { "git": { "sha1": "f210772cda647ca82b6207f30f1a08221e8aef69" }, "path_in_vcs": "pcsc" }pcsc-2.7.0/Cargo.lock0000644000000014460000000000100077520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "pcsc" version = "2.7.0" dependencies = [ "bitflags", "pcsc-sys", ] [[package]] name = "pcsc-sys" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea" dependencies = [ "pkg-config", ] [[package]] name = "pkg-config" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" pcsc-2.7.0/Cargo.toml0000644000000017200000000000100077700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "pcsc" version = "2.7.0" authors = ["Ran Benita "] description = "Bindings to the PC/SC API for smart card communication" homepage = "https://github.com/bluetech/pcsc-rust" documentation = "https://docs.rs/pcsc" readme = "../README.md" keywords = ["pcsc", "smartcard"] categories = ["hardware-support", "api-bindings"] license = "MIT" repository = "https://github.com/bluetech/pcsc-rust" [dependencies.bitflags] version = "1.2.1" [dependencies.pcsc-sys] version = "1.2.0" pcsc-2.7.0/Cargo.toml.orig000064400000000000000000000010020072674642500134720ustar 00000000000000[package] name = "pcsc" description = "Bindings to the PC/SC API for smart card communication" version = "2.7.0" license = "MIT" keywords = ["pcsc", "smartcard"] categories = ["hardware-support", "api-bindings"] documentation = "https://docs.rs/pcsc" repository = "https://github.com/bluetech/pcsc-rust" homepage = "https://github.com/bluetech/pcsc-rust" readme = "../README.md" authors = ["Ran Benita "] [dependencies] bitflags = "1.2.1" pcsc-sys = { version = "1.2.0", path = "../pcsc-sys" } pcsc-2.7.0/examples/cancel.rs000064400000000000000000000025310072674642500142240ustar 00000000000000// Example of how to cancel a blocking Context operation from another // thread. extern crate pcsc; use std::time::Duration; use pcsc::*; fn wait_for_enter_keypress() { use std::io::Read; let mut stdin = std::io::stdin(); let _ = stdin.read(&mut [0]).expect("failed to read key"); } fn main() { // Get a context. let ctx = Context::establish(Scope::User).expect("failed to establish context"); // Spawn a thread which waits for a key-press then cancels the operation. { let ctx = ctx.clone(); std::thread::spawn(move || { wait_for_enter_keypress(); ctx.cancel().expect("failed to cancel"); }); } // Set up the blocking call, and wait for cancel or timeout. println!("Entering blocking call; press Enter to cancel"); let mut reader_states = vec![ ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE), ]; match ctx.get_status_change(Duration::from_secs(5), &mut reader_states) { Ok(()) => { println!("Blocking call exited normally"); } Err(Error::Cancelled) => { println!("Blocking call canceled"); } Err(Error::Timeout) => { println!("Blocking call timed out"); } Err(error) => { panic!("failed to get status changes: {:?}", error); } } } pcsc-2.7.0/examples/connect-owned.rs000064400000000000000000000062630072674642500155500ustar 00000000000000// Example of communication with a smart card. extern crate pcsc; use pcsc::*; fn main() { // Get a context. let ctx = Context::establish(Scope::User).expect("failed to establish context"); // List connected readers. let readers = ctx.list_readers_owned().expect("failed to list readers"); println!("Readers: {:?}", readers); if readers.is_empty() { return; } { // Try to connect to a card in the first reader. let mut card = ctx.connect(&readers[0], ShareMode::Shared, Protocols::ANY).expect("failed to connect to card"); { // Start an exclusive transaction (not required -- can work on card directly). let tx = card.transaction().expect("failed to begin card transaction"); // Get the card status. let status = tx.status2_owned().expect("failed to get card status"); println!("Reader names from status: {:?}", status.reader_names()); if let Some(protocol) = status.protocol2() { println!("Protocol from status: {:?}", protocol); } else { println!("Protocol from status: directly connected"); } println!("ATR from status: {:?}", status.atr()); // Send some harmless APDU to the card. if let Some(_) = status.protocol2() { let apdu = b"\x00\xa4\x04\x00\x08\x31\x54\x49\x43\x2e\x49\x43\x41"; let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; let rapdu = tx.transmit(apdu, &mut rapdu_buf).expect("failed to transmit APDU to card"); println!("RAPDU: {:?}", rapdu); } // Get the card's ATR. let atr = tx.get_attribute_owned(Attribute::AtrString).expect("failed to get ATR attribute"); println!("ATR from attribute: {:?}", atr); // Get some attribute. let ifd_version = tx.get_attribute_owned(Attribute::VendorIfdVersion).expect("failed to get vendor IFD version attribute"); println!("Vendor IFD version: {:?}", ifd_version); // Get some other attribute. let vendor_name = tx.get_attribute_owned(Attribute::VendorName).expect("failed to get vendor name attribute"); println!("Vendor name: {}", String::from_utf8(vendor_name).unwrap()); // Can either end explicity, which allows error handling, // and setting the disposition method, or leave it to drop, which // swallows any error and hardcodes LeaveCard. tx.end(Disposition::LeaveCard).map_err(|(_, err)| err).expect("failed to end transaction"); } // Can either disconnect explicity, which allows error handling, // and setting the disposition method, or leave it to drop, which // swallows any error and hardcodes ResetCard. card.disconnect(Disposition::ResetCard).map_err(|(_, err)| err).expect("failed to disconnect from card"); } // Can either release explicity, which allows error handling, // or leave it to drop, which swallows any error. // The function fails if there are any live clones. ctx.release().map_err(|(_, err)| err).expect("failed to release context"); } pcsc-2.7.0/examples/connect.rs000064400000000000000000000077000072674642500144330ustar 00000000000000// Example of communication with a smart card. extern crate pcsc; use pcsc::*; fn main() { // Get a context. let ctx = Context::establish(Scope::User).expect("failed to establish context"); // List connected readers. let mut readers_buf = [0; 2048]; let readers = ctx.list_readers(&mut readers_buf).expect("failed to list readers").collect::>(); println!("Readers: {:?}", readers); if readers.is_empty() { return; } { // Try to connect to a card in the first reader. let mut card = ctx.connect(readers[0], ShareMode::Shared, Protocols::ANY).expect("failed to connect to card"); { // Start an exclusive transaction (not required -- can work on card directly). let tx = card.transaction().expect("failed to begin card transaction"); // Get the card status. let (names_len, _atr_len) = tx.status2_len().expect("failed to get the status length"); let mut names_buf = vec![0; names_len]; let mut atr_buf = [0; MAX_ATR_SIZE]; let status = tx.status2(&mut names_buf, &mut atr_buf).expect("failed to get card status"); println!("Status from status: {:?}", status.status()); println!("Reader names from status: {:?}", status.reader_names().collect::>()); if let Some(protocol) = status.protocol2() { println!("Protocol from status: {:?}", protocol); } else { println!("Protocol from status: directly connected"); } println!("ATR from status: {:?}", status.atr()); // Send some harmless APDU to the card. if let Some(_) = status.protocol2() { let apdu = b"\x00\xa4\x04\x00\x08\x31\x54\x49\x43\x2e\x49\x43\x41"; let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; let rapdu = tx.transmit(apdu, &mut rapdu_buf).expect("failed to transmit APDU to card"); println!("RAPDU: {:?}", rapdu); } // Get the card's ATR. let mut atr_buf = [0; MAX_ATR_SIZE]; let atr = tx.get_attribute(Attribute::AtrString, &mut atr_buf).expect("failed to get ATR attribute"); println!("ATR from attribute: {:?}", atr); // Get some attribute. let mut ifd_version_buf = [0; 4]; let ifd_version = tx.get_attribute(Attribute::VendorIfdVersion, &mut ifd_version_buf).expect("failed to get vendor IFD version attribute"); println!("Vendor IFD version: {:?}", ifd_version); // Get some other attribute. // This time we allocate a buffer of the needed length. let vendor_name_len = tx.get_attribute_len(Attribute::VendorName).expect("failed to get the vendor name attribute length"); let mut vendor_name_buf = vec![0; vendor_name_len]; let vendor_name = tx.get_attribute(Attribute::VendorName, &mut vendor_name_buf).expect("failed to get vendor name attribute"); println!("Vendor name: {}", std::str::from_utf8(vendor_name).unwrap()); // Can either end explicity, which allows error handling, // and setting the disposition method, or leave it to drop, which // swallows any error and hardcodes LeaveCard. tx.end(Disposition::LeaveCard).map_err(|(_, err)| err).expect("failed to end transaction"); } // Can either disconnect explicity, which allows error handling, // and setting the disposition method, or leave it to drop, which // swallows any error and hardcodes ResetCard. card.disconnect(Disposition::ResetCard).map_err(|(_, err)| err).expect("failed to disconnect from card"); } // Can either release explicity, which allows error handling, // or leave it to drop, which swallows any error. // The function fails if there are any live clones. ctx.release().map_err(|(_, err)| err).expect("failed to release context"); } pcsc-2.7.0/examples/list-readers-owned.rs000064400000000000000000000007110072674642500165050ustar 00000000000000// Example of how to enumerate connected card readers. extern crate pcsc; use pcsc::*; fn main() { // Get a context. let ctx = Context::establish(Scope::User).expect("failed to establish context"); // Instead of manually allocating the buffer for the reader names, we let pcsc take care of // that let names = ctx.list_readers_owned().expect("failed to list readers"); for name in names { println!("{:?}", name); } } pcsc-2.7.0/examples/list-readers.rs000064400000000000000000000013260072674642500153760ustar 00000000000000// Example of how to enumerate connected card readers. extern crate pcsc; use pcsc::*; fn main() { // Get a context. let ctx = Context::establish(Scope::User).expect("failed to establish context"); // First allocate the required buffer. let len = ctx.list_readers_len().expect("failed to list readers needed len"); let mut readers_buf = vec![0; len]; // Alternatively, we could have just used a sufficiently large // statically sized, stack allocated buffer instead, like we do in // other examples: // let mut readers_buf = [0; 2048]; let names = ctx.list_readers(&mut readers_buf).expect("failed to list readers"); for name in names { println!("{:?}", name); } } pcsc-2.7.0/examples/monitor.rs000064400000000000000000000031630072674642500144700ustar 00000000000000// Example of how to monitor card & card reader state changes. extern crate pcsc; use pcsc::*; fn main() { let ctx = Context::establish(Scope::User).expect("failed to establish context"); let mut readers_buf = [0; 2048]; let mut reader_states = vec![ // Listen for reader insertions/removals, if supported. ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE), ]; loop { // Remove dead readers. fn is_dead(rs: &ReaderState) -> bool { rs.event_state().intersects(State::UNKNOWN | State::IGNORE) } for rs in &reader_states { if is_dead(rs) { println!("Removing {:?}", rs.name()); } } reader_states.retain(|rs| !is_dead(rs)); // Add new readers. let names = ctx.list_readers(&mut readers_buf).expect("failed to list readers"); for name in names { if !reader_states.iter().any(|rs| rs.name() == name) { println!("Adding {:?}", name); reader_states.push(ReaderState::new(name, State::UNAWARE)); } } // Update the view of the state to wait on. for rs in &mut reader_states { rs.sync_current_state(); } // Wait until the state changes. ctx.get_status_change(None, &mut reader_states).expect("failed to get status change"); // Print current state. println!(); for rs in &reader_states { if rs.name() != PNP_NOTIFICATION() { println!("{:?} {:?} {:?}", rs.name(), rs.event_state(), rs.atr()); } } } } pcsc-2.7.0/examples/readme.rs000064400000000000000000000033000072674642500142270ustar 00000000000000extern crate pcsc; use pcsc::*; fn main() { // Establish a PC/SC context. let ctx = match Context::establish(Scope::User) { Ok(ctx) => ctx, Err(err) => { eprintln!("Failed to establish context: {}", err); std::process::exit(1); } }; // List available readers. let mut readers_buf = [0; 2048]; let mut readers = match ctx.list_readers(&mut readers_buf) { Ok(readers) => readers, Err(err) => { eprintln!("Failed to list readers: {}", err); std::process::exit(1); } }; // Use the first reader. let reader = match readers.next() { Some(reader) => reader, None => { println!("No readers are connected."); return; } }; println!("Using reader: {:?}", reader); // Connect to the card. let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) { Ok(card) => card, Err(Error::NoSmartcard) => { println!("A smartcard is not present in the reader."); return; } Err(err) => { eprintln!("Failed to connect to card: {}", err); std::process::exit(1); } }; // Send an APDU command. let apdu = b"\x00\xa4\x04\x00\x0A\xA0\x00\x00\x00\x62\x03\x01\x0C\x06\x01"; println!("Sending APDU: {:?}", apdu); let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; let rapdu = match card.transmit(apdu, &mut rapdu_buf) { Ok(rapdu) => rapdu, Err(err) => { eprintln!("Failed to transmit APDU command to card: {}", err); std::process::exit(1); } }; println!("APDU response: {:?}", rapdu); } pcsc-2.7.0/src/lib.rs000064400000000000000000001740600072674642500125250ustar 00000000000000//! Communicate with smart cards using the PC/SC API. //! //! PC/SC (Personal Computer/Smart Card) is a standard API for //! communicating with smart cards -- enumerating card readers, connecting //! to smart cards, sending them commands, etc. See [Wikipedia][1] and //! [PC/SC Workgroup][2] for more information. //! //! [1]: https://en.wikipedia.org/wiki/PC/SC //! [2]: https://pcscworkgroup.com/ //! //! This library is a safe and ergonomic FFI wrapper around the following //! PC/SC implementations: //! //! - On Windows, the built-in `WinSCard.dll` library and "Smart Card" //! service. See [MSDN][3] for documentation of the implemented API. //! //! - On macOS, the built-in PCSC framework. //! //! - On Linux, BSDs and (hopefully) other systems, the PCSC lite library //! and pcscd daemon. See [pcsclite][4] for documentation of the //! implemented API. //! //! This crate depends on the [`pcsc-sys`][5] crate. See its documentation //! if you need to customize how the PCSC implementation is found. //! //! [3]: https://msdn.microsoft.com/EN-US/library/aa374731.aspx#smart_card_functions //! [4]: https://pcsclite.apdu.fr/ //! [5]: https://docs.rs/pcsc-sys //! //! ## Communicating with a smart card //! //! To communicate with a smart card, you send it APDU (Application //! Protocol Data Unit) commands, and receive APDU responses. //! //! The format of these commands is described in the [ISO 7816 Part 4][6] //! standard. The commands themselves vary based on the application on the //! card. //! //! [6]: http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4.aspx //! //! ## Note on portability //! //! The various implementations are not fully consistent with each other, //! and some may also miss various features or exhibit various bugs. //! Hence, you cannot assume that code which works on one platform will //! behave the same in all other platforms - unfortunately, some //! adjustments might be needed to reach a common base. See [pcsclite][4] //! for a list of documented differences, and [Ludovic Rousseau's blog][7] //! archive for many more details. //! //! [7]: https://ludovicrousseau.blogspot.com/ //! //! Not all PC/SC functionality is covered yet; if you are missing //! something, please open an issue. //! //! ## Note on strings //! //! The library uses C strings (`&CStr`) for all strings (e.g. card reader //! names), to avoid any allocation and conversion overhead. //! //! In pcsclite and macOS, all strings are guaranteed to be UTF-8 encoded. //! //! In Windows, the API provides two variants of all functions dealing //! with strings - ASCII and Unicode (in this case, meaning 16-bits wide //! strings). For ease of implementation, this library wraps the ASCII //! variants only. (If you require Unicode names in Windows, please open //! an issue.) //! //! Since ASCII is a subset of UTF-8, you can thus safely use UTF-8 //! conversion functions such as `to_str()` to obtain an `&str`/`String` //! from this library -- but don't do this if you don't need to ☺ //! //! ## Note on thread safety and blocking operations //! //! A library context can be safely moved to another thread or cloned and //! used from multiple threads. //! //! Operations on a given context are not performed concurrently. If one //! thread performs a blocking operation on a context, such as //! `get_status_change()`, then another operation on the context will //! block until the ongoing operation finishes. //! //! An ongoing blocking operation on a context can be canceled from another //! thread by calling the `cancel` function on the context. //! //! If you want to perform concurrent operations, for example, monitor //! smart card reader changes in one thread, and send commands to cards in //! another, create a different context for each thread. //! //! Note however, that if one context has an exclusive transaction with a //! card, any operation on the same underlying card from not within the //! transaction will block even across contexts. //! //! When issuing a series of commands to a card, it is recommended to always //! use a transaction -- other programs and even system services can get in //! the way otherwise, even if you don't expect it. //! //! See [MSDN][8] for more details. //! //! [8]: https://msdn.microsoft.com/en-us/library/ms953432.aspx#smartcardcspcook_topic2 #![allow(deprecated)] #[macro_use] extern crate bitflags; extern crate pcsc_sys as ffi; use std::ffi::{CStr, CString}; use std::mem::{forget, transmute}; use std::ops::Deref; use std::os::raw::c_char; use std::ptr::{null, null_mut}; use std::sync::Arc; use ffi::{DWORD, LONG}; // We use these instead of std::mem::uninitialized -- variables which are // set to this are always overridden and the dummy values are never exposed. const DUMMY_LONG: LONG = -1; const DUMMY_DWORD: DWORD = 0xdead_beef; // Note on potentially problematic casts (clippy lints `cast-sign-loss`, // `cast-possible-truncation`): from my analysis they are all OK, for // both 32bit and 64bit DWORD/LONG. But it is sketchy. bitflags! { /// A mask of the state a card reader. pub struct State: DWORD { const UNAWARE = ffi::SCARD_STATE_UNAWARE; const IGNORE = ffi::SCARD_STATE_IGNORE; const CHANGED = ffi::SCARD_STATE_CHANGED; const UNKNOWN = ffi::SCARD_STATE_UNKNOWN; const UNAVAILABLE = ffi::SCARD_STATE_UNAVAILABLE; const EMPTY = ffi::SCARD_STATE_EMPTY; const PRESENT = ffi::SCARD_STATE_PRESENT; const ATRMATCH = ffi::SCARD_STATE_ATRMATCH; const EXCLUSIVE = ffi::SCARD_STATE_EXCLUSIVE; const INUSE = ffi::SCARD_STATE_INUSE; const MUTE = ffi::SCARD_STATE_MUTE; const UNPOWERED = ffi::SCARD_STATE_UNPOWERED; } } bitflags! { /// A mask of the status of a card in a card reader. /// /// # Portability note /// /// On Windows, Status always has exactly one bit set, and the bit values do /// not correspond to underlying PC/SC constants. This allows Status to be /// used in the same way across all platforms. pub struct Status: DWORD { const UNKNOWN = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_UNKNOWN } #[cfg(target_os = "windows")] { 0x0001 } }; const ABSENT = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_ABSENT } #[cfg(target_os = "windows")] { 0x0002 } }; const PRESENT = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_PRESENT } #[cfg(target_os = "windows")] { 0x0004 } }; const SWALLOWED = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_SWALLOWED } #[cfg(target_os = "windows")] { 0x0008 } }; const POWERED = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_POWERED } #[cfg(target_os = "windows")] { 0x0010 } }; const NEGOTIABLE = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_NEGOTIABLE } #[cfg(target_os = "windows")] { 0x0020 } }; const SPECIFIC = { #[cfg(not(target_os = "windows"))] { ffi::SCARD_SPECIFIC } #[cfg(target_os = "windows")] { 0x0040 } }; } } impl Status { fn from_raw(raw_status: DWORD) -> Self { #[cfg(target_os = "windows")] match raw_status { ffi::SCARD_UNKNOWN => Status::UNKNOWN, ffi::SCARD_ABSENT => Status::ABSENT, ffi::SCARD_PRESENT => Status::PRESENT, ffi::SCARD_SWALLOWED => Status::SWALLOWED, ffi::SCARD_POWERED => Status::POWERED, ffi::SCARD_NEGOTIABLE => Status::NEGOTIABLE, ffi::SCARD_SPECIFIC => Status::SPECIFIC, _ => Status::empty(), } #[cfg(not(target_os = "windows"))] Status::from_bits_truncate(raw_status) } } /// How a reader connection is shared. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ShareMode { Exclusive = ffi::SCARD_SHARE_EXCLUSIVE as u32, Shared = ffi::SCARD_SHARE_SHARED as u32, Direct = ffi::SCARD_SHARE_DIRECT as u32, } impl ShareMode { fn into_raw(self) -> DWORD { DWORD::from(self as u32) } } /// A smart card communication protocol. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Protocol { T0 = ffi::SCARD_PROTOCOL_T0 as u32, T1 = ffi::SCARD_PROTOCOL_T1 as u32, RAW = ffi::SCARD_PROTOCOL_RAW as u32, } impl Protocol { fn from_raw(raw: DWORD) -> Option { match raw { ffi::SCARD_PROTOCOL_UNDEFINED => None, ffi::SCARD_PROTOCOL_T0 => Some(Protocol::T0), ffi::SCARD_PROTOCOL_T1 => Some(Protocol::T1), ffi::SCARD_PROTOCOL_RAW => Some(Protocol::RAW), // This should not be possible, since we only allow to select // from Protocol's variants (or none). _ => panic!("impossible protocol: {:#x}", raw), } } } bitflags! { /// A mask of possible communication protocols. pub struct Protocols: DWORD { const UNDEFINED = ffi::SCARD_PROTOCOL_UNDEFINED; const T0 = ffi::SCARD_PROTOCOL_T0; const T1 = ffi::SCARD_PROTOCOL_T1; const RAW = ffi::SCARD_PROTOCOL_RAW; const ANY = ffi::SCARD_PROTOCOL_ANY; } } /// Disposition method when disconnecting from a card reader. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Disposition { LeaveCard = ffi::SCARD_LEAVE_CARD as u32, ResetCard = ffi::SCARD_RESET_CARD as u32, UnpowerCard = ffi::SCARD_UNPOWER_CARD as u32, EjectCard = ffi::SCARD_EJECT_CARD as u32, } impl Disposition { fn into_raw(self) -> DWORD { DWORD::from(self as u32) } } /// Possible library errors. /// /// See [pcsclite][1], [MSDN][2]. /// /// [1]: https://pcsclite.apdu.fr/api/group__ErrorCodes.html /// [2]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374738(v=vs.85).aspx#smart_card_return_values #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Error { // InternalError = ffi::SCARD_F_INTERNAL_ERROR as u32, Cancelled = ffi::SCARD_E_CANCELLED as u32, InvalidHandle = ffi::SCARD_E_INVALID_HANDLE as u32, InvalidParameter = ffi::SCARD_E_INVALID_PARAMETER as u32, InvalidTarget = ffi::SCARD_E_INVALID_TARGET as u32, NoMemory = ffi::SCARD_E_NO_MEMORY as u32, WaitedTooLong = ffi::SCARD_F_WAITED_TOO_LONG as u32, InsufficientBuffer = ffi::SCARD_E_INSUFFICIENT_BUFFER as u32, UnknownReader = ffi::SCARD_E_UNKNOWN_READER as u32, Timeout = ffi::SCARD_E_TIMEOUT as u32, SharingViolation = ffi::SCARD_E_SHARING_VIOLATION as u32, NoSmartcard = ffi::SCARD_E_NO_SMARTCARD as u32, UnknownCard = ffi::SCARD_E_UNKNOWN_CARD as u32, CantDispose = ffi::SCARD_E_CANT_DISPOSE as u32, ProtoMismatch = ffi::SCARD_E_PROTO_MISMATCH as u32, NotReady = ffi::SCARD_E_NOT_READY as u32, InvalidValue = ffi::SCARD_E_INVALID_VALUE as u32, SystemCancelled = ffi::SCARD_E_SYSTEM_CANCELLED as u32, CommError = ffi::SCARD_F_COMM_ERROR as u32, UnknownError = ffi::SCARD_F_UNKNOWN_ERROR as u32, InvalidAtr = ffi::SCARD_E_INVALID_ATR as u32, NotTransacted = ffi::SCARD_E_NOT_TRANSACTED as u32, ReaderUnavailable = ffi::SCARD_E_READER_UNAVAILABLE as u32, Shutdown = ffi::SCARD_P_SHUTDOWN as u32, PciTooSmall = ffi::SCARD_E_PCI_TOO_SMALL as u32, ReaderUnsupported = ffi::SCARD_E_READER_UNSUPPORTED as u32, DuplicateReader = ffi::SCARD_E_DUPLICATE_READER as u32, CardUnsupported = ffi::SCARD_E_CARD_UNSUPPORTED as u32, NoService = ffi::SCARD_E_NO_SERVICE as u32, ServiceStopped = ffi::SCARD_E_SERVICE_STOPPED as u32, #[cfg(target_os = "windows")] Unexpected = ffi::SCARD_E_UNEXPECTED as u32, IccInstallation = ffi::SCARD_E_ICC_INSTALLATION as u32, IccCreateorder = ffi::SCARD_E_ICC_CREATEORDER as u32, UnsupportedFeature = ffi::SCARD_E_UNSUPPORTED_FEATURE as u32, DirNotFound = ffi::SCARD_E_DIR_NOT_FOUND as u32, FileNotFound = ffi::SCARD_E_FILE_NOT_FOUND as u32, NoDir = ffi::SCARD_E_NO_DIR as u32, NoFile = ffi::SCARD_E_NO_FILE as u32, NoAccess = ffi::SCARD_E_NO_ACCESS as u32, WriteTooMany = ffi::SCARD_E_WRITE_TOO_MANY as u32, BadSeek = ffi::SCARD_E_BAD_SEEK as u32, InvalidChv = ffi::SCARD_E_INVALID_CHV as u32, UnknownResMng = ffi::SCARD_E_UNKNOWN_RES_MNG as u32, NoSuchCertificate = ffi::SCARD_E_NO_SUCH_CERTIFICATE as u32, CertificateUnavailable = ffi::SCARD_E_CERTIFICATE_UNAVAILABLE as u32, NoReadersAvailable = ffi::SCARD_E_NO_READERS_AVAILABLE as u32, CommDataLost = ffi::SCARD_E_COMM_DATA_LOST as u32, NoKeyContainer = ffi::SCARD_E_NO_KEY_CONTAINER as u32, ServerTooBusy = ffi::SCARD_E_SERVER_TOO_BUSY as u32, // // UnsupportedCard = ffi::SCARD_W_UNSUPPORTED_CARD as u32, UnresponsiveCard = ffi::SCARD_W_UNRESPONSIVE_CARD as u32, UnpoweredCard = ffi::SCARD_W_UNPOWERED_CARD as u32, ResetCard = ffi::SCARD_W_RESET_CARD as u32, RemovedCard = ffi::SCARD_W_REMOVED_CARD as u32, SecurityViolation = ffi::SCARD_W_SECURITY_VIOLATION as u32, WrongChv = ffi::SCARD_W_WRONG_CHV as u32, ChvBlocked = ffi::SCARD_W_CHV_BLOCKED as u32, Eof = ffi::SCARD_W_EOF as u32, CancelledByUser = ffi::SCARD_W_CANCELLED_BY_USER as u32, CardNotAuthenticated = ffi::SCARD_W_CARD_NOT_AUTHENTICATED as u32, CacheItemNotFound = ffi::SCARD_W_CACHE_ITEM_NOT_FOUND as u32, CacheItemStale = ffi::SCARD_W_CACHE_ITEM_STALE as u32, CacheItemTooBig = ffi::SCARD_W_CACHE_ITEM_TOO_BIG as u32, // } impl Error { fn from_raw(raw: LONG) -> Error { unsafe { // The ranges here are the "blocks" above. if ffi::SCARD_F_INTERNAL_ERROR <= raw && raw <= ffi::SCARD_E_SERVER_TOO_BUSY || ffi::SCARD_W_UNSUPPORTED_CARD <= raw && raw <= ffi::SCARD_W_CACHE_ITEM_TOO_BIG { transmute::(raw as u32) } else { if cfg!(debug_assertions) { panic!("unknown PCSC error code: {:#x}", raw); } // We mask unknown error codes here; this is not very nice, // but seems better than panicking. Error::UnknownError } } } fn into_raw(self) -> LONG { // Note: not using LONG::from() - won't work when LONG is i32. self as u32 as LONG } } impl std::error::Error for Error { fn description(&self) -> &str { // The descriptions are from MSDN. match *self { Error::InternalError => "An internal consistency check failed", Error::Cancelled => "The action was cancelled by an SCardCancel request", Error::InvalidHandle => "The supplied handle was invalid", Error::InvalidParameter => "One or more of the supplied parameters could not be properly interpreted", Error::InvalidTarget => "Registry startup information is missing or invalid", Error::NoMemory => "Not enough memory available to complete this command", Error::WaitedTooLong => "An internal consistency timer has expired", Error::InsufficientBuffer => "The data buffer to receive returned data is too small for the returned data", Error::UnknownReader => "The specified reader name is not recognized", Error::Timeout => "The user-specified timeout value has expired", Error::SharingViolation => "The smart card cannot be accessed because of other connections outstanding", Error::NoSmartcard => "The operation requires a Smart Card, but no Smart Card is currently in the device", Error::UnknownCard => "The specified smart card name is not recognized", Error::CantDispose => "The system could not dispose of the media in the requested manner", Error::ProtoMismatch => "The requested protocols are incompatible with the protocol currently in use with the smart card", Error::NotReady => "The reader or smart card is not ready to accept commands", Error::InvalidValue => "One or more of the supplied parameters values could not be properly interpreted", Error::SystemCancelled => "The action was cancelled by the system, presumably to log off or shut down", Error::CommError => "An internal communications error has been detected", Error::UnknownError => "An internal error has been detected, but the source is unknown", Error::InvalidAtr => "An ATR obtained from the registry is not a valid ATR string", Error::NotTransacted => "An attempt was made to end a non-existent transaction", Error::ReaderUnavailable => "The specified reader is not currently available for use", Error::Shutdown => "The operation has been aborted to allow the server application to exit", Error::PciTooSmall => "The PCI Receive buffer was too small", Error::ReaderUnsupported => "The reader driver does not meet minimal requirements for support", Error::DuplicateReader => "The reader driver did not produce a unique reader name", Error::CardUnsupported => "The smart card does not meet minimal requirements for support", Error::NoService => "The Smart card resource manager is not running", Error::ServiceStopped => "The Smart card resource manager has shut down", #[cfg(target_os = "windows")] Error::Unexpected => "An unexpected card error has occurred", Error::UnsupportedFeature => "This smart card does not support the requested feature", Error::IccInstallation => "No primary provider can be found for the smart card", Error::IccCreateorder => "The requested order of object creation is not supported", Error::DirNotFound => "The identified directory does not exist in the smart card", Error::FileNotFound => "The identified file does not exist in the smart card", Error::NoDir => "The supplied path does not represent a smart card directory", Error::NoFile => "The supplied path does not represent a smart card file", Error::NoAccess => "Access is denied to this file", Error::WriteTooMany => "The smart card does not have enough memory to store the information", Error::BadSeek => "There was an error trying to set the smart card file object pointer", Error::InvalidChv => "The supplied PIN is incorrect", Error::UnknownResMng => "An unrecognized error code was returned from a layered component", Error::NoSuchCertificate => "The requested certificate does not exist", Error::CertificateUnavailable => "The requested certificate could not be obtained", Error::NoReadersAvailable => "Cannot find a smart card reader", Error::CommDataLost => "A communications error with the smart card has been detected. Retry the operation", Error::NoKeyContainer => "The requested key container does not exist on the smart card", Error::ServerTooBusy => "The smart card resource manager is too busy to complete this operation", Error::UnsupportedCard => "The reader cannot communicate with the card, due to ATR string configuration conflicts", Error::UnresponsiveCard => "The smart card is not responding to a reset", Error::UnpoweredCard => "Power has been removed from the smart card, so that further communication is not possible", Error::ResetCard => "The smart card has been reset, so any shared state information is invalid", Error::RemovedCard => "The smart card has been removed, so further communication is not possible", Error::SecurityViolation => "Access was denied because of a security violation", Error::WrongChv => "The card cannot be accessed because the wrong PIN was presented", Error::ChvBlocked => "The card cannot be accessed because the maximum number of PIN entry attempts has been reached", Error::Eof => "The end of the smart card file has been reached", Error::CancelledByUser => r#"The user pressed "Cancel" on a Smart Card Selection Dialog"#, Error::CardNotAuthenticated => "No PIN was presented to the smart card", Error::CacheItemNotFound => "The requested item could not be found in the cache", Error::CacheItemStale => "The requested cache item is too old and was deleted from the cache", Error::CacheItemTooBig => "The new cache item exceeds the maximum per-item size defined for the cache", } } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { f.write_str(std::error::Error::description(self)) } } macro_rules! try_pcsc { ($e:expr) => (match $e { ffi::SCARD_S_SUCCESS => (), err => return Err(Error::from_raw(err)), }); } /// Scope of a context. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Scope { User = ffi::SCARD_SCOPE_USER as u32, Terminal = ffi::SCARD_SCOPE_TERMINAL as u32, System = ffi::SCARD_SCOPE_SYSTEM as u32, Global = ffi::SCARD_SCOPE_GLOBAL as u32, } impl Scope { fn into_raw(self) -> DWORD { DWORD::from(self as u32) } } /// A class of Attributes. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AttributeClass { VendorInfo = ffi::SCARD_CLASS_VENDOR_INFO as u32, Communications = ffi::SCARD_CLASS_COMMUNICATIONS as u32, Protocol = ffi::SCARD_CLASS_PROTOCOL as u32, PowerMgmt = ffi::SCARD_CLASS_POWER_MGMT as u32, Security = ffi::SCARD_CLASS_SECURITY as u32, Mechanical = ffi::SCARD_CLASS_MECHANICAL as u32, VendorDefined = ffi::SCARD_CLASS_VENDOR_DEFINED as u32, IfdProtocol = ffi::SCARD_CLASS_IFD_PROTOCOL as u32, IccState = ffi::SCARD_CLASS_ICC_STATE as u32, System = ffi::SCARD_CLASS_SYSTEM as u32, } /// Card reader attribute types. #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Attribute { VendorName = ffi::SCARD_ATTR_VENDOR_NAME as u32, VendorIfdType = ffi::SCARD_ATTR_VENDOR_IFD_TYPE as u32, VendorIfdVersion = ffi::SCARD_ATTR_VENDOR_IFD_VERSION as u32, VendorIfdSerialNo = ffi::SCARD_ATTR_VENDOR_IFD_SERIAL_NO as u32, ChannelId = ffi::SCARD_ATTR_CHANNEL_ID as u32, AsyncProtocolTypes = ffi::SCARD_ATTR_ASYNC_PROTOCOL_TYPES as u32, DefaultClk = ffi::SCARD_ATTR_DEFAULT_CLK as u32, MaxClk = ffi::SCARD_ATTR_MAX_CLK as u32, DefaultDataRate = ffi::SCARD_ATTR_DEFAULT_DATA_RATE as u32, MaxDataRate = ffi::SCARD_ATTR_MAX_DATA_RATE as u32, MaxIfsd = ffi::SCARD_ATTR_MAX_IFSD as u32, SyncProtocolTypes = ffi::SCARD_ATTR_SYNC_PROTOCOL_TYPES as u32, PowerMgmtSupport = ffi::SCARD_ATTR_POWER_MGMT_SUPPORT as u32, UserToCardAuthDevice = ffi::SCARD_ATTR_USER_TO_CARD_AUTH_DEVICE as u32, UserAuthInputDevice = ffi::SCARD_ATTR_USER_AUTH_INPUT_DEVICE as u32, Characteristics = ffi::SCARD_ATTR_CHARACTERISTICS as u32, CurrentProtocolType = ffi::SCARD_ATTR_CURRENT_PROTOCOL_TYPE as u32, CurrentClk = ffi::SCARD_ATTR_CURRENT_CLK as u32, CurrentF = ffi::SCARD_ATTR_CURRENT_F as u32, CurrentD = ffi::SCARD_ATTR_CURRENT_D as u32, CurrentN = ffi::SCARD_ATTR_CURRENT_N as u32, CurrentW = ffi::SCARD_ATTR_CURRENT_W as u32, CurrentIfsc = ffi::SCARD_ATTR_CURRENT_IFSC as u32, CurrentIfsd = ffi::SCARD_ATTR_CURRENT_IFSD as u32, CurrentBwt = ffi::SCARD_ATTR_CURRENT_BWT as u32, CurrentCwt = ffi::SCARD_ATTR_CURRENT_CWT as u32, CurrentEbcEncoding = ffi::SCARD_ATTR_CURRENT_EBC_ENCODING as u32, ExtendedBwt = ffi::SCARD_ATTR_EXTENDED_BWT as u32, IccPresence = ffi::SCARD_ATTR_ICC_PRESENCE as u32, IccInterfaceStatus = ffi::SCARD_ATTR_ICC_INTERFACE_STATUS as u32, CurrentIoState = ffi::SCARD_ATTR_CURRENT_IO_STATE as u32, AtrString = ffi::SCARD_ATTR_ATR_STRING as u32, IccTypePerAtr = ffi::SCARD_ATTR_ICC_TYPE_PER_ATR as u32, EscReset = ffi::SCARD_ATTR_ESC_RESET as u32, EscCancel = ffi::SCARD_ATTR_ESC_CANCEL as u32, EscAuthrequest = ffi::SCARD_ATTR_ESC_AUTHREQUEST as u32, Maxinput = ffi::SCARD_ATTR_MAXINPUT as u32, DeviceUnit = ffi::SCARD_ATTR_DEVICE_UNIT as u32, DeviceInUse = ffi::SCARD_ATTR_DEVICE_IN_USE as u32, DeviceFriendlyName = ffi::SCARD_ATTR_DEVICE_FRIENDLY_NAME as u32, DeviceSystemName = ffi::SCARD_ATTR_DEVICE_SYSTEM_NAME as u32, SupressT1IfsRequest = ffi::SCARD_ATTR_SUPRESS_T1_IFS_REQUEST as u32, } impl Attribute { fn into_raw(self) -> DWORD { DWORD::from(self as u32) } } /// Maximum amount of bytes in an ATR. pub const MAX_ATR_SIZE: usize = ffi::MAX_ATR_SIZE; /// Maximum amount of bytes in a short APDU command or response. pub const MAX_BUFFER_SIZE: usize = ffi::MAX_BUFFER_SIZE; /// Maximum amount of bytes in an extended APDU command or response. pub const MAX_BUFFER_SIZE_EXTENDED: usize = ffi::MAX_BUFFER_SIZE_EXTENDED; /// A special reader name for detecting card reader insertions and removals. /// /// # Note /// /// This function is a wrapper around a constant, and is intended to be /// used as such. #[allow(non_snake_case)] // We can't have a const &CStr yet, so we simulate it with a function. pub fn PNP_NOTIFICATION() -> &'static CStr { // The panic can't happen, but we avoid unsafe. CStr::from_bytes_with_nul(b"\\\\?PnP?\\Notification\0").unwrap() } /// Transform a control code in the form expected by the platform. /// /// Control codes passed to `Card::control` are usually defined as inputs /// to this function. /// /// This function wraps the `SCARD_CTL_CODE` macro. pub fn ctl_code(code: DWORD) -> DWORD { ffi::SCARD_CTL_CODE(code) } /// A structure for tracking the current state of card readers and cards. /// /// This structure wraps `SCARD_READERSTATE` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga33247d5d1257d59e55647c3bb717db24 /// [2]: https://msdn.microsoft.com/en-us/library/aa379808.aspx #[repr(C)] pub struct ReaderState { // Note: must be directly transmutable to SCARD_READERSTATE. inner: ffi::SCARD_READERSTATE, } // For some reason, linking in windows fails if we put these directly // in statics. This is why we have this function instead of the // SCARD_PCI_* defines from the C API. fn get_protocol_pci(protocol: Protocol) -> &'static ffi::SCARD_IO_REQUEST { unsafe { match protocol { Protocol::T0 => &ffi::g_rgSCardT0Pci, Protocol::T1 => &ffi::g_rgSCardT1Pci, Protocol::RAW => &ffi::g_rgSCardRawPci, } } } struct ContextInner { handle: ffi::SCARDCONTEXT, } /// Library context to the PCSC service. /// /// This structure wraps `SCARDCONTEXT`. pub struct Context { inner: Arc, } /// A connection to a smart card. /// /// This structure wraps `SCARDHANDLE`. pub struct Card { // Keeps the context alive. _context: Context, handle: ffi::SCARDHANDLE, active_protocol: Option, } /// An exclusive transaction with a card. /// /// A transaction ensures uninterrupted access to the card for its /// duration. All other operations performed on the same underlying /// card (even from other processes) will block until the transaction /// is finished. // By taking a mut reference to the card we statically enforce that: // - There can only be one active transaction at a time. // - All operations on the card must be performed through the transaction // for the duration of the transaction's lifetime. pub struct Transaction<'tx> { card: &'tx mut Card, } /// An iterator over card reader names. /// /// The iterator does not perform any copying or allocations; this is left /// to the caller's discretion. It is therefore tied to the underlying /// buffer. #[derive(Clone, Debug)] pub struct ReaderNames<'buf> { buf: &'buf [u8], pos: usize, } impl<'buf> Iterator for ReaderNames<'buf> { type Item = &'buf CStr; fn next(&mut self) -> Option<&'buf CStr> { match self.buf[self.pos..].iter().position(|&c| c == 0) { None | Some(0) => None, Some(len) => { let old_pos = self.pos; self.pos += len + 1; // The panic can't happen, but we avoid unsafe. Some(CStr::from_bytes_with_nul(&self.buf[old_pos..self.pos]).unwrap()) } } } } impl Context { /// Establish a new context. /// /// This function wraps `SCardEstablishContext` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaa1b8970169fd4883a6dc4a8f43f19b67 /// [2]: https://msdn.microsoft.com/en-us/library/aa379479.aspx pub fn establish( scope: Scope, ) -> Result { unsafe { let mut handle: ffi::SCARDCONTEXT = DUMMY_LONG as ffi::SCARDCONTEXT; try_pcsc!(ffi::SCardEstablishContext( scope.into_raw(), null(), null(), &mut handle, )); Ok(Context { inner: Arc::new(ContextInner { handle, }), }) } } /// Release the context. /// /// In case of error, ownership of the context is returned to the /// caller. /// /// This function wraps `SCardReleaseContext` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga6aabcba7744c5c9419fdd6404f73a934 /// [2]: https://msdn.microsoft.com/en-us/library/aa379798.aspx /// /// ## Note /// /// `Context` implements `Drop` which automatically releases the /// context; you only need to call this function if you want to handle /// errors. /// /// If the `Context` was cloned, and a clone is still alive, this /// function will fail with `Error::CantDispose`. pub fn release( self ) -> Result<(), (Context, Error)> { match Arc::try_unwrap(self.inner) { Ok(inner) => { unsafe { let err = ffi::SCardReleaseContext( inner.handle, ); if err != ffi::SCARD_S_SUCCESS { let context = Context { inner: Arc::new(inner) }; return Err((context, Error::from_raw(err))); } // Skip the drop, we did it "manually". forget(inner); Ok(()) } }, Err(arc_inner) => { let context = Context { inner: arc_inner }; Err((context, Error::CantDispose)) } } } /// Check whether the Context is still valid. /// /// This function wraps `SCardIsValidContext` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga722eb66bcc44d391f700ff9065cc080b /// [2]: https://msdn.microsoft.com/en-us/library/aa379788.aspx pub fn is_valid( &self ) -> Result<(), Error> { unsafe { try_pcsc!(ffi::SCardIsValidContext( self.inner.handle, )); Ok(()) } } /// Cancel any ongoing blocking operation in the Context. /// /// See the `cancel.rs` example program. /// /// This function wraps `SCardCancel` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaacbbc0c6d6c0cbbeb4f4debf6fbeeee6 /// [2]: https://msdn.microsoft.com/en-us/library/aa379470.aspx pub fn cancel( &self, ) -> Result<(), Error> { unsafe { try_pcsc!(ffi::SCardCancel( self.inner.handle, )); Ok(()) } } /// List all connected card readers. /// /// `buffer` is a buffer that should be large enough to hold all of /// the connected reader names. The function `list_readers_len` can be /// used to find the exact required length. /// /// Returns an iterator over the reader names. The iterator yields /// values directly from `buffer`. /// /// If the buffer is not large enough to hold all of the names, /// `Error::InsufficientBuffer` is returned. /// /// This function wraps `SCardListReaders` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga93b07815789b3cf2629d439ecf20f0d9 /// [2]: https://msdn.microsoft.com/en-us/library/aa379793.aspx pub fn list_readers<'buf>( &self, buffer: &'buf mut [u8], ) -> Result, Error> { unsafe { assert!(buffer.len() <= std::u32::MAX as usize); let mut buflen = buffer.len() as DWORD; let err = ffi::SCardListReaders( self.inner.handle, null(), buffer.as_mut_ptr() as *mut c_char, &mut buflen, ); if err == Error::NoReadersAvailable.into_raw() { return Ok(ReaderNames { buf: b"\0", pos: 0, }); } if err != ffi::SCARD_S_SUCCESS { return Err(Error::from_raw(err)); } Ok(ReaderNames { buf: &buffer[..buflen as usize], pos: 0, }) } } /// Get the needed length of a buffer to be passed to `list_readers`. /// /// This function wraps `SCardListReaders` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga93b07815789b3cf2629d439ecf20f0d9 /// [2]: https://msdn.microsoft.com/en-us/library/aa379793.aspx pub fn list_readers_len( &self, ) -> Result { unsafe { let mut buflen = DUMMY_DWORD; let err = ffi::SCardListReaders( self.inner.handle, null(), null_mut(), &mut buflen, ); if err == Error::NoReadersAvailable.into_raw() { return Ok(0); } if err != ffi::SCARD_S_SUCCESS { return Err(Error::from_raw(err)); } Ok(buflen as usize) } } /// List all connected card readers, allocating buffers of the required size. /// /// This function wraps `SCardListReaders` ([pcsclite][1], [MSDN][2]). It is an owned version /// of [`list_readers`](#method.list_readers), calling /// [`list_readers_len`](#method.list_readers_len) to determine the required buffer size. /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga93b07815789b3cf2629d439ecf20f0d9 /// [2]: https://msdn.microsoft.com/en-us/library/aa379793.aspx pub fn list_readers_owned(&self) -> Result, Error> { let mut buffer = vec![0u8; self.list_readers_len()?]; Ok(self.list_readers(&mut buffer)?.map(ToOwned::to_owned).collect()) } /// Connect to a card which is present in a reader. /// /// See the `connect.rs` example program. /// /// This function wraps `SCardConnect` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga4e515829752e0a8dbc4d630696a8d6a5 /// [2]: https://msdn.microsoft.com/en-us/library/aa379473.aspx pub fn connect( &self, reader: &CStr, share_mode: ShareMode, preferred_protocols: Protocols, ) -> Result { unsafe { let mut handle: ffi::SCARDHANDLE = DUMMY_LONG as ffi::SCARDHANDLE; let mut raw_active_protocol: DWORD = DUMMY_DWORD; try_pcsc!(ffi::SCardConnect( self.inner.handle, reader.as_ptr(), share_mode.into_raw(), preferred_protocols.bits(), &mut handle, &mut raw_active_protocol, )); let active_protocol = Protocol::from_raw(raw_active_protocol); Ok(Card { _context: self.clone(), handle, active_protocol, }) } } /// Wait for card and card reader state changes. /// /// The function blocks until the state of one of the readers changes /// from corresponding passed-in `ReaderState`. The `ReaderState`s are /// updated to report the new state. /// /// A special reader name, `\\?PnP?\Notification`, can be used to /// detect card reader insertions and removals, as opposed to state /// changes of a specific reader. Use `PNP_NOTIFICATION()` to easily /// obtain a static reference to this name. /// /// See the `monitor.rs` example program. /// /// This function wraps `SCardGetStatusChange` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga33247d5d1257d59e55647c3bb717db24 /// [2]: https://msdn.microsoft.com/en-us/library/aa379773.aspx pub fn get_status_change( &self, timeout: D, readers: &mut [ReaderState], ) -> Result<(), Error> where D: Into> { let timeout_ms = match timeout.into() { Some(duration) => { let timeout_ms_u64 = duration.as_secs() .saturating_mul(1000) .saturating_add(u64::from(duration.subsec_nanos()) / 1_000_000); std::cmp::min(ffi::INFINITE, timeout_ms_u64 as DWORD) }, None => ffi::INFINITE }; unsafe { assert!(readers.len() <= std::u32::MAX as usize); try_pcsc!(ffi::SCardGetStatusChange( self.inner.handle, timeout_ms, readers.as_mut_ptr() as *mut ffi::SCARD_READERSTATE, readers.len() as DWORD, )); Ok(()) } } } impl Drop for ContextInner { fn drop(&mut self) { unsafe { // Error is ignored here; to do proper error handling, // release() should be called manually. let _err = ffi::SCardReleaseContext( self.handle, ); } } } impl Clone for Context { /// Clone the `Context`. /// /// ## Implementation note /// /// This is implemented in the Rust side with an `Arc::clone`. fn clone(&self) -> Self { Context { inner: Arc::clone(&self.inner), } } } unsafe impl Send for Context {} unsafe impl Sync for Context {} impl ReaderState { /// Create a ReaderState for a card reader with a given presumed /// state. pub fn new>( name: T, current_state: State, ) -> ReaderState { ReaderState { inner: ffi::SCARD_READERSTATE { szReader: name.into().into_raw(), // This seems useless to expose. pvUserData: null_mut(), dwCurrentState: current_state.bits(), dwEventState: State::UNAWARE.bits(), cbAtr: 0, rgbAtr: [0; ffi::ATR_BUFFER_SIZE], }, } } /// The name of the card reader. pub fn name(&self) -> &CStr { // Lifetime elision assigns this the same lifetime as &self; this // is what we want, and is safe. unsafe { CStr::from_ptr(self.inner.szReader) } } /// The ATR (Answer To Reset) of the card inserted to the reader. pub fn atr(&self) -> &[u8] { &self.inner.rgbAtr[0..self.inner.cbAtr as usize] } /// The last reported state. pub fn event_state(&self) -> State { State::from_bits_truncate(self.inner.dwEventState) } /// The card event count. /// /// The count is incremented for each card insertion or removal in the /// reader. This can be used to detect a card removal/insertion /// between two calls to `Context::get_status_change()`. pub fn event_count(&self) -> u32 { ((self.inner.dwEventState & 0xFFFF_0000) >> 16) as u32 } /// Sync the currently-known state to the last reported state. pub fn sync_current_state(&mut self) { // In windows it is important that the event count is included; // otherwise PNP_NOTIFICATION is always reported as changed: // https://stackoverflow.com/a/16467368 self.inner.dwCurrentState = self.inner.dwEventState; } } impl Drop for ReaderState { fn drop(&mut self) { // Reclaim the name and drop it immediately. unsafe { CString::from_raw(self.inner.szReader as *mut c_char) }; } } /// Status of a card in a card reader. #[derive(Clone, Debug)] pub struct CardStatus<'names_buf, 'atr_buf> { reader_names: ReaderNames<'names_buf>, state: DWORD, protocol: Option, atr: &'atr_buf [u8], } impl<'names_buf, 'atr_buf> CardStatus<'names_buf, 'atr_buf> { /// Iterator over the names by which the connected card reader is known. pub fn reader_names(&self) -> ReaderNames<'names_buf> { self.reader_names.clone() } /// Current status of the smart card in the reader. pub fn status(&self) -> Status { Status::from_raw(self.state) } /// Current protocol of the card, if any. /// /// The value is meaningful only if a communication protocol has already /// been established. /// /// If connected to a reader directly without an active protocol, returns /// None. pub fn protocol2(&self) -> Option { self.protocol } /// Current protocol of the card, if any. /// /// The value is meaningful only if a communication protocol has already /// been established. /// /// ## Panics /// /// This function panics when connected to a reader directly without an /// active protocol. Use `protocol2()` instead if you want to avoid this. pub fn protocol(&self) -> Protocol { self.protocol.expect( "pcsc::CardStatus::protocol() does not support direct connections; use protocol2() instead" ) } /// The current ATR string of the card. pub fn atr(&self) -> &'atr_buf [u8] { self.atr } } /// Status of a card in a card reader (owned). /// /// This is an owned version of [`CardStatus`](struct.CardStatus.html). #[derive(Clone, Debug)] pub struct CardStatusOwned { reader_names: Vec, state: DWORD, protocol: Option, atr: Vec, } impl CardStatusOwned { /// Slice of the names by which the connected card reader is known. pub fn reader_names(&self) -> &[CString] { &self.reader_names } /// Current status of the smart card in the reader. pub fn status(&self) -> Status { Status::from_raw(self.state) } /// Current protocol of the card, if any. /// /// The value is meaningful only if a communication protocol has already /// been established. /// /// If connected to a reader directly without an active protocol, returns /// None. pub fn protocol2(&self) -> Option { self.protocol } /// Current protocol of the card, if any. /// /// The value is meaningful only if a communication protocol has already /// been established. /// /// ## Panics /// /// This function panics when connected to a reader directly without an /// active protocol. Use `protocol2()` instead if you want to avoid this. pub fn protocol(&self) -> Protocol { self.protocol.expect( "pcsc::CardStatus::protocol() does not support direct connections; use protocol2() instead" ) } /// The current ATR string of the card. pub fn atr(&self) -> &[u8] { &self.atr } } impl Card { /// Start a new exclusive transaction with the card. /// /// Operations on the card for the duration of the transaction /// can only be performed through the returned `Transaction`. /// /// This function wraps `SCardBeginTransaction` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaddb835dce01a0da1d6ca02d33ee7d861 /// [2]: https://msdn.microsoft.com/en-us/library/aa379469.aspx pub fn transaction( &mut self, ) -> Result { unsafe { try_pcsc!(ffi::SCardBeginTransaction( self.handle, )); Ok(Transaction { card: self, }) } } /// Start a new exclusive transaction with the card. /// /// Operations on the card for the duration of the transaction /// can only be performed through the returned `Transaction`. /// /// This function is like [`Card::transaction`], but also returns the /// reference to `self` on error. When starting a transaction, you might /// want to deal with transient errors, like [`Error::ResetCard`], by /// reconnecting to the card, and retrying the transaction. When this /// functionality is wrapped, this doesn't work, because mutable references /// can't be reborrowed (at least in current Rust). This function returns /// the reference, which allows this construct. /// /// This function wraps `SCardBeginTransaction` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaddb835dce01a0da1d6ca02d33ee7d861 /// [2]: https://msdn.microsoft.com/en-us/library/aa379469.aspx pub fn transaction2( &mut self, ) -> Result { unsafe { let err = ffi::SCardBeginTransaction( self.handle, ); if err != ffi::SCARD_S_SUCCESS { return Err((self, Error::from_raw(err))); } return Ok(Transaction { card: self, }) } } /// Reconnect to the card. /// /// This function wraps `SCardReconnect` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gad5d4393ca8c470112ad9468c44ed8940 /// [2]: https://msdn.microsoft.com/en-us/library/aa379797.aspx pub fn reconnect( &mut self, share_mode: ShareMode, preferred_protocols: Protocols, initialization: Disposition, ) -> Result<(), Error> { unsafe { let mut raw_active_protocol: DWORD = DUMMY_DWORD; try_pcsc!(ffi::SCardReconnect( self.handle, share_mode.into_raw(), preferred_protocols.bits(), initialization.into_raw(), &mut raw_active_protocol, )); self.active_protocol = Protocol::from_raw(raw_active_protocol); Ok(()) } } /// Disconnect from the card. /// /// In case of error, ownership of the card is returned to the caller. /// /// This function wraps `SCardDisconnect` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga4be198045c73ec0deb79e66c0ca1738a /// [2]: https://msdn.microsoft.com/en-us/library/aa379475.aspx /// /// ## Note /// /// `Card` implements `Drop` which automatically disconnects the card /// using `Disposition::ResetCard`; you only need to call this /// function if you want to handle errors or use a different /// disposition method. pub fn disconnect( mut self, disposition: Disposition, ) -> Result<(), (Card, Error)> { unsafe { let err = ffi::SCardDisconnect( self.handle, disposition.into_raw(), ); if err != ffi::SCARD_S_SUCCESS { return Err((self, Error::from_raw(err))); } // Skip the drop, we did it "manually". std::ptr::drop_in_place(&mut self._context); forget(self); Ok(()) } } /// Get current info on the card. /// /// This function wraps `SCardStatus` ([pcsclite][1], [MSDN][2]). /// /// ## Deprecated /// /// The reader names and ATR return values are missing. /// /// When there is no active protocol (as when connecting to the reader /// directly), this function panics. /// /// Use `status2()` or `status2_owned()` instead. /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gae49c3c894ad7ac12a5b896bde70d0382 /// [2]: https://msdn.microsoft.com/en-us/library/aa379803.aspx #[deprecated(since="2.3.0", note="Use status2() or status2_owned() instead.")] pub fn status( &self, ) -> Result<(Status, Protocol), Error> { unsafe { let mut raw_status: DWORD = DUMMY_DWORD; let mut raw_protocol: DWORD = DUMMY_DWORD; try_pcsc!(ffi::SCardStatus( self.handle, null_mut(), null_mut(), &mut raw_status, &mut raw_protocol, null_mut(), null_mut(), )); let status = Status::from_raw(raw_status); let protocol = Protocol::from_raw(raw_protocol).expect( "pcsc::Card::status() does not support direct connections; use status2() instead" ); Ok((status, protocol)) } } /// Get current info on the card. /// /// `names_buffer` is a buffer that should be large enough to hold all of /// the reader names. /// /// `atr_buffer` is a buffer that should be large enough to hold the ATR. /// The recommended size is `MAX_ATR_SIZE`, which should be always /// sufficent. /// /// The function `status2_len` can be used to find the exact required /// lengths. /// /// If the buffers are not large enough to hold all of the names or the /// ATR, `Error::InsufficientBuffer` is returned. /// /// This function wraps `SCardStatus` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gae49c3c894ad7ac12a5b896bde70d0382 /// [2]: https://msdn.microsoft.com/en-us/library/aa379803.aspx pub fn status2<'names_buf, 'atr_buf>( &self, names_buffer: &'names_buf mut [u8], atr_buffer: &'atr_buf mut [u8], ) -> Result, Error> { unsafe { assert!(names_buffer.len() <= std::u32::MAX as usize); let mut names_len: DWORD = names_buffer.len() as DWORD; let mut raw_state: DWORD = DUMMY_DWORD; let mut raw_protocol: DWORD = DUMMY_DWORD; assert!(atr_buffer.len() <= std::u32::MAX as usize); let mut atr_len: DWORD = atr_buffer.len() as DWORD; try_pcsc!(ffi::SCardStatus( self.handle, names_buffer.as_mut_ptr() as *mut c_char, &mut names_len, &mut raw_state, &mut raw_protocol, atr_buffer.as_mut_ptr(), &mut atr_len, )); Ok(CardStatus { reader_names: ReaderNames { buf: &names_buffer[..names_len as usize], pos: 0, }, state: raw_state, protocol: Protocol::from_raw(raw_protocol), atr: &atr_buffer[0..atr_len as usize], }) } } /// Get the needed length of the names buffer (first result) and ATR buffer /// (second result) to be passed to `status2`. /// /// This function wraps `SCardStatus` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gae49c3c894ad7ac12a5b896bde70d0382 /// [2]: https://msdn.microsoft.com/en-us/library/aa379803.aspx pub fn status2_len( &self, ) -> Result<(usize, usize), Error> { unsafe { let mut names_len: DWORD = DUMMY_DWORD; let mut raw_state: DWORD = DUMMY_DWORD; let mut raw_protocol: DWORD = DUMMY_DWORD; let mut atr_len: DWORD = DUMMY_DWORD; try_pcsc!(ffi::SCardStatus( self.handle, null_mut(), &mut names_len, &mut raw_state, &mut raw_protocol, null_mut(), &mut atr_len, )); Ok((names_len as usize, atr_len as usize)) } } /// Get current info on the card, allocating buffers of the required size. /// /// This function wraps `SCardStatus` ([pcsclite][1], [MSDN][2]). It is an owned version of /// [`status2`](#method.status2), calling [`status2_len`](#method.status2_len) to determine the /// required buffer sizes. /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gae49c3c894ad7ac12a5b896bde70d0382 /// [2]: https://msdn.microsoft.com/en-us/library/aa379803.aspx pub fn status2_owned(&self) -> Result { let (names_len, atr_len) = self.status2_len()?; let mut names_buffer = vec![0u8; names_len]; let mut atr_buffer = vec![0u8; atr_len]; let (reader_names, state, protocol, atr_len) = { let card_status = self.status2(&mut names_buffer, &mut atr_buffer)?; let reader_names = card_status.reader_names.map(ToOwned::to_owned).collect(); (reader_names, card_status.state, card_status.protocol, card_status.atr.len()) }; atr_buffer.truncate(atr_len); Ok(CardStatusOwned { reader_names, state, protocol, atr: atr_buffer, }) } /// Get an attribute of the card or card reader. /// /// `buffer` is a buffer that should be large enough for the attribute /// data. The function `get_attribute_len` can be used to find the /// exact required length. /// /// Returns a slice into `buffer` containing the attribute data. /// /// If the buffer is not large enough, `Error::InsufficientBuffer` is /// returned. /// /// This function wraps `SCardGetAttrib` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaacfec51917255b7a25b94c5104961602 /// [2]: https://msdn.microsoft.com/en-us/library/aa379559.aspx pub fn get_attribute<'buf>( &self, attribute: Attribute, buffer: &'buf mut [u8], ) -> Result<&'buf [u8], Error> { unsafe { assert!(buffer.len() <= std::u32::MAX as usize); let mut attribute_len = buffer.len() as DWORD; try_pcsc!(ffi::SCardGetAttrib( self.handle, attribute.into_raw(), buffer.as_mut_ptr(), &mut attribute_len, )); Ok(&buffer[0..attribute_len as usize]) } } /// Get the needed length of a buffer to be passed to `get_attribute`. /// /// This function wraps `SCardGetAttrib` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaacfec51917255b7a25b94c5104961602 /// [2]: https://msdn.microsoft.com/en-us/library/aa379559.aspx pub fn get_attribute_len( &self, attribute: Attribute, ) -> Result { unsafe { let mut attribute_len = DUMMY_DWORD; try_pcsc!(ffi::SCardGetAttrib( self.handle, attribute.into_raw(), null_mut(), &mut attribute_len, )); Ok(attribute_len as usize) } } /// Get an attribute of the card or card reader, allocating a buffer of the required size. /// /// This function wraps `SCardGetAttrib` ([pcsclite][1], [MSDN][2]). It is an owned version of /// [`get_attribute`](#method.get_attribute), calling /// [`get_attribute_len`](#method.get_attribute_len) to determine the required buffer size. /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gaacfec51917255b7a25b94c5104961602 /// [2]: https://msdn.microsoft.com/en-us/library/aa379559.aspx pub fn get_attribute_owned(&self, attribute: Attribute) -> Result, Error> { let mut buf = vec![0u8; self.get_attribute_len(attribute)?]; let n = self.get_attribute(attribute, &mut buf)?.len(); buf.truncate(n); Ok(buf) } /// Set an attribute of the card or card reader. /// /// This function wraps `SCardSetAttrib` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga060f0038a4ddfd5dd2b8fadf3c3a2e4f /// [2]: https://msdn.microsoft.com/en-us/library/aa379801.aspx pub fn set_attribute( &self, attribute: Attribute, attribute_data: &[u8], ) -> Result<(), Error> { unsafe { assert!(attribute_data.len() <= std::u32::MAX as usize); try_pcsc!(ffi::SCardSetAttrib( self.handle, attribute.into_raw(), attribute_data.as_ptr(), attribute_data.len() as DWORD, )); Ok(()) } } /// Transmit an APDU command to the card. /// /// `receive_buffer` is a buffer that should be large enough to hold /// the APDU response. /// /// Returns a slice into `receive_buffer` containing the APDU /// response. /// /// If `receive_buffer` is not large enough to hold the APDU response, /// `Error::InsufficientBuffer` is returned. /// /// This function wraps `SCardTransmit` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#ga9a2d77242a271310269065e64633ab99 /// [2]: https://msdn.microsoft.com/en-us/library/aa379804.aspx pub fn transmit<'buf>( &self, send_buffer: &[u8], receive_buffer: &'buf mut [u8], ) -> Result<&'buf [u8], Error> { let active_protocol = self.active_protocol.expect( "pcsc::Card::transmit() does not work with direct connections" ); let send_pci = get_protocol_pci(active_protocol); let recv_pci = null_mut(); assert!(receive_buffer.len() <= std::u32::MAX as usize); let mut receive_len = receive_buffer.len() as DWORD; unsafe { assert!(send_buffer.len() <= std::u32::MAX as usize); try_pcsc!(ffi::SCardTransmit( self.handle, send_pci, send_buffer.as_ptr(), send_buffer.len() as DWORD, recv_pci, receive_buffer.as_mut_ptr(), &mut receive_len, )); Ok(&receive_buffer[0..receive_len as usize]) } } /// Sends a command directly to the reader (driver). /// /// `control_code` is the reader-specific control code. You may need /// to pass it through the `ctl_code()` function, according to the /// driver documentation. /// /// `receive_buffer` is a buffer that should be large enough to hold /// the response. /// /// Returns a slice into `receive_buffer` containing the response. /// /// If `receive_buffer` is not large enough to hold the response, /// `Error::InsufficientBuffer` is returned. /// /// This function wraps `SCardControl` ([pcsclite][1], [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gac3454d4657110fd7f753b2d3d8f4e32f /// [2]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379474(v=vs.85).aspx pub fn control<'buf>( &self, // TODO: This is a portability hazard -- should change to u32 // in the next breaking change release. control_code: DWORD, send_buffer: &[u8], receive_buffer: &'buf mut [u8], ) -> Result<&'buf [u8], Error> { let mut receive_len: DWORD = DUMMY_DWORD; unsafe { assert!(send_buffer.len() <= std::u32::MAX as usize); assert!(receive_buffer.len() <= std::u32::MAX as usize); try_pcsc!(ffi::SCardControl( self.handle, control_code, send_buffer.as_ptr(), send_buffer.len() as DWORD, receive_buffer.as_mut_ptr(), receive_buffer.len() as DWORD, &mut receive_len, )); Ok(&receive_buffer[0..receive_len as usize]) } } } impl Drop for Card { fn drop(&mut self) { unsafe { // Error is ignored here; to do proper error handling, // disconnect() should be called manually. // // Disposition is hard-coded to ResetCard here; to use // another method, disconnect() should be called manually. let _err = ffi::SCardDisconnect( self.handle, Disposition::ResetCard.into_raw(), ); } } } unsafe impl Send for Card {} unsafe impl Sync for Card {} impl<'tx> Transaction<'tx> { /// End the transaction. /// /// In case of error, ownership of the transaction is returned to the /// caller. /// /// This function wraps `SCardEndTransaction` ([pcsclite][1], /// [MSDN][2]). /// /// [1]: https://pcsclite.apdu.fr/api/group__API.html#gae8742473b404363e5c587f570d7e2f3b /// [2]: https://msdn.microsoft.com/en-us/library/aa379477.aspx /// /// ## Note /// /// `Transaction` implements `Drop` which automatically ends the /// transaction using `Disposition::LeaveCard`; you only need to call /// this function if you want to handle errors or use a different /// disposition method. pub fn end( self, disposition: Disposition, ) -> Result<(), (Transaction<'tx>, Error)> { unsafe { let err = ffi::SCardEndTransaction( self.card.handle, disposition.into_raw(), ); if err != 0 { return Err((self, Error::from_raw(err))); } // Skip the drop, we did it "manually". forget(self); Ok(()) } } } impl<'tx> Drop for Transaction<'tx> { fn drop(&mut self) { unsafe { // Error is ignored here; to do proper error handling, // end() should be called manually. // // Disposition is hard-coded to LeaveCard here; to use // another method, end() should be called manually. let _err = ffi::SCardEndTransaction( self.card.handle, Disposition::LeaveCard.into_raw(), ); } } } impl<'tx> Deref for Transaction<'tx> { type Target = Card; fn deref(&self) -> &Card { self.card } }