Skip to main content

pcsc_mon/
lib.rs

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