Unverified Commit 866dc9b3 authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #86

Revise layering API, add actor_shell abstraction, add WebSocket layer
parents 18776a84 c582e2db
......@@ -31,6 +31,7 @@ option(CAF_INC_ENABLE_STANDALONE_BUILD
option(CAF_INC_ENABLE_TESTING "Build unit test suites" ON)
option(CAF_INC_ENABLE_NET_MODULE "Build networking module" ON)
option(CAF_INC_ENABLE_BB_MODULE "Build building blocks module" ON)
option(CAF_INC_ENABLE_EXAMPLES "Build small programs" ON)
# -- incubator options with non-boolean values ---------------------------------
......@@ -49,7 +50,7 @@ if(CAF_INC_ENABLE_STANDALONE_BUILD)
FetchContent_Declare(
actor_framework
GIT_REPOSITORY https://github.com/actor-framework/actor-framework.git
GIT_TAG 9caa83be4
GIT_TAG 4f8609b
)
FetchContent_Populate(actor_framework)
set(CAF_ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE)
......@@ -208,3 +209,7 @@ endif()
if(CAF_INC_ENABLE_NET_MODULE)
add_subdirectory(libcaf_bb)
endif()
if(CAF_INC_ENABLE_EXAMPLES)
add_subdirectory(examples)
endif()
......@@ -36,6 +36,7 @@ Convenience options:
--build-type=Debug
--sanitizers=address,undefined
--enable-utility-targets
--enable-export-compile-commands
Flags (use --enable-<name> to activate and --disable-<name> to deactivate):
......@@ -143,6 +144,7 @@ while [ $# -ne 0 ]; do
CMakeBuildType='Debug'
append_cache_entry CAF_INC_SANITIZERS STRING 'address,undefined'
set_build_flag utility-targets ON
set_build_flag export-compile-commands ON
;;
--enable-*)
set_build_flag $optarg ON
......
add_custom_target(all_examples)
function(add_example folder name)
add_executable(${name} ${folder}/${name}.cpp ${ARGN})
install(FILES ${folder}/${name}.cpp DESTINATION ${CMAKE_INSTALL_DATADIR}/caf/examples/${folder})
add_dependencies(${name} all_examples)
endfunction()
# -- examples for CAF::net -----------------------------------------------------
if(TARGET CAF::net)
function(add_net_example name)
add_example("net" ${name} ${ARGN})
target_link_libraries(${name} CAF::net CAF::core)
endfunction()
add_net_example(web-socket-calculator)
add_net_example(web-socket-feed)
endif()
// This example application wraps a simple calculator actor and allows clients
// to communicate to this worker via JSON-ish messages over a WebSocket
// connection.
//
// To run the server at port 4242 (defaults to 8080):
//
// ~~~
// web-socket-calculator -p 4242
// ~~~
//
// Once started, the application waits for incoming WebSocket connections that
// send text frames. A simple WebSocket client written in Python could look as
// follows:
//
// ~~~(py)
// #!/usr/bin/env python
//
// import asyncio
// import websockets
//
// line1 = '{ values = [ { "@type" = "task::addition", x = 17, y = 8 } ] }\n'
// line2 = '{ values = [ { "@type" = "task::subtraction", x = 17, y = 8 } ] }\n'
//
// async def hello():
// uri = "ws://localhost:8080"
// async with websockets.connect(uri) as websocket:
// await websocket.send(line1)
// print(f"> {line1}")
// response = await websocket.recv()
// print(f"< {response}")
// await websocket.send(line2)
// print(f"> {line2}")
// response = await websocket.recv()
// print(f"< {response}")
//
// asyncio.get_event_loop().run_until_complete(hello())
// ~~~
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte_span.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/exec_main.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/web_socket_server.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include <cstdint>
// -- custom message types -----------------------------------------------------
// Usually, we prefer atoms to prefix certain operations. However, using custom
// message types provides a nicer interface for the text-based WebSocket
// communication.
namespace task {
struct addition {
int32_t x;
int32_t y;
};
template <class Inspector>
bool inspect(Inspector& f, addition& x) {
return f.object(x).fields(f.field("x", x.x), f.field("y", x.y));
}
struct subtraction {
int32_t x;
int32_t y;
};
template <class Inspector>
bool inspect(Inspector& f, subtraction& x) {
return f.object(x).fields(f.field("x", x.x), f.field("y", x.y));
}
} // namespace task
CAF_BEGIN_TYPE_ID_BLOCK(web_socket_calculator, caf::first_custom_type_id)
CAF_ADD_TYPE_ID(web_socket_calculator, (task::addition))
CAF_ADD_TYPE_ID(web_socket_calculator, (task::subtraction))
CAF_END_TYPE_ID_BLOCK(web_socket_calculator)
// -- implementation of the calculator actor -----------------------------------
caf::behavior calculator() {
return {
[](task::addition input) { return input.x + input.y; },
[](task::subtraction input) { return input.x - input.y; },
};
}
// -- implementation of the WebSocket application ------------------------------
class app {
public:
// -- member types -----------------------------------------------------------
// We expect a stream-oriented interface to the lower communication layers.
using input_tag = caf::tag::mixed_message_oriented;
// -- constants --------------------------------------------------------------
// Restricts our buffer to a maximum of 1MB.
static constexpr size_t max_buf_size = 1024 * 1024;
// -- constructors, destructors, and assignment operators --------------------
explicit app(caf::actor worker) : worker_(std::move(worker)) {
// nop
}
template <class LowerLayerPtr>
caf::error init(caf::net::socket_manager* mgr, LowerLayerPtr down,
const caf::settings&) {
buf_.reserve(max_buf_size);
// Create the actor-shell wrapper for this application.
self_ = mgr->make_actor_shell(down);
std::cout << "*** established new connection on socket "
<< down->handle().id << "\n";
return caf::none;
}
// -- mixed_message_oriented interface functions -----------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
// The lower level calls this function whenever a send event occurs, but
// before performing any socket I/O operation. We use this as a hook for
// constantly checking our mailbox. Returning `false` here aborts the
// application and closes the socket.
while (self_->consume_message()) {
// We set abort_reason in our response handlers in case of an error.
if (down->abort_reason())
return false;
// else: repeat.
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
// The lower level calls this function to check whether we are ready to
// unregister our socket from send events. We must make sure to put our
// mailbox in to the blocking state in order to get re-registered once new
// messages arrive.
return self_->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const caf::error& reason) {
std::cout << "*** lost connection on socket " << down->handle().id << ": "
<< to_string(reason) << "\n";
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, caf::string_view text) {
// The other functions in this class provide mostly boilerplate code. Here
// comes our main logic. In this function, we receive a text data frame from
// the WebSocket. We don't assume that the client sends one line per
// frame, so we buffer all incoming text until finding a newline character.
if (buf_.size() + text.size() > max_buf_size) {
auto err = caf::make_error(caf::sec::runtime_error,
"exceeded max text buffer size");
down->abort_reason(std::move(err));
return -1;
}
buf_.insert(buf_.end(), text.begin(), text.end());
auto nl_pos = [this] { return std::find(buf_.begin(), buf_.end(), '\n'); };
for (auto i = nl_pos(); i != buf_.end(); i = nl_pos()) {
// Skip empty lines.
if (i == buf_.begin()) {
buf_.erase(buf_.begin(), buf_.begin() + 1);
continue;
}
// Deserialize config value / message from received line.
auto num_bytes = std::distance(buf_.begin(), i) + 1;
caf::string_view line{buf_.data(), static_cast<size_t>(num_bytes) - 1};
std::cout << "*** [socket " << down->handle().id << "] INPUT: " << line
<< "\n";
caf::config_value val;
if (auto parsed_val = caf::config_value::parse(line)) {
val = std::move(*parsed_val);
} else {
down->abort_reason(std::move(parsed_val.error()));
return -1;
}
caf::config_value_reader reader{&val};
caf::message msg;
if (!reader.apply_object(msg)) {
down->abort_reason(reader.get_error());
return -1;
}
// Dispatch message to worker.
self_->request(worker_, std::chrono::seconds{1}, std::move(msg))
.then(
[this, down](int32_t value) {
// Simply respond with the value as string, wrapped into a WebSocket
// text message frame.
auto str_response = std::to_string(value);
std::cout << "*** [socket " << down->handle().id
<< "] OUTPUT: " << str_response << "\n";
str_response += '\n';
down->begin_text_message();
auto& buf = down->text_message_buffer();
buf.insert(buf.end(), str_response.begin(), str_response.end());
down->end_text_message();
},
[down](caf::error& err) { down->abort_reason(std::move(err)); });
// Erase consumed data from the buffer.
buf_.erase(buf_.begin(), i + 1);
}
return static_cast<ptrdiff_t>(text.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr down, caf::byte_span) {
// Reject binary input.
auto err = caf::make_error(caf::sec::runtime_error,
"received binary WebSocket frame (unsupported)");
down->abort_reason(std::move(err));
return -1;
}
private:
// Caches incoming text data until finding a newline character.
std::vector<char> buf_;
// Stores a handle to our worker.
caf::actor worker_;
// Enables the application to send and receive actor messages.
caf::net::actor_shell_ptr self_;
};
// -- main ---------------------------------------------------------------------
static constexpr uint16_t default_port = 8080;
struct config : caf::actor_system_config {
config() {
opt_group{custom_options_, "global"} //
.add<uint16_t>("port,p", "port to listen for incoming connections");
}
};
int caf_main(caf::actor_system& sys, const config& cfg) {
using namespace caf;
using namespace caf::net;
// Open up a TCP port for incoming connections.
auto port = get_or(cfg, "port", default_port);
tcp_accept_socket sock;
if (auto sock_res = make_tcp_accept_socket({ipv4_address{}, port})) {
std::cout << "*** started listening for incoming connections on port "
<< port << '\n';
sock = std::move(*sock_res);
} else {
std::cerr << "*** unable to open port " << port << ": "
<< to_string(sock_res.error()) << '\n';
return EXIT_FAILURE;
}
// Spawn our worker actor and initiate the protocol stack.
auto worker = sys.spawn(calculator);
auto add_conn = [worker](tcp_stream_socket sock, multiplexer* mpx) {
return make_socket_manager<app, web_socket_server, stream_transport>(
sock, mpx, worker);
};
sys.network_manager().make_acceptor(sock, add_conn);
return EXIT_SUCCESS;
}
CAF_MAIN(caf::id_block::web_socket_calculator, caf::net::middleman)
// This example application wraps a simple feed actor and allows clients to
// "subscribe" to it via WebSocket connection.
//
// To run the server at port 4242 (defaults to 8080):
//
// ~~~
// web-socket-calculator -p 4242
// ~~~
//
// Once started, the application waits for incoming WebSocket connections and
// sends it randomly-generated stock infos. A simple WebSocket client written in
// Python could look as follows:
//
// ~~~(py)
// #!/usr/bin/env python
//
// import asyncio
// import websockets
//
// async def run():
// uri = "ws://localhost:8080"
// async with websockets.connect(uri) as websocket:
// while True:
// info = await websocket.recv()
// print(f"{info}")
//
// asyncio.get_event_loop().run_until_complete(run())
// ~~~
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte_span.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/exec_main.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/web_socket_server.hpp"
#include "caf/stateful_actor.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/typed_event_based_actor.hpp"
#include <cstdint>
// -- custom message types -----------------------------------------------------
// Usually, we prefer atoms to prefix certain operations. However, using custom
// message types provides a nicer interface for the text-based WebSocket
// communication.
namespace stock {
struct info {
std::string symbol;
std::string currency;
double current;
double open;
double high;
double low;
};
template <class Inspector>
bool inspect(Inspector& f, info& x) {
return f.object(x).fields(f.field("symbol", x.symbol),
f.field("currency", x.currency),
f.field("open", x.open), f.field("high", x.high),
f.field("low", x.low));
}
using listener = caf::typed_actor<caf::result<void>(info)>;
using feed = caf::typed_actor<caf::result<void>(caf::subscribe_atom, listener)>;
} // namespace stock
CAF_BEGIN_TYPE_ID_BLOCK(web_socket_feed, caf::first_custom_type_id)
// -- message types ----------------------------------------------------------
CAF_ADD_TYPE_ID(web_socket_feed, (stock::info))
// -- actor interfaces -------------------------------------------------------
CAF_ADD_TYPE_ID(web_socket_feed, (stock::listener))
CAF_ADD_TYPE_ID(web_socket_feed, (stock::feed))
CAF_END_TYPE_ID_BLOCK(web_socket_feed)
// -- implementation of the feed actor -----------------------------------------
using random_feed = stock::feed::extend<caf::result<void>(caf::update_atom)>;
struct random_feed_state {
random_feed_state() : val_dist(0, 100000), index_dist(0, 19) {
// Init random number generator.
std::random_device rd;
rng.seed(rd());
// Fill vector with some stuff.
for (size_t i = 0; i < 20; ++i) {
std::uniform_int_distribution<int> char_dist{'A', 'Z'};
std::string symbol;
for (size_t j = 0; j < 5; ++j)
symbol += static_cast<char>(char_dist(rng));
auto val = next_value();
infos.emplace_back(
stock::info{std::move(symbol), "USD", val, val, val, val});
}
}
// Picks a random stock, assigns a new value to it, and returns it.
stock::info& update() {
auto& x = infos[index_dist(rng)];
auto val = next_value();
x.current = val;
x.high = std::max(x.high, val);
x.low = std::min(x.high, val);
return x;
}
double next_value() {
return val_dist(rng) / 100.0;
}
std::vector<stock::info> infos;
std::vector<stock::listener> listeners;
std::minstd_rand rng;
std::uniform_int_distribution<int> val_dist;
std::uniform_int_distribution<size_t> index_dist;
};
random_feed::behavior_type
feed_impl(random_feed::stateful_pointer<random_feed_state> self) {
self->set_down_handler([self](const caf::down_msg& dm) {
auto is_addr = [&dm](const auto& hdl) { return hdl == dm.source; };
auto& xs = self->state.listeners;
xs.erase(std::remove_if(xs.begin(), xs.end(), is_addr), xs.end());
std::cout << "Removed listener from feed.\n";
});
self->delayed_send(self, std::chrono::seconds(1), caf::update_atom_v);
return {
[self](caf::update_atom) {
auto& st = self->state;
auto& x = st.update();
for (auto& listener : st.listeners)
self->send(listener, x);
self->delayed_send(self, std::chrono::seconds(1), caf::update_atom_v);
},
[self](caf::subscribe_atom, stock::listener hdl) {
std::cout << "Added new listener to feed.\n";
self->monitor(hdl);
auto& st = self->state;
st.listeners.emplace_back(hdl);
for (auto& info : st.infos)
self->send(hdl, info);
},
};
}
// -- implementation of the WebSocket application ------------------------------
class app {
public:
// -- member types -----------------------------------------------------------
// We expect a stream-oriented interface to the lower communication layers.
using input_tag = caf::tag::mixed_message_oriented;
// -- constants --------------------------------------------------------------
// Restricts our buffer to a maximum of 1MB.
static constexpr size_t max_buf_size = 1024 * 1024;
// -- constructors, destructors, and assignment operators --------------------
explicit app(stock::feed feed) : feed_(std::move(feed)) {
// nop
}
template <class LowerLayerPtr>
caf::error init(caf::net::socket_manager* mgr, LowerLayerPtr down,
const caf::settings&) {
// Create the actor-shell wrapper for this application.
self_ = mgr->make_actor_shell(down);
self_->set_behavior([down](stock::info& x) {
// Send stock info object as JSON-style text.
down->begin_text_message();
auto& buf = down->text_message_buffer();
// This utility creates a lot of unnecessary heap-allocated strings. Good
// enough for an example, but not efficient enough for production.
auto append = [&buf](std::initializer_list<std::string> strs) {
for (auto& str : strs)
buf.insert(buf.end(), str.begin(), str.end());
};
append({"{\n"});
append({" symbol: \"", x.symbol, "\",\n"});
append({" currency: \"", x.currency, "\",\n"});
append({" current: ", std::to_string(x.current), ",\n"});
append({" open: ", std::to_string(x.open), ",\n"});
append({" high: ", std::to_string(x.high), ",\n"});
append({" low: ", std::to_string(x.low), "\n"});
append({"}\n"});
down->end_text_message();
});
// We promise to implement the listener interface and register at the feed.
auto self_hdl = caf::actor_cast<stock::listener>(self_.as_actor());
self_->send(feed_, caf::subscribe_atom_v, self_hdl);
return caf::none;
}
// -- mixed_message_oriented interface functions -----------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
// The lower level calls this function whenever a send event occurs, but
// before performing any socket I/O operation. We use this as a hook for
// constantly checking our mailbox. Returning `false` here aborts the
// application and closes the socket.
while (self_->consume_message()) {
// We set abort_reason in our response handlers in case of an error.
if (down->abort_reason())
return false;
// else: repeat.
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
// The lower level calls this function to check whether we are ready to
// unregister our socket from send events. We must make sure to put our
// mailbox in to the blocking state in order to get re-registered once new
// messages arrive.
return self_->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const caf::error&) {
// nop
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, caf::string_view text) {
// Discard any input.
return static_cast<ptrdiff_t>(text.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr, caf::byte_span bytes) {
// Discard any input.
return static_cast<ptrdiff_t>(bytes.size());
}
private:
// Stores a handle to our worker.
stock::feed feed_;
// Enables the application to send and receive actor messages.
caf::net::actor_shell_ptr self_;
};
// -- main ---------------------------------------------------------------------
static constexpr uint16_t default_port = 8080;
struct config : caf::actor_system_config {
config() {
opt_group{custom_options_, "global"} //
.add<uint16_t>("port,p", "port to listen for incoming connections");
}
};
int caf_main(caf::actor_system& sys, const config& cfg) {
using namespace caf;
using namespace caf::net;
// Open up a TCP port for incoming connections.
auto port = get_or(cfg, "port", default_port);
tcp_accept_socket sock;
if (auto sock_res = make_tcp_accept_socket({ipv4_address{}, port})) {
std::cout << "*** started listening for incoming connections on port "
<< port << '\n';
sock = std::move(*sock_res);
} else {
std::cerr << "*** unable to open port " << port << ": "
<< to_string(sock_res.error()) << '\n';
return EXIT_FAILURE;
}
// Spawn our feed actor and initiate the protocol stack.
stock::feed feed = sys.spawn(feed_impl);
auto add_conn = [feed](tcp_stream_socket sock, multiplexer* mpx) {
return make_socket_manager<app, web_socket_server, stream_transport>(
sock, mpx, feed);
};
sys.network_manager().make_acceptor(sock, add_conn);
return EXIT_SUCCESS;
}
CAF_MAIN(caf::id_block::web_socket_feed, caf::net::middleman)
......@@ -34,8 +34,12 @@ endfunction()
# -- add library targets -------------------------------------------------------
add_library(libcaf_net_obj OBJECT ${CAF_NET_HEADERS}
src/actor_proxy_impl.cpp
src/basp/application.cpp
#src/actor_proxy_impl.cpp
#src/basp/application.cpp
#src/endpoint_manager.cpp
#src/net/backend/tcp.cpp
#src/net/backend/test.cpp
#src/net/endpoint_manager_queue.cpp
src/basp/connection_state_strings.cpp
src/basp/ec_strings.cpp
src/basp/message_type_strings.cpp
......@@ -43,16 +47,13 @@ add_library(libcaf_net_obj OBJECT ${CAF_NET_HEADERS}
src/convert_ip_endpoint.cpp
src/datagram_socket.cpp
src/defaults.cpp
src/defaults.cpp
src/endpoint_manager.cpp
src/detail/rfc6455.cpp
src/header.cpp
src/host.cpp
src/ip.cpp
src/message_queue.cpp
src/multiplexer.cpp
src/net/backend/test.cpp
src/net/backend/tcp.cpp
src/net/endpoint_manager_queue.cpp
src/net/actor_shell.cpp
src/net/middleman.cpp
src/net/middleman_backend.cpp
src/net/packet_writer.cpp
......@@ -80,7 +81,8 @@ set_property(TARGET libcaf_net_obj PROPERTY POSITION_INDEPENDENT_CODE ON)
caf_net_set_default_properties(libcaf_net_obj libcaf_net)
target_include_directories(libcaf_net INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>)
add_library(CAF::net ALIAS libcaf_net)
......@@ -123,30 +125,34 @@ target_include_directories(caf-net-test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tes
target_link_libraries(caf-net-test PRIVATE CAF::test)
caf_incubator_add_test_suites(caf-net-test
net.basp.message_queue
net.basp.ping_pong
net.basp.worker
accept_socket
#application
convert_ip_endpoint
datagram_socket
detail.rfc6455
#datagram_transport
#doorman
#endpoint_manager
header
ip
multiplexer
net.actor_shell
#net.backend.tcp
#net.basp.message_queue
#net.basp.ping_pong
#net.basp.worker
net.length_prefix_framing
net.web_socket_server
network_socket
pipe_socket
application
socket
convert_ip_endpoint
socket_guard
datagram_socket
stream_application
datagram_transport
#stream_application
stream_socket
doorman
stream_transport
endpoint_manager
string_application
header
#string_application
tcp_sockets
ip
transport_worker
multiplexer
transport_worker_dispatcher
#transport_worker
#transport_worker_dispatcher
udp_datagram_socket
network_socket
net.backend.tcp
)
Protocol Layering
=================
Layering plays an important role in the design of the ``caf.net`` module. When
implementing a new protocol for ``caf.net``, this protocol should integrate
naturally with any number of other protocols. To enable this key property,
``caf.net`` establishes a set of conventions and abstract interfaces.
Concepts
--------
A *protocol layer* is a class that implements a single processing step in a
communication pipeline. Multiple layers are organized in a *protocol stack*.
Each layer may only communicate with its direct predecessor or successor in the
stack.
At the bottom of the protocol stack is usually a *transport layer*. For example,
a ``stream_transport`` manages a stream socket and provides access to input and
output buffers to the upper layer.
At the top of the protocol is an *application* that utilizes the lower layers
for communication via the network. Applications should only rely on the
*abstract interface type* when communicating with their lower layer. For
example, an application that processes a data stream should not implement
against a TCP socket interface directly. By programming against the abstract
interface types of ``caf.net``, users can instantiate an application with any
compatible protocol stack of their choosing. For example, a user may add extra
security by using application-level data encryption or combine a custom datagram
transport with protocol layers that establish ordering and reliability to
emulate a stream.
Abstract Interface Types
------------------------
By default, ``caf.net`` distinguishes between these abstract interface types:
* *stream*: A stream interface represents a sequence of Bytes, transmitted
reliable and in order.
* *datagram*: A datagram interface provides access to some basic transfer units
that may arrive out of order or not at all.
* *message*: A message interface provides access to high-level, structured data.
Messages consist of a header and a payload. A single message may span multiple
datagrams, for example.
Note that each interface type also depends on the *direction*, i.e., whether
talking to the upper or lower level. Incoming data always travels the protocol
stack *up*. Outgoing data always travels the protocol stack *down*.
..code-block:: C++
interface base [role: upper layer] {
/// Called whenever the underlying transport is ready to send, allowing the
/// upper layers to produce additional output data or use the event to read
/// from event queues, etc.
/// @returns `true` if the lower layers may proceed, `false` otherwise
/// (aborts execution).
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down);
/// Called whenever the underlying transport finished writing all buffered
/// data for output to query whether an upper layer still has pending events
/// or may produce output data on the next call to `prepare_send`.
/// @returns `true` if the underlying socket may get removed from the I/O
/// event loop, `false` otherwise.
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down);
}
interface base [role: lower layer] {
/// Returns whether the layer has output slots available.
bool can_send_more() const noexcept;
/// Returns the managed socket.
auto handle() const noexcept;
}
interface stream_oriented [role: upper layer] {
/// Called by the lower layer for cleaning up any state in case of an error.
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason);
/// Consumes bytes from the lower layer.
/// @param down Reference to the lower layer that received the data.
/// @param buffer Available bytes to read.
/// @param delta Bytes that arrived since last calling this function.
/// @returns The number of consumed bytes. May be zero if waiting for more
/// input or negative to signal an error.
/// @note When returning a negative value, clients should also call
/// `down.abort_reason(...)` with an appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer, byte_span delta);
}
interface stream_oriented [role: lower layer] {
/// Configures threshold for the next receive operations. Policies remain
/// active until calling this function again.
/// @warning Calling this function in `consume` invalidates both byte spans.
void configure_read(read_policy policy);
/// Prepares the layer for outgoing traffic, e.g., by allocating an output
/// buffer as necessary.
void begin_output();
/// Returns a reference to the output buffer. Users may only call this
/// function and write to the buffer between calling `begin_output()` and
/// `end_output()`.
byte_buffer& output_buffer();
/// Prepares written data for transfer, e.g., by flushing buffers or
/// registering sockets for write events.
void end_output();
/// Propagates an abort reason to the lower layers. After processing the
/// current read or write event, the lowest layer will call `abort` on its
/// upper layer.
void abort_reason(error reason);
/// Returns the last recent abort reason or `none` if no error occurred.
const error& abort_reason();
}
interface datagram_oriented [role: upper layer] {
/// Consumes a datagram from the lower layer.
/// @param down Reference to the lower layer that received the datagram.
/// @param buffer Payload of the received datagram.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
}
interface datagram_oriented [role: lower layer] {
/// Prepares the layer for an outgoing datagram, e.g., by allocating an
/// output buffer as necessary.
void begin_datagram();
/// Returns a reference to the buffer for assembling the current datagram.
/// Users may only call this function and write to the buffer between
/// calling `begin_datagram()` and `end_datagram()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& datagram_buffer();
/// Seals and prepares a datagram for transfer.
void end_datagram();
}
interface message_oriented [role: upper layer] {
/// Consumes a message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param buffer Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
}
interface message_oriented [role: lower layer] {
/// Prepares the layer for an outgoing message, e.g., by allocating an
/// output buffer as necessary.
void begin_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_message()` and `end_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& message_buffer();
/// Seals and prepares a message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_message();
}
interface mixed_message_oriented [role: upper layer] {
/// Consumes a binary message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param buffer Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr down, byte_span buffer);
/// Consumes a text message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param text Payload of the received message. The encoding depends on the
/// application.
/// @returns The number of consumed characters or a negative value to signal
/// an error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed
/// characters, clients must also call `down.set_read_error(...)` with
/// an appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, string_view text);
}
interface mixed_message_oriented [role: lower layer] {
/// Prepares the layer for an outgoing binary message, e.g., by allocating
/// an output buffer as necessary.
void begin_binary_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_binary_message()` and `end_binary_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& binary_message_buffer();
/// Seals and prepares a binary message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_binary_message();
/// Prepares the layer for an outgoing text message, e.g., by allocating
/// an output buffer as necessary.
void begin_text_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_text_message()` and `end_text_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
std::vector<char>& text_message_buffer();
/// Seals and prepares a text message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_text_message();
}
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/byte.hpp"
#include "caf/detail/net_export.hpp"
#include <cstdint>
#include <vector>
namespace caf::detail {
struct CAF_NET_EXPORT rfc6455 {
// -- member types -----------------------------------------------------------
using binary_buffer = std::vector<byte>;
struct header {
bool fin;
uint8_t opcode;
uint32_t mask_key;
uint64_t payload_len;
};
// -- constants --------------------------------------------------------------
static constexpr uint8_t continuation_frame = 0x00;
static constexpr uint8_t text_frame = 0x01;
static constexpr uint8_t binary_frame = 0x02;
static constexpr uint8_t connection_close = 0x08;
static constexpr uint8_t ping = 0x09;
static constexpr uint8_t pong = 0x0A;
// -- utility functions ------------------------------------------------------
static void mask_data(uint32_t key, span<char> data);
static void mask_data(uint32_t key, span<byte> data);
static void assemble_frame(uint32_t mask_key, span<const char> data,
binary_buffer& out);
static void assemble_frame(uint32_t mask_key, span<const byte> data,
binary_buffer& out);
static void assemble_frame(uint8_t opcode, uint32_t mask_key,
span<const byte> data, binary_buffer& out);
static ptrdiff_t decode_header(span<const byte> data, header& hdr);
};
} // namespace caf::detail
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/actor_traits.hpp"
#include "caf/callback.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/extend.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/local_actor.hpp"
#include "caf/mixin/requester.hpp"
#include "caf/mixin/sender.hpp"
#include "caf/net/fwd.hpp"
#include "caf/none.hpp"
#include "caf/policy/normal_messages.hpp"
namespace caf::net {
///
class CAF_NET_EXPORT actor_shell
: public extend<local_actor, actor_shell>::with<mixin::sender,
mixin::requester>,
public dynamically_typed_actor_base,
public non_blocking_actor_base {
public:
// -- friends ----------------------------------------------------------------
friend class actor_shell_ptr;
// -- member types -----------------------------------------------------------
using super
= extend<local_actor, actor_shell>::with<mixin::sender, mixin::requester>;
using signatures = none_t;
using behavior_type = behavior;
struct mailbox_policy {
using queue_type = intrusive::drr_queue<policy::normal_messages>;
using deficit_type = policy::normal_messages::deficit_type;
using mapped_type = policy::normal_messages::mapped_type;
using unique_pointer = policy::normal_messages::unique_pointer;
};
using mailbox_type = intrusive::fifo_inbox<mailbox_policy>;
using fallback_handler = unique_callback_ptr<result<message>(message&)>;
// -- constructors, destructors, and assignment operators --------------------
actor_shell(actor_config& cfg, socket_manager* owner);
~actor_shell() override;
// -- state modifiers --------------------------------------------------------
/// Detaches the shell from its owner and closes the mailbox.
void quit(error reason);
/// Overrides the callbacks for incoming messages.
template <class... Fs>
void set_behavior(Fs... fs) {
bhvr_ = behavior{std::move(fs)...};
}
/// Overrides the default handler for unexpected messages.
template <class F>
void set_fallback(F f) {
fallback_ = make_type_erased_callback(std::move(f));
}
// -- mailbox access ---------------------------------------------------------
auto& mailbox() noexcept {
return mailbox_;
}
/// Dequeues and returns the next message from the mailbox or returns
/// `nullptr` if the mailbox is empty.
mailbox_element_ptr next_message();
/// Tries to put the mailbox into the `blocked` state, causing the next
/// enqueue to register the owning socket manager for write events.
bool try_block_mailbox();
// -- message processing -----------------------------------------------------
/// Dequeues and processes the next message from the mailbox.
/// @returns `true` if a message was dequeued and process, `false` if the
/// mailbox was empty.
bool consume_message();
/// Adds a callback for a multiplexed response.
void add_multiplexed_response_handler(message_id response_id, behavior bhvr);
// -- overridden functions of abstract_actor ---------------------------------
using abstract_actor::enqueue;
void enqueue(mailbox_element_ptr ptr, execution_unit* eu) override;
mailbox_element* peek_at_next_mailbox_element() override;
// -- overridden functions of local_actor ------------------------------------
const char* name() const override;
void launch(execution_unit* eu, bool lazy, bool hide) override;
bool cleanup(error&& fail_state, execution_unit* host) override;
private:
// Stores incoming actor messages.
mailbox_type mailbox_;
// Guards access to owner_.
std::mutex owner_mtx_;
// Points to the owning manager (nullptr after quit was called).
socket_manager* owner_;
// Handler for consuming messages from the mailbox.
behavior bhvr_;
// Handler for unexpected messages.
fallback_handler fallback_;
// Stores callbacks for multiplexed responses.
detail::unordered_flat_map<message_id, behavior> multiplexed_responses_;
};
/// An "owning" pointer to an actor shell in the sense that it calls `quit()` on
/// the shell when going out of scope.
class CAF_NET_EXPORT actor_shell_ptr {
public:
friend class socket_manager;
constexpr actor_shell_ptr() noexcept {
// nop
}
constexpr actor_shell_ptr(std::nullptr_t) noexcept {
// nop
}
actor_shell_ptr(actor_shell_ptr&& other) noexcept = default;
actor_shell_ptr& operator=(actor_shell_ptr&& other) noexcept = default;
actor_shell_ptr(const actor_shell_ptr& other) = delete;
actor_shell_ptr& operator=(const actor_shell_ptr& other) = delete;
~actor_shell_ptr();
/// Returns an actor handle to the managed actor shell.
actor as_actor() const noexcept;
void detach(error reason);
actor_shell* get() const noexcept;
actor_shell* operator->() const noexcept {
return get();
}
actor_shell& operator*() const noexcept {
return *get();
}
bool operator!() const noexcept {
return !ptr_;
}
explicit operator bool() const noexcept {
return static_cast<bool>(ptr_);
}
private:
/// @pre `ptr != nullptr`
explicit actor_shell_ptr(strong_actor_ptr ptr) noexcept;
strong_actor_ptr ptr_;
};
} // namespace caf::net
......@@ -23,7 +23,6 @@
#include "caf/net/datagram_transport.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/host.hpp"
......
......@@ -29,6 +29,7 @@
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/net_export.hpp"
......@@ -57,8 +58,6 @@ class CAF_NET_EXPORT application {
public:
// -- member types -----------------------------------------------------------
using byte_span = span<const byte>;
using hub_type = detail::worker_hub<worker>;
struct test_tag {};
......
......@@ -83,9 +83,10 @@ CAF_NET_EXPORT void to_bytes(header x, byte_buffer& buf);
/// @relates header
template <class Inspector>
typename Inspector::result_type inspect(Inspector& f, header& x) {
return f(meta::type_name("basp::header"), x.type, x.payload_len,
x.operation_data);
bool inspect(Inspector& f, header& x) {
return f.object(x).fields(f.field("type", x.type),
f.field("payload_len", x.payload_len),
f.field("operation_data", x.operation_data));
}
/// @}
......
......@@ -53,8 +53,9 @@ public:
std::vector<strong_actor_ptr> fwd_stack;
message content;
binary_deserializer source{ctx, payload};
if (auto err = source(src_node, src_id, dst_id, fwd_stack, content)) {
CAF_LOG_ERROR("could not deserialize payload: " << CAF_ARG(err));
if (!source.apply_objects(src_node, src_id, dst_id, fwd_stack, content)) {
CAF_LOG_ERROR(
"failed to deserialize payload:" << CAF_ARG(source.get_error()));
return;
}
// Sanity checks.
......
......@@ -29,102 +29,105 @@
namespace caf::net {
/// A doorman accepts TCP connections and creates stream_transports to handle
/// them.
template <class Factory>
class doorman {
/// A connection_acceptor accepts connections from an accept socket and creates
/// socket managers to handle them via its factory.
template <class Socket, class Factory>
class connection_acceptor {
public:
// -- member types -----------------------------------------------------------
using factory_type = Factory;
using input_tag = tag::io_event_oriented;
using socket_type = Socket;
using application_type = typename Factory::application_type;
using factory_type = Factory;
// -- constructors, destructors, and assignment operators --------------------
explicit doorman(net::tcp_accept_socket acceptor, factory_type factory)
: acceptor_(acceptor), factory_(std::move(factory)) {
template <class... Ts>
explicit connection_acceptor(Ts&&... xs) : factory_(std::forward<Ts>(xs)...) {
// nop
}
// -- properties -------------------------------------------------------------
net::tcp_accept_socket handle() {
return acceptor_;
}
// -- member functions -------------------------------------------------------
template <class Parent>
error init(Parent& parent) {
// TODO: is initializing application factory nessecary?
if (auto err = factory_.init(parent))
template <class ParentPtr>
error init(socket_manager* owner, ParentPtr parent, const settings& config) {
CAF_LOG_TRACE("");
owner_ = owner;
cfg_ = config;
if (auto err = factory_.init(owner, config))
return err;
parent->register_reading();
return none;
}
template <class Parent>
bool handle_read_event(Parent& parent) {
auto x = net::accept(acceptor_);
if (!x) {
template <class ParentPtr>
bool handle_read_event(ParentPtr parent) {
CAF_LOG_TRACE("");
if (auto x = accept(parent->handle())) {
socket_manager_ptr child = factory_.make(*x, owner_->mpx_ptr());
CAF_ASSERT(child != nullptr);
if (auto err = child->init(cfg_)) {
CAF_LOG_ERROR("failed to initialize new child:" << err);
parent->abort_reason(std::move(err));
return false;
}
return true;
} else {
CAF_LOG_ERROR("accept failed:" << x.error());
return false;
}
auto mpx = parent.multiplexer();
if (!mpx) {
CAF_LOG_DEBUG("unable to get multiplexer from parent");
return false;
}
auto child = make_endpoint_manager(
mpx, parent.system(),
stream_transport<application_type>{*x, factory_.make()});
if (auto err = child->init())
return false;
return true;
}
template <class Parent>
bool handle_write_event(Parent&) {
CAF_LOG_ERROR("doorman received write event");
template <class ParentPtr>
bool handle_write_event(ParentPtr) {
CAF_LOG_ERROR("connection_acceptor received write event");
return false;
}
template <class Parent>
void
resolve(Parent&, [[maybe_unused]] const uri& locator, const actor& listener) {
CAF_LOG_ERROR("doorman called to resolve" << CAF_ARG(locator));
anon_send(listener, resolve_atom_v, "doormen cannot resolve paths");
template <class ParentPtr>
void abort(ParentPtr, const error& reason) {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason);
factory_.abort(reason);
}
void new_proxy(endpoint_manager&, const node_id& peer, actor_id id) {
CAF_LOG_ERROR("doorman received new_proxy" << CAF_ARG(peer) << CAF_ARG(id));
CAF_IGNORE_UNUSED(peer);
CAF_IGNORE_UNUSED(id);
private:
factory_type factory_;
socket_manager* owner_;
settings cfg_;
};
/// Converts a function object into a factory object for a
/// @ref connection_acceptor.
template <class FunctionObject>
class connection_acceptor_factory_adapter {
public:
explicit connection_acceptor_factory_adapter(FunctionObject f)
: f_(std::move(f)) {
// nop
}
void local_actor_down(endpoint_manager&, const node_id& peer, actor_id id,
error reason) {
CAF_LOG_ERROR("doorman received local_actor_down"
<< CAF_ARG(peer) << CAF_ARG(id) << CAF_ARG(reason));
CAF_IGNORE_UNUSED(peer);
CAF_IGNORE_UNUSED(id);
CAF_IGNORE_UNUSED(reason);
connection_acceptor_factory_adapter(connection_acceptor_factory_adapter&&)
= default;
error init(socket_manager*, const settings&) {
return none;
}
template <class Parent>
void timeout(Parent&, [[maybe_unused]] const std::string& tag,
[[maybe_unused]] uint64_t id) {
CAF_LOG_ERROR("doorman received timeout" << CAF_ARG(tag) << CAF_ARG(id));
template <class Socket>
socket_manager_ptr make(Socket connected_socket, multiplexer* mpx) {
return f_(std::move(connected_socket), mpx);
}
void handle_error([[maybe_unused]] sec err) {
CAF_LOG_ERROR("doorman encounterd error: " << err);
void abort(const error&) {
// nop
}
private:
net::tcp_accept_socket acceptor_;
factory_type factory_;
FunctionObject f_;
};
} // namespace caf::net
......@@ -36,4 +36,9 @@ CAF_NET_EXPORT extern const size_t max_header_buffers;
/// Port to listen on for tcp.
CAF_NET_EXPORT extern const uint16_t tcp_port;
/// Caps how much Bytes a stream transport pushes to its write buffer before
/// stopping to read from its message queue. Default TCP send buffer is 16kB (at
/// least on Linux).
constexpr auto stream_output_buf_cap = size_t{32768};
} // namespace caf::defaults::middleman
......@@ -52,10 +52,16 @@ public:
// -- properties -------------------------------------------------------------
actor_system& system() {
actor_system& system() noexcept {
return sys_;
}
const actor_system_config& config() const noexcept;
// -- queue access -----------------------------------------------------------
bool at_end_of_message_queue();
endpoint_manager_queue::message_ptr next_message();
// -- event management -------------------------------------------------------
......@@ -75,7 +81,7 @@ public:
// -- pure virtual member functions ------------------------------------------
/// Initializes the manager before adding it to the multiplexer's event loop.
virtual error init() = 0;
// virtual error init() = 0;
protected:
bool enqueue(endpoint_manager_queue::element* ptr);
......
......@@ -40,8 +40,8 @@ public:
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager_impl(const multiplexer_ptr& parent, actor_system& sys,
Transport trans)
: super(trans.handle(), parent, sys), transport_(std::move(trans)) {
socket handle, Transport trans)
: super(handle, parent, sys), transport_(std::move(trans)) {
// nop
}
......@@ -73,7 +73,7 @@ public:
// -- interface functions ----------------------------------------------------
error init() override {
error init() /*override*/ {
this->register_reading();
return transport_.init(*this);
}
......@@ -124,6 +124,8 @@ private:
/// Stores the id for the next timeout.
uint64_t next_timeout_id_;
error err_;
};
} // namespace caf::net
......@@ -42,6 +42,8 @@ enum class ec : uint8_t;
// -- classes ------------------------------------------------------------------
class actor_shell;
class actor_shell_ptr;
class endpoint_manager;
class middleman;
class middleman_backend;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <cstring>
#include <memory>
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/error.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/tag/message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
namespace caf::net {
/// Length-prefixed message framing for discretizing a Byte stream into messages
/// of varying size. The framing uses 4 Bytes for the length prefix, but
/// messages (including the 4 Bytes for the length prefix) are limited to a
/// maximum size of INT32_MAX. This limitation comes from the POSIX API (recv)
/// on 32-bit platforms.
template <class UpperLayer>
class length_prefix_framing {
public:
using input_tag = tag::stream_oriented;
using output_tag = tag::message_oriented;
using length_prefix_type = uint32_t;
static constexpr size_t max_message_length = INT32_MAX;
// -- interface for the upper layer ------------------------------------------
template <class LowerLayer>
class access {
public:
access(LowerLayer* lower_layer, length_prefix_framing* this_layer)
: lower_layer_(lower_layer), this_layer_(this_layer) {
// nop
}
void begin_message() {
lower_layer_->begin_output();
auto& buf = message_buffer();
message_offset_ = buf.size();
buf.insert(buf.end(), 4, byte{0});
}
byte_buffer& message_buffer() {
return lower_layer_->output_buffer();
}
bool end_message() {
using detail::to_network_order;
auto& buf = message_buffer();
auto msg_begin = buf.begin() + message_offset_;
auto msg_size = std::distance(msg_begin + 4, buf.end());
if (msg_size > 0 && msg_size < max_message_length) {
auto u32_size = to_network_order(static_cast<uint32_t>(msg_size));
memcpy(std::addressof(*msg_begin), &u32_size, 4);
return true;
} else {
abort_reason(make_error(
sec::runtime_error, msg_size == 0 ? "logic error: message of size 0"
: "maximum message size exceeded"));
return false;
}
}
bool can_send_more() const noexcept {
return lower_layer_->can_send_more();
}
void abort_reason(error reason) {
return lower_layer_->abort_reason(std::move(reason));
}
void configure_read(receive_policy policy) {
lower_layer_->configure_read(policy);
}
private:
LowerLayer* lower_layer_;
length_prefix_framing* this_layer_;
size_t message_offset_ = 0;
};
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayer>
bool prepare_send(LowerLayer& down) {
access<LowerLayer> this_layer{&down, this};
return upper_layer_.prepare_send(this_layer);
}
template <class LowerLayer>
bool done_sending(LowerLayer& down) {
access<LowerLayer> this_layer{&down, this};
return upper_layer_.done_sending(this_layer);
}
template <class LowerLayer>
void abort(LowerLayer& down, const error& reason) {
access<LowerLayer> this_layer{&down, this};
return upper_layer_.abort(this_layer, reason);
}
template <class LowerLayer>
ptrdiff_t consume(LowerLayer& down, byte_span buffer, byte_span) {
using detail::from_network_order;
if (buffer.size() < 4)
return 0;
uint32_t u32_size = 0;
memcpy(&u32_size, buffer.data(), 4);
auto msg_size = static_cast<size_t>(from_network_order(u32_size));
if (buffer.size() < msg_size + 4)
return 0;
upper_layer_.consume(down, make_span(buffer.data() + 4, msg_size));
return msg_size + 4;
}
private:
UpperLayer upper_layer_;
};
} // namespace caf::net
......@@ -26,8 +26,8 @@
namespace caf::net {
template <class Transport>
endpoint_manager_ptr CAF_NET_EXPORT make_endpoint_manager(
const multiplexer_ptr& mpx, actor_system& sys, Transport trans) {
endpoint_manager_ptr make_endpoint_manager(const multiplexer_ptr& mpx,
actor_system& sys, Transport trans) {
using impl = endpoint_manager_impl<Transport>;
return make_counted<impl>(mpx, sys, std::move(trans));
}
......
......@@ -27,8 +27,11 @@
#include "caf/detail/net_export.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
#include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/scoped_actor.hpp"
namespace caf::net {
......@@ -49,8 +52,32 @@ public:
// -- constructors, destructors, and assignment operators --------------------
explicit middleman(actor_system& sys);
~middleman() override;
// -- socket manager functions -----------------------------------------------
///
/// @param sock An accept socket in listening mode. For a TCP socket, this
/// socket must already listen to an address plus port.
/// @param factory
template <class Socket, class Factory>
auto make_acceptor(Socket sock, Factory factory) {
using connected_socket_type = typename Socket::connected_socket_type;
if constexpr (detail::is_callable_with<Factory, connected_socket_type,
multiplexer*>::value) {
connection_acceptor_factory_adapter<Factory> adapter{std::move(factory)};
return make_acceptor(std::move(sock), std::move(adapter));
} else {
using impl = connection_acceptor<Socket, Factory>;
auto ptr = make_socket_manager<impl>(std::move(sock), &mpx_,
std::move(factory));
mpx_.init(ptr);
return ptr;
}
}
// -- interface functions ----------------------------------------------------
void start() override;
......@@ -128,7 +155,11 @@ public:
return sys_.config();
}
const multiplexer_ptr& mpx() const noexcept {
multiplexer& mpx() noexcept {
return mpx_;
}
const multiplexer& mpx() const noexcept {
return mpx_;
}
......@@ -137,10 +168,6 @@ public:
expected<uint16_t> port(string_view scheme) const;
private:
// -- constructors, destructors, and assignment operators --------------------
explicit middleman(actor_system& sys);
// -- utility functions ------------------------------------------------------
static void create_backends(middleman&, detail::type_list<>) {
......@@ -159,7 +186,7 @@ private:
actor_system& sys_;
/// Stores the global socket I/O multiplexer.
multiplexer_ptr mpx_;
multiplexer mpx_;
/// Stores all available backends for managing peers.
middleman_backend_list backends_;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
namespace caf::net {
/// Wraps a pointer to a mixed-message-oriented layer with a pointer to its
/// lower layer. Both pointers are then used to implement the interface required
/// for a mixed-message-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr>
class mixed_message_oriented_layer_ptr {
public:
class access {
public:
access(Layer* layer, LowerLayerPtr down) : lptr_(layer), llptr_(down) {
// nop
}
bool can_send_more() const noexcept {
return lptr_->can_send_more(llptr_);
}
auto handle() const noexcept {
return lptr_->handle(llptr_);
}
void begin_binary_message() {
lptr_->begin_binary_message(llptr_);
}
auto& binary_message_buffer() {
return lptr_->binary_message_buffer(llptr_);
}
void end_binary_message() {
lptr_->end_binary_message(llptr_);
}
void begin_text_message() {
lptr_->begin_text_message(llptr_);
}
auto& text_message_buffer() {
return lptr_->text_message_buffer(llptr_);
}
void end_text_message() {
lptr_->end_text_message(llptr_);
}
void abort_reason(error reason) {
return lptr_->abort_reason(llptr_, std::move(reason));
}
const error& abort_reason() {
return lptr_->abort_reason(llptr_);
}
private:
Layer* lptr_;
LowerLayerPtr llptr_;
};
mixed_message_oriented_layer_ptr(Layer* layer, LowerLayerPtr down)
: access_(layer, down) {
// nop
}
mixed_message_oriented_layer_ptr(const mixed_message_oriented_layer_ptr&)
= default;
explicit operator bool() const noexcept {
return true;
}
access* operator->() const noexcept {
return &access_;
}
access& operator*() const noexcept {
return access_;
}
private:
mutable access access_;
};
template <class Layer, class LowerLayerPtr>
auto make_mixed_message_oriented_layer_ptr(Layer* this_layer,
LowerLayerPtr down) {
using result_t = mixed_message_oriented_layer_ptr<Layer, LowerLayerPtr>;
return result_t{this_layer, down};
}
} // namespace caf::net
......@@ -38,8 +38,7 @@ struct pollfd;
namespace caf::net {
/// Multiplexes any number of ::socket_manager objects with a ::socket.
class CAF_NET_EXPORT multiplexer
: public std::enable_shared_from_this<multiplexer> {
class CAF_NET_EXPORT multiplexer {
public:
// -- member types -----------------------------------------------------------
......@@ -49,10 +48,16 @@ public:
// -- constructors, destructors, and assignment operators --------------------
multiplexer();
/// @param parent Points to the owning middleman instance. May be `nullptr`
/// only for the purpose of unit testing if no @ref
/// socket_manager requires access to the @ref middleman or the
/// @ref actor_system.
explicit multiplexer(middleman* parent);
~multiplexer();
// -- initialization ---------------------------------------------------------
error init();
// -- properties -------------------------------------------------------------
......@@ -63,6 +68,12 @@ public:
/// Returns the index of `mgr` in the pollset or `-1`.
ptrdiff_t index_of(const socket_manager_ptr& mgr);
/// Returns the owning @ref middleman instance.
middleman& owner();
/// Returns the enclosing @ref actor_system.
actor_system& system();
// -- thread-safe signaling --------------------------------------------------
/// Registers `mgr` for read events.
......@@ -73,6 +84,10 @@ public:
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
/// Registers `mgr` for initialization in the multiplexer's thread.
/// @thread-safe
void init(const socket_manager_ptr& mgr);
/// Closes the pipe for signaling updates to the multiplexer. After closing
/// the pipe, calls to `update` no longer have any effect.
/// @thread-safe
......@@ -123,20 +138,17 @@ protected:
/// calling `init()`.
std::thread::id tid_;
/// Used for pushing updates to the multiplexer's thread.
pipe_socket write_handle_;
/// Guards `write_handle_`.
std::mutex write_lock_;
/// Signals shutdown has been requested.
bool shutting_down_;
};
/// Used for pushing updates to the multiplexer's thread.
pipe_socket write_handle_;
/// @relates multiplexer
using multiplexer_ptr = std::shared_ptr<multiplexer>;
/// Points to the owning middleman.
middleman* owner_;
/// @relates multiplexer
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
/// Signals whether shutdown has been requested.
bool shutting_down_ = false;
};
} // namespace caf::net
......@@ -36,9 +36,19 @@ public:
using msg_buf = std::array<byte, sizeof(intptr_t) + 1>;
// -- constants --------------------------------------------------------------
static constexpr uint8_t register_reading_code = 0x00;
static constexpr uint8_t register_writing_code = 0x01;
static constexpr uint8_t init_manager_code = 0x02;
static constexpr uint8_t shutdown_code = 0x04;
// -- constructors, destructors, and assignment operators --------------------
pollset_updater(pipe_socket read_handle, const multiplexer_ptr& parent);
pollset_updater(pipe_socket read_handle, multiplexer* parent);
~pollset_updater() override;
......@@ -51,6 +61,8 @@ public:
// -- interface functions ----------------------------------------------------
error init(const settings& config) override;
bool handle_read_event() override;
bool handle_write_event() override;
......@@ -59,7 +71,7 @@ public:
private:
msg_buf buf_;
size_t buf_size_;
size_t buf_size_ = 0;
};
} // namespace caf::net
......@@ -18,45 +18,32 @@
#pragma once
#include <cstddef>
#include <string>
#include <utility>
#include "caf/config.hpp"
#include <cstdint>
namespace caf::net {
enum class CAF_NET_EXPORT receive_policy_flag : unsigned {
at_least,
at_most,
exactly
};
inline std::string to_string(receive_policy_flag x) {
return x == receive_policy_flag::at_least
? "at_least"
: (x == receive_policy_flag::at_most ? "at_most" : "exactly");
}
struct receive_policy {
uint32_t min_size;
uint32_t max_size;
class CAF_NET_EXPORT receive_policy {
public:
receive_policy() = delete;
using config = std::pair<receive_policy_flag, size_t>;
/// @pre `min_size > 0`
/// @pre `min_size <= max_size`
static constexpr receive_policy between(uint32_t min_size,
uint32_t max_size) {
return {min_size, max_size};
}
static config at_least(size_t num_bytes) {
CAF_ASSERT(num_bytes > 0);
return {receive_policy_flag::at_least, num_bytes};
/// @pre `size > 0`
static constexpr receive_policy exactly(uint32_t size) {
return {size, size};
}
static config at_most(size_t num_bytes) {
CAF_ASSERT(num_bytes > 0);
return {receive_policy_flag::at_most, num_bytes};
static constexpr receive_policy up_to(uint32_t max_size) {
return {1, max_size};
}
static config exactly(size_t num_bytes) {
CAF_ASSERT(num_bytes > 0);
return {receive_policy_flag::exactly, num_bytes};
static constexpr receive_policy stop() {
return {0, 0};
}
};
......
......@@ -55,9 +55,8 @@ struct CAF_NET_EXPORT socket : detail::comparable<socket> {
/// @relates socket
template <class Inspector>
typename Inspector::result_type CAF_NET_EXPORT inspect(Inspector& f,
socket& x) {
return f(x.id);
bool inspect(Inspector& f, socket& x) {
return f.object(x).fields(f.field("id", x.id));
}
/// Denotes the invalid socket.
......@@ -65,7 +64,7 @@ constexpr auto invalid_socket = socket{invalid_socket_id};
/// Converts between different socket types.
template <class To, class From>
To CAF_NET_EXPORT socket_cast(From x) {
To socket_cast(From x) {
return To{x.id};
}
......
......@@ -18,24 +18,32 @@
#pragma once
#include "caf/callback.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/socket.hpp"
#include "caf/ref_counted.hpp"
#include "caf/tag/io_event_oriented.hpp"
namespace caf::net {
/// Manages the lifetime of a single socket and handles any I/O events on it.
class CAF_NET_EXPORT socket_manager : public ref_counted {
public:
// -- member types -----------------------------------------------------------
using fallback_handler = unique_callback_ptr<result<message>(message&)>;
// -- constructors, destructors, and assignment operators --------------------
/// @pre `parent != nullptr`
/// @pre `handle != invalid_socket`
socket_manager(socket handle, const multiplexer_ptr& parent);
/// @pre `parent != nullptr`
socket_manager(socket handle, multiplexer* parent);
~socket_manager() override;
......@@ -46,13 +54,28 @@ public:
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
socket handle() const noexcept {
socket handle() const {
return handle_;
}
/// Returns a pointer to the multiplexer running this `socket_manager`.
multiplexer_ptr multiplexer() const {
return parent_.lock();
/// Returns the owning @ref multiplexer instance.
multiplexer& mpx() noexcept {
return *parent_;
}
/// Returns the owning @ref multiplexer instance.
const multiplexer& mpx() const noexcept {
return *parent_;
}
/// Returns a pointer to the owning @ref multiplexer instance.
multiplexer* mpx_ptr() noexcept {
return parent_;
}
/// Returns a pointer to the owning @ref multiplexer instance.
const multiplexer* mpx_ptr() const noexcept {
return parent_;
}
/// Returns registered operations (read, write, or both).
......@@ -70,6 +93,38 @@ public:
/// @pre `flag != operation::none`
bool mask_del(operation flag) noexcept;
const error& abort_reason() const noexcept {
return abort_reason_;
}
void abort_reason(error reason) noexcept {
abort_reason_ = std::move(reason);
}
template <class... Ts>
const error& abort_reason_or(Ts&&... xs) {
if (!abort_reason_)
abort_reason_ = make_error(std::forward<Ts>(xs)...);
return abort_reason_;
}
// -- factories --------------------------------------------------------------
template <class LowerLayerPtr, class FallbackHandler>
actor_shell_ptr make_actor_shell(LowerLayerPtr, FallbackHandler f) {
auto ptr = make_actor_shell_impl();
ptr->set_fallback(std::move(f));
return ptr;
}
template <class LowerLayerPtr>
actor_shell_ptr make_actor_shell(LowerLayerPtr down) {
return make_actor_shell(down, [down](message& msg) -> result<message> {
down->abort_reason(make_error(sec::unexpected_message, std::move(msg)));
return make_error(sec::unexpected_message);
});
}
// -- event loop management --------------------------------------------------
void register_reading();
......@@ -78,6 +133,8 @@ public:
// -- pure virtual member functions ------------------------------------------
virtual error init(const settings& config) = 0;
/// Called whenever the socket received new data.
virtual bool handle_read_event() = 0;
......@@ -95,9 +152,140 @@ protected:
operation mask_;
weak_multiplexer_ptr parent_;
multiplexer* parent_;
error abort_reason_;
private:
actor_shell_ptr make_actor_shell_impl();
};
template <class Protocol>
class socket_manager_impl : public socket_manager {
public:
// -- member types -----------------------------------------------------------
using output_tag = tag::io_event_oriented;
using socket_type = typename Protocol::socket_type;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
socket_manager_impl(socket_type handle, multiplexer* mpx, Ts&&... xs)
: socket_manager(handle, mpx), protocol_(std::forward<Ts>(xs)...) {
// nop
}
// -- initialization ---------------------------------------------------------
error init(const settings& config) override {
CAF_LOG_TRACE("");
if (auto err = nonblocking(handle(), true)) {
CAF_LOG_ERROR("failed to set nonblocking flag in socket:" << err);
return err;
}
return protocol_.init(static_cast<socket_manager*>(this), this, config);
}
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
socket_type handle() const {
return socket_cast<socket_type>(this->handle_);
}
// -- event callbacks --------------------------------------------------------
bool handle_read_event() override {
return protocol_.handle_read_event(this);
}
bool handle_write_event() override {
return protocol_.handle_write_event(this);
}
void handle_error(sec code) override {
abort_reason_ = code;
return protocol_.abort(this, abort_reason_);
}
auto& protocol() noexcept {
return protocol_;
}
const auto& protocol() const noexcept {
return protocol_;
}
auto& top_layer() noexcept {
return climb(protocol_);
}
const auto& top_layer() const noexcept {
return climb(protocol_);
}
private:
template <class FinalLayer>
static FinalLayer& climb(FinalLayer& layer) {
return layer;
}
template <class FinalLayer>
static const FinalLayer& climb(const FinalLayer& layer) {
return layer;
}
template <template <class> class Layer, class Next>
static auto& climb(Layer<Next>& layer) {
return climb(layer.upper_layer());
}
template <template <class> class Layer, class Next>
static const auto& climb(const Layer<Next>& layer) {
return climb(layer.upper_layer());
}
Protocol protocol_;
};
/// @relates socket_manager
using socket_manager_ptr = intrusive_ptr<socket_manager>;
template <class B, template <class> class... Layers>
struct make_socket_manager_helper;
template <class B>
struct make_socket_manager_helper<B> {
using type = B;
};
template <class B, template <class> class Layer,
template <class> class... Layers>
struct make_socket_manager_helper<B, Layer, Layers...>
: make_socket_manager_helper<Layer<B>, Layers...> {
using upper_input = typename B::input_tag;
using lower_output = typename Layer<B>::output_tag;
static_assert(std::is_same<upper_input, lower_output>::value);
};
template <class B, template <class> class... Layers>
using make_socket_manager_helper_t =
typename make_socket_manager_helper<B, Layers...>::type;
template <class B, template <class> class... Layers>
using socket_type_t =
typename make_socket_manager_helper_t<B, Layers...,
socket_manager_impl>::socket_type;
template <class App, template <class> class... Layers, class... Ts>
auto make_socket_manager(socket_type_t<App, Layers...> handle, multiplexer* mpx,
Ts&&... xs) {
using impl
= make_socket_manager_helper_t<App, Layers..., socket_manager_impl>;
static_assert(std::is_base_of<socket, typename impl::socket_type>::value);
return make_counted<impl>(std::move(handle), mpx, std::forward<Ts>(xs)...);
}
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
namespace caf::net {
/// Wraps a pointer to a stream-oriented layer with a pointer to its lower
/// layer. Both pointers are then used to implement the interface required for a
/// stream-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr>
class stream_oriented_layer_ptr {
public:
class access {
public:
access(Layer* layer, LowerLayerPtr down) : lptr_(layer), llptr_(down) {
// nop
}
bool can_send_more() const noexcept {
return lptr_->can_send_more(llptr_);
}
auto handle() const noexcept {
return lptr_->handle(llptr_);
}
void begin_output() {
lptr_->begin_output(llptr_);
}
byte_buffer& output_buffer() {
return lptr_->output_buffer(llptr_);
}
void end_output() {
lptr_->end_output(llptr_);
}
void abort_reason(error reason) {
return lptr_->abort_reason(llptr_, std::move(reason));
}
const error& abort_reason() {
return lptr_->abort_reason(llptr_);
}
void configure_read(receive_policy policy) {
lptr_->configure_read(llptr_, policy);
}
private:
Layer* lptr_;
LowerLayerPtr llptr_;
};
stream_oriented_layer_ptr(Layer* layer, LowerLayerPtr down)
: access_(layer, down) {
// nop
}
stream_oriented_layer_ptr(const stream_oriented_layer_ptr&) = default;
explicit operator bool() const noexcept {
return true;
}
access* operator->() const noexcept {
return &access_;
}
access& operator*() const noexcept {
return access_;
}
private:
mutable access access_;
};
template <class Layer, class LowerLayerPtr>
auto make_stream_oriented_layer_ptr(Layer* this_layer, LowerLayerPtr down) {
return stream_oriented_layer_ptr<Layer, LowerLayerPtr>{this_layer, down};
}
} // namespace caf::net
......@@ -21,82 +21,222 @@
#include <deque>
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/transport_base.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/tag/io_event_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
namespace caf::net {
template <class Application>
using stream_transport_base
= transport_base<stream_transport<Application>, transport_worker<Application>,
stream_socket, Application, unit_t>;
/// Implements a stream_transport that manages a stream socket.
template <class Application>
class stream_transport : public stream_transport_base<Application> {
template <class UpperLayer>
class stream_transport {
public:
// -- member types -----------------------------------------------------------
using application_type = Application;
using input_tag = tag::io_event_oriented;
using worker_type = transport_worker<application_type>;
using output_tag = tag::stream_oriented;
using super = stream_transport_base<application_type>;
using socket_type = stream_socket;
using id_type = typename super::id_type;
// -- constructors, destructors, and assignment operators --------------------
using write_queue_type = std::deque<std::pair<bool, byte_buffer>>;
template <class... Ts>
explicit stream_transport(Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...) {
// nop
}
// -- constructors, destructors, and assignment operators --------------------
virtual ~stream_transport() {
// nop
}
// -- interface for stream_oriented_layer_ptr --------------------------------
template <class ParentPtr>
bool can_send_more(ParentPtr) const noexcept {
return write_buf_.size() < max_write_buf_size_;
}
template <class ParentPtr>
static socket_type handle(ParentPtr parent) noexcept {
return parent->handle();
}
template <class ParentPtr>
void begin_output(ParentPtr parent) {
if (write_buf_.empty())
parent->register_writing();
}
template <class ParentPtr>
byte_buffer& output_buffer(ParentPtr) {
return write_buf_;
}
template <class ParentPtr>
static constexpr void end_output(ParentPtr) {
// nop
}
template <class ParentPtr>
static void abort_reason(ParentPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
template <class ParentPtr>
static const error& abort_reason(ParentPtr parent) {
return parent->abort_reason();
}
template <class ParentPtr>
void configure_read(ParentPtr parent, receive_policy policy) {
if (policy.max_size > 0 && max_read_size_ == 0)
parent->register_reading();
min_read_size_ = policy.min_size;
max_read_size_ = policy.max_size;
}
// -- properties -------------------------------------------------------------
auto& read_buffer() noexcept {
return read_buf_;
}
const auto& read_buffer() const noexcept {
return read_buf_;
}
auto& write_buffer() noexcept {
return write_buf_;
}
const auto& write_buffer() const noexcept {
return write_buf_;
}
auto& upper_layer() noexcept {
return upper_layer_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
}
stream_transport(stream_socket handle, application_type application)
: super(handle, std::move(application)),
written_(0),
read_threshold_(1024),
collected_(0),
max_(1024),
rd_flag_(net::receive_policy_flag::exactly) {
CAF_ASSERT(handle != invalid_socket);
if (auto err = nodelay(handle, true))
CAF_LOG_ERROR("nodelay failed: " << err);
}
// -- member functions -------------------------------------------------------
bool handle_read_event(endpoint_manager&) override {
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle().id));
auto fail = [this](sec err) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
// -- initialization ---------------------------------------------------------
template <class ParentPtr>
error init(socket_manager* owner, ParentPtr parent, const settings& config) {
namespace mm = defaults::middleman;
auto default_max_reads = static_cast<uint32_t>(mm::max_consecutive_reads);
max_consecutive_reads_ = get_or(
config, "caf.middleman.max-consecutive-reads", default_max_reads);
auto sock = parent->handle();
if constexpr (std::is_base_of<tcp_stream_socket, decltype(sock)>::value) {
if (auto err = nodelay(sock, true)) {
CAF_LOG_ERROR("nodelay failed: " << err);
return err;
}
}
if (auto socket_buf_size = send_buffer_size(parent->handle())) {
max_write_buf_size_ = *socket_buf_size;
CAF_ASSERT(max_write_buf_size_ > 0);
write_buf_.reserve(max_write_buf_size_ * 2);
} else {
CAF_LOG_ERROR("send_buffer_size: " << socket_buf_size.error());
return std::move(socket_buf_size.error());
}
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
return upper_layer_.init(owner, this_layer_ptr, config);
}
// -- event callbacks --------------------------------------------------------
template <class ParentPtr>
bool handle_read_event(ParentPtr parent) {
CAF_LOG_TRACE(CAF_ARG2("handle", parent->handle().id));
auto fail = [this, parent](auto reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
parent->abort_reason(std::move(reason));
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
upper_layer_.abort(this_layer_ptr, parent->abort_reason());
return false;
};
for (size_t reads = 0; reads < this->max_consecutive_reads_; ++reads) {
auto buf = this->read_buf_.data() + collected_;
size_t len = read_threshold_ - collected_;
CAF_LOG_DEBUG(CAF_ARG2("missing", len));
auto num_bytes = read(this->handle_, make_span(buf, len));
CAF_LOG_DEBUG(CAF_ARG(len) << CAF_ARG2("handle", this->handle().id)
<< CAF_ARG(num_bytes));
// Update state.
if (num_bytes > 0) {
collected_ += num_bytes;
if (collected_ >= read_threshold_) {
if (auto err = this->next_layer_.handle_data(
*this, make_span(this->read_buf_))) {
CAF_LOG_ERROR("handle_data failed: " << CAF_ARG(err));
if (read_buf_.size() < max_read_size_)
read_buf_.resize(max_read_size_);
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
for (size_t i = 0; max_read_size_ > 0 && i < max_consecutive_reads_; ++i) {
// Calling configure_read(read_policy::stop()) halts receive events.
if (max_read_size_ == 0) {
return false;
} else if (offset_ >= max_read_size_) {
auto old_max = max_read_size_;
// This may happen if the upper layer changes it receive policy to a
// smaller max. size than what was already available. In this case, the
// upper layer must consume bytes before we can receive new data.
auto bytes = make_span(read_buf_.data(), max_read_size_);
ptrdiff_t consumed = upper_layer_.consume(this_layer_ptr, bytes, {});
CAF_LOG_DEBUG(CAF_ARG2("socket", parent->handle().id)
<< CAF_ARG(consumed));
if (consumed < 0) {
upper_layer_.abort(this_layer_ptr,
parent->abort_reason_or(caf::sec::runtime_error));
return false;
} else if (consumed == 0) {
// At the very least, the upper layer must accept more data next time.
if (old_max >= max_read_size_) {
upper_layer_.abort(this_layer_ptr, parent->abort_reason_or(
caf::sec::runtime_error,
"unable to make progress"));
return false;
}
this->prepare_next_read();
}
} else if (num_bytes < 0) {
// Try again.
continue;
}
auto rd_buf = make_span(read_buf_.data() + offset_,
max_read_size_ - static_cast<size_t>(offset_));
auto read_res = read(parent->handle(), rd_buf);
CAF_LOG_DEBUG(CAF_ARG2("socket", parent->handle().id) //
<< CAF_ARG(max_read_size_) << CAF_ARG(offset_)
<< CAF_ARG(read_res));
// Update state.
if (read_res > 0) {
offset_ += read_res;
if (offset_ < min_read_size_)
continue;
auto bytes = make_span(read_buf_.data(), offset_);
auto delta = bytes.subspan(delta_offset_);
ptrdiff_t consumed = upper_layer_.consume(this_layer_ptr, bytes, delta);
CAF_LOG_DEBUG(CAF_ARG2("socket", parent->handle().id)
<< CAF_ARG(consumed));
if (consumed > 0) {
// Shift unconsumed bytes to the beginning of the buffer.
if (consumed < offset_)
std::copy(read_buf_.begin() + consumed, read_buf_.begin() + offset_,
read_buf_.begin());
offset_ -= consumed;
delta_offset_ = offset_;
} else if (consumed < 0) {
upper_layer_.abort(this_layer_ptr,
parent->abort_reason_or(caf::sec::runtime_error));
return false;
}
// Our thresholds may have changed if the upper layer called
// configure_read. Shrink/grow buffer as necessary.
if (read_buf_.size() != max_read_size_)
if (offset_ < max_read_size_)
read_buf_.resize(max_read_size_);
} else if (read_res < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and
// stop reading on the socket on hard errors.
return last_socket_error_is_temporary()
......@@ -108,127 +248,79 @@ public:
return fail(sec::socket_disconnected);
}
}
return true;
}
bool handle_write_event(endpoint_manager& manager) override {
CAF_LOG_TRACE(CAF_ARG2("handle", this->handle_.id)
<< CAF_ARG2("queue-size", write_queue_.size()));
auto drain_write_queue = [this]() -> error_code<sec> {
// Helper function to sort empty buffers back into the right caches.
auto recycle = [this]() {
auto& front = this->write_queue_.front();
auto& is_header = front.first;
auto& buf = front.second;
written_ = 0;
buf.clear();
if (is_header) {
if (this->header_bufs_.size() < this->header_bufs_.capacity())
this->header_bufs_.emplace_back(std::move(buf));
} else if (this->payload_bufs_.size()
< this->payload_bufs_.capacity()) {
this->payload_bufs_.emplace_back(std::move(buf));
}
write_queue_.pop_front();
};
auto fail = [this](sec err) {
CAF_LOG_DEBUG("write failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return err;
};
// Write buffers from the write_queue_ for as long as possible.
while (!write_queue_.empty()) {
auto& buf = write_queue_.front().second;
CAF_ASSERT(!buf.empty());
auto data = buf.data() + written_;
auto len = buf.size() - written_;
auto num_bytes = write(this->handle(), make_span(data, len));
if (num_bytes > 0) {
CAF_LOG_DEBUG(CAF_ARG(this->handle_.id) << CAF_ARG(num_bytes));
written_ += num_bytes;
if (written_ >= static_cast<ptrdiff_t>(buf.size())) {
recycle();
written_ = 0;
}
} else if (num_bytes < 0) {
return last_socket_error_is_temporary()
? sec::unavailable_or_would_block
: fail(sec::socket_operation_failed);
} else {
// write() returns 0 iff the connection was closed.
return fail(sec::socket_disconnected);
}
}
return none;
};
auto fetch_next_message = [&] {
if (auto msg = manager.next_message()) {
this->next_layer_.write_message(*this, std::move(msg));
return true;
}
// Calling configure_read(read_policy::stop()) halts receive events.
return max_read_size_ > 0;
}
template <class ParentPtr>
bool handle_write_event(ParentPtr parent) {
CAF_LOG_TRACE(CAF_ARG2("handle", parent->handle().id));
auto fail = [this, parent](sec reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
parent->abort_reason(reason);
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
upper_layer_.abort(this_layer_ptr, reason);
return false;
};
do {
if (auto err = drain_write_queue())
return err == sec::unavailable_or_would_block;
} while (fetch_next_message());
CAF_ASSERT(write_queue_.empty());
return false;
}
// Allow the upper layer to add extra data to the write buffer.
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
if (!upper_layer_.prepare_send(this_layer_ptr)) {
upper_layer_.abort(this_layer_ptr,
parent->abort_reason_or(caf::sec::runtime_error));
return false;
}
if (write_buf_.empty())
return !upper_layer_.done_sending(this_layer_ptr);
auto written = write(parent->handle(), write_buf_);
if (written > 0) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + written);
return !write_buf_.empty() || !upper_layer_.done_sending(this_layer_ptr);
} else if (written < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors.
return last_socket_error_is_temporary()
? true
: fail(sec::socket_operation_failed);
void write_packet(id_type, span<byte_buffer*> buffers) override {
CAF_LOG_TRACE("");
CAF_ASSERT(!buffers.empty());
if (this->write_queue_.empty())
this->manager().register_writing();
// By convention, the first buffer is a header buffer. Every other buffer is
// a payload buffer.
auto i = buffers.begin();
this->write_queue_.emplace_back(true, std::move(*(*i++)));
while (i != buffers.end())
this->write_queue_.emplace_back(false, std::move(*(*i++)));
} else {
// write() returns 0 iff the connection was closed.
return fail(sec::socket_disconnected);
}
}
void configure_read(receive_policy::config cfg) override {
rd_flag_ = cfg.first;
max_ = cfg.second;
prepare_next_read();
template <class ParentPtr>
void abort(ParentPtr parent, const error& reason) {
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
upper_layer_.abort(this_layer_ptr, reason);
}
private:
// -- utility functions ------------------------------------------------------
void prepare_next_read() {
collected_ = 0;
switch (rd_flag_) {
case net::receive_policy_flag::exactly:
if (this->read_buf_.size() != max_)
this->read_buf_.resize(max_);
read_threshold_ = max_;
break;
case net::receive_policy_flag::at_most:
if (this->read_buf_.size() != max_)
this->read_buf_.resize(max_);
read_threshold_ = 1;
break;
case net::receive_policy_flag::at_least: {
// read up to 10% more, but at least allow 100 bytes more
auto max_size = max_ + std::max<size_t>(100, max_ / 10);
if (this->read_buf_.size() != max_size)
this->read_buf_.resize(max_size);
read_threshold_ = max_;
break;
}
}
}
// Caches the config parameter for limiting max. socket operations.
uint32_t max_consecutive_reads_ = 0;
// Caches the write buffer size of the socket.
uint32_t max_write_buf_size_ = 0;
// Stores what the user has configured as read threshold.
uint32_t min_read_size_ = 0;
// Stores what the user has configured as max. number of bytes to receive.
uint32_t max_read_size_ = 0;
// Stores the current offset in `read_buf_`.
ptrdiff_t offset_ = 0;
// Stores the offset in `read_buf_` since last calling `upper_layer_.consume`.
ptrdiff_t delta_offset_ = 0;
// Caches incoming data.
byte_buffer read_buf_;
// Caches outgoing data.
byte_buffer write_buf_;
write_queue_type write_queue_;
ptrdiff_t written_;
ptrdiff_t read_threshold_;
ptrdiff_t collected_;
size_t max_;
receive_policy_flag rd_flag_;
// Processes incoming data and generates outgoing data.
UpperLayer upper_layer_;
};
} // namespace caf::net
......@@ -20,6 +20,7 @@
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/uri.hpp"
......@@ -31,6 +32,8 @@ struct CAF_NET_EXPORT tcp_accept_socket : network_socket {
using super = network_socket;
using super::super;
using connected_socket_type = tcp_stream_socket;
};
/// Creates a new TCP socket to accept connections on a given port.
......
......@@ -173,11 +173,6 @@ public:
// -- (pure) virtual functions -----------------------------------------------
/// Configures this transport for the next read event.
virtual void configure_read(receive_policy::config) {
// nop
}
/// Called by the endpoint manager when the transport can read data from its
/// socket.
virtual bool handle_read_event(endpoint_manager&) = 0;
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/rfc6455.hpp"
#include "caf/net/mixed_message_oriented_layer_ptr.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include <random>
#include <string_view>
#include <vector>
namespace caf::net {
/// Implements the WebProtocol framing protocol as defined in RFC-6455.
template <class UpperLayer>
class web_socket_framing {
public:
// -- member types -----------------------------------------------------------
using binary_buffer = std::vector<byte>;
using text_buffer = std::vector<char>;
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
// -- constants --------------------------------------------------------------
/// Restricts the size of received frames (including header).
static constexpr size_t max_frame_size = INT32_MAX;
/// Stored as currently active opcode to mean "no opcode received yet".
static constexpr size_t nil_code = 0xFF;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit web_socket_framing(Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...) {
// nop
}
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
std::random_device rd;
rng_.seed(rd());
return upper_layer_.init(owner, this_layer_ptr(down), cfg);
}
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
}
/// When set to true, causes the layer to mask all outgoing frames with a
/// randomly chosen masking key (cf. RFC 6455, Section 5.3). Servers may set
/// this to false, whereas clients are required to always mask according to
/// the standard.
bool mask_outgoing_frames = true;
// -- interface for mixed_message_oriented_layer_ptr -------------------------
template <class LowerLayerPtr>
static bool can_send_more(LowerLayerPtr parent) noexcept {
return parent->can_send_more();
}
template <class LowerLayerPtr>
static auto handle(LowerLayerPtr parent) noexcept {
return parent->handle();
}
template <class LowerLayerPtr>
static constexpr void begin_binary_message(LowerLayerPtr) {
// nop
}
template <class LowerLayerPtr>
binary_buffer& binary_message_buffer(LowerLayerPtr) {
return binary_buf_;
}
template <class LowerLayerPtr>
void end_binary_message() {
ship_frame(binary_buf_);
}
template <class LowerLayerPtr>
static constexpr void begin_text_message(LowerLayerPtr) {
// nop
}
template <class LowerLayerPtr>
text_buffer& text_message_buffer(LowerLayerPtr) {
return text_buf_;
}
template <class LowerLayerPtr>
void end_text_message(LowerLayerPtr down) {
ship_frame(down, text_buf_);
}
template <class LowerLayerPtr>
static void abort_reason(LowerLayerPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
template <class LowerLayerPtr>
static const error& abort_reason(LowerLayerPtr parent) {
return parent->abort_reason();
}
// -- interface for the lower layer ------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return upper_layer_.prepare_send(down);
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return upper_layer_.done_sending(down);
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
upper_layer_.abort(down, reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span) {
auto buffer = input;
ptrdiff_t consumed = 0;
// Parse all frames in the current input.
for (;;) {
// Parse header.
detail::rfc6455::header hdr;
auto hdr_bytes = detail::rfc6455::decode_header(buffer, hdr);
if (hdr_bytes < 0) {
down->abort_reason(
make_error(sec::runtime_error, "invalid WebSocket frame header"));
return -1;
}
if (hdr_bytes == 0) {
// Wait for more input.
down->configure_read(receive_policy::up_to(2048));
return consumed;
}
// Make sure the entire frame (including header) fits into max_frame_size.
if (hdr.payload_len
>= (max_frame_size - static_cast<size_t>(hdr_bytes))) {
auto err = make_error(sec::runtime_error, "WebSocket frame too large");
down->abort_reason(std::move(err));
return -1;
}
// Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len;
if (buffer.size() < frame_size) {
down->configure_read(receive_policy::exactly(frame_size));
return consumed;
}
// Decode frame.
auto payload_len = static_cast<size_t>(hdr.payload_len);
auto payload = buffer.subspan(hdr_bytes, payload_len);
if (hdr.mask_key != 0) {
detail::rfc6455::mask_data(hdr.mask_key, payload);
}
if (hdr.fin) {
if (opcode_ == nil_code) {
if (!handle(down, hdr.opcode, payload))
return -1;
} else if (hdr.opcode != detail::rfc6455::continuation_frame) {
// Reject continuation frames without prior opcode.
auto err = make_error(sec::runtime_error,
"invalid WebSocket frame "
"(expected a continuation frame)");
down->abort_reason(std::move(err));
return -1;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size.
auto err = make_error(sec::runtime_error,
"fragmented payload exceeds maximum size");
down->abort_reason(std::move(err));
return -1;
} else {
// End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(),
payload.end());
if (!handle(down, hdr.opcode, payload_buf_))
return -1;
opcode_ = nil_code;
payload_buf_.clear();
}
} else {
if (opcode_ == nil_code) {
if (hdr.opcode == detail::rfc6455::continuation_frame) {
// Reject continuation frames without prior opcode.
auto err = make_error(sec::runtime_error,
"invalid WebSocket continuation frame "
"(no prior opcode)");
down->abort_reason(std::move(err));
return -1;
}
opcode_ = hdr.opcode;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size.
auto err = make_error(sec::runtime_error,
"fragmented payload exceeds maximum size");
down->abort_reason(std::move(err));
return -1;
}
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
}
// Advance to next frame in the input.
buffer = buffer.subspan(frame_size);
if (buffer.empty()) {
down->configure_read(receive_policy::up_to(2048));
return consumed + static_cast<ptrdiff_t>(frame_size);
}
consumed += static_cast<ptrdiff_t>(frame_size);
}
}
private:
// -- implementation details -------------------------------------------------
template <class LowerLayerPtr>
bool handle(LowerLayerPtr down, uint8_t opcode, byte_span payload) {
switch (opcode) {
case detail::rfc6455::text_frame: {
string_view text{reinterpret_cast<const char*>(payload.data()),
payload.size()};
return upper_layer_.consume_text(this_layer_ptr(down), text);
}
case detail::rfc6455::binary_frame:
return upper_layer_.consume_binary(this_layer_ptr(down), payload);
case detail::rfc6455::connection_close:
down->abort_reason(sec::connection_closed);
return false;
case detail::rfc6455::ping:
ship_pong(down, payload);
return true;
case detail::rfc6455::pong:
// nop
return true;
default:
// nop
return false;
}
}
template <class LowerLayerPtr>
void ship_pong(LowerLayerPtr down, byte_span payload) {
uint32_t mask_key = 0;
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, payload);
}
down->begin_output();
detail::rfc6455::assemble_frame(detail::rfc6455::pong, mask_key, payload,
down->output_buffer());
down->end_output();
}
template <class LowerLayerPtr, class T>
void ship_frame(LowerLayerPtr down, std::vector<T>& buf) {
uint32_t mask_key = 0;
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, buf);
}
down->begin_output();
detail::rfc6455::assemble_frame(mask_key, buf, down->output_buffer());
down->end_output();
buf.clear();
}
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_mixed_message_oriented_layer_ptr(this, down);
}
// -- member variables -------------------------------------------------------
// Buffer for assembling binary frames.
binary_buffer binary_buf_;
// Buffer for assembling text frames.
text_buffer text_buf_;
// 32-bit random number generator.
std::mt19937 rng_;
uint8_t opcode_ = nil_code;
// Assembles fragmented payloads.
binary_buffer payload_buf_;
// Next layer in the processing chain.
UpperLayer upper_layer_;
};
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <algorithm>
#include "caf/byte_span.hpp"
#include "caf/detail/encode_base64.hpp"
#include "caf/error.hpp"
#include "caf/hash/sha1.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/web_socket_framing.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
namespace caf::net {
/// Implements the WebSocket Protocol as defined in RFC 6455. Initially, the
/// layer performs the WebSocket handshake. Once completed, this layer decodes
/// RFC 6455 frames and forwards binary and text messages to `UpperLayer`.
template <class UpperLayer>
class web_socket_server {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
// -- constants --------------------------------------------------------------
static constexpr std::array<byte, 4> end_of_header{{
byte{'\r'},
byte{'\n'},
byte{'\r'},
byte{'\n'},
}};
// A handshake should usually fit into 200-300 Bytes. 2KB is more than enough.
static constexpr uint32_t max_header_size = 2048;
static constexpr string_view header_too_large
= "HTTP/1.1 431 Request Header Fields Too Large\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Header exceeds 2048 Bytes.\r\n";
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit web_socket_server(Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...) {
// > A server MUST NOT mask any frames that it sends to the client.
// See RFC 6455, Section 5.1.
upper_layer_.mask_outgoing_frames = false;
}
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
owner_ = owner;
cfg_ = cfg;
down->configure_read(net::receive_policy::up_to(max_header_size));
return none;
}
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_.upper_layer();
}
const auto& upper_layer() const noexcept {
return upper_layer_.upper_layer();
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.prepare_send(down) : true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.done_sending(down) : true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
if (handshake_complete_)
upper_layer_.abort(down, reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer, byte_span delta) {
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("bytes", buffer.size())
<< CAF_ARG2("delta", delta.size()));
if (handshake_complete_)
return upper_layer_.consume(down, buffer, delta);
// TODO: we could avoid repeated scans by using the delta parameter.
auto i = std::search(buffer.begin(), buffer.end(), end_of_header.begin(),
end_of_header.end());
if (i == buffer.end()) {
if (buffer.size() == max_header_size) {
write(down, header_too_large);
auto err = make_error(pec::too_many_characters,
"exceeded maximum header size");
down->abort_reason(std::move(err));
return -1;
}
return 0;
}
auto offset = static_cast<size_t>(std::distance(buffer.begin(), i));
offset += end_of_header.size();
// Take all but the last two bytes (to avoid an empty line) as input for
// the header.
string_view header{reinterpret_cast<const char*>(buffer.begin()),
offset - 2};
if (!handle_header(down, header))
return -1;
ptrdiff_t sub_result = 0;
if (offset < buffer.size()) {
auto remainder = buffer.subspan(offset);
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("remainder", remainder.size()));
sub_result = upper_layer_.consume(down, remainder, remainder);
if (sub_result < 0)
return sub_result;
}
return static_cast<ptrdiff_t>(offset) + sub_result;
}
bool handshake_complete() const noexcept {
return handshake_complete_;
}
private:
template <class LowerLayerPtr>
static void write(LowerLayerPtr down, string_view output) {
auto out = as_bytes(make_span(output));
down->begin_output();
auto& buf = down->output_buffer();
buf.insert(buf.end(), out.begin(), out.end());
down->end_output();
}
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, string_view input) {
// Parse the first line, i.e., "METHOD REQUEST-URI VERSION".
auto [first_line, remainder] = split(input, "\r\n");
auto [method, request_uri, version] = split2(first_line, " ");
auto& hdr = cfg_["web-socket"].as_dictionary();
if (method != "GET") {
auto err = make_error(pec::invalid_argument,
"invalid operation: expected GET, got "
+ to_string(method));
down->abort_reason(std::move(err));
return false;
}
// Store the request information in the settings for the upper layer.
put(hdr, "method", method);
put(hdr, "request-uri", request_uri);
put(hdr, "http-version", version);
// Store the remaining header fields.
auto& fields = hdr["fields"].as_dictionary();
for_each_line(remainder, [&fields](string_view line) {
if (auto sep = std::find(line.begin(), line.end(), ':');
sep != line.end()) {
auto key = trim({line.begin(), sep});
auto val = trim({sep + 1, line.end()});
if (!key.empty())
put(fields, key, val);
}
});
// Check whether the mandatory fields exist.
std::string sec_key;
if (auto skey_field = get_if<std::string>(&fields, "Sec-WebSocket-Key")) {
auto magic = *skey_field + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
auto field_hash = hash::sha1::compute(magic);
sec_key = detail::encode_base64(field_hash);
CAF_LOG_DEBUG("received Sec-WebSocket-Key" << *skey_field
<< "respond with" << sec_key);
} else {
auto err = make_error(pec::missing_field,
"Mandatory field Sec-WebSocket-Key not found");
down->abort_reason(std::move(err));
return false;
}
// Send server handshake.
down->begin_output();
auto& buf = down->output_buffer();
auto append = [&buf](string_view output) {
auto out = as_bytes(make_span(output));
buf.insert(buf.end(), out.begin(), out.end());
};
append("HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: ");
append(sec_key);
append("\r\n\r\n");
down->end_output();
// Try initializing the upper layer.
if (auto err = upper_layer_.init(owner_, down, cfg_)) {
down->abort_reason(std::move(err));
return false;
}
// Done.
CAF_LOG_DEBUG("handshake completed");
handshake_complete_ = true;
return true;
}
template <class F>
void for_each_line(string_view input, F&& f) {
constexpr string_view eol{"\r\n"};
for (auto pos = input.begin();;) {
auto line_end = std::search(pos, input.end(), eol.begin(), eol.end());
if (line_end == input.end())
return;
f(string_view{pos, line_end});
pos = line_end + eol.size();
}
}
static string_view trim(string_view str) {
str.remove_prefix(std::min(str.find_first_not_of(' '), str.size()));
auto trim_pos = str.find_last_not_of(' ');
if (trim_pos != str.npos)
str.remove_suffix(str.size() - (trim_pos + 1));
return str;
}
/// Splits `str` at the first occurrence of `sep` into the head and the
/// remainder (excluding the separator).
static std::pair<string_view, string_view> split(string_view str,
string_view sep) {
auto i = std::search(str.begin(), str.end(), sep.begin(), sep.end());
if (i != str.end())
return {{str.begin(), i}, {i + sep.size(), str.end()}};
return {{str}, {}};
}
/// Convenience function for splitting twice.
static std::tuple<string_view, string_view, string_view>
split2(string_view str, string_view sep) {
auto [first, r1] = split(str, sep);
auto [second, third] = split(r1, sep);
return {first, second, third};
}
/// Stores whether the WebSocket handshake completed successfully.
bool handshake_complete_ = false;
/// Stores the upper layer.
web_socket_framing<UpperLayer> upper_layer_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
/// Holds a copy of the settings in order to delay initialization of the upper
/// layer until the handshake completed.
settings cfg_;
};
} // namespace caf::net
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
namespace caf::tag {
struct io_event_oriented {};
} // namespace caf::tag
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
namespace caf::tag {
struct message_oriented {};
} // namespace caf::tag
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
namespace caf::tag {
struct mixed_message_oriented {};
} // namespace caf::tag
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
namespace caf::tag {
struct stream_oriented {};
} // namespace caf::tag
......@@ -62,14 +62,15 @@ error application::write_message(
if (src != nullptr) {
auto src_id = src->id();
system().registry().put(src_id, src);
if (auto err = sink(src->node(), src_id, dst->id(), ptr->msg->stages))
return err;
if (!sink.apply_objects(src->node(), src_id, dst->id(), ptr->msg->stages))
return sink.get_error();
} else {
if (auto err = sink(node_id{}, actor_id{0}, dst->id(), ptr->msg->stages))
return err;
if (!sink.apply_objects(node_id{}, actor_id{0}, dst->id(),
ptr->msg->stages))
return sink.get_error();
}
if (auto err = sink(ptr->msg->content()))
return err;
if (!sink.apply_objects(ptr->msg->content()))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::actor_message,
static_cast<uint32_t>(payload_buf.size()),
......@@ -84,8 +85,8 @@ void application::resolve(packet_writer& writer, string_view path,
CAF_LOG_TRACE(CAF_ARG(path) << CAF_ARG(listener));
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (auto err = sink(path)) {
CAF_LOG_ERROR("unable to serialize path" << CAF_ARG(err));
if (!sink.apply_objects(path)) {
CAF_LOG_ERROR("unable to serialize path:" << sink.get_error());
return;
}
auto req_id = next_request_id_++;
......@@ -108,7 +109,7 @@ void application::local_actor_down(packet_writer& writer, actor_id id,
error reason) {
auto payload = writer.next_payload_buffer();
binary_serializer sink{system(), payload};
if (auto err = sink(reason))
if (!sink.apply_objects(reason))
CAF_RAISE_ERROR("unable to serialize an error");
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
......@@ -218,8 +219,8 @@ error application::handle_handshake(packet_writer&, header hdr,
node_id peer_id;
std::vector<std::string> app_ids;
binary_deserializer source{&executor_, payload};
if (auto err = source(peer_id, app_ids))
return err;
if (!source.apply_objects(peer_id, app_ids))
return source.get_error();
if (!peer_id || app_ids.empty())
return ec::invalid_handshake;
auto ids = get_or(system().config(), "caf.middleman.app-identifiers",
......@@ -277,8 +278,8 @@ error application::handle_resolve_request(packet_writer& writer, header rec_hdr,
CAF_ASSERT(rec_hdr.type == message_type::resolve_request);
size_t path_size = 0;
binary_deserializer source{&executor_, received};
if (auto err = source.begin_sequence(path_size))
return err;
if (!source.begin_sequence(path_size))
return source.get_error();
// We expect the received buffer to contain the path only.
if (path_size != source.remaining())
return ec::invalid_payload;
......@@ -298,8 +299,8 @@ error application::handle_resolve_request(packet_writer& writer, header rec_hdr,
// TODO: figure out how to obtain messaging interface.
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (auto err = sink(aid, ifs))
return err;
if (!sink.apply_objects(aid, ifs))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::resolve_response,
static_cast<uint32_t>(payload.size()),
......@@ -323,9 +324,9 @@ error application::handle_resolve_response(packet_writer&, header received_hdr,
actor_id aid;
std::set<std::string> ifs;
binary_deserializer source{&executor_, received};
if (auto err = source(aid, ifs)) {
if (!source.apply_objects(aid, ifs)) {
anon_send(i->second, sec::remote_lookup_failed);
return err;
return source.get_error();
}
if (aid == 0) {
anon_send(i->second, strong_actor_ptr{nullptr}, std::move(ifs));
......@@ -354,8 +355,8 @@ error application::handle_monitor_message(packet_writer& writer,
error reason = exit_reason::unknown;
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (auto err = sink(reason))
return err;
if (!sink.apply_objects(reason))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
static_cast<uint32_t>(payload.size()),
......@@ -372,17 +373,20 @@ error application::handle_down_message(packet_writer&, header received_hdr,
<< CAF_ARG2("received.size", received.size()));
error reason;
binary_deserializer source{&executor_, received};
if (auto err = source(reason))
return err;
if (!source.apply_objects(reason))
return source.get_error();
proxies_.erase(peer_id_, received_hdr.operation_data, std::move(reason));
return none;
}
error application::generate_handshake(byte_buffer& buf) {
binary_serializer sink{&executor_, buf};
return sink(system().node(),
get_or(system().config(), "caf.middleman.app-identifiers",
application::default_app_ids()));
if (!sink.apply_objects(system().node(),
get_or(system().config(),
"caf.middleman.app-identifiers",
application::default_app_ids())))
return sink.get_error();
return none;
}
} // namespace caf::net::basp
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/detail/rfc6455.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/span.hpp"
#include <cstring>
namespace caf::detail {
void rfc6455::mask_data(uint32_t key, span<char> data) {
mask_data(key, as_writable_bytes(data));
}
void rfc6455::mask_data(uint32_t key, span<byte> data) {
auto no_key = to_network_order(key);
byte arr[4];
memcpy(arr, &no_key, 4);
size_t i = 0;
for (auto& x : data) {
x = x ^ arr[i];
i = (i + 1) % 4;
}
}
void rfc6455::assemble_frame(uint32_t mask_key, span<const char> data,
binary_buffer& out) {
assemble_frame(text_frame, mask_key, as_bytes(data), out);
}
void rfc6455::assemble_frame(uint32_t mask_key, span<const byte> data,
binary_buffer& out) {
assemble_frame(binary_frame, mask_key, data, out);
}
void rfc6455::assemble_frame(uint8_t opcode, uint32_t mask_key,
span<const byte> data, binary_buffer& out) {
// First 8 bits: FIN flag + opcode (we never fragment frames).
out.push_back(byte{static_cast<uint8_t>(0x80 | opcode)});
// Mask flag + payload length (7 bits, 7+16 bits, or 7+64 bits)
auto mask_bit = byte{static_cast<uint8_t>(mask_key == 0 ? 0x00 : 0x80)};
if (data.size() < 126) {
auto len = static_cast<uint8_t>(data.size());
out.push_back(mask_bit | byte{len});
} else if (data.size() < std::numeric_limits<uint16_t>::max()) {
auto len = static_cast<uint16_t>(data.size());
auto no_len = to_network_order(len);
byte len_data[2];
memcpy(len_data, &no_len, 2);
out.push_back(mask_bit | byte{126});
for (auto x : len_data)
out.push_back(x);
} else {
auto len = static_cast<uint64_t>(data.size());
auto no_len = to_network_order(len);
byte len_data[8];
memcpy(len_data, &no_len, 8);
out.push_back(mask_bit | byte{127});
out.insert(out.end(), len_data, len_data + 8);
}
// Masking key: 0 or 4 bytes.
if (mask_key != 0) {
auto no_key = to_network_order(mask_key);
byte key_data[4];
memcpy(key_data, &no_key, 4);
out.insert(out.end(), key_data, key_data + 4);
}
// Application data.
out.insert(out.end(), data.begin(), data.end());
}
ptrdiff_t rfc6455::decode_header(span<const byte> data, header& hdr) {
if (data.size() < 2)
return 0;
auto byte1 = to_integer<uint8_t>(data[0]);
auto byte2 = to_integer<uint8_t>(data[1]);
// Fetch FIN flag and opcode.
hdr.fin = (byte1 & 0x80) != 0;
hdr.opcode = byte1 & 0x0F;
// Decode mask bit and payload length field.
bool masked = (byte2 & 0x80) != 0;
auto len_field = byte2 & 0x7F;
size_t header_length;
if (len_field < 126) {
header_length = 2 + (masked ? 4 : 0);
hdr.payload_len = len_field;
} else if (len_field == 126) {
header_length = 4 + (masked ? 4 : 0);
} else {
header_length = 10 + (masked ? 4 : 0);
}
// Make sure we can read all the data we need.
if (data.size() < header_length)
return 0;
// Start decoding remaining header bytes.
const byte* p = data.data() + 2;
// Fetch payload size
if (len_field == 126) {
uint16_t no_len;
memcpy(&no_len, p, 2);
hdr.payload_len = from_network_order(no_len);
p += 2;
} else if (len_field == 127) {
uint64_t no_len;
memcpy(&no_len, p, 8);
hdr.payload_len = from_network_order(no_len);
p += 8;
}
// Fetch mask key.
if (masked) {
uint32_t no_key;
memcpy(&no_key, p, 4);
hdr.mask_key = from_network_order(no_key);
p += 4;
} else {
hdr.mask_key = 0;
}
// No extension bits allowed.
if (byte1 & 0x70)
return -1;
// Verify opcode and return number of consumed bytes.
switch (hdr.opcode) {
case continuation_frame:
case text_frame:
case binary_frame:
case connection_close:
case ping:
case pong:
return static_cast<ptrdiff_t>(header_length);
default:
return -1;
}
}
} // namespace caf::detail
......@@ -25,6 +25,8 @@
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
endpoint_manager::endpoint_manager(socket handle, const multiplexer_ptr& parent,
actor_system& sys)
: super(handle, parent), sys_(sys), queue_(unit, unit, unit) {
......@@ -35,6 +37,18 @@ endpoint_manager::~endpoint_manager() {
// nop
}
// -- properties ---------------------------------------------------------------
const actor_system_config& endpoint_manager::config() const noexcept {
return sys_.config();
}
// -- queue access -------------------------------------------------------------
bool endpoint_manager::at_end_of_message_queue() {
return queue_.empty() && queue_.try_block();
}
endpoint_manager_queue::message_ptr endpoint_manager::next_message() {
if (queue_.blocked())
return nullptr;
......@@ -50,6 +64,8 @@ endpoint_manager_queue::message_ptr endpoint_manager::next_message() {
return result;
}
// -- event management ---------------------------------------------------------
void endpoint_manager::resolve(uri locator, actor listener) {
using intrusive::inbox_result;
using event_type = endpoint_manager_queue::event;
......
......@@ -26,6 +26,7 @@
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/pollset_updater.hpp"
#include "caf/net/socket_manager.hpp"
......@@ -78,7 +79,9 @@ short to_bitmask(operation op) {
} // namespace
multiplexer::multiplexer() : shutting_down_(false) {
// -- constructors, destructors, and assignment operators ----------------------
multiplexer::multiplexer(middleman* owner) : owner_(owner) {
// nop
}
......@@ -86,15 +89,23 @@ multiplexer::~multiplexer() {
// nop
}
// -- initialization -----------------------------------------------------------
error multiplexer::init() {
auto pipe_handles = make_pipe();
if (!pipe_handles)
return std::move(pipe_handles.error());
add(make_counted<pollset_updater>(pipe_handles->first, shared_from_this()));
auto updater = make_counted<pollset_updater>(pipe_handles->first, this);
settings dummy;
if (auto err = updater->init(dummy))
return err;
add(std::move(updater));
write_handle_ = pipe_handles->second;
return none;
}
// -- properties ---------------------------------------------------------------
size_t multiplexer::num_socket_managers() const noexcept {
return managers_.size();
}
......@@ -106,7 +117,19 @@ ptrdiff_t multiplexer::index_of(const socket_manager_ptr& mgr) {
return i == last ? -1 : std::distance(first, i);
}
middleman& multiplexer::owner() {
CAF_ASSERT(owner_ != nullptr);
return *owner_;
}
actor_system& multiplexer::system() {
return owner().system();
}
// -- thread-safe signaling ----------------------------------------------------
void multiplexer::register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
if (shutting_down_) {
// discard
......@@ -120,11 +143,12 @@ void multiplexer::register_reading(const socket_manager_ptr& mgr) {
add(mgr);
}
} else {
write_to_pipe(0, mgr);
write_to_pipe(pollset_updater::register_reading_code, mgr);
}
}
void multiplexer::register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
if (shutting_down_) {
// discard
......@@ -138,11 +162,30 @@ void multiplexer::register_writing(const socket_manager_ptr& mgr) {
add(mgr);
}
} else {
write_to_pipe(1, mgr);
write_to_pipe(pollset_updater::register_writing_code, mgr);
}
}
void multiplexer::init(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
if (shutting_down_) {
// discard
} else {
if (auto err = mgr->init(content(system().config()))) {
CAF_LOG_ERROR("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. So there's probably nothing we could do
// here other than discarding the manager.
}
}
} else {
write_to_pipe(pollset_updater::init_manager_code, mgr);
}
}
void multiplexer::close_pipe() {
CAF_LOG_TRACE("");
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) {
close(write_handle_);
......@@ -150,7 +193,10 @@ void multiplexer::close_pipe() {
}
}
// -- control flow -------------------------------------------------------------
bool multiplexer::poll_once(bool blocking) {
CAF_LOG_TRACE(CAF_ARG(blocking));
if (pollset_.empty())
return false;
// We'll call poll() until poll() succeeds or fails.
......@@ -216,6 +262,7 @@ bool multiplexer::poll_once(bool blocking) {
}
void multiplexer::set_thread_id() {
CAF_LOG_TRACE("");
tid_ = std::this_thread::get_id();
}
......@@ -226,7 +273,9 @@ void multiplexer::run() {
}
void multiplexer::shutdown() {
CAF_LOG_TRACE("");
if (std::this_thread::get_id() == tid_) {
CAF_LOG_DEBUG("initiate shutdown");
shutting_down_ = true;
// First manager is the pollset_updater. Skip it and delete later.
for (size_t i = 1; i < managers_.size();) {
......@@ -242,10 +291,13 @@ void multiplexer::shutdown() {
}
close_pipe();
} else {
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(4, nullptr);
}
}
// -- utility functions --------------------------------------------------------
short multiplexer::handle(const socket_manager_ptr& mgr, short events,
short revents) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle()));
......@@ -279,6 +331,7 @@ short multiplexer::handle(const socket_manager_ptr& mgr, short events,
}
void multiplexer::add(socket_manager_ptr mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle()));
CAF_ASSERT(index_of(mgr) == -1);
pollfd new_entry{socket_cast<socket_id>(mgr->handle()),
to_bitmask(mgr->mask()), 0};
......@@ -293,10 +346,13 @@ void multiplexer::del(ptrdiff_t index) {
}
void multiplexer::write_to_pipe(uint8_t opcode, const socket_manager_ptr& mgr) {
CAF_ASSERT(opcode == 0 || opcode == 1 || opcode == 4);
CAF_ASSERT(mgr != nullptr || opcode == 4);
CAF_ASSERT(opcode == pollset_updater::register_reading_code
|| opcode == pollset_updater::register_writing_code
|| opcode == pollset_updater::init_manager_code
|| opcode == pollset_updater::shutdown_code);
CAF_ASSERT(mgr != nullptr || opcode == pollset_updater::shutdown_code);
pollset_updater::msg_buf buf;
if (opcode != 4)
if (opcode != pollset_updater::shutdown_code)
mgr->ref();
buf[0] = static_cast<byte>(opcode);
auto value = reinterpret_cast<intptr_t>(mgr.get());
......@@ -307,7 +363,7 @@ void multiplexer::write_to_pipe(uint8_t opcode, const socket_manager_ptr& mgr) {
if (write_handle_ != invalid_socket)
res = write(write_handle_, buf);
}
if (res <= 0 && opcode != 4)
if (res <= 0 && opcode != pollset_updater::shutdown_code)
mgr->deref();
}
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/actor_shell.hpp"
#include "caf/callback.hpp"
#include "caf/config.hpp"
#include "caf/detail/default_invoke_result_visitor.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/invoke_message_result.hpp"
#include "caf/logger.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
actor_shell::actor_shell(actor_config& cfg, socket_manager* owner)
: super(cfg), mailbox_(policy::normal_messages{}), owner_(owner) {
mailbox_.try_block();
}
actor_shell::~actor_shell() {
// nop
}
// -- state modifiers ----------------------------------------------------------
void actor_shell::quit(error reason) {
cleanup(std::move(reason), nullptr);
}
// -- mailbox access -----------------------------------------------------------
mailbox_element_ptr actor_shell::next_message() {
if (!mailbox_.blocked()) {
mailbox_.fetch_more();
auto& q = mailbox_.queue();
if (q.total_task_size() > 0) {
q.inc_deficit(1);
return q.next();
}
}
return nullptr;
}
bool actor_shell::try_block_mailbox() {
return mailbox_.try_block();
}
// -- message processing -------------------------------------------------------
bool actor_shell::consume_message() {
CAF_LOG_TRACE("");
if (auto msg = next_message()) {
current_element_ = msg.get();
CAF_LOG_RECEIVE_EVENT(current_element_);
CAF_BEFORE_PROCESSING(this, *msg);
auto mid = msg->mid;
if (!mid.is_response()) {
detail::default_invoke_result_visitor<actor_shell> visitor{this};
if (auto result = bhvr_(msg->payload)) {
visitor(*result);
} else {
auto fallback_result = (*fallback_)(msg->payload);
visit(visitor, fallback_result);
}
} else if (auto i = multiplexed_responses_.find(mid);
i != multiplexed_responses_.end()) {
auto bhvr = std::move(i->second);
multiplexed_responses_.erase(i);
auto res = bhvr(msg->payload);
if (!res) {
CAF_LOG_DEBUG("got unexpected_response");
auto err_msg = make_message(
make_error(sec::unexpected_response, std::move(msg->payload)));
bhvr(err_msg);
}
}
CAF_AFTER_PROCESSING(this, invoke_message_result::consumed);
CAF_LOG_SKIP_OR_FINALIZE_EVENT(invoke_message_result::consumed);
return true;
}
return false;
}
void actor_shell::add_multiplexed_response_handler(message_id response_id,
behavior bhvr) {
if (bhvr.timeout() != infinite)
request_response_timeout(bhvr.timeout(), response_id);
multiplexed_responses_.emplace(response_id, std::move(bhvr));
}
// -- overridden functions of abstract_actor -----------------------------------
void actor_shell::enqueue(mailbox_element_ptr ptr, execution_unit*) {
CAF_ASSERT(ptr != nullptr);
CAF_ASSERT(!getf(is_blocking_flag));
CAF_LOG_TRACE(CAF_ARG(*ptr));
CAF_LOG_SEND_EVENT(ptr);
auto mid = ptr->mid;
auto sender = ptr->sender;
auto collects_metrics = getf(abstract_actor::collects_metrics_flag);
if (collects_metrics) {
ptr->set_enqueue_time();
metrics_.mailbox_size->inc();
}
switch (mailbox().push_back(std::move(ptr))) {
case intrusive::inbox_result::unblocked_reader: {
CAF_LOG_ACCEPT_EVENT(true);
std::unique_lock<std::mutex> guard{owner_mtx_};
// The owner can only be null if this enqueue succeeds, then we close the
// mailbox and reset owner_ in cleanup() before acquiring the mutex here.
// Hence, the mailbox element has already been disposed and we can simply
// skip any further processing.
if (owner_)
owner_->mpx().register_writing(owner_);
break;
}
case intrusive::inbox_result::queue_closed: {
CAF_LOG_REJECT_EVENT();
home_system().base_metrics().rejected_messages->inc();
if (collects_metrics)
metrics_.mailbox_size->dec();
if (mid.is_request()) {
detail::sync_request_bouncer f{exit_reason()};
f(sender, mid);
}
break;
}
case intrusive::inbox_result::success:
// Enqueued to a running actors' mailbox: nothing to do.
CAF_LOG_ACCEPT_EVENT(false);
break;
}
}
mailbox_element* actor_shell::peek_at_next_mailbox_element() {
return mailbox().closed() || mailbox().blocked() ? nullptr : mailbox().peek();
}
const char* actor_shell::name() const {
return "caf.net.actor-shell";
}
void actor_shell::launch(execution_unit*, bool, bool hide) {
CAF_PUSH_AID_FROM_PTR(this);
CAF_LOG_TRACE(CAF_ARG(hide));
CAF_ASSERT(!getf(is_blocking_flag));
if (!hide)
register_at_system();
}
bool actor_shell::cleanup(error&& fail_state, execution_unit* host) {
CAF_LOG_TRACE(CAF_ARG(fail_state));
// Clear mailbox.
if (!mailbox_.closed()) {
mailbox_.close();
detail::sync_request_bouncer bounce{fail_state};
auto dropped = mailbox_.queue().new_round(1000, bounce).consumed_items;
while (dropped > 0) {
if (getf(abstract_actor::collects_metrics_flag)) {
auto val = static_cast<int64_t>(dropped);
metrics_.mailbox_size->dec(val);
}
dropped = mailbox_.queue().new_round(1000, bounce).consumed_items;
}
}
// Detach from owner.
{
std::unique_lock<std::mutex> guard{owner_mtx_};
owner_ = nullptr;
}
// Dispatch to parent's `cleanup` function.
return super::cleanup(std::move(fail_state), host);
}
actor_shell_ptr::actor_shell_ptr(strong_actor_ptr ptr) noexcept
: ptr_(std::move(ptr)) {
// nop
}
actor actor_shell_ptr::as_actor() const noexcept {
return actor_cast<actor>(ptr_);
}
void actor_shell_ptr::detach(error reason) {
if (auto ptr = get()) {
ptr->quit(std::move(reason));
ptr_.release();
}
}
actor_shell_ptr::~actor_shell_ptr() {
if (auto ptr = get())
ptr->quit(exit_reason::normal);
}
actor_shell* actor_shell_ptr::get() const noexcept {
if (ptr_) {
auto ptr = actor_cast<abstract_actor*>(ptr_);
return static_cast<actor_shell*>(ptr);
}
return nullptr;
}
} // namespace caf::net
......@@ -25,6 +25,7 @@
#include "caf/net/basp/application.hpp"
#include "caf/net/basp/application_factory.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/doorman.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
......
......@@ -25,7 +25,6 @@
#include "caf/net/basp/ec.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/raise_error.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
......@@ -37,8 +36,8 @@ void middleman::init_global_meta_objects() {
caf::init_global_meta_objects<id_block::net_module>();
}
middleman::middleman(actor_system& sys) : sys_(sys) {
mpx_ = std::make_shared<multiplexer>();
middleman::middleman(actor_system& sys) : sys_(sys), mpx_(this) {
// nop
}
middleman::~middleman() {
......@@ -47,15 +46,13 @@ middleman::~middleman() {
void middleman::start() {
if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) {
auto mpx = mpx_;
auto sys_ptr = &system();
mpx_thread_ = std::thread{[mpx, sys_ptr] {
CAF_SET_LOGGER_SYS(sys_ptr);
detail::set_thread_name("caf.multiplexer");
sys_ptr->thread_started();
mpx->set_thread_id();
mpx->run();
sys_ptr->thread_terminates();
mpx_thread_ = std::thread{[this] {
CAF_SET_LOGGER_SYS(&sys_);
detail::set_thread_name("caf.net.mpx");
sys_.thread_started();
mpx_.set_thread_id();
mpx_.run();
sys_.thread_terminates();
}};
}
}
......@@ -63,23 +60,23 @@ void middleman::start() {
void middleman::stop() {
for (const auto& backend : backends_)
backend->stop();
mpx_->shutdown();
mpx_.shutdown();
if (mpx_thread_.joinable())
mpx_thread_.join();
else
mpx_->run();
mpx_.run();
}
void middleman::init(actor_system_config& cfg) {
if (auto err = mpx_->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << err);
CAF_RAISE_ERROR("mpx->init() failed");
if (auto err = mpx_.init()) {
CAF_LOG_ERROR("mpx_.init() failed: " << err);
CAF_RAISE_ERROR("mpx_.init() failed");
}
if (auto node_uri = get_if<uri>(&cfg, "caf.middleman.this-node")) {
auto this_node = make_node_id(std::move(*node_uri));
sys_.node_.swap(this_node);
} else {
CAF_RAISE_ERROR("no valid entry for caf.middleman.this-node found");
// CAF_RAISE_ERROR("no valid entry for caf.middleman.this-node found");
}
for (auto& backend : backends_)
if (auto err = backend->init()) {
......
......@@ -26,6 +26,7 @@
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/message.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/sec.hpp"
......@@ -81,11 +82,13 @@ expected<std::pair<pipe_socket, pipe_socket>> make_pipe() {
}
ptrdiff_t write(pipe_socket x, span<const byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::write(x.id, reinterpret_cast<socket_send_ptr>(buf.data()),
buf.size());
}
ptrdiff_t read(pipe_socket x, span<byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::read(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()),
buf.size());
}
......
......@@ -20,6 +20,7 @@
#include <cstring>
#include "caf/actor_system.hpp"
#include "caf/logger.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/sec.hpp"
......@@ -28,20 +29,24 @@
namespace caf::net {
pollset_updater::pollset_updater(pipe_socket read_handle,
const multiplexer_ptr& parent)
: super(read_handle, parent), buf_size_(0) {
pollset_updater::pollset_updater(pipe_socket read_handle, multiplexer* parent)
: super(read_handle, parent) {
mask_ = operation::read;
if (auto err = nonblocking(read_handle, true))
CAF_LOG_ERROR("nonblocking failed: " << err);
}
pollset_updater::~pollset_updater() {
// nop
}
error pollset_updater::init(const settings&) {
CAF_LOG_TRACE("");
return nonblocking(handle(), true);
}
bool pollset_updater::handle_read_event() {
CAF_LOG_TRACE("");
for (;;) {
CAF_ASSERT((buf_.size() - buf_size_) > 0);
auto num_bytes = read(handle(), make_span(buf_.data() + buf_size_,
buf_.size() - buf_size_));
if (num_bytes > 0) {
......@@ -52,25 +57,29 @@ bool pollset_updater::handle_read_event() {
intptr_t value;
memcpy(&value, buf_.data() + 1, sizeof(intptr_t));
socket_manager_ptr mgr{reinterpret_cast<socket_manager*>(value), false};
if (auto ptr = parent_.lock()) {
switch (opcode) {
case 0:
ptr->register_reading(mgr);
break;
case 1:
ptr->register_writing(mgr);
break;
case 4:
ptr->shutdown();
break;
default:
CAF_LOG_DEBUG("opcode not recognized: " << CAF_ARG(opcode));
break;
}
switch (opcode) {
case register_reading_code:
parent_->register_reading(mgr);
break;
case register_writing_code:
parent_->register_writing(mgr);
break;
case init_manager_code:
parent_->init(mgr);
break;
case shutdown_code:
parent_->shutdown();
break;
default:
CAF_LOG_ERROR("opcode not recognized: " << CAF_ARG(opcode));
break;
}
}
} else if (num_bytes == 0) {
CAF_LOG_DEBUG("pipe closed, assume shutdown");
return false;
} else {
return num_bytes < 0 && last_socket_error_is_temporary();
return last_socket_error_is_temporary();
}
}
}
......
......@@ -19,14 +19,15 @@
#include "caf/net/socket_manager.hpp"
#include "caf/config.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/multiplexer.hpp"
namespace caf::net {
socket_manager::socket_manager(socket handle, const multiplexer_ptr& parent)
socket_manager::socket_manager(socket handle, multiplexer* parent)
: handle_(handle), mask_(operation::none), parent_(parent) {
CAF_ASSERT(parent != nullptr);
CAF_ASSERT(handle_ != invalid_socket);
CAF_ASSERT(parent != nullptr);
}
socket_manager::~socket_manager() {
......@@ -54,17 +55,19 @@ bool socket_manager::mask_del(operation flag) noexcept {
void socket_manager::register_reading() {
if ((mask() & operation::read) == operation::read)
return;
auto ptr = parent_.lock();
if (ptr != nullptr)
ptr->register_reading(this);
parent_->register_reading(this);
}
void socket_manager::register_writing() {
if ((mask() & operation::write) == operation::write)
return;
auto ptr = parent_.lock();
if (ptr != nullptr)
ptr->register_writing(this);
parent_->register_writing(this);
}
actor_shell_ptr socket_manager::make_actor_shell_impl() {
CAF_ASSERT(parent_ != nullptr);
auto hdl = parent_->system().spawn<actor_shell>(this);
return actor_shell_ptr{actor_cast<strong_actor_ptr>(std::move(hdl))};
}
} // namespace caf::net
......@@ -169,11 +169,13 @@ error nodelay(stream_socket x, bool new_value) {
}
ptrdiff_t read(stream_socket x, span<byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::recv(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()), buf.size(),
no_sigpipe_io_flag);
}
ptrdiff_t write(stream_socket x, span<const byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::send(x.id, reinterpret_cast<socket_send_ptr>(buf.data()), buf.size(),
no_sigpipe_io_flag);
}
......
......@@ -98,21 +98,23 @@ expected<tcp_accept_socket> make_tcp_accept_socket(ip_endpoint node,
bool reuse_addr) {
CAF_LOG_TRACE(CAF_ARG(node));
auto addr = to_string(node.address());
auto make_acceptor = node.address().embeds_v4()
? new_tcp_acceptor_impl<AF_INET>
: new_tcp_acceptor_impl<AF_INET6>;
auto p = make_acceptor(node.port(), addr.c_str(), reuse_addr,
node.address().zero());
if (!p) {
CAF_LOG_WARNING("could not create tcp socket for: " << to_string(node));
bool is_v4 = node.address().embeds_v4();
bool is_zero = is_v4 ? node.address().embedded_v4().bits() == 0
: node.address().zero();
auto make_acceptor = is_v4 ? new_tcp_acceptor_impl<AF_INET>
: new_tcp_acceptor_impl<AF_INET6>;
if (auto p = make_acceptor(node.port(), addr.c_str(), reuse_addr, is_zero)) {
auto sock = socket_cast<tcp_accept_socket>(*p);
auto sguard = make_socket_guard(sock);
CAF_NET_SYSCALL("listen", tmp, !=, 0, listen(sock.id, SOMAXCONN));
CAF_LOG_DEBUG(CAF_ARG(sock.id));
return sguard.release();
} else {
CAF_LOG_WARNING("could not create tcp socket:" << CAF_ARG(node)
<< CAF_ARG(p.error()));
return make_error(sec::cannot_open_port, "tcp socket creation failed",
to_string(node));
to_string(node), std::move(p.error()));
}
auto sock = socket_cast<tcp_accept_socket>(*p);
auto sguard = make_socket_guard(sock);
CAF_NET_SYSCALL("listen", tmp, !=, 0, listen(sock.id, SOMAXCONN));
CAF_LOG_DEBUG(CAF_ARG(sock.id));
return sguard.release();
}
expected<tcp_accept_socket>
......
......@@ -114,8 +114,8 @@ variant<size_t, sec> write(udp_datagram_socket x, span<const byte> buf,
ip_endpoint ep) {
sockaddr_storage addr = {};
detail::convert(ep, addr);
auto len = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
auto len = static_cast<socklen_t>(
ep.address().embeds_v4() ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
auto res = ::sendto(x.id, reinterpret_cast<socket_send_ptr>(buf.data()),
buf.size(), 0, reinterpret_cast<sockaddr*>(&addr), len);
auto ret = check_udp_datagram_socket_io_res(res);
......@@ -172,7 +172,7 @@ variant<size_t, sec> write(udp_datagram_socket x, span<byte_buffer*> bufs,
message.msg_namelen = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
message.msg_iov = buf_array;
message.msg_iovlen = bufs.size();
message.msg_iovlen = static_cast<int>(bufs.size());
auto res = sendmsg(x.id, &message, 0);
return check_udp_datagram_socket_io_res(res);
}
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE detail.rfc6455
#include "caf/detail/rfc6455.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <initializer_list>
#include <vector>
using namespace caf;
namespace {
struct fixture {
using impl = detail::rfc6455;
auto bytes(std::initializer_list<uint8_t> xs) {
std::vector<byte> result;
for (auto x : xs)
result.emplace_back(static_cast<byte>(x));
return result;
}
template <class T>
auto take(const T& xs, size_t num_bytes) {
auto n = std::min(xs.size(), num_bytes);
return std::vector<typename T::value_type>{xs.begin(), xs.begin() + n};
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(rfc6455_tests, fixture)
CAF_TEST(masking) {
auto key = uint32_t{0xDEADC0DE};
auto data = bytes({0x12, 0x34, 0x45, 0x67, 0x89, 0x9A});
auto masked_data = data;
CAF_MESSAGE("masking XORs the repeated key to data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, bytes({
0x12 ^ 0xDE,
0x34 ^ 0xAD,
0x45 ^ 0xC0,
0x67 ^ 0xDE,
0x89 ^ 0xDE,
0x9A ^ 0xAD,
}));
CAF_MESSAGE("masking masked data again gives the original data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, data);
}
CAF_TEST(no mask key plus small data) {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
0x82, // FIN + binary frame opcode
0x04, // data size = 4
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 2);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus small data) {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
0x82, // FIN + binary frame opcode
0x84, // MASKED + data size = 4
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 6);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus medium data) {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 8),
bytes({
0x82, // FIN + binary frame opcode
0x7E, // 126 -> uint16 size
0x00, 0x7E, // data size = 126
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 4);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus medium data) {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 12),
bytes({
0x82, // FIN + binary frame opcode
0xFE, // MASKED + 126 -> uint16 size
0x00, 0x7E, // data size = 126
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 8);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus large data) {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 14),
bytes({
0x82, // FIN + binary frame opcode
0x7F, // 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 65536
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 10);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus large data) {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 18),
bytes({
0x82, // FIN + binary frame opcode
0xFF, // MASKED + 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 65536
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 14);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -25,6 +25,7 @@
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/deep_to_string.hpp"
using namespace caf;
using namespace caf::net;
......@@ -34,7 +35,7 @@ CAF_TEST(serialization) {
byte_buffer buf;
{
binary_serializer sink{nullptr, buf};
CAF_CHECK_EQUAL(sink(x), none);
CAF_CHECK(sink.apply_object(x));
}
CAF_CHECK_EQUAL(buf.size(), basp::header_size);
auto buf2 = to_bytes(x);
......@@ -43,7 +44,7 @@ CAF_TEST(serialization) {
basp::header y;
{
binary_deserializer source{nullptr, buf};
CAF_CHECK_EQUAL(source(y), none);
CAF_CHECK(source.apply_object(y));
}
CAF_CHECK_EQUAL(x, y);
auto z = basp::header::from_bytes(buf);
......@@ -53,5 +54,5 @@ CAF_TEST(serialization) {
CAF_TEST(to_string) {
basp::header x{basp::message_type::handshake, 42, 4};
CAF_CHECK_EQUAL(to_string(x), "basp::header(handshake, 42, 4)");
CAF_CHECK_EQUAL(deep_to_string(x), "basp::header(handshake, 42, 4)");
}
......@@ -40,18 +40,22 @@ namespace {
class dummy_manager : public socket_manager {
public:
dummy_manager(size_t& manager_count, stream_socket handle,
multiplexer_ptr parent)
: socket_manager(handle, std::move(parent)),
count_(manager_count),
rd_buf_pos_(0) {
multiplexer* parent)
: socket_manager(handle, parent), count_(manager_count) {
CAF_MESSAGE("created new dummy manager");
++count_;
rd_buf_.resize(1024);
}
~dummy_manager() {
CAF_MESSAGE("destroyed dummy manager");
--count_;
}
error init(const settings&) override {
return none;
}
stream_socket handle() const noexcept {
return socket_cast<stream_socket>(handle_);
}
......@@ -110,7 +114,7 @@ private:
size_t& count_;
size_t rd_buf_pos_;
size_t rd_buf_pos_ = 0;
byte_buffer wr_buf_;
......@@ -120,23 +124,22 @@ private:
using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
struct fixture : host_fixture {
fixture() : manager_count(0), mpx(std::make_shared<multiplexer>()) {
mpx->set_thread_id();
fixture() : mpx(nullptr) {
mpx.set_thread_id();
}
~fixture() {
mpx.reset();
CAF_REQUIRE_EQUAL(manager_count, 0u);
}
void exhaust() {
while (mpx->poll_once(false))
while (mpx.poll_once(false))
; // Repeat.
}
size_t manager_count;
size_t manager_count = 0;
multiplexer_ptr mpx;
multiplexer mpx;
};
} // namespace
......@@ -144,32 +147,36 @@ struct fixture : host_fixture {
CAF_TEST_FIXTURE_SCOPE(multiplexer_tests, fixture)
CAF_TEST(default construction) {
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 0u);
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 0u);
}
CAF_TEST(init) {
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 0u);
CAF_REQUIRE_EQUAL(mpx->init(), none);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
mpx->close_pipe();
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 0u);
CAF_REQUIRE_EQUAL(mpx.init(), none);
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 1u);
mpx.close_pipe();
exhaust();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 0u);
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 0u);
// Calling run must have no effect now.
mpx->run();
mpx.run();
}
CAF_TEST(send and receive) {
CAF_REQUIRE_EQUAL(mpx->init(), none);
CAF_REQUIRE_EQUAL(mpx.init(), none);
auto sockets = unbox(make_stream_socket_pair());
auto alice = make_counted<dummy_manager>(manager_count, sockets.first, mpx);
auto bob = make_counted<dummy_manager>(manager_count, sockets.second, mpx);
alice->register_reading();
bob->register_reading();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 3u);
alice->send("hello bob");
alice->register_writing();
exhaust();
CAF_CHECK_EQUAL(bob->receive(), "hello bob");
{ // Lifetime scope of alice and bob.
auto alice = make_counted<dummy_manager>(manager_count, sockets.first,
&mpx);
auto bob = make_counted<dummy_manager>(manager_count, sockets.second, &mpx);
alice->register_reading();
bob->register_reading();
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 3u);
alice->send("hello bob");
alice->register_writing();
exhaust();
CAF_CHECK_EQUAL(bob->receive(), "hello bob");
}
mpx.shutdown();
}
CAF_TEST(shutdown) {
......@@ -177,26 +184,27 @@ CAF_TEST(shutdown) {
std::condition_variable cv;
bool thread_id_set = false;
auto run_mpx = [&] {
std::unique_lock<std::mutex> lk(m);
mpx->set_thread_id();
thread_id_set = true;
lk.unlock();
cv.notify_one();
mpx->run();
{
std::unique_lock<std::mutex> guard(m);
mpx.set_thread_id();
thread_id_set = true;
cv.notify_one();
}
mpx.run();
};
CAF_REQUIRE_EQUAL(mpx->init(), none);
CAF_REQUIRE_EQUAL(mpx.init(), none);
auto sockets = unbox(make_stream_socket_pair());
auto alice = make_counted<dummy_manager>(manager_count, sockets.first, mpx);
auto bob = make_counted<dummy_manager>(manager_count, sockets.second, mpx);
auto alice = make_counted<dummy_manager>(manager_count, sockets.first, &mpx);
auto bob = make_counted<dummy_manager>(manager_count, sockets.second, &mpx);
alice->register_reading();
bob->register_reading();
CAF_REQUIRE_EQUAL(mpx->num_socket_managers(), 3u);
CAF_REQUIRE_EQUAL(mpx.num_socket_managers(), 3u);
std::thread mpx_thread{run_mpx};
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return thread_id_set; });
mpx->shutdown();
mpx.shutdown();
mpx_thread.join();
CAF_REQUIRE_EQUAL(mpx->num_socket_managers(), 0u);
CAF_REQUIRE_EQUAL(mpx.num_socket_managers(), 0u);
}
CAF_TEST_FIXTURE_SCOPE_END()
#pragma once
#include "caf/error.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/tag/stream_oriented.hpp"
#include "caf/test/dsl.hpp"
template <class UpperLayer>
class mock_stream_transport {
public:
// -- member types -----------------------------------------------------------
using output_tag = caf::tag::stream_oriented;
// -- interface for the upper layer ------------------------------------------
void begin_output() {
// nop
}
auto& output_buffer() {
return output;
}
constexpr void end_output() {
// nop
}
constexpr caf::net::socket handle() noexcept {
return caf::net::invalid_socket;
}
bool can_send_more() const noexcept {
return true;
}
const caf::error& abort_reason() {
return abort_reason_;
}
void abort_reason(caf::error reason) {
abort_reason_ = std::move(reason);
}
void configure_read(caf::net::receive_policy policy) {
min_read_size = policy.min_size;
max_read_size = policy.max_size;
}
// -- initialization ---------------------------------------------------------
caf::error init(const caf::settings& config) {
return upper_layer.init(nullptr, this, config);
}
caf::error init() {
caf::settings config;
return init(config);
}
// -- buffer management ------------------------------------------------------
void push(caf::span<const caf::byte> bytes) {
input.insert(input.begin(), bytes.begin(), bytes.end());
}
void push(caf::string_view str) {
push(caf::as_bytes(caf::make_span(str)));
}
size_t unconsumed() const noexcept {
return read_buf_.size();
}
caf::string_view output_as_str() const noexcept {
return {reinterpret_cast<const char*>(output.data()), output.size()};
}
// -- event callbacks --------------------------------------------------------
ptrdiff_t handle_input() {
ptrdiff_t result = 0;
while (max_read_size > 0) {
CAF_ASSERT(max_read_size > static_cast<size_t>(read_size_));
size_t len = max_read_size - static_cast<size_t>(read_size_);
CAF_LOG_DEBUG(CAF_ARG2("available capacity:", len));
auto num_bytes = std::min(input.size(), len);
if (num_bytes == 0)
return result;
auto delta_offset = static_cast<ptrdiff_t>(read_buf_.size());
read_buf_.insert(read_buf_.end(), input.begin(),
input.begin() + num_bytes);
input.erase(input.begin(), input.begin() + num_bytes);
read_size_ += static_cast<ptrdiff_t>(num_bytes);
if (static_cast<size_t>(read_size_) < min_read_size)
return result;
auto delta = make_span(read_buf_.data() + delta_offset,
read_size_ - delta_offset);
auto consumed = upper_layer.consume(this, caf::make_span(read_buf_),
delta);
if (consumed > 0) {
result += static_cast<ptrdiff_t>(consumed);
read_buf_.erase(read_buf_.begin(), read_buf_.begin() + consumed);
read_size_ -= consumed;
} else if (consumed < 0) {
if (!abort_reason_)
abort_reason_ = caf::sec::runtime_error;
upper_layer.abort(this, abort_reason_);
return -1;
}
}
return result;
}
// -- member variables -------------------------------------------------------
UpperLayer upper_layer;
std::vector<caf::byte> output;
std::vector<caf::byte> input;
uint32_t min_read_size = 0;
uint32_t max_read_size = 0;
private:
std::vector<caf::byte> read_buf_;
ptrdiff_t read_size_ = 0;
caf::error abort_reason_;
};
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.actor_shell
#include "caf/net/actor_shell.hpp"
#include "net-test.hpp"
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_transport.hpp"
using namespace caf;
using namespace std::string_literals;
namespace {
using svec = std::vector<std::string>;
struct app_t {
using input_tag = tag::stream_oriented;
app_t() = default;
explicit app_t(actor hdl) : worker(std::move(hdl)) {
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr down, const settings&) {
self = mgr->make_actor_shell(down);
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
});
self->set_fallback([](message& msg) -> result<message> {
CAF_FAIL("unexpected message: " << msg);
return make_error(sec::unexpected_message);
});
down->configure_read(net::receive_policy::up_to(2048));
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
while (self->consume_message()) {
// We set abort_reason in our response handlers in case of an error.
if (down->abort_reason())
return false;
// else: repeat.
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return self->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf, byte_span) {
// Seek newline character.
constexpr auto nl = byte{'\n'};
if (auto i = std::find(buf.begin(), buf.end(), nl); i != buf.end()) {
// Skip empty lines.
if (i == buf.begin()) {
consumed_bytes += 1;
auto sub_res = consume(down, buf.subspan(1), {});
return sub_res >= 0 ? sub_res + 1 : sub_res;
}
// Deserialize config value from received line.
auto num_bytes = std::distance(buf.begin(), i) + 1;
string_view line{reinterpret_cast<const char*>(buf.data()),
static_cast<size_t>(num_bytes) - 1};
config_value val;
if (auto parsed_res = config_value::parse(line)) {
val = std::move(*parsed_res);
} else {
down->abort_reason(std::move(parsed_res.error()));
return -1;
}
if (!holds_alternative<settings>(val)) {
down->abort_reason(
make_error(pec::type_mismatch,
"expected a dictionary, got a "s + val.type_name()));
return -1;
}
// Deserialize message from received dictionary.
config_value_reader reader{&val};
caf::message msg;
if (!reader.apply_object(msg)) {
down->abort_reason(reader.get_error());
return -1;
}
// Dispatch message to worker.
CAF_MESSAGE("app received a message from its socket: " << msg);
self->request(worker, std::chrono::seconds{1}, std::move(msg))
.then(
[this, down](int32_t value) mutable {
++received_responses;
// Respond with the value as string.
auto str_response = std::to_string(value);
str_response += '\n';
down->begin_output();
auto& buf = down->output_buffer();
auto bytes = as_bytes(make_span(str_response));
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
},
[down](error& err) mutable { down->abort_reason(std::move(err)); });
// Try consuming more from the buffer.
consumed_bytes += static_cast<size_t>(num_bytes);
auto sub_buf = buf.subspan(num_bytes);
auto sub_res = consume(down, sub_buf, {});
return sub_res >= 0 ? num_bytes + sub_res : sub_res;
}
return 0;
}
// Handle to the worker-under-test.
actor worker;
// Lines received as asynchronous messages.
std::vector<std::string> lines;
// Actor shell representing this app.
net::actor_shell_ptr self;
// Counts how many bytes we've consumed in total.
size_t consumed_bytes = 0;
// Counts how many response messages we've received from the worker.
size_t received_responses = 0;
};
struct fixture : host_fixture, test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) {
mpx.set_thread_id();
if (auto err = mpx.init())
CAF_FAIL("mpx.init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second);
if (auto err = nonblocking(self_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
if (auto err = nonblocking(testee_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
}
template <class Predicate>
void run_while(Predicate predicate) {
if (!predicate())
return;
for (size_t i = 0; i < 1000; ++i) {
mpx.poll_once(false);
byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0)
recv_buf.insert(recv_buf.end(), tmp, tmp + bytes);
if (!predicate())
return;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
CAF_FAIL("reached max repeat rate without meeting the predicate");
}
void send(string_view str) {
auto res = write(self_socket_guard.socket(), as_bytes(make_span(str)));
if (res != static_cast<ptrdiff_t>(str.size()))
CAF_FAIL("expected write() to return " << str.size() << ", got: " << res);
}
net::middleman mm;
net::multiplexer mpx;
net::socket_guard<net::stream_socket> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard;
std::vector<byte> recv_buf;
};
constexpr std::string_view input = R"__(
{ values = [ { "@type" : "int32_t", value: 123 } ] }
)__";
} // namespace
CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) {
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
auto hdl = app.self.as_actor();
anon_send(hdl, "line 1");
anon_send(hdl, "line 2");
anon_send(hdl, "line 3");
run_while([&] { return app.lines.size() != 3; });
CAF_CHECK_EQUAL(app.lines, svec({"line 1", "line 2", "line 3"}));
}
CAF_TEST(actor shells can send requests and receive responses) {
auto worker = sys.spawn([] {
return behavior{
[](int32_t value) { return value * 2; },
};
});
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx,
worker);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
send(input);
run_while([&] { return app.consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
string_view expected_response = "246\n";
run_while([&] { return recv_buf.size() < expected_response.size(); });
string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response);
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.length_prefix_framing
#include "caf/net/length_prefix_framing.hpp"
#include "caf/test/dsl.hpp"
#include <deque>
#include <numeric>
#include <vector>
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/span.hpp"
#include "caf/tag/message_oriented.hpp"
using namespace caf;
namespace {
/// upper layer: expect messages
/// Needs to be initilized by the layer two steps down.
struct ul_expect_messages {
using input_tag = tag::message_oriented;
void set_expected_messages(std::vector<byte_buffer> messages) {
expected_messages.clear();
for (auto& msg : messages)
expected_messages.emplace_back(std::move(msg));
}
void add_expected_messages(std::vector<byte_buffer> messages) {
for (auto& msg : messages)
expected_messages.emplace_back(std::move(msg));
}
template <class LowerLayer>
ptrdiff_t consume(LowerLayer&, byte_span buffer) {
CAF_REQUIRE(expected_messages.size() > 0);
auto& next = expected_messages.front();
CAF_CHECK_EQUAL(next.size(), buffer.size());
CAF_CHECK(std::equal(next.begin(), next.end(), buffer.begin()));
expected_messages.pop_front();
return buffer.size();
}
std::deque<byte_buffer> expected_messages;
};
/// lower layer: offer stream for message parsing
template <class UpperLayer>
struct ll_provide_stream_for_messages {
using output_tag = tag::stream_oriented;
void set_expectations(std::vector<byte> data,
std::vector<byte_buffer> messages) {
data_stream = std::move(data);
auto& checking_layer = upper_layer.upper_layer();
checking_layer.set_expected_messages(messages);
}
void add_expectations(const std::vector<byte>& data,
std::vector<byte_buffer> messages) {
data_stream.insert(data_stream.end(), data.begin(), data.end());
auto& checking_layer = upper_layer.upper_layer();
checking_layer.add_expect_messages(messages);
}
void run() {
CAF_CHECK(data_stream.size() != 0);
while (processed != data_stream.size()) {
auto all_data = make_span(data_stream.data() + processed,
data_stream.size() - processed);
auto new_data = make_span(data_stream.data() + offered,
data_stream.size() - offered);
auto newly_offered = new_data.size();
auto consumed = upper_layer.consume(*this, all_data, new_data);
CAF_CHECK(consumed >= 0);
CAF_CHECK(static_cast<size_t>(consumed) <= data_stream.size());
offered += newly_offered;
processed += consumed;
if (consumed > 0) {
data_stream.erase(data_stream.begin(), data_stream.begin() + consumed);
offered -= processed;
processed = 0;
}
if (consumed == 0 || data_stream.size() == 0)
return;
}
}
size_t processed = 0;
size_t offered = 0;
std::vector<byte> data_stream;
UpperLayer upper_layer;
};
template <class... Ts>
byte_buffer to_buf(const Ts&... xs) {
byte_buffer buf;
binary_serializer sink{nullptr, buf};
if (!sink.apply_objects(xs...))
CAF_FAIL("to_buf failed: " << sink.get_error());
return buf;
}
void encode_message(std::vector<byte>& data, const byte_buffer& message) {
using detail::to_network_order;
auto current_size = data.size();
data.insert(data.end(), 4, byte{0});
auto msg_begin = data.begin() + current_size;
auto msg_size = message.size();
auto u32_size = to_network_order(static_cast<uint32_t>(msg_size));
memcpy(std::addressof(*msg_begin), &u32_size, 4);
data.insert(data.end(), message.begin(), message.end());
}
struct fixture {
using test_layers = ll_provide_stream_for_messages<
net::length_prefix_framing<ul_expect_messages>>;
void generate_messages(size_t num, size_t factor = 10) {
for (size_t n = 1; n <= num; n += 1) {
std::vector<int> buf(n * factor);
std::iota(buf.begin(), buf.end(), n);
messages.emplace_back(to_buf(buf));
}
for (auto& msg : messages)
encode_message(data, msg);
}
void set_expectations() {
layers.set_expectations(data, messages);
}
void test_receive_data() {
layers.run();
}
void clear() {
data.clear();
messages.clear();
}
test_layers layers;
std::vector<byte> data;
std::vector<byte_buffer> messages;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(length_prefix_framing_tests, fixture)
CAF_TEST(process messages) {
// Single message.
generate_messages(1);
set_expectations();
test_receive_data();
clear();
// Multiple messages.
generate_messages(10);
set_expectations();
test_receive_data();
}
CAF_TEST(incomplete message) {
generate_messages(1, 1000);
CAF_MESSAGE("data.size() = " << data.size());
auto initial_size = data.size();
auto data_copy = data;
auto mid = data.size() / 2;
data.resize(mid);
CAF_MESSAGE("data.size() = " << data.size());
data_copy.erase(data_copy.begin(), data_copy.begin() + mid);
CAF_MESSAGE("data_copy.size() = " << data_copy.size());
CAF_REQUIRE(data.size() + data_copy.size() == initial_size);
// Don't set expectations because there shouldn't be a complete message
// in the bytes.
auto messages_copy = messages;
messages.clear();
CAF_REQUIRE(messages.empty());
set_expectations();
test_receive_data();
data.insert(data.end(), data_copy.begin(), data_copy.end());
messages = messages_copy;
set_expectations();
test_receive_data();
}
CAF_TEST_FIXTURE_SCOPE_END()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE net.web_socket_server
#include "caf/net/web_socket_server.hpp"
#include "net-test.hpp"
#include "caf/byte_span.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
namespace {
using svec = std::vector<std::string>;
struct app_t {
std::string text_input;
std::vector<byte> binary_input;
settings cfg;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings& init_cfg) {
cfg = init_cfg;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, string_view text) {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr, byte_span bytes) {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
struct fixture : host_fixture {
fixture() {
using namespace caf::net;
ws = std::addressof(transport.upper_layer);
app = std::addressof(ws->upper_layer());
if (auto err = transport.init())
CAF_FAIL("failed to initialize mock transport: " << err);
rng.seed(0xD3ADC0D3);
}
void rfc6455_append(uint8_t opcode, span<const byte> bytes,
std::vector<byte>& out) {
std::vector<byte> payload{bytes.begin(), bytes.end()};
auto key = static_cast<uint32_t>(rng());
detail::rfc6455::mask_data(key, payload);
detail::rfc6455::assemble_frame(opcode, key, payload, out);
}
void rfc6455_append(span<const byte> bytes, std::vector<byte>& out) {
rfc6455_append(detail::rfc6455::binary_frame, bytes, out);
}
void rfc6455_append(string_view text, std::vector<byte>& out) {
rfc6455_append(detail::rfc6455::text_frame, as_bytes(make_span(text)), out);
}
void push(uint8_t opcode, span<const byte> bytes) {
std::vector<byte> frame;
rfc6455_append(opcode, bytes, frame);
transport.push(frame);
}
void push(span<const byte> bytes) {
push(detail::rfc6455::binary_frame, bytes);
}
void push(string_view str) {
push(detail::rfc6455::text_frame, as_bytes(make_span(str)));
}
mock_stream_transport<net::web_socket_server<app_t>> transport;
net::web_socket_server<app_t>* ws;
app_t* app;
std::minstd_rand rng;
};
constexpr auto opening_handshake
= "GET /chat HTTP/1.1\r\n"
"Host: server.example.com\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Origin: http://example.com\r\n"
"Sec-WebSocket-Protocol: chat, superchat\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"_sv;
} // namespace
#define CHECK_SETTING(key, expected_value) \
if (CAF_CHECK(holds_alternative<std::string>(app->cfg, key))) \
CAF_CHECK_EQUAL(get<std::string>(app->cfg, key), expected_value);
CAF_TEST_FIXTURE_SCOPE(web_socket_tests, fixture)
CAF_TEST(applications receive handshake data via config) {
transport.push(opening_handshake);
{
auto consumed = transport.handle_input();
if (consumed < 0)
CAF_FAIL("error handling input: " << transport.abort_reason());
CAF_CHECK_EQUAL(consumed, static_cast<ptrdiff_t>(opening_handshake.size()));
}
CAF_CHECK_EQUAL(transport.input.size(), 0u);
CAF_CHECK_EQUAL(transport.unconsumed(), 0u);
CAF_CHECK(ws->handshake_complete());
CHECK_SETTING("web-socket.method", "GET");
CHECK_SETTING("web-socket.request-uri", "/chat");
CHECK_SETTING("web-socket.http-version", "HTTP/1.1");
CHECK_SETTING("web-socket.fields.Host", "server.example.com");
CHECK_SETTING("web-socket.fields.Upgrade", "websocket");
CHECK_SETTING("web-socket.fields.Connection", "Upgrade");
CHECK_SETTING("web-socket.fields.Origin", "http://example.com");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Protocol", "chat, superchat");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Version", "13");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Key",
"dGhlIHNhbXBsZSBub25jZQ==");
}
CAF_TEST(the server responds with an HTTP response on success) {
transport.push(opening_handshake);
CAF_CHECK_EQUAL(transport.handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CAF_CHECK(ws->handshake_complete());
CAF_CHECK(transport.output_as_str(),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n");
}
CAF_TEST(handshakes may arrive in chunks) {
svec bufs;
size_t chunk_size = opening_handshake.size() / 3;
auto i = opening_handshake.begin();
bufs.emplace_back(i, i + chunk_size);
i += chunk_size;
bufs.emplace_back(i, i + chunk_size);
i += chunk_size;
bufs.emplace_back(i, opening_handshake.end());
transport.push(bufs[0]);
CAF_CHECK_EQUAL(transport.handle_input(), 0u);
CAF_CHECK(!ws->handshake_complete());
transport.push(bufs[1]);
CAF_CHECK_EQUAL(transport.handle_input(), 0u);
CAF_CHECK(!ws->handshake_complete());
transport.push(bufs[2]);
CAF_CHECK_EQUAL(transport.handle_input(), opening_handshake.size());
CAF_CHECK(ws->handshake_complete());
}
CAF_TEST(data may follow the handshake immediately) {
auto hs_bytes = as_bytes(make_span(opening_handshake));
std::vector<byte> buf{hs_bytes.begin(), hs_bytes.end()};
rfc6455_append("Hello WebSocket!\n"_sv, buf);
rfc6455_append("Bye WebSocket!\n"_sv, buf);
transport.push(buf);
CAF_CHECK_EQUAL(transport.handle_input(), static_cast<ptrdiff_t>(buf.size()));
CAF_CHECK(ws->handshake_complete());
CAF_CHECK_EQUAL(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST(data may arrive later) {
transport.push(opening_handshake);
CAF_CHECK_EQUAL(transport.handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CAF_CHECK(ws->handshake_complete());
push("Hello WebSocket!\nBye WebSocket!\n"_sv);
transport.handle_input();
CAF_CHECK_EQUAL(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -61,7 +61,7 @@ struct fixture : host_fixture, test_coordinator_fixture<config> {
mm.mpx()->set_thread_id();
auto backend = dynamic_cast<backend::test*>(mm.backend("test"));
auto mgr = backend->peer(mars);
auto& dref = dynamic_cast<endpoint_manager_impl<transport_type>&>(*mgr);
auto& dref = dynamic_cast<socket_manager_impl<transport_type>&>(*mgr);
app = &dref.transport().application();
sock = backend->socket(mars);
}
......
......@@ -30,11 +30,9 @@
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/span.hpp"
......@@ -47,12 +45,15 @@ constexpr string_view hello_manager = "hello manager!";
struct fixture : test_coordinator_fixture<>, host_fixture {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture() : recv_buf(1024), shared_buf{std::make_shared<byte_buffer>()} {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
mpx->set_thread_id();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
fixture()
: mpx(nullptr),
recv_buf(1024),
shared_recv_buf{std::make_shared<byte_buffer>()},
shared_send_buf{std::make_shared<byte_buffer>()} {
if (auto err = mpx.init())
CAF_FAIL("mpx.init failed: " << err);
mpx.set_thread_id();
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 1u);
auto sockets = unbox(make_stream_socket_pair());
send_socket_guard.reset(sockets.first);
recv_socket_guard.reset(sockets.second);
......@@ -61,84 +62,87 @@ struct fixture : test_coordinator_fixture<>, host_fixture {
}
bool handle_io_event() override {
return mpx->poll_once(false);
return mpx.poll_once(false);
}
multiplexer_ptr mpx;
settings config;
multiplexer mpx;
byte_buffer recv_buf;
socket_guard<stream_socket> send_socket_guard;
socket_guard<stream_socket> recv_socket_guard;
byte_buffer_ptr shared_buf;
byte_buffer_ptr shared_recv_buf;
byte_buffer_ptr shared_send_buf;
};
class dummy_application {
public:
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
public:
dummy_application(byte_buffer_ptr rec_buf)
: rec_buf_(std::move(rec_buf)){
// nop
};
using input_tag = tag::stream_oriented;
explicit dummy_application(byte_buffer_ptr recv_buf, byte_buffer_ptr send_buf)
: recv_buf_(std::move(recv_buf)),
send_buf_(std::move(send_buf)){
// nop
};
~dummy_application() = default;
template <class Parent>
error init(Parent&) {
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
parent->configure_read(receive_policy::exactly(hello_manager.size()));
return none;
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto payload_buf = parent.next_payload_buffer();
binary_serializer sink{parent.system(), payload_buf};
if (auto err = sink(msg->msg->payload))
CAF_FAIL("serializing failed: " << err);
parent.write_packet(payload_buf);
return none;
template <class ParentPtr>
bool prepare_send(ParentPtr parent) {
CAF_MESSAGE("prepare_send called");
auto& buf = parent->output_buffer();
auto data = as_bytes(make_span(hello_manager));
buf.insert(buf.end(), data.begin(), data.end());
return true;
}
template <class Parent>
error handle_data(Parent&, span<const byte> data) {
rec_buf_->clear();
rec_buf_->insert(rec_buf_->begin(), data.begin(), data.end());
return none;
template <class ParentPtr>
bool done_sending(ParentPtr) {
CAF_MESSAGE("done_sending called");
return true;
}
template <class ParentPtr>
size_t consume(ParentPtr, span<const byte> data, span<const byte>) {
recv_buf_->clear();
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
CAF_MESSAGE("Received " << recv_buf_->size()
<< " bytes in dummy_application");
return recv_buf_->size();
}
template <class Parent>
void resolve(Parent& parent, string_view path, const actor& listener) {
template <class ParentPtr>
void resolve(ParentPtr parent, string_view path, const actor& listener) {
actor_id aid = 42;
auto hid = string_view("0011223344556677889900112233445566778899");
auto nid = unbox(make_node_id(42, hid));
actor_config cfg;
endpoint_manager_ptr ptr{&parent.manager()};
endpoint_manager_ptr ptr{&parent->manager()};
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(
aid, nid, &parent.system(), cfg, std::move(ptr));
aid, nid, &parent->system(), cfg, std::move(ptr));
anon_send(listener, resolve_atom_v, std::string{path.begin(), path.end()},
p);
}
template <class Parent>
void timeout(Parent&, const std::string&, uint64_t) {
// nop
static void handle_error(sec code) {
CAF_FAIL("handle_error called with " << CAF_ARG(code));
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
void handle_error(sec) {
// nop
template <class ParentPtr>
static void abort(ParentPtr, const error& reason) {
CAF_FAIL("abort called with " << CAF_ARG(reason));
}
private:
byte_buffer_ptr rec_buf_;
byte_buffer_ptr recv_buf_;
byte_buffer_ptr send_buf_;
};
} // namespace
......@@ -146,57 +150,36 @@ private:
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(receive) {
using transport_type = stream_transport<dummy_application>;
auto mgr = make_endpoint_manager(
mpx, sys,
transport_type{recv_socket_guard.release(), dummy_application{shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
transport.configure_read(receive_policy::exactly(hello_manager.size()));
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_CHECK_EQUAL(write(send_socket_guard.socket(),
as_bytes(make_span(hello_manager))),
hello_manager.size());
auto mgr = make_socket_manager<dummy_application, stream_transport>(
recv_socket_guard.release(), &mpx, shared_recv_buf, shared_send_buf);
CAF_CHECK_EQUAL(mgr->init(config), none);
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 2u);
CAF_CHECK_EQUAL(
static_cast<size_t>(
write(send_socket_guard.socket(), as_bytes(make_span(hello_manager)))),
hello_manager.size());
CAF_MESSAGE("wrote " << hello_manager.size() << " bytes.");
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(shared_buf->data()),
shared_buf->size()),
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(shared_recv_buf->data()),
shared_recv_buf->size()),
hello_manager);
}
CAF_TEST(resolve and proxy communication) {
using transport_type = stream_transport<dummy_application>;
auto mgr = make_endpoint_manager(
mpx, sys,
transport_type{send_socket_guard.release(), dummy_application{shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
run();
mgr->resolve(unbox(make_uri("test:/id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
CAF_MESSAGE("got a proxy, send a message to it");
self->send(actor_cast<actor>(p), "hello proxy!");
},
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
auto read_res = read(recv_socket_guard.socket(), recv_buf);
if (read_res < 0)
CAF_FAIL("read() returned an error: " << last_socket_error_as_string);
else if (read_res == 0)
CAF_FAIL("read() returned 0 (socket closed)");
recv_buf.resize(static_cast<size_t>(read_res));
CAF_MESSAGE("receive buffer contains " << recv_buf.size() << " bytes");
message msg;
binary_deserializer source{sys, recv_buf};
CAF_CHECK_EQUAL(source(msg), none);
if (msg.match_elements<std::string>())
CAF_CHECK_EQUAL(msg.get_as<std::string>(0), "hello proxy!");
else
CAF_ERROR("expected a string, got: " << to_string(msg));
CAF_TEST(send) {
auto mgr = make_socket_manager<dummy_application, stream_transport>(
recv_socket_guard.release(), &mpx, shared_recv_buf, shared_send_buf);
CAF_CHECK_EQUAL(mgr->init(config), none);
CAF_CHECK_EQUAL(mpx.num_socket_managers(), 2u);
mgr->register_writing();
while (handle_io_event())
;
recv_buf.resize(hello_manager.size());
auto res = read(send_socket_guard.socket(), make_span(recv_buf));
CAF_MESSAGE("received " << res << " bytes");
recv_buf.resize(res);
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()),
hello_manager);
}
CAF_TEST_FIXTURE_SCOPE_END()
......@@ -65,9 +65,8 @@ struct string_application_header {
/// @relates header
template <class Inspector>
typename Inspector::result_type
inspect(Inspector& f, string_application_header& hdr) {
return f(meta::type_name("sa_header"), hdr.payload);
bool inspect(Inspector& f, string_application_header& x) {
return f.fields(f.field("payload", x.payload));
}
class string_application {
......
......@@ -75,8 +75,8 @@ public:
std::unique_ptr<endpoint_manager_queue::message> ptr) {
auto payload_buf = parent.next_payload_buffer();
binary_serializer sink(parent.system(), payload_buf);
if (auto err = sink(ptr->msg->content()))
CAF_FAIL("serializing failed: " << err);
if (!sink.apply_objects(ptr->msg->content()))
CAF_FAIL("failed to serialize content: " << sink.get_error());
CAF_MESSAGE("before sending: " << CAF_ARG(ptr->msg->content()));
parent.write_packet(payload_buf);
return none;
......@@ -154,23 +154,23 @@ struct fixture : test_coordinator_fixture<>, host_fixture {
using worker_type = transport_worker<dummy_application, ip_endpoint>;
fixture()
: transport_results{std::make_shared<transport_result>()},
: mpx(nullptr),
transport_results{std::make_shared<transport_result>()},
application_results{std::make_shared<application_result>()},
transport(sys, transport_results),
worker{dummy_application{application_results}} {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
if (auto err = mpx.init())
CAF_FAIL("mpx.init failed: " << err);
if (auto err = parse("[::1]:12345", ep))
CAF_FAIL("parse returned an error: " << err);
worker = worker_type{dummy_application{application_results}, ep};
}
bool handle_io_event() override {
return mpx->poll_once(false);
return mpx.poll_once(false);
}
multiplexer_ptr mpx;
multiplexer mpx;
std::shared_ptr<transport_result> transport_results;
std::shared_ptr<application_result> application_results;
dummy_transport transport;
......@@ -209,7 +209,7 @@ CAF_TEST(write_message) {
auto& buf = transport_results->packet_buffer;
binary_deserializer source{sys, buf};
caf::message received_msg;
CAF_CHECK_EQUAL(source(received_msg), none);
CAF_CHECK(source.apply_objects(received_msg));
CAF_MESSAGE(CAF_ARG(received_msg));
auto received_str = received_msg.get_as<std::string>(0);
string_view result{received_str};
......
......@@ -93,8 +93,8 @@ struct header {
}
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f, header& x) {
return f(meta::type_name("header"), x.payload_size);
friend bool inspect(Inspector& f, header& x) {
return f.object(x).fields(f.field("payload_size", x.payload_size));
}
size_t payload_size;
......@@ -116,10 +116,13 @@ CAF_TEST(socket creation) {
CAF_TEST(read / write using span<byte>) {
if (auto err = nonblocking(socket_cast<net::socket>(receive_socket), true))
CAF_FAIL("setting socket to nonblocking failed: " << err);
CAF_CHECK_EQUAL(read(receive_socket, buf), sec::unavailable_or_would_block);
auto read_res = read(receive_socket, buf);
if (CAF_CHECK(holds_alternative<sec>(read_res)))
CAF_CHECK(get<sec>(read_res) == sec::unavailable_or_would_block);
CAF_MESSAGE("sending data to " << to_string(ep));
CAF_CHECK_EQUAL(write(send_socket, as_bytes(make_span(hello_test)), ep),
hello_test.size());
auto write_res = write(send_socket, as_bytes(make_span(hello_test)), ep);
if (CAF_CHECK(holds_alternative<size_t>(write_res)))
CAF_CHECK_EQUAL(get<size_t>(write_res), hello_test.size());
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
string_view received{reinterpret_cast<const char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(received, hello_test);
......@@ -130,21 +133,23 @@ CAF_TEST(read / write using span<byte_buffer*>) {
header hdr{hello_test.size()};
byte_buffer hdr_buf;
binary_serializer sink(sys, hdr_buf);
if (auto err = sink(hdr))
CAF_FAIL("serializing payload failed" << err);
if (!sink.apply_object(hdr))
CAF_FAIL("failed to serialize payload: " << sink.get_error());
auto bytes = as_bytes(make_span(hello_test));
byte_buffer payload_buf(bytes.begin(), bytes.end());
auto packet_size = hdr_buf.size() + payload_buf.size();
std::vector<byte_buffer*> bufs{&hdr_buf, &payload_buf};
CAF_CHECK_EQUAL(write(send_socket, bufs, ep), packet_size);
auto write_res = write(send_socket, bufs, ep);
if (CAF_CHECK(holds_alternative<size_t>(write_res)))
CAF_CHECK_EQUAL(get<size_t>(write_res), packet_size);
// receive both as one single packet.
buf.resize(packet_size);
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
CAF_CHECK_EQUAL(buf.size(), packet_size);
binary_deserializer source(nullptr, buf);
header recv_hdr;
if (auto err = source(recv_hdr))
CAF_FAIL("serializing failed: " << err);
if (!source.apply_objects(recv_hdr))
CAF_FAIL("failed to deserialize header: " << source.get_error());
CAF_CHECK_EQUAL(hdr.payload_size, recv_hdr.payload_size);
string_view received{reinterpret_cast<const char*>(buf.data())
+ sizeof(header),
......
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