pub use crate::pid::{encode, encode_hash};
pub const CHARMAP1: &[u8] = crate::pid::CHARMAP1;
pub const TESTMAP8: &[u8] = b"YvaZ3FfUm9Nn_c1XuG4yCAzB0beVg-TtHh5SsIiR6rJjQdW2wEq7KkPpL8lOoMxD";
pub const CHARMAP5_PC: &[u8] = b"AzB0bYyCeVvaZ3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_c1XxDdW2wE";
pub const CHARMAP2_PC: &[u8] = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_";
pub const CHARMAP2_MAC: &[u8] = b"ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Platform {
Pc,
Mac,
}
impl Platform {
pub fn charmap5(self) -> &'static [u8] {
match self {
Platform::Pc => CHARMAP5_PC,
Platform::Mac => CHARMAP2_MAC,
}
}
pub fn charmap2(self) -> &'static [u8] {
match self {
Platform::Pc => CHARMAP2_PC,
Platform::Mac => CHARMAP2_MAC,
}
}
}
pub fn decode(data: &[u8], map: &[u8]) -> Vec<u8> {
let n = map.len();
let mut out = Vec::with_capacity(data.len() / 2);
let mut i = 0;
while i + 1 < data.len() {
let (Some(high), Some(low)) = (
map.iter().position(|&c| c == data[i]),
map.iter().position(|&c| c == data[i + 1]),
) else {
break;
};
let value = (((high * n) ^ 0x80) & 0xFF) + low;
out.push(value as u8);
i += 2;
}
out
}
pub fn largest_prime(n: usize) -> Option<usize> {
if n < 2 {
return None;
}
let mut largest = 2;
let mut candidate = 3;
while candidate <= n {
if (2..candidate).all(|d| candidate % d != 0) {
largest = candidate;
}
candidate += 2;
}
Some(largest)
}
pub fn derotate(encdata: &[u8]) -> Vec<u8> {
let contlen = encdata.len();
let Some(prime) = largest_prime(contlen / 3) else {
return encdata.to_vec();
};
let noffset = contlen - prime;
let mut out = Vec::with_capacity(contlen);
out.extend_from_slice(&encdata[noffset..]);
out.extend_from_slice(&encdata[..noffset]);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode_round_trips_all_maps() {
let data: Vec<u8> = (0u16..=255).map(|b| b as u8).collect();
for map in [CHARMAP1, TESTMAP8, CHARMAP5_PC, CHARMAP2_PC, CHARMAP2_MAC] {
let encoded = encode(&data, map);
assert_eq!(encoded.len(), data.len() * 2);
assert_eq!(decode(&encoded, map), data, "round-trip failed for a map");
}
}
#[test]
fn char_maps_are_64_bytes_and_unique() {
for map in [TESTMAP8, CHARMAP5_PC, CHARMAP2_PC, CHARMAP2_MAC] {
assert_eq!(map.len(), 64);
let mut sorted = map.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), 64, "map has duplicate symbols");
}
assert_eq!(CHARMAP1.len(), 32);
}
#[test]
fn largest_prime_matches_reference() {
assert_eq!(largest_prime(0), None);
assert_eq!(largest_prime(1), None);
assert_eq!(largest_prime(2), Some(2));
assert_eq!(largest_prime(3), Some(3));
assert_eq!(largest_prime(10), Some(7));
assert_eq!(largest_prime(11), Some(11));
assert_eq!(largest_prime(100), Some(97));
}
#[test]
fn derotate_inverts_the_encoder_rotation() {
for len in [7usize, 16, 33, 64, 129] {
let aligned: Vec<u8> = (0..len).map(|i| (i * 7 + 3) as u8).collect();
let prime = largest_prime(len / 3).unwrap();
let noffset = len - prime;
let split = len - noffset;
let mut on_disk = Vec::new();
on_disk.extend_from_slice(&aligned[split..]);
on_disk.extend_from_slice(&aligned[..split]);
assert_eq!(derotate(&on_disk), aligned, "derotate failed at len {len}");
}
}
#[test]
fn platform_selects_distinct_maps() {
assert_eq!(Platform::Pc.charmap5(), CHARMAP5_PC);
assert_eq!(Platform::Mac.charmap5(), CHARMAP2_MAC);
assert_eq!(Platform::Mac.charmap2(), CHARMAP2_MAC);
assert_ne!(Platform::Pc.charmap5(), Platform::Mac.charmap5());
}
}