| use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; |
| use rand::Rng; |
| use sha2::{Digest, Sha256}; |
|
|
| pub fn generate_hash() -> String { |
| let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); |
| let mut hasher = Sha256::new(); |
| hasher.update(random_bytes); |
| hex::encode(hasher.finalize()) |
| } |
|
|
| fn obfuscate_bytes(bytes: &mut [u8]) { |
| let mut prev: u8 = 165; |
| for (idx, byte) in bytes.iter_mut().enumerate() { |
| let old_value = *byte; |
| *byte = (old_value ^ prev).wrapping_add((idx % 256) as u8); |
| prev = *byte; |
| } |
| } |
|
|
| fn deobfuscate_bytes(bytes: &mut [u8]) { |
| let mut prev: u8 = 165; |
| for (idx, byte) in bytes.iter_mut().enumerate() { |
| let temp = *byte; |
| *byte = (*byte).wrapping_sub((idx % 256) as u8) ^ prev; |
| prev = temp; |
| } |
| } |
|
|
| pub fn generate_timestamp_header() -> String { |
| let timestamp = std::time::SystemTime::now() |
| .duration_since(std::time::UNIX_EPOCH) |
| .unwrap() |
| .as_secs() |
| / 1_000; |
|
|
| let mut timestamp_bytes = vec![ |
| ((timestamp >> 8) & 0xFF) as u8, |
| (0xFF & timestamp) as u8, |
| ((timestamp >> 24) & 0xFF) as u8, |
| ((timestamp >> 16) & 0xFF) as u8, |
| ((timestamp >> 8) & 0xFF) as u8, |
| (0xFF & timestamp) as u8, |
| ]; |
|
|
| obfuscate_bytes(&mut timestamp_bytes); |
| BASE64.encode(×tamp_bytes) |
| } |
|
|
| pub fn generate_checksum(device_id: &str, mac_addr: Option<&str>) -> String { |
| let encoded = generate_timestamp_header(); |
| match mac_addr { |
| Some(mac) => format!("{}{}/{}", encoded, device_id, mac), |
| None => format!("{}{}", encoded, device_id), |
| } |
| } |
|
|
| pub fn generate_checksum_with_default() -> String { |
| generate_checksum(&generate_hash(), Some(&generate_hash())) |
| } |
|
|
| pub fn generate_checksum_with_repair(checksum: &str) -> String { |
| let bytes = checksum.as_bytes(); |
| let len = bytes.len(); |
|
|
| |
| if len != 72 && len != 129 && len != 137 { |
| return generate_checksum_with_default(); |
| } |
|
|
| |
| for (i, &b) in bytes.iter().enumerate() { |
| let valid = match (len, i) { |
| |
| (_, _) if !b.is_ascii_alphanumeric() && b != b'/' && b != b'+' && b != b'=' => false, |
|
|
| |
| (72, 8..=71) => b.is_ascii_hexdigit(), |
|
|
| |
| (129, 0..=63) => b.is_ascii_hexdigit(), |
| (129, 64) => b == b'/', |
| (129, 65..=128) => b.is_ascii_hexdigit(), |
|
|
| |
| (137, 8..=71) => b.is_ascii_hexdigit(), |
| (137, 72) => b == b'/', |
| (137, 73..=136) => b.is_ascii_hexdigit(), |
|
|
| |
| (72 | 137, 0..=7) => true, |
|
|
| _ => unreachable!(), |
| }; |
|
|
| if !valid { |
| return generate_checksum_with_default(); |
| } |
| } |
|
|
| |
| match len { |
| 72 => format!( |
| "{}{}/{}", |
| generate_timestamp_header(), |
| unsafe { std::str::from_utf8_unchecked(&bytes[8..]) }, |
| generate_hash() |
| ), |
| 129 => format!( |
| "{}{}/{}", |
| generate_timestamp_header(), |
| unsafe { std::str::from_utf8_unchecked(&bytes[..64]) }, |
| unsafe { std::str::from_utf8_unchecked(&bytes[65..]) } |
| ), |
| 137 => format!( |
| "{}{}/{}", |
| generate_timestamp_header(), |
| unsafe { std::str::from_utf8_unchecked(&bytes[8..72]) }, |
| unsafe { std::str::from_utf8_unchecked(&bytes[73..]) } |
| ), |
| _ => unreachable!(), |
| } |
| } |
|
|
| pub fn extract_time_ks(timestamp_base64: &str) -> Option<u64> { |
| let mut timestamp_bytes = BASE64.decode(timestamp_base64).ok()?; |
|
|
| if timestamp_bytes.len() != 6 { |
| return None; |
| } |
|
|
| deobfuscate_bytes(&mut timestamp_bytes); |
|
|
| if timestamp_bytes[0] != timestamp_bytes[4] || timestamp_bytes[1] != timestamp_bytes[5] { |
| return None; |
| } |
|
|
| |
| Some( |
| ((timestamp_bytes[2] as u64) << 24) |
| | ((timestamp_bytes[3] as u64) << 16) |
| | ((timestamp_bytes[4] as u64) << 8) |
| | (timestamp_bytes[5] as u64), |
| ) |
| } |
|
|
| pub fn validate_checksum(checksum: &str) -> bool { |
| let bytes = checksum.as_bytes(); |
| let len = bytes.len(); |
|
|
| |
| if len != 72 && len != 137 { |
| return false; |
| } |
|
|
| |
| for (i, &b) in bytes.iter().enumerate() { |
| let valid = match (len, i) { |
| |
| (_, _) if !b.is_ascii_alphanumeric() && b != b'/' && b != b'+' && b != b'=' => false, |
|
|
| |
| (72, 0..=7) => true, |
| (72, 8..=71) => b.is_ascii_hexdigit(), |
|
|
| (137, 0..=7) => true, |
| (137, 8..=71) => b.is_ascii_hexdigit(), |
| (137, 72) => b == b'/', |
| (137, 73..=136) => b.is_ascii_hexdigit(), |
|
|
| _ => unreachable!(), |
| }; |
|
|
| if !valid { |
| return false; |
| } |
| } |
|
|
| |
| let time_valid = extract_time_ks(&checksum[..8]).is_some(); |
|
|
| |
| let mac_hash_valid = if len == 137 { |
| checksum[73..].len() == 64 |
| } else { |
| true |
| }; |
|
|
| time_valid && mac_hash_valid |
| } |
|
|
| |
| |
| pub fn extract_hashes(checksum: &str) -> Option<(Vec<u8>, Vec<u8>)> { |
| |
| if !validate_checksum(checksum) { |
| return None; |
| } |
|
|
| |
| match checksum.len() { |
| 72 => { |
| |
| let device_hash = hex::decode(&checksum[8..]).ok()?; |
| Some((device_hash, Vec::new())) |
| } |
| 137 => { |
| |
| |
| let device_hash = hex::decode(&checksum[8..72]).ok()?; |
| let mac_hash = hex::decode(&checksum[73..]).ok()?; |
| Some((device_hash, mac_hash)) |
| } |
| |
| _ => unreachable!("Invalid length after validation: {}", checksum.len()), |
| } |
| } |
|
|