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