Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions benchmarks/all/input_buffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include <benchmark/benchmark.h>

#include <cstdint>
#include <rfl/cereal.hpp>
#include <vector>

namespace input_buffer {

void BM_cereal_input_buffer(benchmark::State& state) {
std::vector<std::uint64_t> input(state.range(0));
for (std::size_t i = 0; i < input.size(); ++i) input[i] = i * 17;
const auto bytes = rfl::cereal::write(input);
{
const auto result = rfl::cereal::read<decltype(input)>(bytes);
if (!result || *result != input) {
state.SkipWithError("Round trip failed");
return;
}
}
for (auto _ : state) {
auto result = rfl::cereal::read<decltype(input)>(bytes);
benchmark::DoNotOptimize(result);
}
state.SetBytesProcessed(state.iterations() * bytes.size());
}

BENCHMARK(BM_cereal_input_buffer)
->Arg(1024)
->Arg(128 * 1024)
->Arg(4 * 1024 * 1024);
} // namespace input_buffer
17 changes: 6 additions & 11 deletions include/rfl/boost_serialization/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/string.hpp>
#include <istream>
#include <streambuf>
#include <string>

#include "../Processors.hpp"
#include "../concepts.hpp"
#include "../internal/MemoryInputBuffer.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
Expand All @@ -26,14 +26,7 @@ auto read_from_archive(IArchive& _ar) {
return Parser<IArchive, OArchive, T, Processors<Ps...>>::read(r, var);
}

/// A read-only streambuf that wraps an existing memory buffer without copying.
class MemBuf : public std::streambuf {
public:
MemBuf(const char* _data, size_t _size) {
auto* p = const_cast<char*>(_data);
setg(p, p, p + _size);
}
};
using MemBuf = internal::MemoryInputBuffer;

} // namespace detail

Expand All @@ -51,7 +44,8 @@ auto read_from_archive(IArchive& _ar) {
}

/// Parses an object from raw bytes using Boost binary archive.
/// Creates a memory stream from the bytes and deserializes using Boost.Serialization.
/// Creates a memory stream from the bytes and deserializes using
/// Boost.Serialization.
/// @tparam T The type to deserialize into
/// @tparam Ps Optional processors to apply during deserialization
/// @param _bytes Pointer to the binary data
Expand All @@ -76,7 +70,8 @@ Result<internal::wrap_in_rfl_array_t<T>> read(
/// Parses an object from a byte container using Boost binary archive.
/// @tparam T The type to deserialize into
/// @tparam Ps Optional processors to apply during deserialization
/// @param _bytes A contiguous container (e.g., std::vector, std::string) containing the data
/// @param _bytes A contiguous container (e.g., std::vector, std::string)
/// containing the data
/// @return A Result containing the deserialized object or an error
template <class T, class... Ps>
auto read(const concepts::ContiguousByteContainer auto& _bytes) {
Expand Down
12 changes: 7 additions & 5 deletions include/rfl/cereal/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#include <cereal/archives/portable_binary.hpp>
#include <cereal/cereal.hpp>
#include <istream>
#include <sstream>
#include <string>

#include "../Processors.hpp"
#include "../concepts.hpp"
#include "../internal/MemoryInputBuffer.hpp"
#include "../internal/wrap_in_rfl_array_t.hpp"
#include "Parser.hpp"
#include "Reader.hpp"
Expand All @@ -30,7 +31,7 @@ auto read(Reader::CerealArchive& _archive) {
}

/// Parses an object from raw bytes using Cereal portable binary format.
/// Creates a memory stream and deserializes using Cereal.
/// Reads from the supplied bytes without copying them into an owning stream.
/// @tparam T The type to deserialize into
/// @tparam Ps Optional processors to apply during deserialization
/// @param _bytes Pointer to the binary data
Expand All @@ -40,9 +41,10 @@ template <class T, class... Ps>
Result<internal::wrap_in_rfl_array_t<T>> read(
const concepts::ByteLike auto* _bytes, const size_t _size) {
try {
std::stringstream ss(
std::string(reinterpret_cast<const char*>(_bytes), _size));
::cereal::PortableBinaryInputArchive archive(ss);
internal::MemoryInputBuffer buffer(reinterpret_cast<const char*>(_bytes),
_size);
std::istream stream(&buffer);
::cereal::PortableBinaryInputArchive archive(stream);
return read<T, Ps...>(archive);
} catch (std::exception& e) {
return error(std::string("Cereal read error: ") + e.what());
Expand Down
41 changes: 41 additions & 0 deletions include/rfl/internal/MemoryInputBuffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef RFL_INTERNAL_MEMORYINPUTBUFFER_HPP_
#define RFL_INTERNAL_MEMORYINPUTBUFFER_HPP_

#include <algorithm>
#include <cstddef>
#include <cstring>
#include <streambuf>

namespace rfl::internal {

/// A non-owning input buffer. The referenced bytes must outlive the buffer.
class MemoryInputBuffer final : public std::streambuf {
public:
MemoryInputBuffer(const char* _data, const std::size_t _size) {
// streambuf requires mutable pointers for its get area, but no put area
// is installed and the default pbackfail rejects replacement characters.
auto* begin = _size == 0 ? &empty_ : const_cast<char*>(_data);
setg(begin, begin, begin + _size);
}

MemoryInputBuffer(const MemoryInputBuffer&) = delete;
MemoryInputBuffer& operator=(const MemoryInputBuffer&) = delete;

private:
std::streamsize xsgetn(char* _dest, std::streamsize _count) override {
if (_count <= 0) return 0;
const auto count =
std::min(_count, static_cast<std::streamsize>(egptr() - gptr()));
if (count > 0) {
std::memcpy(_dest, gptr(), static_cast<std::size_t>(count));
setg(eback(), gptr() + count, egptr());
}
return count;
}

char empty_ = '\0';
};

} // namespace rfl::internal

#endif
12 changes: 12 additions & 0 deletions tests/boost_serialization/test_memory_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <rfl/boost_serialization.hpp>

#include "../check_memory_input.hpp"

TEST(boost_serialization, memory_input) {
check_memory_input::check(
[](const auto& value) { return rfl::boost_serialization::write(value); },
[](const auto* data, std::size_t size) {
return rfl::boost_serialization::read<check_memory_input::Record>(data,
size);
});
}
11 changes: 11 additions & 0 deletions tests/cereal/test_memory_input.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <rfl/cereal.hpp>

#include "../check_memory_input.hpp"

TEST(cereal, memory_input) {
check_memory_input::check(
[](const auto& value) { return rfl::cereal::write(value); },
[](const auto* data, std::size_t size) {
return rfl::cereal::read<check_memory_input::Record>(data, size);
});
}
50 changes: 50 additions & 0 deletions tests/check_memory_input.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef CHECK_MEMORY_INPUT_HPP_
#define CHECK_MEMORY_INPUT_HPP_

#include <gtest/gtest.h>

#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>

namespace check_memory_input {

struct Record {
std::uint32_t id;
double value;
std::string name;
bool operator==(const Record&) const = default;
};

template <class Write, class Read>
void check(Write write, Read read) {
const Record original{42, 1.25, std::string("a\0b", 3)};
const auto bytes = write(original);
const auto saved = bytes;
for (int i = 0; i < 2; ++i) {
const auto result = read(bytes.data(), bytes.size());
ASSERT_TRUE(result);
EXPECT_EQ(*result, original);
}
EXPECT_EQ(bytes, saved);
EXPECT_FALSE(read(static_cast<const char*>(nullptr), 0));
for (std::size_t size = 0; size < bytes.size(); ++size) {
EXPECT_FALSE(read(bytes.data(), size)) << "prefix size " << size;
}
std::vector<unsigned char> unsigned_bytes(bytes.begin(), bytes.end());
const auto result = read(unsigned_bytes.data(), unsigned_bytes.size());
ASSERT_TRUE(result);
EXPECT_EQ(*result, original);
// A view must respect its extent, rather than reading into adjacent bytes.
std::vector<char> surrounded{'x'};
surrounded.insert(surrounded.end(), bytes.begin(), bytes.end());
surrounded.push_back('y');
const auto bounded = read(surrounded.data() + 1, bytes.size());
ASSERT_TRUE(bounded);
EXPECT_EQ(*bounded, original);
}

} // namespace check_memory_input

#endif
55 changes: 55 additions & 0 deletions tests/generic/test_memory_input_buffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <gtest/gtest.h>

#include <array>
#include <istream>
#include <ostream>
#include <rfl/internal/MemoryInputBuffer.hpp>
#include <string>

namespace test_memory_input_buffer {

TEST(memory_input_buffer, empty) {
rfl::internal::MemoryInputBuffer buffer(nullptr, 0);
std::istream input(&buffer);
EXPECT_EQ(input.peek(), std::char_traits<char>::eof());
char byte = 'x';
input.read(&byte, 1);
EXPECT_EQ(input.gcount(), 0);
EXPECT_EQ(byte, 'x');
}

TEST(memory_input_buffer, bounded_reads) {
const std::array<char, 5> bytes{'a', '\0', 'b', 'x', 'y'};
rfl::internal::MemoryInputBuffer buffer(bytes.data(), 3);
std::istream input(&buffer);
std::array<char, 6> output{};
input.read(output.data(), output.size());
EXPECT_EQ(input.gcount(), 3);
EXPECT_TRUE(input.eof());
EXPECT_EQ(std::string(output.data(), 3), std::string("a\0b", 3));
EXPECT_EQ(output[3], '\0');
}

TEST(memory_input_buffer, putback_does_not_modify_input) {
const std::string bytes = "abc";
rfl::internal::MemoryInputBuffer buffer(bytes.data(), bytes.size());
std::istream input(&buffer);
EXPECT_EQ(input.get(), 'a');
input.putback('a');
EXPECT_TRUE(input);
EXPECT_EQ(input.get(), 'a');
input.putback('z');
EXPECT_FALSE(input);
EXPECT_EQ(bytes, "abc");
}

TEST(memory_input_buffer, output_is_rejected) {
const std::string bytes = "abc";
rfl::internal::MemoryInputBuffer buffer(bytes.data(), bytes.size());
std::ostream output(&buffer);
output.write("xyz", 3);
EXPECT_FALSE(output);
EXPECT_EQ(bytes, "abc");
}

} // namespace test_memory_input_buffer
Loading