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