wasefire_board_api/usb/
ctap.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! CTAP HID interface.
16
17use usb_device::bus::UsbBus;
18use usbd_hid::UsbError;
19use usbd_hid::hid_class::HIDClass;
20use wasefire_error::Code;
21use {ssmarshal as _, wasefire_logger as log};
22
23use crate::Error;
24
25/// CTAP HID event.
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27#[derive(Debug, PartialEq, Eq)]
28pub enum Event {
29    /// There might be data to read.
30    ///
31    /// This is only guaranteed to be triggered after a failed read.
32    Read,
33
34    /// It might be possible to write data.
35    ///
36    /// This is only guaranteed to be triggered after a failed write.
37    Write,
38}
39
40impl<B: crate::Api> From<Event> for crate::Event<B> {
41    fn from(event: Event) -> Self {
42        super::Event::Ctap(event).into()
43    }
44}
45
46/// CTAP HID interface.
47pub trait Api: Send {
48    /// Reads a CTAP HID packet into a buffer.
49    ///
50    /// Returns whether a packet was read read.
51    fn read(output: &mut [u8; 64]) -> Result<bool, Error>;
52
53    /// Writes a CTAP HID packet from a buffer.
54    ///
55    /// Returns whether the packet was written.
56    fn write(input: &[u8; 64]) -> Result<bool, Error>;
57
58    /// Enables a given event to be triggered.
59    fn enable(event: &Event) -> Result<(), Error>;
60
61    /// Disables a given event from being triggered.
62    fn disable(event: &Event) -> Result<(), Error>;
63}
64
65/// Helper trait for boards using the `usbd-hid` crate.
66pub trait HasHid: Send {
67    /// USB bus type.
68    type UsbBus: UsbBus;
69
70    /// Provides scoped access to the `Ctap` type.
71    fn with_hid<R>(f: impl FnOnce(&mut Ctap<Self::UsbBus>) -> R) -> R;
72}
73
74/// Wrapper type for boards using the `usbd-hid` crate.
75pub struct WithHid<T: HasHid> {
76    _never: !,
77    _has_hid: T,
78}
79
80/// Helper struct for boards using the `usbd-hid` crate.
81pub struct Ctap<'a, T: UsbBus> {
82    class: HIDClass<'a, T>,
83    read_enabled: bool,
84    write_enabled: bool,
85}
86
87impl<'a, T: UsbBus> Ctap<'a, T> {
88    /// Creates a new serial from a port.
89    pub fn new(class: HIDClass<'a, T>) -> Self {
90        Self { class, read_enabled: false, write_enabled: false }
91    }
92
93    /// Accesses the HID class.
94    pub fn class(&mut self) -> &mut HIDClass<'a, T> {
95        &mut self.class
96    }
97
98    /// Pushes events based on whether the USB serial was polled.
99    pub fn tick(&mut self, polled: bool, mut push: impl FnMut(Event)) {
100        if self.read_enabled && polled {
101            push(Event::Read);
102        }
103        if self.write_enabled {
104            push(Event::Write);
105        }
106    }
107
108    fn set(&mut self, event: &Event, enabled: bool) -> Result<(), Error> {
109        match event {
110            Event::Read => self.read_enabled = enabled,
111            Event::Write => self.write_enabled = enabled,
112        }
113        Ok(())
114    }
115}
116
117impl<T: HasHid> Api for WithHid<T> {
118    fn read(output: &mut [u8; 64]) -> Result<bool, Error> {
119        T::with_hid(|x| match x.class.pull_raw_output(output) {
120            Ok(64) => Ok(true),
121            Ok(len) => {
122                log::warn!("bad read len {}", len);
123                Err(Error::world(Code::InvalidLength))
124            }
125            Err(UsbError::WouldBlock) => Ok(false),
126            Err(_) => Err(Error::world(0)),
127        })
128    }
129
130    fn write(input: &[u8; 64]) -> Result<bool, Error> {
131        T::with_hid(|x| match x.class.push_raw_input(input) {
132            Ok(64) => Ok(true),
133            Ok(len) => {
134                log::warn!("bad write len {}", len);
135                Err(Error::world(Code::InvalidLength))
136            }
137            Err(UsbError::WouldBlock) => Ok(false),
138            Err(_) => Err(Error::world(0)),
139        })
140    }
141
142    fn enable(event: &Event) -> Result<(), Error> {
143        T::with_hid(|x| x.set(event, true))
144    }
145
146    fn disable(event: &Event) -> Result<(), Error> {
147        T::with_hid(|x| x.set(event, false))
148    }
149}