pcsc_mon/lib.rs
1use anyhow::Error;
2use once_cell::sync::Lazy;
3use pcsc::{Card, Context, ReaderState, Scope, State};
4use std::panic::{catch_unwind, AssertUnwindSafe};
5use std::sync::Mutex;
6use std::thread::{JoinHandle};
7use std::{
8 ffi::CString,
9 sync::{atomic::AtomicBool, Arc},
10 time::Duration
11};
12
13static PCSC_MONITOR: Lazy<Mutex<PcscMonitor>> = Lazy::new(|| Mutex::new(PcscMonitor::new()));
14
15/// Represents a PC/SC monitor that tracks reader and card state changes.
16///
17/// `PcscMonitor` is a singleton interface for monitoring smart card readers
18/// using PC/SC. It supports hotplug detection, card insertion/removal events,
19/// and thread-safe callback registration.
20///
21/// The monitor runs in a background thread once started.
22#[derive(Debug, thiserror::Error)]
23pub enum ReaderError {
24 #[error("PCSC error: {0}")]
25 Pcsc(#[from] pcsc::Error),
26
27 #[error("reader state mutex poisoned")]
28 ReaderStatePoisoned,
29
30 #[error("known readers mutex poisoned")]
31 KnownReadersPoisoned,
32
33 #[error("Handler Panic:{0}")]
34 HandlerPanicked(#[from] Error),
35}
36
37fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
38 if let Some(s) = payload.downcast_ref::<&str>() {
39 s.to_string()
40 } else if let Some(s) = payload.downcast_ref::<String>() {
41 s.clone()
42 } else {
43 "unknown panic payload".to_string()
44 }
45}
46pub struct PcscMonitor {
47 on_reader_added: Option<Arc<dyn Fn(String) + Send + Sync>>,
48 on_reader_removed: Option<Arc<dyn Fn(String) + Send + Sync>>,
49 on_card_inserted: Option<Arc<dyn Fn(&Context, &Card) + Send + Sync>>,
50 on_card_removed: Option<Arc<dyn Fn(String) + Send + Sync>>,
51 on_error: Option<Arc<dyn Fn(ReaderError) + Send + Sync>>,
52 known_readers: Arc<Mutex<Vec<String>>>,
53 reader_states: Arc<Mutex<Vec<(String, State)>>>,
54 started: AtomicBool,
55}
56
57impl PcscMonitor {
58 /// Gets a global instance of the `PcscMonitor` to register callbacks.
59 ///
60 /// This method returns a locked [`MutexGuard`] to the global monitor instance.
61 /// Use this to attach listeners and start monitoring.
62 ///
63 /// # Example
64 /// ```rust
65 /// let mut monitor = pcsc_mon::PcscMonitor::instance();
66 /// monitor.on_reader_added(|reader| {
67 /// println!("Reader added: {}", reader);
68 /// });
69 /// monitor.start();
70 /// ```
71 pub fn instance() -> std::sync::MutexGuard<'static, PcscMonitor> {
72 PCSC_MONITOR.lock().expect("PcscMonitor mutex poisoned")
73 }
74 fn new() -> Self {
75 Self {
76 // init other fields...
77 started: AtomicBool::new(false),
78 on_reader_added: None,
79 on_reader_removed: None,
80 on_card_inserted: None,
81 on_card_removed: None,
82 on_error: None,
83 known_readers: Arc::new(Mutex::new(Vec::new())),
84 reader_states: Arc::new(Mutex::new(Vec::new())),
85 }
86 }
87 /// Registers a callback for reader addition events.
88 ///
89 /// The callback is called with the name of the reader when a new reader is connected.
90 pub fn on_reader_added<F>(&mut self, f: F)
91 where
92 F: Fn(String) + Send + Sync + 'static,
93 {
94 self.on_reader_added = Some(Arc::new(f));
95 }
96
97 /// Registers a callback for reader removal events.
98 ///
99 /// The callback is called with the name of the reader when a reader is disconnected.
100 ///
101 /// **Note:** Internally sets the reader state to [`State::IGNORE`]. When the same
102 /// reader is reconnected, a card must be inserted and removed again to re-trigger
103 /// `on_card_inserted`.
104 pub fn on_reader_removed<F>(&mut self, f: F)
105 where
106 F: Fn(String) + Send + Sync + 'static,
107 {
108 self.on_reader_removed = Some(Arc::new(f));
109 }
110 /// Registers a callback for card insertion events.
111 ///
112 /// The callback receives a reference to the [`Context`] and [`Card`] for direct interaction
113 /// with the smart card.
114 ///
115 /// # Note
116 /// The context and card are already connected when the callback runs.
117 pub fn on_card_inserted<F>(&mut self, f: F)
118 where
119 F: Fn(&Context, &Card) + Send + Sync + 'static,
120 {
121 self.on_card_inserted = Some(Arc::new(f));
122 }
123 /// Registers a callback for card removal events.
124 ///
125 /// The callback is called with the name of the reader when the card is removed.
126 pub fn on_card_removed<F>(&mut self, f: F)
127 where
128 F: Fn(String) + Send + Sync + 'static,
129 {
130 self.on_card_removed = Some(Arc::new(f));
131 }
132
133 /// Registers a callback to handle Errors during the pcsc events
134 ///
135 /// The callback is called with the error thrown during reader or card state detection
136 pub fn on_error<F>(&mut self, f: F)
137 where
138 F: Fn(ReaderError) + Send + Sync + 'static,
139 {
140 self.on_error = Some(Arc::new(f));
141 }
142
143 fn handle_callback<T: Fn() -> (), E: Fn(ReaderError) + Send + Sync>(f: T, error: E) {
144 match catch_unwind(AssertUnwindSafe(|| f())) {
145 Ok(_) => {}
146 Err(panicms) => error(ReaderError::HandlerPanicked(Error::msg(panic_message(
147 panicms,
148 )))),
149 };
150 }
151
152 fn spawn_reader_detector(&mut self) -> JoinHandle<()> {
153 let on_reader_added = self.on_reader_added.clone();
154 let on_reader_removed = self.on_reader_removed.clone();
155 let on_error_read = self.on_error.clone();
156 let known_readers_mutex = self.known_readers.clone();
157 let reader_states_mutex = self.reader_states.clone();
158
159 std::thread::spawn(move || loop {
160 match Context::establish(Scope::User) {
161 Ok(ctx) => {
162 loop {
163 let mut buf = [0u8; 2048];
164 let mut known_readers;
165 match known_readers_mutex.lock() {
166 Ok(readers) => {
167 known_readers = readers;
168 }
169 Err(e) => {
170 if let Some(ref cb) = on_error_read {
171 Self::handle_callback(
172 || cb(ReaderError::KnownReadersPoisoned),
173 |msg| cb(msg),
174 );
175 } else {
176 eprintln!("Reader listing error: {:?}", e)
177 }
178 e.into_inner().clear();
179 known_readers_mutex.clear_poison();
180 std::thread::sleep(std::time::Duration::from_secs(1));
181 continue;
182 }
183 }
184 let mut reader_states;
185 match reader_states_mutex.lock() {
186 Ok(states) => {
187 reader_states = states;
188 }
189 Err(e) => {
190 if let Some(ref cb) = on_error_read {
191 Self::handle_callback(
192 || cb(ReaderError::ReaderStatePoisoned),
193 |msg| cb(msg),
194 );
195 } else {
196 eprintln!("Reader listing error: {:?}", e)
197 }
198 e.into_inner().clear();
199 reader_states_mutex.clear_poison();
200 std::thread::sleep(std::time::Duration::from_secs(1));
201 continue;
202 }
203 }
204 match ctx.list_readers(&mut buf) {
205 Ok(readers_raw) => {
206 let readers = readers_raw
207 .map(|r| r.to_string_lossy().into_owned())
208 .collect::<Vec<_>>();
209
210 // Detect added readers
211 for r in readers.iter().filter(|r| !known_readers.contains(r)) {
212 reader_states.push((r.clone(), State::UNAWARE));
213 if let Some(ref cb) = on_reader_added {
214
215 Self::handle_callback(
216 || cb(r.clone()),
217 |msg| {
218 if let Some(ref cb) = on_error_read {
219 cb(msg);
220 } else {
221 eprintln!(
222 "failed to connect to card: {:?}",
223 msg
224 );
225 }
226 },
227 );
228 }
229 }
230
231 // Detect removed readers
232 for r in known_readers.iter().filter(|r| !readers.contains(r)) {
233 if let Some(position) =
234 reader_states.iter().position(|(name, _)| name == r)
235 {
236 reader_states.remove(position);
237 }
238 if let Some(ref cb) = on_reader_removed {
239 // match catch_unwind(AssertUnwindSafe(|| cb(r.clone()))) {
240 // Ok(_) => print!("Saul Goodman"),
241 // Err(panicms) => {
242 // if let Some(ref cb) = on_error_read {
243 // cb(ReaderError::HandlerPanicked(Error::msg(
244 // panic_message(panicms),
245 // )));
246 // } else {
247 // eprintln!(
248 // "failed to connect to card: {:?}",
249 // panicms
250 // );
251 // }
252 // }
253 // };
254 Self::handle_callback(
255 || cb(r.clone()),
256 |msg| {
257 if let Some(ref cb) = on_error_read {
258 cb(msg);
259 } else {
260 eprintln!(
261 "failed to connect to card: {:?}",
262 msg
263 );
264 }
265 },
266 );
267 }
268 }
269
270 *known_readers = readers;
271 }
272 Err(e) => {
273 if let Some(ref cb) = on_error_read {
274 Self::handle_callback(
275 || cb(ReaderError::Pcsc(e)),
276 |msg| cb(msg),
277 );
278 } else {
279 eprintln!("Reader listing error: {:?}", e)
280 }
281 if e.eq(&pcsc::Error::ServiceStopped) {
282 *known_readers = vec![];
283 *reader_states = vec![];
284 break;
285 }
286 }
287 }
288 std::mem::drop(reader_states);
289 std::thread::sleep(std::time::Duration::from_secs(10));
290 }
291 }
292 Err(e) => {
293 if let Some(ref cb) = on_error_read {
294 // match catch_unwind(AssertUnwindSafe(|| {
295 // cb(ReaderError::Pcsc(e));
296 // })) {
297 // Ok(_) => print!("Saul Goodman"),
298 // Err(panicms) => {
299 // if let Some(ref cb) = on_error_read {
300 // cb(ReaderError::HandlerPanicked(Error::msg(panic_message(
301 // panicms,
302 // ))));
303 // } else {
304 // eprintln!("failed to connect to card: {:?}", panicms);
305 // }
306 // }
307 // };
308 Self::handle_callback(|| cb(ReaderError::Pcsc(e)), |msg| cb(msg));
309 } else {
310 eprintln!("Reader listing error: {:?}", e)
311 }
312 }
313 }
314 std::thread::sleep(std::time::Duration::from_secs(1));
315 })
316 }
317
318 fn spawn_reader_state(&mut self) -> JoinHandle<()> {
319 let on_card_inserted = self.on_card_inserted.clone();
320 let on_card_removed = self.on_card_removed.clone();
321 let on_error_card = self.on_error.clone();
322 let reader_states_mutex = self.reader_states.clone();
323
324 std::thread::spawn(move || loop {
325 match Context::establish(Scope::User) {
326 Ok(ctx) => {
327
328 loop {
329 let mut reader_states;
330 match reader_states_mutex.lock() {
331 Ok(states) => {
332 reader_states = states;
333 }
334 Err(e) => {
335 if let Some(ref cb) = on_error_card {
336 Self::handle_callback(
337 || cb(ReaderError::ReaderStatePoisoned),
338 |msg| cb(msg),
339 );
340 } else {
341 eprintln!("Reader listing error: {:?}", e)
342 }
343 e.into_inner().clear();
344 reader_states_mutex.clear_poison();
345 std::thread::sleep(Duration::from_millis(100));
346 continue;
347 }
348 }
349
350 let mut reader_states_structs: Vec<ReaderState> = reader_states
351 .iter()
352 .map(|(name, state)| {
353 ReaderState::new(
354 CString::new(name.as_str()).expect("CString::new failed"),
355 *state,
356 )
357 })
358 .collect();
359
360 match ctx.get_status_change(None, &mut reader_states_structs) {
361 Ok(_) => {
362 for (idx, state) in reader_states_structs.iter().enumerate() {
363 let reader_name = &reader_states[idx].0;
364 let event_state = state.event_state();
365 let current_state = state.current_state();
366 let changed = current_state == State::UNAWARE
367 || !event_state.contains(current_state);
368
369 // TODO: Check if event state update can be forced after reader is reattached. State goes to ignore until card is inserted and removed again
370
371 if !changed {
372 continue;
373 }
374 if event_state.contains(State::PRESENT)
375 && !event_state.contains(State::INUSE)
376 {
377 if let Some(ref cb) = on_card_inserted {
378 match ctx.connect(
379 state.name(),
380 pcsc::ShareMode::Shared,
381 pcsc::Protocols::ANY,
382 ) {
383 Ok(card) => {
384
385 Self::handle_callback(
386 || cb(&ctx, &card),
387 |msg| {
388 if let Some(ref cb) = on_error_card {
389 cb(msg);
390 } else {
391 eprintln!(
392 "failed to connect to card: {:?}",
393 msg
394 );
395 }
396 },
397 );
398 }
399 Err(e) => {
400
401 if let Some(ref cb) = on_error_card {
402 Self::handle_callback(
403 || cb(ReaderError::Pcsc(e)),
404 |msg| {
405 if let Some(ref cb) = on_error_card
406 {
407 cb(msg);
408 } else {
409 eprintln!(
410 "failed to connect to card: {:?}",
411 msg
412 );
413 }
414 },
415 );
416 } else {
417 eprintln!(
418 "failed to connect to card: {:?}",
419 e
420 )
421 }
422 }
423 }
424 }
425 } else if event_state.contains(State::EMPTY) {
426
427 if let Some(ref cb) = on_card_removed {
428 Self::handle_callback(
429 || cb(reader_name.clone()),
430 |msg| {
431 if let Some(ref cb) = on_error_card {
432 cb(msg);
433 } else {
434 eprintln!(
435 "failed to connect to card: {:?}",
436 msg
437 );
438 }
439 },
440 );
441 }
442 }
443 reader_states[idx].1 = event_state - State::CHANGED;
444 }
445 }
446 Err(e) => {
447 if let Some(ref cb) = on_error_card {
448 Self::handle_callback(
449 || cb(ReaderError::Pcsc(e)),
450 |msg| cb(msg),
451 );
452 } else {
453 eprintln!("get_status_change error: {:?}", e)
454 }
455 if e.eq(&pcsc::Error::ServiceStopped) {
456 *reader_states = vec![];
457 break;
458 }
459 }
460 }
461 std::mem::drop(reader_states);
462 std::thread::sleep(Duration::from_millis(500));
463 }
464 }
465 Err(e) => {
466 if let Some(ref cb) = on_error_card {
467 Self::handle_callback(|| cb(ReaderError::Pcsc(e)), |msg| cb(msg));
468 } else {
469 eprintln!("get_status_change error: {:?}", e)
470 }
471 }
472 }
473 std::thread::sleep(std::time::Duration::from_secs(1));
474 // Track reader states
475 })
476 }
477
478 /// Starts the monitoring thread.
479 ///
480 /// Begins polling for reader and card state changes. Should be called after all
481 /// desired callbacks are registered.
482 ///
483 /// This is non-blocking: the monitoring thread runs in the background.
484
485 pub fn start(&mut self) {
486 if self.started.swap(true, std::sync::atomic::Ordering::SeqCst) {
487 // Already started
488 return;
489 }
490 // Establish PC/SC context
491
492 // Clone callbacks for reader thread
493 self.spawn_reader_detector();
494
495 // Clone callbacks for card event thread
496 self.spawn_reader_state();
497
498 }
499}