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