devices/usb/backend/fido_backend/
fido_guest.rs

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
5use std::collections::VecDeque;
6
7use base::error;
8use usb_util::TransferBuffer;
9
10use crate::usb::backend::fido_backend::constants;
11use crate::usb::backend::fido_backend::error::Error;
12use crate::usb::backend::fido_backend::error::Result;
13use crate::usb::backend::fido_backend::poll_thread::PollTimer;
14use 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.
19pub 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
28impl FidoGuestKey {
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.
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.
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)]
76mod 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]
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]
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]
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}