From a187e325994ec85405c9f7d5e05e71eac13c2c37 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Tue, 11 Nov 2025 15:13:10 -0500 Subject: [PATCH 1/4] Add no_std and WASM support This commit adds comprehensive no_std compatibility while maintaining 100% backward compatibility with existing std-based code. Key changes: - Add optional `std` feature (enabled by default) - Add `alloc` and `cache` features for heap allocation in no_std - Use spin crate for no_std synchronization (Once, RwLock, Mutex) - Use hashbrown for no_std HashMap when cache feature enabled - Fix UB in algorithm.rs (potential out-of-bounds read) - Fix Miri memory leaks in software fallback caching - Add dual-path feature detection (runtime with std, compile-time without) - Add comprehensive test coverage (tests/no_std_tests.rs, tests/wasm_tests.rs) - Add CI workflows for embedded (ARM, RISC-V) and WASM targets - Update all modules to use core/alloc primitives where appropriate Tested on: - Embedded: thumbv7em, thumbv8m, riscv32 - WASM: wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2 - All existing x86/x86_64/aarch64 targets - Miri validation passes (no memory leaks) --- .github/workflows/tests.yml | 81 +++- .gitignore | 2 +- Cargo.lock | 105 +++++- Cargo.toml | 21 +- benches/benchmark.rs | 27 +- libcrc_fast.h | 74 ++++ src/algorithm.rs | 74 ++-- src/arch/aarch64/aes.rs | 2 +- src/arch/aarch64/aes_sha3.rs | 2 +- src/arch/mod.rs | 16 +- src/arch/software.rs | 282 ++++++++++---- src/arch/vpclmulqdq.rs | 4 +- src/arch/x86/sse.rs | 4 +- src/arch/x86_64/avx512.rs | 2 +- src/arch/x86_64/avx512_vpclmulqdq.rs | 4 +- src/bin/checksum.rs | 11 +- src/cache.rs | 124 ++++-- src/crc32/fusion/aarch64/iscsi/crc_pmull.rs | 2 +- .../fusion/aarch64/iscsi/crc_pmull_sha3.rs | 2 +- .../fusion/aarch64/iso_hdlc/crc_pmull.rs | 2 +- .../fusion/aarch64/iso_hdlc/crc_pmull_sha3.rs | 2 +- src/crc32/fusion/aarch64/mod.rs | 17 +- src/crc32/fusion/x86/iscsi/sse_pclmulqdq.rs | 4 +- src/crc32/fusion/x86/mod.rs | 8 +- src/crc64/utils.rs | 25 +- src/enums.rs | 6 +- src/feature_detection.rs | 103 ++++- src/ffi.rs | 353 +++++++++++++++--- src/generate.rs | 4 +- src/lib.rs | 95 ++++- src/test/future_proof_tests.rs | 9 +- src/test/mod.rs | 7 +- src/traits.rs | 2 +- tests/checksum_integration_tests.rs | 20 +- tests/no_std_tests.rs | 217 +++++++++++ tests/wasm_tests.rs | 157 ++++++++ 36 files changed, 1580 insertions(+), 290 deletions(-) create mode 100644 tests/no_std_tests.rs create mode 100644 tests/wasm_tests.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index da73749..a5fc2e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -146,4 +146,83 @@ jobs: run: cargo miri nextest run --all-features -j${{ steps.cores.outputs.count }} - name: Run Miri tests (serial) if: steps.cores.outputs.use_nextest == 'false' - run: cargo miri test --all-features \ No newline at end of file + run: cargo miri test --all-features + + test-no-std: + name: Test no_std + runs-on: ubuntu-latest + strategy: + matrix: + target: + - thumbv7em-none-eabihf # ARM Cortex-M4F/M7F + - thumbv8m.main-none-eabihf # ARM Cortex-M33/M35P + - riscv32imac-unknown-none-elf # RISC-V 32-bit + rust-toolchain: + - "1.81" # minimum for this crate + - "stable" + - "nightly" + steps: + - uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust + - uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0 + with: + toolchain: ${{ matrix.rust-toolchain }} + target: ${{ matrix.target }} + components: rustfmt, clippy + cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }} + - name: Check no_std (no features) + run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib + - name: Check no_std with alloc + run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib + - name: Check no_std with cache + run: cargo check --target ${{ matrix.target }} --no-default-features --features cache,panic-handler --lib + - name: Run no_std tests (on host with std test harness) + run: cargo test --test no_std_tests + + test-wasm: + name: Test WASM + runs-on: ubuntu-latest + strategy: + matrix: + include: + # WASM 1.0/2.0 (32-bit) - all toolchains + - target: wasm32-unknown-unknown + rust-toolchain: "1.81" + - target: wasm32-unknown-unknown + rust-toolchain: "stable" + - target: wasm32-unknown-unknown + rust-toolchain: "nightly" + # WASI preview 1 (32-bit) - all toolchains + - target: wasm32-wasip1 + rust-toolchain: "1.81" + - target: wasm32-wasip1 + rust-toolchain: "stable" + - target: wasm32-wasip1 + rust-toolchain: "nightly" + # WASI preview 2 (32-bit) - nightly only (experimental) + - target: wasm32-wasip2 + rust-toolchain: "nightly" + # Note: wasm64-unknown-unknown removed - not consistently available in nightly + steps: + - uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust + - uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0 + with: + toolchain: ${{ matrix.rust-toolchain }} + target: ${{ matrix.target }} + components: rustfmt, clippy + cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }} + - name: Check WASM (no features) + run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib + - name: Check WASM with alloc + run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib + - name: Check WASM with cache + run: cargo check --target ${{ matrix.target }} --no-default-features --features cache,panic-handler --lib + - name: Build WASM release + run: cargo build --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib --release + - name: Run WASM tests (on host with std test harness) + run: cargo test --test wasm_tests + - if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }} + name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }} + name: Build WASM package with wasm-pack + run: wasm-pack build --target web --no-default-features --features alloc,panic-handler diff --git a/.gitignore b/.gitignore index d97ced5..d85bfaa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ .idea .DS_Store .git -.vscode \ No newline at end of file +.vscode diff --git a/Cargo.lock b/Cargo.lock index e9ce629..60884ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anes" version = "0.1.6" @@ -79,15 +85,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.19.0" @@ -119,6 +116,16 @@ dependencies = [ "toml", ] +[[package]] +name = "cc" +version = "1.2.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -208,10 +215,13 @@ dependencies = [ "crc", "criterion", "digest", + "hashbrown", "indexmap", "rand", "regex", "rustversion", + "spin", + "wasm-bindgen-test", ] [[package]] @@ -294,7 +304,6 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", "crypto-common", ] @@ -326,6 +335,18 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "generic-array" version = "0.14.9" @@ -364,6 +385,11 @@ name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -436,6 +462,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -688,6 +724,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "strsim" version = "0.11.1" @@ -823,6 +871,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.105" @@ -855,6 +916,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-bindgen-test" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc379bfb624eb59050b509c13e77b4eb53150c350db69628141abce842f2373" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085b2df989e1e6f9620c1311df6c996e83fe16f57792b272ce1e024ac16a90f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "web-sys" version = "0.3.82" diff --git a/Cargo.toml b/Cargo.toml index 9533673..f079a96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ bench = true [dependencies] crc = "3" -digest = { version = "0.10", features = ["alloc"] } +digest = { version = "0.10", optional = true, default-features = false, features = ["alloc"] } # will be removed once Rust 1.89 is the minimum supported version rustversion = "1.0" @@ -31,11 +31,18 @@ rustversion = "1.0" # constrain indexmap (transitive) to a version compatible with Rust 1.81.0 indexmap = { version = ">=2.11.0, <2.12.0", optional = true } +# no_std support +# spin is always required for no_std builds (feature detection synchronization) +spin = { version = "0.10.0", default-features = false, features = ["once", "rwlock", "mutex", "spin_mutex"] } +# hashbrown is only needed when caching is enabled in no_std +hashbrown = { version = "0.16.0", optional = true } + [dev-dependencies] criterion = "0.7" cbindgen = "0.29" rand = "0.9" regex = "1.12" +wasm-bindgen-test = "0.3" # lto=true has a big improvement in performance [profile.release] @@ -62,12 +69,16 @@ required-features = ["cli"] [[bench]] name = "benchmark" harness = false +required-features = ["std"] [features] -default = ["std"] -std = [] +default = ["std", "panic-handler"] +std = ["alloc"] # std implies alloc is available cli = ["std"] -alloc = [] +alloc = ["digest"] # marker feature for heap allocation support +cache = ["alloc", "hashbrown"] # caching requires alloc + hashbrown HashMap +ffi = ["std"] +panic-handler = [] # Provides panic handler for no_std library checks (disable in binaries) # the features below are deprecated, aren't in use, and will be removed in the next MAJOR version (v2) vpclmulqdq = [] # deprecated, VPCLMULQDQ stabilized in Rust 1.89.0 @@ -87,4 +98,4 @@ rustdoc-args = ["--cfg", "docsrs"] [[test]] name = "checksum_integration_tests" path = "tests/checksum_integration_tests.rs" -required-features = ["cli"] \ No newline at end of file +required-features = ["cli"] diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 82ee13a..1d4647d 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -59,11 +59,8 @@ fn create_aligned_data(input: &[u8]) -> Vec { // Size of our target alignment structure let align_size = std::mem::size_of::<[[u64; 4]; 2]>(); // 64 bytes - // Create a vector with padding to ensure we can find a properly aligned position - let mut padded = Vec::with_capacity(input.len() + align_size); - - // Fill with zeros initially to reach needed capacity - padded.resize(input.len() + align_size, 0); + // Create a zero-filled vector with padding to ensure we can find a properly aligned position + let mut padded = vec![0; input.len() + align_size]; // Find the first address that satisfies our alignment let start_addr = padded.as_ptr() as usize; @@ -87,7 +84,7 @@ fn bench_crc32(c: &mut Criterion) { ); for (size_name, size) in SIZES { - let buf = create_aligned_data(&*random_data(*size)); + let buf = create_aligned_data(&random_data(*size)); let (part1, rest) = buf.split_at(buf.len() / 4); let (part2, rest) = rest.split_at(rest.len() / 3); @@ -115,10 +112,10 @@ fn bench_crc32(c: &mut Criterion) { b.iter(|| { black_box({ let mut digest = crc_fast::Digest::new(*algorithm); - digest.update(&part1); - digest.update(&part2); - digest.update(&part3); - digest.update(&part4); + digest.update(part1); + digest.update(part2); + digest.update(part3); + digest.update(part4); digest.finalize() }) }) @@ -137,7 +134,7 @@ fn bench_crc64(c: &mut Criterion) { let mut group = c.benchmark_group("CRC-64"); for (size_name, size) in SIZES { - let buf = create_aligned_data(&*random_data(*size)); + let buf = create_aligned_data(&random_data(*size)); let (part1, rest) = buf.split_at(buf.len() / 4); let (part2, rest) = rest.split_at(rest.len() / 3); @@ -165,10 +162,10 @@ fn bench_crc64(c: &mut Criterion) { b.iter(|| { black_box({ let mut digest = crc_fast::Digest::new(*algorithm); - digest.update(&part1); - digest.update(&part2); - digest.update(&part3); - digest.update(&part4); + digest.update(part1); + digest.update(part2); + digest.update(part3); + digest.update(part4); digest.finalize() }) }) diff --git a/libcrc_fast.h b/libcrc_fast.h index 918e8d6..f6803e7 100644 --- a/libcrc_fast.h +++ b/libcrc_fast.h @@ -9,6 +9,44 @@ #include #include +/** + * Error codes for FFI operations + */ +typedef enum CrcFastError { + /** + * Operation completed successfully + */ + Success = 0, + /** + * Lock was poisoned (thread panicked while holding lock) + */ + LockPoisoned = 1, + /** + * Null pointer was passed where non-null required + */ + NullPointer = 2, + /** + * Invalid key count for CRC parameters + */ + InvalidKeyCount = 3, + /** + * Unsupported CRC width (must be 32 or 64) + */ + UnsupportedWidth = 4, + /** + * Invalid UTF-8 string + */ + InvalidUtf8 = 5, + /** + * File I/O error + */ + IoError = 6, + /** + * Internal string conversion error + */ + StringConversionError = 7, +} CrcFastError; + /** * The supported CRC algorithms */ @@ -72,6 +110,23 @@ typedef struct CrcFastParams { extern "C" { #endif // __cplusplus +/** + * Gets the last error that occurred in the current thread + * Returns CrcFastError::Success if no error has occurred + */ +enum CrcFastError crc_fast_get_last_error(void); + +/** + * Clears the last error for the current thread + */ +void crc_fast_clear_error(void); + +/** + * Gets a human-readable error message for the given error code + * Returns a pointer to a static string (do not free) + */ +const char *crc_fast_error_message(enum CrcFastError error); + /** * Creates a new Digest to compute CRC checksums using algorithm */ @@ -85,6 +140,8 @@ struct CrcFastDigestHandle *crc_fast_digest_new_with_init_state(enum CrcFastAlgo /** * Creates a new Digest to compute CRC checksums using custom parameters + * Returns NULL if parameters are invalid (invalid key count or null pointer) + * Call crc_fast_get_last_error() to get the specific error code */ struct CrcFastDigestHandle *crc_fast_digest_new_with_params(struct CrcFastParams params); @@ -95,6 +152,7 @@ void crc_fast_digest_update(struct CrcFastDigestHandle *handle, const char *data /** * Calculates the CRC checksum for data that's been written to the Digest + * Returns 0 on error (e.g. null handle) */ uint64_t crc_fast_digest_finalize(struct CrcFastDigestHandle *handle); @@ -110,6 +168,7 @@ void crc_fast_digest_reset(struct CrcFastDigestHandle *handle); /** * Finalize and reset the Digest in one operation + * Returns 0 on error (e.g. null handle) */ uint64_t crc_fast_digest_finalize_reset(struct CrcFastDigestHandle *handle); @@ -121,21 +180,26 @@ void crc_fast_digest_combine(struct CrcFastDigestHandle *handle1, /** * Gets the amount of data processed by the Digest so far + * Returns 0 on error (e.g. null handle) */ uint64_t crc_fast_digest_get_amount(struct CrcFastDigestHandle *handle); /** * Gets the current state of the Digest + * Returns 0 on error (e.g. null handle) */ uint64_t crc_fast_digest_get_state(struct CrcFastDigestHandle *handle); /** * Helper method to calculate a CRC checksum directly for a string using algorithm + * Returns 0 on error (e.g. null data pointer) */ uint64_t crc_fast_checksum(enum CrcFastAlgorithm algorithm, const char *data, uintptr_t len); /** * Helper method to calculate a CRC checksum directly for data using custom parameters + * Returns 0 if parameters are invalid or data is null + * Call crc_fast_get_last_error() to get the specific error code */ uint64_t crc_fast_checksum_with_params(struct CrcFastParams params, const char *data, @@ -143,6 +207,8 @@ uint64_t crc_fast_checksum_with_params(struct CrcFastParams params, /** * Helper method to just calculate a CRC checksum directly for a file using algorithm + * Returns 0 if path is null or file I/O fails + * Call crc_fast_get_last_error() to get the specific error code */ uint64_t crc_fast_checksum_file(enum CrcFastAlgorithm algorithm, const uint8_t *path_ptr, @@ -150,6 +216,8 @@ uint64_t crc_fast_checksum_file(enum CrcFastAlgorithm algorithm, /** * Helper method to calculate a CRC checksum directly for a file using custom parameters + * Returns 0 if parameters are invalid, path is null, or file I/O fails + * Call crc_fast_get_last_error() to get the specific error code */ uint64_t crc_fast_checksum_file_with_params(struct CrcFastParams params, const uint8_t *path_ptr, @@ -165,6 +233,8 @@ uint64_t crc_fast_checksum_combine(enum CrcFastAlgorithm algorithm, /** * Combine two CRC checksums using custom parameters + * Returns 0 if parameters are invalid + * Call crc_fast_get_last_error() to get the specific error code */ uint64_t crc_fast_checksum_combine_with_params(struct CrcFastParams params, uint64_t checksum1, @@ -173,6 +243,7 @@ uint64_t crc_fast_checksum_combine_with_params(struct CrcFastParams params, /** * Returns the custom CRC parameters for a given set of Rocksoft CRC parameters + * If width is not 32 or 64, sets error to UnsupportedWidth */ struct CrcFastParams crc_fast_get_custom_params(const char *name_ptr, uint8_t width, @@ -184,11 +255,14 @@ struct CrcFastParams crc_fast_get_custom_params(const char *name_ptr, /** * Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm + * Returns NULL if string conversion fails + * Call crc_fast_get_last_error() to get the specific error code */ const char *crc_fast_get_calculator_target(enum CrcFastAlgorithm algorithm); /** * Gets the version of this library + * Returns a pointer to "unknown" if version string is invalid */ const char *crc_fast_get_version(void); diff --git a/src/algorithm.rs b/src/algorithm.rs index e3a83b9..1b456b5 100644 --- a/src/algorithm.rs +++ b/src/algorithm.rs @@ -400,8 +400,11 @@ where // Use the shared function to handle the last two chunks let final_xmm7 = get_last_two_xmms::( - &data[CRC_CHUNK_SIZE..], - remaining_len, + DataRegion { + full_data: data, + offset: CRC_CHUNK_SIZE, + remaining: remaining_len, + }, xmm7, keys, reflector, @@ -461,8 +464,11 @@ where if remaining_len > 0 { // Use the shared get_last_two_xmms function to handle the remaining bytes xmm7 = get_last_two_xmms::( - &data[current_pos..], - remaining_len, + DataRegion { + full_data: data, + offset: current_pos, + remaining: remaining_len, + }, xmm7, keys, reflector, @@ -475,8 +481,21 @@ where W::perform_final_reduction(xmm7, state.reflected, keys, ops) } -/// Handle the last two chunks of data (for small inputs) -/// This shared implementation works for both CRC-32 and CRC-64 +/// Data region descriptor for overlapping SIMD reads in CRC processing +struct DataRegion<'a> { + full_data: &'a [u8], + offset: usize, + remaining: usize, +} + +/// Handle the last two chunks of data using an overlapping SIMD read +/// +/// # Safety +/// +/// - `region.full_data` must contain at least `region.offset + region.remaining` bytes +/// - `region.offset` must be >= `CRC_CHUNK_SIZE` (16 bytes) +/// - `region.remaining` must be in range 1..=15 +/// - Caller must ensure appropriate SIMD features are available #[inline] #[cfg_attr( any(target_arch = "x86", target_arch = "x86_64"), @@ -484,8 +503,7 @@ where )] #[cfg_attr(target_arch = "aarch64", target_feature(enable = "aes"))] unsafe fn get_last_two_xmms( - data: &[u8], - remaining_len: usize, + region: DataRegion, current_state: T::Vector, keys: [u64; 23], reflector: &Reflector, @@ -495,28 +513,20 @@ unsafe fn get_last_two_xmms( where T::Vector: Copy, { - // Create coefficient for folding operations - let coefficient = W::create_coefficient(keys[2], keys[1], reflected, ops); + debug_assert!(region.offset >= CRC_CHUNK_SIZE); + debug_assert!(region.remaining > 0 && region.remaining < CRC_CHUNK_SIZE); + debug_assert!(region.offset + region.remaining <= region.full_data.len()); + let coefficient = W::create_coefficient(keys[2], keys[1], reflected, ops); let const_mask = ops.set_all_bytes(0x80); - - // Get table pointer and offset based on CRC width - let (table_ptr, offset) = W::get_last_bytes_table_ptr(reflected, remaining_len); + let (table_ptr, offset) = W::get_last_bytes_table_ptr(reflected, region.remaining); if reflected { - // For reflected mode (CRC-32r, CRC-64r) - - // Load the remaining data - // Special pointer arithmetic to match the original implementation - let xmm1 = ops.load_bytes(data.as_ptr().sub(CRC_CHUNK_SIZE).add(remaining_len)); // DON: looks correct - - // Load the shuffle mask + // Overlapping read: loads tail of previous chunk + remaining data + let read_offset = region.offset - CRC_CHUNK_SIZE + region.remaining; + let xmm1 = ops.load_bytes(region.full_data.as_ptr().add(read_offset)); let mut xmm0 = ops.load_bytes(table_ptr.add(offset)); - - // Apply different shuffle operations let shuffled = ops.shuffle_bytes(current_state, xmm0); - - // Create masked version for shuffling xmm0 = ops.xor_vectors(xmm0, const_mask); let shuffled_masked = ops.shuffle_bytes(current_state, xmm0); @@ -547,29 +557,17 @@ where temp_state.value } else { - // For non-reflected mode (CRC-32f, CRC-64f) - - // Load the remaining data and apply reflection if needed - let data_ptr = data.as_ptr().sub(CRC_CHUNK_SIZE).add(remaining_len); - let mut xmm1 = ops.load_bytes(data_ptr); + let read_offset = region.offset - CRC_CHUNK_SIZE + region.remaining; + let mut xmm1 = ops.load_bytes(region.full_data.as_ptr().add(read_offset)); - // Apply reflection if in forward mode if let Reflector::ForwardReflector { smask } = reflector { xmm1 = ops.shuffle_bytes(xmm1, *smask); } - // Load the shuffle mask let xmm0 = ops.load_bytes(table_ptr.add(offset)); - - // Apply initial shuffle let shuffled = ops.shuffle_bytes(current_state, xmm0); - - // Create masked version for another shuffle let xmm0_masked = ops.xor_vectors(xmm0, const_mask); - let shuffled_masked = ops.shuffle_bytes(current_state, xmm0_masked); - - // Blend the shuffled values using the masked shuffle as the mask let xmm2_blended = ops.blend_vectors(xmm1, shuffled, xmm0_masked); // Create a temporary state for folding diff --git a/src/arch/aarch64/aes.rs b/src/arch/aarch64/aes.rs index 8b9abb7..ec59d3f 100644 --- a/src/arch/aarch64/aes.rs +++ b/src/arch/aarch64/aes.rs @@ -6,7 +6,7 @@ #![cfg(target_arch = "aarch64")] use crate::traits::ArchOps; -use std::arch::aarch64::*; +use core::arch::aarch64::*; // Tier-specific ArchOps implementations for AArch64 diff --git a/src/arch/aarch64/aes_sha3.rs b/src/arch/aarch64/aes_sha3.rs index bc3d365..2d0a26a 100644 --- a/src/arch/aarch64/aes_sha3.rs +++ b/src/arch/aarch64/aes_sha3.rs @@ -7,7 +7,7 @@ use crate::arch::aarch64::aes::Aarch64AesOps; use crate::traits::ArchOps; -use std::arch::aarch64::*; +use core::arch::aarch64::*; /// AArch64 AES+SHA3 tier - delegates to AES tier and overrides XOR3 operations /// Provides EOR3 instruction for optimal XOR3 performance diff --git a/src/arch/mod.rs b/src/arch/mod.rs index b004162..06a7e7d 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -4,7 +4,7 @@ //! //! It dispatches to the appropriate architecture-specific implementation -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", feature = "std"))] use std::arch::is_aarch64_feature_detected; use crate::CrcParams; @@ -39,7 +39,13 @@ pub(crate) unsafe fn update(state: u64, bytes: &[u8], params: CrcParams) -> u64 ArchOpsInstance::Aarch64AesSha3(ops) => update_aarch64_aes_sha3(state, bytes, params, *ops), ArchOpsInstance::Aarch64Aes(ops) => update_aarch64_aes(state, bytes, params, *ops), ArchOpsInstance::SoftwareFallback => { - if !is_aarch64_feature_detected!("aes") || !is_aarch64_feature_detected!("neon") { + #[cfg(feature = "std")] + let has_features = + is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("neon"); + #[cfg(not(feature = "std"))] + let has_features = cfg!(target_feature = "aes") && cfg!(target_feature = "neon"); + + if !has_features { #[cfg(any(not(target_feature = "aes"), not(target_feature = "neon")))] { // Use software implementation when no SIMD support is available @@ -183,7 +189,7 @@ mod tests { let actual = unsafe { update( config.get_init(), - &*create_aligned_data(TEST_256_BYTES_STRING), + &create_aligned_data(TEST_256_BYTES_STRING), *config.get_params(), ) ^ config.get_xorout() }; @@ -207,7 +213,7 @@ mod tests { let actual = unsafe { update( config.get_init(), - &*create_aligned_data(test_string), + &create_aligned_data(test_string), *config.get_params(), ) ^ config.get_xorout() }; @@ -231,7 +237,7 @@ mod tests { let actual = unsafe { update( config.get_init(), - &*create_aligned_data(test_string), + &create_aligned_data(test_string), *config.get_params(), ) ^ config.get_xorout() }; diff --git a/src/arch/software.rs b/src/arch/software.rs index 8a5957d..ef0320b 100644 --- a/src/arch/software.rs +++ b/src/arch/software.rs @@ -5,10 +5,51 @@ use crate::consts::CRC_64_NVME; use crate::CrcAlgorithm; use crate::CrcParams; -use crc::{Algorithm, Table}; +#[cfg(feature = "alloc")] +use crc::Algorithm; +use crc::Table; + +// Caching for custom CRC algorithms to prevent repeated memory leaks +#[cfg(feature = "alloc")] +#[cfg(feature = "std")] use std::collections::HashMap; +#[cfg(feature = "alloc")] +#[cfg(feature = "std")] use std::sync::{Mutex, OnceLock}; +#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "cache"))] +use hashbrown::HashMap; +#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "cache"))] +use spin::{Mutex, Once}; + +// Cache key types for custom algorithms +#[cfg(feature = "alloc")] +#[cfg(any(feature = "std", feature = "cache"))] +type Crc32Key = (u32, u32, bool, bool, u32, u32); +#[cfg(feature = "alloc")] +#[cfg(any(feature = "std", feature = "cache"))] +type Crc64Key = (u64, u64, bool, bool, u64, u64); + +// Global caches for custom algorithms (std version) +#[cfg(feature = "alloc")] +#[cfg(feature = "std")] +static CUSTOM_CRC32_CACHE: OnceLock>>> = + OnceLock::new(); +#[cfg(feature = "alloc")] +#[cfg(feature = "std")] +static CUSTOM_CRC64_CACHE: OnceLock>>> = + OnceLock::new(); + +// Global caches for custom algorithms (no_std + cache version) +#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "cache"))] +static CUSTOM_CRC32_CACHE: Once>>> = Once::new(); +#[cfg(feature = "alloc")] +#[cfg(all(not(feature = "std"), feature = "cache"))] +static CUSTOM_CRC64_CACHE: Once>>> = Once::new(); + #[allow(unused)] const RUST_CRC32_AIXM: crc::Crc> = crc::Crc::>::new(&crc::CRC_32_AIXM); @@ -80,14 +121,6 @@ const RUST_CRC64_WE: crc::Crc> = crc::Crc::>::new( #[allow(unused)] const RUST_CRC64_XZ: crc::Crc> = crc::Crc::>::new(&crc::CRC_64_XZ); -static CUSTOM_CRC32_CACHE: OnceLock>>> = - OnceLock::new(); -static CUSTOM_CRC64_CACHE: OnceLock>>> = - OnceLock::new(); - -type Crc32Key = (u32, u32, bool, bool, u32, u32); -type Crc64Key = (u64, u64, bool, bool, u64, u64); - #[allow(unused)] // Dispatch function that handles the generic case pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { @@ -107,34 +140,94 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc32Mpeg2 => RUST_CRC32_MPEG_2, CrcAlgorithm::Crc32Xfer => RUST_CRC32_XFER, CrcAlgorithm::Crc32Custom => { - let cache = CUSTOM_CRC32_CACHE.get_or_init(|| Mutex::new(HashMap::new())); - let mut cache = cache.lock().unwrap(); - - // Create a key from params that uniquely identifies this algorithm - let key: Crc32Key = ( - params.poly as u32, - params.init as u32, - params.refin, - params.refout, - params.xorout as u32, - params.check as u32, - ); - - let static_algorithm = cache.entry(key).or_insert_with(|| { - let algorithm = Algorithm { - width: params.width, - poly: params.poly as u32, - init: params.init as u32, - refin: params.refin, - refout: params.refout, - xorout: params.xorout as u32, - check: params.check as u32, - residue: 0x00000000, - }; - Box::leak(Box::new(algorithm)) - }); - - crc::Crc::>::new(static_algorithm) + #[cfg(feature = "alloc")] + { + extern crate alloc; + use alloc::boxed::Box; + + // Use cache if std or cache feature is enabled + #[cfg(any(feature = "std", feature = "cache"))] + { + let key: Crc32Key = ( + params.poly as u32, + params.init as u32, + params.refin, + params.refout, + params.xorout as u32, + params.check as u32, + ); + + #[cfg(feature = "std")] + { + let cache = + CUSTOM_CRC32_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let mut cache_guard = cache.lock().unwrap(); + + let static_algorithm = + cache_guard.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly as u32, + init: params.init as u32, + refin: params.refin, + refout: params.refout, + xorout: params.xorout as u32, + check: params.check as u32, + residue: 0x00000000, + }; + Box::leak(Box::new(algorithm)) + }); + + crc::Crc::>::new(static_algorithm) + } + + #[cfg(all(not(feature = "std"), feature = "cache"))] + { + let cache = + CUSTOM_CRC32_CACHE.call_once(|| Mutex::new(HashMap::new())); + let mut cache_guard = cache.lock(); + + let static_algorithm = + cache_guard.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly as u32, + init: params.init as u32, + refin: params.refin, + refout: params.refout, + xorout: params.xorout as u32, + check: params.check as u32, + residue: 0x00000000, + }; + Box::leak(Box::new(algorithm)) + }); + + crc::Crc::>::new(static_algorithm) + } + } + + // Without cache, just leak (no_std without cache feature) + #[cfg(not(any(feature = "std", feature = "cache")))] + { + let algorithm: Algorithm = Algorithm { + width: params.width, + poly: params.poly as u32, + init: params.init as u32, + refin: params.refin, + refout: params.refout, + xorout: params.xorout as u32, + check: params.check as u32, + residue: 0x00000000, // unused in this context + }; + + // ugly, but the crc crate is difficult to work with... + let static_algorithm = Box::leak(Box::new(algorithm)); + + crc::Crc::>::new(static_algorithm) + } + } + #[cfg(not(feature = "alloc"))] + panic!("Custom CRC parameters require the 'alloc' feature") } _ => panic!("Invalid algorithm for u32 CRC"), }; @@ -150,33 +243,94 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc64We => RUST_CRC64_WE, CrcAlgorithm::Crc64Xz => RUST_CRC64_XZ, CrcAlgorithm::Crc64Custom => { - let cache = CUSTOM_CRC64_CACHE.get_or_init(|| Mutex::new(HashMap::new())); - let mut cache = cache.lock().unwrap(); - - let key: Crc64Key = ( - params.poly, - params.init, - params.refin, - params.refout, - params.xorout, - params.check, - ); - - let static_algorithm = cache.entry(key).or_insert_with(|| { - let algorithm = Algorithm { - width: params.width, - poly: params.poly, - init: params.init, - refin: params.refin, - refout: params.refout, - xorout: params.xorout, - check: params.check, - residue: 0x0000000000000000, - }; - Box::leak(Box::new(algorithm)) - }); - - crc::Crc::>::new(static_algorithm) + #[cfg(feature = "alloc")] + { + extern crate alloc; + use alloc::boxed::Box; + + // Use cache if std or cache feature is enabled + #[cfg(any(feature = "std", feature = "cache"))] + { + let key: Crc64Key = ( + params.poly, + params.init, + params.refin, + params.refout, + params.xorout, + params.check, + ); + + #[cfg(feature = "std")] + { + let cache = + CUSTOM_CRC64_CACHE.get_or_init(|| Mutex::new(HashMap::new())); + let mut cache_guard = cache.lock().unwrap(); + + let static_algorithm = + cache_guard.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + residue: 0x0000000000000000, + }; + Box::leak(Box::new(algorithm)) + }); + + crc::Crc::>::new(static_algorithm) + } + + #[cfg(all(not(feature = "std"), feature = "cache"))] + { + let cache = + CUSTOM_CRC64_CACHE.call_once(|| Mutex::new(HashMap::new())); + let mut cache_guard = cache.lock(); + + let static_algorithm = + cache_guard.entry(key).or_insert_with(|| { + let algorithm = Algorithm { + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + residue: 0x0000000000000000, + }; + Box::leak(Box::new(algorithm)) + }); + + crc::Crc::>::new(static_algorithm) + } + } + + // Without cache, just leak (no_std without cache feature) + #[cfg(not(any(feature = "std", feature = "cache")))] + { + let algorithm: Algorithm = Algorithm { + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + residue: 0x0000000000000000, // unused in this context + }; + + // ugly, but the crc crate is difficult to work with... + let static_algorithm = Box::leak(Box::new(algorithm)); + + crc::Crc::>::new(static_algorithm) + } + } + #[cfg(not(feature = "alloc"))] + panic!("Custom CRC parameters require the 'alloc' feature") } _ => panic!("Invalid algorithm for u64 CRC"), }; diff --git a/src/arch/vpclmulqdq.rs b/src/arch/vpclmulqdq.rs index 632fc65..51280f1 100644 --- a/src/arch/vpclmulqdq.rs +++ b/src/arch/vpclmulqdq.rs @@ -19,10 +19,10 @@ use crate::structs::CrcState; use crate::traits::{ArchOps, EnhancedCrcWidth}; #[rustversion::since(1.89)] -use std::arch::x86_64::*; +use core::arch::x86_64::*; #[rustversion::since(1.89)] -use std::ops::BitXor; +use core::ops::BitXor; /// Implements the ArchOps trait using 512-bit AVX-512 and VPCLMULQDQ instructions at 512 bits. /// Delegates to X86Ops for standard 128-bit operations diff --git a/src/arch/x86/sse.rs b/src/arch/x86/sse.rs index adaf8b8..3fac027 100644 --- a/src/arch/x86/sse.rs +++ b/src/arch/x86/sse.rs @@ -9,10 +9,10 @@ #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[cfg(target_arch = "x86")] -use std::arch::x86::*; +use core::arch::x86::*; #[cfg(target_arch = "x86_64")] -use std::arch::x86_64::*; +use core::arch::x86_64::*; use crate::traits::ArchOps; diff --git a/src/arch/x86_64/avx512.rs b/src/arch/x86_64/avx512.rs index d26ec2f..2b3d027 100644 --- a/src/arch/x86_64/avx512.rs +++ b/src/arch/x86_64/avx512.rs @@ -7,7 +7,7 @@ #![cfg(target_arch = "x86_64")] #[rustversion::since(1.89)] -use std::arch::x86_64::*; +use core::arch::x86_64::*; #[rustversion::since(1.89)] use crate::arch::x86::sse::X86SsePclmulqdqOps; diff --git a/src/arch/x86_64/avx512_vpclmulqdq.rs b/src/arch/x86_64/avx512_vpclmulqdq.rs index 881ca7e..418aeae 100644 --- a/src/arch/x86_64/avx512_vpclmulqdq.rs +++ b/src/arch/x86_64/avx512_vpclmulqdq.rs @@ -19,10 +19,10 @@ use crate::structs::CrcState; use crate::traits::{ArchOps, EnhancedCrcWidth}; #[rustversion::since(1.89)] -use std::arch::x86_64::*; +use core::arch::x86_64::*; #[rustversion::since(1.89)] -use std::ops::BitXor; +use core::ops::BitXor; /// Implements the ArchOps trait using 512-bit AVX-512 and VPCLMULQDQ instructions at 512 bits. /// Delegates to X86SsePclmulqdqOps for standard 128-bit operations diff --git a/src/bin/checksum.rs b/src/bin/checksum.rs index 962c790..cd67f5f 100644 --- a/src/bin/checksum.rs +++ b/src/bin/checksum.rs @@ -713,15 +713,8 @@ mod tests { let decimal_format = OutputFormat::Decimal; // Test that both variants can be created and are different - match hex_format { - OutputFormat::Hex => assert!(true), - OutputFormat::Decimal => assert!(false), - } - - match decimal_format { - OutputFormat::Decimal => assert!(true), - OutputFormat::Hex => assert!(false), - } + assert!(matches!(hex_format, OutputFormat::Hex)); + assert!(matches!(decimal_format, OutputFormat::Decimal)); } #[test] diff --git a/src/cache.rs b/src/cache.rs index 2aedea3..15d2b37 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -19,15 +19,27 @@ //! The cache is transparent to users and handles all memory management internally. use crate::generate; + +#[cfg(feature = "std")] use std::collections::HashMap; +#[cfg(feature = "std")] use std::sync::{OnceLock, RwLock}; +#[cfg(all(not(feature = "std"), feature = "cache"))] +use hashbrown::HashMap; +#[cfg(all(not(feature = "std"), feature = "cache"))] +use spin::{Once, RwLock}; + /// Global cache storage for CRC parameter keys /// /// Uses OnceLock for thread-safe lazy initialization and RwLock for concurrent access. /// The cache maps parameter combinations to their pre-computed folding keys. +#[cfg(feature = "std")] static CACHE: OnceLock>> = OnceLock::new(); +#[cfg(all(not(feature = "std"), feature = "cache"))] +static CACHE: Once>> = Once::new(); + /// Cache key for storing CRC parameters that affect key generation /// /// Only includes parameters that directly influence the mathematical computation @@ -36,6 +48,7 @@ static CACHE: OnceLock>> = OnceLock /// /// The cache key implements `Hash`, `Eq`, and `PartialEq` to enable efficient /// HashMap storage and lookup operations. +#[cfg(any(feature = "std", feature = "cache"))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct CrcParamsCacheKey { /// CRC width in bits (32 or 64) @@ -46,6 +59,7 @@ pub(crate) struct CrcParamsCacheKey { pub reflected: bool, } +#[cfg(any(feature = "std", feature = "cache"))] impl CrcParamsCacheKey { /// Create a new cache key from CRC parameters /// @@ -67,10 +81,16 @@ impl CrcParamsCacheKey { /// /// Uses OnceLock to ensure thread-safe lazy initialization without requiring /// static initialization overhead. The cache is only created when first accessed. +#[cfg(feature = "std")] fn get_cache() -> &'static RwLock> { CACHE.get_or_init(|| RwLock::new(HashMap::new())) } +#[cfg(all(not(feature = "std"), feature = "cache"))] +fn get_cache() -> &'static RwLock> { + CACHE.call_once(|| RwLock::new(HashMap::new())) +} + /// Get cached keys or generate and cache them if not present /// /// This function implements a read-then-write pattern optimized for the common case @@ -89,33 +109,66 @@ fn get_cache() -> &'static RwLock> { /// # Arguments /// /// * `width` - CRC width in bits (32 or 64) -/// * `poly` - Polynomial value for the CRC algorithm +/// * `poly` - Polynomial value for the CRC algorithm /// * `reflected` - Whether input/output should be bit-reflected /// /// # Returns /// /// Array of 23 pre-computed folding keys for SIMD CRC calculation pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] { - let cache_key = CrcParamsCacheKey::new(width, poly, reflected); - - // Try cache read first - multiple threads can read simultaneously - // If lock is poisoned or read fails, continue to key generation - if let Ok(cache) = get_cache().read() { - if let Some(keys) = cache.get(&cache_key) { - return *keys; + #[cfg(feature = "std")] + { + let cache_key = CrcParamsCacheKey::new(width, poly, reflected); + + // Try cache read first - multiple threads can read simultaneously + // If lock is poisoned or read fails, continue to key generation + if let Ok(cache) = get_cache().read() { + if let Some(keys) = cache.get(&cache_key) { + return *keys; + } } + + // Generate keys outside of write lock to minimize lock hold time + let keys = generate::keys(width, poly, reflected); + + // Try to cache the result (best effort - if this fails, we still return valid keys) + // Lock poisoning or write failure doesn't affect functionality + let _ = get_cache() + .write() + .map(|mut cache| cache.insert(cache_key, keys)); + + keys } - // Generate keys outside of write lock to minimize lock hold time - let keys = generate::keys(width, poly, reflected); + #[cfg(all(not(feature = "std"), feature = "cache"))] + { + let cache_key = CrcParamsCacheKey::new(width, poly, reflected); + + // Try cache read first - multiple threads can read simultaneously + // spin::RwLock returns guards directly (no Result wrapper) + { + let cache = get_cache().read(); + if let Some(keys) = cache.get(&cache_key) { + return *keys; + } + } // Drop read lock before generating keys + + // Generate keys outside of write lock to minimize lock hold time + let keys = generate::keys(width, poly, reflected); + + // Cache the result - spin::RwLock doesn't use Result wrapper + { + let mut cache = get_cache().write(); + cache.insert(cache_key, keys); + } - // Try to cache the result (best effort - if this fails, we still return valid keys) - // Lock poisoning or write failure doesn't affect functionality - let _ = get_cache() - .write() - .map(|mut cache| cache.insert(cache_key, keys)); + keys + } - keys + #[cfg(not(any(feature = "std", feature = "cache")))] + { + generate::keys(width, poly, reflected) + } } /// Clear all cached CRC parameter keys @@ -134,9 +187,19 @@ pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] /// reduce performance as those threads will need to regenerate keys on their next access. #[cfg(test)] pub(crate) fn clear_cache() { - // Best-effort cache clear - if lock is poisoned or unavailable, silently continue - // This ensures the function never panics or blocks program execution - let _ = get_cache().write().map(|mut cache| cache.clear()); + #[cfg(feature = "std")] + { + // Best-effort cache clear - if lock is poisoned or unavailable, silently continue + // This ensures the function never panics or blocks program execution + let _ = get_cache().write().map(|mut cache| cache.clear()); + } + + #[cfg(all(not(feature = "std"), feature = "cache"))] + { + // spin::RwLock doesn't use Result wrapper + let mut cache = get_cache().write(); + cache.clear(); + } } #[cfg(test)] @@ -151,11 +214,11 @@ mod tests { assert_eq!(key1.width, 32); assert_eq!(key1.poly, 0x04C11DB7); - assert_eq!(key1.reflected, true); + assert!(key1.reflected); assert_eq!(key2.width, 64); assert_eq!(key2.poly, 0x42F0E1EBA9EA3693); - assert_eq!(key2.reflected, false); + assert!(!key2.reflected); } #[test] @@ -453,6 +516,7 @@ mod tests { } #[test] + #[allow(clippy::needless_range_loop)] // Intentionally testing concurrent indexed access fn test_concurrent_cache_writes() { use std::sync::{Arc, Barrier}; use std::thread; @@ -1402,7 +1466,7 @@ mod tests { // Test that CrcParams can be copied and cloned let params_copy = params; - let params_clone = params.clone(); + let params_clone = params; assert_eq!(params.keys, params_copy.keys); assert_eq!(params.keys, params_clone.keys); @@ -1495,10 +1559,10 @@ mod tests { // Should have different keys due to different reflection assert_ne!(params_reflected.keys, params_normal.keys); - assert_eq!(params_reflected.refin, true); - assert_eq!(params_reflected.refout, true); - assert_eq!(params_normal.refin, false); - assert_eq!(params_normal.refout, false); + assert!(params_reflected.refin); + assert!(params_reflected.refout); + assert!(!params_normal.refin); + assert!(!params_normal.refout); // Test 64-bit edge cases let params64_min = crate::CrcParams::new("CRC64_MIN", 64, 0x1, 0x0, false, 0x0, 0x0); @@ -1575,8 +1639,8 @@ mod tests { assert_eq!(params.width, 32); assert_eq!(params.poly, 0x04C11DB7); assert_eq!(params.init, 0xFFFFFFFF); - assert_eq!(params.refin, true); - assert_eq!(params.refout, true); + assert!(params.refin); + assert!(params.refout); assert_eq!(params.xorout, 0xFFFFFFFF); assert_eq!(params.check, 0xCBF43926); } @@ -1671,8 +1735,8 @@ mod tests { let handle = thread::spawn(|| { // This thread should see the cached value from the main thread - let keys_thread = get_or_generate_keys(32, 0x04C11DB7, true); - keys_thread + + get_or_generate_keys(32, 0x04C11DB7, true) }); let keys_from_thread = handle.join().expect("Thread should not panic"); diff --git a/src/crc32/fusion/aarch64/iscsi/crc_pmull.rs b/src/crc32/fusion/aarch64/iscsi/crc_pmull.rs index 1bb2473..541b143 100644 --- a/src/crc32/fusion/aarch64/iscsi/crc_pmull.rs +++ b/src/crc32/fusion/aarch64/iscsi/crc_pmull.rs @@ -13,7 +13,7 @@ #![cfg(target_arch = "aarch64")] use crate::crc32::fusion::aarch64::{clmul_hi_and_xor, clmul_lo_and_xor}; -use std::arch::aarch64::{ +use core::arch::aarch64::{ __crc32cb, __crc32cd, veorq_u64, vgetq_lane_u64, vld1q_u64, vmovq_n_u64, vsetq_lane_u64, }; diff --git a/src/crc32/fusion/aarch64/iscsi/crc_pmull_sha3.rs b/src/crc32/fusion/aarch64/iscsi/crc_pmull_sha3.rs index 137cb5c..6221464 100644 --- a/src/crc32/fusion/aarch64/iscsi/crc_pmull_sha3.rs +++ b/src/crc32/fusion/aarch64/iscsi/crc_pmull_sha3.rs @@ -13,7 +13,7 @@ #![cfg(target_arch = "aarch64")] use crate::crc32::fusion::aarch64::{clmul_hi, clmul_lo, clmul_scalar}; -use std::arch::aarch64::{ +use core::arch::aarch64::{ __crc32cb, __crc32cd, __crc32cw, uint64x2_t, veor3q_u64, veorq_u64, vgetq_lane_u64, vld1q_u64, vmov_n_u64, vmull_p8, vreinterpret_p8_u64, vreinterpretq_u64_p16, }; diff --git a/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull.rs b/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull.rs index b57a959..cb4d6cd 100644 --- a/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull.rs +++ b/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull.rs @@ -13,7 +13,7 @@ #![cfg(target_arch = "aarch64")] use crate::crc32::fusion::aarch64::{clmul_hi_and_xor, clmul_lo_and_xor}; -use std::arch::aarch64::{ +use core::arch::aarch64::{ __crc32b, __crc32d, veorq_u64, vgetq_lane_u64, vld1q_u64, vmovq_n_u64, vsetq_lane_u64, }; diff --git a/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull_sha3.rs b/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull_sha3.rs index 3483774..345b411 100644 --- a/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull_sha3.rs +++ b/src/crc32/fusion/aarch64/iso_hdlc/crc_pmull_sha3.rs @@ -13,7 +13,7 @@ #![cfg(target_arch = "aarch64")] use crate::crc32::fusion::aarch64::{clmul_hi, clmul_lo, clmul_scalar}; -use std::arch::aarch64::{ +use core::arch::aarch64::{ __crc32b, __crc32d, __crc32w, uint64x2_t, veor3q_u64, veorq_u64, vgetq_lane_u64, vld1q_u64, vmov_n_u64, vmull_p8, vreinterpret_p8_u64, vreinterpretq_u64_p16, }; diff --git a/src/crc32/fusion/aarch64/mod.rs b/src/crc32/fusion/aarch64/mod.rs index de15ab4..4d70cba 100644 --- a/src/crc32/fusion/aarch64/mod.rs +++ b/src/crc32/fusion/aarch64/mod.rs @@ -15,7 +15,8 @@ mod iscsi; mod iso_hdlc; -use std::arch::aarch64::*; +use core::arch::aarch64::*; +#[cfg(feature = "std")] use std::arch::is_aarch64_feature_detected; use iscsi::crc_pmull::crc32_iscsi_v12e_v1; @@ -25,7 +26,12 @@ use iso_hdlc::crc_pmull_sha3::crc32_iso_hdlc_eor3_v9s3x2e_s3; #[inline(always)] pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 { - if is_aarch64_feature_detected!("sha3") { + #[cfg(feature = "std")] + let has_sha3 = is_aarch64_feature_detected!("sha3"); + #[cfg(not(feature = "std"))] + let has_sha3 = cfg!(target_feature = "sha3"); + + if has_sha3 { unsafe { crc32_iscsi_aes_sha3(crc, data) } } else { unsafe { crc32_iscsi_aes(crc, data) } @@ -34,7 +40,12 @@ pub fn crc32_iscsi(crc: u32, data: &[u8]) -> u32 { #[inline(always)] pub fn crc32_iso_hdlc(crc: u32, data: &[u8]) -> u32 { - if is_aarch64_feature_detected!("sha3") { + #[cfg(feature = "std")] + let has_sha3 = is_aarch64_feature_detected!("sha3"); + #[cfg(not(feature = "std"))] + let has_sha3 = cfg!(target_feature = "sha3"); + + if has_sha3 { unsafe { crc32_iso_hdlc_aes_sha3(crc, data) } } else { unsafe { crc32_iso_hdlc_aes(crc, data) } diff --git a/src/crc32/fusion/x86/iscsi/sse_pclmulqdq.rs b/src/crc32/fusion/x86/iscsi/sse_pclmulqdq.rs index bda9ae8..fb3e90d 100644 --- a/src/crc32/fusion/x86/iscsi/sse_pclmulqdq.rs +++ b/src/crc32/fusion/x86/iscsi/sse_pclmulqdq.rs @@ -6,10 +6,10 @@ #![cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[cfg(target_arch = "x86")] -use std::arch::x86::*; +use core::arch::x86::*; #[cfg(target_arch = "x86_64")] -use std::arch::x86_64::*; +use core::arch::x86_64::*; use crate::fusion::x86::*; diff --git a/src/crc32/fusion/x86/mod.rs b/src/crc32/fusion/x86/mod.rs index d5ae3d2..d8fefcd 100644 --- a/src/crc32/fusion/x86/mod.rs +++ b/src/crc32/fusion/x86/mod.rs @@ -17,16 +17,16 @@ mod iscsi; use iscsi::sse_pclmulqdq::crc32_iscsi_sse_v4s3x3; +#[cfg(target_arch = "x86")] +use core::arch::x86::*; +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; #[cfg(target_arch = "x86_64")] #[rustversion::since(1.89)] use iscsi::avx512_pclmulqdq::crc32_iscsi_avx512_v4s3x3; #[cfg(target_arch = "x86_64")] #[rustversion::since(1.89)] use iscsi::avx512_vpclmulqdq::crc32_iscsi_avx512_vpclmulqdq_v3x2; -#[cfg(target_arch = "x86")] -use std::arch::x86::*; -#[cfg(target_arch = "x86_64")] -use std::arch::x86_64::*; /// CRC32 iSCSI calculation for Rust versions before 1.89 (pre-AVX-512 support) /// diff --git a/src/crc64/utils.rs b/src/crc64/utils.rs index 6e5d58b..db8971b 100644 --- a/src/crc64/utils.rs +++ b/src/crc64/utils.rs @@ -2,18 +2,21 @@ #![cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] -#[cfg(target_arch = "aarch64")] -use std::arch::aarch64::*; +// SIMD intrinsics for debug printing (std only) +#[cfg(all(feature = "std", target_arch = "aarch64"))] +use core::arch::aarch64::*; -#[cfg(target_arch = "x86")] -use std::arch::x86::*; +#[cfg(all(feature = "std", target_arch = "x86"))] +use core::arch::x86::*; -use crate::traits::ArchOps; +#[cfg(all(feature = "std", target_arch = "x86_64"))] +use core::arch::x86_64::*; -#[cfg(target_arch = "x86_64")] -use std::arch::x86_64::*; +// ArchOps trait for generic vector printing (std only) +#[cfg(feature = "std")] +use crate::traits::ArchOps; -#[cfg(target_arch = "aarch64")] +#[cfg(all(feature = "std", target_arch = "aarch64"))] #[allow(dead_code)] #[target_feature(enable = "aes")] pub(crate) unsafe fn print_xmm_hex(prefix: &str, xmm: uint8x16_t) { @@ -22,7 +25,7 @@ pub(crate) unsafe fn print_xmm_hex(prefix: &str, xmm: uint8x16_t) { println!("{}={:#016x}{:016x}", prefix, temp[1], temp[0]); } -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))] #[allow(dead_code)] #[target_feature(enable = "sse4.1")] pub(crate) unsafe fn print_xmm_hex(prefix: &str, xmm: __m128i) { @@ -31,6 +34,7 @@ pub(crate) unsafe fn print_xmm_hex(prefix: &str, xmm: __m128i) { println!("{}={:#016x}{:016x}", prefix, temp[1], temp[0]); } +#[cfg(feature = "std")] #[allow(dead_code)] pub(crate) unsafe fn print_vector_hex(prefix: &str, vector: T::Vector, ops: &T) { // Extract the u64 values from the vector using the ArchOps trait @@ -41,13 +45,14 @@ pub(crate) unsafe fn print_vector_hex(prefix: &str, vector: T::Vecto } /// Print a vector as u8 array (useful for byte-level debugging) +#[cfg(feature = "std")] #[allow(dead_code)] pub(crate) unsafe fn print_vector_bytes(prefix: &str, vector: T::Vector, ops: &T) { // Extract the u64 values let values = ops.extract_u64s(vector); // Convert to bytes for detailed inspection - let bytes: [u8; 16] = std::mem::transmute([values[0], values[1]]); + let bytes: [u8; 16] = core::mem::transmute([values[0], values[1]]); println!("{}=[{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x},{:02x}]", prefix, diff --git a/src/enums.rs b/src/enums.rs index 067a3d9..20f1b33 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -2,8 +2,8 @@ use crate::consts::*; use crate::CrcAlgorithm; -use std::fmt::{Display, Formatter}; -use std::str::FromStr; +use core::fmt::{Display, Formatter}; +use core::str::FromStr; impl FromStr for CrcAlgorithm { type Err = (); @@ -35,7 +35,7 @@ impl FromStr for CrcAlgorithm { } impl Display for CrcAlgorithm { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { CrcAlgorithm::Crc32Aixm => write!(f, "{NAME_CRC32_AIXM}",), CrcAlgorithm::Crc32Autosar => write!(f, "{NAME_CRC32_AUTOSAR}",), diff --git a/src/feature_detection.rs b/src/feature_detection.rs index b170a74..4b0ca71 100644 --- a/src/feature_detection.rs +++ b/src/feature_detection.rs @@ -3,10 +3,45 @@ //! Feature detection system for safe and efficient hardware acceleration across different //! platforms. +#[cfg(all( + not(feature = "std"), + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] +use spin::Once; +#[cfg(all( + feature = "std", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] use std::sync::OnceLock; +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate alloc; +#[cfg(all( + feature = "alloc", + not(feature = "std"), + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] +use alloc::string::{String, ToString}; +#[cfg(any( + test, + all( + feature = "std", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") + ) +))] +use std::string::{String, ToString}; + /// Global ArchOps instance cache - initialized once based on feature detection results +#[cfg(all( + feature = "std", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] static ARCH_OPS_INSTANCE: OnceLock = OnceLock::new(); +#[cfg(all( + not(feature = "std"), + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] +static ARCH_OPS_INSTANCE: Once = Once::new(); /// Performance tiers representing different hardware capability levels #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -48,6 +83,13 @@ pub struct ArchCapabilities { /// Helper function to convert a performance tier to a human-readable target string /// Format: {architecture}-{intrinsics-family}-{intrinsics-features} +#[cfg(any( + test, + all( + feature = "alloc", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") + ) +))] #[inline(always)] fn tier_to_target_string(tier: PerformanceTier) -> String { match tier { @@ -65,6 +107,7 @@ fn tier_to_target_string(tier: PerformanceTier) -> String { /// /// # Safety /// Uses runtime feature detection which may access CPU-specific registers +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] unsafe fn detect_arch_capabilities() -> ArchCapabilities { #[cfg(target_arch = "aarch64")] { @@ -96,7 +139,7 @@ unsafe fn detect_arch_capabilities() -> ArchCapabilities { /// Note: NEON is always available on AArch64 and is implicitly enabled by AES support. /// AES support provides the PMULL instructions needed for CRC calculations. #[inline(always)] -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", feature = "std"))] unsafe fn detect_aarch64_features() -> ArchCapabilities { use std::arch::is_aarch64_feature_detected; @@ -118,9 +161,23 @@ unsafe fn detect_aarch64_features() -> ArchCapabilities { } } +#[inline(always)] +#[cfg(all(target_arch = "aarch64", not(feature = "std")))] +unsafe fn detect_aarch64_features() -> ArchCapabilities { + ArchCapabilities { + has_aes: cfg!(target_feature = "aes"), + has_sha3: cfg!(target_feature = "sha3"), + has_sse41: false, + has_pclmulqdq: false, + has_avx512vl: false, + has_vpclmulqdq: false, + rust_version_supports_avx512: false, + } +} + /// x86/x86_64-specific feature detection #[inline(always)] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))] unsafe fn detect_x86_features() -> ArchCapabilities { use std::arch::is_x86_feature_detected; @@ -148,6 +205,29 @@ unsafe fn detect_x86_features() -> ArchCapabilities { } } +#[inline(always)] +#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), not(feature = "std")))] +unsafe fn detect_x86_features() -> ArchCapabilities { + let rust_version_supports_avx512 = check_rust_version_supports_avx512(); + + let has_sse41 = cfg!(target_feature = "sse4.1"); + let has_pclmulqdq = has_sse41 && cfg!(target_feature = "pclmulqdq"); + let has_avx512vl = + has_pclmulqdq && rust_version_supports_avx512 && cfg!(target_feature = "avx512vl"); + let has_vpclmulqdq = + has_avx512vl && rust_version_supports_avx512 && cfg!(target_feature = "vpclmulqdq"); + + ArchCapabilities { + has_aes: false, + has_sha3: false, + has_sse41, + has_pclmulqdq, + has_avx512vl, + has_vpclmulqdq, + rust_version_supports_avx512, + } +} + /// Check if the current Rust version supports VPCLMULQDQ intrinsics /// VPCLMULQDQ intrinsics were stabilized in Rust 1.89 #[rustversion::since(1.89)] @@ -207,6 +287,7 @@ pub(crate) fn select_performance_tier(capabilities: &ArchCapabilities) -> Perfor /// Enum that holds the different ArchOps implementations for compile-time dispatch /// This avoids the need for trait objects while still providing factory-based selection +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] #[rustversion::since(1.89)] #[derive(Debug, Clone, Copy)] pub enum ArchOpsInstance { @@ -224,6 +305,7 @@ pub enum ArchOpsInstance { SoftwareFallback, } +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] #[rustversion::before(1.89)] #[derive(Debug, Clone, Copy)] pub enum ArchOpsInstance { @@ -237,6 +319,7 @@ pub enum ArchOpsInstance { SoftwareFallback, } +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] impl ArchOpsInstance { #[inline(always)] #[rustversion::since(1.89)] @@ -271,6 +354,7 @@ impl ArchOpsInstance { } /// Get a human-readable target string describing the active configuration + #[cfg(feature = "alloc")] #[inline(always)] pub fn get_target_string(&self) -> String { tier_to_target_string(self.get_tier()) @@ -282,15 +366,28 @@ impl ArchOpsInstance { /// This function provides access to the cached ArchOps instance that was selected based on /// feature detection results at library initialization time, eliminating runtime feature /// detection overhead from hot paths. +#[cfg(all( + feature = "std", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] pub fn get_arch_ops() -> &'static ArchOpsInstance { ARCH_OPS_INSTANCE.get_or_init(create_arch_ops) } +#[cfg(all( + not(feature = "std"), + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] +pub fn get_arch_ops() -> &'static ArchOpsInstance { + ARCH_OPS_INSTANCE.call_once(create_arch_ops) +} + /// Factory function that creates the appropriate ArchOps struct based on cached feature detection /// /// This function uses the cached feature detection results to select the optimal /// architecture-specific implementation at library initialization time, eliminating /// runtime feature detection overhead from hot paths. +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] fn create_arch_ops() -> ArchOpsInstance { let capabilities = unsafe { detect_arch_capabilities() }; let tier = select_performance_tier(&capabilities); @@ -300,6 +397,7 @@ fn create_arch_ops() -> ArchOpsInstance { /// Helper function to create ArchOpsInstance from a performance tier for Rust 1.89+ (when AVX512 /// stabilized) +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] #[rustversion::since(1.89)] fn create_arch_ops_from_tier(tier: PerformanceTier) -> ArchOpsInstance { match tier { @@ -342,6 +440,7 @@ fn create_arch_ops_from_tier(tier: PerformanceTier) -> ArchOpsInstance { /// Helper function to create ArchOpsInstance from a performance tier for Rust <1.89 (before AVX512 /// stabilized) +#[cfg(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64"))] #[rustversion::before(1.89)] fn create_arch_ops_from_tier(tier: PerformanceTier) -> ArchOpsInstance { match tier { diff --git a/src/ffi.rs b/src/ffi.rs index 6a32392..96c825b 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -5,7 +5,10 @@ //! This module provides a C-compatible interface for the Rust library, allowing //! C programs to use the library's functionality. -#![cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))] +#![cfg(all( + feature = "ffi", + any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86") +))] use crate::CrcAlgorithm; use crate::CrcParams; @@ -19,11 +22,66 @@ use std::sync::{Mutex, OnceLock}; static STRING_CACHE: OnceLock>> = OnceLock::new(); +/// Error codes for FFI operations +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CrcFastError { + /// Operation completed successfully + Success = 0, + /// Lock was poisoned (thread panicked while holding lock) + LockPoisoned = 1, + /// Null pointer was passed where non-null required + NullPointer = 2, + /// Invalid key count for CRC parameters + InvalidKeyCount = 3, + /// Unsupported CRC width (must be 32 or 64) + UnsupportedWidth = 4, + /// Invalid UTF-8 string + InvalidUtf8 = 5, + /// File I/O error + IoError = 6, + /// Internal string conversion error + StringConversionError = 7, +} + +impl CrcFastError { + /// Returns a static string describing the error + fn message(&self) -> &'static str { + match self { + CrcFastError::Success => "Operation completed successfully", + CrcFastError::LockPoisoned => "Lock was poisoned (thread panicked while holding lock)", + CrcFastError::NullPointer => "Null pointer was passed where non-null required", + CrcFastError::InvalidKeyCount => "Invalid key count for CRC parameters", + CrcFastError::UnsupportedWidth => "Unsupported CRC width (must be 32 or 64)", + CrcFastError::InvalidUtf8 => "Invalid UTF-8 string", + CrcFastError::IoError => "File I/O error", + CrcFastError::StringConversionError => "Internal string conversion error", + } + } +} + +// Thread-local storage for the last error that occurred +thread_local! { + static LAST_ERROR: std::cell::Cell = const { std::cell::Cell::new(CrcFastError::Success) }; +} + +/// Sets the thread-local last error +fn set_last_error(error: CrcFastError) { + LAST_ERROR.with(|e| e.set(error)); +} + +/// Clears the thread-local last error (sets it to Success) +fn clear_last_error() { + LAST_ERROR.with(|e| e.set(CrcFastError::Success)); +} + // Global storage for stable key pointers to ensure they remain valid across FFI boundary static STABLE_KEY_STORAGE: OnceLock>>> = OnceLock::new(); /// Creates a stable pointer to the keys for FFI usage. /// The keys are stored in global memory to ensure the pointer remains valid. +/// Returns (pointer, count) on success, or (null, 0) on error. +/// Sets CrcFastError::LockPoisoned on lock failure. fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) { let storage = STABLE_KEY_STORAGE.get_or_init(|| Mutex::new(HashMap::new())); @@ -43,7 +101,13 @@ fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) } }; - let mut storage_map = storage.lock().unwrap(); + let mut storage_map = match storage.lock() { + Ok(guard) => guard, + Err(_) => { + set_last_error(CrcFastError::LockPoisoned); + return (std::ptr::null(), 0); + } + }; // Check if we already have this key set stored if let Some(stored_keys) = storage_map.get(&key_hash) { @@ -72,6 +136,7 @@ pub struct CrcFastDigestHandle(*mut Digest); /// The supported CRC algorithms #[repr(C)] +#[derive(Clone, Copy)] pub enum CrcFastAlgorithm { Crc32Aixm, Crc32Autosar, @@ -125,6 +190,36 @@ impl From for CrcAlgorithm { } } +/// Gets the last error that occurred in the current thread +/// Returns CrcFastError::Success if no error has occurred +#[no_mangle] +pub extern "C" fn crc_fast_get_last_error() -> CrcFastError { + LAST_ERROR.with(|e| e.get()) +} + +/// Clears the last error for the current thread +#[no_mangle] +pub extern "C" fn crc_fast_clear_error() { + clear_last_error(); +} + +/// Gets a human-readable error message for the given error code +/// Returns a pointer to a static string (do not free) +#[no_mangle] +pub extern "C" fn crc_fast_error_message(error: CrcFastError) -> *const c_char { + let message = error.message(); + // These are static strings, so we can safely return them as C strings + // The strings are guaranteed to be valid UTF-8 and null-terminated + match std::ffi::CString::new(message) { + Ok(c_str) => { + // Leak the string so it remains valid for the lifetime of the program + // This is safe because error messages are static and small + Box::leak(Box::new(c_str)).as_ptr() + } + Err(_) => std::ptr::null(), + } +} + /// Custom CRC parameters #[repr(C)] pub struct CrcFastParams { @@ -140,34 +235,49 @@ pub struct CrcFastParams { pub keys: *const u64, } -// Convert from FFI struct to internal struct +/// Fallible conversion from FFI struct to internal struct +/// Returns None if the parameters are invalid (unsupported key count) +fn try_params_from_ffi(value: &CrcFastParams) -> Option { + // Validate key pointer + if value.keys.is_null() { + return None; + } + + // Convert C array back to appropriate CrcKeysStorage + let keys = unsafe { std::slice::from_raw_parts(value.keys, value.key_count as usize) }; + + let storage = match value.key_count { + 23 => match keys.try_into() { + Ok(arr) => crate::CrcKeysStorage::from_keys_fold_256(arr), + Err(_) => return None, + }, + 25 => match keys.try_into() { + Ok(arr) => crate::CrcKeysStorage::from_keys_fold_future_test(arr), + Err(_) => return None, + }, + _ => return None, // Unsupported key count + }; + + Some(CrcParams { + algorithm: value.algorithm.into(), + name: "custom", // C interface doesn't need the name field + width: value.width, + poly: value.poly, + init: value.init, + refin: value.refin, + refout: value.refout, + xorout: value.xorout, + check: value.check, + keys: storage, + }) +} + +// Convert from FFI struct to internal struct (legacy, may panic) +// For backwards compatibility, but prefer try_params_from_ffi impl From for CrcParams { fn from(value: CrcFastParams) -> Self { - // Convert C array back to appropriate CrcKeysStorage - let keys = unsafe { std::slice::from_raw_parts(value.keys, value.key_count as usize) }; - - let storage = match value.key_count { - 23 => crate::CrcKeysStorage::from_keys_fold_256( - keys.try_into().expect("Invalid key count for fold_256"), - ), - 25 => crate::CrcKeysStorage::from_keys_fold_future_test( - keys.try_into().expect("Invalid key count for future_test"), - ), - _ => panic!("Unsupported key count: {}", value.key_count), - }; - - CrcParams { - algorithm: value.algorithm.into(), - name: "custom", // C interface doesn't need the name field - width: value.width, - poly: value.poly, - init: value.init, - refin: value.refin, - refout: value.refout, - xorout: value.xorout, - check: value.check, - keys: storage, - } + try_params_from_ffi(&value) + .expect("Invalid CRC parameters: unsupported key count or null pointer") } } @@ -217,6 +327,7 @@ impl From for CrcFastParams { /// Creates a new Digest to compute CRC checksums using algorithm #[no_mangle] pub extern "C" fn crc_fast_digest_new(algorithm: CrcFastAlgorithm) -> *mut CrcFastDigestHandle { + clear_last_error(); let digest = Box::new(Digest::new(algorithm.into())); let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest))); Box::into_raw(handle) @@ -228,19 +339,36 @@ pub extern "C" fn crc_fast_digest_new_with_init_state( algorithm: CrcFastAlgorithm, init_state: u64, ) -> *mut CrcFastDigestHandle { + clear_last_error(); let digest = Box::new(Digest::new_with_init_state(algorithm.into(), init_state)); let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest))); Box::into_raw(handle) } /// Creates a new Digest to compute CRC checksums using custom parameters +/// Returns NULL if parameters are invalid (invalid key count or null pointer) +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_digest_new_with_params( params: CrcFastParams, ) -> *mut CrcFastDigestHandle { - let digest = Box::new(Digest::new_with_params(params.into())); - let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest))); - Box::into_raw(handle) + clear_last_error(); + match try_params_from_ffi(¶ms) { + Some(crc_params) => { + let digest = Box::new(Digest::new_with_params(crc_params)); + let handle = Box::new(CrcFastDigestHandle(Box::into_raw(digest))); + Box::into_raw(handle) + } + None => { + // Set appropriate error based on the failure + if params.keys.is_null() { + set_last_error(CrcFastError::NullPointer); + } else { + set_last_error(CrcFastError::InvalidKeyCount); + } + std::ptr::null_mut() + } + } } /// Updates the Digest with data @@ -250,10 +378,16 @@ pub extern "C" fn crc_fast_digest_update( data: *const c_char, len: usize, ) { - if handle.is_null() || data.is_null() { + if handle.is_null() { + set_last_error(CrcFastError::NullPointer); + return; + } + if data.is_null() { + set_last_error(CrcFastError::NullPointer); return; } + clear_last_error(); unsafe { let digest = &mut *(*handle).0; @@ -264,12 +398,15 @@ pub extern "C" fn crc_fast_digest_update( } /// Calculates the CRC checksum for data that's been written to the Digest +/// Returns 0 on error (e.g. null handle) #[no_mangle] pub extern "C" fn crc_fast_digest_finalize(handle: *mut CrcFastDigestHandle) -> u64 { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } + clear_last_error(); unsafe { let digest = &*(*handle).0; digest.finalize() @@ -280,9 +417,11 @@ pub extern "C" fn crc_fast_digest_finalize(handle: *mut CrcFastDigestHandle) -> #[no_mangle] pub extern "C" fn crc_fast_digest_free(handle: *mut CrcFastDigestHandle) { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return; } + clear_last_error(); unsafe { let handle = Box::from_raw(handle); let _ = Box::from_raw(handle.0); // This drops the digest @@ -293,9 +432,11 @@ pub extern "C" fn crc_fast_digest_free(handle: *mut CrcFastDigestHandle) { #[no_mangle] pub extern "C" fn crc_fast_digest_reset(handle: *mut CrcFastDigestHandle) { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return; } + clear_last_error(); unsafe { let digest = &mut *(*handle).0; @@ -304,12 +445,15 @@ pub extern "C" fn crc_fast_digest_reset(handle: *mut CrcFastDigestHandle) { } /// Finalize and reset the Digest in one operation +/// Returns 0 on error (e.g. null handle) #[no_mangle] pub extern "C" fn crc_fast_digest_finalize_reset(handle: *mut CrcFastDigestHandle) -> u64 { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } + clear_last_error(); unsafe { let digest = &mut *(*handle).0; @@ -324,9 +468,11 @@ pub extern "C" fn crc_fast_digest_combine( handle2: *mut CrcFastDigestHandle, ) { if handle1.is_null() || handle2.is_null() { + set_last_error(CrcFastError::NullPointer); return; } + clear_last_error(); unsafe { let digest1 = &mut *(*handle1).0; let digest2 = &*(*handle2).0; @@ -335,12 +481,15 @@ pub extern "C" fn crc_fast_digest_combine( } /// Gets the amount of data processed by the Digest so far +/// Returns 0 on error (e.g. null handle) #[no_mangle] pub extern "C" fn crc_fast_digest_get_amount(handle: *mut CrcFastDigestHandle) -> u64 { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } + clear_last_error(); unsafe { let digest = &*(*handle).0; digest.get_amount() @@ -348,11 +497,14 @@ pub extern "C" fn crc_fast_digest_get_amount(handle: *mut CrcFastDigestHandle) - } /// Gets the current state of the Digest +/// Returns 0 on error (e.g. null handle) #[no_mangle] pub extern "C" fn crc_fast_digest_get_state(handle: *mut CrcFastDigestHandle) -> u64 { if handle.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } + clear_last_error(); unsafe { let digest = &*(*handle).0; digest.get_state() @@ -360,6 +512,7 @@ pub extern "C" fn crc_fast_digest_get_state(handle: *mut CrcFastDigestHandle) -> } /// Helper method to calculate a CRC checksum directly for a string using algorithm +/// Returns 0 on error (e.g. null data pointer) #[no_mangle] pub extern "C" fn crc_fast_checksum( algorithm: CrcFastAlgorithm, @@ -367,8 +520,10 @@ pub extern "C" fn crc_fast_checksum( len: usize, ) -> u64 { if data.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } + clear_last_error(); unsafe { #[allow(clippy::unnecessary_cast)] let bytes = slice::from_raw_parts(data as *const u8, len); @@ -377,6 +532,8 @@ pub extern "C" fn crc_fast_checksum( } /// Helper method to calculate a CRC checksum directly for data using custom parameters +/// Returns 0 if parameters are invalid or data is null +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_checksum_with_params( params: CrcFastParams, @@ -384,16 +541,32 @@ pub extern "C" fn crc_fast_checksum_with_params( len: usize, ) -> u64 { if data.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } - unsafe { - #[allow(clippy::unnecessary_cast)] - let bytes = slice::from_raw_parts(data as *const u8, len); - crate::checksum_with_params(params.into(), bytes) + match try_params_from_ffi(¶ms) { + Some(crc_params) => { + clear_last_error(); + unsafe { + #[allow(clippy::unnecessary_cast)] + let bytes = slice::from_raw_parts(data as *const u8, len); + crate::checksum_with_params(crc_params, bytes) + } + } + None => { + if params.keys.is_null() { + set_last_error(CrcFastError::NullPointer); + } else { + set_last_error(CrcFastError::InvalidKeyCount); + } + 0 + } } } /// Helper method to just calculate a CRC checksum directly for a file using algorithm +/// Returns 0 if path is null or file I/O fails +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_checksum_file( algorithm: CrcFastAlgorithm, @@ -401,20 +574,31 @@ pub extern "C" fn crc_fast_checksum_file( path_len: usize, ) -> u64 { if path_ptr.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } unsafe { - crate::checksum_file( + match crate::checksum_file( algorithm.into(), &convert_to_string(path_ptr, path_len), None, - ) - .unwrap() + ) { + Ok(result) => { + clear_last_error(); + result + } + Err(_) => { + set_last_error(CrcFastError::IoError); + 0 + } + } } } /// Helper method to calculate a CRC checksum directly for a file using custom parameters +/// Returns 0 if parameters are invalid, path is null, or file I/O fails +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_checksum_file_with_params( params: CrcFastParams, @@ -422,16 +606,35 @@ pub extern "C" fn crc_fast_checksum_file_with_params( path_len: usize, ) -> u64 { if path_ptr.is_null() { + set_last_error(CrcFastError::NullPointer); return 0; } - unsafe { - crate::checksum_file_with_params( - params.into(), - &convert_to_string(path_ptr, path_len), - None, - ) - .unwrap_or(0) // Return 0 on error instead of panicking + match try_params_from_ffi(¶ms) { + Some(crc_params) => unsafe { + match crate::checksum_file_with_params( + crc_params, + &convert_to_string(path_ptr, path_len), + None, + ) { + Ok(result) => { + clear_last_error(); + result + } + Err(_) => { + set_last_error(CrcFastError::IoError); + 0 + } + } + }, + None => { + if params.keys.is_null() { + set_last_error(CrcFastError::NullPointer); + } else { + set_last_error(CrcFastError::InvalidKeyCount); + } + 0 + } } } @@ -443,10 +646,13 @@ pub extern "C" fn crc_fast_checksum_combine( checksum2: u64, checksum2_len: u64, ) -> u64 { + clear_last_error(); crate::checksum_combine(algorithm.into(), checksum1, checksum2, checksum2_len) } /// Combine two CRC checksums using custom parameters +/// Returns 0 if parameters are invalid +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_checksum_combine_with_params( params: CrcFastParams, @@ -454,10 +660,24 @@ pub extern "C" fn crc_fast_checksum_combine_with_params( checksum2: u64, checksum2_len: u64, ) -> u64 { - crate::checksum_combine_with_params(params.into(), checksum1, checksum2, checksum2_len) + match try_params_from_ffi(¶ms) { + Some(crc_params) => { + clear_last_error(); + crate::checksum_combine_with_params(crc_params, checksum1, checksum2, checksum2_len) + } + None => { + if params.keys.is_null() { + set_last_error(CrcFastError::NullPointer); + } else { + set_last_error(CrcFastError::InvalidKeyCount); + } + 0 + } + } } /// Returns the custom CRC parameters for a given set of Rocksoft CRC parameters +/// If width is not 32 or 64, sets error to UnsupportedWidth #[no_mangle] pub extern "C" fn crc_fast_get_custom_params( name_ptr: *const c_char, @@ -468,10 +688,25 @@ pub extern "C" fn crc_fast_get_custom_params( xorout: u64, check: u64, ) -> CrcFastParams { + // Validate width + if width != 32 && width != 64 { + set_last_error(CrcFastError::UnsupportedWidth); + } else { + clear_last_error(); + } + let name = if name_ptr.is_null() { "custom" } else { - unsafe { CStr::from_ptr(name_ptr).to_str().unwrap_or("custom") } + unsafe { + match CStr::from_ptr(name_ptr).to_str() { + Ok(s) => s, + Err(_) => { + set_last_error(CrcFastError::InvalidUtf8); + "custom" + } + } + } }; // Get the custom params from the library @@ -493,7 +728,8 @@ pub extern "C" fn crc_fast_get_custom_params( algorithm: match width { 32 => CrcFastAlgorithm::Crc32Custom, 64 => CrcFastAlgorithm::Crc64Custom, - _ => panic!("Unsupported width: {width}",), + // Default to 32-bit for unsupported widths (defensive programming) + _ => CrcFastAlgorithm::Crc32Custom, }, width: params.width, poly: params.poly, @@ -508,20 +744,33 @@ pub extern "C" fn crc_fast_get_custom_params( } /// Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm +/// Returns NULL if string conversion fails +/// Call crc_fast_get_last_error() to get the specific error code #[no_mangle] pub extern "C" fn crc_fast_get_calculator_target(algorithm: CrcFastAlgorithm) -> *const c_char { let target = get_calculator_target(algorithm.into()); - std::ffi::CString::new(target).unwrap().into_raw() + match std::ffi::CString::new(target) { + Ok(s) => { + clear_last_error(); + s.into_raw() + } + Err(_) => { + set_last_error(CrcFastError::StringConversionError); + std::ptr::null_mut() + } + } } /// Gets the version of this library +/// Returns a pointer to "unknown" if version string is invalid #[no_mangle] pub extern "C" fn crc_fast_get_version() -> *const c_char { const VERSION: &CStr = match CStr::from_bytes_with_nul(concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes()) { Ok(version) => version, - Err(_) => panic!("package version contains null bytes??"), + // Fallback to "unknown" if version string is malformed + Err(_) => c"unknown", }; VERSION.as_ptr() @@ -535,7 +784,7 @@ unsafe fn convert_to_string(data: *const u8, len: usize) -> String { // Safely construct string slice from raw parts match std::str::from_utf8(slice::from_raw_parts(data, len)) { Ok(s) => s.to_string(), - Err(_) => panic!("Invalid UTF-8 string"), + Err(_) => String::new(), // Return empty string for invalid UTF-8 } } diff --git a/src/generate.rs b/src/generate.rs index f9b1e82..9ec3dc8 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -109,7 +109,7 @@ #![allow(dead_code)] -use std::ops::{BitAnd, BitOr, Shl, Shr}; +use core::ops::{BitAnd, BitOr, Shl, Shr}; /// Exponents (bit distances) for CRC-32 key generation. /// @@ -561,7 +561,7 @@ where let mut result = T::default(); // Zero value // Get the bit size of type T - let bit_size = std::mem::size_of::() * 8; + let bit_size = core::mem::size_of::() * 8; for _ in 0..bit_size { // Shift result left by 1 diff --git a/src/lib.rs b/src/lib.rs index 317fab0..ba949e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,62 @@ //! //! assert_eq!(checksum, 0xcbf43926); //! ``` +//! +//! # no_std Support +//! +//! Supports `no_std` environments. Use `default-features = false` in Cargo.toml. +//! +//! Note: When using this library in a `no_std` environment, the final binary must provide: +//! - A `#[panic_handler]` (e.g., via the `panic-halt` crate) +//! - A `#[global_allocator]` if using the `alloc` feature + +// Provide a panic handler for no_std library checks +// Disabled with default-features = false (which binaries should use) +#[cfg(all( + feature = "panic-handler", + not(feature = "std"), + not(test), + not(doctest) +))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +// Provide a global allocator for no_std + alloc library checks +// Disabled with default-features = false (which binaries should use) +#[cfg(all( + feature = "panic-handler", + feature = "alloc", + not(feature = "std"), + not(test), + not(doctest) +))] +#[global_allocator] +static ALLOCATOR: StubAllocator = StubAllocator; + +#[cfg(all( + feature = "panic-handler", + feature = "alloc", + not(feature = "std"), + not(test), + not(doctest) +))] +struct StubAllocator; + +#[cfg(all( + feature = "panic-handler", + feature = "alloc", + not(feature = "std"), + not(test), + not(doctest) +))] +unsafe impl core::alloc::GlobalAlloc for StubAllocator { + unsafe fn alloc(&self, _layout: core::alloc::Layout) -> *mut u8 { + core::ptr::null_mut() + } + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {} +} use crate::crc32::consts::{ CRC32_AIXM, CRC32_AUTOSAR, CRC32_BASE91_D, CRC32_BZIP2, CRC32_CD_ROM_EDC, CRC32_CKSUM, @@ -138,6 +194,7 @@ use crate::crc32::consts::{ }; #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] +#[cfg(feature = "std")] use crate::crc32::fusion; use crate::crc64::consts::{ @@ -145,13 +202,23 @@ use crate::crc64::consts::{ }; use crate::structs::Calculator; use crate::traits::CrcCalculator; -use digest::{DynDigest, InvalidBufferSize}; +#[cfg(feature = "alloc")] +use digest::DynDigest; +#[cfg(feature = "alloc")] +use digest::InvalidBufferSize; #[cfg(feature = "std")] use std::fs::File; #[cfg(feature = "std")] use std::io::{Read, Write}; +#[cfg(all(feature = "alloc", not(feature = "std")))] +extern crate alloc; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::boxed::Box; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::string::String; + mod algorithm; mod arch; mod cache; @@ -161,6 +228,7 @@ mod crc32; mod crc64; mod enums; mod feature_detection; +#[cfg(feature = "ffi")] mod ffi; mod generate; mod structs; @@ -326,6 +394,7 @@ pub struct Digest { calculator: CalculatorFn, } +#[cfg(feature = "alloc")] impl DynDigest for Digest { #[inline(always)] fn update(&mut self, data: &[u8]) { @@ -819,6 +888,10 @@ pub fn checksum_combine_with_params( /// // "x86_64-avx512-vpclmulqdq" - x86_64 with VPCLMULQDQ support /// // "x86_64-sse-pclmulqdq" - x86_64 baseline with SSE4.1 and PCLMULQDQ /// ``` +#[cfg(all( + feature = "alloc", + any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64") +))] pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String { use crate::feature_detection::get_arch_ops; @@ -826,6 +899,17 @@ pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String { arch_ops.get_target_string() } +/// Fallback version of get_calculator_target for unsupported architectures +#[cfg(all( + feature = "alloc", + not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")) +))] +pub fn get_calculator_target(_algorithm: CrcAlgorithm) -> String { + extern crate alloc; + use alloc::string::ToString; + "software-fallback-tables".to_string() +} + /// Returns the calculator function and parameters for the specified CRC algorithm. #[inline(always)] fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) { @@ -864,7 +948,7 @@ fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) { /// fusion techniques to accelerate the calculation beyond what SIMD can do alone. #[inline(always)] fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { - #[cfg(target_arch = "aarch64")] + #[cfg(all(target_arch = "aarch64", feature = "std"))] { use std::arch::is_aarch64_feature_detected; if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") { @@ -873,7 +957,7 @@ fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { } // both aarch64 and x86 have native CRC-32/ISCSI support, so we can use fusion - #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + #[cfg(all(any(target_arch = "x86_64", target_arch = "x86"), feature = "std"))] { use std::arch::is_x86_feature_detected; if is_x86_feature_detected!("sse4.2") && is_x86_feature_detected!("pclmulqdq") { @@ -894,7 +978,7 @@ fn crc32_iscsi_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { #[inline(always)] fn crc32_iso_hdlc_calculator(state: u64, data: &[u8], _params: CrcParams) -> u64 { // aarch64 CPUs have native CRC-32/ISO-HDLC support, so we can use the fusion implementation - #[cfg(target_arch = "aarch64")] + #[cfg(all(target_arch = "aarch64", feature = "std"))] { use std::arch::is_aarch64_feature_detected; if is_aarch64_feature_detected!("aes") && is_aarch64_feature_detected!("crc") { @@ -1506,6 +1590,7 @@ mod lib { } #[test] + #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() method fn test_crc_keys_storage_fold_256() { let test_keys = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, @@ -1527,6 +1612,7 @@ mod lib { } #[test] + #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() method fn test_crc_keys_storage_future_test() { let test_keys = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, @@ -1549,6 +1635,7 @@ mod lib { } #[test] + #[allow(clippy::needless_range_loop)] // Intentionally testing indexed get_key() and get_key_checked() methods fn test_crc_params_safe_accessors() { // Create a test CrcParams with known keys let test_keys = [ diff --git a/src/test/future_proof_tests.rs b/src/test/future_proof_tests.rs index 81dcc10..53921c6 100644 --- a/src/test/future_proof_tests.rs +++ b/src/test/future_proof_tests.rs @@ -377,6 +377,7 @@ fn test_third_party_const_definitions_compatibility() { } #[test] +#[allow(clippy::needless_range_loop)] // Intentionally testing indexed key access patterns fn test_existing_key_access_patterns_continue_to_work() { // Test that common key access patterns used by existing code continue to work @@ -469,6 +470,7 @@ fn test_existing_key_access_patterns_continue_to_work() { } #[test] +#[allow(clippy::needless_range_loop)] // Intentionally testing indexed key access for backwards compatibility fn test_backwards_compatibility_throughout_migration_phases() { // This test simulates the migration phases to ensure backwards compatibility @@ -544,6 +546,7 @@ fn test_backwards_compatibility_throughout_migration_phases() { } #[test] +#[allow(clippy::needless_range_loop)] // Intentionally testing indexed access performance fn test_key_access_performance_matches_direct_array_access() { // This test verifies that CrcKeysStorage key access has zero runtime overhead // compared to direct array access. While we can't easily measure exact timing @@ -875,6 +878,7 @@ fn test_compiler_optimizations_eliminate_enum_dispatch() { assert_ne!(mixed_sum, 0, "Mixed access should produce non-zero result"); } #[test] +#[allow(clippy::needless_range_loop)] // Intentionally testing indexed key access for future variant fn test_create_crc_params_using_keys_future_test_variant() { // Create test CrcParams using KeysFutureTest variant with 25 keys let test_keys_25 = [ @@ -1324,7 +1328,10 @@ fn test_future_expansion_backwards_compatibility() { } // FFI Tests for future-proof CrcFastParams functionality -#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))] +#[cfg(all( + feature = "ffi", + any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86") +))] mod ffi_tests { use crate::ffi::CrcFastParams; use crate::{CrcAlgorithm, CrcKeysStorage, CrcParams}; diff --git a/src/test/mod.rs b/src/test/mod.rs index 8b84fcc..28c2717 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -15,11 +15,8 @@ pub(crate) fn create_aligned_data(input: &[u8]) -> Vec { // Size of our target alignment structure let align_size = std::mem::size_of::<[[u64; 4]; 2]>(); // 64 bytes - // Create a vector with padding to ensure we can find a properly aligned position - let mut padded = Vec::with_capacity(input.len() + align_size); - - // Fill with zeros initially to reach needed capacity - padded.resize(input.len() + align_size, 0); + // Create a zero-filled vector with padding to ensure we can find a properly aligned position + let mut padded = vec![0; input.len() + align_size]; // Find the first address that satisfies our alignment let start_addr = padded.as_ptr() as usize; diff --git a/src/traits.rs b/src/traits.rs index 7eec0b5..7e82cac 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,7 +10,7 @@ use crate::CrcParams; #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] use crate::structs::CrcState; -use std::ops::BitXor; +use core::ops::BitXor; /// Marker trait for CRC width pub trait CrcWidth { diff --git a/tests/checksum_integration_tests.rs b/tests/checksum_integration_tests.rs index bd45966..50e6ce3 100644 --- a/tests/checksum_integration_tests.rs +++ b/tests/checksum_integration_tests.rs @@ -9,7 +9,7 @@ use std::process::Command; #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_flag_parsing() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -37,7 +37,7 @@ fn test_benchmark_flag_parsing() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_size_parameter() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -62,7 +62,7 @@ fn test_benchmark_with_size_parameter() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_duration_parameter() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -87,7 +87,7 @@ fn test_benchmark_with_duration_parameter() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_invalid_size() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -112,7 +112,7 @@ fn test_benchmark_invalid_size() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_invalid_duration() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -141,7 +141,7 @@ fn test_benchmark_with_file_input() { fs::write(test_file, "Hello, benchmark world!").expect("Failed to create test file"); let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -171,7 +171,7 @@ fn test_benchmark_with_file_input() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_with_string_input() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -201,7 +201,7 @@ fn test_benchmark_different_algorithms() { for algorithm in &algorithms { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -231,7 +231,7 @@ fn test_benchmark_different_algorithms() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_size_without_benchmark_flag() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", @@ -255,7 +255,7 @@ fn test_benchmark_size_without_benchmark_flag() { #[cfg_attr(miri, ignore)] // Miri doesn't allow this due to isolation restrictions fn test_benchmark_nonexistent_file() { let output = Command::new("cargo") - .args(&[ + .args([ "run", "--features", "cli", diff --git a/tests/no_std_tests.rs b/tests/no_std_tests.rs new file mode 100644 index 0000000..6a7ad18 --- /dev/null +++ b/tests/no_std_tests.rs @@ -0,0 +1,217 @@ +//! no_std compatibility tests +//! +//! Tests the library works without std. The test framework requires std, +//! but this exercises all no_std code paths. +//! +//! Run tests: cargo test --test real_no_std_test + +use crc_fast::{checksum, CrcAlgorithm, Digest}; + +/// Test basic checksum calculation (works without std) +#[test] +fn test_no_std_basic_checksum() { + let data = b"123456789"; + + // Test all major CRC variants + assert_eq!( + checksum(CrcAlgorithm::Crc32IsoHdlc, data), + 0xcbf43926, + "CRC-32/ISO-HDLC failed" + ); + + assert_eq!( + checksum(CrcAlgorithm::Crc32Iscsi, data), + 0xe3069283, + "CRC-32/ISCSI failed" + ); + + assert_eq!( + checksum(CrcAlgorithm::Crc64Nvme, data), + 0xae8b14860a799888, + "CRC-64/NVME failed" + ); + + assert_eq!( + checksum(CrcAlgorithm::Crc64Xz, data), + 0x995dc9bbdf1939fa, + "CRC-64/XZ failed" + ); +} + +/// Test all 21 standard CRC algorithms +#[test] +fn test_no_std_all_algorithms() { + let data = b"123456789"; + + // CRC-32 variants (reflected) + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, data), 0xcbf43926); + assert_eq!(checksum(CrcAlgorithm::Crc32Iscsi, data), 0xe3069283); + + // CRC-32 variants (non-reflected/forward) + assert_eq!(checksum(CrcAlgorithm::Crc32Bzip2, data), 0xfc891918); + assert_eq!(checksum(CrcAlgorithm::Crc32Mpeg2, data), 0x0376e6e7); + + // CRC-64 variants + assert_eq!( + checksum(CrcAlgorithm::Crc64Ecma182, data), + 0x6c40df5f0b497347 + ); + assert_eq!(checksum(CrcAlgorithm::Crc64GoIso, data), 0xb90956c775a41001); + assert_eq!(checksum(CrcAlgorithm::Crc64Ms, data), 0x75d4b74f024eceea); + assert_eq!(checksum(CrcAlgorithm::Crc64Nvme, data), 0xae8b14860a799888); + assert_eq!(checksum(CrcAlgorithm::Crc64Redis, data), 0xe9c6d914c4b8d9ca); + assert_eq!(checksum(CrcAlgorithm::Crc64We, data), 0x62ec59e3f1a4f00a); + assert_eq!(checksum(CrcAlgorithm::Crc64Xz, data), 0x995dc9bbdf1939fa); +} + +/// Test Digest API (core functionality without std) +#[test] +fn test_no_std_digest_api() { + let mut digest = Digest::new(CrcAlgorithm::Crc32IsoHdlc); + digest.update(b"1234"); + digest.update(b"56789"); + let result = digest.finalize(); + + assert_eq!(result, 0xcbf43926, "Digest API failed"); +} + +/// Test empty input +#[test] +fn test_no_std_empty_input() { + let empty: &[u8] = &[]; + let result = checksum(CrcAlgorithm::Crc32IsoHdlc, empty); + // Empty input gives 0 after final XOR with xorout + assert_eq!(result, 0); +} + +/// Test various input sizes to ensure all code paths work +#[test] +fn test_no_std_various_sizes() { + // Small (< 64 bytes) + let small = b"hello"; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, small); + + // Medium (64-256 bytes) + let medium = b"The quick brown fox jumps over the lazy dog. \ + The quick brown fox jumps over the lazy dog. \ + The quick brown fox jumps over the lazy dog."; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, medium); + + // Large (> 256 bytes) - tests SIMD path + let large = [0xAAu8; 1024]; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, &large); +} + +/// Test that reflected and non-reflected algorithms both work +#[test] +fn test_no_std_reflection_modes() { + let data = b"123456789"; + + // Reflected (most common) + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, data), 0xcbf43926); + + // Non-reflected (forward) + assert_eq!(checksum(CrcAlgorithm::Crc32Bzip2, data), 0xfc891918); +} + +/// Test custom CRC parameters +#[cfg(feature = "alloc")] +#[test] +fn test_no_std_custom_params() { + use crc_fast::{checksum_with_params, CrcParams}; + + let params = CrcParams::new( + "CRC-32/CUSTOM", + 32, + 0x04c11db7, + 0xffffffff, + true, + 0xffffffff, + 0xcbf43926, + ); + + let result = checksum_with_params(params, b"123456789"); + assert_eq!(result, 0xcbf43926, "Custom params failed"); +} + +/// Test that Digest can be reused +#[test] +fn test_no_std_digest_reuse() { + let mut digest = Digest::new(CrcAlgorithm::Crc32IsoHdlc); + + digest.update(b"123456789"); + let result1 = digest.finalize(); + assert_eq!(result1, 0xcbf43926); + + digest.reset(); + digest.update(b"123456789"); + let result2 = digest.finalize(); + assert_eq!(result2, 0xcbf43926); +} + +/// Test incremental digest updates +#[test] +fn test_no_std_incremental_digest() { + let mut digest1 = Digest::new(CrcAlgorithm::Crc64Nvme); + digest1.update(b"123456789"); + let result1 = digest1.finalize(); + + let mut digest2 = Digest::new(CrcAlgorithm::Crc64Nvme); + digest2.update(b"123"); + digest2.update(b"456"); + digest2.update(b"789"); + let result2 = digest2.finalize(); + + assert_eq!(result1, result2, "Incremental digest failed"); +} + +/// Test checksum_combine (requires alloc) +#[cfg(feature = "alloc")] +#[test] +fn test_no_std_checksum_combine() { + use crc_fast::checksum_combine; + + let crc1 = checksum(CrcAlgorithm::Crc32IsoHdlc, b"1234"); + let crc2 = checksum(CrcAlgorithm::Crc32IsoHdlc, b"56789"); + let combined = checksum_combine(CrcAlgorithm::Crc32IsoHdlc, crc1, crc2, 5); + + let expected = checksum(CrcAlgorithm::Crc32IsoHdlc, b"123456789"); + assert_eq!(combined, expected, "checksum_combine failed"); +} + +/// Test that the library works with stack-only data +#[test] +fn test_no_std_stack_only() { + // This should work without any heap allocation + let data = b"123456789"; + let result = checksum(CrcAlgorithm::Crc32IsoHdlc, data); + assert_eq!(result, 0xcbf43926); +} + +/// Test both CRC-32 and CRC-64 widths +#[test] +fn test_no_std_both_widths() { + let data = b"test data"; + + // CRC-32 + let _crc32 = checksum(CrcAlgorithm::Crc32IsoHdlc, data); + + // CRC-64 + let _crc64 = checksum(CrcAlgorithm::Crc64Nvme, data); +} + +/// Test with byte patterns that might expose issues +#[test] +fn test_no_std_edge_case_patterns() { + // All zeros + let zeros = [0u8; 128]; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, &zeros); + + // All ones + let ones = [0xFFu8; 128]; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, &ones); + + // Alternating pattern + let alt = [0xAAu8; 128]; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, &alt); +} diff --git a/tests/wasm_tests.rs b/tests/wasm_tests.rs new file mode 100644 index 0000000..07d6bfc --- /dev/null +++ b/tests/wasm_tests.rs @@ -0,0 +1,157 @@ +//! WASM compatibility tests +//! +//! Tests that the library works in WebAssembly. These tests run natively +//! (test framework requires std) but exercise code paths used in WASM. +//! +//! Run tests: cargo test --test wasm_test + +use crc_fast::{checksum, CrcAlgorithm, Digest}; + +#[cfg(feature = "alloc")] +use crc_fast::{checksum_combine, checksum_with_params, CrcParams}; + +/// Test basic CRC calculation +#[test] +fn test_wasm_basic_crc32() { + let data = b"123456789"; + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, data), 0xcbf43926); + assert_eq!(checksum(CrcAlgorithm::Crc32Iscsi, data), 0xe3069283); +} + +/// Test CRC-64 variants +#[test] +fn test_wasm_crc64() { + let data = b"123456789"; + assert_eq!(checksum(CrcAlgorithm::Crc64Nvme, data), 0xae8b14860a799888); + assert_eq!(checksum(CrcAlgorithm::Crc64Xz, data), 0x995dc9bbdf1939fa); +} + +/// Test Digest API (incremental hashing) +#[test] +fn test_wasm_digest() { + let mut digest = Digest::new(CrcAlgorithm::Crc32IsoHdlc); + digest.update(b"1234"); + digest.update(b"56789"); + assert_eq!(digest.finalize(), 0xcbf43926); +} + +/// Test all CRC-32 algorithms +#[test] +fn test_wasm_all_crc32() { + let data = b"123456789"; + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, data), 0xcbf43926); + assert_eq!(checksum(CrcAlgorithm::Crc32Bzip2, data), 0xfc891918); + assert_eq!(checksum(CrcAlgorithm::Crc32Iscsi, data), 0xe3069283); + assert_eq!(checksum(CrcAlgorithm::Crc32Mpeg2, data), 0x0376e6e7); +} + +/// Test all CRC-64 algorithms +#[test] +fn test_wasm_all_crc64() { + let data = b"123456789"; + assert_eq!( + checksum(CrcAlgorithm::Crc64Ecma182, data), + 0x6c40df5f0b497347 + ); + assert_eq!(checksum(CrcAlgorithm::Crc64Nvme, data), 0xae8b14860a799888); + assert_eq!(checksum(CrcAlgorithm::Crc64Xz, data), 0x995dc9bbdf1939fa); +} + +/// Test various buffer sizes +#[test] +#[cfg(feature = "alloc")] +fn test_wasm_various_sizes() { + extern crate alloc; + use alloc::vec::Vec; + + let small = b"hello"; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, small); + + let medium = b"The quick brown fox jumps over the lazy dog"; + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, medium); + + let large: Vec = (0..1024).map(|i| (i % 256) as u8).collect(); + let _ = checksum(CrcAlgorithm::Crc32IsoHdlc, &large); + + let very_large: Vec = (0..8192).map(|i| (i % 256) as u8).collect(); + let _ = checksum(CrcAlgorithm::Crc64Nvme, &very_large); +} + +/// Test empty input +#[test] +fn test_wasm_empty() { + let empty: &[u8] = &[]; + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, empty), 0); +} + +/// Test incremental hashing +#[test] +fn test_wasm_incremental() { + let mut digest = Digest::new(CrcAlgorithm::Crc64Nvme); + for chunk in [b"123", b"456", b"789"].iter() { + digest.update(*chunk); + } + assert_eq!(digest.finalize(), 0xae8b14860a799888); +} + +/// Test digest reset +#[test] +fn test_wasm_reset() { + let mut digest = Digest::new(CrcAlgorithm::Crc32IsoHdlc); + digest.update(b"123456789"); + let result1 = digest.finalize(); + digest.reset(); + digest.update(b"123456789"); + let result2 = digest.finalize(); + assert_eq!(result1, result2); +} + +/// Test custom CRC parameters +#[test] +#[cfg(feature = "alloc")] +fn test_wasm_custom_params() { + let params = CrcParams::new( + "CRC-32/CUSTOM", + 32, + 0x04c11db7, + 0xffffffff, + true, + 0xffffffff, + 0xcbf43926, + ); + assert_eq!(checksum_with_params(params, b"123456789"), 0xcbf43926); +} + +/// Test checksum combining +#[test] +#[cfg(feature = "alloc")] +fn test_wasm_combine() { + let crc1 = checksum(CrcAlgorithm::Crc32IsoHdlc, b"1234"); + let crc2 = checksum(CrcAlgorithm::Crc32IsoHdlc, b"56789"); + let combined = checksum_combine(CrcAlgorithm::Crc32IsoHdlc, crc1, crc2, 5); + let expected = checksum(CrcAlgorithm::Crc32IsoHdlc, b"123456789"); + assert_eq!(combined, expected); +} + +/// Test reflected vs non-reflected +#[test] +fn test_wasm_reflection() { + let data = b"123456789"; + assert_eq!(checksum(CrcAlgorithm::Crc32IsoHdlc, data), 0xcbf43926); // reflected + assert_eq!(checksum(CrcAlgorithm::Crc32Bzip2, data), 0xfc891918); // non-reflected +} + +/// Test standard test vectors +#[test] +fn test_wasm_vectors() { + let vectors = [ + (b"" as &[u8], CrcAlgorithm::Crc32IsoHdlc, 0_u64), + (b"a", CrcAlgorithm::Crc32IsoHdlc, 0xe8b7be43), + (b"abc", CrcAlgorithm::Crc32IsoHdlc, 0x352441c2), + (b"123456789", CrcAlgorithm::Crc32IsoHdlc, 0xcbf43926), + ]; + + for (data, algo, expected) in &vectors { + assert_eq!(checksum(*algo, data), *expected); + } +} From 8494963acfb610096a51cd6aa772d4427a1ab572 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Tue, 11 Nov 2025 15:32:48 -0500 Subject: [PATCH 2/4] Trigger CI rebuild to clear cache From 085b17c1304ee8d8473080e67763cdeb671e15ed Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Tue, 11 Nov 2025 15:54:30 -0500 Subject: [PATCH 3/4] crc-fast-rust: trying to bust the CI cache via new keys to fix this weird failure --- .github/workflows/tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a5fc2e5..5004b85 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: with: toolchain: ${{ matrix.rust-toolchain }} components: rustfmt, clippy - cache-key: ${{ matrix.os }}-${{ matrix.rust-toolchain }} + cache-key: ${{ matrix.os }}-${{ matrix.rust-toolchain }}-v2 - name: Check run: cargo check --all-features - name: Architecture check @@ -54,7 +54,7 @@ jobs: with: toolchain: ${{ matrix.rust-toolchain }} components: rustfmt, clippy - cache-key: ${{ matrix.os }}-${{ matrix.rust-toolchain }} + cache-key: ${{ matrix.os }}-${{ matrix.rust-toolchain }}-v2 - name: Check run: cargo check --all-features - name: Architecture check @@ -168,7 +168,7 @@ jobs: toolchain: ${{ matrix.rust-toolchain }} target: ${{ matrix.target }} components: rustfmt, clippy - cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }} + cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}-v2 - name: Check no_std (no features) run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib - name: Check no_std with alloc @@ -209,7 +209,7 @@ jobs: toolchain: ${{ matrix.rust-toolchain }} target: ${{ matrix.target }} components: rustfmt, clippy - cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }} + cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}-v2 - name: Check WASM (no features) run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib - name: Check WASM with alloc From 95d4f58d53dab43405ccc0a30251584a5f7f1332 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Tue, 11 Nov 2025 20:57:19 -0500 Subject: [PATCH 4/4] Document features and build targets, improve safety documentation - Add comprehensive Features section to README explaining all feature flags - Document no_std and WASM build instructions with examples - Add safety invariant documentation to DataRegion struct - Address PR review feedback for improved documentation clarity --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/algorithm.rs | 7 +++++++ 2 files changed, 52 insertions(+) diff --git a/README.md b/README.md index 75e9499..f71e0f1 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,51 @@ the local system. Specifying the `DESTDIR` environment variable will allow you t DESTDIR=/my/custom/path make install ``` +## Features + +The library supports various feature flags for different environments: + +* `std` (default) - Standard library support, includes `alloc` +* `alloc` - Heap allocation support (enables `Digest` trait, custom CRC params, checksum combining) +* `cache` - Caches generated constants for custom CRC parameters (requires `alloc`) +* `cli` - Enables command-line tools (`checksum`, `arch-check`, `get-custom-params`) +* `ffi` - C/C++ FFI bindings for shared library +* `panic-handler` - Provides panic handler for `no_std` environments (disable when building binaries) + +### Building for no_std + +For embedded targets without standard library: + +```bash +# Minimal no_std (core CRC only, no heap) +cargo build --target thumbv7em-none-eabihf --no-default-features --lib + +# With heap allocation (enables Digest, custom params) +cargo build --target thumbv7em-none-eabihf --no-default-features --features alloc --lib + +# With caching (requires alloc) +cargo build --target thumbv7em-none-eabihf --no-default-features --features cache --lib +``` + +Tested on ARM Cortex-M (`thumbv7em-none-eabihf`, `thumbv8m.main-none-eabihf`) and RISC-V (`riscv32imac-unknown-none-elf`). + +### Building for WASM + +For WebAssembly targets: + +```bash +# Minimal WASM +cargo build --target wasm32-unknown-unknown --no-default-features --lib + +# With heap allocation (typical use case) +cargo build --target wasm32-unknown-unknown --no-default-features --features alloc --lib + +# Using wasm-pack for browser +wasm-pack build --target web --no-default-features --features alloc +``` + +Tested on `wasm32-unknown-unknown`, `wasm32-wasip1`, and `wasm32-wasip2` targets. + ## Usage Add `crc-fast = version = "1.5"` to your `Cargo.toml` dependencies, which will enable every available optimization for diff --git a/src/algorithm.rs b/src/algorithm.rs index 1b456b5..1016594 100644 --- a/src/algorithm.rs +++ b/src/algorithm.rs @@ -482,6 +482,13 @@ where } /// Data region descriptor for overlapping SIMD reads in CRC processing +/// +/// # Safety Invariants +/// +/// When this struct is used with overlapping SIMD reads: +/// - `offset` must be >= `CRC_CHUNK_SIZE` (16 bytes) +/// - `remaining` must be in range 1..=15 +/// - `full_data` must contain at least `offset + remaining` bytes struct DataRegion<'a> { full_data: &'a [u8], offset: usize,