diff options
author | Tim Schumacher <timschumi@gmx.de> | 2023-01-25 20:06:16 +0100 |
---|---|---|
committer | Andrew Kaster <andrewdkaster@gmail.com> | 2023-01-29 19:16:44 -0700 |
commit | 2470dd3bb5f437658ec2f95ff3e7f0bb2e0b5448 (patch) | |
tree | 647f8b75f2248efbb8d732098e0cad3c2fe0f037 /AK | |
parent | 94f139c1117e2870c45df27085b004723b5b86ca (diff) | |
download | serenity-2470dd3bb5f437658ec2f95ff3e7f0bb2e0b5448.zip |
AK: Move bit streams from `LibCore`
Diffstat (limited to 'AK')
-rw-r--r-- | AK/BitStream.h | 400 | ||||
-rw-r--r-- | AK/Forward.h | 8 |
2 files changed, 408 insertions, 0 deletions
diff --git a/AK/BitStream.h b/AK/BitStream.h new file mode 100644 index 0000000000..922e86da17 --- /dev/null +++ b/AK/BitStream.h @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <AK/MaybeOwned.h> +#include <AK/OwnPtr.h> +#include <AK/Stream.h> + +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: + static ErrorOr<NonnullOwnPtr<BigEndianInputBitStream>> construct(MaybeOwned<Stream> stream) + { + return adopt_nonnull_own_or_enomem<BigEndianInputBitStream>(new BigEndianInputBitStream(move(stream))); + } + + // ^Stream + virtual ErrorOr<Bytes> 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<size_t> write(ReadonlyBytes bytes) override { return m_stream->write(bytes); } + virtual ErrorOr<void> 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<bool> read_bit() + { + return read_bits<bool>(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<Unsigned T = u64> + ErrorOr<T> read_bits(size_t count) + { + if constexpr (IsSame<bool, T>) { + VERIFY(count == 1); + } + T result = 0; + + size_t nread = 0; + while (nread < count) { + if (m_current_byte.has_value()) { + if constexpr (!IsSame<bool, T> && !IsSame<u8, T>) { + // 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<bool, T>) + 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: + BigEndianInputBitStream(MaybeOwned<Stream> stream) + : m_stream(move(stream)) + { + } + + Optional<u8> m_current_byte; + size_t m_bit_offset { 0 }; + MaybeOwned<Stream> 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: + static ErrorOr<NonnullOwnPtr<LittleEndianInputBitStream>> construct(MaybeOwned<Stream> stream) + { + return adopt_nonnull_own_or_enomem<LittleEndianInputBitStream>(new LittleEndianInputBitStream(move(stream))); + } + + LittleEndianInputBitStream(MaybeOwned<Stream> stream) + : m_stream(move(stream)) + { + } + + // ^Stream + virtual ErrorOr<Bytes> 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<size_t> write(ReadonlyBytes bytes) override { return m_stream->write(bytes); } + virtual ErrorOr<void> 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<bool> read_bit() + { + return read_bits<bool>(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<Unsigned T = u64> + ErrorOr<T> read_bits(size_t count) + { + if constexpr (IsSame<bool, T>) { + VERIFY(count == 1); + } + T result = 0; + + size_t nread = 0; + while (nread < count) { + if (m_current_byte.has_value()) { + if constexpr (!IsSame<bool, T> && !IsSame<u8, T>) { + // 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<bool, T>) + 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<u8> m_current_byte; + size_t m_bit_offset { 0 }; + MaybeOwned<Stream> 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<NonnullOwnPtr<BigEndianOutputBitStream>> construct(MaybeOwned<Stream> stream) + { + return adopt_nonnull_own_or_enomem<BigEndianOutputBitStream>(new BigEndianOutputBitStream(move(stream))); + } + + virtual ErrorOr<Bytes> read(Bytes) override + { + return Error::from_errno(EBADF); + } + + virtual ErrorOr<size_t> write(ReadonlyBytes bytes) override + { + VERIFY(m_bit_offset == 0); + return m_stream->write(bytes); + } + + template<Unsigned T> + ErrorOr<void> 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<void> 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> stream) + : m_stream(move(stream)) + { + } + + MaybeOwned<Stream> 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<NonnullOwnPtr<LittleEndianOutputBitStream>> construct(MaybeOwned<Stream> stream) + { + return adopt_nonnull_own_or_enomem<LittleEndianOutputBitStream>(new LittleEndianOutputBitStream(move(stream))); + } + + virtual ErrorOr<Bytes> read(Bytes) override + { + return Error::from_errno(EBADF); + } + + virtual ErrorOr<size_t> write(ReadonlyBytes bytes) override + { + VERIFY(m_bit_offset == 0); + return m_stream->write(bytes); + } + + template<Unsigned T> + ErrorOr<void> 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<void> 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> stream) + : m_stream(move(stream)) + { + } + + MaybeOwned<Stream> m_stream; + u8 m_current_byte { 0 }; + size_t m_bit_offset { 0 }; +}; + +} diff --git a/AK/Forward.h b/AK/Forward.h index d94e7a6fd6..e258449ffd 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -18,6 +18,8 @@ class ByteBuffer; } class Bitmap; +class BigEndianInputBitStream; +class BigEndianOutputBitStream; using ByteBuffer = Detail::ByteBuffer<32>; class CircularBuffer; class DeprecatedInputStream; @@ -31,6 +33,8 @@ class IPv4Address; class JsonArray; class JsonObject; class JsonValue; +class LittleEndianInputBitStream; +class LittleEndianOutputBitStream; class StackInfo; class DeprecatedFlyString; class DeprecatedString; @@ -152,6 +156,8 @@ class [[nodiscard]] ErrorOr; using AK::Array; using AK::Atomic; using AK::Badge; +using AK::BigEndianInputBitStream; +using AK::BigEndianOutputBitStream; using AK::Bitmap; using AK::ByteBuffer; using AK::Bytes; @@ -177,6 +183,8 @@ using AK::IPv4Address; using AK::JsonArray; using AK::JsonObject; using AK::JsonValue; +using AK::LittleEndianInputBitStream; +using AK::LittleEndianOutputBitStream; using AK::NonnullOwnPtr; using AK::NonnullOwnPtrVector; using AK::NonnullRefPtr; |