pub(crate) fn candid_name_hash(name: &str) -> u32 {
let mut hash: u32 = 0;
for byte in name.as_bytes() {
hash = hash.wrapping_mul(223).wrapping_add(u32::from(*byte));
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_hashes_are_stable() {
assert_eq!(candid_name_hash(""), 0);
assert_eq!(candid_name_hash("a"), 97);
assert_eq!(candid_name_hash("ok"), 24_860);
assert_ne!(candid_name_hash("ok"), candid_name_hash("ko"));
}
#[test]
fn documented_collision_pairs_agree() {
assert_eq!(candid_name_hash("jhwlzguu"), candid_name_hash("jsyrjsvk"));
}
#[test]
fn hashing_is_over_utf8_bytes_not_chars() {
let expected = "é"
.as_bytes()
.iter()
.fold(0u32, |hash, byte| hash.wrapping_mul(223) + u32::from(*byte));
assert_eq!(candid_name_hash("é"), expected);
}
#[test]
fn long_names_wrap_instead_of_overflowing() {
let long = "z".repeat(4096);
let _ = candid_name_hash(&long);
}
#[test]
fn parity_with_the_reference_implementation() {
for name in [
"",
"a",
"ok",
"err",
"jhwlzguu",
"jsyrjsvk",
"hyphen-name",
"_1_",
"0",
"0.did",
"méthode",
"日本語",
"\u{1f600}",
"\u{0}",
"\u{7f}\u{80}\u{81}",
"a\"b",
"A_very_long_method_name_that_keeps_going_and_going_and_going_0123456789",
] {
assert_eq!(
candid_name_hash(name),
candid_parser::candid::idl_hash(name),
"hash parity for {name:?}"
);
}
for byte in 0u8..=0xff {
let name = String::from_utf8_lossy(&[byte]).into_owned();
assert_eq!(
candid_name_hash(&name),
candid_parser::candid::idl_hash(&name)
);
let pair = format!("{name}{name}");
assert_eq!(
candid_name_hash(&pair),
candid_parser::candid::idl_hash(&pair)
);
}
for length in [1usize, 2, 3, 4, 5, 6, 7, 8, 9, 64, 255, 256, 1024, 4096] {
let name = "\u{e9}z".repeat(length);
assert_eq!(
candid_name_hash(&name),
candid_parser::candid::idl_hash(&name),
"hash parity at length {length}"
);
}
}
}