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