blob: 42631563b91d6b76916a363af9bacabe9a29447c [file]
// 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