def test_try_read_bitarray(self): # Read next few bits r = bitstream.BitstreamReader(BytesIO(b"\xAA")) assert r.try_read_bitarray(4).to01() == "1010" # Try to read past EOF r = bitstream.BitstreamReader(BytesIO(b"\xAA")) assert r.try_read_bitarray(16).to01() == "10101010" # Read past bounded block r = bitstream.BitstreamReader(BytesIO(b"\xAA")) r.bounded_block_begin(4) assert r.try_read_bitarray(8).to01() == "10101010"
def test_read_past_eof_and_is_end_of_stream(self): r = bitstream.BitstreamReader(BytesIO(b"\xA5\x0F")) for _ in range(16): assert r.is_end_of_stream() is False r.read_bit() # Read past end assert r.is_end_of_stream() is True with pytest.raises(EOFError): r.read_bit()
def print_error_with_mock_state(self, verbosity, message): v = BitstreamViewer(None, verbose=verbosity) v._last_tell = (1, 7) v._reader = bitstream.BitstreamReader(BytesIO(b"\xFF" + b"\xAA" * 16 + b"\x00")) v._reader.seek(1, 3) v._serdes = Mock(path=Mock(return_value=["foo", "bar"])) try: raise KeyError("missing") except KeyError: v._print_error(message)
def test_bounded_block(self): r = bitstream.BitstreamReader(BytesIO(b"\xA0")) r.bounded_block_begin(4) # Can't nest bounded blocks with pytest.raises(Exception, match=r".*nest.*"): r.bounded_block_begin(1) # Read bits from stream assert r.read_bit() == 1 assert r.bits_remaining == 3 assert r.tell() == (0, 6) assert r.read_bit() == 0 assert r.bits_remaining == 2 assert r.tell() == (0, 5) assert r.read_bit() == 1 assert r.bits_remaining == 1 assert r.tell() == (0, 4) assert r.read_bit() == 0 assert r.bits_remaining == 0 assert r.tell() == (0, 3) # End of bounded block, read all '1's but otherwise don't advance assert r.read_bit() == 1 assert r.bits_remaining == -1 assert r.tell() == (0, 3) assert r.read_bit() == 1 assert r.bits_remaining == -2 assert r.tell() == (0, 3) # At end of bounded block, remaining bits are reported assert r.bounded_block_end() == 0 # Can't double-close bounded blocks with pytest.raises(Exception, match=r"Not in bounded block"): r.bounded_block_end() # Now outside of block can read again assert r.read_bit() == 0 assert r.bits_remaining is None assert r.tell() == (0, 2) # If unused bits remain in block, those are reported r.bounded_block_begin(3) assert r.bounded_block_end() == 3
def test_tell(self): r = bitstream.BitstreamReader(BytesIO(b"\xA5\x0F")) assert r.tell() == (0, 7) assert r.read_bit() == 1 assert r.tell() == (0, 6) for _ in range(6): r.read_bit() assert r.tell() == (0, 0) assert r.read_bit() == 1 assert r.tell() == (1, 7) for _ in range(8): r.read_bit()
def test_bounded_block_seek(self): r = bitstream.BitstreamReader(BytesIO(b"\xA0")) r.bounded_block_begin(4) assert r.tell() == (0, 7) assert r.bits_remaining == 4 # Should be able to seek to current position and succeed r.seek(0, 7) assert r.tell() == (0, 7) assert r.bits_remaining == 4 # Should be able to seek to end of bounded block and succeed r.seek(0, 3) assert r.tell() == (0, 3) assert r.bits_remaining == 0 # Should be able to come back again r.seek(0, 4) assert r.tell() == (0, 4) assert r.bits_remaining == 1 # After reading past the end of the block, seeking to the end of the # block shouldn't change bits remaining assert r.read_nbits(5) == 0b01111 assert r.tell() == (0, 3) assert r.bits_remaining == -4 r.seek(0, 3) assert r.tell() == (0, 3) assert r.bits_remaining == -4 # Moving before the end of block again should adjust the count # accordingly, however. r.seek(0, 4) assert r.tell() == (0, 4) assert r.bits_remaining == 1 # Should not be able to seek past the end of the block with pytest.raises(Exception): r.seek(0, 2) assert r.tell() == (0, 4) assert r.bits_remaining == 1
def test_seek(self): r = bitstream.BitstreamReader(BytesIO(b"\xA5\x0F")) r.seek(1) assert [r.read_bit() for _ in range(8)] == [0, 0, 0, 0, 1, 1, 1, 1] r.seek(0) assert [r.read_bit() for _ in range(8)] == [1, 0, 1, 0, 0, 1, 0, 1] r.seek(0, 3) assert [r.read_bit() for _ in range(8)] == [0, 1, 0, 1, 0, 0, 0, 0] # Past end of file r.seek(2, 7) with pytest.raises(EOFError): r.read_bit() r.seek(2, 0) with pytest.raises(EOFError): r.read_bit() r.seek(100, 7) with pytest.raises(EOFError): r.read_bit()
def test_unaligned(self): r = bitstream.BitstreamReader(BytesIO(b"\xDE\xAD\xBE\xEF")) r.seek(0, 3) assert r.read_bytes(2) == b"\xEA\xDB" assert r.tell() == (2, 3)
def test_read_bytes_msb_first(self): r = bitstream.BitstreamReader(BytesIO(b"\xDE\xAD\xBE\xEF")) assert r.read_bytes(2) == b"\xDE\xAD" assert r.tell() == (2, 7)
def test_read_nothing(self): r = bitstream.BitstreamReader(BytesIO()) assert r.read_bytes(0) == b"" assert r.tell() == (0, 7)
def test_read_bytes_msb_first(self): r = bitstream.BitstreamReader(BytesIO(b"\xA0")) assert r.read_bitarray(8) == bitarray([1, 0, 1, 0, 0, 0, 0, 0]) assert r.tell() == (1, 7)
def test_read_bytes_msb_first(self): r = bitstream.BitstreamReader(BytesIO(b"\xA0\x50\xFF")) assert r.read_uint_lit(2) == 0xA050 assert r.tell() == (2, 7)
def run(): r = bitstream.BitstreamReader(BytesIO(b"\xFF" * 100)) with bitstream.Deserialiser(r) as serdes: bitstream.source_parameters(serdes, State(), -1)
def test_reading(self): r = bitstream.BitstreamReader(BytesIO(b"\xA5\x0F")) expected = [1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1] # 0xA5 # 0x0F for expected_bit in expected: assert r.read_bit() == expected_bit
def run(): r = bitstream.BitstreamReader(BytesIO(b"\xFF" * 100)) with bitstream.MonitoredDeserialiser(io=r, monitor=_call) as serdes: bitstream.parse_info(serdes, State())
def test_basic_reading(self, encoded, exp_value, exp_tell): r = bitstream.BitstreamReader(BytesIO(encoded)) assert r.read_sint() == exp_value assert r.tell() == exp_tell
def test_read_bytes_msb_first(self): r = bitstream.BitstreamReader(BytesIO(b"\xA0\x50")) assert r.read_nbits(12) == 0xA05 assert r.tell() == (1, 3)
def test_read_nothing(self): r = bitstream.BitstreamReader(BytesIO()) assert r.read_uint_lit(0) == 0 assert r.tell() == (0, 7)
def run(self): """ Parse the bitstream. Returns 0 if the bitstream was read successfully or another integer otherwise. """ # Open the file try: self._file = open(self._filename, "rb") self._reader = bitstream.BitstreamReader(self._file) filesize_bytes = os.path.getsize(self._filename) except Exception as e: # Catch-all exception handler excuse: Catching only file-related # exceptions is challenging, particularly in a backward-compatible # manner. However, none of the above are known to produce # exceptions *except* due to file-related issues. self._print_error(str(e)) return 1 # Resolve filesizes to absolute values filesize = filesize_bytes * 8 self._from_offset = relative_to_abs_index(self._from_offset, filesize) self._to_offset = max(0, relative_to_abs_index(self._to_offset, filesize)) return_code = 0 error_message = None # Parse the bitstream. A MonitoredDeserialiser is used which calls # __call__ whenever a value is read. That method is responsible for # printing the bitstream to stdout and also deciding when to terminate # the read process, raising _TerminateSuccess and _TerminateError to # end parsing before the end of the file. try: self._serdes = bitstream.MonitoredDeserialiser( io=self._reader, monitor=self, ) bitstream.parse_stream(self._serdes, self._state) except BitstreamViewer._TerminateSuccess: return_code = 0 error_message = None except KeyboardInterrupt: return_code = 1 error_message = None except BitstreamViewer._TerminateError as e: return_code = 2 error_message = str(e) except EOFError: return_code = 3 error_message = "reached the end of the file while parsing {}".format( most_recent_pseudocode_function(sys.exc_info()[2]), ) except Exception: # Other exceptions might be raised during parsing (e.g. due to # invalid bitstream values), attempt to display these sensibly. exc_type, exc_value, exc_tb = sys.exc_info() if is_internal_error(exc_tb): # If the exception does not originate from the VC-2 pseudocode, # we've encountered an internal error in this program. This # should not happen, but if it does it should be made clear. return_code = 255 error_message = ( "internal error in bitstream viewer: {}: {} " "(probably a bug in this program)".format( exc_type.__name__, str(exc_value), ) ) else: # General case: some error in the VC-2 pseudocode due to an # out-of-range value return_code = 4 error_message = ( "{} failed to parse bitstream ({}: {}) " "(missing sequence_header, fragment or earlier out of range value?)" ).format( most_recent_pseudocode_function(exc_tb), exc_type.__name__, str(exc_value), ) finally: self._hide_status_line() if self._last_displayed_tell != self._last_tell: self._print_omitted_bits(self._last_tell) if self._show_internal_state: self._print_internal_state() if error_message is not None: self._print_error(error_message) return return_code