const BASE64_URL_ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
pub fn tn_signature_encode(out: &mut [u8], input: &[u8; 64]) {
let mut checksum: u64 = 0;
out[0] = b't';
out[1] = b's';
let mut encoded_len = 2;
let mut accumulator: u32 = 0;
let mut bits_collected = 0;
let mut data_len = 63;
let mut data_idx = 0;
while data_len > 0 {
checksum += input[data_idx] as u64;
accumulator = (accumulator << 8) | input[data_idx] as u32;
bits_collected += 8;
data_idx += 1;
data_len -= 1;
while bits_collected >= 6 {
out[encoded_len] =
BASE64_URL_ALPHABET[((accumulator >> (bits_collected - 6)) & 0x3F) as usize];
encoded_len += 1;
bits_collected -= 6;
}
}
checksum += input[data_idx] as u64;
accumulator = (accumulator << 8) | input[data_idx] as u32;
bits_collected += 8;
accumulator = (accumulator << 16) | ((checksum & 0xFFFF) as u32);
bits_collected += 16;
while bits_collected >= 6 {
out[encoded_len] =
BASE64_URL_ALPHABET[((accumulator >> (bits_collected - 6)) & 0x3F) as usize];
encoded_len += 1;
bits_collected -= 6;
}
}
pub fn tn_signature_decode(out: &mut [u8; 64], input: &[u8]) -> Result<(), i32> {
if input.len() != 90 {
return Err(-1);
}
if input[0] != b't' || input[1] != b's' {
return Err(-2);
}
let mut in_sz = 84;
let mut in_idx = 2;
let mut checksum: u64 = 0;
let mut out_idx = 0;
let mut invlut = [0xFFu8; 256];
for (i, &b) in BASE64_URL_ALPHABET.iter().enumerate() {
invlut[b as usize] = i as u8;
}
while in_sz >= 4 {
let a = invlut[input[in_idx + 0] as usize] as i32;
let b = invlut[input[in_idx + 1] as usize] as i32;
let c = invlut[input[in_idx + 2] as usize] as i32;
let d = invlut[input[in_idx + 3] as usize] as i32;
if a < 0 || b < 0 || c < 0 || d < 0 {
return Err(-3);
}
let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
let temp1 = ((triple >> 16) & 0xFF) as u8;
checksum += temp1 as u64;
out[out_idx] = temp1;
out_idx += 1;
let temp2 = ((triple >> 8) & 0xFF) as u8;
checksum += temp2 as u64;
out[out_idx] = temp2;
out_idx += 1;
let temp3 = (triple & 0xFF) as u8;
checksum += temp3 as u64;
out[out_idx] = temp3;
out_idx += 1;
in_idx += 4;
in_sz -= 4;
}
let a = invlut[input[in_idx + 0] as usize] as i32;
let b = invlut[input[in_idx + 1] as usize] as i32;
let c = invlut[input[in_idx + 2] as usize] as i32;
let d = invlut[input[in_idx + 3] as usize] as i32;
if a < 0 || b < 0 || c < 0 || d < 0 {
return Err(-4);
}
let triple = ((a as u32) << 18) | ((b as u32) << 12) | ((c as u32) << 6) | (d as u32);
let temp1 = ((triple >> 16) & 0xFF) as u8;
checksum += temp1 as u64;
out[out_idx] = temp1;
let incoming_checksum = (triple & 0xFFFF) as u16;
checksum = checksum & 0xFFFF;
if checksum as u16 != incoming_checksum {
return Err(-5);
}
Ok(())
}
pub fn tn_signature_to_string(signature: &[u8; 64]) -> String {
let mut out = [0u8; 90];
tn_signature_encode(&mut out, signature);
String::from_utf8_lossy(&out).to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
#[test]
fn test_signature_encoder() {
let mut rng = rand::rng();
let mut signature = [0u8; 64];
let mut decoded_signature = [0u8; 64];
let mut encoded_signature = [0u8; 128]; for _ in 0..10_000 {
rng.fill_bytes(&mut signature);
encoded_signature.fill(0);
tn_signature_encode(&mut encoded_signature, &signature);
let encoded_slice = &encoded_signature[..90];
let res = tn_signature_decode(&mut decoded_signature, encoded_slice);
assert!(res.is_ok(), "decode failed: {:?}", res);
assert_eq!(signature, decoded_signature, "roundtrip mismatch");
}
let mut corrupted = [0u8; 128];
rng.fill_bytes(&mut signature);
tn_signature_encode(&mut corrupted, &signature);
let encoded_slice = &mut corrupted[..90];
encoded_slice[88] = if encoded_slice[88] == b'A' {
b'B'
} else {
b'A'
};
let res = tn_signature_decode(&mut decoded_signature, encoded_slice);
assert_eq!(res, Err(-5), "corrupt checksum should fail with -5");
}
}