Skip to content

Commit

Permalink
Add non-breaking API for custom implementations
Browse files Browse the repository at this point in the history
The previous attempt was a breaking change.

Signed-off-by: Akhil Velagapudi <4@4khil.com>
  • Loading branch information
akhilles committed Apr 2, 2024
1 parent 5e38fdd commit b2c638c
Show file tree
Hide file tree
Showing 31 changed files with 202 additions and 1,314 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
matrix:
rust:
- 1.56.0
- 1.65.0
- stable
- beta
steps:
Expand Down
13 changes: 1 addition & 12 deletions Cargo.toml
Expand Up @@ -13,18 +13,7 @@ description = "Rust implementation of CRC with support of various standards"
keywords = ["crc", "crc16", "crc32", "crc64", "hash"]
categories = ["algorithms", "no-std"]
edition = "2021"
rust-version = "1.56"

# [features]
# use the "NoTable" implementation for the default Crc<uXXX> struct, using no additional memory for a lookup table.
# Takes precedence over "bytewise-mem-limit" and "slice16-mem-limit"
# no-table-mem-limit = []
# use the "Bytewise" implementation for the default Crc<uXXX> struct, using 256 entries of the respective width for a lookup table.
# Takes precedence over "slice16-mem-limit" and is used if no feature is selected
# bytewise-mem-limit = []
# use the "Slice16" implementation for the default Crc<uXXX> struct, using 256 * 16 entries of the respective width for a lookup table.
# Can be overriden by setting "bytewise-mem-limit" and "slice16-mem-limit"
# slice16-mem-limit = []
rust-version = "1.65"

[dependencies]
crc-catalog = "2.4.0"
Expand Down
24 changes: 8 additions & 16 deletions README.md
Expand Up @@ -43,29 +43,21 @@ assert_eq!(digest.finalize(), 0xaee7);

### Minimum supported Rust version (MSRV)

This crate's MSRV is 1.56.
This crate's MSRV is 1.65.

At a minimum, the MSRV will be <= the oldest stable release in the last 12 months. MSRV may be bumped in minor version releases.

### Lookup table flavors
### Implementations

This crate offers three flavors of lookup tables providing a tradeoff between computation speed and used memory.
See the benchmark section for hints, but do benchmarks on your target hardware to decide if the tradeoff is worth it to you.
This crate has several pluggable implementations:

1. `NoTable` provides an implementation that uses no additional memory
2. `Bytewise` provides an implementation that uses a lookup table that uses 256 entries of the used width (e.g. for u32 thats 256 * 4 bytes)
3. `Slice16` provides an implementation that uses a lookup table that uses 16 * 256 entries of the used width (e.g. for u32 thats 16 * 256 * 4 bytes)
1. `NoTable` doesn't use a lookup table, and thus minimizes binary size and memory usage.
2. `Table<1>` uses a lookup table with 256 entries (e.g. for u32 thats 256 * 4 bytes).
3. `Table<16>` uses a lookup table with 16 * 256 entries (e.g. for u32 thats 16 * 256 * 4 bytes).

These can be used by substituting `Crc<uxxx>` with e.g. `Crc<Slice16<uxxx>>`. The flavor for `Crc<uxxx>` is chosen based on three crate features:
`Table<1>` is the default implementation, but this can be overridden by specifying `I` in `Crc<W, I>`. E.g.: `Crc<u32, NoTable>`, `Crc<u64, Table<16>>`, ...

* no-table-mem-limit: Takes precedence over "bytewise-mem-limit" or "slice16-mem-limit"
* bytewise-mem-limit: Takes precedence over "slice16-mem-limit" and can be overridden by setting "no-table-mem-limit"
* slice16-mem-limit: Can be overridden by setting "bytewise-mem-limit" or "no-table-mem-limit"

If no feature is selected, the `Bytewise` flavor is used.

Note that these tables can bloat your binary size if you precalculate them at compiletime (this happens in `Crc::new`).
Choosing a crate like oncecell or lazystatic to compute them once at runtime may be preferable where binary size is a concern.
NOTE: Lookup tables will increase binary size if they're generated at compile-time. Wrapping `Crc` initialization in a `std::cell::OnceCell` may be preferable if binary size is a concern.

### Benchmark

Expand Down
30 changes: 15 additions & 15 deletions benches/bench.rs
Expand Up @@ -2,26 +2,26 @@ use crc::*;
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};

pub const BLUETOOTH: Crc<u8> = Crc::<u8>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_SLICE16: Crc<Slice16<u8>> = Crc::<Slice16<u8>>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_BYTEWISE: Crc<Bytewise<u8>> = Crc::<Bytewise<u8>>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_NOLOOKUP: Crc<NoTable<u8>> = Crc::<NoTable<u8>>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_SLICE16: Crc<u8, Table<16>> = Crc::<u8, Table<16>>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_BYTEWISE: Crc<u8, Table<1>> = Crc::<u8, Table<1>>::new(&CRC_8_BLUETOOTH);
pub const BLUETOOTH_NOLOOKUP: Crc<u8, NoTable> = Crc::<u8, NoTable>::new(&CRC_8_BLUETOOTH);
pub const X25: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_SDLC);
pub const X25_SLICE16: Crc<Slice16<u16>> = Crc::<Slice16<u16>>::new(&CRC_16_IBM_SDLC);
pub const X25_BYTEWISE: Crc<Bytewise<u16>> = Crc::<Bytewise<u16>>::new(&CRC_16_IBM_SDLC);
pub const X25_NOLOOKUP: Crc<NoTable<u16>> = Crc::<NoTable<u16>>::new(&CRC_16_IBM_SDLC);
pub const X25_SLICE16: Crc<u16, Table<16>> = Crc::<u16, Table<16>>::new(&CRC_16_IBM_SDLC);
pub const X25_BYTEWISE: Crc<u16, Table<1>> = Crc::<u16, Table<1>>::new(&CRC_16_IBM_SDLC);
pub const X25_NOLOOKUP: Crc<u16, NoTable> = Crc::<u16, NoTable>::new(&CRC_16_IBM_SDLC);
pub const ISCSI: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
pub const ISCSI_SLICE16: Crc<Slice16<u32>> = Crc::<Slice16<u32>>::new(&CRC_32_ISCSI);
pub const ISCSI_BYTEWISE: Crc<Bytewise<u32>> = Crc::<Bytewise<u32>>::new(&CRC_32_ISCSI);
pub const ISCSI_NOLOOKUP: Crc<NoTable<u32>> = Crc::<NoTable<u32>>::new(&CRC_32_ISCSI);
pub const ISCSI_SLICE16: Crc<u32, Table<16>> = Crc::<u32, Table<16>>::new(&CRC_32_ISCSI);
pub const ISCSI_BYTEWISE: Crc<u32, Table<1>> = Crc::<u32, Table<1>>::new(&CRC_32_ISCSI);
pub const ISCSI_NOLOOKUP: Crc<u32, NoTable> = Crc::<u32, NoTable>::new(&CRC_32_ISCSI);
pub const GSM_40: Crc<u64> = Crc::<u64>::new(&CRC_40_GSM);
pub const ECMA: Crc<u64> = Crc::<u64>::new(&CRC_64_ECMA_182);
pub const ECMA_SLICE16: Crc<Slice16<u64>> = Crc::<Slice16<u64>>::new(&CRC_64_ECMA_182);
pub const ECMA_BYTEWISE: Crc<Bytewise<u64>> = Crc::<Bytewise<u64>>::new(&CRC_64_ECMA_182);
pub const ECMA_NOLOOKUP: Crc<NoTable<u64>> = Crc::<NoTable<u64>>::new(&CRC_64_ECMA_182);
pub const ECMA_SLICE16: Crc<u64, Table<16>> = Crc::<u64, Table<16>>::new(&CRC_64_ECMA_182);
pub const ECMA_BYTEWISE: Crc<u64, Table<1>> = Crc::<u64, Table<1>>::new(&CRC_64_ECMA_182);
pub const ECMA_NOLOOKUP: Crc<u64, NoTable> = Crc::<u64, NoTable>::new(&CRC_64_ECMA_182);
pub const DARC: Crc<u128> = Crc::<u128>::new(&CRC_82_DARC);
pub const DARC_SLICE16: Crc<Slice16<u128>> = Crc::<Slice16<u128>>::new(&CRC_82_DARC);
pub const DARC_BYTEWISE: Crc<Bytewise<u128>> = Crc::<Bytewise<u128>>::new(&CRC_82_DARC);
pub const DARC_NOLOOKUP: Crc<NoTable<u128>> = Crc::<NoTable<u128>>::new(&CRC_82_DARC);
pub const DARC_SLICE16: Crc<u128, Table<16>> = Crc::<u128, Table<16>>::new(&CRC_82_DARC);
pub const DARC_BYTEWISE: Crc<u128, Table<1>> = Crc::<u128, Table<1>>::new(&CRC_82_DARC);
pub const DARC_NOLOOKUP: Crc<u128, NoTable> = Crc::<u128, NoTable>::new(&CRC_82_DARC);

static KB: usize = 1024;

Expand Down
100 changes: 4 additions & 96 deletions src/crc128.rs
Expand Up @@ -2,7 +2,6 @@ use crate::util::crc128;
use crc_catalog::Algorithm;

mod bytewise;
mod default;
mod nolookup;
mod slice16;

Expand Down Expand Up @@ -169,100 +168,9 @@ const fn update_slice16(

#[cfg(test)]
mod test {
use crate::{Bytewise, Crc, Implementation, NoTable, Slice16};
use crate::*;
use crc_catalog::{Algorithm, CRC_82_DARC};

#[test]
fn default_table_size() {
const TABLE_SIZE: usize = core::mem::size_of::<<u128 as Implementation>::Table>();
const BYTES_PER_ENTRY: usize = 16;
#[cfg(all(
feature = "no-table-mem-limit",
feature = "bytewise-mem-limit",
feature = "slice16-mem-limit"
))]
{
const EXPECTED: usize = 0;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}
#[cfg(all(
feature = "no-table-mem-limit",
feature = "bytewise-mem-limit",
not(feature = "slice16-mem-limit")
))]
{
const EXPECTED: usize = 0;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}
#[cfg(all(
feature = "no-table-mem-limit",
not(feature = "bytewise-mem-limit"),
feature = "slice16-mem-limit"
))]
{
const EXPECTED: usize = 0;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}
#[cfg(all(
feature = "no-table-mem-limit",
not(feature = "bytewise-mem-limit"),
not(feature = "slice16-mem-limit")
))]
{
const EXPECTED: usize = 0;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}

#[cfg(all(
not(feature = "no-table-mem-limit"),
feature = "bytewise-mem-limit",
feature = "slice16-mem-limit"
))]
{
const EXPECTED: usize = 256 * BYTES_PER_ENTRY;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}
#[cfg(all(
not(feature = "no-table-mem-limit"),
feature = "bytewise-mem-limit",
not(feature = "slice16-mem-limit")
))]
{
const EXPECTED: usize = 256 * BYTES_PER_ENTRY;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}

#[cfg(all(
not(feature = "no-table-mem-limit"),
not(feature = "bytewise-mem-limit"),
feature = "slice16-mem-limit"
))]
{
const EXPECTED: usize = 256 * 16 * BYTES_PER_ENTRY;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}

#[cfg(all(
not(feature = "no-table-mem-limit"),
not(feature = "bytewise-mem-limit"),
not(feature = "slice16-mem-limit")
))]
{
const EXPECTED: usize = 256 * BYTES_PER_ENTRY;
let _ = EXPECTED;
const _: () = assert!(EXPECTED == TABLE_SIZE);
}
let _ = TABLE_SIZE;
let _ = BYTES_PER_ENTRY;
}

/// Test this optimized version against the well known implementation to ensure correctness
#[test]
fn correctness() {
Expand Down Expand Up @@ -291,9 +199,9 @@ mod test {

for alg in algs_to_test {
for data in data {
let crc_slice16 = Crc::<Slice16<u128>>::new(alg);
let crc_nolookup = Crc::<NoTable<u128>>::new(alg);
let expected = Crc::<Bytewise<u128>>::new(alg).checksum(data.as_bytes());
let crc_slice16 = Crc::<u128, Table<16>>::new(alg);
let crc_nolookup = Crc::<u128, NoTable>::new(alg);
let expected = Crc::<u128, Table<1>>::new(alg).checksum(data.as_bytes());

// Check that doing all at once works as expected
assert_eq!(crc_slice16.checksum(data.as_bytes()), expected);
Expand Down
19 changes: 11 additions & 8 deletions src/crc128/bytewise.rs
@@ -1,12 +1,15 @@
use crate::table::crc128_table;
use crate::{Algorithm, Bytewise, Crc, Digest};
use crate::*;

use super::{finalize, init, update_bytewise};

impl Crc<Bytewise<u128>> {
impl Crc<u128, Table<1>> {
pub const fn new(algorithm: &'static Algorithm<u128>) -> Self {
let table = crc128_table(algorithm.width, algorithm.poly, algorithm.refin);
Self { algorithm, table }
Self {
algorithm,
data: [table],
}
}

pub const fn checksum(&self, bytes: &[u8]) -> u128 {
Expand All @@ -16,10 +19,10 @@ impl Crc<Bytewise<u128>> {
}

const fn update(&self, crc: u128, bytes: &[u8]) -> u128 {
update_bytewise(crc, self.algorithm.refin, &self.table, bytes)
update_bytewise(crc, self.algorithm.refin, &self.data[0], bytes)
}

pub const fn digest(&self) -> Digest<Bytewise<u128>> {
pub const fn digest(&self) -> Digest<u128, Table<1>> {
self.digest_with_initial(self.algorithm.init)
}

Expand All @@ -28,14 +31,14 @@ impl Crc<Bytewise<u128>> {
/// This overrides the initial value specified by the algorithm.
/// The effects of the algorithm's properties `refin` and `width`
/// are applied to the custom initial value.
pub const fn digest_with_initial(&self, initial: u128) -> Digest<Bytewise<u128>> {
pub const fn digest_with_initial(&self, initial: u128) -> Digest<u128, Table<1>> {
let value = init(self.algorithm, initial);
Digest::new(self, value)
}
}

impl<'a> Digest<'a, Bytewise<u128>> {
const fn new(crc: &'a Crc<Bytewise<u128>>, value: u128) -> Self {
impl<'a> Digest<'a, u128, Table<1>> {
const fn new(crc: &'a Crc<u128, Table<1>>, value: u128) -> Self {
Digest { crc, value }
}

Expand Down

0 comments on commit b2c638c

Please sign in to comment.