use sha2::{Digest, Sha256};
pub fn generate_task_key(device_id: &str, creation_ms: i64) -> String {
let mut hasher = Sha256::new();
hasher.update(device_id.as_bytes());
hasher.update(creation_ms.to_le_bytes());
let hash = hasher.finalize();
let num = u64::from_le_bytes([hash[0], hash[1], hash[2], hash[3], hash[4], 0, 0, 0]);
base36_encode(num)
}
pub fn base36_encode(mut num: u64) -> String {
if num == 0 {
return "0".into();
}
const CHARS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
let mut result = Vec::new();
while num > 0 {
let remainder = (num % 36) as usize;
result.push(CHARS[remainder]);
num /= 36;
}
result.reverse();
String::from_utf8(result).expect("base36 chars are valid UTF-8")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base36_encode_zero() {
assert_eq!(base36_encode(0), "0");
}
#[test]
fn base36_encode_small_values() {
assert_eq!(base36_encode(1), "1");
assert_eq!(base36_encode(10), "a");
assert_eq!(base36_encode(35), "z");
assert_eq!(base36_encode(36), "10");
assert_eq!(base36_encode(100), "2s");
}
#[test]
fn base36_encode_large_value() {
assert_eq!(base36_encode(1_679_616), "10000");
}
#[test]
fn generate_task_key_is_deterministic() {
let key1 = generate_task_key("device-abc", 1700000000000);
let key2 = generate_task_key("device-abc", 1700000000000);
assert_eq!(key1, key2);
}
#[test]
fn generate_task_key_different_devices_produce_different_keys() {
let key1 = generate_task_key("device-a", 1700000000000);
let key2 = generate_task_key("device-b", 1700000000000);
assert_ne!(key1, key2);
}
#[test]
fn generate_task_key_different_timestamps_produce_different_keys() {
let key1 = generate_task_key("device-a", 1700000000000);
let key2 = generate_task_key("device-a", 1700000000001);
assert_ne!(key1, key2);
}
#[test]
fn generate_task_key_is_not_empty() {
let key = generate_task_key("dev", 12345);
assert!(!key.is_empty());
assert!(key.chars().all(|c| c.is_ascii_alphanumeric()));
}
}