Commit 8e7f7b58 authored by Dominik Charousset's avatar Dominik Charousset

Implement new OpenSSL transport

parent f1a3b267
......@@ -19,6 +19,7 @@ caf_incubator_add_component(
net.http.method
net.http.status
net.operation
net.stream_transport_error
net.web_socket.status
HEADERS
${CAF_NET_HEADERS}
......@@ -79,3 +80,9 @@ caf_incubator_add_component(
stream_transport
tcp_sockets
udp_datagram_socket)
if(TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
caf_incubator_add_test_suites(caf-net-test net.openssl_transport)
target_sources(caf-net-test PRIVATE test/net/openssl_transport_constants.cpp)
target_link_libraries(caf-net-test PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()
......@@ -40,33 +40,32 @@ public:
// nop
}
// -- member functions -------------------------------------------------------
// -- interface functions ----------------------------------------------------
template <class LowerLayerPtr>
error
init(socket_manager* owner, LowerLayerPtr parent, const settings& config) {
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
CAF_LOG_TRACE("");
owner_ = owner;
cfg_ = config;
if (auto err = factory_.init(owner, config))
cfg_ = cfg;
if (auto err = factory_.init(owner, cfg))
return err;
parent->register_reading();
down->register_reading();
return none;
}
template <class LowerLayerPtr>
read_result handle_read_event(LowerLayerPtr parent) {
read_result handle_read_event(LowerLayerPtr down) {
CAF_LOG_TRACE("");
if (auto x = accept(parent->handle())) {
if (auto x = accept(down->handle())) {
socket_manager_ptr child = factory_.make(*x, owner_->mpx_ptr());
if (!child) {
CAF_LOG_ERROR("factory failed to create a new child");
parent->abort_reason(sec::runtime_error);
down->abort_reason(sec::runtime_error);
return read_result::stop;
}
if (auto err = child->init(cfg_)) {
CAF_LOG_ERROR("failed to initialize new child:" << err);
parent->abort_reason(std::move(err));
down->abort_reason(std::move(err));
return read_result::stop;
}
if (limit_ == 0) {
......@@ -81,8 +80,13 @@ public:
}
template <class LowerLayerPtr>
static void continue_reading(LowerLayerPtr) {
// nop
static read_result handle_buffered_data(LowerLayerPtr) {
return read_result::again;
}
template <class LowerLayerPtr>
static read_result handle_continue_reading(LowerLayerPtr) {
return read_result::again;
}
template <class LowerLayerPtr>
......@@ -91,6 +95,12 @@ public:
return write_result::stop;
}
template <class LowerLayerPtr>
static write_result handle_continue_writing(LowerLayerPtr) {
CAF_LOG_ERROR("connection_acceptor received continue writing event");
return write_result::stop;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason);
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_transport_error.hpp"
namespace caf::net {
template <class OnSuccess, class OnError>
struct default_handshake_worker_factory {
OnSuccess make;
OnError abort;
};
/// An connect worker calls an asynchronous `connect` callback until it succeeds.
/// On success, the worker calls a factory object to transfer ownership of
/// socket and communication policy to the create the socket manager that takes
/// care of the established connection.
template <bool IsServer, class Socket, class Policy, class Factory>
class handshake_worker : public socket_manager {
public:
// -- member types -----------------------------------------------------------
using super = socket_manager;
using read_result = typename super::read_result;
using write_result = typename super::write_result;
handshake_worker(Socket handle, multiplexer* parent, Policy policy,
Factory factory)
: super(handle, parent),
policy_(std::move(policy)),
factory_(std::move(factory)) {
// nop
}
// -- interface functions ----------------------------------------------------
error init(const settings& config) override {
cfg_ = config;
register_writing();
return caf::none;
}
read_result handle_read_event() override {
auto fd = socket_cast<Socket>(this->handle());
if (auto res = advance_handshake(fd); res > 0) {
return read_result::handover;
} else if (res == 0) {
factory_.abort(make_error(sec::connection_closed));
return read_result::stop;
} else {
auto err = policy_.last_error(fd, res);
switch (err) {
case stream_transport_error::want_read:
case stream_transport_error::temporary:
return read_result::again;
case stream_transport_error::want_write:
return read_result::want_write;
default:
auto err = make_error(sec::cannot_connect_to_node,
policy_.fetch_error_str());
factory_.abort(std::move(err));
return read_result::stop;
}
}
}
read_result handle_buffered_data() override {
return read_result::again;
}
read_result handle_continue_reading() override {
return read_result::again;
}
write_result handle_write_event() override {
auto fd = socket_cast<Socket>(this->handle());
if (auto res = advance_handshake(fd); res > 0) {
return write_result::handover;
} else if (res == 0) {
factory_.abort(make_error(sec::connection_closed));
return write_result::stop;
} else {
switch (policy_.last_error(fd, res)) {
case stream_transport_error::want_write:
case stream_transport_error::temporary:
return write_result::again;
case stream_transport_error::want_read:
return write_result::want_read;
default:
auto err = make_error(sec::cannot_connect_to_node,
policy_.fetch_error_str());
factory_.abort(std::move(err));
return write_result::stop;
}
}
}
write_result handle_continue_writing() override {
return write_result::again;
}
void handle_error(sec code) override {
factory_.abort(make_error(code));
}
socket_manager_ptr make_next_manager(socket hdl) override {
auto ptr = factory_.make(socket_cast<Socket>(hdl), this->mpx_ptr(),
std::move(policy_));
if (ptr) {
if (auto err = ptr->init(cfg_)) {
factory_.abort(err);
return nullptr;
} else {
return ptr;
}
} else {
factory_.abort(make_error(sec::runtime_error, "factory_.make failed"));
return nullptr;
}
}
private:
ptrdiff_t advance_handshake(Socket fd) {
if constexpr (IsServer)
return policy_.accept(fd);
else
return policy_.connect(fd);
}
settings cfg_;
Policy policy_;
Factory factory_;
};
} // namespace caf::net
......@@ -47,6 +47,12 @@ public:
friend class pollset_updater; // Needs access to the `do_*` functions.
// -- static utility functions -----------------------------------------------
/// Blocks the PIPE signal on the current thread when running on a POSIX
/// windows. Has no effect when running on Windows.
static void block_sigpipe();
// -- constructors, destructors, and assignment operators --------------------
/// @param parent Points to the owning middleman instance. May be `nullptr`
......@@ -91,6 +97,14 @@ public:
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
/// Triggers a continue reading event for `mgr`.
/// @thread-safe
void continue_reading(const socket_manager_ptr& mgr);
/// Triggers a continue writing event for `mgr`.
/// @thread-safe
void continue_writing(const socket_manager_ptr& mgr);
/// Schedules a call to `mgr->handle_error(sec::discarded)`.
/// @thread-safe
void discard(const socket_manager_ptr& mgr);
......@@ -118,10 +132,9 @@ public:
/// @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.
/// Signals the multiplexer to initiate shutdown.
/// @thread-safe
void close_pipe();
void shutdown();
// -- control flow -----------------------------------------------------------
......@@ -138,16 +151,42 @@ public:
/// Polls until no socket event handler remains.
void run();
/// Signals the multiplexer to initiate shutdown.
/// @thread-safe
void shutdown();
protected:
// -- utility functions ------------------------------------------------------
/// Handles an I/O event on given manager.
void handle(const socket_manager_ptr& mgr, short events, short revents);
/// Transfers socket ownership from one manager to another.
void do_handover(const socket_manager_ptr& mgr);
/// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary.
poll_update& update_for(ptrdiff_t index);
/// Returns a change entry for the socket of the manager.
poll_update& update_for(const socket_manager_ptr& mgr);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
template <class T>
void write_to_pipe(uint8_t opcode, T* ptr);
/// @copydoc write_to_pipe
template <class Enum, class T>
std::enable_if_t<std::is_enum_v<Enum>> write_to_pipe(Enum opcode, T* ptr) {
write_to_pipe(static_cast<uint8_t>(opcode), ptr);
}
/// Queries the currently active event bitmask for `mgr`.
short active_mask_of(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for reading.
bool is_reading(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for writing.
bool is_writing(const socket_manager_ptr& mgr);
// -- member variables -------------------------------------------------------
/// Bookkeeping data for managed sockets.
......@@ -178,25 +217,7 @@ protected:
bool shutting_down_ = false;
private:
/// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary.
poll_update& update_for(ptrdiff_t index);
/// Returns a change entry for the socket of the manager.
poll_update& update_for(const socket_manager_ptr& mgr);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
template <class T>
void write_to_pipe(uint8_t opcode, T* ptr);
/// @copydoc write_to_pipe
template <class Enum, class T>
std::enable_if_t<std::is_enum_v<Enum>> write_to_pipe(Enum opcode, T* ptr) {
write_to_pipe(static_cast<uint8_t>(opcode), ptr);
}
// -- internal callback the pollset updater ----------------------------------
// -- internal callbacks the pollset updater ---------------------------------
void do_shutdown();
......@@ -204,6 +225,10 @@ private:
void do_register_writing(const socket_manager_ptr& mgr);
void do_continue_reading(const socket_manager_ptr& mgr);
void do_continue_writing(const socket_manager_ptr& mgr);
void do_discard(const socket_manager_ptr& mgr);
void do_shutdown_reading(const socket_manager_ptr& mgr);
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/handshake_worker.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.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"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
#if OPENSSL_VERSION_NUMBER < 0x10100000L
# error "OpenSSL version too old. CAF::net requires at least OpenSSL 1.1"
#endif
#include <string>
#include <string_view>
// -- small wrappers to help working with OpenSSL ------------------------------
namespace caf::net::openssl {
/// Dispatches to the proper OpenSSL `free` function for each OpenSSL type.
struct deleter {
void operator()(SSL_CTX* ptr) const {
SSL_CTX_free(ptr);
}
void operator()(SSL* ptr) const {
SSL_free(ptr);
}
};
/// A smart pointer to an `SSL_CTX` structure.
/// @note technically, SSL structures are reference counted and we could use
/// `intrusive_ptr` instead. However, we have no need for shared ownership
/// semantics here and use `unique_ptr` for simplicity.
using ctx_ptr = std::unique_ptr<SSL_CTX, deleter>;
/// A smart pointer to an `SSL` structure.
/// @note technically, SSL structures are reference counted and we could use
/// `intrusive_ptr` instead. However, we have no need for shared ownership
/// semantics here and use `unique_ptr` for simplicity.
using conn_ptr = std::unique_ptr<SSL, deleter>;
/// Convenience function for creating an OpenSSL context for given method.
inline ctx_ptr make_ctx(const SSL_METHOD* method) {
if (auto ptr = SSL_CTX_new(method))
return ctx_ptr{ptr};
else
CAF_RAISE_ERROR("SSL_CTX_new failed");
}
/// Fetches a string representation for the last OpenSSL errors.
std::string fetch_error_str() {
auto cb = [](const char* cstr, size_t len, void* vptr) -> int {
auto& str = *reinterpret_cast<std::string*>(vptr);
if (str.empty()) {
str.assign(cstr, len);
} else {
str += "; ";
auto view = std::string_view{cstr, len};
str.insert(str.end(), view.begin(), view.end());
}
return 1;
};
std::string result;
ERR_print_errors_cb(cb, &result);
return result;
}
/// Loads the certificate into the SSL context.
inline error certificate_pem_file(const ctx_ptr& ctx, const std::string& path) {
auto cstr = path.c_str();
if (SSL_CTX_use_certificate_file(ctx.get(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Loads the private key into the SSL context.
inline error private_key_pem_file(const ctx_ptr& ctx, const std::string& path) {
auto cstr = path.c_str();
if (SSL_CTX_use_PrivateKey_file(ctx.get(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Convenience function for creating a new SSL structure from given context.
inline conn_ptr make_conn(const ctx_ptr& ctx) {
if (auto ptr = SSL_new(ctx.get()))
return conn_ptr{ptr};
else
CAF_RAISE_ERROR("SSL_new failed");
}
/// Convenience function for creating a new SSL structure from given context and
/// binding the given socket to it.
inline conn_ptr make_conn(const ctx_ptr& ctx, stream_socket fd) {
auto ptr = make_conn(ctx);
if (SSL_set_fd(ptr.get(), fd.id))
return ptr;
else
CAF_RAISE_ERROR("SSL_set_fd failed");
}
/// Manages an OpenSSL connection.
class policy {
public:
// -- constructors, destructors, and assignment operators --------------------
policy() = delete;
policy(const policy&) = delete;
policy& operator=(const policy&) = delete;
policy(policy&&) = default;
policy& operator=(policy&&) = default;
policy(ctx_ptr ctx, conn_ptr conn)
: ctx_(std::move(ctx)), conn_(std::move(conn)) {
// nop
}
// -- factories --------------------------------------------------------------
/// Creates a policy from an SSL method and socket.
static policy make(const SSL_METHOD* method, stream_socket fd) {
auto ctx = make_ctx(method);
auto conn = make_conn(ctx, fd);
return policy{std::move(ctx), std::move(conn)};
}
/// Creates a policy from an SSL context and socket.
static policy make(ctx_ptr ctx, stream_socket fd) {
auto conn = make_conn(ctx, fd);
return policy{std::move(ctx), std::move(conn)};
}
// -- properties -------------------------------------------------------------
SSL_CTX* ctx() {
return ctx_.get();
}
SSL* conn() {
return conn_.get();
}
// -- OpenSSL settings -------------------------------------------------------
/// Loads the certificate into the SSL connection object.
error certificate_pem_file(const std::string& path) {
auto cstr = path.c_str();
if (SSL_use_certificate_file(conn(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Loads the private key into the SSL connection object.
error private_key_pem_file(const std::string& path) {
auto cstr = path.c_str();
if (SSL_use_PrivateKey_file(conn(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
// -- interface functions for the stream transport ---------------------------
/// Fetches a string representation for the last error.
std::string fetch_error_str() {
return openssl::fetch_error_str();
}
/// Reads data from the SSL connection into the buffer.
ptrdiff_t read(stream_socket, span<byte> buf) {
return SSL_read(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
/// Writes data from the buffer to the SSL connection.
ptrdiff_t write(stream_socket, span<const byte> buf) {
return SSL_write(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
/// Performs a TLS/SSL handshake with the server.
ptrdiff_t connect(stream_socket) {
return SSL_connect(conn_.get());
}
/// Waits for the client to performs a TLS/SSL handshake.
ptrdiff_t accept(stream_socket) {
return SSL_accept(conn_.get());
}
/// Returns the last SSL error.
stream_transport_error last_error(stream_socket fd, ptrdiff_t ret) {
switch (SSL_get_error(conn_.get(), static_cast<int>(ret))) {
case SSL_ERROR_NONE:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_CONNECT:
// For all of these, OpenSSL docs say to do the operation again later.
return stream_transport_error::temporary;
case SSL_ERROR_SYSCALL:
// Need to consult errno, which we just leave to the default policy.
return default_stream_transport_policy::last_error(fd, ret);
case SSL_ERROR_WANT_READ:
return stream_transport_error::want_read;
case SSL_ERROR_WANT_WRITE:
return stream_transport_error::want_write;
default:
// Errors like SSL_ERROR_WANT_X509_LOOKUP are technically temporary, but
// we do not configure any callbacks. So seeing this is a red flag.
return stream_transport_error::permanent;
}
}
/// Graceful shutdown.
void notify_close() {
SSL_shutdown(conn_.get());
}
/// Returns the number of bytes that are buffered internally and that are
/// available for immediate read.
size_t buffered() {
return static_cast<size_t>(SSL_pending(conn_.get()));
}
private:
/// Our SSL context.
openssl::ctx_ptr ctx_;
/// Our SSL connection data.
openssl::conn_ptr conn_;
};
/// Asynchronously starts the TLS/SSL client handshake.
/// @param fd A connected stream socket.
/// @param mpx Pointer to the multiplexer for managing the asynchronous events.
/// @param pol The OpenSSL policy with properly configured SSL/TSL method.
/// @param on_success The callback for creating a @ref socket_manager after
/// successfully connecting to the server.
/// @param on_error The callback for unexpected errors.
/// @pre `fd != invalid_socket`
/// @pre `mpx != nullptr`
template <class Socket, class OnSuccess, class OnError>
void async_connect(Socket fd, multiplexer* mpx, policy pol,
OnSuccess on_success, OnError on_error) {
using res_t = decltype(on_success(fd, mpx, std::move(pol)));
using err_t = decltype(on_error(error{}));
static_assert(std::is_convertible_v<res_t, socket_manager_ptr>,
"on_success must return a socket_manager_ptr");
static_assert(std::is_same_v<err_t, void>,
"on_error may not return anything");
using factory_t = default_handshake_worker_factory<OnSuccess, OnError>;
using worker_t = handshake_worker<false, Socket, policy, factory_t>;
auto factory = factory_t{std::move(on_success), std::move(on_error)};
auto mgr = make_counted<worker_t>(fd, mpx, std::move(pol),
std::move(factory));
mpx->init(mgr);
}
/// Asynchronously starts the TLS/SSL server handshake.
/// @param fd A connected stream socket.
/// @param mpx Pointer to the multiplexer for managing the asynchronous events.
/// @param pol The OpenSSL policy with properly configured SSL/TSL method.
/// @param on_success The callback for creating a @ref socket_manager after
/// successfully connecting to the client.
/// @param on_error The callback for unexpected errors.
/// @pre `fd != invalid_socket`
/// @pre `mpx != nullptr`
template <class Socket, class OnSuccess, class OnError>
void async_accept(Socket fd, multiplexer* mpx, policy pol, OnSuccess on_success,
OnError on_error) {
using res_t = decltype(on_success(fd, mpx, std::move(pol)));
using err_t = decltype(on_error(error{}));
static_assert(std::is_convertible_v<res_t, socket_manager_ptr>,
"on_success must return a socket_manager_ptr");
static_assert(std::is_same_v<err_t, void>,
"on_error may not return anything");
using factory_t = default_handshake_worker_factory<OnSuccess, OnError>;
using worker_t = handshake_worker<true, Socket, policy, factory_t>;
auto factory = factory_t{std::move(on_success), std::move(on_error)};
auto mgr = make_counted<worker_t>(fd, mpx, std::move(pol),
std::move(factory));
mpx->init(mgr);
}
} // namespace caf::net::openssl
namespace caf::net {
/// Implements a stream_transport that manages a stream socket with encrypted
/// communication over OpenSSL.
template <class UpperLayer>
class openssl_transport
: public stream_transport_base<openssl::policy, UpperLayer> {
public:
// -- member types -----------------------------------------------------------
using super = stream_transport_base<openssl::policy, UpperLayer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
openssl_transport(openssl::ctx_ptr ctx, openssl::conn_ptr conn, Ts&&... xs)
: super(openssl::policy{std::move(ctx), std::move(conn)},
std::forward<Ts>(xs)...) {
// nop
}
template <class... Ts>
openssl_transport(openssl::policy policy, Ts&&... xs)
: super(std::move(policy), std::forward<Ts>(xs)...) {
// nop
}
};
} // namespace caf::net
......@@ -22,11 +22,11 @@ public:
using msg_buf = std::array<byte, sizeof(intptr_t) + 1>;
// -- constants --------------------------------------------------------------
enum class code : uint8_t {
register_reading,
continue_reading,
register_writing,
continue_writing,
init_manager,
discard_manager,
shutdown_reading,
......@@ -34,6 +34,7 @@ public:
run_action,
shutdown,
};
// -- constructors, destructors, and assignment operators --------------------
pollset_updater(pipe_socket read_handle, multiplexer* parent);
......@@ -53,11 +54,15 @@ public:
read_result handle_read_event() override;
read_result handle_buffered_data() override;
read_result handle_continue_reading() override;
write_result handle_write_event() override;
void handle_error(sec code) override;
write_result handle_continue_writing() override;
void continue_reading() override;
void handle_error(sec code) override;
private:
msg_buf buf_;
......
......@@ -39,8 +39,7 @@ public:
void on_consumer_demand(size_t new_demand) override {
auto prev = demand_.fetch_add(new_demand);
if (prev == 0)
mgr_->mpx().schedule_fn(
[adapter = strong_this()] { adapter->continue_reading(); });
mgr_->continue_reading();
}
void ref_producer() const noexcept override {
......@@ -129,11 +128,6 @@ private:
// nop
}
void continue_reading() {
if (mgr_)
mgr_->continue_reading();
}
void on_cancel() {
if (buf_) {
mgr_->mpx().shutdown_reading(mgr_);
......
......@@ -164,11 +164,31 @@ public:
// -- event loop management --------------------------------------------------
/// Registers the manager for read operations on the @ref multiplexer.
/// @thread-safe
void register_reading();
/// Registers the manager for write operations on the @ref multiplexer.
/// @thread-safe
void register_writing();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new demand in asynchronous
/// buffer that allow the manager to push available data downstream. The event
/// is a no-op if the manager is already registered for reading.
/// @thread-safe
void continue_reading();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new data for writing in an
/// asynchronous buffer. The event is a no-op if the manager is already
/// registered for writing.
/// @thread-safe
void continue_writing();
// -- callbacks for the multiplexer ------------------------------------------
/// Performs a handover to another manager after `handle_read_event` or
/// `handle_read_event` returned `handover`.
socket_manager_ptr do_handover();
......@@ -181,20 +201,31 @@ public:
/// Called whenever the socket received new data.
virtual read_result handle_read_event() = 0;
/// Called after handovers to allow the manager to process any data that is
/// already buffered at the transport policy and thus would not trigger a read
/// event on the socket.
virtual read_result handle_buffered_data() = 0;
/// Restarts a socket manager that suspended reads. Calling this member
/// function on active managers is a no-op. This function also should read any
/// data buffered outside of the socket.
virtual read_result handle_continue_reading() = 0;
/// Called whenever the socket is allowed to send data.
virtual write_result handle_write_event() = 0;
/// Restarts a socket manager that suspended writes. Calling this member
/// function on active managers is a no-op.
virtual write_result handle_continue_writing() = 0;
/// Called when the remote side becomes unreachable due to an error.
/// @param code The error code as reported by the operating system.
virtual void handle_error(sec code) = 0;
/// Restarts a socket manager that suspended reads. Calling this member
/// function on active managers is a no-op.
virtual void continue_reading() = 0;
/// Returns the new manager for the socket after `handle_read_event` or
/// `handle_read_event` returned `handover`.
/// @note When returning a non-null pointer, the new manager *must* also be
/// @note Called from `do_handover`.
/// @note When returning a non-null pointer, the new manager *must* be
/// initialized.
virtual socket_manager_ptr make_next_manager(socket handle);
......@@ -236,17 +267,6 @@ public:
// 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.
......@@ -254,42 +274,56 @@ public:
return socket_cast<socket_type>(this->handle_);
}
// -- event callbacks --------------------------------------------------------
auto& protocol() noexcept {
return protocol_;
}
read_result handle_read_event() override {
CAF_LOG_TRACE("");
return protocol_.handle_read_event(this);
const auto& protocol() const noexcept {
return protocol_;
}
write_result handle_write_event() override {
auto& top_layer() noexcept {
return climb(protocol_);
}
const auto& top_layer() const noexcept {
return climb(protocol_);
}
// -- interface functions ----------------------------------------------------
error init(const settings& config) override {
CAF_LOG_TRACE("");
return protocol_.handle_write_event(this);
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);
}
void handle_error(sec code) override {
CAF_LOG_TRACE(CAF_ARG(code));
this->abort_reason(make_error(code));
return protocol_.abort(this, this->abort_reason());
read_result handle_read_event() override {
return protocol_.handle_read_event(this);
}
void continue_reading() override {
return protocol_.continue_reading(this);
read_result handle_buffered_data() override {
return protocol_.handle_buffered_data(this);
}
auto& protocol() noexcept {
return protocol_;
read_result handle_continue_reading() override {
return protocol_.handle_continue_reading(this);
}
const auto& protocol() const noexcept {
return protocol_;
write_result handle_write_event() override {
return protocol_.handle_write_event(this);
}
auto& top_layer() noexcept {
return climb(protocol_);
write_result handle_continue_writing() override {
return protocol_.handle_continue_writing(this);
}
const auto& top_layer() const noexcept {
return climb(protocol_);
void handle_error(sec code) override {
this->abort_reason(make_error(code));
return protocol_.abort(this, this->abort_reason());
}
private:
......
......@@ -16,6 +16,7 @@
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport_error.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
......@@ -24,17 +25,6 @@
namespace caf::net {
enum class stream_transport_error {
/// Indicates that the transport should try again later.
temporary,
/// Indicates that the transport must read data before trying again.
want_read,
/// Indicates that the transport must write data before trying again.
want_write,
/// Indicates that the transport cannot resume this operation.
permanent,
};
/// Configures a stream transport with default socket operations.
struct default_stream_transport_policy {
public:
......@@ -77,6 +67,15 @@ public:
using write_result = typename socket_manager::write_result;
/// Bundles various flags into a single block of memory.
struct flags_t {
/// Stores whether we left a read handler due to want_write.
bool wanted_read_from_write_event : 1;
/// Stores whether we left a write handler due to want_read.
bool wanted_write_from_read_event : 1;
};
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
......@@ -204,18 +203,11 @@ public:
upper_layer_.abort(this_layer_ptr, parent->abort_reason());
return read_result::stop;
};
// Convenience lambda for invoking the next layer.
auto invoke_upper_layer = [this, &this_layer_ptr](byte* ptr, ptrdiff_t off,
ptrdiff_t delta) {
auto bytes = make_span(ptr, off);
return upper_layer_.consume(this_layer_ptr, bytes, bytes.subspan(delta));
};
// Resume a write operation if the transport waited for the socket to be
// readable from the last call to handle_write_event.
if (flags.wanted_read_from_write_event) {
flags.wanted_read_from_write_event = false;
switch (handle_write_event(parent))
{
switch (handle_write_event(parent)) {
case write_result::want_read:
CAF_ASSERT(flags.wanted_read_from_write_event);
return read_result::again;
......@@ -230,12 +222,10 @@ public:
}
// Before returning from the event handler, we always call after_reading for
// clients that request this callback.
auto after_reading_guard
= detail::make_scope_guard([this, &this_layer_ptr] {
if constexpr (detail::has_after_reading_v<UpperLayer,
decltype(this_layer_ptr)>)
upper_layer_.after_reading(this_layer_ptr);
});
using detail::make_scope_guard;
auto after_reading_guard = make_scope_guard([this, &this_layer_ptr] { //
after_reading(this_layer_ptr);
});
// Loop until meeting one of our stop criteria.
for (size_t read_count = 0;;) {
// Stop condition 1: the application halted receive operations. Usually by
......@@ -273,55 +263,10 @@ public:
return fail(sec::socket_disconnected);
}
++read_count;
// Ask the next layer to process some data.
offset_ += read_res;
auto internal_buffer_size = policy_.buffered();
// The offset_ may change as a result of invoking the upper layer. Hence,
// need to run this in a loop to push data up for as long as we have
// buffered data available.
while (offset_ >= min_read_size_) {
// Here, we have yet another loop. This one makes sure that we do not
// leave this event handler if we can make progress from the data
// buffered inside the socket policy. For 'raw' policies (like the
// default policy), there is no buffer. However, any block-oriented
// transport like OpenSSL has to buffer data internally. We need to make
// sure to consume the buffer because the OS does not know about it and
// will not trigger a read event based on data available there.
do {
ptrdiff_t consumed = invoke_upper_layer(read_buf_.data(), offset_,
delta_offset_);
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,
"consumed < 0"));
return read_result::stop;
}
// 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_;
// Stop if the application asked for it.
if (max_read_size_ == 0)
return read_result::stop;
if (internal_buffer_size > 0 && offset_ < max_read_size_) {
// Fetch already buffered data to 'refill' the buffer as we go.
auto n = std::min(internal_buffer_size,
max_read_size_ - static_cast<size_t>(offset_));
auto rdb = make_span(read_buf_.data() + offset_, n);
auto rd = policy_.read(parent->handle(), rdb);
if (rd < 0)
return fail(make_error(caf::sec::runtime_error,
"policy error: reading buffered data "
"may not result in an error"));
offset_ += rd;
internal_buffer_size = policy_.buffered();
}
} while (internal_buffer_size > 0);
}
// Stop condition 3: application indicates to stop while processing data.
if (auto res = handle_buffered_data(parent); res != read_result::again)
return res;
// Our thresholds may have changed if the upper layer called
// configure_read. Shrink/grow buffer as necessary.
if (read_buf_.size() != max_read_size_ && offset_ <= max_read_size_)
......@@ -397,11 +342,92 @@ public:
}
template <class ParentPtr>
void continue_reading(ParentPtr parent) {
read_result handle_continue_reading(ParentPtr parent) {
if (max_read_size_ == 0) {
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
upper_layer_.continue_reading(this_layer_ptr);
}
if (max_read_size_ > 0)
return handle_buffered_data(parent);
else
return read_result::stop;
}
template <class ParentPtr>
read_result handle_buffered_data(ParentPtr parent) {
CAF_ASSERT(max_read_size_ > 0);
// Pointer for passing "this layer" to the next one down the chain.
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
// Convenience lambda for failing the application.
auto fail = [this, &parent, &this_layer_ptr](auto reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
parent->abort_reason(std::move(reason));
upper_layer_.abort(this_layer_ptr, parent->abort_reason());
return read_result::stop;
};
// Convenience lambda for invoking the next layer.
auto invoke_upper_layer = [this, &this_layer_ptr](byte* ptr, ptrdiff_t off,
ptrdiff_t delta) {
auto bytes = make_span(ptr, off);
return upper_layer_.consume(this_layer_ptr, bytes, bytes.subspan(delta));
};
// Keep track of how many bytes of data are still pending in the policy.
auto internal_buffer_size = policy_.buffered();
// The offset_ may change as a result of invoking the upper layer. Hence,
// need to run this in a loop to push data up for as long as we have
// buffered data available.
while (offset_ >= min_read_size_) {
// Here, we have yet another loop. This one makes sure that we do not
// leave this event handler if we can make progress from the data
// buffered inside the socket policy. For 'raw' policies (like the
// default policy), there is no buffer. However, any block-oriented
// transport like OpenSSL has to buffer data internally. We need to make
// sure to consume the buffer because the OS does not know about it and
// will not trigger a read event based on data available there.
do {
ptrdiff_t consumed = invoke_upper_layer(read_buf_.data(), offset_,
delta_offset_);
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,
"consumed < 0"));
return read_result::stop;
}
// 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_;
// Stop if the application asked for it.
if (max_read_size_ == 0)
return read_result::stop;
if (internal_buffer_size > 0 && offset_ < max_read_size_) {
// Fetch already buffered data to 'refill' the buffer as we go.
auto n = std::min(internal_buffer_size,
max_read_size_ - static_cast<size_t>(offset_));
auto rdb = make_span(read_buf_.data() + offset_, n);
auto rd = policy_.read(parent->handle(), rdb);
if (rd < 0)
return fail(make_error(caf::sec::runtime_error,
"policy error: reading buffered data "
"may not result in an error"));
offset_ += rd;
internal_buffer_size = policy_.buffered();
}
} while (internal_buffer_size > 0);
}
return read_result::again;
}
template <class ParentPtr>
write_result handle_continue_writing(ParentPtr) {
// TODO: consider whether we need another callback for the upper layer.
// For now, we always return `again`, which triggers the write
// handler later.
return write_result::again;
}
template <class ParentPtr>
......@@ -411,11 +437,13 @@ public:
}
private:
///
struct flags_t {
bool wanted_read_from_write_event : 1;
bool wanted_write_from_read_event : 1;
} flags;
template <class ThisLayerPtr>
void after_reading([[maybe_unused]] ThisLayerPtr& this_layer_ptr) {
if constexpr (detail::has_after_reading_v<UpperLayer, ThisLayerPtr>)
upper_layer_.after_reading(this_layer_ptr);
}
flags_t flags;
/// Caches the config parameter for limiting max. socket operations.
uint32_t max_consecutive_reads_ = 0;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/is_error_code_enum.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net {
enum class stream_transport_error {
/// Indicates that the transport should try again later.
temporary,
/// Indicates that the transport must read data before trying again.
want_read,
/// Indicates that the transport must write data before trying again.
want_write,
/// Indicates that the transport cannot resume this operation.
permanent,
};
/// @relates stream_transport_error
CAF_NET_EXPORT std::string to_string(stream_transport_error);
/// @relates stream_transport_error
CAF_NET_EXPORT bool from_string(string_view, stream_transport_error&);
/// @relates stream_transport_error
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<stream_transport_error>,
stream_transport_error&);
/// @relates stream_transport_error
template <class Inspector>
bool inspect(Inspector& f, stream_transport_error& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net
......@@ -24,6 +24,7 @@
#ifndef CAF_WINDOWS
# include <poll.h>
# include <signal.h>
#else
# include "caf/detail/socket_sys_includes.hpp"
#endif // CAF_WINDOWS
......@@ -75,24 +76,29 @@ operation to_operation(const socket_manager_ptr& mgr,
} // namespace
template <class T>
void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
pollset_updater::msg_buf buf;
if (ptr)
intrusive_ptr_add_ref(ptr);
buf[0] = static_cast<byte>(opcode);
auto value = reinterpret_cast<intptr_t>(ptr);
memcpy(buf.data() + 1, &value, sizeof(intptr_t));
ptrdiff_t res = -1;
{ // Lifetime scope of guard.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket)
res = write(write_handle_, buf);
// -- static utility functions -------------------------------------------------
#ifndef CAF_WINDOWS
void multiplexer::block_sigpipe() {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
sigset_t saved_mask;
if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask) == -1) {
perror("pthread_sigmask");
exit(1);
}
if (res <= 0 && ptr)
intrusive_ptr_release(ptr);
}
#else
void multiplexer::block_sigpipe() {
// nop
}
#endif
// -- constructors, destructors, and assignment operators ----------------------
multiplexer::multiplexer(middleman* owner) : owner_(owner) {
......@@ -158,7 +164,7 @@ operation multiplexer::mask_of(const socket_manager_ptr& mgr) {
return to_operation(mgr, std::nullopt);
}
// -- thread-safe signaling and their internal callbacks -----------------------
// -- thread-safe signaling ----------------------------------------------------
void multiplexer::register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
......@@ -169,15 +175,6 @@ void multiplexer::register_reading(const socket_manager_ptr& mgr) {
}
}
void multiplexer::do_register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, no new reads are allowed.
if (shutting_down_)
mgr->close_read();
else if (!mgr->read_closed())
update_for(mgr).events |= input_mask;
}
void multiplexer::register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
......@@ -188,29 +185,32 @@ void multiplexer::register_writing(const socket_manager_ptr& mgr) {
}
}
void multiplexer::do_register_writing(const socket_manager_ptr& mgr) {
void multiplexer::continue_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, we do allow managers to write whatever is currently
// pending but we make sure that all read channels are closed.
if (shutting_down_)
mgr->close_read();
if (!mgr->write_closed())
update_for(mgr).events |= output_mask;
if (std::this_thread::get_id() == tid_) {
do_continue_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::continue_reading, mgr.get());
}
}
void multiplexer::discard(const socket_manager_ptr& mgr) {
void multiplexer::continue_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) {
do_discard(mgr);
do_continue_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::discard_manager, mgr.get());
write_to_pipe(pollset_updater::code::continue_writing, mgr.get());
}
}
void multiplexer::do_discard(const socket_manager_ptr& mgr) {
void multiplexer::discard(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
mgr->handle_error(sec::disposed);
update_for(mgr).events = 0;
if (std::this_thread::get_id() == tid_) {
do_discard(mgr);
} else {
write_to_pipe(pollset_updater::code::discard_manager, mgr.get());
}
}
void multiplexer::shutdown_reading(const socket_manager_ptr& mgr) {
......@@ -222,14 +222,6 @@ void multiplexer::shutdown_reading(const socket_manager_ptr& mgr) {
}
}
void multiplexer::do_shutdown_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->read_closed()) {
mgr->close_read();
update_for(mgr).events &= ~input_mask;
}
}
void multiplexer::shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
......@@ -239,14 +231,6 @@ void multiplexer::shutdown_writing(const socket_manager_ptr& mgr) {
}
}
void multiplexer::do_shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->write_closed()) {
mgr->close_write();
update_for(mgr).events &= ~output_mask;
}
}
void multiplexer::schedule(const action& what) {
CAF_LOG_TRACE("");
write_to_pipe(pollset_updater::code::run_action, what.ptr());
......@@ -255,42 +239,20 @@ void multiplexer::schedule(const action& what) {
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_) {
if (auto err = mgr->init(content(system().config()))) {
CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case.
update_for(mgr).events = 0;
}
// Else: no update since the manager is supposed to call continue_reading
// and continue_writing as necessary.
}
do_init(mgr);
} else {
write_to_pipe(pollset_updater::code::init_manager, mgr.get());
}
}
void multiplexer::do_init(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_) {
if (auto err = mgr->init(content(system().config()))) {
CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case.
update_for(mgr).events = 0;
}
// Else: no update since the manager is supposed to call continue_reading
// and continue_writing as necessary.
}
}
void multiplexer::close_pipe() {
void multiplexer::shutdown() {
CAF_LOG_TRACE("");
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) {
close(write_handle_);
write_handle_ = pipe_socket{};
}
// Note: there is no 'shortcut' when calling the function in the multiplexer's
// thread, because do_shutdown calls apply_updates. This must only be called
// from the pollset_updater.
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
}
// -- control flow -------------------------------------------------------------
......@@ -389,33 +351,18 @@ void multiplexer::set_thread_id() {
void multiplexer::run() {
CAF_LOG_TRACE("");
// On systems like Linux, we cannot disable sigpipe on the socket alone. We
// need to block the signal at thread level since some APIs (such as OpenSSL)
// are unsafe to call otherwise.
block_sigpipe();
while (!shutting_down_ || pollset_.size() > 1)
poll_once(true);
close_pipe();
}
void multiplexer::shutdown() {
// Note: there is no 'shortcut' when calling the function in the multiplexer's
// thread, because do_shutdown calls apply_updates. This must only be called
// from the pollset_updater.
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
}
void multiplexer::do_shutdown() {
// Note: calling apply_updates here is only safe because we know that the
// pollset updater runs outside of the for-loop in run_once.
CAF_LOG_DEBUG("initiate shutdown");
shutting_down_ = true;
apply_updates();
// Skip the first manager (the pollset updater).
for (size_t i = 1; i < managers_.size(); ++i) {
auto& mgr = managers_[i];
mgr->close_read();
update_for(static_cast<ptrdiff_t>(i)).events &= ~input_mask;
// Close the pipe to block any future event.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) {
close(write_handle_);
write_handle_ = pipe_socket{};
}
apply_updates();
}
// -- utility functions --------------------------------------------------------
......@@ -426,18 +373,6 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
<< CAF_ARG(events) << CAF_ARG(revents));
CAF_ASSERT(mgr != nullptr);
bool checkerror = true;
// Convenience function for performing a handover between managers.
auto do_handover = [this, &mgr] {
// Make sure to override the manager pointer in the update. Updates are
// associated to sockets, so the new manager is likely to modify this update
// again. Hence, it *must not* point to the old manager.
auto& update = update_for(mgr);
auto new_mgr = mgr->do_handover();
update.events = 0;
if (new_mgr != nullptr)
update.mgr = new_mgr;
};
//
// Note: we double-check whether the manager is actually reading because a
// previous action from the pipe may have called shutdown_reading.
if ((events & revents & input_mask) != 0) {
......@@ -453,7 +388,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover();
do_handover(mgr);
return;
}
}
......@@ -472,7 +407,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
update_for(mgr).events = input_mask;
break;
case socket_manager::write_result::handover:
do_handover();
do_handover(mgr);
return;
}
}
......@@ -487,6 +422,38 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
}
}
void multiplexer::do_handover(const socket_manager_ptr& mgr) {
// Make sure to override the manager pointer in the update. Updates are
// associated to sockets, so the new manager is likely to modify this update
// again. Hence, it *must not* point to the old manager.
auto& update = update_for(mgr);
update.events = 0;
auto new_mgr = mgr->do_handover(); // May alter the events mask.
if (new_mgr != nullptr) {
update.mgr = new_mgr;
// If the new manager registered itself for reading, make sure it processes
// whatever data is available in buffers outside of the socket that may not
// trigger read events.
if ((update.events & input_mask)) {
switch (mgr->handle_buffered_data()) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::stop:
update.events &= ~input_mask;
break;
case socket_manager::read_result::want_write:
update.events = output_mask;
break;
case socket_manager::read_result::handover: {
// Down the rabbit hole we go!
do_handover(new_mgr);
}
}
}
}
}
multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) {
auto fd = socket{pollset_[index].fd};
if (auto i = updates_.find(fd); i != updates_.end()) {
......@@ -501,10 +468,169 @@ multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) {
multiplexer::poll_update&
multiplexer::update_for(const socket_manager_ptr& mgr) {
auto fd = mgr->handle();
if (auto index = index_of(fd); index != -1) {
return update_for(index);
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second;
} else if (auto index = index_of(fd); index != -1) {
updates_.container().emplace_back(fd,
poll_update{pollset_[index].events, mgr});
return updates_.container().back().second;
} else {
updates_.container().emplace_back(fd, poll_update{0, mgr});
return updates_.container().back().second;
}
}
template <class T>
void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
pollset_updater::msg_buf buf;
if (ptr)
intrusive_ptr_add_ref(ptr);
buf[0] = static_cast<byte>(opcode);
auto value = reinterpret_cast<intptr_t>(ptr);
memcpy(buf.data() + 1, &value, sizeof(intptr_t));
ptrdiff_t res = -1;
{ // Lifetime scope of guard.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket)
res = write(write_handle_, buf);
}
if (res <= 0 && ptr)
intrusive_ptr_release(ptr);
}
short multiplexer::active_mask_of(const socket_manager_ptr& mgr) {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second.events;
} else if (auto index = index_of(fd); index != -1) {
return pollset_[index].events;
} else {
return updates_.emplace(fd, poll_update{0, mgr}).first->second;
return 0;
}
}
bool multiplexer::is_reading(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & input_mask);
}
bool multiplexer::is_writing(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & output_mask);
}
// -- internal callbacks the pollset updater -----------------------------------
void multiplexer::do_shutdown() {
// Note: calling apply_updates here is only safe because we know that the
// pollset updater runs outside of the for-loop in run_once.
CAF_LOG_DEBUG("initiate shutdown");
shutting_down_ = true;
apply_updates();
// Skip the first manager (the pollset updater).
for (size_t i = 1; i < managers_.size(); ++i) {
auto& mgr = managers_[i];
mgr->close_read();
update_for(static_cast<ptrdiff_t>(i)).events &= ~input_mask;
}
apply_updates();
}
void multiplexer::do_register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, no new reads are allowed.
if (shutting_down_)
mgr->close_read();
else if (!mgr->read_closed())
update_for(mgr).events |= input_mask;
}
void multiplexer::do_register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, we do allow managers to write whatever is currently
// pending but we make sure that all read channels are closed.
if (shutting_down_)
mgr->close_read();
if (!mgr->write_closed())
update_for(mgr).events |= output_mask;
}
void multiplexer::do_continue_reading(const socket_manager_ptr& mgr) {
if (!is_reading(mgr)) {
switch (mgr->handle_continue_reading()) {
default: // socket_manager::read_result::stop
// Nothing to do, bitmask may remain unchanged (i.e., not reading).
break;
case socket_manager::read_result::again:
update_for(mgr).events |= input_mask;
break;
case socket_manager::read_result::want_write:
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover(mgr);
return;
}
}
}
}
void multiplexer::do_continue_writing(const socket_manager_ptr& mgr) {
if (!is_writing(mgr)) {
switch (mgr->handle_continue_writing()) {
default: // socket_manager::read_result::stop
// Nothing to do, bitmask may remain unchanged (i.e., not writing).
break;
case socket_manager::write_result::again:
update_for(mgr).events |= output_mask;
break;
case socket_manager::write_result::want_read:
update_for(mgr).events = input_mask;
break;
case socket_manager::write_result::handover: {
do_handover(mgr);
return;
}
}
}
}
void multiplexer::do_discard(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
mgr->handle_error(sec::disposed);
update_for(mgr).events = 0;
}
void multiplexer::do_shutdown_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->read_closed()) {
mgr->close_read();
update_for(mgr).events &= ~input_mask;
}
}
void multiplexer::do_shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->write_closed()) {
mgr->close_write();
update_for(mgr).events &= ~output_mask;
}
}
void multiplexer::do_init(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_) {
error err;
if (owner_)
err = mgr->init(content(system().config()));
else
err = mgr->init(settings{});
if (err) {
CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case.
update_for(mgr).events = 0;
}
// Else: no update since the manager is supposed to call continue_reading
// and continue_writing as necessary.
}
}
......
......@@ -16,6 +16,8 @@
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
pollset_updater::pollset_updater(pipe_socket read_handle, multiplexer* parent)
: super(read_handle, parent) {
// nop
......@@ -25,28 +27,22 @@ pollset_updater::~pollset_updater() {
// nop
}
// -- interface functions ------------------------------------------------------
error pollset_updater::init(const settings&) {
CAF_LOG_TRACE("");
return nonblocking(handle(), true);
}
namespace {
auto as_mgr(intptr_t ptr) {
CAF_LOG_TRACE(CAF_ARG(ptr));
return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false};
}
void run_action(intptr_t ptr) {
CAF_LOG_TRACE(CAF_ARG(ptr));
auto f = action{intrusive_ptr{reinterpret_cast<action::impl*>(ptr), false}};
f.run();
}
} // namespace
pollset_updater::read_result pollset_updater::handle_read_event() {
CAF_LOG_TRACE("");
auto as_mgr = [](intptr_t ptr) {
return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false};
};
auto run_action = [](intptr_t ptr) {
auto f = action{intrusive_ptr{reinterpret_cast<action::impl*>(ptr), false}};
f.run();
};
for (;;) {
CAF_ASSERT((buf_.size() - buf_size_) > 0);
auto num_bytes = read(handle(), make_span(buf_.data() + buf_size_,
......@@ -62,9 +58,15 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
case code::register_reading:
mpx_->do_register_reading(as_mgr(ptr));
break;
case code::continue_reading:
mpx_->do_continue_reading(as_mgr(ptr));
break;
case code::register_writing:
mpx_->do_register_writing(as_mgr(ptr));
break;
case code::continue_writing:
mpx_->do_continue_writing(as_mgr(ptr));
break;
case code::init_manager:
mpx_->do_init(as_mgr(ptr));
break;
......@@ -100,16 +102,24 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
}
}
pollset_updater::read_result pollset_updater::handle_buffered_data() {
return read_result::again;
}
pollset_updater::read_result pollset_updater::handle_continue_reading() {
return read_result::again;
}
pollset_updater::write_result pollset_updater::handle_write_event() {
return write_result::stop;
}
void pollset_updater::handle_error(sec) {
// nop
pollset_updater::write_result pollset_updater::handle_continue_writing() {
return write_result::stop;
}
void pollset_updater::continue_reading() {
register_reading();
void pollset_updater::handle_error(sec) {
// nop
}
} // namespace caf::net
......@@ -43,13 +43,19 @@ void socket_manager::abort_reason(error reason) noexcept {
}
void socket_manager::register_reading() {
if (!read_closed())
mpx_->register_reading(this);
mpx_->register_reading(this);
}
void socket_manager::continue_reading() {
mpx_->continue_reading(this);
}
void socket_manager::register_writing() {
if (!write_closed())
mpx_->register_writing(this);
mpx_->register_writing(this);
}
void socket_manager::continue_writing() {
mpx_->continue_writing(this);
}
socket_manager_ptr socket_manager::do_handover() {
......
......@@ -26,6 +26,8 @@ using shared_atomic_count = std::shared_ptr<std::atomic<size_t>>;
class dummy_manager : public socket_manager {
public:
// -- constructors, destructors, and assignment operators --------------------
dummy_manager(stream_socket handle, multiplexer* parent, std::string name,
shared_atomic_count count)
: socket_manager(handle, parent), name(std::move(name)), count_(count) {
......@@ -39,14 +41,31 @@ public:
--*count_;
}
error init(const settings&) override {
return none;
}
// -- properties -------------------------------------------------------------
stream_socket handle() const noexcept {
return socket_cast<stream_socket>(handle_);
}
// -- testing DSL ------------------------------------------------------------
void send(string_view x) {
auto x_bytes = as_bytes(make_span(x));
wr_buf_.insert(wr_buf_.end(), x_bytes.begin(), x_bytes.end());
}
std::string receive() {
std::string result(reinterpret_cast<char*>(rd_buf_.data()), rd_buf_pos_);
rd_buf_pos_ = 0;
return result;
}
// -- interface functions ----------------------------------------------------
error init(const settings&) override {
return none;
}
read_result handle_read_event() override {
if (trigger_handover) {
MESSAGE(name << " triggered a handover");
......@@ -67,6 +86,14 @@ public:
}
}
read_result handle_buffered_data() override {
return read_result::again;
}
read_result handle_continue_reading() override {
return read_result::again;
}
write_result handle_write_event() override {
if (trigger_handover) {
MESSAGE(name << " triggered a handover");
......@@ -84,12 +111,12 @@ public:
: write_result::stop;
}
void handle_error(sec code) override {
FAIL("handle_error called with code " << code);
write_result handle_continue_writing() override {
return write_result::again;
}
void continue_reading() override {
FAIL("continue_reading called");
void handle_error(sec code) override {
FAIL("handle_error called with code " << code);
}
socket_manager_ptr make_next_manager(socket handle) override {
......@@ -102,16 +129,7 @@ public:
return next;
}
void send(string_view x) {
auto x_bytes = as_bytes(make_span(x));
wr_buf_.insert(wr_buf_.end(), x_bytes.begin(), x_bytes.end());
}
std::string receive() {
std::string result(reinterpret_cast<char*>(rd_buf_.data()), rd_buf_pos_);
rd_buf_pos_ = 0;
return result;
}
// --
bool trigger_handover = false;
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.openssl_transport
#include "caf/net/openssl_transport.hpp"
#include "net-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.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"
#include <filesystem>
#include <random>
// Note: these constants are defined in openssl_transport_constants.cpp.
extern std::string_view ca_pem;
extern std::string_view cert_1_pem;
extern std::string_view cert_2_pem;
extern std::string_view key_1_enc_pem;
extern std::string_view key_1_pem;
extern std::string_view key_2_pem;
using namespace caf;
using namespace caf::net;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
struct fixture : host_fixture {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture(){
multiplexer::block_sigpipe();
OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, nullptr);
// Make a directory name with 8 random (hex) character suffix.
std::string dir_name = "caf-net-test-";
std::random_device rd;
std::minstd_rand rng{rd()};
std::uniform_int_distribution<int> dist(0, 255);
for (int i = 0; i < 4; ++i)
detail::append_hex(dir_name, static_cast<uint8_t>(dist(rng)));
// Create the directory under /tmp (or its equivalent on non-POSIX).
namespace fs = std::filesystem;
tmp_dir = fs::temp_directory_path() / dir_name;
if (!fs::create_directory(tmp_dir)) {
std::cerr << "*** failed to create " << tmp_dir.string() << "\n";
abort();
}
// Create the .pem files on disk.
write_file("ca.pem", ca_pem);
write_file("cert.1.pem", cert_1_pem);
write_file("cert.2.pem", cert_1_pem);
write_file("key.1.enc.pem", key_1_enc_pem);
write_file("key.1.pem", key_1_pem);
write_file("key.2.pem", key_1_pem);
}
~fixture() {
// Clean up our files under /tmp.
if (!tmp_dir.empty())
std::filesystem::remove_all(tmp_dir);
}
std::string abs_path(std::string_view fname) {
auto path = tmp_dir / fname;
return path.string();
}
void write_file(std::string_view fname, std::string_view content) {
std::ofstream out{abs_path(fname)};
out << content;
}
std::filesystem::path tmp_dir;
};
class dummy_app {
public:
using input_tag = tag::stream_oriented;
explicit dummy_app(std::shared_ptr<bool> done, byte_buffer_ptr recv_buf)
: done_(std::move(done)), recv_buf_(std::move(recv_buf)) {
// nop
}
~dummy_app() {
*done_ = true;
}
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
MESSAGE("initialize dummy app");
parent->configure_read(receive_policy::exactly(4));
parent->begin_output();
auto& buf = parent->output_buffer();
caf::binary_serializer out{nullptr, buf};
static_cast<void>(out.apply(10));
parent->end_output();
return none;
}
template <class ParentPtr>
bool prepare_send(ParentPtr) {
return true;
}
template <class ParentPtr>
bool done_sending(ParentPtr) {
return true;
}
template <class ParentPtr>
void continue_reading(ParentPtr) {
// nop
}
template <class ParentPtr>
size_t consume(ParentPtr down, span<const byte> data, span<const byte>) {
MESSAGE("dummy app received " << data.size() << " bytes");
// Store the received bytes.
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
// Respond with the same data and return.
down->begin_output();
auto& out = down->output_buffer();
out.insert(out.end(), data.begin(), data.end());
down->end_output();
return static_cast<ptrdiff_t>(data.size());
}
template <class ParentPtr>
void abort(ParentPtr, const error& reason) {
MESSAGE("dummy_app::abort called: " << reason);
*done_ = true;
}
private:
std::shared_ptr<bool> done_;
byte_buffer_ptr recv_buf_;
};
// Simulates a remote SSL server.
void dummy_tls_server(stream_socket fd, std::string cert_file,
std::string key_file) {
namespace ssl = caf::net::openssl;
multiplexer::block_sigpipe();
// Make sure we close our socket.
auto guard = detail::make_scope_guard([fd] { close(fd); });
// Get and configure our SSL context.
auto ctx = ssl::make_ctx(TLS_server_method());
if (auto err = ssl::certificate_pem_file(ctx, cert_file)) {
std::cerr << "*** certificate_pem_file failed: " << ssl::fetch_error_str();
return;
}
if (auto err = ssl::private_key_pem_file(ctx, key_file)) {
std::cerr << "*** private_key_pem_file failed: " << ssl::fetch_error_str();
return;
}
// Perform SSL handshake.
auto f = net::openssl::policy::make(std::move(ctx), fd);
if (f.accept(fd) <= 0) {
std::cerr << "*** accept failed: " << ssl::fetch_error_str();
return;
}
// Do some ping-pong messaging.
for (int i = 0; i < 4; ++i) {
byte_buffer buf;
buf.resize(4);
f.read(fd, buf);
f.write(fd, buf);
}
// Graceful shutdown.
f.notify_close();
}
// Simulates a remote SSL client.
void dummy_tls_client(stream_socket fd) {
multiplexer::block_sigpipe();
// Make sure we close our socket.
auto guard = detail::make_scope_guard([fd] { close(fd); });
// Perform SSL handshake.
auto f = net::openssl::policy::make(TLS_client_method(), fd);
if (f.connect(fd) <= 0) {
ERR_print_errors_fp(stderr);
return;
}
// Do some ping-pong messaging.
for (int i = 0; i < 4; ++i) {
byte_buffer buf;
buf.resize(4);
f.read(fd, buf);
f.write(fd, buf);
}
// Graceful shutdown.
f.notify_close();
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("openssl::async_connect performs the client handshake") {
GIVEN("a connection to a TLS server") {
auto [serv_fd, client_fd] = unbox(make_stream_socket_pair());
if (auto err = net::nonblocking(client_fd, true))
FAIL("net::nonblocking failed: " << err);
std::thread server{dummy_tls_server, serv_fd, abs_path("cert.1.pem"),
abs_path("key.1.pem")};
WHEN("connecting as a client to an OpenSSL server") {
THEN("openssl::async_connect transparently calls SSL_connect") {
using stack_t = openssl_transport<dummy_app>;
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto done = std::make_shared<bool>(false);
auto buf = std::make_shared<byte_buffer>();
auto make_manager = [done, buf](stream_socket fd, multiplexer* ptr,
net::openssl::policy policy) {
return make_socket_manager<stack_t>(fd, ptr, std::move(policy), done,
buf);
};
auto on_connect_error = [](const error& reason) {
FAIL("connect failed: " << reason);
};
net::openssl::async_connect(client_fd, &mpx,
net::openssl::policy::make(SSLv23_method(),
client_fd),
make_manager, on_connect_error);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
int32_t value = 0;
static_cast<void>(src.apply(value));
CHECK_EQ(value, 10);
}
}
}
}
server.join();
}
}
SCENARIO("openssl::async_accept performs the server handshake") {
GIVEN("a socket that is connected to a client") {
auto [serv_fd, client_fd] = unbox(make_stream_socket_pair());
if (auto err = net::nonblocking(serv_fd, true))
FAIL("net::nonblocking failed: " << err);
std::thread client{dummy_tls_client, client_fd};
WHEN("acting as the OpenSSL server") {
THEN("openssl::async_accept transparently calls SSL_accept") {
using stack_t = openssl_transport<dummy_app>;
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto done = std::make_shared<bool>(false);
auto buf = std::make_shared<byte_buffer>();
auto make_manager = [done, buf](stream_socket fd, multiplexer* ptr,
net::openssl::policy policy) {
return make_socket_manager<stack_t>(fd, ptr, std::move(policy), done,
buf);
};
auto on_accept_error = [](const error& reason) {
FAIL("accept failed: " << reason);
};
auto ssl = net::openssl::policy::make(TLS_server_method(), serv_fd);
if (auto err = ssl.certificate_pem_file(abs_path("cert.1.pem")))
FAIL("certificate_pem_file failed: " << err);
if (auto err = ssl.private_key_pem_file(abs_path("key.1.pem")))
FAIL("privat_key_pem_file failed: " << err);
net::openssl::async_accept(serv_fd, &mpx, std::move(ssl), make_manager,
on_accept_error);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
int32_t value = 0;
static_cast<void>(src.apply(value));
CHECK_EQ(value, 10);
}
}
}
}
client.join();
}
}
END_FIXTURE_SCOPE()
#include <string_view>
std::string_view ca_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDmzCCAoOgAwIBAgIJAPLZ3e3WR0LLMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV\n"
"BAYTAlVTMQswCQYDVQQIDAJDQTERMA8GA1UEBwwIQmVya2VsZXkxIzAhBgNVBAoM\n"
"GkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRAwDgYDVQQDDAdmb28uYmFyMB4X\n"
"DTE3MDQyMTIzMjM0OFoXDTQyMDQyMTIzMjM0OFowZDELMAkGA1UEBhMCVVMxCzAJ\n"
"BgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEjMCEGA1UECgwaQUNNRSBTaWdu\n"
"aW5nIEF1dGhvcml0eSBJbmMxEDAOBgNVBAMMB2Zvby5iYXIwggEiMA0GCSqGSIb3\n"
"DQEBAQUAA4IBDwAwggEKAoIBAQC6ah79JvrN3LtcPzc9bX5THdzfidWncSmowotG\n"
"SZA3gcIhlsYD3P3RCaUR9g+f2Z/l0l7ciKgWetpNtN9hRBbg5/9tFzSpCb/Y0SSG\n"
"mwtHHovEqN2MWV+Od/MUcYSlL6MmPjSDc8Ls5NSniTr9OBE9J1jm72AsuzHasjPQ\n"
"D84TlWeTSs0HW3H5VxDb15xWYFnmgBo0JylDWj0+VWI+G41Xr7Ubu9699lWSFYF9\n"
"FCtdjzM5e1CGZOMvqUbUBus38BhUAdQ4fE7Dwnn8seKh+7HpJ70omIgqG87e4DBo\n"
"HbnMAkZaekk8+LBl0Hfu8c66Utw9mNoMIlFf/AMlJyLDIpNxAgMBAAGjUDBOMB0G\n"
"A1UdDgQWBBRc6Cbyshtny6jFWZtd/cEUUfMQ3DAfBgNVHSMEGDAWgBRc6Cbyshtn\n"
"y6jFWZtd/cEUUfMQ3DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCY\n"
"numHau9XYH5h4R2CoMdnKPMGk6V7UZZdbidLLcE4roQrYhnBdyhT69b/ySJK2Ee4\n"
"mt8T+E0wcg3k8Pr3aJEJA8eYYaJTqZvvv+TwuMBPjmE2rYSIpgMZv2tRD3XWMaQu\n"
"duLbwkclfejQHDD26xNXsxuU+WNB5kuvtNAg0oKFyFdNKElLQEcjyYzfxmCF4YX5\n"
"WmElijr1Tzuzd59rWPqC/tVIsh42vQ+P6g8Y1PDmo8eTUFveZ+wcr/eEPW6IOMrg\n"
"OW7tATcrgzNuXZ1umiuGgAPuIVqPfr9ssZHBqi9UOK9L/8MQrnOxecNUpPohcTFR\n"
"vq+Zqu15QV9T4BVWKHv0\n"
"-----END CERTIFICATE-----\n";
std::string_view cert_1_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDOjCCAiICCQDz7oMOR7Wm7jANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV\n"
"UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F\n"
"IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0\n"
"MjEyMzI2MzhaGA80NzU1MDMxOTIzMjYzOFowWDELMAkGA1UEBhMCVVMxCzAJBgNV\n"
"BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl\n"
"MRIwEAYDVQQDDAkxLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQDHobccAQQqbZANdOdx852W/nUzGcwpurOi8zbh9yCxMwnFMogW9AsqKEnd\n"
"sypV6Ah/cIz45PAgCdEg+1pc2DG7+E0+QlV4ChNwCDuk+FSWB6pqMTCdZcLeIwlA\n"
"GPp6Ow9v40dW7IFpDetFKXEo6kqEzR5P58Q0a6KpCtpsSMqhk57Py83wB9gPA1vp\n"
"s77kN7D5CI3oay86TA5j5nfFMT1X/77Hs24csW6CLnW/OD4f1RK79UgPd/kpPKQ1\n"
"jNq+hsR7NZTcfrAF1hcfScxnKaznO7WopSt1k75NqLdnSN1GIci2GpiXYKtXZ9l5\n"
"TErv2Oucpw/u+a/wjKlXjrgLL9lfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKuW\n"
"yKA2uuiNc9MKU+yVbNaP8kPaMb/wMvVaFG8FFFpCTZ0MFMLsqRpeqtj7gMK/gaJC\n"
"CQm4EyadjzfWFYDLkHzm6b7gI8digvvhjr/C2RJ5Qxr2P0iFP1buGq0CqnF20XgQ\n"
"Q+ecS43CZ77CfKfS6ZLPmAZMAwgFLImVyo5mkaTECo3+9oCnjDYBapvXLJqCJRhk\n"
"NosoTmGCV0HecWN4l38ojnXd44aSktQIND9iCLus3S6++nFnX5DHGZiv6/SnSO/6\n"
"+Op7nV0A6zKVcMOYQ0SGZPD8UQs5wDJgrR9LY29Ox5QBwu/5NqyvNSrMQaTop5vb\n"
"wkMInaq5lLxEYQDSLBc=\n"
"-----END CERTIFICATE-----\n";
std::string_view cert_2_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDOjCCAiICCQDz7oMOR7Wm7TANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV\n"
"UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F\n"
"IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0\n"
"MjEyMzI2MzNaGA80NzU1MDMxOTIzMjYzM1owWDELMAkGA1UEBhMCVVMxCzAJBgNV\n"
"BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl\n"
"MRIwEAYDVQQDDAkyLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQDG9fAvW9qnhjGRmLpA++RvOHaesu7NiUQvxf2F6gF2rLJV0/+DSA/PztEv\n"
"1WJaGhgJSaEqUjaHk3HY2EKlbGXEPh1mxqgPZD5plGlu4ddTwutxCxxQiFIBH+3N\n"
"MYRjJvDN7ozJoi4uRiK0QQdDWAqWJs5hMOJqeWd6MCgmVXSP6pj5/omGROktbHzD\n"
"9jJhAW9fnYFg6k+7cGN5kLmjqqnGhJkNtgom6uW9j73S9OpU/9Er2aZme6/PrujI\n"
"qYFBV81TJK2vmonWUITxfQjk9JVJYhBdHamGTxUqVBbuRcbAqdImV9yx4LoGh55u\n"
"L6xnsW4i0n1o1k+bh03NgwPz12O3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJmN\n"
"yCdInFIeEwomE6m+Su82BWBzkztOfMG9iRE+1aGuC8EQ8kju5NNMmWQcuKetNh0s\n"
"hJVdY6LXh27O0ZUllhQ/ig9c+dYFh6AHoZU7WjiNKIyWuyl4IAOkQ4IEdsBvst+l\n"
"0rafcdJjUpqNOMWeyg6x1s+gUD5o+ZLCZGCdkCW3fZbKgF52L+vmsSRiJg2JkYZW\n"
"8BPNNsroHZw2UXnLvRqUXCMf1hnOrlx/B0a0Q46hD4NQvl+OzlKaxfR2L2USmJ8M\n"
"XZvT6+i8fWvkGv18iunm23Yu+8Zf08wTXnbqXvmMda5upAYLmwD0YKIVYC3ycihh\n"
"mkYCYI6PVeH63a2/zxw=\n"
"-----END CERTIFICATE-----\n";
std::string_view key_1_enc_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"Proc-Type: 4,ENCRYPTED\n"
"DEK-Info: DES-EDE3-CBC,4FEF2D042C3D3463\n"
"\n"
"S3uHh5KH2Vsxzhw+B8zKsZNUG7CjMAvYfZwUptDnCms2dqxdfyzioxiRVNTN2MBb\n"
"nFxMKZayl5Xjr+04LhUpCAKdmvTfqGmWRFVSa8ol1hvqN/Vmg3a11B+3MD9pHXKu\n"
"ipgHMDTmE34DsDun6cXZ8W2k9ZSSJ20Y7qhcb4lTYUZuWq+94+Bup684COZytbyG\n"
"Eb6tOvVs/N6KeFhvd4vBL4YgTR3aY/vu1cHQQJeHvSYFtGKTKr7qpsfFfDFB7ExM\n"
"bqt2qbiUq1s+uZ0hlDjs+/TCZpC9Wuqpl9m05FrZhboubBRK9pw3wRVNvrpfwunH\n"
"2/E4Ta8/YaxypUwtk4saiPofKcJwvYx3Te38OHKKXgVKlein1p1e6eJNZt3qJaPd\n"
"BEBAR11iWstE+ghPS2WmrwT17cDqNA8wG5CRM9unYVWRMtO8Nl98OYars0vAXOgd\n"
"TI8bHDx6OHm2sJnkc56HAfu6hMdTbKzZXsWyOqLXUU79ioNmTvbMQUO0F+0VKDrv\n"
"3LikXewv69aOhhk+M96ng8KpWUW4z2kbDReiFuWQfjtiFNyEdS9wQIElpq5gU98r\n"
"EAeZoT6s8O0fkE/tt374ZJREEe9YEkMFcv06c96GPkMKnScncOFKRa1w0AblGxOM\n"
"vjICMLIk3CesFNWNzJk4MQu5ZVerJsz27UFocjNUvT6iM10rDCeU55W71bIPWRSb\n"
"SZL3JUi1gOeORrv3jAEamOd6YRlvPRYQjpDENg/qnx+bdXp5S3qHeHFSkRRHBCfy\n"
"UcYUba5hZJVDyBJXC4dkc2Ftcna149PZP1aKe9r//ZGa25PVZl1Mg6HhLROSJrEN\n"
"D2lqPMYcBDuSiZ6QK5ZHh2gsXJPktDgMhw4ZNIfIIqIpMDJ7rV4tQXXwB3d+iZte\n"
"/Ib/y1Mn8fZOo448nBqagpNFvU70eg7WkA4rzgiS3VzFYosP3g48PlZ07bEZ0awy\n"
"9lMGE0OKJv6x8EkVY0xHDxujzD5WVFEkanpGo8bWl1ofBrsnnDsFrvvRkDRsQbrG\n"
"WZkRqn7zLaGKOUirfKjfktajvPOAKMsvbl9KXAfp88sOtTZuYppCivZ/2qnzQ8xg\n"
"9/fuGfpAPXYfUu44+7dhrbvxJzrPp+GQRLUb1e3KA2MLgVak+yV1TIKdDGYek5vM\n"
"xDEmLLgOQqsE/OAcdXD2b/qIVYEmFPdut6N9K0m65AoqrN5nY6r0BzuC7usCgxnu\n"
"K6Z7Y2L6Ax4DjnKt21RRcjIVLLpea7KXnMiTogCYJ7+zmD82fE3i1xCDQs7R9b4T\n"
"tZhnkGfQS2RjOp3WaiV5rcmOKXDK3kYE+ezD0wbiVFDgB2jNdR7duOFqzW/WuPac\n"
"hsIPYn7VmjLB3W4p7PBf7OQ1fIiQ6IofeMvu8I8IOVRfC7eYDCJ0QKsHI8uA6j9T\n"
"vOCfjsZcpjygtN1Xy3uXIEZUVJJq/DF5t2Lxn95wDG9VfstJQR3KuDbPwYAWOYCh\n"
"W6gYu6HftIr3OlIYKurtSUFM4+iargMUaBVOUQ4XWLN8LzoyHNL3fsiGKn1afBV/\n"
"J3Qaqrn/YBGCAc2SOUCRl3F8yfWjmidXy6rQIzrjpF8aX2QSflB+fw==\n"
"-----END RSA PRIVATE KEY-----\n";
std::string_view key_1_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEogIBAAKCAQEAx6G3HAEEKm2QDXTncfOdlv51MxnMKbqzovM24fcgsTMJxTKI\n"
"FvQLKihJ3bMqVegIf3CM+OTwIAnRIPtaXNgxu/hNPkJVeAoTcAg7pPhUlgeqajEw\n"
"nWXC3iMJQBj6ejsPb+NHVuyBaQ3rRSlxKOpKhM0eT+fENGuiqQrabEjKoZOez8vN\n"
"8AfYDwNb6bO+5Dew+QiN6GsvOkwOY+Z3xTE9V/++x7NuHLFugi51vzg+H9USu/VI\n"
"D3f5KTykNYzavobEezWU3H6wBdYXH0nMZyms5zu1qKUrdZO+Tai3Z0jdRiHIthqY\n"
"l2CrV2fZeUxK79jrnKcP7vmv8IypV464Cy/ZXwIDAQABAoIBAC0Y7jmoTR2clJ9F\n"
"modWhnI215kMqd9/atdT5EEVx8/f/MQMj0vII8GJSm6H6/duLIVFksMjTM+gCBtQ\n"
"TPCOcmXJSQHYkGBGvm9fnMG+y7T81FWa+SWFeIkgFxXgzqzQLMOU72fGk9F8sHp2\n"
"Szb3/o+TmtZoQB2rdxqC9ibiJsxrG5IBVKkzlSPv3POkPXwSb1HcETqrTwefuioj\n"
"WMuMrqtm5Y3HddJ5l4JEF5VA3KrsfXWl3JLHH0UViemVahiNjXQAVTKAXIL1PHAV\n"
"J2MCEvlpA7sIgXREbmvPvZUTkt3pIqhVjZVJ7tHiSnSecqNTbuxcocnhKhZrHNtC\n"
"v2zYKHkCgYEA6cAIhz0qOGDycZ1lf9RSWw0RO1hO8frATMQNVoFVuJJCVL22u96u\n"
"0FvJ0JGyYbjthULnlOKyRe7DUL5HRLVS4D7vvKCrgwDmsJp1VFxMdASUdaBfq6aX\n"
"oKLUW4q7kC2lQcmK/PVRYwp2GQSx8bodWe+DtXUY/GcN03znY8mhSB0CgYEA2qJK\n"
"1GSZsm6kFbDek3BiMMHfO+X819owB2FmXiH+GQckyIZu9xA3HWrkOWTqwglEvzfO\n"
"qzFF96E9iEEtseAxhcM8gPvfFuXiUj9t2nH/7SzMnVGikhtYi0p6jrgHmscc4NBx\n"
"AOUA15kYEFOGqpZfl2uuKqgHidrHdGkJzzSUBqsCgYAVCjb6TVQejQNlnKBFOExN\n"
"a8iwScuZVlO21TLKJYwct/WGgSkQkgO0N37b6jFfQHEIvLPxn9IiH1KvUuFBWvzh\n"
"uGiF1wR5HzykitKizEgJbVwbllrmLXGagO2Sa9NkL+efG1AKYt53hrqIl/aYZoM7\n"
"1CZL0AV2uqPw9F4zijOdNQKBgH1WmvWGMsKjQTgaLI9z1ybCjjqlj70jHXOtt+Tx\n"
"Md2hRcobn5PN3PrlY68vlpHkhF/nG3jzB3x+GGt7ijm2IE3h7la3jl5vLb8fE9gu\n"
"kJykmSz7Nurx+GHqMbaN8/Ycfga4GIB9yGzRHIWHjOVQzb5eAfv8Vk4GeV/YM8Jx\n"
"Dwd/AoGAILn8pVC9dIFac2BDOFU5y9ZvMmZAvwRxh9vEWewNvkzg27vdYc+rCHNm\n"
"I7H0S/RqfqVeo0ApE5PQ8Sll6RvxN/mbSQo9YeCDGQ1r1rNe4Vs12GAYXAbE4ipf\n"
"BTdqMbieumB/zL97iK5baHUFEJ4VRtLQhh/SOXgew/BF8ccpilI=\n"
"-----END RSA PRIVATE KEY-----\n";
std::string_view key_2_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpQIBAAKCAQEAxvXwL1vap4YxkZi6QPvkbzh2nrLuzYlEL8X9heoBdqyyVdP/\n"
"g0gPz87RL9ViWhoYCUmhKlI2h5Nx2NhCpWxlxD4dZsaoD2Q+aZRpbuHXU8LrcQsc\n"
"UIhSAR/tzTGEYybwze6MyaIuLkYitEEHQ1gKlibOYTDianlnejAoJlV0j+qY+f6J\n"
"hkTpLWx8w/YyYQFvX52BYOpPu3BjeZC5o6qpxoSZDbYKJurlvY+90vTqVP/RK9mm\n"
"Znuvz67oyKmBQVfNUyStr5qJ1lCE8X0I5PSVSWIQXR2phk8VKlQW7kXGwKnSJlfc\n"
"seC6Boeebi+sZ7FuItJ9aNZPm4dNzYMD89djtwIDAQABAoIBAQDDaWquGRl40GR/\n"
"C/JjQQPr+RkIZdYGKXu/MEcA8ATf+l5tzfp3hp+BCzCKOpqOxHI3LQoN9xF3t2lq\n"
"AX3z27NYO2nFN/h4pYxnRk0Hiulia1+zd6YnsrxYPnPhxXCxsd1xZYsBvzh8WoZb\n"
"ZEMt8Zr0PskUzF6VFQh9Ci9k9ym07ooo/KqP4wjXsm/JK1ueOCTpRtabrBI1icrV\n"
"iTaw1JEGqlTAQ92vg3pXqSG5yy69Krt7miZZtiOA5mJ90VrHtlNSgp31AOcVv/Ve\n"
"/LMIwJp9EzTN+4ipT7AKPeJAoeVqpFjQk+2cW44zJ7xyzw73pTs5ErxkEIhQOp4M\n"
"ak2iMg4BAoGBAOivDZSaOcTxEB3oKxYvN/jL9eU2Io9wdZwAZdYQpkgc8lkM9elW\n"
"2rbHIwifkDxQnZbl3rXM8xmjA4c5PSCUYdPnLvx6nsUJrWTG0RjakHRliSLthNEC\n"
"LpL9MR1aQblyz1D/ulWTFOCNvHU7m3XI3RVJEQWu3qQ5pCndzT56wXjnAoGBANrl\n"
"zKvR9o2SONU8SDIcMzXrO2647Z8yXn4Kz1WhWojhRQQ1V3VOLm8gBwv8bPtc7LmE\n"
"MSX5MIcxRoHu7D98d53hd+K/ZGYV2h/638qaIEgZDf2oa8QylBgvoGljoy1DH8nN\n"
"KKOgksqWK0AAEkP0+S4IFugTxHVanw8JUkV0gVSxAoGBANIRUGJrxmHt/M3zUArs\n"
"QE0G3o28DQGQ1y0rEsVrLKQINid9UvoBpt3C9PcRD2fUpCGakDFzwbnQeRv46h3i\n"
"uFtV6Q6aKYLcFMXZ1ObqU+Yx0NhOtUz4+lFL8q58UL/7Tf3jkjc13XBJpe31DYoN\n"
"+MMBvzNxR6HeRD5j96tDqi3bAoGAT57SqZS/l5MeNQGuSPvU7MHZZlbBp+xMTpBk\n"
"BgOgyLUXw4Ybf8GmRiliJsv0YCHWwUwCDIvtSN91hAGB0T3WzIiccM+pFzDPnF5G\n"
"VI1nPJJQcnl2aXD0SS/ZqzvguK/3uhFzvMDFZAbnSGo+OpW6pTGwE05NYVpLDM8Z\n"
"K8ZK3KECgYEApNoI5Mr5tmtjq4sbZrgQq6cMlfkIj9gUubOzFCryUb6NaB38Xqkp\n"
"2N3/jqdkR+5ZiKOYhsYj+Iy6U3jyqiEl9VySYTfEIfP/ky1CD0a8/EVC9HR4iG8J\n"
"im6G7/osaSBYAZctryLqVJXObTelgEy/EFwW9jW8HVph/G+ljmHOmuQ=\n"
"-----END RSA PRIVATE KEY-----\n";
......@@ -125,6 +125,7 @@ struct fixture : test_coordinator_fixture<>, host_fixture {
}
bool handle_io_event() override {
mm.mpx().apply_updates();
return mm.mpx().poll_once(false);
}
......
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