#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
use std::io::{self, Read, Write};
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0100_0000_01b3;
#[must_use]
pub fn fnv1a_64(data: &[u8]) -> u64 {
let mut h = FNV_OFFSET;
for &b in data {
h ^= u64::from(b);
h = h.wrapping_mul(FNV_PRIME);
}
h
}
#[derive(Debug, Clone)]
pub struct Fnv1aHasher {
state: u64,
}
impl Fnv1aHasher {
#[must_use]
pub fn new() -> Self {
Self { state: FNV_OFFSET }
}
pub fn update_byte(&mut self, b: u8) {
self.state ^= u64::from(b);
self.state = self.state.wrapping_mul(FNV_PRIME);
}
pub fn update(&mut self, data: &[u8]) {
for &b in data {
self.update_byte(b);
}
}
#[must_use]
pub fn finish(&self) -> u64 {
self.state
}
pub fn reset(&mut self) {
self.state = FNV_OFFSET;
}
}
impl Default for Fnv1aHasher {
fn default() -> Self {
Self::new()
}
}
pub struct VerifyWriter<W> {
inner: W,
hasher: Fnv1aHasher,
bytes_written: u64,
}
impl<W: Write> VerifyWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner,
hasher: Fnv1aHasher::new(),
bytes_written: 0,
}
}
#[must_use]
pub fn checksum(&self) -> u64 {
self.hasher.finish()
}
#[must_use]
pub fn bytes_written(&self) -> u64 {
self.bytes_written
}
#[must_use]
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write> Write for VerifyWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.inner.write(buf)?;
self.hasher.update(&buf[..n]);
self.bytes_written += n as u64;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompareResult {
Equal {
bytes: u64,
},
DifferentAt {
offset: u64,
byte_a: u8,
byte_b: u8,
},
DifferentLength {
shorter_len: u64,
},
}
impl CompareResult {
#[must_use]
pub fn is_equal(&self) -> bool {
matches!(self, Self::Equal { .. })
}
}
pub fn compare_streams<A: Read, B: Read>(mut a: A, mut b: B) -> io::Result<CompareResult> {
let mut buf_a = [0u8; 8192];
let mut buf_b = [0u8; 8192];
let mut offset: u64 = 0;
loop {
let na = read_full(&mut a, &mut buf_a)?;
let nb = read_full(&mut b, &mut buf_b)?;
let min_len = na.min(nb);
for i in 0..min_len {
if buf_a[i] != buf_b[i] {
return Ok(CompareResult::DifferentAt {
offset: offset + i as u64,
byte_a: buf_a[i],
byte_b: buf_b[i],
});
}
}
if na != nb {
return Ok(CompareResult::DifferentLength {
shorter_len: offset + min_len as u64,
});
}
if na == 0 {
return Ok(CompareResult::Equal { bytes: offset });
}
offset += na as u64;
}
}
fn read_full<R: Read>(reader: &mut R, buf: &mut [u8]) -> io::Result<usize> {
let mut pos = 0;
while pos < buf.len() {
match reader.read(&mut buf[pos..]) {
Ok(0) => break,
Ok(n) => pos += n,
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(pos)
}
pub struct PatternFill {
pattern: Vec<u8>,
}
impl PatternFill {
#[must_use]
pub fn new(pattern: &[u8]) -> Self {
let pattern = if pattern.is_empty() {
vec![0]
} else {
pattern.to_vec()
};
Self { pattern }
}
pub fn write_to<W: Write>(&self, w: &mut W, total: usize) -> io::Result<()> {
let mut remaining = total;
while remaining > 0 {
let chunk = remaining.min(self.pattern.len());
w.write_all(&self.pattern[..chunk])?;
remaining -= chunk;
}
Ok(())
}
#[must_use]
pub fn pattern(&self) -> &[u8] {
&self.pattern
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_fnv1a_empty() {
assert_eq!(fnv1a_64(b""), FNV_OFFSET);
}
#[test]
fn test_fnv1a_deterministic() {
let h1 = fnv1a_64(b"hello");
let h2 = fnv1a_64(b"hello");
assert_eq!(h1, h2);
}
#[test]
fn test_fnv1a_different() {
assert_ne!(fnv1a_64(b"abc"), fnv1a_64(b"abd"));
}
#[test]
fn test_fnv1a_hasher_incremental() {
let mut h = Fnv1aHasher::new();
h.update(b"hel");
h.update(b"lo");
assert_eq!(h.finish(), fnv1a_64(b"hello"));
}
#[test]
fn test_fnv1a_hasher_reset() {
let mut h = Fnv1aHasher::new();
h.update(b"data");
h.reset();
assert_eq!(h.finish(), FNV_OFFSET);
}
#[test]
fn test_verify_writer_basic() {
let mut out = Vec::new();
let mut vw = VerifyWriter::new(&mut out);
vw.write_all(b"test data").expect("failed to write");
assert_eq!(vw.bytes_written(), 9);
assert_eq!(vw.checksum(), fnv1a_64(b"test data"));
}
#[test]
fn test_verify_writer_empty() {
let mut out = Vec::new();
let vw = VerifyWriter::new(&mut out);
assert_eq!(vw.bytes_written(), 0);
assert_eq!(vw.checksum(), FNV_OFFSET);
}
#[test]
fn test_compare_equal() {
let a = Cursor::new(b"identical data".to_vec());
let b = Cursor::new(b"identical data".to_vec());
let result = compare_streams(a, b).expect("compare should succeed");
assert!(result.is_equal());
assert_eq!(result, CompareResult::Equal { bytes: 14 });
}
#[test]
fn test_compare_different_byte() {
let a = Cursor::new(b"abc".to_vec());
let b = Cursor::new(b"axc".to_vec());
let result = compare_streams(a, b).expect("compare should succeed");
assert_eq!(
result,
CompareResult::DifferentAt {
offset: 1,
byte_a: b'b',
byte_b: b'x',
}
);
}
#[test]
fn test_compare_different_length() {
let a = Cursor::new(b"short".to_vec());
let b = Cursor::new(b"short and longer".to_vec());
let result = compare_streams(a, b).expect("compare should succeed");
assert_eq!(result, CompareResult::DifferentLength { shorter_len: 5 });
}
#[test]
fn test_compare_both_empty() {
let a = Cursor::new(Vec::<u8>::new());
let b = Cursor::new(Vec::<u8>::new());
let result = compare_streams(a, b).expect("compare should succeed");
assert!(result.is_equal());
}
#[test]
fn test_pattern_fill_basic() {
let pf = PatternFill::new(b"ab");
let mut out = Vec::new();
pf.write_to(&mut out, 7).expect("write_to should succeed");
assert_eq!(out, b"abababa");
}
#[test]
fn test_pattern_fill_empty_pattern() {
let pf = PatternFill::new(b"");
assert_eq!(pf.pattern(), &[0u8]);
}
#[test]
fn test_pattern_fill_zero_len() {
let pf = PatternFill::new(b"xyz");
let mut out = Vec::new();
pf.write_to(&mut out, 0).expect("write_to should succeed");
assert!(out.is_empty());
}
}