| // 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/serialization_utils.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #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/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/test_utils.h" | |
| #include "riegeli/base/maker.h" | |
| #include "riegeli/bytes/chain_backward_writer.h" | |
| #include "riegeli/bytes/position_shifting_backward_writer.h" | |
| #include "riegeli/endian/endian_writing.h" | |
| namespace jumbf_internal { | |
| namespace { | |
| using ::absl_testing::IsOkAndHolds; | |
| using ::absl_testing::StatusIs; | |
| using ::jumbf::CordFromConstexpr; | |
| using ::testing::Eq; | |
| using ::testing::IsEmpty; | |
| using ::testing::Test; | |
| constexpr absl::string_view kExtraData = "12345678"; | |
| auto IsBoxSizeExceededStatus() { | |
| return StatusIs(absl::StatusCode::kResourceExhausted); | |
| } | |
| TEST(ConsumeBigEndianTest, ConsumeSuccessfulExtraData) { | |
| constexpr size_t kBufferLength = sizeof(uint32_t) + kExtraData.length(); | |
| std::string buffer(kBufferLength, 'A'); | |
| char* cursor = buffer.data(); | |
| riegeli::WriteBigEndian<uint32_t>(0xF00DBABE, cursor); | |
| cursor += sizeof(uint32_t); | |
| memcpy(cursor, kExtraData.data(), kExtraData.length()); | |
| absl::string_view sv = buffer; | |
| EXPECT_THAT(ConsumeInteger<uint32_t>(&sv), IsOkAndHolds(0xF00DBABE)); | |
| EXPECT_THAT(sv, Eq(kExtraData)); | |
| } | |
| TEST(ConsumeBigEndianTest, ConsumeSuccessful) { | |
| constexpr size_t kBufferLength = sizeof(uint32_t); | |
| std::string buffer(kBufferLength, 'A'); | |
| char* cursor = buffer.data(); | |
| riegeli::WriteBigEndian<uint32_t>(0xF00DBABE, cursor); | |
| cursor += sizeof(uint32_t); | |
| absl::string_view sv = buffer; | |
| EXPECT_THAT(ConsumeInteger<uint32_t>(&sv), IsOkAndHolds(0xF00DBABE)); | |
| EXPECT_THAT(sv, IsEmpty()); | |
| } | |
| TEST(ConsumeBigEndianTest, InputTooShort) { | |
| constexpr size_t kBufferLength = sizeof(uint32_t); | |
| std::string buffer(kBufferLength, 'A'); | |
| char* cursor = buffer.data(); | |
| riegeli::WriteBigEndian<uint32_t>(0xF00DBABE, cursor); | |
| cursor += sizeof(uint32_t); | |
| absl::string_view sv = buffer; | |
| EXPECT_THAT(ConsumeInteger<uint64_t>(&sv), | |
| StatusIs(absl::StatusCode::kOutOfRange, | |
| "not enough input bytes remaining; have 4 need 8")); | |
| EXPECT_THAT(sv, Eq(buffer)); | |
| } | |
| TEST(ConsumeBigEndianTest, InputEmpty) { | |
| std::string buffer; | |
| absl::string_view sv = buffer; | |
| EXPECT_THAT(ConsumeInteger<uint64_t>(&sv), | |
| StatusIs(absl::StatusCode::kOutOfRange, | |
| "not enough input bytes remaining; have 0 need 8")); | |
| EXPECT_THAT(sv, Eq(buffer)); | |
| } | |
| TEST(ConsumeNullTerminatedTest, ConsumeSuccessfulExtraData) { | |
| // Can't directly initialize `absl::string_view` because the null in the | |
| // middle confuses the constructor. | |
| const char raw_input[] = "hello\0extradata"; | |
| absl::string_view sv(raw_input, sizeof(raw_input) - 1); | |
| EXPECT_THAT(ConsumeNullTerminated(&sv), IsOkAndHolds("hello")); | |
| EXPECT_THAT(sv, Eq("extradata")); | |
| } | |
| TEST(ConsumeNullTerminatedTest, ConsumeSuccessful) { | |
| // Can't directly initialize `absl::string_view` because the null in the | |
| // middle confuses the constructor. | |
| const char raw_input[] = "hello\0"; | |
| absl::string_view sv(raw_input, sizeof(raw_input) - 1); | |
| EXPECT_THAT(ConsumeNullTerminated(&sv), IsOkAndHolds("hello")); | |
| EXPECT_THAT(sv, IsEmpty()); | |
| } | |
| TEST(ConsumeNullTerminatedTest, NullTerminatorOutOfBounds) { | |
| // A standard string, which is null terminated, but outside of the size of | |
| // the `absl::string_view`. | |
| absl::string_view sv = "hello"; | |
| EXPECT_THAT( | |
| ConsumeNullTerminated(&sv), | |
| StatusIs(absl::StatusCode::kOutOfRange, | |
| "ran out of input bytes before reading null terminator")); | |
| EXPECT_THAT(sv, Eq("hello")); | |
| } | |
| TEST(ConsumeBytesTest, ConsumeSuccessful) { | |
| constexpr absl::string_view kTestData = "87654321"; | |
| absl::string_view sv = kTestData; | |
| EXPECT_THAT(ConsumeBytes(&sv, 8), IsOkAndHolds(kTestData)); | |
| EXPECT_THAT(sv, IsEmpty()); | |
| } | |
| TEST(ConsumeBytesTest, ConsumeSuccessfulExtraData) { | |
| constexpr absl::string_view kTestData = "87654321"; | |
| const std::string buffer = absl::StrCat(kTestData, kExtraData); | |
| absl::string_view sv = buffer; | |
| EXPECT_THAT(ConsumeBytes(&sv, 8), IsOkAndHolds(kTestData)); | |
| EXPECT_THAT(sv, Eq(kExtraData)); | |
| } | |
| TEST(ConsumeBytesTest, LengthOutOfBounds) { | |
| absl::string_view sv = kExtraData; | |
| EXPECT_THAT( | |
| ConsumeBytes(&sv, 11), | |
| StatusIs(absl::StatusCode::kOutOfRange, | |
| "ran out of input bytes; 11 bytes requested, 8 remaining")); | |
| EXPECT_THAT(sv, Eq(kExtraData)); | |
| } | |
| TEST(WriteRawTest, WriteSucceeds) { | |
| constexpr absl::string_view kPayload = R"(Hi, I am a payload!)"; | |
| riegeli::ChainBackwardWriter writer; | |
| ABSL_ASSERT_OK(WriteRaw(CordFromConstexpr(kPayload), &writer)); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_THAT(writer.pos(), Eq(kPayload.length())); | |
| EXPECT_THAT(writer.dest().Flatten(), Eq(kPayload)); | |
| } | |
| TEST(WriteRawTest, MaximumSizeExceeded) { | |
| constexpr absl::string_view kPayload = R"(Hi, I am a payload!)"; | |
| riegeli::PositionShiftingBackwardWriter writer( | |
| riegeli::Maker<riegeli::ChainBackwardWriter>(), | |
| riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos( | |
| std::numeric_limits<size_t>::max())); | |
| EXPECT_THAT(WriteRaw(CordFromConstexpr(kPayload), &writer), | |
| IsBoxSizeExceededStatus()); | |
| } | |
| TEST(WriteNulTerminatedTest, WriteSucceeds) { | |
| constexpr absl::string_view kPayload = R"(Hi, I am a payload!)"; | |
| riegeli::ChainBackwardWriter writer; | |
| // This is safe because the underlying string literal is actually NUL | |
| // terminated. | |
| absl::string_view expected(kPayload.data(), kPayload.length() + 1); | |
| ABSL_ASSERT_OK(WriteNulTerminated(CordFromConstexpr(kPayload), &writer)); | |
| ASSERT_TRUE(writer.Close()); | |
| EXPECT_THAT(writer.pos(), Eq(kPayload.length() + 1)); | |
| EXPECT_THAT(writer.dest().Flatten(), Eq(expected)); | |
| } | |
| TEST(WriteNulTerminatedTest, MaximumSizeExceeded) { | |
| constexpr absl::string_view kPayload = R"(Hi, I am a payload!)"; | |
| riegeli::PositionShiftingBackwardWriter writer( | |
| riegeli::Maker<riegeli::ChainBackwardWriter>(), | |
| riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos( | |
| std::numeric_limits<size_t>::max())); | |
| EXPECT_THAT(WriteNulTerminated(CordFromConstexpr(kPayload), &writer), | |
| IsBoxSizeExceededStatus()); | |
| } | |
| template <typename T> | |
| class WriteIntegerTest : public Test {}; | |
| TYPED_TEST_SUITE_P(WriteIntegerTest); | |
| TYPED_TEST_P(WriteIntegerTest, WriteSucceeds) { | |
| const TypeParam expected = 42; | |
| constexpr size_t kTotalBytesInitial = 55; | |
| riegeli::PositionShiftingBackwardWriter writer( | |
| riegeli::Maker<riegeli::ChainBackwardWriter>(), | |
| riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos( | |
| kTotalBytesInitial)); | |
| ABSL_ASSERT_OK(WriteInteger(expected, &writer)); | |
| ASSERT_TRUE(writer.Close()); | |
| absl::string_view result_view = writer.dest().dest().Flatten(); | |
| EXPECT_THAT(ConsumeInteger<TypeParam>(&result_view), IsOkAndHolds(expected)); | |
| EXPECT_THAT(result_view, IsEmpty()); | |
| EXPECT_THAT(writer.pos(), Eq(kTotalBytesInitial + sizeof(TypeParam))); | |
| } | |
| TYPED_TEST_P(WriteIntegerTest, MaximumSizeExceeded) { | |
| const TypeParam expected = 42; | |
| riegeli::PositionShiftingBackwardWriter writer( | |
| riegeli::Maker<riegeli::ChainBackwardWriter>(), | |
| riegeli::PositionShiftingBackwardWriterBase::Options().set_base_pos( | |
| std::numeric_limits<size_t>::max())); | |
| EXPECT_THAT(WriteInteger(expected, &writer), IsBoxSizeExceededStatus()); | |
| } | |
| REGISTER_TYPED_TEST_SUITE_P(WriteIntegerTest, WriteSucceeds, | |
| MaximumSizeExceeded); | |
| using WriteIntegerTestTypes = testing::Types<uint8_t, uint32_t, uint64_t>; | |
| INSTANTIATE_TYPED_TEST_SUITE_P(SerializationUtilsTest, WriteIntegerTest, | |
| WriteIntegerTestTypes); | |
| } // namespace | |
| } // namespace jumbf_internal |