blob: f8dd4400e0ddddf173e52c6f5dea9da36f15ee48 [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/consume_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/internal/intermediate.h"
#include "jumbf/test_utils.h"
#include "riegeli/endian/endian_writing.h"
namespace jumbf_internal {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::jumbf::WrapBox;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
TEST(ConsumeBoxTest, ConsumeSuccessfulExtraData) {
constexpr absl::string_view kExtraData = R"(this is not JUMBF data)";
constexpr absl::string_view kPayload = R"(this is the box's payload)";
const std::string buffer = absl::StrCat(WrapBox(kPayload, 1234), kExtraData);
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kPayload,
.serialized = buffer.substr(0, kPayload.size() + 8),
.type = 1234,
}));
EXPECT_THAT(input, Eq(kExtraData));
}
TEST(ConsumeBoxTest, ConsumeSuccessfulTwoBoxes) {
constexpr absl::string_view kFirstPayload =
R"(this is the first box's payload)";
constexpr absl::string_view kSecondPayload =
R"(this is the second box's payload)";
const std::string buffer =
absl::StrCat(WrapBox(kFirstPayload, 1234), WrapBox(kSecondPayload, 5678));
absl::string_view input = buffer;
size_t first_box_size = kFirstPayload.size() + 8;
size_t second_box_size = kSecondPayload.size() + 8;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kFirstPayload,
.serialized = buffer.substr(0, first_box_size),
.type = 1234,
}));
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kSecondPayload,
.serialized = buffer.substr(first_box_size, second_box_size),
.type = 5678,
}));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, ConsumeSuccessful) {
constexpr absl::string_view kPayload = R"(this is the box's payload)";
const std::string buffer = WrapBox(kPayload, 1234);
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kPayload, .serialized = buffer, .type = 1234}));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, EmptyInputFails) {
absl::string_view input = "";
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
StatusIs(absl::StatusCode::kOutOfRange,
"cannot read LBox value: not enough input bytes "
"remaining; have 0 need 4"));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, SizeTooLargeFails) {
constexpr absl::string_view kPayload = R"(this is the box's payload)";
std::string buffer = WrapBox(kPayload, 1234);
char* buffer_data = buffer.data();
// Overwrite size with too large of value
riegeli::WriteBigEndian<uint32_t>(5280, buffer_data);
buffer_data += sizeof(uint32_t);
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
"not enough input bytes for declared box size; have 33 "
"input bytes, box declares its size as 5280 bytes"));
EXPECT_THAT(input, Eq(buffer));
}
TEST(ConsumeBoxTest, XlBoxSizeSmallPayloadSucceeds) {
// Small payload, could have fit without using XLBox but the standard never
// states a minimum size to use XLBox.
constexpr absl::string_view kFakePayload = "some random data blah blah";
constexpr uint32_t kLBox = 1;
constexpr uint32_t kTBox = 12345;
constexpr uint64_t kXlBox =
/*LBox*/ 4 + /*TBox*/ 4 + /*XLBox*/ 8 + kFakePayload.length();
std::string buffer(kXlBox, 'X');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint64_t>(kXlBox, cursor);
cursor += sizeof(uint64_t);
// Safe because size of string was computed to be large enough for
// kFakePayload above.
memcpy(cursor, kFakePayload.data(), kFakePayload.size());
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kFakePayload,
.serialized = buffer,
.type = kTBox,
.implicit_length = false,
}));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, XlBoxSizeLargePayloadSucceeds) {
// Large payload, could not have fit without using XLBox.
const std::string payload(5'000'000'000, 'X');
constexpr uint32_t kLBox = 1;
constexpr uint32_t kTBox = 12345;
const uint64_t XlBox =
/*LBox=*/4 + /*TBox=*/4 + /*XLBox=*/8 + payload.length();
std::string buffer(XlBox, 'X');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint64_t>(XlBox, cursor);
cursor += sizeof(uint64_t);
// Safe because size of string was computed to be large enough for
// kFakePayload above.
memcpy(cursor, payload.data(), payload.size());
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = payload,
.serialized = buffer,
.type = kTBox,
.implicit_length = false,
}));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, XlBoxSizeTooLargeFails) {
constexpr absl::string_view kFakePayload = "some random data blah blah";
constexpr uint32_t kLBox = 1;
constexpr uint32_t kTBox = 12345;
constexpr uint64_t kXlBox = 8008888888;
std::string buffer(
/*LBox=*/4 + /*XLBox=*/8 + /*TBox=*/4 + kFakePayload.length(), 'X');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint64_t>(kXlBox, cursor);
cursor += sizeof(uint64_t);
// Safe because size of string was computed to be large enough for
// kFakePayload above.
memcpy(cursor, kFakePayload.data(), kFakePayload.size());
absl::string_view input = buffer;
EXPECT_THAT(
ConsumeBox(&input, /*allow_implicit_length=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
"not enough input bytes for declared box size; have 42 "
"input bytes, box declares its size as 8008888888 bytes"));
EXPECT_THAT(input, Eq(buffer));
}
TEST(ConsumeBoxTest, XlBoxSizeTooSmallFails) {
constexpr absl::string_view kFakePayload = "some random data blah blah";
constexpr uint32_t kLBox = 1;
constexpr uint32_t kTBox = 12345;
constexpr uint64_t kXlBox = 2;
std::string buffer(
/*LBox=*/4 + /*XLBox=*/8 + /*TBox=*/4 + kFakePayload.length(), 'X');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint64_t>(kXlBox, cursor);
cursor += sizeof(uint64_t);
// Safe because size of string was computed to be large enough for
// kFakePayload above.
memcpy(cursor, kFakePayload.data(), kFakePayload.size());
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
"XLBox is 2, which is less than the number of bytes "
"already consumed by this box's header"));
EXPECT_THAT(input, Eq(buffer));
}
TEST(ConsumeBoxTest, ReservedBoxLengthFails) {
constexpr uint32_t kLBox = 5;
constexpr uint32_t kTBox = 12345;
std::string buffer(8, 'A');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
absl::string_view input = buffer;
EXPECT_THAT(
ConsumeBox(&input, /*allow_implicit_length=*/true),
StatusIs(absl::StatusCode::kInvalidArgument,
"LBox is 5; LBox values 2-7 are reserved by the standard"));
EXPECT_THAT(input, Eq(buffer));
}
TEST(ConsumeBoxTest, ZeroBoxLengthSucceedsWhenPermitted) {
constexpr uint32_t kLBox = 0;
constexpr uint32_t kTBox = 12345;
constexpr absl::string_view kContent =
R"(blah blah blah blah when will I stop writing? I don't know so you won't know until you reach the end because the box length is 0 okay now I'm done bye)";
std::string buffer(8 + kContent.length(), '\0');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
memcpy(cursor, kContent.data(), kContent.size());
absl::string_view input = buffer;
EXPECT_THAT(ConsumeBox(&input, /*allow_implicit_length=*/true),
IsOkAndHolds(IntermediateBox{
.payload = kContent,
.serialized = buffer,
.type = kTBox,
.implicit_length = true,
}));
EXPECT_THAT(input, IsEmpty());
}
TEST(ConsumeBoxTest, ZeroBoxLengthFailsWhenNotPermitted) {
constexpr uint32_t kLBox = 0;
constexpr uint32_t kTBox = 12345;
std::string buffer(8, '\0');
char* cursor = buffer.data();
riegeli::WriteBigEndian<uint32_t>(kLBox, cursor);
cursor += sizeof(uint32_t);
riegeli::WriteBigEndian<uint32_t>(kTBox, cursor);
cursor += sizeof(uint32_t);
absl::string_view input = buffer;
EXPECT_THAT(
ConsumeBox(&input, /*allow_implicit_length=*/false),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("LBox is 0, indicating box extends to end of input, but "
"context does not allow for implicit box lengths")));
EXPECT_THAT(input, Eq(buffer));
}
} // namespace
} // namespace jumbf_internal