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-12-05 10:43 +0000

1""" 

2The module implements abstraction for holding bit sequence. 

3""" 

4 

5import typing 

6 

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 

12 

13 

14class BitBuffer: 

15 """ 

16 Bit buffer. 

17 

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 """ 

21 

22 def __init__(self, buffer: bytes, bitsize: typing.Optional[int] = None) -> None: 

23 """ 

24 Constructs bit buffer from bytes buffer and bit size. 

25 

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 """ 

30 

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 

40 

41 def __eq__(self, other: object) -> bool: 

42 if not isinstance(other, BitBuffer): 

43 return False 

44 

45 if self._bitsize != other._bitsize: 

46 return False 

47 

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 

53 

54 if self._masked_last_byte() != other._masked_last_byte(): 

55 return False 

56 

57 return True 

58 

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) 

66 

67 result = calc_hashcode_int32(result, self._masked_last_byte()) 

68 

69 return result 

70 

71 @property 

72 def buffer(self) -> bytes: 

73 """ 

74 Gets the underlying byte buffer. 

75 

76 Not all bits of the last byte must be used. 

77 

78 :returns: The underlying byte buffer. 

79 """ 

80 return self._buffer 

81 

82 @property 

83 def bitsize(self) -> int: 

84 """ 

85 Gets the number of bits stored in the bit buffer. 

86 

87 :returns: Size of the bit buffer in bits. 

88 """ 

89 return self._bitsize 

90 

91 def _masked_last_byte(self) -> int: 

92 rounded_bytesize = self._bitsize // 8 

93 last_byte_bits = self._bitsize - 8 * rounded_bytesize 

94 

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 ) 

100 

101 

102BitBuffer = import_cpp_class("BitBuffer") or BitBuffer # type: ignore