Skip to main content

prns_interfaces_embassy/usb_auto/
device.rs

1use core::mem::MaybeUninit;
2
3use ::embassy_usb::driver::{
4    Driver as UsbDriver, Endpoint as UsbEndpoint, EndpointError, EndpointIn, EndpointOut,
5};
6use ::embassy_usb::types::StringIndex;
7use ::embassy_usb::{msos, Builder, Handler};
8
9pub const WEBUSB_AUTO_PACKET_SIZE: u16 = 64;
10
11#[derive(Debug)]
12pub enum WebUsbAutoError {
13    Disconnected,
14    PacketTooLarge,
15}
16
17impl embedded_io_async::Error for WebUsbAutoError {
18    fn kind(&self) -> embedded_io_async::ErrorKind {
19        match self {
20            Self::Disconnected => embedded_io_async::ErrorKind::NotConnected,
21            Self::PacketTooLarge => embedded_io_async::ErrorKind::OutOfMemory,
22        }
23    }
24}
25
26pub struct WebUsbAutoState {
27    control: MaybeUninit<WebUsbAutoControl>,
28}
29
30impl WebUsbAutoState {
31    #[must_use]
32    pub const fn new() -> Self {
33        Self {
34            control: MaybeUninit::uninit(),
35        }
36    }
37}
38
39impl Default for WebUsbAutoState {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45struct WebUsbAutoControl {
46    iface_string: StringIndex,
47}
48
49impl Handler for WebUsbAutoControl {
50    fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> {
51        (index == self.iface_string).then_some("Personal Hopspot WebUSB Auto")
52    }
53}
54
55pub struct WebUsbAutoClass<'d, D: UsbDriver<'d>> {
56    read_ep: D::EndpointOut,
57    write_ep: D::EndpointIn,
58}
59
60impl<'d, D: UsbDriver<'d>> WebUsbAutoClass<'d, D> {
61    #[must_use]
62    pub fn new(
63        builder: &mut Builder<'d, D>,
64        state: &'d mut WebUsbAutoState,
65        max_packet_size: u16,
66    ) -> Self {
67        let iface_string = builder.string();
68        let mut function = builder.function(0xff, 0, 0);
69        function.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
70        function.msos_feature(msos::RegistryPropertyFeatureDescriptor::new(
71            "DeviceInterfaceGUIDs",
72            msos::PropertyData::RegMultiSz(&["{D6F980C1-0B65-4B3B-A029-01A93A3DEB44}"]),
73        ));
74        let mut interface = function.interface();
75        let mut alt = interface.alt_setting(0xff, 0, 0, Some(iface_string));
76        let read_ep = alt.endpoint_bulk_out(None, max_packet_size);
77        let write_ep = alt.endpoint_bulk_in(None, max_packet_size);
78        drop(function);
79
80        builder.handler(state.control.write(WebUsbAutoControl { iface_string }));
81
82        Self { read_ep, write_ep }
83    }
84
85    #[must_use]
86    pub fn split(self) -> (WebUsbAutoTx<'d, D>, WebUsbAutoRx<'d, D>) {
87        (
88            WebUsbAutoTx {
89                write_ep: self.write_ep,
90            },
91            WebUsbAutoRx {
92                read_ep: self.read_ep,
93            },
94        )
95    }
96}
97
98pub struct WebUsbAutoRx<'d, D: UsbDriver<'d>> {
99    read_ep: D::EndpointOut,
100}
101
102impl<'d, D: UsbDriver<'d>> embedded_io_async::ErrorType for WebUsbAutoRx<'d, D> {
103    type Error = WebUsbAutoError;
104}
105
106impl<'d, D: UsbDriver<'d>> embedded_io_async::Read for WebUsbAutoRx<'d, D> {
107    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
108        loop {
109            if let Some(n) = endpoint_read(self.read_ep.read(buf).await)? {
110                return Ok(n);
111            }
112        }
113    }
114}
115
116fn endpoint_read(result: Result<usize, EndpointError>) -> Result<Option<usize>, WebUsbAutoError> {
117    match result {
118        Ok(0) => Ok(None),
119        Ok(n) => Ok(Some(n)),
120        Err(EndpointError::Disabled) => Err(WebUsbAutoError::Disconnected),
121        Err(EndpointError::BufferOverflow) => Err(WebUsbAutoError::PacketTooLarge),
122    }
123}
124
125pub struct WebUsbAutoTx<'d, D: UsbDriver<'d>> {
126    write_ep: D::EndpointIn,
127}
128
129impl<'d, D: UsbDriver<'d>> embedded_io_async::ErrorType for WebUsbAutoTx<'d, D> {
130    type Error = WebUsbAutoError;
131}
132
133impl<'d, D: UsbDriver<'d>> embedded_io_async::Write for WebUsbAutoTx<'d, D> {
134    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
135        let len = core::cmp::min(buf.len(), self.write_ep.info().max_packet_size as usize);
136        match self.write_ep.write(&buf[..len]).await {
137            Ok(()) => Ok(len),
138            Err(EndpointError::Disabled) => Err(WebUsbAutoError::Disconnected),
139            Err(EndpointError::BufferOverflow) => Err(WebUsbAutoError::PacketTooLarge),
140        }
141    }
142
143    async fn flush(&mut self) -> Result<(), Self::Error> {
144        Ok(())
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn zero_length_usb_packets_are_transport_idle_not_stream_eof() {
154        assert!(matches!(endpoint_read(Ok(0)), Ok(None)));
155        assert!(matches!(endpoint_read(Ok(17)), Ok(Some(17))));
156    }
157
158    #[test]
159    fn endpoint_failures_preserve_disconnect_and_capacity_meaning() {
160        assert!(matches!(
161            endpoint_read(Err(EndpointError::Disabled)),
162            Err(WebUsbAutoError::Disconnected)
163        ));
164        assert!(matches!(
165            endpoint_read(Err(EndpointError::BufferOverflow)),
166            Err(WebUsbAutoError::PacketTooLarge)
167        ));
168    }
169}