| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! USB descriptor structures and serialization. |
| //! |
| //! This module provides the tools to define and serialize standard USB |
| //! descriptors, including Device, Configuration, Interface, and Endpoint |
| //! descriptors. |
| |
| use aligned::Aligned; |
| use aligned::A4; |
| #[cfg(feature = "ufmt_console")] |
| use ufmt::uWrite; |
| |
| /// USB Audio class code. |
| pub const USB_CLASS_AUDIO: u8 = 0x01; |
| /// USB Communications and CDC Control class code. |
| pub const USB_CLASS_COMMUNICATIONS: u8 = 0x02; |
| /// USB HID (Human Interface Device) class code. |
| pub const USB_CLASS_HID: u8 = 0x03; |
| /// USB Physical class code. |
| pub const USB_CLASS_PHYSICAL: u8 = 0x05; |
| /// USB Image class code. |
| pub const USB_CLASS_IMAGE: u8 = 0x06; |
| /// USB Printer class code. |
| pub const USB_CLASS_PRINTER: u8 = 0x07; |
| /// USB Mass Storage class code. |
| pub const USB_CLASS_MASS_STORAGE: u8 = 0x08; |
| /// USB Hub class code. |
| pub const USB_CLASS_HUB: u8 = 0x09; |
| /// USB CDC-Data class code. |
| pub const USB_CLASS_CDC_DATA: u8 = 0x0a; |
| /// USB Smart Card class code. |
| pub const USB_CLASS_SMART_CARD: u8 = 0x0b; |
| /// USB Content Security class code. |
| pub const USB_CLASS_CONTENT_SECURITY: u8 = 0x0d; |
| /// USB Video class code. |
| pub const USB_CLASS_VIDEO: u8 = 0x0e; |
| /// USB Personal Healthcare class code. |
| pub const USB_CLASS_PERSONAL_HEALTHCARE: u8 = 0x0f; |
| /// USB Audio/Video class code. |
| pub const USB_CLASS_AUDIO_VIDEO: u8 = 0x10; |
| /// USB Billboard class code. |
| pub const USB_CLASS_BILLBOARD: u8 = 0x11; |
| /// USB Type-C Bridge class code. |
| pub const USB_CLASS_USB_TYPEC_BRIDGE: u8 = 0x12; |
| /// USB Bulk Display class code. |
| pub const USB_CLASS_BULK_DISPLAY: u8 = 0x13; |
| /// USB MCTP class code. |
| pub const USB_CLASS_MCTP: u8 = 0x14; |
| /// USB I3C class code. |
| pub const USB_CLASS_I3C: u8 = 0x3c; |
| /// USB Diagnostic Device class code. |
| pub const USB_CLASS_DIAGNOSTIC_DEVICE: u8 = 0xdc; |
| /// USB Wireless Controller class code. |
| pub const USB_CLASS_WIRELESS_CONTROLLER: u8 = 0xe0; |
| /// USB Miscellaneous class code. |
| pub const USB_CLASS_MISC: u8 = 0xef; |
| /// USB Application Specific class code. |
| pub const USB_CLASS_APPLICATION_SPECIFIC: u8 = 0xfe; |
| /// USB Vendor Specific class code. |
| pub const USB_CLASS_VENDOR: u8 = 0xff; |
| |
| /// DFU (Device Firmware Upgrade) subclass code. |
| pub const USB_SUBCLASS_APPLICATION_SPECIFIC_DFU: u8 = 0x01; |
| |
| /// DFU Runtime Mode protocol code. |
| pub const USB_PROTOCOL_APPLICATION_SPECIFIC_DFU_RUNTIME_MODE: u8 = 0x01; |
| /// DFU Mode protocol code. |
| pub const USB_PROTOCOL_APPLICATION_SPECIFIC_DFU_DFU_MODE: u8 = 0x02; |
| |
| use crate::DescriptorType; |
| use crate::Direction; |
| |
| /// A handle to a USB string descriptor. |
| #[derive(Clone, Copy, Eq, PartialEq)] |
| #[repr(transparent)] |
| pub struct StringHandle(pub u8); |
| impl StringHandle { |
| /// Indicates that no string descriptor is provided. |
| pub const NONE: Self = StringHandle(0); |
| } |
| |
| /// A standard USB device descriptor. |
| #[derive(Clone, Copy)] |
| pub struct DeviceDescriptor { |
| /// The class of the device. |
| pub device_class: DeviceClass, |
| /// The subclass of the device. |
| pub device_sub_class: u8, |
| /// The protocol used by the device. |
| pub device_protocol: u8, |
| /// Maximum packet size for Endpoint 0. |
| pub max_packet_size: u8, |
| /// Vendor ID assigned by the USB-IF. |
| pub vendor_id: u16, |
| /// Product ID assigned by the manufacturer. |
| pub product_id: u16, |
| /// Device release number (in binary-coded decimal). |
| pub device_release_num: u16, |
| /// Handle for the manufacturer string descriptor. |
| pub manufacturer: StringHandle, |
| /// Handle for the product string descriptor. |
| pub product: StringHandle, |
| /// Handle for the serial number string descriptor. |
| pub serial_num: StringHandle, |
| } |
| impl DeviceDescriptor { |
| const SIZE: usize = 18; |
| |
| #[allow(dead_code)] |
| pub(crate) const fn total_size(&self) -> usize { |
| Self::SIZE |
| } |
| |
| /// Serializes the device descriptor into a byte array. |
| #[allow(clippy::identity_op)] |
| pub const fn serialize(&self) -> [u8; Self::SIZE] { |
| let mut buf = [0u8; Self::SIZE]; |
| |
| // sizeof descriptor |
| buf[0] = 18; |
| // bDescriptorType = Device |
| buf[1] = 1; |
| // USB version 2.0 |
| buf[2] = 0x00; |
| buf[3] = 0x02; |
| |
| buf[4] = self.device_class.0; |
| buf[5] = self.device_sub_class; |
| buf[6] = self.device_protocol; |
| buf[7] = self.max_packet_size; |
| |
| buf[8] = ((self.vendor_id & 0x00ff) >> 0) as u8; |
| buf[9] = ((self.vendor_id & 0xff00) >> 8) as u8; |
| |
| buf[10] = ((self.product_id & 0x00ff) >> 0) as u8; |
| buf[11] = ((self.product_id & 0xff00) >> 8) as u8; |
| |
| buf[12] = ((self.device_release_num & 0x00ff) >> 0) as u8; |
| buf[13] = ((self.device_release_num & 0xff00) >> 8) as u8; |
| |
| buf[14] = self.manufacturer.0; |
| buf[15] = self.product.0; |
| buf[16] = self.serial_num.0; |
| |
| // num configurations |
| buf[17] = 1; |
| buf |
| } |
| } |
| |
| /// A standard USB configuration descriptor. |
| #[derive(Clone, Copy)] |
| pub struct ConfigDescriptor { |
| /// The configuration value for this configuration. |
| pub configuration_value: u8, |
| /// Maximum power consumption in 2 mA units. |
| pub max_power: u8, |
| /// Indicates if the device is self-powered. |
| pub self_powered: bool, |
| /// Indicates if the device supports remote wakeup. |
| pub remote_wakeup: bool, |
| /// List of interfaces included in this configuration. |
| pub interfaces: &'static [InterfaceDescriptor], |
| } |
| |
| impl ConfigDescriptor { |
| const SIZE: usize = 9; |
| |
| /// Returns the total size of the configuration descriptor, including |
| /// all interfaces and endpoints. |
| pub const fn total_size(&self) -> usize { |
| let mut result = Self::SIZE; |
| let mut i = 0; |
| while i < self.interfaces.len() { |
| result += self.interfaces[i].total_size(); |
| i += 1; |
| } |
| result |
| } |
| |
| /// Serializes the configuration descriptor and its children into a byte array. |
| #[allow(clippy::identity_op)] |
| pub const fn serialize<const RESULT_SIZE: usize>(&self) -> [u8; RESULT_SIZE] { |
| assert!(self.total_size() == RESULT_SIZE); |
| let mut buf = [0u8; RESULT_SIZE]; |
| |
| // alternates don't count towards total |
| // as interfaces numbers are per spec monotonically increasing, we can use that as the count |
| let mut uniq_interface_count = 0; |
| let mut i = 0; |
| while i < self.interfaces.len() { |
| if self.interfaces[i].interface_number + 1 > uniq_interface_count { |
| uniq_interface_count = self.interfaces[i].interface_number + 1 |
| } |
| i += 1; |
| } |
| |
| // sizeof descriptor |
| buf[0] = 9; |
| // bDescriptorType = Configuration |
| buf[1] = 2; |
| buf[2] = ((RESULT_SIZE & 0x00ff) >> 0) as u8; |
| buf[3] = ((RESULT_SIZE & 0xff00) >> 8) as u8; |
| buf[4] = uniq_interface_count; |
| buf[5] = self.configuration_value; |
| // iConfiguration |
| buf[6] = 0; |
| buf[7] = (1 << 7) | // must be 1 (USB 1.0 bus powered) |
| if self.self_powered { 1 << 6 } else { 0 } | |
| if self.remote_wakeup { 1 << 5 } else { 0 }; |
| buf[8] = self.max_power; |
| |
| let mut offset = 9; |
| |
| let mut i = 0; |
| while i < self.interfaces.len() { |
| let (iface_buf, iface_buf_len) = self.interfaces[i].serialize::<RESULT_SIZE>(); |
| let mut iface_offset = 0; |
| while iface_offset < iface_buf_len { |
| buf[offset] = iface_buf[iface_offset]; |
| iface_offset += 1; |
| offset += 1; |
| } |
| i += 1; |
| } |
| buf |
| } |
| } |
| |
| /// A standard USB interface descriptor. |
| #[derive(Clone, Copy)] |
| pub struct InterfaceDescriptor { |
| /// Handle for the interface name string descriptor. |
| pub name: StringHandle, |
| /// The alternate setting for this interface. |
| pub alternate_setting: u8, |
| /// The interface number. |
| pub interface_number: u8, |
| /// The interface class. |
| pub interface_class: u8, |
| /// The interface subclass. |
| pub interface_sub_class: u8, |
| /// The interface protocol. |
| pub interface_protocol: u8, |
| /// List of class-specific functional descriptors. |
| pub func_descs: &'static [FunctionalDescriptor], |
| /// List of endpoints used by this interface. |
| pub endpoints: &'static [EndpointDescriptor], |
| } |
| impl InterfaceDescriptor { |
| const SIZE: usize = 9; |
| |
| pub(crate) const fn total_size(&self) -> usize { |
| let mut result = Self::SIZE; |
| let mut i = 0; |
| while i < self.func_descs.len() { |
| result += self.func_descs[i].total_size(); |
| i += 1; |
| } |
| let mut i = 0; |
| while i < self.endpoints.len() { |
| result += self.endpoints[i].total_size(); |
| i += 1; |
| } |
| result |
| } |
| /// Serializes the interface descriptor and its children. |
| pub const fn serialize<const RESULT_SIZE: usize>(&self) -> ([u8; RESULT_SIZE], usize) { |
| assert!(RESULT_SIZE >= self.total_size()); |
| |
| let mut buf = [0u8; RESULT_SIZE]; |
| |
| // sizeof descriptor |
| buf[0] = 9; |
| // bDescriptorType = Interface |
| buf[1] = 4; |
| buf[2] = self.interface_number; |
| buf[3] = self.alternate_setting; |
| buf[4] = self.endpoints.len() as u8; |
| buf[5] = self.interface_class; |
| buf[6] = self.interface_sub_class; |
| buf[7] = self.interface_protocol; |
| // iInterface: Index of string descriptor describing this interface |
| buf[8] = self.name.0; |
| let mut offset = 9; |
| |
| let mut i = 0; |
| while i < self.func_descs.len() { |
| self.func_descs[i].serialize(&mut buf, offset); |
| offset += self.func_descs[i].total_size(); |
| i += 1; |
| } |
| |
| let mut i = 0; |
| while i < self.endpoints.len() { |
| let ep_buf = self.endpoints[i].serialize(i as u8); |
| let mut ep_offset = 0; |
| while ep_offset < ep_buf.len() { |
| buf[offset] = ep_buf[ep_offset]; |
| offset += 1; |
| ep_offset += 1; |
| } |
| i += 1; |
| } |
| (buf, offset) |
| } |
| } |
| |
| /// A standard USB endpoint descriptor. |
| #[derive(Clone, Copy)] |
| pub struct EndpointDescriptor { |
| /// The data direction of the endpoint. |
| pub direction: Direction, |
| /// The endpoint number (0-15). |
| pub endpoint_num: u8, |
| /// The transfer type of the endpoint. |
| pub transfer_type: TransferType, |
| /// Maximum packet size for this endpoint. |
| pub max_packet_size: u16, |
| /// Polling interval (for interrupt and isochronous endpoints). |
| pub interval: u8, |
| } |
| impl EndpointDescriptor { |
| const SIZE: usize = 7; |
| |
| pub(crate) const fn total_size(&self) -> usize { |
| Self::SIZE |
| } |
| |
| #[allow(clippy::identity_op)] |
| const fn serialize(&self, _index: u8) -> [u8; Self::SIZE] { |
| let mut buf = [0u8; Self::SIZE]; |
| |
| // sizeof descriptor |
| buf[0] = Self::SIZE as u8; |
| // bDescriptorType = endpoint |
| buf[1] = 5; |
| buf[2] = self.endpoint_num & 0x7 |
| | match self.direction { |
| Direction::HostToDevice => 0, |
| Direction::DeviceToHost => 1 << 7, |
| }; |
| buf[3] = match self.transfer_type { |
| TransferType::Control => 0, |
| TransferType::Isochronous(sync_type, usage_type) => { |
| 1 | match sync_type { |
| SynchronizationType::None => 0 << 2, |
| SynchronizationType::Asynchronous => 1 << 2, |
| SynchronizationType::Adaptive => 2 << 2, |
| SynchronizationType::Synchronous => 3 << 3, |
| } | match usage_type { |
| UsageType::DataEndpoint => 0 << 4, |
| UsageType::FeedbackEndpoint => 1 << 4, |
| UsageType::ExplicitFeedbackDataEndpoint => 2 << 4, |
| } |
| } |
| TransferType::Bulk => 2, |
| TransferType::Interrupt => 3, |
| }; |
| |
| buf[4] = ((self.max_packet_size & 0x00ff) >> 0) as u8; |
| buf[5] = ((self.max_packet_size & 0xff00) >> 8) as u8; |
| buf[6] = self.interval; |
| buf |
| } |
| } |
| |
| /// A standard USB String Descriptor 0 (listing supported languages). |
| #[derive(Clone, Copy)] |
| pub struct StringDescriptor0 { |
| /// List of supported LANGIDs. |
| pub langs: &'static [u16], |
| } |
| impl StringDescriptor0 { |
| /// Returns the total size of the descriptor. |
| pub const fn total_size(&self) -> usize { |
| 2 + core::mem::size_of_val(self.langs) |
| } |
| |
| /// Serializes the language list into a byte array. |
| #[allow(clippy::identity_op)] |
| pub const fn serialize<const RESULT_SIZE: usize>(&self) -> [u8; RESULT_SIZE] { |
| assert!(RESULT_SIZE == self.total_size()); |
| assert!(self.total_size() <= (u8::MAX as usize)); |
| |
| let mut buf = [0u8; RESULT_SIZE]; |
| // sizeof descriptor |
| buf[0] = self.total_size() as u8; |
| // bDescriptorType = String |
| buf[1] = 3; |
| |
| let mut offset = 2; |
| let mut i = 0; |
| while i < self.langs.len() { |
| let bytes = self.langs[i].to_le_bytes(); |
| buf[offset + 0] = bytes[0]; |
| buf[offset + 1] = bytes[1]; |
| i += 1; |
| offset += 2; |
| } |
| buf |
| } |
| } |
| |
| /// USB transfer type. |
| #[derive(Clone, Copy, Debug)] |
| #[allow(dead_code)] |
| pub enum TransferType { |
| /// Control transfer. |
| Control, |
| /// Isochronous transfer. |
| Isochronous(SynchronizationType, UsageType), |
| /// Bulk transfer. |
| Bulk, |
| /// Interrupt transfer. |
| Interrupt, |
| } |
| impl TransferType { |
| #[allow(dead_code)] |
| fn as_eptyp(self) -> u32 { |
| match self { |
| TransferType::Control => 0, |
| TransferType::Isochronous(_, _) => 1, |
| TransferType::Bulk => 2, |
| TransferType::Interrupt => 3, |
| } |
| } |
| } |
| |
| /// Isochronous synchronization type. |
| #[derive(Clone, Copy, Debug)] |
| #[allow(dead_code)] |
| pub enum SynchronizationType { |
| None = 0, |
| Asynchronous = 1, |
| Adaptive = 2, |
| Synchronous = 3, |
| } |
| |
| /// Isochronous usage type. |
| #[derive(Clone, Copy, Debug)] |
| #[allow(dead_code, clippy::enum_variant_names)] |
| pub enum UsageType { |
| DataEndpoint, |
| FeedbackEndpoint, |
| ExplicitFeedbackDataEndpoint, |
| } |
| |
| /// USB device class code. |
| #[derive(Clone, Copy)] |
| pub struct DeviceClass(pub u8); |
| impl DeviceClass { |
| /// Class is specified at the interface level. |
| pub const SPECIFIED_BY_INTERFACE: Self = Self(0x00); |
| /// CDC (Communication Device Class). |
| pub const COMMUNICATIONS_AND_CDC: Self = Self(0x02); |
| /// Hub device. |
| pub const HUB: Self = Self(0x09); |
| /// Billboard device. |
| pub const BILLBOARD: Self = Self(0x11); |
| /// Diagnostic device. |
| pub const DIAGNOSTIC_DEVICE: Self = Self(0x3c); |
| /// Miscellaneous device. |
| pub const MISCELLANEOUS: Self = Self(0xef); |
| /// Vendor-specified device class. |
| pub const VENDOR_SPECIFIED: Self = Self(0xff); |
| } |
| impl From<DeviceClass> for u8 { |
| fn from(val: DeviceClass) -> Self { |
| val.0 |
| } |
| } |
| |
| /// A statically-allocated USB string descriptor. |
| pub struct StringDescriptor<const BYTE_LEN: usize>(Aligned<A4, [u8; BYTE_LEN]>); |
| |
| impl<const BYTE_LEN: usize> StringDescriptor<BYTE_LEN> { |
| /// Creates a string descriptor from an ASCII string at compile-time. |
| pub const fn const_from_ascii(s: &str) -> Self { |
| assert!(BYTE_LEN <= (u8::MAX as usize)); |
| assert!(s.len() * 2 + 2 == BYTE_LEN); |
| let mut result = [0u8; BYTE_LEN]; |
| result[0] = BYTE_LEN as u8; |
| result[1] = 0x03; // DescriptorType string |
| |
| let s = s.as_bytes(); |
| let mut i = 0; |
| while i < s.len() { |
| if s[i] >= 0x80 { |
| panic!("ascii characters only"); |
| } |
| result[2 + i * 2] = s[i]; |
| i += 1; |
| } |
| StringDescriptor(Aligned(result)) |
| } |
| /// Returns a reference to the string descriptor. |
| pub const fn as_ref(&self) -> StringDescriptorRef<'_> { |
| StringDescriptorRef(&self.0) |
| } |
| } |
| |
| /// A reference to an aligned USB string descriptor. |
| #[derive(Clone, Copy)] |
| pub struct StringDescriptorRef<'a>(pub &'a Aligned<A4, [u8]>); |
| impl<'a> StringDescriptorRef<'a> { |
| /// Returns the descriptor as a byte slice. |
| pub const fn as_bytes(self) -> &'a Aligned<A4, [u8]> { |
| self.0 |
| } |
| } |
| |
| /// Macro for easily creating static string descriptors. |
| #[macro_export] |
| macro_rules! string_descriptor { |
| ($s:expr) => { |
| $crate::StringDescriptor::<{ $s.len() * 2 + 2 }>::const_from_ascii($s) |
| }; |
| } |
| |
| /// Descriptor generation error. |
| #[derive(Debug)] |
| pub enum DescriptorErr { |
| /// Buffer is too small. |
| Overflow, |
| /// Invalid encoding. |
| Encoding, |
| } |
| |
| /// Generates a UTF-16 hex-encoded string descriptor from a byte slice. |
| #[inline(always)] |
| pub fn hex_utf16_descriptor(dest: &mut [u8], src: &[u8]) -> Result<usize, DescriptorErr> { |
| const { assert!(cfg!(target_endian = "little")) }; |
| const HEX_CHARS: [u8; 16] = *b"0123456789abcdef"; |
| let total_len = src.len() * 4 + 2; |
| if dest.len() < total_len || total_len > 255 { |
| return Err(DescriptorErr::Overflow); |
| } |
| dest[0] = total_len as u8; |
| dest[1] = DescriptorType::STRING.0; |
| |
| let mut i = 2; |
| for src_byte in src.iter() { |
| dest[i] = HEX_CHARS[usize::from(*src_byte >> 4)]; |
| dest[i + 1] = 0; |
| dest[i + 2] = HEX_CHARS[usize::from(*src_byte & 0xf)]; |
| dest[i + 3] = 0; |
| i += 4; |
| } |
| Ok(total_len) |
| } |
| |
| /// Generates an aligned UTF-16 hex-encoded string descriptor. |
| #[inline(always)] |
| pub fn hex_utf16_descriptor_aligned<'a>( |
| dest: &'a mut Aligned<A4, [u8]>, |
| src: &[u8], |
| ) -> Result<StringDescriptorRef<'a>, DescriptorErr> { |
| let len = hex_utf16_descriptor(dest, src)?; |
| Ok(StringDescriptorRef(&dest[..len])) |
| } |
| |
| /// Utility for dynamically writing content into a USB string descriptor. |
| pub struct StringDescriptorWritter<'a> { |
| buf: &'a mut Aligned<A4, [u8]>, |
| index: usize, |
| } |
| impl<'a> StringDescriptorWritter<'a> { |
| /// Creates a new writer using the provided buffer. |
| pub fn new(buf: &'a mut Aligned<A4, [u8]>) -> Result<Self, DescriptorErr> { |
| if buf.len() < 2 || buf.len() > 2 + 255 { |
| return Err(DescriptorErr::Overflow); |
| } |
| *buf.get_mut(1).unwrap() = DescriptorType::STRING.0; |
| Ok(StringDescriptorWritter { buf, index: 2 }) |
| } |
| /// Finalizes the descriptor and returns a reference to it. |
| pub fn finalize(self) -> Result<StringDescriptorRef<'a>, DescriptorErr> { |
| *self.buf.get_mut(0).ok_or(DescriptorErr::Overflow)? = |
| u8::try_from(self.index).map_err(|_| DescriptorErr::Overflow)?; |
| |
| if self.index > self.buf.len() { |
| return Err(DescriptorErr::Overflow); |
| } |
| Ok(StringDescriptorRef(&self.buf[..self.index])) |
| } |
| } |
| #[cfg(feature = "ufmt_console")] |
| impl uWrite for StringDescriptorWritter<'_> { |
| type Error = core::fmt::Error; |
| |
| fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { |
| let bytes = s.as_bytes(); |
| let remaining_buf = self.buf.get_mut(self.index..).ok_or(core::fmt::Error)?; |
| |
| if remaining_buf.len() < bytes.len() * 2 { |
| return Err(core::fmt::Error); |
| } |
| |
| for &b in bytes { |
| if b >= 0x80 { |
| return Err(core::fmt::Error); |
| } |
| *self.buf.get_mut(self.index).ok_or(core::fmt::Error)? = b; |
| *self.buf.get_mut(self.index + 1).ok_or(core::fmt::Error)? = 0; |
| self.index += 2; |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test_string_descriptor_writter { |
| use aligned::Aligned; |
| use aligned::A4; |
| #[cfg(feature = "ufmt_console")] |
| use core::ops::Deref; |
| #[cfg(feature = "ufmt_console")] |
| use ufmt::uwrite; |
| |
| use crate::StringDescriptorWritter; |
| |
| #[test] |
| #[cfg(feature = "ufmt_console")] |
| fn works() { |
| let mut buf = Aligned::<A4, _>([0u8; 30]); |
| let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); |
| uwrite!(writter, "Hello").unwrap(); |
| uwrite!(writter, " ").unwrap(); |
| uwrite!(writter, "World").unwrap(); |
| let result = writter.finalize().unwrap(); |
| assert_eq!( |
| result.as_bytes().deref(), |
| &[ |
| 24, 3, b'H', 0, b'e', 0, b'l', 0, b'l', 0, b'o', 0, b' ', 0, b'W', 0, b'o', 0, |
| b'r', 0, b'l', 0, b'd', 0 |
| ] |
| ); |
| } |
| |
| #[test] |
| fn too_small_buffer() { |
| let mut buf = Aligned::<A4, _>([0u8; 1]); |
| assert!(StringDescriptorWritter::new(&mut buf).is_err()); |
| } |
| |
| #[test] |
| fn too_big_buffer() { |
| let mut buf = Aligned::<A4, _>([0u8; 258]); |
| assert!(StringDescriptorWritter::new(&mut buf).is_err()); |
| } |
| |
| #[test] |
| #[cfg(feature = "ufmt_console")] |
| fn too_small_to_fit() { |
| let mut buf = Aligned::<A4, _>([0u8; 12]); |
| let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); |
| uwrite!(writter, "Hello").unwrap(); |
| assert!(uwrite!(writter, " ").is_err()); |
| } |
| |
| #[test] |
| #[cfg(feature = "ufmt_console")] |
| fn non_ascii_char() { |
| let mut buf = Aligned::<A4, _>([0u8; 20]); |
| let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); |
| assert!(uwrite!(writter, "Héllö").is_err()); |
| } |
| } |
| |
| /// A DFU functional descriptor. |
| #[derive(Clone, Copy)] |
| pub struct DfuFunctionalDescriptor { |
| /// New firmware can be received from the host |
| pub can_download: bool, |
| /// Current firmware can be sent back to the host |
| pub can_upload: bool, |
| /// Device can still communicate with the host after the manifestation phase. |
| pub manifestation_tolerant: bool, |
| /// Device will detach from the USB bus autonomously after receiving |
| /// DFU_DETACH; the host does not need to explicitly issue a bus reset. |
| pub will_detach: bool, |
| /// Timeout the device will wait to be reset by host after receiving DFU_DETACH. |
| pub detach_timeout_ms: u16, |
| /// The number of bytes the device can receive per control request. |
| pub transfer_size: u16, |
| } |
| impl DfuFunctionalDescriptor { |
| /// Returns the total size of the descriptor. |
| pub const fn total_size(&self) -> usize { |
| 9 |
| } |
| /// Serializes the DFU functional descriptor. |
| pub const fn serialize(&self, dest: &mut [u8], offset: usize) { |
| const fn bit(index: u8, val: bool) -> u8 { |
| (if val { 1 } else { 0 }) << index |
| } |
| const fn copy_u16(dest: &mut [u8], index: usize, val: u16) { |
| let bytes = val.to_le_bytes(); |
| dest[index] = bytes[0]; |
| dest[index + 1] = bytes[1]; |
| } |
| // sizeof descriptor |
| dest[offset] = 9; |
| // bDescriptorType = DFU Functional |
| dest[offset + 1] = 0x21; |
| // bmAttributes |
| dest[offset + 2] = bit(0, self.can_download) |
| | bit(1, self.can_upload) |
| | bit(2, self.manifestation_tolerant) |
| | bit(3, self.will_detach); |
| copy_u16(dest, offset + 3, self.detach_timeout_ms); |
| copy_u16(dest, offset + 5, self.transfer_size); |
| // bcdDFUVersion |
| copy_u16(dest, offset + 7, 0x0100); |
| } |
| } |
| |
| /// A raw class-specific functional descriptor. |
| #[derive(Clone, Copy)] |
| pub struct RawFunctionalDescriptor { |
| /// The type of the descriptor. |
| pub descriptor_type: u8, |
| /// The raw content length. |
| pub len: u8, |
| /// The raw content of the descriptor. |
| pub content: [u8; 16], |
| } |
| impl RawFunctionalDescriptor { |
| /// Returns the total size of the descriptor. |
| pub const fn total_size(&self) -> usize { |
| (self.len as usize) + 2 |
| } |
| /// Serializes the raw functional descriptor. |
| pub const fn serialize(&self, dest: &mut [u8], offset: usize) { |
| dest[offset] = self.total_size() as u8; |
| dest[offset + 1] = self.descriptor_type; |
| let mut i = 0; |
| while i < (self.len as usize) { |
| dest[offset + 2 + i] = self.content[i]; |
| i += 1; |
| } |
| } |
| } |
| |
| /// Represents a class-specific functional descriptor. |
| #[derive(Clone, Copy)] |
| pub enum FunctionalDescriptor { |
| /// DFU functional descriptor. |
| Dfu(DfuFunctionalDescriptor), |
| /// Raw class-specific functional descriptor. |
| Raw(RawFunctionalDescriptor), |
| } |
| |
| impl FunctionalDescriptor { |
| /// Creates a raw functional descriptor. |
| pub const fn raw(descriptor_type: u8, content: &[u8]) -> Self { |
| let mut buf = [0u8; 16]; |
| let mut i = 0; |
| while i < content.len() { |
| buf[i] = content[i]; |
| i += 1; |
| } |
| Self::Raw(RawFunctionalDescriptor { |
| descriptor_type, |
| len: content.len() as u8, |
| content: buf, |
| }) |
| } |
| /// Returns the total size of the descriptor. |
| pub const fn total_size(&self) -> usize { |
| match self { |
| Self::Dfu(dfu) => dfu.total_size(), |
| Self::Raw(raw) => raw.total_size(), |
| } |
| } |
| /// Serializes the functional descriptor. |
| #[allow(clippy::identity_op)] |
| pub const fn serialize(&self, dest: &mut [u8], offset: usize) { |
| assert!(offset + self.total_size() <= dest.len()); |
| match self { |
| Self::Dfu(dfu) => dfu.serialize(dest, offset), |
| Self::Raw(raw) => raw.serialize(dest, offset), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| |
| use super::*; |
| |
| const INTERFACE_NAME_HANDLE: StringHandle = StringHandle(5); |
| |
| const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { |
| configuration_value: 1, |
| max_power: 250, |
| self_powered: false, |
| remote_wakeup: false, |
| interfaces: &[InterfaceDescriptor { |
| name: INTERFACE_NAME_HANDLE, |
| interface_number: 0, |
| alternate_setting: 0, |
| interface_class: 0xff, |
| interface_sub_class: 0xff, |
| interface_protocol: 0xff, |
| func_descs: &[], |
| endpoints: &[ |
| EndpointDescriptor { |
| direction: Direction::DeviceToHost, |
| endpoint_num: 1, |
| transfer_type: TransferType::Bulk, |
| max_packet_size: 64, |
| interval: 0, |
| }, |
| EndpointDescriptor { |
| direction: Direction::HostToDevice, |
| endpoint_num: 2, |
| transfer_type: TransferType::Bulk, |
| max_packet_size: 64, |
| interval: 0, |
| }, |
| ], |
| }], |
| }; |
| const CONFIG_DESC_RAW: [u8; CONFIG_DESC.total_size()] = CONFIG_DESC.serialize(); |
| |
| #[test] |
| fn test_config_desc() { |
| assert_eq!( |
| &CONFIG_DESC_RAW, |
| &[ |
| 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x04, 0x00, 0x00, 0x02, |
| 0xff, 0xff, 0xff, 0x05, 0x07, 0x05, 0x81, 0x02, 0x40, 0x00, 0x00, 0x07, 0x05, 0x02, |
| 0x02, 0x40, 0x00, 0x00 |
| ] |
| ) |
| } |
| |
| #[test] |
| fn test_config_desc_dfu() { |
| const CONFIG_DESC_DFU: ConfigDescriptor = ConfigDescriptor { |
| configuration_value: 1, |
| max_power: 250, |
| self_powered: false, |
| remote_wakeup: false, |
| interfaces: &[InterfaceDescriptor { |
| name: INTERFACE_NAME_HANDLE, |
| interface_number: 0, |
| alternate_setting: 0, |
| interface_class: 0xfe, |
| interface_sub_class: 0x01, |
| interface_protocol: 0x02, |
| func_descs: &[FunctionalDescriptor::Dfu(DfuFunctionalDescriptor { |
| can_download: true, |
| can_upload: false, |
| manifestation_tolerant: true, |
| will_detach: true, |
| transfer_size: 2048, |
| detach_timeout_ms: 8000, |
| })], |
| endpoints: &[], |
| }], |
| }; |
| const CONFIG_DESC_BYTES: [u8; CONFIG_DESC_DFU.total_size()] = CONFIG_DESC_DFU.serialize(); |
| |
| assert_eq!( |
| &CONFIG_DESC_BYTES, |
| &[ |
| 0x09, 0x02, 0x1b, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x04, 0x00, 0x00, 0x00, |
| 0xfe, 0x01, 0x02, 0x05, 0x09, 0x21, 0x0d, 0x40, 0x1f, 0x00, 0x08, 0x00, 0x01 |
| ] |
| ) |
| } |
| |
| #[test] |
| fn test_string_descriptor() { |
| use core::ops::Deref; |
| const USB_VENDOR: StringDescriptorRef = string_descriptor!("Mutask").as_ref(); |
| assert_eq!( |
| USB_VENDOR.as_bytes().deref(), |
| &[14, 3, b'M', 0, b'u', 0, b't', 0, b'a', 0, b's', 0, b'k', 0,] |
| ); |
| } |
| |
| #[test] |
| pub fn test_hex_utf16_descriptor() { |
| let mut buf = [0_u8; 80]; |
| let len = hex_utf16_descriptor(&mut buf, &[0xab, 0x1c, 0xd2, 0xe3, 0x4f, 0x56, 0x78, 0x90]) |
| .unwrap(); |
| assert_eq!( |
| [ |
| 34, 3, b'a', 0, b'b', 0, b'1', 0, b'c', 0, b'd', 0, b'2', 0, b'e', 0, b'3', 0, |
| b'4', 0, b'f', 0, b'5', 0, b'6', 0, b'7', 0, b'8', 0, b'9', 0, b'0', 0 |
| ], |
| &buf[..len] |
| ); |
| |
| // empty string; tight fit |
| let mut buf = [0_u8; 2]; |
| let len = hex_utf16_descriptor(&mut buf, b"").unwrap(); |
| assert_eq!(&[2, 3], &buf[..len]); |
| |
| // 1 byte; tight fit |
| let mut buf = [0_u8; 6]; |
| let len = hex_utf16_descriptor(&mut buf, &[0xca]).unwrap(); |
| assert_eq!(&[6, 3, b'c', 0, b'a', 0], &buf[..len]); |
| |
| // 2 bytes; tight fit |
| let mut buf = [0_u8; 10]; |
| let len = hex_utf16_descriptor(&mut buf, &[0xca, 0xfe]).unwrap(); |
| assert_eq!(&[10, 3, b'c', 0, b'a', 0, b'f', 0, b'e', 0], &buf[..len]); |
| |
| // too small to fit descriptor |
| let mut buf = [0_u8; 1]; |
| hex_utf16_descriptor(&mut buf, b"").unwrap_err(); |
| |
| // too small to fit 1 byte hex string |
| let mut buf = [0_u8; 5]; |
| hex_utf16_descriptor(&mut buf, b"H").unwrap_err(); |
| |
| // too small to fit 2 byte hex string |
| let mut buf = [0_u8; 9]; |
| hex_utf16_descriptor(&mut buf, b"Hi").unwrap_err(); |
| |
| // length too big to fit in length field |
| let mut buf = [0_u8; 258]; |
| hex_utf16_descriptor(&mut buf, &[0x42_u8; 64]).unwrap_err(); |
| } |
| |
| #[test] |
| fn test_composite_config_desc() { |
| const USB_CDC_COMM_HANDLE: StringHandle = StringHandle(4); |
| const USB_CDC_DATA_HANDLE: StringHandle = StringHandle(5); |
| const DFU_FIRMWARE_HANDLE: StringHandle = StringHandle(6); |
| const DFU_UDS_CERT_HANDLE: StringHandle = StringHandle(7); |
| const DFU_CDI0_CERT_HANDLE: StringHandle = StringHandle(8); |
| const DFU_CDI1_CERT_HANDLE: StringHandle = StringHandle(9); |
| |
| const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { |
| configuration_value: 1, |
| max_power: 250, |
| self_powered: false, |
| remote_wakeup: false, |
| interfaces: &[ |
| InterfaceDescriptor { |
| name: USB_CDC_COMM_HANDLE, |
| interface_number: 0, |
| alternate_setting: 0, |
| interface_class: 0x02, |
| interface_sub_class: 0x02, |
| interface_protocol: 0x00, |
| func_descs: &[ |
| FunctionalDescriptor::raw(0x24, &[0x00, 0x10, 0x01]), |
| FunctionalDescriptor::raw(0x24, &[0x02, 0x02]), |
| FunctionalDescriptor::raw(0x24, &[0x06, 0x00, 0x01]), |
| ], |
| endpoints: &[EndpointDescriptor { |
| direction: Direction::DeviceToHost, |
| endpoint_num: 1, |
| interval: 255, |
| max_packet_size: 8, |
| transfer_type: TransferType::Interrupt, |
| }], |
| }, |
| InterfaceDescriptor { |
| name: USB_CDC_DATA_HANDLE, |
| interface_number: 1, |
| alternate_setting: 0, |
| interface_class: 0x0a, |
| interface_sub_class: 0, |
| interface_protocol: 0x00, |
| func_descs: &[], |
| endpoints: &[ |
| EndpointDescriptor { |
| direction: Direction::HostToDevice, |
| endpoint_num: 2, |
| interval: 0, |
| max_packet_size: 64, |
| transfer_type: TransferType::Bulk, |
| }, |
| EndpointDescriptor { |
| direction: Direction::DeviceToHost, |
| endpoint_num: 3, |
| interval: 0, |
| max_packet_size: 64, |
| transfer_type: TransferType::Bulk, |
| }, |
| ], |
| }, |
| InterfaceDescriptor { |
| name: DFU_FIRMWARE_HANDLE, |
| interface_number: 2, |
| alternate_setting: 0, |
| interface_class: 0xfe, |
| interface_sub_class: 0x01, |
| interface_protocol: 0x01, |
| func_descs: &[], |
| endpoints: &[], |
| }, |
| InterfaceDescriptor { |
| name: DFU_UDS_CERT_HANDLE, |
| interface_number: 2, |
| alternate_setting: 1, |
| interface_class: 0xfe, |
| interface_sub_class: 0x01, |
| interface_protocol: 0x01, |
| func_descs: &[], |
| endpoints: &[], |
| }, |
| InterfaceDescriptor { |
| name: DFU_CDI0_CERT_HANDLE, |
| interface_number: 2, |
| alternate_setting: 2, |
| interface_class: 0xfe, |
| interface_sub_class: 0x01, |
| interface_protocol: 0x01, |
| func_descs: &[], |
| endpoints: &[], |
| }, |
| InterfaceDescriptor { |
| name: DFU_CDI1_CERT_HANDLE, |
| interface_number: 2, |
| alternate_setting: 3, |
| interface_class: 0xfe, |
| interface_sub_class: 0x01, |
| interface_protocol: 0x01, |
| func_descs: &[FunctionalDescriptor::raw( |
| 0x21, |
| &[0x07, 0x00, 0x00, 0x00, 0x08, 0x10, 0x01], |
| )], |
| endpoints: &[], |
| }, |
| ], |
| }; |
| |
| let bytes = CONFIG_DESC.serialize::<{ CONFIG_DESC.total_size() }>(); |
| |
| // Print the bytes for manual inspection |
| println!("Serialized: {bytes:?}"); |
| |
| // Assert size |
| assert_eq!(bytes.len(), 107); |
| assert_eq!(CONFIG_DESC.total_size(), 107); |
| |
| // Assert bNumInterfaces (offset 4) is 3 |
| assert_eq!(bytes[4], 3); |
| |
| // Assert bLength (offset 0) is 9 |
| assert_eq!(bytes[0], 9); |
| |
| // Assert bDescriptorType (offset 1) is 2 |
| assert_eq!(bytes[1], 2); |
| |
| // Assert wTotalLength (offset 2, 3) is 107 (0x006b) |
| assert_eq!(bytes[2], 0x6b); |
| assert_eq!(bytes[3], 0x00); |
| } |
| } |