webauthn_authenticator_rs/transport/
any.rs

1//! Abstraction to merge all available transports for the platform.
2//!
3//! This is still a work in progress, and doesn't yet handle tokens quite as
4//! well as we'd like.
5#[cfg(doc)]
6use crate::stubs::*;
7
8use async_stream::stream;
9use futures::{stream::FusedStream, StreamExt};
10
11#[cfg(any(all(doc, not(doctest)), feature = "bluetooth"))]
12use crate::bluetooth::*;
13#[cfg(any(all(doc, not(doctest)), feature = "nfc"))]
14use crate::nfc::*;
15use crate::transport::*;
16#[cfg(any(all(doc, not(doctest)), feature = "usb"))]
17use crate::usb::*;
18
19/// [AnyTransport] merges all available transports for the platform.
20#[derive(Debug)]
21pub struct AnyTransport {
22    #[cfg(any(all(doc, not(doctest)), feature = "bluetooth"))]
23    pub bluetooth: BluetoothTransport,
24    #[cfg(any(all(doc, not(doctest)), feature = "nfc"))]
25    pub nfc: Option<NFCTransport>,
26    #[cfg(any(all(doc, not(doctest)), feature = "usb"))]
27    pub usb: USBTransport,
28}
29
30/// [AnyToken] abstracts calls to physical authenticators.
31#[derive(Debug)]
32pub enum AnyToken {
33    /// No-op stub entry, never used.
34    Stub,
35    #[cfg(any(all(doc, not(doctest)), feature = "bluetooth"))]
36    Bluetooth(BluetoothToken),
37    #[cfg(any(all(doc, not(doctest)), feature = "nfc"))]
38    Nfc(NFCCard),
39    #[cfg(any(all(doc, not(doctest)), feature = "usb"))]
40    Usb(USBToken),
41}
42
43#[derive(Debug)]
44pub enum AnyTokenId {
45    /// No-op stub entry, never used.
46    Stub,
47    #[cfg(any(all(doc, not(doctest)), feature = "bluetooth"))]
48    Bluetooth(<BluetoothToken as Token>::Id),
49    #[cfg(any(all(doc, not(doctest)), feature = "nfc"))]
50    Nfc(<NFCCard as Token>::Id),
51    #[cfg(any(all(doc, not(doctest)), feature = "usb"))]
52    Usb(<USBToken as Token>::Id),
53}
54
55impl AnyTransport {
56    /// Creates connections to all available transports.
57    ///
58    /// For NFC, uses `Scope::User`, and [ignores unavailability of the PC/SC
59    /// Service][0].
60    ///
61    /// [0]: crate::nfc#smart-card-service
62    pub async fn new() -> Result<Self, WebauthnCError> {
63        Ok(AnyTransport {
64            #[cfg(feature = "bluetooth")]
65            bluetooth: BluetoothTransport::new().await?,
66            #[cfg(feature = "nfc")]
67            nfc: match NFCTransport::new(pcsc::Scope::User) {
68                Ok(reader) => Some(reader),
69                Err(e) => {
70                    warn!("PC/SC service not available ({e:?}), continuing without NFC support...");
71                    None
72                }
73            },
74            #[cfg(feature = "usb")]
75            usb: USBTransport::new().await?,
76        })
77    }
78}
79
80#[async_trait]
81impl Transport<'_> for AnyTransport {
82    type Token = AnyToken;
83
84    #[allow(unreachable_code)]
85    async fn watch(&self) -> Result<BoxStream<TokenEvent<Self::Token>>, WebauthnCError> {
86        // Bluetooth
87        let mut bluetooth_complete = !cfg!(feature = "bluetooth");
88        #[cfg(feature = "bluetooth")]
89        let bluetooth: BoxStream<TokenEvent<BluetoothToken>> = match self.bluetooth.watch().await {
90            Err(e) => {
91                error!("Bluetooth transport failure: {e:?}");
92                bluetooth_complete = true;
93                Box::pin(futures::stream::empty())
94            }
95            Ok(s) => s,
96        };
97
98        #[cfg(not(feature = "bluetooth"))]
99        let bluetooth: BoxStream<TokenEvent<AnyToken>> = Box::pin(futures::stream::empty());
100
101        let mut bluetooth = bluetooth.fuse();
102
103        // NFC
104        let mut nfc_complete = !cfg!(feature = "nfc");
105        #[cfg(feature = "nfc")]
106        let nfc: BoxStream<TokenEvent<NFCCard>> = if let Some(nfc) = &self.nfc {
107            match nfc.watch().await {
108                Err(e) => {
109                    error!("NFC transport failure: {e:?}");
110                    nfc_complete = true;
111                    Box::pin(futures::stream::empty())
112                }
113                Ok(s) => s,
114            }
115        } else {
116            nfc_complete = true;
117            Box::pin(futures::stream::empty())
118        };
119
120        #[cfg(not(feature = "nfc"))]
121        let nfc: BoxStream<TokenEvent<AnyToken>> = Box::pin(futures::stream::empty());
122
123        let mut nfc = nfc.fuse();
124
125        // USB HID
126        let mut usb_complete = !cfg!(feature = "usb");
127        #[cfg(feature = "usb")]
128        let usb: BoxStream<TokenEvent<USBToken>> = match self.usb.watch().await {
129            Err(e) => {
130                error!("USB transport failure: {e:?}");
131                usb_complete = true;
132                Box::pin(futures::stream::empty())
133            }
134            Ok(s) => s,
135        };
136
137        #[cfg(not(feature = "usb"))]
138        let usb: BoxStream<TokenEvent<AnyToken>> = Box::pin(futures::stream::empty());
139
140        let mut usb = usb.fuse();
141
142        if bluetooth_complete && nfc_complete && usb_complete {
143            error!("no transports available!");
144            return Err(WebauthnCError::NotSupported);
145        }
146
147        // Main stream
148        let s = stream! {
149            #[cfg(not(doc))]
150            while !bluetooth.is_terminated() || !nfc.is_terminated() || !usb.is_terminated() {
151                tokio::select! {
152                    Some(b) = bluetooth.next() => {
153                        #[cfg(feature = "bluetooth")]
154                        let b: TokenEvent<AnyToken> = b.into();
155                        if matches!(b, TokenEvent::EnumerationComplete) {
156                            if nfc_complete && usb_complete {
157                                trace!("Sending enumeration complete from Bluetooth");
158                                yield TokenEvent::EnumerationComplete;
159                            }
160                            bluetooth_complete = true;
161                        } else {
162                            yield b;
163                        }
164                    }
165
166                    Some(n) = nfc.next() => {
167                        #[cfg(feature = "nfc")]
168                        let n: TokenEvent<AnyToken> = n.into();
169                        if matches!(n, TokenEvent::EnumerationComplete) {
170                            if bluetooth_complete && usb_complete {
171                                trace!("Sending enumeration complete from NFC");
172                                yield TokenEvent::EnumerationComplete;
173                            }
174                            nfc_complete = true;
175                        } else {
176                            yield n;
177                        }
178                    }
179
180                    Some(u) = usb.next() => {
181                        #[cfg(feature = "usb")]
182                        let u: TokenEvent<AnyToken> = u.into();
183                        if matches!(u, TokenEvent::EnumerationComplete) {
184                            if bluetooth_complete && nfc_complete {
185                                trace!("Sending enumeration complete from USB");
186                                yield TokenEvent::EnumerationComplete;
187                            }
188                            usb_complete = true;
189                        } else {
190                            yield u;
191                        }
192                    }
193
194                    else => continue,
195                }
196            }
197        };
198
199        Ok(Box::pin(s))
200    }
201
202    #[allow(unreachable_code)]
203    async fn tokens(&self) -> Result<Vec<Self::Token>, WebauthnCError> {
204        #[cfg(not(any(feature = "bluetooth", feature = "nfc", feature = "usb")))]
205        {
206            error!("No transports available!");
207            return Err(WebauthnCError::NotSupported);
208        }
209
210        let mut o: Vec<Self::Token> = Vec::new();
211
212        #[cfg(feature = "bluetooth")]
213        o.extend(
214            self.bluetooth
215                .tokens()
216                .await?
217                .into_iter()
218                .map(AnyToken::Bluetooth),
219        );
220
221        #[cfg(feature = "nfc")]
222        if let Some(nfc) = &self.nfc {
223            o.extend(nfc.tokens().await?.into_iter().map(AnyToken::Nfc));
224        }
225
226        #[cfg(feature = "usb")]
227        o.extend(self.usb.tokens().await?.into_iter().map(AnyToken::Usb));
228
229        Ok(o)
230    }
231}
232
233#[async_trait]
234#[allow(clippy::unimplemented)]
235impl Token for AnyToken {
236    type Id = AnyTokenId;
237
238    #[allow(unused_variables)]
239    async fn transmit_raw<U>(&mut self, cmd: &[u8], ui: &U) -> Result<Vec<u8>, WebauthnCError>
240    where
241        U: UiCallback,
242    {
243        match self {
244            AnyToken::Stub => unimplemented!(),
245            #[cfg(feature = "bluetooth")]
246            AnyToken::Bluetooth(b) => Token::transmit_raw(b, cmd, ui).await,
247            #[cfg(feature = "nfc")]
248            AnyToken::Nfc(n) => Token::transmit_raw(n, cmd, ui).await,
249            #[cfg(feature = "usb")]
250            AnyToken::Usb(u) => Token::transmit_raw(u, cmd, ui).await,
251        }
252    }
253
254    async fn init(&mut self) -> Result<(), WebauthnCError> {
255        match self {
256            AnyToken::Stub => unimplemented!(),
257            #[cfg(feature = "bluetooth")]
258            AnyToken::Bluetooth(b) => b.init().await,
259            #[cfg(feature = "nfc")]
260            AnyToken::Nfc(n) => n.init().await,
261            #[cfg(feature = "usb")]
262            AnyToken::Usb(u) => u.init().await,
263        }
264    }
265
266    async fn close(&mut self) -> Result<(), WebauthnCError> {
267        match self {
268            AnyToken::Stub => unimplemented!(),
269            #[cfg(feature = "bluetooth")]
270            AnyToken::Bluetooth(b) => b.close().await,
271            #[cfg(feature = "nfc")]
272            AnyToken::Nfc(n) => n.close().await,
273            #[cfg(feature = "usb")]
274            AnyToken::Usb(u) => u.close().await,
275        }
276    }
277
278    fn get_transport(&self) -> AuthenticatorTransport {
279        match self {
280            AnyToken::Stub => unimplemented!(),
281            #[cfg(feature = "bluetooth")]
282            AnyToken::Bluetooth(b) => b.get_transport(),
283            #[cfg(feature = "nfc")]
284            AnyToken::Nfc(n) => n.get_transport(),
285            #[cfg(feature = "usb")]
286            AnyToken::Usb(u) => u.get_transport(),
287        }
288    }
289
290    async fn cancel(&mut self) -> Result<(), WebauthnCError> {
291        match self {
292            AnyToken::Stub => unimplemented!(),
293            #[cfg(feature = "bluetooth")]
294            AnyToken::Bluetooth(b) => b.cancel().await,
295            #[cfg(feature = "nfc")]
296            AnyToken::Nfc(n) => n.cancel().await,
297            #[cfg(feature = "usb")]
298            AnyToken::Usb(u) => u.cancel().await,
299        }
300    }
301
302    fn has_button(&self) -> bool {
303        match self {
304            AnyToken::Stub => unimplemented!(),
305            #[cfg(feature = "bluetooth")]
306            AnyToken::Bluetooth(b) => b.has_button(),
307            #[cfg(feature = "nfc")]
308            AnyToken::Nfc(n) => n.has_button(),
309            #[cfg(feature = "usb")]
310            AnyToken::Usb(u) => u.has_button(),
311        }
312    }
313}
314
315#[cfg(feature = "bluetooth")]
316impl From<TokenEvent<BluetoothToken>> for TokenEvent<AnyToken> {
317    fn from(e: TokenEvent<BluetoothToken>) -> Self {
318        match e {
319            TokenEvent::Added(t) => TokenEvent::Added(AnyToken::Bluetooth(t)),
320            TokenEvent::Removed(i) => TokenEvent::Removed(AnyTokenId::Bluetooth(i)),
321            TokenEvent::EnumerationComplete => TokenEvent::EnumerationComplete,
322        }
323    }
324}
325
326#[cfg(feature = "nfc")]
327impl From<TokenEvent<NFCCard>> for TokenEvent<AnyToken> {
328    fn from(e: TokenEvent<NFCCard>) -> Self {
329        match e {
330            TokenEvent::Added(t) => TokenEvent::Added(AnyToken::Nfc(t)),
331            TokenEvent::Removed(i) => TokenEvent::Removed(AnyTokenId::Nfc(i)),
332            TokenEvent::EnumerationComplete => TokenEvent::EnumerationComplete,
333        }
334    }
335}
336
337#[cfg(feature = "usb")]
338impl From<TokenEvent<USBToken>> for TokenEvent<AnyToken> {
339    fn from(e: TokenEvent<USBToken>) -> Self {
340        match e {
341            TokenEvent::Added(t) => TokenEvent::Added(AnyToken::Usb(t)),
342            TokenEvent::Removed(i) => TokenEvent::Removed(AnyTokenId::Usb(i)),
343            TokenEvent::EnumerationComplete => TokenEvent::EnumerationComplete,
344        }
345    }
346}