import binascii, struct


class BufferError(Exception):
    pass


class Buffer(object):
    def __init__(self, data=b''):
        self.data = bytearray(data)
        self.offset = 0

    def remaining(self):
        return len(self.data) - self.offset

    def get(self, length):
        if length > self.remaining():
            raise BufferError('Not enough bytes [offset=%d,remaining=%d,requested=%d]' % (self.offset, self.remaining(), length))

        start = self.offset
        end = self.offset + length
        self.offset += length
        return bytes(self.data[start:end])

    def hex(self):
        return binascii.hexlify(self.data)

    def pack(self, fmt, *args):
        self.offset += struct.calcsize(fmt)
        self.data += struct.pack(fmt, *args)

    def append(self, s):
        self.offset += len(s)
        self.data += s

    def update(self, ptr, fmt, *args):
        s = struct.pack(fmt, *args)
        self.data[ptr:ptr+len(s)] = s

    def unpack(self, fmt):
        try:
            data = self.get(struct.calcsize(fmt))
            return struct.unpack(fmt, data)
        except struct.error:
            raise BufferError("Error unpacking struct '%s' <%s>" % (fmt, binascii.hexlify(data).decode()))

    def __len__(self):
        return len(self.data)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

import binascii, struct


class BufferError(Exception):
    pass


class Buffer(object):
    def __init__(self, data=b''):
        self.data = bytearray(data)
        self.offset = 0

    def remaining(self):
        return len(self.data) - self.offset

    def get(self, length):
        if length > self.remaining():
            raise BufferError('Not enough bytes [offset=%d,remaining=%d,requested=%d]' % (self.offset, self.remaining(), length))

        start = self.offset
        end = self.offset + length
        self.offset += length
        return bytes(self.data[start:end])

    def hex(self):
        return binascii.hexlify(self.data)

    def pack(self, fmt, *args):
        self.offset += struct.calcsize(fmt)
        self.data += struct.pack(fmt, *args)

    def append(self, s):
        self.offset += len(s)
        self.data += s

    def update(self, ptr, fmt, *args):
        s = struct.pack(fmt, *args)
        self.data[ptr:ptr+len(s)] = s

    def unpack(self, fmt):
        try:
            data = self.get(struct.calcsize(fmt))
            return struct.unpack(fmt, data)
        except struct.error:
            raise BufferError("Error unpacking struct '%s' <%s>" % (fmt, binascii.hexlify(data).decode()))

    def __len__(self):
        return len(self.data)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
