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}