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