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}