From 5af152c214987f55b747cdb407546b0625da6802 Mon Sep 17 00:00:00 2001 From: Danil Chapovalov Date: Tue, 31 Aug 2021 15:27:51 +0200 Subject: [PATCH] Inroduce BitstreamReader class to parse sequences of bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With intent to replace BitBuffer. This version is optimised for binary size and readability of the parsers at the cost of slower parsing of invalid bitstreams Bug: None Change-Id: Ib054e2a7758b9a69cbf2559e739465b104a7dcf3 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230244 Commit-Queue: Danil Chapovalov Reviewed-by: Mirko Bonadei Reviewed-by: Erik Språng Cr-Commit-Position: refs/heads/main@{#34886} --- rtc_base/BUILD.gn | 20 ++ rtc_base/bitstream_reader.cc | 133 ++++++++++ rtc_base/bitstream_reader.h | 142 +++++++++++ rtc_base/bitstream_reader_unittest.cc | 335 ++++++++++++++++++++++++++ 4 files changed, 630 insertions(+) create mode 100644 rtc_base/bitstream_reader.cc create mode 100644 rtc_base/bitstream_reader.h create mode 100644 rtc_base/bitstream_reader_unittest.cc diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 809bb4aadf..ac1b813805 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -36,6 +36,23 @@ rtc_source_set("protobuf_utils") { } } +rtc_source_set("bitstream_reader") { + sources = [ + "bitstream_reader.cc", + "bitstream_reader.h", + ] + deps = [ + ":checks", + ":safe_conversions", + "../api:array_view", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/numeric:bits", + "//third_party/abseil-cpp/absl/strings", + ] +} + rtc_source_set("compile_assert_c") { sources = [ "compile_assert_c.h" ] } @@ -1323,6 +1340,7 @@ if (rtc_include_tests) { "atomic_ops_unittest.cc", "base64_unittest.cc", "bit_buffer_unittest.cc", + "bitstream_reader_unittest.cc", "bounded_inline_vector_unittest.cc", "buffer_queue_unittest.cc", "buffer_unittest.cc", @@ -1366,6 +1384,7 @@ if (rtc_include_tests) { sources += [ "win/windows_version_unittest.cc" ] } deps = [ + ":bitstream_reader", ":bounded_inline_vector", ":checks", ":criticalsection", @@ -1406,6 +1425,7 @@ if (rtc_include_tests) { absl_deps = [ "//third_party/abseil-cpp/absl/base:core_headers", "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/numeric:bits", "//third_party/abseil-cpp/absl/types:optional", ] } diff --git a/rtc_base/bitstream_reader.cc b/rtc_base/bitstream_reader.cc new file mode 100644 index 0000000000..0bb604970b --- /dev/null +++ b/rtc_base/bitstream_reader.cc @@ -0,0 +1,133 @@ +/* + * Copyright 2021 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/bitstream_reader.h" + +#include + +#include "absl/numeric/bits.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { + +uint64_t BitstreamReader::ReadBits(int bits) { + RTC_DCHECK_GE(bits, 0); + RTC_DCHECK_LE(bits, 64); + set_last_read_is_verified(false); + + if (remaining_bits_ < bits) { + remaining_bits_ -= bits; + return 0; + } + + int remaining_bits_in_first_byte = remaining_bits_ % 8; + remaining_bits_ -= bits; + if (bits < remaining_bits_in_first_byte) { + // Reading fewer bits than what's left in the current byte, just + // return the portion of this byte that is needed. + int offset = (remaining_bits_in_first_byte - bits); + return ((*bytes_) >> offset) & ((1 << bits) - 1); + } + + uint64_t result = 0; + if (remaining_bits_in_first_byte > 0) { + // Read all bits that were left in the current byte and consume that byte. + bits -= remaining_bits_in_first_byte; + uint8_t mask = (1 << remaining_bits_in_first_byte) - 1; + result = static_cast(*bytes_ & mask) << bits; + ++bytes_; + } + + // Read as many full bytes as we can. + while (bits >= 8) { + bits -= 8; + result |= uint64_t{*bytes_} << bits; + ++bytes_; + } + // Whatever is left to read is smaller than a byte, so grab just the needed + // bits and shift them into the lowest bits. + if (bits > 0) { + result |= (*bytes_ >> (8 - bits)); + } + return result; +} + +int BitstreamReader::ReadBit() { + set_last_read_is_verified(false); + --remaining_bits_; + if (remaining_bits_ < 0) { + return 0; + } + + int bit_position = remaining_bits_ % 8; + if (bit_position == 0) { + // Read the last bit from current byte and move to the next byte. + return (*bytes_++) & 0x01; + } + + return (*bytes_ >> bit_position) & 0x01; +} + +void BitstreamReader::ConsumeBits(int bits) { + RTC_DCHECK_GE(bits, 0); + set_last_read_is_verified(false); + + int remaining_bytes = (remaining_bits_ + 7) / 8; + remaining_bits_ -= bits; + int new_remaining_bytes = (remaining_bits_ + 7) / 8; + // When `remaining_bits_` is negative, `BitstreamReader` is in failure state + // and `bytes_' member no longer used, thus its value doesn't matter. + // In such case it doesn't matter that negative integer division rounds up + // instead of down and thus this byte adjustement might seem incorrect. + bytes_ += (remaining_bytes - new_remaining_bytes); +} + +uint32_t BitstreamReader::ReadNonSymmetric(uint32_t num_values) { + RTC_DCHECK_GT(num_values, 0); + RTC_DCHECK_LE(num_values, uint32_t{1} << 31); + + int width = absl::bit_width(num_values); + uint32_t num_min_bits_values = (uint32_t{1} << width) - num_values; + + uint64_t val = ReadBits(width - 1); + if (val < num_min_bits_values) { + return val; + } + return (val << 1) + ReadBit() - num_min_bits_values; +} + +uint32_t BitstreamReader::ReadExponentialGolomb() { + // Count the number of leading 0. + int zero_bit_count = 0; + while (ReadBit() == 0) { + if (++zero_bit_count >= 32) { + // Golob value won't fit into 32 bits of the return value. Fail the parse. + Invalidate(); + return 0; + } + } + + // The bit count of the value is the number of zeros + 1. + // However the first '1' was already read above. + return (uint32_t{1} << zero_bit_count) + + rtc::dchecked_cast(ReadBits(zero_bit_count)) - 1; +} + +int BitstreamReader::ReadSignedExponentialGolomb() { + uint32_t unsigned_val = ReadExponentialGolomb(); + if ((unsigned_val & 1) == 0) { + return -static_cast(unsigned_val / 2); + } else { + return (unsigned_val + 1) / 2; + } +} + +} // namespace webrtc diff --git a/rtc_base/bitstream_reader.h b/rtc_base/bitstream_reader.h new file mode 100644 index 0000000000..8c0f66fa91 --- /dev/null +++ b/rtc_base/bitstream_reader.h @@ -0,0 +1,142 @@ +/* + * Copyright 2021 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef RTC_BASE_BITSTREAM_READER_H_ +#define RTC_BASE_BITSTREAM_READER_H_ + +#include + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { + +// A class to parse sequence of bits. Byte order is assumed big-endian/network. +// This class is optimized for successful parsing and binary size. +// Individual calls to `Read` and `ConsumeBits` never fail. Instead they may +// change the class state into 'failure state'. User of this class should verify +// parsing by checking if class is in that 'failure state' by calling `Ok`. +// That verification can be done once after multiple reads. +class BitstreamReader { + public: + explicit BitstreamReader( + rtc::ArrayView bytes ABSL_ATTRIBUTE_LIFETIME_BOUND); + explicit BitstreamReader( + absl::string_view bytes ABSL_ATTRIBUTE_LIFETIME_BOUND); + BitstreamReader(const BitstreamReader&) = default; + BitstreamReader& operator=(const BitstreamReader&) = default; + ~BitstreamReader(); + + // Return number of unread bits in the buffer, or negative number if there + // was a reading error. + int RemainingBitCount() const; + + // Returns `true` iff all calls to `Read` and `ConsumeBits` were successful. + bool Ok() const { return RemainingBitCount() >= 0; } + + // Sets `BitstreamReader` into the failure state. + void Invalidate() { remaining_bits_ = -1; } + + // Moves current read position forward. `bits` must be non-negative. + void ConsumeBits(int bits); + + // Reads single bit. Returns 0 or 1. + ABSL_MUST_USE_RESULT int ReadBit(); + + // Reads `bits` from the bitstream. `bits` must be in range [0, 64]. + // Returns an unsigned integer in range [0, 2^bits - 1]. + // On failure sets `BitstreamReader` into the failure state and returns 0. + ABSL_MUST_USE_RESULT uint64_t ReadBits(int bits); + + // Reads unsigned integer of fixed width. + template ::value && + sizeof(T) <= 8>::type* = nullptr> + ABSL_MUST_USE_RESULT T Read() { + return rtc::dchecked_cast(ReadBits(sizeof(T) * 8)); + } + + // Reads single bit as boolean. + template <> + ABSL_MUST_USE_RESULT bool Read() { + return ReadBit() != 0; + } + + // Reads value in range [0, `num_values` - 1]. + // This encoding is similar to ReadBits(val, Ceil(Log2(num_values)), + // but reduces wastage incurred when encoding non-power of two value ranges + // Non symmetric values are encoded as: + // 1) n = bit_width(num_values) + // 2) k = (1 << n) - num_values + // Value v in range [0, k - 1] is encoded in (n-1) bits. + // Value v in range [k, num_values - 1] is encoded as (v+k) in n bits. + // https://aomediacodec.github.io/av1-spec/#nsn + uint32_t ReadNonSymmetric(uint32_t num_values); + + // Reads exponential golomb encoded value. + // On failure sets `BitstreamReader` into the failure state and returns + // unspecified value. + // Exponential golomb values are encoded as: + // 1) x = source val + 1 + // 2) In binary, write [bit_width(x) - 1] 0s, then x + // To decode, we count the number of leading 0 bits, read that many + 1 bits, + // and increment the result by 1. + // Fails the parsing if the value wouldn't fit in a uint32_t. + uint32_t ReadExponentialGolomb(); + + // Reads signed exponential golomb values at the current offset. Signed + // exponential golomb values are just the unsigned values mapped to the + // sequence 0, 1, -1, 2, -2, etc. in order. + // On failure sets `BitstreamReader` into the failure state and returns + // unspecified value. + int ReadSignedExponentialGolomb(); + + private: + void set_last_read_is_verified(bool value) const; + + // Next byte with at least one unread bit. + const uint8_t* bytes_; + + // Number of bits remained to read. + int remaining_bits_; + + // Unused in release mode. + mutable bool last_read_is_verified_ = true; +}; + +inline BitstreamReader::BitstreamReader(rtc::ArrayView bytes) + : bytes_(bytes.data()), remaining_bits_(bytes.size() * 8) {} + +inline BitstreamReader::BitstreamReader(absl::string_view bytes) + : bytes_(reinterpret_cast(bytes.data())), + remaining_bits_(bytes.size() * 8) {} + +inline BitstreamReader::~BitstreamReader() { + RTC_DCHECK(last_read_is_verified_) << "Latest calls to Read or ConsumeBit " + "were not checked with Ok function."; +} + +inline void BitstreamReader::set_last_read_is_verified(bool value) const { +#ifdef RTC_DCHECK_IS_ON + last_read_is_verified_ = value; +#endif +} + +inline int BitstreamReader::RemainingBitCount() const { + set_last_read_is_verified(true); + return remaining_bits_; +} + +} // namespace webrtc + +#endif // RTC_BASE_BITSTREAM_READER_H_ diff --git a/rtc_base/bitstream_reader_unittest.cc b/rtc_base/bitstream_reader_unittest.cc new file mode 100644 index 0000000000..45c4eca8d4 --- /dev/null +++ b/rtc_base/bitstream_reader_unittest.cc @@ -0,0 +1,335 @@ +/* + * Copyright 2021 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/bitstream_reader.h" + +#include +#include + +#include +#include + +#include "absl/numeric/bits.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "rtc_base/checks.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +TEST(BitstreamReaderTest, InDebugModeRequiresToCheckOkStatusBeforeDestruction) { + const uint8_t bytes[32] = {}; + absl::optional reader(absl::in_place, bytes); + + EXPECT_GE(reader->ReadBits(7), 0u); +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(OS_ANDROID) + EXPECT_DEATH(reader = absl::nullopt, ""); +#endif + EXPECT_TRUE(reader->Ok()); + reader = absl::nullopt; +} + +TEST(BitstreamReaderTest, InDebugModeMayCheckRemainingBitsInsteadOfOkStatus) { + const uint8_t bytes[32] = {}; + absl::optional reader(absl::in_place, bytes); + + EXPECT_GE(reader->ReadBit(), 0); +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(OS_ANDROID) + EXPECT_DEATH(reader = absl::nullopt, ""); +#endif + EXPECT_GE(reader->RemainingBitCount(), 0); + reader = absl::nullopt; +} + +TEST(BitstreamReaderTest, ConsumeBits) { + const uint8_t bytes[32] = {}; + BitstreamReader reader(bytes); + + int total_bits = 32 * 8; + EXPECT_EQ(reader.RemainingBitCount(), total_bits); + reader.ConsumeBits(3); + total_bits -= 3; + EXPECT_EQ(reader.RemainingBitCount(), total_bits); + reader.ConsumeBits(3); + total_bits -= 3; + EXPECT_EQ(reader.RemainingBitCount(), total_bits); + reader.ConsumeBits(15); + total_bits -= 15; + EXPECT_EQ(reader.RemainingBitCount(), total_bits); + reader.ConsumeBits(67); + total_bits -= 67; + EXPECT_EQ(reader.RemainingBitCount(), total_bits); + EXPECT_TRUE(reader.Ok()); + + reader.ConsumeBits(32 * 8); + EXPECT_FALSE(reader.Ok()); + EXPECT_LT(reader.RemainingBitCount(), 0); +} + +TEST(BitstreamReaderTest, ReadBit) { + const uint8_t bytes[] = {0b0100'0001, 0b1011'0001}; + BitstreamReader reader(bytes); + // First byte. + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 1); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 0); + + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 1); + + // Second byte. + EXPECT_EQ(reader.ReadBit(), 1); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 1); + EXPECT_EQ(reader.ReadBit(), 1); + + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_EQ(reader.ReadBit(), 1); + + EXPECT_TRUE(reader.Ok()); + // Try to read beyound the buffer. + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBoolConsumesSingleBit) { + const uint8_t bytes[] = {0b1010'1010}; + BitstreamReader reader(bytes); + ASSERT_EQ(reader.RemainingBitCount(), 8); + EXPECT_TRUE(reader.Read()); + EXPECT_EQ(reader.RemainingBitCount(), 7); +} + +TEST(BitstreamReaderTest, ReadBytesAligned) { + const uint8_t bytes[] = {0x0A, // + 0xBC, // + 0xDE, 0xF1, // + 0x23, 0x45, 0x67, 0x89}; + BitstreamReader reader(bytes); + EXPECT_EQ(reader.Read(), 0x0Au); + EXPECT_EQ(reader.Read(), 0xBCu); + EXPECT_EQ(reader.Read(), 0xDEF1u); + EXPECT_EQ(reader.Read(), 0x23456789u); + EXPECT_TRUE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBytesOffset4) { + const uint8_t bytes[] = {0x0A, 0xBC, 0xDE, 0xF1, 0x23, + 0x45, 0x67, 0x89, 0x0A}; + BitstreamReader reader(bytes); + reader.ConsumeBits(4); + + EXPECT_EQ(reader.Read(), 0xABu); + EXPECT_EQ(reader.Read(), 0xCDu); + EXPECT_EQ(reader.Read(), 0xEF12u); + EXPECT_EQ(reader.Read(), 0x34567890u); + EXPECT_TRUE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBytesOffset3) { + // The pattern we'll check against is counting down from 0b1111. It looks + // weird here because it's all offset by 3. + // Byte pattern is: + // 56701234 + // 0b00011111, + // 0b11011011, + // 0b10010111, + // 0b01010011, + // 0b00001110, + // 0b11001010, + // 0b10000110, + // 0b01000010 + // xxxxx <-- last 5 bits unused. + + // The bytes. It almost looks like counting down by two at a time, except the + // jump at 5->3->0, since that's when the high bit is turned off. + const uint8_t bytes[] = {0x1F, 0xDB, 0x97, 0x53, 0x0E, 0xCA, 0x86, 0x42}; + + BitstreamReader reader(bytes); + reader.ConsumeBits(3); + EXPECT_EQ(reader.Read(), 0xFEu); + EXPECT_EQ(reader.Read(), 0xDCBAu); + EXPECT_EQ(reader.Read(), 0x98765432u); + EXPECT_TRUE(reader.Ok()); + + // 5 bits left unread. Not enough to read a uint8_t. + EXPECT_EQ(reader.RemainingBitCount(), 5); + EXPECT_EQ(reader.Read(), 0); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBits) { + const uint8_t bytes[] = {0b010'01'101, 0b0011'00'1'0}; + BitstreamReader reader(bytes); + EXPECT_EQ(reader.ReadBits(3), 0b010u); + EXPECT_EQ(reader.ReadBits(2), 0b01u); + EXPECT_EQ(reader.ReadBits(7), 0b101'0011u); + EXPECT_EQ(reader.ReadBits(2), 0b00u); + EXPECT_EQ(reader.ReadBits(1), 0b1u); + EXPECT_EQ(reader.ReadBits(1), 0b0u); + EXPECT_TRUE(reader.Ok()); + + EXPECT_EQ(reader.ReadBits(1), 0u); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadZeroBits) { + BitstreamReader reader(rtc::ArrayView(nullptr, 0)); + + EXPECT_EQ(reader.ReadBits(0), 0u); + EXPECT_TRUE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBitFromEmptyArray) { + BitstreamReader reader(rtc::ArrayView(nullptr, 0)); + + // Trying to read from the empty array shouldn't dereference the pointer, + // i.e. shouldn't crash. + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBitsFromEmptyArray) { + BitstreamReader reader(rtc::ArrayView(nullptr, 0)); + + // Trying to read from the empty array shouldn't dereference the pointer, + // i.e. shouldn't crash. + EXPECT_EQ(reader.ReadBits(1), 0u); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadBits64) { + const uint8_t bytes[] = {0x4D, 0x32, 0xAB, 0x54, 0x00, 0xFF, 0xFE, 0x01, + 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89}; + BitstreamReader reader(bytes); + + EXPECT_EQ(reader.ReadBits(33), 0x4D32AB5400FFFE01u >> (64 - 33)); + + constexpr uint64_t kMask31Bits = (1ull << 32) - 1; + EXPECT_EQ(reader.ReadBits(31), 0x4D32AB5400FFFE01ull & kMask31Bits); + + EXPECT_EQ(reader.ReadBits(64), 0xABCDEF0123456789ull); + EXPECT_TRUE(reader.Ok()); + + // Nothing more to read. + EXPECT_EQ(reader.ReadBit(), 0); + EXPECT_FALSE(reader.Ok()); +} + +TEST(BitstreamReaderTest, CanPeekBitsUsingCopyConstructor) { + // BitstreamReader doesn't have peek function. To simulate it, user may use + // cheap BitstreamReader copy constructor. + const uint8_t bytes[] = {0x0A, 0xBC}; + BitstreamReader reader(bytes); + reader.ConsumeBits(4); + ASSERT_EQ(reader.RemainingBitCount(), 12); + + BitstreamReader peeker = reader; + EXPECT_EQ(peeker.ReadBits(8), 0xABu); + EXPECT_EQ(peeker.RemainingBitCount(), 4); + + EXPECT_EQ(reader.RemainingBitCount(), 12); + // Can resume reading from before peeker was created. + EXPECT_EQ(reader.ReadBits(4), 0xAu); + EXPECT_EQ(reader.RemainingBitCount(), 8); +} + +TEST(BitstreamReaderTest, + ReadNonSymmetricSameNumberOfBitsWhenNumValuesPowerOf2) { + const uint8_t bytes[2] = {0xf3, 0xa0}; + BitstreamReader reader(bytes); + + ASSERT_EQ(reader.RemainingBitCount(), 16); + EXPECT_EQ(reader.ReadNonSymmetric(/*num_values=*/1 << 4), 0xfu); + EXPECT_EQ(reader.ReadNonSymmetric(/*num_values=*/1 << 4), 0x3u); + EXPECT_EQ(reader.ReadNonSymmetric(/*num_values=*/1 << 4), 0xau); + EXPECT_EQ(reader.ReadNonSymmetric(/*num_values=*/1 << 4), 0x0u); + EXPECT_EQ(reader.RemainingBitCount(), 0); + EXPECT_TRUE(reader.Ok()); +} + +TEST(BitstreamReaderTest, ReadNonSymmetricOnlyValueConsumesZeroBits) { + const uint8_t bytes[2] = {}; + BitstreamReader reader(bytes); + + ASSERT_EQ(reader.RemainingBitCount(), 16); + EXPECT_EQ(reader.ReadNonSymmetric(/*num_values=*/1), 0u); + EXPECT_EQ(reader.RemainingBitCount(), 16); +} + +std::array GolombEncoded(uint32_t val) { + int val_width = absl::bit_width(val + 1); + int total_width = 2 * val_width - 1; + uint64_t representation = (uint64_t{val} + 1) << (64 - total_width); + std::array result; + for (int i = 0; i < 8; ++i) { + result[i] = representation >> (7 - i) * 8; + } + return result; +} + +TEST(BitstreamReaderTest, GolombUint32Values) { + // Test over the uint32_t range with a large enough step that the test doesn't + // take forever. Around 20,000 iterations should do. + const int kStep = std::numeric_limits::max() / 20000; + for (uint32_t i = 0; i < std::numeric_limits::max() - kStep; + i += kStep) { + std::array buffer = GolombEncoded(i); + BitstreamReader reader(buffer); + // Use assert instead of EXPECT to avoid spamming thousands of failed + // expectation when this test fails. + ASSERT_EQ(reader.ReadExponentialGolomb(), i); + EXPECT_TRUE(reader.Ok()); + } +} + +TEST(BitstreamReaderTest, SignedGolombValues) { + uint8_t golomb_bits[][1] = { + {0b1'0000000}, {0b010'00000}, {0b011'00000}, {0b00100'000}, {0b00111'000}, + }; + int expected[] = {0, 1, -1, 2, -3}; + for (size_t i = 0; i < sizeof(golomb_bits); ++i) { + BitstreamReader reader(golomb_bits[i]); + EXPECT_EQ(reader.ReadSignedExponentialGolomb(), expected[i]) + << "Mismatch in expected/decoded value for golomb_bits[" << i + << "]: " << static_cast(golomb_bits[i][0]); + EXPECT_TRUE(reader.Ok()); + } +} + +TEST(BitstreamReaderTest, NoGolombOverread) { + const uint8_t bytes[] = {0x00, 0xFF, 0xFF}; + // Make sure the bit buffer correctly enforces byte length on golomb reads. + // If it didn't, the above buffer would be valid at 3 bytes. + BitstreamReader reader1(rtc::MakeArrayView(bytes, 1)); + // When parse fails, `ReadExponentialGolomb` may return any number. + reader1.ReadExponentialGolomb(); + EXPECT_FALSE(reader1.Ok()); + + BitstreamReader reader2(rtc::MakeArrayView(bytes, 2)); + reader2.ReadExponentialGolomb(); + EXPECT_FALSE(reader2.Ok()); + + BitstreamReader reader3(bytes); + // Golomb should have read 9 bits, so 0x01FF, and since it is golomb, the + // result is 0x01FF - 1 = 0x01FE. + EXPECT_EQ(reader3.ReadExponentialGolomb(), 0x01FEu); + EXPECT_TRUE(reader3.Ok()); +} + +} // namespace +} // namespace webrtc