prns_interfaces_embassy/usb_auto/
device.rs1use 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}