blob: c7c0fad4517e924a6b28dbf640e4b3467da52275 [file]
// 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