Skip to content

Commit

Permalink
Add structs implementing Hasher, Read, and Write (#42)
Browse files Browse the repository at this point in the history
Add structs implementing Hasher, Read, and Write
  • Loading branch information
clbarnes authored Jul 13, 2023
1 parent ba34025 commit c04baed
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/hasher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Provide a CRC-32C implementor of [Hasher].
use std::hash::Hasher;

use crate::crc32c_append;

/// Implementor of [Hasher] for CRC-32C.
///
/// Note that CRC-32C produces a 32-bit hash (as [u32]),
/// but the trait requires that the output value be [u64].
#[derive(Default)]
pub struct Crc32cHasher {
checksum: u32,
}

impl Crc32cHasher {
/// Create the [Hasher] pre-loaded with a particular checksum.
///
/// Use the [Default::default()] constructor for a clean start.
pub fn new(initial: u32) -> Self {
Self { checksum: initial }
}
}

impl Hasher for Crc32cHasher {
fn finish(&self) -> u64 {
self.checksum as u64
}

fn write(&mut self, bytes: &[u8]) {
self.checksum = crc32c_append(self.checksum, bytes);
}
}

#[cfg(test)]
mod tests {
use super::*;

const TEST_STRING: &[u8] =
b"This is a very long string which is used to test the CRC-32-Castagnoli function.";
const CHECKSUM: u32 = 0x20_CB_1E_59;

#[test]
fn can_hash() {
let mut hasher = Crc32cHasher::default();
hasher.write(TEST_STRING);
assert_eq!(hasher.finish(), CHECKSUM as u64);
}
}
119 changes: 119 additions & 0 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Provides wrappers for [Read] and [Write] types which checksum the bytes being read/written.
use std::io::{Read, Write};

use crate::crc32c_append;

/// [Read]er wrapper which tracks the checksum of all bytes read.
pub struct Crc32cReader<R: Read> {
checksum: u32,
inner: R,
}

impl<R: Read> Crc32cReader<R> {
/// Wrap an instance of a [Read]er.
pub fn new(r: R) -> Self {
Self::new_with_seed(r, 0)
}

/// Wrap a [Read]er, with the checksum seeded with a particular value.
pub fn new_with_seed(r: R, seed: u32) -> Self {
Self {
checksum: seed,
inner: r,
}
}

/// Unwrap the inner [Read]er.
pub fn into_inner(self) -> R {
self.inner
}

/// Get the checksum of all bytes read.
pub fn crc32c(&self) -> u32 {
self.checksum
}
}

impl<R: Read> Read for Crc32cReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let out = self.inner.read(buf)?;
self.checksum = crc32c_append(self.checksum, &buf[..out]);
Ok(out)
}
}

/// [Write]r wrapper which tracks the checksum of all bytes written.
pub struct Crc32cWriter<W: Write> {
checksum: u32,
inner: W,
}

impl<W: Write> Crc32cWriter<W> {
/// Wrap an instance of a [Write]r.
pub fn new(w: W) -> Self {
Self::new_with_seed(w, 0)
}

/// Wrap a [Write]r, with the checksum seeded with a particular value.
pub fn new_with_seed(w: W, seed: u32) -> Self {
Self {
checksum: seed,
inner: w,
}
}

/// Unwrap the inner [Write]r.
pub fn into_inner(self) -> W {
self.inner
}

/// Get the checksum of all bytes written.
pub fn crc32c(&self) -> u32 {
self.checksum
}
}

impl<W: Write> Write for Crc32cWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let out = self.inner.write(buf)?;
self.checksum = crc32c_append(self.checksum, &buf[..out]);
Ok(out)
}

fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}

#[cfg(test)]
mod tests {
use std::io::Cursor;

use super::*;

const TEST_STRING: &[u8] =
b"This is a very long string which is used to test the CRC-32-Castagnoli function.";
const CHECKSUM: u32 = 0x20_CB_1E_59;

#[test]
fn can_read() {
let mut reader = Crc32cReader::new(&TEST_STRING[..]);
let mut buf = Vec::default();
let n_read = reader.read_to_end(&mut buf).unwrap();
assert_eq!(n_read, TEST_STRING.len());
assert_eq!(buf.as_slice(), TEST_STRING);
assert_eq!(reader.crc32c(), CHECKSUM);
}

#[test]
fn can_write() {
let mut buf = Vec::<u8>::default();

let mut writer = Crc32cWriter::<Cursor<&mut Vec<u8>>>::new(Cursor::new(&mut buf));
writer.write_all(TEST_STRING).unwrap();
let checksum = writer.crc32c();

assert_eq!(buf.as_slice(), TEST_STRING);
assert_eq!(checksum, CHECKSUM);
}
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
#![cfg_attr(nightly, feature(stdsimd))]

mod combine;
mod hasher;
#[cfg(all(target_arch = "aarch64", nightly))]
mod hw_aarch64;
#[cfg(any(target_arch = "x86_64", all(target_arch = "aarch64", nightly)))]
mod hw_tables;
#[cfg(target_arch = "x86_64")]
mod hw_x86_64;
mod io;
mod sw;
mod util;

pub use hasher::Crc32cHasher;

pub use io::{Crc32cReader, Crc32cWriter};

/// Computes the CRC for the data payload.
///
/// Equivalent to calling `crc32c_append(0, data)`.
Expand Down

0 comments on commit c04baed

Please sign in to comment.