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::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}