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