Skip to main content

pcsc_mon/
lib.rs

1use once_cell::sync::Lazy;
2use pcsc::{Card, Context, ReaderState, Scope, State};
3use std::sync::Mutex;
4use std::{
5    ffi::CString,
6    sync::{atomic::AtomicBool, Arc},
7    time::Duration,
8};
9
10static PCSC_MONITOR: Lazy<Mutex<PcscMonitor>> = Lazy::new(|| Mutex::new(PcscMonitor::new()));
11
12/// Represents a PC/SC monitor that tracks reader and card state changes.
13///
14/// `PcscMonitor` is a singleton interface for monitoring smart card readers
15/// using PC/SC. It supports hotplug detection, card insertion/removal events,
16/// and thread-safe callback registration.
17///
18/// The monitor runs in a background thread once started.
19#[derive(Debug, thiserror::Error)]
20pub enum ReaderError {
21    #[error("PCSC error: {0}")]
22    Pcsc(#[from] pcsc::Error),
23
24    #[error("reader state mutex poisoned")]
25    ReaderStatePoisoned,
26
27    #[error("known readers mutex poisoned")]
28    KnownReadersPoisoned,
29}
30pub struct PcscMonitor {
31    on_reader_added: Option<Arc<dyn Fn(String) + Send + Sync>>,
32    on_reader_removed: Option<Arc<dyn Fn(String) + Send + Sync>>,
33    on_card_inserted: Option<Arc<dyn Fn(&Context, &Card) + Send + Sync>>,
34    on_card_removed: Option<Arc<dyn Fn(String) + Send + Sync>>,
35    on_error: Option<Arc<dyn Fn(ReaderError) + Send + Sync>>,
36    known_readers: Arc<Mutex<Vec<String>>>,
37    reader_states: Arc<Mutex<Vec<(String, State)>>>,
38    started: AtomicBool,
39}
40
41
42
43impl PcscMonitor {
44    /// Gets a global instance of the `PcscMonitor` to register callbacks.
45    ///
46    /// This method returns a locked [`MutexGuard`] to the global monitor instance.
47    /// Use this to attach listeners and start monitoring.
48    ///
49    /// # Example
50    /// ```rust
51    /// let mut monitor = pcsc_mon::PcscMonitor::instance();
52    /// monitor.on_reader_added(|reader| {
53    ///     println!("Reader added: {}", reader);
54    /// });
55    /// monitor.start();
56    /// ```
57    pub fn instance() -> std::sync::MutexGuard<'static, PcscMonitor> {
58        PCSC_MONITOR.lock().expect("PcscMonitor mutex poisoned")
59    }
60    fn new() -> Self {
61        Self {
62            // init other fields...
63            started: AtomicBool::new(false),
64            on_reader_added: None,
65            on_reader_removed: None,
66            on_card_inserted: None,
67            on_card_removed: None,
68            on_error: None,
69            known_readers: Arc::new(Mutex::new(Vec::new())),
70            reader_states: Arc::new(Mutex::new(Vec::new())),
71        }
72    }
73    /// Registers a callback for reader addition events.
74    ///
75    /// The callback is called with the name of the reader when a new reader is connected.
76    pub fn on_reader_added<F>(&mut self, f: F)
77    where
78        F: Fn(String) + Send + Sync + 'static,
79    {
80        self.on_reader_added = Some(Arc::new(f));
81    }
82
83    /// Registers a callback for reader removal events.
84    ///
85    /// The callback is called with the name of the reader when a reader is disconnected.
86    ///
87    /// **Note:** Internally sets the reader state to [`State::IGNORE`]. When the same
88    /// reader is reconnected, a card must be inserted and removed again to re-trigger
89    /// `on_card_inserted`.
90    pub fn on_reader_removed<F>(&mut self, f: F)
91    where
92        F: Fn(String) + Send + Sync + 'static,
93    {
94        self.on_reader_removed = Some(Arc::new(f));
95    }
96    /// Registers a callback for card insertion events.
97    ///
98    /// The callback receives a reference to the [`Context`] and [`Card`] for direct interaction
99    /// with the smart card.
100    ///
101    /// # Note
102    /// The context and card are already connected when the callback runs.
103    pub fn on_card_inserted<F>(&mut self, f: F)
104    where
105        F: Fn(&Context, &Card) + Send + Sync + 'static,
106    {
107        self.on_card_inserted = Some(Arc::new(f));
108    }
109    /// Registers a callback for card removal events.
110    ///
111    /// The callback is called with the name of the reader when the card is removed.
112    pub fn on_card_removed<F>(&mut self, f: F)
113    where
114        F: Fn(String) + Send + Sync + 'static,
115    {
116        self.on_card_removed = Some(Arc::new(f));
117    }
118
119    /// Registers a callback to handle Errors during the pcsc events
120    ///
121    /// The callback is called with the error thrown during reader or card state detection
122    pub fn on_error<F>(&mut self, f: F)
123    where
124        F: Fn(ReaderError) + Send + Sync + 'static,
125    {
126        self.on_error = Some(Arc::new(f));
127    }
128
129    /// Starts the monitoring thread.
130    ///
131    /// Begins polling for reader and card state changes. Should be called after all
132    /// desired callbacks are registered.
133    ///
134    /// This is non-blocking: the monitoring thread runs in the background.
135
136    pub fn start(&mut self) {
137        if self.started.swap(true, std::sync::atomic::Ordering::SeqCst) {
138            // Already started
139            return;
140        }
141        // Establish PC/SC context
142
143        // Clone callbacks for reader thread
144        let on_reader_added = self.on_reader_added.clone();
145        let on_reader_removed = self.on_reader_removed.clone();
146        let on_error_read = self.on_error.clone();
147        let known_readers_mutex = self.known_readers.clone();
148        println!("known readers mutex: {:?}", known_readers_mutex);
149        let reader_states_mutex = self.reader_states.clone();
150        println!(
151            "reader states mutex (list readers thread): {:?}",
152            reader_states_mutex
153        );
154        std::thread::spawn(move || {
155            match Context::establish(Scope::User) {
156                Ok(ctx) => {
157                    loop {
158                        if let Ok(monitor) = PCSC_MONITOR.try_lock() {
159                            if !monitor.started.load(std::sync::atomic::Ordering::SeqCst) {
160                                println!("pcsc-mon has been unstarted");
161                                return ();
162                            }
163                        }
164                        let mut buf = [0u8; 2048];
165                        match ctx.list_readers(&mut buf) {
166                            Ok(readers_raw) => {
167                                let readers = readers_raw
168                                    .map(|r| r.to_string_lossy().into_owned())
169                                    .collect::<Vec<_>>();
170
171                                let mut known_readers;
172                                match known_readers_mutex.lock() {
173                                    Ok(readers) => {
174                                        known_readers = readers;
175                                    }
176                                    Err(e) => {
177                                        if let Some(ref cb) = on_error_read {
178                                            cb(ReaderError::KnownReadersPoisoned);
179                                        } else {
180                                            eprintln!("Reader listing error: {:?}", e)
181                                        }
182                                        e.into_inner().clear();
183                                        continue;
184                                    }
185                                }
186                                let mut reader_states;
187                                match reader_states_mutex.lock() {
188                                    Ok(states) => {
189                                        reader_states = states;
190                                    }
191                                    Err(e) => {
192                                        if let Some(ref cb) = on_error_read {
193                                            cb(ReaderError::ReaderStatePoisoned);
194                                        } else {
195                                            eprintln!("Reader listing error: {:?}", e)
196                                        }
197                                        e.into_inner().clear();
198                                        continue;
199                                    }
200                                }
201
202                                // Detect added readers
203                                for r in readers.iter().filter(|r| !known_readers.contains(r)) {
204                                    reader_states.push((r.clone(), State::UNAWARE));
205                                    if let Some(ref cb) = on_reader_added {
206                                        cb(r.clone());
207                                    }
208                                }
209
210                                // Detect removed readers
211                                for r in known_readers.iter().filter(|r| !readers.contains(r)) {
212                                    if let Some(position) =
213                                        reader_states.iter().position(|(name, _)| name == r)
214                                    {
215                                        reader_states.remove(position);
216                                    }
217                                    if let Some(ref cb) = on_reader_removed {
218                                        cb(r.clone());
219                                    }
220                                }
221
222                                *known_readers = readers;
223                            }
224                            Err(e) => {
225                                if let Some(ref cb) = on_error_read {
226                                    cb(ReaderError::Pcsc(e));
227                                } else {
228                                    eprintln!("Reader listing error: {:?}", e)
229                                }
230                            }
231                        }
232                        std::thread::sleep(std::time::Duration::from_secs(1));
233                    }
234                }
235                Err(e) => {
236                    if let Some(ref cb) = on_error_read {
237                        cb(ReaderError::Pcsc(e));
238                    } else {
239                        eprintln!("Reader listing error: {:?}", e)
240                    }
241                }
242            }
243        });
244
245        // Clone callbacks for card event thread
246        let on_card_inserted = self.on_card_inserted.clone();
247        let on_card_removed = self.on_card_removed.clone();
248        let on_error_card = self.on_error.clone();
249        let reader_states_mutex = self.reader_states.clone();
250        println!(
251            "reader states mutex (reader state thread): {:?}",
252            reader_states_mutex
253        );
254        std::thread::spawn(move || {
255            match Context::establish(Scope::User) {
256                Ok(ctx) => {
257                    loop {
258                        if let Ok(monitor) = PCSC_MONITOR.try_lock() {
259                            if !monitor.started.load(std::sync::atomic::Ordering::SeqCst) {
260                                println!("pcsc-mon has been unstarted");
261                                return ();
262                            }
263                        }
264                        let mut reader_states;
265                        match reader_states_mutex.lock() {
266                            Ok(states) => {
267                                reader_states = states;
268                            }
269                            Err(e) => {
270                                if let Some(ref cb) = on_error_card {
271                                    cb(ReaderError::ReaderStatePoisoned);
272                                } else {
273                                    eprintln!("Reader listing error: {:?}", e)
274                                }
275                                e.into_inner().clear();
276                                continue;
277                            }
278                        }
279
280                        let mut reader_states_structs: Vec<ReaderState> = reader_states
281                            .iter()
282                            .map(|(name, state)| {
283                                ReaderState::new(
284                                    CString::new(name.as_str()).expect("CString::new failed"),
285                                    *state,
286                                )
287                            })
288                            .collect();
289
290                        match ctx.get_status_change(None, &mut reader_states_structs) {
291                            Ok(_) => {
292                                for (idx, state) in reader_states_structs.iter().enumerate() {
293                                    let reader_name = &reader_states[idx].0;
294                                    let event_state = state.event_state();
295                                    let current_state = state.current_state();
296                                    let changed = current_state == State::UNAWARE
297                                        || !event_state.contains(current_state);
298
299                                    // TODO: Check if event state update can be forced after reader is reattached. State goes to ignore until card is inserted and removed again
300
301                                    if !changed {
302                                        continue;
303                                    }
304                                    println!("state changed to {:?}", event_state);
305                                    if event_state.contains(State::PRESENT)
306                                        && !event_state.contains(State::INUSE)
307                                    {
308                                        if let Some(ref cb) = on_card_inserted {
309                                            match ctx.connect(
310                                                state.name(),
311                                                pcsc::ShareMode::Shared,
312                                                pcsc::Protocols::ANY,
313                                            ) {
314                                                Ok(card) => {
315                                                    println!("card connected");
316                                                    cb(&ctx, &card);
317                                                }
318                                                Err(e) => {
319                                                    if let Some(ref cb) = on_error_card {
320                                                        cb(ReaderError::Pcsc(e));
321                                                    } else {
322                                                        eprintln!(
323                                                            "failed to connect to card: {:?}",
324                                                            e
325                                                        )
326                                                    }
327                                                }
328                                            }
329                                        }
330                                    } else if event_state.contains(State::EMPTY) {
331                                        if let Some(ref cb) = on_card_removed {
332                                            cb(reader_name.clone());
333                                        }
334                                    }
335
336                                    reader_states[idx].1 = event_state - State::CHANGED;
337                                }
338                            }
339                            Err(e) => {
340                                if let Some(ref cb) = on_error_card {
341                                    cb(ReaderError::Pcsc(e));
342                                } else {
343                                    eprintln!("get_status_change error: {:?}", e)
344                                }
345                            }
346                        }
347
348                        std::thread::sleep(Duration::from_millis(100));
349                    }
350                }
351                Err(e) => {
352                    if let Some(ref cb) = on_error_card {
353                        cb(ReaderError::Pcsc(e));
354                    } else {
355                        eprintln!("get_status_change error: {:?}", e)
356                    }
357                }
358            }
359            // Track reader states
360        });
361    }
362}