@@ -556,3 +556,128 @@ def timeout_override(
556556 finally :
557557 self ._port .timeout = saved_timeout
558558 self ._logger .debug (f"Restored timeout to { saved_timeout } " )
559+
560+
561+ # The GeoCOM protocol supports CRC-16 checksums in message exchanges.
562+ # From testing it appears it uses the ARC/IBM parameter set.
563+ #
564+ # Parameters and other info on various CRC versions:
565+ # https://reveng.sourceforge.io/crc-catalogue/16.htm#disclaimer
566+ #
567+ # Good reference article and calculator:
568+ # https://www.sunshine2k.de/articles/coding/crc/understanding_crc.html
569+ # https://www.sunshine2k.de/coding/javascript/crc/crc_js.html
570+ #
571+ # The implementations below are for this specific variant of CRC-16, not
572+ # general solutions. The methods were modified to take into account the
573+ # input and output reflection.
574+ def crc16_bitwise (value : str | bytes ) -> int :
575+ """
576+ Bitwise CRC-16/ARC calculation method.
577+
578+ Adapted from: https://stackoverflow.com/a/68095008
579+
580+ Parameters
581+ ----------
582+ value : str | bytes
583+ Data to digest.
584+
585+ Returns
586+ -------
587+ int
588+ CRC-16 checksum
589+ """
590+ crc = 0
591+
592+ if len (value ) == 0 :
593+ return crc
594+
595+ if isinstance (value , bytes ):
596+ data = value
597+ else :
598+ data = value .encode ("ascii" )
599+
600+ for char in data :
601+ crc ^= char
602+
603+ for _ in range (8 ):
604+ crc = (crc >> 1 ) ^ 0xa001 if (crc & 1 ) else (crc >> 1 )
605+
606+ return crc
607+
608+
609+ _crc16_table_reflected : list [int ] = [
610+ 0x0000 , 0xc0c1 , 0xc181 , 0x0140 , 0xc301 , 0x03c0 , 0x0280 , 0xc241 ,
611+ 0xc601 , 0x06c0 , 0x0780 , 0xc741 , 0x0500 , 0xc5c1 , 0xc481 , 0x0440 ,
612+ 0xcc01 , 0x0cc0 , 0x0d80 , 0xcd41 , 0x0f00 , 0xcfc1 , 0xce81 , 0x0e40 ,
613+ 0x0a00 , 0xcac1 , 0xcb81 , 0x0b40 , 0xc901 , 0x09c0 , 0x0880 , 0xc841 ,
614+ 0xd801 , 0x18c0 , 0x1980 , 0xd941 , 0x1b00 , 0xdbc1 , 0xda81 , 0x1a40 ,
615+ 0x1e00 , 0xdec1 , 0xdf81 , 0x1f40 , 0xdd01 , 0x1dc0 , 0x1c80 , 0xdc41 ,
616+ 0x1400 , 0xd4c1 , 0xd581 , 0x1540 , 0xd701 , 0x17c0 , 0x1680 , 0xd641 ,
617+ 0xd201 , 0x12c0 , 0x1380 , 0xd341 , 0x1100 , 0xd1c1 , 0xd081 , 0x1040 ,
618+ 0xf001 , 0x30c0 , 0x3180 , 0xf141 , 0x3300 , 0xf3c1 , 0xf281 , 0x3240 ,
619+ 0x3600 , 0xf6c1 , 0xf781 , 0x3740 , 0xf501 , 0x35c0 , 0x3480 , 0xf441 ,
620+ 0x3c00 , 0xfcc1 , 0xfd81 , 0x3d40 , 0xff01 , 0x3fc0 , 0x3e80 , 0xfe41 ,
621+ 0xfa01 , 0x3ac0 , 0x3b80 , 0xfb41 , 0x3900 , 0xf9c1 , 0xf881 , 0x3840 ,
622+ 0x2800 , 0xe8c1 , 0xe981 , 0x2940 , 0xeb01 , 0x2bc0 , 0x2a80 , 0xea41 ,
623+ 0xee01 , 0x2ec0 , 0x2f80 , 0xef41 , 0x2d00 , 0xedc1 , 0xec81 , 0x2c40 ,
624+ 0xe401 , 0x24c0 , 0x2580 , 0xe541 , 0x2700 , 0xe7c1 , 0xe681 , 0x2640 ,
625+ 0x2200 , 0xe2c1 , 0xe381 , 0x2340 , 0xe101 , 0x21c0 , 0x2080 , 0xe041 ,
626+ 0xa001 , 0x60c0 , 0x6180 , 0xa141 , 0x6300 , 0xa3c1 , 0xa281 , 0x6240 ,
627+ 0x6600 , 0xa6c1 , 0xa781 , 0x6740 , 0xa501 , 0x65c0 , 0x6480 , 0xa441 ,
628+ 0x6c00 , 0xacc1 , 0xad81 , 0x6d40 , 0xaf01 , 0x6fc0 , 0x6e80 , 0xae41 ,
629+ 0xaa01 , 0x6ac0 , 0x6b80 , 0xab41 , 0x6900 , 0xa9c1 , 0xa881 , 0x6840 ,
630+ 0x7800 , 0xb8c1 , 0xb981 , 0x7940 , 0xbb01 , 0x7bc0 , 0x7a80 , 0xba41 ,
631+ 0xbe01 , 0x7ec0 , 0x7f80 , 0xbf41 , 0x7d00 , 0xbdc1 , 0xbc81 , 0x7c40 ,
632+ 0xb401 , 0x74c0 , 0x7580 , 0xb541 , 0x7700 , 0xb7c1 , 0xb681 , 0x7640 ,
633+ 0x7200 , 0xb2c1 , 0xb381 , 0x7340 , 0xb101 , 0x71c0 , 0x7080 , 0xb041 ,
634+ 0x5000 , 0x90c1 , 0x9181 , 0x5140 , 0x9301 , 0x53c0 , 0x5280 , 0x9241 ,
635+ 0x9601 , 0x56c0 , 0x5780 , 0x9741 , 0x5500 , 0x95c1 , 0x9481 , 0x5440 ,
636+ 0x9c01 , 0x5cc0 , 0x5d80 , 0x9d41 , 0x5f00 , 0x9fc1 , 0x9e81 , 0x5e40 ,
637+ 0x5a00 , 0x9ac1 , 0x9b81 , 0x5b40 , 0x9901 , 0x59c0 , 0x5880 , 0x9841 ,
638+ 0x8801 , 0x48c0 , 0x4980 , 0x8941 , 0x4b00 , 0x8bc1 , 0x8a81 , 0x4a40 ,
639+ 0x4e00 , 0x8ec1 , 0x8f81 , 0x4f40 , 0x8d01 , 0x4dc0 , 0x4c80 , 0x8c41 ,
640+ 0x4400 , 0x84c1 , 0x8581 , 0x4540 , 0x8701 , 0x47c0 , 0x4680 , 0x8641 ,
641+ 0x8201 , 0x42c0 , 0x4380 , 0x8341 , 0x4100 , 0x81c1 , 0x8081 , 0x4040
642+ ]
643+ """
644+ Reflected CRC-16/ARC lookup table for bytewise computation.
645+
646+ https://www.sunshine2k.de/coding/javascript/crc/crc_js.html
647+ """
648+
649+
650+ def crc16_bytewise (value : str | bytes ) -> int :
651+ """
652+ Bytewise CRC-16/ARC calculation method.
653+
654+ The method uses a precomputed lookup table to reduce the number of
655+ required operations to calculate a checksum.
656+
657+ Parameters
658+ ----------
659+ value : str | bytes
660+ Data to digest.
661+
662+ Returns
663+ -------
664+ int
665+ CRC-16 checksum
666+ """
667+ crc = 0
668+
669+ if len (value ) == 0 :
670+ return crc
671+
672+ if isinstance (value , bytes ):
673+ data : bytes = value
674+ else :
675+ data = value .encode ("ascii" )
676+
677+ for char in data :
678+ # Due to the arbirarily sized integers in Python, the index must be
679+ # masked to 8 bit
680+ idx = (char ^ (crc )) & 0xff
681+ crc = _crc16_table_reflected [idx ] ^ (crc >> 8 )
682+
683+ return crc
0 commit comments