| // 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_description_box.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.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; | |
| constexpr absl::string_view kFakeUuid = | |
| R"(deadbeef-f00d-babe-b4df-00d528013378)"; | |
| TEST(ParseDescriptionBoxTest, ParseOkNoToggles) { | |
| // Can't use absl::string_view because the compiler warns against assuming it | |
| // is null terminated, even when it truly is null terminated. | |
| const size_t description_content_length = /*uuid*/ 16 + /*toggles*/ 1; | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(0, content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid)})); | |
| } | |
| TEST(ParseDescriptionBoxTest, InvalidPayloadSize) { | |
| // A fuzzer crafted this box, with a payload that is too short. | |
| IntermediateBox box = { | |
| .payload = "", | |
| .serialized = absl::string_view("\x00\x00\x00\x08jumd", 8), | |
| .type = 1786080612, | |
| .implicit_length = false}; | |
| EXPECT_THAT(ParseDescriptionBox(box), | |
| StatusIs(absl::StatusCode::kOutOfRange)); | |
| } | |
| TEST(ParseDescriptionBoxTest, ParseOkAllToggles) { | |
| // 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"; | |
| constexpr uint32_t kFakeId = 0xB33FD00D; | |
| const std::string fake_hash(32, 'X'); | |
| constexpr absl::string_view kPrivatePayload = | |
| R"(The format of this data is unimportant for this test case.)"; | |
| const std::string private_box = jumbf::WrapBox(kPrivatePayload, 12345); | |
| const size_t description_content_length = | |
| /*uuid*/ 16 + /*toggles*/ 1 + strlen(kFakeLabel) + | |
| /*label null terminator*/ 1 + /*id*/ 4 + fake_hash.length() + | |
| private_box.length(); | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| uint8_t toggles = jumbf::kDescriptionToggleRequestable | | |
| jumbf::kDescriptionToggleLabelPresent | | |
| jumbf::kDescriptionToggleIdPresent | | |
| jumbf::kDescriptionToggleHashPresent | | |
| jumbf::kDescriptionTogglePrivatePresent; | |
| riegeli::WriteBigEndian<uint8_t>(toggles, content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| memcpy(content_cursor, kFakeLabel, strlen(kFakeLabel) + 1); | |
| content_cursor += strlen(kFakeLabel) + 1; | |
| riegeli::WriteBigEndian<uint32_t>(kFakeId, content_cursor); | |
| content_cursor += sizeof(uint32_t); | |
| memcpy(content_cursor, fake_hash.data(), 32); | |
| content_cursor += 32; | |
| memcpy(content_cursor, private_box.data(), private_box.length()); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .requestable = true, | |
| .label = kFakeLabel, | |
| .id = kFakeId, | |
| .hash = fake_hash, | |
| .private_box = | |
| IntermediateBox{ | |
| .payload = kPrivatePayload, | |
| .serialized = private_box, | |
| .type = 12345, | |
| }, | |
| })); | |
| } | |
| TEST(ParseDescriptionBoxTest, RequestableNoLabelFails) { | |
| const size_t description_content_length = /*uuid*/ 16 + /*toggles*/ 1; | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(jumbf::kDescriptionToggleRequestable, | |
| content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| "requestable bit is set, but box does not have a label; " | |
| "if a box is requestable the label is required")); | |
| } | |
| TEST(ParseDescriptionBoxTest, ParseOkWithLabel) { | |
| // 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, 'A'); | |
| 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; | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), .label = kFakeLabel})); | |
| } | |
| TEST(ParseDescriptionBoxTest, ParseOkWithId) { | |
| constexpr uint32_t kFakeId = 0xB33FD00D; | |
| const size_t description_content_length = | |
| /*uuid*/ 16 + /*toggles*/ 1 + /*id*/ 4; | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(jumbf::kDescriptionToggleIdPresent, | |
| content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| riegeli::WriteBigEndian<uint32_t>(kFakeId, content_cursor); | |
| content_cursor += sizeof(uint32_t); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), .id = kFakeId})); | |
| } | |
| TEST(ParseDescriptionBoxTest, ParseOkWithHash) { | |
| const std::string fake_hash(32, 'X'); | |
| const size_t description_content_length = | |
| /*uuid*/ 16 + /*toggles*/ 1 + fake_hash.length(); | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(jumbf::kDescriptionToggleHashPresent, | |
| content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| memcpy(content_cursor, fake_hash.data(), 32); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT( | |
| ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), .hash = fake_hash})); | |
| } | |
| TEST(ParseDescriptionBoxTest, ParseOkWithPrivate) { | |
| constexpr absl::string_view kPrivatePayload = | |
| R"(The format of this data is unimportant for this test case.)"; | |
| const std::string private_box = jumbf::WrapBox(kPrivatePayload, 12345); | |
| const size_t description_content_length = | |
| /*uuid*/ 16 + /*toggles*/ 1 + private_box.length(); | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(jumbf::kDescriptionTogglePrivatePresent, | |
| content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| memcpy(content_cursor, private_box.data(), private_box.length()); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| IsOkAndHolds(IntermediateDescriptionBox{ | |
| .type_uuid = Uuid::FromStringOrDie(kFakeUuid), | |
| .private_box = | |
| IntermediateBox{ | |
| .payload = kPrivatePayload, | |
| .serialized = private_box, | |
| .type = 12345, | |
| }, | |
| })); | |
| } | |
| TEST(ParseDescriptionBoxTest, WrongTypeFails) { | |
| // Can't use absl::string_view because the compiler warns against assuming it | |
| // is null terminated, even when it truly is null terminated. | |
| const size_t description_content_length = /*uuid*/ 16 + /*toggles*/ 1; | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| char* content_cursor = fake_description_contents.data(); | |
| SerializeUuid(&content_cursor, Uuid::FromStringOrDie(kFakeUuid)); | |
| riegeli::WriteBigEndian<uint8_t>(0, content_cursor); | |
| content_cursor += sizeof(uint8_t); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kSuperBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| "box is not a description box; got type 0x6a756d62 need " | |
| "0x6a756d64 ('jumd')")); | |
| } | |
| TEST(ParseDescriptionBoxTest, UnterminatedLabelFails) { | |
| // 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); | |
| std::string fake_description_contents(description_content_length, 'A'); | |
| 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); | |
| // Intentionally not copying null terminator. | |
| memcpy(content_cursor, kFakeLabel, strlen(kFakeLabel)); | |
| IntermediateBox unwrapped_description{ | |
| .payload = fake_description_contents, | |
| .type = jumbf::kDescriptionBoxType, | |
| }; | |
| EXPECT_THAT(ParseDescriptionBox(unwrapped_description), | |
| StatusIs(absl::StatusCode::kOutOfRange, | |
| "cannot read label: ran out of input bytes before " | |
| "reading null terminator")); | |
| } | |
| } // namespace | |
| } // namespace jumbf_internal |