blob: 09d8ae47dda84d32d5921635462cb7de6a879305 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/bmff/offset_adjustment.h"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "formats/bmff/box_header.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "riegeli/base/types.h"
#include "riegeli/bytes/string_reader.h"
#include "riegeli/bytes/string_writer.h"
#include "testing/test_string_utils.h"
namespace credentio {
namespace {
using ::absl_testing::StatusIs;
using ::credentio_testing::Uint16Str;
using ::credentio_testing::Uint32Str;
using ::credentio_testing::Uint64Str;
using ::credentio_testing::Uint8Str;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
class NoSizeStringReader : public riegeli::StringReader<> {
public:
using riegeli::StringReader<>::StringReader;
bool SupportsSize() override { return false; }
};
class SizeNotValuedStringReader : public riegeli::StringReader<> {
public:
using riegeli::StringReader<>::StringReader;
bool SupportsSize() override { return true; }
protected:
std::optional<riegeli::Position> SizeImpl() override { return std::nullopt; }
};
class CustomSizeStringReader : public riegeli::StringReader<> {
public:
explicit CustomSizeStringReader(absl::string_view src,
riegeli::Position reported_size)
: riegeli::StringReader<>(src), reported_size_(reported_size) {}
bool SupportsSize() override { return true; }
protected:
std::optional<riegeli::Position> SizeImpl() override {
return reported_size_;
}
private:
riegeli::Position reported_size_;
};
TEST(OffsetAdjustmentTest, Stco) {
std::string data = absl::StrCat(Uint32Str(2), // entry_count
Uint32Str(100), // offset 1
Uint32Str(200) // offset 2
);
riegeli::StringReader reader(data);
auto box_or = ParseStco(reader);
ABSL_ASSERT_OK(box_or);
StcoBox box = *box_or;
EXPECT_THAT(box.offsets, ElementsAre(100, 200));
AdjustOffsets(box, 50);
EXPECT_THAT(box.offsets, ElementsAre(150, 250));
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeStco(box, writer));
writer.Close();
std::string expected_data = absl::StrCat(Uint32Str(2), // entry_count
Uint32Str(150), // offset 1
Uint32Str(250) // offset 2
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, Co64) {
std::string data = absl::StrCat(Uint32Str(2), // entry_count
Uint64Str(100), // offset 1
Uint64Str(200) // offset 2
);
riegeli::StringReader reader(data);
auto box_or = ParseCo64(reader);
ABSL_ASSERT_OK(box_or);
Co64Box box = *box_or;
EXPECT_THAT(box.offsets, ElementsAre(100, 200));
AdjustOffsets(box, 50);
EXPECT_THAT(box.offsets, ElementsAre(150, 250));
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeCo64(box, writer));
writer.Close();
std::string expected_data = absl::StrCat(Uint32Str(2), // entry_count
Uint64Str(150), // offset 1
Uint64Str(250) // offset 2
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, SaioVersion0NoAuxInfo) {
BmffBoxHeader header;
header.version = 0;
header.flags = std::string("\x00\x00\x00", 3);
std::string data = absl::StrCat(Uint32Str(2), // entry_count
Uint32Str(100), // offset 1
Uint32Str(200) // offset 2
);
riegeli::StringReader reader(data);
auto box_or = ParseSaio(reader, header);
ABSL_ASSERT_OK(box_or);
SaioBox box = *box_or;
EXPECT_THAT(box.offsets, ElementsAre(100, 200));
AdjustOffsets(box, 50);
EXPECT_THAT(box.offsets, ElementsAre(150, 250));
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeSaio(box, header, writer));
writer.Close();
std::string expected_data = absl::StrCat(Uint32Str(2), // entry_count
Uint32Str(150), // offset 1
Uint32Str(250) // offset 2
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, SaioVersion1WithAuxInfo) {
BmffBoxHeader header;
header.version = 1;
header.flags = std::string("\x00\x00\x01", 3); // flags & 1
std::string data =
absl::StrCat(Uint32Str(0x12345678), // aux_info_type
Uint32Str(0xabcdef01), // aux_info_type_parameter
Uint32Str(2), // entry_count
Uint64Str(100), // offset 1
Uint64Str(200) // offset 2
);
riegeli::StringReader reader(data);
auto box_or = ParseSaio(reader, header);
ABSL_ASSERT_OK(box_or);
SaioBox box = *box_or;
EXPECT_EQ(box.aux_info_type, 0x12345678);
EXPECT_EQ(box.aux_info_type_parameter, 0xabcdef01);
EXPECT_THAT(box.offsets, ElementsAre(100, 200));
AdjustOffsets(box, 50);
EXPECT_THAT(box.offsets, ElementsAre(150, 250));
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeSaio(box, header, writer));
writer.Close();
std::string expected_data =
absl::StrCat(Uint32Str(0x12345678), // aux_info_type
Uint32Str(0xabcdef01), // aux_info_type_parameter
Uint32Str(2), // entry_count
Uint64Str(150), // offset 1
Uint64Str(250) // offset 2
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, IlocVersion0) {
BmffBoxHeader header;
header.version = 0;
// offset_size = 4, length_size = 4 -> 0x44
// base_offset_size = 4, index_size = 0 -> 0x40
std::string data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x40), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0), // data_reference_index
Uint32Str(1000), // base_offset
Uint16Str(2), // extent_count
// Extent 1
Uint32Str(100), // offset
Uint32Str(20), // length
// Extent 2
Uint32Str(200), // offset
Uint32Str(30) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
EXPECT_EQ(box.offset_size, 4);
EXPECT_EQ(box.length_size, 4);
EXPECT_EQ(box.base_offset_size, 4);
EXPECT_EQ(box.index_size, 0);
ASSERT_EQ(box.items.size(), 1);
EXPECT_EQ(box.items[0].item_id, 1);
EXPECT_EQ(box.items[0].data_reference_index, 0);
EXPECT_EQ(box.items[0].base_offset, 1000);
ASSERT_EQ(box.items[0].extents.size(), 2);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
EXPECT_EQ(box.items[0].extents[0].length, 20);
EXPECT_EQ(box.items[0].extents[1].offset, 200);
EXPECT_EQ(box.items[0].extents[1].length, 30);
AdjustOffsets(box, 50);
EXPECT_EQ(box.items[0].base_offset, 1050);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
EXPECT_EQ(box.items[0].extents[1].offset, 200);
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeIloc(box, header, writer));
writer.Close();
std::string expected_data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x40), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0), // data_reference_index
Uint32Str(1050), // base_offset
Uint16Str(2), // extent_count
// Extent 1
Uint32Str(100), // offset
Uint32Str(20), // length
// Extent 2
Uint32Str(200), // offset
Uint32Str(30) // length
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, IlocVersion1WithIndex) {
BmffBoxHeader header;
header.version = 1;
// offset_size = 4, length_size = 4 -> 0x44
// base_offset_size = 4, index_size = 4 -> 0x44
std::string data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x44), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0x1230), // reserved_and_construction_method
Uint16Str(0), // data_reference_index
Uint32Str(1000), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(5), // index
Uint32Str(100), // offset
Uint32Str(20) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
EXPECT_EQ(box.offset_size, 4);
EXPECT_EQ(box.length_size, 4);
EXPECT_EQ(box.base_offset_size, 4);
EXPECT_EQ(box.index_size, 4);
ASSERT_EQ(box.items.size(), 1);
EXPECT_EQ(box.items[0].item_id, 1);
EXPECT_EQ(box.items[0].construction_method, 0x1230);
EXPECT_EQ(box.items[0].data_reference_index, 0);
EXPECT_EQ(box.items[0].base_offset, 1000);
ASSERT_EQ(box.items[0].extents.size(), 1);
EXPECT_EQ(box.items[0].extents[0].index, 5);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
EXPECT_EQ(box.items[0].extents[0].length, 20);
AdjustOffsets(box, 50);
EXPECT_EQ(box.items[0].base_offset, 1050);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeIloc(box, header, writer));
writer.Close();
std::string expected_data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x44), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0x1230), // reserved_and_construction_method
Uint16Str(0), // data_reference_index
Uint32Str(1050), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(5), // index
Uint32Str(100), // offset
Uint32Str(20) // length
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, IlocVersion2LargeSizes) {
BmffBoxHeader header;
header.version = 2;
// offset_size = 8, length_size = 8 -> 0x88
// base_offset_size = 8, index_size = 8 -> 0x88
std::string data =
absl::StrCat(Uint8Str(0x88), // offset_size, length_size
Uint8Str(0x88), // base_offset_size, index_size
Uint32Str(1), // item_count (32-bit in v2)
// Item 1
Uint32Str(1), // item_id (32-bit in v2)
Uint16Str(0x1230), // reserved_and_construction_method
Uint16Str(0), // data_reference_index
Uint64Str(1000), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint64Str(5), // index
Uint64Str(100), // offset
Uint64Str(20) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
EXPECT_EQ(box.offset_size, 8);
EXPECT_EQ(box.length_size, 8);
EXPECT_EQ(box.base_offset_size, 8);
EXPECT_EQ(box.index_size, 8);
ASSERT_EQ(box.items.size(), 1);
EXPECT_EQ(box.items[0].item_id, 1);
EXPECT_EQ(box.items[0].construction_method, 0x1230);
EXPECT_EQ(box.items[0].data_reference_index, 0);
EXPECT_EQ(box.items[0].base_offset, 1000);
ASSERT_EQ(box.items[0].extents.size(), 1);
EXPECT_EQ(box.items[0].extents[0].index, 5);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
EXPECT_EQ(box.items[0].extents[0].length, 20);
AdjustOffsets(box, 50);
EXPECT_EQ(box.items[0].base_offset, 1050);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
std::string serialized;
riegeli::StringWriter writer(&serialized);
ABSL_ASSERT_OK(SerializeIloc(box, header, writer));
writer.Close();
std::string expected_data =
absl::StrCat(Uint8Str(0x88), // offset_size, length_size
Uint8Str(0x88), // base_offset_size, index_size
Uint32Str(1), // item_count (32-bit in v2)
// Item 1
Uint32Str(1), // item_id (32-bit in v2)
Uint16Str(0x1230), // reserved_and_construction_method
Uint16Str(0), // data_reference_index
Uint64Str(1050), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint64Str(5), // index
Uint64Str(100), // offset
Uint64Str(20) // length
);
EXPECT_EQ(serialized, expected_data);
}
TEST(OffsetAdjustmentTest, IlocNonZeroDataReferenceIndexNotAdjusted) {
BmffBoxHeader header;
header.version = 0;
std::string data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x40), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(1), // data_reference_index (Non-zero)
Uint32Str(1000), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(100), // offset
Uint32Str(20) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
AdjustOffsets(box, 50);
// Offset should NOT be adjusted because data_reference_index != 0
EXPECT_EQ(box.items[0].extents[0].offset, 100);
}
TEST(OffsetAdjustmentTest, IlocZeroBaseOffsetSize) {
BmffBoxHeader header;
header.version = 0;
// offset_size = 4, length_size = 4 -> 0x44
// base_offset_size = 0, index_size = 0 -> 0x00
std::string data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x00), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0), // data_reference_index
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(100), // offset
Uint32Str(20) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
AdjustOffsets(box, 50);
// Extent offset should be adjusted because base_offset_size == 0
EXPECT_EQ(box.items[0].extents[0].offset, 150);
}
TEST(OffsetAdjustmentTest, IlocZeroOffsetSize) {
BmffBoxHeader header;
header.version = 0;
// offset_size = 0, length_size = 4 -> 0x04
// base_offset_size = 4, index_size = 0 -> 0x40
std::string data =
absl::StrCat(Uint8Str(0x04), // offset_size, length_size
Uint8Str(0x40), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0), // data_reference_index
Uint32Str(1000), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(20) // length (No offset)
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
AdjustOffsets(box, 50);
// Base offset should be adjusted
EXPECT_EQ(box.items[0].base_offset, 1050);
}
TEST(OffsetAdjustmentTest, IlocNonZeroConstructionMethodNotAdjusted) {
BmffBoxHeader header;
header.version = 1;
// offset_size = 4, length_size = 4 -> 0x44
// base_offset_size = 4, index_size = 0 -> 0x40
std::string data =
absl::StrCat(Uint8Str(0x44), // offset_size, length_size
Uint8Str(0x40), // base_offset_size, index_size
Uint16Str(1), // item_count
// Item 1
Uint16Str(1), // item_id
Uint16Str(0x0001), // reserved (0), method (1)
Uint16Str(0), // data_reference_index
Uint32Str(1000), // base_offset
Uint16Str(1), // extent_count
// Extent 1
Uint32Str(100), // offset
Uint32Str(20) // length
);
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
IlocBox box = *box_or;
AdjustOffsets(box, 50);
// Offset should NOT be adjusted because construction_method != 0
EXPECT_EQ(box.items[0].base_offset, 1000);
EXPECT_EQ(box.items[0].extents[0].offset, 100);
}
TEST(OffsetAdjustmentTest, IlocSerializeInvalidSize) {
BmffBoxHeader header;
header.version = 0;
IlocBox box;
box.offset_size = 4;
box.length_size = 4;
box.base_offset_size = 1; // Invalid size
box.index_size = 0;
box.items.resize(1);
box.items[0].item_id = 1;
box.items[0].data_reference_index = 0;
box.items[0].base_offset = 1000;
box.items[0].extents.resize(1);
box.items[0].extents[0].offset = 100;
box.items[0].extents[0].length = 20;
std::string serialized;
riegeli::StringWriter writer(&serialized);
EXPECT_THAT(SerializeIloc(box, header, writer),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("invalid byte_size: 1")));
writer.Close();
}
// The following tests use counts that exceed the remaining data. They must fail
// before reserving memory for the declared entries, which would otherwise take
// gigabytes.
TEST(OffsetAdjustmentTest, StcoEntryCountExceedsData) {
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint32Str(100));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseStco(reader),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 4294967295 exceeds the "
"remaining 4 bytes")));
}
TEST(OffsetAdjustmentTest, Co64EntryCountExceedsData) {
// Two entries need 16 bytes, but only 15 remain.
std::string data =
absl::StrCat(Uint32Str(2), Uint64Str(100), std::string(7, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseCo64(reader),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 15 "
"bytes")));
}
TEST(OffsetAdjustmentTest, SaioEntryCountExceedsData) {
BmffBoxHeader header;
header.version = 1;
header.flags = std::string("\x00\x00\x00", 3);
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint64Str(100));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseSaio(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 4294967295 exceeds the "
"remaining 8 bytes")));
}
TEST(OffsetAdjustmentTest, SaioVersion0EntryCountExceedsData) {
// Two version 0 entries need 8 bytes, but only 7 remain.
BmffBoxHeader header;
header.version = 0;
header.flags = std::string("\x00\x00\x00", 3);
std::string data =
absl::StrCat(Uint32Str(2), Uint32Str(100), std::string(3, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseSaio(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 7 "
"bytes")));
}
TEST(OffsetAdjustmentTest, IlocItemCountExceedsData) {
BmffBoxHeader header;
header.version = 2;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint32Str(0xffffffff), // item_count
Uint32Str(1)); // item_ID
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 4294967295 exceeds the "
"remaining 4 bytes")));
}
TEST(OffsetAdjustmentTest, IlocExtentCountExceedsData) {
BmffBoxHeader header;
header.version = 0;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint16Str(1), // item_count
Uint16Str(1), // item_ID
Uint16Str(0), // data_reference_index
Uint16Str(0xffff), // extent_count
Uint32Str(100), // extent_offset
Uint32Str(20)); // extent_length
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 65535 exceeds the remaining 8 "
"bytes")));
}
TEST(OffsetAdjustmentTest, IlocZeroSizeExtent) {
// With all extent fields absent, a single extent occupies no bytes and refers
// to the entire referenced container.
BmffBoxHeader header;
header.version = 0;
std::string data = absl::StrCat(Uint8Str(0x00), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint16Str(1), // item_count
Uint16Str(1), // item_ID
Uint16Str(0), // data_reference_index
Uint16Str(1)); // extent_count
riegeli::StringReader reader(data);
auto box_or = ParseIloc(reader, header);
ABSL_ASSERT_OK(box_or);
const IlocBox& box = *box_or;
ASSERT_EQ(box.items.size(), 1);
ASSERT_EQ(box.items[0].extents.size(), 1);
EXPECT_EQ(box.items[0].extents[0].offset, 0);
EXPECT_EQ(box.items[0].extents[0].length, 0);
}
TEST(OffsetAdjustmentTest, IlocZeroSizeMultipleExtents) {
BmffBoxHeader header;
header.version = 0;
std::string data = absl::StrCat(Uint8Str(0x00), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint16Str(1), // item_count
Uint16Str(1), // item_ID
Uint16Str(0), // data_reference_index
Uint16Str(3)); // extent_count
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("iloc item 1 has 3 extents of size 0")));
}
// When the reader cannot report its size, counts cannot be checked up front, so
// parsing proceeds entry by entry and fails at the end of the data.
TEST(OffsetAdjustmentTest, Co64NoSizeReader) {
std::string data = absl::StrCat(Uint32Str(2), Uint64Str(100), Uint64Str(200));
NoSizeStringReader reader(data);
auto box_or = ParseCo64(reader);
ABSL_ASSERT_OK(box_or);
EXPECT_THAT(box_or->offsets, ElementsAre(100, 200));
}
TEST(OffsetAdjustmentTest, Co64NoSizeReaderEntryCountExceedsData) {
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint64Str(100));
NoSizeStringReader reader(data);
EXPECT_THAT(ParseCo64(reader), StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read offset")));
}
TEST(OffsetAdjustmentTest, Co64UnknownSizeEntryCountExceedsData) {
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint64Str(100));
SizeNotValuedStringReader reader(data);
EXPECT_THAT(ParseCo64(reader), StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read offset")));
}
TEST(OffsetAdjustmentTest, Co64ReportedSizeSmallerThanPos) {
std::string data = absl::StrCat(Uint32Str(1), Uint64Str(100));
CustomSizeStringReader reader(data, /*reported_size=*/0);
EXPECT_THAT(ParseCo64(reader),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 1 exceeds the remaining 0 "
"bytes")));
}
// When the reader reports a size large enough to satisfy the entry count check
// (e.g. a sparse file or spoofed size), the initial reservation is still capped
// at kMaxEntryCountToReserve rather than allocating for all declared entries.
TEST(OffsetAdjustmentTest, StcoLargeReportedSizeCapsReserve) {
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint32Str(100));
CustomSizeStringReader reader(data, /*reported_size=*/uint64_t{1} << 40);
EXPECT_THAT(ParseStco(reader), StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read offset")));
}
TEST(OffsetAdjustmentTest, Co64LargeReportedSizeCapsReserve) {
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint64Str(100));
CustomSizeStringReader reader(data, /*reported_size=*/uint64_t{1} << 40);
EXPECT_THAT(ParseCo64(reader), StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read offset")));
}
TEST(OffsetAdjustmentTest, SaioLargeReportedSizeCapsReserve) {
BmffBoxHeader header;
header.version = 1;
header.flags = std::string("\x00\x00\x00", 3);
std::string data = absl::StrCat(Uint32Str(0xffffffff), Uint64Str(100));
CustomSizeStringReader reader(data, /*reported_size=*/uint64_t{1} << 40);
EXPECT_THAT(ParseSaio(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read offset")));
}
TEST(OffsetAdjustmentTest, IlocLargeReportedSizeCapsReserve) {
BmffBoxHeader header;
header.version = 2;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x00), // base offset/index
Uint32Str(0xffffffff), // item_count
Uint32Str(1)); // item_ID
CustomSizeStringReader reader(data, /*reported_size=*/uint64_t{1} << 40);
EXPECT_THAT(ParseIloc(reader, header), StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("Failed to read")));
}
// The following tests pin the minimum item and extent sizes for each iloc
// version: the data holds enough bytes for the count only if the size is
// underestimated.
TEST(OffsetAdjustmentTest, IlocVersion2ItemCountUsesFourByteItemId) {
// A version 2 item has at least 10 bytes: item_ID (4),
// construction_method (2), data_reference_index (2), and extent_count (2).
BmffBoxHeader header;
header.version = 2;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint32Str(2), // item_count
std::string(16, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 16 "
"bytes")));
}
TEST(OffsetAdjustmentTest, IlocVersion1ItemCountIncludesConstructionMethod) {
// A version 1 item has at least 8 bytes: item_ID (2),
// construction_method (2), data_reference_index (2), and extent_count (2).
BmffBoxHeader header;
header.version = 1;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x00), // base offset/index size
Uint16Str(2), // item_count
std::string(12, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 12 "
"bytes")));
}
TEST(OffsetAdjustmentTest, IlocItemCountIncludesBaseOffset) {
// A version 0 item with a 4 byte base_offset has at least 10 bytes:
// item_ID (2), data_reference_index (2), base_offset (4), and
// extent_count (2).
BmffBoxHeader header;
header.version = 0;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x40), // base offset/index size
Uint16Str(2), // item_count
std::string(16, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 16 "
"bytes")));
}
TEST(OffsetAdjustmentTest, IlocVersion1ExtentCountIncludesIndex) {
// Each extent has an extent_index (4), extent_offset (4), and
// extent_length (4).
BmffBoxHeader header;
header.version = 1;
std::string data = absl::StrCat(Uint8Str(0x44), // offset/length size
Uint8Str(0x04), // base offset/index size
Uint16Str(1), // item_count
Uint16Str(1), // item_ID
Uint16Str(0), // construction_method
Uint16Str(0), // data_reference_index
Uint16Str(2), // extent_count
std::string(16, '\0'));
riegeli::StringReader reader(data);
EXPECT_THAT(ParseIloc(reader, header),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("entry count 2 exceeds the remaining 16 "
"bytes")));
}
} // namespace
} // namespace credentio