/* * Copyright (c) 2021, kleines Filmröllchen . * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include namespace AK { /// A stream wrapper class that allows you to read arbitrary amounts of bits /// in big-endian order from another stream. class BigEndianInputBitStream : public Stream { public: explicit BigEndianInputBitStream(MaybeOwned stream) : m_stream(move(stream)) { } // ^Stream virtual ErrorOr read(Bytes bytes) override { if (m_current_byte.has_value() && is_aligned_to_byte_boundary()) { bytes[0] = m_current_byte.release_value(); return m_stream->read(bytes.slice(1)); } align_to_byte_boundary(); return m_stream->read(bytes); } virtual ErrorOr write(ReadonlyBytes bytes) override { return m_stream->write(bytes); } virtual ErrorOr write_entire_buffer(ReadonlyBytes bytes) override { return m_stream->write_entire_buffer(bytes); } virtual bool is_eof() const override { return m_stream->is_eof() && !m_current_byte.has_value(); } virtual bool is_open() const override { return m_stream->is_open(); } virtual void close() override { m_stream->close(); align_to_byte_boundary(); } ErrorOr read_bit() { return read_bits(1); } /// Depending on the number of bits to read, the return type can be chosen appropriately. /// This avoids a bunch of static_cast<>'s for the user. // TODO: Support u128, u256 etc. as well: The concepts would be quite complex. template ErrorOr read_bits(size_t count) { if constexpr (IsSame) { VERIFY(count == 1); } T result = 0; size_t nread = 0; while (nread < count) { if (m_current_byte.has_value()) { if constexpr (!IsSame && !IsSame) { // read as many bytes as possible directly if (((count - nread) >= 8) && is_aligned_to_byte_boundary()) { // shift existing data over result <<= 8; result |= m_current_byte.value(); nread += 8; m_current_byte.clear(); } else { auto const bit = (m_current_byte.value() >> (7 - m_bit_offset)) & 1; result <<= 1; result |= bit; ++nread; if (m_bit_offset++ == 7) m_current_byte.clear(); } } else { // Always take this branch for booleans or u8: there's no purpose in reading more than a single bit auto const bit = (m_current_byte.value() >> (7 - m_bit_offset)) & 1; if constexpr (IsSame) result = bit; else { result <<= 1; result |= bit; } ++nread; if (m_bit_offset++ == 7) m_current_byte.clear(); } } else { auto temp_buffer = TRY(ByteBuffer::create_uninitialized(1)); TRY(m_stream->read(temp_buffer.bytes())); m_current_byte = temp_buffer[0]; m_bit_offset = 0; } } return result; } /// Discards any sub-byte stream positioning the input stream may be keeping track of. /// Non-bitwise reads will implicitly call this. void align_to_byte_boundary() { m_current_byte.clear(); m_bit_offset = 0; } /// Whether we are (accidentally or intentionally) at a byte boundary right now. ALWAYS_INLINE bool is_aligned_to_byte_boundary() const { return m_bit_offset == 0; } private: Optional m_current_byte; size_t m_bit_offset { 0 }; MaybeOwned m_stream; }; /// A stream wrapper class that allows you to read arbitrary amounts of bits /// in little-endian order from another stream. class LittleEndianInputBitStream : public Stream { public: explicit LittleEndianInputBitStream(MaybeOwned stream) : m_stream(move(stream)) { } // ^Stream virtual ErrorOr read(Bytes bytes) override { if (m_current_byte.has_value() && is_aligned_to_byte_boundary()) { bytes[0] = m_current_byte.release_value(); return m_stream->read(bytes.slice(1)); } align_to_byte_boundary(); return m_stream->read(bytes); } virtual ErrorOr write(ReadonlyBytes bytes) override { return m_stream->write(bytes); } virtual ErrorOr write_entire_buffer(ReadonlyBytes bytes) override { return m_stream->write_entire_buffer(bytes); } virtual bool is_eof() const override { return m_stream->is_eof() && !m_current_byte.has_value(); } virtual bool is_open() const override { return m_stream->is_open(); } virtual void close() override { m_stream->close(); align_to_byte_boundary(); } ErrorOr read_bit() { return read_bits(1); } /// Depending on the number of bits to read, the return type can be chosen appropriately. /// This avoids a bunch of static_cast<>'s for the user. // TODO: Support u128, u256 etc. as well: The concepts would be quite complex. template ErrorOr read_bits(size_t count) { if constexpr (IsSame) { VERIFY(count == 1); } T result = 0; size_t nread = 0; while (nread < count) { if (m_current_byte.has_value()) { if constexpr (!IsSame && !IsSame) { // read as many bytes as possible directly if (((count - nread) >= 8) && is_aligned_to_byte_boundary()) { // shift existing data over result |= (m_current_byte.value() << nread); nread += 8; m_current_byte.clear(); } else { auto const bit = (m_current_byte.value() >> m_bit_offset) & 1; result |= (bit << nread); ++nread; if (m_bit_offset++ == 7) m_current_byte.clear(); } } else { // Always take this branch for booleans or u8: there's no purpose in reading more than a single bit auto const bit = (m_current_byte.value() >> m_bit_offset) & 1; if constexpr (IsSame) result = bit; else result |= (bit << nread); ++nread; if (m_bit_offset++ == 7) m_current_byte.clear(); } } else { auto temp_buffer = TRY(ByteBuffer::create_uninitialized(1)); auto read_bytes = TRY(m_stream->read(temp_buffer.bytes())); if (read_bytes.is_empty()) return Error::from_string_literal("eof"); m_current_byte = temp_buffer[0]; m_bit_offset = 0; } } return result; } /// Discards any sub-byte stream positioning the input stream may be keeping track of. /// Non-bitwise reads will implicitly call this. u8 align_to_byte_boundary() { u8 remaining_bits = m_current_byte.value_or(0) >> m_bit_offset; m_current_byte.clear(); m_bit_offset = 0; return remaining_bits; } /// Whether we are (accidentally or intentionally) at a byte boundary right now. ALWAYS_INLINE bool is_aligned_to_byte_boundary() const { return m_bit_offset == 0; } private: Optional m_current_byte; size_t m_bit_offset { 0 }; MaybeOwned m_stream; }; /// A stream wrapper class that allows you to write arbitrary amounts of bits /// in big-endian order to another stream. class BigEndianOutputBitStream : public Stream { public: static ErrorOr> construct(MaybeOwned stream) { return adopt_nonnull_own_or_enomem(new BigEndianOutputBitStream(move(stream))); } virtual ErrorOr read(Bytes) override { return Error::from_errno(EBADF); } virtual ErrorOr write(ReadonlyBytes bytes) override { VERIFY(m_bit_offset == 0); return m_stream->write(bytes); } template ErrorOr write_bits(T value, size_t bit_count) { VERIFY(m_bit_offset <= 7); while (bit_count > 0) { u8 next_bit = (value >> (bit_count - 1)) & 1; bit_count--; m_current_byte <<= 1; m_current_byte |= next_bit; m_bit_offset++; if (m_bit_offset > 7) { TRY(m_stream->write({ &m_current_byte, sizeof(m_current_byte) })); m_bit_offset = 0; m_current_byte = 0; } } return {}; } virtual bool is_eof() const override { return true; } virtual bool is_open() const override { return m_stream->is_open(); } virtual void close() override { } size_t bit_offset() const { return m_bit_offset; } ErrorOr align_to_byte_boundary() { if (m_bit_offset == 0) return {}; TRY(write_bits(0u, 8 - m_bit_offset)); VERIFY(m_bit_offset == 0); return {}; } private: BigEndianOutputBitStream(MaybeOwned stream) : m_stream(move(stream)) { } MaybeOwned m_stream; u8 m_current_byte { 0 }; size_t m_bit_offset { 0 }; }; /// A stream wrapper class that allows you to write arbitrary amounts of bits /// in little-endian order to another stream. class LittleEndianOutputBitStream : public Stream { public: static ErrorOr> construct(MaybeOwned stream) { return adopt_nonnull_own_or_enomem(new LittleEndianOutputBitStream(move(stream))); } virtual ErrorOr read(Bytes) override { return Error::from_errno(EBADF); } virtual ErrorOr write(ReadonlyBytes bytes) override { VERIFY(m_bit_offset == 0); return m_stream->write(bytes); } template ErrorOr write_bits(T value, size_t bit_count) { VERIFY(m_bit_offset <= 7); size_t input_offset = 0; while (input_offset < bit_count) { u8 next_bit = (value >> input_offset) & 1; input_offset++; m_current_byte |= next_bit << m_bit_offset; m_bit_offset++; if (m_bit_offset > 7) { TRY(m_stream->write({ &m_current_byte, sizeof(m_current_byte) })); m_bit_offset = 0; m_current_byte = 0; } } return {}; } virtual bool is_eof() const override { return true; } virtual bool is_open() const override { return m_stream->is_open(); } virtual void close() override { } size_t bit_offset() const { return m_bit_offset; } ErrorOr align_to_byte_boundary() { if (m_bit_offset == 0) return {}; TRY(write_bits(0u, 8 - m_bit_offset)); VERIFY(m_bit_offset == 0); return {}; } private: LittleEndianOutputBitStream(MaybeOwned stream) : m_stream(move(stream)) { } MaybeOwned m_stream; u8 m_current_byte { 0 }; size_t m_bit_offset { 0 }; }; }