diff cutils/util/crc32.py @ 255:d852559df523

Wrap zlib.crc32 into a hashlib-compatible interface
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 15 Feb 2025 16:36:05 +0100
parents
children 57057028f13e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cutils/util/crc32.py	Sat Feb 15 16:36:05 2025 +0100
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# :-
+# :Copyright: (c) 2025 Franz Glasner
+# :License:   BSD-3-Clause
+# :-
+r"""Wrap :func:`zlib.crc32` into a :mod:`hashlib` compatible interface.
+
+"""
+
+from __future__ import print_function, absolute_import
+
+
+__all__ = ["crc32"]
+
+
+import struct
+import zlib
+
+from . import PY2
+
+
+try:
+    long
+except NameError:
+    INT_TYPES = (int, )
+else:
+    INT_TYPES = (int, long)            # noqa: F821  long is undefined
+
+
+class crc32(object):
+
+    __slots__ = ["_crc"]
+
+    zlib_crc32 = zlib.crc32
+
+    def __init__(self, data=b""):
+        self._crc = self.zlib_crc32(data)
+
+    def update(self, data):
+        self._crc = self.zlib_crc32(data, self._crc)
+
+    def digest(self):
+        return struct.pack(">I", self._get_crc_as_uint32())
+
+    def hexdigest(self):
+        return "{:08X}".format(self._get_crc_as_uint32())
+
+    def copy(self):
+        copied_crc = crc32()
+        copied_crc._crc = self._crc
+        return copied_crc
+
+    def __eq__(self, other):
+        if isinstance(other, crc32):
+            return self._crc == other._crc
+        elif isinstance(other, INT_TYPES):
+            return self._get_crc_as_uint32() == self.as_uint32(other)
+        else:
+            return NotImplemented
+
+    def __ne__(self, other):
+        equal = self.__eq__(other)
+        return equal if equal is NotImplemented else not equal
+
+    def _get_crc_as_uint32(self):
+        """Get the current CRC always as positive number with the same bit
+        pattern because Python2 yields negative numbers also.
+
+        :return: The current CRC value as positive number on all Python
+                 versions
+        :rtype: int
+
+        """
+        if PY2:
+            if self._crc < 0:
+                # Return the bitpattern as unsigned 32-bit number
+                return (~self._crc ^ 0xFFFFFFFF)
+            else:
+                return self._crc
+        else:
+            return self._crc
+
+    @staticmethod
+    def as_uint32(i):
+        if i < 0:
+            if i < -2147483648:
+                raise OverflowError("")
+            return (~i ^ 0xFFFFFFFF)
+        else:
+            return i