diff --git a/iamf/common/BUILD b/iamf/common/BUILD index 770a093a..fa847990 100644 --- a/iamf/common/BUILD +++ b/iamf/common/BUILD @@ -39,7 +39,9 @@ cc_library( ":bit_buffer_util", ":macros", "//iamf/obu:leb128", + "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", ], ) diff --git a/iamf/common/read_bit_buffer.cc b/iamf/common/read_bit_buffer.cc index 9ce68375..9cd2e2b6 100644 --- a/iamf/common/read_bit_buffer.cc +++ b/iamf/common/read_bit_buffer.cc @@ -14,10 +14,13 @@ #include #include +#include #include #include +#include "absl/functional/any_invocable.h" #include "absl/status/status.h" +#include "absl/strings/str_cat.h" #include "iamf/common/bit_buffer_util.h" #include "iamf/common/macros.h" #include "iamf/obu/leb128.h" @@ -86,21 +89,50 @@ void ReadUnsignedLiteralBytes(int64_t& buffer_bit_offset, } } -absl::Status AccumulateUleb128Byte(const uint64_t& byte, const int index, - bool& is_terminal_block, +typedef absl::AnyInvocable + ByteAccumulator; + +absl::Status AccumulateUleb128Byte(const ByteAccumulator& accumulator, + uint32_t max_output, const uint64_t& byte, + const int index, bool& is_terminal_block, uint64_t& accumulated_value) { - accumulated_value |= (byte & 0x7f) << (7 * index); + accumulator(byte, index, accumulated_value); is_terminal_block = ((byte & 0x80) == 0); if ((index == (kMaxLeb128Size - 1)) && !is_terminal_block) { return absl::InvalidArgumentError( "Have read the max allowable bytes for a uleb128, but bitstream " "says to keep reading."); } - if (accumulated_value > UINT32_MAX) { + if (accumulated_value > max_output) { return absl::InvalidArgumentError( - "Overflow - data does not fit into a DecodedUleb128, i.e. a " - "uint32_t"); + absl::StrCat("Overflow - data is larger than max_output=", max_output)); + } + return absl::OkStatus(); +} + +// Common internal function for reading uleb128 and iso14496_1 expanded. They +// have similar logic except the bytes are accumulated in different orders, and +// they have different max output values. +absl::Status AccumulateUleb128OrIso14496_1Internal( + const ByteAccumulator& accumulator, const uint32_t max_output, + ReadBitBuffer& rb, uint32_t& output, int8_t& encoded_size) { + uint64_t accumulated_value = 0; + uint64_t byte = 0; + bool terminal_block = false; + encoded_size = 0; + for (int i = 0; i < kMaxLeb128Size; ++i) { + RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, byte)); + encoded_size++; + RETURN_IF_NOT_OK(AccumulateUleb128Byte(accumulator, max_output, byte, i, + terminal_block, accumulated_value)); + + if (terminal_block) { + break; + } } + // Accumulated value is guaranteed to fit into a `uint32_t` at this + // stage. + output = static_cast(accumulated_value); return absl::OkStatus(); } @@ -203,53 +235,34 @@ absl::Status ReadBitBuffer::ReadString(std::string& output) { "Failed to find the null terminator for data= "); } -/*!\brief Reads an unsigned leb128 from buffer into `uleb128`. - * - * - * In accordance with the encoder implementation, this function will consume at - * most `kMaxLeb128Size` bytes of the read buffer. - * - * \param uleb128 Decoded unsigned leb128 from buffer will be written here. - * \return `absl::OkStatus()` on success. `absl::InvalidArgumentError()` if - * the consumed value from the buffer does not fit into the 32 bits of - * uleb128, or if the buffer is exhausted before the uleb128 is fully read. - * `absl::UnknownError()` if the `rb->bit_offset` is negative. - */ absl::Status ReadBitBuffer::ReadULeb128(DecodedUleb128& uleb128) { - uint64_t accumulated_value = 0; - uint64_t byte = 0; - bool terminal_block = false; - for (int i = 0; i < kMaxLeb128Size; ++i) { - RETURN_IF_NOT_OK(ReadUnsignedLiteral(8, byte)); - RETURN_IF_NOT_OK( - AccumulateUleb128Byte(byte, i, terminal_block, accumulated_value)); - if (terminal_block) { - break; - } - } - // Accumulated value is guaranteed to fit into a uint_32_t at this stage. - uleb128 = static_cast(accumulated_value); - return absl::OkStatus(); + int8_t unused_size; + return ReadULeb128(uleb128, unused_size); } absl::Status ReadBitBuffer::ReadULeb128(DecodedUleb128& uleb128, int8_t& encoded_uleb128_size) { - uint64_t accumulated_value = 0; - uint64_t byte = 0; - bool terminal_block = false; - encoded_uleb128_size = 0; - for (int i = 0; i < kMaxLeb128Size; ++i) { - RETURN_IF_NOT_OK(ReadUnsignedLiteral(8, byte)); - encoded_uleb128_size++; - RETURN_IF_NOT_OK( - AccumulateUleb128Byte(byte, i, terminal_block, accumulated_value)); - if (terminal_block) { - break; - } - } - // Accumulated value is guaranteed to fit into a uint_32_t at this stage. - uleb128 = static_cast(accumulated_value); - return absl::OkStatus(); + static const ByteAccumulator little_endian_accumulator = + [&](uint64_t byte, int index, uint64_t& accumulated_value) { + accumulated_value |= (byte & 0x7f) << (7 * index); + }; + // IAMF requires all `leb128`s to decode to a value that fits in 32 bits. + const uint32_t kMaxUleb128 = std::numeric_limits::max(); + return AccumulateUleb128OrIso14496_1Internal(little_endian_accumulator, + kMaxUleb128, *this, uleb128, + encoded_uleb128_size); +} + +absl::Status ReadBitBuffer::ReadIso14496_1Expanded(uint32_t max_class_size, + uint32_t& size_of_instance) { + static const ByteAccumulator big_endian_accumulator = + [](uint64_t byte, int /*index*/, uint64_t& accumulated_value) { + accumulated_value = accumulated_value << 7 | (byte & 0x7f); + }; + int8_t unused_encoded_size = 0; + return AccumulateUleb128OrIso14496_1Internal( + big_endian_accumulator, max_class_size, *this, size_of_instance, + unused_encoded_size); } absl::Status ReadBitBuffer::ReadUint8Vector(const int& count, diff --git a/iamf/common/read_bit_buffer.h b/iamf/common/read_bit_buffer.h index 6d63162d..97544e43 100644 --- a/iamf/common/read_bit_buffer.h +++ b/iamf/common/read_bit_buffer.h @@ -109,6 +109,9 @@ class ReadBitBuffer { absl::Status ReadString(std::string& output); /*!\brief Reads an unsigned leb128 from buffer into `uleb128`. + * + * This version is useful when the caller does not care about the number of + * bytes used to encode the data in the bitstream. * * \param uleb128 Decoded unsigned leb128 from buffer will be written here. * \return `absl::OkStatus()` on success. `absl::InvalidArgumentError()` if @@ -140,6 +143,21 @@ class ReadBitBuffer { absl::Status ReadULeb128(DecodedUleb128& uleb128, int8_t& encoded_uleb128_size); + /*!\brief Reads the expandable size according to ISO 14496-1. + * + * \param max_class_size Maximum class size in bits. + * \param size_of_instance Size of instance according to the expandable size. + * \return `absl::OkStatus()` on success. `absl::InvalidArgumentError()` if + * the consumed data from the buffer does not fit into the 32 bit output, + * or if the data encoded is larger than the `max_class_size` bits. + * `absl::ResourceExhaustedError()` if the buffer is exhausted + * before the expanded field is fully read and source does not have the + * requisite data to complete the expanded field. `absl::UnknownError()` + * if the `rb->bit_offset` is negative. + */ + absl::Status ReadIso14496_1Expanded(uint32_t max_class_size, + uint32_t& size_of_instance); + /*!\brief Reads an uint8 vector from buffer into `output`. * * \param count Number of uint8s to read from the buffer. diff --git a/iamf/common/tests/read_bit_buffer_test.cc b/iamf/common/tests/read_bit_buffer_test.cc index 6dade40b..17423173 100644 --- a/iamf/common/tests/read_bit_buffer_test.cc +++ b/iamf/common/tests/read_bit_buffer_test.cc @@ -12,6 +12,7 @@ #include "iamf/common/read_bit_buffer.h" #include +#include #include #include #include @@ -24,13 +25,23 @@ #include "iamf/obu/leb128.h" namespace iamf_tools { +namespace { using absl::StatusCode::kInvalidArgument; using absl::StatusCode::kResourceExhausted; using testing::ElementsAreArray; -namespace { using ::absl_testing::IsOk; +constexpr int kBitsPerByte = 8; +constexpr int kMaxUint32 = std::numeric_limits::max(); + +ReadBitBuffer CreateReadBitBufferAndLoadBitsExpectOk( + std::vector& source_data) { + ReadBitBuffer rb(source_data.size() * kBitsPerByte, &source_data); + EXPECT_THAT(rb.LoadBits(source_data.size() * kBitsPerByte), IsOk()); + return rb; +} + class ReadBitBufferTest : public ::testing::Test { public: std::vector source_data_; @@ -398,6 +409,101 @@ TEST_F(ReadBitBufferTest, ReadUleb128NotEnoughDataInBufferOrSource) { EXPECT_EQ(rb_->buffer_bit_offset(), 0); } +// ---- ReadIso14496_1Expanded Tests ----- + +struct ReadIso14496_1ExpandedTestCase { + std::vector source_data; + uint32_t expected_size_of_instance; +}; + +using ReadIso14496_1Expanded = + ::testing::TestWithParam; + +TEST_P(ReadIso14496_1Expanded, ReadIso14496_1Expanded) { + // Grab a copy of the data because the ReadBitBuffer will consume it. + std::vector source_data = GetParam().source_data; + ReadBitBuffer rb = CreateReadBitBufferAndLoadBitsExpectOk(source_data); + + uint32_t output_size_of_instance = 0; + EXPECT_THAT(rb.ReadIso14496_1Expanded(kMaxUint32, output_size_of_instance), + IsOk()); + + EXPECT_EQ(output_size_of_instance, GetParam().expected_size_of_instance); +} + +INSTANTIATE_TEST_SUITE_P(OneByteInput, ReadIso14496_1Expanded, + testing::ValuesIn({ + {{0x00}, 0}, + {{0x40}, 64}, + {{0x7f}, 127}, + })); + +INSTANTIATE_TEST_SUITE_P(TwoByteInput, ReadIso14496_1Expanded, + testing::ValuesIn({ + {{0x81, 0x00}, 128}, + {{0x81, 0x01}, 129}, + {{0xff, 0x7e}, 0x3ffe}, + {{0xff, 0x7f}, 0x3fff}, + })); + +INSTANTIATE_TEST_SUITE_P(FourByteInput, ReadIso14496_1Expanded, + testing::ValuesIn({ + {{0x81, 0x80, 0x80, 0x00}, 0x0200000}, + {{0x81, 0x80, 0x80, 0x01}, 0x0200001}, + {{0xff, 0xff, 0xff, 0x7e}, 0x0ffffffe}, + {{0xff, 0xff, 0xff, 0x7f}, 0x0fffffff}, + })); + +INSTANTIATE_TEST_SUITE_P(FiveByteInput, ReadIso14496_1Expanded, + testing::ValuesIn({ + {{0x81, 0x80, 0x80, 0x80, 0x00}, 0x10000000}, + {{0x8f, 0x80, 0x80, 0x80, 0x00}, 0xf0000000}, + {{0x8f, 0xff, 0xff, 0xff, 0x7f}, 0xffffffff}, + })); + +INSTANTIATE_TEST_SUITE_P(HandlesLeadingZeroes, ReadIso14496_1Expanded, + testing::ValuesIn({ + {{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}, + 1}, + })); + +TEST(ReadIso14496_1Expanded, ValidWhenDecodedValueEqualToMaxClassSize) { + constexpr uint32_t kMaxClassSizeExact = 127; + std::vector source_data_ = {0x7f}; + ReadBitBuffer rb = CreateReadBitBufferAndLoadBitsExpectOk(source_data_); + + uint32_t unused_output = 0; + EXPECT_THAT(rb.ReadIso14496_1Expanded(kMaxClassSizeExact, unused_output), + IsOk()); +} + +TEST(ReadIso14496_1Expanded, InvalidWhenDecodedValueIsGreaterThanMaxClassSize) { + constexpr uint32_t kMaxClassSizeTooLow = 126; + std::vector source_data_ = {0x7f}; + ReadBitBuffer rb = CreateReadBitBufferAndLoadBitsExpectOk(source_data_); + + uint32_t unused_output = 0; + EXPECT_FALSE( + rb.ReadIso14496_1Expanded(kMaxClassSizeTooLow, unused_output).ok()); +} + +TEST(ReadIso14496_1Expanded, InvalidWhenDecodedValueDoesNotFitIntoUint32) { + std::vector source_data_ = {0x90, 0x80, 0x80, 0x80, 0x00}; + ReadBitBuffer rb = CreateReadBitBufferAndLoadBitsExpectOk(source_data_); + + uint32_t unused_output = 0; + EXPECT_FALSE(rb.ReadIso14496_1Expanded(kMaxUint32, unused_output).ok()); +} + +TEST(ReadIso14496_1Expanded, InvalidWhenInputDataSignalsMoreThan8Bytes) { + std::vector source_data_ = {0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x01}; + ReadBitBuffer rb = CreateReadBitBufferAndLoadBitsExpectOk(source_data_); + + uint32_t unused_output = 0; + EXPECT_FALSE(rb.ReadIso14496_1Expanded(kMaxUint32, unused_output).ok()); +} + // --- ReadUint8Vector tests --- // Successful ReadUint8Vector reads