comparison cutils/crcmod/python3/test.py @ 180:d038f0a9ba49

Vendored crcmod2 into sub-package "cutils.crcmod" as pure-Python implementation of additional "digests": CRC sums. Running python -m cutils.crcmod.test works successfully.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 13 Jan 2025 04:09:35 +0100
parents
children 5c5c0c5a7402
comparison
equal deleted inserted replaced
179:53614a724bf0 180:d038f0a9ba49
1 #-----------------------------------------------------------------------------
2 # Copyright (c) 2010 Raymond L. Buvel
3 # Copyright (c) 2010 Craig McQueen
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 # SOFTWARE.
22 #-----------------------------------------------------------------------------
23 '''Unit tests for crcmod functionality'''
24
25
26 import unittest
27
28 from array import array
29 import binascii
30
31 from .crcmod import mkCrcFun, Crc
32 from .crcmod import _usingExtension
33 from .predefined import PredefinedCrc
34 from .predefined import mkPredefinedCrcFun
35 from .predefined import _crc_definitions as _predefined_crc_definitions
36
37
38 #-----------------------------------------------------------------------------
39 # This polynomial was chosen because it is the product of two irreducible
40 # polynomials.
41 # g8 = (x^7+x+1)*(x+1)
42 g8 = 0x185
43
44 #-----------------------------------------------------------------------------
45 # The following reproduces all of the entries in the Numerical Recipes table.
46 # This is the standard CCITT polynomial.
47 g16 = 0x11021
48
49 #-----------------------------------------------------------------------------
50 g24 = 0x15D6DCB
51
52 #-----------------------------------------------------------------------------
53 # This is the standard AUTODIN-II polynomial which appears to be used in a
54 # wide variety of standards and applications.
55 g32 = 0x104C11DB7
56
57
58 #-----------------------------------------------------------------------------
59 # I was able to locate a couple of 64-bit polynomials on the web. To make it
60 # easier to input the representation, define a function that builds a
61 # polynomial from a list of the bits that need to be turned on.
62
63 def polyFromBits(bits):
64 p = 0
65 for n in bits:
66 p = p | (1 << n)
67 return p
68
69 # The following is from the paper "An Improved 64-bit Cyclic Redundancy Check
70 # for Protein Sequences" by David T. Jones
71
72 g64a = polyFromBits([64, 63, 61, 59, 58, 56, 55, 52, 49, 48, 47, 46, 44, 41,
73 37, 36, 34, 32, 31, 28, 26, 23, 22, 19, 16, 13, 12, 10, 9, 6, 4,
74 3, 0])
75
76 # The following is from Standard ECMA-182 "Data Interchange on 12,7 mm 48-Track
77 # Magnetic Tape Cartridges -DLT1 Format-", December 1992.
78
79 g64b = polyFromBits([64, 62, 57, 55, 54, 53, 52, 47, 46, 45, 40, 39, 38, 37,
80 35, 33, 32, 31, 29, 27, 24, 23, 22, 21, 19, 17, 13, 12, 10, 9, 7,
81 4, 1, 0])
82
83 #-----------------------------------------------------------------------------
84 # This class is used to check the CRC calculations against a direct
85 # implementation using polynomial division.
86
87 class poly:
88 '''Class implementing polynomials over the field of integers mod 2'''
89 def __init__(self,p):
90 p = int(p)
91 if p < 0: raise ValueError('invalid polynomial')
92 self.p = p
93
94 def __int__(self):
95 return self.p
96
97 def __eq__(self,other):
98 return self.p == other.p
99
100 def __ne__(self,other):
101 return self.p != other.p
102
103 # To allow sorting of polynomials, use their long integer form for
104 # comparison
105 def __cmp__(self,other):
106 return cmp(self.p, other.p)
107
108 def __bool__(self):
109 return self.p != 0
110
111 def __neg__(self):
112 return self # These polynomials are their own inverse under addition
113
114 def __invert__(self):
115 n = max(self.deg() + 1, 1)
116 x = (1 << n) - 1
117 return poly(self.p ^ x)
118
119 def __add__(self,other):
120 return poly(self.p ^ other.p)
121
122 def __sub__(self,other):
123 return poly(self.p ^ other.p)
124
125 def __mul__(self,other):
126 a = self.p
127 b = other.p
128 if a == 0 or b == 0: return poly(0)
129 x = 0
130 while b:
131 if b&1:
132 x = x ^ a
133 a = a<<1
134 b = b>>1
135 return poly(x)
136
137 def __divmod__(self,other):
138 u = self.p
139 m = self.deg()
140 v = other.p
141 n = other.deg()
142 if v == 0: raise ZeroDivisionError('polynomial division by zero')
143 if n == 0: return (self,poly(0))
144 if m < n: return (poly(0),self)
145 k = m-n
146 a = 1 << m
147 v = v << k
148 q = 0
149 while k > 0:
150 if a & u:
151 u = u ^ v
152 q = q | 1
153 q = q << 1
154 a = a >> 1
155 v = v >> 1
156 k -= 1
157 if a & u:
158 u = u ^ v
159 q = q | 1
160 return (poly(q),poly(u))
161
162 def __div__(self,other):
163 return self.__divmod__(other)[0]
164
165 def __mod__(self,other):
166 return self.__divmod__(other)[1]
167
168 def __repr__(self):
169 return 'poly(0x%XL)' % self.p
170
171 def __str__(self):
172 p = self.p
173 if p == 0: return '0'
174 lst = { 0:[], 1:['1'], 2:['x'], 3:['1','x'] }[p&3]
175 p = p>>2
176 n = 2
177 while p:
178 if p&1: lst.append('x^%d' % n)
179 p = p>>1
180 n += 1
181 lst.reverse()
182 return '+'.join(lst)
183
184 def deg(self):
185 '''return the degree of the polynomial'''
186 a = self.p
187 if a == 0: return -1
188 n = 0
189 while a >= 0x10000:
190 n += 16
191 a = a >> 16
192 a = int(a)
193 while a > 1:
194 n += 1
195 a = a >> 1
196 return n
197
198 #-----------------------------------------------------------------------------
199 # The following functions compute the CRC using direct polynomial division.
200 # These functions are checked against the result of the table driven
201 # algorithms.
202
203 g8p = poly(g8)
204 x8p = poly(1<<8)
205 def crc8p(d):
206 p = 0
207 for i in d:
208 p = p*256 + i
209 p = poly(p)
210 return int(p*x8p%g8p)
211
212 g16p = poly(g16)
213 x16p = poly(1<<16)
214 def crc16p(d):
215 p = 0
216 for i in d:
217 p = p*256 + i
218 p = poly(p)
219 return int(p*x16p%g16p)
220
221 g24p = poly(g24)
222 x24p = poly(1<<24)
223 def crc24p(d):
224 p = 0
225 for i in d:
226 p = p*256 + i
227 p = poly(p)
228 return int(p*x24p%g24p)
229
230 g32p = poly(g32)
231 x32p = poly(1<<32)
232 def crc32p(d):
233 p = 0
234 for i in d:
235 p = p*256 + i
236 p = poly(p)
237 return int(p*x32p%g32p)
238
239 g64ap = poly(g64a)
240 x64p = poly(1<<64)
241 def crc64ap(d):
242 p = 0
243 for i in d:
244 p = p*256 + i
245 p = poly(p)
246 return int(p*x64p%g64ap)
247
248 g64bp = poly(g64b)
249 def crc64bp(d):
250 p = 0
251 for i in d:
252 p = p*256 + i
253 p = poly(p)
254 return int(p*x64p%g64bp)
255
256
257 class KnownAnswerTests(unittest.TestCase):
258 test_messages = [
259 b'T',
260 b'CatMouse987654321',
261 ]
262
263 known_answers = [
264 [ (g8,0,0), (0xFE, 0x9D) ],
265 [ (g8,-1,1), (0x4F, 0x9B) ],
266 [ (g8,0,1), (0xFE, 0x62) ],
267 [ (g16,0,0), (0x1A71, 0xE556) ],
268 [ (g16,-1,1), (0x1B26, 0xF56E) ],
269 [ (g16,0,1), (0x14A1, 0xC28D) ],
270 [ (g24,0,0), (0xBCC49D, 0xC4B507) ],
271 [ (g24,-1,1), (0x59BD0E, 0x0AAA37) ],
272 [ (g24,0,1), (0xD52B0F, 0x1523AB) ],
273 [ (g32,0,0), (0x6B93DDDB, 0x12DCA0F4) ],
274 [ (g32,0xFFFFFFFF,1), (0x41FB859F, 0xF7B400A7) ],
275 [ (g32,0,1), (0x6C0695ED, 0xC1A40EE5) ],
276 [ (g32,0,1,0xFFFFFFFF), (0xBE047A60, 0x084BFF58) ],
277 ]
278
279 def test_known_answers(self):
280 for crcfun_params, v in self.known_answers:
281 crcfun = mkCrcFun(*crcfun_params)
282 self.assertEqual(crcfun(b'',0), 0, "Wrong answer for CRC parameters %s, input ''" % (crcfun_params,))
283 for i, msg in enumerate(self.test_messages):
284 self.assertEqual(crcfun(msg), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg))
285 self.assertEqual(crcfun(msg[4:], crcfun(msg[:4])), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg))
286 self.assertEqual(crcfun(msg[-1:], crcfun(msg[:-1])), v[i], "Wrong answer for CRC parameters %s, input '%s'" % (crcfun_params,msg))
287
288
289 class CompareReferenceCrcTest(unittest.TestCase):
290 test_messages = [
291 b'',
292 b'T',
293 b'123456789',
294 b'CatMouse987654321',
295 ]
296
297 test_poly_crcs = [
298 [ (g8,0,0), crc8p ],
299 [ (g16,0,0), crc16p ],
300 [ (g24,0,0), crc24p ],
301 [ (g32,0,0), crc32p ],
302 [ (g64a,0,0), crc64ap ],
303 [ (g64b,0,0), crc64bp ],
304 ]
305
306 @staticmethod
307 def reference_crc32(d, crc=0):
308 """This function modifies the return value of binascii.crc32
309 to be an unsigned 32-bit value. I.e. in the range 0 to 2**32-1."""
310 # Work around the future warning on constants.
311 if crc > 0x7FFFFFFF:
312 x = int(crc & 0x7FFFFFFF)
313 crc = x | -2147483648
314 x = binascii.crc32(d,crc)
315 return int(x) & 0xFFFFFFFF
316
317 def test_compare_crc32(self):
318 """The binascii module has a 32-bit CRC function that is used in a wide range
319 of applications including the checksum used in the ZIP file format.
320 This test compares the CRC-32 implementation of this crcmod module to
321 that of binascii.crc32."""
322 # The following function should produce the same result as
323 # self.reference_crc32 which is derived from binascii.crc32.
324 crc32 = mkCrcFun(g32,0,1,0xFFFFFFFF)
325
326 for msg in self.test_messages:
327 self.assertEqual(crc32(msg), self.reference_crc32(msg))
328
329 def test_compare_poly(self):
330 """Compare various CRCs of this crcmod module to a pure
331 polynomial-based implementation."""
332 for crcfun_params, crc_poly_fun in self.test_poly_crcs:
333 # The following function should produce the same result as
334 # the associated polynomial CRC function.
335 crcfun = mkCrcFun(*crcfun_params)
336
337 for msg in self.test_messages:
338 self.assertEqual(crcfun(msg), crc_poly_fun(msg))
339
340
341 class CrcClassTest(unittest.TestCase):
342 """Verify the Crc class"""
343
344 msg = b'CatMouse987654321'
345
346 def test_simple_crc32_class(self):
347 """Verify the CRC class when not using xorOut"""
348 crc = Crc(g32)
349
350 str_rep = \
351 '''poly = 0x104C11DB7
352 reverse = True
353 initCrc = 0xFFFFFFFF
354 xorOut = 0x00000000
355 crcValue = 0xFFFFFFFF'''
356 self.assertEqual(str(crc), str_rep)
357 self.assertEqual(crc.digest(), b'\xff\xff\xff\xff')
358 self.assertEqual(crc.hexdigest(), 'FFFFFFFF')
359
360 crc.update(self.msg)
361 self.assertEqual(crc.crcValue, 0xF7B400A7)
362 self.assertEqual(crc.digest(), b'\xf7\xb4\x00\xa7')
363 self.assertEqual(crc.hexdigest(), 'F7B400A7')
364
365 # Verify the .copy() method
366 x = crc.copy()
367 self.assertTrue(x is not crc)
368 str_rep = \
369 '''poly = 0x104C11DB7
370 reverse = True
371 initCrc = 0xFFFFFFFF
372 xorOut = 0x00000000
373 crcValue = 0xF7B400A7'''
374 self.assertEqual(str(crc), str_rep)
375 self.assertEqual(str(x), str_rep)
376
377 def test_full_crc32_class(self):
378 """Verify the CRC class when using xorOut"""
379
380 crc = Crc(g32, initCrc=0, xorOut= ~0)
381
382 str_rep = \
383 '''poly = 0x104C11DB7
384 reverse = True
385 initCrc = 0x00000000
386 xorOut = 0xFFFFFFFF
387 crcValue = 0x00000000'''
388 self.assertEqual(str(crc), str_rep)
389 self.assertEqual(crc.digest(), b'\x00\x00\x00\x00')
390 self.assertEqual(crc.hexdigest(), '00000000')
391
392 crc.update(self.msg)
393 self.assertEqual(crc.crcValue, 0x84BFF58)
394 self.assertEqual(crc.digest(), b'\x08\x4b\xff\x58')
395 self.assertEqual(crc.hexdigest(), '084BFF58')
396
397 # Verify the .copy() method
398 x = crc.copy()
399 self.assertTrue(x is not crc)
400 str_rep = \
401 '''poly = 0x104C11DB7
402 reverse = True
403 initCrc = 0x00000000
404 xorOut = 0xFFFFFFFF
405 crcValue = 0x084BFF58'''
406 self.assertEqual(str(crc), str_rep)
407 self.assertEqual(str(x), str_rep)
408
409 # Verify the .new() method
410 y = crc.new()
411 self.assertTrue(y is not crc)
412 self.assertTrue(y is not x)
413 str_rep = \
414 '''poly = 0x104C11DB7
415 reverse = True
416 initCrc = 0x00000000
417 xorOut = 0xFFFFFFFF
418 crcValue = 0x00000000'''
419 self.assertEqual(str(y), str_rep)
420
421
422 class PredefinedCrcTest(unittest.TestCase):
423 """Verify the predefined CRCs"""
424
425 test_messages_for_known_answers = [
426 b'', # Test cases below depend on this first entry being the empty string.
427 b'T',
428 b'CatMouse987654321',
429 ]
430
431 known_answers = [
432 [ 'crc-aug-ccitt', (0x1D0F, 0xD6ED, 0x5637) ],
433 [ 'x-25', (0x0000, 0xE4D9, 0x0A91) ],
434 [ 'crc-32', (0x00000000, 0xBE047A60, 0x084BFF58) ],
435 ]
436
437 def test_known_answers(self):
438 for crcfun_name, v in self.known_answers:
439 crcfun = mkPredefinedCrcFun(crcfun_name)
440 self.assertEqual(crcfun(b'',0), 0, "Wrong answer for CRC '%s', input ''" % crcfun_name)
441 for i, msg in enumerate(self.test_messages_for_known_answers):
442 self.assertEqual(crcfun(msg), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg))
443 self.assertEqual(crcfun(msg[4:], crcfun(msg[:4])), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg))
444 self.assertEqual(crcfun(msg[-1:], crcfun(msg[:-1])), v[i], "Wrong answer for CRC %s, input '%s'" % (crcfun_name,msg))
445
446 def test_class_with_known_answers(self):
447 for crcfun_name, v in self.known_answers:
448 for i, msg in enumerate(self.test_messages_for_known_answers):
449 crc1 = PredefinedCrc(crcfun_name)
450 crc1.update(msg)
451 self.assertEqual(crc1.crcValue, v[i], "Wrong answer for crc1 %s, input '%s'" % (crcfun_name,msg))
452
453 crc2 = crc1.new()
454 # Check that crc1 maintains its same value, after .new() call.
455 self.assertEqual(crc1.crcValue, v[i], "Wrong state for crc1 %s, input '%s'" % (crcfun_name,msg))
456 # Check that the new class instance created by .new() contains the initialisation value.
457 # This depends on the first string in self.test_messages_for_known_answers being
458 # the empty string.
459 self.assertEqual(crc2.crcValue, v[0], "Wrong state for crc2 %s, input '%s'" % (crcfun_name,msg))
460
461 crc2.update(msg)
462 # Check that crc1 maintains its same value, after crc2 has called .update()
463 self.assertEqual(crc1.crcValue, v[i], "Wrong state for crc1 %s, input '%s'" % (crcfun_name,msg))
464 # Check that crc2 contains the right value after calling .update()
465 self.assertEqual(crc2.crcValue, v[i], "Wrong state for crc2 %s, input '%s'" % (crcfun_name,msg))
466
467 def test_function_predefined_table(self):
468 for table_entry in _predefined_crc_definitions:
469 # Check predefined function
470 crc_func = mkPredefinedCrcFun(table_entry['name'])
471 calc_value = crc_func(b"123456789")
472 self.assertEqual(calc_value, table_entry['check'], "Wrong answer for CRC '%s'" % table_entry['name'])
473
474 def test_class_predefined_table(self):
475 for table_entry in _predefined_crc_definitions:
476 # Check predefined class
477 crc1 = PredefinedCrc(table_entry['name'])
478 crc1.update(b"123456789")
479 self.assertEqual(crc1.crcValue, table_entry['check'], "Wrong answer for CRC '%s'" % table_entry['name'])
480
481
482 class InputTypesTest(unittest.TestCase):
483 """Check the various input types that CRC functions can accept."""
484
485 msg = b'CatMouse987654321'
486
487 check_crc_names = [
488 'crc-aug-ccitt',
489 'x-25',
490 'crc-32',
491 ]
492
493 array_check_types = [
494 'B',
495 'H',
496 'I',
497 'L',
498 ]
499
500 def test_bytearray_input(self):
501 """Test that bytearray inputs are accepted, as an example
502 of a type that implements the buffer protocol."""
503 for crc_name in self.check_crc_names:
504 crcfun = mkPredefinedCrcFun(crc_name)
505 for i in range(len(self.msg) + 1):
506 test_msg = self.msg[:i]
507 bytes_answer = crcfun(test_msg)
508 bytearray_answer = crcfun(bytearray(test_msg))
509 self.assertEqual(bytes_answer, bytearray_answer)
510
511 def test_array_input(self):
512 """Test that array inputs are accepted, as an example
513 of a type that implements the buffer protocol."""
514 for crc_name in self.check_crc_names:
515 crcfun = mkPredefinedCrcFun(crc_name)
516 for i in range(len(self.msg) + 1):
517 test_msg = self.msg[:i]
518 bytes_answer = crcfun(test_msg)
519 for array_type in self.array_check_types:
520 if i % array(array_type).itemsize == 0:
521 test_array = array(array_type, test_msg)
522 array_answer = crcfun(test_array)
523 self.assertEqual(bytes_answer, array_answer)
524
525 def test_unicode_input(self):
526 """Test that Unicode input raises TypeError"""
527 for crc_name in self.check_crc_names:
528 crcfun = mkPredefinedCrcFun(crc_name)
529 with self.assertRaises(TypeError):
530 crcfun("123456789")
531
532
533 def runtests():
534 print("Using extension:", _usingExtension)
535 print()
536 unittest.main()
537
538
539 if __name__ == '__main__':
540 runtests()