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