Skip to main content

km003c_lib/
auth.rs

1//! Authentication module for KM003C device
2//!
3//! This module provides data structures and encryption helpers for the
4//! StreamingAuth (0x4C) and MemoryRead (0x44) commands required for AdcQueue streaming.
5//!
6//! # Authentication Flow
7//!
8//! 1. Read HardwareID from device memory at 0x40010450 using MemoryRead (0x44)
9//! 2. Send StreamingAuth (0x4C) with HardwareID embedded in encrypted payload
10//! 3. Device validates and grants auth level 1 (AdcQueue access)
11//!
12//! # AES Keys
13//!
14//! - StreamingAuth encrypt (host→device): `Fa0b4tA25f4R038a`
15//! - StreamingAuth decrypt (device→host): `FX0b4tA25f4R038a`
16//! - MemoryRead: `Lh2yfB7n6X7d9a5Z`
17//!
18//! # Usage
19//!
20//! Use the `Packet::MemoryRead` and `Packet::StreamingAuth` variants from the
21//! message module to send authentication commands. This module provides the
22//! underlying encryption and data structures.
23
24use aes::Aes128;
25use aes::cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
26
27use crate::packet::{PacketType, StreamingAuthHeader};
28
29const STREAMING_AUTH_HEADER_SIZE: usize = 4;
30const STREAMING_AUTH_PAYLOAD_SIZE: usize = 32;
31const STREAMING_AUTH_RESULT_FLAG: u16 = 1;
32const STREAMING_AUTH_GRANTED_FLAG: u16 = 1 << 1;
33const AES_BLOCK_SIZE: usize = 16;
34pub(crate) const STREAMING_AUTH_RESPONSE_ID: u8 = 0;
35
36/// AES-128 key for StreamingAuth encryption (host → device)
37pub const STREAMING_AUTH_KEY_ENC: &[u8; 16] = b"Fa0b4tA25f4R038a";
38
39/// AES-128 key for StreamingAuth decryption (device → host)
40pub const STREAMING_AUTH_KEY_DEC: &[u8; 16] = b"FX0b4tA25f4R038a";
41
42/// AES-128 key for MemoryRead command
43pub const MEMORY_READ_KEY: &[u8; 16] = b"Lh2yfB7n6X7d9a5Z";
44
45/// Memory address where HardwareID is stored
46pub const HARDWARE_ID_ADDRESS: u32 = 0x40010450;
47
48/// Size of HardwareID in bytes
49pub const HARDWARE_ID_SIZE: usize = 12;
50
51/// Memory address for DeviceInfo1 (model, HW version, mfg date)
52pub const DEVICE_INFO_ADDRESS: u32 = 0x00000420;
53
54/// Memory address for FirmwareInfo (FW version, build date)
55pub const FIRMWARE_INFO_ADDRESS: u32 = 0x00004420;
56
57/// Memory address for CalibrationData (serial, UUID, timestamp)
58pub const CALIBRATION_ADDRESS: u32 = 0x03000C00;
59
60/// Size of info blocks (DeviceInfo, FirmwareInfo, Calibration)
61pub const INFO_BLOCK_SIZE: usize = 64;
62
63/// 12-byte Hardware ID read from device memory at 0x40010450
64///
65/// Structure:
66/// - Bytes 0-5: Serial prefix (e.g., "071KBP" ASCII)
67/// - Bytes 6-7: Separator (typically 0x0D 0xFF)
68/// - Bytes 8-9: Device ID (little-endian u16)
69/// - Bytes 10-11: Padding (typically 0xFF 0xFF)
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct HardwareId {
72    pub bytes: [u8; HARDWARE_ID_SIZE],
73}
74
75impl HardwareId {
76    /// Create HardwareId from raw bytes
77    pub fn from_bytes(bytes: [u8; HARDWARE_ID_SIZE]) -> Self {
78        Self { bytes }
79    }
80
81    /// Get the serial prefix (first 6 bytes as ASCII string)
82    pub fn serial_prefix(&self) -> Option<String> {
83        let prefix = &self.bytes[0..6];
84        if prefix.iter().all(|&b| b.is_ascii_alphanumeric()) {
85            Some(String::from_utf8_lossy(prefix).to_string())
86        } else {
87            None
88        }
89    }
90
91    /// Get the device ID (bytes 8-9 as little-endian u16)
92    pub fn device_id(&self) -> u16 {
93        u16::from_le_bytes([self.bytes[8], self.bytes[9]])
94    }
95
96    /// Get raw bytes
97    pub fn as_bytes(&self) -> &[u8; HARDWARE_ID_SIZE] {
98        &self.bytes
99    }
100}
101
102impl std::fmt::Display for HardwareId {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(f, "{}", hex::encode(self.bytes))
105    }
106}
107
108/// Device information parsed from memory blocks
109#[derive(Debug, Clone, Default)]
110pub struct DeviceInfo {
111    /// Model name (e.g., "KM003C")
112    pub model: String,
113    /// Hardware version (e.g., "2.1")
114    pub hw_version: String,
115    /// Manufacturing date (e.g., "2022.11.7")
116    pub mfg_date: String,
117    /// Firmware version (e.g., "1.9.9")
118    pub fw_version: String,
119    /// Firmware build date (e.g., "2025.9.22")
120    pub fw_date: String,
121    /// Serial ID from calibration (e.g., "007965")
122    pub serial_id: String,
123    /// UUID/hash from calibration
124    pub uuid: String,
125}
126
127impl DeviceInfo {
128    /// Parse DeviceInfo1 block (64 bytes from 0x420)
129    ///
130    /// Layout:
131    /// - 0x00-0x0F: reserved
132    /// - 0x10-0x1B: model (12 bytes, null-terminated)
133    /// - 0x1C-0x27: hw_version (12 bytes, null-terminated)
134    /// - 0x28-0x3F: mfg_date (24 bytes, null-terminated)
135    pub fn parse_device_info(&mut self, data: &[u8]) {
136        if data.len() >= 64 {
137            self.model = extract_string(data, 0x10, 0x1C);
138            self.hw_version = extract_string(data, 0x1C, 0x28);
139            self.mfg_date = extract_string(data, 0x28, 0x40);
140        }
141    }
142
143    /// Parse FirmwareInfo block (64 bytes from 0x4420)
144    ///
145    /// Layout:
146    /// - 0x00-0x03: magic (0x00004000 or 0xFFFFFFFF if invalid)
147    /// - 0x10-0x1B: model (12 bytes)
148    /// - 0x1C-0x27: fw_version (12 bytes, null-terminated)
149    /// - 0x28-0x33: fw_date (12 bytes, null-terminated)
150    pub fn parse_firmware_info(&mut self, data: &[u8]) {
151        if data.len() >= 64 {
152            // Check magic - if 0xFFFFFFFF, firmware info is invalid
153            let magic = u32::from_le_bytes(data[0..4].try_into().unwrap_or([0xFF; 4]));
154            if magic != 0xFFFFFFFF {
155                self.fw_version = extract_string(data, 0x1C, 0x28);
156                self.fw_date = extract_string(data, 0x28, 0x34);
157            }
158        }
159    }
160
161    /// Parse CalibrationData block (64 bytes from 0x3000C00)
162    ///
163    /// Layout:
164    /// - 0x00-0x06: serial_id (7 bytes, space-padded)
165    /// - 0x07-0x26: UUID (32 bytes hex string)
166    pub fn parse_calibration(&mut self, data: &[u8]) {
167        if data.len() >= 64 {
168            self.serial_id = extract_string(data, 0x00, 0x07).trim().to_string();
169            self.uuid = extract_string(data, 0x07, 0x27);
170        }
171    }
172}
173
174/// Extract null-terminated string from byte slice
175fn extract_string(data: &[u8], start: usize, end: usize) -> String {
176    if start >= data.len() || end > data.len() || start >= end {
177        return String::new();
178    }
179    let slice = &data[start..end];
180    // Find null terminator or end of slice
181    let len = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
182    String::from_utf8_lossy(&slice[..len]).to_string()
183}
184
185/// Result of StreamingAuth command
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct StreamingAuthResult {
188    /// Whether authentication was successful (AdcQueue access granted)
189    pub success: bool,
190    /// Raw attribute value from response
191    pub attribute: u16,
192    /// Auth level: 0 = failed, 1 = device auth, 2 = calibration auth
193    pub auth_level: u8,
194    /// Decrypted response payload (32 bytes)
195    pub decrypted_payload: [u8; 32],
196}
197
198impl StreamingAuthResult {
199    /// Check if AdcQueue streaming is enabled
200    pub fn adcqueue_enabled(&self) -> bool {
201        (self.attribute & STREAMING_AUTH_GRANTED_FLAG) != 0
202    }
203}
204
205/// Result of device initialization
206///
207/// Convenience struct for protocol research scripts that manage
208/// the init sequence manually rather than using `KM003C::new()`.
209#[derive(Debug, Clone)]
210pub struct InitResult {
211    /// Device information (model, versions, serial, etc.)
212    pub device_info: DeviceInfo,
213    /// Hardware ID used for authentication
214    pub hardware_id: HardwareId,
215    /// Authentication result
216    pub auth: StreamingAuthResult,
217}
218
219impl InitResult {
220    /// Check if device is ready for AdcQueue streaming
221    pub fn is_authenticated(&self) -> bool {
222        self.auth.success && self.auth.adcqueue_enabled()
223    }
224}
225
226/// Build MemoryRead encrypted payload (32 bytes)
227///
228/// # Arguments
229/// * `address` - Memory address to read from
230/// * `size` - Number of bytes to read
231///
232/// # Returns
233/// 32-byte AES-encrypted payload
234pub fn build_memory_read_payload(address: u32, size: u32) -> [u8; 32] {
235    // Build 32-byte plaintext
236    let mut plaintext = [0xFFu8; 32];
237
238    // Bytes 0-3: Address (little-endian)
239    plaintext[0..4].copy_from_slice(&address.to_le_bytes());
240
241    // Bytes 4-7: Size (little-endian)
242    plaintext[4..8].copy_from_slice(&size.to_le_bytes());
243
244    // Bytes 8-11: Magic (0xFFFFFFFF)
245    plaintext[8..12].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
246
247    // Bytes 12-15: CRC32 of bytes 0-11
248    let crc = crc32fast::hash(&plaintext[0..12]);
249    plaintext[12..16].copy_from_slice(&crc.to_le_bytes());
250
251    // Bytes 16-31: Already 0xFF from initialization
252
253    // Encrypt with AES-128-ECB
254    aes_ecb_encrypt(&plaintext, MEMORY_READ_KEY)
255}
256
257/// Build a MemoryRead (0x44) request packet
258///
259/// # Arguments
260/// * `address` - Memory address to read from
261/// * `size` - Number of bytes to read
262/// * `tid` - Transaction ID
263///
264/// # Returns
265/// 36-byte packet: 4-byte header + 32-byte AES-encrypted payload
266pub fn build_memory_read_packet(address: u32, size: u32, tid: u8) -> Vec<u8> {
267    let ciphertext = build_memory_read_payload(address, size);
268
269    // Build packet: header + encrypted payload
270    let mut packet = Vec::with_capacity(36);
271    packet.push(0x44); // Packet type: MemoryRead
272    packet.push(tid); // Transaction ID
273    packet.push(0x01); // Attribute low byte
274    packet.push(0x01); // Attribute high byte (0x0101)
275    packet.extend_from_slice(&ciphertext);
276
277    packet
278}
279
280/// Build StreamingAuth encrypted payload (32 bytes)
281///
282/// # Arguments
283/// * `hardware_id` - 12-byte HardwareID from device
284///
285/// # Returns
286/// 32-byte AES-encrypted payload
287pub fn build_streaming_auth_payload(hardware_id: &HardwareId) -> [u8; 32] {
288    // Build 32-byte plaintext
289    let mut plaintext = [0u8; 32];
290
291    // Bytes 0-7: Timestamp (milliseconds since epoch)
292    let timestamp = std::time::SystemTime::now()
293        .duration_since(std::time::UNIX_EPOCH)
294        .map(|d| d.as_millis() as u64)
295        .unwrap_or(0);
296    plaintext[0..8].copy_from_slice(&timestamp.to_le_bytes());
297
298    // Bytes 8-19: HardwareID (12 bytes) - THIS IS THE CRITICAL PART
299    plaintext[8..20].copy_from_slice(hardware_id.as_bytes());
300
301    // Bytes 20-31: Random padding
302    let random_bytes: [u8; 12] = rand::random();
303    plaintext[20..32].copy_from_slice(&random_bytes);
304
305    // Encrypt with AES-128-ECB
306    aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_ENC)
307}
308
309/// Encrypt a StreamingAuth payload (for serializing responses)
310///
311/// # Arguments
312/// * `plaintext` - 32-byte plaintext payload
313///
314/// # Returns
315/// 32-byte AES-encrypted payload
316pub fn encrypt_streaming_auth_response_payload(plaintext: &[u8; 32]) -> [u8; 32] {
317    aes_ecb_encrypt(plaintext, STREAMING_AUTH_KEY_DEC)
318}
319
320/// Build a StreamingAuth (0x4C) request packet
321///
322/// # Arguments
323/// * `hardware_id` - 12-byte HardwareID from device
324/// * `tid` - Transaction ID
325///
326/// # Returns
327/// 36-byte packet: 4-byte header + 32-byte AES-encrypted payload
328pub fn build_streaming_auth_packet(hardware_id: &HardwareId, tid: u8) -> Vec<u8> {
329    let ciphertext = build_streaming_auth_payload(hardware_id);
330
331    // Build packet: header + encrypted payload
332    let mut packet = Vec::with_capacity(36);
333    packet.push(0x4C); // Packet type: StreamingAuth
334    packet.push(tid); // Transaction ID
335    packet.push(0x00); // Attribute low byte
336    packet.push(0x02); // Attribute high byte (0x0002)
337    packet.extend_from_slice(&ciphertext);
338
339    packet
340}
341
342/// Parse StreamingAuth (0x4C) response from full packet
343///
344/// # Arguments
345/// * `response` - Raw response bytes (at least 36 bytes: 4-byte header + 32-byte payload)
346///
347/// # Returns
348/// Parsed authentication result
349pub fn parse_streaming_auth_response(response: &[u8]) -> Option<StreamingAuthResult> {
350    if response.len() < STREAMING_AUTH_HEADER_SIZE + STREAMING_AUTH_PAYLOAD_SIZE {
351        return None;
352    }
353
354    let (header_bytes, payload) = response.split_at(STREAMING_AUTH_HEADER_SIZE);
355    let header = StreamingAuthHeader::from_bytes(header_bytes.try_into().ok()?);
356    parse_streaming_auth_response_parts(header, payload)
357}
358
359pub(crate) fn parse_streaming_auth_response_parts(
360    header: StreamingAuthHeader,
361    payload: &[u8],
362) -> Option<StreamingAuthResult> {
363    if header.packet_type() != u8::from(PacketType::StreamingAuth)
364        || header.reserved_flag()
365        || header.id() != STREAMING_AUTH_RESPONSE_ID
366        || header.attribute() & STREAMING_AUTH_RESULT_FLAG == 0
367    {
368        return None;
369    }
370
371    parse_streaming_auth_response_payload(payload, header.attribute())
372}
373
374/// Parse and decrypt a StreamingAuth response payload with its raw response attribute.
375pub fn parse_streaming_auth_response_payload(payload: &[u8], attribute: u16) -> Option<StreamingAuthResult> {
376    let encrypted: [u8; STREAMING_AUTH_PAYLOAD_SIZE] = payload.get(..STREAMING_AUTH_PAYLOAD_SIZE)?.try_into().ok()?;
377    let decrypted = aes_ecb_decrypt(&encrypted, STREAMING_AUTH_KEY_DEC);
378    let success = (attribute & STREAMING_AUTH_GRANTED_FLAG) != 0;
379
380    Some(StreamingAuthResult {
381        success,
382        attribute,
383        auth_level: u8::from(success),
384        decrypted_payload: decrypted,
385    })
386}
387
388/// AES-128-ECB encrypt 32 bytes
389fn aes_ecb_encrypt(plaintext: &[u8; 32], key: &[u8; 16]) -> [u8; 32] {
390    let cipher = Aes128::new(key.into());
391    let mut output = *plaintext;
392
393    for block in output.chunks_exact_mut(AES_BLOCK_SIZE) {
394        encrypt_aes_block(&cipher, block);
395    }
396
397    output
398}
399
400/// Decrypt an unframed MemoryRead response payload
401///
402/// The response payload is AES-encrypted with MEMORY_READ_KEY.
403/// Useful for protocol research and manual memory reads.
404pub fn decrypt_memory_read_response(ciphertext: &[u8]) -> Option<Vec<u8>> {
405    if ciphertext.len() < AES_BLOCK_SIZE {
406        return None;
407    }
408
409    // Decrypt in 16-byte blocks
410    let cipher = Aes128::new(MEMORY_READ_KEY.into());
411    let mut output = ciphertext.to_vec();
412
413    for block in output.chunks_mut(AES_BLOCK_SIZE) {
414        if block.len() == AES_BLOCK_SIZE {
415            decrypt_aes_block(&cipher, block);
416        }
417    }
418
419    Some(output)
420}
421
422/// AES-128-ECB decrypt 32 bytes
423fn aes_ecb_decrypt(ciphertext: &[u8; 32], key: &[u8; 16]) -> [u8; 32] {
424    let cipher = Aes128::new(key.into());
425    let mut output = *ciphertext;
426
427    for block in output.chunks_exact_mut(AES_BLOCK_SIZE) {
428        decrypt_aes_block(&cipher, block);
429    }
430
431    output
432}
433
434fn encrypt_aes_block(cipher: &Aes128, bytes: &mut [u8]) {
435    let block: &mut [u8; AES_BLOCK_SIZE] = bytes.try_into().expect("AES chunk has the block size");
436    cipher.encrypt_block(block.into());
437}
438
439fn decrypt_aes_block(cipher: &Aes128, bytes: &mut [u8]) {
440    let block: &mut [u8; AES_BLOCK_SIZE] = bytes.try_into().expect("AES chunk has the block size");
441    cipher.decrypt_block(block.into());
442}
443
444/// AES-128-ECB decrypt a single 16-byte block
445///
446/// Useful for protocol research and manual decryption of individual blocks.
447pub fn aes_ecb_decrypt_block(ciphertext: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
448    let cipher = Aes128::new(key.into());
449    let mut output = *ciphertext;
450    decrypt_aes_block(&cipher, &mut output);
451    output
452}
453
454/// AES-128-ECB decrypt multiple 16-byte blocks
455///
456/// # Arguments
457/// * `ciphertext` - Encrypted data (must be multiple of 16 bytes)
458/// * `key` - 16-byte AES key
459///
460/// # Returns
461/// Decrypted data, or error if ciphertext length is not a multiple of 16
462pub fn aes_ecb_decrypt_blocks(ciphertext: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, crate::error::KMError> {
463    if !ciphertext.len().is_multiple_of(AES_BLOCK_SIZE) {
464        return Err(crate::error::KMError::InvalidPacket(format!(
465            "AES ciphertext must be a multiple of {AES_BLOCK_SIZE} bytes, got {}",
466            ciphertext.len()
467        )));
468    }
469
470    let cipher = Aes128::new(key.into());
471    let mut output = ciphertext.to_vec();
472
473    for block in output.chunks_exact_mut(AES_BLOCK_SIZE) {
474        decrypt_aes_block(&cipher, block);
475    }
476
477    Ok(output)
478}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483
484    // Known working hardcoded packets from CLI (captured from working session)
485    // MemoryRead packet (tid=2): reads HardwareID from 0x40010450
486    const HARDCODED_MEMORY_READ: &str = "4402010133f8860c0054288cdc7e52729826872dd18b539a39c407d5c063d91102e36a9e";
487    // StreamingAuth packet (tid=6): authenticates with embedded HardwareID
488    const HARDCODED_STREAMING_AUTH: &str = "4c0600025538815b69a452c83e54ef1d70f3bc9ae6aac1b12a6ac07c20fde58c7bf517ca";
489
490    #[test]
491    fn test_hardware_id_parsing() {
492        // Example HardwareID: "071KBP" + separator + device ID 2577
493        let bytes: [u8; 12] = [
494            0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, // "071KBP"
495            0x0d, 0xff, // separator
496            0x11, 0x0a, // device ID 2577 (LE)
497            0xff, 0xff, // padding
498        ];
499
500        let hw_id = HardwareId::from_bytes(bytes);
501        assert_eq!(hw_id.serial_prefix(), Some("071KBP".to_string()));
502        assert_eq!(hw_id.device_id(), 2577);
503    }
504
505    #[test]
506    fn test_aes_roundtrip() {
507        let plaintext = [0x42u8; 32];
508        let encrypted = aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_ENC);
509        // Note: Can't decrypt with same key in real protocol, but can test structure
510        assert_ne!(encrypted, plaintext);
511        assert_eq!(encrypted.len(), 32);
512    }
513
514    #[test]
515    fn test_memory_read_packet_structure() {
516        let packet = build_memory_read_packet(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32, 0x02);
517        assert_eq!(packet.len(), 36);
518        assert_eq!(packet[0], 0x44); // MemoryRead
519        assert_eq!(packet[1], 0x02); // TID
520        assert_eq!(packet[2], 0x01); // Attribute low
521        assert_eq!(packet[3], 0x01); // Attribute high
522    }
523
524    #[test]
525    fn test_streaming_auth_packet_structure() {
526        let hw_id = HardwareId::from_bytes([0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff]);
527        let packet = build_streaming_auth_packet(&hw_id, 0x06);
528        assert_eq!(packet.len(), 36);
529        assert_eq!(packet[0], 0x4C); // StreamingAuth
530        assert_eq!(packet[1], 0x06); // TID
531        assert_eq!(packet[2], 0x00); // Attribute low
532        assert_eq!(packet[3], 0x02); // Attribute high (0x0002)
533    }
534
535    #[test]
536    fn test_streaming_auth_response_uses_header_result() {
537        use crate::{Packet, RawPacket};
538        use bytes::Bytes;
539
540        let plaintext = [0x5a; STREAMING_AUTH_PAYLOAD_SIZE];
541        let encrypted = aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_DEC);
542
543        let response = |attribute| {
544            let header = StreamingAuthHeader::new()
545                .with_packet_type(PacketType::StreamingAuth.into())
546                .with_reserved_flag(false)
547                .with_id(0)
548                .with_attribute(attribute);
549            let mut bytes = Vec::from(header.into_bytes());
550            bytes.extend_from_slice(&encrypted);
551            bytes
552        };
553
554        let success = parse_streaming_auth_response(&response(0x0203)).unwrap();
555        assert!(success.success);
556        assert_eq!(success.auth_level, 1);
557        assert_eq!(success.attribute, 0x0203);
558        assert_eq!(success.decrypted_payload, plaintext);
559
560        let serialized = Bytes::from(
561            Packet::StreamingAuthResponse(success.clone())
562                .to_raw_packet(99)
563                .unwrap(),
564        );
565        let reparsed = RawPacket::try_from(serialized).unwrap();
566        assert_eq!(
567            Packet::try_from(reparsed).unwrap(),
568            Packet::StreamingAuthResponse(success)
569        );
570
571        let raw_failure = RawPacket::try_from(Bytes::from(response(0x0201))).unwrap();
572        let Packet::StreamingAuthResponse(failure) = Packet::try_from(raw_failure).unwrap() else {
573            panic!("expected StreamingAuthResponse");
574        };
575        assert!(!failure.success);
576        assert_eq!(failure.auth_level, 0);
577        assert_eq!(failure.attribute, 0x0201);
578        assert_eq!(failure.decrypted_payload, plaintext);
579    }
580
581    #[test]
582    fn test_decrypt_hardcoded_memory_read() {
583        // Decrypt the hardcoded MemoryRead packet to see what it reads
584        // NOTE: The hardcoded packet reads from 0x420 (size 64), NOT 0x40010450 (HardwareID)
585        let packet = hex::decode(HARDCODED_MEMORY_READ).unwrap();
586
587        // Check header
588        assert_eq!(packet[0], 0x44, "MemoryRead type");
589        assert_eq!(packet[1], 0x02, "TID=2");
590        assert_eq!(packet[2], 0x01, "Attribute low");
591        assert_eq!(packet[3], 0x01, "Attribute high");
592
593        // Decrypt payload (bytes 4-35)
594        let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
595        let plaintext = aes_ecb_decrypt(&ciphertext, MEMORY_READ_KEY);
596
597        // Extract address (bytes 0-3, little-endian)
598        let address = u32::from_le_bytes([plaintext[0], plaintext[1], plaintext[2], plaintext[3]]);
599        // Extract size (bytes 4-7, little-endian)
600        let size = u32::from_le_bytes([plaintext[4], plaintext[5], plaintext[6], plaintext[7]]);
601
602        println!("Decrypted MemoryRead: address=0x{:08X}, size={}", address, size);
603        println!("Plaintext: {:02x?}", plaintext);
604
605        // The hardcoded packet actually reads from 0x420 with size 64 (different memory region)
606        assert_eq!(address, 0x420, "Hardcoded reads from 0x420");
607        assert_eq!(size, 64, "Hardcoded reads 64 bytes");
608    }
609
610    #[test]
611    fn test_memory_read_for_hardware_id() {
612        // Verify our library generates correct MemoryRead for HardwareID
613        let generated = build_memory_read_packet(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32, 0x02);
614
615        // Check header
616        assert_eq!(generated[0], 0x44, "MemoryRead type");
617        assert_eq!(generated[1], 0x02, "TID=2");
618        assert_eq!(generated[2], 0x01, "Attribute low");
619        assert_eq!(generated[3], 0x01, "Attribute high");
620
621        // Decrypt and verify address/size
622        let ciphertext: [u8; 32] = generated[4..36].try_into().unwrap();
623        let plaintext = aes_ecb_decrypt(&ciphertext, MEMORY_READ_KEY);
624
625        let address = u32::from_le_bytes([plaintext[0], plaintext[1], plaintext[2], plaintext[3]]);
626        let size = u32::from_le_bytes([plaintext[4], plaintext[5], plaintext[6], plaintext[7]]);
627
628        assert_eq!(address, HARDWARE_ID_ADDRESS, "Should read from 0x40010450");
629        assert_eq!(size, HARDWARE_ID_SIZE as u32, "Should read 12 bytes");
630
631        // Verify CRC32 is correct (bytes 12-15)
632        let expected_crc = crc32fast::hash(&plaintext[0..12]);
633        let actual_crc = u32::from_le_bytes([plaintext[12], plaintext[13], plaintext[14], plaintext[15]]);
634        assert_eq!(actual_crc, expected_crc, "CRC32 should be valid");
635    }
636
637    #[test]
638    fn test_decrypt_hardcoded_streaming_auth() {
639        // Decrypt the hardcoded StreamingAuth packet to extract HardwareID
640        let packet = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
641
642        // Check header
643        assert_eq!(packet[0], 0x4C, "StreamingAuth type");
644        assert_eq!(packet[1], 0x06, "TID=6");
645        assert_eq!(packet[2], 0x00, "Attribute low");
646        assert_eq!(packet[3], 0x02, "Attribute high");
647
648        // Decrypt payload (bytes 4-35) using ENCRYPT key (since host->device uses encrypt key)
649        let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
650        let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
651
652        // Structure: timestamp(8) + HardwareID(12) + padding(12)
653        let timestamp = u64::from_le_bytes(plaintext[0..8].try_into().unwrap());
654        let hardware_id_bytes: [u8; 12] = plaintext[8..20].try_into().unwrap();
655        let hardware_id = HardwareId::from_bytes(hardware_id_bytes);
656
657        println!("Decrypted StreamingAuth:");
658        println!("  Timestamp: {} ms", timestamp);
659        println!("  HardwareID: {}", hardware_id);
660        println!("  Serial prefix: {:?}", hardware_id.serial_prefix());
661        println!("  Device ID: {}", hardware_id.device_id());
662        println!("  Plaintext: {:02x?}", plaintext);
663
664        // Verify the extracted HardwareID
665        assert_eq!(hardware_id.serial_prefix(), Some("071KBP".to_string()));
666        assert_eq!(hardware_id.device_id(), 2577);
667        assert!(timestamp > 0, "Timestamp should be non-zero");
668    }
669
670    #[test]
671    fn test_streaming_auth_with_known_hardware_id() {
672        // Use the HardwareID extracted from the working hardcoded packet
673        // HardwareID: 071KBP, device ID 2577
674        let hw_id = HardwareId::from_bytes([
675            0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, // "071KBP"
676            0x0d, 0xff, // separator
677            0x11, 0x0a, // device ID 2577 (LE)
678            0xff, 0xff, // padding
679        ]);
680
681        // Generate StreamingAuth packet
682        let packet = build_streaming_auth_packet(&hw_id, 0x06);
683
684        // Verify header matches hardcoded
685        let hardcoded = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
686        assert_eq!(&packet[0..4], &hardcoded[0..4], "Headers must match");
687
688        // Decrypt our generated packet to verify HardwareID is embedded correctly
689        let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
690        let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
691
692        // Extract and verify HardwareID
693        let extracted_hw: [u8; 12] = plaintext[8..20].try_into().unwrap();
694        assert_eq!(extracted_hw, hw_id.bytes, "HardwareID must be embedded correctly");
695
696        // Timestamp should be recent (within last hour - 3600000 ms)
697        let timestamp = u64::from_le_bytes(plaintext[0..8].try_into().unwrap());
698        let now = std::time::SystemTime::now()
699            .duration_since(std::time::UNIX_EPOCH)
700            .unwrap()
701            .as_millis() as u64;
702        assert!(timestamp > now - 3600000, "Timestamp should be recent");
703        assert!(timestamp <= now + 1000, "Timestamp should not be in future");
704    }
705}