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