Coverage for /home/runner/work/zserio/zserio/compiler/extensions/python/runtime/src/zserio/bitbuffer.py: 100%
47 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-10-29 13:10 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-10-29 13:10 +0000
1"""
2The module implements abstraction for holding bit sequence.
3"""
5import typing
7from zserio.exception import PythonRuntimeException
8from zserio.hashcode import HASH_SEED
9from zserio.hashcode import calc_hashcode_int32
10from zserio.bitposition import bitsize_to_bytesize
11from zserio.cppbind import import_cpp_class
14class BitBuffer:
15 """
16 Bit buffer.
18 Because bit buffer size does not have to be byte aligned (divisible by 8), it's possible that not all bits
19 of the last byte are used. In this case, only most significant bits of the corresponded size are used.
20 """
22 def __init__(self, buffer: bytes, bitsize: typing.Optional[int] = None) -> None:
23 """
24 Constructs bit buffer from bytes buffer and bit size.
26 :param buffer: Bytes-like buffer to construct from.
27 :param bitsize: Number of bits stored in buffer to use.
28 :raises PythonRuntimeException: If bitsize is out of range.
29 """
31 if bitsize is None:
32 bitsize = len(buffer) * 8
33 elif len(buffer) * 8 < bitsize:
34 raise PythonRuntimeException(
35 f"BitBuffer: Bit size '{bitsize}' out of range "
36 f"for the given buffer byte size '{len(buffer)}'!"
37 )
38 self._buffer: bytes = buffer
39 self._bitsize: int = bitsize
41 def __eq__(self, other: object) -> bool:
42 if not isinstance(other, BitBuffer):
43 return False
45 if self._bitsize != other._bitsize:
46 return False
48 bytesize = bitsize_to_bytesize(self._bitsize)
49 if bytesize > 0:
50 if bytesize > 1:
51 if self._buffer[0 : bytesize - 1] != other._buffer[0 : bytesize - 1]:
52 return False
54 if self._masked_last_byte() != other._masked_last_byte():
55 return False
57 return True
59 def __hash__(self) -> int:
60 result = HASH_SEED
61 bytesize = bitsize_to_bytesize(self._bitsize)
62 if bytesize > 0:
63 if bytesize > 1:
64 for element in self._buffer[0 : bytesize - 1]:
65 result = calc_hashcode_int32(result, element)
67 result = calc_hashcode_int32(result, self._masked_last_byte())
69 return result
71 @property
72 def buffer(self) -> bytes:
73 """
74 Gets the underlying byte buffer.
76 Not all bits of the last byte must be used.
78 :returns: The underlying byte buffer.
79 """
80 return self._buffer
82 @property
83 def bitsize(self) -> int:
84 """
85 Gets the number of bits stored in the bit buffer.
87 :returns: Size of the bit buffer in bits.
88 """
89 return self._bitsize
91 def _masked_last_byte(self) -> int:
92 rounded_bytesize = self._bitsize // 8
93 last_byte_bits = self._bitsize - 8 * rounded_bytesize
95 return (
96 self._buffer[rounded_bytesize - 1]
97 if last_byte_bits == 0
98 else self._buffer[rounded_bytesize] & (0xFF << (8 - last_byte_bits))
99 )
102BitBuffer = import_cpp_class("BitBuffer") or BitBuffer # type: ignore