#[expect(
deprecated,
reason = "https://github.com/RustCrypto/traits/issues/2036"
)]
use digest::{
FixedOutput, FixedOutputReset, HashMarker, Output, Reset, Update, consts::U8,
core_api::OutputSizeUser, generic_array::GenericArray,
};
use std::sync::OnceLock;
const EMPTY: i64 = -4513414715797952619;
fn fp_table() -> &'static [i64; 256] {
static FPTABLE_ONCE: OnceLock<[i64; 256]> = OnceLock::new();
FPTABLE_ONCE.get_or_init(|| {
let mut fp_table: [i64; 256] = [0; 256];
for i in 0..256 {
let mut fp = i;
for _ in 0..8 {
fp = (fp as u64 >> 1) as i64 ^ (EMPTY & -(fp & 1));
}
fp_table[i as usize] = fp;
}
fp_table
})
}
#[derive(Clone)]
pub struct Rabin {
result: i64,
}
impl Default for Rabin {
fn default() -> Self {
Rabin { result: EMPTY }
}
}
impl Update for Rabin {
fn update(&mut self, data: &[u8]) {
for b in data {
self.result = (self.result as u64 >> 8) as i64
^ fp_table()[((self.result ^ *b as i64) & 0xff) as usize];
}
}
}
impl FixedOutput for Rabin {
#[expect(
deprecated,
reason = "https://github.com/RustCrypto/traits/issues/2036"
)]
fn finalize_into(self, out: &mut GenericArray<u8, Self::OutputSize>) {
out.copy_from_slice(&self.result.to_le_bytes());
}
}
impl Reset for Rabin {
fn reset(&mut self) {
self.result = EMPTY;
}
}
impl OutputSizeUser for Rabin {
type OutputSize = U8;
}
impl HashMarker for Rabin {}
impl FixedOutputReset for Rabin {
fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
out.copy_from_slice(&self.result.to_le_bytes());
self.reset();
}
}
#[cfg(test)]
mod tests {
use super::Rabin;
use apache_avro_test_helper::TestResult;
use digest::Digest;
use pretty_assertions::assert_eq;
#[test]
fn test1() -> TestResult {
let data: &[(&str, i64)] = &[
(r#""null""#, 7195948357588979594),
(r#""boolean""#, -6970731678124411036),
(
r#"{"name":"foo","type":"fixed","size":15}"#,
1756455273707447556,
),
(
r#"{"name":"PigValue","type":"record","fields":[{"name":"value","type":["null","int","long","PigValue"]}]}"#,
-1759257747318642341,
),
];
let mut hasher = Rabin::new();
for (s, fp) in data {
hasher.update(s.as_bytes());
let res: &[u8] = &hasher.finalize_reset();
let result = i64::from_le_bytes(res.try_into()?);
assert_eq!(*fp, result);
}
Ok(())
}
}