Skip to main content

km003c_lib/
device.rs

1//! KM003C USB device communication
2//!
3//! # KM003C USB Interfaces
4//!
5//! The KM003C provides multiple USB interfaces for communication.
6//! All interfaces support the same protocol commands (4-byte packets).
7//!
8//! ## Interface 0: Vendor Specific (Primary Protocol)
9//! - **Transfer Type**: Bulk
10//! - **Endpoints**: 0x01 OUT, 0x81 IN
11//! - **Max Packet Size**: 64 bytes
12//! - **Throughput**: ~200 KB/s (official spec)
13//! - **Latency**: ~0.6 ms (measured)
14//! - **Linux Driver**: `powerz` (hwmon)
15//! - **Use Case**: Best performance, same as kernel driver
16//! - **Note**: Requires detaching kernel driver on Linux
17//!
18//! ## Interface 1+2: CDC (Virtual Serial Port)
19//! - **Transfer Type**: Bulk (Interface 2) + Interrupt (Interface 1)
20//! - **Endpoints**: 0x02 OUT, 0x82 IN (data), 0x83 IN (control)
21//! - **Throughput**: ~200 KB/s (official spec)
22//! - **Linux Driver**: `cdc_acm`
23//! - **Use Case**: Serial port compatibility
24//! - **Note**: Not currently implemented in this library
25//!
26//! ## Interface 3: HID (Human Interface Device)
27//! - **Transfer Type**: Interrupt
28//! - **Endpoints**: 0x05 OUT, 0x85 IN
29//! - **Max Packet Size**: 64 bytes
30//! - **Throughput**: ~60 KB/s (official spec)
31//! - **Latency**: ~3.8 ms (measured)
32//! - **Linux Driver**: `usbhid`
33//! - **Use Case**: Most compatible, no driver installation needed
34//! - **Note**: Works on all platforms without custom drivers
35//!
36//! ## Performance Comparison
37//! Based on real device measurements:
38//! - **Interface 0 (Bulk)**: 0.6 ms latency - **6x faster** ⚡
39//! - **Interface 3 (Interrupt)**: 3.8 ms latency - most compatible
40//!
41//! ## Recommendation
42//! - Use **Interface 0** for performance-critical applications (same as kernel driver)
43//! - Use **Interface 3** for maximum compatibility across platforms
44
45use crate::adc::AdcDataSimple;
46use crate::adcqueue::GraphSampleRate;
47use crate::auth::{
48    AuthCredential, CALIBRATION_ADDRESS, DeviceInfo, HardwareId, PREFERRED_CALIBRATION_ADDRESS,
49    STREAMING_AUTH_CREDENTIAL_SIZE, StreamingAuthResult,
50};
51use crate::error::KMError;
52use crate::message::Packet;
53use crate::offline::{LogMetadata, LogMetadataResponse, OfflineLog};
54use crate::packet::{Attribute, AttributeSet, PacketPeek, PacketType, RawPacket};
55use crate::pd::{PdEventStream, PdStatus};
56use crate::settings::Settings;
57use bytes::Bytes;
58use nusb::Interface;
59use nusb::io::{EndpointRead, EndpointWrite};
60use nusb::transfer::{Bulk, Interrupt};
61use std::collections::VecDeque;
62use std::fmt;
63use std::time::Duration;
64use tokio::io::{AsyncReadExt, AsyncWriteExt};
65use tokio::time::timeout;
66use tracing::{debug, info, trace, warn};
67
68fn calibration_record_is_erased(record: &[u8]) -> bool {
69    record.starts_with(&[0xff; 4])
70}
71
72/// Device state populated by initialization
73///
74/// Contains all information gathered during device init:
75/// - Device info (model, versions, serials)
76/// - Hardware ID (used for authentication)
77/// - Authentication state
78#[derive(Debug, Clone)]
79pub struct DeviceState {
80    /// Device information (model, firmware, serials, etc.)
81    pub info: DeviceInfo,
82    /// Hardware ID used for authentication
83    pub hardware_id: HardwareId,
84    /// Authentication level (0 = not authenticated, 1+ = authenticated)
85    pub auth_level: u8,
86    /// Whether AdcQueue streaming is enabled
87    pub adcqueue_enabled: bool,
88}
89
90impl DeviceState {
91    /// Check if device is authenticated
92    pub fn is_authenticated(&self) -> bool {
93        self.auth_level > 0
94    }
95
96    /// Get device model name
97    pub fn model(&self) -> &str {
98        &self.info.model
99    }
100
101    /// Get firmware version
102    pub fn firmware_version(&self) -> &str {
103        &self.info.fw_version
104    }
105}
106
107impl fmt::Display for DeviceState {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        const W: usize = 58; // inner width between │ and │
110
111        // Helper to create a content line with label and value
112        fn line(label: &str, value: &str) -> String {
113            let content = format!(" {}: {}", label, value);
114            format!("│{:<W$}│", content, W = W)
115        }
116
117        let sep = format!("├{:─<W$}┤", "", W = W);
118
119        writeln!(f, "┌{:─<W$}┐", "", W = W)?;
120        writeln!(f, "│{:^W$}│", "POWER-Z KM003C Device Information", W = W)?;
121        writeln!(f, "{}", sep)?;
122        writeln!(f, "{}", line("Model", &self.info.model))?;
123        writeln!(f, "{}", line("Hardware Version", &self.info.hw_version))?;
124        writeln!(f, "{}", line("Mfg Date", &self.info.mfg_date))?;
125        writeln!(f, "{}", sep)?;
126        writeln!(f, "{}", line("Firmware Version", &self.info.fw_version))?;
127        writeln!(f, "{}", line("Firmware Date", &self.info.fw_date))?;
128        writeln!(f, "{}", sep)?;
129        writeln!(f, "{}", line("Serial ID", &self.info.serial_id))?;
130        writeln!(f, "{}", line("UUID", &self.info.uuid))?;
131        writeln!(f, "{}", line("Hardware ID", &format!("{}", self.hardware_id)))?;
132        writeln!(f, "{}", sep)?;
133        writeln!(f, "{}", line("Auth Level", &format!("{}", self.auth_level)))?;
134        writeln!(f, "{}", line("AdcQueue Enabled", &format!("{}", self.adcqueue_enabled)))?;
135        write!(f, "└{:─<W$}┘", "", W = W)
136    }
137}
138
139/// USB device identification constants
140pub const VID: u16 = 0x5FC9; // ChargerLAB vendor ID
141pub const PID: u16 = 0x0063; // KM003C product ID
142
143/// Interface 0 (Vendor Specific): Bulk transfers, fastest (~0.6ms)
144pub const INTERFACE_VENDOR: u8 = 0;
145pub const ENDPOINT_OUT_VENDOR: u8 = 0x01;
146pub const ENDPOINT_IN_VENDOR: u8 = 0x81;
147
148/// Interface 3 (HID): Interrupt transfers, most compatible (~3.8ms)
149pub const INTERFACE_HID: u8 = 3;
150pub const ENDPOINT_OUT_HID: u8 = 0x05;
151pub const ENDPOINT_IN_HID: u8 = 0x85;
152
153// Default timeout for USB operations
154const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2);
155const BULK_TRANSFER_SIZE: usize = 2048;
156const INTERRUPT_TRANSFER_SIZE: usize = 64;
157const MAX_PENDING_RESPONSES: usize = 256;
158const AES_BLOCK_SIZE: usize = 16;
159
160/// Find one attached KM003C matching `selector`.
161async fn find_device(selector: &DeviceSelector) -> Result<nusb::DeviceInfo, KMError> {
162    nusb::list_devices()
163        .await?
164        .find(|device| device.vendor_id() == VID && device.product_id() == PID && selector.matches(device))
165        .ok_or(KMError::DeviceNotFound)
166}
167
168fn response_matches(bytes: &[u8], id: u8, packet_type: PacketType) -> bool {
169    PacketPeek::from_bytes(bytes).is_some_and(|peek| peek.id == id && peek.packet_type == packet_type)
170}
171
172fn response_type_matches(bytes: &[u8], packet_type: PacketType) -> bool {
173    PacketPeek::from_bytes(bytes).is_some_and(|peek| peek.packet_type == packet_type)
174}
175
176fn control_response_matches(bytes: &[u8], id: u8) -> bool {
177    PacketPeek::from_bytes(bytes).is_some_and(|peek| peek.is_ctrl() && peek.id == id)
178}
179
180fn memory_confirmation_matches(bytes: &[u8], id: u8) -> bool {
181    PacketPeek::from_bytes(bytes).is_some_and(|peek| {
182        peek.id == id
183            && matches!(
184                peek.packet_type,
185                PacketType::MemoryRead | PacketType::Rejected | PacketType::NotReadable
186            )
187    })
188}
189
190fn memory_response_size(requested_size: u32) -> usize {
191    (requested_size as usize).div_ceil(AES_BLOCK_SIZE) * AES_BLOCK_SIZE
192}
193
194fn append_memory_chunk(buffer: &mut Vec<u8>, chunk: &[u8], expected_size: usize) -> Result<bool, KMError> {
195    if chunk.is_empty() {
196        return Err(KMError::Protocol(
197            "Received an empty encrypted memory chunk".to_string(),
198        ));
199    }
200
201    let remaining = expected_size.saturating_sub(buffer.len());
202    if chunk.len() > remaining {
203        return Err(KMError::Protocol(format!(
204            "Encrypted memory response exceeded expected size: expected {expected_size} bytes, got at least {}",
205            buffer.len() + chunk.len()
206        )));
207    }
208
209    buffer.extend_from_slice(chunk);
210    Ok(buffer.len() == expected_size)
211}
212
213fn decrypt_memory_response(encrypted: &[u8], requested_size: u32) -> Result<Vec<u8>, KMError> {
214    let expected_size = memory_response_size(requested_size);
215    if encrypted.len() != expected_size {
216        return Err(KMError::Protocol(format!(
217            "Encrypted memory response has the wrong size: expected {expected_size} bytes, got {}",
218            encrypted.len()
219        )));
220    }
221
222    let mut decrypted = crate::auth::aes_ecb_decrypt_blocks(encrypted, crate::auth::MEMORY_READ_KEY)?;
223    decrypted.truncate(requested_size as usize);
224    Ok(decrypted)
225}
226
227fn validate_memory_read_confirmation(
228    packet: &RawPacket,
229    expected_id: u8,
230    expected_address: u32,
231    expected_size: u32,
232) -> Result<(), KMError> {
233    let RawPacket::SimpleData { header, payload } = packet else {
234        return Err(KMError::Protocol(
235            "MemoryRead confirmation has an unexpected packet layout".to_string(),
236        ));
237    };
238
239    if header.id() != expected_id || PacketType::from(header.packet_type()) != PacketType::MemoryRead {
240        return Err(KMError::Protocol(
241            "MemoryRead confirmation does not match the request".to_string(),
242        ));
243    }
244    if payload.len() != crate::auth::MEMORY_READ_CONFIRMATION_SIZE {
245        return Err(KMError::Protocol(format!(
246            "MemoryRead confirmation must contain {} bytes, got {}",
247            crate::auth::MEMORY_READ_CONFIRMATION_SIZE,
248            payload.len()
249        )));
250    }
251
252    let confirmation = crate::auth::MemoryReadConfirmation::from_bytes(payload)
253        .ok_or_else(|| KMError::Protocol("MemoryRead confirmation body is truncated".to_string()))?;
254
255    if confirmation.address != expected_address || confirmation.size != expected_size {
256        return Err(KMError::Protocol(format!(
257            "MemoryRead confirmation echoed address 0x{:08X} and size {}, expected 0x{expected_address:08X} and {expected_size}",
258            confirmation.address, confirmation.size
259        )));
260    }
261    if confirmation.magic != u32::MAX {
262        return Err(KMError::Protocol(format!(
263            "MemoryRead confirmation has invalid magic 0x{:08X}",
264            confirmation.magic
265        )));
266    }
267    if confirmation.checksum != confirmation.expected_checksum {
268        return Err(KMError::Protocol(format!(
269            "MemoryRead confirmation CRC mismatch: expected 0x{:08X}, got 0x{:08X}",
270            confirmation.expected_checksum, confirmation.checksum
271        )));
272    }
273
274    Ok(())
275}
276
277/// Transfer type for USB communication
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub enum TransferType {
280    Bulk,
281    Interrupt,
282}
283
284/// Connection mode determined by interface type
285///
286/// - **Basic**: HID interface - ADC/PD polling only, no initialization needed
287/// - **Full**: Vendor interface - all features including AdcQueue streaming
288#[derive(Debug, Clone)]
289pub enum ConnectionMode {
290    /// Basic mode (HID interface) - ADC/PD polling only
291    Basic,
292    /// Full mode (Vendor interface) - all features, includes device state
293    Full(DeviceState),
294}
295
296fn ensure_adcqueue_available(mode: &ConnectionMode) -> Result<(), KMError> {
297    match mode {
298        ConnectionMode::Basic => Err(KMError::Protocol(
299            "AdcQueue streaming requires Full mode (vendor interface)".to_string(),
300        )),
301        ConnectionMode::Full(state) if !state.adcqueue_enabled => Err(KMError::Protocol(
302            "StreamingAuth did not enable AdcQueue streaming".to_string(),
303        )),
304        ConnectionMode::Full(_) => Ok(()),
305    }
306}
307
308/// Configuration for KM003C device communication
309///
310/// Specifies which USB interface to use. The interface determines the connection mode:
311/// - **Vendor** (Interface 0): Full mode - all features including AdcQueue
312/// - **HID** (Interface 3): Basic mode - ADC/PD polling only
313///
314/// # Examples
315///
316/// ```no_run
317/// use km003c_lib::{KM003C, DeviceConfig};
318///
319/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
320/// // Full mode with vendor interface (fastest, all features)
321/// let device = KM003C::new(DeviceConfig::vendor()).await?;
322///
323/// // Basic mode with HID interface (most compatible, ADC/PD only)
324/// let device = KM003C::new(DeviceConfig::hid()).await?;
325///
326/// // With options
327/// let device = KM003C::new(DeviceConfig::vendor().skip_reset()).await?;
328/// # Ok(())
329/// # }
330/// ```
331#[derive(Debug, Clone)]
332pub struct DeviceConfig {
333    /// USB interface number (0 or 3)
334    interface: u8,
335    /// OUT endpoint address
336    endpoint_out: u8,
337    /// IN endpoint address
338    endpoint_in: u8,
339    /// Transfer type (Bulk or Interrupt)
340    transfer_type: TransferType,
341    /// Skip initial USB reset
342    skip_reset: bool,
343    /// Which attached KM003C to open
344    selector: DeviceSelector,
345}
346
347/// Which KM003C to open when several are attached.
348///
349/// A USB reset re-enumerates the device, so the selector is also what makes the
350/// post-reset reopen land on the same unit rather than on whichever KM003C the
351/// OS happens to list first.
352#[derive(Debug, Clone, Default, PartialEq, Eq)]
353pub enum DeviceSelector {
354    /// Open the first KM003C the OS reports.
355    #[default]
356    First,
357    /// Open the KM003C with this USB serial number string.
358    SerialNumber(String),
359    /// Open the KM003C at this bus and device address.
360    ///
361    /// The address changes across a USB reset, so prefer
362    /// [`DeviceSelector::SerialNumber`] together with `skip_reset()`.
363    BusAddress { bus_id: String, device_address: u8 },
364}
365
366impl DeviceSelector {
367    fn matches(&self, device: &nusb::DeviceInfo) -> bool {
368        match self {
369            Self::First => true,
370            Self::SerialNumber(serial) => device.serial_number() == Some(serial.as_str()),
371            Self::BusAddress { bus_id, device_address } => {
372                device.bus_id() == bus_id && device.device_address() == *device_address
373            }
374        }
375    }
376
377    fn describe(&self) -> String {
378        match self {
379            Self::First => "the first attached KM003C".to_string(),
380            Self::SerialNumber(serial) => format!("KM003C with serial {serial}"),
381            Self::BusAddress { bus_id, device_address } => format!("KM003C at bus {bus_id} address {device_address}"),
382        }
383    }
384}
385
386impl DeviceConfig {
387    /// Vendor interface (Interface 0) - Full mode with all features
388    ///
389    /// This is the **fastest option** with ~0.6ms latency (6x faster than HID).
390    /// Enables all features including AdcQueue streaming.
391    ///
392    /// **Performance**:
393    /// - Latency: ~600 µs per request
394    /// - Throughput: ~200 KB/s
395    ///
396    /// **Features**: Full mode - ADC, PD, AdcQueue, device info, authentication
397    ///
398    /// **Note**: On Linux, requires detaching the `powerz` kernel driver,
399    /// which this library handles automatically.
400    pub fn vendor() -> Self {
401        Self {
402            interface: INTERFACE_VENDOR,
403            endpoint_out: ENDPOINT_OUT_VENDOR,
404            endpoint_in: ENDPOINT_IN_VENDOR,
405            transfer_type: TransferType::Bulk,
406            skip_reset: false,
407            selector: DeviceSelector::First,
408        }
409    }
410
411    /// HID interface (Interface 3) - Basic mode for ADC/PD polling
412    ///
413    /// This is the **most compatible option** that works on all platforms
414    /// without custom drivers. Only supports basic ADC and PD polling.
415    ///
416    /// **Performance**:
417    /// - Latency: ~3.8 ms per request
418    /// - Throughput: ~60 KB/s
419    ///
420    /// **Features**: Basic mode - ADC and PD polling only (no AdcQueue, no device info)
421    ///
422    /// **Advantages**:
423    /// - Works without driver installation on Windows/Mac/Linux
424    /// - Uses standard HID protocol
425    /// - No permission issues
426    pub fn hid() -> Self {
427        Self {
428            interface: INTERFACE_HID,
429            endpoint_out: ENDPOINT_OUT_HID,
430            endpoint_in: ENDPOINT_IN_HID,
431            transfer_type: TransferType::Interrupt,
432            skip_reset: false,
433            selector: DeviceSelector::First,
434        }
435    }
436
437    /// Skip USB reset during connection
438    ///
439    /// Some systems (particularly MacOS) may have issues with USB reset.
440    /// Use this if device connection fails with reset errors.
441    pub fn skip_reset(mut self) -> Self {
442        self.skip_reset = true;
443        self
444    }
445
446    /// Open a specific KM003C instead of the first one found.
447    ///
448    /// # Examples
449    ///
450    /// ```no_run
451    /// use km003c_lib::{DeviceConfig, DeviceSelector, KM003C};
452    ///
453    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
454    /// let config = DeviceConfig::vendor()
455    ///     .select(DeviceSelector::SerialNumber("007965".to_string()))
456    ///     .skip_reset();
457    /// let device = KM003C::new(config).await?;
458    /// # Ok(())
459    /// # }
460    /// ```
461    pub fn select(mut self, selector: DeviceSelector) -> Self {
462        self.selector = selector;
463        self
464    }
465
466    /// Which KM003C this config opens.
467    pub fn selector(&self) -> &DeviceSelector {
468        &self.selector
469    }
470
471    /// Check if this config uses vendor interface (full mode)
472    pub fn is_vendor(&self) -> bool {
473        self.interface == INTERFACE_VENDOR
474    }
475
476    /// Check if this config uses HID interface (basic mode)
477    pub fn is_hid(&self) -> bool {
478        self.interface == INTERFACE_HID
479    }
480}
481
482/// Endpoint reader wrapper to handle both Bulk and Interrupt types
483enum EndpointReaderType {
484    Bulk(EndpointRead<Bulk>),
485    Interrupt(EndpointRead<Interrupt>),
486}
487
488/// Endpoint writer wrapper to handle both Bulk and Interrupt types
489enum EndpointWriterType {
490    Bulk(EndpointWrite<Bulk>),
491    Interrupt(EndpointWrite<Interrupt>),
492}
493
494pub struct KM003C {
495    /// Kept alive for RAII - dropping this releases the USB interface claim
496    #[allow(dead_code)]
497    interface: Interface,
498    transaction_id: u8,
499    reader: EndpointReaderType,
500    writer: EndpointWriterType,
501    pending_responses: VecDeque<Vec<u8>>,
502    /// Rate currently selected with StartGraph, used to decode rate-dependent fields.
503    graph_sample_rate: Option<GraphSampleRate>,
504    /// Connection mode: Basic (HID) or Full (Vendor with device state)
505    mode: ConnectionMode,
506}
507
508impl KM003C {
509    /// Connect to device with the given configuration
510    ///
511    /// The config determines the connection mode:
512    /// - **`DeviceConfig::vendor()`**: Full mode - runs init sequence, all features enabled
513    /// - **`DeviceConfig::hid()`**: Basic mode - no init, ADC/PD polling only
514    ///
515    /// # Examples
516    ///
517    /// ```no_run
518    /// use km003c_lib::{KM003C, DeviceConfig};
519    ///
520    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
521    /// // Full mode (vendor interface) - all features
522    /// let device = KM003C::new(DeviceConfig::vendor()).await?;
523    /// println!("Model: {}", device.state().unwrap().model());
524    /// println!("AdcQueue enabled: {}", device.adcqueue_enabled());
525    ///
526    /// // Basic mode (HID interface) - ADC/PD polling only
527    /// let mut device = KM003C::new(DeviceConfig::hid()).await?;
528    /// let adc = device.request_adc_data().await?;
529    ///
530    /// // With options
531    /// let device = KM003C::new(DeviceConfig::vendor().skip_reset()).await?;
532    /// # Ok(())
533    /// # }
534    /// ```
535    pub async fn new(config: DeviceConfig) -> Result<Self, KMError> {
536        let is_vendor = config.is_vendor();
537        let mut device = Self::connect(config).await?;
538
539        if is_vendor {
540            // Full mode: run init sequence
541            device.run_init().await?;
542        }
543        // HID mode: stays in Basic mode, no init needed
544
545        Ok(device)
546    }
547
548    /// Internal: Connect to USB device without initialization
549    async fn connect(config: DeviceConfig) -> Result<Self, KMError> {
550        info!("Searching for {}...", config.selector.describe());
551        let device_info = find_device(&config.selector).await?;
552
553        info!(
554            "Found device on bus {} addr {}",
555            device_info.bus_id(),
556            device_info.device_address()
557        );
558
559        // A reset re-enumerates the device, so reopening must target the same
560        // unit. Addresses are not stable across a reset, so fall back to the
561        // serial number of the device that was just reset.
562        let reopen_selector = match &config.selector {
563            DeviceSelector::SerialNumber(_) => config.selector.clone(),
564            _ => device_info
565                .serial_number()
566                .map(|serial| DeviceSelector::SerialNumber(serial.to_string()))
567                .unwrap_or(DeviceSelector::First),
568        };
569
570        let mut device = device_info.open().await?;
571
572        // Optionally reset device (skip on MacOS if having issues)
573        if !config.skip_reset {
574            info!("Resetting device...");
575            device.reset().await?;
576            // CRITICAL: Device needs 1.5 seconds to fully initialize after reset
577            // (validated through protocol research - 100ms is insufficient for AdcQueue)
578            tokio::time::sleep(Duration::from_millis(1500)).await;
579            // Re-enumerate and reopen after reset (old handle may be invalid).
580            device = find_device(&reopen_selector).await?.open().await?;
581        } else {
582            debug!("Skipping USB reset (skip_reset=true)");
583        }
584
585        // Detach kernel drivers from ALL interfaces
586        // All 4 interfaces have kernel drivers on Linux:
587        //   Interface 0: powerz (hwmon)
588        //   Interface 1+2: cdc_acm (serial)
589        //   Interface 3: usbhid (HID generic)
590        // Detaching all prevents re-binding and ensures clean access
591        for interface_num in 0..4 {
592            if let Err(e) = device.detach_kernel_driver(interface_num) {
593                // Ignore errors - driver may already be detached
594                trace!("Could not detach interface {}: {}", interface_num, e);
595            } else {
596                debug!("Detached kernel driver from interface {}", interface_num);
597            }
598        }
599
600        // Claim the configured interface
601        let interface = device.claim_interface(config.interface).await?;
602        info!("Interface {} claimed successfully", config.interface);
603
604        // Create persistent endpoints based on transfer type
605        // Using 4 concurrent transfers for better throughput
606        // A bulk response can span multiple 2048-byte transfers. receive_raw()
607        // joins transfers until the device terminates the response with a short packet.
608        let (reader, writer) = match config.transfer_type {
609            TransferType::Bulk => {
610                let ep_in = interface.endpoint::<Bulk, _>(config.endpoint_in)?;
611                let ep_out = interface.endpoint::<Bulk, _>(config.endpoint_out)?;
612                (
613                    EndpointReaderType::Bulk(ep_in.reader(BULK_TRANSFER_SIZE).with_num_transfers(4)),
614                    EndpointWriterType::Bulk(ep_out.writer(INTERRUPT_TRANSFER_SIZE).with_num_transfers(4)),
615                )
616            }
617            TransferType::Interrupt => {
618                let ep_in = interface.endpoint::<Interrupt, _>(config.endpoint_in)?;
619                let ep_out = interface.endpoint::<Interrupt, _>(config.endpoint_out)?;
620                (
621                    EndpointReaderType::Interrupt(ep_in.reader(INTERRUPT_TRANSFER_SIZE).with_num_transfers(4)),
622                    EndpointWriterType::Interrupt(ep_out.writer(INTERRUPT_TRANSFER_SIZE).with_num_transfers(4)),
623                )
624            }
625        };
626
627        let km003c = Self {
628            interface,
629            transaction_id: 0,
630            reader,
631            writer,
632            pending_responses: VecDeque::new(),
633            graph_sample_rate: None,
634            mode: ConnectionMode::Basic,
635        };
636
637        info!("USB connection established");
638        Ok(km003c)
639    }
640
641    /// Get the next transaction ID (internal use)
642    fn next_transaction_id(&mut self) -> u8 {
643        let id = self.transaction_id;
644        self.transaction_id = self.transaction_id.wrapping_add(1);
645        // A buffered response predates this new request, even if its eight-bit
646        // transaction ID happens to match after rollover.
647        self.pending_responses
648            .retain(|response| !PacketPeek::from_bytes(response).is_some_and(|peek| peek.id == id));
649        id
650    }
651
652    /// Get the next transaction ID (for auth module and external use)
653    pub fn next_tid(&mut self) -> u8 {
654        self.next_transaction_id()
655    }
656
657    /// Set the transaction ID counter (for protocol research/sync purposes)
658    pub fn set_transaction_id(&mut self, id: u8) {
659        self.transaction_id = id;
660    }
661
662    /// Send a high-level packet without waiting for or correlating its response.
663    ///
664    /// This is a low-level protocol-research API. Prefer command-specific methods
665    /// such as [`Self::request_data`] and [`Self::read_memory_block`] for normal use.
666    pub async fn send(&mut self, packet: Packet) -> Result<(), KMError> {
667        self.send_tracked(packet).await.map(|_| ())
668    }
669
670    /// Send a high-level packet and return the transaction ID placed on the wire.
671    ///
672    /// `MemoryRead` and `StreamingAuth` need no special casing here: their
673    /// vendor header layout is produced by `Packet::to_raw_packet` like every
674    /// other command.
675    async fn send_tracked(&mut self, packet: Packet) -> Result<u8, KMError> {
676        let id = self.next_transaction_id();
677        let raw_packet = packet.to_raw_packet(id)?;
678        self.send_raw_packet(raw_packet).await?;
679        Ok(id)
680    }
681
682    /// Send a raw packet to the device
683    async fn send_raw_packet(&mut self, packet: RawPacket) -> Result<(), KMError> {
684        let (reserved_flag, has_logical_packets) = match &packet {
685            RawPacket::Ctrl { header, .. } => (header.reserved_flag(), false),
686            RawPacket::SimpleData { header, .. } => (header.reserved_flag(), false),
687            RawPacket::Data {
688                header,
689                logical_packets,
690            } => (header.reserved_flag(), !logical_packets.is_empty()),
691        };
692
693        debug!(
694            "Sending packet: type={:?}, reserved_flag={}, has_logical_packets={}, id={}",
695            packet.packet_type(),
696            reserved_flag,
697            has_logical_packets,
698            packet.id(),
699        );
700
701        let message_bytes = Bytes::from(packet);
702        let message = message_bytes.to_vec();
703        trace!("TX [{} bytes]: {:02x?}", message.len(), message);
704
705        // Use the persistent writer
706        match &mut self.writer {
707            EndpointWriterType::Bulk(writer) => {
708                timeout(DEFAULT_TIMEOUT, writer.write_all(&message)).await??;
709                timeout(DEFAULT_TIMEOUT, writer.flush_end_async()).await??;
710            }
711            EndpointWriterType::Interrupt(writer) => {
712                timeout(DEFAULT_TIMEOUT, writer.write_all(&message)).await??;
713                timeout(DEFAULT_TIMEOUT, writer.flush_end_async()).await??;
714            }
715        }
716
717        debug!("Sent successfully");
718        Ok(())
719    }
720
721    /// Send raw bytes to the device (for protocol research/testing)
722    pub async fn send_raw(&mut self, data: &[u8]) -> Result<(), KMError> {
723        trace!("TX [{} bytes]: {:02x?}", data.len(), data);
724        match &mut self.writer {
725            EndpointWriterType::Bulk(writer) => {
726                timeout(DEFAULT_TIMEOUT, writer.write_all(data)).await??;
727                timeout(DEFAULT_TIMEOUT, writer.flush_end_async()).await??;
728            }
729            EndpointWriterType::Interrupt(writer) => {
730                timeout(DEFAULT_TIMEOUT, writer.write_all(data)).await??;
731                timeout(DEFAULT_TIMEOUT, writer.flush_end_async()).await??;
732            }
733        }
734        Ok(())
735    }
736
737    /// Read one complete response directly from the USB endpoint.
738    async fn read_raw_from_usb(&mut self) -> Result<Vec<u8>, KMError> {
739        let mut buffer = Vec::new();
740        let bytes_read = match &mut self.reader {
741            EndpointReaderType::Bulk(reader) => {
742                // Bulk messages are delimited by a short USB packet. Reading through
743                // this adapter preserves one complete protocol response even when it
744                // is larger than either the caller's buffer or one 2048-byte transfer.
745                let mut message = reader.until_short_packet();
746                let bytes_read = timeout(DEFAULT_TIMEOUT, message.read_to_end(&mut buffer)).await??;
747                message
748                    .consume_end()
749                    .map_err(|_| KMError::Protocol("Bulk response ended without a short packet".to_string()))?;
750                bytes_read
751            }
752            EndpointReaderType::Interrupt(reader) => {
753                buffer.resize(INTERRUPT_TRANSFER_SIZE, 0);
754                let bytes_read = timeout(DEFAULT_TIMEOUT, reader.read(&mut buffer)).await??;
755                buffer.truncate(bytes_read);
756                bytes_read
757            }
758        };
759        trace!("RX [{} bytes]: {:02x?}", bytes_read, buffer);
760        Ok(buffer)
761    }
762
763    fn queue_pending_response(&mut self, response: Vec<u8>) {
764        if self.pending_responses.len() == MAX_PENDING_RESPONSES {
765            warn!("Dropping oldest unmatched response because the pending queue is full");
766            self.pending_responses.pop_front();
767        }
768        self.pending_responses.push_back(response);
769    }
770
771    async fn receive_matching_raw<F>(&mut self, mut predicate: F) -> Result<Vec<u8>, KMError>
772    where
773        F: FnMut(&[u8]) -> bool,
774    {
775        if let Some(index) = self.pending_responses.iter().position(|response| predicate(response)) {
776            return Ok(self
777                .pending_responses
778                .remove(index)
779                .expect("pending response index is valid"));
780        }
781
782        let deadline = tokio::time::Instant::now() + DEFAULT_TIMEOUT;
783        loop {
784            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
785            if remaining.is_zero() {
786                return Err(KMError::Protocol(
787                    "Timed out waiting for a correlated response".to_string(),
788                ));
789            }
790
791            let response = timeout(remaining, self.read_raw_from_usb()).await??;
792            if predicate(&response) {
793                return Ok(response);
794            }
795
796            if let Some(peek) = PacketPeek::from_bytes(&response) {
797                debug!(
798                    "Queueing unmatched response: type={:?}, id={}, len={}",
799                    peek.packet_type,
800                    peek.id,
801                    response.len()
802                );
803            } else {
804                debug!("Queueing unframed response: len={}", response.len());
805            }
806            self.queue_pending_response(response);
807        }
808    }
809
810    /// Receive raw bytes without transaction correlation.
811    ///
812    /// This is a low-level protocol-research API. Previously received
813    /// out-of-order responses are returned first.
814    pub async fn receive_raw(&mut self) -> Result<Vec<u8>, KMError> {
815        if let Some(response) = self.pending_responses.pop_front() {
816            return Ok(response);
817        }
818        self.read_raw_from_usb().await
819    }
820
821    fn parse_response(raw_bytes: Vec<u8>, graph_rate: Option<GraphSampleRate>) -> Result<Packet, KMError> {
822        if raw_bytes.is_empty() {
823            return Err(KMError::Protocol("Received 0 bytes".to_string()));
824        }
825
826        let raw_packet = RawPacket::try_from(Bytes::from(raw_bytes))?;
827        if let Some(rate) = graph_rate {
828            Packet::from_raw_with_graph_rate(raw_packet, rate)
829        } else {
830            Packet::try_from(raw_packet)
831        }
832    }
833
834    /// Receive the next framed high-level packet without transaction correlation.
835    ///
836    /// Unframed transfers such as encrypted MemoryRead data cannot be parsed by
837    /// this method. Use [`Self::read_memory_block`] for device memory reads.
838    pub async fn receive(&mut self) -> Result<Packet, KMError> {
839        let raw_bytes = self.receive_raw().await?;
840        Self::parse_response(raw_bytes, self.graph_sample_rate)
841    }
842
843    async fn receive_control_response(&mut self, id: u8) -> Result<Packet, KMError> {
844        let raw_bytes = self
845            .receive_matching_raw(|bytes| control_response_matches(bytes, id))
846            .await?;
847        Self::parse_response(raw_bytes, self.graph_sample_rate)
848    }
849
850    async fn expect_accept(&mut self, id: u8, command: &str) -> Result<(), KMError> {
851        match self.receive_control_response(id).await? {
852            Packet::Accept { .. } => Ok(()),
853            Packet::Reject { .. } => Err(KMError::Protocol(format!("{command} was rejected by the device"))),
854            Packet::NotReadable { .. } => Err(KMError::Protocol(format!("{command} is not readable"))),
855            other => Err(KMError::Protocol(format!(
856                "Expected Accept for {command}, got {other:?}"
857            ))),
858        }
859    }
860
861    /// Read the unframed ciphertext that follows a MemoryRead confirmation.
862    ///
863    /// These transfers carry no header and AES ciphertext is indistinguishable
864    /// from a protocol frame, so nothing else may be in flight while they are
865    /// read. [`Self::read_memory_block`] enforces that precondition.
866    async fn receive_memory_read_data_exact(&mut self, requested_size: u32) -> Result<Vec<u8>, KMError> {
867        let expected_size = memory_response_size(requested_size);
868        if expected_size == 0 {
869            return Ok(Vec::new());
870        }
871
872        let mut encrypted = Vec::with_capacity(expected_size);
873        while encrypted.len() < expected_size {
874            let chunk = self.read_raw_from_usb().await?;
875            if append_memory_chunk(&mut encrypted, &chunk, expected_size)? {
876                break;
877            }
878        }
879
880        decrypt_memory_response(&encrypted, requested_size)
881    }
882
883    /// Request data with a specific attribute set
884    pub async fn request_data(&mut self, mask: AttributeSet) -> Result<Packet, KMError> {
885        let id = self.send_tracked(Packet::GetData { attributes: mask }).await?;
886        let raw_bytes = self
887            .receive_matching_raw(|bytes| response_matches(bytes, id, PacketType::PutData))
888            .await?;
889        let raw_packet = RawPacket::try_from(Bytes::from(raw_bytes))?;
890        raw_packet.validate_correlation(mask)?;
891        if let Some(rate) = self.graph_sample_rate {
892            Packet::from_raw_with_graph_rate(raw_packet, rate)
893        } else {
894            Packet::try_from(raw_packet)
895        }
896    }
897
898    /// Request ADC data only
899    pub async fn request_adc_data(&mut self) -> Result<AdcDataSimple, KMError> {
900        let packet = self.request_data(AttributeSet::single(Attribute::Adc)).await?;
901        packet
902            .get_adc()
903            .cloned()
904            .ok_or_else(|| KMError::Protocol("No ADC data in response".to_string()))
905    }
906
907    /// Request and parse the two read-only settings blocks.
908    pub async fn request_settings(&mut self) -> Result<Settings, KMError> {
909        let packet = self.request_data(AttributeSet::single(Attribute::Settings)).await?;
910        packet
911            .get_settings()
912            .cloned()
913            .ok_or_else(|| KMError::Protocol("No Settings data in response".to_string()))
914    }
915
916    /// Request and drain the internal USB PD state-machine trace queues.
917    pub async fn request_pd_trace(&mut self) -> Result<crate::pd_trace::PdTrace, KMError> {
918        let packet = self.request_data(AttributeSet::single(Attribute::PdTrace)).await?;
919        packet
920            .get_pd_trace()
921            .cloned()
922            .ok_or_else(|| KMError::Protocol("No PD trace data in response".to_string()))
923    }
924
925    /// Request metadata for the offline log currently selected on the device.
926    ///
927    /// Returns an empty vector when no offline log is available.
928    pub async fn request_log_metadata(&mut self) -> Result<Vec<LogMetadata>, KMError> {
929        let packet = self.request_data(AttributeSet::single(Attribute::LogMetadata)).await?;
930        match packet.get_log_metadata() {
931            Some(LogMetadataResponse::Empty) => Ok(Vec::new()),
932            Some(LogMetadataResponse::Available(metadata)) => Ok(metadata.clone()),
933            None => Err(KMError::Protocol("No LogMetadata data in response".to_string())),
934        }
935    }
936
937    /// Download the offline samples described by previously requested metadata.
938    pub async fn download_offline_log(&mut self, metadata: LogMetadata) -> Result<OfflineLog, KMError> {
939        let data = self
940            .read_memory_block(metadata.data_address()?, metadata.data_size())
941            .await?;
942        OfflineLog::from_bytes(metadata, &data)
943    }
944
945    /// Request PD data (returns full packet as it can contain PdStatus OR PdEventStream)
946    ///
947    /// The response depends on the device state:
948    /// - If payload is 12 bytes: returns PdStatus (use packet.get_pd_status())
949    /// - If payload > 12 bytes: returns PdEventStream (use packet.get_pd_events())
950    ///
951    /// Use `request_adc_with_pd()` to get ADC + PdStatus together (68 bytes).
952    pub async fn request_pd_data(&mut self) -> Result<Packet, KMError> {
953        self.request_data(AttributeSet::single(Attribute::PdPacket)).await
954    }
955
956    /// Request both ADC and PD data in a single request
957    ///
958    /// This typically returns ADC + PdStatus (68 bytes total).
959    /// Use packet.get_adc() and packet.get_pd_status() to extract data.
960    pub async fn request_adc_with_pd(&mut self) -> Result<Packet, KMError> {
961        let mask = AttributeSet::single(Attribute::Adc).with(Attribute::PdPacket);
962        self.request_data(mask).await
963    }
964
965    /// Helper: Try to get PD status from a packet
966    ///
967    /// Returns Some if the packet contains PdStatus (12-byte payload),
968    /// None otherwise. Useful after request_pd_data() or request_adc_with_pd().
969    pub fn extract_pd_status(packet: &Packet) -> Option<&PdStatus> {
970        packet.get_pd_status()
971    }
972
973    /// Helper: Try to get PD event stream from a packet
974    ///
975    /// Returns Some if the packet contains PdEventStream (>12 bytes),
976    /// None otherwise. Useful after request_pd_data().
977    pub fn extract_pd_events(packet: &Packet) -> Option<&PdEventStream> {
978        packet.get_pd_events()
979    }
980
981    /// Enable PD monitor/sniffer
982    ///
983    /// Sends the EnablePdMonitor command (0x10) to start capturing PD events.
984    /// This may be required before `request_pd_data()` returns PD events,
985    /// especially in Basic mode (HID interface).
986    ///
987    /// Returns Ok(()) on Accept, error otherwise.
988    pub async fn enable_pd_monitor(&mut self) -> Result<(), KMError> {
989        let id = self.send_tracked(Packet::EnablePdMonitor).await?;
990        self.expect_accept(id, "EnablePdMonitor").await
991    }
992
993    /// Disable PD monitor/sniffer
994    ///
995    /// Sends the DisablePdMonitor command (0x11) to stop capturing PD events.
996    ///
997    /// Returns Ok(()) on Accept, error otherwise.
998    pub async fn disable_pd_monitor(&mut self) -> Result<(), KMError> {
999        let id = self.send_tracked(Packet::DisablePdMonitor).await?;
1000        self.expect_accept(id, "DisablePdMonitor").await
1001    }
1002
1003    /// Internal: Run initialization sequence for vendor interface (Full mode)
1004    ///
1005    /// Performs the full initialization sequence:
1006    /// 1. Connect to device
1007    /// 2. Read DeviceInfo (0x420)
1008    /// 3. Read FirmwareInfo (0x4420)
1009    /// 4. Read Calibration (0x3000C00)
1010    /// 5. Read HardwareID (0x40010450)
1011    /// 6. StreamingAuth (authenticate for AdcQueue)
1012    ///
1013    /// After successful init, mode is set to Full(DeviceState).
1014    async fn run_init(&mut self) -> Result<(), KMError> {
1015        use crate::auth::{HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE};
1016
1017        // 1. Connect (with retries - sometimes device responds with Disconnect on first try)
1018        const MAX_CONNECT_RETRIES: u8 = 3;
1019        let mut last_error = None;
1020        for attempt in 1..=MAX_CONNECT_RETRIES {
1021            match self.send_tracked(Packet::Connect).await {
1022                Err(err) => last_error = Some(format!("Connect send failed: {err:?}")),
1023                Ok(id) => match self.expect_accept(id, "Connect").await {
1024                    Ok(()) => {
1025                        last_error = None;
1026                        break;
1027                    }
1028                    Err(err) => last_error = Some(format!("Connect receive failed: {err:?}")),
1029                },
1030            }
1031            if attempt < MAX_CONNECT_RETRIES {
1032                debug!("Connect attempt {} failed, retrying...", attempt);
1033                tokio::time::sleep(Duration::from_millis(100)).await;
1034            }
1035        }
1036        if let Some(err) = last_error {
1037            return Err(KMError::Protocol(err));
1038        }
1039
1040        let info = self.read_device_info_blocks().await;
1041
1042        // 5. Read HardwareID. A short read must fail rather than authenticate
1043        // with a zero-filled credential.
1044        let data = self
1045            .read_memory_block(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32)
1046            .await
1047            .map_err(|error| {
1048                KMError::Protocol(format!(
1049                    "Failed to read HardwareID - required for authentication: {error}"
1050                ))
1051            })?;
1052        let hardware_id_bytes: [u8; HARDWARE_ID_SIZE] = data.as_slice().try_into().map_err(|_| {
1053            KMError::Protocol(format!(
1054                "HardwareID read returned {} bytes, expected {HARDWARE_ID_SIZE}",
1055                data.len()
1056            ))
1057        })?;
1058        let hardware_id = HardwareId::from_bytes(hardware_id_bytes);
1059
1060        // 6. StreamingAuth
1061        let auth = self.perform_streaming_auth(AuthCredential::from(&hardware_id)).await?;
1062
1063        // Store device state in Full mode
1064        let state = DeviceState {
1065            info,
1066            hardware_id,
1067            auth_level: auth.auth_level,
1068            adcqueue_enabled: auth.adcqueue_enabled(),
1069        };
1070
1071        info!("Device initialized: {} (auth_level={})", state.model(), auth.auth_level);
1072
1073        self.mode = ConnectionMode::Full(state);
1074
1075        Ok(())
1076    }
1077
1078    async fn perform_streaming_auth(&mut self, credential: AuthCredential) -> Result<StreamingAuthResult, KMError> {
1079        self.send_tracked(Packet::StreamingAuth { credential }).await?;
1080        let response = self
1081            // StreamingAuth is the documented exception to normal transaction
1082            // correlation: captured device responses always carry ID 0.
1083            .receive_matching_raw(|bytes| response_type_matches(bytes, PacketType::StreamingAuth))
1084            .await?;
1085
1086        match Self::parse_response(response, self.graph_sample_rate)? {
1087            Packet::StreamingAuthResponse(result) => Ok(result),
1088            other => Err(KMError::Protocol(format!(
1089                "Expected StreamingAuthResponse, got {other:?}"
1090            ))),
1091        }
1092    }
1093
1094    /// Get the current connection mode
1095    ///
1096    /// - `ConnectionMode::Basic` - HID interface, ADC/PD polling only
1097    /// - `ConnectionMode::Full(state)` - Vendor interface, all features
1098    pub fn mode(&self) -> &ConnectionMode {
1099        &self.mode
1100    }
1101
1102    /// Check if device is in full mode (vendor interface)
1103    pub fn is_full_mode(&self) -> bool {
1104        matches!(self.mode, ConnectionMode::Full(_))
1105    }
1106
1107    /// Check if device is in basic mode (HID interface)
1108    pub fn is_basic_mode(&self) -> bool {
1109        matches!(self.mode, ConnectionMode::Basic)
1110    }
1111
1112    /// Get device state (only available in Full mode)
1113    ///
1114    /// Returns `Some` when connected via vendor interface (Full mode).
1115    /// Returns `None` when connected via HID interface (Basic mode).
1116    pub fn state(&self) -> Option<&DeviceState> {
1117        match &self.mode {
1118            ConnectionMode::Full(state) => Some(state),
1119            ConnectionMode::Basic => None,
1120        }
1121    }
1122
1123    /// Check if device is authenticated (Full mode only)
1124    ///
1125    /// Returns `false` in Basic mode or if authentication failed.
1126    pub fn is_authenticated(&self) -> bool {
1127        self.state().map(|s| s.is_authenticated()).unwrap_or(false)
1128    }
1129
1130    /// Check if AdcQueue streaming is enabled (Full mode only)
1131    ///
1132    /// Returns `false` in Basic mode or if not authenticated for streaming.
1133    pub fn adcqueue_enabled(&self) -> bool {
1134        self.state().map(|s| s.adcqueue_enabled).unwrap_or(false)
1135    }
1136
1137    /// Authenticate with the device's calibration credential and enter auth level 2.
1138    ///
1139    /// KM003C firmware V1.9.9 prefers the first 12 bytes at `0x03000D80`.
1140    /// When that record starts with `0xFFFFFFFF`, it falls back to
1141    /// `0x03000C00`. This method only reads those known addresses and sends a
1142    /// StreamingAuth request; it does not write settings or flash memory.
1143    pub async fn authenticate_calibration(&mut self) -> Result<StreamingAuthResult, KMError> {
1144        if !self.is_full_mode() {
1145            return Err(KMError::Protocol(
1146                "Calibration authentication requires the vendor interface".to_string(),
1147            ));
1148        }
1149
1150        let preferred = self
1151            .read_memory_block(PREFERRED_CALIBRATION_ADDRESS, STREAMING_AUTH_CREDENTIAL_SIZE as u32)
1152            .await?;
1153        let selected = if calibration_record_is_erased(&preferred) {
1154            self.read_memory_block(CALIBRATION_ADDRESS, STREAMING_AUTH_CREDENTIAL_SIZE as u32)
1155                .await?
1156        } else {
1157            preferred
1158        };
1159        let credential = AuthCredential::from_bytes(
1160            selected
1161                .as_slice()
1162                .try_into()
1163                .map_err(|_| KMError::Protocol("Calibration credential has the wrong length".to_string()))?,
1164        );
1165        let result = self.perform_streaming_auth(credential).await?;
1166
1167        if result.auth_level != 2 {
1168            return Err(KMError::Protocol(format!(
1169                "Calibration authentication returned level {} instead of 2",
1170                result.auth_level
1171            )));
1172        }
1173
1174        if let ConnectionMode::Full(state) = &mut self.mode {
1175            state.auth_level = result.auth_level;
1176            state.adcqueue_enabled = result.adcqueue_enabled();
1177        }
1178
1179        Ok(result)
1180    }
1181
1182    /// Read an encrypted memory block from the device
1183    ///
1184    /// Sends a MemoryRead request, receives the confirmation, then receives
1185    /// and decrypts the actual data. Returns exactly `size` decrypted bytes;
1186    /// AES block padding received from the device is removed.
1187    ///
1188    /// # Errors
1189    ///
1190    /// The encrypted payload arrives unframed, so a response still in flight
1191    /// from another request would be consumed as ciphertext. AdcQueue
1192    /// streaming must therefore be stopped first; calling this while graph
1193    /// mode is active returns an error instead of corrupting the read.
1194    pub async fn read_memory_block(&mut self, address: u32, size: u32) -> Result<Vec<u8>, KMError> {
1195        if self.graph_sample_rate.is_some() {
1196            return Err(KMError::Protocol(
1197                "Device memory cannot be read while AdcQueue streaming is active; \
1198                 call stop_graph_mode() first"
1199                    .to_string(),
1200            ));
1201        }
1202
1203        let id = self.send_tracked(Packet::MemoryRead { address, size }).await?;
1204        let confirmation = self
1205            .receive_matching_raw(|bytes| memory_confirmation_matches(bytes, id))
1206            .await?;
1207
1208        let confirmation = RawPacket::try_from(Bytes::from(confirmation))?;
1209        match confirmation.packet_type() {
1210            PacketType::MemoryRead => {
1211                validate_memory_read_confirmation(&confirmation, id, address, size)?;
1212                self.receive_memory_read_data_exact(size).await
1213            }
1214            PacketType::Rejected => Err(KMError::Protocol(format!("MemoryRead at 0x{address:08X} was rejected"))),
1215            PacketType::NotReadable => Err(KMError::Protocol(format!(
1216                "Memory address 0x{address:08X} is not readable"
1217            ))),
1218            other => Err(KMError::Protocol(format!(
1219                "Expected MemoryRead confirmation, got {other:?}"
1220            ))),
1221        }
1222    }
1223
1224    /// Read device information from memory
1225    ///
1226    /// Reads and parses:
1227    /// - DeviceInfo1 (0x420): model, hw_version, mfg_date
1228    /// - FirmwareInfo (0x4420): fw_version, fw_date
1229    /// - CalibrationData (0x3000C00): serial_id, uuid
1230    ///
1231    /// **Note:** Requires Connect to be sent first. Only works in Full mode.
1232    /// When using `DeviceConfig::vendor()`, this info is already available via `state()`.
1233    ///
1234    /// # Example
1235    /// ```no_run
1236    /// # use km003c_lib::{KM003C, DeviceConfig};
1237    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1238    /// // In Full mode, device info is already available
1239    /// let device = KM003C::new(DeviceConfig::vendor()).await?;
1240    /// println!("Model: {}", device.state().unwrap().model());
1241    /// # Ok(())
1242    /// # }
1243    /// ```
1244    pub async fn get_device_info(&mut self) -> Result<crate::auth::DeviceInfo, KMError> {
1245        Ok(self.read_device_info_blocks().await)
1246    }
1247
1248    /// Internal: Read DeviceInfo, FirmwareInfo, and CalibrationData blocks
1249    ///
1250    /// Used by both `run_init()` and `get_device_info()`. Non-critical reads
1251    /// are logged as warnings on failure but don't prevent initialization.
1252    async fn read_device_info_blocks(&mut self) -> crate::auth::DeviceInfo {
1253        use crate::auth::{
1254            CALIBRATION_ADDRESS, DEVICE_INFO_ADDRESS, DeviceInfo, FIRMWARE_INFO_ADDRESS, INFO_BLOCK_SIZE,
1255        };
1256        use tracing::warn;
1257
1258        let mut info = DeviceInfo::default();
1259
1260        match self
1261            .read_memory_block(DEVICE_INFO_ADDRESS, INFO_BLOCK_SIZE as u32)
1262            .await
1263        {
1264            Ok(data) => info.parse_device_info(&data),
1265            Err(e) => warn!("Failed to read DeviceInfo at 0x{:08X}: {}", DEVICE_INFO_ADDRESS, e),
1266        }
1267
1268        match self
1269            .read_memory_block(FIRMWARE_INFO_ADDRESS, INFO_BLOCK_SIZE as u32)
1270            .await
1271        {
1272            Ok(data) => info.parse_firmware_info(&data),
1273            Err(e) => warn!("Failed to read FirmwareInfo at 0x{:08X}: {}", FIRMWARE_INFO_ADDRESS, e),
1274        }
1275
1276        match self
1277            .read_memory_block(CALIBRATION_ADDRESS, INFO_BLOCK_SIZE as u32)
1278            .await
1279        {
1280            Ok(data) => info.parse_calibration(&data),
1281            Err(e) => warn!("Failed to read CalibrationData at 0x{:08X}: {}", CALIBRATION_ADDRESS, e),
1282        }
1283
1284        info
1285    }
1286
1287    /// Start AdcQueue graph/streaming mode with specified sample rate
1288    ///
1289    /// # Arguments
1290    /// * `rate` - Sample rate (use GraphSampleRate enum)
1291    ///
1292    /// **Requires Full mode** (vendor interface) and successful StreamingAuth.
1293    ///
1294    /// Rate values:
1295    /// - `GraphSampleRate::Sps2` = 2 samples per second
1296    /// - `GraphSampleRate::Sps10` = 10 SPS
1297    /// - `GraphSampleRate::Sps50` = 50 SPS
1298    /// - `GraphSampleRate::Sps1000` = 1000 SPS
1299    ///
1300    /// After calling this, poll with `request_data(AttributeSet::single(Attribute::AdcQueue))`
1301    /// to receive buffered samples.
1302    ///
1303    /// # Example
1304    /// ```no_run
1305    /// # use km003c_lib::{KM003C, DeviceConfig, GraphSampleRate};
1306    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1307    /// let mut device = KM003C::new(DeviceConfig::vendor()).await?;
1308    ///
1309    /// // Start 1000 SPS streaming
1310    /// device.start_graph_mode(GraphSampleRate::Sps1000).await?;
1311    ///
1312    /// // Poll for data...
1313    ///
1314    /// // Stop when done
1315    /// device.stop_graph_mode().await?;
1316    /// # Ok(())
1317    /// # }
1318    /// ```
1319    pub async fn start_graph_mode(&mut self, rate: GraphSampleRate) -> Result<(), KMError> {
1320        ensure_adcqueue_available(&self.mode)?;
1321
1322        let id = self.send_tracked(Packet::StartGraph { rate }).await?;
1323        self.expect_accept(id, "StartGraph").await?;
1324        self.graph_sample_rate = Some(rate);
1325        Ok(())
1326    }
1327
1328    /// Stop AdcQueue graph/streaming mode
1329    ///
1330    /// **Requires Full mode** (vendor interface). Will return error in Basic mode.
1331    ///
1332    /// Returns device to normal ADC polling mode.
1333    pub async fn stop_graph_mode(&mut self) -> Result<(), KMError> {
1334        if self.is_basic_mode() {
1335            return Err(KMError::Protocol(
1336                "AdcQueue streaming requires Full mode (vendor interface)".to_string(),
1337            ));
1338        }
1339
1340        let id = self.send_tracked(Packet::StopGraph).await?;
1341        self.expect_accept(id, "StopGraph").await?;
1342        self.graph_sample_rate = None;
1343        Ok(())
1344    }
1345}
1346
1347#[cfg(test)]
1348mod tests {
1349    use super::*;
1350
1351    #[test]
1352    fn calibration_auth_falls_back_only_for_an_erased_preferred_record() {
1353        assert!(calibration_record_is_erased(&[0xff; STREAMING_AUTH_CREDENTIAL_SIZE]));
1354        assert!(!calibration_record_is_erased(b"007965 CDFDD"));
1355        assert!(!calibration_record_is_erased(&[0xff, 0xff, 0xff]));
1356    }
1357
1358    #[test]
1359    fn response_matching_uses_the_typed_header_parser() {
1360        assert!(response_matches(&[0xc1, 7, 0, 0], 7, PacketType::PutData));
1361    }
1362
1363    #[test]
1364    fn graph_mode_requires_streaming_auth_permission() {
1365        assert!(ensure_adcqueue_available(&ConnectionMode::Basic).is_err());
1366
1367        let state = DeviceState {
1368            info: DeviceInfo::default(),
1369            hardware_id: HardwareId::from_bytes([0; 12]),
1370            auth_level: 0,
1371            adcqueue_enabled: false,
1372        };
1373        assert!(ensure_adcqueue_available(&ConnectionMode::Full(state.clone())).is_err());
1374
1375        let enabled = DeviceState {
1376            auth_level: 1,
1377            adcqueue_enabled: true,
1378            ..state
1379        };
1380        assert!(ensure_adcqueue_available(&ConnectionMode::Full(enabled)).is_ok());
1381    }
1382
1383    #[test]
1384    fn response_matching_requires_type_and_transaction_id() {
1385        assert!(!response_matches(&[0xc1, 8, 0, 0], 7, PacketType::PutData));
1386        assert!(!response_matches(&[0xc0, 7, 0, 0], 7, PacketType::PutData));
1387        assert!(!response_matches(&[0xc1], 7, PacketType::PutData));
1388    }
1389
1390    #[test]
1391    fn response_type_matching_supports_fixed_id_protocol_exceptions() {
1392        assert!(response_type_matches(&[0x4c, 0, 3, 2], PacketType::StreamingAuth));
1393    }
1394
1395    #[test]
1396    fn memory_response_size_rounds_to_complete_aes_blocks() {
1397        assert_eq!(memory_response_size(1), 16);
1398        assert_eq!(memory_response_size(12), 16);
1399        assert_eq!(memory_response_size(16), 16);
1400        assert_eq!(memory_response_size(17), 32);
1401        assert_eq!(memory_response_size(64), 64);
1402    }
1403
1404    #[test]
1405    fn decrypted_memory_response_is_trimmed_to_the_requested_size() {
1406        let ciphertext = crate::auth::build_memory_read_payload(0x4001_0450, 12);
1407        let decrypted = decrypt_memory_response(&ciphertext[..16], 12).unwrap();
1408
1409        assert_eq!(decrypted.len(), 12);
1410        assert_eq!(&decrypted[..4], &0x4001_0450_u32.to_le_bytes());
1411        assert_eq!(&decrypted[4..8], &12_u32.to_le_bytes());
1412        assert_eq!(&decrypted[8..12], &u32::MAX.to_le_bytes());
1413    }
1414
1415    #[test]
1416    fn decrypted_memory_response_rejects_an_incorrect_ciphertext_size() {
1417        assert!(decrypt_memory_response(&[0; 32], 12).is_err());
1418    }
1419
1420    #[test]
1421    fn memory_chunks_accumulate_across_usb_transfers() {
1422        // Source: reading_logs0.11; a requested 8336-byte log arrived as
1423        // three 2544-byte USB transfers followed by one 704-byte transfer.
1424        let mut response = Vec::new();
1425        assert!(!append_memory_chunk(&mut response, &vec![0x11; 2544], 8336).unwrap());
1426        assert!(!append_memory_chunk(&mut response, &vec![0x22; 2544], 8336).unwrap());
1427        assert!(!append_memory_chunk(&mut response, &vec![0x33; 2544], 8336).unwrap());
1428        assert!(append_memory_chunk(&mut response, &vec![0x44; 704], 8336).unwrap());
1429        assert_eq!(response.len(), 8336);
1430    }
1431
1432    #[test]
1433    fn memory_chunks_reject_empty_and_oversized_transfers() {
1434        let mut response = vec![0; 15];
1435        assert!(append_memory_chunk(&mut response, &[], 16).is_err());
1436        assert!(append_memory_chunk(&mut response, &[1, 2], 16).is_err());
1437    }
1438
1439    #[test]
1440    fn validates_recorded_memory_read_confirmation() {
1441        // Source: usb_master_dataset.parquet, orig_adc_1000hz.6, frame 264.
1442        let bytes = hex::decode("c40201012004000040000000ffffffff1b8c1b24").unwrap();
1443        let packet = RawPacket::try_from(Bytes::from(bytes)).unwrap();
1444
1445        validate_memory_read_confirmation(&packet, 2, 0x420, 64).unwrap();
1446    }
1447
1448    #[test]
1449    fn rejects_corrupted_memory_read_confirmation() {
1450        let mut bytes = hex::decode("c40201012004000040000000ffffffff1b8c1b24").unwrap();
1451        bytes[8] ^= 1;
1452        let packet = RawPacket::try_from(Bytes::from(bytes)).unwrap();
1453
1454        assert!(validate_memory_read_confirmation(&packet, 2, 0x420, 64).is_err());
1455    }
1456}