Coverage for ccsds124/bitreader.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-29 15:01 +0000

1""" 

2Sequential bit reader for decompression. 

3 

4This module provides sequential bit-level reading from bytes, 

5used for parsing compressed CCSDS 124.0-B-1 streams. 

6 

7Bit Ordering: 

8Bits are read MSB-first within each byte (matching BitBuffer output): 

9- First bit read is bit position 7 (MSB) 

10- Last bit read is bit position 0 (LSB) 

11 

12MicroPython compatible - no typing module imports. 

13""" 

14 

15 

16class BitReader: 

17 """Sequential bit reader from bytes.""" 

18 

19 def __init__(self, data: bytes) -> None: 

20 """ 

21 Initialize a bit reader. 

22 

23 Args: 

24 data: Bytes to read from 

25 """ 

26 self._data = data 

27 self._total_bits = len(data) * 8 

28 self.position = 0 

29 

30 @property 

31 def remaining(self) -> int: 

32 """Number of bits remaining to read.""" 

33 return self._total_bits - self.position 

34 

35 def peek_bit(self) -> int: 

36 """ 

37 Peek at the next bit without consuming it. 

38 

39 Returns: 

40 Next bit value (0 or 1) 

41 

42 Raises: 

43 EOFError: If no more bits available 

44 """ 

45 if self.position >= self._total_bits: 

46 raise EOFError("No more bits to read") 

47 

48 byte_index = self.position // 8 

49 bit_index = self.position % 8 

50 

51 # MSB-first: bit 0 in stream is bit 7 of first byte 

52 return (self._data[byte_index] >> (7 - bit_index)) & 1 

53 

54 def read_bit(self) -> int: 

55 """ 

56 Read and consume a single bit. 

57 

58 Returns: 

59 Bit value (0 or 1) 

60 

61 Raises: 

62 EOFError: If no more bits available 

63 """ 

64 bit = self.peek_bit() 

65 self.position += 1 

66 return bit 

67 

68 def read_bits(self, num_bits: int) -> int: 

69 """ 

70 Read and consume multiple bits as an integer. 

71 

72 Args: 

73 num_bits: Number of bits to read 

74 

75 Returns: 

76 Integer value of bits (MSB-first) 

77 

78 Raises: 

79 EOFError: If not enough bits available 

80 """ 

81 if num_bits == 0: 

82 return 0 

83 

84 if self.position + num_bits > self._total_bits: 

85 raise EOFError(f"Not enough bits: need {num_bits}, have {self.remaining}") 

86 

87 result = 0 

88 for _ in range(num_bits): 

89 result = (result << 1) | self.read_bit() 

90 

91 return result 

92 

93 def align_byte(self) -> None: 

94 """ 

95 Advance to the next byte boundary. 

96 

97 If already at a byte boundary, does nothing. 

98 Used for packet separation in compressed streams. 

99 """ 

100 bit_offset = self.position % 8 

101 if bit_offset != 0: 

102 self.position += 8 - bit_offset