| 1 |
# Design Document |
| 2 |
|
| 3 |
## Overview |
| 4 |
|
| 5 |
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. |
| 6 |
|
| 7 |
## Architecture |
| 8 |
|
| 9 |
### Core Components |
| 10 |
|
| 11 |
1. **CrcKeysStorage Enum**: Internal storage that can hold different key array sizes |
| 12 |
2. **CrcParams Structure**: Updated to use CrcKeysStorage internally while maintaining public API |
| 13 |
3. **Safe Accessor Methods**: Bounds-checked key access methods on CrcParams |
| 14 |
4. **Helper Functions**: Const-friendly constructors for CrcKeysStorage variants |
| 15 |
|
| 16 |
### Design Principles |
| 17 |
|
| 18 |
- **Zero Runtime Overhead**: Enum dispatch optimized away by compiler |
| 19 |
- **Backwards Compatibility**: Existing const definitions require minimal changes |
| 20 |
- **Gradual Migration**: Can be implemented in phases without breaking builds |
| 21 |
- **Safety First**: Bounds checking prevents panics from out-of-range access |
| 22 |
|
| 23 |
## Components and Interfaces |
| 24 |
|
| 25 |
### CrcKeysStorage Enum |
| 26 |
|
| 27 |
```rust |
| 28 |
#[derive(Clone, Copy, Debug)] |
| 29 |
enum CrcKeysStorage { |
| 30 |
/// Current 23-key format (for existing algorithms which includes 256 byte folding distances) |
| 31 |
KeysFold256([u64; 23]), |
| 32 |
/// Future 25-key format (for potential future expanded folding distances, for testing purposes only) |
| 33 |
KeysFutureTest([u64; 25]), |
| 34 |
// Additional variants can be added as needed |
| 35 |
} |
| 36 |
``` |
| 37 |
|
| 38 |
**Key Methods:** |
| 39 |
- `get_key(index: usize) -> u64`: Safe key access with bounds checking |
| 40 |
- `key_count() -> usize`: Returns actual number of keys available |
| 41 |
- `from_keys_fold_256(keys: [u64; 23]) -> Self`: Const constructor for 23-key arrays |
| 42 |
- `from_keys_fold_future_test(keys: [u64; 25]) -> Self`: Const constructor for 25-key arrays |
| 43 |
|
| 44 |
### Updated CrcParams Structure |
| 45 |
|
| 46 |
```rust |
| 47 |
#[derive(Clone, Copy, Debug)] |
| 48 |
pub struct CrcParams { |
| 49 |
pub algorithm: CrcAlgorithm, |
| 50 |
pub name: &'static str, |
| 51 |
pub width: u8, |
| 52 |
pub poly: u64, |
| 53 |
pub init: u64, |
| 54 |
pub refin: bool, |
| 55 |
pub refout: bool, |
| 56 |
pub xorout: u64, |
| 57 |
pub check: u64, |
| 58 |
pub keys: CrcKeysStorage, // Changed from [u64; 23] |
| 59 |
} |
| 60 |
``` |
| 61 |
|
| 62 |
**Key Methods:** |
| 63 |
- `get_key(index: usize) -> u64`: Delegates to CrcKeysStorage |
| 64 |
- `get_key_checked(index: usize) -> Option<u64>`: Optional key access |
| 65 |
- `key_count() -> usize`: Returns actual key count |
| 66 |
|
| 67 |
### Const Definition Pattern |
| 68 |
|
| 69 |
```rust |
| 70 |
// Before (Phase 2): |
| 71 |
pub const CRC32_ISCSI: CrcParams = CrcParams { |
| 72 |
// ... other fields unchanged ... |
| 73 |
keys: KEYS_1EDC6F41_REFLECTED, // [u64; 23] |
| 74 |
}; |
| 75 |
|
| 76 |
// After (Phase 3): |
| 77 |
pub const CRC32_ISCSI: CrcParams = CrcParams { |
| 78 |
// ... other fields unchanged ... |
| 79 |
keys: CrcKeysStorage::from_keys_fold_256(KEYS_1EDC6F41_REFLECTED), |
| 80 |
}; |
| 81 |
``` |
| 82 |
|
| 83 |
## Data Models |
| 84 |
|
| 85 |
### Key Storage Variants |
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
| KeysFold256 | [u64; 23] | Current implementation (128/256-byte folding) | |
| 90 |
| KeysFutureTest | [u64; 25] | Future expansion | |
| 91 |
|
| 92 |
### Migration States |
| 93 |
|
| 94 |
|
| 95 |
|
| 96 |
| 1 | [u64; 23] | Direct access | Unchanged | |
| 97 |
| 2 | [u64; 23] | Safe accessors | Unchanged | |
| 98 |
| 3 | CrcKeysStorage | Safe accessors | Updated | |
| 99 |
|
| 100 |
## Error Handling |
| 101 |
|
| 102 |
### Bounds Checking Strategy |
| 103 |
|
| 104 |
1. **Safe Default**: Out-of-bounds key access returns 0 instead of panicking |
| 105 |
2. **Optional Access**: `get_key_checked()` returns `None` for invalid indices |
| 106 |
3. **Graceful Degradation**: Code continues to function with missing keys |
| 107 |
|
| 108 |
### Error Scenarios |
| 109 |
|
| 110 |
|
| 111 |
|
| 112 |
| Access key[30] with 23-key storage | Returns 0 | Allows future expansion without breaking existing code | |
| 113 |
| Invalid key index | Returns 0 | Prevents panics, maintains stability | |
| 114 |
| Empty key storage | Returns 0 for all indices | Defensive programming | |
| 115 |
|
| 116 |
## Testing Strategy |
| 117 |
|
| 118 |
### Unit Tests |
| 119 |
|
| 120 |
1. **CrcKeysStorage Tests**: |
| 121 |
- Verify correct key storage and retrieval for each variant |
| 122 |
- Test bounds checking behavior |
| 123 |
- Validate const constructor functions |
| 124 |
|
| 125 |
2. **CrcParams Integration Tests**: |
| 126 |
- Verify safe accessor methods work correctly |
| 127 |
- Test backwards compatibility with existing const definitions |
| 128 |
- Validate zero runtime overhead through benchmarks |
| 129 |
|
| 130 |
3. **Migration Tests**: |
| 131 |
- Test each phase independently |
| 132 |
- Verify existing functionality remains intact |
| 133 |
- Validate const definition updates |
| 134 |
|
| 135 |
### Compatibility Tests |
| 136 |
|
| 137 |
1. **Third-Party Simulation**: |
| 138 |
- Create mock third-party const definitions |
| 139 |
- Verify they continue working through all phases |
| 140 |
- Test key access patterns used by external code |
| 141 |
|
| 142 |
2. **Performance Tests**: |
| 143 |
- Benchmark key access performance vs direct array access |
| 144 |
- Verify compiler optimizations eliminate enum dispatch |
| 145 |
- Measure memory usage impact |
| 146 |
|
| 147 |
### Integration Tests |
| 148 |
|
| 149 |
1. **Architecture Code Tests**: |
| 150 |
- Update existing architecture tests to use safe accessors |
| 151 |
- Verify SIMD operations work correctly with new key access |
| 152 |
- Test folding operations across different key storage variants |
| 153 |
|
| 154 |
2. **End-to-End Tests**: |
| 155 |
- Verify CRC calculations remain correct after migration |
| 156 |
- Test custom CrcParams creation and usage |
| 157 |
- Validate `get-custom-params` binary output |
| 158 |
|
| 159 |
## Implementation Phases |
| 160 |
|
| 161 |
### Phase 1: Add New Types |
| 162 |
- Add CrcKeysStorage enum to codebase |
| 163 |
- Add helper methods to CrcParams (delegating to existing keys field) |
| 164 |
- Maintain existing [u64; 23] field for compatibility |
| 165 |
- All tests continue to pass |
| 166 |
|
| 167 |
### Phase 2: Update Architecture Code |
| 168 |
- Replace direct key array access with safe accessor methods |
| 169 |
- Update SIMD and folding code to use `params.get_key(index)` |
| 170 |
- Maintain backwards compatibility |
| 171 |
- Performance remains identical |
| 172 |
|
| 173 |
### Phase 3: Switch to New Storage |
| 174 |
- Change CrcParams.keys field from [u64; 23] to CrcKeysStorage |
| 175 |
- Update all const definitions to use CrcKeysStorage::from_keys_23() |
| 176 |
- Update `get-custom-params` binary output format |
| 177 |
- This is the only breaking change, but minimal impact |
| 178 |
|
| 179 |
## Performance Considerations |
| 180 |
|
| 181 |
### Compiler Optimizations |
| 182 |
|
| 183 |
The Rust compiler optimizes enum dispatch when: |
| 184 |
1. All variants have the same access pattern |
| 185 |
2. The enum is used in hot paths with predictable patterns |
| 186 |
3. Inlining is enabled for accessor methods |
| 187 |
|
| 188 |
Expected assembly output for `params.get_key(21)`: |
| 189 |
```assembly |
| 190 |
; Same as direct array access keys[21] |
| 191 |
mov rax, qword ptr [rdi + 168 + 21*8] |
| 192 |
``` |
| 193 |
|
| 194 |
### Memory Layout |
| 195 |
|
| 196 |
|
| 197 |
|
| 198 |
| KeysFold256 | 184 bytes | 8-byte aligned | |
| 199 |
| KeysFutureTest | 200 bytes | 8-byte aligned | |
| 200 |
| Enum overhead | 0 bytes | (optimized away) | |
| 201 |
|
| 202 |
## FFI Future-Proofing Design |
| 203 |
|
| 204 |
### Problem Analysis |
| 205 |
|
| 206 |
The current C FFI interface has several limitations: |
| 207 |
1. **Fixed Key Array**: `CrcFastParams` struct uses `uint64_t keys[23]` hardcoded |
| 208 |
2. **No Expansion Path**: Cannot support future key variants with different sizes |
| 209 |
3. **Conversion Limitation**: `to_keys_array_23()` only works for current 23-key variant |
| 210 |
|
| 211 |
### FFI Design Solution |
| 212 |
|
| 213 |
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. |
| 214 |
|
| 215 |
#### Truly Future-Proof CrcFastParams Structure |
| 216 |
|
| 217 |
```c |
| 218 |
// Completely future-proof structure using pointer to keys |
| 219 |
typedef struct CrcFastParams { |
| 220 |
enum CrcFastAlgorithm algorithm; |
| 221 |
uint8_t width; |
| 222 |
uint64_t poly; |
| 223 |
uint64_t init; |
| 224 |
bool refin; |
| 225 |
bool refout; |
| 226 |
uint64_t xorout; |
| 227 |
uint64_t check; |
| 228 |
uint32_t key_count; // Number of keys available |
| 229 |
const uint64_t *keys; // Pointer to keys array (managed by Rust) |
| 230 |
} CrcFastParams; |
| 231 |
``` |
| 232 |
|
| 233 |
#### Key Management Strategy |
| 234 |
|
| 235 |
1. **Rust-Managed Memory**: Keys remain in Rust-managed memory |
| 236 |
2. **Stable Pointers**: Use Box::leak or static storage for stable pointers |
| 237 |
3. **Automatic Cleanup**: Rust handles memory management transparently |
| 238 |
4. **No Size Limits**: Can support any number of keys (23, 25, 50, 100+) |
| 239 |
|
| 240 |
#### Internal Implementation |
| 241 |
|
| 242 |
```rust |
| 243 |
// Helper to create stable key pointers |
| 244 |
fn create_stable_key_pointer(keys: &CrcKeysStorage) -> *const u64 { |
| 245 |
match keys { |
| 246 |
CrcKeysStorage::KeysFold256(keys) => keys.as_ptr(), |
| 247 |
CrcKeysStorage::KeysFutureTest(keys) => keys.as_ptr(), |
| 248 |
// Future variants automatically supported |
| 249 |
} |
| 250 |
} |
| 251 |
|
| 252 |
impl From<CrcParams> for CrcFastParams { |
| 253 |
fn from(params: CrcParams) -> Self { |
| 254 |
CrcFastParams { |
| 255 |
algorithm: params.algorithm.into(), |
| 256 |
width: params.width, |
| 257 |
poly: params.poly, |
| 258 |
init: params.init, |
| 259 |
refin: params.refin, |
| 260 |
refout: params.refout, |
| 261 |
xorout: params.xorout, |
| 262 |
check: params.check, |
| 263 |
key_count: params.key_count() as u32, |
| 264 |
keys: create_stable_key_pointer(¶ms.keys), |
| 265 |
} |
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
impl From<CrcFastParams> for CrcParams { |
| 270 |
fn from(value: CrcFastParams) -> Self { |
| 271 |
// Convert C array back to appropriate CrcKeysStorage |
| 272 |
let keys = unsafe { |
| 273 |
std::slice::from_raw_parts(value.keys, value.key_count as usize) |
| 274 |
}; |
| 275 |
|
| 276 |
let storage = match value.key_count { |
| 277 |
23 => CrcKeysStorage::from_keys_fold_256( |
| 278 |
keys.try_into().expect("Invalid key count for fold_256") |
| 279 |
), |
| 280 |
25 => CrcKeysStorage::from_keys_fold_future_test( |
| 281 |
keys.try_into().expect("Invalid key count for future_test") |
| 282 |
), |
| 283 |
_ => panic!("Unsupported key count: {}", value.key_count), |
| 284 |
}; |
| 285 |
|
| 286 |
CrcParams { |
| 287 |
algorithm: value.algorithm.into(), |
| 288 |
name: "custom", |
| 289 |
width: value.width, |
| 290 |
poly: value.poly, |
| 291 |
init: value.init, |
| 292 |
refin: value.refin, |
| 293 |
refout: value.refout, |
| 294 |
xorout: value.xorout, |
| 295 |
check: value.check, |
| 296 |
keys: storage, |
| 297 |
} |
| 298 |
} |
| 299 |
} |
| 300 |
``` |
| 301 |
|
| 302 |
#### Enhanced FFI Functions |
| 303 |
|
| 304 |
```rust |
| 305 |
#[no_mangle] |
| 306 |
pub extern "C" fn crc_fast_get_custom_params(...) -> CrcFastParams { |
| 307 |
let params = CrcParams::new(...); |
| 308 |
params.into() // Automatic conversion |
| 309 |
} |
| 310 |
``` |
| 311 |
|
| 312 |
#### Benefits of This Approach |
| 313 |
|
| 314 |
1. **Truly Future-Proof**: No hardcoded limits, supports any key count |
| 315 |
2. **Zero Copy**: Keys remain in original Rust memory, just expose pointer |
| 316 |
3. **Memory Safe**: Rust manages memory, C gets stable pointers |
| 317 |
4. **Performance**: Direct pointer access, no copying overhead |
| 318 |
5. **Automatic Support**: New CrcKeysStorage variants automatically work |
| 319 |
6. **Idiomatic C**: Direct array access pattern familiar to C developers |
| 320 |
|
| 321 |
#### C Usage Pattern |
| 322 |
|
| 323 |
```c |
| 324 |
// Get custom parameters |
| 325 |
CrcFastParams params = crc_fast_get_custom_params(...); |
| 326 |
|
| 327 |
// Direct access with bounds checking (C developer responsibility) |
| 328 |
for (uint32_t i = 0; i < params.key_count; i++) { |
| 329 |
uint64_t key = params.keys[i]; |
| 330 |
// Use key... |
| 331 |
} |
| 332 |
|
| 333 |
// Or access specific keys directly |
| 334 |
uint64_t key21 = params.keys[21]; // Direct access (if bounds known) |
| 335 |
``` |
| 336 |
|
| 337 |
## Security Considerations |
| 338 |
|
| 339 |
### Bounds Safety |
| 340 |
|
| 341 |
The new design eliminates array bounds panics, which could be exploited in unsafe contexts. Safe key access prevents: |
| 342 |
- Buffer overflow attacks through malicious key indices |
| 343 |
- Denial of service through panic-induced crashes |
| 344 |
- Information disclosure through out-of-bounds memory access |
| 345 |
|
| 346 |
### FFI Safety |
| 347 |
|
| 348 |
The enhanced FFI design adds additional safety measures: |
| 349 |
- **Key Count Validation**: V2 functions validate key_count before conversion |
| 350 |
- **Buffer Bounds**: 32-key buffer prevents overflow while allowing future expansion |
| 351 |
- **Graceful Degradation**: Invalid key counts return error codes instead of panicking |
| 352 |
|
| 353 |
### Const Safety |
| 354 |
|
| 355 |
All const definitions remain compile-time validated, preventing: |
| 356 |
- Runtime key generation vulnerabilities |
| 357 |
- Dynamic key modification attacks |
| 358 |
- Timing-based side-channel attacks on key access |