| // 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/internal/parse_super_box.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/constants.h" | |
| #include "jumbf/internal/intermediate.h" | |
| #include "jumbf/test_utils.h" | |
| #include "riegeli/endian/endian_writing.h" | |
| #include "uuid/uuid.h" | |
| namespace jumbf_internal { | |
| namespace { | |
| using ::absl_testing::IsOkAndHolds; | |
| using ::absl_testing::StatusIs; | |
| using ::credentio::Uuid; | |
| using ::jumbf::SerializeUuid; | |
| using ::jumbf::WrapBox; | |
| using ::testing::HasSubstr; | |
| constexpr absl::string_view kFakeUuid = | |
| R"(deadbeef-f00d-babe-b4df-00d528013378)"; | |
| std::string FakeDescriptionBoxContents() { | |
| // Can't use absl::string_view because the compiler warns against assuming it | |
| // is null terminated, even when it truly is null terminated. | |
| constexpr char kFakeLabel[] = "fakelabel"; | |
| const size_t description_content_length = /*uuid*/ 16 + /*toggles*/ 1 + | |
| strlen(kFakeLabel) + | |
| /*label null terminator*/ 1; | |
| std::string fake_description_contents(description_content_length, '\0'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(jumbf::kDescriptionToggleLabelPresent, | |
| content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| memcpy(content_cursor, kFakeLabel, strlen(kFakeLabel) + 1); | |
| content_cursor += strlen(kFakeLabel) + 1; | |
| return fake_description_contents; | |
| } | |
| TEST(ParseSuperBoxTest, ParseOkSingleContent) { | |
| std::string serialized_content_box = | |
| WrapBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseSuperBox(unwrapped_super_box), | |
| IsOkAndHolds(IntermediateSuperBox{ | |
| .description = | |
| IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .label = "fakelabel", | |
| }, | |
| .content = {IntermediateBox{.payload = "fake CBOR data", | |
| .serialized = serialized_content_box, | |
| .type = jumbf::kCborBoxType}}, | |
| .serialized = serialized_super_box})); | |
| } | |
| TEST(ParseSuperBoxTest, ParseOkTwoContent) { | |
| std::string serialized_first_content_box = | |
| WrapBox("first fake CBOR data", jumbf::kCborBoxType); | |
| std::string serialized_second_content_box = | |
| WrapBox("second fake CBOR data", jumbf::kCborBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_first_content_box, serialized_second_content_box); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseSuperBox(unwrapped_super_box), | |
| IsOkAndHolds(IntermediateSuperBox{ | |
| .description = | |
| IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .label = "fakelabel", | |
| }, | |
| .content = | |
| { | |
| IntermediateBox{.payload = "first fake CBOR data", | |
| .serialized = serialized_first_content_box, | |
| .type = jumbf::kCborBoxType}, | |
| IntermediateBox{.payload = "second fake CBOR data", | |
| .serialized = serialized_second_content_box, | |
| .type = jumbf::kCborBoxType}, | |
| }, | |
| .serialized = serialized_super_box})); | |
| } | |
| TEST(ParseSuperBoxTest, ParseOkPaddingBox) { | |
| std::string serialized_content_box = | |
| WrapBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string serialized_padding_box = | |
| WrapBox(std::string(199, '\0'), jumbf::kPaddingBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box, serialized_padding_box); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT(ParseSuperBox(unwrapped_super_box), | |
| IsOkAndHolds(IntermediateSuperBox{ | |
| .description = | |
| IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .label = "fakelabel", | |
| }, | |
| .content = | |
| { | |
| IntermediateBox{.payload = "fake CBOR data", | |
| .serialized = serialized_content_box, | |
| .type = jumbf::kCborBoxType}, | |
| }, | |
| .serialized = serialized_super_box})); | |
| } | |
| TEST(ParseSuperBoxTest, ParseImplicitSuperBoxAndContent) { | |
| std::string serialized_content_box = | |
| jumbf::WrapUndeterminedLengthBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box); | |
| std::string serialized_super_box = | |
| jumbf::WrapUndeterminedLengthBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| .implicit_length = true, | |
| }; | |
| EXPECT_THAT(ParseSuperBox(unwrapped_super_box), | |
| IsOkAndHolds(IntermediateSuperBox{ | |
| .description = | |
| IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .label = "fakelabel", | |
| }, | |
| .content = | |
| { | |
| IntermediateBox{ | |
| .payload = "fake CBOR data", | |
| .serialized = serialized_content_box, | |
| .type = jumbf::kCborBoxType, | |
| .implicit_length = true, | |
| }, | |
| }, | |
| .serialized = serialized_super_box})); | |
| } | |
| TEST(ParseSuperBoxTest, ImplicitLengthContentInExplicitLengthSuperBoxFails) { | |
| std::string serialized_content_box = | |
| jumbf::WrapUndeterminedLengthBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string inner_super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box); | |
| std::string serialized_inner_super_box = jumbf::WrapUndeterminedLengthBox( | |
| inner_super_box_payload, jumbf::kSuperBoxType); | |
| std::string outer_super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_inner_super_box); | |
| std::string serialized_outer_super_box = | |
| WrapBox(outer_super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = outer_super_box_payload, | |
| .serialized = serialized_outer_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| .implicit_length = false, | |
| }; | |
| EXPECT_THAT( | |
| ParseSuperBox(unwrapped_super_box), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr("LBox is 0, indicating box extends to end of input, but " | |
| "context does not allow for implicit box lengths"))); | |
| } | |
| TEST(ParseSuperBoxTest, NoContentFails) { | |
| std::string super_box_payload = | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseSuperBox(unwrapped_super_box), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| "super box has no content; at least one content box is required")); | |
| } | |
| TEST(ParseSuperBoxTest, ContentAfterPaddingBoxFails) { | |
| std::string serialized_content_box = | |
| WrapBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string serialized_padding_box = | |
| WrapBox(std::string(199, '\0'), jumbf::kPaddingBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box, serialized_padding_box, | |
| WrapBox("extra content that should not be here", jumbf::kCborBoxType)); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kSuperBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT(ParseSuperBox(unwrapped_super_box), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| "input remaining after padding box; if a padding box is " | |
| "present within a super box, it must occupy the entire " | |
| "remainder of the box's size; after reading one padding " | |
| "box there were 45 bytes remaining")); | |
| } | |
| TEST(ParseSuperBoxTest, WrongTypeFails) { | |
| std::string serialized_content_box = | |
| WrapBox("fake CBOR data", jumbf::kCborBoxType); | |
| std::string super_box_payload = absl::StrCat( | |
| WrapBox(FakeDescriptionBoxContents(), jumbf::kDescriptionBoxType), | |
| serialized_content_box); | |
| std::string serialized_super_box = | |
| WrapBox(super_box_payload, jumbf::kCborBoxType); | |
| IntermediateBox unwrapped_super_box{ | |
| .payload = super_box_payload, | |
| .serialized = serialized_super_box, | |
| .type = jumbf::kCborBoxType, | |
| }; | |
| EXPECT_THAT(ParseSuperBox(unwrapped_super_box), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| "box is not a super box: got type 0x63626f72 need " | |
| "0x6a756d62 ('jumb')")); | |
| } | |
| } // namespace | |
| } // namespace jumbf_internal |