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