blob: de559e24d9998b612a2a3c4255d444d29aaeecc8 [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/box_matcher.h"
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "constants/labels.h"
#include "formats/bmff/box_header.h"
#include "formats/bmff/test_utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "proto/bmff_based_hash_assertion.pb.h"
#include "riegeli/bytes/string_reader.h"
namespace credentio {
namespace {
using ::credentio_testing::BigBox;
using ::credentio_testing::Box;
using ::credentio_testing::BoxFlags;
using ::credentio_testing::BoxVersion;
using ::credentio_testing::UuidBoxPayload;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
constexpr absl::string_view kFlagsForZero("\x00\x00\x00", 3);
constexpr absl::string_view kFlagsForOne("\x00\x00\x01", 3);
constexpr absl::string_view kFlagsForTwo("\x00\x00\x02", 3);
std::vector<std::string> MatchBmffBoxes(
std::vector<credentio::BmffBoxHeader> boxes,
const credentio::BMFFBoxMatcher* matcher) {
std::vector<std::string> matched;
for (const auto& box : boxes) {
if (matcher->Matches(box)) {
matched.push_back(box.xpath);
}
}
return matched;
}
std::vector<credentio::BMFFBoxMatcher::Range> MatchBmffBoxRanges(
std::vector<credentio::BmffBoxHeader> boxes,
const credentio::BMFFBoxMatcher* matcher) {
std::vector<credentio::BMFFBoxMatcher::Range> matched;
for (const auto& box : boxes) {
if (!matcher->Matches(box)) {
continue;
}
auto ranges = matcher->MatchedRanges(box);
matched.insert(matched.end(), ranges.begin(), ranges.end());
}
return matched;
}
TEST(BoxMatcher, MatchXPath) {
std::string uuid = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string moov = Box("moov", "moov");
std::string trak_trak1_trak1 = Box("trak", "abcd");
std::string trak_trak1 = Box("trak", trak_trak1_trak1);
std::string trak_trak2 = BigBox("trak", "efgh");
std::string trak1 =
BigBox("trak", absl::StrCat(trak_trak1, moov, trak_trak2));
std::string trak2 = Box("trak", "ijkl");
std::string contents = absl::StrCat(uuid, trak1, trak2);
riegeli::StringReader<> input(contents);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
{
credentio::BmffRange range;
range.set_xpath("/trak[1]/trak[1]/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]/trak[1]/trak[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak[1]/trak/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]/trak[1]/trak[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak/trak/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]/trak[1]/trak[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak/trak/trak[1]");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]/trak[1]/trak[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak/trak/abcd");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range;
range.set_xpath("/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]", "/trak[2]"));
}
}
TEST(BoxMatcher, MatchLength) {
std::string uuid = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string moov = Box("moov", "moov");
std::string trak_trak1_trak1 = Box("trak", "abcd");
std::string trak_trak1 = Box("trak", trak_trak1_trak1);
std::string trak_trak2 = BigBox("trak", "efgh");
std::string trak1 =
BigBox("trak", absl::StrCat(trak_trak1, moov, trak_trak2));
std::string trak2 = Box("trak", "ijkl");
std::string contents = absl::StrCat(uuid, trak1, trak2);
riegeli::StringReader<> input(contents);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
{
credentio::BmffRange range;
range.set_xpath("/trak");
range.set_length(68);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/trak[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak");
range.set_length(12);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/trak[2]"));
}
{
credentio::BmffRange range;
range.set_xpath("/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher),
ElementsAre("/trak[1]", "/trak[2]"));
}
}
TEST(BoxMatcher, MatchVersion) {
std::string uuid = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(2), BoxFlags(0), ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
{
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_version(2);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_version(1);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range;
range.set_xpath("/uuid");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
}
TEST(BoxMatcher, MatchFlagsDefaultExactMatch) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0),
kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_flags(kFlagsForOne);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
};
TEST(BoxMatcher, MatchFlagsExplicitlyExactMatch) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0),
kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_flags(kFlagsForOne);
range.set_exact(true);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
};
TEST(BoxMatcher, MatchFlagsNotMatched) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0),
kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_flags(kFlagsForZero);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
TEST(BoxMatcher, MatchFlagsNotExactButMatched) {
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_flags(kFlagsForZero);
range.set_exact(false);
{
std::string uuid =
Box("uuid",
UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
{
std::string uuid =
Box("uuid",
UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), kFlagsForTwo, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
};
TEST(BoxMatcher, MatchFlagsNotExactNotMatched) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0),
kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/uuid");
range.set_flags(kFlagsForTwo);
range.set_exact(false);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
};
TEST(BoxMatcher, MatchFlagsNotFullBoxNotMatched) {
std::string some_box = Box("some", "abcd");
riegeli::StringReader<> input(some_box);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/some");
range.set_flags(kFlagsForZero);
range.set_exact(false);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
};
TEST(BoxMatcher, NoFlagsMatched) {
{
std::string uuid =
Box("uuid",
UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), kFlagsForOne, ""));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange range;
range.set_xpath("/uuid");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
};
}
TEST(BoxMatcher, MatchData) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0),
BoxFlags(0), "abcd"));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
{
credentio::BmffRange range;
range.set_xpath("/uuid");
auto data1 = range.add_data();
data1->set_offset(4);
data1->set_value("uuid");
auto data2 = range.add_data();
data2->set_offset(28);
data2->set_value("abcd");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
{
credentio::BmffRange range;
range.set_xpath("/uuid");
auto data1 = range.add_data();
data1->set_offset(4);
data1->set_value("aaaa");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range;
range.set_xpath("/uuid");
auto data1 = range.add_data();
data1->set_offset(10000);
data1->set_value("uuid");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
}
TEST(BoxMatcher, MismatchOneField) {
std::string uuid = Box("uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(1),
kFlagsForOne, "abcd"));
riegeli::StringReader<> input(uuid);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
credentio::BmffRange matched_range;
matched_range.set_xpath("/uuid");
matched_range.set_length(32);
matched_range.set_version(1);
matched_range.set_flags(kFlagsForOne);
auto data1 = matched_range.add_data();
data1->set_offset(4);
data1->set_value("uuid");
auto data2 = matched_range.add_data();
data2->set_offset(28);
data2->set_value("abcd");
credentio::BMFFBoxMatcher matcher(matched_range, &input);
ASSERT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
{
credentio::BmffRange range = matched_range;
range.set_xpath("/uuid[1]");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), ElementsAre("/uuid[1]"));
}
{
credentio::BmffRange range = matched_range;
range.set_length(31); // length not matched.
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range = matched_range;
range.set_version(2); // version not matched.
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range = matched_range;
range.set_length(1); // length not matched.
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range = matched_range;
range.set_flags(kFlagsForTwo); // flags not matched.
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
{
credentio::BmffRange range = matched_range;
range.mutable_data(1)->set_value("abce"); // data not matched.
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxes(boxes, &matcher), IsEmpty());
}
}
TEST(BoxMatcher, MatchedRanges) {
std::string uuid = Box(
"uuid", UuidBoxPayload(kC2paBmffBoxUuid, BoxVersion(0), BoxFlags(0), ""));
std::string moov = Box("moov", "moov"); // box_size = 12
std::string trak1 = Box("trak", moov); // box_size = 20
std::string trak2 = BigBox("trak", "ijkl"); // box_size = 20
std::string contents = absl::StrCat(uuid, trak1, trak2);
riegeli::StringReader<> input(contents);
auto boxes_or = ReadBmffBoxHeaders(input);
ASSERT_TRUE(boxes_or.ok());
auto boxes = *boxes_or;
// Single box matched.
{
credentio::BmffRange range;
range.set_xpath("/trak[1]");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxRanges(boxes, &matcher),
ElementsAre(credentio::BMFFBoxMatcher::Range{.offset = 28,
.length = 20}));
}
// Multiple boxes matched.
{
credentio::BmffRange range;
range.set_xpath("/trak");
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(
MatchBmffBoxRanges(boxes, &matcher),
ElementsAre(
credentio::BMFFBoxMatcher::Range{.offset = 28, .length = 20},
credentio::BMFFBoxMatcher::Range{.offset = 48, .length = 20}));
}
// Return subsets of the box.
{
credentio::BmffRange range;
range.set_xpath("/trak[1]");
auto subset = range.add_subsets();
subset->set_offset(12);
subset->set_length(1);
subset = range.add_subsets();
subset->set_offset(16);
subset->set_length(2);
subset = range.add_subsets();
subset->set_offset(18);
subset->set_length(0);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(
MatchBmffBoxRanges(boxes, &matcher),
ElementsAre(
credentio::BMFFBoxMatcher::Range{.offset = 40, .length = 1},
credentio::BMFFBoxMatcher::Range{.offset = 44, .length = 2},
credentio::BMFFBoxMatcher::Range{.offset = 46, .length = 2}));
}
// The last entry may have a length of zero; this indicates that the remainder
// of the box from that relative byte offset onward is excluded.
{
credentio::BmffRange range;
range.set_xpath("/trak[1]");
auto subset = range.add_subsets();
subset->set_offset(18);
subset->set_length(0);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxRanges(boxes, &matcher),
ElementsAre(credentio::BMFFBoxMatcher::Range{.offset = 46,
.length = 2}));
}
// Relative byte offset plus length that exceeds the length of the box is
// allowed; bytes beyond the end of the box are never hashed.
{
credentio::BmffRange range;
range.set_xpath("/trak[1]");
auto subset = range.add_subsets();
subset->set_offset(18);
subset->set_length(100);
credentio::BMFFBoxMatcher matcher(range, &input);
EXPECT_THAT(MatchBmffBoxRanges(boxes, &matcher),
ElementsAre(credentio::BMFFBoxMatcher::Range{.offset = 46,
.length = 2}));
}
}
} // namespace
} // namespace credentio