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}