const POLYNOMIAL: u64 = 0xe543_2797_6592_7881;
const TABLE: [u64; 256] = build_table();
const fn build_table() -> [u64; 256] {
let mut table = [0u64; 256];
let mut index = 0usize;
while index < 256 {
let mut register = (index as u64) << 56;
let mut bit = 0;
while bit < 8 {
register = if register & (1 << 63) != 0 {
(register << 1) ^ POLYNOMIAL
} else {
register << 1
};
bit += 1;
}
table[index] = register.swap_bytes();
index += 1;
}
table
}
pub fn checksum(bytes: &[u8]) -> u64 {
Crc64::new().chain(bytes).finish()
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Crc64 {
register: u64,
}
impl Crc64 {
pub const fn new() -> Self {
Self { register: 0 }
}
pub fn update(&mut self, bytes: &[u8]) {
let mut register = self.register;
for &byte in bytes {
register = TABLE[((register ^ byte as u64) & 0xff) as usize] ^ (register >> 8);
}
self.register = register;
}
#[must_use]
pub fn chain(mut self, bytes: &[u8]) -> Self {
self.update(bytes);
self
}
pub const fn finish(&self) -> u64 {
self.register.swap_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
const CANONICAL: &[(u64, &str)] = &[
(0x0000000000000000, ""),
(0x74b42565ce6232d5, "a"),
(0x5f02be5e81cf7b1c, "ab"),
(0xaadaac6d7d340c20, "abc"),
(0xd35b54234f7f70a0, "abcd"),
(0xe729d85f050fa861, "abcde"),
(0x4852bb31b666ae4f, "abcdef"),
(0xab31ee2e0fe39abb, "abcdefg"),
(0x3dc543531acca62b, "abcdefgh"),
(0x43c501e26fc35778, "abcdefghi"),
(0x4cc4843d59c1373e, "abcdefghij"),
(
0x481ac76eee0d3ebd,
"There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977",
),
];
#[test]
fn canonical_vectors() {
for &(expected, input) in CANONICAL {
assert_eq!(
checksum(input.as_bytes()),
expected,
"checksum of {input:?} disagrees with the Go SDK"
);
}
}
#[test]
fn table_matches_the_go_sdk() {
assert_eq!(TABLE[0], 0x0000000000000000);
assert_eq!(TABLE[1], 0x81789265972743e5);
assert_eq!(TABLE[128], 0xceb75cdca3c3c984);
assert_eq!(TABLE[255], 0x0b0d194bb09f4fa4);
}
#[test]
fn incremental_matches_one_shot() {
let data: Vec<u8> = (0..=255u8).cycle().take(1000).collect();
for split in [0, 1, 7, 8, 9, 255, 256, 999, 1000] {
let (head, tail) = data.split_at(split);
assert_eq!(
Crc64::new().chain(head).chain(tail).finish(),
checksum(&data),
"split at {split} disagrees with the one-shot checksum"
);
}
}
#[test]
fn empty_input_is_zero() {
assert_eq!(checksum(b""), 0);
assert_eq!(Crc64::new().finish(), 0);
}
}