Skip to content

Commit 02f837e

Browse files
committed
crc-fast-rust: fixed spin::RwLock incompatibility (dual paths) and cleaned up imports. added no_std example and improved CI workflow to validate no_std/wasm builds.
1 parent 605ed03 commit 02f837e

File tree

8 files changed

+344
-13
lines changed

8 files changed

+344
-13
lines changed

.github/workflows/tests.yml

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,88 @@ jobs:
118118
- name: Architecture check
119119
run: cross run --features cli --bin arch-check --target ${{ matrix.target }}
120120
- name: Test
121-
run: cross test --features cli --target ${{ matrix.target }}
121+
run: cross test --features cli --target ${{ matrix.target }}
122+
123+
test-no-std:
124+
name: Test no_std
125+
runs-on: ubuntu-latest
126+
strategy:
127+
matrix:
128+
target:
129+
- thumbv7em-none-eabihf # ARM Cortex-M4F/M7F
130+
- thumbv8m.main-none-eabihf # ARM Cortex-M33/M35P
131+
- riscv32imac-unknown-none-elf # RISC-V 32-bit
132+
rust-toolchain:
133+
- "1.81" # minimum for this crate
134+
- "stable"
135+
- "nightly"
136+
steps:
137+
- uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust
138+
- uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0
139+
with:
140+
toolchain: ${{ matrix.rust-toolchain }}
141+
target: ${{ matrix.target }}
142+
components: rustfmt, clippy
143+
cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}
144+
- name: Check no_std (no features)
145+
run: cargo check --target ${{ matrix.target }} --no-default-features --lib
146+
- name: Check no_std with alloc
147+
run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc --lib
148+
- name: Check no_std with cache
149+
run: cargo check --target ${{ matrix.target }} --no-default-features --features cache --lib
150+
- if: ${{ matrix.target == 'thumbv7em-none-eabihf' }}
151+
name: Build no_std example
152+
run: cargo build --target ${{ matrix.target }} --manifest-path examples/no_std_embedded/Cargo.toml --release
153+
- name: Run no_std tests (on host with std test harness)
154+
run: cargo test --test no_std_tests
155+
156+
test-wasm:
157+
name: Test WASM
158+
runs-on: ubuntu-latest
159+
strategy:
160+
matrix:
161+
include:
162+
# WASM 1.0/2.0 (32-bit) - all toolchains
163+
- target: wasm32-unknown-unknown
164+
rust-toolchain: "1.81"
165+
- target: wasm32-unknown-unknown
166+
rust-toolchain: "stable"
167+
- target: wasm32-unknown-unknown
168+
rust-toolchain: "nightly"
169+
# WASI preview 1 (32-bit) - all toolchains
170+
- target: wasm32-wasip1
171+
rust-toolchain: "1.81"
172+
- target: wasm32-wasip1
173+
rust-toolchain: "stable"
174+
- target: wasm32-wasip1
175+
rust-toolchain: "nightly"
176+
# WASI preview 2 (32-bit) - nightly only (experimental)
177+
- target: wasm32-wasip2
178+
rust-toolchain: "nightly"
179+
# WASM 3.0 (64-bit address space) - nightly only (experimental)
180+
- target: wasm64-unknown-unknown
181+
rust-toolchain: "nightly"
182+
steps:
183+
- uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust
184+
- uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0
185+
with:
186+
toolchain: ${{ matrix.rust-toolchain }}
187+
target: ${{ matrix.target }}
188+
components: rustfmt, clippy
189+
cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}
190+
- name: Check WASM (no features)
191+
run: cargo check --target ${{ matrix.target }} --no-default-features --lib
192+
- name: Check WASM with alloc
193+
run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc --lib
194+
- name: Check WASM with cache
195+
run: cargo check --target ${{ matrix.target }} --no-default-features --features cache --lib
196+
- name: Build WASM release
197+
run: cargo build --target ${{ matrix.target }} --no-default-features --features alloc --lib --release
198+
- name: Run WASM tests (on host with std test harness)
199+
run: cargo test --test wasm_tests
200+
- if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }}
201+
name: Install wasm-pack
202+
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
203+
- if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }}
204+
name: Build WASM package with wasm-pack
205+
run: wasm-pack build --target web --no-default-features --features alloc

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ required-features = ["std"]
7373

7474
[features]
7575
default = ["std"]
76-
std = ["alloc"] # std implies alloc is available
76+
std = ["alloc"] # std implies alloc is available
7777
cli = ["std"]
78-
alloc = ["digest"] # marker feature for heap allocation support
79-
cache = ["alloc", "hashbrown"] # caching requires alloc + hashbrown HashMap
78+
alloc = ["digest"] # marker feature for heap allocation support
79+
cache = ["alloc", "hashbrown"] # caching requires alloc + hashbrown HashMap
8080
ffi = ["std"]
8181

8282
# the features below are deprecated, aren't in use, and will be removed in the next MAJOR version (v2)

examples/.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Rust build artifacts
2+
target/
3+
Cargo.lock
4+
5+
# Debug/Release builds
6+
debug/
7+
release/
8+
9+
# Mac OS
10+
.DS_Store
11+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "no_std_embedded_example"
3+
version = "0.1.0"
4+
edition = "2024"
5+
publish = false
6+
7+
[dependencies]
8+
crc-fast = { path = "../..", default-features = false }
9+
cortex-m = "0.7.7"
10+
cortex-m-rt = "0.7.5"
11+
panic-halt = "1.0.0"
12+
13+
# Use a specific embedded target
14+
[profile.dev]
15+
panic = "abort"
16+
17+
[profile.release]
18+
panic = "abort"
19+
lto = true
20+
opt-level = "z"

examples/no_std_embedded/README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# no_std Embedded Example
2+
3+
This example demonstrates using `crc-fast` in a bare-metal embedded environment without the Rust standard library.
4+
5+
## Overview
6+
7+
The example targets ARM Cortex-M microcontrollers and shows:
8+
9+
- Simple CRC-32 and CRC-64 calculations
10+
- Incremental digest updates for streaming data
11+
- Using multiple CRC algorithms
12+
- Stack-only operation (no heap allocation)
13+
14+
## Requirements
15+
16+
```bash
17+
# Install the ARM Cortex-M target
18+
rustup target add thumbv7em-none-eabihf
19+
```
20+
21+
## Building
22+
23+
```bash
24+
cd examples/no_std_embedded
25+
cargo build --target thumbv7em-none-eabihf --release
26+
```
27+
28+
## Using in Your Project
29+
30+
Add to your `Cargo.toml`:
31+
32+
```toml
33+
[dependencies]
34+
crc-fast = { version = "1.7", default-features = false }
35+
36+
# Optional: enable caching (requires alloc)
37+
# crc-fast = { version = "1.7", default-features = false, features = ["cache"] }
38+
39+
[profile.dev]
40+
panic = "abort"
41+
42+
[profile.release]
43+
panic = "abort"
44+
```
45+
46+
## Available Features
47+
48+
- **Default (no features)**: Core CRC calculation, no allocations, no std
49+
- **`alloc`**: Enables heap-dependent features (custom CRC parameters)
50+
- **`cache`**: Enables caching of generated CRC keys (requires `alloc`)
51+
52+
## Example Usage
53+
54+
```rust
55+
#![no_std]
56+
57+
use crc_fast::{checksum, CrcAlgorithm};
58+
59+
// Simple CRC-32 calculation (no heap allocation)
60+
let data = b"sensor_reading_12345";
61+
let crc = checksum(CrcAlgorithm::Crc32IsoHdlc, data);
62+
63+
// Use the CRC to validate data integrity, protect against corruption, etc.
64+
```
65+
66+
## Common Use Cases
67+
68+
### 1. Packet Validation
69+
70+
```rust
71+
fn validate_packet(packet: &[u8]) -> bool {
72+
let received_crc = u32::from_le_bytes([packet[packet.len()-4..]]);
73+
let calculated_crc = checksum(CrcAlgorithm::Crc32Iscsi, &packet[..packet.len()-4]) as u32;
74+
received_crc == calculated_crc
75+
}
76+
```
77+
78+
### 2. Flash Memory Verification
79+
80+
```rust
81+
fn verify_flash_sector(sector_data: &[u8], expected_crc: u64) -> bool {
82+
checksum(CrcAlgorithm::Crc64Nvme, sector_data) == expected_crc
83+
}
84+
```
85+
86+
### 3. Streaming Data
87+
88+
```rust
89+
let mut digest = Digest::new(CrcAlgorithm::Crc32IsoHdlc);
90+
for chunk in incoming_data_chunks {
91+
digest.update(chunk);
92+
}
93+
let final_crc = digest.finalize();
94+
```
95+
96+
## Memory Usage
97+
98+
- **Code size**: ~2-5KB (depending on optimization and algorithms used)
99+
- **Stack**: Minimal (< 256 bytes per digest)
100+
- **No heap required**: Works without allocator in no_std
101+
102+
## Performance
103+
104+
Hardware-accelerated on supported platforms:
105+
106+
- **ARM Cortex-M with NEON/AES**: Uses PMULL instructions
107+
- **Other platforms**: Optimized software implementation
108+
- **Speed**: Typically 50-100+ MB/s even on low-power MCUs
109+
110+
## Notes
111+
112+
- The library automatically selects the best implementation for your target
113+
- No runtime feature detection overhead in no_std builds
114+
- All algorithms are compile-time dispatched
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! no_std embedded example for crc-fast
2+
//!
3+
//! This example demonstrates using crc-fast in a bare-metal embedded environment
4+
//! without the Rust standard library.
5+
//!
6+
//! Target: Cortex-M (ARM Thumb)
7+
//! Build: cargo build --target thumbv7em-none-eabihf
8+
9+
#![no_std]
10+
#![no_main]
11+
12+
use panic_halt as _;
13+
14+
use cortex_m_rt::entry;
15+
use crc_fast::{checksum, CrcAlgorithm, Digest};
16+
17+
#[entry]
18+
fn main() -> ! {
19+
// Example 1: Simple checksum calculation
20+
// No heap allocation required - all stack-based
21+
let data = b"123456789";
22+
let crc32 = checksum(CrcAlgorithm::Crc32IsoHdlc, data);
23+
24+
// Expected: 0xcbf43926
25+
// In a real application, you'd send this over UART, store in flash, etc.
26+
let _ = crc32;
27+
28+
// Example 2: CRC-64 calculation
29+
let crc64 = checksum(CrcAlgorithm::Crc64Nvme, data);
30+
let _ = crc64; // Expected: 0xae8b14860a799888
31+
32+
// Example 3: Incremental digest (useful for streaming data)
33+
let mut digest = Digest::new(CrcAlgorithm::Crc32Iscsi);
34+
35+
// Process data in chunks as it arrives
36+
digest.update(b"1234");
37+
digest.update(b"5678");
38+
digest.update(b"9");
39+
40+
let result = digest.finalize(); // Expected: 0xe3069283
41+
let _ = result;
42+
43+
// Example 4: Using different CRC algorithms
44+
// All standard algorithms are available
45+
let bzip2_crc = checksum(CrcAlgorithm::Crc32Bzip2, data);
46+
let _ = bzip2_crc;
47+
48+
// Example 5: Reusing digest
49+
digest.reset();
50+
digest.update(b"new data");
51+
let new_result = digest.finalize();
52+
let _ = new_result;
53+
54+
// In a real embedded application, you would:
55+
// - Use CRCs for data integrity checks
56+
// - Validate received packets
57+
// - Verify flash memory contents
58+
// - Check configuration data
59+
60+
loop {
61+
// Main loop would handle your application logic
62+
cortex_m::asm::nop();
63+
}
64+
}

src/cache.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ fn get_cache() -> &'static RwLock<HashMap<CrcParamsCacheKey, [u64; 23]>> {
114114
///
115115
/// Array of 23 pre-computed folding keys for SIMD CRC calculation
116116
pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] {
117-
#[cfg(any(feature = "std", feature = "cache"))]
117+
#[cfg(feature = "std")]
118118
{
119119
let cache_key = CrcParamsCacheKey::new(width, poly, reflected);
120120

@@ -138,6 +138,31 @@ pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23]
138138
keys
139139
}
140140

141+
#[cfg(all(not(feature = "std"), feature = "cache"))]
142+
{
143+
let cache_key = CrcParamsCacheKey::new(width, poly, reflected);
144+
145+
// Try cache read first - multiple threads can read simultaneously
146+
// spin::RwLock returns guards directly (no Result wrapper)
147+
{
148+
let cache = get_cache().read();
149+
if let Some(keys) = cache.get(&cache_key) {
150+
return *keys;
151+
}
152+
} // Drop read lock before generating keys
153+
154+
// Generate keys outside of write lock to minimize lock hold time
155+
let keys = generate::keys(width, poly, reflected);
156+
157+
// Cache the result - spin::RwLock doesn't use Result wrapper
158+
{
159+
let mut cache = get_cache().write();
160+
cache.insert(cache_key, keys);
161+
}
162+
163+
keys
164+
}
165+
141166
#[cfg(not(any(feature = "std", feature = "cache")))]
142167
{
143168
generate::keys(width, poly, reflected)
@@ -160,9 +185,19 @@ pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23]
160185
/// reduce performance as those threads will need to regenerate keys on their next access.
161186
#[cfg(test)]
162187
pub(crate) fn clear_cache() {
163-
// Best-effort cache clear - if lock is poisoned or unavailable, silently continue
164-
// This ensures the function never panics or blocks program execution
165-
let _ = get_cache().write().map(|mut cache| cache.clear());
188+
#[cfg(feature = "std")]
189+
{
190+
// Best-effort cache clear - if lock is poisoned or unavailable, silently continue
191+
// This ensures the function never panics or blocks program execution
192+
let _ = get_cache().write().map(|mut cache| cache.clear());
193+
}
194+
195+
#[cfg(all(not(feature = "std"), feature = "cache"))]
196+
{
197+
// spin::RwLock doesn't use Result wrapper
198+
let mut cache = get_cache().write();
199+
cache.clear();
200+
}
166201
}
167202

168203
#[cfg(test)]

0 commit comments

Comments
 (0)