8
8
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
9
9
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
10
10
//! Error detection code
11
+ //!
12
+ //! The checksum used by the RTCM protocol is CRC-24Q. This module provides a
13
+ //! function for calculating that checksum value over a set of data.
14
+ //!
15
+ //! # Examples
16
+ //!
17
+ //! To generate a CRC value in one shot you can simply give the [`compute_crc24q`]
18
+ //! function all of the data as a slice of bytes and the initial value:
19
+ //!
20
+ //! ```
21
+ //! # use swiftnav::edc::compute_crc24q;
22
+ //! let msg_data = vec![0xD3, 0x00, 0x13, 0x3E, 0xD7, 0xD3, 0x02, 0x02, 0x98, 0x0E, 0xDE, 0xEF, 0x34, 0xB4, 0xBD, 0x62, 0xAC, 0x09, 0x41, 0x98, 0x6F, 0x33];
23
+ //! let init = 0;
24
+ //!
25
+ //! let crc = compute_crc24q(&msg_data, init);
26
+ //! assert_eq!(crc, 0x00360B98);
27
+ //! ```
28
+ //!
29
+ //! If the data is split up you can use the CRC from a previous invokation as the initial
30
+ //! value for a subsequent invokation:
31
+ //!
32
+ //! ```
33
+ //! # use swiftnav::edc::compute_crc24q;
34
+ //! let block1 = vec![0xD3, 0x00, 0x13, 0x3E, 0xD7, 0xD3, 0x02, 0x02, 0x98, 0x0E];
35
+ //! let block2 = vec![0xDE, 0xEF, 0x34, 0xB4, 0xBD, 0x62, 0xAC, 0x09, 0x41, 0x98, 0x6F, 0x33];
36
+ //! let init = 0;
37
+ //!
38
+ //! let intermediate = compute_crc24q(&block1, init);
39
+ //! let crc = compute_crc24q(&block2, intermediate);
40
+ //! assert_eq!(crc, 0x00360B98);
41
+ //! ```
42
+
43
+ const CRC24Q_TABLE : [ u32 ; 256 ] = [
44
+ 0x000000 , 0x864CFB , 0x8AD50D , 0x0C99F6 , 0x93E6E1 , 0x15AA1A , 0x1933EC , 0x9F7F17 , 0xA18139 ,
45
+ 0x27CDC2 , 0x2B5434 , 0xAD18CF , 0x3267D8 , 0xB42B23 , 0xB8B2D5 , 0x3EFE2E , 0xC54E89 , 0x430272 ,
46
+ 0x4F9B84 , 0xC9D77F , 0x56A868 , 0xD0E493 , 0xDC7D65 , 0x5A319E , 0x64CFB0 , 0xE2834B , 0xEE1ABD ,
47
+ 0x685646 , 0xF72951 , 0x7165AA , 0x7DFC5C , 0xFBB0A7 , 0x0CD1E9 , 0x8A9D12 , 0x8604E4 , 0x00481F ,
48
+ 0x9F3708 , 0x197BF3 , 0x15E205 , 0x93AEFE , 0xAD50D0 , 0x2B1C2B , 0x2785DD , 0xA1C926 , 0x3EB631 ,
49
+ 0xB8FACA , 0xB4633C , 0x322FC7 , 0xC99F60 , 0x4FD39B , 0x434A6D , 0xC50696 , 0x5A7981 , 0xDC357A ,
50
+ 0xD0AC8C , 0x56E077 , 0x681E59 , 0xEE52A2 , 0xE2CB54 , 0x6487AF , 0xFBF8B8 , 0x7DB443 , 0x712DB5 ,
51
+ 0xF7614E , 0x19A3D2 , 0x9FEF29 , 0x9376DF , 0x153A24 , 0x8A4533 , 0x0C09C8 , 0x00903E , 0x86DCC5 ,
52
+ 0xB822EB , 0x3E6E10 , 0x32F7E6 , 0xB4BB1D , 0x2BC40A , 0xAD88F1 , 0xA11107 , 0x275DFC , 0xDCED5B ,
53
+ 0x5AA1A0 , 0x563856 , 0xD074AD , 0x4F0BBA , 0xC94741 , 0xC5DEB7 , 0x43924C , 0x7D6C62 , 0xFB2099 ,
54
+ 0xF7B96F , 0x71F594 , 0xEE8A83 , 0x68C678 , 0x645F8E , 0xE21375 , 0x15723B , 0x933EC0 , 0x9FA736 ,
55
+ 0x19EBCD , 0x8694DA , 0x00D821 , 0x0C41D7 , 0x8A0D2C , 0xB4F302 , 0x32BFF9 , 0x3E260F , 0xB86AF4 ,
56
+ 0x2715E3 , 0xA15918 , 0xADC0EE , 0x2B8C15 , 0xD03CB2 , 0x567049 , 0x5AE9BF , 0xDCA544 , 0x43DA53 ,
57
+ 0xC596A8 , 0xC90F5E , 0x4F43A5 , 0x71BD8B , 0xF7F170 , 0xFB6886 , 0x7D247D , 0xE25B6A , 0x641791 ,
58
+ 0x688E67 , 0xEEC29C , 0x3347A4 , 0xB50B5F , 0xB992A9 , 0x3FDE52 , 0xA0A145 , 0x26EDBE , 0x2A7448 ,
59
+ 0xAC38B3 , 0x92C69D , 0x148A66 , 0x181390 , 0x9E5F6B , 0x01207C , 0x876C87 , 0x8BF571 , 0x0DB98A ,
60
+ 0xF6092D , 0x7045D6 , 0x7CDC20 , 0xFA90DB , 0x65EFCC , 0xE3A337 , 0xEF3AC1 , 0x69763A , 0x578814 ,
61
+ 0xD1C4EF , 0xDD5D19 , 0x5B11E2 , 0xC46EF5 , 0x42220E , 0x4EBBF8 , 0xC8F703 , 0x3F964D , 0xB9DAB6 ,
62
+ 0xB54340 , 0x330FBB , 0xAC70AC , 0x2A3C57 , 0x26A5A1 , 0xA0E95A , 0x9E1774 , 0x185B8F , 0x14C279 ,
63
+ 0x928E82 , 0x0DF195 , 0x8BBD6E , 0x872498 , 0x016863 , 0xFAD8C4 , 0x7C943F , 0x700DC9 , 0xF64132 ,
64
+ 0x693E25 , 0xEF72DE , 0xE3EB28 , 0x65A7D3 , 0x5B59FD , 0xDD1506 , 0xD18CF0 , 0x57C00B , 0xC8BF1C ,
65
+ 0x4EF3E7 , 0x426A11 , 0xC426EA , 0x2AE476 , 0xACA88D , 0xA0317B , 0x267D80 , 0xB90297 , 0x3F4E6C ,
66
+ 0x33D79A , 0xB59B61 , 0x8B654F , 0x0D29B4 , 0x01B042 , 0x87FCB9 , 0x1883AE , 0x9ECF55 , 0x9256A3 ,
67
+ 0x141A58 , 0xEFAAFF , 0x69E604 , 0x657FF2 , 0xE33309 , 0x7C4C1E , 0xFA00E5 , 0xF69913 , 0x70D5E8 ,
68
+ 0x4E2BC6 , 0xC8673D , 0xC4FECB , 0x42B230 , 0xDDCD27 , 0x5B81DC , 0x57182A , 0xD154D1 , 0x26359F ,
69
+ 0xA07964 , 0xACE092 , 0x2AAC69 , 0xB5D37E , 0x339F85 , 0x3F0673 , 0xB94A88 , 0x87B4A6 , 0x01F85D ,
70
+ 0x0D61AB , 0x8B2D50 , 0x145247 , 0x921EBC , 0x9E874A , 0x18CBB1 , 0xE37B16 , 0x6537ED , 0x69AE1B ,
71
+ 0xEFE2E0 , 0x709DF7 , 0xF6D10C , 0xFA48FA , 0x7C0401 , 0x42FA2F , 0xC4B6D4 , 0xC82F22 , 0x4E63D9 ,
72
+ 0xD11CCE , 0x575035 , 0x5BC9C3 , 0xDD8538 ,
73
+ ] ;
11
74
12
75
/// Calculate Qualcomm 24-bit Cyclical Redundancy Check (CRC-24Q).
13
76
///
20
83
/// ]$$
21
84
///
22
85
/// Mask 0x1864CFB, not reversed, not XOR'd
86
+ ///
87
+ /// # Notes
88
+ ///
89
+ /// Only the lower 24 bits of the initial value are used!
90
+ #[ must_use]
23
91
pub fn compute_crc24q ( buf : & [ u8 ] , initial_value : u32 ) -> u32 {
24
- unsafe { swiftnav_sys:: crc24q ( buf. as_ptr ( ) , buf. len ( ) as u32 , initial_value) }
92
+ let mut crc = initial_value & 0xFFFFFF ;
93
+ for & byte in buf {
94
+ let index = ( ( crc >> 16 ) ^ byte as u32 ) as usize & 0xFF ;
95
+ crc = ( ( crc << 8 ) & 0xFFFFFF ) ^ CRC24Q_TABLE [ index] ;
96
+ }
97
+ crc
25
98
}
26
99
27
100
#[ cfg( test) ]
28
101
mod tests {
102
+ use super :: * ;
103
+ use proptest:: prelude:: * ;
104
+
29
105
const TEST_DATA : & [ u8 ] = "123456789" . as_bytes ( ) ;
30
106
107
+ /// Helper function to append a CRC-24Q value as 3 bytes (big-endian) to a buffer
108
+ fn append_crc24q ( data : & mut Vec < u8 > , crc : u32 ) {
109
+ data. push ( ( crc >> 16 ) as u8 ) ;
110
+ data. push ( ( crc >> 8 ) as u8 ) ;
111
+ data. push ( crc as u8 ) ;
112
+ }
113
+
114
+ /// Helper function to flip a single bit in the data at the given bit position
115
+ fn flip_bit ( data : & mut [ u8 ] , bit_position : usize ) {
116
+ if !data. is_empty ( ) {
117
+ let byte_index = ( bit_position / 8 ) % data. len ( ) ;
118
+ let bit_index = bit_position % 8 ;
119
+ data[ byte_index] ^= 1 << bit_index;
120
+ }
121
+ }
122
+
31
123
#[ test]
32
- fn crc24q ( ) {
33
- let crc = super :: compute_crc24q ( & TEST_DATA [ 0 ..0 ] , 0 ) ;
124
+ fn test_crc24q ( ) {
125
+ let crc = compute_crc24q ( & TEST_DATA [ 0 ..0 ] , 0 ) ;
34
126
assert ! (
35
127
crc == 0 ,
36
128
"CRC of empty buffer with starting value 0 should be 0, not {}" ,
37
129
crc
38
130
) ;
39
131
40
- let crc = super :: compute_crc24q ( & TEST_DATA [ 0 ..0 ] , 22 ) ;
132
+ let crc = compute_crc24q ( & TEST_DATA [ 0 ..0 ] , 22 ) ;
41
133
assert ! (
42
134
crc == 22 ,
43
135
"CRC of empty buffer with starting value 22 should be 22, not {}" ,
@@ -46,11 +138,126 @@ mod tests {
46
138
47
139
/* Test value taken from python crcmod package tests, see:
48
140
* http://crcmod.sourceforge.net/crcmod.predefined.html */
49
- let crc = super :: compute_crc24q ( TEST_DATA , 0xB704CE ) ;
141
+ let crc = compute_crc24q ( TEST_DATA , 0xB704CE ) ;
50
142
assert ! (
51
143
crc == 0x21CF02 ,
52
- "CRC of \" 123456789\" with init value 0xB704CE should be {} , not 0x%06X " ,
144
+ "CRC of \" 123456789\" with init value 0xB704CE should be 0x21CF02 , not {} " ,
53
145
crc
54
146
) ;
55
147
}
148
+
149
+ // Property-based tests using proptest
150
+ proptest ! {
151
+ #![ proptest_config( ProptestConfig :: with_cases( 1000 ) ) ]
152
+
153
+ /// Property: Appending the CRC to data and recalculating should yield zero.
154
+ /// This is the fundamental property used for error detection in protocols.
155
+ #[ test]
156
+ fn prop_crc_append_yields_zero( data in prop:: collection:: vec( any:: <u8 >( ) , 0 ..1000 ) ) {
157
+ let crc = compute_crc24q( & data, 0 ) ;
158
+ let mut data_with_crc = data. clone( ) ;
159
+ append_crc24q( & mut data_with_crc, crc) ;
160
+
161
+ let verification_crc = compute_crc24q( & data_with_crc, 0 ) ;
162
+ prop_assert_eq!( verification_crc, 0 ,
163
+ "CRC of data with appended CRC should be 0, got 0x{:06X} for data length {}" ,
164
+ verification_crc, data. len( ) ) ;
165
+ }
166
+
167
+ /// Property: CRC calculation is deterministic - same input always produces same output.
168
+ #[ test]
169
+ fn prop_crc_is_deterministic( data in prop:: collection:: vec( any:: <u8 >( ) , 0 ..1000 ) , init in any:: <u32 >( ) ) {
170
+ let crc1 = compute_crc24q( & data, init) ;
171
+ let crc2 = compute_crc24q( & data, init) ;
172
+ prop_assert_eq!( crc1, crc2, "CRC calculation should be deterministic" ) ;
173
+ }
174
+
175
+ /// Property: CRC result always stays within 24-bit bounds (0x000000 to 0xFFFFFF).
176
+ #[ test]
177
+ fn prop_crc_stays_within_24_bits( data in prop:: collection:: vec( any:: <u8 >( ) , 0 ..1000 ) , init in any:: <u32 >( ) ) {
178
+ let crc = compute_crc24q( & data, init) ;
179
+ prop_assert!( crc <= 0xFFFFFF , "CRC result 0x{:08X} exceeds 24-bit maximum" , crc) ;
180
+ }
181
+
182
+ /// Property: Incremental CRC calculation equals full calculation.
183
+ /// CRC(data1 + data2) should equal CRC(data2, initial=CRC(data1))
184
+ #[ test]
185
+ fn prop_crc_incremental_calculation(
186
+ data1 in prop:: collection:: vec( any:: <u8 >( ) , 0 ..500 ) ,
187
+ data2 in prop:: collection:: vec( any:: <u8 >( ) , 0 ..500 ) ,
188
+ init in any:: <u32 >( )
189
+ ) {
190
+ // Calculate CRC on combined data
191
+ let mut combined_data = data1. clone( ) ;
192
+ combined_data. extend_from_slice( & data2) ;
193
+ let full_crc = compute_crc24q( & combined_data, init) ;
194
+
195
+ // Calculate CRC incrementally
196
+ let intermediate_crc = compute_crc24q( & data1, init) ;
197
+ let incremental_crc = compute_crc24q( & data2, intermediate_crc) ;
198
+
199
+ prop_assert_eq!( full_crc, incremental_crc,
200
+ "Incremental CRC calculation should match full calculation" ) ;
201
+ }
202
+
203
+ /// Property: Initial values are properly masked to 24 bits.
204
+ /// init and (init & 0xFFFFFF) should produce the same result.
205
+ #[ test]
206
+ fn prop_crc_initial_value_masked( data in prop:: collection:: vec( any:: <u8 >( ) , 0 ..100 ) , init in any:: <u32 >( ) ) {
207
+ let crc1 = compute_crc24q( & data, init) ;
208
+ let crc2 = compute_crc24q( & data, init & 0xFFFFFF ) ;
209
+ prop_assert_eq!( crc1, crc2,
210
+ "CRC with init 0x{:08X} should equal CRC with masked init 0x{:06X}" ,
211
+ init, init & 0xFFFFFF ) ;
212
+ }
213
+
214
+ /// Property: Single bit errors are detected (CRC changes).
215
+ /// Flipping any single bit in non-empty data should change the CRC.
216
+ #[ test]
217
+ fn prop_crc_detects_single_bit_errors(
218
+ mut data in prop:: collection:: vec( any:: <u8 >( ) , 1 ..100 ) ,
219
+ bit_position in any:: <usize >( ) ,
220
+ init in any:: <u32 >( )
221
+ ) {
222
+ let original_crc = compute_crc24q( & data, init) ;
223
+ flip_bit( & mut data, bit_position) ;
224
+ let modified_crc = compute_crc24q( & data, init) ;
225
+
226
+ prop_assert_ne!( original_crc, modified_crc,
227
+ "CRC should change when a bit is flipped (original: 0x{:06X}, modified: 0x{:06X})" ,
228
+ original_crc, modified_crc) ;
229
+ }
230
+
231
+ /// Property: CRC calculation is associative when split into arbitrary chunks.
232
+ #[ test]
233
+ fn prop_crc_associative_chunks(
234
+ data in prop:: collection:: vec( any:: <u8 >( ) , 1 ..200 ) ,
235
+ chunk_sizes in prop:: collection:: vec( 1usize ..50 , 1 ..10 ) ,
236
+ init in any:: <u32 >( )
237
+ ) {
238
+ // Calculate CRC on full data
239
+ let full_crc = compute_crc24q( & data, init) ;
240
+
241
+ // Calculate CRC in chunks
242
+ let mut current_crc = init;
243
+ let mut pos = 0 ;
244
+
245
+ for & chunk_size in & chunk_sizes {
246
+ if pos >= data. len( ) {
247
+ break ;
248
+ }
249
+ let end = std:: cmp:: min( pos + chunk_size, data. len( ) ) ;
250
+ current_crc = compute_crc24q( & data[ pos..end] , current_crc) ;
251
+ pos = end;
252
+ }
253
+
254
+ // Process any remaining data
255
+ if pos < data. len( ) {
256
+ current_crc = compute_crc24q( & data[ pos..] , current_crc) ;
257
+ }
258
+
259
+ prop_assert_eq!( full_crc, current_crc,
260
+ "CRC calculated in chunks should match full calculation" ) ;
261
+ }
262
+ }
56
263
}
0 commit comments