OXYGENHUB
◆ main oxygen / vendor / crc-fast / src / generate.rs
26.3 KiB · Rust · last changed Strata: the cloud stores send through ureq, so every release target links 1 month ago source blame history raw
1
// Copyright 2025 Don MacAskill. Licensed under MIT or Apache-2.0.
2
3
//! This module calculates the keys needed for CRC calculations using PCLMULQDQ.
4
//!
5
//! Docs generated by Claude.ai
6
//!
7
//! # Overview
8
//!
9
//! This implements key generation for hardware-accelerated CRC computation using Intel's
10
//! PCLMULQDQ instruction (carryless multiplication). The keys enable a "fold-by-8" algorithm
11
//! that processes data in parallel chunks.
12
//!
13
//! # The PCLMULQDQ Algorithm
14
//!
15
//! Traditional CRC computation processes data byte-by-byte, which is slow. The PCLMULQDQ
16
//! instruction enables parallel processing by treating CRC computation as polynomial
17
//! multiplication in GF(2) (Galois Field with 2 elements, where addition is XOR).
18
//!
19
//! The algorithm works by:
20
//! 1. Loading multiple data chunks into SIMD registers
21
//! 2. Using precomputed keys to "fold" distant chunks together
22
//! 3. Reducing the result modulo the CRC polynomial
23
//!
24
//! # What Are "Keys"?
25
//!
26
//! Keys are precomputed constants representing x^n mod P(x), where:
27
//! - x^n represents a bit shift of n positions
28
//! - P(x) is the CRC polynomial
29
//! - The modulo operation is polynomial division in GF(2)
30
//!
31
//! Each key answers: "If I have a CRC value, and I want to shift it by N bits and reduce it,
32
//! what constant should I multiply by?"
33
//!
34
//! # The Distance Concept
35
//!
36
//! The "distance" or "exponent" refers to how many bits apart two data chunks are. When
37
//! processing data in 128-bit (16-byte) SIMD registers:
38
//!
39
//! - For fold-by-8: We process 8 registers (128 bytes) at once
40
//! - Keys at distance 32*N (CRC-32) or 64*N (CRC-64) let us combine chunks N registers apart
41
//!
42
//! Example for CRC-32:
43
//! - Distance 32*3 = 96 bits = 12 bytes (3 registers of 32-bit data)
44
//! - This key folds together data chunks that are 12 bytes apart
45
//!
46
//! The larger distances (32*63, 32*65) handle 256-byte chunks for very high throughput, such as
47
//! using AVX-512 VPCLMULQDQ.
48
49
//! # Why CRC-32 and CRC-64 Implementations Differ So Greatly
50
//!
51
//! The implementations look very different, but they're solving the same problem at
52
//! different scales. Here are the key differences:
53
//!
54
//! ## 1. Polynomial Size
55
//!
56
//! - **CRC-32**: 33-bit polynomial (32 data bits + implicit leading 1)
57
//! - **CRC-64**: 65-bit polynomial (64 data bits + implicit leading 1)
58
//!
59
//! This fundamental difference cascades through every function.
60
//!
61
//! ## 2. Register Representation
62
//!
63
//! - **CRC-32**: Uses u64 with values in upper bits (e.g., 0x080000000 = bit 35)
64
//!   - Has "headroom" above 32 bits for intermediate calculations
65
//!   - Results often need alignment shifts (>> 31)
66
//!
67
//! - **CRC-64**: Uses u64 directly (e.g., 0x8000000000000000 = bit 63)
68
//!   - No headroom - must use two u64s for μ calculation
69
//!   - Results are naturally aligned, no extra shifts needed
70
//!
71
//! ## 3. Distance Scaling
72
//!
73
//! Both algorithms fold data in chunks, but the chunk sizes differ:
74
//!
75
//! | Concept | CRC-32 | CRC-64 | Why? |
76
//! |---------|--------|--------|------|
77
//! | Base unit | 32 bits (4 bytes) | 64 bits (8 bytes) | Matches polynomial width |
78
//! | Distance 1 | 96 bits (12 bytes) | 128 bits (16 bytes) | 3× vs 2× base unit |
79
//! | Large chunk | 2016 bits (252 bytes) | 2048 bits (256 bytes) | Aligned to cache lines |
80
//!
81
//! The multipliers are smaller for CRC-64 (2×, 3× vs 3×, 5×) because the base unit
82
//! is already larger.
83
//!
84
//! ## 4. Algorithm Variations
85
//!
86
//! - **Key generation**: CRC-32 uses simple if-check; CRC-64 uses bit-mask trick
87
//!   - Both compute x^n mod P(x), but CRC-64's approach is more efficient
88
//!
89
//! - **Mu calculation**: CRC-32 uses single u64; CRC-64 needs double-register
90
//!   - CRC-64 can't fit x^128 ÷ P(x) in one u64
91
//!
92
//! - **Iteration counts**: Off-by-one differences everywhere
93
//!   - Reflects different starting points and alignment requirements
94
//!
95
//! ## 5. Reflection Handling
96
//!
97
//! - **CRC-32**: Reverses 32 bits, then shifts by 31
98
//! - **CRC-64**: Reverses 64 bits, no additional shift
99
//!
100
//! Both create the reflected representation needed for LSB-first CRCs, but
101
//! CRC-64's cleaner alignment means less post-processing.
102
//!
103
//! ## The Big Picture
104
//!
105
//! Despite the implementation differences, both are doing the same thing:
106
//! precomputing constants that let PCLMULQDQ fold distant data chunks together
107
//! efficiently. The differences arise from working with different-sized polynomials
108
//! within the constraints of fixed-size integer types (u64).
109
110
#![allow(dead_code)]
111
112
use core::ops::{BitAnd, BitOr, Shl, Shr};
113
114
/// Exponents (bit distances) for CRC-16 key generation.
115
///
116
/// CRC-16 uses the same exponents as CRC-32 because the folding algorithm operates on
117
/// 128-bit SIMD registers regardless of CRC width. CRC-16 computation is performed by
118
/// scaling 16-bit values to 32-bit space, using the CRC-32 algorithm infrastructure,
119
/// and then scaling the result back to 16 bits.
120
///
121
/// See CRC32_EXPONENTS for detailed documentation of the exponent values.
122
const CRC16_EXPONENTS: [u64; 23] = [
123
    0, // unused, just aligns indexes with the literature
124
    32 * 3,
125
    32 * 5,
126
    32 * 31,
127
    32 * 33,
128
    32 * 3,
129
    32 * 2,
130
    0, // mu, generate separately
131
    0, // poly, generate separately
132
    32 * 27,
133
    32 * 29,
134
    32 * 23,
135
    32 * 25,
136
    32 * 19,
137
    32 * 21,
138
    32 * 15,
139
    32 * 17,
140
    32 * 11,
141
    32 * 13,
142
    32 * 7,
143
    32 * 9,
144
    32 * 63, // for 256 byte distances (2048 - 32)
145
    32 * 65, // for 256 byte distances (2048 + 32)
146
];
147
148
/// Exponents (bit distances) for CRC-32 key generation.
149
///
150
/// These represent bit distances for the fold-by-8 algorithm. Each exponent defines
151
/// how many bits apart two data chunks are when folding.
152
///
153
/// # Why These Specific Values?
154
///
155
/// For CRC-32, the polynomial is 33 bits (including the implicit leading 1), so we work
156
/// with 32-bit data chunks. The exponents are designed to efficiently fold 128-byte chunks:
157
///
158
/// - Indices 1-4: Primary folding constants for 8-chunk (128-byte) processing
159
///   - 32*3, 32*5: Used for folding the first half
160
///   - 32*31, 32*33: Used for folding the second half
161
/// - Indices 5-6: Short-distance folding
162
///   - 32*3, 32*2: For final reduction stages
163
/// - Indices 7-8: Special values (mu and polynomial) computed separately
164
/// - Indices 9-20: Additional folding distances for progressive reduction
165
///   - These allow folding from 8 chunks down to 4, then 2, then 1
166
/// - Indices 21-22: Large distances for 256-byte chunk processing, such as using AVX-512 VPCLMULQDQ
167
///   - 32*63 = 2016 bits (252 bytes)
168
///   - 32*65 = 2080 bits (260 bytes)
169
///   - These enable very high-throughput processing of large buffers
170
const CRC32_EXPONENTS: [u64; 23] = [
171
    0, // unused, just aligns indexes with the literature
172
    32 * 3,
173
    32 * 5,
174
    32 * 31,
175
    32 * 33,
176
    32 * 3,
177
    32 * 2,
178
    0, // mu, generate separately
179
    0, // poly, generate separately
180
    32 * 27,
181
    32 * 29,
182
    32 * 23,
183
    32 * 25,
184
    32 * 19,
185
    32 * 21,
186
    32 * 15,
187
    32 * 17,
188
    32 * 11,
189
    32 * 13,
190
    32 * 7,
191
    32 * 9,
192
    32 * 63, // for 256 byte distances (2048 - 32)
193
    32 * 65, // for 256 byte distances (2048 + 32)
194
];
195
196
/// Exponents (bit distances) for CRC-64 key generation.
197
///
198
/// Similar to CRC32_EXPONENTS but scaled for 64-bit CRC polynomials.
199
///
200
/// # Why Different From CRC-32?
201
///
202
/// CRC-64 uses a 65-bit polynomial (64 bits + implicit leading 1), so:
203
/// - Data chunks are 64 bits instead of 32
204
/// - All distances are in multiples of 64 instead of 32
205
/// - The actual byte distances are larger (64*N bits = 8*N bytes)
206
///
207
/// # Distance Comparison
208
///
209
/// | Index | CRC-32 Distance | CRC-64 Distance | Purpose              |
210
/// |-------|-----------------|-----------------|----------------------|
211
/// | 1     | 96 bits (12B)   | 128 bits (16B)  | Primary fold         |
212
/// | 2     | 160 bits (20B)  | 192 bits (24B)  | Primary fold         |
213
/// | 21    | 2016 bits (252B)| 2048 bits (256B)| Large chunk folding  |
214
///
215
/// The smaller multipliers (2, 3 vs 3, 5) reflect the larger base unit (64 vs 32 bits).
216
const CRC64_EXPONENTS: [u64; 23] = [
217
    0, // unused, just aligns indexes with the literature
218
    64 * 2,
219
    64 * 3,
220
    64 * 16,
221
    64 * 17,
222
    64 * 2,
223
    64,
224
    0, // mu, generate separately
225
    0, // poly, generate separately
226
    64 * 14,
227
    64 * 15,
228
    64 * 12,
229
    64 * 13,
230
    64 * 10,
231
    64 * 11,
232
    64 * 8,
233
    64 * 9,
234
    64 * 6,
235
    64 * 7,
236
    64 * 4,
237
    64 * 5,
238
    64 * 32, // for 256 byte distances (2048)
239
    64 * 33, // for 256 byte distances (2048 + 64)
240
];
241
242
/// Generates the 23 keys needed to calculate CRCs for a given polynomial using PCLMULQDQ when
243
/// folding by 8.
244
pub fn keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] {
245
    let mut keys: [u64; 23] = [0; 23];
246
247
    let exponents = if 16 == width {
248
        CRC16_EXPONENTS
249
    } else if 32 == width {
250
        CRC32_EXPONENTS
251
    } else if 64 == width {
252
        CRC64_EXPONENTS
253
    } else {
254
        panic!("Unsupported width: {width}",);
255
    };
256
257
    let poly = if 16 == width {
258
        // CRC-16 uses a 17-bit polynomial (16 bits + implicit leading 1) scaled to 32-bit space
259
        (poly << 16) | (1u64 << 32)
260
    } else if 32 == width {
261
        poly | (1u64 << 32)
262
    } else {
263
        poly
264
    };
265
266
    for i in 1..23 {
267
        keys[i] = key(width, poly, reflected, exponents[i]);
268
    }
269
270
    keys[7] = mu(width, poly, reflected);
271
    keys[8] = polynomial(width, poly, reflected);
272
273
    keys
274
}
275
276
fn key(width: u8, poly: u64, reflected: bool, exponent: u64) -> u64 {
277
    if width == 16 {
278
        crc16_key(exponent, reflected, poly)
279
    } else if width == 32 {
280
        crc32_key(exponent, reflected, poly)
281
    } else if width == 64 {
282
        crc64_key(exponent, reflected, poly)
283
    } else {
284
        panic!("Unsupported width: {width}",);
285
    }
286
}
287
288
/// Computes a CRC-16 folding key for a given bit distance (exponent).
289
///
290
/// # Algorithm
291
///
292
/// CRC-16 key generation uses the same algorithm as CRC-32 because CRC-16 computation
293
/// is performed by scaling 16-bit values to 32-bit space. The polynomial is already
294
/// scaled to 32-bit space (poly << 16 | 1 << 32) before this function is called.
295
///
296
/// 1. Start with x^32 (represented as 0x080000000, bit 35 set)
297
/// 2. Multiply by x repeatedly (left shift), reducing modulo P(x) each time
298
/// 3. After (exponent - 31) iterations, we have x^exponent mod P(x)
299
///
300
/// # Reflection
301
///
302
/// For reflected CRC-16, we bit-reverse the 16-bit result and shift right by 31 bits
303
/// to align it properly for PCLMULQDQ operations.
304
fn crc16_key(exponent: u64, reflected: bool, polynomial: u64) -> u64 {
305
    if exponent < 32 {
306
        return 0;
307
    }
308
309
    let mut n: u64 = 0x080000000;
310
    let e = exponent - 31;
311
312
    for _ in 0..e {
313
        n <<= 1;
314
        if (n & 0x100000000) != 0 {
315
            n ^= polynomial;
316
        }
317
    }
318
319
    if reflected {
320
        bit_reverse(n) >> 31
321
    } else {
322
        n << 32
323
    }
324
}
325
326
/// Computes a CRC-32 folding key for a given bit distance (exponent).
327
///
328
/// # Algorithm
329
///
330
/// This calculates x^exponent mod P(x) where P(x) is the CRC-32 polynomial.
331
///
332
/// 1. Start with x^32 (represented as 0x080000000, bit 35 set)
333
/// 2. Multiply by x repeatedly (left shift), reducing modulo P(x) each time
334
/// 3. After (exponent - 31) iterations, we have x^exponent mod P(x)
335
///
336
/// # Why Start at x^32?
337
///
338
/// For CRC-32, we're working with 33-bit polynomials (32 data bits + leading 1).
339
/// Starting at x^32 (bit 35 in a 36-bit representation) gives us the right alignment
340
/// for the first reduction step.
341
///
342
/// # Why exponent - 31?
343
///
344
/// We start at x^32 and want to reach x^exponent, so we need (exponent - 32) more
345
/// multiplications. But the first shift happens before the first check, so it's
346
/// actually (exponent - 31) iterations.
347
///
348
/// # Reflection
349
///
350
/// If the CRC is reflected (LSB-first), we bit-reverse the result and shift right
351
/// by 31 bits to align it properly for PCLMULQDQ operations.
352
fn crc32_key(exponent: u64, reflected: bool, polynomial: u64) -> u64 {
353
    if exponent < 32 {
354
        return 0;
355
    }
356
357
    let mut n: u64 = 0x080000000;
358
    let e = exponent - 31;
359
360
    for _ in 0..e {
361
        n <<= 1;
362
        if (n & 0x100000000) != 0 {
363
            n ^= polynomial;
364
        }
365
    }
366
367
    if reflected {
368
        bit_reverse(n) >> 31
369
    } else {
370
        n << 32
371
    }
372
}
373
374
/// Computes a CRC-64 folding key for a given bit distance (exponent).
375
///
376
/// # Algorithm
377
///
378
/// Similar to CRC-32 but adapted for 64-bit polynomials:
379
///
380
/// 1. Start with x^63 (represented as 0x8000000000000000, bit 63 set)
381
/// 2. Multiply by x repeatedly, reducing modulo P(x) each time
382
/// 3. After (exponent - 63 or - 64) iterations, we have x^exponent mod P(x)
383
///
384
/// # Key Differences From CRC-32
385
///
386
/// 1. **Starting point**: 0x8000000000000000 vs 0x080000000
387
///    - CRC-64 uses bit 63 (the MSB of a 64-bit value)
388
///    - CRC-32 uses bit 35 (extended beyond 32 bits)
389
///
390
/// 2. **Iteration count**: Depends on reflection
391
///    - Reflected: exponent - 64 (because we iterate from x^64)
392
///    - Non-reflected: exponent - 63 (because we start at x^63)
393
///
394
/// 3. **Reduction step**: Uses a more efficient formulation
395
///    - `(n << 1) ^ ((0_u64.wrapping_sub(n >> 63)) & polynomial)`
396
///    - This is equivalent to: if MSB is set, shift and XOR with poly; else just shift
397
///    - The wrapping_sub creates a mask of all 1s or all 0s based on the MSB
398
///
399
/// 4. **No additional shift in result**: The result is already properly aligned
400
///    - CRC-32 needs `>> 31` adjustment for reflected case
401
///    - CRC-64 doesn't need this because it operates on full 64-bit values
402
fn crc64_key(exponent: u64, reflected: bool, polynomial: u64) -> u64 {
403
    if exponent <= 64 {
404
        return 0;
405
    }
406
407
    let mut n: u64 = 0x8000000000000000;
408
    let e = if reflected {
409
        exponent - 64
410
    } else {
411
        exponent - 63
412
    };
413
414
    for _ in 0..e {
415
        n = (n << 1) ^ ((0_u64.wrapping_sub(n >> 63)) & polynomial);
416
    }
417
418
    if reflected {
419
        bit_reverse(n)
420
    } else {
421
        n
422
    }
423
}
424
425
fn polynomial(width: u8, polynomial: u64, reflected: bool) -> u64 {
426
    if width == 16 {
427
        crc16_polynomial(polynomial, reflected)
428
    } else if width == 32 {
429
        crc32_polynomial(polynomial, reflected)
430
    } else if width == 64 {
431
        crc64_polynomial(polynomial, reflected)
432
    } else {
433
        panic!("Unsupported width: {width}",);
434
    }
435
}
436
437
/// Formats a CRC-16 polynomial for use in PCLMULQDQ operations.
438
///
439
/// # Polynomial Representation
440
///
441
/// The polynomial passed to this function is already scaled to 32-bit space
442
/// (original_poly << 16 | 1 << 32). This function formats it for PCLMULQDQ.
443
///
444
/// # Non-Reflected Case
445
///
446
/// For non-reflected CRCs, the polynomial is already in the correct format
447
/// from the scaling done in keys().
448
///
449
/// # Reflected Case
450
///
451
/// For reflected (LSB-first) CRCs, we need to:
452
/// 1. Extract the original 16-bit polynomial from the scaled value
453
/// 2. Bit-reverse the 16-bit polynomial
454
/// 3. Shift left by 1 bit and set the LSB to 1
455
fn crc16_polynomial(polynomial: u64, reflected: bool) -> u64 {
456
    if !reflected {
457
        return polynomial;
458
    }
459
460
    // Extract original 16-bit poly from scaled polynomial (poly << 16 | 1 << 32)
461
    let original_poly = ((polynomial >> 16) & 0xFFFF) as u16;
462
    let reversed = bit_reverse(original_poly);
463
    ((reversed as u64) << 1) | 1
464
}
465
466
/// Formats a CRC-32 polynomial for use in PCLMULQDQ operations.
467
///
468
/// # Polynomial Representation
469
///
470
/// CRC polynomials are typically written without the implicit leading 1. For example:
471
/// - CRC-32 (IEEE 802.3): 0x04C11DB7
472
/// - The full polynomial is actually 0x104C11DB7 (33 bits)
473
///
474
/// # Non-Reflected Case
475
///
476
/// For non-reflected CRCs, we simply add the leading 1:
477
/// - Input: 0x04C11DB7
478
/// - Output: 0x104C11DB7 (bit 32 set)
479
///
480
/// # Reflected Case
481
///
482
/// For reflected (LSB-first) CRCs, we need to:
483
/// 1. Bit-reverse the 32-bit polynomial value
484
/// 2. Shift left by 1 bit
485
/// 3. Set the LSB to 1
486
///
487
/// This creates the reflected polynomial in the format expected by PCLMULQDQ.
488
///
489
/// Example:
490
/// - Original: 0x04C11DB7
491
/// - Bit-reversed: 0xEDB88320
492
/// - Shifted and ORed: 0x1DB710641
493
fn crc32_polynomial(polynomial: u64, reflected: bool) -> u64 {
494
    if !reflected {
495
        return polynomial | (1u64 << 32);
496
    };
497
498
    // For 32-bit polynomials, operate on full 33 bits including leading 1
499
    let reversed = bit_reverse((polynomial & 0xFFFFFFFF) as u32);
500
    // Need to set bit 32 (33rd bit) to get the 1 in the right position after reflection
501
    ((reversed as u64) << 1) | 1
502
}
503
504
/// Formats a CRC-64 polynomial for use in PCLMULQDQ operations.
505
///
506
/// # Key Difference From CRC-32
507
///
508
/// # Non-Reflected Case
509
///
510
/// For non-reflected CRCs, we simply return the polynomial as-is.
511
///
512
/// # Reflected Case
513
///
514
/// For reflected CRC-64:
515
/// 1. Bit-reverse all 64 bits of the polynomial
516
/// 2. Shift left by 1
517
/// 3. Set LSB to 1
518
///
519
/// Unlike CRC-32 which only reverses 32 bits, this reverses the full 64-bit value.
520
fn crc64_polynomial(polynomial: u64, reflected: bool) -> u64 {
521
    if !reflected {
522
        return polynomial;
523
    };
524
525
    // For 64-bit polynomials, operate on all 64 bits
526
    (bit_reverse(polynomial) << 1) | 1
527
}
528
529
fn mu(width: u8, polynomial: u64, reflected: bool) -> u64 {
530
    if width == 16 {
531
        crc16_mu(polynomial, reflected)
532
    } else if width == 32 {
533
        crc32_mu(polynomial, reflected)
534
    } else if width == 64 {
535
        crc64_mu(polynomial, reflected)
536
    } else {
537
        panic!("Unsupported width: {width}",);
538
    }
539
}
540
541
/// Computes the Barrett reduction constant (μ) for CRC-16.
542
///
543
/// # What Is μ (Mu)?
544
///
545
/// Mu is used in Barrett reduction for fast modular reduction without division.
546
/// For CRC-16 operations (scaled to 32-bit space), μ = floor(x^64 / P(x)).
547
///
548
/// # Algorithm
549
///
550
/// This uses the same algorithm as CRC-32 mu calculation because CRC-16 is
551
/// computed in 32-bit space. The polynomial is already scaled (poly << 16 | 1 << 32).
552
///
553
/// 1. Start with x^64 (represented as 0x100000000, bit 32 set)
554
/// 2. For each bit position from MSB down:
555
///    - If the dividend has a bit at position 32, record a 1 in quotient
556
///    - XOR the dividend with the polynomial
557
///    - Shift dividend left
558
/// 3. After 33 iterations, q contains μ
559
fn crc16_mu(polynomial: u64, reflected: bool) -> u64 {
560
    let mut n: u64 = 0x100000000;
561
    let mut q: u64 = 0;
562
563
    for _ in 0..33 {
564
        q <<= 1;
565
        if n & 0x100000000 != 0 {
566
            q |= 1;
567
            n ^= polynomial;
568
        }
569
        n <<= 1;
570
    }
571
572
    if reflected {
573
        bit_reverse(q) >> 31
574
    } else {
575
        q
576
    }
577
}
578
579
/// Computes the Barrett reduction constant (μ) for CRC-32.
580
///
581
/// # What Is μ (Mu)?
582
///
583
/// Mu is used in Barrett reduction, an algorithm for fast modular reduction without division.
584
/// For CRC operations, μ = floor(x^(2n) / P(x)) where n is the polynomial degree.
585
///
586
/// # Algorithm
587
///
588
/// This performs polynomial long division:
589
/// 1. Start with x^64 (represented as 0x100000000, bit 32 set in a 65-bit view)
590
/// 2. For each bit position from MSB down:
591
///    - If the dividend has a bit at position 32, record a 1 in quotient
592
///    - XOR the dividend with the polynomial (subtract in GF(2))
593
///    - Shift dividend left
594
/// 3. After 33 iterations, q contains μ
595
///
596
/// # Why 33 Iterations?
597
///
598
/// We're dividing a 65-bit value (x^64) by a 33-bit polynomial, giving a 33-bit quotient.
599
///
600
/// # Reflection
601
///
602
/// If reflected, the result is bit-reversed and shifted right by 31 to align properly.
603
fn crc32_mu(polynomial: u64, reflected: bool) -> u64 {
604
    let mut n: u64 = 0x100000000;
605
    let mut q: u64 = 0;
606
607
    for _ in 0..33 {
608
        q <<= 1;
609
        if n & 0x100000000 != 0 {
610
            q |= 1;
611
            n ^= polynomial;
612
        }
613
        n <<= 1;
614
    }
615
616
    if reflected {
617
        bit_reverse(q) >> 31
618
    } else {
619
        q
620
    }
621
}
622
623
/// Computes the Barrett reduction constant (μ) for CRC-64.
624
///
625
/// # Key Differences From CRC-32
626
///
627
/// CRC-64 requires dividing a 129-bit value (x^128) by a 65-bit polynomial, which
628
/// doesn't fit in a single u64. This implementation uses two u64 variables to represent
629
/// a 128-bit dividend.
630
///
631
/// # Algorithm
632
///
633
/// 1. Start with x^128 represented as (n_hi=1, n_lo=0)
634
/// 2. For each bit position:
635
///    - If n_hi is non-zero, record 1 in quotient and XOR n_lo with polynomial
636
///    - Shift the double-register left: n_hi = n_lo >> 63; n_lo <<= 1
637
/// 3. After 64 or 65 iterations, q contains μ
638
///
639
/// # Why Two Registers?
640
///
641
/// - n_hi:n_lo forms a 128-bit value
642
/// - We can only XOR with the polynomial when "bit 64" is set (n_hi != 0)
643
/// - The shift operation propagates the MSB of n_lo into n_hi
644
///
645
/// # Iteration Count Difference
646
///
647
/// - Reflected: 64 iterations (matches the polynomial degree)
648
/// - Non-reflected: 65 iterations (one extra for proper alignment)
649
///
650
/// This asymmetry exists because reflected CRCs process data LSB-first,
651
/// changing the division mechanics slightly.
652
///
653
/// # No Additional Shift
654
///
655
/// Unlike CRC-32's `>> 31`, CRC-64 doesn't need an extra shift in the reflected
656
/// case because the 64-bit result is already properly aligned.
657
fn crc64_mu(polynomial: u64, reflected: bool) -> u64 {
658
    let mut n_hi: u64 = 0x0000000000000001;
659
    let mut n_lo: u64 = 0x0000000000000000;
660
    let mut q: u64 = 0;
661
662
    let max = if reflected { 64 } else { 65 };
663
664
    for _ in 0..max {
665
        q <<= 1;
666
        if n_hi != 0 {
667
            q |= 1;
668
            n_lo ^= polynomial;
669
        }
670
        n_hi = n_lo >> 63;
671
        n_lo <<= 1;
672
    }
673
674
    if reflected {
675
        bit_reverse(q)
676
    } else {
677
        q
678
    }
679
}
680
681
/// Reverses the bits of a value (MSB becomes LSB and vice versa).
682
///
683
/// # Purpose in CRC Calculations
684
///
685
/// Reflected (LSB-first) CRCs process data in reverse bit order. This function
686
/// converts between normal and reflected representations.
687
///
688
/// # Why Generic?
689
///
690
/// This works with any unsigned integer type (u32, u64) through trait bounds,
691
/// allowing the same code to handle both CRC-32 (which reverses 32 bits) and
692
/// CRC-64 (which reverses 64 bits).
693
///
694
/// # Example
695
///
696
/// For u32: 0x12345678 → 0x1E6A2C48
697
/// - Binary: 0001 0010 0011 0100 0101 0110 0111 1000
698
/// - Reversed: 0001 1110 0110 1010 0010 1100 0100 1000
699
fn bit_reverse<T>(mut value: T) -> T
700
where
701
    T: Copy
702
        + Default
703
        + PartialEq
704
        + BitAnd<Output = T>
705
        + BitOr<Output = T>
706
        + Shl<usize, Output = T>
707
        + Shr<usize, Output = T>
708
        + From<u8>,
709
{
710
    let one = T::from(1u8);
711
    let mut result = T::default(); // Zero value
712
713
    // Get the bit size of type T
714
    let bit_size = core::mem::size_of::<T>() * 8;
715
716
    for _ in 0..bit_size {
717
        // Shift result left by 1
718
        result = result << 1;
719
720
        // OR with least significant bit of value
721
        result = result | (value & one);
722
723
        // Shift value right by 1
724
        value = value >> 1;
725
    }
726
727
    result
728
}
729
730
#[cfg(test)]
731
mod tests {
732
    use super::*;
733
    use crate::crc16::consts::{KEYS_1021_REVERSE, KEYS_8BB7_FORWARD};
734
    use crate::test::consts::TEST_ALL_CONFIGS;
735
736
    #[test]
737
    fn test_all() {
738
        for config in TEST_ALL_CONFIGS {
739
            let keys = keys(config.get_width(), config.get_poly(), config.get_refin());
740
            let expected = config.get_keys();
741
742
            for (i, key) in keys.iter().enumerate() {
743
                assert_eq!(
744
                    *key,
745
                    expected[i],
746
                    "Mismatch in keys for {} at index {}: expected 0x{:016x}, got 0x{:016x}",
747
                    config.get_name(),
748
                    i,
749
                    expected[i],
750
                    *key
751
                );
752
            }
753
        }
754
    }
755
756
    #[test]
757
    fn test_crc16_t10_dif_keys() {
758
        // CRC-16/T10-DIF: forward mode, poly=0x8BB7
759
        let generated = keys(16, 0x8BB7, false);
760
761
        for (i, key) in generated.iter().enumerate().take(21) {
762
            assert_eq!(
763
                *key, KEYS_8BB7_FORWARD[i],
764
                "CRC-16/T10-DIF key mismatch at index {}: expected 0x{:016x}, got 0x{:016x}",
765
                i, KEYS_8BB7_FORWARD[i], *key
766
            );
767
        }
768
    }
769
770
    #[test]
771
    fn test_crc16_ibm_sdlc_keys() {
772
        // CRC-16/IBM-SDLC: reflected mode, poly=0x1021
773
        let generated = keys(16, 0x1021, true);
774
775
        for (i, key) in generated.iter().enumerate().take(21) {
776
            assert_eq!(
777
                *key, KEYS_1021_REVERSE[i],
778
                "CRC-16/IBM-SDLC key mismatch at index {}: expected 0x{:016x}, got 0x{:016x}",
779
                i, KEYS_1021_REVERSE[i], *key
780
            );
781
        }
782
    }
783
}
784
785
#[cfg(test)]
786
mod property_tests {
787
    use super::*;
788
    use crate::test::miri_compatible_proptest_config;
789
    use proptest::prelude::*;
790
791
    /// Helper function to bit-reverse a 16-bit value
792
    fn bit_reverse_16(value: u16) -> u16 {
793
        let mut v = value;
794
        let mut result: u16 = 0;
795
        for _ in 0..16 {
796
            result = (result << 1) | (v & 1);
797
            v >>= 1;
798
        }
799
        result
800
    }
801
802
    proptest! {
803
        #![proptest_config(miri_compatible_proptest_config())]
804
805
        /// Feature: crc16-hardware-acceleration, Property 1: Forward polynomial formatting
806
        /// *For any* 16-bit polynomial value P, when generating the polynomial key for forward
807
        /// (non-reflected) CRC-16, the result SHALL equal `(P << 16) | (1 << 32)`.
808
        /// **Validates: Requirements 1.5**
809
        #[test]
810
        fn prop_forward_polynomial_formatting(poly in 0u16..=0xFFFFu16) {
811
            let generated_keys = keys(16, poly as u64, false);
812
            let polynomial_key = generated_keys[8];
813
            let expected = ((poly as u64) << 16) | (1u64 << 32);
814
            prop_assert_eq!(
815
                polynomial_key, expected,
816
                "Forward polynomial formatting failed for poly=0x{:04X}: expected 0x{:016X}, got 0x{:016X}",
817
                poly, expected, polynomial_key
818
            );
819
        }
820
821
        /// Feature: crc16-hardware-acceleration, Property 2: Reflected polynomial formatting
822
        /// *For any* 16-bit polynomial value P, when generating the polynomial key for reflected
823
        /// CRC-16, the result SHALL equal `(bit_reverse_16(P) << 1) | 1`.
824
        /// **Validates: Requirements 1.6**
825
        #[test]
826
        fn prop_reflected_polynomial_formatting(poly in 0u16..=0xFFFFu16) {
827
            let generated_keys = keys(16, poly as u64, true);
828
            let polynomial_key = generated_keys[8];
829
            let reversed = bit_reverse_16(poly);
830
            let expected = ((reversed as u64) << 1) | 1;
831
            prop_assert_eq!(
832
                polynomial_key, expected,
833
                "Reflected polynomial formatting failed for poly=0x{:04X}: expected 0x{:016X}, got 0x{:016X}",
834
                poly, expected, polynomial_key
835
            );
836
        }
837
    }
838
}