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