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 // match catch_unwind(AssertUnwindSafe(|| cb(r.clone()))) {
215 // Ok(_) => print!("Saul Goodman"),
216 // Err(panicms) => {
217 // if let Some(ref cb) = on_error_read {
218 // cb(ReaderError::HandlerPanicked(Error::msg(
219 // panic_message(panicms),
220 // )));
221 // } else {
222 // eprintln!(
223 // "failed to connect to card: {:?}",
224 // panicms
225 // );
226 // }
227 // }
228 // };
229 Self::handle_callback(
230 || cb(r.clone()),
231 |msg| {
232 if let Some(ref cb) = on_error_read {
233 cb(msg);
234 } else {
235 eprintln!(
236 "failed to connect to card: {:?}",
237 msg
238 );
239 }
240 },
241 );
242 }
243 }
244
245 // Detect removed readers
246 for r in known_readers.iter().filter(|r| !readers.contains(r)) {
247 if let Some(position) =
248 reader_states.iter().position(|(name, _)| name == r)
249 {
250 reader_states.remove(position);
251 }
252 if let Some(ref cb) = on_reader_removed {
253 // match catch_unwind(AssertUnwindSafe(|| cb(r.clone()))) {
254 // Ok(_) => print!("Saul Goodman"),
255 // Err(panicms) => {
256 // if let Some(ref cb) = on_error_read {
257 // cb(ReaderError::HandlerPanicked(Error::msg(
258 // panic_message(panicms),
259 // )));
260 // } else {
261 // eprintln!(
262 // "failed to connect to card: {:?}",
263 // panicms
264 // );
265 // }
266 // }
267 // };
268 Self::handle_callback(
269 || cb(r.clone()),
270 |msg| {
271 if let Some(ref cb) = on_error_read {
272 cb(msg);
273 } else {
274 eprintln!(
275 "failed to connect to card: {:?}",
276 msg
277 );
278 }
279 },
280 );
281 }
282 }
283
284 *known_readers = readers;
285 }
286 Err(e) => {
287 if let Some(ref cb) = on_error_read {
288 Self::handle_callback(
289 || cb(ReaderError::Pcsc(e)),
290 |msg| cb(msg),
291 );
292 } else {
293 eprintln!("Reader listing error: {:?}", e)
294 }
295 if e.eq(&pcsc::Error::ServiceStopped) {
296 *known_readers = vec![];
297 *reader_states = vec![];
298 break;
299 }
300 }
301 }
302 std::thread::sleep(std::time::Duration::from_secs(1));
303 }
304 }
305 Err(e) => {
306 if let Some(ref cb) = on_error_read {
307 // match catch_unwind(AssertUnwindSafe(|| {
308 // cb(ReaderError::Pcsc(e));
309 // })) {
310 // Ok(_) => print!("Saul Goodman"),
311 // Err(panicms) => {
312 // if let Some(ref cb) = on_error_read {
313 // cb(ReaderError::HandlerPanicked(Error::msg(panic_message(
314 // panicms,
315 // ))));
316 // } else {
317 // eprintln!("failed to connect to card: {:?}", panicms);
318 // }
319 // }
320 // };
321 Self::handle_callback(|| cb(ReaderError::Pcsc(e)), |msg| cb(msg));
322 } else {
323 eprintln!("Reader listing error: {:?}", e)
324 }
325 }
326 }
327 std::thread::sleep(std::time::Duration::from_secs(1));
328 })
329 }
330
331 fn spawn_reader_state(&mut self) -> JoinHandle<()> {
332 let on_card_inserted = self.on_card_inserted.clone();
333 let on_card_removed = self.on_card_removed.clone();
334 let on_error_card = self.on_error.clone();
335 let reader_states_mutex = self.reader_states.clone();
336 println!(
337 "reader states mutex (reader state thread): {:?}",
338 reader_states_mutex
339 );
340 std::thread::spawn(move || loop {
341 match Context::establish(Scope::User) {
342 Ok(ctx) => {
343 loop {
344 let mut reader_states;
345 match reader_states_mutex.lock() {
346 Ok(states) => {
347 reader_states = states;
348 }
349 Err(e) => {
350 if let Some(ref cb) = on_error_card {
351 Self::handle_callback(
352 || cb(ReaderError::ReaderStatePoisoned),
353 |msg| cb(msg),
354 );
355 } else {
356 eprintln!("Reader listing error: {:?}", e)
357 }
358 e.into_inner().clear();
359 reader_states_mutex.clear_poison();
360 std::thread::sleep(Duration::from_millis(100));
361 continue;
362 }
363 }
364
365 let mut reader_states_structs: Vec<ReaderState> = reader_states
366 .iter()
367 .map(|(name, state)| {
368 ReaderState::new(
369 CString::new(name.as_str()).expect("CString::new failed"),
370 *state,
371 )
372 })
373 .collect();
374
375 match ctx.get_status_change(None, &mut reader_states_structs) {
376 Ok(_) => {
377 for (idx, state) in reader_states_structs.iter().enumerate() {
378 let reader_name = &reader_states[idx].0;
379 let event_state = state.event_state();
380 let current_state = state.current_state();
381 let changed = current_state == State::UNAWARE
382 || !event_state.contains(current_state);
383
384 // TODO: Check if event state update can be forced after reader is reattached. State goes to ignore until card is inserted and removed again
385
386 if !changed {
387 continue;
388 }
389 //println!("state changed to {:?}", event_state);
390 if event_state.contains(State::PRESENT)
391 && !event_state.contains(State::INUSE)
392 {
393 if let Some(ref cb) = on_card_inserted {
394 match ctx.connect(
395 state.name(),
396 pcsc::ShareMode::Shared,
397 pcsc::Protocols::ANY,
398 ) {
399 Ok(card) => {
400 Self::handle_callback(
401 || cb(&ctx, &card),
402 |msg| {
403 if let Some(ref cb) = on_error_card {
404 cb(msg);
405 } else {
406 eprintln!(
407 "failed to connect to card: {:?}",
408 msg
409 );
410 }
411 },
412 );
413 }
414 Err(e) => {
415 if let Some(ref cb) = on_error_card {
416 Self::handle_callback(
417 || cb(ReaderError::Pcsc(e)),
418 |msg| {
419 if let Some(ref cb) = on_error_card
420 {
421 cb(msg);
422 } else {
423 eprintln!(
424 "failed to connect to card: {:?}",
425 msg
426 );
427 }
428 },
429 );
430 } else {
431 eprintln!(
432 "failed to connect to card: {:?}",
433 e
434 )
435 }
436 }
437 }
438 }
439 } else if event_state.contains(State::EMPTY) {
440 if let Some(ref cb) = on_card_removed {
441 Self::handle_callback(
442 || cb(reader_name.clone()),
443 |msg| {
444 if let Some(ref cb) = on_error_card {
445 cb(msg);
446 } else {
447 eprintln!(
448 "failed to connect to card: {:?}",
449 msg
450 );
451 }
452 },
453 );
454 }
455 }
456
457 reader_states[idx].1 = event_state - State::CHANGED;
458 }
459 }
460 Err(e) => {
461 if let Some(ref cb) = on_error_card {
462 Self::handle_callback(
463 || cb(ReaderError::Pcsc(e)),
464 |msg| cb(msg),
465 );
466 } else {
467 eprintln!("get_status_change error: {:?}", e)
468 }
469 if e.eq(&pcsc::Error::ServiceStopped) {
470 *reader_states = vec![];
471 break;
472 }
473 }
474 }
475
476 std::thread::sleep(Duration::from_millis(100));
477 }
478 }
479 Err(e) => {
480 if let Some(ref cb) = on_error_card {
481 Self::handle_callback(|| cb(ReaderError::Pcsc(e)), |msg| cb(msg));
482 } else {
483 eprintln!("get_status_change error: {:?}", e)
484 }
485 }
486 }
487 std::thread::sleep(std::time::Duration::from_secs(1));
488 // Track reader states
489 })
490 }
491
492 /// Starts the monitoring thread.
493 ///
494 /// Begins polling for reader and card state changes. Should be called after all
495 /// desired callbacks are registered.
496 ///
497 /// This is non-blocking: the monitoring thread runs in the background.
498
499 pub fn start(&mut self) {
500 if self.started.swap(true, std::sync::atomic::Ordering::SeqCst) {
501 // Already started
502 return;
503 }
504 // Establish PC/SC context
505
506 // Clone callbacks for reader thread
507 let _detector_handle = self.spawn_reader_detector();
508
509 // Clone callbacks for card event thread
510 let _state_handle = self.spawn_reader_state();
511
512 // std::thread::spawn(move || loop {
513 // if detector_handle.is_finished() {
514 // match PCSC_MONITOR.try_lock(){
515 // Ok(mut monitor)=>{
516 // detector_handle = monitor.spawn_reader_detector();
517 // },
518 // Err(e)=>println!("{:?}", e),
519
520 // }
521
522 // }
523 // if state_handle.is_finished() {
524 // match PCSC_MONITOR.try_lock(){
525 // Ok(mut monitor)=>{
526 // state_handle = monitor.spawn_reader_state();
527 // },
528 // Err(e)=>println!("{:?}", e),
529
530 // }
531 // }
532 // std::thread::sleep(Duration::from_secs(10));
533 // });
534 }
535}