blob: b19b387d75aa2efb5324665553a0dfbd17db5802 [file] [edit]
// 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/encode.h"
#include <sys/types.h>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include "absl/status/status.h"
#include "absl/status/status_macros.h" // IWYU pragma: keep
#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/encode_params.h"
#include "jumbf/internal/serialization_utils.h"
#include "jumbf/test_utils.h"
#include "riegeli/base/chain.h"
#include "riegeli/base/maker.h"
#include "riegeli/bytes/chain_backward_writer.h"
#include "riegeli/bytes/position_shifting_backward_writer.h"
namespace jumbf {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::jumbf_internal::ConsumeInteger;
using ::testing::Eq;
using ::testing::IsEmpty;
TEST(EncodePaddingBoxTest, EncodesPaddingBox) {
constexpr uint64_t kPaddingSize = 7;
constexpr size_t kExpectedOutputSize =
sizeof(uint32_t) /*LBox*/ + sizeof(uint32_t) /*TBox*/ + kPaddingSize;
riegeli::ChainBackwardWriter writer;
ABSL_ASSERT_OK(EncodePaddingBox(7, &writer));
writer.Close();
absl::string_view result_view = writer.dest().Flatten();
EXPECT_THAT(writer.pos(), kExpectedOutputSize);
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds('free'));
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, kPaddingSize),
IsOkAndHolds(std::string("\0\0\0\0\0\0\0\0", 7)));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeContentBoxTest, EncodesContentBox) {
constexpr absl::string_view kPayload = R"(I am payload!)";
constexpr uint32_t kTbox = 0xfeedbabe;
constexpr size_t kExpectedOutputSize =
sizeof(uint32_t) /*LBox*/ + sizeof(uint32_t) /*TBox*/ + kPayload.length();
// Simulating content already written in superbox
constexpr size_t kTotalBytesInitial = 5280;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kTotalBytesInitial));
ABSL_ASSERT_OK(EncodeContentBox(kTbox, CordFromConstexpr(kPayload), &writer));
writer.Close();
absl::string_view result_view = writer.dest().dest().Flatten();
EXPECT_THAT(writer.pos(), Eq(kTotalBytesInitial + kExpectedOutputSize));
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kTbox));
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, kPayload.length()),
IsOkAndHolds(kPayload));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeDescriptionBoxTest, DefaultOptions) {
constexpr size_t kExpectedOutputSize = 25;
// Simulating content already written in superbox
constexpr size_t kTotalBytesInitial = 0xf00dbabe;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kTotalBytesInitial));
ABSL_ASSERT_OK(
EncodeDescriptionBox(kCborBoxTypeUuid, DescriptionBoxOptions{}, &writer));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
EXPECT_THAT(writer.pos(), Eq(kExpectedOutputSize + 0xf00dbabe));
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kDescriptionBoxType));
// Type UUID
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, 16),
IsOkAndHolds(kCborBoxTypeUuid.ToProtoBytes()));
// Toggles
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint8_t>(&result_view),
IsOkAndHolds(0));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeDescriptionBoxTest, HasLabel) {
constexpr absl::string_view kLabel = "mylabel";
constexpr size_t kExpectedOutputSize =
4 /*lbox*/ + 4 /*tbox*/ + 16 /*type UUID*/ + 1 /*toggles*/ +
kLabel.length() + 1 /*label NUL terminator*/;
// Simulating content already written in superbox
constexpr size_t kTotalBytesInitial = 0xdeadbeef;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kTotalBytesInitial));
ABSL_ASSERT_OK(EncodeDescriptionBox(kCborBoxTypeUuid,
DescriptionBoxOptions{
.label = CordFromConstexpr(kLabel),
},
&writer));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
EXPECT_THAT(writer.pos(), Eq(kExpectedOutputSize + kTotalBytesInitial));
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kDescriptionBoxType));
// Type UUID
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, 16),
IsOkAndHolds(kCborBoxTypeUuid.ToProtoBytes()));
// Toggles
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint8_t>(&result_view),
IsOkAndHolds(kDescriptionToggleLabelPresent |
kDescriptionToggleRequestable));
// Label
EXPECT_THAT(jumbf_internal::ConsumeNullTerminated(&result_view),
IsOkAndHolds("mylabel"));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeDescriptionBoxTest, HasPrivateContent) {
constexpr absl::string_view kPrivateContent =
"Hi, I'm not actually JUMBF data but for this test it doesn't matter.";
constexpr size_t kExpectedOutputSize = 4 /*lbox*/ + 4 /*tbox*/ +
16 /*type UUID*/ + 1 /*toggles*/ +
kPrivateContent.length();
// Simulating content already written in superbox
constexpr size_t kTotalBytesInitial = 0xbaadf00d;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kTotalBytesInitial));
ABSL_ASSERT_OK(EncodeDescriptionBox(
kCborBoxTypeUuid,
DescriptionBoxOptions{
.requestable_iff_label = false,
.private_content = CordFromConstexpr(kPrivateContent),
},
&writer));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
EXPECT_THAT(writer.pos(), Eq(kExpectedOutputSize + kTotalBytesInitial));
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kDescriptionBoxType));
// Type UUID
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, 16),
IsOkAndHolds(kCborBoxTypeUuid.ToProtoBytes()));
// Toggles
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint8_t>(&result_view),
IsOkAndHolds(kDescriptionTogglePrivatePresent));
// Private content
EXPECT_THAT(
jumbf_internal::ConsumeBytes(&result_view, kPrivateContent.length()),
IsOkAndHolds(kPrivateContent));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeDescriptionBoxTest, HasLabelNotRequestable) {
constexpr size_t kExpectedOutputSize = 33;
constexpr absl::string_view kLabel = "mylabel";
// Simulating content already written in superbox
constexpr size_t kTotalBytesInitial = 0xdefec8ed;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kTotalBytesInitial));
ABSL_ASSERT_OK(EncodeDescriptionBox(kCborBoxTypeUuid,
DescriptionBoxOptions{
.requestable_iff_label = false,
.label = CordFromConstexpr(kLabel),
},
&writer));
EXPECT_THAT(writer.pos(), Eq(kExpectedOutputSize + kTotalBytesInitial));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
// LBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kExpectedOutputSize));
// TBox
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(kDescriptionBoxType));
// Type UUID
EXPECT_THAT(jumbf_internal::ConsumeBytes(&result_view, 16),
IsOkAndHolds(kCborBoxTypeUuid.ToProtoBytes()));
// Toggles
EXPECT_THAT(jumbf_internal::ConsumeInteger<uint8_t>(&result_view),
IsOkAndHolds(kDescriptionToggleLabelPresent));
// Label
EXPECT_THAT(jumbf_internal::ConsumeNullTerminated(&result_view),
IsOkAndHolds("mylabel"));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeBoxHeaderTest, LBox) {
// Fake value, pretending we already wrote some content to `writer`.
constexpr size_t kPayloadSize = 10;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kPayloadSize));
ABSL_EXPECT_OK(EncodeBoxHeader(kPayloadSize, 0xdeadbeef, &writer));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
// Should have been updated to include the (fake) content plus LBox and TBox.
EXPECT_THAT(writer.pos(), Eq(kPayloadSize + 8));
// LBox
EXPECT_THAT(ConsumeInteger<uint32_t>(&result_view),
IsOkAndHolds(writer.pos()));
// TBox
EXPECT_THAT(ConsumeInteger<uint32_t>(&result_view), IsOkAndHolds(0xdeadbeef));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeBoxHeaderTest, XLBox) {
// Fake value, pretending we already wrote some content to `writer`.
constexpr size_t kPayloadSize =
static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 10;
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kPayloadSize));
ABSL_EXPECT_OK(EncodeBoxHeader(kPayloadSize, 0xdeadbeef, &writer));
ASSERT_TRUE(writer.Close());
absl::string_view result_view = writer.dest().dest().Flatten();
// Should have been updated to include the (fake) content plus LBox, TBox, and
// XLBox.
EXPECT_THAT(writer.pos(), Eq(kPayloadSize + 16));
// LBox
EXPECT_THAT(ConsumeInteger<uint32_t>(&result_view), IsOkAndHolds(1));
// TBox
EXPECT_THAT(ConsumeInteger<uint32_t>(&result_view), IsOkAndHolds(0xdeadbeef));
// XLBox
EXPECT_THAT(ConsumeInteger<uint64_t>(&result_view),
IsOkAndHolds(writer.pos()));
EXPECT_THAT(result_view, IsEmpty());
}
TEST(EncodeBoxHeaderTest, XLBoxPayloadExceedsMaxBoxSize) {
// Fake value, pretending we already wrote some content to `writer`.
constexpr size_t kPayloadSize = std::numeric_limits<size_t>::max();
riegeli::PositionShiftingBackwardWriter writer(
riegeli::Maker<riegeli::ChainBackwardWriter>(),
riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos(
kPayloadSize));
EXPECT_THAT(
EncodeBoxHeader(kPayloadSize, 0xdeadbeef, &writer),
StatusIs(absl::StatusCode::kOutOfRange, "maximum box size exceeded"));
}
} // namespace
} // namespace jumbf