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 pub 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 fn build_memory_read_packet(address: u32, size: u32, tid: u8) -> Vec<u8> {
329 let ciphertext = build_memory_read_payload(address, size);
330
331 let mut packet = Vec::with_capacity(36);
333 packet.push(0x44); packet.push(tid); packet.push(0x01); packet.push(0x01); packet.extend_from_slice(&ciphertext);
338
339 packet
340}
341
342pub fn build_streaming_auth_payload(credential: &AuthCredential) -> [u8; 32] {
350 let mut plaintext = [0u8; 32];
352
353 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(×tamp.to_le_bytes());
359
360 plaintext[8..20].copy_from_slice(credential.as_bytes());
362
363 let random_bytes: [u8; 12] = rand::random();
365 plaintext[20..32].copy_from_slice(&random_bytes);
366
367 aes_ecb_encrypt(&plaintext, STREAMING_AUTH_KEY_ENC)
369}
370
371pub 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
378pub fn encrypt_streaming_auth_response_payload(plaintext: &[u8; 32]) -> [u8; 32] {
386 aes_ecb_encrypt(plaintext, STREAMING_AUTH_KEY_DEC)
387}
388
389pub fn build_streaming_auth_packet(credential: &AuthCredential, tid: u8) -> Vec<u8> {
398 let ciphertext = build_streaming_auth_payload(credential);
399
400 let mut packet = Vec::with_capacity(36);
402 packet.push(0x4C); packet.push(tid); packet.push(0x00); packet.push(0x02); packet.extend_from_slice(&ciphertext);
407
408 packet
409}
410
411pub 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
443pub 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
457fn 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
469pub 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 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
491fn 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
513pub 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
523pub 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 const HARDCODED_MEMORY_READ: &str = "4402010133f8860c0054288cdc7e52729826872dd18b539a39c407d5c063d91102e36a9e";
556 const HARDCODED_STREAMING_AUTH: &str = "4c0600025538815b69a452c83e54ef1d70f3bc9ae6aac1b12a6ac07c20fde58c7bf517ca";
558
559 #[test]
560 fn test_hardware_id_parsing() {
561 let bytes: [u8; 12] = [
563 0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff, ];
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 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); assert_eq!(packet[1], 0x02); assert_eq!(packet[2], 0x01); assert_eq!(packet[3], 0x01); }
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); assert_eq!(packet[1], 0x06); assert_eq!(packet[2], 0x00); assert_eq!(packet[3], 0x02); }
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 let packet = hex::decode(HARDCODED_MEMORY_READ).unwrap();
672
673 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 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
681 let plaintext = aes_ecb_decrypt(&ciphertext, MEMORY_READ_KEY);
682
683 let address = u32::from_le_bytes([plaintext[0], plaintext[1], plaintext[2], plaintext[3]]);
685 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 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 let generated = build_memory_read_packet(HARDWARE_ID_ADDRESS, HARDWARE_ID_SIZE as u32, 0x02);
700
701 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 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 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 let packet = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
727
728 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 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
736 let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
737
738 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 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 let hw_id = HardwareId::from_bytes([
761 0x30, 0x37, 0x31, 0x4b, 0x42, 0x50, 0x0d, 0xff, 0x11, 0x0a, 0xff, 0xff, ]);
766
767 let packet = build_streaming_auth_packet(&AuthCredential::from(&hw_id), 0x06);
769
770 let hardcoded = hex::decode(HARDCODED_STREAMING_AUTH).unwrap();
772 assert_eq!(&packet[0..4], &hardcoded[0..4], "Headers must match");
773
774 let ciphertext: [u8; 32] = packet[4..36].try_into().unwrap();
776 let plaintext = aes_ecb_decrypt(&ciphertext, STREAMING_AUTH_KEY_ENC);
777
778 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 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}