blob: 7f906e7ee6908566105bd76620fee42f65ef5ff6 [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/box_header.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "constants/labels.h"
#include "formats/bmff/constants.h"
#include "formats/bmff/test_utils.h"
#include "formats/byte_range.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "riegeli/bytes/string_reader.h"
#include "riegeli/bytes/string_writer.h"
#include "riegeli/bytes/writer.h"
namespace credentio {
namespace {
using ::absl_testing::IsOk;
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::credentio_testing::BadBox;
using ::credentio_testing::BigBox;
using ::credentio_testing::Box;
using ::credentio_testing::BoxFlags;
using ::credentio_testing::BoxVersion;
using ::credentio_testing::C2paUuidBox;
using ::credentio_testing::UuidBoxPayload;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
constexpr absl::string_view kZeroFlags("\x00\x00\x00", 3);
absl::StatusOr<std::vector<BmffBoxHeader>> ReadAllBmffBoxHeaders(
absl::string_view contents) {
riegeli::StringReader<> reader(contents);
return ReadBmffBoxHeaders(reader);
}
class BoxHeaderTest : public testing::Test {
public:
absl::StatusOr<int64_t> WriteBox(const BmffBox& box, absl::string_view data,
std::string& destination,
bool start_with_error = false) {
riegeli::StringWriter<> writer(&destination);
if (start_with_error) {
writer.Fail(absl::InternalError("failed to write box header"));
}
ABSL_ASSIGN_OR_RETURN(int64_t written, WriteBmffBoxHeader(box, writer));
if (!writer.Write(data)) {
return writer.status();
}
if (!writer.Close()) {
return writer.status();
}
return written;
}
absl::Status ReadBox(absl::string_view data, BmffBoxHeader& header) {
riegeli::StringReader<> reader(data);
ABSL_ASSIGN_OR_RETURN(header, ReadBmffBoxHeader(reader));
return absl::OkStatus();
}
};
TEST_F(BoxHeaderTest, ReadAllBoxesWithEmptyContent) {
EXPECT_THAT(ReadAllBmffBoxHeaders(""), IsOkAndHolds(IsEmpty()));
}
TEST_F(BoxHeaderTest, ReadAllBoxesUUIDBox) {
std::string uuid_box = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(1), BoxFlags(2), ""));
EXPECT_THAT(ReadAllBmffBoxHeaders(uuid_box),
IsOkAndHolds(ElementsAre(BmffBoxHeader{
.start = 0,
.box_size = 28, // header_size + 4 (version+flags)
.header_size = 24, // 4 (size) + 4 (type) + 16 (UUID)
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 1,
.flags = BoxFlags(2)})));
}
TEST_F(BoxHeaderTest, ReadAllBoxesFullBox) {
std::string uuid_box =
Box("pdin", absl::StrCat(BoxVersion(0), BoxFlags(0), ""));
EXPECT_THAT(ReadAllBmffBoxHeaders(uuid_box),
IsOkAndHolds(ElementsAre(BmffBoxHeader{
.start = 0,
.box_size = 12, // header_size + 4 (version+flags)
.header_size = 8, // 4 (size) + 4 (type)
.version_and_flags_size = 4,
.type = "pdin",
.xpath = "/pdin[1]",
.user_type = "",
.version = 0,
.flags = std::string(kZeroFlags)})));
}
TEST_F(BoxHeaderTest, ReadAllBoxesTwoBoxes) {
std::string uuid_box = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string other_box = Box("blah", "abcd");
EXPECT_THAT(ReadAllBmffBoxHeaders(absl::StrCat(uuid_box, other_box)),
IsOkAndHolds(ElementsAre(
BmffBoxHeader{.start = 0,
.box_size = 28,
.header_size = 24,
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 0,
.flags = std::string(kZeroFlags)},
BmffBoxHeader{.start = 28,
.box_size = 12,
.header_size = 8,
.type = "blah",
.xpath = "/blah[1]"})));
}
TEST_F(BoxHeaderTest, ReadAllBoxesWithSubBoxes) {
std::string uuid = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string trak_trak1_trak1 = Box("trak", "abcd"); // box_size = 4+4+4;
std::string trak_trak1 = Box("trak", trak_trak1_trak1); // box_size = 4+4+12;
std::string trak_trak2 = BigBox("trak", "efgh"); // box_size = (4+8)+4+4;
std::string moov = Box("moov", "moov"); // box_size = 4+4+4;
std::string trak1 = BigBox(
"trak", absl::StrCat(trak_trak1, moov,
trak_trak2)); // box_size = (4+8)+4+(20+20+12);
std::string trak2 = Box("trak", "ijkl"); // box_size = 4+4+4;
EXPECT_THAT(ReadAllBmffBoxHeaders(absl::StrCat(uuid, trak1, trak2)),
IsOkAndHolds(ElementsAre(
BmffBoxHeader{.start = 0,
.box_size = 28,
.header_size = 24,
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 0,
.flags = std::string(kZeroFlags)},
BmffBoxHeader{.start = 28,
.box_size = 68,
.header_size = 16,
.type = "trak",
.xpath = "/trak[1]"},
BmffBoxHeader{.start = 44,
.box_size = 20,
.header_size = 8,
.type = "trak",
.xpath = "/trak[1]/trak[1]"},
BmffBoxHeader{.start = 52,
.box_size = 12,
.header_size = 8,
.type = "trak",
.xpath = "/trak[1]/trak[1]/trak[1]"},
BmffBoxHeader{.start = 64,
.box_size = 12,
.header_size = 8,
.type = "moov",
.xpath = "/trak[1]/moov[1]"},
BmffBoxHeader{.start = 76,
.box_size = 20,
.header_size = 16,
.type = "trak",
.xpath = "/trak[1]/trak[2]"},
BmffBoxHeader{.start = 96,
.box_size = 12,
.header_size = 8,
.type = "trak",
.xpath = "/trak[2]"})));
}
TEST_F(BoxHeaderTest, ReadAllBoxesBoxExtendsBeyondParent) {
std::string input = Box("moov", BadBox("trak", "ijkl", /*data_size=*/100));
EXPECT_THAT(ReadAllBmffBoxHeaders(input),
StatusIs(absl::StatusCode::kDataLoss,
HasSubstr("sub-box extends beyond the parent")));
}
TEST_F(BoxHeaderTest, ReadAllBoxesBoxExtendsBeyondInput) {
std::string input =
absl::StrCat(BadBox("trak", "ijkl", /*data_size=*/100), Box("moov", ""));
EXPECT_THAT(
ReadAllBmffBoxHeaders(input),
StatusIs(absl::StatusCode::kDataLoss, HasSubstr("truncated BMFF box")));
}
TEST_F(BoxHeaderTest, ReadAllBoxesMissing64bitSize) {
std::string moov =
BadBox("moov", "ijkl",
/*data_size=*/1); // box size set to 1, indicating a 64-bit size,
// but does not have a 64-bit size.
EXPECT_THAT(ReadAllBmffBoxHeaders(moov),
StatusIs(absl::StatusCode::kDataLoss,
AnyOf(HasSubstr("kUnexpectedEof"),
HasSubstr("failed to read box size"))));
}
TEST_F(BoxHeaderTest, ReadAllBoxesBoxDataSizeTooSmall) {
std::string moov =
BadBox("moov", "ijkl", /*data_size=*/2); // data size must be at least 8.
EXPECT_THAT(ReadAllBmffBoxHeaders(moov),
StatusIs(absl::StatusCode::kDataLoss,
AnyOf(HasSubstr("kInvalidData"),
HasSubstr("unsupported box size"))));
}
TEST_F(BoxHeaderTest, ReadsQuickTimeMetaBox) {
std::string data = Box(
"meta", Box("hdlr", absl::StrCat(BoxVersion(0), BoxFlags(0), "abcd")));
EXPECT_THAT(ReadAllBmffBoxHeaders(data), IsOkAndHolds(ElementsAre(
BmffBoxHeader{
.start = 0,
.box_size = 24,
.header_size = 8,
.version_and_flags_size = 0,
.type = "meta",
.xpath = "/meta[1]",
.user_type = "",
.version = 0,
.flags = "",
},
BmffBoxHeader{
.start = 8,
.box_size = 16,
.header_size = 8,
.version_and_flags_size = 4,
.type = "hdlr",
.xpath = "/meta[1]/hdlr[1]",
.user_type = "",
.version = 0,
.flags = BoxFlags(0),
})));
}
TEST_F(BoxHeaderTest, ReadsIsoMetaBox) {
std::string data =
absl::StrCat(Box("meta", absl::StrCat(BoxVersion(0), BoxFlags(0))),
Box("free", "padding"));
EXPECT_THAT(ReadAllBmffBoxHeaders(data), IsOkAndHolds(ElementsAre(
BmffBoxHeader{
.start = 0,
.box_size = 12,
.header_size = 8,
.version_and_flags_size = 4,
.type = "meta",
.xpath = "/meta[1]",
.user_type = "",
.version = 0,
.flags = BoxFlags(0),
},
BmffBoxHeader{
.start = 12,
.box_size = 15,
.header_size = 8,
.version_and_flags_size = 0,
.type = "free",
.xpath = "/free[1]",
.user_type = "",
.version = 0,
.flags = "",
})));
}
TEST_F(BoxHeaderTest, StopsEarly) {
std::string uuid_box = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string other_box = Box("blah", "abcd");
std::string data = absl::StrCat(uuid_box, other_box);
std::vector<BmffBoxHeader> result;
riegeli::StringReader<> reader(data);
EXPECT_THAT(IterateOverBmffBoxes(reader,
[&result](const BmffBoxHeader& box) {
result.push_back(box);
return false; // Stop early!
}),
IsOk());
EXPECT_THAT(result, ElementsAre(BmffBoxHeader{
.start = 0,
.box_size = 28,
.header_size = 24,
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 0,
.flags = std::string(kZeroFlags)}));
}
TEST_F(BoxHeaderTest, PropagatesError) {
std::string data = Box("blah", "abcd");
riegeli::StringReader<> reader(data);
auto status = IterateOverBmffBoxes(reader, [](const BmffBoxHeader& box) {
return absl::InternalError("custom error");
});
EXPECT_THAT(status, StatusIs(absl::StatusCode::kInternal, "custom error"));
}
TEST_F(BoxHeaderTest, IterateOverBmffBoxesStopsEarlyInSubBox) {
std::string sub_box = Box("blah", "abcd");
std::string container_box = Box("moov", sub_box);
std::string data = container_box;
std::vector<BmffBoxHeader> result;
riegeli::StringReader<> reader(data);
EXPECT_THAT(IterateOverBmffBoxes(reader,
[&result](const BmffBoxHeader& box) {
result.push_back(box);
if (box.type == "blah") {
return false; // Stop early in sub-box!
}
return true;
}),
IsOk());
EXPECT_THAT(result, ElementsAre(BmffBoxHeader{.start = 0,
.box_size = 20,
.header_size = 8,
.type = "moov",
.xpath = "/moov[1]"},
BmffBoxHeader{.start = 8,
.box_size = 12,
.header_size = 8,
.type = "blah",
.xpath = "/moov[1]/blah[1]"}));
}
class NoSizeReader : public riegeli::Reader {
public:
bool SupportsSize() override { return false; }
bool PullSlow(size_t min_length, size_t recommended_length) override {
return false;
}
};
TEST_F(BoxHeaderTest, IterateOverBmffBoxesRequiresSize) {
NoSizeReader reader;
EXPECT_THAT(
IterateOverBmffBoxes(reader, [](const BmffBoxHeader&) { return true; }),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("reader size cannot be determined")));
}
TEST_F(BoxHeaderTest, BoxSizeTooSmallForUuidExploit) {
std::string custom_uuid = absl::StrCat(
absl::string_view("\x00\x00\x00\x08", 4), "free", std::string(8, '\x00'));
ASSERT_EQ(custom_uuid.size(), 16);
std::string payload = absl::StrCat(custom_uuid, BoxVersion(0), BoxFlags(0));
std::string input = BadBox("uuid", payload, 8);
riegeli::StringReader<> reader(input);
std::vector<std::string> parsed_types;
auto status = IterateOverBmffBoxes(reader, [&](const BmffBoxHeader& box) {
parsed_types.push_back(box.type);
return true;
});
// We expect it to fail and NOT process the "free" box.
EXPECT_FALSE(status.ok());
EXPECT_THAT(parsed_types, IsEmpty());
}
TEST_F(BoxHeaderTest, BoxSizeTooSmallFor64BitHeader) {
std::string input =
absl::StrCat(absl::string_view("\x00\x00\x00\x01", 4), "blah",
absl::string_view("\x00\x00\x00\x00\x00\x00\x00\x08", 8));
riegeli::StringReader<> reader(input);
std::vector<std::string> parsed_types;
auto status = IterateOverBmffBoxes(reader, [&](const BmffBoxHeader& box) {
parsed_types.push_back(box.type);
return true;
});
EXPECT_FALSE(status.ok());
EXPECT_THAT(parsed_types, IsEmpty());
}
TEST_F(BoxHeaderTest, GenericUuidBoxAtEndWithoutVersionFlags) {
std::string custom_uuid(16, '\x00');
std::string input = BadBox("uuid", custom_uuid, 24);
EXPECT_THAT(
ReadAllBmffBoxHeaders(input),
IsOkAndHolds(ElementsAre(BmffBoxHeader{.start = 0,
.box_size = 24,
.header_size = 24,
.version_and_flags_size = 0,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = custom_uuid,
.version = 0,
.flags = ""})));
}
std::string NestBoxes(absl::string_view type, int depth) {
std::string current_data = "";
for (int i = 0; i < depth; ++i) {
current_data = Box(type, current_data);
}
return current_data;
}
TEST_F(BoxHeaderTest, DeeplyNestedBoxes) {
std::string input = NestBoxes("moov", 100);
EXPECT_THAT(ReadAllBmffBoxHeaders(input),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Too many nested BMFF boxes")));
}
using BoxHeaderContainerTest = testing::TestWithParam<std::string>;
INSTANTIATE_TEST_SUITE_P(, BoxHeaderContainerTest,
testing::Values("moov", "trak", "mdia", "minf", "stbl",
"moof", "traf", "edts", "udta", "dinf",
"tref", "treg", "mvex", "mfra", "meta",
"schi"),
[](const testing::TestParamInfo<std::string>& info) {
return info.param.data();
});
TEST_P(BoxHeaderContainerTest, IsContainerBox) {
EXPECT_TRUE(BmffBoxHeader{.type = GetParam()}.IsContainerBox());
}
TEST(BoxHeader, IsRootBox) {
{
BmffBoxHeader header = {};
EXPECT_TRUE(header.IsRootBox());
}
{
BmffBoxHeader header = {.xpath = "/"};
EXPECT_TRUE(header.IsRootBox());
}
{
BmffBoxHeader header = {.xpath = "/uuid"};
EXPECT_TRUE(header.IsRootBox());
}
{
BmffBoxHeader header = {.xpath = "/trak[1]"};
EXPECT_TRUE(header.IsRootBox());
}
{
BmffBoxHeader header = {.xpath = "/trak/moov"};
EXPECT_FALSE(header.IsRootBox());
}
{
BmffBoxHeader header = {.xpath = "/trak[1]/moov"};
EXPECT_FALSE(header.IsRootBox());
}
}
TEST_F(BoxHeaderTest, WriteUUIDBox) {
std::string data = "abcd";
BmffBox box{.type = "uuid",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 1,
.flags = BoxFlags(2),
.data_size = data.size()};
std::string serialized;
EXPECT_THAT(WriteBox(box, data, serialized), IsOkAndHolds(28));
EXPECT_EQ(serialized.size(), BmffBoxHeaderSize(box) + data.size());
EXPECT_THAT(
ReadAllBmffBoxHeaders(serialized),
IsOkAndHolds(ElementsAre(BmffBoxHeader{
.start = 0,
.box_size = 28 + data.size(), // header_size + 4 (version+flags)
.header_size = 24, // 4 (size) + 4 (type) + 16 (UUID)
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 1,
.flags = BoxFlags(2)})));
}
TEST_F(BoxHeaderTest, WriteFullBox) {
std::string data = "abcd";
BmffBox box{.type = "pdin",
.user_type = "",
.version = 0,
.flags = std::string(kZeroFlags),
.data_size = data.size()};
std::string serialized;
EXPECT_THAT(WriteBox(box, data, serialized), IsOkAndHolds(12));
EXPECT_EQ(serialized.size(), BmffBoxHeaderSize(box) + data.size());
EXPECT_THAT(
ReadAllBmffBoxHeaders(serialized),
IsOkAndHolds(ElementsAre(BmffBoxHeader{
.start = 0,
.box_size = 12 + data.size(), // header_size + 4 (version+flags)
.header_size = 8, // 4 (size) + 4 (type)
.version_and_flags_size = 4,
.type = "pdin",
.xpath = "/pdin[1]",
.user_type = "",
.version = 0,
.flags = std::string(kZeroFlags)})));
}
TEST_F(BoxHeaderTest, WriteNotFullBox) {
std::string data = "abcd";
BmffBox box{.type = "xxxx", .data_size = data.size()};
std::string serialized;
EXPECT_THAT(WriteBox(box, data, serialized), IsOkAndHolds(8));
EXPECT_EQ(serialized.size(), BmffBoxHeaderSize(box) + data.size());
EXPECT_THAT(ReadAllBmffBoxHeaders(serialized),
IsOkAndHolds(ElementsAre(
BmffBoxHeader{.start = 0,
.box_size = 8 + data.size(),
.header_size = 8, // 4 (size) + 4 (type)
.type = "xxxx",
.xpath = "/xxxx[1]"})));
}
TEST_F(BoxHeaderTest, WriteTwoBoxes) {
std::string data = "abcd";
BmffBox uuid_box{.type = "uuid",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 1,
.flags = BoxFlags(2),
.data_size = data.size()};
BmffBox other_box{.type = "blah", .data_size = data.size()};
std::string uuid_serialized;
EXPECT_THAT(WriteBox(uuid_box, data, uuid_serialized), IsOkAndHolds(28));
std::string other_serialized;
EXPECT_THAT(WriteBox(other_box, data, other_serialized), IsOkAndHolds(8));
std::string serialized = absl::StrCat(uuid_serialized, other_serialized);
EXPECT_EQ(serialized.size(), BmffBoxHeaderSize(uuid_box) + data.size() +
BmffBoxHeaderSize(other_box) + data.size());
EXPECT_THAT(ReadAllBmffBoxHeaders(serialized),
IsOkAndHolds(ElementsAre(
BmffBoxHeader{.start = 0,
.box_size = 28 + data.size(),
.header_size = 24,
.version_and_flags_size = 4,
.type = "uuid",
.xpath = "/uuid[1]",
.user_type = std::string(kC2paBmffBoxUuid),
.version = 1,
.flags = BoxFlags(2)},
BmffBoxHeader{.start = 32,
.box_size = 8 + data.size(),
.header_size = 8,
.type = "blah",
.xpath = "/blah[1]"})));
}
TEST_F(BoxHeaderTest, WriteFailsWhenStatusNotOKBeforeWrite) {
std::string data = "abcd";
BmffBox box{.type = "xxxx", .data_size = data.size()};
std::string serialized;
EXPECT_THAT(WriteBox(box, data, serialized, /*start_with_error=*/true),
StatusIs(absl::StatusCode::kInternal));
EXPECT_TRUE(serialized.empty());
}
TEST_F(BoxHeaderTest, WriteSize64bits) {
uint64_t data_size = 4294967296; // uint32_t max + 1
BmffBox box{.type = "xxxx", .data_size = data_size};
std::string serialized;
EXPECT_THAT(WriteBox(box, "", serialized), IsOkAndHolds(16));
EXPECT_EQ(serialized.size(), BmffBoxHeaderSize(box));
BmffBoxHeader read;
ABSL_ASSERT_OK(ReadBox(serialized, read));
BmffBoxHeader expected{
.start = 0,
.box_size = 8 + 8 + data_size,
.header_size = 8 + 8, // 4 (size) + 4 (type) + 8 (64bits)
.type = "xxxx"};
EXPECT_EQ(read, expected);
}
struct LocateInsertionPointTestParam {
std::string test_name;
std::string contents;
absl::StatusOr<ByteRange> expected;
};
using LocateInsertionPointTest =
testing::TestWithParam<LocateInsertionPointTestParam>;
INSTANTIATE_TEST_SUITE_P(
LocateInsertionPointTests, LocateInsertionPointTest,
testing::ValuesIn<LocateInsertionPointTestParam>({
{
.test_name = "BlankFile",
.contents = "",
.expected = absl::InvalidArgumentError("no mdat/moov box found"),
},
{
.test_name = "NoMdatOrMoov",
.contents = Box("free", "00"),
.expected = absl::InvalidArgumentError("no mdat/moov box found"),
},
{
.test_name = "OnlyMdat",
.contents = Box("mdat", "00"),
.expected = ByteRange{.offset = 0, .length = 0},
},
{
.test_name = "OnlyMoov",
.contents = Box("moov", "00"),
.expected = ByteRange{.offset = 0, .length = 0},
},
{
.test_name = "Mdat",
.contents = absl::StrCat(Box("ftyp", "00"), Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 0},
},
{
.test_name = "Moov",
.contents = absl::StrCat(Box("ftyp", "00"), Box("moov", "00")),
.expected = ByteRange{.offset = 10, .length = 0},
},
{
.test_name = "MdatAndMoov",
.contents = absl::StrCat(Box("ftyp", "00"), Box("mdat", "00"),
Box("moov", "00")),
.expected = ByteRange{.offset = 10, .length = 0},
},
{
.test_name = "FreeAfterMdat",
.contents = absl::StrCat(Box("ftyp", "00"), Box("mdat", "00"),
Box("free", "00")),
.expected = ByteRange{.offset = 10, .length = 0},
},
{
.test_name = "FreeAfterMoov",
.contents = absl::StrCat(Box("ftyp", "00"), Box("moov", "00"),
Box("free", "00")),
.expected = ByteRange{.offset = 10, .length = 0},
},
{
.test_name = "OneFreeBox",
.contents = absl::StrCat(Box("ftyp", "00"), Box("free", "00"),
Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 10},
},
{
.test_name = "TwoSeparateFreeBoxesFirstLarger",
.contents = absl::StrCat(
Box("ftyp", "00"), Box("free", "000000000000"),
Box("data", "00"), Box("free", "00"), Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 20},
},
{
.test_name = "TwoSeparateFreeBoxesSecondLarger",
.contents = absl::StrCat(
Box("ftyp", "00"), Box("free", "00"), Box("data", "00"),
Box("free", "000000000000"), Box("mdat", "00")),
.expected = ByteRange{.offset = 30, .length = 20},
},
{
.test_name = "AdjacentFreeBoxes",
.contents = absl::StrCat(Box("ftyp", "00"), Box("free", "00"),
Box("free", "00"), Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 20},
},
{
.test_name = "LargerFreeBoxAndSeparateC2paManifest",
.contents = absl::StrCat(
Box("ftyp", "00"), Box("free", std::string(102, '0')),
Box("data", "00"), C2paUuidBox("00000"), Box("mdat", "00")),
.expected = ByteRange{.offset = 130, .length = 50},
},
{
.test_name = "C2paManifestThenFreeBox",
.contents = absl::StrCat(Box("ftyp", "00"), C2paUuidBox("00000"),
Box("free", "00"), Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 60},
},
{
.test_name = "FreeBoxThenC2paManifest",
.contents = absl::StrCat(Box("ftyp", "00"), Box("free", "00"),
C2paUuidBox("00000"), Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 60},
},
{
.test_name = "C2paManifest",
.contents = absl::StrCat(Box("ftyp", "00"), C2paUuidBox("00000"),
Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 50},
},
{
.test_name = "C2paOriginal",
.contents = absl::StrCat(Box("ftyp", "00"),
C2paUuidBox("00000",
kBmffC2paBoxPurposeOriginal),
Box("mdat", "00")),
.expected = ByteRange{.offset = 10, .length = 50},
},
{
.test_name = "C2paMerkle",
.contents = absl::StrCat(Box("ftyp", "00"),
C2paUuidBox("0000000",
kBmffC2paBoxPurposeMerkle),
Box("mdat", "00")),
.expected = ByteRange{.offset = 60, .length = 0},
},
{
.test_name = "C2paUpdate",
.contents = absl::StrCat(Box("ftyp", "00"),
C2paUuidBox("0000000",
kBmffC2paBoxPurposeUpdate),
Box("mdat", "00")),
.expected = ByteRange{.offset = 60, .length = 0},
},
}),
[](const testing::TestParamInfo<LocateInsertionPointTest::ParamType>&
info) { return info.param.test_name; });
TEST_P(LocateInsertionPointTest, FindsInsertionPoint) {
riegeli::StringReader reader(GetParam().contents);
if (GetParam().expected.ok()) {
EXPECT_THAT(LocateManifestInsertionPoint(reader),
IsOkAndHolds(Eq(GetParam().expected.value())));
} else {
EXPECT_THAT(LocateManifestInsertionPoint(reader),
StatusIs(GetParam().expected.status().code(),
HasSubstr(GetParam().expected.status().message())));
}
}
TEST(IsBmffFullBoxTest, True) {
for (const absl::string_view type : credentio_internal::kFullBoxTypes) {
EXPECT_TRUE(IsBmffFullBox(type));
}
}
TEST(IsBmffFullBoxTest, False) {
EXPECT_FALSE(IsBmffFullBox(""));
EXPECT_FALSE(IsBmffFullBox("a"));
EXPECT_FALSE(IsBmffFullBox("uri"));
EXPECT_FALSE(IsBmffFullBox("aaaa"));
EXPECT_FALSE(IsBmffFullBox("aaaaaaaaaaaaaaaaaaaa"));
}
} // namespace
} // namespace credentio