1use super::ocard::{card_error, OpenCard};
34use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError};
35use openpgp_card::state::Open;
36use sioul_core::securitykey::{Card, CardError, Readers, Visit, Visited};
37use std::sync::OnceLock;
38use std::time::Duration;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum LinkState {
43 NoNfc,
45 Off,
47 Ready,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Held {
54 pub max_transceive: usize,
56 pub extended: bool,
58 pub via: Via,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum LinkError {
65 Lost,
67 Other(String),
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
73pub enum Via {
74 #[default]
76 Nfc,
77 Usb,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Reach {
84 None,
86 Held(Held),
88 Refused,
90 Busy,
92}
93
94pub trait Link: Send + Sync {
97 fn state(&self) -> LinkState;
99 fn usb_host(&self) -> bool;
101 fn reach(&self) -> Reach;
103 fn transceive(&self, command: &[u8]) -> Result<Vec<u8>, LinkError>;
105 fn wait(&self, timeout: Duration);
107 fn want(&self, wanted: bool);
114}
115
116static LINK: OnceLock<&'static dyn Link> = OnceLock::new();
117
118pub fn set_link(link: &'static dyn Link) {
120 let _ = LINK.set(link);
121}
122
123pub fn link() -> Option<&'static dyn Link> {
125 LINK.get().copied()
126}
127
128pub struct PhoneReaders {
130 link: &'static dyn Link,
131 last: std::sync::Mutex<Via>,
133}
134
135impl PhoneReaders {
136 pub const fn new(link: &'static dyn Link) -> PhoneReaders {
138 PhoneReaders { link, last: std::sync::Mutex::new(Via::Nfc) }
139 }
140
141 fn reached(&self) -> Result<Option<Held>, CardError> {
144 match self.link.reach() {
145 Reach::Held(held) => Ok(Some(held)),
146 Reach::Refused => Err(CardError::UsbRefused),
147 Reach::Busy => Err(CardError::UsbBusy),
148 Reach::None if self.link.usb_host() => Ok(None),
149 Reach::None => match self.link.state() {
150 LinkState::NoNfc => Err(CardError::NoNfc),
151 LinkState::Off => Err(CardError::NfcOff),
152 LinkState::Ready => Ok(None),
153 },
154 }
155 }
156}
157
158pub const HELD: &str = "";
161
162impl Readers for PhoneReaders {
163 fn present(&self) -> Result<Vec<String>, CardError> {
164 Ok(self.reached()?.map(|_| vec![HELD.to_string()]).unwrap_or_default())
165 }
166
167 fn visit(&self, visit: &mut dyn FnMut(&mut dyn Card) -> Visit) -> Result<Visited, CardError> {
168 let Some(held) = self.reached()? else { return Ok(Visited::default()) };
169 if let Ok(mut last) = self.last.lock() {
170 *last = held.via;
171 }
172 let backend: Box<dyn CardBackend + Send + Sync> = Box::new(PhoneBackend { link: self.link, held, exchanges: std::sync::atomic::AtomicUsize::new(0) });
173 let opened = openpgp_card::Card::<Open>::new(backend).map_err(|e| match card_error(e, false) {
174 CardError::Removed => CardError::Removed,
175 _ => CardError::NotOpenPgp,
177 });
178 let result = opened.and_then(|mut opened| {
179 let transaction = opened.transaction().map_err(|e| card_error(e, false))?;
180 let mut card = OpenCard::new(transaction, HELD.to_string());
181 let _ = visit(&mut card);
182 Ok(Visited { opened: 1, busy: 0 })
183 });
184 unverify(self.link);
185 result
186 }
187
188 fn wait_change(&self, timeout: Duration) {
189 self.link.wait(timeout);
190 }
191
192 fn taps(&self) -> bool {
193 true
194 }
195
196 fn want(&self, wanted: bool) {
197 self.link.want(wanted);
198 }
199
200 fn by_cable(&self) -> bool {
201 match self.link.reach() {
202 Reach::Held(held) => held.via == Via::Usb,
203 _ => self.last.lock().is_ok_and(|last| *last == Via::Usb),
204 }
205 }
206
207 fn reach_note(&self) -> Option<CardError> {
208 if !self.link.usb_host() {
209 return None;
210 }
211 match self.link.state() {
212 LinkState::NoNfc => Some(CardError::NoNfc),
213 LinkState::Off => Some(CardError::NfcOff),
214 LinkState::Ready => None,
215 }
216 }
217}
218
219fn unverify(link: &dyn Link) {
224 for mode in [0x81, 0x82] {
225 let _ = link.transceive(&[0x00, 0x20, 0xFF, mode]);
226 }
227}
228
229pub fn limit(caps: CardCaps, held: Held) -> CardCaps {
233 let extended = caps.ext_support() && held.extended;
234 let room = u16::try_from(held.max_transceive.saturating_sub(9)).unwrap_or(u16::MAX);
236 let (command, answer) = if extended { (caps.max_cmd_bytes().min(room), caps.max_rsp_bytes().min(room)) } else { (caps.max_cmd_bytes().min(255), caps.max_rsp_bytes().min(256)) };
237 CardCaps::new(extended, caps.chaining_support(), command, answer, caps.pw1_max_len(), caps.pw3_max_len())
238}
239
240pub const MOST_EXCHANGES: usize = 512;
245
246struct PhoneBackend {
247 link: &'static dyn Link,
248 held: Held,
249 exchanges: std::sync::atomic::AtomicUsize,
251}
252
253impl CardBackend for PhoneBackend {
254 fn limit_card_caps(&self, caps: CardCaps) -> CardCaps {
255 limit(caps, self.held)
256 }
257
258 fn transaction(&mut self, _reselect: Option<&[u8]>) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, SmartcardError> {
259 Ok(Box::new(PhoneTransaction { link: self.link, exchanges: &self.exchanges }))
260 }
261}
262
263struct PhoneTransaction<'a> {
264 link: &'static dyn Link,
265 exchanges: &'a std::sync::atomic::AtomicUsize,
266}
267
268impl CardTransaction for PhoneTransaction<'_> {
269 fn transmit(&mut self, command: &[u8], _buf_size: usize) -> Result<Vec<u8>, SmartcardError> {
270 if self.exchanges.fetch_add(1, std::sync::atomic::Ordering::Relaxed) >= MOST_EXCHANGES {
271 return Err(SmartcardError::Error(format!("more than {MOST_EXCHANGES} exchanges in one visit")));
272 }
273 self.link.transceive(command).map_err(|e| match e {
274 LinkError::Lost => SmartcardError::Error("RemovedCard: the key left the phone".into()),
276 LinkError::Other(code) => SmartcardError::Error(code),
277 })
278 }
279
280 fn feature_pinpad_verify(&self) -> bool {
281 false
282 }
283
284 fn feature_pinpad_modify(&self) -> bool {
285 false
286 }
287
288 fn pinpad_verify(&mut self, _pin: PinType, _card_caps: &Option<CardCaps>) -> Result<Vec<u8>, SmartcardError> {
289 Err(SmartcardError::Error("no PIN pad".into()))
290 }
291
292 fn pinpad_modify(&mut self, _pin: PinType, _card_caps: &Option<CardCaps>) -> Result<Vec<u8>, SmartcardError> {
293 Err(SmartcardError::Error("no PIN pad".into()))
294 }
295
296 fn was_reset(&self) -> bool {
297 false
298 }
299}
300
301#[cfg(test)]
302#[path = "phone_tests.rs"]
303mod tests;