| // 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 <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "formats/bmff/box_header.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.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; | |
| 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(); | |
| } | |
| } // namespace | |
| } // namespace credentio |