1use 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
41pub const STREAMING_AUTH_KEY_ENC: &[u8; 16] = b"Fa0b4tA25f4R038a";
43
44pub const STREAMING_AUTH_KEY_DEC: &[u8; 16] = b"FX0b4tA25f4R038a";
46
47pub const MEMORY_READ_KEY: &[u8; 16] = b"Lh2yfB7n6X7d9a5Z";
49
50pub const HARDWARE_ID_ADDRESS: u32 = 0x40010450;
52
53pub const HARDWARE_ID_SIZE: usize = 12;
55
56pub const DEVICE_INFO_ADDRESS: u32 = 0x00000420;
58
59pub const FIRMWARE_INFO_ADDRESS: u32 = 0x00004420;
61
62pub const CALIBRATION_ADDRESS: u32 = 0x03000C00;
64
65pub const PREFERRED_CALIBRATION_ADDRESS: u32 = 0x03000D80;
67
68pub const INFO_BLOCK_SIZE: usize = 64;
70
71pub const STREAMING_AUTH_CREDENTIAL_SIZE: usize = 12;
73
74#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct HardwareId {
83 bytes: [u8; HARDWARE_ID_SIZE],
84}
85
86impl HardwareId {
87 pub fn from_bytes(bytes: [u8; HARDWARE_ID_SIZE]) -> Self {
89 Self { bytes }
90 }
91
92 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 pub fn device_id(&self) -> u16 {
104 u16::from_le_bytes([self.bytes[8], self.bytes[9]])
105 }
106
107 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#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct AuthCredential {
125 bytes: [u8; STREAMING_AUTH_CREDENTIAL_SIZE],
126}
127
128impl AuthCredential {
129 pub fn from_bytes(bytes: [u8; STREAMING_AUTH_CREDENTIAL_SIZE]) -> Self {
131 Self { bytes }
132 }
133
134 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#[derive(Debug, Clone, Default)]
154pub struct DeviceInfo {
155 pub model: String,
157 pub hw_version: String,
159 pub mfg_date: String,
161 pub fw_version: String,
163 pub fw_date: String,
165 pub serial_id: String,
167 pub uuid: String,
169}
170
171impl DeviceInfo {
172 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 pub fn parse_firmware_info(&mut self, data: &[u8]) {
195 if data.len() >= 64 {
196 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 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
218fn 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 let len = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
226 String::from_utf8_lossy(&slice[..len]).to_string()
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct StreamingAuthResult {
232 pub success: bool,
234 pub attribute: u16,
236 pub auth_level: u8,
238 pub decrypted_payload: [u8; 32],
240}
241
242impl StreamingAuthResult {
243 pub fn adcqueue_enabled(&self) -> bool {
245 self.auth_level != 0
246 }
247}
248
249#[derive(Debug, Clone)]
254pub struct InitResult {
255 pub device_info: DeviceInfo,
257 pub hardware_id: HardwareId,
259 pub auth: StreamingAuthResult,
261}
262
263impl InitResult {
264 pub fn is_authenticated(&self) -> bool {
266 self.auth.success && self.auth.adcqueue_enabled()
267 }
268}
269
270pub fn build_memory_read_payload(address: u32, size: u32) -> [u8; 32] {
279 let mut plaintext = [0xFFu8; 32];
281
282 plaintext[0..4].copy_from_slice(&address.to_le_bytes());
284
285 plaintext[4..8].copy_from_slice(&size.to_le_bytes());
287
288 plaintext[8..12].copy_from_slice(&0xFFFFFFFFu32.to_le_bytes());
290
291 let crc = crc32fast::hash(&plaintext[0..12]);
293 plaintext[12..16].copy_from_slice(&crc.to_le_bytes());
294
295 aes_ecb_encrypt(&plaintext, MEMORY_READ_KEY)
299}
300
301pub 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
319pub const MEMORY_READ_CONFIRMATION_SIZE: usize = 16;
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub struct MemoryReadConfirmation {
325 pub address: u32,
326 pub size: u32,
327 pub magic: u32,
328 pub checksum: u32,
329 pub expected_checksum: u32,
331}
332
333impl MemoryReadConfirmation {
334 pub fn from_bytes(body: &[u8]) -> Option<Self> {
336 let body: &[u8; MEMORY_READ_CONFIRMATION_SIZE] = body.first_chunk()?;
337 let word = |range: std::ops::Range<usize>| {
338 u32::from_le_bytes(body[range].try_into().expect("fixed-size confirmation field"))
339 };
340
341 Some(Self {
342 address: word(0..4),
343 size: word(4..8),
344 magic: word(8..12),
345 checksum: word(12..16),
346 expected_checksum: crc32fast::hash(&body[..12]),
347 })
348 }
349
350 pub fn is_intact(&self) -> bool {
352 self.magic == u32::MAX && self.checksum == self.expected_checksum
353 }
354}
355
356pub fn parse_memory_read_confirmation(body: &[u8]) -> Option<(u32, u32)> {
361 let confirmation = MemoryReadConfirmation::from_bytes(body)?;
362 confirmation
363 .is_intact()
364 .then_some((confirmation.address, confirmation.size))
365}
366
367pub fn build_memory_read_packet(address: u32, size: u32, tid: u8) -> Vec<u8> {
380 serialize_infallible(crate::message::Packet::MemoryRead { address, size }, tid)
381}
382
383pub fn build_streaming_auth_payload(credential: &AuthCredential) -> [u8; 32] {
391 let mut plaintext = [0u8; 32];
393
394 let timestamp = std::time::SystemTime::now()
396 .duration_since(std::time::UNIX_EPOCH)
397 .map(|d| d.as_millis() as u64)
398 .unwrap_or(0);
399 plaintext[0..8].copy_from_slice(×tamp.to_le_bytes());
400
401 plaintext[8..20].copy_from_slice(credential.as_bytes());
403
404 let random_bytes: [u8; 12] = rand::random();
406 plaintext[20..32].copy_from_slice(&random_bytes);
407
408 aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_ENC)
410}
411
412pub fn parse_streaming_auth_request_payload(ciphertext: &[u8]) -> Option<AuthCredential> {
414 let encrypted: [u8; STREAMING_AUTH_PAYLOAD_SIZE] = ciphertext.try_into().ok()?;
415 let plaintext = aes_ecb_decrypt(&encrypted, STREAMING_AUTH_KEY_ENC);
416 Some(AuthCredential::from_bytes(plaintext[8..20].try_into().ok()?))
417}
418
419pub fn encrypt_streaming_auth_response_payload(plaintext: &[u8; 32]) -> [u8; 32] {
427 aes_ecb_encrypt(plaintext, STREAMING_AUTH_KEY_DEC)
428}
429
430pub fn build_streaming_auth_packet(credential: &AuthCredential, tid: u8) -> Vec<u8> {
442 serialize_infallible(
443 crate::message::Packet::StreamingAuth {
444 credential: credential.clone(),
445 },
446 tid,
447 )
448}
449
450fn serialize_infallible(packet: crate::message::Packet, tid: u8) -> Vec<u8> {
452 let raw = packet
453 .to_raw_packet(tid)
454 .expect("auth command packets are always serializable");
455 bytes::Bytes::from(raw).to_vec()
456}
457
458pub fn parse_streaming_auth_response(response: &[u8]) -> Option<StreamingAuthResult> {
466 if response.len() < STREAMING_AUTH_HEADER_SIZE + STREAMING_AUTH_PAYLOAD_SIZE {
467 return None;
468 }
469
470 let (header_bytes, payload) = response.split_at(STREAMING_AUTH_HEADER_SIZE);
471 let header = StreamingAuthHeader::from_bytes(header_bytes.try_into().ok()?);
472 parse_streaming_auth_response_parts(header, payload)
473}
474
475pub(crate) fn parse_streaming_auth_response_parts(
476 header: StreamingAuthHeader,
477 payload: &[u8],
478) -> Option<StreamingAuthResult> {
479 if header.packet_type() != u8::from(PacketType::StreamingAuth)
480 || header.reserved_flag()
481 || header.id() != STREAMING_AUTH_RESPONSE_ID
482 || header.attribute() & STREAMING_AUTH_RESULT_FLAG == 0
483 {
484 return None;
485 }
486
487 parse_streaming_auth_response_payload(payload, header.attribute())
488}
489
490pub fn parse_streaming_auth_response_payload(payload: &[u8], attribute: u16) -> Option<StreamingAuthResult> {
492 let encrypted: [u8; STREAMING_AUTH_PAYLOAD_SIZE] = payload.get(..STREAMING_AUTH_PAYLOAD_SIZE)?.try_into().ok()?;
493 let decrypted = aes_ecb_decrypt(&encrypted, STREAMING_AUTH_KEY_DEC);
494 let auth_level = ((attribute & STREAMING_AUTH_LEVEL_MASK) >> STREAMING_AUTH_LEVEL_SHIFT) as u8;
495
496 Some(StreamingAuthResult {
497 success: auth_level != 0,
498 attribute,
499 auth_level,
500 decrypted_payload: decrypted,
501 })
502}
503
504fn aes_ecb_encrypt(plaintext: &[u8; 32], key: &[u8; 16]) -> [u8; 32] {
506 let cipher = Aes128::new(key.into());
507 let mut output = *plaintext;
508
509 for block in output.as_chunks_mut::<AES_BLOCK_SIZE>().0 {
510 encrypt_aes_block(&cipher, block);
511 }
512
513 output
514}
515
516pub fn decrypt_memory_read_response(ciphertext: &[u8]) -> Option<Vec<u8>> {
521 if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(AES_BLOCK_SIZE) {
522 return None;
523 }
524
525 let cipher = Aes128::new(MEMORY_READ_KEY.into());
527 let mut output = ciphertext.to_vec();
528
529 for block in output.chunks_mut(AES_BLOCK_SIZE) {
530 if block.len() == AES_BLOCK_SIZE {
531 decrypt_aes_block(&cipher, block);
532 }
533 }
534
535 Some(output)
536}
537
538fn aes_ecb_decrypt(ciphertext: &[u8; 32], key: &[u8; 16]) -> [u8; 32] {
540 let cipher = Aes128::new(key.into());
541 let mut output = *ciphertext;
542
543 for block in output.as_chunks_mut::<AES_BLOCK_SIZE>().0 {
544 decrypt_aes_block(&cipher, block);
545 }
546
547 output
548}
549
550fn encrypt_aes_block(cipher: &Aes128, bytes: &mut [u8]) {
551 let block: &mut [u8; AES_BLOCK_SIZE] = bytes.try_into().expect("AES chunk has the block size");
552 cipher.encrypt_block(block.into());
553}
554
555fn decrypt_aes_block(cipher: &Aes128, bytes: &mut [u8]) {
556 let block: &mut [u8; AES_BLOCK_SIZE] = bytes.try_into().expect("AES chunk has the block size");
557 cipher.decrypt_block(block.into());
558}
559
560pub fn aes_ecb_decrypt_block(ciphertext: &[u8; 16], key: &[u8; 16]) -> [u8; 16] {
564 let cipher = Aes128::new(key.into());
565 let mut output = *ciphertext;
566 decrypt_aes_block(&cipher, &mut output);
567 output
568}
569
570pub fn aes_ecb_decrypt_blocks(ciphertext: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, crate::error::KMError> {
579 if !ciphertext.len().is_multiple_of(AES_BLOCK_SIZE) {
580 return Err(crate::error::KMError::InvalidPacket(format!(
581 "AES ciphertext must be a multiple of {AES_BLOCK_SIZE} bytes, got {}",
582 ciphertext.len()
583 )));
584 }
585
586 let cipher = Aes128::new(key.into());
587 let mut output = ciphertext.to_vec();
588
589 for block in output.as_chunks_mut::<AES_BLOCK_SIZE>().0 {
590 decrypt_aes_block(&cipher, block);
591 }
592
593 Ok(output)
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599
600 const HARDCODED_MEMORY_READ: &str = "4402010133f8860c0054288cdc7e52729826872dd18b539a39c407d5c063d91102e36a9e";
603 const HARDCODED_STREAMING_AUTH: &str = "4c0600025538815b69a452c83e54ef1d70f3bc9ae6aac1b12a6ac07c20fde58c7bf517ca";
605
606 #[test]
607 fn test_hardware_id_parsing() {
608 let bytes: [u8; 12] = [
610 0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff, ];
615
616 let hw_id = HardwareId::from_bytes(bytes);
617 assert_eq!(hw_id.serial_prefix(), Some("071KBP".to_string()));
618 assert_eq!(hw_id.device_id(), 2577);
619 }
620
621 #[test]
622 fn test_aes_roundtrip() {
623 let plaintext = [0x42u8; 32];
624 let encrypted = aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_ENC);
625 assert_ne!(encrypted, plaintext);
627 assert_eq!(encrypted.len(), 32);
628 }
629
630 #[test]
631 fn test_memory_read_packet_structure() {
632 let packet = build_memory_read_packet(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32, 0x02);
633 assert_eq!(packet.len(), 36);
634 assert_eq!(packet[0], 0x44); assert_eq!(packet[1], 0x02); assert_eq!(packet[2], 0x01); assert_eq!(packet[3], 0x01); }
639
640 #[test]
641 fn test_streaming_auth_packet_structure() {
642 let hw_id = HardwareId::from_bytes([0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff]);
643 let packet = build_streaming_auth_packet(&AuthCredential::from(&hw_id), 0x06);
644 assert_eq!(packet.len(), 36);
645 assert_eq!(packet[0], 0x4C); assert_eq!(packet[1], 0x06); assert_eq!(packet[2], 0x00); assert_eq!(packet[3], 0x02); }
650
651 #[test]
652 fn test_streaming_auth_calibration_credential_roundtrip() {
653 let credential = AuthCredential::from_bytes(*b"007965 CDFDD");
654 let encrypted = build_streaming_auth_payload(&credential);
655
656 assert_eq!(parse_streaming_auth_request_payload(&encrypted), Some(credential));
657 }
658
659 #[test]
660 fn test_streaming_auth_response_uses_header_result() {
661 use crate::{Packet, RawPacket};
662 use bytes::Bytes;
663
664 let plaintext = [0x5a; STREAMING_AUTH_PAYLOAD_SIZE];
665 let encrypted = aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_DEC);
666
667 let response = |attribute| {
668 let header = StreamingAuthHeader::new()
669 .with_packet_type(PacketType::StreamingAuth.into())
670 .with_reserved_flag(false)
671 .with_id(0)
672 .with_attribute(attribute);
673 let mut bytes = Vec::from(header.into_bytes());
674 bytes.extend_from_slice(&encrypted);
675 bytes
676 };
677
678 let device_auth = parse_streaming_auth_response(&response(0x0203)).unwrap();
679 assert!(device_auth.success);
680 assert!(device_auth.adcqueue_enabled());
681 assert_eq!(device_auth.auth_level, 1);
682 assert_eq!(device_auth.attribute, 0x0203);
683 assert_eq!(device_auth.decrypted_payload, plaintext);
684
685 let calibration_auth = parse_streaming_auth_response(&response(0x0205)).unwrap();
686 assert!(calibration_auth.success);
687 assert!(calibration_auth.adcqueue_enabled());
688 assert_eq!(calibration_auth.auth_level, 2);
689 assert_eq!(calibration_auth.attribute, 0x0205);
690 assert_eq!(calibration_auth.decrypted_payload, plaintext);
691
692 let serialized = Bytes::from(
693 Packet::StreamingAuthResponse(calibration_auth.clone())
694 .to_raw_packet(99)
695 .unwrap(),
696 );
697 let reparsed = RawPacket::try_from(serialized).unwrap();
698 assert_eq!(
699 Packet::try_from(reparsed).unwrap(),
700 Packet::StreamingAuthResponse(calibration_auth)
701 );
702
703 let raw_failure = RawPacket::try_from(Bytes::from(response(0x0201))).unwrap();
704 let Packet::StreamingAuthResponse(failure) = Packet::try_from(raw_failure).unwrap() else {
705 panic!("expected StreamingAuthResponse");
706 };
707 assert!(!failure.success);
708 assert!(!failure.adcqueue_enabled());
709 assert_eq!(failure.auth_level, 0);
710 assert_eq!(failure.attribute, 0x0201);
711 assert_eq!(failure.decrypted_payload, plaintext);
712 }
713
714 #[test]
715 fn test_decrypt_hardcoded_memory_read() {
716 let packet = hex::decode(HARDCODED_MEMORY_READ).unwrap();
719
720 assert_eq!(packet[0], 0x44, "MemoryRead type");
722 assert_eq!(packet[1], 0x02, "TID=2");
723 assert_eq!(packet[2], 0x01, "Attribute low");
724 assert_eq!(packet[3], 0x01, "Attribute high");
725
726 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
728 let plaintext = aes_ecb_decrypt(&ciphertext, MEMORY_READ_KEY);
729
730 let address = u32::from_le_bytes([plaintext[0], plaintext[1], plaintext[2], plaintext[3]]);
732 let size = u32::from_le_bytes([plaintext[4], plaintext[5], plaintext[6], plaintext[7]]);
734
735 println!("Decrypted MemoryRead: address=0x{:08X}, size={}", address, size);
736 println!("Plaintext: {:02x?}", plaintext);
737
738 assert_eq!(address, 0x420, "Hardcoded reads from 0x420");
740 assert_eq!(size, 64, "Hardcoded reads 64 bytes");
741 }
742
743 #[test]
744 fn test_memory_read_for_hardware_id() {
745 let generated = build_memory_read_packet(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32, 0x02);
747
748 assert_eq!(generated[0], 0x44, "MemoryRead type");
750 assert_eq!(generated[1], 0x02, "TID=2");
751 assert_eq!(generated[2], 0x01, "Attribute low");
752 assert_eq!(generated[3], 0x01, "Attribute high");
753
754 let ciphertext: [u8; 32] = generated[4..36].try_into().unwrap();
756 let plaintext = aes_ecb_decrypt(&ciphertext, MEMORY_READ_KEY);
757
758 let address = u32::from_le_bytes([plaintext[0], plaintext[1], plaintext[2], plaintext[3]]);
759 let size = u32::from_le_bytes([plaintext[4], plaintext[5], plaintext[6], plaintext[7]]);
760
761 assert_eq!(address, HARDWARE_ID_ADDRESS, "Should read from 0x40010450");
762 assert_eq!(size, HARDWARE_ID_SIZE as u32, "Should read 12 bytes");
763
764 let expected_crc = crc32fast::hash(&plaintext[0..12]);
766 let actual_crc = u32::from_le_bytes([plaintext[12], plaintext[13], plaintext[14], plaintext[15]]);
767 assert_eq!(actual_crc, expected_crc, "CRC32 should be valid");
768 }
769
770 #[test]
771 fn test_decrypt_hardcoded_streaming_auth() {
772 let packet = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
774
775 assert_eq!(packet[0], 0x4C, "StreamingAuth type");
777 assert_eq!(packet[1], 0x06, "TID=6");
778 assert_eq!(packet[2], 0x00, "Attribute low");
779 assert_eq!(packet[3], 0x02, "Attribute high");
780
781 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
783 let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
784
785 let timestamp = u64::from_le_bytes(plaintext[0..8].try_into().unwrap());
787 let hardware_id_bytes: [u8; 12] = plaintext[8..20].try_into().unwrap();
788 let hardware_id = HardwareId::from_bytes(hardware_id_bytes);
789
790 println!("Decrypted StreamingAuth:");
791 println!(" Timestamp: {} ms", timestamp);
792 println!(" HardwareID: {}", hardware_id);
793 println!(" Serial prefix: {:?}", hardware_id.serial_prefix());
794 println!(" Device ID: {}", hardware_id.device_id());
795 println!(" Plaintext: {:02x?}", plaintext);
796
797 assert_eq!(hardware_id.serial_prefix(), Some("071KBP".to_string()));
799 assert_eq!(hardware_id.device_id(), 2577);
800 assert!(timestamp > 0, "Timestamp should be non-zero");
801 }
802
803 #[test]
804 fn test_streaming_auth_with_known_hardware_id() {
805 let hw_id = HardwareId::from_bytes([
808 0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff, ]);
813
814 let packet = build_streaming_auth_packet(&AuthCredential::from(&hw_id), 0x06);
816
817 let hardcoded = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
819 assert_eq!(&packet[0..4], &hardcoded[0..4], "Headers must match");
820
821 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
823 let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
824
825 let extracted_hw: [u8; 12] = plaintext[8..20].try_into().unwrap();
827 assert_eq!(extracted_hw, hw_id.bytes, "HardwareID must be embedded correctly");
828
829 let timestamp = u64::from_le_bytes(plaintext[0..8].try_into().unwrap());
831 let now = std::time::SystemTime::now()
832 .duration_since(std::time::UNIX_EPOCH)
833 .unwrap()
834 .as_millis() as u64;
835 assert!(timestamp > now - 3600000, "Timestamp should be recent");
836 assert!(timestamp <= now + 1000, "Timestamp should not be in future");
837 }
838}