xref: /aosp_15_r20/external/crosvm/devices/src/usb/backend/fido_backend/fido_guest.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2023 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::collections::VecDeque;
6 
7 use base::error;
8 use usb_util::TransferBuffer;
9 
10 use crate::usb::backend::fido_backend::constants;
11 use crate::usb::backend::fido_backend::error::Error;
12 use crate::usb::backend::fido_backend::error::Result;
13 use crate::usb::backend::fido_backend::poll_thread::PollTimer;
14 use crate::usb::backend::fido_backend::transfer::FidoTransfer;
15 
16 /// `FidoGuestKey` is the struct representation of a virtual fido device as seen by the guest VM.
17 /// It takes care of bubbling up transactions from the host into the guest and show a
18 /// representation of the device's state into the guest.
19 pub struct FidoGuestKey {
20     /// Queue of packets already processed by the host that need to be sent to the guest.
21     pub pending_in_packets: VecDeque<[u8; constants::U2FHID_PACKET_SIZE]>,
22     /// HID Idle state of the security key.
23     pub idle: u8,
24     /// Timer used to poll to periodically send packets to pending USB transfers.
25     pub timer: PollTimer,
26 }
27 
28 impl FidoGuestKey {
new() -> Result<Self>29     pub fn new() -> Result<Self> {
30         let timer = PollTimer::new(
31             "guest packet timer".to_string(),
32             std::time::Duration::from_nanos(constants::PACKET_POLL_RATE_NANOS),
33         )?;
34         Ok(FidoGuestKey {
35             pending_in_packets: VecDeque::with_capacity(constants::U2FHID_MAX_IN_PENDING),
36             idle: 1,
37             timer,
38         })
39     }
40 
41     /// Resets the guest key representation, stopping the poll and clearing the packet queue.
reset(&mut self)42     pub fn reset(&mut self) {
43         self.pending_in_packets.clear();
44         if let Err(e) = self.timer.clear() {
45             error!("Unable to clear guest key timer, silently failing. {}", e);
46         }
47     }
48 
49     /// Sends data to the guest by associating a given transfer to the oldest packet in the queue.
50     /// If the data from the host hasn't been read yet (the packet queue is empty), it returns the
51     /// same transfer back to the caller, unmodified.
return_data_to_guest( &mut self, transfer_opt: Option<FidoTransfer>, ) -> Result<Option<FidoTransfer>>52     pub fn return_data_to_guest(
53         &mut self,
54         transfer_opt: Option<FidoTransfer>,
55     ) -> Result<Option<FidoTransfer>> {
56         // If this happens, it means we passed around an empty reference to a
57         // non existing transfer that was already cancelled and removed.
58         let mut transfer = transfer_opt.ok_or(Error::FidoTransferLost)?;
59         match self.pending_in_packets.pop_front() {
60             Some(packet) => {
61                 transfer.buffer = TransferBuffer::Vector(packet.to_vec());
62                 transfer.actual_length = packet.len();
63                 transfer.complete_transfer();
64                 Ok(None)
65             }
66             None => {
67                 // Pending queue is empty, nothing to do so we return the original transfer without
68                 // consuming it.
69                 Ok(Some(transfer))
70             }
71         }
72     }
73 }
74 
75 #[cfg(test)]
76 mod tests {
77 
78     use std::sync::Arc;
79 
80     use sync::Mutex;
81     use usb_util::TransferBuffer;
82     use usb_util::TransferStatus;
83 
84     use crate::usb::backend::fido_backend::constants::U2FHID_PACKET_SIZE;
85     use crate::usb::backend::fido_backend::fido_guest::FidoGuestKey;
86     use crate::usb::backend::fido_backend::transfer::FidoTransfer;
87     use crate::usb::backend::transfer::BackendTransfer;
88     use crate::usb::backend::transfer::BackendTransferType;
89 
90     #[test]
test_reset()91     fn test_reset() {
92         let mut fido_key = FidoGuestKey::new().unwrap();
93         let fake_packet = [0; U2FHID_PACKET_SIZE];
94 
95         fido_key.pending_in_packets.push_back(fake_packet);
96         assert_eq!(fido_key.pending_in_packets.len(), 1);
97         fido_key.reset();
98         assert_eq!(fido_key.pending_in_packets.len(), 0);
99     }
100 
101     #[test]
test_return_data_to_guest_no_packet_retry()102     fn test_return_data_to_guest_no_packet_retry() {
103         let mut fido_key = FidoGuestKey::new().unwrap();
104         let transfer_buffer = TransferBuffer::Vector(vec![0u8; U2FHID_PACKET_SIZE]);
105         let fake_transfer = FidoTransfer::new(1, transfer_buffer);
106 
107         let returned_transfer = fido_key.return_data_to_guest(Some(fake_transfer)).unwrap();
108         assert!(returned_transfer.is_some());
109     }
110 
111     #[test]
test_return_data_to_guest_success()112     fn test_return_data_to_guest_success() {
113         let mut fido_key = FidoGuestKey::new().unwrap();
114         let fake_packet = [5; U2FHID_PACKET_SIZE];
115         let transfer_buffer = TransferBuffer::Vector(vec![0u8; U2FHID_PACKET_SIZE]);
116         let mut fake_transfer = FidoTransfer::new(1, transfer_buffer);
117 
118         let callback_outer = Arc::new(Mutex::new(false));
119         let callback_inner = callback_outer.clone();
120 
121         fake_transfer.set_callback(move |t: BackendTransferType| {
122             assert_eq!(t.actual_length(), U2FHID_PACKET_SIZE);
123             assert!(t.status() == TransferStatus::Completed);
124             *callback_inner.lock() = true;
125         });
126         fido_key.pending_in_packets.push_back(fake_packet);
127 
128         let returned_transfer = fido_key.return_data_to_guest(Some(fake_transfer)).unwrap();
129         assert!(returned_transfer.is_none());
130         assert!(*callback_outer.lock());
131     }
132 }
133