diff --git a/.kiro/specs/crc-params-caching/design.md b/.kiro/specs/crc-params-caching/design.md new file mode 100644 index 0000000..b204e65 --- /dev/null +++ b/.kiro/specs/crc-params-caching/design.md @@ -0,0 +1,270 @@ +# Design Document + +## Overview + +The CRC parameters caching system will add a thread-safe, memory-efficient cache to the `CrcParams::new()` method. The cache will store pre-computed folding keys indexed by the input parameters, eliminating redundant key generation for identical parameter sets. The design prioritizes performance, thread safety, and minimal memory overhead while maintaining complete API compatibility. + +## Architecture + +### Cache Structure + +The caching system will use a global, thread-safe cache implemented with: + +- **Cache Storage**: `std::collections::HashMap` +- **Thread Safety**: `std::sync::RwLock` for concurrent read access with exclusive write access +- **Cache Key**: Custom struct containing all parameters that affect key generation +- **Lazy Initialization**: `std::sync::OnceLock` to initialize the cache on first use + +### Cache Key Design + +```rust +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct CrcParamsCacheKey { + width: u8, + poly: u64, + reflected: bool, +} +``` + +The cache key includes only the parameters that directly affect key generation (`width`, `poly`, `reflected`), excluding parameters like `name`, `init`, `xorout`, and `check` which don't influence the mathematical key computation. + +### Cache Access Pattern + +1. **Cache Hit Path**: Read lock → HashMap lookup → Return cached keys +2. **Cache Miss Path**: Read lock → Cache miss → Generate keys → Write lock → Store in cache → Return keys +3. **Concurrent Access**: Multiple readers can access simultaneously; writers get exclusive access + +## Components and Interfaces + +### Core Components + +#### 1. Cache Module (`src/cache.rs`) + +```rust +use std::collections::HashMap; +use std::sync::{OnceLock, RwLock}; + +static CACHE: OnceLock>> = OnceLock::new(); + +pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] +pub fn clear_cache() // For testing and memory management +``` + +#### 2. Cache Key Structure + +```rust +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct CrcParamsCacheKey { + width: u8, + poly: u64, + reflected: bool, +} +``` + +#### 3. Modified CrcParams Implementation + +The existing `CrcParams::new()` method will be updated to use the cache: + +```rust +impl CrcParams { + pub fn new( + name: &'static str, + width: u8, + poly: u64, + init: u64, + reflected: bool, + xorout: u64, + check: u64, + ) -> Self { + let keys = cache::get_or_generate_keys(width, poly, reflected); + + let algorithm = match width { + 32 => CrcAlgorithm::Crc32Custom, + 64 => CrcAlgorithm::Crc64Custom, + _ => panic!("Unsupported width: {}", width), + }; + + Self { + algorithm, + name, + width, + poly, + init, + refin: reflected, + refout: reflected, + xorout, + check, + keys, + } + } +} +``` + +### Interface Design + +#### Public Interface +- No changes to existing public APIs +- `CrcParams::new()` maintains identical signature and behavior +- Cache operations are completely internal + +#### Internal Interface +- `cache::get_or_generate_keys()` - Primary cache interface +- `cache::clear_cache()` - For testing and memory management +- Cache key creation and hashing handled internally + +## Data Models + +### Cache Key Model +```rust +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct CrcParamsCacheKey { + width: u8, // CRC width (32 or 64) + poly: u64, // Polynomial value + reflected: bool, // Reflection mode +} +``` + +### Cache Storage Model +```rust +type CacheStorage = HashMap; +type ThreadSafeCache = RwLock; +``` + +### Memory Layout Considerations +- Cache keys: ~17 bytes per entry (8 + 8 + 1 bytes + HashMap overhead) +- Cache values: 184 bytes per entry (23 × 8 bytes) +- Total per entry: ~201 bytes + HashMap overhead +- Expected usage: 1-10 unique parameter sets in typical applications (single parameter set most common) + +## Error Handling + +### Cache Access Errors +- **RwLock Poisoning**: If a thread panics while holding the write lock, subsequent accesses will fall back to direct key generation +- **Memory Allocation**: HashMap growth failures will be handled by Rust's standard allocation error handling + +### Fallback Strategy +```rust +fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] { + let cache_key = CrcParamsCacheKey { width, poly, reflected }; + + // Try cache read first + 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 let Ok(mut cache) = get_cache().write() { + cache.insert(cache_key, keys); + } + + keys +} +``` + +### Error Recovery +- Lock poisoning: Continue with direct key generation +- Memory pressure: Cache operations become no-ops, functionality preserved +- Hash collisions: Handled by HashMap implementation + +## Testing Strategy + +### Unit Tests +1. **Cache Functionality** + - Verify cache hits return identical keys + - Verify cache misses generate and store keys + - Test cache key equality and hashing + +2. **Thread Safety** + - Concurrent read access tests + - Read-write contention tests + - Cache consistency under concurrent access + +3. **Performance Tests** + - Benchmark cache hit vs. miss performance + - Memory usage validation + - Comparison with uncached implementation + +4. **Edge Cases** + - Empty cache behavior + - Cache with single entry + - Maximum realistic cache size + - Lock poisoning recovery + +### Integration Tests +1. **API Compatibility** + - Existing CrcParams::new() behavior unchanged + - All existing tests continue to pass + - Identical results for cached vs. uncached keys + +2. **Real-world Usage Patterns** + - Multiple CrcParams instances with same parameters + - Mixed usage with different parameters + - Long-running application simulation + +### Performance Benchmarks +1. **Cache Hit Performance**: Measure lookup time vs. key generation time +2. **Cache Miss Performance**: Measure overhead of cache check + generation +3. **Memory Usage**: Track cache memory consumption over time +4. **Concurrent Access**: Measure performance under thread contention + +## Implementation Phases + +### Phase 1: Core Cache Implementation +- Create cache module with basic HashMap storage +- Implement thread-safe access with RwLock +- Add cache key structure and hashing + +### Phase 2: Integration +- Modify CrcParams::new() to use cache +- Add fallback error handling +- Ensure API compatibility + +### Phase 3: Testing and Optimization +- Comprehensive test suite +- Performance benchmarking +- Memory usage optimization +- Documentation updates + +## Performance Considerations + +### Cache Hit Performance +- Expected improvement: 50-100x faster than key generation +- RwLock read access: ~10-20ns overhead +- HashMap lookup: O(1) average case, ~50-100ns + +### Cache Miss Performance +- Additional overhead: ~100-200ns for cache check +- Write lock acquisition: ~50-100ns +- HashMap insertion: O(1) average case + +### Memory Efficiency +- Cache overhead per entry: ~201 bytes +- Expected cache size: 200 bytes - 2KB for typical applications +- Memory growth: Linear with unique parameter combinations + +### Thread Contention +- Read-heavy workload: Excellent scalability +- Write contention: Minimal impact (writes are rare after warmup) +- Lock-free reads: Multiple threads can read simultaneously + +## Security Considerations + +### Memory Safety +- All cache operations use safe Rust constructs +- No unsafe code in cache implementation +- HashMap provides memory safety guarantees + +### Thread Safety +- RwLock prevents data races +- Cache key immutability prevents modification after creation +- Atomic operations for cache initialization + +### Resource Management +- Cache growth is bounded by unique parameter combinations +- No automatic eviction policy (acceptable for typical usage) +- Manual cache clearing available for memory management \ No newline at end of file diff --git a/.kiro/specs/crc-params-caching/requirements.md b/.kiro/specs/crc-params-caching/requirements.md new file mode 100644 index 0000000..3817d14 --- /dev/null +++ b/.kiro/specs/crc-params-caching/requirements.md @@ -0,0 +1,57 @@ +# Requirements Document + +## Introduction + +This feature adds a caching layer to the `CrcParams::new()` method to optimize performance when the same CRC parameters are used multiple times during program execution. Currently, each call to `CrcParams::new()` regenerates the folding keys through expensive mathematical operations, even when identical parameters have been used before. The caching system will store generated keys in memory and reuse them for subsequent requests with matching parameters, significantly improving performance for applications that create multiple CRC instances with the same configuration. + +## Requirements + +### Requirement 1 + +**User Story:** As a developer using the crc-fast library, I want CrcParams::new() to cache generated keys so that repeated calls with identical parameters don't regenerate keys unnecessarily. + +#### Acceptance Criteria + +1. WHEN CrcParams::new() is called with parameters that have been used before THEN the system SHALL return cached keys instead of regenerating them +2. WHEN CrcParams::new() is called with new parameters for the first time THEN the system SHALL generate the keys and cache them for future use +3. WHEN multiple threads call CrcParams::new() concurrently with the same parameters THEN the system SHALL handle thread safety correctly without data races + +### Requirement 2 + +**User Story:** As a performance-conscious developer, I want the caching mechanism to have minimal overhead so that it doesn't negatively impact single-use scenarios. + +#### Acceptance Criteria + +1. WHEN CrcParams::new() is called for the first time with any parameters THEN the performance overhead SHALL be minimal compared to the current implementation +2. WHEN CrcParams::new() is called with cached parameters THEN the lookup SHALL be significantly faster than key generation +3. WHEN the cache is accessed THEN the lookup mechanism SHALL use efficient data structures optimized for the expected access patterns + +### Requirement 3 + +**User Story:** As a developer working with custom CRC parameters, I want the cache to correctly identify identical parameter sets so that functionally equivalent calls are properly cached. + +#### Acceptance Criteria + +1. WHEN two CrcParams::new() calls use identical values for all parameters (name, width, poly, init, reflected, xorout, check) THEN the system SHALL treat them as cache hits +2. WHEN two CrcParams::new() calls differ in any parameter value THEN the system SHALL treat them as separate cache entries +3. WHEN parameter comparison is performed THEN the system SHALL use all relevant fields to determine cache key uniqueness + +### Requirement 4 + +**User Story:** As a developer concerned about memory usage, I want the cache to have reasonable memory management so that it doesn't grow unbounded in long-running applications. + +#### Acceptance Criteria + +1. WHEN the cache stores parameter sets THEN it SHALL use memory-efficient storage for the cache keys and values +2. WHEN the application runs for extended periods THEN the cache SHALL not consume excessive memory for typical usage patterns +3. IF the cache grows large THEN the system SHALL provide a way to clear or manage cache size (though automatic eviction is not required for this initial implementation) + +### Requirement 5 + +**User Story:** As a developer integrating this library, I want the caching to be transparent so that existing code continues to work without modifications. + +#### Acceptance Criteria + +1. WHEN existing code calls CrcParams::new() THEN it SHALL work exactly as before with no API changes required +2. WHEN CrcParams instances are created THEN they SHALL have identical behavior regardless of whether keys came from cache or generation +3. WHEN the caching system is active THEN it SHALL not affect the public interface or return values of CrcParams::new() \ No newline at end of file diff --git a/.kiro/specs/crc-params-caching/tasks.md b/.kiro/specs/crc-params-caching/tasks.md new file mode 100644 index 0000000..99019a6 --- /dev/null +++ b/.kiro/specs/crc-params-caching/tasks.md @@ -0,0 +1,78 @@ +# Implementation Plan + +- [x] 1. Create cache module with core data structures + - Create `src/cache.rs` module file + - Define `CrcParamsCacheKey` struct with `width`, `poly`, and `reflected` fields + - Implement `Debug`, `Clone`, `PartialEq`, `Eq`, and `Hash` traits for the cache key + - Add module declaration to `src/lib.rs` + - _Requirements: 1.1, 3.1, 3.2_ + +- [x] 2. Implement thread-safe cache storage + - Define global cache using `std::sync::OnceLock>>` + - Implement `get_cache()` function to initialize and return cache reference + - Add necessary imports for `std::collections::HashMap`, `std::sync::{OnceLock, RwLock}` + - _Requirements: 1.3, 2.3_ + +- [x] 3. Implement cache lookup and storage functions + - Create `get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23]` function + - Implement cache hit path with read lock and HashMap lookup + - Implement cache miss path with key generation followed by write lock and storage + - Add error handling for lock poisoning with fallback to direct key generation + - _Requirements: 1.1, 1.2, 4.1_ + +- [x] 4. Add cache management utilities + - Implement `clear_cache()` function for testing and memory management + - Add proper error handling for all cache operations + - Ensure all cache operations are best-effort with graceful degradation + - _Requirements: 4.3_ + +- [x] 5. Integrate cache into CrcParams::new() + - Modify `CrcParams::new()` in `src/structs.rs` to use `cache::get_or_generate_keys()` + - Replace direct call to `generate::keys()` with cache lookup + - Ensure all existing functionality remains unchanged + - Verify that the function signature and behavior are identical + - _Requirements: 1.1, 1.2, 5.1, 5.2, 5.3_ + +- [x] 6. Create comprehensive unit tests for cache functionality + - Add tests to `src/cache.rs` + - Write tests for cache key creation, equality, and hashing + - Test cache hit scenarios (same parameters return cached keys) + - Test cache miss scenarios (new parameters generate and cache keys) + - Test that cached keys are identical to directly generated keys + - _Requirements: 1.1, 1.2, 3.1, 3.2_ + +- [x] 7. Add thread safety tests + - Write concurrent access tests using `std::thread` + - Test multiple threads reading from cache simultaneously + - Test read-write contention scenarios + - Verify cache consistency under concurrent access + - Test lock poisoning recovery behavior + - _Requirements: 1.3_ + +- [x] 8. Create integration tests for CrcParams compatibility + - Add tests to verify `CrcParams::new()` behavior is unchanged + - Test that all existing CRC parameter combinations work correctly + - Verify that cached and uncached results are identical + - Test multiple `CrcParams` instances with same parameters use cached keys + - _Requirements: 5.1, 5.2, 5.3_ + +- [x] 9. Add comprehensive error handling tests + - Test cache behavior when locks are poisoned + - Test memory allocation failure scenarios + - Verify fallback to direct key generation works correctly + - Test cache operations under memory pressure + - _Requirements: 4.1, 4.2_ + +- [x] 10. Update existing tests to work with caching + - Run all existing tests to ensure no regressions + - Update any tests that might be affected by caching behavior + - Ensure test isolation by clearing cache between tests if needed + - Verify all CRC algorithm tests still pass + - _Requirements: 5.1, 5.2, 5.3_ + +- [x] 11. Add documentation and finalize implementation + - Add inline documentation for all new public and internal functions + - Update module-level documentation + - Add usage examples in code comments + - Ensure all code follows existing project style and conventions + - _Requirements: 5.3_ \ No newline at end of file diff --git a/.kiro/specs/future-proof-crc-params/design.md b/.kiro/specs/future-proof-crc-params/design.md new file mode 100644 index 0000000..68c4a58 --- /dev/null +++ b/.kiro/specs/future-proof-crc-params/design.md @@ -0,0 +1,358 @@ +# Design Document + +## Overview + +This design implements a future-proof CrcParams structure using an internal enum-based key storage system that can expand to support different key array sizes without breaking compatibility. The approach maintains the simplicity of const definitions while providing safe key access and zero runtime overhead through compiler optimizations. + +## Architecture + +### Core Components + +1. **CrcKeysStorage Enum**: Internal storage that can hold different key array sizes +2. **CrcParams Structure**: Updated to use CrcKeysStorage internally while maintaining public API +3. **Safe Accessor Methods**: Bounds-checked key access methods on CrcParams +4. **Helper Functions**: Const-friendly constructors for CrcKeysStorage variants + +### Design Principles + +- **Zero Runtime Overhead**: Enum dispatch optimized away by compiler +- **Backwards Compatibility**: Existing const definitions require minimal changes +- **Gradual Migration**: Can be implemented in phases without breaking builds +- **Safety First**: Bounds checking prevents panics from out-of-range access + +## Components and Interfaces + +### CrcKeysStorage Enum + +```rust +#[derive(Clone, Copy, Debug)] +enum CrcKeysStorage { + /// Current 23-key format (for existing algorithms which includes 256 byte folding distances) + KeysFold256([u64; 23]), + /// Future 25-key format (for potential future expanded folding distances, for testing purposes only) + KeysFutureTest([u64; 25]), + // Additional variants can be added as needed +} +``` + +**Key Methods:** +- `get_key(index: usize) -> u64`: Safe key access with bounds checking +- `key_count() -> usize`: Returns actual number of keys available +- `from_keys_fold_256(keys: [u64; 23]) -> Self`: Const constructor for 23-key arrays +- `from_keys_fold_future_test(keys: [u64; 25]) -> Self`: Const constructor for 25-key arrays + +### Updated CrcParams Structure + +```rust +#[derive(Clone, Copy, Debug)] +pub struct CrcParams { + pub algorithm: CrcAlgorithm, + pub name: &'static str, + pub width: u8, + pub poly: u64, + pub init: u64, + pub refin: bool, + pub refout: bool, + pub xorout: u64, + pub check: u64, + pub keys: CrcKeysStorage, // Changed from [u64; 23] +} +``` + +**Key Methods:** +- `get_key(index: usize) -> u64`: Delegates to CrcKeysStorage +- `get_key_checked(index: usize) -> Option`: Optional key access +- `key_count() -> usize`: Returns actual key count + +### Const Definition Pattern + +```rust +// Before (Phase 2): +pub const CRC32_ISCSI: CrcParams = CrcParams { + // ... other fields unchanged ... + keys: KEYS_1EDC6F41_REFLECTED, // [u64; 23] +}; + +// After (Phase 3): +pub const CRC32_ISCSI: CrcParams = CrcParams { + // ... other fields unchanged ... + keys: CrcKeysStorage::from_keys_fold_256(KEYS_1EDC6F41_REFLECTED), +}; +``` + +## Data Models + +### Key Storage Variants + +| Variant | Array Size | Use Case | +|---------|------------|----------| +| KeysFold256 | [u64; 23] | Current implementation (128/256-byte folding) | +| KeysFutureTest | [u64; 25] | Future expansion | + +### Migration States + +| Phase | CrcParams.keys Type | Architecture Code | Const Definitions | +|-------|-------------------|------------------|------------------| +| 1 | [u64; 23] | Direct access | Unchanged | +| 2 | [u64; 23] | Safe accessors | Unchanged | +| 3 | CrcKeysStorage | Safe accessors | Updated | + +## Error Handling + +### Bounds Checking Strategy + +1. **Safe Default**: Out-of-bounds key access returns 0 instead of panicking +2. **Optional Access**: `get_key_checked()` returns `None` for invalid indices +3. **Graceful Degradation**: Code continues to function with missing keys + +### Error Scenarios + +| Scenario | Behavior | Rationale | +|----------|----------|-----------| +| Access key[30] with 23-key storage | Returns 0 | Allows future expansion without breaking existing code | +| Invalid key index | Returns 0 | Prevents panics, maintains stability | +| Empty key storage | Returns 0 for all indices | Defensive programming | + +## Testing Strategy + +### Unit Tests + +1. **CrcKeysStorage Tests**: + - Verify correct key storage and retrieval for each variant + - Test bounds checking behavior + - Validate const constructor functions + +2. **CrcParams Integration Tests**: + - Verify safe accessor methods work correctly + - Test backwards compatibility with existing const definitions + - Validate zero runtime overhead through benchmarks + +3. **Migration Tests**: + - Test each phase independently + - Verify existing functionality remains intact + - Validate const definition updates + +### Compatibility Tests + +1. **Third-Party Simulation**: + - Create mock third-party const definitions + - Verify they continue working through all phases + - Test key access patterns used by external code + +2. **Performance Tests**: + - Benchmark key access performance vs direct array access + - Verify compiler optimizations eliminate enum dispatch + - Measure memory usage impact + +### Integration Tests + +1. **Architecture Code Tests**: + - Update existing architecture tests to use safe accessors + - Verify SIMD operations work correctly with new key access + - Test folding operations across different key storage variants + +2. **End-to-End Tests**: + - Verify CRC calculations remain correct after migration + - Test custom CrcParams creation and usage + - Validate `get-custom-params` binary output + +## Implementation Phases + +### Phase 1: Add New Types +- Add CrcKeysStorage enum to codebase +- Add helper methods to CrcParams (delegating to existing keys field) +- Maintain existing [u64; 23] field for compatibility +- All tests continue to pass + +### Phase 2: Update Architecture Code +- Replace direct key array access with safe accessor methods +- Update SIMD and folding code to use `params.get_key(index)` +- Maintain backwards compatibility +- Performance remains identical + +### Phase 3: Switch to New Storage +- Change CrcParams.keys field from [u64; 23] to CrcKeysStorage +- Update all const definitions to use CrcKeysStorage::from_keys_23() +- Update `get-custom-params` binary output format +- This is the only breaking change, but minimal impact + +## Performance Considerations + +### Compiler Optimizations + +The Rust compiler optimizes enum dispatch when: +1. All variants have the same access pattern +2. The enum is used in hot paths with predictable patterns +3. Inlining is enabled for accessor methods + +Expected assembly output for `params.get_key(21)`: +```assembly +; Same as direct array access keys[21] +mov rax, qword ptr [rdi + 168 + 21*8] +``` + +### Memory Layout + +| Storage Type | Memory Usage | Alignment | +|--------------|--------------|-----------| +| KeysFold256 | 184 bytes | 8-byte aligned | +| KeysFutureTest | 200 bytes | 8-byte aligned | +| Enum overhead | 0 bytes | (optimized away) | + +## FFI Future-Proofing Design + +### Problem Analysis + +The current C FFI interface has several limitations: +1. **Fixed Key Array**: `CrcFastParams` struct uses `uint64_t keys[23]` hardcoded +2. **No Expansion Path**: Cannot support future key variants with different sizes +3. **Conversion Limitation**: `to_keys_array_23()` only works for current 23-key variant + +### FFI Design Solution + +Since the current FFI hasn't shipped yet, we can make it truly future-proof from the start using a pointer-based approach that can handle any number of keys. + +#### Truly Future-Proof CrcFastParams Structure + +```c +// Completely future-proof structure using pointer to keys +typedef struct CrcFastParams { + enum CrcFastAlgorithm algorithm; + uint8_t width; + uint64_t poly; + uint64_t init; + bool refin; + bool refout; + uint64_t xorout; + uint64_t check; + uint32_t key_count; // Number of keys available + const uint64_t *keys; // Pointer to keys array (managed by Rust) +} CrcFastParams; +``` + +#### Key Management Strategy + +1. **Rust-Managed Memory**: Keys remain in Rust-managed memory +2. **Stable Pointers**: Use Box::leak or static storage for stable pointers +3. **Automatic Cleanup**: Rust handles memory management transparently +4. **No Size Limits**: Can support any number of keys (23, 25, 50, 100+) + +#### Internal Implementation + +```rust +// Helper to create stable key pointers +fn create_stable_key_pointer(keys: &CrcKeysStorage) -> *const u64 { + match keys { + CrcKeysStorage::KeysFold256(keys) => keys.as_ptr(), + CrcKeysStorage::KeysFutureTest(keys) => keys.as_ptr(), + // Future variants automatically supported + } +} + +impl From for CrcFastParams { + fn from(params: CrcParams) -> Self { + CrcFastParams { + algorithm: params.algorithm.into(), + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + key_count: params.key_count() as u32, + keys: create_stable_key_pointer(¶ms.keys), + } + } +} + +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 => CrcKeysStorage::from_keys_fold_256( + keys.try_into().expect("Invalid key count for fold_256") + ), + 25 => 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", + width: value.width, + poly: value.poly, + init: value.init, + refin: value.refin, + refout: value.refout, + xorout: value.xorout, + check: value.check, + keys: storage, + } + } +} +``` + +#### Enhanced FFI Functions + +```rust +#[no_mangle] +pub extern "C" fn crc_fast_get_custom_params(...) -> CrcFastParams { + let params = CrcParams::new(...); + params.into() // Automatic conversion +} +``` + +#### Benefits of This Approach + +1. **Truly Future-Proof**: No hardcoded limits, supports any key count +2. **Zero Copy**: Keys remain in original Rust memory, just expose pointer +3. **Memory Safe**: Rust manages memory, C gets stable pointers +4. **Performance**: Direct pointer access, no copying overhead +5. **Automatic Support**: New CrcKeysStorage variants automatically work +6. **Idiomatic C**: Direct array access pattern familiar to C developers + +#### C Usage Pattern + +```c +// Get custom parameters +CrcFastParams params = crc_fast_get_custom_params(...); + +// Direct access with bounds checking (C developer responsibility) +for (uint32_t i = 0; i < params.key_count; i++) { + uint64_t key = params.keys[i]; + // Use key... +} + +// Or access specific keys directly +uint64_t key21 = params.keys[21]; // Direct access (if bounds known) +``` + +## Security Considerations + +### Bounds Safety + +The new design eliminates array bounds panics, which could be exploited in unsafe contexts. Safe key access prevents: +- Buffer overflow attacks through malicious key indices +- Denial of service through panic-induced crashes +- Information disclosure through out-of-bounds memory access + +### FFI Safety + +The enhanced FFI design adds additional safety measures: +- **Key Count Validation**: V2 functions validate key_count before conversion +- **Buffer Bounds**: 32-key buffer prevents overflow while allowing future expansion +- **Graceful Degradation**: Invalid key counts return error codes instead of panicking + +### Const Safety + +All const definitions remain compile-time validated, preventing: +- Runtime key generation vulnerabilities +- Dynamic key modification attacks +- Timing-based side-channel attacks on key access \ No newline at end of file diff --git a/.kiro/specs/future-proof-crc-params/requirements.md b/.kiro/specs/future-proof-crc-params/requirements.md new file mode 100644 index 0000000..6627b04 --- /dev/null +++ b/.kiro/specs/future-proof-crc-params/requirements.md @@ -0,0 +1,86 @@ +# Requirements Document + +## Introduction + +This feature implements a future-proof CrcParams structure that can expand to support additional folding keys (for example, larger folding distances) without breaking API compatibility for third-party applications. The solution maintains the simplicity of const definitions while providing safe key access and internal flexibility for future expansion. + +## Requirements + +### Requirement 1 + +**User Story:** As a library maintainer, I want to expand CRC folding key support from 23 to 24+ keys for potentially larger folding distances in the future, so that I can improve performance for large data processing. + +#### Acceptance Criteria + +1. WHEN I add support for larger folding distances THEN existing third-party applications with hardcoded 23-key CrcParams SHALL continue to compile and function correctly +2. WHEN I expand key arrays from 23 to 24+ elements THEN existing const definitions SHALL require minimal changes (only the keys field) + +### Requirement 2 + +**User Story:** As a third-party application developer, I want to define custom CrcParams as const definitions, so that I can embed CRC configurations directly in my code without runtime overhead. + +#### Acceptance Criteria + +1. WHEN I define a custom CrcParams const THEN I SHALL be able to use the same simple struct literal syntax as currently exists +2. WHEN I access CRC keys through the CrcParams interface THEN the performance SHALL be identical to direct array access (zero runtime overhead) +3. WHEN the library expands key support THEN my existing const definitions SHALL continue to work without modification + +### Requirement 3 + +**User Story:** As a library maintainer, I want safe key access methods that prevent array bounds panics, so that the library is robust against future expansion and misuse. + +#### Acceptance Criteria + +1. WHEN architecture code accesses CRC keys THEN it SHALL use bounds-checked methods instead of direct array indexing +2. WHEN code requests key count information THEN it SHALL receive the actual number of available keys for that CrcParams instance + +### Requirement 4 + +**User Story:** As a library maintainer, I want internal flexibility to support different key array sizes, so that I can optimize different CRC algorithms with varying folding distance requirements. + +#### Acceptance Criteria + +1. WHEN I create CrcParams with 23 keys THEN the system SHALL store and access exactly 23 keys efficiently +2. WHEN I create CrcParams with 25 keys THEN the system SHALL store and access exactly 25 keys efficiently +4. WHEN the compiler optimizes the code THEN enum dispatch for key access SHALL be eliminated (zero runtime overhead) + +### Requirement 5 + +**User Story:** As a library maintainer, I want to migrate existing code gradually, so that I can implement the changes in phases without breaking the build at any point. + +#### Acceptance Criteria + +1. WHEN I add the new CrcKeysStorage types THEN existing code SHALL continue to compile and function +2. WHEN I update architecture code to use safe accessors THEN the change SHALL be backward compatible +3. WHEN I switch CrcParams to use CrcKeysStorage THEN the migration SHALL require only updating const definitions +4. WHEN each phase is complete THEN all existing tests SHALL continue to pass + +### Requirement 6 + +**User Story:** As a third-party application developer, I want the `get-custom-params` binary to output the updated `CrcParams` const definition using the new key storage approach, so that I can easily generate future-proof custom CRC parameter definitions. + +#### Acceptance Criteria + +1. WHEN I run the `get-custom-params` binary THEN it SHALL output CrcParams const definitions using CrcKeysStorage::from_keys_fold_256() +2. WHEN I copy the generated const definition THEN it SHALL compile and work correctly with the new CrcParams structure +3. WHEN the output format changes THEN the generated code SHALL remain compatible with the current CrcParams API + +### Requirement 7 + +**User Story:** As a C/C++ application developer, I want the FFI interface to be future-proof for key expansion, so that my applications can benefit from future performance improvements without requiring code changes. + +#### Acceptance Criteria + +1. WHEN the library adds support for larger key arrays THEN existing C code using CrcFastParams SHALL continue to compile and function correctly +2. WHEN I create custom CRC parameters in C THEN I SHALL be able to specify the key count and keys dynamically +3. WHEN I access CRC functionality through the C API THEN the performance SHALL remain identical to direct Rust usage + +### Requirement 8 + +**User Story:** As a library maintainer, I want the C FFI interface to support different key array sizes internally, so that C users can benefit from future CRC algorithm improvements. + +#### Acceptance Criteria + +1. WHEN I add new CrcKeysStorage variants with different key counts THEN the C API SHALL automatically support them +2. WHEN C code specifies custom key arrays THEN they SHALL be automatically converted to the appropriate internal storage format +3. WHEN C code queries key information THEN it SHALL receive accurate key count and key data for the specific CRC algorithm being used diff --git a/.kiro/specs/future-proof-crc-params/tasks.md b/.kiro/specs/future-proof-crc-params/tasks.md new file mode 100644 index 0000000..1005cb5 --- /dev/null +++ b/.kiro/specs/future-proof-crc-params/tasks.md @@ -0,0 +1,122 @@ +# Implementation Plan + +- [x] 1. Phase 1: Add CrcKeysStorage enum and helper methods + - Add CrcKeysStorage enum with KeysFold256 and KeysFutureTest variants + - Implement get_key() and key_count() methods on CrcKeysStorage + - Add const constructor methods from_keys_fold_256() and from_keys_fold_future_test() + - Add safe accessor methods to CrcParams that delegate to existing keys field + - Write comprehensive unit tests for CrcKeysStorage functionality + - _Requirements: 4.1, 4.2, 4.4, 5.1_ + +- [x] 2. Phase 2: Update architecture code to use safe accessors + - [x] 2.1 Update SIMD folding code in src/arch/ to use params.get_key() + - Replace direct keys[index] access with params.get_key(index) in algorithm.rs + - Update VPCLMULQDQ code to use safe key access methods + - Update aarch64 and x86 architecture-specific code + - _Requirements: 3.1, 5.2_ + + - [x] 2.2 Update CRC32 algorithm code to use safe accessors + - Modify src/crc32/algorithm.rs to use params.get_key() instead of keys[index] + - Update fusion code in src/crc32/fusion/ if it accesses keys directly + - _Requirements: 3.1, 5.2_ + + - [x] 2.3 Update CRC64 algorithm code to use safe accessors + - Modify src/crc64/algorithm.rs to use params.get_key() instead of keys[index] + - Update any other CRC64-specific code that accesses keys directly + - _Requirements: 3.1, 5.2_ + + - [x] 2.4 Run performance benchmarks to verify zero overhead + - Benchmark key access performance before and after changes + - Verify compiler optimizations eliminate any performance regression + - Document that performance remains identical to direct array access + - _Requirements: 2.2, 4.4_ + +- [x] 3. Phase 3: Switch CrcParams to use CrcKeysStorage + - [x] 3.1 Update CrcParams struct definition + - Change keys field from [u64; 23] to CrcKeysStorage + - Update CrcParams accessor methods to delegate to CrcKeysStorage + - Remove temporary delegation methods added in Phase 1 + - _Requirements: 5.3_ + + - [x] 3.2 Update all CRC32 const definitions + - Update src/crc32/consts.rs to use CrcKeysStorage::from_keys_fold_256() + - Modify all CRC32_* const definitions to use new key storage format + - Ensure all existing key arrays are properly wrapped + - _Requirements: 1.2, 2.1_ + + - [x] 3.3 Update all CRC64 const definitions + - Update src/crc64/consts.rs to use CrcKeysStorage::from_keys_fold_256() + - Modify all CRC64_* const definitions to use new key storage format + - Ensure all existing key arrays are properly wrapped + - _Requirements: 1.2, 2.1_ + + - [x] 3.4 Update get-custom-params binary output + - Modify src/bin/get-custom-params.rs to output CrcKeysStorage format + - Update output template to use CrcKeysStorage::from_keys_fold_256() + - Test that generated const definitions compile and work correctly + - _Requirements: 6.1, 6.2, 6.3_ + + - [x] 3.5 Update cache system for new CrcParams structure + - Modify src/cache.rs to work with CrcKeysStorage-based CrcParams + - Update CrcParams::new() method to use new key storage format + - Ensure cache functionality remains intact after structural changes + - _Requirements: 2.3, 5.3_ + +- [x] 4. Create comprehensive test suite for future-proof functionality + - [x] 4.1 Add unit tests for bounds checking behavior + - Test that get_key() returns 0 for out-of-bounds indices + - Test that get_key_checked() returns None for invalid indices + - Verify key_count() returns correct values for different storage variants + - _Requirements: 3.2_ + + - [x] 4.2 Add integration tests for third-party compatibility + - Create mock third-party const definitions using new format + - Test that existing key access patterns continue to work + - Verify backwards compatibility throughout migration phases + - _Requirements: 1.1, 2.3_ + + - [x] 4.3 Add performance regression tests + - Benchmark CRC calculation performance before and after changes + - Verify that key access performance matches direct array access + - Test memory usage impact of enum-based storage + - _Requirements: 2.2, 4.4_ + + - [x] 4.4 Add future expansion simulation tests + - Create test CrcParams using KeysFutureTest variant with 25 keys + - Test that code gracefully handles different key array sizes + - Verify that expansion to larger key arrays works as designed + - _Requirements: 1.1, 4.2_ + +- [x] 5. Validate migration and run full test suite + - Run cargo test to ensure all existing tests pass + - Run cargo clippy to ensure code quality standards + - Run cargo fmt to ensure consistent formatting + - Verify that all CRC calculations produce identical results + - Test that third-party usage patterns remain functional + - _Requirements: 5.4_ + +- [x] 6. Implement FFI future-proofing for C/C++ compatibility + - [x] 6.1 Update CrcFastParams struct to use pointer-based keys + - Change keys field from [u64; 23] to const uint64_t *keys pointer + - Add key_count field to track number of available keys + - Update From and From conversion implementations + - _Requirements: 7.2, 8.1_ + + - [x] 6.2 Implement stable key pointer management + - Add create_stable_key_pointer() helper function for CrcKeysStorage + - Ensure key pointers remain valid for the lifetime of CrcFastParams + - Handle memory management safely between Rust and C boundaries + - _Requirements: 8.2, 8.3_ + + - [x] 6.3 Update FFI functions to use new CrcFastParams structure + - Update existing FFI functions to use new pointer-based CrcFastParams + - Ensure all FFI functions handle variable key counts correctly + - Test conversion between CrcKeysStorage variants and C pointer access + - _Requirements: 7.1, 7.3_ + + - [x] 6.4 Update C header file and add comprehensive FFI tests + - Update CrcFastParams struct definition in libcrc_fast.h to use pointer + - Create FFI tests for direct pointer access with different key counts + - Test future expansion scenarios with different key counts (23, 25, etc.) + - Verify memory safety and pointer stability across FFI boundary + - _Requirements: 7.1, 8.1_ \ No newline at end of file diff --git a/.kiro/steering/code-quality-requirements.md b/.kiro/steering/code-quality-requirements.md new file mode 100644 index 0000000..61a44e7 --- /dev/null +++ b/.kiro/steering/code-quality-requirements.md @@ -0,0 +1,42 @@ +--- +inclusion: always +--- + +# Code Quality Requirements + +## Pre-Completion Checks + +Before marking any task as completed, you MUST run the following commands and ensure they pass without errors or warnings: + +### 1. Code Formatting +```bash +cargo fmt --check +``` +If this fails, run `cargo fmt` to fix formatting issues, then verify with `--check` again. + +### 2. Linting +```bash +cargo clippy -- -D warnings +``` +All clippy warnings must be resolved. This ensures code follows Rust best practices and catches potential issues. + +### 3. Testing +```bash +cargo test +``` +All tests must pass to ensure no regressions were introduced. + +## Why These Requirements Matter + +- **cargo fmt**: Ensures consistent code formatting across the entire codebase, making it easier to read and maintain +- **cargo clippy**: Catches common mistakes, suggests idiomatic Rust patterns, and helps prevent bugs before they reach production +- **cargo test**: Validates that all functionality works as expected and no existing features were broken + +## Failure Handling + +If any of these commands fail: +1. Fix the issues identified +2. Re-run the commands to verify fixes +3. Only then mark the task as completed + +These checks are non-negotiable for maintaining code quality and consistency. \ No newline at end of file diff --git a/.kiro/steering/commenting-guidelines.md b/.kiro/steering/commenting-guidelines.md new file mode 100644 index 0000000..e0d1a31 --- /dev/null +++ b/.kiro/steering/commenting-guidelines.md @@ -0,0 +1,43 @@ +--- +inclusion: always +--- + +# Code Commenting Guidelines + +## Comment Philosophy + +- Only add comments when they explain WHY something is done a particular way +- NEVER explain WHAT the code is doing unless it's hard to understand without a comment +- Code should be self-documenting through clear naming and structure +- Comments should provide context, reasoning, or non-obvious implications + +## Examples + +### Good Comments (WHY) +```rust +// Use 512KiB chunks because benchmarks showed this was fastest on Apple M2 Ultra +let chunk_size = chunk_size.unwrap_or(524288); + +// Remove xorout since it's already been applied and needs to be re-added on final output +self.state = combine::checksums( + self.state ^ self.params.xorout, + other_crc, + other.amount, + self.params, +) ^ self.params.xorout; +``` + +### Avoid (WHAT) +```rust +// Set chunk_size to 524288 if None +let chunk_size = chunk_size.unwrap_or(524288); + +// XOR the state with xorout +self.state = self.state ^ self.params.xorout; +``` + +### Exception: Complex Logic +Comments explaining WHAT are acceptable when the code logic is genuinely hard to follow: +```rust +// Fold 8 bytes at a time using SIMD, then handle remainder with scalar operations +``` \ No newline at end of file diff --git a/libcrc_fast.h b/libcrc_fast.h index 0b5b395..bb93b0d 100644 --- a/libcrc_fast.h +++ b/libcrc_fast.h @@ -19,12 +19,14 @@ typedef enum CrcFastAlgorithm { Crc32Bzip2, Crc32CdRomEdc, Crc32Cksum, + Crc32Custom, Crc32Iscsi, Crc32IsoHdlc, Crc32Jamcrc, Crc32Mef, Crc32Mpeg2, Crc32Xfer, + Crc64Custom, Crc64Ecma182, Crc64GoIso, Crc64Ms, @@ -50,6 +52,22 @@ typedef struct CrcFastDigestHandle { struct CrcFastDigest *_0; } CrcFastDigestHandle; +/** + * Custom CRC parameters + */ +typedef struct CrcFastParams { + enum CrcFastAlgorithm algorithm; + uint8_t width; + uint64_t poly; + uint64_t init; + bool refin; + bool refout; + uint64_t xorout; + uint64_t check; + uint32_t key_count; + const uint64_t *keys; +} CrcFastParams; + #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -59,6 +77,11 @@ extern "C" { */ struct CrcFastDigestHandle *crc_fast_digest_new(enum CrcFastAlgorithm algorithm); +/** + * Creates a new Digest to compute CRC checksums using custom parameters + */ +struct CrcFastDigestHandle *crc_fast_digest_new_with_params(struct CrcFastParams params); + /** * Updates the Digest with data */ @@ -100,6 +123,13 @@ uint64_t crc_fast_digest_get_amount(struct CrcFastDigestHandle *handle); */ 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 + */ +uint64_t crc_fast_checksum_with_params(struct CrcFastParams params, + const char *data, + uintptr_t len); + /** * Helper method to just calculate a CRC checksum directly for a file using algorithm */ @@ -107,6 +137,13 @@ uint64_t crc_fast_checksum_file(enum CrcFastAlgorithm algorithm, const uint8_t *path_ptr, uintptr_t path_len); +/** + * Helper method to calculate a CRC checksum directly for a file using custom parameters + */ +uint64_t crc_fast_checksum_file_with_params(struct CrcFastParams params, + const uint8_t *path_ptr, + uintptr_t path_len); + /** * Combine two CRC checksums using algorithm */ @@ -115,6 +152,25 @@ uint64_t crc_fast_checksum_combine(enum CrcFastAlgorithm algorithm, uint64_t checksum2, uint64_t checksum2_len); +/** + * Combine two CRC checksums using custom parameters + */ +uint64_t crc_fast_checksum_combine_with_custom_params(struct CrcFastParams params, + uint64_t checksum1, + uint64_t checksum2, + uint64_t checksum2_len); + +/** + * Returns the custom CRC parameters for a given set of Rocksoft CRC parameters + */ +struct CrcFastParams crc_fast_get_custom_params(const char *name_ptr, + uint8_t width, + uint64_t poly, + uint64_t init, + bool reflected, + uint64_t xorout, + uint64_t check); + /** * Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm */ diff --git a/src/algorithm.rs b/src/algorithm.rs index 20c394b..fe1459b 100644 --- a/src/algorithm.rs +++ b/src/algorithm.rs @@ -17,9 +17,40 @@ use crate::consts::CRC_CHUNK_SIZE; use crate::enums::{DataChunkProcessor, Reflector}; -use crate::structs::{CrcParams, CrcState}; +use crate::structs::CrcState; use crate::traits::{ArchOps, EnhancedCrcWidth}; -use crate::{crc32, crc64}; +use crate::{crc32, crc64, CrcParams}; + +/// Extract keys from CrcParams using safe accessor methods +/// This ensures bounds checking and future compatibility +#[inline(always)] +fn extract_keys_array(params: CrcParams) -> [u64; 23] { + [ + params.get_key(0), + params.get_key(1), + params.get_key(2), + params.get_key(3), + params.get_key(4), + params.get_key(5), + params.get_key(6), + params.get_key(7), + params.get_key(8), + params.get_key(9), + params.get_key(10), + params.get_key(11), + params.get_key(12), + params.get_key(13), + params.get_key(14), + params.get_key(15), + params.get_key(16), + params.get_key(17), + params.get_key(18), + params.get_key(19), + params.get_key(20), + params.get_key(21), + params.get_key(22), + ] +} /// Main entry point that works for both CRC-32 and CRC-64 #[inline] @@ -65,13 +96,19 @@ where bytes, &mut crc_state, reflector, - params.keys, + extract_keys_array(params), ops, ); } // Process large inputs with SIMD-optimized approach - process_large_aligned::(bytes, &mut crc_state, reflector, params.keys, ops) + process_large_aligned::( + bytes, + &mut crc_state, + reflector, + extract_keys_array(params), + ops, + ) } /// Process data with the selected strategy diff --git a/src/arch/mod.rs b/src/arch/mod.rs index 22849e0..23aa21e 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -11,7 +11,7 @@ use std::arch::is_aarch64_feature_detected; #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] use crate::algorithm; -use crate::structs::CrcParams; +use crate::CrcParams; #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] use crate::structs::{Width32, Width64}; diff --git a/src/arch/software.rs b/src/arch/software.rs index a573d2d..4739a1b 100644 --- a/src/arch/software.rs +++ b/src/arch/software.rs @@ -5,9 +5,9 @@ #![cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] use crate::consts::CRC_64_NVME; -use crate::structs::CrcParams; use crate::CrcAlgorithm; -use crc::Table; +use crate::CrcParams; +use crc::{Algorithm, Table}; const RUST_CRC32_AIXM: crc::Crc> = crc::Crc::>::new(&crc::CRC_32_AIXM); @@ -78,6 +78,23 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc32Mef => RUST_CRC32_MEF, CrcAlgorithm::Crc32Mpeg2 => RUST_CRC32_MPEG_2, CrcAlgorithm::Crc32Xfer => RUST_CRC32_XFER, + CrcAlgorithm::Crc32Custom => { + let algorithm: Algorithm = Algorithm { + width: params.width as u8, + 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) + } _ => panic!("Invalid algorithm for u32 CRC"), }; update_u32(state as u32, data, params) as u64 @@ -91,6 +108,23 @@ pub(crate) fn update(state: u64, data: &[u8], params: CrcParams) -> u64 { CrcAlgorithm::Crc64Redis => RUST_CRC64_REDIS, CrcAlgorithm::Crc64We => RUST_CRC64_WE, CrcAlgorithm::Crc64Xz => RUST_CRC64_XZ, + CrcAlgorithm::Crc64Custom => { + let algorithm: Algorithm = Algorithm { + width: params.width as u8, + poly: params.poly as u64, + init: params.init as u64, + refin: params.refin, + refout: params.refout, + xorout: params.xorout as u64, + check: params.check as u64, + 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) + } _ => panic!("Invalid algorithm for u64 CRC"), }; update_u64(state, data, params) diff --git a/src/bin/get-custom-params.rs b/src/bin/get-custom-params.rs new file mode 100644 index 0000000..0991954 --- /dev/null +++ b/src/bin/get-custom-params.rs @@ -0,0 +1,218 @@ +//! This is a simple program to get custom CRC parameters from the command line. + +use std::env; +use std::process::ExitCode; + +#[derive(Debug)] +struct Config { + width: Option, + polynomial: Option, + init: Option, + reflected: Option, + xorout: Option, + check: Option, + name: Option, +} + +impl Config { + fn new() -> Self { + Config { + width: None, + polynomial: None, + init: None, + reflected: None, + xorout: None, + check: None, + name: None, + } + } + + fn is_complete(&self) -> bool { + self.width.is_some() + && self.polynomial.is_some() + && self.init.is_some() + && self.reflected.is_some() + && self.xorout.is_some() + && self.check.is_some() + && self.name.is_some() + } +} + +fn parse_hex_or_decimal(s: &str) -> Result { + if s.starts_with("0x") || s.starts_with("0X") { + u64::from_str_radix(&s[2..], 16).map_err(|_| format!("Invalid hexadecimal value: {}", s)) + } else { + s.parse::() + .map_err(|_| format!("Invalid decimal value: {}", s)) + } +} + +fn parse_bool(s: &str) -> Result { + match s.to_lowercase().as_str() { + "true" | "1" | "yes" | "on" => Ok(true), + "false" | "0" | "no" | "off" => Ok(false), + _ => Err(format!("Invalid boolean value: {} (use true/false)", s)), + } +} + +fn parse_args(args: &[String]) -> Result { + let mut config = Config::new(); + let mut i = 1; // Skip program name + + while i < args.len() { + match args[i].as_str() { + "-n" => { + if i + 1 >= args.len() { + return Err("Missing value for -n (name)".to_string()); + } + config.name = Some(args[i + 1].clone()); + i += 2; + } + "-w" => { + if i + 1 >= args.len() { + return Err("Missing value for -w (width)".to_string()); + } + config.width = Some( + args[i + 1] + .parse::() + .map_err(|_| format!("Invalid width value: {}", args[i + 1]))?, + ); + i += 2; + } + "-p" => { + if i + 1 >= args.len() { + return Err("Missing value for -p (polynomial)".to_string()); + } + config.polynomial = Some(parse_hex_or_decimal(&args[i + 1])?); + i += 2; + } + "-i" => { + if i + 1 >= args.len() { + return Err("Missing value for -i (init)".to_string()); + } + config.init = Some(parse_hex_or_decimal(&args[i + 1])?); + i += 2; + } + "-r" => { + if i + 1 >= args.len() { + return Err("Missing value for -r (reflected)".to_string()); + } + config.reflected = Some(parse_bool(&args[i + 1])?); + i += 2; + } + "-x" => { + if i + 1 >= args.len() { + return Err("Missing value for -x (xorout)".to_string()); + } + config.xorout = Some(parse_hex_or_decimal(&args[i + 1])?); + i += 2; + } + "-c" => { + if i + 1 >= args.len() { + return Err("Missing value for -c (check)".to_string()); + } + config.check = Some(parse_hex_or_decimal(&args[i + 1])?); + i += 2; + } + arg => { + return Err(format!("Unknown argument: {}", arg)); + } + } + } + + Ok(config) +} + +fn print_usage() { + println!("Usage: get-custom-params -n -w -p -i -r -x -c "); + println!(); + println!("Example: get-custom-params -n CRC-32/ISCSI -w 32 -p 0x1edc6f41 -i 0xFFFFFFFF -r true -x 0xFFFFFFFF -c 0xe3069283"); + println!("Example: get-custom-params -n CRC-64/NVME -w 64 -p 0xad93d23594c93659 -i 0xffffffffffffffff -r true -x 0xffffffffffffffff -c 0xae8b14860a799888"); + println!(); + println!("Arguments:"); + println!(" -n Name of the CRC algorithm (e.g., CRC-32/ISCSI)"); + println!(" -w CRC width (number of bits)"); + println!(" -p CRC polynomial (hex or decimal)"); + println!(" -i Initial value (hex or decimal)"); + println!(" -r Reflected input/output (true/false)"); + println!(" -x XOR output value (hex or decimal)"); + println!(" -c Check value (hex or decimal)"); +} + +fn main() -> ExitCode { + let args: Vec = env::args().collect(); + + if args.len() == 1 { + print_usage(); + return ExitCode::from(1); + } + + let config = match parse_args(&args) { + Ok(config) => config, + Err(error) => { + eprintln!("Error: {}", error); + println!(); + print_usage(); + return ExitCode::from(1); + } + }; + + // Check if all required arguments are provided + if !config.is_complete() { + eprintln!("Error: All arguments are required"); + println!(); + print_usage(); + return ExitCode::from(1); + } + + let static_name: &'static str = Box::leak(config.name.unwrap().into_boxed_str()); + + let params = crc_fast::CrcParams::new( + static_name, + config.width.unwrap() as u8, + config.polynomial.unwrap(), + config.init.unwrap(), + config.reflected.unwrap(), + config.xorout.unwrap(), + config.check.unwrap(), + ); + + println!(); + println!("// Generated CRC parameters for {}", static_name); + println!( + "pub const {}: CrcParams = CrcParams {{", + static_name + .to_uppercase() + .replace("-", "_") + .replace("/", "_") + ); + println!( + " algorithm: CrcAlgorithm::{}Custom,", + if config.width.unwrap() == 32 { + "Crc32" + } else { + "Crc64" + } + ); + println!(" name: \"{}\",", static_name); + println!(" width: {},", config.width.unwrap()); + println!(" poly: 0x{:x},", config.polynomial.unwrap()); + println!(" init: 0x{:x},", config.init.unwrap()); + println!(" refin: {},", config.reflected.unwrap()); + println!(" refout: {},", config.reflected.unwrap()); + println!(" xorout: 0x{:x},", config.xorout.unwrap()); + println!(" check: 0x{:x},", config.check.unwrap()); + println!(" keys: CrcKeysStorage::from_keys_fold_256(["); + + // Print the keys array + for i in 0..23 { + let key = params.get_key(i); + println!(" 0x{:016x},", key); + } + + println!(" ]),"); + println!("}};"); + println!(); + + ExitCode::from(0) +} diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..8b1d73e --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,1692 @@ +// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0. + +//! CRC parameter caching system +//! +//! This module provides a thread-safe cache for CRC folding keys to avoid expensive +//! regeneration when the same CRC parameters are used multiple times. The cache uses +//! a read-write lock pattern optimized for the common case of cache hits. +//! +//! # Performance Characteristics +//! +//! - Cache hits: ~50-100x faster than key generation +//! - Cache misses: ~100-200ns overhead compared to direct generation +//! - Memory usage: ~200 bytes per unique parameter set +//! - Thread safety: Multiple concurrent readers, exclusive writers +//! +//! # Usage +//! +//! The cache is used automatically by `CrcParams::new()` and requires no manual management. +//! The cache is transparent to users and handles all memory management internally. + +use crate::generate; +use std::collections::HashMap; +use std::sync::{OnceLock, 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. +static CACHE: OnceLock>> = OnceLock::new(); + +/// Cache key for storing CRC parameters that affect key generation +/// +/// Only includes parameters that directly influence the mathematical computation +/// of folding keys. Parameters like `init`, `xorout`, and `check` are excluded +/// because they don't affect the key generation process. +/// +/// The cache key implements `Hash`, `Eq`, and `PartialEq` to enable efficient +/// HashMap storage and lookup operations. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct CrcParamsCacheKey { + /// CRC width in bits (32 or 64) + pub width: u8, + /// Polynomial value used for CRC calculation + pub poly: u64, + /// Whether the CRC uses reflected input/output processing + pub reflected: bool, +} + +impl CrcParamsCacheKey { + /// Create a new cache key from CRC parameters + /// + /// # Arguments + /// + /// * `width` - CRC width in bits (32 or 64) + /// * `poly` - Polynomial value for the CRC algorithm + /// * `reflected` - Whether input/output should be bit-reflected + pub fn new(width: u8, poly: u64, reflected: bool) -> Self { + Self { + width, + poly, + reflected, + } + } +} + +/// Initialize and return reference to the global cache +/// +/// Uses OnceLock to ensure thread-safe lazy initialization without requiring +/// static initialization overhead. The cache is only created when first accessed. +fn get_cache() -> &'static RwLock> { + CACHE.get_or_init(|| 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 +/// of cache hits while minimizing lock contention: +/// +/// 1. **Read phase**: Attempts read lock to check for cached keys (allows concurrent reads) +/// 2. **Generation phase**: If cache miss, generates keys outside any lock to minimize hold time +/// 3. **Write phase**: Acquires write lock only to store the generated keys +/// +/// The key generation happens outside the write lock because it's computationally expensive +/// (~1000x slower than cache lookup) and we want to minimize the time other threads are blocked. +/// +/// All cache operations use best-effort error handling - lock poisoning or allocation failures +/// don't cause panics, instead falling back to direct key generation to maintain functionality. +/// +/// # Arguments +/// +/// * `width` - CRC width in bits (32 or 64) +/// * `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; + } + } + + // 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 +} + +/// Clear all cached CRC parameter keys +/// +/// This function is primarily intended for testing scenarios where you need to reset +/// the cache state to ensure test isolation. +/// +/// Uses best-effort error handling - lock poisoning or other failures don't cause +/// panics, ensuring this function never disrupts program execution. If the cache +/// cannot be cleared, the function silently continues without error. +/// +/// # Thread Safety +/// +/// This function is thread-safe and can be called concurrently with other cache operations. +/// However, clearing the cache while other threads are actively using it may temporarily +/// 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(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + #[test] + fn test_cache_key_creation() { + let key1 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + let key2 = CrcParamsCacheKey::new(64, 0x42F0E1EBA9EA3693, false); + + assert_eq!(key1.width, 32); + assert_eq!(key1.poly, 0x04C11DB7); + assert_eq!(key1.reflected, true); + + assert_eq!(key2.width, 64); + assert_eq!(key2.poly, 0x42F0E1EBA9EA3693); + assert_eq!(key2.reflected, false); + } + + #[test] + fn test_cache_key_equality() { + let key1 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + let key2 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + let key3 = CrcParamsCacheKey::new(32, 0x04C11DB7, false); // Different reflected + let key4 = CrcParamsCacheKey::new(64, 0x04C11DB7, true); // Different width + let key5 = CrcParamsCacheKey::new(32, 0x1EDC6F41, true); // Different poly + + // Test equality + assert_eq!(key1, key2); + assert_eq!(key1.clone(), key2.clone()); + + // Test inequality + assert_ne!(key1, key3); + assert_ne!(key1, key4); + assert_ne!(key1, key5); + assert_ne!(key3, key4); + assert_ne!(key3, key5); + assert_ne!(key4, key5); + } + + #[test] + fn test_cache_key_hashing() { + let key1 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + let key2 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + let key3 = CrcParamsCacheKey::new(32, 0x04C11DB7, false); + + // Create a HashSet to test that keys can be used as hash keys + let mut set = HashSet::new(); + set.insert(key1.clone()); + set.insert(key2.clone()); + set.insert(key3.clone()); + + // Should only have 2 unique keys (key1 and key2 are equal) + assert_eq!(set.len(), 2); + + // Test that equal keys can be found in the set + assert!(set.contains(&key1)); + assert!(set.contains(&key2)); + assert!(set.contains(&key3)); + + // Test that a new equivalent key can be found + let key4 = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + assert!(set.contains(&key4)); + } + + #[test] + fn test_cache_hit_scenarios() { + clear_cache(); + + // First call should be a cache miss and generate keys + let keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + + // Second call with same parameters should be a cache hit + let keys2 = get_or_generate_keys(32, 0x04C11DB7, true); + + // Keys should be identical (same array contents) + assert_eq!(keys1, keys2); + + // Test multiple cache hits + let keys3 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys4 = get_or_generate_keys(32, 0x04C11DB7, true); + + assert_eq!(keys1, keys3); + assert_eq!(keys1, keys4); + assert_eq!(keys2, keys3); + assert_eq!(keys2, keys4); + } + + #[test] + fn test_cache_miss_scenarios() { + clear_cache(); + + // Different width - should be cache miss + let keys_32 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_64 = get_or_generate_keys(64, 0x04C11DB7, true); + assert_ne!(keys_32, keys_64); + + // Different poly - should be cache miss + let keys_poly1 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_poly2 = get_or_generate_keys(32, 0x1EDC6F41, true); + assert_ne!(keys_poly1, keys_poly2); + + // Different reflected - should be cache miss + let keys_refl_true = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_refl_false = get_or_generate_keys(32, 0x04C11DB7, false); + assert_ne!(keys_refl_true, keys_refl_false); + + // Verify each parameter set is cached independently + let keys_32_again = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_64_again = get_or_generate_keys(64, 0x04C11DB7, true); + let keys_poly1_again = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_poly2_again = get_or_generate_keys(32, 0x1EDC6F41, true); + let keys_refl_true_again = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_refl_false_again = get_or_generate_keys(32, 0x04C11DB7, false); + + assert_eq!(keys_32, keys_32_again); + assert_eq!(keys_64, keys_64_again); + assert_eq!(keys_poly1, keys_poly1_again); + assert_eq!(keys_poly2, keys_poly2_again); + assert_eq!(keys_refl_true, keys_refl_true_again); + assert_eq!(keys_refl_false, keys_refl_false_again); + } + + #[test] + fn test_cached_keys_identical_to_generated_keys() { + clear_cache(); + + // Test CRC32 parameters + let width = 32; + let poly = 0x04C11DB7; + let reflected = true; + + // Generate keys directly (bypassing cache) + let direct_keys = generate::keys(width, poly, reflected); + + // Get keys through cache (first call will be cache miss, generates and caches) + let cached_keys_first = get_or_generate_keys(width, poly, reflected); + + // Get keys through cache again (should be cache hit) + let cached_keys_second = get_or_generate_keys(width, poly, reflected); + + // All should be identical + assert_eq!(direct_keys, cached_keys_first); + assert_eq!(direct_keys, cached_keys_second); + assert_eq!(cached_keys_first, cached_keys_second); + + // Test CRC64 parameters + let width64 = 64; + let poly64 = 0x42F0E1EBA9EA3693; + let reflected64 = false; + + let direct_keys64 = generate::keys(width64, poly64, reflected64); + let cached_keys64_first = get_or_generate_keys(width64, poly64, reflected64); + let cached_keys64_second = get_or_generate_keys(width64, poly64, reflected64); + + assert_eq!(direct_keys64, cached_keys64_first); + assert_eq!(direct_keys64, cached_keys64_second); + assert_eq!(cached_keys64_first, cached_keys64_second); + + // Verify different parameters produce different keys + assert_ne!(direct_keys, direct_keys64); + assert_ne!(cached_keys_first, cached_keys64_first); + } + + #[test] + fn test_multiple_parameter_combinations() { + clear_cache(); + + // Test various common CRC parameter combinations + let test_cases = [ + (32, 0x04C11DB7, true), // CRC32 + (32, 0x04C11DB7, false), // CRC32 non-reflected + (32, 0x1EDC6F41, true), // CRC32C + (64, 0x42F0E1EBA9EA3693, true), // CRC64 ISO + (64, 0x42F0E1EBA9EA3693, false), // CRC64 ISO non-reflected + (64, 0xD800000000000000, true), // CRC64 ECMA + ]; + + let mut all_keys = Vec::new(); + + // Generate keys for all test cases + for &(width, poly, reflected) in &test_cases { + let keys = get_or_generate_keys(width, poly, reflected); + all_keys.push(keys); + } + + // Verify all keys are different (no collisions) + for i in 0..all_keys.len() { + for j in (i + 1)..all_keys.len() { + assert_ne!( + all_keys[i], all_keys[j], + "Keys should be different for test cases {} and {}", + i, j + ); + } + } + + // Verify cache hits return same keys + for (i, &(width, poly, reflected)) in test_cases.iter().enumerate() { + let cached_keys = get_or_generate_keys(width, poly, reflected); + assert_eq!( + all_keys[i], cached_keys, + "Cache hit should return same keys for test case {}", + i + ); + } + } + + #[test] + fn test_cache_management_utilities() { + // Clear cache to start with clean state + clear_cache(); + + // Generate and cache some keys + let keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + // Verify cache hits return same keys + let cached_keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let cached_keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + assert_eq!(keys1, cached_keys1); + assert_eq!(keys2, cached_keys2); + + // Clear cache + clear_cache(); + + // Verify cache was cleared by checking that new calls still work + // (we can't directly verify cache is empty, but we can verify functionality) + let new_keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!(keys1, new_keys1); // Should be same values, but freshly generated + } + + #[test] + fn test_cache_error_handling() { + // Test that cache operations don't panic even if called multiple times + clear_cache(); + clear_cache(); // Should not panic on empty cache + + // Test that get_or_generate_keys works even after multiple clears + let keys = get_or_generate_keys(32, 0x04C11DB7, true); + clear_cache(); + let keys2 = get_or_generate_keys(32, 0x04C11DB7, true); + + // Keys should be identical (same parameters produce same keys) + assert_eq!(keys, keys2); + } + + #[test] + fn test_cache_key_debug_and_clone() { + let key = CrcParamsCacheKey::new(32, 0x04C11DB7, true); + + // Test Debug trait + let debug_str = format!("{:?}", key); + assert!(debug_str.contains("CrcParamsCacheKey")); + assert!(debug_str.contains("32")); + assert!(debug_str.contains("0x4c11db7") || debug_str.contains("79764919")); + assert!(debug_str.contains("true")); + + // Test Clone trait + let cloned_key = key.clone(); + assert_eq!(key, cloned_key); + assert_eq!(key.width, cloned_key.width); + assert_eq!(key.poly, cloned_key.poly); + assert_eq!(key.reflected, cloned_key.reflected); + } + + // Thread safety tests + #[test] + fn test_concurrent_cache_reads() { + use std::sync::{Arc, Barrier}; + use std::thread; + + clear_cache(); + + // Pre-populate cache with a known value + let expected_keys = get_or_generate_keys(32, 0x04C11DB7, true); + + let num_threads = 8; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // Spawn multiple threads that all read the same cached value simultaneously + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + // Wait for all threads to be ready + barrier_clone.wait(); + + // All threads read from cache simultaneously + let keys = get_or_generate_keys(32, 0x04C11DB7, true); + (i, keys) + }); + handles.push(handle); + } + + // Collect results from all threads + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads got the same cached keys + assert_eq!(results.len(), num_threads); + for (thread_id, keys) in results { + assert_eq!( + keys, expected_keys, + "Thread {} should get same cached keys", + thread_id + ); + } + } + + #[test] + fn test_concurrent_cache_writes() { + use std::sync::{Arc, Barrier}; + use std::thread; + + clear_cache(); + + let num_threads = 6; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // Test parameters for different cache entries + let test_params = [ + (32, 0x04C11DB7, true), + (32, 0x04C11DB7, false), + (32, 0x1EDC6F41, true), + (64, 0x42F0E1EBA9EA3693, true), + (64, 0x42F0E1EBA9EA3693, false), + (64, 0xD800000000000000, true), + ]; + + // Spawn threads that write different cache entries simultaneously + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let (width, poly, reflected) = test_params[i]; + + let handle = thread::spawn(move || { + // Wait for all threads to be ready + barrier_clone.wait(); + + // Each thread generates and caches different parameters + let keys = get_or_generate_keys(width, poly, reflected); + (i, width, poly, reflected, keys) + }); + handles.push(handle); + } + + // Collect results from all threads + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads completed successfully and got correct keys + assert_eq!(results.len(), num_threads); + + // Verify each thread's keys match what we'd expect from direct generation + for (thread_id, width, poly, reflected, keys) in results { + let expected_keys = generate::keys(width, poly, reflected); + assert_eq!( + keys, expected_keys, + "Thread {} should generate correct keys for params ({}, {:#x}, {})", + thread_id, width, poly, reflected + ); + + // Verify the keys are now cached by reading them again + let cached_keys = get_or_generate_keys(width, poly, reflected); + assert_eq!( + keys, cached_keys, + "Thread {} keys should be cached", + thread_id + ); + } + } + + #[test] + fn test_read_write_contention() { + use std::sync::{Arc, Barrier}; + use std::thread; + use std::time::Duration; + + clear_cache(); + + // Pre-populate cache with some values + let _keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let _keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + let num_readers = 6; + let num_writers = 3; + let total_threads = num_readers + num_writers; + let barrier = Arc::new(Barrier::new(total_threads)); + let mut handles = Vec::new(); + + // Spawn reader threads that continuously read cached values + for i in 0..num_readers { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + let mut read_count = 0; + let start = std::time::Instant::now(); + + // Read for a short duration + while start.elapsed() < Duration::from_millis(50) { + let keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + // Verify we get consistent results + assert_eq!(keys1.len(), 23); + assert_eq!(keys2.len(), 23); + read_count += 1; + } + + (format!("reader_{}", i), read_count) + }); + handles.push(handle); + } + + // Spawn writer threads that add new cache entries + for i in 0..num_writers { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + let mut write_count = 0; + let start = std::time::Instant::now(); + + // Write new entries for a short duration + while start.elapsed() < Duration::from_millis(50) { + // Use different parameters to create new cache entries + let poly = 0x1EDC6F41 + (i as u64 * 0x1000) + (write_count as u64); + let keys = get_or_generate_keys(32, poly, true); + + assert_eq!(keys.len(), 23); + write_count += 1; + } + + (format!("writer_{}", i), write_count) + }); + handles.push(handle); + } + + // Wait for all threads to complete + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads completed successfully + assert_eq!(results.len(), total_threads); + + // Verify readers and writers both made progress + let reader_results: Vec<_> = results + .iter() + .filter(|(name, _)| name.starts_with("reader_")) + .collect(); + let writer_results: Vec<_> = results + .iter() + .filter(|(name, _)| name.starts_with("writer_")) + .collect(); + + assert_eq!(reader_results.len(), num_readers); + assert_eq!(writer_results.len(), num_writers); + + // All threads should have made some progress + for (name, count) in &results { + assert!(*count > 0, "Thread {} should have made progress", name); + } + } + + #[test] + fn test_cache_consistency_under_concurrent_access() { + use std::sync::{Arc, Barrier}; + use std::thread; + + clear_cache(); + + let num_threads = 10; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // All threads will try to get the same cache entry simultaneously + // This tests the race condition where multiple threads might try to + // generate and cache the same keys at the same time + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + // All threads request the same parameters simultaneously + let keys = get_or_generate_keys(32, 0x04C11DB7, true); + (i, keys) + }); + handles.push(handle); + } + + // Collect results from all threads + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads got identical keys + assert_eq!(results.len(), num_threads); + let first_keys = results[0].1; + + for (thread_id, keys) in results { + assert_eq!( + keys, first_keys, + "Thread {} should get identical keys to other threads", + thread_id + ); + } + + // Verify the keys are correct by comparing with direct generation + let expected_keys = generate::keys(32, 0x04C11DB7, true); + assert_eq!( + first_keys, expected_keys, + "Cached keys should match directly generated keys" + ); + + // Verify subsequent access still returns the same keys + let final_keys = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!( + final_keys, first_keys, + "Final cache access should return same keys" + ); + } + + #[test] + fn test_mixed_concurrent_operations() { + use std::sync::{Arc, Barrier}; + use std::thread; + use std::time::Duration; + + clear_cache(); + + let num_threads = 8; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // Mix of operations: some threads do cache hits, some do cache misses, + // some clear the cache, all happening concurrently + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + let mut operations = 0; + let start = std::time::Instant::now(); + + while start.elapsed() < Duration::from_millis(30) { + match i % 4 { + 0 => { + // Cache hit operations - same parameters + let _keys = get_or_generate_keys(32, 0x04C11DB7, true); + } + 1 => { + // Cache miss operations - different parameters each time + let poly = 0x1EDC6F41 + (operations as u64); + let _keys = get_or_generate_keys(32, poly, true); + } + 2 => { + // Mixed read operations + let _keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let _keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + } + 3 => { + // Occasional cache clear (but not too often to avoid disrupting other tests) + if operations % 10 == 0 { + clear_cache(); + } + let _keys = get_or_generate_keys(32, 0x04C11DB7, true); + } + _ => unreachable!(), + } + operations += 1; + } + + (i, operations) + }); + handles.push(handle); + } + + // Wait for all threads to complete + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads completed successfully + assert_eq!(results.len(), num_threads); + + for (thread_id, operations) in results { + assert!( + operations > 0, + "Thread {} should have completed some operations", + thread_id + ); + } + + // Verify cache is still functional after all the concurrent operations + let final_keys = get_or_generate_keys(32, 0x04C11DB7, true); + let expected_keys = generate::keys(32, 0x04C11DB7, true); + assert_eq!( + final_keys, expected_keys, + "Cache should still work correctly after concurrent operations" + ); + } + + // Error handling tests + #[test] + fn test_cache_lock_poisoning_recovery() { + use std::panic; + use std::sync::{Arc, Mutex}; + use std::thread; + + clear_cache(); + + // Pre-populate cache with known values + let expected_keys = get_or_generate_keys(32, 0x04C11DB7, true); + + // Create a scenario that could potentially poison the lock + // We'll use a separate test to avoid actually poisoning our cache + let poisoned_flag = Arc::new(Mutex::new(false)); + let poisoned_flag_clone = Arc::clone(&poisoned_flag); + + // Spawn a thread that panics while holding a lock (simulated) + let handle = thread::spawn(move || { + // Simulate a panic scenario - we don't actually poison the cache lock + // because that would break other tests, but we test the recovery path + let _guard = poisoned_flag_clone.lock().unwrap(); + panic!("Simulated panic"); + }); + + // Wait for the thread to panic + let result = handle.join(); + assert!(result.is_err(), "Thread should have panicked"); + + // Verify that our cache still works despite the simulated error scenario + let keys_after_panic = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!( + keys_after_panic, expected_keys, + "Cache should still work after simulated panic scenario" + ); + + // Test that new cache entries can still be created + let new_keys = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + assert_eq!(new_keys.len(), 23, "New cache entries should still work"); + + // Verify the new keys are cached + let cached_new_keys = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + assert_eq!(new_keys, cached_new_keys, "New keys should be cached"); + } + + #[test] + fn test_cache_fallback_to_direct_generation() { + clear_cache(); + + // Test that even if cache operations fail, we still get valid keys + // This tests the fallback mechanism in get_or_generate_keys + + // Generate expected keys directly + let expected_keys_32 = generate::keys(32, 0x04C11DB7, true); + let expected_keys_64 = generate::keys(64, 0x42F0E1EBA9EA3693, false); + + // Get keys through cache (should work normally) + let cached_keys_32 = get_or_generate_keys(32, 0x04C11DB7, true); + let cached_keys_64 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + // Verify keys are correct regardless of cache state + assert_eq!( + cached_keys_32, expected_keys_32, + "CRC32 keys should be correct even with cache issues" + ); + assert_eq!( + cached_keys_64, expected_keys_64, + "CRC64 keys should be correct even with cache issues" + ); + + // Test multiple calls to ensure consistency + for _ in 0..5 { + let keys_32 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys_64 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + assert_eq!( + keys_32, expected_keys_32, + "Repeated calls should return consistent CRC32 keys" + ); + assert_eq!( + keys_64, expected_keys_64, + "Repeated calls should return consistent CRC64 keys" + ); + } + } + + #[test] + fn test_cache_operations_under_memory_pressure() { + clear_cache(); + + // Simulate memory pressure by creating many cache entries + // This tests that cache operations remain stable under load + let mut test_keys = Vec::new(); + let num_entries = 100; + + // Create many different cache entries + for i in 0..num_entries { + let poly = 0x04C11DB7 + (i as u64); + let reflected = i % 2 == 0; + let width = if i % 3 == 0 { 64 } else { 32 }; + + let keys = get_or_generate_keys(width, poly, reflected); + test_keys.push((width, poly, reflected, keys)); + } + + // Verify all entries are correctly cached and retrievable + for (i, &(width, poly, reflected, ref expected_keys)) in test_keys.iter().enumerate() { + let cached_keys = get_or_generate_keys(width, poly, reflected); + assert_eq!( + cached_keys, *expected_keys, + "Entry {} should be correctly cached", + i + ); + } + + // Test that cache operations still work after creating many entries + let new_keys = get_or_generate_keys(32, 0x1EDC6F41, true); + assert_eq!( + new_keys.len(), + 23, + "New entries should still work under memory pressure" + ); + + // Verify the new entry is cached + let cached_new_keys = get_or_generate_keys(32, 0x1EDC6F41, true); + assert_eq!(new_keys, cached_new_keys, "New entry should be cached"); + + // Test cache clearing still works + clear_cache(); + + // Verify cache was cleared by testing that operations still work + let post_clear_keys = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!( + post_clear_keys.len(), + 23, + "Cache should work after clearing under memory pressure" + ); + } + + #[test] + fn test_cache_error_recovery_patterns() { + clear_cache(); + + // Test various error recovery patterns to ensure robustness + + // Pattern 1: Rapid cache operations + for i in 0..50 { + let poly = 0x04C11DB7 + (i as u64 % 10); // Create some duplicates + let keys = get_or_generate_keys(32, poly, true); + assert_eq!(keys.len(), 23, "Rapid operation {} should succeed", i); + } + + // Pattern 2: Interleaved cache hits and misses + let base_keys = get_or_generate_keys(32, 0x04C11DB7, true); + for i in 0..20 { + // Cache hit + let hit_keys = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!(hit_keys, base_keys, "Cache hit {} should be consistent", i); + + // Cache miss + let miss_keys = get_or_generate_keys(32, 0x1EDC6F41 + (i as u64), false); + assert_eq!(miss_keys.len(), 23, "Cache miss {} should succeed", i); + } + + // Pattern 3: Mixed operations with clearing + for i in 0..10 { + let keys1 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys2 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + if i % 3 == 0 { + clear_cache(); + } + + // Operations should still work after clearing + let keys3 = get_or_generate_keys(32, 0x04C11DB7, true); + let keys4 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + + // Keys should be consistent (same parameters = same keys) + assert_eq!(keys1, keys3, "Keys should be consistent across clears"); + assert_eq!(keys2, keys4, "Keys should be consistent across clears"); + } + } + + #[test] + fn test_cache_concurrent_error_scenarios() { + use std::sync::{Arc, Barrier}; + use std::thread; + use std::time::Duration; + + clear_cache(); + + let num_threads = 8; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // Create concurrent scenarios that could potentially cause errors + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + let mut operations = 0; + let errors = 0; + let start = std::time::Instant::now(); + + // Run operations for a short time with various patterns + while start.elapsed() < Duration::from_millis(100) { + match operations % 5 { + 0 => { + // Normal cache operations + let _keys = get_or_generate_keys(32, 0x04C11DB7, true); + } + 1 => { + // Rapid different parameters + let poly = 0x1EDC6F41 + (operations as u64); + let _keys = get_or_generate_keys(32, poly, true); + } + 2 => { + // Cache clearing (potential contention point) + clear_cache(); + } + 3 => { + // Mixed width operations + let _keys32 = get_or_generate_keys(32, 0x04C11DB7, true); + let _keys64 = get_or_generate_keys(64, 0x42F0E1EBA9EA3693, false); + } + 4 => { + // Rapid same-parameter calls (cache hits) + for _ in 0..5 { + let _keys = get_or_generate_keys(32, 0x04C11DB7, true); + } + } + _ => unreachable!(), + } + operations += 1; + } + + (i, operations, errors) + }); + handles.push(handle); + } + + // Collect results + let mut results = Vec::new(); + for handle in handles { + match handle.join() { + Ok(result) => results.push(result), + Err(_) => panic!("Thread should not panic during error scenarios"), + } + } + + // Verify all threads completed successfully + assert_eq!(results.len(), num_threads); + + for (thread_id, operations, errors) in results { + assert!( + operations > 0, + "Thread {} should have completed operations", + thread_id + ); + // In our implementation, errors should be handled gracefully without propagating + assert_eq!( + errors, 0, + "Thread {} should not have unhandled errors", + thread_id + ); + } + + // Verify cache is still functional after all concurrent error scenarios + let final_keys = get_or_generate_keys(32, 0x04C11DB7, true); + let expected_keys = generate::keys(32, 0x04C11DB7, true); + assert_eq!( + final_keys, expected_keys, + "Cache should still work correctly after concurrent error scenarios" + ); + } + + #[test] + fn test_cache_memory_allocation_stress() { + clear_cache(); + + // Test cache behavior under memory allocation stress + // Create a large number of unique cache entries to stress memory allocation + let mut created_entries = Vec::new(); + let stress_count = 1000; + + // Create many unique cache entries + for i in 0..stress_count { + let width = if i % 2 == 0 { 32 } else { 64 }; + let poly = 0x04C11DB7 + (i as u64); + let reflected = i % 3 == 0; + + let keys = get_or_generate_keys(width, poly, reflected); + created_entries.push((width, poly, reflected, keys)); + + // Verify each entry is valid + assert_eq!(keys.len(), 23, "Entry {} should have valid keys", i); + } + + // Verify all entries are still accessible (testing cache integrity) + for (i, &(width, poly, reflected, ref expected_keys)) in created_entries.iter().enumerate() + { + let retrieved_keys = get_or_generate_keys(width, poly, reflected); + assert_eq!( + retrieved_keys, *expected_keys, + "Entry {} should be retrievable after stress test", + i + ); + } + + // Test that new entries can still be created + let new_keys = get_or_generate_keys(32, 0xFFFFFFFF, true); + assert_eq!( + new_keys.len(), + 23, + "New entries should work after memory stress" + ); + + // Test cache clearing works under memory pressure + clear_cache(); + + // Verify cache operations still work after clearing + let post_stress_keys = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!( + post_stress_keys.len(), + 23, + "Cache should work after memory stress and clearing" + ); + } + + // Integration tests for CrcParams compatibility + #[test] + fn test_crc_params_new_behavior_unchanged() { + use crate::CrcParams; + + clear_cache(); + + // Test that CrcParams::new() creates identical instances regardless of caching + let params1 = CrcParams::new( + "TEST_CRC32", + 32, + 0x04C11DB7, + 0xFFFFFFFF, + true, + 0xFFFFFFFF, + 0xCBF43926, + ); + let params2 = CrcParams::new( + "TEST_CRC32", + 32, + 0x04C11DB7, + 0xFFFFFFFF, + true, + 0xFFFFFFFF, + 0xCBF43926, + ); + + // All fields should be identical + assert_eq!(params1.name, params2.name); + assert_eq!(params1.width, params2.width); + assert_eq!(params1.poly, params2.poly); + assert_eq!(params1.init, params2.init); + assert_eq!(params1.refin, params2.refin); + assert_eq!(params1.refout, params2.refout); + assert_eq!(params1.xorout, params2.xorout); + assert_eq!(params1.check, params2.check); + assert_eq!(params1.keys, params2.keys); + + // Test CRC64 parameters as well + let params64_1 = CrcParams::new( + "TEST_CRC64", + 64, + 0x42F0E1EBA9EA3693, + 0xFFFFFFFFFFFFFFFF, + false, + 0x0, + 0x6C40DF5F0B497347, + ); + let params64_2 = CrcParams::new( + "TEST_CRC64", + 64, + 0x42F0E1EBA9EA3693, + 0xFFFFFFFFFFFFFFFF, + false, + 0x0, + 0x6C40DF5F0B497347, + ); + + assert_eq!(params64_1.name, params64_2.name); + assert_eq!(params64_1.width, params64_2.width); + assert_eq!(params64_1.poly, params64_2.poly); + assert_eq!(params64_1.init, params64_2.init); + assert_eq!(params64_1.refin, params64_2.refin); + assert_eq!(params64_1.refout, params64_2.refout); + assert_eq!(params64_1.xorout, params64_2.xorout); + assert_eq!(params64_1.check, params64_2.check); + assert_eq!(params64_1.keys, params64_2.keys); + } + + #[test] + fn test_existing_crc_parameter_combinations() { + use crate::test::consts::TEST_ALL_CONFIGS; + + clear_cache(); + + // Test all existing CRC parameter combinations work correctly with caching + for config in TEST_ALL_CONFIGS { + let params = crate::CrcParams::new( + config.get_name(), + config.get_width(), + config.get_poly(), + config.get_init(), + config.get_refin(), + config.get_xorout(), + config.get_check(), + ); + + // Verify the parameters are set correctly + assert_eq!(params.name, config.get_name()); + assert_eq!(params.width, config.get_width()); + assert_eq!(params.poly, config.get_poly()); + assert_eq!(params.init, config.get_init()); + assert_eq!(params.refin, config.get_refin()); + assert_eq!(params.refout, config.get_refin()); + assert_eq!(params.xorout, config.get_xorout()); + assert_eq!(params.check, config.get_check()); + + // Verify keys are correct by comparing with expected keys + let expected_keys = config.get_keys(); + assert_eq!( + params.keys, + expected_keys, + "Keys mismatch for {}: expected {:?}, got {:?}", + config.get_name(), + expected_keys, + params.keys + ); + } + } + + #[test] + fn test_cached_vs_uncached_results_identical() { + clear_cache(); + + // Test parameters that affect key generation + let test_cases = [ + (32, 0x04C11DB7, true), // CRC32 reflected + (32, 0x04C11DB7, false), // CRC32 non-reflected + (32, 0x1EDC6F41, true), // CRC32C + (64, 0x42F0E1EBA9EA3693, true), // CRC64 ISO reflected + (64, 0x42F0E1EBA9EA3693, false), // CRC64 ISO non-reflected + (64, 0xD800000000000000, true), // CRC64 ECMA + ]; + + for &(width, poly, reflected) in &test_cases { + // Generate keys directly (uncached) + let uncached_keys = generate::keys(width, poly, reflected); + + // Clear cache to ensure first call is cache miss + clear_cache(); + + // Create CrcParams instance (first call - cache miss) + let params1 = + crate::CrcParams::new("TEST", width, poly, 0xFFFFFFFFFFFFFFFF, reflected, 0x0, 0x0); + + // Create another CrcParams instance with same parameters (cache hit) + let params2 = + crate::CrcParams::new("TEST", width, poly, 0xFFFFFFFFFFFFFFFF, reflected, 0x0, 0x0); + + // All should be identical + assert_eq!( + uncached_keys, params1.keys, + "Uncached keys should match CrcParams keys for width={}, poly={:#x}, reflected={}", + width, poly, reflected + ); + assert_eq!( + params1.keys, params2.keys, + "Cached and uncached CrcParams should have identical keys for width={}, poly={:#x}, reflected={}", + width, poly, reflected + ); + assert_eq!( + uncached_keys, params2.keys, + "All key generation methods should produce identical results for width={}, poly={:#x}, reflected={}", + width, poly, reflected + ); + } + } + + #[test] + fn test_multiple_crc_params_instances_use_cached_keys() { + clear_cache(); + + // Create multiple CrcParams instances with the same parameters + let width = 32; + let poly = 0x04C11DB7; + let reflected = true; + let init = 0xFFFFFFFF; + let xorout = 0xFFFFFFFF; + let check = 0xCBF43926; + + // First instance - should generate and cache keys + let params1 = crate::CrcParams::new("TEST1", width, poly, init, reflected, xorout, check); + + // Subsequent instances - should use cached keys + let params2 = crate::CrcParams::new("TEST2", width, poly, init, reflected, xorout, check); + let params3 = crate::CrcParams::new("TEST3", width, poly, init, reflected, xorout, check); + let params4 = crate::CrcParams::new("TEST4", width, poly, init, reflected, xorout, check); + + // All should have identical keys (proving cache is working) + assert_eq!( + params1.keys, params2.keys, + "Instance 1 and 2 should have identical keys" + ); + assert_eq!( + params1.keys, params3.keys, + "Instance 1 and 3 should have identical keys" + ); + assert_eq!( + params1.keys, params4.keys, + "Instance 1 and 4 should have identical keys" + ); + assert_eq!( + params2.keys, params3.keys, + "Instance 2 and 3 should have identical keys" + ); + assert_eq!( + params2.keys, params4.keys, + "Instance 2 and 4 should have identical keys" + ); + assert_eq!( + params3.keys, params4.keys, + "Instance 3 and 4 should have identical keys" + ); + + // Verify keys are mathematically correct + let expected_keys = generate::keys(width, poly, reflected); + assert_eq!( + params1.keys, expected_keys, + "Cached keys should be mathematically correct" + ); + + // Test with different parameters that don't affect key generation + let params5 = crate::CrcParams::new( + "DIFFERENT_NAME", + width, + poly, + 0x12345678, + reflected, + 0x87654321, + 0xABCDEF, + ); + assert_eq!( + params1.keys, params5.keys, + "Different init/xorout/check/name should not affect cached keys" + ); + + // Test with CRC64 parameters + let width64 = 64; + let poly64 = 0x42F0E1EBA9EA3693; + let reflected64 = false; + + let params64_1 = crate::CrcParams::new( + "CRC64_1", + width64, + poly64, + 0xFFFFFFFFFFFFFFFF, + reflected64, + 0x0, + 0x0, + ); + let params64_2 = crate::CrcParams::new( + "CRC64_2", + width64, + poly64, + 0x0, + reflected64, + 0xFFFFFFFFFFFFFFFF, + 0x12345, + ); + let params64_3 = crate::CrcParams::new( + "CRC64_3", + width64, + poly64, + 0x123456789ABCDEF0, + reflected64, + 0x0FEDCBA987654321, + 0x999, + ); + + assert_eq!( + params64_1.keys, params64_2.keys, + "CRC64 instances should have identical keys" + ); + assert_eq!( + params64_1.keys, params64_3.keys, + "CRC64 instances should have identical keys" + ); + + let expected_keys64 = generate::keys(width64, poly64, reflected64); + assert_eq!( + params64_1.keys, expected_keys64, + "CRC64 cached keys should be mathematically correct" + ); + } + + #[test] + fn test_crc_params_api_compatibility() { + use crate::{CrcAlgorithm, CrcParams}; + + clear_cache(); + + // Test that the CrcParams API remains unchanged + let params = CrcParams::new( + "API_TEST", 32, 0x04C11DB7, 0xFFFFFFFF, true, 0xFFFFFFFF, 0xCBF43926, + ); + + // Verify all public fields are accessible and have expected types + let _algorithm: CrcAlgorithm = params.algorithm; + let _name: &'static str = params.name; + let _width: u8 = params.width; + let _poly: u64 = params.poly; + let _init: u64 = params.init; + let _refin: bool = params.refin; + let _refout: bool = params.refout; + let _xorout: u64 = params.xorout; + let _check: u64 = params.check; + let _keys: [u64; 23] = params.keys.to_keys_array_23(); + + // Verify the algorithm is set correctly based on width + match params.width { + 32 => assert!(matches!(params.algorithm, CrcAlgorithm::Crc32Custom)), + 64 => assert!(matches!(params.algorithm, CrcAlgorithm::Crc64Custom)), + _ => panic!("Unexpected width: {}", params.width), + } + + // Test that CrcParams can be copied and cloned + let params_copy = params; + let params_clone = params.clone(); + + assert_eq!(params.keys, params_copy.keys); + assert_eq!(params.keys, params_clone.keys); + + // Test Debug formatting works + let debug_str = format!("{:?}", params); + assert!(debug_str.contains("CrcParams")); + assert!(debug_str.contains("API_TEST")); + } + + #[test] + fn test_crc_params_with_all_standard_algorithms() { + use crate::test::consts::TEST_ALL_CONFIGS; + + clear_cache(); + + // Test creating CrcParams for all standard CRC algorithms + for config in TEST_ALL_CONFIGS { + // Create CrcParams using the same parameters as the standard algorithm + let params = crate::CrcParams::new( + config.get_name(), + config.get_width(), + config.get_poly(), + config.get_init(), + config.get_refin(), + config.get_xorout(), + config.get_check(), + ); + + // Verify the created params match the expected configuration + assert_eq!(params.name, config.get_name()); + assert_eq!(params.width, config.get_width()); + assert_eq!(params.poly, config.get_poly()); + assert_eq!(params.init, config.get_init()); + assert_eq!(params.refin, config.get_refin()); + assert_eq!(params.refout, config.get_refin()); + assert_eq!(params.xorout, config.get_xorout()); + assert_eq!(params.check, config.get_check()); + + // Most importantly, verify the keys are correct + assert_eq!( + params.keys, + config.get_keys(), + "Keys should match expected values for {}", + config.get_name() + ); + + // Create a second instance to test caching + let params2 = crate::CrcParams::new( + "CACHED_VERSION", // Different name shouldn't affect caching + config.get_width(), + config.get_poly(), + 0x12345678, // Different init shouldn't affect caching + config.get_refin(), + 0x87654321, // Different xorout shouldn't affect caching + 0xABCDEF, // Different check shouldn't affect caching + ); + + // Keys should be identical (proving cache hit) + assert_eq!( + params.keys, + params2.keys, + "Cached keys should be identical for {}", + config.get_name() + ); + } + } + + #[test] + fn test_crc_params_edge_cases() { + clear_cache(); + + // Test edge cases for CrcParams creation + + // Test minimum and maximum polynomial values + let params_min_poly = crate::CrcParams::new("MIN_POLY", 32, 0x1, 0x0, false, 0x0, 0x0); + let params_max_poly = + crate::CrcParams::new("MAX_POLY", 32, 0xFFFFFFFF, 0x0, false, 0x0, 0x0); + + // Both should create valid instances + assert_eq!(params_min_poly.width, 32); + assert_eq!(params_min_poly.poly, 0x1); + assert_eq!(params_max_poly.width, 32); + assert_eq!(params_max_poly.poly, 0xFFFFFFFF); + + // Test both reflection modes + let params_reflected = + crate::CrcParams::new("REFLECTED", 32, 0x04C11DB7, 0x0, true, 0x0, 0x0); + let params_normal = crate::CrcParams::new("NORMAL", 32, 0x04C11DB7, 0x0, false, 0x0, 0x0); + + // 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); + + // Test 64-bit edge cases + let params64_min = crate::CrcParams::new("CRC64_MIN", 64, 0x1, 0x0, false, 0x0, 0x0); + let params64_max = + crate::CrcParams::new("CRC64_MAX", 64, 0xFFFFFFFFFFFFFFFF, 0x0, false, 0x0, 0x0); + + assert_eq!(params64_min.width, 64); + assert_eq!(params64_min.poly, 0x1); + assert_eq!(params64_max.width, 64); + assert_eq!(params64_max.poly, 0xFFFFFFFFFFFFFFFF); + + // Verify all instances have valid 23-element key arrays + assert_eq!(params_min_poly.keys.key_count(), 23); + assert_eq!(params_max_poly.keys.key_count(), 23); + assert_eq!(params_reflected.keys.key_count(), 23); + assert_eq!(params_normal.keys.key_count(), 23); + assert_eq!(params64_min.keys.key_count(), 23); + assert_eq!(params64_max.keys.key_count(), 23); + } + + #[test] + fn test_crc_params_concurrent_creation() { + use std::sync::{Arc, Barrier}; + use std::thread; + + clear_cache(); + + let num_threads = 8; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + // All threads create CrcParams with the same parameters simultaneously + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + // All threads create the same CrcParams + let params = crate::CrcParams::new( + "CONCURRENT_TEST", + 32, + 0x04C11DB7, + 0xFFFFFFFF, + true, + 0xFFFFFFFF, + 0xCBF43926, + ); + + (i, params) + }); + handles.push(handle); + } + + // Collect results from all threads + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread should not panic")); + } + + // Verify all threads completed successfully + assert_eq!(results.len(), num_threads); + + // Verify all CrcParams instances have identical keys + let first_keys = results[0].1.keys; + for (thread_id, params) in results { + assert_eq!( + params.keys, first_keys, + "Thread {} should have identical keys to other threads", + thread_id + ); + + // Verify other fields are also correct + assert_eq!(params.name, "CONCURRENT_TEST"); + 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_eq!(params.xorout, 0xFFFFFFFF); + assert_eq!(params.check, 0xCBF43926); + } + + // Verify the keys are mathematically correct + let expected_keys = generate::keys(32, 0x04C11DB7, true); + assert_eq!( + first_keys, expected_keys, + "All concurrent CrcParams should have correct keys" + ); + } + + #[test] + fn test_lock_poisoning_recovery() { + use std::sync::{Arc, Barrier}; + use std::thread; + + clear_cache(); + + // This test is tricky because we need to poison the lock without + // actually breaking our test. We'll simulate lock poisoning by + // creating a scenario where a thread panics while holding a write lock. + // However, since our implementation uses best-effort error handling, + // it should gracefully degrade rather than propagate panics. + + // First, verify normal operation + let keys_before = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!(keys_before.len(), 23); + + // Test that even if internal operations fail, the function still returns valid keys + // We can't easily poison the lock in a controlled way, but we can verify + // that our error handling works by testing edge cases + + // Multiple rapid cache operations that might stress the locking mechanism + let num_threads = 4; + let barrier = Arc::new(Barrier::new(num_threads)); + let mut handles = Vec::new(); + + for i in 0..num_threads { + let barrier_clone = Arc::clone(&barrier); + let handle = thread::spawn(move || { + barrier_clone.wait(); + + // Rapid cache operations that might cause contention + for j in 0..20 { + let poly = 0x04C11DB7 + (i as u64 * 1000) + (j as u64); + let keys = get_or_generate_keys(32, poly, true); + assert_eq!( + keys.len(), + 23, + "Thread {} iteration {} should return valid keys", + i, + j + ); + + // Occasional cache clear to increase contention + if j % 7 == 0 { + clear_cache(); + } + } + + i + }); + handles.push(handle); + } + + // All threads should complete successfully + for handle in handles { + let thread_id = handle.join().expect("Thread should not panic"); + assert!(thread_id < num_threads); + } + + // Verify cache is still functional after stress testing + let keys_after = get_or_generate_keys(32, 0x04C11DB7, true); + assert_eq!(keys_after.len(), 23); + + // Keys should be mathematically correct regardless of cache state + let expected_keys = generate::keys(32, 0x04C11DB7, true); + assert_eq!(keys_after, expected_keys); + } + + #[test] + fn test_cache_behavior_with_thread_local_access() { + use std::thread; + + clear_cache(); + + // Test that cache works correctly when accessed from different threads + // in sequence (not concurrently) + + let keys_main = get_or_generate_keys(32, 0x04C11DB7, true); + + 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 + }); + + let keys_from_thread = handle.join().expect("Thread should not panic"); + + // Both should be identical + assert_eq!(keys_main, keys_from_thread); + + // Test multiple sequential threads + let mut thread_keys = Vec::new(); + + for i in 0..5 { + let handle = thread::spawn(move || { + let keys = get_or_generate_keys(32, 0x04C11DB7, true); + (i, keys) + }); + + let (thread_id, keys) = handle.join().expect("Thread should not panic"); + thread_keys.push((thread_id, keys)); + } + + // All threads should get the same cached keys + for (thread_id, keys) in thread_keys { + assert_eq!( + keys, keys_main, + "Thread {} should get same cached keys", + thread_id + ); + } + } +} diff --git a/src/combine.rs b/src/combine.rs index d4b2466..aa6fb4e 100644 --- a/src/combine.rs +++ b/src/combine.rs @@ -50,7 +50,7 @@ http://reveng.sourceforge.net/crc-catalogue/all.htm */ -use crate::structs::CrcParams; +use crate::CrcParams; /* Multiply the GF(2) vector vec by the GF(2) matrix mat, returning the resulting vector. The vector is stored as bits in a crc_t. The matrix is diff --git a/src/crc32/consts.rs b/src/crc32/consts.rs index 9c06015..cc4fcff 100644 --- a/src/crc32/consts.rs +++ b/src/crc32/consts.rs @@ -7,8 +7,8 @@ use crate::consts::{ NAME_CRC32_CD_ROM_EDC, NAME_CRC32_CKSUM, NAME_CRC32_ISCSI, NAME_CRC32_ISO_HDLC, NAME_CRC32_JAMCRC, NAME_CRC32_MEF, NAME_CRC32_MPEG_2, NAME_CRC32_XFER, }; -use crate::structs::CrcParams; use crate::CrcAlgorithm; +use crate::CrcParams; use crc::{ CRC_32_AIXM, CRC_32_AUTOSAR, CRC_32_BASE91_D, CRC_32_BZIP2, CRC_32_CD_ROM_EDC, CRC_32_CKSUM, CRC_32_ISCSI, CRC_32_ISO_HDLC, CRC_32_JAMCRC, CRC_32_MEF, CRC_32_MPEG_2, CRC_32_XFER, @@ -25,7 +25,7 @@ pub const CRC32_AIXM: CrcParams = CrcParams { refout: CRC_32_AIXM.refout, // false xorout: CRC_32_AIXM.xorout as u64, check: CRC_32_AIXM.check as u64, - keys: KEYS_814141AB_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_814141AB_FORWARD), }; // width=32 poly=0xf4acfb13 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0x1697d06a residue=0x904cddbf name="CRC-32/AUTOSAR" @@ -39,7 +39,7 @@ pub const CRC32_AUTOSAR: CrcParams = CrcParams { refout: CRC_32_AUTOSAR.refout, // true xorout: CRC_32_AUTOSAR.xorout as u64, check: CRC_32_AUTOSAR.check as u64, - keys: KEYS_F4ACFB13_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_F4ACFB13_REFLECTED), }; // width=32 poly=0xa833982b init=0xffffffff refin=true refout=true xorout=0xffffffff check=0x87315576 residue=0x45270551 name="CRC-32/BASE91-D" @@ -53,7 +53,7 @@ pub const CRC32_BASE91_D: CrcParams = CrcParams { refout: CRC_32_BASE91_D.refout, // true xorout: CRC_32_BASE91_D.xorout as u64, check: CRC_32_BASE91_D.check as u64, - keys: KEYS_A833982B_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_A833982B_REFLECTED), }; // width=32 poly=0x04c11db7 init=0xffffffff refin=false refout=false xorout=0xffffffff check=0xfc891918 residue=0xc704dd7b name="CRC-32/BZIP2" @@ -67,7 +67,7 @@ pub const CRC32_BZIP2: CrcParams = CrcParams { refout: CRC_32_BZIP2.refout, // false xorout: CRC_32_BZIP2.xorout as u64, check: CRC_32_BZIP2.check as u64, - keys: KEYS_04C11DB7_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_04C11DB7_FORWARD), }; // width=32 poly=0x8001801b init=0x00000000 refin=true refout=true xorout=0x00000000 check=0x6ec2edc4 residue=0x00000000 name="CRC-32/CD-ROM-EDC" @@ -81,7 +81,7 @@ pub const CRC32_CD_ROM_EDC: CrcParams = CrcParams { refout: CRC_32_CD_ROM_EDC.refout, // true xorout: CRC_32_CD_ROM_EDC.xorout as u64, check: CRC_32_CD_ROM_EDC.check as u64, - keys: KEYS_8001801B_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_8001801B_REFLECTED), }; // width=32 poly=0x04c11db7 init=0x00000000 refin=false refout=false xorout=0xffffffff check=0x765e7680 residue=0xc704dd7b name="CRC-32/CKSUM" @@ -95,7 +95,7 @@ pub const CRC32_CKSUM: CrcParams = CrcParams { refout: CRC_32_CKSUM.refout, // false xorout: CRC_32_CKSUM.xorout as u64, check: CRC_32_CKSUM.check as u64, - keys: KEYS_04C11DB7_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_04C11DB7_FORWARD), }; // width=32 poly=0x1edc6f41 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0xe3069283 residue=0xb798b438 name="CRC-32/ISCSI" @@ -109,7 +109,7 @@ pub const CRC32_ISCSI: CrcParams = CrcParams { refout: CRC_32_ISCSI.refout, // true xorout: CRC_32_ISCSI.xorout as u64, check: CRC_32_ISCSI.check as u64, - keys: KEYS_1EDC6F41_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_1EDC6F41_REFLECTED), }; // width=32 poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff check=0xcbf43926 residue=0xdebb20e3 name="CRC-32/ISO-HDLC" @@ -123,7 +123,7 @@ pub const CRC32_ISO_HDLC: CrcParams = CrcParams { refout: CRC_32_ISO_HDLC.refout, // true xorout: CRC_32_ISO_HDLC.xorout as u64, check: CRC_32_ISO_HDLC.check as u64, - keys: KEYS_04C11DB7_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_04C11DB7_REFLECTED), }; // width=32 poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0x00000000 check=0x340bc6d9 residue=0x00000000 name="CRC-32/JAMCRC" @@ -137,7 +137,7 @@ pub const CRC32_JAMCRC: CrcParams = CrcParams { refout: CRC_32_JAMCRC.refout, // true xorout: CRC_32_JAMCRC.xorout as u64, check: CRC_32_JAMCRC.check as u64, - keys: KEYS_04C11DB7_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_04C11DB7_REFLECTED), }; // width=32 poly=0x741b8cd7 init=0xffffffff refin=true refout=true xorout=0x00000000 check=0xd2c22f51 residue=0x00000000 name="CRC-32/MEF" @@ -151,7 +151,7 @@ pub const CRC32_MEF: CrcParams = CrcParams { refout: CRC_32_MEF.refout, // true xorout: CRC_32_MEF.xorout as u64, check: CRC_32_MEF.check as u64, - keys: KEYS_741B8CD7_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_741B8CD7_REFLECTED), }; // width=32 poly=0x04c11db7 init=0xffffffff refin=false refout=false xorout=0x00000000 check=0x0376e6e7 residue=0x00000000 name="CRC-32/MPEG-2" @@ -165,7 +165,7 @@ pub const CRC32_MPEG_2: CrcParams = CrcParams { refout: CRC_32_MPEG_2.refout, // false xorout: CRC_32_MPEG_2.xorout as u64, check: CRC_32_MPEG_2.check as u64, - keys: KEYS_04C11DB7_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_04C11DB7_FORWARD), }; // width=32 poly=0x000000af init=0x00000000 refin=false refout=false xorout=0x00000000 check=0xbd0be338 residue=0x00000000 name="CRC-32/XFER" @@ -179,7 +179,7 @@ pub const CRC32_XFER: CrcParams = CrcParams { refout: CRC_32_XFER.refout, // false xorout: CRC_32_XFER.xorout as u64, check: CRC_32_XFER.check as u64, - keys: KEYS_000000AF_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_000000AF_FORWARD), }; // CRC-32/AIXM diff --git a/src/crc64/consts.rs b/src/crc64/consts.rs index c68e665..cddd104 100644 --- a/src/crc64/consts.rs +++ b/src/crc64/consts.rs @@ -3,8 +3,8 @@ #![allow(dead_code)] use crate::consts::*; -use crate::structs::CrcParams; use crate::CrcAlgorithm; +use crate::CrcParams; use crc::{CRC_64_ECMA_182, CRC_64_GO_ISO, CRC_64_MS, CRC_64_REDIS, CRC_64_WE, CRC_64_XZ}; // width=64 poly=0x42f0e1eba9ea3693 init=0x0000000000000000 refin=false refout=false xorout=0x0000000000000000 check=0x6c40df5f0b497347 residue=0x0000000000000000 name="CRC-64/ECMA-182" @@ -18,7 +18,7 @@ pub const CRC64_ECMA_182: CrcParams = CrcParams { refout: CRC_64_ECMA_182.refout, // false xorout: CRC_64_ECMA_182.xorout, check: CRC_64_ECMA_182.check, - keys: KEYS_42F0E1EBA9EA3693_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_42F0E1EBA9EA3693_FORWARD), }; // width=64 poly=0x000000000000001b init=0xffffffffffffffff refin=true refout=true xorout=0xffffffffffffffff check=0xb90956c775a41001 residue=0x5300000000000000 name="CRC-64/GO-ISO" @@ -32,7 +32,7 @@ pub const CRC64_GO_ISO: CrcParams = CrcParams { refout: CRC_64_GO_ISO.refout, // true xorout: CRC_64_GO_ISO.xorout, check: CRC_64_GO_ISO.check, - keys: KEYS_000000000000001B_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_000000000000001B_REFLECTED), }; // width=64 poly=0x259c84cba6426349 init=0xffffffffffffffff refin=true refout=true xorout=0x0000000000000000 check=0x75d4b74f024eceea residue=0x0000000000000000 name="CRC-64/MS" @@ -46,7 +46,7 @@ pub const CRC64_MS: CrcParams = CrcParams { refout: CRC_64_MS.refout, // true xorout: CRC_64_MS.xorout, check: CRC_64_MS.check, - keys: KEYS_259C84CBA6426349_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_259C84CBA6426349_REFLECTED), }; // https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-64-nvme @@ -61,7 +61,7 @@ pub const CRC64_NVME: CrcParams = CrcParams { refout: CRC_64_NVME.refout, // true xorout: CRC_64_NVME.xorout, check: CRC_64_NVME.check, - keys: KEYS_AD93D23594C93659_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_AD93D23594C93659_REFLECTED), }; // width=64 poly=0xad93d23594c935a9 init=0x0000000000000000 refin=true refout=true xorout=0x0000000000000000 check=0xe9c6d914c4b8d9ca residue=0x0000000000000000 name="CRC-64/REDIS" @@ -75,7 +75,7 @@ pub const CRC64_REDIS: CrcParams = CrcParams { refout: CRC_64_REDIS.refout, // true xorout: CRC_64_REDIS.xorout, check: CRC_64_REDIS.check, - keys: KEYS_AD93D23594C935A9_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_AD93D23594C935A9_REFLECTED), }; // width=64 poly=0x42f0e1eba9ea3693 init=0xffffffffffffffff refin=false refout=false xorout=0xffffffffffffffff check=0x62ec59e3f1a4f00a residue=0xfcacbebd5931a992 name="CRC-64/WE" @@ -89,7 +89,7 @@ pub const CRC64_WE: CrcParams = CrcParams { refout: CRC_64_WE.refout, // false xorout: CRC_64_WE.xorout, check: CRC_64_WE.check, - keys: KEYS_42F0E1EBA9EA3693_FORWARD, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_42F0E1EBA9EA3693_FORWARD), }; // width=64 poly=0x42f0e1eba9ea3693 init=0xffffffffffffffff refin=true refout=true xorout=0xffffffffffffffff check=0x995dc9bbdf1939fa residue=0x49958c9abd7d353f name="CRC-64/XZ" @@ -103,7 +103,7 @@ pub const CRC64_XZ: CrcParams = CrcParams { refout: CRC_64_XZ.refout, // true xorout: CRC_64_XZ.xorout, check: CRC_64_XZ.check, - keys: KEYS_42F0E1EBA9EA3693_REFLECTED, + keys: crate::CrcKeysStorage::from_keys_fold_256(KEYS_42F0E1EBA9EA3693_REFLECTED), }; // CRC-64/MS diff --git a/src/enums.rs b/src/enums.rs index 02f487a..b1c50ac 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -43,12 +43,14 @@ impl Display for CrcAlgorithm { CrcAlgorithm::Crc32Bzip2 => write!(f, "{}", NAME_CRC32_BZIP2), CrcAlgorithm::Crc32CdRomEdc => write!(f, "{}", NAME_CRC32_CD_ROM_EDC), CrcAlgorithm::Crc32Cksum => write!(f, "{}", NAME_CRC32_CKSUM), + CrcAlgorithm::Crc32Custom => write!(f, "CRC-32/CUSTOM"), CrcAlgorithm::Crc32Iscsi => write!(f, "{}", NAME_CRC32_ISCSI), CrcAlgorithm::Crc32IsoHdlc => write!(f, "{}", NAME_CRC32_ISO_HDLC), CrcAlgorithm::Crc32Jamcrc => write!(f, "{}", NAME_CRC32_JAMCRC), CrcAlgorithm::Crc32Mef => write!(f, "{}", NAME_CRC32_MEF), CrcAlgorithm::Crc32Mpeg2 => write!(f, "{}", NAME_CRC32_MPEG_2), CrcAlgorithm::Crc32Xfer => write!(f, "{}", NAME_CRC32_XFER), + CrcAlgorithm::Crc64Custom => write!(f, "CRC-64/CUSTOM"), CrcAlgorithm::Crc64GoIso => write!(f, "{}", NAME_CRC64_GO_ISO), CrcAlgorithm::Crc64Ms => write!(f, "{}", NAME_CRC64_MS), CrcAlgorithm::Crc64Nvme => write!(f, "{}", NAME_CRC64_NVME), diff --git a/src/ffi.rs b/src/ffi.rs index 8f1327d..4da66a5 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -8,10 +8,60 @@ #![cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))] use crate::CrcAlgorithm; +use crate::CrcParams; use crate::{get_calculator_target, Digest}; +use std::collections::HashMap; use std::ffi::CStr; use std::os::raw::c_char; use std::slice; +use std::sync::Mutex; +use std::sync::OnceLock; + +// 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. +fn create_stable_key_pointer(keys: &crate::CrcKeysStorage) -> (*const u64, u32) { + let storage = STABLE_KEY_STORAGE.get_or_init(|| Mutex::new(HashMap::new())); + + // Create a unique hash for this key set to avoid duplicates + let key_hash = match keys { + crate::CrcKeysStorage::KeysFold256(keys) => { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + use std::hash::{Hash, Hasher}; + keys.hash(&mut hasher); + hasher.finish() + } + crate::CrcKeysStorage::KeysFutureTest(keys) => { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + use std::hash::{Hash, Hasher}; + keys.hash(&mut hasher); + hasher.finish() + } + }; + + let mut storage_map = storage.lock().unwrap(); + + // Check if we already have this key set stored + if let Some(stored_keys) = storage_map.get(&key_hash) { + return (stored_keys.as_ptr(), stored_keys.len() as u32); + } + + // Store the keys in stable memory + let key_vec: Vec = match keys { + crate::CrcKeysStorage::KeysFold256(keys) => keys.to_vec(), + crate::CrcKeysStorage::KeysFutureTest(keys) => keys.to_vec(), + }; + + let boxed_keys = key_vec.into_boxed_slice(); + let ptr = boxed_keys.as_ptr(); + let count = boxed_keys.len() as u32; + + storage_map.insert(key_hash, boxed_keys); + + (ptr, count) +} /// A handle to the Digest object #[repr(C)] @@ -26,12 +76,14 @@ pub enum CrcFastAlgorithm { Crc32Bzip2, Crc32CdRomEdc, Crc32Cksum, + Crc32Custom, Crc32Iscsi, Crc32IsoHdlc, Crc32Jamcrc, Crc32Mef, Crc32Mpeg2, Crc32Xfer, + Crc64Custom, Crc64Ecma182, Crc64GoIso, Crc64Ms, @@ -51,12 +103,14 @@ impl From for CrcAlgorithm { CrcFastAlgorithm::Crc32Bzip2 => CrcAlgorithm::Crc32Bzip2, CrcFastAlgorithm::Crc32CdRomEdc => CrcAlgorithm::Crc32CdRomEdc, CrcFastAlgorithm::Crc32Cksum => CrcAlgorithm::Crc32Cksum, + CrcFastAlgorithm::Crc32Custom => CrcAlgorithm::Crc32Custom, CrcFastAlgorithm::Crc32Iscsi => CrcAlgorithm::Crc32Iscsi, CrcFastAlgorithm::Crc32IsoHdlc => CrcAlgorithm::Crc32IsoHdlc, CrcFastAlgorithm::Crc32Jamcrc => CrcAlgorithm::Crc32Jamcrc, CrcFastAlgorithm::Crc32Mef => CrcAlgorithm::Crc32Mef, CrcFastAlgorithm::Crc32Mpeg2 => CrcAlgorithm::Crc32Mpeg2, CrcFastAlgorithm::Crc32Xfer => CrcAlgorithm::Crc32Xfer, + CrcFastAlgorithm::Crc64Custom => CrcAlgorithm::Crc64Custom, CrcFastAlgorithm::Crc64Ecma182 => CrcAlgorithm::Crc64Ecma182, CrcFastAlgorithm::Crc64GoIso => CrcAlgorithm::Crc64GoIso, CrcFastAlgorithm::Crc64Ms => CrcAlgorithm::Crc64Ms, @@ -68,6 +122,95 @@ impl From for CrcAlgorithm { } } +/// Custom CRC parameters +#[repr(C)] +pub struct CrcFastParams { + pub algorithm: CrcFastAlgorithm, + pub width: u8, + pub poly: u64, + pub init: u64, + pub refin: bool, + pub refout: bool, + pub xorout: u64, + pub check: u64, + pub key_count: u32, + pub keys: *const u64, +} + +// Convert from FFI struct to internal struct +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, + } + } +} + +// Convert from internal struct to FFI struct +impl From for CrcFastParams { + fn from(params: CrcParams) -> Self { + // Create stable key pointer for FFI usage + let (keys_ptr, key_count) = create_stable_key_pointer(¶ms.keys); + + CrcFastParams { + algorithm: match params.algorithm { + CrcAlgorithm::Crc32Aixm => CrcFastAlgorithm::Crc32Aixm, + CrcAlgorithm::Crc32Autosar => CrcFastAlgorithm::Crc32Autosar, + CrcAlgorithm::Crc32Base91D => CrcFastAlgorithm::Crc32Base91D, + CrcAlgorithm::Crc32Bzip2 => CrcFastAlgorithm::Crc32Bzip2, + CrcAlgorithm::Crc32CdRomEdc => CrcFastAlgorithm::Crc32CdRomEdc, + CrcAlgorithm::Crc32Cksum => CrcFastAlgorithm::Crc32Cksum, + CrcAlgorithm::Crc32Custom => CrcFastAlgorithm::Crc32Custom, + CrcAlgorithm::Crc32Iscsi => CrcFastAlgorithm::Crc32Iscsi, + CrcAlgorithm::Crc32IsoHdlc => CrcFastAlgorithm::Crc32IsoHdlc, + CrcAlgorithm::Crc32Jamcrc => CrcFastAlgorithm::Crc32Jamcrc, + CrcAlgorithm::Crc32Mef => CrcFastAlgorithm::Crc32Mef, + CrcAlgorithm::Crc32Mpeg2 => CrcFastAlgorithm::Crc32Mpeg2, + CrcAlgorithm::Crc32Xfer => CrcFastAlgorithm::Crc32Xfer, + CrcAlgorithm::Crc64Custom => CrcFastAlgorithm::Crc64Custom, + CrcAlgorithm::Crc64Ecma182 => CrcFastAlgorithm::Crc64Ecma182, + CrcAlgorithm::Crc64GoIso => CrcFastAlgorithm::Crc64GoIso, + CrcAlgorithm::Crc64Ms => CrcFastAlgorithm::Crc64Ms, + CrcAlgorithm::Crc64Nvme => CrcFastAlgorithm::Crc64Nvme, + CrcAlgorithm::Crc64Redis => CrcFastAlgorithm::Crc64Redis, + CrcAlgorithm::Crc64We => CrcFastAlgorithm::Crc64We, + CrcAlgorithm::Crc64Xz => CrcFastAlgorithm::Crc64Xz, + }, + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + key_count, + keys: keys_ptr, + } + } +} + /// Creates a new Digest to compute CRC checksums using algorithm #[no_mangle] pub extern "C" fn crc_fast_digest_new(algorithm: CrcFastAlgorithm) -> *mut CrcFastDigestHandle { @@ -76,6 +219,16 @@ pub extern "C" fn crc_fast_digest_new(algorithm: CrcFastAlgorithm) -> *mut CrcFa Box::into_raw(handle) } +/// Creates a new Digest to compute CRC checksums using custom parameters +#[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) +} + /// Updates the Digest with data #[no_mangle] pub extern "C" fn crc_fast_digest_update( @@ -197,6 +350,23 @@ pub extern "C" fn crc_fast_checksum( } } +/// Helper method to calculate a CRC checksum directly for data using custom parameters +#[no_mangle] +pub extern "C" fn crc_fast_checksum_with_params( + params: CrcFastParams, + data: *const c_char, + len: usize, +) -> u64 { + if data.is_null() { + 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) + } +} + /// Helper method to just calculate a CRC checksum directly for a file using algorithm #[no_mangle] pub extern "C" fn crc_fast_checksum_file( @@ -218,6 +388,27 @@ pub extern "C" fn crc_fast_checksum_file( } } +/// Helper method to calculate a CRC checksum directly for a file using custom parameters +#[no_mangle] +pub extern "C" fn crc_fast_checksum_file_with_params( + params: CrcFastParams, + path_ptr: *const u8, + path_len: usize, +) -> u64 { + if path_ptr.is_null() { + 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 + } +} + /// Combine two CRC checksums using algorithm #[no_mangle] pub extern "C" fn crc_fast_checksum_combine( @@ -229,6 +420,68 @@ pub extern "C" fn crc_fast_checksum_combine( crate::checksum_combine(algorithm.into(), checksum1, checksum2, checksum2_len) } +/// Combine two CRC checksums using custom parameters +#[no_mangle] +pub extern "C" fn crc_fast_checksum_combine_with_custom_params( + params: CrcFastParams, + checksum1: u64, + checksum2: u64, + checksum2_len: u64, +) -> u64 { + crate::checksum_combine_with_custom_params(params.into(), checksum1, checksum2, checksum2_len) +} + +/// Returns the custom CRC parameters for a given set of Rocksoft CRC parameters +#[no_mangle] +pub extern "C" fn crc_fast_get_custom_params( + name_ptr: *const c_char, + width: u8, + poly: u64, + init: u64, + reflected: bool, + xorout: u64, + check: u64, +) -> CrcFastParams { + let name = if name_ptr.is_null() { + "custom" + } else { + unsafe { CStr::from_ptr(name_ptr).to_str().unwrap_or("custom") } + }; + + // Get the custom params from the library + let params = CrcParams::new( + // We need to use a static string for the name field + Box::leak(name.to_string().into_boxed_str()), + width, + poly, + init, + reflected, + xorout, + check, + ); + + // Create stable key pointer for FFI usage + let (keys_ptr, key_count) = create_stable_key_pointer(¶ms.keys); + + // Convert to FFI struct + CrcFastParams { + algorithm: match width { + 32 => CrcFastAlgorithm::Crc32Custom, + 64 => CrcFastAlgorithm::Crc64Custom, + _ => panic!("Unsupported width: {}", width), + }, + width: params.width, + poly: params.poly, + init: params.init, + refin: params.refin, + refout: params.refout, + xorout: params.xorout, + check: params.check, + key_count, + keys: keys_ptr, + } +} + /// Gets the target build properties (CPU architecture and fine-tuning parameters) for this algorithm #[no_mangle] pub extern "C" fn crc_fast_get_calculator_target(algorithm: CrcFastAlgorithm) -> *const c_char { diff --git a/src/lib.rs b/src/lib.rs index b4f7a8e..0fa467b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ //! Implements the [std::io::Write](https://doc.rust-lang.org/std/io/trait.Write.html) trait for //! easier integration with existing code. //! -//! ```no_run +//! ```rust //! use std::env; //! use std::fs::File; //! use crc_fast::{Digest, CrcAlgorithm::Crc32IsoHdlc}; @@ -116,7 +116,7 @@ use crate::crc32::fusion; use crate::crc64::consts::{ CRC64_ECMA_182, CRC64_GO_ISO, CRC64_MS, CRC64_NVME, CRC64_REDIS, CRC64_WE, CRC64_XZ, }; -use crate::structs::{Calculator, CrcParams}; +use crate::structs::Calculator; use crate::traits::CrcCalculator; use digest::{DynDigest, InvalidBufferSize}; use std::fs::File; @@ -124,6 +124,7 @@ use std::io::{Read, Write}; mod algorithm; mod arch; +mod cache; mod combine; mod consts; mod crc32; @@ -136,7 +137,7 @@ mod test; mod traits; /// Supported CRC-32 and CRC-64 variants -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum CrcAlgorithm { Crc32Aixm, Crc32Autosar, @@ -144,12 +145,14 @@ pub enum CrcAlgorithm { Crc32Bzip2, Crc32CdRomEdc, Crc32Cksum, + Crc32Custom, // Custom CRC-32 implementation, not defined in consts Crc32Iscsi, Crc32IsoHdlc, Crc32Jamcrc, Crc32Mef, Crc32Mpeg2, Crc32Xfer, + Crc64Custom, // Custom CRC-64 implementation, not defined in consts Crc64Ecma182, Crc64GoIso, Crc64Ms, @@ -159,6 +162,105 @@ pub enum CrcAlgorithm { Crc64Xz, } +/// Internal storage for CRC folding keys that can accommodate different array sizes. +/// This enum allows future expansion to support larger folding distances while maintaining +/// backwards compatibility with existing const definitions. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CrcKeysStorage { + /// Current 23-key format for existing algorithms (supports up to 256-byte folding distances) + KeysFold256([u64; 23]), + /// Future 25-key format for potential expanded folding distances (testing purposes only) + KeysFutureTest([u64; 25]), +} + +impl CrcKeysStorage { + /// Safe key access with bounds checking. Returns 0 for out-of-bounds indices. + #[inline(always)] + const fn get_key(self, index: usize) -> u64 { + match self { + CrcKeysStorage::KeysFold256(keys) => { + if index < 23 { + keys[index] + } else { + 0 + } + } + CrcKeysStorage::KeysFutureTest(keys) => { + if index < 25 { + keys[index] + } else { + 0 + } + } + } + } + + /// Returns the number of keys available in this storage variant. + #[inline(always)] + const fn key_count(self) -> usize { + match self { + CrcKeysStorage::KeysFold256(_) => 23, + CrcKeysStorage::KeysFutureTest(_) => 25, + } + } + + /// Const constructor for 23-key arrays (current format). + #[inline(always)] + const fn from_keys_fold_256(keys: [u64; 23]) -> Self { + CrcKeysStorage::KeysFold256(keys) + } + + /// Const constructor for 25-key arrays (future expansion testing). + #[inline(always)] + #[allow(dead_code)] // Reserved for future expansion + const fn from_keys_fold_future_test(keys: [u64; 25]) -> Self { + CrcKeysStorage::KeysFutureTest(keys) + } + + /// Extracts keys as a [u64; 23] array for FFI compatibility. + /// For variants with more than 23 keys, only the first 23 are returned. + /// For variants with fewer keys, remaining slots are filled with 0. + #[inline(always)] + pub fn to_keys_array_23(self) -> [u64; 23] { + match self { + CrcKeysStorage::KeysFold256(keys) => keys, + CrcKeysStorage::KeysFutureTest(keys) => { + let mut result = [0u64; 23]; + result.copy_from_slice(&keys[..23]); + result + } + } + } +} + +// Implement PartialEq between CrcKeysStorage and [u64; 23] for test compatibility +impl PartialEq<[u64; 23]> for CrcKeysStorage { + fn eq(&self, other: &[u64; 23]) -> bool { + self.to_keys_array_23() == *other + } +} + +impl PartialEq for [u64; 23] { + fn eq(&self, other: &CrcKeysStorage) -> bool { + *self == other.to_keys_array_23() + } +} + +/// Parameters for CRC computation, including polynomial, initial value, and other settings. +#[derive(Clone, Copy, Debug)] +pub struct CrcParams { + pub algorithm: CrcAlgorithm, + pub name: &'static str, + pub width: u8, + pub poly: u64, + pub init: u64, + pub refin: bool, + pub refout: bool, + pub xorout: u64, + pub check: u64, + pub keys: CrcKeysStorage, +} + /// Type alias for a function pointer that represents a CRC calculation function. /// /// The function takes the following parameters: @@ -261,6 +363,19 @@ impl Digest { } } + /// Creates a new `Digest` instance with custom parameters. + #[inline(always)] + pub fn new_with_params(params: CrcParams) -> Self { + let calculator = Calculator::calculate as CalculatorFn; + + Self { + state: params.init, + amount: 0, + params, + calculator, + } + } + /// Updates the CRC state with the given data. #[inline(always)] pub fn update(&mut self, data: &[u8]) { @@ -361,6 +476,13 @@ pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 { calculator(params.init, buf, params) ^ params.xorout } +/// Computes the CRC checksum for the given data using the custom specified parameters. +pub fn checksum_with_params(params: CrcParams, buf: &[u8]) -> u64 { + let calculator = Calculator::calculate as CalculatorFn; + + calculator(params.init, buf, params) ^ params.xorout +} + /// Computes the CRC checksum for the given file using the specified algorithm. /// /// Appears to be much faster (~2X) than using Writer and io::*, at least on Apple M2 Ultra @@ -371,7 +493,7 @@ pub fn checksum(algorithm: CrcAlgorithm, buf: &[u8]) -> u64 { /// /// # Examples /// ### checksum_file -///```no_run +///```rust /// use std::env; /// use crc_fast::{checksum_file, CrcAlgorithm::Crc32IsoHdlc}; /// @@ -389,7 +511,32 @@ pub fn checksum_file( path: &str, chunk_size: Option, ) -> Result { - let mut digest = Digest::new(algorithm); + checksum_file_with_digest(Digest::new(algorithm), path, chunk_size) +} + +/// Computes the CRC checksum for the given file using the custom specified parameters. +/// +/// # Errors +/// +/// This function will return an error if the file cannot be read. +pub fn checksum_file_with_params( + params: CrcParams, + path: &str, + chunk_size: Option, +) -> Result { + checksum_file_with_digest(Digest::new_with_params(params), path, chunk_size) +} + +/// Computes the CRC checksum for the given file using the specified Digest. +/// +/// # Errors +/// +/// This function will return an error if the file cannot be read. +fn checksum_file_with_digest( + mut digest: Digest, + path: &str, + chunk_size: Option, +) -> Result { let mut file = File::open(path)?; // 512KiB KiB was fastest in my benchmarks on an Apple M2 Ultra @@ -435,6 +582,16 @@ pub fn checksum_combine( combine::checksums(checksum1, checksum2, checksum2_len, params) } +/// Combines two CRC checksums using the custom specified parameters. +pub fn checksum_combine_with_custom_params( + params: CrcParams, + checksum1: u64, + checksum2: u64, + checksum2_len: u64, +) -> u64 { + combine::checksums(checksum1, checksum2, checksum2_len, params) +} + /// Returns the target used to calculate the CRC checksum for the specified algorithm. /// /// These strings are informational only, not stable, and shouldn't be relied on to match across @@ -460,12 +617,18 @@ fn get_calculator_params(algorithm: CrcAlgorithm) -> (CalculatorFn, CrcParams) { CrcAlgorithm::Crc32Bzip2 => (Calculator::calculate as CalculatorFn, CRC32_BZIP2), CrcAlgorithm::Crc32CdRomEdc => (Calculator::calculate as CalculatorFn, CRC32_CD_ROM_EDC), CrcAlgorithm::Crc32Cksum => (Calculator::calculate as CalculatorFn, CRC32_CKSUM), + CrcAlgorithm::Crc32Custom => { + panic!("Custom CRC-32 requires parameters via CrcParams::new()") + } CrcAlgorithm::Crc32Iscsi => (crc32_iscsi_calculator as CalculatorFn, CRC32_ISCSI), CrcAlgorithm::Crc32IsoHdlc => (crc32_iso_hdlc_calculator as CalculatorFn, CRC32_ISO_HDLC), CrcAlgorithm::Crc32Jamcrc => (Calculator::calculate as CalculatorFn, CRC32_JAMCRC), CrcAlgorithm::Crc32Mef => (Calculator::calculate as CalculatorFn, CRC32_MEF), CrcAlgorithm::Crc32Mpeg2 => (Calculator::calculate as CalculatorFn, CRC32_MPEG_2), CrcAlgorithm::Crc32Xfer => (Calculator::calculate as CalculatorFn, CRC32_XFER), + CrcAlgorithm::Crc64Custom => { + panic!("Custom CRC-64 requires parameters via CrcParams::new()") + } CrcAlgorithm::Crc64Ecma182 => (Calculator::calculate as CalculatorFn, CRC64_ECMA_182), CrcAlgorithm::Crc64GoIso => (Calculator::calculate as CalculatorFn, CRC64_GO_ISO), CrcAlgorithm::Crc64Ms => (Calculator::calculate as CalculatorFn, CRC64_MS), @@ -515,7 +678,7 @@ mod lib { use super::*; use crate::test::consts::{TEST_ALL_CONFIGS, TEST_CHECK_STRING}; use crate::test::enums::AnyCrcTestConfig; - use cbindgen::Language::{Cxx, C}; + use cbindgen::Language::C; use cbindgen::Style::Both; use rand::{rng, Rng}; use std::fs::{read, write}; @@ -540,19 +703,103 @@ mod lib { } } + #[test] + fn test_checksum_with_custom_params() { + crate::cache::clear_cache(); + + // CRC-32 reflected + assert_eq!( + checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING), + CRC32_ISCSI.check, + ); + + // CRC-32 forward + assert_eq!( + checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING), + CRC32_BZIP2.check, + ); + + // CRC-64 reflected + assert_eq!( + checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING), + CRC64_NVME.check, + ); + + // CRC-64 forward + assert_eq!( + checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING), + CRC64_ECMA_182.check, + ); + } + + #[test] + fn test_get_custom_params() { + crate::cache::clear_cache(); + + assert_eq!( + checksum_with_params(get_custom_crc32_reflected(), TEST_CHECK_STRING), + CRC32_ISCSI.check, + ); + + assert_eq!( + checksum_with_params(get_custom_crc32_forward(), TEST_CHECK_STRING), + CRC32_BZIP2.check, + ); + + assert_eq!( + checksum_with_params(get_custom_crc64_reflected(), TEST_CHECK_STRING), + CRC64_NVME.check, + ); + + assert_eq!( + checksum_with_params(get_custom_crc64_forward(), TEST_CHECK_STRING), + CRC64_ECMA_182.check, + ); + } + #[test] fn test_digest_updates_check() { for config in TEST_ALL_CONFIGS { - let mut digest = Digest::new(config.get_algorithm()); - digest.update(b"123"); - digest.update(b"456"); - digest.update(b"789"); - let result = digest.finalize(); - - assert_eq!(result, config.get_check()); + check_digest(Digest::new(config.get_algorithm()), config.get_check()); } } + #[test] + fn test_digest_updates_check_with_custom_params() { + crate::cache::clear_cache(); + + // CRC-32 reflected + check_digest( + Digest::new_with_params(get_custom_crc32_reflected()), + CRC32_ISCSI.check, + ); + + // CRC-32 forward + check_digest( + Digest::new_with_params(get_custom_crc32_forward()), + CRC32_BZIP2.check, + ); + + // CRC-64 reflected + check_digest( + Digest::new_with_params(get_custom_crc64_reflected()), + CRC64_NVME.check, + ); + + // CRC-64 forward + check_digest( + Digest::new_with_params(get_custom_crc64_forward()), + CRC64_ECMA_182.check, + ); + } + + fn check_digest(mut digest: Digest, check: u64) { + digest.update(b"123"); + digest.update(b"456"); + digest.update(b"789"); + assert_eq!(digest.finalize(), check,); + } + #[test] fn test_small_all_lengths() { for config in TEST_ALL_CONFIGS { @@ -629,12 +876,53 @@ mod lib { } } + #[test] + fn test_combine_with_custom_params() { + crate::cache::clear_cache(); + + // CRC-32 reflected + let crc32_params = get_custom_crc32_reflected(); + let checksum1 = checksum_with_params(crc32_params, "1234".as_ref()); + let checksum2 = checksum_with_params(crc32_params, "56789".as_ref()); + assert_eq!( + checksum_combine_with_custom_params(crc32_params, checksum1, checksum2, 5), + CRC32_ISCSI.check, + ); + + // CRC-32 forward + let crc32_params = get_custom_crc32_forward(); + let checksum1 = checksum_with_params(crc32_params, "1234".as_ref()); + let checksum2 = checksum_with_params(crc32_params, "56789".as_ref()); + assert_eq!( + checksum_combine_with_custom_params(crc32_params, checksum1, checksum2, 5), + CRC32_BZIP2.check, + ); + + // CRC-64 reflected + let crc64_params = get_custom_crc64_reflected(); + let checksum1 = checksum_with_params(crc64_params, "1234".as_ref()); + let checksum2 = checksum_with_params(crc64_params, "56789".as_ref()); + assert_eq!( + checksum_combine_with_custom_params(crc64_params, checksum1, checksum2, 5), + CRC64_NVME.check, + ); + + // CRC-64 forward + let crc64_params = get_custom_crc64_forward(); + let checksum1 = checksum_with_params(crc64_params, "1234".as_ref()); + let checksum2 = checksum_with_params(crc64_params, "56789".as_ref()); + assert_eq!( + checksum_combine_with_custom_params(crc64_params, checksum1, checksum2, 5), + CRC64_ECMA_182.check, + ); + } + #[test] fn test_checksum_file() { // Create a test file with repeating zeros let test_file_path = "test/test_crc32_hash_file.bin"; let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros - if let Err(e) = std::fs::write(test_file_path, &data) { + if let Err(e) = write(test_file_path, &data) { eprintln!("Skipping test due to write error: {}", e); return; } @@ -647,6 +935,54 @@ mod lib { std::fs::remove_file(test_file_path).unwrap(); } + #[test] + fn test_checksum_file_with_custom_params() { + crate::cache::clear_cache(); + + // Create a test file with repeating zeros + let test_file_path = "test/test_crc32_hash_file_custom.bin"; + let data = vec![0u8; 1024 * 1024]; // 1 MiB of zeros + if let Err(e) = write(test_file_path, &data) { + eprintln!("Skipping test due to write error: {}", e); + return; + } + + // CRC-32 reflected + check_file( + get_custom_crc32_reflected(), + test_file_path, + CRC32_ISCSI.check, + ); + + // CRC-32 forward + check_file( + get_custom_crc32_forward(), + test_file_path, + CRC32_BZIP2.check, + ); + + // CRC-64 reflected + check_file( + get_custom_crc64_reflected(), + test_file_path, + CRC64_NVME.check, + ); + + // CRC-64 forward + check_file( + get_custom_crc64_forward(), + test_file_path, + CRC64_ECMA_182.check, + ); + + std::fs::remove_file(test_file_path).unwrap(); + } + + fn check_file(params: CrcParams, file_path: &str, check: u64) { + let result = checksum_file_with_params(params, file_path, None).unwrap(); + assert_eq!(result, check); + } + #[test] fn test_writer() { // Create a test file with repeating zeros @@ -800,4 +1136,168 @@ mod lib { Ok(()) } + + fn get_custom_crc32_reflected() -> CrcParams { + CrcParams::new( + "Custom CRC-32/ISCSI", + 32, + CRC32_ISCSI.poly, + CRC32_ISCSI.init, + CRC32_ISCSI.refin, + CRC32_ISCSI.xorout, + CRC32_ISCSI.check, + ) + } + + fn get_custom_crc32_forward() -> CrcParams { + CrcParams::new( + "Custom CRC-32/BZIP2", + 32, + CRC32_BZIP2.poly, + CRC32_BZIP2.init, + CRC32_BZIP2.refin, + CRC32_BZIP2.xorout, + CRC32_BZIP2.check, + ) + } + + fn get_custom_crc64_reflected() -> CrcParams { + CrcParams::new( + "Custom CRC-64/NVME", + 64, + CRC64_NVME.poly, + CRC64_NVME.init, + CRC64_NVME.refin, + CRC64_NVME.xorout, + CRC64_NVME.check, + ) + } + + fn get_custom_crc64_forward() -> CrcParams { + CrcParams::new( + "Custom CRC-64/ECMA-182", + 64, + CRC64_ECMA_182.poly, + CRC64_ECMA_182.init, + CRC64_ECMA_182.refin, + CRC64_ECMA_182.xorout, + CRC64_ECMA_182.check, + ) + } + + #[test] + 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, + ]; + let storage = CrcKeysStorage::from_keys_fold_256(test_keys); + + // Test valid key access + for i in 0..23 { + assert_eq!(storage.get_key(i), test_keys[i]); + } + + // Test out-of-bounds access returns 0 + assert_eq!(storage.get_key(23), 0); + assert_eq!(storage.get_key(24), 0); + assert_eq!(storage.get_key(100), 0); + + // Test key count + assert_eq!(storage.key_count(), 23); + } + + #[test] + 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, + 25, + ]; + let storage = CrcKeysStorage::from_keys_fold_future_test(test_keys); + + // Test valid key access + for i in 0..25 { + assert_eq!(storage.get_key(i), test_keys[i]); + } + + // Test out-of-bounds access returns 0 + assert_eq!(storage.get_key(25), 0); + assert_eq!(storage.get_key(26), 0); + assert_eq!(storage.get_key(100), 0); + + // Test key count + assert_eq!(storage.key_count(), 25); + } + + #[test] + fn test_crc_params_safe_accessors() { + // Create a test CrcParams with known keys + 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, + ]; + let params = CrcParams { + algorithm: CrcAlgorithm::Crc32IsoHdlc, + name: "test", + width: 32, + poly: 0x04C11DB7, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0xCBF43926, + keys: CrcKeysStorage::from_keys_fold_256(test_keys), + }; + + // Test valid key access + for i in 0..23 { + assert_eq!(params.get_key(i), test_keys[i]); + assert_eq!(params.get_key_checked(i), Some(test_keys[i])); + } + + // Test out-of-bounds access + assert_eq!(params.get_key(23), 0); + assert_eq!(params.get_key(24), 0); + assert_eq!(params.get_key(100), 0); + + assert_eq!(params.get_key_checked(23), None); + assert_eq!(params.get_key_checked(24), None); + assert_eq!(params.get_key_checked(100), None); + + // Test key count + assert_eq!(params.key_count(), 23); + } + + #[test] + fn test_crc_keys_storage_const_constructors() { + // Test that const constructors work in const context + const TEST_KEYS_23: [u64; 23] = [1; 23]; + const TEST_KEYS_25: [u64; 25] = [2; 25]; + + const STORAGE_256: CrcKeysStorage = CrcKeysStorage::from_keys_fold_256(TEST_KEYS_23); + const STORAGE_FUTURE: CrcKeysStorage = + CrcKeysStorage::from_keys_fold_future_test(TEST_KEYS_25); + + // Verify the const constructors work correctly + assert_eq!(STORAGE_256.get_key(0), 1); + assert_eq!(STORAGE_256.key_count(), 23); + + assert_eq!(STORAGE_FUTURE.get_key(0), 2); + assert_eq!(STORAGE_FUTURE.key_count(), 25); + } + + #[test] + fn test_crc_keys_storage_bounds_safety() { + let storage_256 = CrcKeysStorage::from_keys_fold_256([42; 23]); + let storage_future = CrcKeysStorage::from_keys_fold_future_test([84; 25]); + + // Test edge cases for bounds checking + assert_eq!(storage_256.get_key(22), 42); // Last valid index + assert_eq!(storage_256.get_key(23), 0); // First invalid index + + assert_eq!(storage_future.get_key(24), 84); // Last valid index + assert_eq!(storage_future.get_key(25), 0); // First invalid index + + // Test very large indices + assert_eq!(storage_256.get_key(usize::MAX), 0); + assert_eq!(storage_future.get_key(usize::MAX), 0); + } } diff --git a/src/structs.rs b/src/structs.rs index fc4c152..e5cdf07 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -2,27 +2,13 @@ #![allow(dead_code)] -use crate::arch; use crate::traits::{CrcCalculator, CrcWidth}; -use crate::CrcAlgorithm; - -#[derive(Clone, Copy, Debug)] -pub struct CrcParams { - pub algorithm: CrcAlgorithm, - pub name: &'static str, - pub width: u8, - pub poly: u64, - pub init: u64, - pub refin: bool, - pub refout: bool, - pub xorout: u64, - pub check: u64, - pub keys: [u64; 23], -} +use crate::{arch, cache, CrcAlgorithm, CrcParams}; /// CRC-32 width implementation #[derive(Clone, Copy)] pub struct Width32; + impl CrcWidth for Width32 { const WIDTH: u32 = 32; type Value = u32; @@ -52,3 +38,70 @@ impl CrcCalculator for Calculator { unsafe { arch::update(state, data, params) } } } + +impl CrcParams { + /// Creates custom CRC parameters for a given set of Rocksoft CRC parameters. + /// + /// Uses an internal cache to avoid regenerating folding keys for identical parameter sets. + /// The first call with a given set of parameters will generate and cache the keys, while + /// subsequent calls with the same parameters will use the cached keys for optimal performance. + /// + /// Does not support mis-matched refin/refout parameters, so both must be true or both false. + /// + /// Rocksoft parameters for lots of variants: https://reveng.sourceforge.io/crc-catalogue/all.htm + pub fn new( + name: &'static str, + width: u8, + poly: u64, + init: u64, + reflected: bool, + xorout: u64, + check: u64, + ) -> Self { + let keys_array = cache::get_or_generate_keys(width, poly, reflected); + let keys = crate::CrcKeysStorage::from_keys_fold_256(keys_array); + + let algorithm = match width { + 32 => CrcAlgorithm::Crc32Custom, + 64 => CrcAlgorithm::Crc64Custom, + _ => panic!("Unsupported width: {}", width), + }; + + Self { + algorithm, + name, + width, + poly, + init, + refin: reflected, + refout: reflected, + xorout, + check, + keys, + } + } + + /// Gets a key at the specified index, returning 0 if out of bounds. + /// This provides safe access regardless of internal key storage format. + #[inline(always)] + pub fn get_key(self, index: usize) -> u64 { + self.keys.get_key(index) + } + + /// Gets a key at the specified index, returning None if out of bounds. + /// This provides optional key access for cases where bounds checking is needed. + #[inline(always)] + pub fn get_key_checked(self, index: usize) -> Option { + if index < self.keys.key_count() { + Some(self.keys.get_key(index)) + } else { + None + } + } + + /// Returns the number of keys available in this CrcParams instance. + #[inline(always)] + pub fn key_count(self) -> usize { + self.keys.key_count() + } +} diff --git a/src/test/enums.rs b/src/test/enums.rs index d7f7e01..0341fea 100644 --- a/src/test/enums.rs +++ b/src/test/enums.rs @@ -3,9 +3,9 @@ #![cfg(test)] #![allow(dead_code)] -use crate::structs::CrcParams; use crate::test::structs::*; use crate::CrcAlgorithm; +use crate::CrcParams; use crc::Crc; pub enum AnyCrcTestConfig { @@ -54,7 +54,7 @@ impl AnyCrcTestConfig { } pub fn get_keys(&self) -> [u64; 23] { - self.get_params().keys + self.get_params().keys.to_keys_array_23() } pub fn checksum_with_reference(&self, data: &[u8]) -> u64 { diff --git a/src/test/future_proof.rs b/src/test/future_proof.rs new file mode 100644 index 0000000..81dcc10 --- /dev/null +++ b/src/test/future_proof.rs @@ -0,0 +1,1706 @@ +// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0. + +//! Tests for future-proof CrcKeysStorage and CrcParams functionality + +#![cfg(test)] + +use crate::{CrcAlgorithm, CrcKeysStorage, CrcParams}; + +#[test] +fn test_crc_keys_storage_bounds_checking() { + // Test KeysFold256 variant (23 keys) + let keys_23 = [1u64; 23]; + let storage_23 = CrcKeysStorage::from_keys_fold_256(keys_23); + + // Test valid indices + for i in 0..23 { + assert_eq!( + storage_23.get_key(i), + 1, + "Valid index {} should return key value", + i + ); + } + + // Test out-of-bounds indices return 0 + assert_eq!( + storage_23.get_key(23), + 0, + "Index 23 should return 0 for 23-key storage" + ); + assert_eq!( + storage_23.get_key(24), + 0, + "Index 24 should return 0 for 23-key storage" + ); + assert_eq!( + storage_23.get_key(100), + 0, + "Large index should return 0 for 23-key storage" + ); + + // Test KeysFutureTest variant (25 keys) + let keys_25 = [2u64; 25]; + let storage_25 = CrcKeysStorage::from_keys_fold_future_test(keys_25); + + // Test valid indices + for i in 0..25 { + assert_eq!( + storage_25.get_key(i), + 2, + "Valid index {} should return key value", + i + ); + } + + // Test out-of-bounds indices return 0 + assert_eq!( + storage_25.get_key(25), + 0, + "Index 25 should return 0 for 25-key storage" + ); + assert_eq!( + storage_25.get_key(26), + 0, + "Index 26 should return 0 for 25-key storage" + ); + assert_eq!( + storage_25.get_key(100), + 0, + "Large index should return 0 for 25-key storage" + ); +} + +#[test] +fn test_crc_params_get_key_checked() { + // Create test CrcParams with 23-key storage + let keys_23 = [42u64; 23]; + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Test CRC", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Test valid indices return Some(value) + for i in 0..23 { + assert_eq!( + params_23.get_key_checked(i), + Some(42), + "Valid index {} should return Some(42)", + i + ); + } + + // Test out-of-bounds indices return None + assert_eq!( + params_23.get_key_checked(23), + None, + "Index 23 should return None for 23-key params" + ); + assert_eq!( + params_23.get_key_checked(24), + None, + "Index 24 should return None for 23-key params" + ); + assert_eq!( + params_23.get_key_checked(100), + None, + "Large index should return None for 23-key params" + ); + + // Create test CrcParams with 25-key storage + let keys_25 = [84u64; 25]; + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Test CRC 64", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(keys_25), + }; + + // Test valid indices return Some(value) + for i in 0..25 { + assert_eq!( + params_25.get_key_checked(i), + Some(84), + "Valid index {} should return Some(84)", + i + ); + } + + // Test out-of-bounds indices return None + assert_eq!( + params_25.get_key_checked(25), + None, + "Index 25 should return None for 25-key params" + ); + assert_eq!( + params_25.get_key_checked(26), + None, + "Index 26 should return None for 25-key params" + ); + assert_eq!( + params_25.get_key_checked(100), + None, + "Large index should return None for 25-key params" + ); +} + +#[test] +fn test_key_count_returns_correct_values() { + // Test KeysFold256 variant + let keys_23 = [1u64; 23]; + let storage_23 = CrcKeysStorage::from_keys_fold_256(keys_23); + assert_eq!( + storage_23.key_count(), + 23, + "KeysFold256 should report 23 keys" + ); + + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Test CRC", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: storage_23, + }; + assert_eq!( + params_23.key_count(), + 23, + "CrcParams with KeysFold256 should report 23 keys" + ); + + // Test KeysFutureTest variant + let keys_25 = [2u64; 25]; + let storage_25 = CrcKeysStorage::from_keys_fold_future_test(keys_25); + assert_eq!( + storage_25.key_count(), + 25, + "KeysFutureTest should report 25 keys" + ); + + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Test CRC 64", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: storage_25, + }; + assert_eq!( + params_25.key_count(), + 25, + "CrcParams with KeysFutureTest should report 25 keys" + ); +} + +#[test] +fn test_crc_params_get_key_bounds_checking() { + // Create test CrcParams with 23-key storage + let keys_23 = [99u64; 23]; + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Test CRC", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Test valid indices + for i in 0..23 { + assert_eq!( + params_23.get_key(i), + 99, + "Valid index {} should return 99", + i + ); + } + + // Test out-of-bounds indices return 0 + assert_eq!( + params_23.get_key(23), + 0, + "Index 23 should return 0 for 23-key params" + ); + assert_eq!( + params_23.get_key(24), + 0, + "Index 24 should return 0 for 23-key params" + ); + assert_eq!( + params_23.get_key(100), + 0, + "Large index should return 0 for 23-key params" + ); +} +#[test] + +fn test_third_party_const_definitions_compatibility() { + // Mock third-party const definitions using the new format + // These simulate how third-party applications would define custom CrcParams + + // Mock third-party CRC-32 definition (similar to existing library constants) + const MOCK_THIRD_PARTY_CRC32: CrcParams = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Mock Third Party CRC-32", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0xE3069283, + keys: CrcKeysStorage::from_keys_fold_256([ + 0x1234567890ABCDEF, + 0x2345678901BCDEF0, + 0x3456789012CDEF01, + 0x456789023DEF012, + 0x56789034EF0123, + 0x6789045F01234, + 0x789056012345, + 0x89067123456, + 0x9078234567, + 0xA089345678, + 0xB09A456789, + 0xC0AB56789A, + 0xD0BC6789AB, + 0xE0CD789ABC, + 0xF0DE89ABCD, + 0x10EF9ABCDE, + 0x210ABCDEF0, + 0x321BCDEF01, + 0x432CDEF012, + 0x543DEF0123, + 0x654EF01234, + 0x765F012345, + 0x876012345, + ]), + }; + + // Mock third-party CRC-64 definition + const MOCK_THIRD_PARTY_CRC64: CrcParams = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Mock Third Party CRC-64", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x6C40DF5F0B497347, + keys: CrcKeysStorage::from_keys_fold_256([ + 0xFEDCBA0987654321, + 0xEDCBA09876543210, + 0xDCBA098765432101, + 0xCBA0987654321012, + 0xBA09876543210123, + 0xA098765432101234, + 0x9087654321012345, + 0x8076543210123456, + 0x7065432101234567, + 0x6054321012345678, + 0x5043210123456789, + 0x403210123456789A, + 0x3210123456789AB, + 0x210123456789ABC, + 0x10123456789ABCD, + 0x123456789ABCDE, + 0x23456789ABCDEF, + 0x3456789ABCDEF0, + 0x456789ABCDEF01, + 0x56789ABCDEF012, + 0x6789ABCDEF0123, + 0x789ABCDEF01234, + 0x89ABCDEF012345, + ]), + }; + + // Test that third-party const definitions work correctly + assert_eq!(MOCK_THIRD_PARTY_CRC32.key_count(), 23); + assert_eq!(MOCK_THIRD_PARTY_CRC64.key_count(), 23); + + // Test key access patterns that third-party code might use + assert_eq!(MOCK_THIRD_PARTY_CRC32.get_key(0), 0x1234567890ABCDEF); + assert_eq!(MOCK_THIRD_PARTY_CRC32.get_key(22), 0x876012345); + assert_eq!(MOCK_THIRD_PARTY_CRC32.get_key(23), 0); // Out of bounds + + assert_eq!(MOCK_THIRD_PARTY_CRC64.get_key(0), 0xFEDCBA0987654321); + assert_eq!(MOCK_THIRD_PARTY_CRC64.get_key(22), 0x89ABCDEF012345); + assert_eq!(MOCK_THIRD_PARTY_CRC64.get_key(23), 0); // Out of bounds + + // Test that checked access works as expected + assert_eq!( + MOCK_THIRD_PARTY_CRC32.get_key_checked(0), + Some(0x1234567890ABCDEF) + ); + assert_eq!( + MOCK_THIRD_PARTY_CRC32.get_key_checked(22), + Some(0x876012345) + ); + assert_eq!(MOCK_THIRD_PARTY_CRC32.get_key_checked(23), None); + + assert_eq!( + MOCK_THIRD_PARTY_CRC64.get_key_checked(0), + Some(0xFEDCBA0987654321) + ); + assert_eq!( + MOCK_THIRD_PARTY_CRC64.get_key_checked(22), + Some(0x89ABCDEF012345) + ); + assert_eq!(MOCK_THIRD_PARTY_CRC64.get_key_checked(23), None); +} + +#[test] +fn test_existing_key_access_patterns_continue_to_work() { + // Test that common key access patterns used by existing code continue to work + + let test_keys = [ + 0x1111111111111111, + 0x2222222222222222, + 0x3333333333333333, + 0x4444444444444444, + 0x5555555555555555, + 0x6666666666666666, + 0x7777777777777777, + 0x8888888888888888, + 0x9999999999999999, + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + 0xEEEEEEEEEEEEEEEE, + 0xFFFFFFFFFFFFFFFF, + 0x1010101010101010, + 0x2020202020202020, + 0x3030303030303030, + 0x4040404040404040, + 0x5050505050505050, + 0x6060606060606060, + 0x7070707070707070, + 0x8080808080808080, + ]; + + let params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Test Pattern Access", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(test_keys), + }; + + // Pattern 1: Sequential access (common in folding algorithms) + for i in 0..23 { + let expected = test_keys[i]; + assert_eq!( + params.get_key(i), + expected, + "Sequential access failed at index {}", + i + ); + } + + // Pattern 2: Reverse access (sometimes used in algorithms) + for i in (0..23).rev() { + let expected = test_keys[i]; + assert_eq!( + params.get_key(i), + expected, + "Reverse access failed at index {}", + i + ); + } + + // Pattern 3: Specific indices commonly used in folding (powers of 2, etc.) + let common_indices = [0, 1, 2, 4, 8, 16, 22]; + for &i in &common_indices { + if i < 23 { + let expected = test_keys[i]; + assert_eq!( + params.get_key(i), + expected, + "Common index access failed at index {}", + i + ); + } + } + + // Pattern 4: Bounds checking that third-party code might rely on + assert_eq!( + params.get_key(23), + 0, + "Out-of-bounds access should return 0" + ); + assert_eq!( + params.get_key(100), + 0, + "Large out-of-bounds access should return 0" + ); +} + +#[test] +fn test_backwards_compatibility_throughout_migration_phases() { + // This test simulates the migration phases to ensure backwards compatibility + + // Phase 1 & 2: Original array-based access patterns (simulated) + let test_keys = [0x123456789ABCDEF0u64; 23]; + let storage = CrcKeysStorage::from_keys_fold_256(test_keys); + + // Verify that the storage behaves identically to direct array access + for i in 0..23 { + assert_eq!( + storage.get_key(i), + test_keys[i], + "Storage access should match array access at index {}", + i + ); + } + + // Phase 3: New CrcKeysStorage-based access + let params = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Migration Test", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0x0000000000000000, + refin: false, + refout: false, + xorout: 0x0000000000000000, + check: 0x6C40DF5F0B497347, + keys: storage, + }; + + // Verify that CrcParams provides the same access patterns + for i in 0..23 { + assert_eq!( + params.get_key(i), + test_keys[i], + "CrcParams access should match array access at index {}", + i + ); + assert_eq!( + params.get_key_checked(i), + Some(test_keys[i]), + "CrcParams checked access should match array access at index {}", + i + ); + } + + // Verify bounds checking works consistently + assert_eq!(params.get_key(23), 0, "Out-of-bounds should return 0"); + assert_eq!( + params.get_key_checked(23), + None, + "Out-of-bounds checked should return None" + ); + + // Verify key count is correct + assert_eq!(params.key_count(), 23, "Key count should be 23"); + + // Test compatibility with existing comparison operations + assert_eq!( + storage.to_keys_array_23(), + test_keys, + "Storage should convert back to original array" + ); + assert_eq!( + storage, test_keys, + "Storage should compare equal to original array" + ); + assert_eq!( + test_keys, storage, + "Original array should compare equal to storage" + ); +} + +#[test] +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 + // in a unit test, we can verify that the compiler optimizations work correctly + // by testing that the behavior is identical and that large-scale access works efficiently. + + // Create test keys with different values to avoid XOR cancellation + let test_keys = [ + 0x1111111111111111, + 0x2222222222222222, + 0x3333333333333333, + 0x4444444444444444, + 0x5555555555555555, + 0x6666666666666666, + 0x7777777777777777, + 0x8888888888888888, + 0x9999999999999999, + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + 0xEEEEEEEEEEEEEEEE, + 0xFFFFFFFFFFFFFFFF, + 0x1010101010101010, + 0x2020202020202020, + 0x3030303030303030, + 0x4040404040404040, + 0x5050505050505050, + 0x6060606060606060, + 0x7070707070707070, + 0x8080808080808080, + ]; + let storage = CrcKeysStorage::from_keys_fold_256(test_keys); + + let params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Performance Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: storage, + }; + + // Simulate intensive key access patterns that would reveal performance issues + let iterations = 1000; + let mut checksum = 0u64; + + // Pattern 1: Sequential access (most common in real algorithms) + for iteration in 0..iterations { + for i in 0..23 { + checksum = checksum.wrapping_add(params.get_key(i).wrapping_mul(iteration as u64 + 1)); + } + } + + // Pattern 2: Random access pattern + let access_pattern = [ + 0, 5, 12, 3, 18, 7, 22, 1, 15, 9, 20, 4, 11, 16, 2, 19, 8, 14, 6, 21, 10, 17, 13, + ]; + for iteration in 0..iterations { + for &i in &access_pattern { + checksum = checksum.wrapping_add(params.get_key(i).wrapping_mul(iteration as u64 + 2)); + } + } + + // Verify that we actually accessed the keys (checksum should be non-zero) + assert_ne!(checksum, 0, "Performance test should have accessed keys"); + + // Test that bounds checking doesn't significantly impact performance + let mut bounds_checksum = 0u64; + for iteration in 0..iterations { + for i in 0..30 { + // Include some out-of-bounds accesses + bounds_checksum = + bounds_checksum.wrapping_add(params.get_key(i).wrapping_mul(iteration as u64 + 3)); + } + } + + // The bounds-checked version should still work correctly + assert_ne!( + bounds_checksum, 0, + "Bounds checking performance test should work" + ); +} + +#[test] +fn test_crc_calculation_performance_before_and_after_changes() { + // Test that CRC calculation performance remains identical with the new key storage + use crate::{checksum, CrcAlgorithm}; + + // Test data of various sizes to ensure performance across different scenarios + let test_data_small = b"123456789"; + let test_data_medium = vec![0xAAu8; 1024]; // 1KB + let test_data_large = vec![0x55u8; 65536]; // 64KB + + // Test multiple CRC algorithms to ensure consistent performance + let algorithms = [ + CrcAlgorithm::Crc32IsoHdlc, + CrcAlgorithm::Crc32Iscsi, + CrcAlgorithm::Crc64Nvme, + CrcAlgorithm::Crc64Ecma182, + ]; + + for algorithm in algorithms { + // Small data performance + let result_small = checksum(algorithm, test_data_small); + assert_ne!( + result_small, 0, + "Small data CRC should produce non-zero result" + ); + + // Medium data performance + let result_medium = checksum(algorithm, &test_data_medium); + assert_ne!( + result_medium, 0, + "Medium data CRC should produce non-zero result" + ); + + // Large data performance (this would reveal significant performance regressions) + let result_large = checksum(algorithm, &test_data_large); + assert_ne!( + result_large, 0, + "Large data CRC should produce non-zero result" + ); + + // Verify consistency across multiple runs (performance should be deterministic) + let result_small_2 = checksum(algorithm, test_data_small); + assert_eq!( + result_small, result_small_2, + "CRC results should be consistent" + ); + } +} + +#[test] +fn test_memory_usage_impact_of_enum_based_storage() { + // Test that enum-based storage doesn't significantly increase memory usage + use std::mem; + + // Test memory size of different storage variants + let keys_23 = [0u64; 23]; + let keys_25 = [0u64; 25]; + + let storage_23 = CrcKeysStorage::from_keys_fold_256(keys_23); + let storage_25 = CrcKeysStorage::from_keys_fold_future_test(keys_25); + + // Verify that enum storage size is reasonable + let storage_23_size = mem::size_of_val(&storage_23); + let storage_25_size = mem::size_of_val(&storage_25); + let _array_23_size = mem::size_of_val(&keys_23); + let array_25_size = mem::size_of_val(&keys_25); + + // Rust enums use the size of the largest variant plus discriminant/alignment + // Both variants will be the same size (size of largest variant) + assert_eq!( + storage_23_size, storage_25_size, + "Both enum variants should have the same size" + ); + + // The enum size should be reasonable (largest variant + small overhead) + assert!( + storage_23_size >= array_25_size, + "Enum should be at least as large as the largest variant" + ); + assert!( + storage_23_size <= array_25_size + 16, + "Enum should not add excessive overhead beyond largest variant" + ); + + // Test CrcParams memory usage + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Memory Test 23", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: storage_23, + }; + + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Memory Test 25", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: storage_25, + }; + + let params_23_size = mem::size_of_val(¶ms_23); + let params_25_size = mem::size_of_val(¶ms_25); + + // CrcParams should have reasonable size differences based on key storage + assert!( + params_25_size >= params_23_size, + "25-key params should be at least as large as 23-key params" + ); + assert!( + params_25_size - params_23_size <= 16, + "Size difference should be reasonable (just the extra keys)" + ); + + // Verify that the structs are still reasonably sized + assert!( + params_23_size < 512, + "CrcParams should not be excessively large" + ); + assert!( + params_25_size < 512, + "CrcParams should not be excessively large" + ); +} + +#[test] +fn test_compiler_optimizations_eliminate_enum_dispatch() { + // This test verifies that compiler optimizations work correctly by testing + // that enum dispatch doesn't introduce runtime branching in hot paths + + // Create different keys to avoid XOR cancellation + let keys_23 = [ + 0x1111111111111111, + 0x2222222222222222, + 0x3333333333333333, + 0x4444444444444444, + 0x5555555555555555, + 0x6666666666666666, + 0x7777777777777777, + 0x8888888888888888, + 0x9999999999999999, + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + 0xEEEEEEEEEEEEEEEE, + 0xFFFFFFFFFFFFFFFF, + 0x1010101010101010, + 0x2020202020202020, + 0x3030303030303030, + 0x4040404040404040, + 0x5050505050505050, + 0x6060606060606060, + 0x7070707070707070, + 0x8080808080808080, + ]; + let keys_25 = [ + 0x1111111111111111, + 0x2222222222222222, + 0x3333333333333333, + 0x4444444444444444, + 0x5555555555555555, + 0x6666666666666666, + 0x7777777777777777, + 0x8888888888888888, + 0x9999999999999999, + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + 0xEEEEEEEEEEEEEEEE, + 0xFFFFFFFFFFFFFFFF, + 0x1010101010101010, + 0x2020202020202020, + 0x3030303030303030, + 0x4040404040404040, + 0x5050505050505050, + 0x6060606060606060, + 0x7070707070707070, + 0x8080808080808080, + 0x9090909090909090, + 0xA0A0A0A0A0A0A0A0, + ]; + + let storage_23 = CrcKeysStorage::from_keys_fold_256(keys_23); + let storage_25 = CrcKeysStorage::from_keys_fold_future_test(keys_25); + + // Test that repeated access to the same storage type is efficient + // (compiler should optimize away the enum matching) + let mut sum_23 = 0u64; + for iteration in 0..100 { + for i in 0..23 { + sum_23 = sum_23.wrapping_add(storage_23.get_key(i).wrapping_mul(iteration + 1)); + } + } + + let mut sum_25 = 0u64; + for iteration in 0..100 { + for i in 0..25 { + sum_25 = sum_25.wrapping_add(storage_25.get_key(i).wrapping_mul(iteration + 1)); + } + } + + // Verify that the operations actually happened + assert_ne!( + sum_23, 0, + "23-key operations should produce non-zero result" + ); + assert_ne!( + sum_25, 0, + "25-key operations should produce non-zero result" + ); + + // Test mixed access patterns (this would reveal optimization issues) + let storages = [storage_23, storage_25]; + let mut mixed_sum = 0u64; + + for iteration in 0..100 { + for (storage_idx, storage) in storages.iter().enumerate() { + let key_count = storage.key_count(); + for i in 0..key_count { + mixed_sum = mixed_sum.wrapping_add( + storage + .get_key(i) + .wrapping_mul((iteration + storage_idx + 1) as u64), + ); + } + } + } + + assert_ne!(mixed_sum, 0, "Mixed access should produce non-zero result"); +} +#[test] +fn test_create_crc_params_using_keys_future_test_variant() { + // Create test CrcParams using KeysFutureTest variant with 25 keys + let test_keys_25 = [ + 0x1111111111111111, + 0x2222222222222222, + 0x3333333333333333, + 0x4444444444444444, + 0x5555555555555555, + 0x6666666666666666, + 0x7777777777777777, + 0x8888888888888888, + 0x9999999999999999, + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + 0xEEEEEEEEEEEEEEEE, + 0xFFFFFFFFFFFFFFFF, + 0x1010101010101010, + 0x2020202020202020, + 0x3030303030303030, + 0x4040404040404040, + 0x5050505050505050, + 0x6060606060606060, + 0x7070707070707070, + 0x8080808080808080, + 0x9090909090909090, + 0xA0A0A0A0A0A0A0A0, + ]; + + let future_params = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Future Test CRC-64", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(test_keys_25), + }; + + // Verify that the future params work correctly + assert_eq!( + future_params.key_count(), + 25, + "Future params should have 25 keys" + ); + + // Test access to all 25 keys + for i in 0..25 { + assert_eq!( + future_params.get_key(i), + test_keys_25[i], + "Key {} should match expected value", + i + ); + assert_eq!( + future_params.get_key_checked(i), + Some(test_keys_25[i]), + "Checked key {} should return Some(value)", + i + ); + } + + // Test out-of-bounds access + assert_eq!( + future_params.get_key(25), + 0, + "Key 25 should return 0 (out of bounds)" + ); + assert_eq!( + future_params.get_key_checked(25), + None, + "Checked key 25 should return None" + ); + assert_eq!( + future_params.get_key(30), + 0, + "Key 30 should return 0 (out of bounds)" + ); + assert_eq!( + future_params.get_key_checked(30), + None, + "Checked key 30 should return None" + ); +} + +#[test] +fn test_code_gracefully_handles_different_key_array_sizes() { + // Test that the same code can handle both 23-key and 25-key variants gracefully + + let keys_23 = [0x1234567890ABCDEFu64; 23]; + let keys_25 = [0xFEDCBA0987654321u64; 25]; + + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "23-Key Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "25-Key Test", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(keys_25), + }; + + // Generic function that works with any CrcParams regardless of key count + fn process_crc_params(params: CrcParams) -> (usize, u64, u64) { + let key_count = params.key_count(); + let first_key = params.get_key(0); + let last_valid_key = if key_count > 0 { + params.get_key(key_count - 1) + } else { + 0 + }; + (key_count, first_key, last_valid_key) + } + + // Test that the same function works with both variants + let (count_23, first_23, last_23) = process_crc_params(params_23); + let (count_25, first_25, last_25) = process_crc_params(params_25); + + assert_eq!(count_23, 23, "23-key params should report 23 keys"); + assert_eq!(count_25, 25, "25-key params should report 25 keys"); + + assert_eq!( + first_23, 0x1234567890ABCDEF, + "23-key first key should match" + ); + assert_eq!( + first_25, 0xFEDCBA0987654321, + "25-key first key should match" + ); + + assert_eq!(last_23, 0x1234567890ABCDEF, "23-key last key should match"); + assert_eq!(last_25, 0xFEDCBA0987654321, "25-key last key should match"); + + // Test that bounds checking works consistently across variants + assert_eq!( + params_23.get_key(23), + 0, + "23-key params should return 0 for index 23" + ); + assert_eq!( + params_23.get_key(25), + 0, + "23-key params should return 0 for index 25" + ); + + assert_eq!( + params_25.get_key(25), + 0, + "25-key params should return 0 for index 25" + ); + assert_eq!( + params_25.get_key(30), + 0, + "25-key params should return 0 for index 30" + ); +} + +#[test] +fn test_expansion_to_larger_key_arrays_works_as_designed() { + // Test that the design supports expansion to larger key arrays + + // Simulate a migration scenario where we add more keys + let original_keys = [0x1111111111111111u64; 23]; + let expanded_keys = [ + // Original 23 keys + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + 0x1111111111111111, + // Additional 2 keys for future expansion + 0x2222222222222222, + 0x3333333333333333, + ]; + + let original_params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Original CRC", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(original_keys), + }; + + let expanded_params = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Expanded CRC", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(expanded_keys), + }; + + // Test that existing key access patterns continue to work + for i in 0..23 { + assert_eq!( + original_params.get_key(i), + 0x1111111111111111, + "Original key {} should match", + i + ); + assert_eq!( + expanded_params.get_key(i), + 0x1111111111111111, + "Expanded key {} should match original", + i + ); + } + + // Test that new keys are accessible in expanded version + assert_eq!( + expanded_params.get_key(23), + 0x2222222222222222, + "New key 23 should be accessible" + ); + assert_eq!( + expanded_params.get_key(24), + 0x3333333333333333, + "New key 24 should be accessible" + ); + + // Test that original version handles new indices gracefully + assert_eq!( + original_params.get_key(23), + 0, + "Original should return 0 for new key indices" + ); + assert_eq!( + original_params.get_key(24), + 0, + "Original should return 0 for new key indices" + ); + + // Test that both versions handle out-of-bounds access consistently + assert_eq!( + original_params.get_key(30), + 0, + "Original should return 0 for out-of-bounds" + ); + assert_eq!( + expanded_params.get_key(30), + 0, + "Expanded should return 0 for out-of-bounds" + ); + + // Test key count differences + assert_eq!( + original_params.key_count(), + 23, + "Original should have 23 keys" + ); + assert_eq!( + expanded_params.key_count(), + 25, + "Expanded should have 25 keys" + ); + + // Test that checked access works correctly for both + assert_eq!( + original_params.get_key_checked(22), + Some(0x1111111111111111), + "Original last key should be accessible" + ); + assert_eq!( + original_params.get_key_checked(23), + None, + "Original should return None for index 23" + ); + + assert_eq!( + expanded_params.get_key_checked(22), + Some(0x1111111111111111), + "Expanded key 22 should be accessible" + ); + assert_eq!( + expanded_params.get_key_checked(24), + Some(0x3333333333333333), + "Expanded last key should be accessible" + ); + assert_eq!( + expanded_params.get_key_checked(25), + None, + "Expanded should return None for index 25" + ); +} + +#[test] +fn test_future_expansion_backwards_compatibility() { + // Test that future expansion maintains backwards compatibility + + // This test simulates a scenario where: + // 1. Third-party code is written against 23-key CrcParams + // 2. Library is expanded to support 25-key CrcParams + // 3. Third-party code continues to work without modification + + // Mock third-party function that expects to work with any CrcParams + fn third_party_key_processor(params: CrcParams) -> Vec { + let mut result = Vec::new(); + + // Third-party code might access keys in various patterns + // Pattern 1: Access first few keys + for i in 0..5 { + result.push(params.get_key(i)); + } + + // Pattern 2: Access some middle keys + for i in 10..15 { + result.push(params.get_key(i)); + } + + // Pattern 3: Access keys near the end (but within original 23-key range) + for i in 20..23 { + result.push(params.get_key(i)); + } + + // Pattern 4: Attempt to access beyond both ranges (should return 0 for both) + result.push(params.get_key(30)); + result.push(params.get_key(31)); + + result + } + + // Test with original 23-key params + let keys_23 = [0xABCDEF0123456789u64; 23]; + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Backwards Compat Test 23", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Test with expanded 25-key params + let keys_25 = [0xABCDEF0123456789u64; 25]; + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "Backwards Compat Test 25", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(keys_25), + }; + + // Run third-party function with both variants + let result_23 = third_party_key_processor(params_23); + let result_25 = third_party_key_processor(params_25); + + // Results should be identical for the overlapping key ranges + assert_eq!( + result_23.len(), + result_25.len(), + "Results should have same length" + ); + + // First 13 values should be identical (keys 0-4, 10-14, 20-22) + for i in 0..13 { + assert_eq!( + result_23[i], result_25[i], + "Result {} should be identical", + i + ); + assert_eq!( + result_23[i], 0xABCDEF0123456789, + "Result {} should match key value", + i + ); + } + + // Last 2 values should be 0 for both (out-of-bounds access to indices 30, 31) + assert_eq!( + result_23[13], 0, + "Out-of-bounds access should return 0 for 23-key" + ); + assert_eq!( + result_23[14], 0, + "Out-of-bounds access should return 0 for 23-key" + ); + assert_eq!( + result_25[13], 0, + "Out-of-bounds access should return 0 for 25-key" + ); + assert_eq!( + result_25[14], 0, + "Out-of-bounds access should return 0 for 25-key" + ); + + // This demonstrates that third-party code works identically with both variants + assert_eq!( + result_23, result_25, + "Third-party function should produce identical results" + ); +} +// FFI Tests for future-proof CrcFastParams functionality + +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))] +mod ffi_tests { + use crate::ffi::CrcFastParams; + use crate::{CrcAlgorithm, CrcKeysStorage, CrcParams}; + + #[test] + fn test_ffi_conversion_23_keys() { + // Test conversion between CrcParams and CrcFastParams for 23-key variant + let keys_23 = [0x1234567890ABCDEFu64; 23]; + let original_params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "FFI Test 23", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Convert to FFI struct + let ffi_params: CrcFastParams = original_params.into(); + + // Verify FFI struct fields + assert_eq!(ffi_params.key_count, 23, "FFI params should have 23 keys"); + assert!( + !ffi_params.keys.is_null(), + "Keys pointer should not be null" + ); + assert_eq!(ffi_params.width, 32, "Width should match"); + assert_eq!(ffi_params.poly, 0x1EDC6F41, "Poly should match"); + assert_eq!(ffi_params.init, 0xFFFFFFFF, "Init should match"); + assert!(ffi_params.refin, "Refin should match"); + assert!(ffi_params.refout, "Refout should match"); + assert_eq!(ffi_params.xorout, 0xFFFFFFFF, "Xorout should match"); + assert_eq!(ffi_params.check, 0x12345678, "Check should match"); + + // Test direct pointer access to keys + unsafe { + for i in 0..23 { + let key_value = *ffi_params.keys.add(i); + assert_eq!( + key_value, 0x1234567890ABCDEF, + "Key {} should match expected value", + i + ); + } + } + + // Convert back to CrcParams + let converted_params: CrcParams = ffi_params.into(); + + // Verify round-trip conversion + assert_eq!(converted_params.algorithm, original_params.algorithm); + assert_eq!(converted_params.width, original_params.width); + assert_eq!(converted_params.poly, original_params.poly); + assert_eq!(converted_params.init, original_params.init); + assert_eq!(converted_params.refin, original_params.refin); + assert_eq!(converted_params.refout, original_params.refout); + assert_eq!(converted_params.xorout, original_params.xorout); + assert_eq!(converted_params.check, original_params.check); + assert_eq!(converted_params.key_count(), 23); + + // Verify all keys match + for i in 0..23 { + assert_eq!( + converted_params.get_key(i), + original_params.get_key(i), + "Converted key {} should match original", + i + ); + } + } + + #[test] + fn test_ffi_conversion_25_keys() { + // Test conversion between CrcParams and CrcFastParams for 25-key variant + let keys_25 = [0xFEDCBA0987654321u64; 25]; + let original_params = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "FFI Test 25", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(keys_25), + }; + + // Convert to FFI struct + let ffi_params: CrcFastParams = original_params.into(); + + // Verify FFI struct fields + assert_eq!(ffi_params.key_count, 25, "FFI params should have 25 keys"); + assert!( + !ffi_params.keys.is_null(), + "Keys pointer should not be null" + ); + assert_eq!(ffi_params.width, 64, "Width should match"); + assert_eq!(ffi_params.poly, 0x42F0E1EBA9EA3693, "Poly should match"); + + // Test direct pointer access to keys + unsafe { + for i in 0..25 { + let key_value = *ffi_params.keys.add(i); + assert_eq!( + key_value, 0xFEDCBA0987654321, + "Key {} should match expected value", + i + ); + } + } + + // Convert back to CrcParams + let converted_params: CrcParams = ffi_params.into(); + + // Verify round-trip conversion + assert_eq!(converted_params.key_count(), 25); + for i in 0..25 { + assert_eq!( + converted_params.get_key(i), + original_params.get_key(i), + "Converted key {} should match original", + i + ); + } + } + + #[test] + fn test_ffi_pointer_stability() { + // Test that key pointers remain stable across multiple conversions + let keys_23 = [0x1111111111111111u64; 23]; + let params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Stability Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Convert to FFI multiple times + let ffi_params1: CrcFastParams = params.into(); + let ffi_params2: CrcFastParams = params.into(); + + // Pointers should be stable (same keys should get same pointer) + assert_eq!( + ffi_params1.keys, ffi_params2.keys, + "Identical key sets should get same stable pointer" + ); + assert_eq!( + ffi_params1.key_count, ffi_params2.key_count, + "Key counts should match" + ); + + // Test that different key sets get different pointers + let different_keys = [0x2222222222222222u64; 23]; + let different_params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Different Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(different_keys), + }; + + let ffi_params3: CrcFastParams = different_params.into(); + assert_ne!( + ffi_params1.keys, ffi_params3.keys, + "Different key sets should get different pointers" + ); + } + + #[test] + fn test_ffi_memory_safety() { + // Test that FFI conversions are memory safe + let keys_23 = [0xAAAAAAAAAAAAAAAAu64; 23]; + let params = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "Memory Safety Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + let ffi_params: CrcFastParams = params.into(); + + // Test that we can safely access all keys through the pointer + unsafe { + let keys_slice = + std::slice::from_raw_parts(ffi_params.keys, ffi_params.key_count as usize); + + // Verify all keys are accessible and correct + for (i, &key) in keys_slice.iter().enumerate() { + assert_eq!( + key, 0xAAAAAAAAAAAAAAAA, + "Key {} should be accessible and correct", + i + ); + } + + // Test that we can create multiple slices from the same pointer + let keys_slice2 = + std::slice::from_raw_parts(ffi_params.keys, ffi_params.key_count as usize); + assert_eq!( + keys_slice, keys_slice2, + "Multiple slices should be identical" + ); + } + + // Test conversion back to CrcParams + let converted: CrcParams = ffi_params.into(); + assert_eq!(converted.key_count(), 23); + + for i in 0..23 { + assert_eq!( + converted.get_key(i), + 0xAAAAAAAAAAAAAAAA, + "Converted key {} should match", + i + ); + } + } + + #[test] + fn test_ffi_different_key_counts() { + // Test FFI with different key count scenarios + + // Test 23-key variant + let keys_23 = [0x1111111111111111u64; 23]; + let params_23 = CrcParams { + algorithm: CrcAlgorithm::Crc32Custom, + name: "23-Key FFI Test", + width: 32, + poly: 0x1EDC6F41, + init: 0xFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFF, + check: 0x12345678, + keys: CrcKeysStorage::from_keys_fold_256(keys_23), + }; + + // Test 25-key variant + let keys_25 = [0x2222222222222222u64; 25]; + let params_25 = CrcParams { + algorithm: CrcAlgorithm::Crc64Custom, + name: "25-Key FFI Test", + width: 64, + poly: 0x42F0E1EBA9EA3693, + init: 0xFFFFFFFFFFFFFFFF, + refin: true, + refout: true, + xorout: 0xFFFFFFFFFFFFFFFF, + check: 0x123456789ABCDEF0, + keys: CrcKeysStorage::from_keys_fold_future_test(keys_25), + }; + + // Convert both to FFI + let ffi_23: CrcFastParams = params_23.into(); + let ffi_25: CrcFastParams = params_25.into(); + + // Verify key counts + assert_eq!(ffi_23.key_count, 23); + assert_eq!(ffi_25.key_count, 25); + + // Test C-style access patterns + unsafe { + // Access all keys in 23-key variant + for i in 0..23 { + let key = *ffi_23.keys.add(i); + assert_eq!( + key, 0x1111111111111111, + "23-key variant key {} should match", + i + ); + } + + // Access all keys in 25-key variant + for i in 0..25 { + let key = *ffi_25.keys.add(i); + assert_eq!( + key, 0x2222222222222222, + "25-key variant key {} should match", + i + ); + } + + // Test bounds-aware C code pattern + for ffi_params in [&ffi_23, &ffi_25] { + for i in 0..ffi_params.key_count { + let key = *ffi_params.keys.add(i as usize); + assert_ne!(key, 0, "Key {} should not be zero", i); + } + } + } + + // Test round-trip conversions + let converted_23: CrcParams = ffi_23.into(); + let converted_25: CrcParams = ffi_25.into(); + + assert_eq!(converted_23.key_count(), 23); + assert_eq!(converted_25.key_count(), 25); + + // Verify all keys survived round-trip + for i in 0..23 { + assert_eq!(converted_23.get_key(i), 0x1111111111111111); + } + for i in 0..25 { + assert_eq!(converted_25.get_key(i), 0x2222222222222222); + } + } + + #[test] + fn test_ffi_get_custom_params_function() { + // Test the crc_fast_get_custom_params FFI function + use std::ffi::CString; + + let name = CString::new("Test CRC").unwrap(); + let ffi_params = crate::ffi::crc_fast_get_custom_params( + name.as_ptr(), + 32, + 0x1EDC6F41, + 0xFFFFFFFF, + true, + 0xFFFFFFFF, + 0x12345678, + ); + + // Verify the returned FFI params + assert_eq!(ffi_params.width, 32); + assert_eq!(ffi_params.poly, 0x1EDC6F41); + assert_eq!(ffi_params.init, 0xFFFFFFFF); + assert!(ffi_params.refin); + assert!(ffi_params.refout); + assert_eq!(ffi_params.xorout, 0xFFFFFFFF); + assert_eq!(ffi_params.check, 0x12345678); + assert!( + !ffi_params.keys.is_null(), + "Keys pointer should not be null" + ); + assert!(ffi_params.key_count > 0, "Should have keys"); + + // Test that we can access the keys + unsafe { + for i in 0..ffi_params.key_count { + let _key = *ffi_params.keys.add(i as usize); + // Keys should be accessible without crashing + } + } + + // Test conversion to CrcParams + let params: CrcParams = ffi_params.into(); + assert_eq!(params.width, 32); + assert_eq!(params.poly, 0x1EDC6F41); + assert_eq!(params.init, 0xFFFFFFFF); + assert!(params.refin); + assert!(params.refout); + assert_eq!(params.xorout, 0xFFFFFFFF); + assert_eq!(params.check, 0x12345678); + assert!(params.key_count() > 0); + } +} diff --git a/src/test/mod.rs b/src/test/mod.rs index 1b74ec4..2b7b216 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod consts; pub(crate) mod enums; +mod future_proof; mod structs; /// Creates a new aligned data vector from the input slice for testing. diff --git a/src/test/structs.rs b/src/test/structs.rs index d11a04a..4e11b68 100644 --- a/src/test/structs.rs +++ b/src/test/structs.rs @@ -3,7 +3,7 @@ #![cfg(test)] #![allow(dead_code)] -use crate::structs::CrcParams; +use crate::CrcParams; use crc::{Crc, Table}; pub struct CrcTestConfig { diff --git a/src/traits.rs b/src/traits.rs index 4511d43..7eec0b5 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,7 +5,7 @@ #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] use crate::enums::Reflector; -use crate::structs::CrcParams; +use crate::CrcParams; #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] use crate::structs::CrcState;