use super::{ClientLaboratory, ClientLaboratoryType};
const ALPHABET: &[u8; 62] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
fn base62_encode_str(s: &str) -> String {
let bytes = s.as_bytes();
let leading_zeros = bytes.iter().take_while(|b| **b == 0).count();
let mut digits: Vec<u8> = Vec::new();
let mut num: Vec<u8> = bytes[leading_zeros..].to_vec();
while !num.is_empty() {
let mut quotient: Vec<u8> = Vec::with_capacity(num.len());
let mut remainder: u32 = 0;
for byte in &num {
let acc = remainder * 256 + *byte as u32;
let q = (acc / 62) as u8;
remainder = acc % 62;
if !(quotient.is_empty() && q == 0) {
quotient.push(q);
}
}
digits.push(ALPHABET[remainder as usize]);
num = quotient;
}
let mut out = String::with_capacity(leading_zeros + digits.len());
for _ in 0..leading_zeros {
out.push('0');
}
out.extend(digits.iter().rev().map(|d| *d as char));
out
}
fn base62_decode_str(s: &str) -> Option<String> {
let digits = s.as_bytes();
let leading_zeros = digits.iter().take_while(|d| **d == b'0').count();
let mut num: Vec<u8> = Vec::new();
for digit in &digits[leading_zeros..] {
let value = ALPHABET.iter().position(|a| a == digit)? as u32;
let mut carry = value;
for byte in num.iter_mut().rev() {
let acc = *byte as u32 * 62 + carry;
*byte = (acc % 256) as u8;
carry = acc / 256;
}
while carry > 0 {
num.insert(0, (carry % 256) as u8);
carry /= 256;
}
}
let mut bytes = vec![0u8; leading_zeros];
bytes.extend(num);
String::from_utf8(bytes).ok()
}
fn fnv1a32(bytes: &[u8]) -> u32 {
let mut hash: u32 = 0x811c9dc5;
for byte in bytes {
hash ^= *byte as u32;
hash = hash.wrapping_mul(0x0100_0193);
}
hash
}
fn base62_encode_u32(mut value: u32) -> String {
let mut digits = [b'0'; 6];
for slot in digits.iter_mut().rev() {
*slot = ALPHABET[(value % 62) as usize];
value /= 62;
}
String::from_utf8(digits.to_vec()).expect("alphabet is ASCII")
}
impl ClientLaboratory {
pub fn server_name(&self) -> Option<String> {
self.composite_id()
.map(|composite| format!("oail-{}", base62_encode_u32(fnv1a32(composite.as_bytes()))))
}
pub fn composite_id(&self) -> Option<String> {
let machine = self.machine.as_deref()?;
let machine_state = self.machine_state.as_deref()?;
Some(format!(
"{machine}/{}/{}",
base62_encode_str(machine_state),
base62_encode_str(&self.id)
))
}
pub fn from_composite_id(s: &str) -> Option<Self> {
let mut parts = s.split('/');
let machine = parts.next()?;
let machine_state = parts.next()?;
let id = parts.next()?;
if parts.next().is_some() || machine.is_empty() {
return None;
}
Some(Self {
r#type: ClientLaboratoryType::Client,
id: base62_decode_str(id)?,
machine: Some(machine.to_string()),
machine_state: Some(base62_decode_str(machine_state)?),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn marker(machine: &str, state: &str, id: &str) -> ClientLaboratory {
ClientLaboratory {
r#type: ClientLaboratoryType::Client,
id: id.to_string(),
machine: Some(machine.to_string()),
machine_state: Some(state.to_string()),
}
}
#[test]
fn roundtrips() {
for (state, id) in [
("default", "my-lab"),
("state/with/slashes", "id/with/slashes"),
("under_score.dots", "0leading-zero-digit"),
("ünïcodé 状態", "研究室/λ"),
("", "empty-state"),
("s", ""),
("\u{0}\u{0}leading-nuls", "trailing-nul\u{0}"),
] {
let original = marker("ab12", state, id);
let composite = original.composite_id().expect("pair present");
assert_eq!(
composite.matches('/').count(),
2,
"exactly three segments: {composite}"
);
let parsed = ClientLaboratory::from_composite_id(&composite)
.unwrap_or_else(|| panic!("parses: {composite}"));
assert_eq!(parsed, original, "roundtrip for {state:?}/{id:?}");
}
}
#[test]
fn server_name_is_fixed_width_and_name_safe() {
for (state, id) in [
("default", "my-lab"),
("state/with/slashes", &"x".repeat(500) as &str),
("ünïcodé 状態", "研究室/λ"),
] {
let lab = marker("3fa8", state, id);
let name = lab.server_name().expect("pair present");
assert_eq!(name.len(), "oail-".len() + 6, "fixed width: {name}");
assert!(name.starts_with("oail-"));
assert!(
name["oail-".len()..].bytes().all(|b| b.is_ascii_alphanumeric()),
"hash token is [0-9A-Za-z]: {name}"
);
assert_eq!(lab.server_name(), Some(name));
}
let mut lab = marker("m", "s", "i");
lab.machine = None;
assert_eq!(lab.server_name(), None);
}
#[test]
fn pairless_marker_has_no_composite() {
let mut lab = marker("m", "s", "i");
lab.machine_state = None;
assert_eq!(lab.composite_id(), None);
lab.machine_state = Some("s".to_string());
lab.machine = None;
assert_eq!(lab.composite_id(), None);
}
#[test]
fn malformed_inputs_rejected() {
for bad in [
"", "bare-id", "machine/only-two", "m/a/b/c", "/state62/id62", "m/bad!chars/aa", "m/aa/bad chars", ] {
assert_eq!(
ClientLaboratory::from_composite_id(bad),
None,
"must reject {bad:?}"
);
}
}
}