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