tlsfuzzer/tlslite-ng

View on GitHub
tlslite/utils/brotlidecpy/bit_reader.py

Summary

Maintainability
A
45 mins
Test Coverage
C
76%
# Copyright 2021 Sidney Markowitz All Rights Reserved.
# Distributed under MIT license.
# See file LICENSE for detail or copy at https://opensource.org/licenses/MIT


class BrotliBitReader:
    """
    Wrap a bytes buffer to enable reading 0 < n <=24 bits at a time, or
    transfer of arbitrary number of bytes
    """

    kBitMask = [
        0x000000, 0x000001, 0x000003, 0x000007, 0x00000f, 0x00001f, 0x00003f,
        0x00007f, 0x0000ff, 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, 0x001fff,
        0x003fff, 0x007fff, 0x00ffff, 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff,
        0x1fffff, 0x3fffff, 0x7fffff, 0xffffff
    ]

    def __init__(self, input_buffer):
        self.buf_ = bytearray(input_buffer)
        self.buf_len_ = len(input_buffer)
        self.pos_ = 0          # byte position in stream
        # current bit-reading position in current byte (number bits already
        # read from byte, 0-7)
        self.bit_pos_ = 0

    def reset(self):
        """Reset an initialized BrotliBitReader to start of input buffer"""
        self.pos_ = 0
        self.bit_pos_ = 0

    def read_bits(self, n_bits, bits_to_skip=None):
        """
        Get n_bits unsigned integer treating input as little-endian byte
        stream, maybe advancing input buffer pointer

        n_bits: is number of bits to read from input buffer. Set to None or 0
            to seek ahead ignoring the value
        bits_to_skip: number of bits to advance in input_buffer, defaults to
            n_bits if it is None pass in 0 to peek at the next n_bits of value
            without advancing

        It is ok to have n_bits and bits_to_skip be different non-zero values
        if that is what is wanted

        Returns: the next n_bits from the buffer as a little-endian integer,
            0 if n_bits is None or 0
        """
        val = 0
        if bits_to_skip is None:
            bits_to_skip = n_bits
        if n_bits:
            bytes_shift = 0
            buf_pos = self.pos_
            bit_pos_when_done = n_bits + self.bit_pos_
            while bytes_shift < bit_pos_when_done:
                if buf_pos >= self.buf_len_:
                    # if hit end of buffer, this simulates zero padding after
                    # end, which is correct
                    break
                val |= self.buf_[buf_pos] << bytes_shift
                bytes_shift += 8
                buf_pos += 1
            val = (val >> self.bit_pos_) & self.kBitMask[n_bits]
        if bits_to_skip:
            next_in_bits = self.bit_pos_ + bits_to_skip
            self.bit_pos_ = next_in_bits & 7
            self.pos_ += next_in_bits >> 3
        return val

    def copy_bytes(self, dest_buffer, dest_pos, n_bytes):
        """
        Copy bytes from input buffer. This will first skip to next byte
        boundary if not already on one
        """
        if self.bit_pos_ != 0:
            self.bit_pos_ = 0
            self.pos_ += 1
        # call with n_bytes == 0 to just skip to next byte boundary
        if n_bytes > 0:
            new_pos = self.pos_ + n_bytes
            memoryview(dest_buffer)[dest_pos:dest_pos+n_bytes] = \
                self.buf_[self.pos_:new_pos]
            self.pos_ = new_pos