Skip to main content

netget/server/usb/
descriptors.rs

1//! USB descriptor builders for various device types
2//!
3//! This module provides functions to build USB descriptors for:
4//! - HID devices (keyboard, mouse)
5//! - CDC ACM devices (serial)
6//! - Custom devices
7//!
8//! # Most of the builders here have no callers, and that is not obvious
9//!
10//! Nine of them — `build_device_descriptor`, `build_string_descriptor`,
11//! `build_language_id_descriptor`, `char_to_usage`, and every `build_*_config_descriptor`
12//! (keyboard, mouse, FIDO2, CDC ACM, MSC) — are reachable from nothing in `src/` or `tests/`.
13//! The `usbip` crate builds the device and configuration descriptors itself from
14//! `UsbDevice::new(..).with_interface(class, subclass, protocol, name, endpoints, handler)`,
15//! so every protocol here describes its device that way and none of these are consulted.
16//!
17//! They do not warn, because `dead_code` does not fire on a `pub` item in a library crate. So
18//! a reader — or a model reading the source — reasonably concludes the wire format a device
19//! presents is decided here, when it is decided by the endpoint lists in each protocol's
20//! `mod.rs`. Verify with:
21//!
22//! ```bash
23//! grep -rn build_device_descriptor src/ tests/ | grep -v descriptors.rs
24//! ```
25//!
26//! What *is* live: `MouseReport` and `mouse_buttons`, `build_hid_mouse_report_descriptor`,
27//! `FIDO_HID_REPORT_DESCRIPTOR`, `LineCoding` and `ControlLineState`, and the MSC
28//! `CommandBlockWrapper` / `CommandStatusWrapper` / `scsi_*` tables. HID *report* descriptors
29//! are the exception to the rule above precisely because `usbip` cannot invent one — it is the
30//! device's own description of its report layout, which is why those are the ones with callers.
31//!
32//! The rest are kept as a checked reference rather than deleted, but do not read a caller into
33//! them.
34
35use crate::server::usb::common::*;
36
37/// Standard USB device descriptor (18 bytes)
38pub fn build_device_descriptor(
39    vendor_id: u16,
40    product_id: u16,
41    device_class: u8,
42    device_subclass: u8,
43    device_protocol: u8,
44    manufacturer_str_index: u8,
45    product_str_index: u8,
46    serial_str_index: u8,
47) -> Vec<u8> {
48    vec![
49        18,                      // bLength
50        descriptor_type::DEVICE, // bDescriptorType
51        0x00,
52        0x02,                      // bcdUSB (USB 2.0)
53        device_class,              // bDeviceClass
54        device_subclass,           // bDeviceSubClass
55        device_protocol,           // bDeviceProtocol
56        64,                        // bMaxPacketSize0 (EP0)
57        (vendor_id & 0xff) as u8,  // idVendor (low byte)
58        (vendor_id >> 8) as u8,    // idVendor (high byte)
59        (product_id & 0xff) as u8, // idProduct (low byte)
60        (product_id >> 8) as u8,   // idProduct (high byte)
61        0x00,
62        0x01,                   // bcdDevice (1.0)
63        manufacturer_str_index, // iManufacturer
64        product_str_index,      // iProduct
65        serial_str_index,       // iSerialNumber
66        1,                      // bNumConfigurations
67    ]
68}
69
70/// HID keyboard report descriptor (boot protocol compatible)
71/// Defines the format of keyboard input reports:
72/// - Byte 0: Modifier keys (Ctrl, Shift, Alt, GUI)
73/// - Byte 1: Reserved
74/// - Bytes 2-7: Up to 6 simultaneous key presses
75#[cfg(all(unix, feature = "libusb"))]
76pub fn build_hid_keyboard_report_descriptor() -> Vec<u8> {
77    vec![
78        0x05, 0x01, // Usage Page (Generic Desktop)
79        0x09, 0x06, // Usage (Keyboard)
80        0xA1, 0x01, // Collection (Application)
81        // Modifier keys (byte 0)
82        0x05, 0x07, //   Usage Page (Key Codes)
83        0x19, 0xE0, //   Usage Minimum (224) - Left Control
84        0x29, 0xE7, //   Usage Maximum (231) - Right GUI
85        0x15, 0x00, //   Logical Minimum (0)
86        0x25, 0x01, //   Logical Maximum (1)
87        0x75, 0x01, //   Report Size (1 bit)
88        0x95, 0x08, //   Report Count (8 bits = 8 modifier keys)
89        0x81, 0x02, //   Input (Data, Variable, Absolute) - Modifier byte
90        // Reserved byte (byte 1)
91        0x95, 0x01, //   Report Count (1)
92        0x75, 0x08, //   Report Size (8 bits)
93        0x81, 0x01, //   Input (Constant) - Reserved byte
94        // LED output report (Num Lock, Caps Lock, Scroll Lock, etc.)
95        0x95, 0x05, //   Report Count (5)
96        0x75, 0x01, //   Report Size (1 bit)
97        0x05, 0x08, //   Usage Page (LEDs)
98        0x19, 0x01, //   Usage Minimum (1) - Num Lock
99        0x29, 0x05, //   Usage Maximum (5) - Kana
100        0x91, 0x02, //   Output (Data, Variable, Absolute) - LED bits
101        // LED padding (3 bits)
102        0x95, 0x01, //   Report Count (1)
103        0x75, 0x03, //   Report Size (3 bits)
104        0x91, 0x01, //   Output (Constant) - Padding
105        // Key array (bytes 2-7)
106        0x95, 0x06, //   Report Count (6) - Up to 6 simultaneous keys
107        0x75, 0x08, //   Report Size (8 bits)
108        0x15, 0x00, //   Logical Minimum (0)
109        0x25, 0x65, //   Logical Maximum (101) - Max key code
110        0x05, 0x07, //   Usage Page (Key Codes)
111        0x19, 0x00, //   Usage Minimum (0)
112        0x29, 0x65, //   Usage Maximum (101)
113        0x81, 0x00, //   Input (Data, Array) - Key array
114        0xC0, // End Collection
115    ]
116}
117
118/// Build a complete HID keyboard configuration descriptor
119/// Includes: Configuration, Interface, HID, and Endpoint descriptors
120#[cfg(all(unix, feature = "libusb"))]
121pub fn build_hid_keyboard_config_descriptor() -> Vec<u8> {
122    let mut desc = Vec::new();
123
124    // Configuration descriptor (9 bytes)
125    desc.extend_from_slice(&[
126        9,                              // bLength
127        descriptor_type::CONFIGURATION, // bDescriptorType
128        34,
129        0,    // wTotalLength (34 bytes total) - will be calculated
130        1,    // bNumInterfaces
131        1,    // bConfigurationValue
132        0,    // iConfiguration (no string)
133        0xA0, // bmAttributes (bus powered, remote wakeup)
134        50,   // bMaxPower (100mA)
135    ]);
136
137    // Interface descriptor (9 bytes)
138    desc.extend_from_slice(&[
139        9,                          // bLength
140        descriptor_type::INTERFACE, // bDescriptorType
141        0,                          // bInterfaceNumber
142        0,                          // bAlternateSetting
143        1,                          // bNumEndpoints (1 interrupt IN endpoint)
144        device_class::HID,          // bInterfaceClass (HID)
145        1,                          // bInterfaceSubClass (Boot Interface)
146        1,                          // bInterfaceProtocol (Keyboard)
147        0,                          // iInterface (no string)
148    ]);
149
150    // HID descriptor (9 bytes)
151    let report_desc = build_hid_keyboard_report_descriptor();
152    let report_desc_len = report_desc.len() as u16;
153    desc.extend_from_slice(&[
154        9,                    // bLength
155        descriptor_type::HID, // bDescriptorType (HID)
156        0x11,
157        0x01,                           // bcdHID (HID 1.11)
158        0x00,                           // bCountryCode (not localized)
159        0x01,                           // bNumDescriptors
160        descriptor_type::HID_REPORT,    // bDescriptorType (Report)
161        (report_desc_len & 0xff) as u8, // wDescriptorLength (low)
162        (report_desc_len >> 8) as u8,   // wDescriptorLength (high)
163    ]);
164
165    // Endpoint descriptor (7 bytes) - Interrupt IN
166    desc.extend_from_slice(&[
167        7,                         // bLength
168        descriptor_type::ENDPOINT, // bDescriptorType
169        0x81,                      // bEndpointAddress (EP1 IN)
170        transfer_type::INTERRUPT,  // bmAttributes (Interrupt)
171        0x08,
172        0x00, // wMaxPacketSize (8 bytes)
173        10,   // bInterval (10ms polling interval)
174    ]);
175
176    // Update total length
177    let total_len = desc.len() as u16;
178    desc[2] = (total_len & 0xff) as u8;
179    desc[3] = (total_len >> 8) as u8;
180
181    desc
182}
183
184/// Build a USB string descriptor.
185///
186/// String descriptors are UTF-16LE with a 2-byte header, and `bLength` is a **single byte** —
187/// so the whole descriptor cannot exceed 255 bytes, which is 126 UTF-16 code units of text.
188/// This used to be `desc.push(len as u8)` with no check: a 127-character string produced
189/// `bLength = 0` and a 128-character one produced 2, in both cases a descriptor whose declared
190/// length contradicts its own contents. The string is truncated to what the field can describe
191/// instead, on a code-unit boundary so a surrogate pair is never split in half.
192pub fn build_string_descriptor(s: &str) -> Vec<u8> {
193    /// UTF-16 code units that fit alongside the 2-byte header within a `u8` `bLength`.
194    const MAX_CODE_UNITS: usize = (u8::MAX as usize - 2) / 2;
195
196    let mut desc = Vec::new();
197
198    // Encode string as UTF-16LE
199    let mut utf16: Vec<u16> = s.encode_utf16().collect();
200    if utf16.len() > MAX_CODE_UNITS {
201        // Do not cut between the halves of a surrogate pair: a lone surrogate is not valid
202        // UTF-16 and a host decoding it gets a replacement character rather than a short name.
203        let mut keep = MAX_CODE_UNITS;
204        if (0xD800..0xDC00).contains(&utf16[keep - 1]) {
205            keep -= 1;
206        }
207        utf16.truncate(keep);
208    }
209    let len = 2 + utf16.len() * 2;
210
211    desc.push(len as u8); // bLength
212    desc.push(descriptor_type::STRING); // bDescriptorType
213
214    // Add UTF-16LE encoded characters
215    for ch in utf16 {
216        desc.push((ch & 0xff) as u8);
217        desc.push((ch >> 8) as u8);
218    }
219
220    desc
221}
222
223/// Build language ID string descriptor (string index 0)
224/// US English (0x0409) is the most common
225pub fn build_language_id_descriptor() -> Vec<u8> {
226    vec![
227        4,                       // bLength
228        descriptor_type::STRING, // bDescriptorType
229        0x09,
230        0x04, // wLANGID[0] (US English)
231    ]
232}
233
234/// HID keyboard input report builder
235/// 8 bytes: [modifiers, reserved, key1, key2, key3, key4, key5, key6]
236#[cfg(all(unix, feature = "libusb"))]
237pub struct KeyboardReport {
238    pub modifiers: u8, // Bit flags for Ctrl, Shift, Alt, GUI
239    pub keys: [u8; 6], // Up to 6 simultaneous key presses
240}
241
242#[cfg(all(unix, feature = "libusb"))]
243impl KeyboardReport {
244    /// Create empty keyboard report (no keys pressed)
245    pub fn new() -> Self {
246        Self {
247            modifiers: 0,
248            keys: [0; 6],
249        }
250    }
251
252    /// Convert to 8-byte array for USB transmission
253    pub fn to_bytes(&self) -> [u8; 8] {
254        [
255            self.modifiers,
256            0, // Reserved
257            self.keys[0],
258            self.keys[1],
259            self.keys[2],
260            self.keys[3],
261            self.keys[4],
262            self.keys[5],
263        ]
264    }
265}
266
267#[cfg(all(unix, feature = "libusb"))]
268impl Default for KeyboardReport {
269    fn default() -> Self {
270        Self::new()
271    }
272}
273
274/// HID keyboard modifier key bit positions
275#[cfg(all(unix, feature = "libusb"))]
276pub mod keyboard_modifiers {
277    pub const LEFT_CTRL: u8 = 0x01;
278    pub const LEFT_SHIFT: u8 = 0x02;
279    pub const LEFT_ALT: u8 = 0x04;
280    pub const LEFT_GUI: u8 = 0x08;
281    pub const RIGHT_CTRL: u8 = 0x10;
282    pub const RIGHT_SHIFT: u8 = 0x20;
283    pub const RIGHT_ALT: u8 = 0x40;
284    pub const RIGHT_GUI: u8 = 0x80;
285}
286
287/// HID keyboard usage codes (scan codes)
288/// These are the values that go in the key array (bytes 2-7)
289#[cfg(all(unix, feature = "libusb"))]
290pub mod keyboard_usage {
291    pub const NONE: u8 = 0x00;
292    pub const ERROR_ROLLOVER: u8 = 0x01;
293    pub const A: u8 = 0x04;
294    pub const B: u8 = 0x05;
295    pub const C: u8 = 0x06;
296    pub const D: u8 = 0x07;
297    pub const E: u8 = 0x08;
298    pub const F: u8 = 0x09;
299    pub const G: u8 = 0x0a;
300    pub const H: u8 = 0x0b;
301    pub const I: u8 = 0x0c;
302    pub const J: u8 = 0x0d;
303    pub const K: u8 = 0x0e;
304    pub const L: u8 = 0x0f;
305    pub const M: u8 = 0x10;
306    pub const N: u8 = 0x11;
307    pub const O: u8 = 0x12;
308    pub const P: u8 = 0x13;
309    pub const Q: u8 = 0x14;
310    pub const R: u8 = 0x15;
311    pub const S: u8 = 0x16;
312    pub const T: u8 = 0x17;
313    pub const U: u8 = 0x18;
314    pub const V: u8 = 0x19;
315    pub const W: u8 = 0x1a;
316    pub const X: u8 = 0x1b;
317    pub const Y: u8 = 0x1c;
318    pub const Z: u8 = 0x1d;
319    pub const NUM_1: u8 = 0x1e;
320    pub const NUM_2: u8 = 0x1f;
321    pub const NUM_3: u8 = 0x20;
322    pub const NUM_4: u8 = 0x21;
323    pub const NUM_5: u8 = 0x22;
324    pub const NUM_6: u8 = 0x23;
325    pub const NUM_7: u8 = 0x24;
326    pub const NUM_8: u8 = 0x25;
327    pub const NUM_9: u8 = 0x26;
328    pub const NUM_0: u8 = 0x27;
329    pub const ENTER: u8 = 0x28;
330    pub const ESCAPE: u8 = 0x29;
331    pub const BACKSPACE: u8 = 0x2a;
332    pub const TAB: u8 = 0x2b;
333    pub const SPACE: u8 = 0x2c;
334    pub const MINUS: u8 = 0x2d;
335    pub const EQUALS: u8 = 0x2e;
336    pub const LEFT_BRACKET: u8 = 0x2f;
337    pub const RIGHT_BRACKET: u8 = 0x30;
338    pub const BACKSLASH: u8 = 0x31;
339    pub const SEMICOLON: u8 = 0x33;
340    pub const QUOTE: u8 = 0x34;
341    pub const GRAVE: u8 = 0x35;
342    pub const COMMA: u8 = 0x36;
343    pub const PERIOD: u8 = 0x37;
344    pub const SLASH: u8 = 0x38;
345    pub const CAPS_LOCK: u8 = 0x39;
346    pub const F1: u8 = 0x3a;
347    pub const F2: u8 = 0x3b;
348    pub const F3: u8 = 0x3c;
349    pub const F4: u8 = 0x3d;
350    pub const F5: u8 = 0x3e;
351    pub const F6: u8 = 0x3f;
352    pub const F7: u8 = 0x40;
353    pub const F8: u8 = 0x41;
354    pub const F9: u8 = 0x42;
355    pub const F10: u8 = 0x43;
356    pub const F11: u8 = 0x44;
357    pub const F12: u8 = 0x45;
358}
359
360/// Helper to convert character to HID keyboard usage code and modifiers
361/// Returns (usage_code, needs_shift)
362#[cfg(all(unix, feature = "libusb"))]
363pub fn char_to_usage(ch: char) -> Option<(u8, bool)> {
364    match ch {
365        'a'..='z' => Some((keyboard_usage::A + (ch as u8 - b'a'), false)),
366        'A'..='Z' => Some((keyboard_usage::A + (ch as u8 - b'A'), true)),
367        '1'..='9' => Some((keyboard_usage::NUM_1 + (ch as u8 - b'1'), false)),
368        '0' => Some((keyboard_usage::NUM_0, false)),
369        ' ' => Some((keyboard_usage::SPACE, false)),
370        '\n' => Some((keyboard_usage::ENTER, false)),
371        '\t' => Some((keyboard_usage::TAB, false)),
372        '-' => Some((keyboard_usage::MINUS, false)),
373        '_' => Some((keyboard_usage::MINUS, true)),
374        '=' => Some((keyboard_usage::EQUALS, false)),
375        '+' => Some((keyboard_usage::EQUALS, true)),
376        '[' => Some((keyboard_usage::LEFT_BRACKET, false)),
377        '{' => Some((keyboard_usage::LEFT_BRACKET, true)),
378        ']' => Some((keyboard_usage::RIGHT_BRACKET, false)),
379        '}' => Some((keyboard_usage::RIGHT_BRACKET, true)),
380        '\\' => Some((keyboard_usage::BACKSLASH, false)),
381        '|' => Some((keyboard_usage::BACKSLASH, true)),
382        ';' => Some((keyboard_usage::SEMICOLON, false)),
383        ':' => Some((keyboard_usage::SEMICOLON, true)),
384        '\'' => Some((keyboard_usage::QUOTE, false)),
385        '"' => Some((keyboard_usage::QUOTE, true)),
386        '`' => Some((keyboard_usage::GRAVE, false)),
387        '~' => Some((keyboard_usage::GRAVE, true)),
388        ',' => Some((keyboard_usage::COMMA, false)),
389        '<' => Some((keyboard_usage::COMMA, true)),
390        '.' => Some((keyboard_usage::PERIOD, false)),
391        '>' => Some((keyboard_usage::PERIOD, true)),
392        '/' => Some((keyboard_usage::SLASH, false)),
393        '?' => Some((keyboard_usage::SLASH, true)),
394        '!' => Some((keyboard_usage::NUM_1, true)),
395        '@' => Some((keyboard_usage::NUM_2, true)),
396        '#' => Some((keyboard_usage::NUM_3, true)),
397        '$' => Some((keyboard_usage::NUM_4, true)),
398        '%' => Some((keyboard_usage::NUM_5, true)),
399        '^' => Some((keyboard_usage::NUM_6, true)),
400        '&' => Some((keyboard_usage::NUM_7, true)),
401        '*' => Some((keyboard_usage::NUM_8, true)),
402        '(' => Some((keyboard_usage::NUM_9, true)),
403        ')' => Some((keyboard_usage::NUM_0, true)),
404        _ => None,
405    }
406}
407
408// ============================================================================
409// HID Mouse Descriptors
410// ============================================================================
411
412/// HID mouse report descriptor (boot protocol compatible)
413/// Defines the format of mouse input reports:
414/// - Byte 0: Button states (left, right, middle)
415/// - Byte 1: X movement (signed, relative)
416/// - Byte 2: Y movement (signed, relative)
417/// - Byte 3: Wheel movement (signed, vertical scroll)
418#[cfg(all(unix, feature = "libusb"))]
419pub fn build_hid_mouse_report_descriptor() -> Vec<u8> {
420    vec![
421        0x05, 0x01, // Usage Page (Generic Desktop)
422        0x09, 0x02, // Usage (Mouse)
423        0xA1, 0x01, // Collection (Application)
424        0x09, 0x01, //   Usage (Pointer)
425        0xA1, 0x00, //   Collection (Physical)
426        // Buttons (byte 0)
427        0x05, 0x09, //     Usage Page (Button)
428        0x19, 0x01, //     Usage Minimum (1) - Left button
429        0x29, 0x03, //     Usage Maximum (3) - Middle button
430        0x15, 0x00, //     Logical Minimum (0)
431        0x25, 0x01, //     Logical Maximum (1)
432        0x95, 0x03, //     Report Count (3 buttons)
433        0x75, 0x01, //     Report Size (1 bit)
434        0x81, 0x02, //     Input (Data, Variable, Absolute) - Button bits
435        // Padding (5 bits to complete the byte)
436        0x95, 0x01, //     Report Count (1)
437        0x75, 0x05, //     Report Size (5 bits)
438        0x81, 0x01, //     Input (Constant) - Padding
439        // X and Y movement (bytes 1-2)
440        0x05, 0x01, //     Usage Page (Generic Desktop)
441        0x09, 0x30, //     Usage (X)
442        0x09, 0x31, //     Usage (Y)
443        0x15, 0x81, //     Logical Minimum (-127)
444        0x25, 0x7F, //     Logical Maximum (127)
445        0x75, 0x08, //     Report Size (8 bits)
446        0x95, 0x02, //     Report Count (2) - X and Y
447        0x81, 0x06, //     Input (Data, Variable, Relative) - X,Y movement
448        // Wheel (byte 3)
449        0x09, 0x38, //     Usage (Wheel)
450        0x15, 0x81, //     Logical Minimum (-127)
451        0x25, 0x7F, //     Logical Maximum (127)
452        0x75, 0x08, //     Report Size (8 bits)
453        0x95, 0x01, //     Report Count (1)
454        0x81, 0x06, //     Input (Data, Variable, Relative) - Wheel
455        0xC0, //   End Collection (Physical)
456        0xC0, // End Collection (Application)
457    ]
458}
459
460/// Build a complete HID mouse configuration descriptor
461/// Includes: Configuration, Interface, HID, and Endpoint descriptors
462#[cfg(all(unix, feature = "libusb"))]
463pub fn build_hid_mouse_config_descriptor() -> Vec<u8> {
464    let mut desc = Vec::new();
465
466    // Configuration descriptor (9 bytes)
467    desc.extend_from_slice(&[
468        9,                              // bLength
469        descriptor_type::CONFIGURATION, // bDescriptorType
470        34,
471        0,    // wTotalLength (will be calculated)
472        1,    // bNumInterfaces
473        1,    // bConfigurationValue
474        0,    // iConfiguration (no string)
475        0xA0, // bmAttributes (bus powered, remote wakeup)
476        50,   // bMaxPower (100mA)
477    ]);
478
479    // Interface descriptor (9 bytes)
480    desc.extend_from_slice(&[
481        9,                          // bLength
482        descriptor_type::INTERFACE, // bDescriptorType
483        0,                          // bInterfaceNumber
484        0,                          // bAlternateSetting
485        1,                          // bNumEndpoints (1 interrupt IN endpoint)
486        device_class::HID,          // bInterfaceClass (HID)
487        1,                          // bInterfaceSubClass (Boot Interface)
488        2,                          // bInterfaceProtocol (Mouse)
489        0,                          // iInterface (no string)
490    ]);
491
492    // HID descriptor (9 bytes)
493    let report_desc = build_hid_mouse_report_descriptor();
494    let report_desc_len = report_desc.len() as u16;
495    desc.extend_from_slice(&[
496        9,                    // bLength
497        descriptor_type::HID, // bDescriptorType (HID)
498        0x11,
499        0x01,                           // bcdHID (HID 1.11)
500        0x00,                           // bCountryCode (not localized)
501        0x01,                           // bNumDescriptors
502        descriptor_type::HID_REPORT,    // bDescriptorType (Report)
503        (report_desc_len & 0xff) as u8, // wDescriptorLength (low)
504        (report_desc_len >> 8) as u8,   // wDescriptorLength (high)
505    ]);
506
507    // Endpoint descriptor (7 bytes) - Interrupt IN
508    desc.extend_from_slice(&[
509        7,                         // bLength
510        descriptor_type::ENDPOINT, // bDescriptorType
511        0x81,                      // bEndpointAddress (EP1 IN)
512        transfer_type::INTERRUPT,  // bmAttributes (Interrupt)
513        0x04,
514        0x00, // wMaxPacketSize (4 bytes)
515        10,   // bInterval (10ms polling interval)
516    ]);
517
518    // Update total length
519    let total_len = desc.len() as u16;
520    desc[2] = (total_len & 0xff) as u8;
521    desc[3] = (total_len >> 8) as u8;
522
523    desc
524}
525
526/// HID mouse input report builder
527/// 4 bytes: [buttons, x, y, wheel]
528#[cfg(all(unix, feature = "libusb"))]
529pub struct MouseReport {
530    pub buttons: u8, // Bit 0: left, Bit 1: right, Bit 2: middle
531    pub x: i8,       // X movement (-127 to 127)
532    pub y: i8,       // Y movement (-127 to 127)
533    pub wheel: i8,   // Wheel movement (-127 to 127)
534}
535
536#[cfg(all(unix, feature = "libusb"))]
537impl MouseReport {
538    /// Create empty mouse report (no movement, no buttons)
539    pub fn new() -> Self {
540        Self {
541            buttons: 0,
542            x: 0,
543            y: 0,
544            wheel: 0,
545        }
546    }
547
548    /// Convert to 4-byte array for USB transmission
549    pub fn to_bytes(&self) -> [u8; 4] {
550        [self.buttons, self.x as u8, self.y as u8, self.wheel as u8]
551    }
552}
553
554#[cfg(all(unix, feature = "libusb"))]
555impl Default for MouseReport {
556    fn default() -> Self {
557        Self::new()
558    }
559}
560
561/// HID mouse button bit positions
562#[cfg(all(unix, feature = "libusb"))]
563pub mod mouse_buttons {
564    pub const LEFT: u8 = 0x01;
565    pub const RIGHT: u8 = 0x02;
566    pub const MIDDLE: u8 = 0x04;
567}
568
569// ============================================================================
570// FIDO2 HID Descriptors
571// ============================================================================
572
573/// FIDO HID report descriptor (CTAPHID protocol)
574/// Uses 64-byte input and output reports for CTAPHID transport
575#[cfg(all(unix, feature = "libusb"))]
576pub const FIDO_HID_REPORT_DESCRIPTOR: &[u8] = &[
577    0x06, 0xD0, 0xF1, // Usage Page (FIDO Alliance)
578    0x09, 0x01, // Usage (U2F Authenticator Device)
579    0xA1, 0x01, // Collection (Application)
580    // Input report (device to host)
581    0x09, 0x20, //   Usage (Input Report Data)
582    0x15, 0x00, //   Logical Minimum (0)
583    0x26, 0xFF, 0x00, //   Logical Maximum (255)
584    0x75, 0x08, //   Report Size (8 bits)
585    0x95, 0x40, //   Report Count (64 bytes)
586    0x81, 0x02, //   Input (Data, Variable, Absolute)
587    // Output report (host to device)
588    0x09, 0x21, //   Usage (Output Report Data)
589    0x15, 0x00, //   Logical Minimum (0)
590    0x26, 0xFF, 0x00, //   Logical Maximum (255)
591    0x75, 0x08, //   Report Size (8 bits)
592    0x95, 0x40, //   Report Count (64 bytes)
593    0x91, 0x02, //   Output (Data, Variable, Absolute)
594    0xC0, // End Collection
595];
596
597/// Build a complete FIDO2 HID configuration descriptor
598/// Includes: Configuration, Interface, HID, and Endpoint descriptors
599#[cfg(all(unix, feature = "libusb"))]
600pub fn build_fido2_hid_config_descriptor() -> Vec<u8> {
601    let mut desc = Vec::new();
602
603    // Configuration descriptor (9 bytes)
604    desc.extend_from_slice(&[
605        9, // bLength
606        descriptor_type::CONFIGURATION,
607        34,
608        0,    // wTotalLength (9 + 9 + 9 + 7 + 7 = 41, but we'll calculate)
609        1,    // bNumInterfaces
610        1,    // bConfigurationValue
611        0,    // iConfiguration
612        0xA0, // bmAttributes (bus-powered, remote wakeup)
613        50,   // bMaxPower (100mA)
614    ]);
615
616    // Interface descriptor (9 bytes)
617    desc.extend_from_slice(&[
618        9, // bLength
619        descriptor_type::INTERFACE,
620        0,                 // bInterfaceNumber
621        0,                 // bAlternateSetting
622        2,                 // bNumEndpoints (IN and OUT)
623        device_class::HID, // bInterfaceClass (HID)
624        0,                 // bInterfaceSubClass (No subclass)
625        0,                 // bInterfaceProtocol (None)
626        0,                 // iInterface
627    ]);
628
629    // HID descriptor (9 bytes)
630    desc.extend_from_slice(&[
631        9,                    // bLength
632        descriptor_type::HID, // bDescriptorType (HID)
633        0x11,
634        0x01,                        // bcdHID (HID 1.11)
635        0,                           // bCountryCode (not localized)
636        1,                           // bNumDescriptors (1 report descriptor)
637        descriptor_type::HID_REPORT, // bDescriptorType (Report)
638        (FIDO_HID_REPORT_DESCRIPTOR.len() & 0xFF) as u8,
639        ((FIDO_HID_REPORT_DESCRIPTOR.len() >> 8) & 0xFF) as u8,
640    ]);
641
642    // Endpoint descriptor - IN (7 bytes)
643    desc.extend_from_slice(&[
644        7, // bLength
645        descriptor_type::ENDPOINT,
646        0x81, // bEndpointAddress (IN, endpoint 1)
647        0x03, // bmAttributes (Interrupt)
648        64,
649        0, // wMaxPacketSize (64 bytes)
650        5, // bInterval (5ms polling)
651    ]);
652
653    // Endpoint descriptor - OUT (7 bytes)
654    desc.extend_from_slice(&[
655        7, // bLength
656        descriptor_type::ENDPOINT,
657        0x01, // bEndpointAddress (OUT, endpoint 1)
658        0x03, // bmAttributes (Interrupt)
659        64,
660        0, // wMaxPacketSize (64 bytes)
661        5, // bInterval (5ms polling)
662    ]);
663
664    // Update total length
665    let total_len = desc.len() as u16;
666    desc[2] = (total_len & 0xFF) as u8;
667    desc[3] = ((total_len >> 8) & 0xFF) as u8;
668
669    desc
670}
671
672// ============================================================================
673// CDC ACM (Serial) Descriptors
674// ============================================================================
675
676/// CDC ACM descriptor subtypes
677#[cfg(all(unix, feature = "libusb"))]
678pub mod cdc_descriptor_subtype {
679    pub const HEADER: u8 = 0x00;
680    pub const CALL_MANAGEMENT: u8 = 0x01;
681    pub const ACM: u8 = 0x02;
682    pub const UNION: u8 = 0x06;
683}
684
685/// Build CDC ACM configuration descriptor
686/// CDC ACM uses two interfaces: Communication (control) and Data (bulk I/O)
687#[cfg(all(unix, feature = "libusb"))]
688pub fn build_cdc_acm_config_descriptor() -> Vec<u8> {
689    let mut desc = Vec::new();
690
691    // Configuration descriptor (9 bytes)
692    desc.extend_from_slice(&[
693        9, // bLength
694        descriptor_type::CONFIGURATION,
695        67,
696        0,    // wTotalLength (will be calculated)
697        2,    // bNumInterfaces (Communication + Data)
698        1,    // bConfigurationValue
699        0,    // iConfiguration
700        0xC0, // bmAttributes (self-powered)
701        50,   // bMaxPower (100mA)
702    ]);
703
704    // Interface 0: Communication Class Interface (control)
705    desc.extend_from_slice(&[
706        9, // bLength
707        descriptor_type::INTERFACE,
708        0,                  // bInterfaceNumber
709        0,                  // bAlternateSetting
710        1,                  // bNumEndpoints (1 interrupt endpoint)
711        device_class::COMM, // bInterfaceClass (CDC)
712        0x02,               // bInterfaceSubClass (ACM)
713        0x01,               // bInterfaceProtocol (AT commands)
714        0,                  // iInterface
715    ]);
716
717    // CDC Header Functional Descriptor (5 bytes)
718    desc.extend_from_slice(&[
719        5,    // bLength
720        0x24, // bDescriptorType (CS_INTERFACE)
721        cdc_descriptor_subtype::HEADER,
722        0x10,
723        0x01, // bcdCDC (1.10)
724    ]);
725
726    // CDC Call Management Functional Descriptor (5 bytes)
727    desc.extend_from_slice(&[
728        5,    // bLength
729        0x24, // bDescriptorType (CS_INTERFACE)
730        cdc_descriptor_subtype::CALL_MANAGEMENT,
731        0x00, // bmCapabilities (no call management)
732        0x01, // bDataInterface (interface 1)
733    ]);
734
735    // CDC ACM Functional Descriptor (4 bytes)
736    desc.extend_from_slice(&[
737        4,    // bLength
738        0x24, // bDescriptorType (CS_INTERFACE)
739        cdc_descriptor_subtype::ACM,
740        0x02, // bmCapabilities (supports line coding)
741    ]);
742
743    // CDC Union Functional Descriptor (5 bytes)
744    desc.extend_from_slice(&[
745        5,    // bLength
746        0x24, // bDescriptorType (CS_INTERFACE)
747        cdc_descriptor_subtype::UNION,
748        0x00, // bMasterInterface (interface 0)
749        0x01, // bSlaveInterface (interface 1)
750    ]);
751
752    // Endpoint descriptor: Interrupt IN (control)
753    desc.extend_from_slice(&[
754        7, // bLength
755        descriptor_type::ENDPOINT,
756        0x81, // bEndpointAddress (EP1 IN)
757        transfer_type::INTERRUPT,
758        0x08,
759        0x00, // wMaxPacketSize (8 bytes)
760        16,   // bInterval (16ms)
761    ]);
762
763    // Interface 1: Data Class Interface (bulk data)
764    desc.extend_from_slice(&[
765        9, // bLength
766        descriptor_type::INTERFACE,
767        1,                      // bInterfaceNumber
768        0,                      // bAlternateSetting
769        2,                      // bNumEndpoints (bulk IN + bulk OUT)
770        device_class::CDC_DATA, // bInterfaceClass
771        0x00,                   // bInterfaceSubClass
772        0x00,                   // bInterfaceProtocol
773        0,                      // iInterface
774    ]);
775
776    // Endpoint descriptor: Bulk OUT (host to device)
777    desc.extend_from_slice(&[
778        7, // bLength
779        descriptor_type::ENDPOINT,
780        0x02, // bEndpointAddress (EP2 OUT)
781        transfer_type::BULK,
782        0x40,
783        0x00, // wMaxPacketSize (64 bytes)
784        0,    // bInterval (ignored for bulk)
785    ]);
786
787    // Endpoint descriptor: Bulk IN (device to host)
788    desc.extend_from_slice(&[
789        7, // bLength
790        descriptor_type::ENDPOINT,
791        0x83, // bEndpointAddress (EP3 IN)
792        transfer_type::BULK,
793        0x40,
794        0x00, // wMaxPacketSize (64 bytes)
795        0,    // bInterval (ignored for bulk)
796    ]);
797
798    // Update total length
799    let total_len = desc.len() as u16;
800    desc[2] = (total_len & 0xff) as u8;
801    desc[3] = (total_len >> 8) as u8;
802
803    desc
804}
805
806/// CDC ACM Line Coding structure
807/// Used in SET_LINE_CODING and GET_LINE_CODING requests
808#[cfg(all(unix, feature = "libusb"))]
809#[derive(Debug, Clone, Copy)]
810pub struct LineCoding {
811    pub baud_rate: u32, // Bits per second
812    pub stop_bits: u8,  // 0=1 stop bit, 1=1.5, 2=2 stop bits
813    pub parity: u8,     // 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
814    pub data_bits: u8,  // 5, 6, 7, 8, or 16
815}
816
817#[cfg(all(unix, feature = "libusb"))]
818impl LineCoding {
819    /// Create default line coding (115200 8N1)
820    pub fn default_115200_8n1() -> Self {
821        Self {
822            baud_rate: 115200,
823            stop_bits: 0, // 1 stop bit
824            parity: 0,    // No parity
825            data_bits: 8,
826        }
827    }
828
829    /// Convert to 7-byte array for USB transmission
830    pub fn to_bytes(&self) -> [u8; 7] {
831        [
832            (self.baud_rate & 0xff) as u8,
833            ((self.baud_rate >> 8) & 0xff) as u8,
834            ((self.baud_rate >> 16) & 0xff) as u8,
835            ((self.baud_rate >> 24) & 0xff) as u8,
836            self.stop_bits,
837            self.parity,
838            self.data_bits,
839        ]
840    }
841
842    /// How many octets a CDC ACM line-coding structure occupies (USB CDC 1.2 §6.3.11).
843    pub const WIRE_LEN: usize = 7;
844
845    /// Parse from the 7 octets of a SET_LINE_CODING payload, or `None` if there are fewer.
846    ///
847    /// **This is `pub` and takes an arbitrary slice, so the length check belongs here.** It
848    /// used to index `bytes[0..6]` unchecked and rely on its one call site guarding with
849    /// `req.len() >= 7` — a latent panic one new caller away, and one that would be easy to
850    /// miss: a panic inside a `tokio::spawn`ed connection task is swallowed by the task, so
851    /// the server stays `Running`, the log shows the transfer succeeding and the peer simply
852    /// hangs. (`src/panic_log.rs` now writes such a panic to `netget.log`, which makes it
853    /// findable rather than harmless.) The payload comes from the USB host, so its length is
854    /// decided by the peer, not by us.
855    ///
856    /// A short payload is a malformed request, not a reason to guess: returning `None` leaves
857    /// the caller's existing line coding in place, which is what a device does when it cannot
858    /// honour a SET_LINE_CODING.
859    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
860        let b: &[u8; Self::WIRE_LEN] = bytes.get(..Self::WIRE_LEN)?.try_into().ok()?;
861        Some(Self {
862            baud_rate: u32::from_le_bytes([b[0], b[1], b[2], b[3]]),
863            stop_bits: b[4],
864            parity: b[5],
865            data_bits: b[6],
866        })
867    }
868}
869
870/// CDC ACM Control Line State
871/// DTR (Data Terminal Ready) and RTS (Request To Send)
872#[cfg(all(unix, feature = "libusb"))]
873#[derive(Debug, Clone, Copy)]
874pub struct ControlLineState {
875    pub dtr: bool, // Data Terminal Ready
876    pub rts: bool, // Request To Send
877}
878
879#[cfg(all(unix, feature = "libusb"))]
880impl ControlLineState {
881    pub fn from_value(value: u16) -> Self {
882        Self {
883            dtr: (value & 0x01) != 0,
884            rts: (value & 0x02) != 0,
885        }
886    }
887
888    pub fn to_value(&self) -> u16 {
889        let mut value = 0u16;
890        if self.dtr {
891            value |= 0x01;
892        }
893        if self.rts {
894            value |= 0x02;
895        }
896        value
897    }
898}
899
900// ============================================================================
901// USB Mass Storage Class (MSC) Descriptors
902// ============================================================================
903
904/// Build USB Mass Storage Class configuration descriptor
905/// MSC uses Bulk-Only Transport (BOT) with two bulk endpoints (IN + OUT)
906#[cfg(all(unix, feature = "libusb"))]
907pub fn build_msc_config_descriptor() -> Vec<u8> {
908    let mut desc = Vec::new();
909
910    // Configuration descriptor (9 bytes)
911    desc.extend_from_slice(&[
912        9, // bLength
913        descriptor_type::CONFIGURATION,
914        32,
915        0,    // wTotalLength (32 bytes total) - will be updated
916        1,    // bNumInterfaces
917        1,    // bConfigurationValue
918        0,    // iConfiguration (no string)
919        0xC0, // bmAttributes (self-powered)
920        50,   // bMaxPower (100mA)
921    ]);
922
923    // Interface descriptor (9 bytes)
924    desc.extend_from_slice(&[
925        9, // bLength
926        descriptor_type::INTERFACE,
927        0,    // bInterfaceNumber
928        0,    // bAlternateSetting
929        2,    // bNumEndpoints (Bulk IN + Bulk OUT)
930        0x08, // bInterfaceClass (Mass Storage)
931        0x06, // bInterfaceSubClass (SCSI transparent command set)
932        0x50, // bInterfaceProtocol (Bulk-Only Transport)
933        0,    // iInterface (no string)
934    ]);
935
936    // Endpoint descriptor: Bulk IN (7 bytes)
937    desc.extend_from_slice(&[
938        7, // bLength
939        descriptor_type::ENDPOINT,
940        0x81,                // bEndpointAddress (EP1 IN)
941        transfer_type::BULK, // bmAttributes (Bulk)
942        0x00,
943        0x02, // wMaxPacketSize (512 bytes for High Speed)
944        0,    // bInterval (ignored for bulk)
945    ]);
946
947    // Endpoint descriptor: Bulk OUT (7 bytes)
948    desc.extend_from_slice(&[
949        7, // bLength
950        descriptor_type::ENDPOINT,
951        0x02,                // bEndpointAddress (EP2 OUT)
952        transfer_type::BULK, // bmAttributes (Bulk)
953        0x00,
954        0x02, // wMaxPacketSize (512 bytes for High Speed)
955        0,    // bInterval (ignored for bulk)
956    ]);
957
958    // Update total length
959    let total_len = desc.len() as u16;
960    desc[2] = (total_len & 0xff) as u8;
961    desc[3] = (total_len >> 8) as u8;
962
963    desc
964}
965
966/// Command Block Wrapper (CBW) for Bulk-Only Transport
967/// Size: Exactly 31 bytes
968#[cfg(all(unix, feature = "libusb"))]
969#[repr(C, packed)]
970#[derive(Debug, Clone, Copy)]
971pub struct CommandBlockWrapper {
972    pub signature: u32,            // 0x43425355 ("USBC")
973    pub tag: u32,                  // Command tag (echo in CSW)
974    pub data_transfer_length: u32, // Expected data transfer bytes
975    pub flags: u8,                 // Direction: 0x80=IN, 0x00=OUT
976    pub lun: u8,                   // Logical Unit Number (0-15)
977    pub cb_length: u8,             // Command Block length (1-16)
978    pub cb: [u8; 16],              // SCSI command block
979}
980
981#[cfg(all(unix, feature = "libusb"))]
982impl CommandBlockWrapper {
983    pub const SIGNATURE: u32 = 0x43425355; // "USBC" in little-endian
984    pub const SIZE: usize = 31;
985
986    pub fn parse(data: &[u8]) -> Option<Self> {
987        if data.len() != Self::SIZE {
988            return None;
989        }
990
991        let signature = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
992        if signature != Self::SIGNATURE {
993            return None;
994        }
995
996        let tag = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
997        let data_transfer_length = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
998        let flags = data[12];
999        let lun = data[13];
1000        let cb_length = data[14];
1001
1002        let mut cb = [0u8; 16];
1003        cb.copy_from_slice(&data[15..31]);
1004
1005        Some(Self {
1006            signature,
1007            tag,
1008            data_transfer_length,
1009            flags,
1010            lun,
1011            cb_length,
1012            cb,
1013        })
1014    }
1015
1016    pub fn is_data_in(&self) -> bool {
1017        (self.flags & 0x80) != 0
1018    }
1019
1020    pub fn scsi_command(&self) -> &[u8] {
1021        &self.cb[0..(self.cb_length as usize).min(16)]
1022    }
1023}
1024
1025/// Command Status Wrapper (CSW) for Bulk-Only Transport
1026/// Size: Exactly 13 bytes
1027#[cfg(all(unix, feature = "libusb"))]
1028#[repr(C, packed)]
1029#[derive(Debug, Clone, Copy)]
1030pub struct CommandStatusWrapper {
1031    pub signature: u32,    // 0x53425355 ("USBS")
1032    pub tag: u32,          // Echo of CBW tag
1033    pub data_residue: u32, // Difference (expected - actual)
1034    pub status: u8,        // 0x00=success, 0x01=fail, 0x02=phase_error
1035}
1036
1037#[cfg(all(unix, feature = "libusb"))]
1038impl CommandStatusWrapper {
1039    pub const SIGNATURE: u32 = 0x53425355; // "USBS" in little-endian
1040    pub const SIZE: usize = 13;
1041
1042    pub const STATUS_PASSED: u8 = 0x00;
1043    pub const STATUS_FAILED: u8 = 0x01;
1044    pub const STATUS_PHASE_ERROR: u8 = 0x02;
1045
1046    pub fn new(tag: u32, data_residue: u32, status: u8) -> Self {
1047        Self {
1048            signature: Self::SIGNATURE,
1049            tag,
1050            data_residue,
1051            status,
1052        }
1053    }
1054
1055    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
1056        let mut bytes = [0u8; Self::SIZE];
1057        bytes[0..4].copy_from_slice(&self.signature.to_le_bytes());
1058        bytes[4..8].copy_from_slice(&self.tag.to_le_bytes());
1059        bytes[8..12].copy_from_slice(&self.data_residue.to_le_bytes());
1060        bytes[12] = self.status;
1061        bytes
1062    }
1063}
1064
1065/// SCSI command opcodes (subset)
1066#[cfg(all(unix, feature = "libusb"))]
1067pub mod scsi_opcode {
1068    pub const TEST_UNIT_READY: u8 = 0x00;
1069    pub const REQUEST_SENSE: u8 = 0x03;
1070    pub const INQUIRY: u8 = 0x12;
1071    pub const MODE_SENSE_6: u8 = 0x1A;
1072    pub const START_STOP_UNIT: u8 = 0x1B;
1073    pub const PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E;
1074    pub const READ_FORMAT_CAPACITIES: u8 = 0x23;
1075    pub const READ_CAPACITY_10: u8 = 0x25;
1076    pub const READ_10: u8 = 0x28;
1077    pub const WRITE_10: u8 = 0x2A;
1078}
1079
1080/// SCSI sense key codes
1081#[cfg(all(unix, feature = "libusb"))]
1082pub mod scsi_sense_key {
1083    pub const NO_SENSE: u8 = 0x00;
1084    pub const RECOVERED_ERROR: u8 = 0x01;
1085    pub const NOT_READY: u8 = 0x02;
1086    pub const MEDIUM_ERROR: u8 = 0x03;
1087    pub const HARDWARE_ERROR: u8 = 0x04;
1088    pub const ILLEGAL_REQUEST: u8 = 0x05;
1089    pub const UNIT_ATTENTION: u8 = 0x06;
1090    pub const DATA_PROTECT: u8 = 0x07;
1091    pub const ABORTED_COMMAND: u8 = 0x0B;
1092}