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