blob: 00fb2753a12cef83da50a8802ae4d3ef1df69111 [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 "jumbf/utils.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/numeric/int128.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h" // IWYU pragma: keep
#include "absl/status/status_matchers.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "jumbf/box.h"
#include "jumbf/box_builder.h"
#include "jumbf/constants.h"
#include "jumbf/parse.h"
#include "jumbf/test_utils.h"
#include "riegeli/endian/endian_writing.h"
#include "uuid/uuid.h"
namespace jumbf {
namespace {
void WriteUint32NetworkOrder(uint32_t value, std::vector<uint8_t>* bytes) {
bytes->push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
bytes->push_back(static_cast<uint8_t>(value & 0xFF));
}
void WriteUint64NetworkOrder(uint64_t value, std::vector<uint8_t>* bytes) {
bytes->push_back(static_cast<uint8_t>((value >> 56) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 48) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 40) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 32) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
bytes->push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
bytes->push_back(static_cast<uint8_t>(value & 0xFF));
}
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::testing::HasSubstr;
inline constexpr uint32_t kUnknownBoxType = 0x1111'1111;
inline constexpr credentio::Uuid kUuid1(
credentio::Uuid::FromStringOrDie("11111111-1111-1111-1111-111111111111"));
inline constexpr absl::uint128 kUuid1_uint128 =
absl::MakeUint128(0x1111111111111111, 0x1111111111111111);
inline constexpr absl::uint128 kUuid2_uint128 =
absl::MakeUint128(0x2222222222222222, 0x2222222222222222);
inline constexpr uint8_t kToggles1 = 0b00001111;
inline constexpr uint8_t kToggles2 = 0b11110000;
inline constexpr absl::string_view kLabel1 = "expected";
inline constexpr absl::string_view kLabel2 = "unexpected";
TEST(UtilsTest, TrimJumbfSuperBoxHeader) {
std::string input = WrapBox("content", kSuperBoxType);
EXPECT_THAT(StripBoxHeaders(input), IsOkAndHolds("content"));
}
TEST(UtilsTest, TrimEmptyJumbfSuperBoxHeader) {
EXPECT_THAT(StripBoxHeaders(""), StatusIs(absl::StatusCode::kOutOfRange));
}
struct BoxData {
std::optional<uint32_t> super_lbox = std::nullopt;
std::optional<uint32_t> super_tbox = std::nullopt;
std::optional<uint64_t> super_xlbox = std::nullopt;
std::optional<uint32_t> desc_lbox = std::nullopt;
std::optional<uint32_t> desc_tbox = std::nullopt;
std::optional<uint64_t> desc_xlbox = std::nullopt;
std::optional<absl::uint128> uuid = std::nullopt;
std::optional<uint8_t> toggles = std::nullopt;
std::optional<absl::string_view> label = std::nullopt;
bool null_terminator = true;
};
std::string CreatePayload(const BoxData& box_data) {
std::vector<uint8_t> bytes;
if (box_data.super_lbox.has_value()) {
WriteUint32NetworkOrder(*box_data.super_lbox, &bytes);
}
if (box_data.super_tbox.has_value()) {
WriteUint32NetworkOrder(*box_data.super_tbox, &bytes);
}
if (box_data.super_xlbox.has_value()) {
WriteUint64NetworkOrder(*box_data.super_xlbox, &bytes);
}
if (box_data.desc_lbox.has_value()) {
WriteUint32NetworkOrder(*box_data.desc_lbox, &bytes);
}
if (box_data.desc_tbox.has_value()) {
WriteUint32NetworkOrder(*box_data.desc_tbox, &bytes);
}
if (box_data.desc_xlbox.has_value()) {
WriteUint64NetworkOrder(*box_data.desc_xlbox, &bytes);
}
if (box_data.uuid.has_value()) {
WriteUint64NetworkOrder(absl::Uint128High64(*box_data.uuid), &bytes);
WriteUint64NetworkOrder(absl::Uint128Low64(*box_data.uuid), &bytes);
}
if (box_data.toggles.has_value()) {
bytes.push_back(*box_data.toggles);
}
if (box_data.label.has_value()) {
bytes.insert(bytes.end(), box_data.label->begin(), box_data.label->end());
}
if (box_data.null_terminator) {
bytes.push_back(0x00);
}
return std::string(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
// Serializes an absl::string_view to a char array, followed by a null
// terminator.
void SerializeStringWithNull(char** dest, absl::string_view str) {
memcpy(*dest, str.data(), str.size());
*dest += str.size();
**dest = 0x00;
*dest += 1;
}
TEST(HasDescriptionBoxMatchingTest, NoData) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({}), kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("not enough input bytes remaining; have 1 need 4")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsSuperBoxLbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1}), kUuid1,
kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("not enough input bytes remaining; have 1 need 4")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsSuperBoxTbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(
CreatePayload({.super_lbox = 1, .super_tbox = 1}), kUuid1, kToggles1,
kLabel1),
StatusIs(
absl::StatusCode::kOutOfRange,
HasSubstr("ran out of input bytes; 8 bytes requested, 1 remaining")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsSuperBoxXlboxIncorrectTbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(
CreatePayload({.super_lbox = 1, .super_tbox = 1, .super_xlbox = 1}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsSuperBoxTboxCorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(
CreatePayload(
{.super_lbox = 1, .super_tbox = kSuperBoxType, .super_xlbox = 1}),
kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("not enough input bytes remaining; have 1 need 4")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsDescriptorBoxLbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1}),
kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("not enough input bytes remaining; have 1 need 4")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsDescriptorBoxTbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = 1}),
kUuid1, kToggles1, kLabel1),
StatusIs(
absl::StatusCode::kOutOfRange,
HasSubstr("ran out of input bytes; 8 bytes requested, 1 remaining")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsDescriptorBoxXlboxIncorrectTbox) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = 1,
.desc_xlbox = 1}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsDescriptorBoxTboxCorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1}),
kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("Description Box UUID is missing.")));
}
TEST(HasDescriptionBoxMatchingTest,
ContainsDescriptorBoxTboxCorrectEmptyUuidPayload) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.null_terminator = false}),
kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("Description Box UUID is missing.")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsUuidIncorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid2_uint128}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsUuidCorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid1_uint128,
.null_terminator = false}),
kUuid1, kToggles1, kLabel1),
StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("not enough input bytes remaining; have 0 need 1")));
}
TEST(HasDescriptionBoxMatchingTest, ContainsTogglesIncorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid1_uint128,
.toggles = kToggles2}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsTogglesCorrect) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid1_uint128,
.toggles = kToggles1}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsWrongLabel) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid1_uint128,
.toggles = kToggles1,
.label = kLabel2}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(false));
}
TEST(HasDescriptionBoxMatchingTest, ContainsManifest) {
EXPECT_THAT(
HasDescriptionBoxMatching(CreatePayload({.super_lbox = 1,
.super_tbox = kSuperBoxType,
.super_xlbox = 1,
.desc_lbox = 1,
.desc_tbox = kDescriptionBoxType,
.desc_xlbox = 1,
.uuid = kUuid1_uint128,
.toggles = kToggles1,
.label = kLabel1}),
kUuid1, kToggles1, kLabel1),
IsOkAndHolds(true));
}
TEST(GetContextBoxSizeTest, GetContextBoxSize) {
std::string payload = "test";
SuperBoxBuilder builder(
kUuid1, {.requestable_iff_label = true, .label = absl::Cord("main")});
size_t efdb_payload_length = 0;
{
constexpr uint8_t kToggles = jumbf::kEfdbToggleFileNamePresent;
constexpr absl::string_view kFakeMediatype = "media/MyFormat";
constexpr absl::string_view kFakePath = "path/to/some/file";
efdb_payload_length =
1 /*toggles*/ + kFakeMediatype.size() + 1 + kFakePath.size() + 1;
std::string payload(efdb_payload_length, 'A');
char* cursor = payload.data();
riegeli::WriteBigEndian<uint8_t>(kToggles, cursor);
cursor += sizeof(uint8_t);
SerializeStringWithNull(&cursor, kFakeMediatype);
SerializeStringWithNull(&cursor, kFakePath);
ABSL_ASSERT_OK(builder.AddContent(kEmbeddedFileDescriptionBoxType,
absl::Cord(payload)));
}
ABSL_ASSERT_OK(builder.AddContent(kUuidBoxType, absl::Cord(payload)));
ABSL_ASSERT_OK(builder.AddContent(kJsonBoxType, absl::Cord(payload)));
ABSL_ASSERT_OK(builder.AddContent(kC2paSaltBoxType, absl::Cord(payload)));
ABSL_ASSERT_OK(builder.AddContent(kBinaryDataBoxType, absl::Cord(payload)));
size_t superbox_payload_size = 0;
size_t child_payload_size = 0;
{
SuperBoxBuilder child_builder(
kUuid1, {.requestable_iff_label = true, .label = absl::Cord("child")});
ABSL_ASSERT_OK(
child_builder.AddContent(kSuperBoxType, absl::Cord(payload)));
auto child_box_or = std::move(child_builder).Finalize();
ABSL_ASSERT_OK(child_box_or);
BuiltSuperBox child_box = std::move(*child_box_or);
absl::Cord child_cord = std::move(child_box).AsCord();
superbox_payload_size = child_cord.size();
absl::string_view child_view = child_cord.Flatten();
auto parsed_child_box_or =
ConsumeSuperBox(&child_view, /*recursion_limit=*/0);
ABSL_ASSERT_OK(parsed_child_box_or);
SuperBox parsed_child_box = std::move(*parsed_child_box_or);
child_payload_size = parsed_child_box.contents[0]
.Get<SerializedSuperbox>()
.serialized.size();
ABSL_ASSERT_OK(builder.AddChild(child_cord));
}
ABSL_ASSERT_OK(builder.AddContent(kCborBoxType, absl::Cord(payload)));
ABSL_ASSERT_OK(builder.AddContent(kUnknownBoxType, absl::Cord(payload)));
auto built_superbox_or = std::move(builder).Finalize();
ABSL_ASSERT_OK(built_superbox_or);
BuiltSuperBox built_superbox = std::move(*built_superbox_or);
absl::Cord superbox_cord = std::move(built_superbox).AsCord();
absl::string_view superbox_view = superbox_cord.Flatten();
auto box_or = ConsumeSuperBox(&superbox_view, /*recursion_limit=*/1);
ABSL_ASSERT_OK(box_or);
SuperBox box = std::move(*box_or);
EXPECT_TRUE(box.contents[0].Holds<UnknownBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[0]), payload.size());
EXPECT_TRUE(box.contents[1].Holds<CborBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[1]), payload.size());
EXPECT_TRUE(box.contents[2].Holds<SuperBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[2]), superbox_payload_size);
SuperBox serialized_superbox = box.contents[2].Get<SuperBox>();
EXPECT_TRUE(serialized_superbox.contents[0].Holds<SerializedSuperbox>());
EXPECT_THAT(GetContextBoxSize(serialized_superbox.contents[0]),
child_payload_size);
EXPECT_TRUE(box.contents[3].Holds<BinaryDataBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[3]), payload.size());
EXPECT_TRUE(box.contents[4].Holds<C2paSaltBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[4]), payload.size());
EXPECT_TRUE(box.contents[5].Holds<JsonBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[5]), payload.size());
EXPECT_TRUE(box.contents[6].Holds<UuidBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[6]), payload.size());
EXPECT_TRUE(box.contents[7].Holds<EmbeddedFileDescriptionBox>());
EXPECT_THAT(GetContextBoxSize(box.contents[7]), efdb_payload_length);
}
TEST(TestUtilsTest, SerializeUuid) {
char buf[32] = {'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
'\xff', '\xff', '\xff', '\xff'};
char* cursor = buf;
SerializeUuid(&cursor, credentio::Uuid::kInvalid);
EXPECT_EQ(cursor - buf, 16);
for (int i = 0; i < 16; ++i) {
EXPECT_EQ(buf[i], '\0');
}
SerializeUuid(&cursor, kUuid1);
EXPECT_EQ(cursor - buf, 32);
EXPECT_EQ(std::string(buf + 16, 16), kUuid1.ToProtoBytes());
}
} // namespace
} // namespace jumbf