mirror of https://github.com/SeanOMik/libki.git
350 lines
10 KiB
C++
350 lines
10 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
#include <sstream>
|
|
#include "ki/util/BitTypes.h"
|
|
#include "exception.h"
|
|
|
|
#define KI_BITBUFFER_DEFAULT_SIZE 0x2000
|
|
|
|
namespace ki
|
|
{
|
|
// Forward declaration so that buffers can create segments of themselves
|
|
class BitBufferSegment;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class IBitBuffer
|
|
{
|
|
friend BitBufferSegment;
|
|
|
|
public:
|
|
/**
|
|
*
|
|
*/
|
|
struct buffer_pos
|
|
{
|
|
explicit buffer_pos(uint32_t byte = 0, int bit = 0);
|
|
buffer_pos(const buffer_pos &cp);
|
|
|
|
uint32_t as_bytes() const;
|
|
uint32_t as_bits() const;
|
|
uint32_t get_byte() const;
|
|
uint8_t get_bit() const;
|
|
|
|
buffer_pos operator +(const buffer_pos &rhs) const;
|
|
buffer_pos operator -(const buffer_pos &rhs) const;
|
|
buffer_pos operator +(const int &rhs) const;
|
|
buffer_pos operator -(const int &rhs) const;
|
|
buffer_pos &operator +=(buffer_pos lhs);
|
|
buffer_pos &operator -=(buffer_pos lhs);
|
|
buffer_pos &operator +=(int bits);
|
|
buffer_pos &operator -=(int bits);
|
|
buffer_pos &operator ++();
|
|
buffer_pos &operator --();
|
|
buffer_pos operator ++(int increment);
|
|
buffer_pos operator --(int increment);
|
|
|
|
private:
|
|
uint32_t m_byte;
|
|
uint8_t m_bit;
|
|
|
|
void set_bit(int bit);
|
|
};
|
|
|
|
virtual ~IBitBuffer() {}
|
|
|
|
/**
|
|
* @returns
|
|
*/
|
|
virtual uint8_t *data() const = 0;
|
|
|
|
/**
|
|
* @returns The size of the buffer in bytes.
|
|
*/
|
|
virtual std::size_t size() const = 0;
|
|
|
|
/**
|
|
* Resize the buffer to the specified size.
|
|
* If the current size is larger than the specified new size, then the data is truncated.
|
|
* @param[in] new_size The size to resize the buffer to in bytes.
|
|
*/
|
|
virtual void resize(std::size_t new_size) = 0;
|
|
|
|
/**
|
|
* @param[in] from The position of the start of the segment.
|
|
* @param[in] bitsize The size of the segment in bits.
|
|
* @returns A new segment of this buffer.
|
|
*/
|
|
virtual std::unique_ptr<BitBufferSegment> segment(
|
|
buffer_pos from, std::size_t bitsize
|
|
);
|
|
|
|
/**
|
|
* Reads a value from the buffer.
|
|
* @tparam ValueT The type of value (must be an integral type).
|
|
* @param[in] position Where in the buffer to retrieve the value from.
|
|
* @param[in] bits The number of bits the value occupies in the buffer.
|
|
* @returns The n-bit value retrieved from the buffer.
|
|
* @throws ki::runtime_error Buffer is not large enough to read the specified number of bits at the specified position.
|
|
*/
|
|
template <typename ValueT>
|
|
ValueT read(const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value) const
|
|
{
|
|
// Check for buffer overflow
|
|
if ((position + bits).as_bytes() > size())
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Buffer is not large enough to read a "
|
|
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
|
|
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
|
|
<< ", size=" << size() << ")";
|
|
throw runtime_error(oss.str());
|
|
}
|
|
|
|
return static_cast<ValueT>(read(position, bits));
|
|
}
|
|
|
|
/**
|
|
* Writes a value to the buffer.
|
|
* @tparam ValueT The type of value (must be an integral type).
|
|
* @param[in] value The value to write.
|
|
* @param[in] position Where in the buffer to write the value to.
|
|
* @param[in] bits The number of bits the value should occupy in the buffer. Defaults to the bitsize of ValueT.
|
|
* @throws ki::runtime_error Buffer is not large enough to write the specified number of bits at the specified position.
|
|
*/
|
|
template <typename ValueT>
|
|
void write(const ValueT value, const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value)
|
|
{
|
|
// Check for buffer overflow
|
|
if ((position + bits).as_bytes() > size())
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Buffer is not large enough to write a "
|
|
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
|
|
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
|
|
<< ", size=" << size() << ")";
|
|
throw runtime_error(oss.str());
|
|
}
|
|
|
|
write(static_cast<uint64_t>(value), position, bits);
|
|
}
|
|
|
|
/**
|
|
* Copy memory from this buffer into an external buffer.
|
|
* @param[out] dst The destination buffer to copy data to.
|
|
* @param[in] position Where in the buffer to start copying data from.
|
|
* @param[in] bitsize The number of bits to copy into the destination buffer.
|
|
*/
|
|
void read_copy(uint8_t *dst, buffer_pos position, std::size_t bitsize) const;
|
|
|
|
/**
|
|
* Copy memory from an external buffer into this buffer at the specified position.
|
|
* @param[in] src The source buffer to copy data from.
|
|
* @param[in] position Where in the buffer to start copying data to.
|
|
* @param[in] bitsize The number of bits to copy from the source buffer.
|
|
*/
|
|
void write_copy(uint8_t *src, buffer_pos position, std::size_t bitsize);
|
|
|
|
protected:
|
|
virtual uint64_t read(buffer_pos position, uint8_t bits) const = 0;
|
|
virtual void write(uint64_t value, buffer_pos position, uint8_t bits) = 0;
|
|
};
|
|
|
|
/**
|
|
* TODO: Documentation
|
|
*/
|
|
class BitBuffer : public IBitBuffer
|
|
{
|
|
public:
|
|
explicit BitBuffer(std::size_t buffer_size = KI_BITBUFFER_DEFAULT_SIZE);
|
|
~BitBuffer();
|
|
|
|
BitBuffer(const BitBuffer &that);
|
|
BitBuffer &operator=(const BitBuffer &that);
|
|
|
|
/**
|
|
* Create a new BitBuffer from an existing buffer.
|
|
* @param[in] buffer The buffer to take ownership of.
|
|
* @param[in] buffer_size The size of the buffer in bytes.
|
|
*/
|
|
explicit BitBuffer(uint8_t *buffer, std::size_t buffer_size);
|
|
|
|
std::size_t size() const override;
|
|
uint8_t* data() const override;
|
|
void resize(std::size_t new_size) override;
|
|
|
|
/**
|
|
* @copydoc IBitBuffer::read<ValueT>(buffer_pos, uint8_t)
|
|
*/
|
|
template <typename ValueT>
|
|
ValueT read(const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value) const
|
|
{
|
|
return IBitBuffer::read<ValueT>(position, bits);
|
|
}
|
|
|
|
/**
|
|
* @copydoc IBitBuffer::write<ValueT>(ValueT, buffer_pos, uint8_t)
|
|
*/
|
|
template <typename ValueT>
|
|
void write(const ValueT value, const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value)
|
|
{
|
|
return IBitBuffer::write<ValueT>(value, position, bits);
|
|
}
|
|
|
|
protected:
|
|
uint64_t read(buffer_pos position, uint8_t bits) const override;
|
|
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
|
|
|
|
private:
|
|
uint8_t *m_buffer;
|
|
std::size_t m_buffer_size;
|
|
};
|
|
|
|
/**
|
|
* TODO: Documentation
|
|
*/
|
|
class BitBufferSegment : public IBitBuffer
|
|
{
|
|
public:
|
|
BitBufferSegment(IBitBuffer &buffer, buffer_pos from, std::size_t bitsize);
|
|
|
|
std::size_t size() const override;
|
|
void resize(std::size_t new_size) override;
|
|
uint8_t *data() const override;
|
|
std::unique_ptr<BitBufferSegment> segment(
|
|
buffer_pos from, std::size_t bitsize
|
|
) override;
|
|
|
|
/**
|
|
* @copydoc IBitBuffer::read<ValueT>(buffer_pos, uint8_t)
|
|
*/
|
|
template <typename ValueT>
|
|
ValueT read(const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value) const
|
|
{
|
|
return IBitBuffer::read<ValueT>(position, bits);
|
|
}
|
|
|
|
/**
|
|
* @copydoc IBitBuffer::write<ValueT>(ValueT, buffer_pos, uint8_t)
|
|
*/
|
|
template <typename ValueT>
|
|
void write(const ValueT value, const buffer_pos position,
|
|
const uint8_t bits = bitsizeof<ValueT>::value)
|
|
{
|
|
return IBitBuffer::write<ValueT>(value, position, bits);
|
|
}
|
|
|
|
protected:
|
|
uint64_t read(buffer_pos position, uint8_t bits) const override;
|
|
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
|
|
|
|
private:
|
|
IBitBuffer *m_buffer;
|
|
buffer_pos m_from;
|
|
std::size_t m_bitsize;
|
|
};
|
|
|
|
/**
|
|
* A read/write-able stream of bits.
|
|
*/
|
|
class BitStream
|
|
{
|
|
public:
|
|
using stream_pos = BitBuffer::buffer_pos;
|
|
|
|
explicit BitStream(IBitBuffer &buffer);
|
|
virtual ~BitStream();
|
|
|
|
BitStream(const BitStream &that);
|
|
BitStream &operator=(const BitStream &that);
|
|
|
|
/**
|
|
* @returns The stream's current position.
|
|
*/
|
|
stream_pos tell() const;
|
|
|
|
/**
|
|
* Sets the position of the stream.
|
|
* @param[in] position The new position of the stream.
|
|
* @param[in] expand Whether or not to expand the buffer.
|
|
*/
|
|
void seek(stream_pos position, bool expand = true);
|
|
|
|
/**
|
|
* @returns The current size of the internal buffer.
|
|
*/
|
|
std::size_t capacity() const;
|
|
|
|
/**
|
|
* @returns The BitBuffer that the stream is reading/writing to.
|
|
*/
|
|
IBitBuffer &buffer() const;
|
|
|
|
/**
|
|
* Reads a value from the buffer.
|
|
* @tparam ValueT The type used to store the value.
|
|
* @param[in] bits The number of bits to read. Defaults to the bitsize of ValueT.
|
|
* @returns The value read from the buffer.
|
|
*/
|
|
template <
|
|
typename ValueT,
|
|
typename = std::enable_if<is_integral<ValueT>::value>
|
|
>
|
|
ValueT read(const uint8_t bits = bitsizeof<ValueT>::value)
|
|
{
|
|
ValueT value = m_buffer->read<ValueT>(m_position, bits);
|
|
m_position += bits;
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Writes a value to the buffer.
|
|
* If the buffer is not big enough to write the value, then the buffer is resized.
|
|
* @tparam ValueT The type of value (must be an integral type).
|
|
* @param[in] value The value to write.
|
|
* @param[in] bits The number of bits to use. Defaults to the bitsize of ValueT.
|
|
*/
|
|
template <
|
|
typename ValueT,
|
|
typename = std::enable_if<is_integral<ValueT>::value>
|
|
>
|
|
void write(ValueT value, const uint8_t bits = bitsizeof<ValueT>::value)
|
|
{
|
|
expand_buffer(m_position + bits);
|
|
m_buffer->write<ValueT>(value, m_position, bits);
|
|
m_position += bits;
|
|
}
|
|
|
|
/**
|
|
* Copy memory from the BitStream's buffer into an external buffer at the current position.
|
|
* @param[out] dst The destination buffer to copy data to.
|
|
* @param[in] bitsize The number of bits to copy into the destination buffer.
|
|
*/
|
|
void read_copy(uint8_t *dst, std::size_t bitsize);
|
|
|
|
/**
|
|
* Copy memory from an external buffer into the BitStream's buffer from the current position.
|
|
* @param[in] src The source buffer to copy data from.
|
|
* @param[in] bitsize The number of bits to copy from the source buffer.
|
|
*/
|
|
void write_copy(uint8_t *src, std::size_t bitsize);
|
|
|
|
private:
|
|
IBitBuffer *m_buffer;
|
|
stream_pos m_position;
|
|
|
|
/**
|
|
* Expand the buffer such that a position becomes valid.
|
|
* @param[in] position The minimum position that should be accessible after expanding.
|
|
*/
|
|
void expand_buffer(stream_pos position) const;
|
|
};
|
|
}
|