Source code for crccheck.base

""" Base class for CRC and checksum classes.

  License::

    MIT License

    Copyright (c) 2015-2022 by Martin Scharrer <martin.scharrer@web.de>

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software
    and associated documentation files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use, copy, modify, merge, publish,
    distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or
    substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
    BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
    DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
import math

REFLECT_BIT_ORDER_TABLE = (
    0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
    0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
    0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
    0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
    0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
    0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
    0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
    0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
    0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
    0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
    0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
    0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
    0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
    0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
    0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
    0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
    0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
    0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
    0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
    0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
    0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
    0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
    0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
    0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
    0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
    0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
    0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
    0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
    0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
    0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
    0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
    0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
)


[docs]def reflectbitorder(width, value): """ Reflects the bit order of the given value according to the given bit width. Args: width (int): bitwidth value (int): value to reflect """ binstr = ("0" * width + bin(value)[2:])[-width:] return int(binstr[::-1], 2)
[docs]class CrccheckError(Exception): """General checksum error exception""" pass
[docs]class CrccheckBase(object): """ Abstract base class for checksumming classes. Args: initvalue (int): Initial value. If None then the default value for the class is used. """ _initvalue = 0x00 _check_result = None _check_data = None _width = 0 def __init__(self, initvalue=None, **kwargs): if initvalue is None: self._value = self._initvalue else: self._value = initvalue
[docs] @classmethod def initvalue(cls): """Getter for initvalue.""" return cls._initvalue
[docs] @classmethod def check_result(cls): """Getter for check_result.""" return cls._check_result
[docs] @classmethod def check_data(cls): """Getter for check_data.""" return cls._check_data
[docs] @classmethod def width(cls): return cls._width
[docs] @classmethod def bytewidth(cls): return (cls._width + 7) // 8
[docs] def reset(self, value=None): """ Reset instance. Resets the instance state to the initial value. This is not required for a just created instance. Args: value (int): Set internal value. If None then the default initial value for the class is used. Returns: self """ if value is None: self._value = self._initvalue else: self._value = value return self
[docs] def process(self, data): """ Process given data. Args: data (bytes, bytearray or list of ints [0-255]): input data to process. Returns: self """ raise NotImplementedError
[docs] def final(self): """Return final check value. The internal state is not modified by this so further data can be processed afterwards. Return: int: final value """ return self._value
[docs] def finalhex(self, byteorder='big'): """Return final checksum value as hexadecimal string (without leading "0x"). The hex value is zero padded to bitwidth/8. The internal state is not modified by this so further data can be processed afterwards. Return: str: final value as hex string without leading '0x'. """ asbytes = self.finalbytes(byteorder) try: # bytearray.hex() is new in Python 3.5 return asbytes.hex() except AttributeError: # pragma: no cover return "".join(["{:02x}".format(b) for b in asbytes])
[docs] def finalbytes(self, byteorder='big'): """Return final checksum value as bytes. The internal state is not modified by this so further data can be processed afterwards. Return: bytes: final value as bytes """ bytelength = int(math.ceil(self._width / 8.0)) asint = self.final() try: # int.to_bytes() is new in Python 3.2 return asint.to_bytes(bytelength, byteorder) except AttributeError: # pragma: no cover asbytes = bytearray(bytelength) for i in range(0, bytelength): asbytes[i] = asint & 0xFF asint >>= 8 if byteorder == 'big': asbytes.reverse() return asbytes
[docs] def value(self): """Returns current intermediate value. Note that in general final() must be used to get the final value. Return: int: current value """ return self._value
[docs] @classmethod def calc(cls, data, initvalue=None, **kwargs): """ Fully calculate CRC/checksum over given data. Args: data (bytes, bytearray or list of ints [0-255]): input data to process. initvalue (int): Initial value. If None then the default value for the class is used. Return: int: final value """ inst = cls(initvalue, **kwargs) inst.process(data) return inst.final()
[docs] @classmethod def calchex(cls, data, initvalue=None, byteorder='big', **kwargs): """Fully calculate checksum over given data. Return result as hex string. Args: data (bytes, bytearray or list of ints [0-255]): input data to process. initvalue (int): Initial value. If None then the default value for the class is used. byteorder ('big' or 'little'): order (endianness) of returned bytes. Return: str: final value as hex string without leading '0x'. """ inst = cls(initvalue, **kwargs) inst.process(data) return inst.finalhex(byteorder)
[docs] @classmethod def calcbytes(cls, data, initvalue=None, byteorder='big', **kwargs): """Fully calculate checksum over given data. Return result as bytearray. Args: data (bytes, bytearray or list of ints [0-255]): input data to process. initvalue (int): Initial value. If None then the default value for the class is used. byteorder ('big' or 'little'): order (endianness) of returned bytes. Return: bytes: final value as bytes """ inst = cls(initvalue, **kwargs) inst.process(data) return inst.finalbytes(byteorder)
[docs] @classmethod def selftest(cls, data=None, expectedresult=None, **kwargs): """ Selftest method for automated tests. Args: data (bytes, bytearray or list of int [0-255]): data to process expectedresult (int): expected result Raises: CrccheckError: if result is not as expected """ if data is None: data = cls._check_data expectedresult = cls._check_result result = cls.calc(data, **kwargs) if result != expectedresult: raise CrccheckError("{:s}: expected {:s}, got {:s}".format(cls.__name__, hex(expectedresult), hex(result)))
[docs] def copy(self): """Creates a copy of the Crc/Checksum instance. This can be used to efficiently compute the CRC/checksum of data sharing common initial part.""" import copy return copy.deepcopy(self)