const M: u64 = 0xC6A4_A793_5BD1_E995;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fold {
RightFold,
LeftFold,
}
impl Fold {
pub fn from_algorithm(algo: &str) -> Option<Fold> {
match algo {
"right_fold" => Some(Fold::RightFold),
"left_fold" => Some(Fold::LeftFold),
_ => None,
}
}
fn order(self) -> [usize; 4] {
match self {
Fold::RightFold => [3, 2, 1, 0],
Fold::LeftFold => [0, 1, 2, 3],
}
}
}
#[inline]
fn mix32(v: u32) -> u64 {
let mut x = v;
x = (!x).wrapping_add(x << 15);
x ^= x >> 12;
x = x.wrapping_add(x << 2);
x ^= x >> 4;
x = x.wrapping_mul(2057);
x ^= x >> 16;
x as u64 }
#[inline]
fn hash_combine(mut seed: u64, value: u64) -> u64 {
let mut h = value.wrapping_mul(M);
h ^= h >> 47;
h = h.wrapping_mul(M);
seed ^= h;
seed.wrapping_mul(M)
}
pub fn version_hash(fold: Fold, major: u32, minor: u32, build: u32, patch: u32) -> u32 {
let v = [major, minor, build, patch];
let mut seed = 0u64;
for &i in &fold.order() {
seed = hash_combine(seed, mix32(v[i]));
}
seed as u32
}
pub fn brute_force(target: u32) -> Option<(Fold, (u32, u32, u32, u32))> {
for fold in [Fold::RightFold, Fold::LeftFold] {
for major in 1u32..30 {
for minor in 0u32..25 {
for build in 0u32..512 {
for patch in 0u32..128 {
if version_hash(fold, major, minor, build, patch) == target {
return Some((fold, (major, minor, build, patch)));
}
}
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn right_fold_matches_node_16_18_20() {
assert_eq!(version_hash(Fold::RightFold, 9, 4, 146, 26), 0xacdd_64ee);
assert_eq!(version_hash(Fold::RightFold, 10, 2, 154, 26), 0x3569_a082);
assert_eq!(version_hash(Fold::RightFold, 11, 3, 244, 8), 0x00e4_c20b);
}
#[test]
fn left_fold_matches_node_22_24() {
assert_eq!(version_hash(Fold::LeftFold, 12, 4, 254, 21), 0x79da_fe74);
assert_eq!(version_hash(Fold::LeftFold, 13, 6, 233, 17), 0xdc33_8cfa);
}
#[test]
fn brute_finds_known_hashes_with_fold() {
let (fold, v) = brute_force(version_hash(Fold::LeftFold, 12, 4, 254, 21)).unwrap();
assert_eq!(fold, Fold::LeftFold);
assert_eq!(v, (12, 4, 254, 21));
}
}