Skip to main content

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}