| // 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 |