⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/drivers/usb/
hid.rs

1//! USB HID (Human Interface Device) Driver
2//!
3//! Implements HID Boot Protocol support for keyboards and mice,
4//! HID report descriptor parsing stubs, and input event generation.
5//!
6//! Reference: USB HID Specification 1.11, USB HID Usage Tables 1.12
7
8#![allow(dead_code)]
9
10use crate::error::KernelError;
11
12// ---------------------------------------------------------------------------
13// HID Class Constants
14// ---------------------------------------------------------------------------
15
16/// HID descriptor type (returned in GET_DESCRIPTOR)
17pub const HID_DESCRIPTOR_TYPE: u8 = 0x21;
18/// HID report descriptor type
19pub const HID_REPORT_DESCRIPTOR_TYPE: u8 = 0x22;
20/// HID physical descriptor type
21pub const HID_PHYSICAL_DESCRIPTOR_TYPE: u8 = 0x23;
22
23// HID class-specific requests
24/// Get a report from the device
25pub const HID_GET_REPORT: u8 = 0x01;
26/// Get the idle rate
27pub const HID_GET_IDLE: u8 = 0x02;
28/// Get the active protocol (boot vs report)
29pub const HID_GET_PROTOCOL: u8 = 0x03;
30/// Send a report to the device
31pub const HID_SET_REPORT: u8 = 0x09;
32/// Set the idle rate
33pub const HID_SET_IDLE: u8 = 0x0A;
34/// Set the active protocol (boot vs report)
35pub const HID_SET_PROTOCOL: u8 = 0x0B;
36
37// HID protocol values for SET_PROTOCOL / GET_PROTOCOL
38/// Boot protocol (simplified fixed-format reports)
39pub const HID_PROTOCOL_BOOT: u8 = 0;
40/// Report protocol (full report descriptor driven)
41pub const HID_PROTOCOL_REPORT: u8 = 1;
42
43// HID subclass values
44/// No subclass
45pub const HID_SUBCLASS_NONE: u8 = 0x00;
46/// Boot interface subclass
47pub const HID_SUBCLASS_BOOT: u8 = 0x01;
48
49// HID boot interface protocol values
50/// Keyboard boot protocol
51pub const HID_BOOT_PROTOCOL_KEYBOARD: u8 = 0x01;
52/// Mouse boot protocol
53pub const HID_BOOT_PROTOCOL_MOUSE: u8 = 0x02;
54
55// ---------------------------------------------------------------------------
56// HID Report Descriptor Item Tags (parsing stubs)
57// ---------------------------------------------------------------------------
58
59/// Report descriptor item types
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[repr(u8)]
62pub enum HidItemType {
63    /// Main items: Input, Output, Feature, Collection, End Collection
64    Main = 0,
65    /// Global items: Usage Page, Logical Min/Max, Report Size/Count/ID
66    Global = 1,
67    /// Local items: Usage, Usage Min/Max, Designator, String
68    Local = 2,
69    /// Reserved
70    Reserved = 3,
71}
72
73/// Main item tags
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u8)]
76pub enum HidMainTag {
77    Input = 0x08,
78    Output = 0x09,
79    Feature = 0x0B,
80    Collection = 0x0A,
81    EndCollection = 0x0C,
82}
83
84/// Global item tags
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[repr(u8)]
87pub enum HidGlobalTag {
88    UsagePage = 0x00,
89    LogicalMinimum = 0x01,
90    LogicalMaximum = 0x02,
91    PhysicalMinimum = 0x03,
92    PhysicalMaximum = 0x04,
93    UnitExponent = 0x05,
94    Unit = 0x06,
95    ReportSize = 0x07,
96    ReportId = 0x08,
97    ReportCount = 0x09,
98    Push = 0x0A,
99    Pop = 0x0B,
100}
101
102/// Usage page values
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[repr(u16)]
105pub enum HidUsagePage {
106    GenericDesktop = 0x01,
107    SimulationControls = 0x02,
108    VrControls = 0x03,
109    SportControls = 0x04,
110    GameControls = 0x05,
111    GenericDevice = 0x06,
112    Keyboard = 0x07,
113    Led = 0x08,
114    Button = 0x09,
115    Consumer = 0x0C,
116}
117
118/// Generic Desktop usage IDs
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120#[repr(u16)]
121pub enum HidDesktopUsage {
122    Pointer = 0x01,
123    Mouse = 0x02,
124    Joystick = 0x04,
125    Gamepad = 0x05,
126    Keyboard = 0x06,
127    Keypad = 0x07,
128    X = 0x30,
129    Y = 0x31,
130    Z = 0x32,
131    Wheel = 0x38,
132}
133
134// ---------------------------------------------------------------------------
135// HID Device Types
136// ---------------------------------------------------------------------------
137
138/// Classification of HID device types
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum HidDeviceType {
141    /// Boot-protocol keyboard
142    Keyboard,
143    /// Boot-protocol mouse
144    Mouse,
145    /// Gamepad / joystick
146    Gamepad,
147    /// Generic HID device (parsed from report descriptor)
148    Generic,
149}
150
151// ---------------------------------------------------------------------------
152// Input Events
153// ---------------------------------------------------------------------------
154
155/// Input events generated from HID reports
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum InputEvent {
158    /// A key was pressed (HID scancode)
159    KeyPress(u8),
160    /// A key was released (HID scancode)
161    KeyRelease(u8),
162    /// Relative mouse movement (dx, dy)
163    MouseMove(i16, i16),
164    /// Mouse button state change (button index 0-based, true = pressed)
165    MouseButton(u8, bool),
166    /// Mouse scroll wheel delta (positive = up)
167    MouseScroll(i8),
168}
169
170// ---------------------------------------------------------------------------
171// Boot Protocol Report Structures
172// ---------------------------------------------------------------------------
173
174/// Boot keyboard report: 8 bytes
175///
176/// Byte 0: Modifier keys bitmask
177///   bit 0: Left Ctrl,  bit 1: Left Shift,  bit 2: Left Alt,  bit 3: Left GUI
178///   bit 4: Right Ctrl, bit 5: Right Shift, bit 6: Right Alt, bit 7: Right GUI
179/// Byte 1: Reserved (OEM use)
180/// Bytes 2-7: Up to 6 simultaneous key codes (0 = no key)
181#[derive(Debug, Clone, Copy)]
182#[repr(C, packed)]
183pub struct BootKeyboardReport {
184    pub modifiers: u8,
185    pub reserved: u8,
186    pub keys: [u8; 6],
187}
188
189impl BootKeyboardReport {
190    /// Create an empty keyboard report
191    pub const fn empty() -> Self {
192        Self {
193            modifiers: 0,
194            reserved: 0,
195            keys: [0; 6],
196        }
197    }
198
199    /// Parse a raw 8-byte buffer into a keyboard report
200    pub fn from_bytes(data: &[u8]) -> Result<Self, KernelError> {
201        if data.len() < 8 {
202            return Err(KernelError::InvalidArgument {
203                name: "hid_report",
204                value: "keyboard report must be 8 bytes",
205            });
206        }
207        Ok(Self {
208            modifiers: data[0],
209            reserved: data[1],
210            keys: [data[2], data[3], data[4], data[5], data[6], data[7]],
211        })
212    }
213
214    /// Check if a modifier bit is set
215    pub fn has_modifier(&self, bit: u8) -> bool {
216        (self.modifiers & (1 << bit)) != 0
217    }
218
219    /// Check for Left Ctrl
220    pub fn left_ctrl(&self) -> bool {
221        self.has_modifier(0)
222    }
223    /// Check for Left Shift
224    pub fn left_shift(&self) -> bool {
225        self.has_modifier(1)
226    }
227    /// Check for Left Alt
228    pub fn left_alt(&self) -> bool {
229        self.has_modifier(2)
230    }
231    /// Check for Left GUI (Super/Windows)
232    pub fn left_gui(&self) -> bool {
233        self.has_modifier(3)
234    }
235}
236
237/// Boot mouse report: 3 bytes minimum, optional 4th byte for scroll
238///
239/// Byte 0: Button bitmask (bit 0 = left, bit 1 = right, bit 2 = middle)
240/// Byte 1: X displacement (signed, -127 to +127)
241/// Byte 2: Y displacement (signed, -127 to +127)
242/// Byte 3 (optional): Scroll wheel (signed)
243#[derive(Debug, Clone, Copy)]
244#[repr(C, packed)]
245pub struct BootMouseReport {
246    pub buttons: u8,
247    pub x_displacement: i8,
248    pub y_displacement: i8,
249    pub scroll: i8,
250}
251
252impl BootMouseReport {
253    /// Create an empty mouse report
254    pub const fn empty() -> Self {
255        Self {
256            buttons: 0,
257            x_displacement: 0,
258            y_displacement: 0,
259            scroll: 0,
260        }
261    }
262
263    /// Parse raw bytes into a mouse report (3 or 4 bytes)
264    pub fn from_bytes(data: &[u8]) -> Result<Self, KernelError> {
265        if data.len() < 3 {
266            return Err(KernelError::InvalidArgument {
267                name: "hid_report",
268                value: "mouse report must be at least 3 bytes",
269            });
270        }
271        Ok(Self {
272            buttons: data[0],
273            x_displacement: data[1] as i8,
274            y_displacement: data[2] as i8,
275            scroll: if data.len() >= 4 { data[3] as i8 } else { 0 },
276        })
277    }
278
279    /// Check if the left button is pressed
280    pub fn left_button(&self) -> bool {
281        (self.buttons & 0x01) != 0
282    }
283    /// Check if the right button is pressed
284    pub fn right_button(&self) -> bool {
285        (self.buttons & 0x02) != 0
286    }
287    /// Check if the middle button is pressed
288    pub fn middle_button(&self) -> bool {
289        (self.buttons & 0x04) != 0
290    }
291}
292
293// ---------------------------------------------------------------------------
294// HID Report Descriptor Parser (Stub)
295// ---------------------------------------------------------------------------
296
297/// Parsed capabilities from a HID report descriptor
298#[derive(Debug, Clone)]
299pub struct HidCapabilities {
300    /// Detected device type
301    pub device_type: HidDeviceType,
302    /// Number of report IDs found (0 means no report ID prefix)
303    pub report_id_count: u8,
304    /// Whether the device supports boot protocol
305    pub boot_protocol: bool,
306    /// Maximum input report size in bytes
307    pub max_input_report_size: u16,
308    /// Maximum output report size in bytes
309    pub max_output_report_size: u16,
310}
311
312impl HidCapabilities {
313    /// Default capabilities for a keyboard in boot mode
314    pub fn boot_keyboard() -> Self {
315        Self {
316            device_type: HidDeviceType::Keyboard,
317            report_id_count: 0,
318            boot_protocol: true,
319            max_input_report_size: 8,
320            max_output_report_size: 1, // LED output report
321        }
322    }
323
324    /// Default capabilities for a mouse in boot mode
325    pub fn boot_mouse() -> Self {
326        Self {
327            device_type: HidDeviceType::Mouse,
328            report_id_count: 0,
329            boot_protocol: true,
330            max_input_report_size: 4,
331            max_output_report_size: 0,
332        }
333    }
334}
335
336/// Parse a HID report descriptor to determine device capabilities.
337///
338/// This is a stub implementation that detects basic device types by
339/// scanning for Usage Page and Usage items in the descriptor. Full
340/// report descriptor parsing (nested collections, push/pop state) is
341/// left as a future enhancement.
342pub fn parse_report_descriptor(descriptor: &[u8]) -> Result<HidCapabilities, KernelError> {
343    if descriptor.is_empty() {
344        return Err(KernelError::InvalidArgument {
345            name: "descriptor",
346            value: "empty report descriptor",
347        });
348    }
349
350    let mut device_type = HidDeviceType::Generic;
351    let mut usage_page: u16 = 0;
352    let mut report_id_count: u8 = 0;
353    let mut i = 0;
354
355    while i < descriptor.len() {
356        let prefix = descriptor[i];
357
358        // Long items (prefix == 0xFE) not supported in stub
359        if prefix == 0xFE {
360            if i + 2 < descriptor.len() {
361                let data_size = descriptor[i + 1] as usize;
362                i += 3 + data_size;
363            } else {
364                break;
365            }
366            continue;
367        }
368
369        // Short item: prefix encodes size (bits 0-1), type (bits 2-3), tag (bits 4-7)
370        let size = match prefix & 0x03 {
371            0 => 0usize,
372            1 => 1,
373            2 => 2,
374            3 => 4, // size code 3 means 4 bytes
375            _ => unreachable!(),
376        };
377        let item_type = (prefix >> 2) & 0x03;
378        let tag = (prefix >> 4) & 0x0F;
379
380        // Read up to 4 bytes of item data as a u32
381        let data = if size > 0 && i + size < descriptor.len() {
382            let mut val: u32 = 0;
383            for j in 0..size {
384                val |= (descriptor[i + 1 + j] as u32) << (j * 8);
385            }
386            val
387        } else {
388            0
389        };
390
391        match item_type {
392            // Global items
393            1 => {
394                match tag {
395                    0x00 => {
396                        // Usage Page
397                        usage_page = data as u16;
398                    }
399                    0x08 => {
400                        // Report ID
401                        report_id_count = report_id_count.saturating_add(1);
402                    }
403                    _ => {}
404                }
405            }
406            // Local items
407            2 => {
408                if tag == 0x00 {
409                    // Usage
410                    let usage = data as u16;
411                    // Detect device type from Generic Desktop usages
412                    if usage_page == HidUsagePage::GenericDesktop as u16 {
413                        match usage {
414                            0x06 | 0x07 => device_type = HidDeviceType::Keyboard,
415                            0x02 => device_type = HidDeviceType::Mouse,
416                            0x04 | 0x05 => device_type = HidDeviceType::Gamepad,
417                            _ => {}
418                        }
419                    }
420                }
421            }
422            _ => {}
423        }
424
425        i += 1 + size;
426    }
427
428    Ok(HidCapabilities {
429        device_type,
430        report_id_count,
431        boot_protocol: matches!(device_type, HidDeviceType::Keyboard | HidDeviceType::Mouse),
432        max_input_report_size: match device_type {
433            HidDeviceType::Keyboard => 8,
434            HidDeviceType::Mouse => 4,
435            _ => 64,
436        },
437        max_output_report_size: match device_type {
438            HidDeviceType::Keyboard => 1,
439            _ => 0,
440        },
441    })
442}
443
444// ---------------------------------------------------------------------------
445// HID Device
446// ---------------------------------------------------------------------------
447
448/// Maximum report buffer size
449const MAX_REPORT_SIZE: usize = 64;
450
451/// Represents a USB HID device with state tracking
452#[derive(Debug)]
453pub struct HidDevice {
454    /// USB device address on the bus
455    pub address: u8,
456    /// Interface number within the USB configuration
457    pub interface: u8,
458    /// Detected device type
459    pub device_type: HidDeviceType,
460    /// Interrupt IN endpoint address (for polling reports)
461    pub interrupt_endpoint: u8,
462    /// Interrupt endpoint polling interval in milliseconds
463    pub poll_interval_ms: u8,
464    /// Maximum packet size for the interrupt endpoint
465    pub max_packet_size: u16,
466    /// Whether the device is in boot protocol mode
467    pub boot_protocol: bool,
468    /// Parsed device capabilities
469    pub capabilities: HidCapabilities,
470    /// Previous keyboard report (for detecting key press/release)
471    prev_keyboard_report: BootKeyboardReport,
472    /// Previous mouse button state (for detecting button changes)
473    prev_mouse_buttons: u8,
474    /// Report receive buffer
475    report_buffer: [u8; MAX_REPORT_SIZE],
476    /// Number of valid bytes in the report buffer
477    report_len: usize,
478}
479
480impl HidDevice {
481    /// Create a new HID device with boot protocol configuration
482    pub fn new_boot_keyboard(address: u8, interface: u8, endpoint: u8, interval: u8) -> Self {
483        Self {
484            address,
485            interface,
486            device_type: HidDeviceType::Keyboard,
487            interrupt_endpoint: endpoint,
488            poll_interval_ms: interval,
489            max_packet_size: 8,
490            boot_protocol: true,
491            capabilities: HidCapabilities::boot_keyboard(),
492            prev_keyboard_report: BootKeyboardReport::empty(),
493            prev_mouse_buttons: 0,
494            report_buffer: [0; MAX_REPORT_SIZE],
495            report_len: 0,
496        }
497    }
498
499    /// Create a new HID device configured as a boot mouse
500    pub fn new_boot_mouse(address: u8, interface: u8, endpoint: u8, interval: u8) -> Self {
501        Self {
502            address,
503            interface,
504            device_type: HidDeviceType::Mouse,
505            interrupt_endpoint: endpoint,
506            poll_interval_ms: interval,
507            max_packet_size: 4,
508            boot_protocol: true,
509            capabilities: HidCapabilities::boot_mouse(),
510            prev_keyboard_report: BootKeyboardReport::empty(),
511            prev_mouse_buttons: 0,
512            report_buffer: [0; MAX_REPORT_SIZE],
513            report_len: 0,
514        }
515    }
516
517    /// Create a HID device from parsed capabilities
518    pub fn from_capabilities(
519        address: u8,
520        interface: u8,
521        endpoint: u8,
522        interval: u8,
523        caps: HidCapabilities,
524    ) -> Self {
525        Self {
526            address,
527            interface,
528            device_type: caps.device_type,
529            interrupt_endpoint: endpoint,
530            poll_interval_ms: interval,
531            max_packet_size: caps.max_input_report_size,
532            boot_protocol: caps.boot_protocol,
533            capabilities: caps,
534            prev_keyboard_report: BootKeyboardReport::empty(),
535            prev_mouse_buttons: 0,
536            report_buffer: [0; MAX_REPORT_SIZE],
537            report_len: 0,
538        }
539    }
540
541    /// Submit report data received from an interrupt transfer.
542    ///
543    /// Stores the data in the internal buffer for subsequent
544    /// [`process_report`](Self::process_report) calls.
545    pub fn submit_report(&mut self, data: &[u8]) -> Result<(), KernelError> {
546        if data.is_empty() {
547            return Err(KernelError::InvalidArgument {
548                name: "report_data",
549                value: "empty report",
550            });
551        }
552        let len = data.len().min(MAX_REPORT_SIZE);
553        self.report_buffer[..len].copy_from_slice(&data[..len]);
554        self.report_len = len;
555        Ok(())
556    }
557
558    /// Process the current report buffer and generate input events.
559    ///
560    /// Returns a small array of events. For keyboards this includes
561    /// key press/release deltas compared to the previous report; for
562    /// mice this includes movement, button changes, and scroll.
563    pub fn process_report(&mut self) -> Result<InputEventBatch, KernelError> {
564        if self.report_len == 0 {
565            return Ok(InputEventBatch::empty());
566        }
567
568        // Copy report data to a local buffer to avoid borrowing self
569        // immutably (report_buffer) while also borrowing mutably (self).
570        let mut local_buf = [0u8; MAX_REPORT_SIZE];
571        let len = self.report_len;
572        local_buf[..len].copy_from_slice(&self.report_buffer[..len]);
573
574        match self.device_type {
575            HidDeviceType::Keyboard => self.process_keyboard_report(&local_buf[..len]),
576            HidDeviceType::Mouse => self.process_mouse_report(&local_buf[..len]),
577            _ => Ok(InputEventBatch::empty()),
578        }
579    }
580
581    /// Process a boot keyboard report and generate press/release events
582    fn process_keyboard_report(&mut self, data: &[u8]) -> Result<InputEventBatch, KernelError> {
583        let report = BootKeyboardReport::from_bytes(data)?;
584        let mut batch = InputEventBatch::empty();
585
586        // Detect modifier changes (bits 0-7)
587        let mod_diff = report.modifiers ^ self.prev_keyboard_report.modifiers;
588        for bit in 0..8u8 {
589            if mod_diff & (1 << bit) != 0 {
590                // Modifier keycodes: 0xE0 + bit (per HID usage tables)
591                let keycode = 0xE0 + bit;
592                if report.modifiers & (1 << bit) != 0 {
593                    batch.push(InputEvent::KeyPress(keycode));
594                } else {
595                    batch.push(InputEvent::KeyRelease(keycode));
596                }
597            }
598        }
599
600        // Detect newly pressed keys (in new report but not in previous)
601        for &key in &report.keys {
602            if key == 0 {
603                continue;
604            }
605            let was_pressed = self.prev_keyboard_report.keys.contains(&key);
606            if !was_pressed {
607                batch.push(InputEvent::KeyPress(key));
608            }
609        }
610
611        // Detect released keys (in previous report but not in new)
612        for &key in &self.prev_keyboard_report.keys {
613            if key == 0 {
614                continue;
615            }
616            let still_pressed = report.keys.contains(&key);
617            if !still_pressed {
618                batch.push(InputEvent::KeyRelease(key));
619            }
620        }
621
622        self.prev_keyboard_report = report;
623        Ok(batch)
624    }
625
626    /// Process a boot mouse report and generate movement/button/scroll events
627    fn process_mouse_report(&mut self, data: &[u8]) -> Result<InputEventBatch, KernelError> {
628        let report = BootMouseReport::from_bytes(data)?;
629        let mut batch = InputEventBatch::empty();
630
631        // Mouse movement
632        if report.x_displacement != 0 || report.y_displacement != 0 {
633            batch.push(InputEvent::MouseMove(
634                report.x_displacement as i16,
635                report.y_displacement as i16,
636            ));
637        }
638
639        // Button changes (check bits 0-2: left, right, middle)
640        let btn_diff = report.buttons ^ self.prev_mouse_buttons;
641        for bit in 0..3u8 {
642            if btn_diff & (1 << bit) != 0 {
643                let pressed = report.buttons & (1 << bit) != 0;
644                batch.push(InputEvent::MouseButton(bit, pressed));
645            }
646        }
647
648        // Scroll wheel
649        if report.scroll != 0 {
650            batch.push(InputEvent::MouseScroll(report.scroll));
651        }
652
653        self.prev_mouse_buttons = report.buttons;
654        Ok(batch)
655    }
656
657    /// Poll the device for new input (stub).
658    ///
659    /// In a real implementation this would issue an interrupt transfer
660    /// to the device's interrupt IN endpoint via the host controller
661    /// and return the first event from the resulting report. This stub
662    /// always returns `None`.
663    #[cfg(target_os = "none")]
664    pub fn poll_device(&mut self) -> Option<InputEvent> {
665        // TODO(phase7.5): Issue interrupt transfer via host controller
666        // let transfer = UsbTransfer::interrupt_in(
667        //     self.address, self.interrupt_endpoint, self.max_packet_size);
668        // if let Ok(data) = host_controller.submit(transfer) {
669        //     self.submit_report(&data).ok();
670        //     let batch = self.process_report().ok()?;
671        //     return batch.first();
672        // }
673        None
674    }
675
676    /// Poll the device for new input (non-hardware stub, always None)
677    #[cfg(not(target_os = "none"))]
678    pub fn poll_device(&mut self) -> Option<InputEvent> {
679        None
680    }
681}
682
683// ---------------------------------------------------------------------------
684// Input Event Batch
685// ---------------------------------------------------------------------------
686
687/// Maximum number of events from a single report
688const MAX_EVENTS_PER_REPORT: usize = 16;
689
690/// Small fixed-capacity collection of input events from a single report
691#[derive(Debug, Clone)]
692pub struct InputEventBatch {
693    events: [Option<InputEvent>; MAX_EVENTS_PER_REPORT],
694    count: usize,
695}
696
697impl InputEventBatch {
698    /// Create an empty batch
699    pub const fn empty() -> Self {
700        Self {
701            events: [None; MAX_EVENTS_PER_REPORT],
702            count: 0,
703        }
704    }
705
706    /// Push an event into the batch (drops if full)
707    pub fn push(&mut self, event: InputEvent) {
708        if self.count < MAX_EVENTS_PER_REPORT {
709            self.events[self.count] = Some(event);
710            self.count += 1;
711        }
712    }
713
714    /// Number of events in the batch
715    pub fn len(&self) -> usize {
716        self.count
717    }
718
719    /// Whether the batch is empty
720    pub fn is_empty(&self) -> bool {
721        self.count == 0
722    }
723
724    /// Get the first event, if any
725    pub fn first(&self) -> Option<InputEvent> {
726        if self.count > 0 {
727            self.events[0]
728        } else {
729            None
730        }
731    }
732
733    /// Iterate over events in the batch
734    pub fn iter(&self) -> InputEventBatchIter<'_> {
735        InputEventBatchIter {
736            batch: self,
737            index: 0,
738        }
739    }
740}
741
742/// Iterator over events in an [`InputEventBatch`]
743pub struct InputEventBatchIter<'a> {
744    batch: &'a InputEventBatch,
745    index: usize,
746}
747
748impl<'a> Iterator for InputEventBatchIter<'a> {
749    type Item = InputEvent;
750
751    fn next(&mut self) -> Option<Self::Item> {
752        if self.index < self.batch.count {
753            let event = self.batch.events[self.index];
754            self.index += 1;
755            event
756        } else {
757            None
758        }
759    }
760}
761
762// ---------------------------------------------------------------------------
763// Tests
764// ---------------------------------------------------------------------------
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769
770    #[test]
771    fn test_boot_keyboard_report_parse() {
772        // Modifier: Left Shift (bit 1), key 'A' (0x04)
773        let data = [0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00];
774        let report = BootKeyboardReport::from_bytes(&data).unwrap();
775        assert_eq!(report.modifiers, 0x02);
776        assert!(report.left_shift());
777        assert!(!report.left_ctrl());
778        assert_eq!(report.keys[0], 0x04);
779        assert_eq!(report.keys[1], 0x00);
780    }
781
782    #[test]
783    fn test_boot_keyboard_report_too_short() {
784        let data = [0x02, 0x00, 0x04];
785        let result = BootKeyboardReport::from_bytes(&data);
786        assert!(result.is_err());
787    }
788
789    #[test]
790    fn test_boot_mouse_report_parse() {
791        // Left button pressed, X=+10, Y=-5
792        let data: [u8; 3] = [0x01, 10, (-5i8) as u8];
793        let report = BootMouseReport::from_bytes(&data).unwrap();
794        assert!(report.left_button());
795        assert!(!report.right_button());
796        assert_eq!(report.x_displacement, 10);
797        assert_eq!(report.y_displacement, -5);
798        assert_eq!(report.scroll, 0); // no scroll byte
799    }
800
801    #[test]
802    fn test_boot_mouse_report_with_scroll() {
803        let data: [u8; 4] = [0x04, 0, 0, (-3i8) as u8]; // middle button, scroll -3
804        let report = BootMouseReport::from_bytes(&data).unwrap();
805        assert!(report.middle_button());
806        assert!(!report.left_button());
807        assert_eq!(report.scroll, -3);
808    }
809
810    #[test]
811    fn test_keyboard_event_generation() {
812        let mut dev = HidDevice::new_boot_keyboard(1, 0, 0x81, 10);
813
814        // Press 'A' (0x04)
815        let data = [0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00];
816        dev.submit_report(&data).unwrap();
817        let batch = dev.process_report().unwrap();
818        assert_eq!(batch.len(), 1);
819        assert_eq!(batch.first(), Some(InputEvent::KeyPress(0x04)));
820
821        // Release 'A', press 'B' (0x05)
822        let data = [0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00];
823        dev.submit_report(&data).unwrap();
824        let batch = dev.process_report().unwrap();
825        // Should have KeyPress(0x05) and KeyRelease(0x04)
826        assert_eq!(batch.len(), 2);
827        let events: [Option<InputEvent>; 2] = [
828            batch
829                .iter()
830                .find(|e| matches!(e, InputEvent::KeyPress(0x05))),
831            batch
832                .iter()
833                .find(|e| matches!(e, InputEvent::KeyRelease(0x04))),
834        ];
835        assert!(events[0].is_some());
836        assert!(events[1].is_some());
837    }
838
839    #[test]
840    fn test_mouse_event_generation() {
841        let mut dev = HidDevice::new_boot_mouse(2, 0, 0x81, 10);
842
843        // Move mouse and press left button
844        let data: [u8; 4] = [0x01, 20, (-10i8) as u8, 0];
845        dev.submit_report(&data).unwrap();
846        let batch = dev.process_report().unwrap();
847
848        // Should have MouseMove + MouseButton(0, true)
849        assert!(batch.len() >= 2);
850        let has_move = batch
851            .iter()
852            .any(|e| matches!(e, InputEvent::MouseMove(20, -10)));
853        let has_btn = batch
854            .iter()
855            .any(|e| matches!(e, InputEvent::MouseButton(0, true)));
856        assert!(has_move);
857        assert!(has_btn);
858    }
859
860    #[test]
861    fn test_report_descriptor_keyboard_detection() {
862        // Minimal descriptor: Usage Page (Generic Desktop), Usage (Keyboard)
863        let descriptor: [u8; 4] = [
864            0x05, 0x01, // Usage Page: Generic Desktop (global, 1 byte)
865            0x09, 0x06, // Usage: Keyboard (local, 1 byte)
866        ];
867        let caps = parse_report_descriptor(&descriptor).unwrap();
868        assert_eq!(caps.device_type, HidDeviceType::Keyboard);
869        assert!(caps.boot_protocol);
870        assert_eq!(caps.max_input_report_size, 8);
871    }
872
873    #[test]
874    fn test_report_descriptor_mouse_detection() {
875        // Minimal descriptor: Usage Page (Generic Desktop), Usage (Mouse)
876        let descriptor: [u8; 4] = [
877            0x05, 0x01, // Usage Page: Generic Desktop
878            0x09, 0x02, // Usage: Mouse
879        ];
880        let caps = parse_report_descriptor(&descriptor).unwrap();
881        assert_eq!(caps.device_type, HidDeviceType::Mouse);
882        assert!(caps.boot_protocol);
883        assert_eq!(caps.max_input_report_size, 4);
884    }
885
886    #[test]
887    fn test_report_descriptor_empty() {
888        let result = parse_report_descriptor(&[]);
889        assert!(result.is_err());
890    }
891
892    #[test]
893    fn test_input_event_batch_overflow() {
894        let mut batch = InputEventBatch::empty();
895        for i in 0..MAX_EVENTS_PER_REPORT + 5 {
896            batch.push(InputEvent::KeyPress(i as u8));
897        }
898        // Should cap at MAX_EVENTS_PER_REPORT
899        assert_eq!(batch.len(), MAX_EVENTS_PER_REPORT);
900    }
901
902    #[test]
903    fn test_poll_device_returns_none() {
904        let mut dev = HidDevice::new_boot_keyboard(1, 0, 0x81, 10);
905        assert_eq!(dev.poll_device(), None);
906    }
907}