| // 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 |