webauthn_authenticator_rs/transport/
any.rs1#[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#[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#[derive(Debug)]
32pub enum AnyToken {
33 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 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 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 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 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 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 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}