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