Commit 6ada45be authored by Dominik Charousset's avatar Dominik Charousset

Allow merging multiple buffers into caf.net frames

parent 063022f1
......@@ -94,6 +94,7 @@ caf_add_component(
detail.rfc6455
net.accept_socket
net.actor_shell
net.binary.frame
net.datagram_socket
net.http.server
net.ip
......@@ -112,5 +113,6 @@ caf_add_component(
net.typed_actor_shell
net.udp_datagram_socket
net.web_socket.client
net.web_socket.frame
net.web_socket.handshake
net.web_socket.server)
......@@ -19,13 +19,30 @@ namespace caf::net::binary {
/// An implicitly shared type for binary data frames.
class CAF_NET_EXPORT frame {
public:
// -- constructors, destructors, and assignment operators --------------------
frame() = default;
frame(frame&&) = default;
frame(const frame&) = default;
frame& operator=(frame&&) = default;
frame& operator=(const frame&) = default;
explicit frame(const_byte_span data);
explicit frame(const_byte_span buf);
// -- factory functions ------------------------------------------------------
template <class... ByteBuffers>
static frame from_buffers(const ByteBuffers&... buffers) {
static_assert(sizeof...(ByteBuffers) > 0);
const_byte_span bufs[sizeof...(ByteBuffers)] = {make_span(buffers)...};
return frame(make_span(bufs));
}
// -- properties -------------------------------------------------------------
explicit operator bool() const noexcept {
return static_cast<bool>(data_);
......@@ -44,10 +61,16 @@ public:
}
const_byte_span bytes() const noexcept {
return {data_->storage(), data_->size()};
return data_ ? const_byte_span{data_->storage(), data_->size()}
: const_byte_span{};
}
private:
explicit frame(caf::span<const const_byte_span> bufs);
template <class... Args>
void init(size_t payload_size, Args&&... arg);
intrusive_ptr<web_socket::frame::data> data_;
};
......
......@@ -34,43 +34,7 @@ namespace caf::net::web_socket {
/// text or binary frames.
class CAF_NET_EXPORT frame {
public:
frame() = default;
frame(frame&&) = default;
frame(const frame&) = default;
frame& operator=(frame&&) = default;
frame& operator=(const frame&) = default;
explicit frame(const_byte_span bytes);
explicit frame(std::string_view text);
explicit operator bool() const noexcept {
return static_cast<bool>(data_);
}
size_t size() const noexcept {
return data_ ? data_->size() : 0;
}
bool empty() const noexcept {
return data_ ? data_->size() == 0 : true;
}
void swap(frame& other) {
data_.swap(other.data_);
}
bool is_binary() const noexcept {
return data_ && data_->is_binary();
}
bool is_text() const noexcept {
return data_ && !data_->is_binary();
}
const_byte_span as_binary() const noexcept;
std::string_view as_text() const noexcept;
// -- member types -----------------------------------------------------------
class data {
public:
......@@ -80,10 +44,18 @@ public:
data& operator=(const data&) = delete;
explicit data(bool bin, size_t size) : rc_(1), bin_(bin), size_(size) {
data(bool is_bin, size_t size) : rc_(1), bin_(is_bin), size_(size) {
static_cast<void>(padding_); // Silence unused-private-field warning.
}
explicit data(const_byte_span bytes);
explicit data(std::string_view str);
data(size_t total_size, span<const const_byte_span> bufs);
data(size_t total_size, span<const std::string_view> bufs);
// -- reference counting ---------------------------------------------------
bool unique() const noexcept {
......@@ -132,12 +104,79 @@ public:
std::byte storage_[];
};
// -- constructors, destructors, and assignment operators --------------------
frame() = default;
frame(frame&&) = default;
frame(const frame&) = default;
frame& operator=(frame&&) = default;
frame& operator=(const frame&) = default;
explicit frame(const_byte_span bytes);
explicit frame(std::string_view text);
// -- factory functions ------------------------------------------------------
template <class... Buffers>
static frame from_buffers(const Buffers&... buffers) {
static_assert(sizeof...(Buffers) > 0);
const_byte_span bufs[sizeof...(Buffers)] = {make_span(buffers)...};
return frame(make_span(bufs));
}
template <class... Buffers>
static frame from_strings(const Buffers&... buffers) {
static_assert(sizeof...(Buffers) > 0);
std::string_view bufs[sizeof...(Buffers)] = {std::string_view(buffers)...};
return frame(make_span(bufs));
}
// -- properties -------------------------------------------------------------
explicit operator bool() const noexcept {
return static_cast<bool>(data_);
}
size_t size() const noexcept {
return data_ ? data_->size() : 0;
}
bool empty() const noexcept {
return data_ ? data_->size() == 0 : true;
}
void swap(frame& other) {
data_.swap(other.data_);
}
bool is_binary() const noexcept {
return data_ && data_->is_binary();
}
bool is_text() const noexcept {
return data_ && !data_->is_binary();
}
const_byte_span as_binary() const noexcept;
std::string_view as_text() const noexcept;
private:
explicit frame(intrusive_ptr<data> ptr) : data_(std::move(ptr)) {
// nop
}
void alloc(bool is_binary, size_t num_bytes);
explicit frame(caf::span<const const_byte_span> buffers);
explicit frame(caf::span<const std::string_view> buffers);
template <class... Args>
void init(size_t payload_size, Args&&... arg);
intrusive_ptr<data> data_;
};
......
......@@ -6,14 +6,34 @@
#include <cstring>
#include <new>
#include <numeric>
namespace caf::net::binary {
frame::frame(const_byte_span data) {
auto total_size = sizeof(web_socket::frame::data) + data.size();
auto vptr = malloc(total_size);
data_.reset(new (vptr) web_socket::frame::data(true, data.size()), false);
memcpy(data_->storage(), data.data(), data.size());
namespace {
template <class Buffer>
size_t calc_total_size(span<const Buffer> buffers) {
auto fn = [](size_t n, const Buffer& buf) { return n + buf.size(); };
return std::accumulate(buffers.begin(), buffers.end(), size_t{0}, fn);
}
} // namespace
frame::frame(const_byte_span buf) {
init(buf.size(), buf);
}
frame::frame(caf::span<const const_byte_span> bufs) {
auto payload_size = calc_total_size(bufs);
init(payload_size, payload_size, bufs);
}
template <class... Args>
void frame::init(size_t payload_size, Args&&... args) {
using data_t = web_socket::frame::data;
auto vptr = malloc(sizeof(data_t) + payload_size);
data_.reset(new (vptr) data_t(std::forward<Args>(args)...), false);
}
} // namespace caf::net::binary
......@@ -6,25 +6,78 @@
#include <cstring>
#include <new>
#include <numeric>
namespace caf::net::web_socket {
namespace {
template <class Buffer>
size_t calc_total_size(span<const Buffer> buffers) {
auto fn = [](size_t n, const Buffer& buf) { return n + buf.size(); };
return std::accumulate(buffers.begin(), buffers.end(), size_t{0}, fn);
}
} // namespace
frame::data::data(const_byte_span bytes) : data(true, bytes.size()) {
memcpy(storage(), bytes.data(), bytes.size());
}
frame::data::data(std::string_view str) : data(false, str.size()) {
memcpy(storage(), str.data(), str.size());
}
frame::data::data(size_t total_size, span<const const_byte_span> bufs)
: data(true, total_size) {
std::byte* pos = storage_;
for (const auto& buf : bufs) {
if (!buf.empty()) {
memcpy(pos, buf.data(), buf.size());
pos += buf.size();
}
}
}
frame::data::data(size_t total_size, span<const std::string_view> bufs)
: data(false, total_size) {
std::byte* pos = storage_;
for (const auto& buf : bufs) {
if (!buf.empty()) {
memcpy(pos, buf.data(), buf.size());
pos += buf.size();
}
}
}
frame::frame(const_byte_span bytes) {
alloc(true, bytes.size());
memcpy(data_->storage(), bytes.data(), bytes.size());
init(bytes.size(), bytes);
}
frame::frame(std::string_view text) {
alloc(false, text.size());
memcpy(data_->storage(), text.data(), text.size());
init(text.size(), text);
}
frame::frame(caf::span<const const_byte_span> buffers) {
auto payload_size = calc_total_size(buffers);
init(payload_size, payload_size, buffers);
}
frame::frame(caf::span<const std::string_view> buffers) {
auto payload_size = calc_total_size(buffers);
init(payload_size, payload_size, buffers);
}
const_byte_span frame::as_binary() const noexcept {
return {data_->storage(), data_->size()};
using res_t = const_byte_span;
return data_ ? res_t{data_->storage(), data_->size()} : res_t{};
}
std::string_view frame::as_text() const noexcept {
return {reinterpret_cast<const char*>(data_->storage()), data_->size()};
using res_t = std::string_view;
return data_ ? res_t{reinterpret_cast<const char*>(data_->storage()),
data_->size()}
: res_t{};
}
void frame::data::deref() noexcept {
......@@ -34,10 +87,10 @@ void frame::data::deref() noexcept {
}
}
void frame::alloc(bool is_binary, size_t num_bytes) {
auto total_size = sizeof(data) + num_bytes;
auto vptr = malloc(total_size);
data_.reset(new (vptr) data(is_binary, num_bytes), false);
template <class... Args>
void frame::init(size_t payload_size, Args&&... args) {
auto vptr = malloc(sizeof(data) + payload_size);
data_.reset(new (vptr) data(std::forward<Args>(args)...), false);
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.binary.frame
#include "caf/net/binary/frame.hpp"
#include "net-test.hpp"
using namespace caf;
namespace {
template <class... Ts>
std::vector<std::byte> to_byte_buf(Ts... values) {
return std::vector<std::byte>{static_cast<std::byte>(values)...};
}
template <class T>
std::vector<std::byte> to_vec(span<const T> values) {
return std::vector<std::byte>{values.begin(), values.end()};
}
} // namespace
TEST_CASE("default construction") {
net::binary::frame uut;
CHECK(!uut);
CHECK(uut.empty());
CHECK(uut.bytes().empty());
CHECK_EQ(uut.size(), 0u);
}
TEST_CASE("construction from a single buffer") {
auto buf = to_byte_buf(1, 2, 3);
auto uut = net::binary::frame{make_span(buf)};
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK(!uut.bytes().empty());
CHECK_EQ(uut.size(), 3u);
CHECK_EQ(uut.bytes().size(), 3u);
CHECK_EQ(to_vec(uut.bytes()), buf);
}
TEST_CASE("construction from multiple buffers") {
auto buf1 = to_byte_buf(1, 2);
auto buf2 = to_byte_buf();
auto buf3 = to_byte_buf(3, 4, 5);
auto buf4 = to_byte_buf(1, 2, 3, 4, 5);
auto uut = net::binary::frame::from_buffers(buf1, buf2, buf3);
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK(!uut.bytes().empty());
CHECK_EQ(uut.size(), 5u);
CHECK_EQ(uut.bytes().size(), 5u);
CHECK_EQ(to_vec(uut.bytes()), buf4);
}
TEST_CASE("copying, moving and swapping") {
auto buf = to_byte_buf(1, 2, 3);
auto uut1 = net::binary::frame{};
auto uut2 = net::binary::frame{make_span(buf)};
auto uut3 = uut1;
auto uut4 = uut2;
CHECK_EQ(uut1.bytes().data(), uut3.bytes().data());
CHECK_EQ(uut2.bytes().data(), uut4.bytes().data());
CHECK_NE(uut1.bytes().data(), uut2.bytes().data());
auto uut5 = std::move(uut1);
auto uut6 = std::move(uut2);
CHECK_EQ(uut5.bytes().data(), uut3.bytes().data());
CHECK_EQ(uut6.bytes().data(), uut4.bytes().data());
CHECK_NE(uut5.bytes().data(), uut6.bytes().data());
uut5.swap(uut6);
CHECK_EQ(uut6.bytes().data(), uut3.bytes().data());
CHECK_EQ(uut5.bytes().data(), uut4.bytes().data());
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.web_socket.frame
#include "caf/net/web_socket/frame.hpp"
#include "net-test.hpp"
using namespace caf;
using namespace std::literals;
namespace {
template <class... Ts>
std::vector<std::byte> to_byte_buf(Ts... values) {
return std::vector<std::byte>{static_cast<std::byte>(values)...};
}
template <class T>
std::vector<std::byte> to_vec(span<const T> values) {
return std::vector<std::byte>{values.begin(), values.end()};
}
} // namespace
TEST_CASE("default construction") {
net::web_socket::frame uut;
CHECK(!uut);
CHECK(!uut.is_binary());
CHECK(!uut.is_text());
CHECK(uut.empty());
CHECK_EQ(uut.size(), 0u);
}
TEST_CASE("construction from a single byte buffer") {
auto buf = to_byte_buf(1, 2, 3);
auto uut = net::web_socket::frame{make_span(buf)};
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK_EQ(uut.size(), 3u);
if (uut.is_binary()) {
auto bytes = uut.as_binary();
CHECK_EQ(bytes.size(), 3u);
CHECK_EQ(to_vec(bytes), buf);
}
}
TEST_CASE("construction from multiple byte buffers") {
auto buf1 = to_byte_buf(1, 2);
auto buf2 = to_byte_buf();
auto buf3 = to_byte_buf(3, 4, 5);
auto buf4 = to_byte_buf(1, 2, 3, 4, 5);
auto uut = net::web_socket::frame::from_buffers(buf1, buf2, buf3);
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK_EQ(uut.size(), 5u);
if (uut.is_binary()) {
auto bytes = uut.as_binary();
CHECK_EQ(bytes.size(), 5u);
CHECK_EQ(to_vec(bytes), buf4);
}
}
TEST_CASE("construction from a single text buffer") {
auto uut = net::web_socket::frame{"foo"sv};
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK_EQ(uut.size(), 3u);
if (CHECK(uut.is_text())) {
CHECK_EQ(uut.as_text(), "foo"sv);
}
}
TEST_CASE("construction from multiple text buffers") {
auto buf1 = "foo"sv;
auto buf2 = ""sv;
auto buf3 = "bar"sv;
auto buf4 = "foobar"sv;
auto uut = net::web_socket::frame::from_strings(buf1, buf2, buf3);
CHECK(static_cast<bool>(uut));
CHECK(!uut.empty());
CHECK_EQ(uut.size(), 6u);
if (uut.is_text()) {
CHECK_EQ(uut.as_text(), buf4);
}
}
TEST_CASE("copying, moving and swapping") {
auto buf = to_byte_buf(1, 2, 3);
auto uut1 = net::web_socket::frame{};
auto uut2 = net::web_socket::frame{make_span(buf)};
auto uut3 = uut1;
auto uut4 = uut2;
CHECK_EQ(uut1.as_binary().data(), uut3.as_binary().data());
CHECK_EQ(uut2.as_binary().data(), uut4.as_binary().data());
CHECK_NE(uut1.as_binary().data(), uut2.as_binary().data());
auto uut5 = std::move(uut1);
auto uut6 = std::move(uut2);
CHECK_EQ(uut5.as_binary().data(), uut3.as_binary().data());
CHECK_EQ(uut6.as_binary().data(), uut4.as_binary().data());
CHECK_NE(uut5.as_binary().data(), uut6.as_binary().data());
uut5.swap(uut6);
CHECK_EQ(uut6.as_binary().data(), uut3.as_binary().data());
CHECK_EQ(uut5.as_binary().data(), uut4.as_binary().data());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment