Skip to content

Commit

Permalink
ReadBitBuffer: Implement a function to read the ISO 14496-1 expande…
Browse files Browse the repository at this point in the history
…d field.

  - The behavior mirrors `ReadLeb128`, expect bytes are accumulated in big-endian order and the upper limit varies depending on the field.
  - Unify logic with `ReadLeb128` processing by providing a custom accumulator and custom upper limit.
  - Drive-by: Unify duplicate logic in the different `ReadUleb128` interfaces.
  - As an analog of `leb128` : Support logic of reading in padded zero bytes for safety (up to 8 bytes).

PiperOrigin-RevId: 662502476
  • Loading branch information
jwcullen committed Aug 13, 2024
1 parent ee6c740 commit 9767cc7
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 49 deletions.
2 changes: 2 additions & 0 deletions iamf/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

Expand Down
109 changes: 61 additions & 48 deletions iamf/common/read_bit_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@

#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <vector>

#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"
Expand Down Expand Up @@ -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<void(uint64_t, int, uint64_t&) const>
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<uint32_t>(accumulated_value);
return absl::OkStatus();
}

Expand Down Expand Up @@ -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<uint64_t>(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<uint64_t>(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<uint32_t>::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,
Expand Down
18 changes: 18 additions & 0 deletions iamf/common/read_bit_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
108 changes: 107 additions & 1 deletion iamf/common/tests/read_bit_buffer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "iamf/common/read_bit_buffer.h"

#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <vector>
Expand All @@ -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<uint32_t>::max();

ReadBitBuffer CreateReadBitBufferAndLoadBitsExpectOk(
std::vector<uint8_t>& 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<uint8_t> source_data_;
Expand Down Expand Up @@ -398,6 +409,101 @@ TEST_F(ReadBitBufferTest, ReadUleb128NotEnoughDataInBufferOrSource) {
EXPECT_EQ(rb_->buffer_bit_offset(), 0);
}

// ---- ReadIso14496_1Expanded Tests -----

struct ReadIso14496_1ExpandedTestCase {
std::vector<uint8_t> source_data;
uint32_t expected_size_of_instance;
};

using ReadIso14496_1Expanded =
::testing::TestWithParam<ReadIso14496_1ExpandedTestCase>;

TEST_P(ReadIso14496_1Expanded, ReadIso14496_1Expanded) {
// Grab a copy of the data because the ReadBitBuffer will consume it.
std::vector<uint8_t> 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<ReadIso14496_1ExpandedTestCase>({
{{0x00}, 0},
{{0x40}, 64},
{{0x7f}, 127},
}));

INSTANTIATE_TEST_SUITE_P(TwoByteInput, ReadIso14496_1Expanded,
testing::ValuesIn<ReadIso14496_1ExpandedTestCase>({
{{0x81, 0x00}, 128},
{{0x81, 0x01}, 129},
{{0xff, 0x7e}, 0x3ffe},
{{0xff, 0x7f}, 0x3fff},
}));

INSTANTIATE_TEST_SUITE_P(FourByteInput, ReadIso14496_1Expanded,
testing::ValuesIn<ReadIso14496_1ExpandedTestCase>({
{{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<ReadIso14496_1ExpandedTestCase>({
{{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<ReadIso14496_1ExpandedTestCase>({
{{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01},
1},
}));

TEST(ReadIso14496_1Expanded, ValidWhenDecodedValueEqualToMaxClassSize) {
constexpr uint32_t kMaxClassSizeExact = 127;
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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
Expand Down

0 comments on commit 9767cc7

Please sign in to comment.