blob: eb512bc876e4894dcdffc93f7fd93cef9ca74f7d [file]
// 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