Unverified Commit 4aa07140 authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1333

Use proper interfaces for caf-net
parents 65ecf353 787d4259
......@@ -45,6 +45,7 @@ cmake_dependent_option(CAF_ENABLE_OPENSSL_MODULE "Build OpenSSL module" ON
# -- CAF options with non-boolean values ---------------------------------------
set(CAF_LOG_LEVEL "QUIET" CACHE STRING "Set log verbosity of CAF components")
set(CAF_EXCLUDE_TESTS "" CACHE STRING "List of excluded test suites")
set(CAF_SANITIZERS "" CACHE STRING
"Comma separated sanitizers, e.g., 'address,undefined'")
set(CAF_BUILD_INFO_FILE_PATH "" CACHE FILEPATH
......@@ -112,11 +113,13 @@ if(CAF_ENABLE_TESTING)
enable_testing()
function(caf_add_test_suites target)
foreach(suiteName ${ARGN})
string(REPLACE "." "/" suitePath ${suiteName})
target_sources(${target} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/test/${suitePath}.cpp")
add_test(NAME ${suiteName}
COMMAND ${target} -r300 -n -v5 -s "^${suiteName}$")
if(NOT ${suiteName} IN_LIST CAF_EXCLUDE_TESTS)
string(REPLACE "." "/" suitePath ${suiteName})
target_sources(${target} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/test/${suitePath}.cpp")
add_test(NAME ${suiteName}
COMMAND ${target} -r300 -n -v5 -s "^${suiteName}$")
endif()
endforeach()
endfunction()
endif()
......
......@@ -126,8 +126,10 @@ config = [
['FreeBSD', [
numCores: 4,
builds: ['debug', 'release'],
extraBuildFlags: [
extraDebugBuildFlags: [
'CAF_SANITIZERS:STRING=address',
// Deadlocks on FreeBSD in CRYPTO_THREAD_run_once under ASAN.
'CAF_EXCLUDE_TESTS:STRING=net.ssl.transport'
],
]],
// Non-UNIX systems.
......
......@@ -5,7 +5,9 @@
#include "caf/caf_main.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/web_socket/accept.hpp"
#include "caf/scheduled_actor/flow.hpp"
......
......@@ -8,7 +8,9 @@
#include "caf/cow_tuple.hpp"
#include "caf/event_based_actor.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/web_socket/accept.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/span.hpp"
......
......@@ -270,7 +270,6 @@ caf_add_component(
detail.serialized_size
detail.type_id_list_builder
detail.unique_function
detail.unordered_flat_map
dictionary
dsl
dynamic_spawn
......@@ -360,6 +359,7 @@ caf_add_component(
typed_response_promise
typed_spawn
unit
unordered_flat_map
uri
uuid)
......
......@@ -20,6 +20,7 @@ enum class hex_format {
template <hex_format format = hex_format::uppercase, class Buf = std::string>
void append_hex(Buf& result, const void* vptr, size_t n) {
using value_type = typename Buf::value_type;
if (n == 0)
return;
auto xs = reinterpret_cast<const uint8_t*>(vptr);
......@@ -30,8 +31,8 @@ void append_hex(Buf& result, const void* vptr, size_t n) {
tbl = "0123456789abcdef";
for (size_t i = 0; i < n; ++i) {
auto c = xs[i];
result.push_back(tbl[c >> 4]);
result.push_back(tbl[c & 0x0F]);
result.push_back(static_cast<value_type>(tbl[c >> 4]));
result.push_back(static_cast<value_type>(tbl[c & 0x0F]));
}
}
......
......@@ -11,7 +11,6 @@
#include "caf/cow_vector.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/disposable.hpp"
#include "caf/flow/coordinated.hpp"
#include "caf/flow/coordinator.hpp"
......@@ -33,6 +32,7 @@
#include "caf/make_counted.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/unordered_flat_map.hpp"
#include <cstddef>
#include <functional>
......
......@@ -112,6 +112,9 @@ public:
/// Returns an observer that ignores any of its inputs.
static observer ignore();
/// Returns an observer that disposes its subscription immediately.
static observer cancel();
private:
intrusive_ptr<impl> pimpl_;
};
......@@ -170,6 +173,26 @@ private:
flow::subscription sub_;
};
template <class T>
class canceling_observer : public flow::observer_impl_base<T> {
public:
void on_next(const T&) override {
// nop
}
void on_error(const error&) override {
// nop
}
void on_complete() override {
// nop
}
void on_subscribe(flow::subscription sub) override {
sub.dispose();
}
};
template <class OnNextSignature>
struct on_next_trait;
......@@ -262,6 +285,11 @@ observer<T> observer<T>::ignore() {
return observer<T>{make_counted<detail::ignoring_observer<T>>()};
}
template <class T>
observer<T> observer<T>::cancel() {
return observer<T>{make_counted<detail::canceling_observer<T>>()};
}
/// Creates an observer from given callbacks.
/// @param on_next Callback for handling incoming elements.
/// @param on_error Callback for handling an error.
......
......@@ -40,7 +40,7 @@ public:
using input_ptr = std::unique_ptr<input_t>;
using input_map = detail::unordered_flat_map<input_key, input_ptr>;
using input_map = unordered_flat_map<input_key, input_ptr>;
// -- constructors, destructors, and assignment operators --------------------
......
......@@ -8,6 +8,7 @@
#include <cstdint>
#include <memory>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
......@@ -48,6 +49,9 @@ template <class Iterator, class Sentinel = Iterator> struct parser_state;
template <class, class, int> class actor_cast_access;
template <class K, class V, class = std::allocator<std::pair<K, V>>>
class unordered_flat_map;
// -- variadic templates -------------------------------------------------------
template <class...> class const_typed_message_view;
......
......@@ -19,7 +19,6 @@
#include "caf/actor_traits.hpp"
#include "caf/detail/behavior_stack.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/disposable.hpp"
#include "caf/error.hpp"
#include "caf/extend.hpp"
......@@ -45,6 +44,7 @@
#include "caf/response_handle.hpp"
#include "caf/sec.hpp"
#include "caf/telemetry/timer.hpp"
#include "caf/unordered_flat_map.hpp"
namespace caf {
......@@ -546,7 +546,7 @@ protected:
std::forward_list<pending_response> awaited_responses_;
/// Stores callbacks for multiplexed responses.
detail::unordered_flat_map<message_id, behavior> multiplexed_responses_;
unordered_flat_map<message_id, behavior> multiplexed_responses_;
/// Customization point for setting a default `message` callback.
default_handler default_handler_;
......
......@@ -175,6 +175,10 @@ enum class sec : uint8_t {
disposed,
/// Failed to open a resource.
cannot_open_resource,
/// Received malformed data on a network socket.
protocol_error,
/// Encountered faulty logic in the program.
logic_error,
};
// --(rst-sec-end)--
......
......@@ -10,13 +10,13 @@
#include "caf/detail/comparable.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/fwd.hpp"
#include "caf/raise_error.hpp"
namespace caf::detail {
namespace caf {
/// A map abstraction with an unsorted `std::vector` providing `O(n)` lookup.
template <class Key, class T,
class Allocator = std::allocator<std::pair<Key, T>>>
template <class Key, class T, class Allocator>
class unordered_flat_map {
public:
// -- member types ----------------------------------------------------------
......@@ -209,7 +209,7 @@ public:
auto i = find(key);
if (i == end())
CAF_RAISE_ERROR(std::out_of_range,
"caf::detail::unordered_flat_map::at out of range");
"caf::unordered_flat_map::at out of range");
return i->second;
}
......@@ -278,4 +278,4 @@ bool operator>=(const unordered_flat_map<K, T, A>& xs,
return !(xs < ys);
}
} // namespace caf::detail
} // namespace caf
......@@ -10,7 +10,6 @@
#include "caf/detail/comparable.hpp"
#include "caf/detail/core_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/fwd.hpp"
#include "caf/hash/fnv.hpp"
#include "caf/inspector_access.hpp"
......@@ -18,6 +17,7 @@
#include "caf/intrusive_ptr.hpp"
#include "caf/ip_address.hpp"
#include "caf/make_counted.hpp"
#include "caf/unordered_flat_map.hpp"
namespace caf {
......@@ -59,7 +59,7 @@ public:
using path_list = std::vector<std::string_view>;
/// Separates the query component into key-value pairs.
using query_map = detail::unordered_flat_map<std::string, std::string>;
using query_map = unordered_flat_map<std::string, std::string>;
class CAF_CORE_EXPORT impl_type {
public:
......
......@@ -2,9 +2,9 @@
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE detail.unordered_flat_map
#define CAF_SUITE unordered_flat_map
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/unordered_flat_map.hpp"
#include "core-test.hpp"
......@@ -12,7 +12,7 @@
#include <utility>
#include <vector>
namespace caf::detail {
namespace caf {
template <class T>
bool operator==(const unordered_flat_map<int, T>& xs,
......@@ -26,14 +26,14 @@ bool operator==(const std::vector<std::pair<int, T>>& xs,
return ys == xs;
}
} // namespace caf::detail
} // namespace caf
using std::make_pair;
using std::pair;
using std::string;
using std::vector;
using caf::detail::unordered_flat_map;
using caf::unordered_flat_map;
using namespace caf;
......
......@@ -4,6 +4,8 @@ file(GLOB_RECURSE CAF_NET_HEADERS "caf/*.hpp")
# -- add targets ---------------------------------------------------------------
configure_file(test/pem.cpp.in test/pem.cpp @ONLY)
caf_add_component(
net
DEPENDENCIES
......@@ -18,39 +20,65 @@ caf_add_component(
net.http.method
net.http.status
net.operation
net.ssl.dtls
net.ssl.errc
net.ssl.format
net.ssl.tls
net.stream_transport_error
net.web_socket.status
HEADERS
${CAF_NET_HEADERS}
SOURCES
src/datagram_socket.cpp
src/detail/convert_ip_endpoint.cpp
src/detail/rfc6455.cpp
src/host.cpp
src/ip.cpp
src/multiplexer.cpp
src/net/abstract_actor_shell.cpp
src/net/actor_shell.cpp
src/net/datagram_socket.cpp
src/net/generic_lower_layer.cpp
src/net/generic_upper_layer.cpp
src/net/http/header.cpp
src/net/http/lower_layer.cpp
src/net/http/method.cpp
src/net/http/server.cpp
src/net/http/status.cpp
src/net/http/upper_layer.cpp
src/net/http/v1.cpp
src/net/ip.cpp
src/net/length_prefix_framing.cpp
src/net/message_oriented.cpp
src/net/middleman.cpp
src/net/multiplexer.cpp
src/net/network_socket.cpp
src/net/pipe_socket.cpp
src/net/pollset_updater.cpp
src/net/socket.cpp
src/net/socket_event_layer.cpp
src/net/socket_manager.cpp
src/net/ssl/connection.cpp
src/net/ssl/context.cpp
src/net/ssl/dtls.cpp
src/net/ssl/format.cpp
src/net/ssl/startup.cpp
src/net/ssl/tls.cpp
src/net/ssl/transport.cpp
src/net/stream_oriented.cpp
src/net/stream_socket.cpp
src/net/stream_transport.cpp
src/net/tcp_accept_socket.cpp
src/net/tcp_stream_socket.cpp
src/net/this_host.cpp
src/net/udp_datagram_socket.cpp
src/net/web_socket/client.cpp
src/net/web_socket/default_trait.cpp
src/net/web_socket/frame.cpp
src/net/web_socket/framing.cpp
src/net/web_socket/handshake.cpp
src/network_socket.cpp
src/pipe_socket.cpp
src/pollset_updater.cpp
src/socket.cpp
src/socket_manager.cpp
src/stream_socket.cpp
src/tcp_accept_socket.cpp
src/tcp_stream_socket.cpp
src/udp_datagram_socket.cpp
src/net/web_socket/lower_layer.cpp
src/net/web_socket/server.cpp
src/net/web_socket/upper_layer.cpp
TEST_SOURCES
${CMAKE_CURRENT_BINARY_DIR}/test/pem.cpp
test/net-test.cpp
test/pem.cpp
TEST_SUITES
detail.convert_ip_endpoint
detail.rfc6455
......@@ -68,6 +96,7 @@ caf_add_component(
net.producer_adapter
net.socket
net.socket_guard
net.ssl.transport
net.stream_socket
net.stream_transport
net.tcp_socket
......@@ -76,21 +105,3 @@ caf_add_component(
net.web_socket.client
net.web_socket.handshake
net.web_socket.server)
# Our OpenSSL test currently depends on <filesystem>.
check_cxx_source_compiles("
#include <cstdlib>
#include <filesystem>
int main(int, char**) {
auto cwd = std::filesystem::current_path();
auto str = cwd.string();
return str.empty() ? EXIT_FAILURE : EXIT_SUCCESS;
}
"
CAF_NET_HAS_STD_FILESYSTEM)
if(CAF_NET_HAS_STD_FILESYSTEM)
caf_add_test_suites(caf-net-test net.openssl_transport)
else()
message(STATUS "<filesystem> not working, skip OpenSSL test in CAF::net")
endif()
......@@ -7,7 +7,6 @@
#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"
......@@ -18,6 +17,7 @@
#include "caf/net/fwd.hpp"
#include "caf/none.hpp"
#include "caf/policy/normal_messages.hpp"
#include "caf/unordered_flat_map.hpp"
namespace caf::net {
......@@ -40,7 +40,9 @@ public:
using mailbox_type = intrusive::fifo_inbox<mailbox_policy>;
using fallback_handler = unique_callback_ptr<result<message>(message&)>;
using fallback_handler_sig = result<message>(abstract_actor_shell*, message&);
using fallback_handler = unique_callback_ptr<fallback_handler_sig>;
// -- constructors, destructors, and assignment operators --------------------
......@@ -48,6 +50,10 @@ public:
~abstract_actor_shell() override;
// -- properties -------------------------------------------------------------
bool terminated() const noexcept;
// -- state modifiers --------------------------------------------------------
/// Detaches the shell from its owner and closes the mailbox.
......@@ -56,7 +62,20 @@ public:
/// Overrides the default handler for unexpected messages.
template <class F>
void set_fallback(F f) {
fallback_ = make_type_erased_callback(std::move(f));
using msg_res_t = result<message>;
using self_ptr_t = abstract_actor_shell*;
if constexpr (std::is_invocable_r_v<msg_res_t, F, self_ptr_t, message&>) {
fallback_ = make_type_erased_callback(std::move(f));
} else {
static_assert(std::is_invocable_r_v<msg_res_t, F, message&>,
"illegal signature for the fallback handler: must be "
"'result<message>(message&)' or "
"'result<message>(abstract_actor_shell*, message&)'");
auto g = [f = std::move(f)](self_ptr_t, message& msg) mutable {
return f(msg);
};
fallback_ = make_type_erased_callback(std::move(g));
}
}
// -- mailbox access ---------------------------------------------------------
......@@ -114,7 +133,7 @@ protected:
fallback_handler fallback_;
// Stores callbacks for multiplexed responses.
detail::unordered_flat_map<message_id, behavior> multiplexed_responses_;
unordered_flat_map<message_id, behavior> multiplexed_responses_;
};
} // namespace caf::net
......@@ -4,68 +4,67 @@
#pragma once
#include "caf/logger.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/send.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/settings.hpp"
namespace caf::net {
/// 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 {
class connection_acceptor : public socket_event_layer {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::io_event_oriented;
using socket_type = Socket;
using factory_type = Factory;
using read_result = typename socket_manager::read_result;
using read_result = typename socket_event_layer::read_result;
using write_result = typename socket_manager::write_result;
using write_result = typename socket_event_layer::write_result;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit connection_acceptor(size_t limit, Ts&&... xs)
: factory_(std::forward<Ts>(xs)...), limit_(limit) {
explicit connection_acceptor(Socket fd, size_t limit, Ts&&... xs)
: fd_(fd), limit_(limit), factory_(std::forward<Ts>(xs)...) {
// nop
}
// -- interface functions ----------------------------------------------------
// -- factories --------------------------------------------------------------
template <class... Ts>
static std::unique_ptr<connection_acceptor>
make(Socket fd, size_t limit, Ts&&... xs) {
return std::make_unique<connection_acceptor>(fd, limit,
std::forward<Ts>(xs)...);
}
// -- implementation of socket_event_layer -----------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
error init(socket_manager* owner, const settings& cfg) override {
CAF_LOG_TRACE("");
owner_ = owner;
cfg_ = cfg;
if (auto err = factory_.init(owner, cfg))
return err;
down->register_reading();
owner->register_reading();
return none;
}
template <class LowerLayerPtr>
read_result handle_read_event(LowerLayerPtr down) {
read_result handle_read_event() override {
CAF_LOG_TRACE("");
if (auto x = accept(down->handle())) {
socket_manager_ptr child = factory_.make(*x, owner_->mpx_ptr());
if (auto x = accept(fd_)) {
socket_manager_ptr child = factory_.make(owner_->mpx_ptr(), *x);
if (!child) {
CAF_LOG_ERROR("factory failed to create a new child");
down->abort_reason(sec::runtime_error);
return read_result::stop;
return read_result::abort;
}
if (auto err = child->init(cfg_)) {
CAF_LOG_ERROR("failed to initialize new child:" << err);
down->abort_reason(std::move(err));
return read_result::stop;
return read_result::abort;
}
if (limit_ == 0) {
return read_result::again;
......@@ -78,74 +77,36 @@ public:
}
}
template <class LowerLayerPtr>
static read_result handle_buffered_data(LowerLayerPtr) {
read_result handle_buffered_data() override {
return read_result::again;
}
template <class LowerLayerPtr>
static read_result handle_continue_reading(LowerLayerPtr) {
read_result handle_continue_reading() override {
return read_result::again;
}
template <class LowerLayerPtr>
write_result handle_write_event(LowerLayerPtr) {
write_result handle_write_event() override {
CAF_LOG_ERROR("connection_acceptor received write event");
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) {
void abort(const error& reason) override {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason);
factory_.abort(reason);
}
private:
factory_type factory_;
socket_manager* owner_;
Socket fd_;
size_t limit_;
size_t accepted_ = 0;
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
}
connection_acceptor_factory_adapter(connection_acceptor_factory_adapter&&)
= default;
error init(socket_manager*, const settings&) {
return none;
}
factory_type factory_;
template <class Socket>
socket_manager_ptr make(Socket connected_socket, multiplexer* mpx) {
return f_(std::move(connected_socket), mpx);
}
socket_manager* owner_ = nullptr;
void abort(const error&) {
// nop
}
size_t accepted_ = 0;
private:
FunctionObject f_;
settings cfg_;
};
} // namespace caf::net
......@@ -4,27 +4,21 @@
#pragma once
#include <memory>
#include "caf/intrusive_ptr.hpp"
#include "caf/type_id.hpp"
#include <memory>
#include <vector>
namespace caf::net {
// -- templates ----------------------------------------------------------------
template <class UpperLayer>
class stream_transport;
template <class Factory>
class datagram_transport;
template <class Application, class IdType = unit_t>
class transport_worker;
template <class Transport, class IdType = unit_t>
class transport_worker_dispatcher;
template <class... Sigs>
class typed_actor_shell;
......@@ -38,16 +32,18 @@ class actor_shell_ptr;
class middleman;
class multiplexer;
class socket_manager;
class this_host;
// -- structs ------------------------------------------------------------------
struct datagram_socket;
struct network_socket;
struct pipe_socket;
struct receive_policy;
struct socket;
struct stream_socket;
struct tcp_accept_socket;
struct tcp_stream_socket;
struct datagram_socket;
struct udp_datagram_socket;
// -- smart pointer aliases ----------------------------------------------------
......@@ -56,4 +52,8 @@ using multiplexer_ptr = std::shared_ptr<multiplexer>;
using socket_manager_ptr = intrusive_ptr<socket_manager>;
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
// -- miscellaneous aliases ----------------------------------------------------
using text_buffer = std::vector<char>;
} // namespace caf::net
// 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/detail/net_export.hpp"
namespace caf::net {
/// Bundles protocol-independent, generic member functions for (almost all)
/// lower layers.
class CAF_NET_EXPORT generic_lower_layer {
public:
virtual ~generic_lower_layer();
/// Queries whether the output device can accept more data straight away.
[[nodiscard]] virtual bool can_send_more() const noexcept = 0;
/// Halt receiving of additional bytes or messages.
virtual void suspend_reading() = 0;
/// Queries whether the lower layer is currently configured to halt receiving
/// of additional bytes or messages.
[[nodiscard]] virtual bool stopped_reading() const noexcept = 0;
};
} // namespace caf::net
// 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/detail/net_export.hpp"
#include "caf/fwd.hpp"
namespace caf::net {
/// Bundles protocol-independent, generic member functions for (almost all)
/// upper layers.
class CAF_NET_EXPORT generic_upper_layer {
public:
virtual ~generic_upper_layer();
/// Prepares any pending data for sending.
/// @returns `false` in case of an error to cause the lower layer to stop,
/// `true` otherwise.
[[nodiscard]] virtual bool prepare_send() = 0;
/// Queries whether all pending data has been sent. The lower calls this
/// function to decide whether it has to wait for write events on the socket.
[[nodiscard]] virtual bool done_sending() = 0;
/// Called by the lower layer after some event triggered re-registering the
/// socket manager for read operations after it has been stopped previously
/// by the read policy. May restart consumption of bytes or messages.
virtual void continue_reading() = 0;
/// Called by the lower layer for cleaning up any state in case of an error.
virtual void abort(const error& reason) = 0;
};
} // namespace caf::net
// 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
......@@ -7,6 +7,6 @@
namespace caf::net::http {
/// Stores context information for a request. For HTTP/2, this is the stream ID.
struct context {};
class context {};
} // namespace caf::net::http
......@@ -4,13 +4,23 @@
#pragma once
#include "caf/net/web_socket/framing.hpp"
#include "caf/fwd.hpp"
namespace caf::net {
#include <string_view>
template <class UpperLayer>
using web_socket_framing
[[deprecated("use caf::net::web_socket::framing instead")]]
= web_socket::framing<UpperLayer>;
namespace caf::net::http {
} // namespace caf::net
class context;
class header;
class lower_layer;
class server;
class upper_layer;
using header_fields_map
= unordered_flat_map<std::string_view, std::string_view>;
enum class method : uint8_t;
enum class status : uint16_t;
} // namespace caf::net::http
......@@ -4,7 +4,7 @@
#pragma once
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/unordered_flat_map.hpp"
#include <string_view>
......@@ -12,6 +12,6 @@ namespace caf::net::http {
/// Convenience type alias for a key-value map storing header fields.
using header_fields_map
= detail::unordered_flat_map<std::string_view, std::string_view>;
= unordered_flat_map<std::string_view, std::string_view>;
} // namespace caf::net::http
// 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/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/http/fwd.hpp"
namespace caf::net::http {
/// Parses HTTP requests and passes them to the upper layer.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
/// Sends the next header to the client.
virtual bool
send_header(context ctx, status code, const header_fields_map& fields)
= 0;
/// Sends the payload after the header.
virtual bool send_payload(context, const_byte_span bytes) = 0;
/// Sends a chunk of data if the full payload is unknown when starting to
/// send.
virtual bool send_chunk(context, const_byte_span bytes) = 0;
/// Sends the last chunk, completing a chunked payload.
virtual bool send_end_of_chunks() = 0;
/// Terminates an HTTP context.
virtual void fin(context) = 0;
/// Convenience function for sending header and payload. Automatically sets
/// the header fields `Content-Type` and `Content-Length`.
bool send_response(context ctx, status codek, std::string_view content_type,
const_byte_span content);
};
} // namespace caf::net::http
......@@ -6,6 +6,7 @@
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/fwd.hpp"
#include <cstdint>
#include <string>
......
......@@ -6,7 +6,6 @@
#include "caf/byte_span.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp"
......@@ -14,32 +13,28 @@
#include "caf/net/http/context.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/lower_layer.hpp"
#include "caf/net/http/status.hpp"
#include "caf/net/http/upper_layer.hpp"
#include "caf/net/http/v1.hpp"
#include "caf/net/hypertext_oriented_layer_ptr.hpp"
#include "caf/net/message_flow_bridge.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/hypertext_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include "caf/unordered_flat_map.hpp"
#include <algorithm>
namespace caf::net::http {
/// Implements the server part for the HTTP Protocol as defined in RFC 7231.
template <class UpperLayer>
class server {
class server : public stream_oriented::upper_layer, public http::lower_layer {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using output_tag = tag::hypertext_oriented;
using header_fields_type = header_fields_map;
using status_code_type = status;
......@@ -54,6 +49,8 @@ public:
read_chunks,
};
using upper_layer_ptr = std::unique_ptr<http::upper_layer>;
// -- constants --------------------------------------------------------------
/// Default maximum size for incoming HTTP requests: 64KiB.
......@@ -61,256 +58,70 @@ public:
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit server(Ts&&... xs) : upper_layer_(std::forward<Ts>(xs)...) {
explicit server(upper_layer_ptr up) : up_(std::move(up)) {
// nop
}
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
owner_ = owner;
if (auto err = upper_layer_.init(owner, this_layer_ptr(down), cfg))
return err;
if (auto max_size = get_as<uint32_t>(cfg, "http.max-request-size"))
max_request_size_ = *max_size;
down->configure_read(receive_policy::up_to(max_request_size_));
return none;
}
// -- factories --------------------------------------------------------------
static std::unique_ptr<server> make(upper_layer_ptr up);
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_;
return *up_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
return *up_;
}
// -- interface for the upper layer ------------------------------------------
template <class LowerLayerPtr>
static bool can_send_more(LowerLayerPtr down) noexcept {
return down->can_send_more();
}
// -- http::lower_layer implementation ---------------------------------------
template <class LowerLayerPtr>
static void suspend_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::stop());
}
bool can_send_more() const noexcept override;
template <class LowerLayerPtr>
static auto handle(LowerLayerPtr down) noexcept {
return down->handle();
}
void suspend_reading() override;
template <class LowerLayerPtr>
bool send_header(LowerLayerPtr down, context, status code,
const header_fields_map& fields) {
down->begin_output();
v1::write_header(code, fields, down->output_buffer());
down->end_output();
return true;
}
bool stopped_reading() const noexcept override;
template <class LowerLayerPtr>
bool send_payload(LowerLayerPtr down, context, const_byte_span bytes) {
down->begin_output();
auto& buf = down->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
return true;
}
bool send_header(context, status code,
const header_fields_map& fields) override;
template <class LowerLayerPtr>
bool send_chunk(LowerLayerPtr down, context, const_byte_span bytes) {
down->begin_output();
auto& buf = down->output_buffer();
auto size = bytes.size();
detail::append_hex(buf, &size, sizeof(size));
buf.emplace_back(std::byte{'\r'});
buf.emplace_back(std::byte{'\n'});
buf.insert(buf.end(), bytes.begin(), bytes.end());
buf.emplace_back(std::byte{'\r'});
buf.emplace_back(std::byte{'\n'});
return down->end_output();
}
bool send_payload(context, const_byte_span bytes) override;
template <class LowerLayerPtr>
bool send_end_of_chunks(LowerLayerPtr down, context) {
std::string_view str = "0\r\n\r\n";
auto bytes = as_bytes(make_span(str));
down->begin_output();
auto& buf = down->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
return down->end_chunk();
}
bool send_chunk(context, const_byte_span bytes) override;
template <class LowerLayerPtr>
void fin(LowerLayerPtr, context) {
// nop
}
bool send_end_of_chunks() override;
template <class LowerLayerPtr>
static void abort_reason(LowerLayerPtr down, error reason) {
return down->abort_reason(std::move(reason));
}
void fin(context) override;
template <class LowerLayerPtr>
static const error& abort_reason(LowerLayerPtr down) {
return down->abort_reason();
}
// -- stream_oriented::upper_layer implementation ----------------------------
// -- interface for the lower layer ------------------------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return upper_layer_.prepare_send(this_layer_ptr(down));
}
void abort(const error& reason) override;
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return upper_layer_.prepare_send(this_layer_ptr(down));
}
bool prepare_send() override;
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(max_request_size_));
}
bool done_sending() override;
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
upper_layer_.abort(this_layer_ptr(down), reason);
}
void continue_reading() override;
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span) {
using namespace literals;
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("bytes", input.size()));
ptrdiff_t consumed = 0;
for (;;) {
switch (mode_) {
case mode::read_header: {
auto [hdr, remainder] = v1::split_header(input);
if (hdr.empty()) {
if (input.size() >= max_request_size_) {
write_response(down, status::request_header_fields_too_large,
"Header exceeds maximum size.");
auto err = make_error(pec::too_many_characters,
"exceeded maximum request size");
down->abort_reason(std::move(err));
return -1;
} else {
return consumed;
}
} else if (!handle_header(down, hdr)) {
return -1;
} else {
// Prepare for next loop iteration.
consumed += static_cast<ptrdiff_t>(hdr.size());
input = remainder;
// Transition to the next mode.
if (hdr_.chunked_transfer_encoding()) {
mode_ = mode::read_chunks;
} else if (auto len = hdr_.content_length()) {
// Protect against payloads that exceed the maximum size.
if (*len >= max_request_size_) {
write_response(down, status::payload_too_large,
"Payload exceeds maximum size.");
auto err = make_error(sec::invalid_argument,
"exceeded maximum payload size");
down->abort_reason(std::move(err));
return -1;
}
// Transition to read_payload mode and continue.
payload_len_ = *len;
mode_ = mode::read_payload;
} else {
// TODO: we may *still* have a payload since HTTP can omit the
// Content-Length field and simply close the connection
// after the payload.
if (!invoke_upper_layer(down, const_byte_span{}))
return -1;
}
}
break;
}
case mode::read_payload: {
if (input.size() >= payload_len_) {
if (!invoke_upper_layer(down, input.subspan(0, payload_len_)))
return -1;
consumed += static_cast<ptrdiff_t>(payload_len_);
mode_ = mode::read_header;
} else {
// Wait for more data.
return consumed;
}
break;
}
case mode::read_chunks: {
// TODO: implement me
write_response(down, status::not_implemented,
"Chunked transfer not implemented yet.");
auto err = make_error(sec::invalid_argument,
"exceeded maximum payload size");
down->abort_reason(std::move(err));
return -1;
}
}
}
}
ptrdiff_t consume(byte_span input, byte_span) override;
private:
// -- implementation details -------------------------------------------------
// -- utility functions ------------------------------------------------------
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_hypertext_oriented_layer_ptr(this, down);
}
void write_response(status code, std::string_view content);
template <class LowerLayerPtr>
void
write_response(LowerLayerPtr down, status code, std::string_view content) {
down->begin_output();
v1::write_response(code, "text/plain", content, down->output_buffer());
down->end_output();
}
bool invoke_upper_layer(const_byte_span payload);
template <class LowerLayerPtr>
bool invoke_upper_layer(LowerLayerPtr down, const_byte_span payload) {
if (!upper_layer_.consume(this_layer_ptr(down), context{}, hdr_, payload)) {
if (!down->abort_reason()) {
down->abort_reason(make_error(sec::unsupported_operation,
"requested method not implemented yet"));
}
return false;
}
return true;
}
bool handle_header(std::string_view http);
// -- HTTP request processing ------------------------------------------------
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, std::string_view http) {
// Parse the header and reject invalid inputs.
auto [code, msg] = hdr_.parse(http);
if (code != status::ok) {
write_response(down, code, msg);
down->abort_reason(make_error(pec::invalid_argument, "malformed header"));
return false;
} else {
return true;
}
}
/// Stores the upper layer.
UpperLayer upper_layer_;
stream_oriented::lower_layer* down_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
upper_layer_ptr up_;
/// Buffer for re-using memory.
header hdr_;
......@@ -325,22 +136,4 @@ private:
uint32_t max_request_size_ = default_max_request_size;
};
/// Creates a WebSocket server on the connected socket `fd`.
/// @param mpx The multiplexer that takes ownership of the socket.
/// @param fd A connected stream socket.
/// @param in Inputs for writing to the socket.
/// @param out Outputs from the socket.
/// @param trait Converts between the native and the wire format.
template <template <class> class Transport = stream_transport, class Socket,
class T, class Trait>
socket_manager_ptr make_server(multiplexer& mpx, Socket fd,
async::consumer_resource<T> in,
async::producer_resource<T> out, Trait trait) {
using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>;
using stack_t = Transport<server<app_t>>;
auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(in),
std::move(out), std::move(trait));
return mgr;
}
} // namespace caf::net::http
......@@ -6,6 +6,7 @@
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/fwd.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
......
// 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/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_upper_layer.hpp"
#include "caf/net/http/fwd.hpp"
namespace caf::net::http {
/// Operates on HTTP requests.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes an HTTP message.
/// @param ctx Identifies this request. The response message must pass this
/// back to the lower layer. Allows clients to send multiple
/// requests in "parallel" (i.e., send multiple requests before
/// receiving the response on the first one).
/// @param hdr The header fields for the received message.
/// @param payload The 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.
virtual ptrdiff_t
consume(context ctx, const header& hdr, const_byte_span payload)
= 0;
};
} // namespace caf::net::http
// 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_span.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::net {
/// Wraps a pointer to a hypertext-oriented layer with a pointer to its lower
/// layer. Both pointers are then used to implement the interface required for a
/// hypertext-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr>
class hypertext_oriented_layer_ptr {
public:
using context_type = typename Layer::context_type;
using status_code_type = typename Layer::status_code_type;
using header_fields_type = typename Layer::header_fields_type;
class access {
public:
access(Layer* layer, LowerLayerPtr down) : lptr_(layer), llptr_(down) {
// nop
}
/// Queries whether the underlying transport can send additional data.
bool can_send_more() const noexcept {
return lptr_->can_send_more(llptr_);
}
/// Asks the underlying transport to stop receiving additional data until
/// resumed.
void suspend_reading() {
return lptr_->suspend_reading(llptr_);
}
/// Returns the socket handle.
auto handle() const noexcept {
return lptr_->handle(llptr_);
}
/// Sends a response header for answering the request identified by the
/// context.
/// @param context Identifies which request this response belongs to.
/// @param code Indicates either success or failure to the client.
/// @param fields Various informational fields for the client. When sending
/// a payload afterwards, the fields should at least include
/// the content length.
bool send_header(context_type context, status_code_type code,
const header_fields_type& fields) {
return lptr_->send_header(llptr_, context, code, fields);
}
/// Sends a payload to the client. Must follow a header.
/// @param context Identifies which request this response belongs to.
/// @param bytes Arbitrary data for the client.
/// @pre `bytes.size() > 0`
[[nodiscard]] bool send_payload(context_type context,
const_byte_span bytes) {
return lptr_->send_payload(llptr_, context, bytes);
}
/// Sends a single chunk of arbitrary data. The chunks must follow a header.
/// @pre `bytes.size() > 0`
[[nodiscard]] bool send_chunk(context_type context, const_byte_span bytes) {
return lptr_->send_chunk(llptr_, context, bytes);
}
/// Informs the client that the transfer completed, i.e., that the server
/// will not send additional chunks.
[[nodiscard]] bool send_end_of_chunks(context_type context) {
return lptr_->send_end_of_chunks(llptr_, context);
}
/// Convenience function for completing a request 'raw' (without adding
/// additional header fields) in a single function call. Calls
/// `send_header`, `send_payload` and `fin`.
bool send_raw_response(context_type context, status_code_type code,
const header_fields_type& fields,
const_byte_span content) {
if (lptr_->send_header(llptr_, context, code, fields)
&& (content.empty()
|| lptr_->send_payload(llptr_, context, content))) {
lptr_->fin(llptr_, context);
return true;
} else {
return false;
}
}
/// Convenience function for completing a request in a single function call.
/// Automatically sets the header fields 'Content-Type' and
/// 'Content-Length'. Calls `send_header`, `send_payload` and `fin`.
bool send_response(context_type context, status_code_type code,
header_fields_type fields, std::string_view content_type,
const_byte_span content) {
std::string len;
if (!content.empty()) {
auto len = std::to_string(content.size());
fields.emplace("Content-Length", len);
fields.emplace("Content-Type", content_type);
}
return send_raw_response(context, code, fields, content);
}
/// Convenience function for completing a request in a single function call.
/// Automatically sets the header fields 'Content-Type' and
/// 'Content-Length'. Calls `send_header`, `send_payload` and `fin`.
bool send_response(context_type context, status_code_type code,
std::string_view content_type, const_byte_span content) {
std::string len;
header_fields_type fields;
if (!content.empty()) {
len = std::to_string(content.size());
fields.emplace("Content-Type", content_type);
fields.emplace("Content-Length", len);
}
return send_raw_response(context, code, fields, content);
}
bool send_response(context_type context, status_code_type code,
header_fields_type fields, std::string_view content_type,
std::string_view content) {
return send_response(context, code, content_type, std::move(fields),
as_bytes(make_span(content)));
}
bool send_response(context_type context, status_code_type code,
std::string_view content_type,
std::string_view content) {
return send_response(context, code, content_type,
as_bytes(make_span(content)));
}
void fin(context_type context) {
lptr_->fin(llptr_, context);
}
/// Sets an abort reason on the transport.
void abort_reason(error reason) {
return lptr_->abort_reason(llptr_, std::move(reason));
}
/// Returns the current abort reason on the transport or a
/// default-constructed error is no error occurred yet.
const error& abort_reason() {
return lptr_->abort_reason(llptr_);
}
private:
Layer* lptr_;
LowerLayerPtr llptr_;
};
hypertext_oriented_layer_ptr(Layer* layer, LowerLayerPtr down)
: access_(layer, down) {
// nop
}
hypertext_oriented_layer_ptr(const hypertext_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_hypertext_oriented_layer_ptr(Layer* this_layer, LowerLayerPtr down) {
using result_t = hypertext_oriented_layer_ptr<Layer, LowerLayerPtr>;
return result_t{this_layer, down};
}
} // namespace caf::net
......@@ -4,26 +4,16 @@
#pragma once
#include "caf/byte_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/net/stream_oriented.hpp"
#include <cstdint>
#include <cstring>
#include <memory>
#include <type_traits>
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/error.hpp"
#include "caf/net/message_flow_bridge.hpp"
#include "caf/net/message_oriented_layer_ptr.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/tag/message_oriented.hpp"
#include "caf/tag/no_auto_reading.hpp"
#include "caf/tag/stream_oriented.hpp"
namespace caf::net {
/// Length-prefixed message framing for discretizing a Byte stream into messages
......@@ -31,14 +21,15 @@ namespace caf::net {
/// 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 {
class CAF_NET_EXPORT length_prefix_framing
: public stream_oriented::upper_layer,
public message_oriented::lower_layer {
public:
using input_tag = tag::stream_oriented;
// -- member types -----------------------------------------------------------
using output_tag = tag::message_oriented;
using upper_layer_ptr = std::unique_ptr<message_oriented::upper_layer>;
using length_prefix_type = uint32_t;
// -- constants --------------------------------------------------------------
static constexpr size_t hdr_size = sizeof(uint32_t);
......@@ -46,229 +37,86 @@ public:
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit length_prefix_framing(Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...) {
// nop
}
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
if constexpr (!std::is_base_of_v<tag::no_auto_reading, UpperLayer>)
down->configure_read(receive_policy::exactly(hdr_size));
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_;
}
// -- interface for the upper layer ------------------------------------------
template <class LowerLayerPtr>
static bool can_send_more(LowerLayerPtr down) noexcept {
return down->can_send_more();
}
template <class LowerLayerPtr>
static auto handle(LowerLayerPtr down) noexcept {
return down->handle();
}
template <class LowerLayerPtr>
static void suspend_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::stop());
}
template <class LowerLayerPtr>
void begin_message(LowerLayerPtr down) {
down->begin_output();
auto& buf = down->output_buffer();
message_offset_ = buf.size();
buf.insert(buf.end(), 4, std::byte{0});
}
template <class LowerLayerPtr>
byte_buffer& message_buffer(LowerLayerPtr down) {
return down->output_buffer();
}
template <class LowerLayerPtr>
[[nodiscard]] bool end_message(LowerLayerPtr down) {
using detail::to_network_order;
auto& buf = down->output_buffer();
CAF_ASSERT(message_offset_ < buf.size());
auto msg_begin = buf.begin() + message_offset_;
auto msg_size = std::distance(msg_begin + 4, buf.end());
if (msg_size > 0 && static_cast<size_t>(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);
down->end_output();
return true;
} else {
auto err = make_error(sec::runtime_error,
msg_size == 0 ? "logic error: message of size 0"
: "maximum message size exceeded");
down->abort_reason(std::move(err));
return false;
}
}
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr) {
// nop; this framing layer has no close handshake
return true;
}
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr, const error&) {
// nop; this framing layer has no close handshake
return true;
}
template <class LowerLayerPtr>
static void abort_reason(LowerLayerPtr down, error reason) {
return down->abort_reason(std::move(reason));
}
template <class LowerLayerPtr>
static const error& abort_reason(LowerLayerPtr down) {
return down->abort_reason();
}
// -- interface for the lower layer ------------------------------------------
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::exactly(hdr_size));
}
template <class LowerLayerPtr>
std::enable_if_t<detail::has_after_reading_v<
UpperLayer,
message_oriented_layer_ptr<length_prefix_framing, LowerLayerPtr>>>
after_reading(LowerLayerPtr down) {
return upper_layer_.after_reading(this_layer_ptr(down));
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return upper_layer_.prepare_send(this_layer_ptr(down));
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return upper_layer_.done_sending(this_layer_ptr(down));
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
upper_layer_.abort(this_layer_ptr(down), reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span) {
CAF_LOG_TRACE("got" << input.size() << "bytes");
auto this_layer = this_layer_ptr(down);
if (input.size() < sizeof(uint32_t)) {
auto err = make_error(sec::runtime_error,
"received too few bytes from underlying transport");
down->abort_reason(std::move(err));
return -1;
} else if (input.size() == hdr_size) {
auto u32_size = uint32_t{0};
memcpy(&u32_size, input.data(), sizeof(uint32_t));
auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
if (msg_size == 0) {
// Ignore empty messages.
CAF_LOG_DEBUG("received empty message");
return static_cast<ptrdiff_t>(input.size());
} else if (msg_size > max_message_length) {
CAF_LOG_DEBUG("maximum message size exceeded");
auto err = make_error(sec::runtime_error,
"maximum message size exceeded");
down->abort_reason(std::move(err));
return -1;
} else {
CAF_LOG_DEBUG("wait for payload of size" << msg_size);
down->configure_read(receive_policy::exactly(hdr_size + msg_size));
return 0;
}
} else {
auto [msg_size, msg] = split(input);
if (msg_size == msg.size() && msg_size + hdr_size == input.size()) {
CAF_LOG_DEBUG("got message of size" << msg_size);
if (upper_layer_.consume(this_layer, msg) >= 0) {
if (!down->stopped())
down->configure_read(receive_policy::exactly(hdr_size));
return static_cast<ptrdiff_t>(input.size());
} else {
return -1;
}
} else {
CAF_LOG_DEBUG("received malformed message");
auto err = make_error(sec::runtime_error, "received malformed message");
down->abort_reason(std::move(err));
return -1;
}
}
}
// -- convenience functions --------------------------------------------------
static std::pair<size_t, byte_span> split(byte_span buffer) noexcept {
CAF_ASSERT(buffer.size() >= sizeof(uint32_t));
auto u32_size = uint32_t{0};
memcpy(&u32_size, buffer.data(), sizeof(uint32_t));
auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
return std::make_pair(msg_size, buffer.subspan(sizeof(uint32_t)));
}
explicit length_prefix_framing(upper_layer_ptr up);
private:
// -- implementation details -------------------------------------------------
// -- factories --------------------------------------------------------------
static std::unique_ptr<length_prefix_framing> make(upper_layer_ptr up);
// -- high-level factory functions -------------------------------------------
// /// Runs a WebSocket server on the connected socket `fd`.
// /// @param mpx The multiplexer that takes ownership of the socket.
// /// @param fd A connected stream socket.
// /// @param cfg Additional configuration parameters for the protocol
// stack.
// /// @param in Inputs for writing to the socket.
// /// @param out Outputs from the socket.
// /// @param trait Converts between the native and the wire format.
// /// @relates length_prefix_framing
// template <template <class> class Transport = stream_transport, class
// Socket,
// class T, class Trait, class... TransportArgs>
// error run(actor_system&sys,Socket fd,
// const settings& cfg,
// async::consumer_resource<T> in,
// async::producer_resource<T> out,
// Trait trait, TransportArgs&&...
// args) {
// using app_t
// = Transport<length_prefix_framing<message_flow_bridge<T, Trait>>>;
// auto mgr = make_socket_manager<app_t>(fd, &mpx,
// std::forward<TransportArgs>(args)...,
// std::move(in), std::move(out),
// std::move(trait));
// return mgr->init(cfg);
// }
// -- implementation of stream_oriented::upper_layer -------------------------
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override;
bool prepare_send() override;
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_message_oriented_layer_ptr(this, down);
}
bool done_sending() override;
// -- implementation of message_oriented::lower_layer ------------------------
bool can_send_more() const noexcept override;
void request_messages() override;
void suspend_reading() override;
bool stopped_reading() const noexcept override;
void begin_message() override;
byte_buffer& message_buffer() override;
bool end_message() override;
void send_close_message() override;
void send_close_message(const error& reason) override;
// -- utility functions ------------------------------------------------------
static std::pair<size_t, byte_span> split(byte_span buffer) noexcept;
private:
// -- member variables -------------------------------------------------------
UpperLayer upper_layer_;
stream_oriented::lower_layer* down_;
upper_layer_ptr up_;
size_t message_offset_ = 0;
};
// -- high-level factory functions -------------------------------------------
/// Runs a WebSocket server on the connected socket `fd`.
/// @param mpx The multiplexer that takes ownership of the socket.
/// @param fd A connected stream socket.
/// @param cfg Additional configuration parameters for the protocol stack.
/// @param in Inputs for writing to the socket.
/// @param out Outputs from the socket.
/// @param trait Converts between the native and the wire format.
/// @relates length_prefix_framing
template <template <class> class Transport = stream_transport, class Socket,
class T, class Trait, class... TransportArgs>
error run_with_length_prefix_framing(multiplexer& mpx, Socket fd,
const settings& cfg,
async::consumer_resource<T> in,
async::producer_resource<T> out,
Trait trait, TransportArgs&&... args) {
using app_t = Transport<length_prefix_framing<message_flow_bridge<T, Trait>>>;
auto mgr = make_socket_manager<app_t>(fd, &mpx,
std::forward<TransportArgs>(args)...,
std::move(in), std::move(out),
std::move(trait));
return mgr->init(cfg);
}
} // namespace caf::net
......@@ -7,14 +7,12 @@
#include "caf/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/net/consumer_adapter.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/message_oriented.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/no_auto_reading.hpp"
#include <utility>
......@@ -22,26 +20,39 @@ namespace caf::net {
/// Translates between a message-oriented transport and data flows.
///
/// The trait class converts between the native and the wire format:
/// The `Trait` provides a customization point that converts between native and
/// wire format.
///
/// ~~~
/// struct my_trait {
/// bool convert(const T& value, byte_buffer& bytes);
/// bool convert(const_byte_span bytes, T& value);
/// class my_trait {
/// using input_type = ...;
/// using output_type = ...;
/// bool convert(const_byte_span bytes, input_type& value);
/// bool convert(const output_type& value, byte_buffer& bytes);
/// };
/// ~~~
template <class T, class Trait, class Tag = tag::message_oriented>
class message_flow_bridge : public tag::no_auto_reading {
template <class Trait>
class message_flow_bridge : public message_oriented::upper_layer {
public:
using input_tag = Tag;
/// The input type for the application.
using input_type = typename Trait::input_type;
using buffer_type = async::spsc_buffer<T>;
/// The output type for of application.
using output_type = typename Trait::output_type;
using consumer_resource_t = async::consumer_resource<T>;
/// The resource type we pull from. We consume the output of the application.
using pull_resource_t = async::consumer_resource<output_type>;
using producer_resource_t = async::producer_resource<T>;
/// The buffer type from the @ref pull_resource_t.
using pull_buffer_t = async::spsc_buffer<output_type>;
message_flow_bridge(consumer_resource_t in_res, producer_resource_t out_res,
/// Type for the producer adapter. We produce the input of the application.
using push_resource_t = async::producer_resource<input_type>;
/// The buffer type from the @ref push_resource_t.
using push_buffer_t = async::spsc_buffer<input_type>;
message_flow_bridge(pull_resource_t in_res, push_resource_t out_res,
Trait trait)
: in_res_(std::move(in_res)),
trait_(std::move(trait)),
......@@ -53,66 +64,41 @@ public:
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings& cfg) {
mgr_ = mgr;
if constexpr (caf::detail::has_init_v<Trait>) {
if (auto err = init_res(trait_.init(cfg)))
return err;
}
error init(net::socket_manager* mgr, message_oriented::lower_layer* down,
const settings&) {
down_ = down;
if (in_res_) {
in_ = consumer_adapter<buffer_type>::try_open(mgr, in_res_);
in_ = consumer_adapter<pull_buffer_t>::try_open(mgr, in_res_);
in_res_ = nullptr;
}
if (out_res_) {
out_ = producer_adapter<buffer_type>::try_open(mgr, out_res_);
out_ = producer_adapter<push_buffer_t>::try_open(mgr, out_res_);
out_res_ = nullptr;
}
if (!in_ && !out_)
return make_error(sec::cannot_open_resource,
"flow bridge cannot run without at least one resource");
"a flow bridge needs at least one valid resource");
return none;
}
template <class LowerLayerPtr>
bool write(LowerLayerPtr down, const T& item) {
if constexpr (std::is_same_v<Tag, tag::message_oriented>) {
down->begin_message();
auto& buf = down->message_buffer();
return trait_.convert(item, buf) && down->end_message();
} else {
static_assert(std::is_same_v<Tag, tag::mixed_message_oriented>);
if (trait_.converts_to_binary(item)) {
down->begin_binary_message();
auto& bytes = down->binary_message_buffer();
return trait_.convert(item, bytes) && down->end_binary_message();
} else {
down->begin_text_message();
auto& text = down->text_message_buffer();
return trait_.convert(item, text) && down->end_text_message();
}
}
bool write(const input_type& item) {
down_->begin_message();
auto& buf = down_->message_buffer();
return trait_.convert(item, buf) && down_->end_message();
}
template <class LowerLayerPtr>
struct write_helper {
using bridge_type = message_flow_bridge;
bridge_type* bridge;
LowerLayerPtr down;
message_flow_bridge* bridge;
bool aborted = false;
size_t consumed = 0;
error err;
write_helper(bridge_type* bridge, LowerLayerPtr down)
: bridge(bridge), down(down) {
write_helper(message_flow_bridge* bridge) : bridge(bridge) {
// nop
}
void on_next(const T& item) {
if (!bridge->write(down, item)) {
void on_next(const input_type& item) {
if (!bridge->write(item))
aborted = true;
return;
}
}
void on_complete() {
......@@ -124,20 +110,18 @@ public:
}
};
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
write_helper<LowerLayerPtr> helper{this, down};
while (down->can_send_more() && in_) {
bool prepare_send() override {
write_helper helper{this};
while (down_->can_send_more() && in_) {
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper);
if (!again) {
if (helper.err) {
down->send_close_message(helper.err);
down_->send_close_message(helper.err);
} else {
down->send_close_message();
down_->send_close_message();
}
in_ = nullptr;
} else if (helper.aborted) {
down->abort_reason(make_error(sec::conversion_failed));
in_->cancel();
in_ = nullptr;
return false;
......@@ -148,13 +132,11 @@ public:
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return !in_ || !in_->has_data();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed)
......@@ -169,89 +151,35 @@ public:
}
}
template <class U = Tag, class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf) {
if (!out_) {
down->abort_reason(make_error(sec::connection_closed));
return -1;
}
T val;
if (!trait_.convert(buf, val)) {
down->abort_reason(make_error(sec::conversion_failed));
return -1;
}
if (out_->push(std::move(val)) == 0)
down->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
template <class U = Tag, class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr down, byte_span buf) {
return consume(down, buf);
}
template <class U = Tag, class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, std::string_view buf) {
if (!out_) {
down->abort_reason(make_error(sec::connection_closed));
ptrdiff_t consume(byte_span buf) {
if (!out_)
return -1;
}
T val;
if (!trait_.convert(buf, val)) {
down->abort_reason(make_error(sec::conversion_failed));
input_type val;
if (!trait_.convert(buf, val))
return -1;
}
if (out_->push(std::move(val)) == 0)
down->suspend_reading();
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
private:
error init_res(error err) {
return err;
}
error init_res(consumer_resource_t in, producer_resource_t out) {
in_res_ = std::move(in);
out_res_ = std::move(out);
return caf::none;
}
error init_res(std::tuple<consumer_resource_t, producer_resource_t> in_out) {
auto& [in, out] = in_out;
return init_res(std::move(in), std::move(out));
}
error init_res(std::pair<consumer_resource_t, producer_resource_t> in_out) {
auto& [in, out] = in_out;
return init_res(std::move(in), std::move(out));
}
template <class R>
error init_res(expected<R> res) {
if (res)
return init_res(*res);
else
return std::move(res.error());
}
/// Points to the manager that runs this protocol stack.
net::socket_manager* mgr_ = nullptr;
/// Points to the next layer down the protocol stack.
message_oriented::lower_layer* down_ = nullptr;
/// Incoming messages, serialized to the socket.
consumer_adapter_ptr<buffer_type> in_;
consumer_adapter_ptr<pull_buffer_t> in_;
/// Outgoing messages, deserialized from the socket.
producer_adapter_ptr<buffer_type> out_;
producer_adapter_ptr<push_buffer_t> out_;
/// Converts between raw bytes and items.
Trait trait_;
/// Discarded after initialization.
consumer_resource_t in_res_;
pull_resource_t in_res_;
/// Discarded after initialization.
producer_resource_t out_res_;
push_resource_t out_res_;
};
} // namespace caf::net
// 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/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/generic_upper_layer.hpp"
namespace caf::net::message_oriented {
class lower_layer;
/// Consumes binary messages from the lower layer.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes bytes from the lower layer.
/// @param payload 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.
[[nodiscard]] virtual ptrdiff_t consume(byte_span payload) = 0;
};
/// Provides access to a resource that operates on the granularity of messages,
/// e.g., a UDP socket.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
/// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0;
/// Prepares the layer for an outgoing message, e.g., by allocating an
/// output buffer as necessary.
virtual void begin_message() = 0;
/// 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 The lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
virtual byte_buffer& message_buffer() = 0;
/// Seals and prepares a message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
virtual bool end_message() = 0;
/// Inform the remote endpoint that no more messages will arrive.
/// @note Not every protocol has a dedicated close message. Some
/// implementation may simply do nothing.
virtual void send_close_message() = 0;
/// Inform the remote endpoint that no more messages will arrive because of
/// an error.
/// @note Not every protocol has a dedicated close message. Some
/// implementation may simply do nothing.
virtual void send_close_message(const error& reason) = 0;
};
} // namespace caf::net::message_oriented
// 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/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::net {
/// Wraps a pointer to a message-oriented layer with a pointer to its lower
/// layer. Both pointers are then used to implement the interface required for a
/// message-oriented layer when calling into its upper layer.
template <class Layer, class LowerLayerPtr>
class message_oriented_layer_ptr {
public:
class access {
public:
access(Layer* layer, LowerLayerPtr down) : lptr_(layer), llptr_(down) {
// nop
}
void suspend_reading() {
return lptr_->suspend_reading(llptr_);
}
bool can_send_more() const noexcept {
return lptr_->can_send_more(llptr_);
}
auto handle() const noexcept {
return lptr_->handle(llptr_);
}
void begin_message() {
lptr_->begin_message(llptr_);
}
[[nodiscard]] auto& message_buffer() {
return lptr_->message_buffer(llptr_);
}
[[nodiscard]] bool end_message() {
return lptr_->end_message(llptr_);
}
template <class... Ts>
bool send_close_message(Ts&&... xs) {
return lptr_->send_close_message(llptr_, std::forward<Ts>(xs)...);
}
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_;
};
message_oriented_layer_ptr(Layer* layer, LowerLayerPtr down)
: access_(layer, down) {
// nop
}
message_oriented_layer_ptr(const 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_message_oriented_layer_ptr(Layer* this_layer, LowerLayerPtr down) {
using result_t = message_oriented_layer_ptr<Layer, LowerLayerPtr>;
return result_t{this_layer, down};
}
} // namespace caf::net
......@@ -39,32 +39,6 @@ public:
~middleman() override;
// -- socket manager functions -----------------------------------------------
/// Creates a new acceptor that accepts incoming connections from @p sock and
/// creates socket managers using @p factory.
/// @param sock An accept socket in listening mode. For a TCP socket, this
/// socket must already listen to an address plus port.
/// @param factory A function object for creating socket managers that take
/// ownership of incoming connections.
/// @param limit The maximum number of connections that this acceptor should
/// establish or 0 for 'no limit'.
template <class Socket, class Factory>
auto make_acceptor(Socket sock, Factory factory, size_t limit = 0) {
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), limit);
} else {
using impl = connection_acceptor<Socket, Factory>;
auto ptr = make_socket_manager<impl>(std::move(sock), &mpx_, limit,
std::move(factory));
mpx_.init(ptr);
return ptr;
}
}
// -- interface functions ----------------------------------------------------
void start() override;
......
// 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/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.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 suspend_reading() {
return lptr_->suspend_reading(llptr_);
}
void begin_binary_message() {
lptr_->begin_binary_message(llptr_);
}
auto& binary_message_buffer() {
return lptr_->binary_message_buffer(llptr_);
}
bool end_binary_message() {
return lptr_->end_binary_message(llptr_);
}
void begin_text_message() {
lptr_->begin_text_message(llptr_);
}
auto& text_message_buffer() {
return lptr_->text_message_buffer(llptr_);
}
bool end_text_message() {
return lptr_->end_text_message(llptr_);
}
template <class... Ts>
bool send_close_message(Ts&&... xs) {
return lptr_->send_close_message(llptr_, std::forward<Ts>(xs)...);
}
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
......@@ -10,12 +10,12 @@
#include "caf/action.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/socket.hpp"
#include "caf/ref_counted.hpp"
#include "caf/unordered_flat_map.hpp"
extern "C" {
......@@ -37,7 +37,7 @@ public:
socket_manager_ptr mgr;
};
using poll_update_map = detail::unordered_flat_map<socket, poll_update>;
using poll_update_map = unordered_flat_map<socket, poll_update>;
using pollfd_list = std::vector<pollfd>;
......
......@@ -4,7 +4,9 @@
#pragma once
#include "caf/detail/net_export.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/socket_manager.hpp"
#include <array>
......@@ -14,7 +16,7 @@
namespace caf::net {
class pollset_updater : public socket_manager {
class CAF_NET_EXPORT pollset_updater : public socket_event_layer {
public:
// -- member types -----------------------------------------------------------
......@@ -37,20 +39,15 @@ public:
// -- constructors, destructors, and assignment operators --------------------
pollset_updater(pipe_socket read_handle, multiplexer* parent);
explicit pollset_updater(pipe_socket fd);
~pollset_updater() override;
// -- factories --------------------------------------------------------------
// -- properties -------------------------------------------------------------
static std::unique_ptr<pollset_updater> make(pipe_socket fd);
/// Returns the managed socket.
pipe_socket handle() const noexcept {
return socket_cast<pipe_socket>(handle_);
}
// -- implementation of socket_event_layer -----------------------------------
// -- interface functions ----------------------------------------------------
error init(const settings& config) override;
error init(socket_manager* owner, const settings& cfg) override;
read_result handle_read_event() override;
......@@ -60,11 +57,11 @@ public:
write_result handle_write_event() override;
write_result handle_continue_writing() override;
void handle_error(sec code) override;
void abort(const error& reason) override;
private:
pipe_socket fd_;
multiplexer* mpx_ = nullptr;
msg_buf buf_;
size_t buf_size_ = 0;
};
......
......@@ -8,8 +8,15 @@
namespace caf::net {
/// Configures how many bytes a @ref stream_transport receives before calling
/// `consume` on its upper layer.
struct receive_policy {
/// Configures how many bytes @ref stream_transport must read before it may
/// call `consume` on its upper layer.
uint32_t min_size;
/// Configures how many bytes @ref stream_transport may read at most before
/// it calls `consume` on its upper layer.
uint32_t max_size;
/// @pre `min_size > 0`
......@@ -24,6 +31,7 @@ struct receive_policy {
return {size, size};
}
/// @pre `max_size >= 1`
static constexpr receive_policy up_to(uint32_t max_size) {
return {1, max_size};
}
......
// 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/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::net {
/// The lowest layer in a protocol stack. Called by a @ref socket_manager
/// directly.
class CAF_NET_EXPORT socket_event_layer {
public:
virtual ~socket_event_layer();
/// Encodes how to proceed after a read operation.
enum class read_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to write to the socket instead of reading
/// from the socket.
want_write,
/// Indicates that the manager no longer reads from the socket.
close,
/// Indicates that the manager encountered a fatal error and stops both
/// reading and writing.
abort,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
/// Encodes how to proceed after a write operation.
enum class write_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to read from the socket instead of
/// writing to the socket.
want_read,
/// Indicates that the manager no longer writes to the socket.
close,
/// Indicates that the manager encountered a fatal error and stops both
/// reading and writing.
abort,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
/// Initializes the layer.
virtual error init(socket_manager* owner, const settings& cfg) = 0;
/// Handles a read event on the managed socket.
virtual read_result handle_read_event() = 0;
/// Handles internally buffered data.
virtual read_result handle_buffered_data() = 0;
/// Handles a request to continue reading on the socket.
virtual read_result handle_continue_reading() = 0;
/// Handles a write event on the managed socket.
virtual write_result handle_write_event() = 0;
/// Called after returning `handover` from a read or write handler.
virtual bool do_handover(std::unique_ptr<socket_event_layer>& next);
/// Called on hard errors on the managed socket.
virtual void abort(const error& reason) = 0;
};
} // namespace caf::net
......@@ -12,15 +12,15 @@ namespace caf::net {
template <class Socket>
class socket_guard {
public:
socket_guard() noexcept : sock_(invalid_socket_id) {
socket_guard() noexcept : fd_(invalid_socket_id) {
// nop
}
explicit socket_guard(Socket sock) noexcept : sock_(sock) {
explicit socket_guard(Socket fd) noexcept : fd_(fd) {
// nop
}
socket_guard(socket_guard&& other) noexcept : sock_(other.release()) {
socket_guard(socket_guard&& other) noexcept : fd_(other.release()) {
// nop
}
......@@ -34,28 +34,32 @@ public:
socket_guard& operator=(const socket_guard&) = delete;
~socket_guard() {
if (sock_.id != invalid_socket_id)
close(sock_);
if (fd_.id != invalid_socket_id)
close(fd_);
}
void reset(Socket x) noexcept {
if (sock_.id != invalid_socket_id)
close(sock_);
sock_ = x;
if (fd_.id != invalid_socket_id)
close(fd_);
fd_ = x;
}
Socket release() noexcept {
auto sock = sock_;
sock_.id = invalid_socket_id;
auto sock = fd_;
fd_.id = invalid_socket_id;
return sock;
}
Socket get() noexcept {
return fd_;
}
Socket socket() const noexcept {
return sock_;
return fd_;
}
private:
Socket sock_;
Socket fd_;
};
template <class Socket>
......
......@@ -11,14 +11,15 @@
#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/socket.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/tag/io_event_oriented.hpp"
#include <type_traits>
namespace caf::net {
......@@ -27,36 +28,11 @@ class CAF_NET_EXPORT socket_manager : public ref_counted {
public:
// -- member types -----------------------------------------------------------
/// A callback for unprocessed messages.
using fallback_handler = unique_callback_ptr<result<message>(message&)>;
/// Encodes how a manager wishes to proceed after a read operation.
enum class read_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to write to the socket instead of reading
/// from the socket.
want_write,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
using read_result = socket_event_layer::read_result;
/// Encodes how a manager wishes to proceed after a write operation.
enum class write_result {
/// Indicates that a manager wants to read again later.
again,
/// Indicates that a manager wants to stop reading until explicitly resumed.
stop,
/// Indicates that a manager wants to read from the socket instead of
/// writing to the socket.
want_read,
/// Indicates that a manager is done with the socket and hands ownership to
/// another manager.
handover,
};
using write_result = socket_event_layer::write_result;
using event_handler_ptr = std::unique_ptr<socket_event_layer>;
/// Stores manager-related flags in a single block.
struct flags_t {
......@@ -68,7 +44,7 @@ public:
/// @pre `handle != invalid_socket`
/// @pre `mpx!= nullptr`
socket_manager(socket handle, multiplexer* mpx);
socket_manager(multiplexer* mpx, socket handle, event_handler_ptr handler);
~socket_manager() override;
......@@ -76,16 +52,41 @@ public:
socket_manager& operator=(const socket_manager&) = delete;
// -- factories --------------------------------------------------------------
static socket_manager_ptr make(multiplexer* mpx, socket handle,
event_handler_ptr handler);
template <class Handle = actor, class FallbackHandler>
detail::infer_actor_shell_ptr_type<Handle>
make_actor_shell(FallbackHandler f) {
using ptr_type = detail::infer_actor_shell_ptr_type<Handle>;
using impl_type = typename ptr_type::element_type;
auto hdl = system().spawn<impl_type>(this);
auto ptr = ptr_type{actor_cast<strong_actor_ptr>(std::move(hdl))};
ptr->set_fallback(std::move(f));
return ptr;
}
template <class Handle = actor>
auto make_actor_shell() {
auto f = [](abstract_actor_shell* self, message& msg) -> result<message> {
self->quit(make_error(sec::unexpected_message, std::move(msg)));
return make_error(sec::unexpected_message);
};
return make_actor_shell<Handle>(std::move(f));
}
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
/// Returns the handle for the managed socket.
socket handle() const {
return handle_;
return fd_;
}
/// @private
void handle(socket new_handle) {
handle_ = new_handle;
fd_ = new_handle;
}
/// Returns a reference to the hosting @ref actor_system instance.
......@@ -111,12 +112,6 @@ public:
return mpx_;
}
/// Closes the read channel of the socket.
void close_read() noexcept;
/// Closes the write channel of the socket.
void close_write() noexcept;
/// Returns whether the manager closed read operations on the socket.
[[nodiscard]] bool read_closed() const noexcept {
return flags_.read_closed;
......@@ -127,40 +122,6 @@ public:
return flags_.write_closed;
}
const error& abort_reason() const noexcept {
return abort_reason_;
}
void abort_reason(error reason) noexcept;
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 Handle = actor, class LowerLayerPtr, class FallbackHandler>
auto make_actor_shell(LowerLayerPtr, FallbackHandler f) {
using ptr_type = detail::infer_actor_shell_ptr_type<Handle>;
using impl_type = typename ptr_type::element_type;
auto hdl = system().spawn<impl_type>(this);
auto ptr = ptr_type{actor_cast<strong_actor_ptr>(std::move(hdl))};
ptr->set_fallback(std::move(f));
return ptr;
}
template <class Handle = actor, class LowerLayerPtr>
auto make_actor_shell(LowerLayerPtr down) {
auto f = [down](message& msg) -> result<message> {
down->abort_reason(make_error(sec::unexpected_message, std::move(msg)));
return make_error(sec::unexpected_message);
};
return make_actor_shell<Handle>(down, std::move(f));
}
// -- event loop management --------------------------------------------------
/// Registers the manager for read operations on the @ref multiplexer.
......@@ -189,203 +150,57 @@ public:
// -- 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();
/// Closes the read channel of the socket.
void close_read() noexcept;
// -- pure virtual member functions ------------------------------------------
/// Closes the write channel of the socket.
void close_write() noexcept;
/// Initializes the manager and its all of its sub-components.
virtual error init(const settings& config) = 0;
error init(const settings& cfg);
/// Called whenever the socket received new data.
virtual read_result handle_read_event() = 0;
read_result handle_read_event();
/// 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;
read_result handle_buffered_data();
/// 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;
read_result handle_continue_reading();
/// 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;
write_result handle_write_event();
/// 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;
void handle_error(sec code);
/// Returns the new manager for the socket after `handle_read_event` or
/// Performs a handover to another transport after `handle_read_event` or
/// `handle_read_event` returned `handover`.
/// @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);
[[nodiscard]] bool do_handover();
protected:
// -- protected member variables ---------------------------------------------
private:
// -- member variables -------------------------------------------------------
socket handle_;
/// Stores the socket file descriptor. The socket manager automatically closes
/// the socket in its destructor.
socket fd_;
/// Points to the multiplexer that owns this manager.
multiplexer* mpx_;
private:
// -- private member variables -----------------------------------------------
error abort_reason_;
/// Stores flags for the socket file descriptor.
flags_t flags_;
};
template <class Protocol>
class socket_manager_impl : public socket_manager {
public:
// -- member types -----------------------------------------------------------
using super = socket_manager;
using output_tag = tag::io_event_oriented;
using socket_type = typename Protocol::socket_type;
using read_result = typename super::read_result;
using write_result = typename super::write_result;
// -- 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
}
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
socket_type handle() const {
return socket_cast<socket_type>(this->handle_);
}
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_);
}
// -- interface functions ----------------------------------------------------
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);
}
read_result handle_read_event() override {
return protocol_.handle_read_event(this);
}
read_result handle_buffered_data() override {
return protocol_.handle_buffered_data(this);
}
read_result handle_continue_reading() override {
return protocol_.handle_continue_reading(this);
}
write_result handle_write_event() override {
return protocol_.handle_write_event(this);
}
write_result handle_continue_writing() override {
return protocol_.handle_continue_writing(this);
}
void handle_error(sec code) override {
this->abort_reason(make_error(code));
return protocol_.abort(this, this->abort_reason());
}
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_;
/// Stores the event handler that operators on the socket file descriptor.
event_handler_ptr handler_;
};
/// @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
......@@ -7,21 +7,18 @@
#include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/detail/net_export.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>
......@@ -32,31 +29,37 @@ CAF_POP_WARNINGS
#include <string>
#include <string_view>
// -- small wrappers to help working with OpenSSL ------------------------------
namespace caf::net::ssl {
namespace caf::net::openssl {
/// State shared by multiple connections.
class context {
public:
struct impl;
context() = delete;
context(context&& other);
context& operator=(context&& other);
private:
};
/// State per connection.
struct connection;
/// Deletes an SSL object.
struct deleter {
void operator()(SSL_CTX* ptr) const noexcept {
SSL_CTX_free(ptr);
}
void operator()(context* ptr) const noexcept;
void operator()(SSL* ptr) const noexcept {
SSL_free(ptr);
}
void operator()(connection* ptr) const noexcept;
};
/// 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>;
/// An owning smart pointer for a @ref context.
using ctx_ptr = std::unique_ptr<context, 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>;
/// An owning smart pointer for a @ref connection.
using conn_ptr = std::unique_ptr<connection, deleter>;
/// Convenience function for creating an OpenSSL context for given method.
inline ctx_ptr make_ctx(const SSL_METHOD* method) {
......@@ -123,7 +126,7 @@ inline conn_ptr make_conn(const ctx_ptr& ctx, stream_socket fd) {
}
/// Manages an OpenSSL connection.
class policy {
class policy : public stream_transport::policy {
public:
// -- constructors, destructors, and assignment operators --------------------
......@@ -182,150 +185,72 @@ public:
}
}
// -- interface functions for the stream transport ---------------------------
/// Graceful shutdown.
void notify_close() {
SSL_shutdown(conn_.get());
}
// -- implementation of stream_transport::policy -----------------------------
/// 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, byte_span buf) {
return SSL_read(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
ptrdiff_t read(stream_socket, byte_span) override;
/// Writes data from the buffer to the SSL connection.
ptrdiff_t write(stream_socket, const_byte_span buf) {
return SSL_write(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
ptrdiff_t write(stream_socket, const_byte_span) override;
/// Performs a TLS/SSL handshake with the server.
ptrdiff_t connect(stream_socket) {
return SSL_connect(conn_.get());
}
stream_transport_error last_error(stream_socket, ptrdiff_t) override;
/// Waits for the client to performs a TLS/SSL handshake.
ptrdiff_t accept(stream_socket) {
return SSL_accept(conn_.get());
}
ptrdiff_t connect(stream_socket) override;
/// 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;
}
}
ptrdiff_t accept(stream_socket) override;
/// 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()));
}
size_t buffered() const noexcept override;
private:
/// 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> {
class CAF_NET_EXPORT transport : public stream_transport {
public:
// -- member types -----------------------------------------------------------
using super = stream_transport_base<openssl::policy, UpperLayer>;
using super = stream_transport;
using openssl_transport_ptr = std::unique_ptr<openssl_transport>;
using worker_ptr = std::unique_ptr<socket_event_layer>;
// -- factories --------------------------------------------------------------
/// Creates a new instance of the SSL transport for a socket that has already
/// performed the SSL handshake.
static openssl_transport_ptr make(stream_socket fd, openssl::policy policy,
upper_layer_ptr up);
/// Returns a worker that performs the OpenSSL server handshake on the socket.
/// On success, the worker performs a handover to an `openssl_transport` that
/// runs `up`.
static worker_ptr make_server(stream_socket fd, openssl::policy policy,
upper_layer_ptr up);
/// Returns a worker that performs the OpenSSL client handshake on the socket.
/// On success, the worker performs a handover to an `openssl_transport` that
/// runs `up`.
static worker_ptr make_client(stream_socket fd, openssl::policy policy,
upper_layer_ptr up);
private:
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit openssl_transport(openssl::conn_ptr conn, Ts&&... xs)
: super(openssl::policy{std::move(conn)}, std::forward<Ts>(xs)...) {
// nop
}
openssl_transport(stream_socket fd, openssl::policy pol, upper_layer_ptr up);
template <class... Ts>
openssl_transport(openssl::policy policy, Ts&&... xs)
: super(std::move(policy), std::forward<Ts>(xs)...) {
// nop
}
openssl::policy ssl_policy_;
};
} // namespace caf::net
} // namespace caf::net::ssl
// 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_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/ssl/errc.hpp"
#include "caf/net/stream_socket.hpp"
#include <cstddef>
namespace caf::net::ssl {
/// SSL state for a single connections.
class CAF_NET_EXPORT connection {
public:
// -- member types -----------------------------------------------------------
/// The opaque implementation type.
struct impl;
// -- constructors, destructors, and assignment operators --------------------
connection() = delete;
connection(connection&& other);
connection& operator=(connection&& other);
~connection();
// -- factories --------------------------------------------------------------
expected<connection> make(stream_socket fd);
// -- native handles ---------------------------------------------------------
/// Reinterprets `native_handle` as the native implementation type and takes
/// ownership of the handle.
static connection from_native(void* native_handle);
/// Retrieves the native handle from the connection.
void* native_handle() const noexcept;
// -- error handling ---------------------------------------------------------
/// Returns the error code for a preceding call to `connect`, `accept`,
/// `read`, `write` or `close.
/// @param ret The (negative) result from the preceding call.
errc last_error(ptrdiff_t ret) const;
/// Returns a human-readable representation of the error for a preceding call
/// to `connect`, `accept`, `read`, `write` or `close.
/// @param ret The (negative) result from the preceding call.
std::string last_error_string(ptrdiff_t ret) const;
// -- connecting and teardown ------------------------------------------------
/// Performs the client-side TLS/SSL handshake after connection to the server.
[[nodiscard]] ptrdiff_t connect();
/// Performs the server-side TLS/SSL handshake after accepting a connection
/// from a client.
[[nodiscard]] ptrdiff_t accept();
/// Gracefully closes the SSL connection.
ptrdiff_t close();
// -- reading and writing ----------------------------------------------------
/// Tries to fill @p buf with data from the managed socket.
[[nodiscard]] ptrdiff_t read(byte_span buf);
/// Tries to write bytes from @p buf to the managed socket.
[[nodiscard]] ptrdiff_t write(const_byte_span buf);
// -- properties -------------------------------------------------------------
/// Returns the number of bytes that are currently buffered outside of the
/// managed socket.
size_t buffered() const noexcept;
/// Returns the file descriptor for this connection.
stream_socket fd() const noexcept;
private:
constexpr explicit connection(impl* ptr) : pimpl_(ptr) {
// nop
}
impl* pimpl_;
};
} // namespace caf::net::ssl
// 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/detail/net_export.hpp"
#include "caf/net/ssl/dtls.hpp"
#include "caf/net/ssl/format.hpp"
#include "caf/net/ssl/fwd.hpp"
#include "caf/net/ssl/tls.hpp"
#include "caf/net/stream_socket.hpp"
namespace caf::net::ssl {
/// SSL state, shared by multiple connections.
class CAF_NET_EXPORT context {
public:
// -- member types -----------------------------------------------------------
/// The opaque implementation type.
struct impl;
// -- constructors, destructors, and assignment operators --------------------
context() = delete;
context(context&& other);
context& operator=(context&& other);
~context();
// -- factories --------------------------------------------------------------
static expected<context> make(tls min_version, tls max_version = tls::any);
static expected<context> make_server(tls min_version,
tls max_version = tls::any);
static expected<context> make_client(tls min_version,
tls max_version = tls::any);
static expected<context> make(dtls min_version, dtls max_version = dtls::any);
static expected<context> make_server(dtls min_version,
dtls max_version = dtls::any);
static expected<context> make_client(dtls min_version,
dtls max_version = dtls::any);
// -- native handles ---------------------------------------------------------
/// Reinterprets `native_handle` as the native implementation type and takes
/// ownership of the handle.
static context from_native(void* native_handle);
/// Retrieves the native handle from the context.
void* native_handle() const noexcept;
// -- error handling ---------------------------------------------------------
/// Retrieves a human-readable error description for a preceding call to
/// another member functions and removes that error from the error queue. Call
/// repeatedly until @ref has_last_error returns `false` to retrieve all
/// errors from the queue.
static std::string last_error_string();
/// Queries whether the error stack has at least one entry.
static bool has_last_error() noexcept;
// -- connections ------------------------------------------------------------
expected<connection> new_connection(stream_socket fd);
// -- certificates and keys --------------------------------------------------
/// Configure the context to use the default locations for loading CA
/// certificates.
/// @returns `true` on success, `false` otherwise and `last_error` can be used
/// to retrieve a human-readable error representation.
[[nodiscard]] bool set_default_verify_paths();
/// Configures the context to load CA certificate from a directory.
/// @param path Null-terminated string with a path to a directory. Files in
/// the directory must use the CA subject name hash value as file
/// name with a suffix to disambiguate multiple certificates,
/// e.g., `9d66eef0.0` and `9d66eef0.1`.
/// @returns `true` on success, `false` otherwise and `last_error` can be used
/// to retrieve a human-readable error representation.
[[nodiscard]] bool load_verify_dir(const char* path);
/// Loads a CA certificate file.
/// @param path Null-terminated string with a path to a single PEM file.
/// @returns `true` on success, `false` otherwise and `last_error` can be used
/// to retrieve a human-readable error representation.
[[nodiscard]] bool load_verify_file(const char* path);
/// Loads the first certificate found in given file.
/// @param path Null-terminated string with a path to a single PEM file.
[[nodiscard]] bool use_certificate_from_file(const char* path,
format file_format);
/// Loads the first private key found in given file.
[[nodiscard]] bool use_private_key_from_file(const char* path,
format file_format);
private:
constexpr explicit context(impl* ptr) : pimpl_(ptr) {
// nop
}
impl* pimpl_;
};
} // namespace caf::net::ssl
// 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.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/is_error_code_enum.hpp"
namespace caf::net::ssl {
/// Configures the allowed DTLS versions on a @ref context.
enum class dtls {
any,
v1_0,
v1_2,
};
/// @relates dtls
CAF_NET_EXPORT std::string to_string(dtls);
/// @relates dtls
CAF_NET_EXPORT bool from_string(std::string_view, dtls&);
/// @relates dtls
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<dtls>, dtls&);
/// @relates dtls
template <class Inspector>
bool inspect(Inspector& f, dtls& x) {
return default_enum_inspect(f, x);
}
/// @relates dtls
CAF_NET_EXPORT int native(dtls);
} // namespace caf::net::ssl
// 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.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/is_error_code_enum.hpp"
namespace caf::net::ssl {
/// SSL error code for I/O operations on a @ref connection.
enum class errc : uint8_t {
/// Not-an-error.
none = 0,
/// SSL has closed the connection. The underlying transport may remain open.
closed,
/// Temporary error. SSL failed to write to a socket because it needs to read
/// first.
want_read,
/// Temporary error. SSL failed to read from a socket because it needs to
/// write first.
want_write,
/// Temporary error. The SSL client handshake did not complete yet.
want_connect,
/// Temporary error. The SSL server handshake did not complete yet.
want_accept,
/// Temporary error. An application callback has asked to be called again.
want_x509_lookup,
/// Temporary error. An asynchronous is still processing data and the user
/// must call the preceding function again from the same thread.
want_async,
/// The pool for starting asynchronous jobs is exhausted.
want_async_job,
/// Temporary error. An application callback has asked to be called again.
want_client_hello,
/// The operating system reported a non-recoverable, fatal I/O error. Users
/// may consult OS-specific means to retrieve the underlying error, e.g.,
/// `errno` on UNIX or `WSAGetLastError` on Windows.
syscall_failed,
/// SSL encountered a fatal error, usually a protocol violation.
fatal,
/// An unexpected error occurred with no further explanation available.
unspecified,
};
/// @relates errc
CAF_NET_EXPORT std::string to_string(errc);
/// @relates errc
CAF_NET_EXPORT bool from_string(std::string_view, errc&);
/// @relates errc
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<errc>, errc&);
/// @relates errc
template <class Inspector>
bool inspect(Inspector& f, errc& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net::ssl
CAF_ERROR_CODE_ENUM(caf::net::ssl::errc)
// 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.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include <string>
namespace caf::net::ssl {
/// Format of keys and certificates.
enum class format {
/// Privacy Enhanced Mail format.
pem,
/// Binary ASN1 format.
asn1,
};
/// @relates format
CAF_NET_EXPORT std::string to_string(format);
/// @relates format
CAF_NET_EXPORT bool from_string(std::string_view, format&);
/// @relates format
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<format>, format&);
/// @relates format
template <class Inspector>
bool inspect(Inspector& f, format& x) {
return default_enum_inspect(f, x);
}
/// @relates dtls
CAF_NET_EXPORT int native(format);
} // namespace caf::net::ssl
// 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
namespace caf::net::ssl {
class connection;
class context;
class transport;
enum class dtls;
enum class errc : uint8_t;
enum class format;
enum class tls;
} // namespace caf::net::ssl
......@@ -2,22 +2,18 @@
// 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/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include <cstddef>
#include <cstdint>
namespace caf::net::ssl {
// -- hard-coded default values for various CAF options ------------------------
/// Initializes the SSL layer. Depending on the version, this may be mandatory
/// to call before accessing any SSL functions (OpenSSL prior to version 1.1) or
/// it may have no effect (newer versions of OpenSSL).
CAF_NET_EXPORT void startup();
namespace caf::defaults::middleman {
/// Cleans up any state for the SSL layer. Like @ref startup, this step is
/// mandatory for some versions of the linked SSL library.
CAF_NET_EXPORT void cleanup();
/// Maximum number of cached buffers for sending payloads.
constexpr size_t max_payload_buffers = 100;
/// Maximum number of cached buffers for sending headers.
constexpr size_t max_header_buffers = 10;
/// Port to listen on for tcp.
constexpr uint16_t tcp_port = 0;
} // namespace caf::defaults::middleman
} // namespace caf::net::ssl
// 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.
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/is_error_code_enum.hpp"
namespace caf::net::ssl {
/// Configures the allowed DTLS versions on a @ref context.
enum class tls {
any,
v1_0,
v1_1,
v1_2,
v1_3,
};
/// @relates tls
CAF_NET_EXPORT std::string to_string(tls);
/// @relates tls
CAF_NET_EXPORT bool from_string(std::string_view, tls&);
/// @relates tls
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<tls>, tls&);
/// @relates tls
template <class Inspector>
bool inspect(Inspector& f, tls& x) {
return default_enum_inspect(f, x);
}
/// @relates tls
CAF_NET_EXPORT int native(tls);
/// @relates tls
CAF_NET_EXPORT bool has(tls, tls, tls);
} // namespace caf::net::ssl
// 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/detail/net_export.hpp"
#include "caf/net/ssl/connection.hpp"
#include "caf/net/stream_transport.hpp"
namespace caf::net::ssl {
/// Implements a stream_transport that manages a stream socket with encrypted
/// communication over OpenSSL.
class CAF_NET_EXPORT transport : public stream_transport {
public:
// -- member types -----------------------------------------------------------
using super = stream_transport;
using worker_ptr = std::unique_ptr<socket_event_layer>;
class policy_impl : public stream_transport::policy {
public:
explicit policy_impl(connection conn);
ptrdiff_t read(stream_socket, byte_span) override;
ptrdiff_t write(stream_socket, const_byte_span) override;
stream_transport_error last_error(stream_socket, ptrdiff_t) override;
ptrdiff_t connect(stream_socket) override;
ptrdiff_t accept(stream_socket) override;
size_t buffered() const noexcept override;
connection conn;
};
// -- factories --------------------------------------------------------------
/// Creates a new instance of the SSL transport for a socket that has already
/// performed the SSL handshake.
/// @param conn The connection object for managing @p fd.
/// @param up The layer operating on top of this transport.
static std::unique_ptr<transport> make(connection conn, upper_layer_ptr up);
/// Returns a worker that performs the OpenSSL server handshake on the socket.
/// On success, the worker performs a handover to an `openssl_transport` that
/// runs `up`.
/// @param conn The connection object for managing @p fd.
/// @param up The layer operating on top of this transport.
static worker_ptr make_server(connection conn, upper_layer_ptr up);
/// Returns a worker that performs the OpenSSL client handshake on the socket.
/// On success, the worker performs a handover to an `openssl_transport` that
/// runs `up`.
/// @param conn The connection object for managing @p fd.
/// @param up The layer operating on top of this transport.
static worker_ptr make_client(connection conn, upper_layer_ptr up);
private:
// -- constructors, destructors, and assignment operators --------------------
transport(stream_socket fd, connection conn, upper_layer_ptr up);
policy_impl policy_impl_;
};
} // namespace caf::net::ssl
// 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/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/generic_upper_layer.hpp"
namespace caf::net::stream_oriented {
class lower_layer;
/// The upper layer requests bytes from the lower layer and consumes raw chunks
/// of data.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
virtual ~upper_layer();
/// Initializes the upper layer.
/// @param owner A pointer to the socket manager that owns the entire
/// protocol stack. Remains valid for the lifetime of the upper
/// layer.
/// @param down A pointer to the lower layer that remains valid for the
/// lifetime of the upper layer.
virtual error
init(socket_manager* owner, lower_layer* down, const settings& config)
= 0;
/// Consumes bytes from the lower layer.
/// @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.
[[nodiscard]] virtual ptrdiff_t consume(byte_span buffer, byte_span delta)
= 0;
};
/// Provides access to a resource that operates on a byte stream, e.g., a TCP
/// socket.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~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.
virtual void configure_read(receive_policy policy) = 0;
/// Prepares the layer for outgoing traffic, e.g., by allocating an output
/// buffer as necessary.
virtual void begin_output() = 0;
/// 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()`.
virtual byte_buffer& output_buffer() = 0;
/// Prepares written data for transfer, e.g., by flushing buffers or
/// registering sockets for write events.
virtual bool end_output() = 0;
};
} // namespace caf::net::stream_oriented
// 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/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);
}
bool stopped() const noexcept {
return lptr_->stopped(llptr_);
}
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
......@@ -7,78 +7,76 @@
#include <deque>
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/socket_event_layer.hpp"
#include "caf/net/stream_oriented.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"
#include "caf/tag/io_event_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
namespace caf::net {
/// Configures a stream transport with default socket operations.
struct default_stream_transport_policy {
class CAF_NET_EXPORT stream_transport : public socket_event_layer,
public stream_oriented::lower_layer {
public:
/// Reads data from the socket into the buffer.
static ptrdiff_t read(stream_socket x, byte_span buf) {
return net::read(x, buf);
}
// -- member types -----------------------------------------------------------
/// Writes data from the buffer to the socket.
static ptrdiff_t write(stream_socket x, const_byte_span buf) {
return net::write(x, buf);
}
using socket_type = stream_socket;
/// Returns the last socket error on this thread.
static stream_transport_error last_error(stream_socket, ptrdiff_t) {
return last_socket_error_is_temporary() ? stream_transport_error::temporary
: stream_transport_error::permanent;
}
/// An owning smart pointer type for storing an upper layer object.
using upper_layer_ptr = std::unique_ptr<stream_oriented::upper_layer>;
class policy {
public:
enum class ec {
/// 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,
};
/// Checks whether connecting a non-blocking socket was successful.
static ptrdiff_t connect(stream_socket x) {
// A connection is established if the OS reports a socket as ready for read
// or write and if there is no error on the socket.
return net::probe(x) ? 1 : -1;
}
virtual ~policy();
/// Convenience function that always returns 1. Exists to make writing code
/// against multiple policies easier by providing the same interface.
static ptrdiff_t accept(stream_socket) {
return 1;
}
/// Reads data from the socket into the buffer.
virtual ptrdiff_t read(stream_socket x, byte_span buf) = 0;
/// Returns the number of bytes that are buffered internally and that
/// available for immediate read.
static constexpr size_t buffered() {
return 0;
}
};
/// Writes data from the buffer to the socket.
virtual ptrdiff_t write(stream_socket x, const_byte_span buf) = 0;
/// Implements a stream_transport that manages a stream socket.
template <class Policy, class UpperLayer>
class stream_transport_base {
public:
// -- member types -----------------------------------------------------------
/// Returns the last socket error on this thread.
virtual stream_transport_error last_error(stream_socket, ptrdiff_t) = 0;
using input_tag = tag::io_event_oriented;
/// Checks whether connecting a non-blocking socket was successful.
virtual ptrdiff_t connect(stream_socket x) = 0;
using output_tag = tag::stream_oriented;
/// Convenience function that always returns 1. Exists to make writing code
/// against multiple policies easier by providing the same interface.
virtual ptrdiff_t accept(stream_socket) = 0;
using socket_type = stream_socket;
/// Returns the number of bytes that are buffered internally and available
/// for immediate read.
virtual size_t buffered() const noexcept = 0;
};
using read_result = typename socket_manager::read_result;
class default_policy : public policy {
public:
ptrdiff_t read(stream_socket x, byte_span buf) override;
using write_result = typename socket_manager::write_result;
ptrdiff_t write(stream_socket x, const_byte_span buf) override;
stream_transport_error last_error(stream_socket, ptrdiff_t) override;
ptrdiff_t connect(stream_socket x) override;
ptrdiff_t accept(stream_socket) override;
size_t buffered() const noexcept override;
};
/// Bundles various flags into a single block of memory.
struct flags_t {
......@@ -95,64 +93,34 @@ public:
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit stream_transport_base(Policy policy, Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...), policy_(std::move(policy)) {
memset(&flags, 0, sizeof(flags_t));
read_buf_.resize(default_buf_size);
}
stream_transport(stream_socket fd, upper_layer_ptr up);
// -- interface for stream_oriented_layer_ptr --------------------------------
stream_transport(stream_socket fd, upper_layer_ptr up, policy* custom);
template <class ParentPtr>
bool can_send_more(ParentPtr) const noexcept {
return write_buf_.size() < max_write_buf_size_;
}
stream_transport(const stream_transport&) = delete;
template <class ParentPtr>
static socket_type handle(ParentPtr parent) noexcept {
return parent->handle();
}
stream_transport& operator=(const stream_transport&) = delete;
template <class ParentPtr>
void begin_output(ParentPtr parent) {
if (write_buf_.empty())
parent->register_writing();
}
// -- factories --------------------------------------------------------------
template <class ParentPtr>
byte_buffer& output_buffer(ParentPtr) {
return write_buf_;
}
static std::unique_ptr<stream_transport> make(stream_socket fd,
upper_layer_ptr up);
template <class ParentPtr>
static constexpr void end_output(ParentPtr) {
// nop
}
// -- implementation of stream_oriented::lower_layer -------------------------
template <class ParentPtr>
static void abort_reason(ParentPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
bool can_send_more() const noexcept override;
template <class ParentPtr>
static const error& abort_reason(ParentPtr parent) {
return parent->abort_reason();
}
void configure_read(receive_policy policy) override;
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;
}
void begin_output() override;
template <class ParentPtr>
bool stopped(ParentPtr) const noexcept {
return max_read_size_ == 0;
}
byte_buffer& output_buffer() override;
bool end_output() override;
void suspend_reading() override;
bool stopped_reading() const noexcept override;
// -- properties -------------------------------------------------------------
......@@ -173,288 +141,33 @@ public:
}
auto& upper_layer() noexcept {
return upper_layer_;
return *up_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
return *up_;
}
// -- 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);
}
// -- implementation of socket_event_layer -----------------------------------
// -- event callbacks --------------------------------------------------------
template <class ParentPtr>
read_result handle_read_event(ParentPtr parent) {
CAF_LOG_TRACE(CAF_ARG2("socket", parent->handle().id));
// 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;
};
// 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)) {
case write_result::want_read:
CAF_ASSERT(flags.wanted_read_from_write_event);
return read_result::again;
case write_result::handover:
return read_result::handover;
case write_result::again:
parent->register_writing();
break;
default:
break;
}
}
// Before returning from the event handler, we always call after_reading for
// clients that request this callback.
using detail::make_scope_guard;
auto after_reading_guard = make_scope_guard([this, &this_layer_ptr] { //
after_reading(this_layer_ptr);
});
// Make sure the buffer is large enough.
if (read_buf_.size() < max_read_size_)
read_buf_.resize(max_read_size_);
// Fill up our buffer.
auto rd = policy_.read(parent->handle(),
make_span(read_buf_.data() + buffered_,
read_buf_.size() - buffered_));
// Stop if we failed to get more data.
if (rd < 0) {
switch (policy_.last_error(parent->handle(), rd)) {
case stream_transport_error::temporary:
case stream_transport_error::want_read:
return read_result::again;
case stream_transport_error::want_write:
flags.wanted_write_from_read_event = true;
return read_result::want_write;
default:
return fail(sec::socket_operation_failed);
}
} else if (rd == 0) {
return fail(sec::socket_disconnected);
}
// Make sure we actually have all data currently available to us and the
// policy is not holding on to some bytes. This may happen when using
// OpenSSL or any other transport policy operating on blocks.
buffered_ += static_cast<size_t>(rd);
if (auto policy_buffered = policy_.buffered(); policy_buffered > 0) {
if (auto n = buffered_ + policy_buffered; n > read_buf_.size())
read_buf_.resize(n);
auto rd2 = policy_.read(parent->handle(),
make_span(read_buf_.data() + buffered_,
policy_buffered));
if (rd2 != static_cast<ptrdiff_t>(policy_buffered)) {
CAF_LOG_ERROR("failed to read buffered data from the policy");
return fail(sec::socket_operation_failed);
}
buffered_ += static_cast<size_t>(rd2);
}
// Try to make progress on the application and return control to the
// multiplexer to allow other sockets to run.
return handle_buffered_data(parent);
}
error init(socket_manager* owner, const settings& config) override;
template <class ParentPtr>
read_result handle_buffered_data(ParentPtr parent) {
CAF_LOG_TRACE(CAF_ARG(buffered_));
// 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;
};
// Loop until we have drained the buffer as much as we can.
CAF_ASSERT(min_read_size_ <= max_read_size_);
while (max_read_size_ > 0 && buffered_ >= min_read_size_) {
auto old_max_read_size = max_read_size_;
auto n = std::min(buffered_, size_t{max_read_size_});
auto bytes = make_span(read_buf_.data(), n);
auto delta = bytes.subspan(delta_offset_);
auto consumed = upper_layer_.consume(this_layer_ptr, bytes, delta);
if (consumed < 0) {
// Negative values indicate that the application encountered an
// unrecoverable error.
upper_layer_.abort(this_layer_ptr,
parent->abort_reason_or(caf::sec::runtime_error,
"consumed < 0"));
return read_result::stop;
} else if (consumed == 0) {
// Returning 0 means that the application wants more data. Note:
// max_read_size_ may have changed if the application realized it
// requires more data to parse the input. It may of course only increase
// the max_read_size_ in this case, everything else makes no sense.
delta_offset_ = static_cast<ptrdiff_t>(n);
if (n == max_read_size_ || max_read_size_ < old_max_read_size) {
CAF_LOG_ERROR("application failed to make progress");
return fail(sec::runtime_error);
} else if (n == buffered_) {
// Either the application has increased max_read_size_ or we
// did not reach max_read_size_ the first time. In both cases, we
// cannot proceed without receiving more data.
return read_result::again;
}
} else if (static_cast<size_t>(consumed) > n) {
// Must not happen. An application cannot handle more data then we pass
// to it.
upper_layer_.abort(this_layer_ptr,
parent->abort_reason_or(caf::sec::runtime_error,
"consumed > buffer.size"));
return read_result::stop;
} else {
// Shove the unread bytes to the beginning of the buffer and continue
// to the next loop iteration.
auto del = static_cast<size_t>(consumed);
auto prev = buffered_;
buffered_ -= del;
delta_offset_ = 0;
if (buffered_ > 0) {
auto new_begin = read_buf_.begin() + del;
auto new_end = read_buf_.begin() + prev;
std::copy(new_begin, new_end, read_buf_.begin());
}
}
}
return max_read_size_ > 0 ? read_result::again : read_result::stop;
}
read_result handle_read_event() override;
template <class ParentPtr>
write_result handle_write_event(ParentPtr parent) {
CAF_LOG_TRACE(CAF_ARG2("socket", 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 write_result::stop;
};
// Resume a read operation if the transport waited for the socket to be
// writable from the last call to handle_read_event.
if (flags.wanted_write_from_read_event) {
flags.wanted_write_from_read_event = false;
switch (handle_read_event(parent)) {
case read_result::want_write:
CAF_ASSERT(flags.wanted_write_from_read_event);
return write_result::again;
case read_result::handover:
return write_result::handover;
case read_result::again:
parent->register_reading();
break;
default:
break;
}
// Fall though and see if we also have something to write.
}
// 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,
"prepare_send failed"));
return write_result::stop;
}
if (write_buf_.empty())
return !upper_layer_.done_sending(this_layer_ptr) ? write_result::again
: write_result::stop;
auto write_res = policy_.write(parent->handle(), write_buf_);
if (write_res > 0) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + write_res);
return !write_buf_.empty() || !upper_layer_.done_sending(this_layer_ptr)
? write_result::again
: write_result::stop;
} else if (write_res < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors.
switch (policy_.last_error(parent->handle(), write_res)) {
case stream_transport_error::temporary:
case stream_transport_error::want_write:
return write_result::again;
case stream_transport_error::want_read:
flags.wanted_read_from_write_event = true;
return write_result::want_read;
default:
return fail(sec::socket_operation_failed);
}
} else {
// write() returns 0 if the connection was closed.
return fail(sec::socket_disconnected);
}
}
read_result handle_buffered_data() override;
template <class ParentPtr>
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;
}
} else {
return handle_buffered_data(parent);
}
}
write_result handle_write_event() override;
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;
}
read_result handle_continue_reading() override;
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);
}
void abort(const error& reason) override;
private:
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);
}
protected:
/// The socket file descriptor.
stream_socket fd_;
flags_t flags;
/// Stores temporary flags.
flags_t flags_;
/// Caches the config parameter for limiting max. socket operations.
uint32_t max_consecutive_reads_ = 0;
......@@ -483,29 +196,34 @@ private:
byte_buffer write_buf_;
/// Processes incoming data and generates outgoing data.
UpperLayer upper_layer_;
upper_layer_ptr up_;
/// Configures how we read and write to the socket.
Policy policy_;
socket_manager* parent_ = nullptr;
/// Configures how we read and write to the socket.
policy* policy_ = nullptr;
/// Fallback policy.
default_policy default_policy_;
// TODO: add [[no_unique_address]] to default_policy_ when switching to C++20.
};
/// Implements a stream_transport that manages a stream socket.
template <class UpperLayer>
class stream_transport
: public stream_transport_base<default_stream_transport_policy, UpperLayer> {
public:
// -- member types -----------------------------------------------------------
/// @relates stream_transport
CAF_NET_EXPORT std::string to_string(stream_transport::policy::ec);
using super
= stream_transport_base<default_stream_transport_policy, UpperLayer>;
/// @relates stream_transport
CAF_NET_EXPORT bool from_string(std::string_view,
stream_transport::policy::ec&);
// -- constructors, destructors, and assignment operators --------------------
/// @relates stream_transport
CAF_NET_EXPORT bool from_integer(int, stream_transport::policy::ec&);
template <class... Ts>
explicit stream_transport(Ts&&... xs)
: super(default_stream_transport_policy{}, std::forward<Ts>(xs)...) {
// nop
}
};
/// @relates stream_transport
template <class Inspector>
bool inspect(Inspector& f, stream_transport::policy::ec& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net
......@@ -9,9 +9,11 @@
namespace caf::net {
struct CAF_NET_EXPORT this_host {
/// Groups functions for managing the host system.
class CAF_NET_EXPORT this_host {
public:
/// Initializes the network subsystem.
static error startup();
static void startup();
/// Release any resources of the network subsystem.
static void cleanup();
......
......@@ -17,7 +17,7 @@
namespace caf::detail {
template <template <class> class Transport, class Trait>
template <class Transport, class Trait>
class ws_acceptor_factory {
public:
using connector_pointer = net::web_socket::flow_connector_ptr<Trait>;
......@@ -32,10 +32,11 @@ public:
}
template <class Socket>
net::socket_manager_ptr make(Socket fd, net::multiplexer* mpx) {
using app_t = net::web_socket::flow_bridge<Trait>;
using stack_t = Transport<net::web_socket::server<app_t>>;
return net::make_socket_manager<stack_t>(fd, mpx, connector_);
net::socket_manager_ptr make(net::multiplexer* mpx, Socket fd) {
auto app = net::web_socket::flow_bridge<Trait>::make(connector_);
auto ws = net::web_socket::server::make(std::move(app));
auto transport = Transport::make(fd, std::move(ws));
return net::socket_manager::make(mpx, fd, std::move(transport));
}
void abort(const error&) {
......@@ -80,8 +81,8 @@ auto make_accept_event_resources() {
/// @param on_request Function object for accepting incoming requests.
/// @param limit The maximum amount of connections before closing @p fd. Passing
/// 0 means "no limit".
template <template <class> class Transport = stream_transport, class Socket,
class... Ts, class OnRequest>
template <class Transport = stream_transport, class Socket, class... Ts,
class OnRequest>
void accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out,
OnRequest on_request, size_t limit = 0) {
using trait_t = default_trait;
......@@ -95,8 +96,8 @@ void accept(actor_system& sys, Socket fd, acceptor_resource_t<Ts...> out,
auto& mpx = sys.network_manager().mpx();
auto conn = std::make_shared<conn_t>(std::move(on_request), std::move(buf));
auto factory = factory_t{std::move(conn)};
auto ptr = make_socket_manager<impl_t>(std::move(fd), &mpx, limit,
std::move(factory));
auto impl = impl_t::make(fd, limit, std::move(factory));
auto ptr = socket_manager::make(&mpx, fd, std::move(impl));
mpx.init(ptr);
}
// TODO: accept() should return a disposable to stop the WebSocket server.
......
......@@ -13,12 +13,11 @@
#include "caf/net/http/v1.hpp"
#include "caf/net/message_flow_bridge.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/net/web_socket/framing.hpp"
#include "caf/net/web_socket/handshake.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include <algorithm>
......@@ -28,150 +27,71 @@ namespace caf::net::web_socket {
/// 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 client {
class client : public stream_oriented::upper_layer {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using handshake_ptr = std::unique_ptr<handshake>;
using output_tag = tag::mixed_message_oriented;
using upper_layer_ptr = std::unique_ptr<web_socket::upper_layer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit client(handshake hs, Ts&&... xs)
: upper_layer_(std::forward<Ts>(xs)...) {
handshake_.reset(new handshake(std::move(hs)));
}
client(handshake_ptr hs, upper_layer_ptr up);
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
CAF_ASSERT(handshake_ != nullptr);
owner_ = owner;
if (!handshake_->has_mandatory_fields())
return make_error(sec::runtime_error,
"handshake data lacks mandatory fields");
if (!handshake_->has_valid_key())
handshake_->randomize_key();
cfg_ = cfg;
down->begin_output();
handshake_->write_http_1_request(down->output_buffer());
down->end_output();
down->configure_read(receive_policy::up_to(handshake::max_http_size));
return none;
}
// -- factories --------------------------------------------------------------
static std::unique_ptr<client> make(handshake_ptr hs, upper_layer_ptr up);
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_.upper_layer();
web_socket::upper_layer& upper_layer() noexcept {
return framing_.upper_layer();
}
const auto& upper_layer() const noexcept {
return upper_layer_.upper_layer();
const web_socket::upper_layer& upper_layer() const noexcept {
return framing_.upper_layer();
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return handshake_complete() ? upper_layer_.prepare_send(down) : true;
stream_oriented::lower_layer& lower_layer() noexcept {
return framing_.lower_layer();
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return handshake_complete() ? upper_layer_.done_sending(down) : true;
const stream_oriented::lower_layer& lower_layer() const noexcept {
return framing_.lower_layer();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
if (handshake_complete())
upper_layer_.abort(down, reason);
bool handshake_completed() const noexcept {
return hs_ == nullptr;
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr down) {
if (handshake_complete())
upper_layer_.continue_reading(down);
}
// -- implementation of stream_oriented::upper_layer -------------------------
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span delta) {
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("bytes", input.size()));
// Short circuit to the framing layer after the handshake completed.
if (handshake_complete())
return upper_layer_.consume(down, input, delta);
// Check whether received a HTTP header or else wait for more data or abort
// when exceeding the maximum size.
auto [hdr, remainder] = http::v1::split_header(input);
if (hdr.empty()) {
if (input.size() >= handshake::max_http_size) {
CAF_LOG_ERROR("server response exceeded maximum header size");
auto err = make_error(pec::too_many_characters,
"exceeded maximum header size");
down->abort_reason(std::move(err));
return -1;
} else {
return 0;
}
} else if (!handle_header(down, hdr)) {
return -1;
} else if (remainder.empty()) {
CAF_ASSERT(hdr.size() == input.size());
return hdr.size();
} else {
CAF_LOG_DEBUG(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("remainder", remainder.size()));
if (auto sub_result = upper_layer_.consume(down, remainder, remainder);
sub_result >= 0) {
return hdr.size() + sub_result;
} else {
return sub_result;
}
}
}
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
bool handshake_complete() const noexcept {
return handshake_ == nullptr;
}
void abort(const error& reason) override;
ptrdiff_t consume(byte_span buffer, byte_span delta) override;
void continue_reading() override;
bool prepare_send() override;
bool done_sending() override;
private:
// -- HTTP response processing -----------------------------------------------
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, std::string_view http) {
CAF_ASSERT(handshake_ != nullptr);
auto http_ok = handshake_->is_valid_http_1_response(http);
handshake_.reset();
if (http_ok) {
if (auto err = upper_layer_.init(owner_, down, cfg_)) {
CAF_LOG_DEBUG("failed to initialize WebSocket framing layer");
down->abort_reason(std::move(err));
return false;
} else {
CAF_LOG_DEBUG("completed WebSocket handshake");
return true;
}
} else {
CAF_LOG_DEBUG("received invalid WebSocket handshake");
down->abort_reason(make_error(
sec::runtime_error,
"received invalid WebSocket handshake response from server"));
return false;
}
}
bool handle_header(std::string_view http);
// -- member variables -------------------------------------------------------
/// Stores the WebSocket handshake data until the handshake completed.
std::unique_ptr<handshake> handshake_;
handshake_ptr hs_;
/// Stores the upper layer.
framing<UpperLayer> upper_layer_;
framing framing_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
......@@ -179,17 +99,4 @@ private:
settings cfg_;
};
template <template <class> class Transport = stream_transport, class Socket,
class T, class Trait>
void run_client(multiplexer& mpx, Socket fd, handshake hs,
async::consumer_resource<T> in, async::producer_resource<T> out,
Trait trait) {
using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>;
using stack_t = Transport<client<app_t>>;
auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(hs),
std::move(in), std::move(out),
std::move(trait));
mpx.init(mgr);
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/async/publisher.hpp"
#include "caf/net/observer_adapter.hpp"
#include "caf/net/publisher_adapter.hpp"
namespace caf::net::web_socket::internal {
/// Implements a WebSocket application that uses two flows for bidirectional
/// communication: one input flow and one output flow.
template <class Reader, class Writer>
class bidir_app {
public:
using input_tag = tag::message_oriented;
using reader_output_type = typename Reader::value_type;
using writer_input_type = typename Writer::value_type;
bidir_app(Reader&& reader, Writer&& writer)
: reader_(std::move(reader)), writer_(std::move(writer)) {
// nop
}
async::publisher<reader_input_type>
connect_flows(net::socket_manager* mgr,
async::publisher<writer_input_type> in) {
using make_counted;
// Connect the writer adapter.
using writer_input_t = net::observer_adapter<writer_input_type>;
writer_input_ = make_counted<writer_input_t>(mgr);
in.subscribe(writer_input_->as_observer());
// Create the reader adapter.
using reader_output_t = net::publisher_adapter<node_message>;
reader_output_ = make_counted<reader_output_t>(mgr, reader_.buffer_size(),
reader_.batch_size());
return reader_output_->as_publisher();
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr&&, const settings& cfg) {
if (auto err = reader_.init(cfg))
return err;
if (auto err = writer_.init(cfg))
return err;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
while (down->can_send_more()) {
auto [val, done, err] = writer_input_->poll();
if (val) {
if (!write(down, *val)) {
down->abort_reason(make_error(ec::invalid_message));
return false;
}
} else if (done) {
if (err) {
down->abort_reason(*err);
return false;
}
} else {
break;
}
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return !writer_input_->has_data();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
reader_output_->flush();
if (reason == sec::socket_disconnected || reason == sec::disposed)
reader_output_->on_complete();
else
reader_output_->on_error(reason);
}
template <class LowerLayerPtr>
void after_reading(LowerLayerPtr) {
reader_output_->flush();
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, std::string_view text) {
reader_msgput_type msg;
if (reader_.deserialize_text(text, msg)) {
if (reader_output_->push(std::move(msg)) == 0)
down->suspend_reading();
return static_cast<ptrdiff_t>(text.size());
} else {
down->abort_reason(make_error(ec::invalid_message));
return -1;
}
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr, caf::byte_span bytes) {
reader_msgput_type msg;
if (reader_.deserialize_binary(bytes, msg)) {
if (reader_output_->push(std::move(msg)) == 0)
down->suspend_reading();
return static_cast<ptrdiff_t>(text.size());
} else {
down->abort_reason(make_error(ec::invalid_message));
return -1;
}
}
private:
template <class LowerLayerPtr>
bool write(LowerLayerPtr down, const writer_input_type& msg) {
if (writer_.is_text_message(msg)) {
down->begin_text_message();
if (writer_.serialize_text(msg, down->text_message_buffer())) {
down->end_text_message();
return true;
} else {
return false;
}
} else {
down->begin_binary_message();
if (writer_.serialize_binary(msg, down->binary_message_buffer())) {
down->end_binary_message();
return true;
} else {
return false;
}
}
}
/// Deserializes text or binary messages from the socket.
Reader reader_;
/// Serializes text or binary messages to the socket.
Writer writer_;
/// Forwards outgoing messages to the peer. We write whatever we receive from
/// this channel to the socket.
net::observer_adapter_ptr<node_message> writer_input_;
/// After receiving messages from the socket, we publish to this adapter for
/// downstream consumers.
net::publisher_adapter_ptr<node_message> reader_output_;
};
} // namespace caf::net::web_socket::internal
namespace caf::net::web_socket {
/// Connects to a WebSocket server for bidirectional communication.
/// @param sys The enclosing actor system.
/// @param cfg Provides optional configuration parameters such as WebSocket
/// protocols and extensions for the handshake.
/// @param locator Identifies the WebSocket server.
/// @param writer_input Publisher of events that go out to the server.
/// @param reader Reads messages from the server and publishes them locally.
/// @param writer Writes messages from the @p writer_input to text or binary
/// messages for sending them to the server.
/// @returns a publisher that makes messages from the server accessible on
/// success, an error otherwise.
template <template <class> class Transport = stream_transport, class Reader,
class Writer>
expected<async::publisher<typename Reader::value_type>>
flow_connect_bidir(actor_system& sys, const settings& cfg, uri locator,
async::publisher writer_input, Reader reader,
Writer writer) {
using stack_t
= Transport<web_socket::client<internal::bidir_app<Reader, Writer>>>;
using impl = socket_manager_impl<stack_t>;
if (locator.empty()) {
return make_error(sec::invalid_argument, __func__,
"cannot connect to empty URI");
} else if (locator.scheme() != "ws") {
return make_error(sec::invalid_argument, __func__,
"malformed URI, expected format 'ws://<authority>'");
} else if (!locator.fragment().empty()) {
return make_error(sec::invalid_argument, __func__,
"query and fragment components are not supported");
} else if (locator.authority().empty()) {
return make_error(sec::invalid_argument, __func__,
"malformed URI, expected format 'ws://<authority>'");
} else if (auto sock = impl::connect_to(locator.authority())) {
web_socket::handshake hs;
hs.host(to_string(locator.authority()));
if (locator.path().empty())
hs.endpoint("/");
else
hs.endpoint(to_string(locator.path()));
if (auto protocols = get_as<std::string>(cfg, "protocols"))
hs.protocols(std::move(*protocols));
if (auto extensions = get_as<std::string>(cfg, "extensions"))
hs.extensions(std::move(*extensions));
auto mgr = make_counted<impl>(*sock, sys.network_manager().mpx_ptr(),
std::move(hs), std::move(reader),
std::move(writer));
auto out = mgr->upper_layer().connect_flows(std::move(writer_input));
if (auto err = mgr->init(cfg); !err) {
return {std::move(out)};
} else {
return {std::move(err)};
}
} else {
return {std::move(sock.error())};
}
}
} // namespace caf::net::web_socket
......@@ -9,10 +9,10 @@
#include "caf/net/consumer_adapter.hpp"
#include "caf/net/producer_adapter.hpp"
#include "caf/net/web_socket/flow_connector.hpp"
#include "caf/net/web_socket/lower_layer.hpp"
#include "caf/net/web_socket/request.hpp"
#include "caf/net/web_socket/upper_layer.hpp"
#include "caf/sec.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/no_auto_reading.hpp"
#include <utility>
......@@ -20,10 +20,8 @@ namespace caf::net::web_socket {
/// Translates between a message-oriented transport and data flows.
template <class Trait>
class flow_bridge : public tag::no_auto_reading {
class flow_bridge : public web_socket::upper_layer {
public:
using input_tag = tag::mixed_message_oriented;
using input_type = typename Trait::input_type;
using output_type = typename Trait::output_type;
......@@ -42,54 +40,34 @@ public:
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings& cfg) {
auto [err, pull, push] = conn_->on_request(cfg);
if (!err) {
in_ = consumer_type::try_open(mgr, pull);
out_ = producer_type::try_open(mgr, push);
CAF_ASSERT(in_ != nullptr);
CAF_ASSERT(out_ != nullptr);
conn_ = nullptr;
return none;
} else {
conn_ = nullptr;
return err;
}
static std::unique_ptr<flow_bridge> make(connector_pointer conn) {
return std::make_unique<flow_bridge>(std::move(conn));
}
template <class LowerLayerPtr>
bool write(LowerLayerPtr down, const output_type& item) {
bool write(const output_type& item) {
if (trait_.converts_to_binary(item)) {
down->begin_binary_message();
auto& bytes = down->binary_message_buffer();
return trait_.convert(item, bytes) && down->end_binary_message();
down_->begin_binary_message();
auto& bytes = down_->binary_message_buffer();
return trait_.convert(item, bytes) && down_->end_binary_message();
} else {
down->begin_text_message();
auto& text = down->text_message_buffer();
return trait_.convert(item, text) && down->end_text_message();
down_->begin_text_message();
auto& text = down_->text_message_buffer();
return trait_.convert(item, text) && down_->end_text_message();
}
}
template <class LowerLayerPtr>
struct write_helper {
using bridge_type = flow_bridge;
bridge_type* bridge;
LowerLayerPtr down;
flow_bridge* thisptr;
bool aborted = false;
size_t consumed = 0;
error err;
write_helper(bridge_type* bridge, LowerLayerPtr down)
: bridge(bridge), down(down) {
explicit write_helper(flow_bridge* thisptr) : thisptr(thisptr) {
// nop
}
void on_next(const output_type& item) {
if (!bridge->write(down, item)) {
if (!thisptr->write(item))
aborted = true;
return;
}
}
void on_complete() {
......@@ -101,20 +79,42 @@ public:
}
};
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
write_helper<LowerLayerPtr> helper{this, down};
while (down->can_send_more() && in_) {
// -- implementation of web_socket::lower_layer ------------------------------
error init(net::socket_manager* mgr, web_socket::lower_layer* down,
const settings& cfg) override {
down_ = down;
auto [err, pull, push] = conn_->on_request(cfg);
if (!err) {
in_ = consumer_type::try_open(mgr, pull);
out_ = producer_type::try_open(mgr, push);
CAF_ASSERT(in_ != nullptr);
CAF_ASSERT(out_ != nullptr);
conn_ = nullptr;
return none;
} else {
conn_ = nullptr;
return err;
}
}
void continue_reading() override {
// nop
}
bool prepare_send() override {
write_helper helper{this};
while (down_->can_send_more() && in_) {
auto [again, consumed] = in_->pull(async::delay_errors, 1, helper);
if (!again) {
if (helper.err) {
down->send_close_message(helper.err);
down_->send_close_message(helper.err);
} else {
down->send_close_message();
down_->send_close_message();
}
in_ = nullptr;
return false;
} else if (helper.aborted) {
down->abort_reason(make_error(sec::conversion_failed));
in_->cancel();
in_ = nullptr;
return false;
......@@ -125,13 +125,11 @@ public:
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return !in_ || !in_->has_data();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void abort(const error& reason) override {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed)
......@@ -146,39 +144,31 @@ public:
}
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr down, byte_span buf) {
if (!out_) {
down->abort_reason(make_error(sec::connection_closed));
ptrdiff_t consume_binary(byte_span buf) override {
if (!out_)
return -1;
}
input_type val;
if (!trait_.convert(buf, val)) {
down->abort_reason(make_error(sec::conversion_failed));
if (!trait_.convert(buf, val))
return -1;
}
if (out_->push(std::move(val)) == 0)
down->suspend_reading();
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, std::string_view buf) {
if (!out_) {
down->abort_reason(make_error(sec::connection_closed));
ptrdiff_t consume_text(std::string_view buf) override {
if (!out_)
return -1;
}
input_type val;
if (!trait_.convert(buf, val)) {
down->abort_reason(make_error(sec::conversion_failed));
if (!trait_.convert(buf, val))
return -1;
}
if (out_->push(std::move(val)) == 0)
down->suspend_reading();
down_->suspend_reading();
return static_cast<ptrdiff_t>(buf.size());
}
private:
web_socket::lower_layer* down_;
/// The output of the application. Serialized to the socket.
intrusive_ptr<consumer_type> in_;
......
......@@ -7,14 +7,17 @@
#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/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/net/web_socket/lower_layer.hpp"
#include "caf/net/web_socket/status.hpp"
#include "caf/net/web_socket/upper_layer.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 <memory>
#include <random>
#include <string_view>
#include <vector>
......@@ -22,18 +25,13 @@
namespace caf::net::web_socket {
/// Implements the WebSocket framing protocol as defined in RFC-6455.
template <class UpperLayer>
class framing {
class framing : public web_socket::lower_layer {
public:
// -- member types -----------------------------------------------------------
using binary_buffer = std::vector<std::byte>;
using text_buffer = std::vector<char>;
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
using upper_layer_ptr = std::unique_ptr<web_socket::upper_layer>;
// -- constants --------------------------------------------------------------
......@@ -45,28 +43,30 @@ public:
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit framing(Ts&&... xs) : upper_layer_(std::forward<Ts>(xs)...) {
explicit framing(upper_layer_ptr up) : up_(std::move(up)) {
// 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);
}
void init(socket_manager* owner, stream_oriented::lower_layer* down);
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_;
return *up_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
return *up_;
}
auto& lower_layer() noexcept {
return *down_;
}
const auto& lower_layer() const noexcept {
return *down_;
}
/// When set to true, causes the layer to mask all outgoing frames with a
......@@ -75,320 +75,77 @@ public:
/// the standard.
bool mask_outgoing_frames = true;
// -- interface for mixed_message_oriented_layer_ptr -------------------------
// -- web_socket::lower_layer implementation ---------------------------------
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 void suspend_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::stop());
}
bool can_send_more() const noexcept override;
template <class LowerLayerPtr>
static constexpr void begin_binary_message(LowerLayerPtr) {
// nop
}
void suspend_reading() override;
template <class LowerLayerPtr>
binary_buffer& binary_message_buffer(LowerLayerPtr) {
return binary_buf_;
}
bool stopped_reading() const noexcept override;
template <class LowerLayerPtr>
bool end_binary_message(LowerLayerPtr down) {
ship_frame(down, binary_buf_);
return true;
}
void request_messages() override;
template <class LowerLayerPtr>
static constexpr void begin_text_message(LowerLayerPtr) {
// nop
}
void begin_binary_message() override;
template <class LowerLayerPtr>
text_buffer& text_message_buffer(LowerLayerPtr) {
return text_buf_;
}
byte_buffer& binary_message_buffer() override;
template <class LowerLayerPtr>
bool end_text_message(LowerLayerPtr down) {
ship_frame(down, text_buf_);
return true;
}
bool end_binary_message() override;
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr down) {
ship_close(down);
return true;
}
void begin_text_message() override;
template <class LowerLayerPtr>
bool
send_close_message(LowerLayerPtr down, status code, std::string_view desc) {
ship_close(down, static_cast<uint16_t>(code), desc);
return true;
}
text_buffer& text_message_buffer() override;
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr down, const error& reason) {
ship_close(down, static_cast<uint16_t>(status::unexpected_condition),
to_string(reason));
return true;
}
bool end_text_message() override;
template <class LowerLayerPtr>
static void abort_reason(LowerLayerPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
void send_close_message() override;
template <class LowerLayerPtr>
static const error& abort_reason(LowerLayerPtr parent) {
return parent->abort_reason();
}
void send_close_message(status code, std::string_view desc) override;
// -- interface for the lower layer ------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return upper_layer_.prepare_send(this_layer_ptr(down));
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return upper_layer_.done_sending(this_layer_ptr(down));
}
template <class LowerLayerPtr>
static void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(2048));
}
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) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down->stopped())
down->configure_read(receive_policy::up_to(2048));
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) {
// Call upper layer.
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()) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down->stopped())
down->configure_read(receive_policy::up_to(2048));
return consumed + static_cast<ptrdiff_t>(frame_size);
}
consumed += static_cast<ptrdiff_t>(frame_size);
}
}
ptrdiff_t consume(byte_span input, byte_span);
private:
// -- implementation details -------------------------------------------------
template <class LowerLayerPtr>
bool handle(LowerLayerPtr down, uint8_t opcode, byte_span payload) {
switch (opcode) {
case detail::rfc6455::text_frame: {
std::string_view text{reinterpret_cast<const char*>(payload.data()),
payload.size()};
return upper_layer_.consume_text(this_layer_ptr(down), text) >= 0;
}
case detail::rfc6455::binary_frame:
return upper_layer_.consume_binary(this_layer_ptr(down), payload) >= 0;
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();
}
bool handle(uint8_t opcode, byte_span payload);
template <class LowerLayerPtr>
void ship_close(LowerLayerPtr down, uint16_t code, std::string_view msg) {
uint32_t mask_key = 0;
byte_buffer payload;
payload.reserve(msg.size() + 2);
payload.push_back(static_cast<std::byte>((code & 0xFF00) >> 8));
payload.push_back(static_cast<std::byte>(code & 0x00FF));
for (auto c : msg)
payload.push_back(static_cast<std::byte>(c));
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::connection_close, mask_key,
payload, down->output_buffer());
down->end_output();
}
void ship_pong(byte_span payload);
template <class LowerLayerPtr>
void ship_close(LowerLayerPtr down) {
uint32_t mask_key = 0;
std::byte payload[] = {// Error code 1000: normal close.
std::byte{0x03}, std::byte{0xE8},
// "EOF" string as goodbye message.
std::byte{'E'}, std::byte{'O'}, std::byte{'F'}};
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::connection_close, mask_key,
payload, down->output_buffer());
down->end_output();
}
void ship_close(uint16_t code, std::string_view msg);
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();
}
void ship_close();
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_mixed_message_oriented_layer_ptr(this, down);
}
template <class T>
void ship_frame(std::vector<T>& buf);
// -- member variables -------------------------------------------------------
// Buffer for assembling binary frames.
/// Points to the transport layer below.
stream_oriented::lower_layer* down_;
/// Buffer for assembling binary frames.
binary_buffer binary_buf_;
// Buffer for assembling text frames.
/// Buffer for assembling text frames.
text_buffer text_buf_;
// 32-bit random number generator.
/// A 32-bit random number generator.
std::mt19937 rng_;
/// Caches the opcode while decoding.
uint8_t opcode_ = nil_code;
// Assembles fragmented payloads.
/// Assembles fragmented payloads.
binary_buffer payload_buf_;
// Next layer in the processing chain.
UpperLayer upper_layer_;
/// Next layer in the processing chain.
upper_layer_ptr up_;
};
} // namespace caf::net::web_socket
......@@ -18,6 +18,12 @@ class flow_bridge;
template <class Trait, class... Ts>
class request;
class CAF_CORE_EXPORT frame;
class frame;
class framing;
class lower_layer;
class server;
class upper_layer;
enum class status : uint16_t;
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_lower_layer.hpp"
#include "caf/net/web_socket/fwd.hpp"
#include <string_view>
namespace caf::net::web_socket {
/// Produces text and binary messages for the upper layer.
class CAF_NET_EXPORT lower_layer : public generic_lower_layer {
public:
virtual ~lower_layer();
/// Pulls messages from the transport until calling `suspend_reading`.
virtual void request_messages() = 0;
/// Begins transmission of a binary message.
virtual void begin_binary_message() = 0;
/// Returns the buffer for the current binary message. Must be called between
/// calling `begin_binary_message` and `end_binary_message`.
virtual byte_buffer& binary_message_buffer() = 0;
/// Seals the current binary message buffer and ships a new WebSocket frame.
virtual bool end_binary_message() = 0;
/// Begins transmission of a text message.
virtual void begin_text_message() = 0;
/// Returns the buffer for the current text message. Must be called between
/// calling `begin_text_message` and `end_text_message`.
virtual text_buffer& text_message_buffer() = 0;
/// Seals the current text message buffer and ships a new WebSocket frame.
virtual bool end_text_message() = 0;
/// Sends the close message with @ref status `normal_close`.
virtual void send_close_message() = 0;
/// Sends the close message with an custom @ref status.
virtual void send_close_message(status code, std::string_view desc) = 0;
/// Sends the close message with @ref status `unexpected_condition`.
void send_close_message(const error& reason);
};
} // namespace caf::net::web_socket
......@@ -5,6 +5,7 @@
#pragma once
#include "caf/byte_span.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp"
......@@ -17,14 +18,15 @@
#include "caf/net/multiplexer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/net/web_socket/framing.hpp"
#include "caf/net/web_socket/handshake.hpp"
#include "caf/net/web_socket/lower_layer.hpp"
#include "caf/net/web_socket/status.hpp"
#include "caf/net/web_socket/upper_layer.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include <algorithm>
......@@ -34,210 +36,69 @@ namespace caf::net::web_socket {
/// 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 server {
class CAF_NET_EXPORT server : public stream_oriented::upper_layer {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
using upper_layer_ptr = std::unique_ptr<web_socket::upper_layer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit 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;
}
explicit server(upper_layer_ptr up);
// -- initialization ---------------------------------------------------------
// -- factories --------------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
owner_ = owner;
cfg_ = cfg;
down->configure_read(receive_policy::up_to(handshake::max_http_size));
return none;
}
static std::unique_ptr<server> make(upper_layer_ptr up);
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_.upper_layer();
return framing_.upper_layer();
}
const auto& upper_layer() const noexcept {
return upper_layer_.upper_layer();
return framing_.upper_layer();
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.prepare_send(down) : true;
auto& lower_layer() noexcept {
return framing_.lower_layer();
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.done_sending(down) : true;
const auto& lower_layer() const noexcept {
return framing_.lower_layer();
}
template <class LowerLayerPtr>
static void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(handshake::max_http_size));
bool handshake_complete() const noexcept {
return handshake_complete_;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
if (handshake_complete_)
upper_layer_.abort(down, reason);
}
// -- stream_oriented::upper_layer implementation ----------------------------
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span delta) {
using namespace std::literals;
CAF_LOG_TRACE(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("bytes", input.size()));
// Short circuit to the framing layer after the handshake completed.
if (handshake_complete_)
return upper_layer_.consume(down, input, delta);
// Check whether we received an HTTP header or else wait for more data.
// Abort when exceeding the maximum size.
auto [hdr, remainder] = http::v1::split_header(input);
if (hdr.empty()) {
if (input.size() >= handshake::max_http_size) {
down->begin_output();
http::v1::write_response(http::status::request_header_fields_too_large,
"text/plain"sv,
"Header exceeds maximum size."sv,
down->output_buffer());
down->end_output();
auto err = make_error(pec::too_many_characters,
"exceeded maximum header size");
down->abort_reason(std::move(err));
return -1;
} else {
return 0;
}
} else if (!handle_header(down, hdr)) {
return -1;
} else if (remainder.empty()) {
CAF_ASSERT(hdr.size() == input.size());
return hdr.size();
} else {
CAF_LOG_DEBUG(CAF_ARG2("socket", down->handle().id)
<< CAF_ARG2("remainder", remainder.size()));
if (auto sub_result = upper_layer_.consume(down, remainder, remainder);
sub_result >= 0) {
return hdr.size() + sub_result;
} else {
return sub_result;
}
}
}
error init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& config) override;
bool handshake_complete() const noexcept {
return handshake_complete_;
}
void abort(const error& reason) override;
private:
// -- HTTP request processing ------------------------------------------------
ptrdiff_t consume(byte_span input, byte_span) override;
template <class LowerLayerPtr>
void
write_response(LowerLayerPtr down, http::status code, std::string_view msg) {
down->begin_output();
http::v1::write_response(code, "text/plain", msg, down->output_buffer());
down->end_output();
}
void continue_reading() override;
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, std::string_view http) {
using namespace std::literals;
// Parse the header and reject invalid inputs.
http::header hdr;
auto [code, msg] = hdr.parse(http);
if (code != http::status::ok) {
write_response(down, code, msg);
down->abort_reason(make_error(pec::invalid_argument, "malformed header"));
return false;
}
if (hdr.method() != http::method::get) {
write_response(down, http::status::bad_request,
"Expected a WebSocket handshake.");
auto err = make_error(pec::invalid_argument,
"invalid operation: expected method get, got "
+ to_string(hdr.method()));
down->abort_reason(std::move(err));
return false;
}
// Check whether the mandatory fields exist.
auto sec_key = hdr.field("Sec-WebSocket-Key");
if (sec_key.empty()) {
auto descr = "Mandatory field Sec-WebSocket-Key missing or invalid."s;
write_response(down, http::status::bad_request, descr);
CAF_LOG_DEBUG("received invalid WebSocket handshake");
down->abort_reason(make_error(pec::missing_field, std::move(descr)));
return false;
}
// Store the request information in the settings for the upper layer.
auto& ws = cfg_["web-socket"].as_dictionary();
put(ws, "method", to_rfc_string(hdr.method()));
put(ws, "path", std::string{hdr.path()});
put(ws, "query", hdr.query());
put(ws, "fragment", hdr.fragment());
put(ws, "http-version", hdr.version());
if (!hdr.fields().empty()) {
auto& fields = ws["fields"].as_dictionary();
for (auto& [key, val] : hdr.fields())
put(fields, std::string{key}, std::string{val});
}
// Try to initialize the upper layer.
if (auto err = upper_layer_.init(owner_, down, cfg_)) {
auto descr = to_string(err);
CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr);
write_response(down, http::status::bad_request, descr);
down->abort_reason(std::move(err));
return false;
}
// Finalize the WebSocket handshake.
handshake hs;
hs.assign_key(sec_key);
down->begin_output();
hs.write_http_1_response(down->output_buffer());
down->end_output();
CAF_LOG_DEBUG("completed WebSocket handshake");
handshake_complete_ = true;
return true;
}
bool prepare_send() override;
template <class F>
void for_each_line(std::string_view input, F&& f) {
constexpr std::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(make_string_view(pos, line_end));
pos = line_end + eol.size();
}
}
bool done_sending() override;
static std::string_view trim(std::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;
}
private:
// -- HTTP request processing ------------------------------------------------
void write_response(http::status code, std::string_view msg);
bool handle_header(std::string_view http);
/// Stores whether the WebSocket handshake completed successfully.
bool handshake_complete_ = false;
/// Stores the upper layer.
framing<UpperLayer> upper_layer_;
framing framing_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
......@@ -248,22 +109,4 @@ private:
settings cfg_;
};
/// Creates a WebSocket server on the connected socket `fd`.
/// @param mpx The multiplexer that takes ownership of the socket.
/// @param fd A connected stream socket.
/// @param in Inputs for writing to the socket.
/// @param out Outputs from the socket.
/// @param trait Converts between the native and the wire format.
template <template <class> class Transport = stream_transport, class Socket,
class T, class Trait>
socket_manager_ptr make_server(multiplexer& mpx, Socket fd,
async::consumer_resource<T> in,
async::producer_resource<T> out, Trait trait) {
using app_t = message_flow_bridge<T, Trait, tag::mixed_message_oriented>;
using stack_t = Transport<server<app_t>>;
auto mgr = make_socket_manager<stack_t>(fd, &mpx, std::move(in),
std::move(out), std::move(trait));
return mgr;
}
} // namespace caf::net::web_socket
......@@ -4,26 +4,23 @@
#pragma once
namespace caf::detail {
#include "caf/detail/net_export.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/generic_upper_layer.hpp"
#include "caf/net/web_socket/fwd.hpp"
template <class T, class LowerLayerPtr>
class has_after_reading {
private:
template <class A, class B>
static auto sfinae(A& up, B& ptr)
-> decltype(up.after_reading(ptr), std::true_type{});
template <class A>
static std::false_type sfinae(A&, ...);
using sfinae_result
= decltype(sfinae(std::declval<T&>(), std::declval<LowerLayerPtr&>()));
namespace caf::net::web_socket {
/// Consumes text and binary messages from the lower layer.
class CAF_NET_EXPORT upper_layer : public generic_upper_layer {
public:
static constexpr bool value = sfinae_result::value;
};
virtual ~upper_layer();
template <class T, class LowerLayerPtr>
constexpr bool has_after_reading_v = has_after_reading<T, LowerLayerPtr>::value;
virtual error
init(net::socket_manager* mgr, lower_layer* down, const settings& cfg)
= 0;
virtual ptrdiff_t consume_binary(byte_span buf) = 0;
virtual ptrdiff_t consume_text(std::string_view buf) = 0;
};
} // namespace caf::detail
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
namespace caf::tag {
/// Tells message-oriented transports to *not* implicitly register an
/// application for reading as part of the initialization when used a base type.
struct no_auto_reading {};
} // namespace caf::tag
// 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
namespace caf::tag {
struct stream_oriented {};
} // namespace caf::tag
......@@ -27,6 +27,12 @@ abstract_actor_shell::~abstract_actor_shell() {
// nop
}
// -- properties ---------------------------------------------------------------
bool abstract_actor_shell::terminated() const noexcept {
return mailbox_.closed();
}
// -- state modifiers ----------------------------------------------------------
void abstract_actor_shell::quit(error reason) {
......@@ -65,7 +71,7 @@ bool abstract_actor_shell::consume_message() {
if (auto result = bhvr_(msg->payload)) {
visitor(*result);
} else {
auto fallback_result = (*fallback_)(msg->payload);
auto fallback_result = (*fallback_)(this, msg->payload);
visit(visitor, fallback_result);
}
} else if (auto i = multiplexed_responses_.find(mid);
......
......@@ -2,10 +2,12 @@
// 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/generic_lower_layer.hpp"
namespace caf::tag {
namespace caf::net {
struct io_event_oriented {};
generic_lower_layer::~generic_lower_layer() {
// nop
}
} // namespace caf::tag
} // namespace caf::net
......@@ -2,10 +2,12 @@
// 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/generic_upper_layer.hpp"
namespace caf::tag {
namespace caf::net {
struct message_oriented {};
generic_upper_layer::~generic_upper_layer() {
// nop
}
} // namespace caf::tag
} // namespace caf::net
// 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.
#include "caf/net/http/lower_layer.hpp"
#include "caf/net/http/context.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/status.hpp"
#include <string>
#include <string_view>
using namespace std::literals;
namespace caf::net::http {
lower_layer::~lower_layer() {
// nop
}
bool lower_layer::send_response(context ctx, status code,
std::string_view content_type,
const_byte_span content) {
auto content_size = std::to_string(content.size());
header_fields_map fields;
fields.emplace("Content-Type"sv, content_type);
fields.emplace("Content-Length"sv, content_size);
return send_header(ctx, code, fields) && send_payload(ctx, content);
}
} // namespace caf::net::http
#include "caf/net/http/server.hpp"
namespace caf::net::http {
// -- factories ----------------------------------------------------------------
std::unique_ptr<server> server::make(upper_layer_ptr up) {
return std::make_unique<server>(std::move(up));
}
// -- http::lower_layer implementation -----------------------------------------
bool server::can_send_more() const noexcept {
return down_->can_send_more();
}
void server::suspend_reading() {
return down_->suspend_reading();
}
bool server::stopped_reading() const noexcept {
return down_->stopped_reading();
}
bool server::send_header(context, status code,
const header_fields_map& fields) {
down_->begin_output();
v1::write_header(code, fields, down_->output_buffer());
down_->end_output();
return true;
}
bool server::send_payload(context, const_byte_span bytes) {
down_->begin_output();
auto& buf = down_->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
down_->end_output();
return true;
}
bool server::send_chunk(context, const_byte_span bytes) {
down_->begin_output();
auto& buf = down_->output_buffer();
auto size = bytes.size();
detail::append_hex(buf, &size, sizeof(size));
buf.emplace_back(std::byte{'\r'});
buf.emplace_back(std::byte{'\n'});
buf.insert(buf.end(), bytes.begin(), bytes.end());
buf.emplace_back(std::byte{'\r'});
buf.emplace_back(std::byte{'\n'});
return down_->end_output();
}
bool server::send_end_of_chunks() {
std::string_view str = "0\r\n\r\n";
auto bytes = as_bytes(make_span(str));
down_->begin_output();
auto& buf = down_->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
return down_->end_output();
}
void server::fin(context) {
// nop
}
// -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
down_ = down;
if (auto err = up_->init(owner, this, cfg))
return err;
if (auto max_size = get_as<uint32_t>(cfg, "http.max-request-size"))
max_request_size_ = *max_size;
down_->configure_read(receive_policy::up_to(max_request_size_));
return none;
}
void server::abort(const error& reason) {
up_->abort(reason);
}
bool server::prepare_send() {
return up_->prepare_send();
}
bool server::done_sending() {
return up_->done_sending();
}
void server::continue_reading() {
down_->configure_read(receive_policy::up_to(max_request_size_));
}
ptrdiff_t server::consume(byte_span input, byte_span) {
using namespace literals;
CAF_LOG_TRACE(CAF_ARG2("bytes", input.size()));
ptrdiff_t consumed = 0;
for (;;) {
switch (mode_) {
case mode::read_header: {
auto [hdr, remainder] = v1::split_header(input);
if (hdr.empty()) {
if (input.size() >= max_request_size_) {
up_->abort(
make_error(sec::protocol_error, "header exceeds maximum size"));
write_response(status::request_header_fields_too_large,
"Header exceeds maximum size.");
return -1;
} else {
return consumed;
}
} else if (!handle_header(hdr)) {
// Note: handle_header already calls up_->abort().
return -1;
} else {
// Prepare for next loop iteration.
consumed += static_cast<ptrdiff_t>(hdr.size());
input = remainder;
// Transition to the next mode.
if (hdr_.chunked_transfer_encoding()) {
mode_ = mode::read_chunks;
} else if (auto len = hdr_.content_length()) {
// Protect against payloads that exceed the maximum size.
if (*len >= max_request_size_) {
up_->abort(make_error(sec::protocol_error,
"payload exceeds maximum size"));
write_response(status::payload_too_large,
"Payload exceeds maximum size.");
return -1;
}
// Transition to read_payload mode and continue.
payload_len_ = *len;
mode_ = mode::read_payload;
} else {
// TODO: we may *still* have a payload since HTTP can omit the
// Content-Length field and simply close the connection
// after the payload.
if (!invoke_upper_layer(const_byte_span{}))
return -1;
}
}
break;
}
case mode::read_payload: {
if (input.size() >= payload_len_) {
if (!invoke_upper_layer(input.subspan(0, payload_len_)))
return -1;
consumed += static_cast<ptrdiff_t>(payload_len_);
mode_ = mode::read_header;
} else {
// Wait for more data.
return consumed;
}
break;
}
case mode::read_chunks: {
// TODO: implement me
write_response(status::not_implemented,
"Chunked transfer not implemented yet.");
return -1;
}
}
}
}
// -- utility functions ------------------------------------------------------
void server::write_response(status code, std::string_view content) {
down_->begin_output();
v1::write_response(code, "text/plain", content, down_->output_buffer());
down_->end_output();
}
bool server::invoke_upper_layer(const_byte_span payload) {
return up_->consume(context{}, hdr_, payload) >= 0;
}
bool server::handle_header(std::string_view http) {
// Parse the header and reject invalid inputs.
auto [code, msg] = hdr_.parse(http);
if (code != status::ok) {
CAF_LOG_DEBUG("received malformed header");
up_->abort(make_error(sec::protocol_error, "received malformed header"));
write_response(code, msg);
return false;
} else {
return true;
}
}
} // namespace caf::net::http
......@@ -2,11 +2,14 @@
// 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/http/upper_layer.hpp"
namespace caf::tag {
using namespace std::literals;
/// Tags a layer that expects an HTTP-like transport.
struct hypertext_oriented {};
namespace caf::net::http {
} // namespace caf::tag
upper_layer::~upper_layer() {
// nop
}
} // namespace caf::net::http
#include "caf/net/length_prefix_framing.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/sec.hpp"
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
length_prefix_framing::length_prefix_framing(upper_layer_ptr up)
: up_(std::move(up)) {
// nop
}
// -- factories ----------------------------------------------------------------
std::unique_ptr<length_prefix_framing>
length_prefix_framing::make(upper_layer_ptr up) {
return std::make_unique<length_prefix_framing>(std::move(up));
}
// -- implementation of stream_oriented::upper_layer ---------------------------
error length_prefix_framing::init(socket_manager* owner,
stream_oriented::lower_layer* down,
const settings& cfg) {
down_ = down;
return up_->init(owner, this, cfg);
}
void length_prefix_framing::abort(const error& reason) {
up_->abort(reason);
}
ptrdiff_t length_prefix_framing::consume(byte_span input, byte_span) {
CAF_LOG_TRACE("got" << input.size() << "bytes");
if (input.size() < sizeof(uint32_t)) {
CAF_LOG_ERROR("received too few bytes from underlying transport");
up_->abort(make_error(sec::logic_error,
"received too few bytes from underlying transport"));
return -1;
} else if (input.size() == hdr_size) {
auto u32_size = uint32_t{0};
memcpy(&u32_size, input.data(), sizeof(uint32_t));
auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
if (msg_size == 0) {
// Ignore empty messages.
CAF_LOG_DEBUG("received empty message");
return static_cast<ptrdiff_t>(input.size());
} else if (msg_size > max_message_length) {
CAF_LOG_DEBUG("exceeded maximum message size");
up_->abort(
make_error(sec::protocol_error, "exceeded maximum message size"));
return -1;
} else {
CAF_LOG_DEBUG("wait for payload of size" << msg_size);
down_->configure_read(receive_policy::exactly(hdr_size + msg_size));
return 0;
}
} else {
auto [msg_size, msg] = split(input);
if (msg_size == msg.size() && msg_size + hdr_size == input.size()) {
CAF_LOG_DEBUG("got message of size" << msg_size);
if (up_->consume(msg) >= 0) {
if (!down_->stopped_reading())
down_->configure_read(receive_policy::exactly(hdr_size));
return static_cast<ptrdiff_t>(input.size());
} else {
return -1;
}
} else {
CAF_LOG_DEBUG("received malformed message");
up_->abort(make_error(sec::protocol_error, "received malformed message"));
return -1;
}
}
}
void length_prefix_framing::continue_reading() {
up_->continue_reading();
}
bool length_prefix_framing::prepare_send() {
return up_->prepare_send();
}
bool length_prefix_framing::done_sending() {
return up_->done_sending();
}
// -- implementation of message_oriented::lower_layer --------------------------
bool length_prefix_framing::can_send_more() const noexcept {
return down_->can_send_more();
}
void length_prefix_framing::suspend_reading() {
down_->suspend_reading();
}
bool length_prefix_framing::stopped_reading() const noexcept {
return down_->stopped_reading();
}
void length_prefix_framing::request_messages() {
if (down_->stopped_reading())
down_->configure_read(receive_policy::exactly(hdr_size));
}
void length_prefix_framing::begin_message() {
down_->begin_output();
auto& buf = down_->output_buffer();
message_offset_ = buf.size();
buf.insert(buf.end(), 4, std::byte{0});
}
byte_buffer& length_prefix_framing::message_buffer() {
return down_->output_buffer();
}
bool length_prefix_framing::end_message() {
using detail::to_network_order;
auto& buf = down_->output_buffer();
CAF_ASSERT(message_offset_ < buf.size());
auto msg_begin = buf.begin() + message_offset_;
auto msg_size = std::distance(msg_begin + 4, buf.end());
if (msg_size > 0 && static_cast<size_t>(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);
down_->end_output();
return true;
} else {
CAF_LOG_DEBUG_IF(msg_size == 0, "logic error: message of size 0");
CAF_LOG_DEBUG_IF(msg_size != 0, "maximum message size exceeded");
return false;
}
}
void length_prefix_framing::send_close_message() {
// nop: this layer has no close message
}
void length_prefix_framing::send_close_message(const error&) {
// nop: this layer has no close message
}
// -- utility functions ------------------------------------------------------
std::pair<size_t, byte_span>
length_prefix_framing::split(byte_span buffer) noexcept {
CAF_ASSERT(buffer.size() >= sizeof(uint32_t));
auto u32_size = uint32_t{0};
memcpy(&u32_size, buffer.data(), sizeof(uint32_t));
auto msg_size = static_cast<size_t>(detail::from_network_order(u32_size));
return std::make_pair(msg_size, buffer.subspan(sizeof(uint32_t)));
}
} // namespace caf::net
// 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.
#include "caf/net/message_oriented.hpp"
namespace caf::net::message_oriented {
upper_layer::~upper_layer() {
// nop
}
lower_layer::~lower_layer() {
// nop
}
} // namespace caf::net::message_oriented
......@@ -114,12 +114,14 @@ error multiplexer::init() {
auto pipe_handles = make_pipe();
if (!pipe_handles)
return std::move(pipe_handles.error());
auto updater = make_counted<pollset_updater>(pipe_handles->first, this);
if (auto err = updater->init(settings{}))
auto updater = pollset_updater::make(pipe_handles->first);
auto mgr = socket_manager::make(this, pipe_handles->first,
std::move(updater));
if (auto err = mgr->init(settings{}))
return err;
write_handle_ = pipe_handles->second;
pollset_.emplace_back(pollfd{pipe_handles->first.id, input_mask, 0});
managers_.emplace_back(updater);
managers_.emplace_back(mgr);
return none;
}
......@@ -380,6 +382,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::abort:
case socket_manager::read_result::stop:
update_for(mgr).events &= ~input_mask;
break;
......@@ -399,6 +402,7 @@ void multiplexer::handle(const socket_manager_ptr& mgr,
switch (mgr->handle_write_event()) {
default: // socket_manager::write_result::again
break;
case socket_manager::write_result::abort:
case socket_manager::write_result::stop:
update_for(mgr).events &= ~output_mask;
break;
......@@ -427,9 +431,7 @@ void multiplexer::do_handover(const socket_manager_ptr& mgr) {
// 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 (mgr->do_handover()) {
// 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.
......@@ -438,6 +440,7 @@ void multiplexer::do_handover(const socket_manager_ptr& mgr) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::abort:
case socket_manager::read_result::stop:
update.events &= ~input_mask;
break;
......@@ -446,7 +449,7 @@ void multiplexer::do_handover(const socket_manager_ptr& mgr) {
break;
case socket_manager::read_result::handover: {
// Down the rabbit hole we go!
do_handover(new_mgr);
do_handover(mgr);
}
}
}
......@@ -556,7 +559,7 @@ void multiplexer::do_register_writing(const socket_manager_ptr& mgr) {
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
default: // socket_manager::read_result::(stop | abort)
update_for(mgr).events &= ~input_mask;
break;
case socket_manager::read_result::again:
......@@ -574,20 +577,7 @@ void multiplexer::do_continue_reading(const socket_manager_ptr& mgr) {
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
update_for(mgr).events &= ~output_mask;
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);
}
}
update_for(mgr).events |= output_mask;
}
}
......
......@@ -17,20 +17,22 @@ namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
pollset_updater::pollset_updater(pipe_socket read_handle, multiplexer* parent)
: super(read_handle, parent) {
pollset_updater::pollset_updater(pipe_socket fd) : fd_(fd) {
// nop
}
pollset_updater::~pollset_updater() {
// nop
// -- factories ----------------------------------------------------------------
std::unique_ptr<pollset_updater> pollset_updater::make(pipe_socket fd) {
return std::make_unique<pollset_updater>(fd);
}
// -- interface functions ------------------------------------------------------
error pollset_updater::init(const settings&) {
error pollset_updater::init(socket_manager* owner, const settings&) {
CAF_LOG_TRACE("");
return nonblocking(handle(), true);
mpx_ = owner->mpx_ptr();
return nonblocking(fd_, true);
}
pollset_updater::read_result pollset_updater::handle_read_event() {
......@@ -44,8 +46,8 @@ pollset_updater::read_result pollset_updater::handle_read_event() {
};
for (;;) {
CAF_ASSERT((buf_.size() - buf_size_) > 0);
auto num_bytes = read(handle(), make_span(buf_.data() + buf_size_,
buf_.size() - buf_size_));
auto num_bytes
= read(fd_, make_span(buf_.data() + buf_size_, buf_.size() - buf_size_));
if (num_bytes > 0) {
buf_size_ += static_cast<size_t>(num_bytes);
if (buf_.size() == buf_size_) {
......@@ -113,11 +115,7 @@ pollset_updater::write_result pollset_updater::handle_write_event() {
return write_result::stop;
}
pollset_updater::write_result pollset_updater::handle_continue_writing() {
return write_result::stop;
}
void pollset_updater::handle_error(sec) {
void pollset_updater::abort(const error&) {
// nop
}
......
......@@ -2,15 +2,16 @@
// 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/web_socket/server.hpp"
#include "caf/net/socket_event_layer.hpp"
namespace caf::net {
template <class UpperLayer>
using web_socket_server
[[deprecated("use caf::net::web_socket::server instead")]]
= web_socket::server<UpperLayer>;
socket_event_layer::~socket_event_layer() {
// nop
}
bool socket_event_layer::do_handover(std::unique_ptr<socket_event_layer>&) {
return false;
}
} // namespace caf::net
......@@ -5,43 +5,43 @@
#include "caf/net/socket_manager.hpp"
#include "caf/config.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/multiplexer.hpp"
namespace caf::net {
socket_manager::socket_manager(socket handle, multiplexer* mpx)
: handle_(handle), mpx_(mpx) {
CAF_ASSERT(handle_ != invalid_socket);
// -- constructors, destructors, and assignment operators ----------------------
socket_manager::socket_manager(multiplexer* mpx, socket fd,
event_handler_ptr handler)
: fd_(fd), mpx_(mpx), handler_(std::move(handler)) {
CAF_ASSERT(fd_ != invalid_socket);
CAF_ASSERT(mpx_ != nullptr);
CAF_ASSERT(handler_ != nullptr);
memset(&flags_, 0, sizeof(flags_t));
}
socket_manager::~socket_manager() {
close(handle_);
close(fd_);
}
actor_system& socket_manager::system() noexcept {
CAF_ASSERT(mpx_ != nullptr);
return mpx_->system();
}
// -- factories ----------------------------------------------------------------
void socket_manager::close_read() noexcept {
// TODO: extend transport API for closing read operations.
flags_.read_closed = true;
socket_manager_ptr socket_manager::make(multiplexer* mpx, socket handle,
event_handler_ptr handler) {
return make_counted<socket_manager>(mpx, handle, std::move(handler));
}
void socket_manager::close_write() noexcept {
// TODO: extend transport API for closing write operations.
flags_.write_closed = true;
}
// -- properties ---------------------------------------------------------------
void socket_manager::abort_reason(error reason) noexcept {
abort_reason_ = std::move(reason);
flags_.read_closed = true;
flags_.write_closed = true;
actor_system& socket_manager::system() noexcept {
CAF_ASSERT(mpx_ != nullptr);
return mpx_->system();
}
// -- event loop management ----------------------------------------------------
void socket_manager::register_reading() {
mpx_->register_reading(this);
}
......@@ -58,21 +58,70 @@ void socket_manager::continue_writing() {
mpx_->continue_writing(this);
}
socket_manager_ptr socket_manager::do_handover() {
// -- callbacks for the multiplexer --------------------------------------------
void socket_manager::close_read() noexcept {
// TODO: extend transport API for closing read operations.
flags_.read_closed = true;
}
void socket_manager::close_write() noexcept {
// TODO: extend transport API for closing write operations.
flags_.write_closed = true;
auto hdl = handle_;
handle_ = invalid_socket;
if (auto ptr = make_next_manager(hdl)) {
return ptr;
}
bool socket_manager::do_handover() {
event_handler_ptr next;
if (handler_->do_handover(next)) {
handler_.swap(next);
return true;
} else {
close(hdl);
return nullptr;
return false;
}
}
error socket_manager::init(const settings& cfg) {
CAF_LOG_TRACE(CAF_ARG(cfg));
if (auto err = nonblocking(fd_, true)) {
CAF_LOG_ERROR("failed to set nonblocking flag in socket:" << err);
return err;
}
return handler_->init(this, cfg);
}
socket_manager_ptr socket_manager::make_next_manager(socket) {
return {};
socket_manager::read_result socket_manager::handle_read_event() {
auto result = handler_->handle_read_event();
switch (result) {
default:
break;
case read_result::close:
flags_.read_closed = true;
break;
case read_result::abort:
flags_.read_closed = true;
flags_.write_closed = true;
break;
}
return result;
}
socket_manager::read_result socket_manager::handle_buffered_data() {
return handler_->handle_buffered_data();
}
socket_manager::read_result socket_manager::handle_continue_reading() {
return handler_->handle_continue_reading();
}
socket_manager::write_result socket_manager::handle_write_event() {
return handler_->handle_write_event();
}
void socket_manager::handle_error(sec code) {
if (handler_) {
handler_->abort(make_error(code));
handler_ = nullptr;
}
}
} // namespace caf::net
// 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.
#include "caf/net/ssl/connection.hpp"
#include "caf/config.hpp"
#include "caf/net/ssl/context.hpp"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
namespace {
auto native(connection::impl* ptr) {
return reinterpret_cast<SSL*>(ptr);
}
} // namespace
// -- constructors, destructors, and assignment operators ----------------------
connection::connection(connection&& other) {
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
}
connection& connection::operator=(connection&& other) {
SSL_free(native(pimpl_));
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
return *this;
}
connection::~connection() {
SSL_free(native(pimpl_)); // Already checks for NULL.
}
// -- native handles -----------------------------------------------------------
connection connection::from_native(void* native_handle) {
return connection{static_cast<impl*>(native_handle)};
}
void* connection::native_handle() const noexcept {
return static_cast<void*>(pimpl_);
}
// -- error handling -----------------------------------------------------------
std::string connection::last_error_string(ptrdiff_t ret) const {
auto code = last_error(ret);
switch (code) {
default:
return to_string(code);
case errc::fatal:
return context::last_error_string();
case errc::syscall_failed:
return last_socket_error_as_string();
}
}
errc connection::last_error(ptrdiff_t ret) const {
switch (SSL_get_error(native(pimpl_), static_cast<int>(ret))) {
case SSL_ERROR_NONE:
return errc::none;
case SSL_ERROR_ZERO_RETURN:
return errc::closed;
case SSL_ERROR_WANT_READ:
return errc::want_read;
case SSL_ERROR_WANT_WRITE:
return errc::want_write;
case SSL_ERROR_WANT_CONNECT:
return errc::want_connect;
case SSL_ERROR_WANT_ACCEPT:
return errc::want_accept;
case SSL_ERROR_WANT_X509_LOOKUP:
return errc::want_x509_lookup;
#ifdef SSL_ERROR_WANT_ASYNC
case SSL_ERROR_WANT_ASYNC:
return errc::want_async;
#endif
#ifdef SSL_ERROR_WANT_ASYNC_JOB
case SSL_ERROR_WANT_ASYNC_JOB:
return errc::want_async_job;
#endif
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
return errc::want_client_hello;
#endif
case SSL_ERROR_SYSCALL:
return errc::syscall_failed;
case SSL_ERROR_SSL:
return errc::fatal;
default:
return errc::unspecified;
}
}
// -- connecting and teardown --------------------------------------------------
ptrdiff_t connection::connect() {
return SSL_connect(native(pimpl_));
}
ptrdiff_t connection::accept() {
return SSL_accept(native(pimpl_));
}
ptrdiff_t connection::close() {
return SSL_shutdown(native(pimpl_));
}
// -- reading and writing ------------------------------------------------------
ptrdiff_t connection::read(byte_span buf) {
return SSL_read(native(pimpl_), buf.data(), static_cast<int>(buf.size()));
}
ptrdiff_t connection::write(const_byte_span buf) {
return SSL_write(native(pimpl_), buf.data(), static_cast<int>(buf.size()));
}
// -- properties ---------------------------------------------------------------
size_t connection::buffered() const noexcept {
return static_cast<size_t>(SSL_pending(native(pimpl_)));
}
stream_socket connection::fd() const noexcept {
if (auto id = SSL_get_fd(native(pimpl_)))
return stream_socket{static_cast<socket_id>(id)};
else
return stream_socket{invalid_socket_id};
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/context.hpp"
#include "caf/config.hpp"
#include "caf/expected.hpp"
#include "caf/net/ssl/connection.hpp"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
namespace {
auto native(context::impl* ptr) {
return reinterpret_cast<SSL_CTX*>(ptr);
}
} // namespace
// -- constructors, destructors, and assignment operators ----------------------
context::context(context&& other) {
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
}
context& context::operator=(context&& other) {
SSL_CTX_free(native(pimpl_));
pimpl_ = other.pimpl_;
other.pimpl_ = nullptr;
return *this;
}
context::~context() {
SSL_CTX_free(native(pimpl_)); // Already checks for NULL.
}
// -- factories ----------------------------------------------------------------
namespace {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
# define CAF_TLS_METHOD(infix) SSLv23##infix##method()
#else
# define CAF_TLS_METHOD(infix) TLS##infix##method()
#endif
template <class Enum>
std::pair<SSL_CTX*, const char*>
make_ctx(const SSL_METHOD* method, Enum min_val, Enum max_val) {
if (min_val > max_val && max_val != Enum::any)
return {nullptr, "invalid version range"};
auto ctx = SSL_CTX_new(method);
if (!ctx)
return {nullptr, "SSL_CTX_new returned null"};
// Avoid fallback to SSLv3.
// Select the protocol versions in the configured range.
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if constexpr (std::is_same_v<Enum, tls>) {
auto opts = SSL_OP_NO_SSLv3;
if (!has(tls::v1_0, min_val, max_val))
opts |= SSL_OP_NO_TLSv1;
if (!has(tls::v1_1, min_val, max_val))
opts |= SSL_OP_NO_TLSv1_1;
if (!has(tls::v1_2, min_val, max_val))
opts |= SSL_OP_NO_TLSv1_2;
SSL_CTX_set_options(ctx, opts);
} else {
static_assert(std::is_same_v<Enum, dtls>);
// Nothing to do for DTLS in this OpenSSL version.
CAF_IGNORE_UNUSED(min_val);
CAF_IGNORE_UNUSED(max_val);
}
#else
if constexpr (std::is_same_v<Enum, tls>) {
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
}
auto vmin = native(min_val);
auto vmax = native(max_val);
if (vmin != 0 && SSL_CTX_set_min_proto_version(ctx, vmin) != 1)
return {ctx, "SSL_CTX_set_min_proto_version returned an error"};
if (vmax != 0 && SSL_CTX_set_max_proto_version(ctx, vmax) != 1)
return {ctx, "SSL_CTX_set_max_proto_version returned an error"};
#endif
return {ctx, nullptr};
}
} // namespace
expected<context> context::make(tls vmin, tls vmax) {
auto [raw, errstr] = make_ctx(CAF_TLS_METHOD(_), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
expected<context> context::make_server(tls vmin, tls vmax) {
auto [raw, errstr] = make_ctx(CAF_TLS_METHOD(_server_), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
expected<context> context::make_client(tls vmin, tls vmax) {
auto [raw, errstr] = make_ctx(CAF_TLS_METHOD(_client_), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
expected<context> context::make(dtls vmin, dtls vmax) {
auto [raw, errstr] = make_ctx(DTLS_method(), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
expected<context> context::make_server(dtls vmin, dtls vmax) {
auto [raw, errstr] = make_ctx(DTLS_server_method(), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
expected<context> context::make_client(dtls vmin, dtls vmax) {
auto [raw, errstr] = make_ctx(DTLS_client_method(), vmin, vmax);
context ctx{reinterpret_cast<impl*>(raw)};
if (errstr == nullptr)
return {std::move(ctx)};
else
return {make_error(sec::logic_error, errstr)};
}
// -- native handles -----------------------------------------------------------
context context::from_native(void* native_handle) {
return context{static_cast<impl*>(native_handle)};
}
void* context::native_handle() const noexcept {
return static_cast<void*>(pimpl_);
}
// -- error handling -----------------------------------------------------------
std::string context::last_error_string() {
auto save_cstr = [](const char* cstr) { return cstr ? cstr : "NULL"; };
if (auto code = ERR_get_error(); code != 0) {
std::string result;
result.reserve(256);
result = "error:";
result += std::to_string(code);
result += ':';
result += save_cstr(ERR_lib_error_string(code));
result += "::";
result += save_cstr(ERR_reason_error_string(code));
return result;
} else {
return "no-error";
}
}
bool context::has_last_error() noexcept {
return ERR_peek_error() != 0;
}
// -- connections --------------------------------------------------------------
expected<connection> context::new_connection(stream_socket fd) {
if (auto ptr = SSL_new(native(pimpl_))) {
auto conn = connection::from_native(ptr);
if (SSL_set_fd(ptr, fd.id) == 1)
return {std::move(conn)};
else
return {make_error(sec::logic_error, "SSL_set_fd failed")};
} else {
return {make_error(sec::logic_error, "SSL_new returned null")};
}
}
// -- certificates and keys ----------------------------------------------------
bool context::set_default_verify_paths() {
ERR_clear_error();
return SSL_CTX_set_default_verify_paths(native(pimpl_)) == 1;
}
bool context::load_verify_dir(const char* path) {
ERR_clear_error();
return SSL_CTX_load_verify_locations(native(pimpl_), nullptr, path) == 1;
}
bool context::load_verify_file(const char* path) {
ERR_clear_error();
return SSL_CTX_load_verify_locations(native(pimpl_), path, nullptr) == 1;
}
bool context::use_certificate_from_file(const char* path, format file_format) {
ERR_clear_error();
return SSL_CTX_use_certificate_file(native(pimpl_), path, native(file_format))
== 1;
}
bool context::use_private_key_from_file(const char* path, format file_format) {
ERR_clear_error();
return SSL_CTX_use_PrivateKey_file(native(pimpl_), path, native(file_format))
== 1;
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/dtls.hpp"
#include "caf/config.hpp"
CAF_PUSH_WARNINGS
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
int native(dtls x) {
switch (x) {
default:
return 0;
case dtls::v1_0:
return DTLS1_VERSION;
case dtls::v1_2:
return DTLS1_2_VERSION;
}
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/format.hpp"
#include "caf/config.hpp"
CAF_PUSH_WARNINGS
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
int native(format x) {
switch (x) {
default:
CAF_ASSERT(x == format::pem);
return SSL_FILETYPE_PEM;
case format::asn1:
return SSL_FILETYPE_ASN1;
}
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/startup.hpp"
#include "caf/config.hpp"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
#if OPENSSL_VERSION_NUMBER < 0x10100000L
# include <mutex>
# include <vector>
struct CRYPTO_dynlock_value {
std::mutex mtx;
};
namespace {
std::vector<std::mutex> mutexes;
void locking_function(int mode, int n, const char*, int) {
if (mode & CRYPTO_LOCK)
mutexes[static_cast<size_t>(n)].lock();
else
mutexes[static_cast<size_t>(n)].unlock();
}
CRYPTO_dynlock_value* dynlock_create(const char*, int) {
return new CRYPTO_dynlock_value;
}
void dynlock_lock(int mode, CRYPTO_dynlock_value* dynlock, const char*, int) {
if (mode & CRYPTO_LOCK)
dynlock->mtx.lock();
else
dynlock->mtx.unlock();
}
void dynlock_destroy(CRYPTO_dynlock_value* dynlock, const char*, int) {
delete dynlock;
}
} // namespace
#endif
namespace caf::net::ssl {
void startup() {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
mutexes = std::vector<std::mutex>{static_cast<size_t>(CRYPTO_num_locks())};
CRYPTO_set_locking_callback(locking_function);
CRYPTO_set_dynlock_create_callback(dynlock_create);
CRYPTO_set_dynlock_lock_callback(dynlock_lock);
CRYPTO_set_dynlock_destroy_callback(dynlock_destroy);
#endif
}
void cleanup() {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
CRYPTO_set_locking_callback(nullptr);
CRYPTO_set_dynlock_create_callback(nullptr);
CRYPTO_set_dynlock_lock_callback(nullptr);
CRYPTO_set_dynlock_destroy_callback(nullptr);
mutexes.clear();
#endif
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/tls.hpp"
#include "caf/config.hpp"
CAF_PUSH_WARNINGS
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
int native(tls x) {
switch (x) {
default:
return 0;
case tls::v1_0:
return TLS1_VERSION;
case tls::v1_1:
return TLS1_1_VERSION;
case tls::v1_2:
return TLS1_2_VERSION;
#ifdef TLS1_3_VERSION
case tls::v1_3:
return TLS1_3_VERSION;
#endif
}
}
bool has(tls val, tls vmin, tls vmax) {
CAF_ASSERT(val != tls::any);
return val >= vmin && (vmax == tls::any || val <= vmax);
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/ssl/transport.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/ssl/connection.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/settings.hpp"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
namespace caf::net::ssl {
namespace {
/// Calls `connect` or `accept` until it succeeds or fails. On success, the
/// worker creates a new SSL transport and performs a handover.
class handshake_worker : public socket_event_layer {
public:
// -- member types -----------------------------------------------------------
using super = socket_event_layer;
using read_result = typename super::read_result;
using write_result = typename super::write_result;
using upper_layer_ptr = transport::upper_layer_ptr;
handshake_worker(connection conn, bool is_server, upper_layer_ptr up)
: is_server_(is_server), policy_(std::move(conn)), up_(std::move(up)) {
// nop
}
// -- interface functions ----------------------------------------------------
error init(socket_manager* owner, const settings& cfg) override {
owner_ = owner;
cfg_ = cfg;
owner->register_writing();
return caf::none;
}
read_result handle_read_event() override {
if (auto res = advance_handshake(); res > 0) {
return read_result::handover;
} else if (res == 0) {
up_->abort(make_error(sec::connection_closed));
return read_result::stop;
} else {
auto err = policy_.last_error(policy_.conn.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_.conn.last_error_string(res));
up_->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 {
if (auto res = advance_handshake(); res > 0) {
return write_result::handover;
} else if (res == 0) {
up_->abort(make_error(sec::connection_closed));
return write_result::stop;
} else {
switch (policy_.last_error(policy_.conn.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_.conn.last_error_string(res));
up_->abort(std::move(err));
return write_result::stop;
}
}
}
bool do_handover(std::unique_ptr<socket_event_layer>& next) override {
next = transport::make(std::move(policy_.conn), std::move(up_));
if (auto err = next->init(owner_, cfg_))
return false;
else
return true;
}
void abort(const error& reason) override {
up_->abort(reason);
}
private:
ptrdiff_t advance_handshake() {
if (is_server_)
return policy_.conn.accept();
else
return policy_.conn.connect();
}
bool is_server_ = false;
socket_manager* owner_ = nullptr;
settings cfg_;
transport::policy_impl policy_;
upper_layer_ptr up_;
};
} // namespace
// -- member types -------------------------------------------------------------
transport::policy_impl::policy_impl(connection conn) : conn(std::move(conn)) {
// nop
}
ptrdiff_t transport::policy_impl::read(stream_socket, byte_span buf) {
return conn.read(buf);
}
ptrdiff_t transport::policy_impl::write(stream_socket, const_byte_span buf) {
return conn.write(buf);
}
stream_transport_error transport::policy_impl::last_error(stream_socket fd,
ptrdiff_t ret) {
switch (conn.last_error(ret)) {
case errc::none:
case errc::want_accept:
case errc::want_connect:
// For all of these, OpenSSL docs say to do the operation again later.
return stream_transport_error::temporary;
case errc::syscall_failed:
// Need to consult errno, which we just leave to the default policy.
return stream_transport::default_policy{}.last_error(fd, ret);
case errc::want_read:
return stream_transport_error::want_read;
case errc::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;
}
}
ptrdiff_t transport::policy_impl::connect(stream_socket) {
return conn.connect();
}
ptrdiff_t transport::policy_impl::accept(stream_socket) {
return conn.accept();
}
size_t transport::policy_impl::buffered() const noexcept {
return conn.buffered();
}
// -- factories ----------------------------------------------------------------
std::unique_ptr<transport> transport::make(connection conn,
upper_layer_ptr up) {
// Note: can't use make_unique because the constructor is private.
auto fd = conn.fd();
auto ptr = new transport(fd, std::move(conn), std::move(up));
return std::unique_ptr<transport>{ptr};
}
transport::worker_ptr transport::make_server(connection conn,
upper_layer_ptr up) {
return std::make_unique<handshake_worker>(std::move(conn), true,
std::move(up));
}
transport::worker_ptr transport::make_client(connection conn,
upper_layer_ptr up) {
return std::make_unique<handshake_worker>(std::move(conn), false,
std::move(up));
}
// -- constructors, destructors, and assignment operators ----------------------
transport::transport(stream_socket fd, connection conn, upper_layer_ptr up)
: super(fd, std::move(up), &policy_impl_), policy_impl_(std::move(conn)) {
// nop
}
} // namespace caf::net::ssl
// 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.
#include "caf/net/stream_oriented.hpp"
namespace caf::net::stream_oriented {
upper_layer::~upper_layer() {
// nop
}
lower_layer::~lower_layer() {
// nop
}
} // namespace caf::net::stream_oriented
// 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.
#include "caf/net/stream_transport.hpp"
#include "caf/defaults.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
namespace caf::net {
// -- member types -------------------------------------------------------------
stream_transport::policy::~policy() {
// nop
}
ptrdiff_t stream_transport::default_policy::read(stream_socket x,
byte_span buf) {
return net::read(x, buf);
}
ptrdiff_t stream_transport::default_policy::write(stream_socket x,
const_byte_span buf) {
return net::write(x, buf);
}
stream_transport_error
stream_transport::default_policy::last_error(stream_socket, ptrdiff_t) {
return last_socket_error_is_temporary() ? stream_transport_error::temporary
: stream_transport_error::permanent;
}
ptrdiff_t stream_transport::default_policy::connect(stream_socket x) {
// A connection is established if the OS reports a socket as ready for read
// or write and if there is no error on the socket.
return net::probe(x) ? 1 : -1;
}
ptrdiff_t stream_transport::default_policy::accept(stream_socket) {
return 1;
}
size_t stream_transport::default_policy::buffered() const noexcept {
return 0;
}
// -- constructors, destructors, and assignment operators ----------------------
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up)
: fd_(fd), up_(std::move(up)) {
policy_ = &default_policy_;
}
stream_transport::stream_transport(stream_socket fd, upper_layer_ptr up,
policy* policy)
: fd_(fd), up_(std::move(up)), policy_(policy) {
// nop
}
// -- factories ----------------------------------------------------------------
std::unique_ptr<stream_transport> stream_transport::make(stream_socket fd,
upper_layer_ptr up) {
return std::make_unique<stream_transport>(fd, std::move(up));
}
// -- implementation of stream_oriented::lower_layer ---------------------------
bool stream_transport::can_send_more() const noexcept {
return write_buf_.size() < max_write_buf_size_;
}
void stream_transport::configure_read(receive_policy rd) {
if (rd.max_size > 0 && max_read_size_ == 0) {
parent_->register_reading();
}
min_read_size_ = rd.min_size;
max_read_size_ = rd.max_size;
}
void stream_transport::begin_output() {
if (write_buf_.empty())
parent_->register_writing();
}
byte_buffer& stream_transport::output_buffer() {
return write_buf_;
}
bool stream_transport::end_output() {
return true;
}
void stream_transport::suspend_reading() {
configure_read(receive_policy::stop());
}
bool stream_transport::stopped_reading() const noexcept {
return max_read_size_ == 0;
}
// -- implementation of transport ----------------------------------------------
error stream_transport::init(socket_manager* owner, const settings& config) {
parent_ = owner;
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);
// if (auto err = nodelay(fd_, true)) {
// CAF_LOG_ERROR("nodelay failed: " << err);
// return err;
// }
if (auto socket_buf_size = send_buffer_size(fd_)) {
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());
}
return up_->init(owner, this, config);
}
stream_transport::read_result stream_transport::handle_read_event() {
CAF_LOG_TRACE(CAF_ARG2("socket", fd_.id));
// Convenience lambda for failing the application.
auto fail = [this](error reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
up_->abort(reason);
return read_result::abort;
};
// 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()) {
case write_result::want_read:
CAF_ASSERT(flags_.wanted_read_from_write_event);
return read_result::again;
case write_result::handover:
return read_result::handover;
case write_result::again:
parent_->register_writing();
break;
default:
break;
}
}
// Make sure the buffer is large enough.
if (read_buf_.size() < max_read_size_)
read_buf_.resize(max_read_size_);
// Fill up our buffer.
auto rd = policy_->read(fd_, make_span(read_buf_.data() + buffered_,
read_buf_.size() - buffered_));
// Stop if we failed to get more data.
if (rd < 0) {
switch (policy_->last_error(fd_, rd)) {
case stream_transport_error::temporary:
case stream_transport_error::want_read:
return read_result::again;
case stream_transport_error::want_write:
flags_.wanted_write_from_read_event = true;
return read_result::want_write;
default:
return fail(sec::socket_operation_failed);
}
} else if (rd == 0) {
return fail(sec::socket_disconnected);
}
// Make sure we actually have all data currently available to us and the
// policy is not holding on to some bytes. This may happen when using
// OpenSSL or any other transport policy operating on blocks.
buffered_ += static_cast<size_t>(rd);
if (auto policy_buffered = policy_->buffered(); policy_buffered > 0) {
if (auto n = buffered_ + policy_buffered; n > read_buf_.size())
read_buf_.resize(n);
auto rd2 = policy_->read(fd_, make_span(read_buf_.data() + buffered_,
policy_buffered));
if (rd2 != static_cast<ptrdiff_t>(policy_buffered)) {
CAF_LOG_ERROR("failed to read buffered data from the policy");
return fail(sec::socket_operation_failed);
}
buffered_ += static_cast<size_t>(rd2);
}
// Try to make progress on the application and return control to the
// multiplexer to allow other sockets to run.
return handle_buffered_data();
}
stream_transport::read_result stream_transport::handle_buffered_data() {
CAF_LOG_TRACE(CAF_ARG(buffered_));
// Convenience lambda for failing the application.
auto fail = [this](error reason) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(reason));
up_->abort(reason);
return read_result::abort;
};
// Loop until we have drained the buffer as much as we can.
CAF_ASSERT(min_read_size_ <= max_read_size_);
while (max_read_size_ > 0 && buffered_ >= min_read_size_) {
auto old_max_read_size = max_read_size_;
auto n = std::min(buffered_, size_t{max_read_size_});
auto bytes = make_span(read_buf_.data(), n);
auto delta = bytes.subspan(delta_offset_);
auto consumed = up_->consume(bytes, delta);
if (consumed < 0) {
// Negative values indicate that the application encountered an
// unrecoverable error.
up_->abort(make_error(caf::sec::runtime_error, "consumed < 0"));
return read_result::abort;
} else if (consumed == 0) {
// Returning 0 means that the application wants more data. Note:
// max_read_size_ may have changed if the application realized it
// requires more data to parse the input. It may of course only increase
// the max_read_size_ in this case, everything else makes no sense.
delta_offset_ = static_cast<ptrdiff_t>(n);
if (n == max_read_size_ || max_read_size_ < old_max_read_size) {
CAF_LOG_ERROR("application failed to make progress");
return fail(sec::runtime_error);
} else if (n == buffered_) {
// Either the application has increased max_read_size_ or we
// did not reach max_read_size_ the first time. In both cases, we
// cannot proceed without receiving more data.
return read_result::again;
}
} else if (static_cast<size_t>(consumed) > n) {
// Must not happen. An application cannot handle more data then we pass
// to it.
up_->abort(make_error(sec::logic_error, "consumed > buffer.size"));
return read_result::abort;
} else {
// Shove the unread bytes to the beginning of the buffer and continue
// to the next loop iteration.
auto del = static_cast<size_t>(consumed);
auto prev = buffered_;
buffered_ -= del;
delta_offset_ = 0;
if (buffered_ > 0) {
auto new_begin = read_buf_.begin() + del;
auto new_end = read_buf_.begin() + prev;
std::copy(new_begin, new_end, read_buf_.begin());
}
}
}
return max_read_size_ > 0 ? read_result::again : read_result::stop;
}
stream_transport::write_result stream_transport::handle_write_event() {
CAF_LOG_TRACE(CAF_ARG2("socket", fd_.id));
auto fail = [this](sec code) {
CAF_LOG_DEBUG("read failed" << CAF_ARG(code));
up_->abort(make_error(code));
return write_result::abort;
};
// Resume a read operation if the transport waited for the socket to be
// writable from the last call to handle_read_event.
if (flags_.wanted_write_from_read_event) {
flags_.wanted_write_from_read_event = false;
switch (handle_read_event()) {
case read_result::want_write:
CAF_ASSERT(flags_.wanted_write_from_read_event);
return write_result::again;
case read_result::handover:
return write_result::handover;
case read_result::again:
parent_->register_reading();
break;
default:
break;
}
// Fall though and see if we also have something to write.
}
// Allow the upper layer to add extra data to the write buffer.
if (!up_->prepare_send()) {
up_->abort(make_error(caf::sec::runtime_error, "prepare_send failed"));
return write_result::abort;
}
if (write_buf_.empty())
return !up_->done_sending() ? write_result::again : write_result::stop;
auto write_res = policy_->write(fd_, write_buf_);
if (write_res > 0) {
write_buf_.erase(write_buf_.begin(), write_buf_.begin() + write_res);
return !write_buf_.empty() || !up_->done_sending() ? write_result::again
: write_result::stop;
} else if (write_res < 0) {
// Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors.
switch (policy_->last_error(fd_, write_res)) {
case stream_transport_error::temporary:
case stream_transport_error::want_write:
return write_result::again;
case stream_transport_error::want_read:
flags_.wanted_read_from_write_event = true;
return write_result::want_read;
default:
return fail(sec::socket_operation_failed);
}
} else {
// write() returns 0 if the connection was closed.
return fail(sec::socket_disconnected);
}
}
stream_transport::read_result stream_transport::handle_continue_reading() {
if (max_read_size_ == 0) {
up_->continue_reading();
if (max_read_size_ != 0) {
return handle_buffered_data();
} else {
return read_result::stop;
}
} else {
return handle_buffered_data();
}
}
void stream_transport::abort(const error& reason) {
up_->abort(reason);
}
// -- free functions -----------------------------------------------------------
std::string to_string(stream_transport::policy::ec code) {
switch (code) {
case stream_transport::policy::ec::temporary:
return "temporary";
case stream_transport::policy::ec::want_read:
return "want_read";
case stream_transport::policy::ec::want_write:
return "want_write";
case stream_transport::policy::ec::permanent:
return "permanent";
default:
return "invalid";
}
}
bool from_string(std::string_view str, stream_transport::policy::ec& code) {
if (str == "temporary") {
code = stream_transport::policy::ec::temporary;
return true;
} else if (str == "want_read") {
code = stream_transport::policy::ec::want_read;
return true;
} else if (str == "want_write") {
code = stream_transport::policy::ec::want_write;
return true;
} else if (str == "permanent") {
code = stream_transport::policy::ec::permanent;
return true;
} else {
return false;
}
}
bool from_integer(int value, stream_transport::policy::ec& code) {
if (value >= 0 && value < 4) {
code = static_cast<stream_transport::policy::ec>(value);
return true;
} else {
return false;
}
}
} // namespace caf::net
......@@ -2,7 +2,7 @@
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/host.hpp"
#include "caf/net/this_host.hpp"
#include "caf/config.hpp"
#include "caf/detail/net_syscall.hpp"
......@@ -16,11 +16,10 @@ namespace caf::net {
#ifdef CAF_WINDOWS
error this_host::startup() {
void this_host::startup() {
WSADATA WinsockData;
CAF_NET_SYSCALL("WSAStartup", result, !=, 0,
WSAStartup(MAKEWORD(2, 2), &WinsockData));
return none;
CAF_NET_CRITICAL_SYSCALL("WSAStartup", result, !=, 0,
WSAStartup(MAKEWORD(2, 2), &WinsockData));
}
void this_host::cleanup() {
......@@ -29,8 +28,8 @@ void this_host::cleanup() {
#else // CAF_WINDOWS
error this_host::startup() {
return none;
void this_host::startup() {
// nop
}
void this_host::cleanup() {
......
// 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.
#include "caf/net/web_socket/client.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/base64.hpp"
#include "caf/error.hpp"
#include "caf/hash/sha1.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/http/v1.hpp"
#include "caf/net/message_flow_bridge.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/net/web_socket/framing.hpp"
#include "caf/net/web_socket/handshake.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include <algorithm>
namespace caf::net::web_socket {
// -- constructors, destructors, and assignment operators ----------------------
client::client(handshake_ptr hs, upper_layer_ptr up)
: hs_(std::move(hs)), framing_(std::move(up)) {
// nop
}
// -- factories ----------------------------------------------------------------
std::unique_ptr<client> client::make(handshake_ptr hs, upper_layer_ptr up) {
return std::make_unique<client>(std::move(hs), std::move(up));
}
// -- implementation of stream_oriented::upper_layer ---------------------------
error client::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
CAF_ASSERT(hs_ != nullptr);
framing_.init(owner, down);
if (!hs_->has_mandatory_fields())
return make_error(sec::runtime_error,
"handshake data lacks mandatory fields");
if (!hs_->has_valid_key())
hs_->randomize_key();
cfg_ = cfg;
down->begin_output();
hs_->write_http_1_request(down->output_buffer());
down->end_output();
down->configure_read(receive_policy::up_to(handshake::max_http_size));
return none;
}
void client::abort(const error& reason) {
upper_layer().abort(reason);
}
ptrdiff_t client::consume(byte_span buffer, byte_span delta) {
CAF_LOG_TRACE(CAF_ARG2("buffer", buffer.size()));
if (handshake_completed()) {
// Short circuit to the framing layer after the handshake completed.
return framing_.consume(buffer, delta);
} else {
// Check whether we have received the HTTP header or else wait for more
// data. Abort when exceeding the maximum size.
auto [hdr, remainder] = http::v1::split_header(buffer);
if (hdr.empty()) {
if (buffer.size() >= handshake::max_http_size) {
CAF_LOG_ERROR("server response exceeded the maximum header size");
upper_layer().abort(make_error(sec::protocol_error,
"server response exceeded "
"the maximum header size"));
return -1;
} else {
return 0;
}
} else if (!handle_header(hdr)) {
// Note: handle_header() already calls upper_layer().abort().
return -1;
} else if (remainder.empty()) {
CAF_ASSERT(hdr.size() == buffer.size());
return hdr.size();
} else {
CAF_LOG_DEBUG(CAF_ARG2("remainder.size", remainder.size()));
if (auto res = framing_.consume(remainder, remainder); res >= 0) {
return hdr.size() + res;
} else {
return res;
}
}
}
}
void client::continue_reading() {
if (handshake_completed())
upper_layer().continue_reading();
}
bool client::prepare_send() {
return handshake_completed() ? upper_layer().prepare_send() : true;
}
bool client::done_sending() {
return handshake_completed() ? upper_layer().done_sending() : true;
}
// -- HTTP response processing -------------------------------------------------
bool client::handle_header(std::string_view http) {
CAF_ASSERT(hs_ != nullptr);
auto http_ok = hs_->is_valid_http_1_response(http);
hs_.reset();
if (http_ok) {
if (auto err = upper_layer().init(owner_, &framing_, cfg_)) {
CAF_LOG_DEBUG("failed to initialize WebSocket framing layer");
return false;
} else {
CAF_LOG_DEBUG("completed WebSocket handshake");
return true;
}
} else {
CAF_LOG_DEBUG("received an invalid WebSocket handshake");
upper_layer().abort(make_error(sec::protocol_error,
"received an invalid WebSocket handshake"));
return false;
}
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/web_socket/framing.hpp"
#include "caf/logger.hpp"
namespace caf::net::web_socket {
// -- initialization ---------------------------------------------------------
void framing::init(socket_manager*, stream_oriented::lower_layer* down) {
std::random_device rd;
rng_.seed(rd());
down_ = down;
}
// -- web_socket::lower_layer implementation -----------------------------------
bool framing::can_send_more() const noexcept {
return down_->can_send_more();
}
void framing::suspend_reading() {
down_->suspend_reading();
}
bool framing::stopped_reading() const noexcept {
return down_->stopped_reading();
}
void framing::request_messages() {
if (down_->stopped_reading())
down_->configure_read(receive_policy::up_to(2048));
}
void framing::begin_binary_message() {
// nop
}
byte_buffer& framing::binary_message_buffer() {
return binary_buf_;
}
bool framing::end_binary_message() {
ship_frame(binary_buf_);
return true;
}
void framing::begin_text_message() {
// nop
}
text_buffer& framing::text_message_buffer() {
return text_buf_;
}
bool framing::end_text_message() {
ship_frame(text_buf_);
return true;
}
void framing::send_close_message() {
ship_close();
}
void framing::send_close_message(status code, std::string_view desc) {
ship_close(static_cast<uint16_t>(code), desc);
}
// -- interface for the lower layer --------------------------------------------
ptrdiff_t framing::consume(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) {
CAF_LOG_DEBUG("decoded malformed data: hdr_bytes < 0");
up_->abort(make_error(sec::protocol_error,
"negative header size on WebSocket connection"));
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))) {
CAF_LOG_DEBUG("WebSocket frame too large");
up_->abort(make_error(sec::protocol_error, "WebSocket frame too large"));
return -1;
}
// Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len;
if (buffer.size() < frame_size) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down_->stopped_reading())
down_->configure_read(receive_policy::up_to(2048));
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) {
// Call upper layer.
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
return -1;
} else if (!handle(hdr.opcode, payload)) {
return -1;
}
} else if (hdr.opcode != detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("expected a WebSocket continuation_frame");
up_->abort(make_error(sec::protocol_error,
"expected a WebSocket continuation_frame"));
return -1;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
up_->abort(make_error(sec::protocol_error,
"fragmented WebSocket payload "
"exceeds maximum size"));
return -1;
} else {
if (hdr.opcode == detail::rfc6455::connection_close) {
up_->abort(make_error(sec::connection_closed));
return -1;
} else {
// End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(),
payload.end());
if (!handle(hdr.opcode, payload_buf_))
return -1;
opcode_ = nil_code;
payload_buf_.clear();
}
}
} else {
if (opcode_ == nil_code) {
if (hdr.opcode == detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("received WebSocket continuation "
"frame without prior opcode");
up_->abort(make_error(sec::protocol_error,
"received WebSocket continuation "
"frame without prior opcode"));
return -1;
}
opcode_ = hdr.opcode;
} else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size.
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
up_->abort(make_error(sec::protocol_error,
"fragmented WebSocket payload "
"exceeds maximum size"));
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()) {
// Ask for more data unless the upper layer called suspend_reading.
if (!down_->stopped_reading())
down_->configure_read(receive_policy::up_to(2048));
return consumed + static_cast<ptrdiff_t>(frame_size);
}
consumed += static_cast<ptrdiff_t>(frame_size);
}
}
// -- implementation details ---------------------------------------------------
bool framing::handle(uint8_t opcode, byte_span payload) {
// Code rfc6455::connection_close must be treated separately.
CAF_ASSERT(opcode != detail::rfc6455::connection_close);
switch (opcode) {
case detail::rfc6455::text_frame: {
std::string_view text{reinterpret_cast<const char*>(payload.data()),
payload.size()};
return up_->consume_text(text) >= 0;
}
case detail::rfc6455::binary_frame:
return up_->consume_binary(payload) >= 0;
case detail::rfc6455::ping:
ship_pong(payload);
return true;
case detail::rfc6455::pong:
// nop
return true;
default:
// nop
return false;
}
}
void framing::ship_pong(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();
}
void framing::ship_close(uint16_t code, std::string_view msg) {
uint32_t mask_key = 0;
byte_buffer payload;
payload.reserve(msg.size() + 2);
payload.push_back(static_cast<std::byte>((code & 0xFF00) >> 8));
payload.push_back(static_cast<std::byte>(code & 0x00FF));
for (auto c : msg)
payload.push_back(static_cast<std::byte>(c));
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::connection_close, mask_key,
payload, down_->output_buffer());
down_->end_output();
}
void framing::ship_close() {
uint32_t mask_key = 0;
std::byte payload[] = {// Error code 1000: normal close.
std::byte{0x03}, std::byte{0xE8},
// "EOF" string as goodbye message.
std::byte{'E'}, std::byte{'O'}, std::byte{'F'}};
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::connection_close, mask_key,
payload, down_->output_buffer());
down_->end_output();
}
template <class T>
void framing::ship_frame(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();
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/web_socket/lower_layer.hpp"
#include "caf/error.hpp"
#include "caf/net/web_socket/status.hpp"
namespace caf::net::web_socket {
lower_layer::~lower_layer() {
// nop
}
void lower_layer::send_close_message(const error& reason) {
send_close_message(status::unexpected_condition, to_string(reason));
}
} // namespace caf::net::web_socket
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/net/web_socket/server.hpp"
namespace caf::net::web_socket {
// -- constructors, destructors, and assignment operators ----------------------
server::server(upper_layer_ptr up) : framing_(std::move(up)) {
// > A server MUST NOT mask any frames that it sends to the client.
// See RFC 6455, Section 5.1.
framing_.mask_outgoing_frames = false;
}
// -- factories ----------------------------------------------------------------
std::unique_ptr<server> server::make(upper_layer_ptr up) {
return std::make_unique<server>(std::move(up));
}
// -- stream_oriented::upper_layer implementation ------------------------------
error server::init(socket_manager* owner, stream_oriented::lower_layer* down,
const settings& cfg) {
framing_.init(owner, down);
cfg_ = cfg;
lower_layer().configure_read(receive_policy::up_to(handshake::max_http_size));
return none;
}
void server::abort(const error& reason) {
if (handshake_complete_)
upper_layer().abort(reason);
}
ptrdiff_t server::consume(byte_span input, byte_span delta) {
using namespace std::literals;
CAF_LOG_TRACE(CAF_ARG2("bytes.size", input.size()));
if (handshake_complete_) {
// Short circuit to the framing layer after the handshake completed.
return framing_.consume(input, delta);
} else {
// Check whether we received an HTTP header or else wait for more data.
// Abort when exceeding the maximum size.
auto [hdr, remainder] = http::v1::split_header(input);
if (hdr.empty()) {
if (input.size() >= handshake::max_http_size) {
lower_layer().begin_output();
http::v1::write_response(http::status::request_header_fields_too_large,
"text/plain"sv,
"Header exceeds maximum size."sv,
lower_layer().output_buffer());
lower_layer().end_output();
return -1;
} else {
return 0;
}
} else if (!handle_header(hdr)) {
return -1;
} else if (remainder.empty()) {
CAF_ASSERT(hdr.size() == input.size());
return hdr.size();
} else {
CAF_LOG_DEBUG(CAF_ARG2("remainder.size", remainder.size()));
if (auto sub_result = framing_.consume(remainder, remainder);
sub_result >= 0) {
return hdr.size() + sub_result;
} else {
return sub_result;
}
}
}
}
void server::continue_reading() {
auto rp = receive_policy::up_to(handshake::max_http_size);
lower_layer().configure_read(rp);
}
bool server::prepare_send() {
return handshake_complete_ ? upper_layer().prepare_send() : true;
}
bool server::done_sending() {
return handshake_complete_ ? upper_layer().done_sending() : true;
}
// -- HTTP request processing ------------------------------------------------
void server::write_response(http::status code, std::string_view msg) {
lower_layer().begin_output();
http::v1::write_response(code, "text/plain", msg,
lower_layer().output_buffer());
lower_layer().end_output();
}
bool server::handle_header(std::string_view http) {
using namespace std::literals;
// Parse the header and reject invalid inputs.
http::header hdr;
auto [code, msg] = hdr.parse(http);
if (code != http::status::ok) {
write_response(code, msg);
return false;
}
if (hdr.method() != http::method::get) {
write_response(http::status::bad_request,
"Expected a WebSocket handshake.");
return false;
}
// Check whether the mandatory fields exist.
auto sec_key = hdr.field("Sec-WebSocket-Key");
if (sec_key.empty()) {
auto descr = "Mandatory field Sec-WebSocket-Key missing or invalid."s;
write_response(http::status::bad_request, descr);
CAF_LOG_DEBUG("received invalid WebSocket handshake");
return false;
}
// Store the request information in the settings for the upper layer.
auto& ws = cfg_["web-socket"].as_dictionary();
put(ws, "method", to_rfc_string(hdr.method()));
put(ws, "path", std::string{hdr.path()});
put(ws, "query", hdr.query());
put(ws, "fragment", hdr.fragment());
put(ws, "http-version", hdr.version());
if (!hdr.fields().empty()) {
auto& fields = ws["fields"].as_dictionary();
for (auto& [key, val] : hdr.fields())
put(fields, std::string{key}, std::string{val});
}
// Try to initialize the upper layer.
if (auto err = upper_layer().init(owner_, &framing_, cfg_)) {
auto descr = to_string(err);
CAF_LOG_DEBUG("upper layer rejected a WebSocket connection:" << descr);
write_response(http::status::bad_request, descr);
return false;
}
// Finalize the WebSocket handshake.
handshake hs;
hs.assign_key(sec_key);
lower_layer().begin_output();
hs.write_http_1_response(lower_layer().output_buffer());
lower_layer().end_output();
CAF_LOG_DEBUG("completed WebSocket handshake");
handshake_complete_ = true;
return true;
}
} // namespace caf::net::web_socket
......@@ -2,10 +2,12 @@
// 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/web_socket/upper_layer.hpp"
namespace caf::tag {
namespace caf::net::web_socket {
struct mixed_message_oriented {};
upper_layer::~upper_layer() {
// nop
}
} // namespace caf::tag
} // namespace caf::net::web_socket
#define CAF_TEST_NO_MAIN
#include "caf/test/unit_test_impl.hpp"
#include "net-test.hpp"
#include "caf/error.hpp"
#include "caf/init_global_meta_objects.hpp"
#include "caf/net/host.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/ssl/startup.hpp"
#include "caf/net/this_host.hpp"
#include "caf/raise_error.hpp"
#define CAF_TEST_NO_MAIN
#include "caf/test/unit_test_impl.hpp"
using namespace caf;
// -- mock_stream_transport ----------------------------------------------------
bool mock_stream_transport::can_send_more() const noexcept {
return true;
}
void mock_stream_transport::suspend_reading() {
configure_read(net::receive_policy::stop());
}
void mock_stream_transport::configure_read(net::receive_policy policy) {
min_read_size = policy.min_size;
max_read_size = policy.max_size;
}
void mock_stream_transport::begin_output() {
// nop
}
byte_buffer& mock_stream_transport::output_buffer() {
return output;
}
bool mock_stream_transport::end_output() {
return true;
}
bool mock_stream_transport::stopped_reading() const noexcept {
return max_read_size == 0;
}
ptrdiff_t mock_stream_transport::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 = up->consume(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) {
return -1;
}
}
return result;
}
// -- barrier ------------------------------------------------------------------
void barrier::arrive_and_wait() {
std::unique_lock<std::mutex> guard{mx_};
auto new_count = ++count_;
if (new_count == num_threads_) {
cv_.notify_all();
} else if (new_count > num_threads_) {
count_ = 1;
cv_.wait(guard, [this] { return count_.load() == num_threads_; });
} else {
cv_.wait(guard, [this] { return count_.load() == num_threads_; });
}
}
// -- main --------------------------------------------------------------------
int main(int argc, char** argv) {
if (auto err = caf::net::this_host::startup())
CAF_RAISE_ERROR(std::logic_error, "this_host::startup failed");
net::this_host::startup();
net::ssl::startup();
using namespace caf;
net::middleman::init_global_meta_objects();
core::init_global_meta_objects();
auto result = test::main(argc, argv);
caf::net::this_host::cleanup();
net::ssl::cleanup();
net::this_host::cleanup();
return result;
}
......@@ -3,75 +3,56 @@
#include "caf/error.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/stream_oriented.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/tag/stream_oriented.hpp"
#include "caf/test/bdd_dsl.hpp"
template <class UpperLayer>
class mock_stream_transport {
class mock_stream_transport : public caf::net::stream_oriented::lower_layer {
public:
// -- member types -----------------------------------------------------------
using output_tag = caf::tag::stream_oriented;
using upper_layer_ptr
= std::unique_ptr<caf::net::stream_oriented::upper_layer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit mock_stream_transport(Ts&&... xs)
: upper_layer(std::forward<Ts>(xs)...) {
explicit mock_stream_transport(upper_layer_ptr ptr) : up(std::move(ptr)) {
// nop
}
// -- interface for the upper layer ------------------------------------------
// -- factories --------------------------------------------------------------
void begin_output() {
// nop
static std::unique_ptr<mock_stream_transport> make(upper_layer_ptr ptr) {
return std::make_unique<mock_stream_transport>(std::move(ptr));
}
auto& output_buffer() {
return output;
}
// -- implementation of stream_oriented::lower_layer -------------------------
constexpr void end_output() {
// nop
}
bool can_send_more() const noexcept override;
constexpr caf::net::socket handle() noexcept {
return caf::net::invalid_socket;
}
void suspend_reading() override;
bool can_send_more() const noexcept {
return true;
}
void configure_read(caf::net::receive_policy policy) override;
const caf::error& abort_reason() {
return abort_reason_;
}
void begin_output() override;
void abort_reason(caf::error reason) {
abort_reason_ = std::move(reason);
}
caf::byte_buffer& output_buffer() override;
bool stopped() const noexcept {
return max_read_size == 0;
}
bool end_output() override;
void configure_read(caf::net::receive_policy policy) {
min_read_size = policy.min_size;
max_read_size = policy.max_size;
}
bool stopped_reading() const noexcept override;
// -- initialization ---------------------------------------------------------
caf::error init(const caf::settings& config) {
return upper_layer.init(nullptr, this, config);
caf::error init(const caf::settings& cfg) {
return up->init(nullptr, this, cfg);
}
caf::error init() {
caf::settings config;
return init(config);
caf::settings cfg;
return init(cfg);
}
// -- buffer management ------------------------------------------------------
......@@ -94,43 +75,11 @@ public:
// -- 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 = caf::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;
}
ptrdiff_t handle_input();
// -- member variables -------------------------------------------------------
UpperLayer upper_layer;
upper_layer_ptr up;
caf::byte_buffer output;
......@@ -156,18 +105,7 @@ public:
// nop
}
void arrive_and_wait() {
std::unique_lock<std::mutex> guard{mx_};
auto new_count = ++count_;
if (new_count == num_threads_) {
cv_.notify_all();
} else if (new_count > num_threads_) {
count_ = 1;
cv_.wait(guard, [this] { return count_.load() == num_threads_; });
} else {
cv_.wait(guard, [this] { return count_.load() == num_threads_; });
}
}
void arrive_and_wait();
private:
ptrdiff_t num_threads_;
......
......@@ -24,65 +24,67 @@ namespace {
using svec = std::vector<std::string>;
struct app_t {
using input_tag = tag::stream_oriented;
class app_t : public net::stream_oriented::upper_layer {
public:
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);
static auto make() {
return std::make_unique<app_t>();
}
static auto make(actor hdl) {
return std::make_unique<app_t>(std::move(hdl));
}
error init(net::socket_manager* mgr, net::stream_oriented::lower_layer* down,
const settings&) override {
this->down = down;
self = mgr->make_actor_shell();
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);
});
self->set_fallback(
[](net::abstract_actor_shell* self, message& msg) -> result<message> {
CAF_FAIL("unexpected message: " << msg);
auto err = make_error(sec::unexpected_message);
self->quit(err);
return err;
});
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;
bool prepare_send() override {
while (self->consume_message())
; // repeat
return !self->terminated();
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
void continue_reading() override {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return self->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf, byte_span) {
ptrdiff_t consume(byte_span buf, byte_span) override {
// Seek newline character.
constexpr auto nl = std::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), {});
auto sub_res = consume(buf.subspan(1), {});
return sub_res >= 0 ? sub_res + 1 : sub_res;
}
auto num_bytes = std::distance(buf.begin(), i) + 1;
......@@ -93,21 +95,19 @@ struct app_t {
if (auto parsed_res = config_value::parse(line)) {
val = std::move(*parsed_res);
} else {
down->abort_reason(std::move(parsed_res.error()));
return -1;
}
CAF_MESSAGE("deserialize message from: " << val);
config_value_reader reader{&val};
caf::message msg;
if (!reader.apply(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 {
[this](int32_t value) mutable {
++received_responses;
// Respond with the value as string.
auto str_response = std::to_string(value);
......@@ -118,16 +118,19 @@ struct app_t {
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
},
[down](error& err) mutable { down->abort_reason(std::move(err)); });
[this](error& err) mutable { self->quit(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, {});
auto sub_res = consume(sub_buf, {});
return sub_res >= 0 ? num_bytes + sub_res : sub_res;
}
return 0;
}
// Pointer to the layer below.
net::stream_oriented::lower_layer* down;
// Handle to the worker-under-test.
actor worker;
......@@ -198,17 +201,19 @@ constexpr std::string_view input = R"__(
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);
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make();
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
auto hdl = app.self.as_actor();
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"}));
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) {
......@@ -217,14 +222,15 @@ CAF_TEST(actor shells can send requests and receive responses) {
[](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);
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make(worker);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
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(); });
run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
std::string_view expected_response = "246\n";
run_while([&] { return recv_buf.size() < expected_response.size(); });
......
......@@ -12,6 +12,7 @@
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
......@@ -58,10 +59,8 @@ private:
net::socket_guard<net::stream_socket> sg_;
};
class app_t {
class app_t : public net::stream_oriented::upper_layer {
public:
using input_tag = tag::stream_oriented;
using resource_type = async::consumer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
......@@ -74,8 +73,14 @@ public:
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings&) {
static auto make(resource_type input) {
return std::make_unique<app_t>(std::move(input));
}
error init(net::socket_manager* mgr,
net::stream_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
if (auto ptr = adapter_type::try_open(mgr, std::move(input))) {
adapter = std::move(ptr);
return none;
......@@ -84,10 +89,8 @@ public:
}
}
template <class LowerLayerPtr>
struct send_helper {
app_t* thisptr;
LowerLayerPtr down;
void on_next(int32_t item) {
thisptr->written_values.emplace_back(item);
......@@ -96,10 +99,10 @@ public:
if (!sink.apply(item))
FAIL("sink.apply failed: " << sink.get_error());
auto bytes = make_span(thisptr->written_bytes).subspan(offset);
down->begin_output();
auto& buf = down->output_buffer();
thisptr->down->begin_output();
auto& buf = thisptr->down->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
thisptr->down->end_output();
}
void on_complete() {
......@@ -111,11 +114,10 @@ public:
}
};
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
bool prepare_send() override {
if (done || !adapter)
return true;
auto helper = send_helper<LowerLayerPtr>{this, down};
auto helper = send_helper{this};
while (down->can_send_more()) {
auto [again, consumed] = adapter->pull(async::delay_errors, 1, helper);
if (!again) {
......@@ -131,26 +133,23 @@ public:
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return done || !adapter->has_data();
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
void continue_reading() override {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
FAIL("app::abort called: " << reason);
void abort(const error& reason) override {
MESSAGE("app::abort: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr, byte_span, byte_span) {
ptrdiff_t consume(byte_span, byte_span) override {
FAIL("app::consume called: unexpected data");
}
net::stream_oriented::lower_layer* down = nullptr;
bool done = false;
std::vector<int32_t> written_values;
byte_buffer written_bytes;
......@@ -169,6 +168,10 @@ struct fixture : test_coordinator_fixture<> {
return mm.mpx().poll_once(false);
}
auto mpx() {
return mm.mpx_ptr();
}
net::middleman mm;
};
......@@ -189,9 +192,10 @@ SCENARIO("subscriber adapters wake up idle socket managers") {
FAIL("nonblocking(fd1) returned an error: " << err);
if (auto err = nonblocking(fd2, true))
FAIL("nonblocking(fd2) returned an error: " << err);
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(
fd1, mm.mpx_ptr(), std::move(rd));
auto& app = mgr->top_layer();
auto app = app_t::make(std::move(rd));
auto& state = *app;
auto transport = net::stream_transport::make(fd1, std::move(app));
auto mgr = net::socket_manager::make(mpx(), fd1, std::move(transport));
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the reader receives all items before the connection closes") {
......@@ -205,10 +209,10 @@ SCENARIO("subscriber adapters wake up idle socket managers") {
run();
rd.read_some();
}
CHECK_EQ(app.written_values, std::vector<int32_t>(num_items, 42));
CHECK_EQ(app.written_bytes.size(), num_items * sizeof(int32_t));
CHECK_EQ(state.written_values, std::vector<int32_t>(num_items, 42));
CHECK_EQ(state.written_bytes.size(), num_items * sizeof(int32_t));
CHECK_EQ(rd.buf().size(), num_items * sizeof(int32_t));
CHECK_EQ(app.written_bytes, rd.buf());
CHECK_EQ(state.written_bytes, rd.buf());
}
}
}
......
......@@ -9,46 +9,21 @@
#include "net-test.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
using namespace std::literals;
namespace {
struct app_t {
class app_t : public net::http::upper_layer {
public:
// -- member variables -------------------------------------------------------
net::http::header hdr;
caf::byte_buffer payload;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings&) {
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
net::http::lower_layer* down = nullptr;
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>
bool consume(LowerLayerPtr down, net::http::context ctx,
const net::http::header& request_hdr,
caf::const_byte_span body) {
hdr = request_hdr;
down->send_response(ctx, net::http::status::ok, "text/plain",
"Hello world!");
payload.assign(body.begin(), body.end());
return true;
}
// -- properties -------------------------------------------------------------
std::string_view field(std::string_view key) {
if (auto i = hdr.fields().find(key); i != hdr.fields().end())
......@@ -64,9 +39,48 @@ struct app_t {
else
return {};
}
};
using mock_server_type = mock_stream_transport<net::http::server<app_t>>;
// -- factories --------------------------------------------------------------
static auto make() {
return std::make_unique<app_t>();
}
// -- implementation of http::upper_layer ------------------------------------
error init(net::socket_manager*, net::http::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
return none;
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
bool prepare_send() override {
return true;
}
bool done_sending() override {
return true;
}
ptrdiff_t consume(net::http::context ctx,
const net::http::header& request_hdr,
const_byte_span body) override {
hdr = request_hdr;
auto content = "Hello world!"sv;
down->send_response(ctx, net::http::status::ok, "text/plain",
as_bytes(make_span(content)));
payload.assign(body.begin(), body.end());
return static_cast<ptrdiff_t>(body.size());
}
void continue_reading() override {
// nop
}
};
} // namespace
......@@ -82,22 +96,24 @@ SCENARIO("the server parses HTTP GET requests into header fields") {
"\r\n"
"Hello world!";
WHEN("sending it to an HTTP server") {
mock_server_type serv;
CHECK_EQ(serv.init(), error{});
serv.push(req);
auto app_ptr = app_t::make();
auto app = app_ptr.get();
auto http_ptr = net::http::server::make(std::move(app_ptr));
auto serv = mock_stream_transport::make(std::move(http_ptr));
CHECK_EQ(serv->init(settings{}), error{});
serv->push(req);
THEN("the HTTP layer parses the data and calls the application layer") {
CHECK_EQ(serv.handle_input(), static_cast<ptrdiff_t>(req.size()));
auto& app = serv.upper_layer.upper_layer();
auto& hdr = app.hdr;
CHECK_EQ(serv->handle_input(), static_cast<ptrdiff_t>(req.size()));
auto& hdr = app->hdr;
CHECK_EQ(hdr.method(), net::http::method::get);
CHECK_EQ(hdr.version(), "HTTP/1.1");
CHECK_EQ(hdr.path(), "/foo/bar");
CHECK_EQ(app.field("Host"), "localhost:8090");
CHECK_EQ(app.field("User-Agent"), "AwesomeLib/1.0");
CHECK_EQ(app.field("Accept-Encoding"), "gzip");
CHECK_EQ(app->field("Host"), "localhost:8090");
CHECK_EQ(app->field("User-Agent"), "AwesomeLib/1.0");
CHECK_EQ(app->field("Accept-Encoding"), "gzip");
}
AND("the server properly formats a response from the application layer") {
CHECK_EQ(serv.output_as_str(), res);
CHECK_EQ(serv->output_as_str(), res);
}
}
}
......
......@@ -17,13 +17,13 @@
#include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/net/message_oriented.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/net/stream_transport.hpp"
#include "caf/span.hpp"
#include "caf/tag/message_oriented.hpp"
using namespace caf;
using namespace std::literals;
......@@ -32,43 +32,59 @@ namespace {
using string_list = std::vector<std::string>;
using shared_string_list = std::shared_ptr<string_list>;
template <bool EnableSuspend>
struct app {
using input_tag = tag::message_oriented;
class app_t : public net::message_oriented::upper_layer {
public:
static auto make(shared_string_list inputs) {
return std::make_unique<app_t>(std::move(inputs));
}
app_t(shared_string_list ls_ptr) : inputs(std::move(ls_ptr)) {
// nop
}
template <class LowerLayerPtr>
caf::error init(net::socket_manager*, LowerLayerPtr, const settings&) {
caf::error init(net::socket_manager*,
net::message_oriented::lower_layer* down_ptr,
const settings&) override {
// Start reading immediately.
down = down_ptr;
down->request_messages();
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
bool prepare_send() override {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error&) {
void abort(const error&) override {
// nop
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf) {
void continue_reading() override {
down->request_messages();
}
ptrdiff_t consume(byte_span buf) override {
printf("app_t::consume %d\n", __LINE__);
auto printable = [](std::byte x) {
return ::isprint(static_cast<uint8_t>(x));
};
if (CHECK(std::all_of(buf.begin(), buf.end(), printable))) {
printf("app_t::consume %d\n", __LINE__);
auto str_buf = reinterpret_cast<char*>(buf.data());
inputs.emplace_back(std::string{str_buf, buf.size()});
inputs->emplace_back(std::string{str_buf, buf.size()});
printf("app_t::consume %d added %s\n", __LINE__, inputs->back().c_str());
if constexpr (EnableSuspend)
if (inputs.back() == "pause")
if (inputs->back() == "pause")
down->suspend_reading();
std::string response = "ok ";
response += std::to_string(inputs.size());
response += std::to_string(inputs->size());
auto response_bytes = as_bytes(make_span(response));
down->begin_message();
auto& buf = down->message_buffer();
......@@ -76,11 +92,14 @@ struct app {
CHECK(down->end_message());
return static_cast<ptrdiff_t>(buf.size());
} else {
printf("app_t::consume %d\n", __LINE__);
return -1;
}
}
std::vector<std::string> inputs;
net::message_oriented::lower_layer* down = nullptr;
shared_string_list inputs;
};
void encode(byte_buffer& buf, std::string_view msg) {
......@@ -99,7 +118,7 @@ auto decode(byte_buffer& buf) {
string_list result;
auto input = make_span(buf);
while (!input.empty()) {
auto [msg_size, msg] = net::length_prefix_framing<app<false>>::split(input);
auto [msg_size, msg] = net::length_prefix_framing::split(input);
if (msg_size > msg.size()) {
CAF_FAIL("cannot decode buffer: invalid message size");
} else if (!std::all_of(msg.begin(), msg.begin() + msg_size, printable)) {
......@@ -118,19 +137,21 @@ auto decode(byte_buffer& buf) {
SCENARIO("length-prefix framing reads data with 32-bit size headers") {
GIVEN("a length_prefix_framing with an app that consumes strings") {
WHEN("pushing data into the unit-under-test") {
mock_stream_transport<net::length_prefix_framing<app<false>>> uut;
CHECK_EQ(uut.init(), error{});
auto buf = std::make_shared<string_list>();
auto app = app_t<false>::make(buf);
auto framing = net::length_prefix_framing::make(std::move(app));
auto uut = mock_stream_transport::make(std::move(framing));
CHECK_EQ(uut->init(), error{});
THEN("the app receives all strings as individual messages") {
encode(uut.input, "hello");
encode(uut.input, "world");
auto input_size = static_cast<ptrdiff_t>(uut.input.size());
CHECK_EQ(uut.handle_input(), input_size);
auto& state = uut.upper_layer.upper_layer();
if (CHECK_EQ(state.inputs.size(), 2u)) {
CHECK_EQ(state.inputs[0], "hello");
CHECK_EQ(state.inputs[1], "world");
encode(uut->input, "hello");
encode(uut->input, "world");
auto input_size = static_cast<ptrdiff_t>(uut->input.size());
CHECK_EQ(uut->handle_input(), input_size);
if (CHECK_EQ(buf->size(), 2u)) {
CHECK_EQ(buf->at(0), "hello");
CHECK_EQ(buf->at(1), "world");
}
CHECK_EQ(decode(uut.output), string_list({"ok 1", "ok 2"}));
CHECK_EQ(decode(uut->output), string_list({"ok 1", "ok 2"}));
}
}
}
......@@ -162,34 +183,37 @@ SCENARIO("calling suspend_reading removes message apps temporarily") {
REQUIRE_EQ(mpx.num_socket_managers(), 1u);
if (auto err = net::nonblocking(fd2, true))
CAF_FAIL("nonblocking returned an error: " << err);
auto mgr = net::make_socket_manager<app<true>, net::length_prefix_framing,
net::stream_transport>(fd2, &mpx);
auto buf = std::make_shared<string_list>();
auto app = app_t<true>::make(buf);
auto framing = net::length_prefix_framing::make(std::move(app));
auto transport = net::stream_transport::make(fd2, std::move(framing));
auto mgr = net::socket_manager::make(&mpx, fd2, std::move(transport));
CHECK_EQ(mgr->init(settings{}), none);
mpx.apply_updates();
REQUIRE_EQ(mpx.num_socket_managers(), 2u);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read);
auto& state = mgr->top_layer();
WHEN("the app calls suspend_reading") {
while (mpx.num_socket_managers() > 1u)
mpx.poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::none);
if (CHECK_EQ(state.inputs.size(), 3u)) {
CHECK_EQ(state.inputs[0], "first");
CHECK_EQ(state.inputs[1], "second");
CHECK_EQ(state.inputs[2], "pause");
if (CHECK_EQ(buf->size(), 3u)) {
CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second");
CHECK_EQ(buf->at(2), "pause");
}
THEN("users can resume it via continue_reading ") {
mgr->continue_reading();
mpx.apply_updates();
mpx.poll_once(true);
CHECK_EQ(mpx.mask_of(mgr), net::operation::read);
while (mpx.num_socket_managers() > 1u)
mpx.poll_once(true);
if (CHECK_EQ(state.inputs.size(), 5u)) {
CHECK_EQ(state.inputs[0], "first");
CHECK_EQ(state.inputs[1], "second");
CHECK_EQ(state.inputs[2], "pause");
CHECK_EQ(state.inputs[3], "third");
CHECK_EQ(state.inputs[4], "fourth");
if (CHECK_EQ(buf->size(), 5u)) {
CHECK_EQ(buf->at(0), "first");
CHECK_EQ(buf->at(1), "second");
CHECK_EQ(buf->at(2), "pause");
CHECK_EQ(buf->at(3), "third");
CHECK_EQ(buf->at(4), "fourth");
}
}
}
......
......@@ -19,33 +19,33 @@
#include <tuple>
using namespace caf;
using namespace caf::net;
namespace {
using shared_atomic_count = std::shared_ptr<std::atomic<size_t>>;
using shared_count = std::shared_ptr<std::atomic<size_t>>;
class dummy_manager : public socket_manager {
class mock_event_layer : public net::socket_event_layer {
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) {
MESSAGE("created new dummy manager");
mock_event_layer(net::stream_socket fd, std::string name, shared_count count)
: name(std::move(name)), fd_(fd), count_(count) {
MESSAGE("created new mock event layer");
++*count_;
rd_buf_.resize(1024);
}
~dummy_manager() {
MESSAGE("destroyed dummy manager");
~mock_event_layer() {
MESSAGE("destroyed mock event layer");
--*count_;
}
// -- properties -------------------------------------------------------------
// -- factories --------------------------------------------------------------
stream_socket handle() const noexcept {
return socket_cast<stream_socket>(handle_);
static auto make(net::stream_socket fd, std::string name,
shared_count count) {
return std::make_unique<mock_event_layer>(fd, std::move(name),
std::move(count));
}
// -- testing DSL ------------------------------------------------------------
......@@ -61,26 +61,26 @@ public:
return result;
}
// -- interface functions ----------------------------------------------------
// -- implementation of socket_event_layer -----------------------------------
error init(const settings&) override {
error init(net::socket_manager*, const settings&) override {
return none;
}
read_result handle_read_event() override {
if (trigger_handover) {
MESSAGE(name << " triggered a handover");
return read_result::handover;
}
// if (trigger_handover) {
// MESSAGE(name << " triggered a handover");
// return read_result::handover;
// }
if (read_capacity() < 1024)
rd_buf_.resize(rd_buf_.size() + 2048);
auto num_bytes = read(handle(),
auto num_bytes = read(fd_,
make_span(read_position_begin(), read_capacity()));
if (num_bytes > 0) {
CAF_ASSERT(num_bytes > 0);
rd_buf_pos_ += num_bytes;
return read_result::again;
} else if (num_bytes < 0 && last_socket_error_is_temporary()) {
} else if (num_bytes < 0 && net::last_socket_error_is_temporary()) {
return read_result::again;
} else {
return read_result::stop;
......@@ -96,46 +96,26 @@ public:
}
write_result handle_write_event() override {
if (trigger_handover) {
MESSAGE(name << " triggered a handover");
return write_result::handover;
}
// if (trigger_handover) {
// MESSAGE(name << " triggered a handover");
// return write_result::handover;
// }
if (wr_buf_.size() == 0)
return write_result::stop;
auto num_bytes = write(handle(), wr_buf_);
auto num_bytes = write(fd_, wr_buf_);
if (num_bytes > 0) {
wr_buf_.erase(wr_buf_.begin(), wr_buf_.begin() + num_bytes);
return wr_buf_.size() > 0 ? write_result::again : write_result::stop;
}
return num_bytes < 0 && last_socket_error_is_temporary()
return num_bytes < 0 && net::last_socket_error_is_temporary()
? write_result::again
: write_result::stop;
}
write_result handle_continue_writing() override {
return write_result::again;
}
void handle_error(sec code) override {
FAIL("handle_error called with code " << code);
}
socket_manager_ptr make_next_manager(socket handle) override {
if (next != nullptr)
FAIL("asked to do handover twice!");
next = make_counted<dummy_manager>(socket_cast<stream_socket>(handle), mpx_,
"Carl", count_);
if (auto err = next->init(settings{}))
FAIL("next->init failed: " << err);
return next;
void abort(const error& reason) override {
FAIL("abort called: " << reason);
}
// --
bool trigger_handover = false;
intrusive_ptr<dummy_manager> next;
std::string name;
private:
......@@ -151,7 +131,9 @@ private:
return rd_buf_.size() - rd_buf_pos_;
}
shared_atomic_count count_;
net::stream_socket fd_;
shared_count count_;
size_t rd_buf_pos_ = 0;
......@@ -160,8 +142,6 @@ private:
byte_buffer rd_buf_;
};
using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
struct fixture {
fixture() : mpx(nullptr) {
manager_count = std::make_shared<std::atomic<size_t>>(0);
......@@ -184,9 +164,11 @@ struct fixture {
mpx.apply_updates();
}
auto make_manager(stream_socket fd, std::string name) {
return make_counted<dummy_manager>(fd, &mpx, std::move(name),
manager_count);
std::pair<mock_event_layer*, net::socket_manager_ptr>
make_manager(net::stream_socket fd, std::string name) {
auto mock = mock_event_layer::make(fd, std::move(name), manager_count);
auto mock_ptr = mock.get();
return {mock_ptr, net::socket_manager::make(&mpx, fd, std::move(mock))};
}
void init() {
......@@ -195,9 +177,9 @@ struct fixture {
exhaust();
}
shared_atomic_count manager_count;
shared_count manager_count;
multiplexer mpx;
net::multiplexer mpx;
};
} // namespace
......@@ -231,16 +213,16 @@ SCENARIO("socket managers can register for read and write operations") {
GIVEN("an initialized multiplexer") {
init();
WHEN("socket managers register for read and write operations") {
auto [alice_fd, bob_fd] = unbox(make_stream_socket_pair());
auto alice = make_manager(alice_fd, "Alice");
auto bob = make_manager(bob_fd, "Bob");
alice->register_reading();
bob->register_reading();
auto [alice_fd, bob_fd] = unbox(net::make_stream_socket_pair());
auto [alice, alice_mgr] = make_manager(alice_fd, "Alice");
auto [bob, bob_mgr] = make_manager(bob_fd, "Bob");
alice_mgr->register_reading();
bob_mgr->register_reading();
apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 3u);
THEN("the multiplexer runs callbacks on socket activity") {
alice->send("Hello Bob!");
alice->register_writing();
alice_mgr->register_writing();
exhaust();
CHECK_EQ(bob->receive(), "Hello Bob!");
}
......@@ -258,56 +240,58 @@ SCENARIO("a multiplexer terminates its thread after shutting down") {
mpx.run();
}};
go_time->arrive_and_wait();
auto [alice_fd, bob_fd] = unbox(make_stream_socket_pair());
auto alice = make_manager(alice_fd, "Alice");
auto bob = make_manager(bob_fd, "Bob");
alice->register_reading();
bob->register_reading();
auto [alice_fd, bob_fd] = unbox(net::make_stream_socket_pair());
auto [alice, alice_mgr] = make_manager(alice_fd, "Alice");
auto [bob, bob_mgr] = make_manager(bob_fd, "Bob");
alice_mgr->register_reading();
bob_mgr->register_reading();
WHEN("calling shutdown on the multiplexer") {
mpx.shutdown();
THEN("the thread terminates and all socket managers get shut down") {
mpx_thread.join();
CHECK(alice->read_closed());
CHECK(bob->read_closed());
CHECK(alice_mgr->read_closed());
CHECK(bob_mgr->read_closed());
}
}
}
}
SCENARIO("a multiplexer allows managers to perform socket handovers") {
GIVEN("an initialized multiplexer") {
init();
WHEN("socket manager triggers a handover") {
auto [alice_fd, bob_fd] = unbox(make_stream_socket_pair());
auto alice = make_manager(alice_fd, "Alice");
auto bob = make_manager(bob_fd, "Bob");
alice->register_reading();
bob->register_reading();
apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 3u);
THEN("the multiplexer swaps out the socket managers for the socket") {
alice->send("Hello Bob!");
alice->register_writing();
exhaust();
CHECK_EQ(bob->receive(), "Hello Bob!");
bob->trigger_handover = true;
alice->send("Hello Carl!");
alice->register_writing();
bob->register_reading();
exhaust();
CHECK_EQ(bob->receive(), "");
CHECK_EQ(bob->handle(), invalid_socket);
if (CHECK_NE(bob->next, nullptr)) {
auto carl = bob->next;
CHECK_EQ(carl->handle(), socket{bob_fd});
carl->register_reading();
exhaust();
CHECK_EQ(carl->name, "Carl");
CHECK_EQ(carl->receive(), "Hello Carl!");
}
}
}
}
}
// TODO: re-implement handovers
//
// SCENARIO("a multiplexer allows managers to perform socket handovers") {
// GIVEN("an initialized multiplexer") {
// init();
// WHEN("socket manager triggers a handover") {
// auto [alice_fd, bob_fd] = unbox(make_stream_socket_pair());
// auto alice = make_manager(alice_fd, "Alice");
// auto bob = make_manager(bob_fd, "Bob");
// alice->register_reading();
// bob->register_reading();
// apply_updates();
// CHECK_EQ(mpx.num_socket_managers(), 3u);
// THEN("the multiplexer swaps out the socket managers for the socket") {
// alice->send("Hello Bob!");
// alice->register_writing();
// exhaust();
// CHECK_EQ(bob->receive(), "Hello Bob!");
// bob->trigger_handover = true;
// alice->send("Hello Carl!");
// alice->register_writing();
// bob->register_reading();
// exhaust();
// CHECK_EQ(bob->receive(), "");
// CHECK_EQ(bob->handle(), invalid_socket);
// if (CHECK_NE(bob->next, nullptr)) {
// auto carl = bob->next;
// CHECK_EQ(carl->handle(), socket{bob_fd});
// carl->register_reading();
// exhaust();
// CHECK_EQ(carl->name, "Carl");
// CHECK_EQ(carl->receive(), "Hello Carl!");
// }
// }
// }
// }
// }
END_FIXTURE_SCOPE()
......@@ -10,11 +10,12 @@
#include "caf/detail/network_order.hpp"
#include "caf/net/length_prefix_framing.hpp"
#include "caf/net/message_oriented.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/tag/message_oriented.hpp"
using namespace caf;
......@@ -51,10 +52,8 @@ private:
net::socket_guard<net::stream_socket> sg_;
};
class app_t {
class app_t : public net::message_oriented::upper_layer {
public:
using input_tag = tag::message_oriented;
using resource_type = async::producer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
......@@ -67,8 +66,15 @@ public:
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings&) {
static auto make(resource_type output) {
return std::make_unique<app_t>(std::move(output));
}
error init(net::socket_manager* mgr,
net::message_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
down->request_messages();
if (auto ptr = adapter_type::try_open(mgr, std::move(output_))) {
adapter_ = std::move(ptr);
return none;
......@@ -77,31 +83,26 @@ public:
}
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
void continue_reading() override {
down->request_messages();
}
bool prepare_send() override {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void abort(const error& reason) override {
if (reason == caf::sec::socket_disconnected || reason == caf::sec::disposed)
adapter_->close();
else
adapter_->abort(reason);
}
template <class LowerLayerPtr>
void after_reading(LowerLayerPtr) {
// nop
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf) {
ptrdiff_t consume(byte_span buf) override {
auto val = int32_t{0};
auto str = std::string_view{reinterpret_cast<char*>(buf.data()),
buf.size()};
......@@ -114,6 +115,7 @@ public:
return static_cast<ptrdiff_t>(buf.size());
}
net::message_oriented::lower_layer* down = nullptr;
size_t received_messages = 0;
adapter_ptr adapter_;
resource_type output_;
......@@ -130,6 +132,10 @@ struct fixture : test_coordinator_fixture<> {
return mm.mpx().poll_once(false);
}
auto mpx() {
return mm.mpx_ptr();
}
net::middleman mm;
};
......@@ -157,10 +163,10 @@ SCENARIO("publisher adapters suspend reads if the buffer becomes full") {
}};
if (auto err = nonblocking(fd2, true))
FAIL("nonblocking(fd2) returned an error: " << err);
auto mgr = net::make_socket_manager<app_t, net::length_prefix_framing,
net::stream_transport>(fd2,
mm.mpx_ptr(),
std::move(wr));
auto app = app_t::make(std::move(wr));
auto framing = net::length_prefix_framing::make(std::move(app));
auto transport = net::stream_transport::make(fd2, std::move(framing));
auto mgr = net::socket_manager::make(mpx(), fd2, std::move(transport));
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the actor receives all items from the writer (socket)") {
......
-----BEGIN CERTIFICATE-----
MIIDmzCCAoOgAwIBAgIJAPLZ3e3WR0LLMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTERMA8GA1UEBwwIQmVya2VsZXkxIzAhBgNVBAoM
GkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRAwDgYDVQQDDAdmb28uYmFyMB4X
DTE3MDQyMTIzMjM0OFoXDTQyMDQyMTIzMjM0OFowZDELMAkGA1UEBhMCVVMxCzAJ
BgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEjMCEGA1UECgwaQUNNRSBTaWdu
aW5nIEF1dGhvcml0eSBJbmMxEDAOBgNVBAMMB2Zvby5iYXIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC6ah79JvrN3LtcPzc9bX5THdzfidWncSmowotG
SZA3gcIhlsYD3P3RCaUR9g+f2Z/l0l7ciKgWetpNtN9hRBbg5/9tFzSpCb/Y0SSG
mwtHHovEqN2MWV+Od/MUcYSlL6MmPjSDc8Ls5NSniTr9OBE9J1jm72AsuzHasjPQ
D84TlWeTSs0HW3H5VxDb15xWYFnmgBo0JylDWj0+VWI+G41Xr7Ubu9699lWSFYF9
FCtdjzM5e1CGZOMvqUbUBus38BhUAdQ4fE7Dwnn8seKh+7HpJ70omIgqG87e4DBo
HbnMAkZaekk8+LBl0Hfu8c66Utw9mNoMIlFf/AMlJyLDIpNxAgMBAAGjUDBOMB0G
A1UdDgQWBBRc6Cbyshtny6jFWZtd/cEUUfMQ3DAfBgNVHSMEGDAWgBRc6Cbyshtn
y6jFWZtd/cEUUfMQ3DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCY
numHau9XYH5h4R2CoMdnKPMGk6V7UZZdbidLLcE4roQrYhnBdyhT69b/ySJK2Ee4
mt8T+E0wcg3k8Pr3aJEJA8eYYaJTqZvvv+TwuMBPjmE2rYSIpgMZv2tRD3XWMaQu
duLbwkclfejQHDD26xNXsxuU+WNB5kuvtNAg0oKFyFdNKElLQEcjyYzfxmCF4YX5
WmElijr1Tzuzd59rWPqC/tVIsh42vQ+P6g8Y1PDmo8eTUFveZ+wcr/eEPW6IOMrg
OW7tATcrgzNuXZ1umiuGgAPuIVqPfr9ssZHBqi9UOK9L/8MQrnOxecNUpPohcTFR
vq+Zqu15QV9T4BVWKHv0
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDOjCCAiICCQDz7oMOR7Wm7jANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F
IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0
MjEyMzI2MzhaGA80NzU1MDMxOTIzMjYzOFowWDELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl
MRIwEAYDVQQDDAkxLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDHobccAQQqbZANdOdx852W/nUzGcwpurOi8zbh9yCxMwnFMogW9AsqKEnd
sypV6Ah/cIz45PAgCdEg+1pc2DG7+E0+QlV4ChNwCDuk+FSWB6pqMTCdZcLeIwlA
GPp6Ow9v40dW7IFpDetFKXEo6kqEzR5P58Q0a6KpCtpsSMqhk57Py83wB9gPA1vp
s77kN7D5CI3oay86TA5j5nfFMT1X/77Hs24csW6CLnW/OD4f1RK79UgPd/kpPKQ1
jNq+hsR7NZTcfrAF1hcfScxnKaznO7WopSt1k75NqLdnSN1GIci2GpiXYKtXZ9l5
TErv2Oucpw/u+a/wjKlXjrgLL9lfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKuW
yKA2uuiNc9MKU+yVbNaP8kPaMb/wMvVaFG8FFFpCTZ0MFMLsqRpeqtj7gMK/gaJC
CQm4EyadjzfWFYDLkHzm6b7gI8digvvhjr/C2RJ5Qxr2P0iFP1buGq0CqnF20XgQ
Q+ecS43CZ77CfKfS6ZLPmAZMAwgFLImVyo5mkaTECo3+9oCnjDYBapvXLJqCJRhk
NosoTmGCV0HecWN4l38ojnXd44aSktQIND9iCLus3S6++nFnX5DHGZiv6/SnSO/6
+Op7nV0A6zKVcMOYQ0SGZPD8UQs5wDJgrR9LY29Ox5QBwu/5NqyvNSrMQaTop5vb
wkMInaq5lLxEYQDSLBc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDOjCCAiICCQDz7oMOR7Wm7TANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F
IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0
MjEyMzI2MzNaGA80NzU1MDMxOTIzMjYzM1owWDELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl
MRIwEAYDVQQDDAkyLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDG9fAvW9qnhjGRmLpA++RvOHaesu7NiUQvxf2F6gF2rLJV0/+DSA/PztEv
1WJaGhgJSaEqUjaHk3HY2EKlbGXEPh1mxqgPZD5plGlu4ddTwutxCxxQiFIBH+3N
MYRjJvDN7ozJoi4uRiK0QQdDWAqWJs5hMOJqeWd6MCgmVXSP6pj5/omGROktbHzD
9jJhAW9fnYFg6k+7cGN5kLmjqqnGhJkNtgom6uW9j73S9OpU/9Er2aZme6/PrujI
qYFBV81TJK2vmonWUITxfQjk9JVJYhBdHamGTxUqVBbuRcbAqdImV9yx4LoGh55u
L6xnsW4i0n1o1k+bh03NgwPz12O3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJmN
yCdInFIeEwomE6m+Su82BWBzkztOfMG9iRE+1aGuC8EQ8kju5NNMmWQcuKetNh0s
hJVdY6LXh27O0ZUllhQ/ig9c+dYFh6AHoZU7WjiNKIyWuyl4IAOkQ4IEdsBvst+l
0rafcdJjUpqNOMWeyg6x1s+gUD5o+ZLCZGCdkCW3fZbKgF52L+vmsSRiJg2JkYZW
8BPNNsroHZw2UXnLvRqUXCMf1hnOrlx/B0a0Q46hD4NQvl+OzlKaxfR2L2USmJ8M
XZvT6+i8fWvkGv18iunm23Yu+8Zf08wTXnbqXvmMda5upAYLmwD0YKIVYC3ycihh
mkYCYI6PVeH63a2/zxw=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAx6G3HAEEKm2QDXTncfOdlv51MxnMKbqzovM24fcgsTMJxTKI
FvQLKihJ3bMqVegIf3CM+OTwIAnRIPtaXNgxu/hNPkJVeAoTcAg7pPhUlgeqajEw
nWXC3iMJQBj6ejsPb+NHVuyBaQ3rRSlxKOpKhM0eT+fENGuiqQrabEjKoZOez8vN
8AfYDwNb6bO+5Dew+QiN6GsvOkwOY+Z3xTE9V/++x7NuHLFugi51vzg+H9USu/VI
D3f5KTykNYzavobEezWU3H6wBdYXH0nMZyms5zu1qKUrdZO+Tai3Z0jdRiHIthqY
l2CrV2fZeUxK79jrnKcP7vmv8IypV464Cy/ZXwIDAQABAoIBAC0Y7jmoTR2clJ9F
modWhnI215kMqd9/atdT5EEVx8/f/MQMj0vII8GJSm6H6/duLIVFksMjTM+gCBtQ
TPCOcmXJSQHYkGBGvm9fnMG+y7T81FWa+SWFeIkgFxXgzqzQLMOU72fGk9F8sHp2
Szb3/o+TmtZoQB2rdxqC9ibiJsxrG5IBVKkzlSPv3POkPXwSb1HcETqrTwefuioj
WMuMrqtm5Y3HddJ5l4JEF5VA3KrsfXWl3JLHH0UViemVahiNjXQAVTKAXIL1PHAV
J2MCEvlpA7sIgXREbmvPvZUTkt3pIqhVjZVJ7tHiSnSecqNTbuxcocnhKhZrHNtC
v2zYKHkCgYEA6cAIhz0qOGDycZ1lf9RSWw0RO1hO8frATMQNVoFVuJJCVL22u96u
0FvJ0JGyYbjthULnlOKyRe7DUL5HRLVS4D7vvKCrgwDmsJp1VFxMdASUdaBfq6aX
oKLUW4q7kC2lQcmK/PVRYwp2GQSx8bodWe+DtXUY/GcN03znY8mhSB0CgYEA2qJK
1GSZsm6kFbDek3BiMMHfO+X819owB2FmXiH+GQckyIZu9xA3HWrkOWTqwglEvzfO
qzFF96E9iEEtseAxhcM8gPvfFuXiUj9t2nH/7SzMnVGikhtYi0p6jrgHmscc4NBx
AOUA15kYEFOGqpZfl2uuKqgHidrHdGkJzzSUBqsCgYAVCjb6TVQejQNlnKBFOExN
a8iwScuZVlO21TLKJYwct/WGgSkQkgO0N37b6jFfQHEIvLPxn9IiH1KvUuFBWvzh
uGiF1wR5HzykitKizEgJbVwbllrmLXGagO2Sa9NkL+efG1AKYt53hrqIl/aYZoM7
1CZL0AV2uqPw9F4zijOdNQKBgH1WmvWGMsKjQTgaLI9z1ybCjjqlj70jHXOtt+Tx
Md2hRcobn5PN3PrlY68vlpHkhF/nG3jzB3x+GGt7ijm2IE3h7la3jl5vLb8fE9gu
kJykmSz7Nurx+GHqMbaN8/Ycfga4GIB9yGzRHIWHjOVQzb5eAfv8Vk4GeV/YM8Jx
Dwd/AoGAILn8pVC9dIFac2BDOFU5y9ZvMmZAvwRxh9vEWewNvkzg27vdYc+rCHNm
I7H0S/RqfqVeo0ApE5PQ8Sll6RvxN/mbSQo9YeCDGQ1r1rNe4Vs12GAYXAbE4ipf
BTdqMbieumB/zL97iK5baHUFEJ4VRtLQhh/SOXgew/BF8ccpilI=
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,4FEF2D042C3D3463
S3uHh5KH2Vsxzhw+B8zKsZNUG7CjMAvYfZwUptDnCms2dqxdfyzioxiRVNTN2MBb
nFxMKZayl5Xjr+04LhUpCAKdmvTfqGmWRFVSa8ol1hvqN/Vmg3a11B+3MD9pHXKu
ipgHMDTmE34DsDun6cXZ8W2k9ZSSJ20Y7qhcb4lTYUZuWq+94+Bup684COZytbyG
Eb6tOvVs/N6KeFhvd4vBL4YgTR3aY/vu1cHQQJeHvSYFtGKTKr7qpsfFfDFB7ExM
bqt2qbiUq1s+uZ0hlDjs+/TCZpC9Wuqpl9m05FrZhboubBRK9pw3wRVNvrpfwunH
2/E4Ta8/YaxypUwtk4saiPofKcJwvYx3Te38OHKKXgVKlein1p1e6eJNZt3qJaPd
BEBAR11iWstE+ghPS2WmrwT17cDqNA8wG5CRM9unYVWRMtO8Nl98OYars0vAXOgd
TI8bHDx6OHm2sJnkc56HAfu6hMdTbKzZXsWyOqLXUU79ioNmTvbMQUO0F+0VKDrv
3LikXewv69aOhhk+M96ng8KpWUW4z2kbDReiFuWQfjtiFNyEdS9wQIElpq5gU98r
EAeZoT6s8O0fkE/tt374ZJREEe9YEkMFcv06c96GPkMKnScncOFKRa1w0AblGxOM
vjICMLIk3CesFNWNzJk4MQu5ZVerJsz27UFocjNUvT6iM10rDCeU55W71bIPWRSb
SZL3JUi1gOeORrv3jAEamOd6YRlvPRYQjpDENg/qnx+bdXp5S3qHeHFSkRRHBCfy
UcYUba5hZJVDyBJXC4dkc2Ftcna149PZP1aKe9r//ZGa25PVZl1Mg6HhLROSJrEN
D2lqPMYcBDuSiZ6QK5ZHh2gsXJPktDgMhw4ZNIfIIqIpMDJ7rV4tQXXwB3d+iZte
/Ib/y1Mn8fZOo448nBqagpNFvU70eg7WkA4rzgiS3VzFYosP3g48PlZ07bEZ0awy
9lMGE0OKJv6x8EkVY0xHDxujzD5WVFEkanpGo8bWl1ofBrsnnDsFrvvRkDRsQbrG
WZkRqn7zLaGKOUirfKjfktajvPOAKMsvbl9KXAfp88sOtTZuYppCivZ/2qnzQ8xg
9/fuGfpAPXYfUu44+7dhrbvxJzrPp+GQRLUb1e3KA2MLgVak+yV1TIKdDGYek5vM
xDEmLLgOQqsE/OAcdXD2b/qIVYEmFPdut6N9K0m65AoqrN5nY6r0BzuC7usCgxnu
K6Z7Y2L6Ax4DjnKt21RRcjIVLLpea7KXnMiTogCYJ7+zmD82fE3i1xCDQs7R9b4T
tZhnkGfQS2RjOp3WaiV5rcmOKXDK3kYE+ezD0wbiVFDgB2jNdR7duOFqzW/WuPac
hsIPYn7VmjLB3W4p7PBf7OQ1fIiQ6IofeMvu8I8IOVRfC7eYDCJ0QKsHI8uA6j9T
vOCfjsZcpjygtN1Xy3uXIEZUVJJq/DF5t2Lxn95wDG9VfstJQR3KuDbPwYAWOYCh
W6gYu6HftIr3OlIYKurtSUFM4+iargMUaBVOUQ4XWLN8LzoyHNL3fsiGKn1afBV/
J3Qaqrn/YBGCAc2SOUCRl3F8yfWjmidXy6rQIzrjpF8aX2QSflB+fw==
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxvXwL1vap4YxkZi6QPvkbzh2nrLuzYlEL8X9heoBdqyyVdP/
g0gPz87RL9ViWhoYCUmhKlI2h5Nx2NhCpWxlxD4dZsaoD2Q+aZRpbuHXU8LrcQsc
UIhSAR/tzTGEYybwze6MyaIuLkYitEEHQ1gKlibOYTDianlnejAoJlV0j+qY+f6J
hkTpLWx8w/YyYQFvX52BYOpPu3BjeZC5o6qpxoSZDbYKJurlvY+90vTqVP/RK9mm
Znuvz67oyKmBQVfNUyStr5qJ1lCE8X0I5PSVSWIQXR2phk8VKlQW7kXGwKnSJlfc
seC6Boeebi+sZ7FuItJ9aNZPm4dNzYMD89djtwIDAQABAoIBAQDDaWquGRl40GR/
C/JjQQPr+RkIZdYGKXu/MEcA8ATf+l5tzfp3hp+BCzCKOpqOxHI3LQoN9xF3t2lq
AX3z27NYO2nFN/h4pYxnRk0Hiulia1+zd6YnsrxYPnPhxXCxsd1xZYsBvzh8WoZb
ZEMt8Zr0PskUzF6VFQh9Ci9k9ym07ooo/KqP4wjXsm/JK1ueOCTpRtabrBI1icrV
iTaw1JEGqlTAQ92vg3pXqSG5yy69Krt7miZZtiOA5mJ90VrHtlNSgp31AOcVv/Ve
/LMIwJp9EzTN+4ipT7AKPeJAoeVqpFjQk+2cW44zJ7xyzw73pTs5ErxkEIhQOp4M
ak2iMg4BAoGBAOivDZSaOcTxEB3oKxYvN/jL9eU2Io9wdZwAZdYQpkgc8lkM9elW
2rbHIwifkDxQnZbl3rXM8xmjA4c5PSCUYdPnLvx6nsUJrWTG0RjakHRliSLthNEC
LpL9MR1aQblyz1D/ulWTFOCNvHU7m3XI3RVJEQWu3qQ5pCndzT56wXjnAoGBANrl
zKvR9o2SONU8SDIcMzXrO2647Z8yXn4Kz1WhWojhRQQ1V3VOLm8gBwv8bPtc7LmE
MSX5MIcxRoHu7D98d53hd+K/ZGYV2h/638qaIEgZDf2oa8QylBgvoGljoy1DH8nN
KKOgksqWK0AAEkP0+S4IFugTxHVanw8JUkV0gVSxAoGBANIRUGJrxmHt/M3zUArs
QE0G3o28DQGQ1y0rEsVrLKQINid9UvoBpt3C9PcRD2fUpCGakDFzwbnQeRv46h3i
uFtV6Q6aKYLcFMXZ1ObqU+Yx0NhOtUz4+lFL8q58UL/7Tf3jkjc13XBJpe31DYoN
+MMBvzNxR6HeRD5j96tDqi3bAoGAT57SqZS/l5MeNQGuSPvU7MHZZlbBp+xMTpBk
BgOgyLUXw4Ybf8GmRiliJsv0YCHWwUwCDIvtSN91hAGB0T3WzIiccM+pFzDPnF5G
VI1nPJJQcnl2aXD0SS/ZqzvguK/3uhFzvMDFZAbnSGo+OpW6pTGwE05NYVpLDM8Z
K8ZK3KECgYEApNoI5Mr5tmtjq4sbZrgQq6cMlfkIj9gUubOzFCryUb6NaB38Xqkp
2N3/jqdkR+5ZiKOYhsYj+Iy6U3jyqiEl9VySYTfEIfP/ky1CD0a8/EVC9HR4iG8J
im6G7/osaSBYAZctryLqVJXObTelgEy/EFwW9jW8HVph/G+ljmHOmuQ=
-----END RSA PRIVATE KEY-----
......@@ -2,9 +2,9 @@
// 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
#define CAF_SUITE net.ssl.transport
#include "caf/net/openssl_transport.hpp"
#include "caf/net/ssl/transport.hpp"
#include "net-test.hpp"
#include "pem.hpp"
......@@ -14,11 +14,9 @@
#include "caf/byte_buffer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/ssl/context.hpp"
#include "caf/net/stream_socket.hpp"
#include <filesystem>
#include <random>
using namespace caf;
using namespace caf::net;
......@@ -31,28 +29,6 @@ struct fixture {
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);
}
template <class SocketPair>
......@@ -64,122 +40,113 @@ struct fixture {
FAIL("allow_sigpipe failed: " << err);
return pair;
}
~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 {
class mock_application : public net::stream_oriented::upper_layer {
public:
using input_tag = tag::stream_oriented;
explicit dummy_app(std::shared_ptr<bool> done, byte_buffer_ptr recv_buf)
explicit mock_application(std::shared_ptr<bool> done,
byte_buffer_ptr recv_buf)
: done_(std::move(done)), recv_buf_(std::move(recv_buf)) {
// nop
}
~dummy_app() {
static auto make(std::shared_ptr<bool> done, byte_buffer_ptr recv_buf) {
return std::make_unique<mock_application>(std::move(done),
std::move(recv_buf));
}
~mock_application() {
*done_ = true;
}
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
error init(socket_manager*, net::stream_oriented::lower_layer* down,
const settings&) override {
MESSAGE("initialize dummy app");
parent->configure_read(receive_policy::exactly(4));
parent->begin_output();
auto& buf = parent->output_buffer();
down_ = down;
down->configure_read(receive_policy::exactly(4));
down->begin_output();
auto& buf = down->output_buffer();
caf::binary_serializer out{nullptr, buf};
static_cast<void>(out.apply(10));
parent->end_output();
static_cast<void>(out.apply(int32_t{10}));
down->end_output();
return none;
}
template <class ParentPtr>
bool prepare_send(ParentPtr) {
bool prepare_send() override {
return true;
}
template <class ParentPtr>
bool done_sending(ParentPtr) {
bool done_sending() override {
return true;
}
template <class ParentPtr>
void continue_reading(ParentPtr) {
void continue_reading() override {
// nop
}
template <class ParentPtr>
size_t consume(ParentPtr down, const_byte_span data, const_byte_span) {
ptrdiff_t consume(byte_span data, byte_span) override {
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();
down_->begin_output();
auto& out = down_->output_buffer();
out.insert(out.end(), data.begin(), data.end());
down->end_output();
down_->end_output();
return static_cast<ptrdiff_t>(data.size());
}
template <class ParentPtr>
void abort(ParentPtr, const error& reason) {
void abort(const error& reason) override {
MESSAGE("dummy_app::abort called: " << reason);
*done_ = true;
}
private:
net::stream_oriented::lower_layer* down_ = nullptr;
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;
void dummy_tls_server(stream_socket fd, const char* cert_file,
const char* key_file) {
namespace ssl = caf::net::ssl;
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();
auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
if (!ctx.use_certificate_from_file(cert_file, ssl::format::pem)) {
std::cerr << "*** failed to load certificate_file: "
<< ctx.last_error_string() << '\n';
return;
}
if (auto err = ssl::private_key_pem_file(ctx, key_file)) {
std::cerr << "*** private_key_pem_file failed: " << ssl::fetch_error_str();
if (!ctx.use_private_key_from_file(key_file, ssl::format::pem)) {
std::cerr << "*** failed to load private key file: "
<< ctx.last_error_string() << '\n';
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();
auto conn = unbox(ctx.new_connection(fd));
if (auto ret = conn.accept(); ret <= 0) {
std::cerr << "*** accept failed: " << conn.last_error_string(ret) << '\n';
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);
if (auto ret = conn.read(buf); ret <= 0) {
std::cerr << "*** read failed: " << conn.last_error_string(ret) << '\n';
return;
}
if (auto ret = conn.write(buf); ret <= 0) {
std::cerr << "*** write failed: " << conn.last_error_string(ret) << '\n';
return;
}
}
// Graceful shutdown.
f.notify_close();
conn.close();
}
// Simulates a remote SSL client.
......@@ -188,52 +155,54 @@ void dummy_tls_client(stream_socket fd) {
// 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);
auto ctx = unbox(ssl::context::make_client(ssl::tls::any));
auto conn = unbox(ctx.new_connection(fd));
if (auto ret = conn.connect(); ret <= 0) {
std::cerr << "*** connect failed: " << conn.last_error_string(ret) << '\n';
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);
if (auto ret = conn.read(buf); ret <= 0) {
std::cerr << "*** read failed: " << conn.last_error_string(ret) << '\n';
return;
}
if (auto ret = conn.write(buf); ret <= 0) {
std::cerr << "*** write failed: " << conn.last_error_string(ret) << '\n';
return;
}
}
// Graceful shutdown.
f.notify_close();
conn.close();
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("openssl::async_connect performs the client handshake") {
SCENARIO("ssl::transport::make_client performs the client handshake") {
GIVEN("a connection to a TLS server") {
auto [serv_fd, client_fd] = no_sigpipe(unbox(make_stream_socket_pair()));
auto [server_fd, client_fd] = no_sigpipe(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>;
std::thread server{dummy_tls_server, server_fd, cert_1_pem_path,
key_1_pem_path};
WHEN("connecting as a client to an SSL server") {
THEN("CAF transparently calls SSL_connect") {
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto ctx = unbox(ssl::context::make_client(ssl::tls::any));
auto conn = unbox(ctx.new_connection(client_fd));
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);
auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_client(std::move(conn),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, client_fd,
std::move(transport));
mpx.init(mgr);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
......@@ -251,35 +220,30 @@ SCENARIO("openssl::async_connect performs the client handshake") {
}
}
SCENARIO("openssl::async_accept performs the server handshake") {
SCENARIO("ssl::transport::make_server performs the server handshake") {
GIVEN("a socket that is connected to a client") {
auto [serv_fd, client_fd] = no_sigpipe(unbox(make_stream_socket_pair()));
if (auto err = net::nonblocking(serv_fd, true))
auto [server_fd, client_fd] = no_sigpipe(unbox(make_stream_socket_pair()));
if (auto err = net::nonblocking(server_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>;
WHEN("acting as the SSL server") {
THEN("CAF transparently calls SSL_accept") {
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto ctx = unbox(ssl::context::make_server(ssl::tls::any));
REQUIRE(ctx.use_certificate_from_file(cert_1_pem_path, //
ssl::format::pem));
REQUIRE(ctx.use_private_key_from_file(key_1_pem_path, //
ssl::format::pem));
auto conn = unbox(ctx.new_connection(server_fd));
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);
auto mock = mock_application::make(done, buf);
auto transport = ssl::transport::make_server(std::move(conn),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, server_fd,
std::move(transport));
mpx.init(mgr);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
......
......@@ -21,7 +21,6 @@
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
......@@ -40,7 +39,7 @@ struct fixture : test_coordinator_fixture<> {
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
REQUIRE_EQ(mpx.num_socket_managers(), 1u);
auto sockets = unbox(make_stream_socket_pair());
auto sockets = unbox(net::make_stream_socket_pair());
send_socket_guard.reset(sockets.first);
recv_socket_guard.reset(sockets.second);
if (auto err = nonblocking(recv_socket_guard.socket(), true))
......@@ -52,69 +51,63 @@ struct fixture : test_coordinator_fixture<> {
}
settings config;
multiplexer mpx;
net::multiplexer mpx;
byte_buffer recv_buf;
socket_guard<stream_socket> send_socket_guard;
socket_guard<stream_socket> recv_socket_guard;
net::socket_guard<net::stream_socket> send_socket_guard;
net::socket_guard<net::stream_socket> recv_socket_guard;
byte_buffer_ptr shared_recv_buf;
byte_buffer_ptr shared_send_buf;
};
class dummy_application {
class mock_application : public net::stream_oriented::upper_layer {
public:
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
using input_tag = tag::stream_oriented;
explicit mock_application(byte_buffer_ptr recv_buf, byte_buffer_ptr send_buf)
: recv_buf_(std::move(recv_buf)), send_buf_(std::move(send_buf)) {
// nop
}
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
};
static auto make(byte_buffer_ptr recv_buf, byte_buffer_ptr send_buf) {
return std::make_unique<mock_application>(std::move(recv_buf),
std::move(send_buf));
}
~dummy_application() = default;
net::stream_oriented::lower_layer* down;
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
parent->configure_read(receive_policy::exactly(hello_manager.size()));
error init(net::socket_manager*, net::stream_oriented::lower_layer* down_ptr,
const settings&) override {
down = down_ptr;
down->configure_read(net::receive_policy::exactly(hello_manager.size()));
return none;
}
template <class ParentPtr>
bool prepare_send(ParentPtr parent) {
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;
void abort(const error& reason) override {
FAIL("abort called: " << CAF_ARG(reason));
}
template <class ParentPtr>
bool done_sending(ParentPtr) {
MESSAGE("done_sending called");
return true;
ptrdiff_t consume(byte_span data, byte_span) override {
recv_buf_->clear();
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
MESSAGE("Received " << recv_buf_->size() << " bytes in mock_application");
return static_cast<ptrdiff_t>(recv_buf_->size());
}
template <class ParentPtr>
void continue_reading(ParentPtr) {
void continue_reading() override {
FAIL("continue_reading called");
}
template <class ParentPtr>
size_t consume(ParentPtr, const_byte_span data, const_byte_span) {
recv_buf_->clear();
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
MESSAGE("Received " << recv_buf_->size() << " bytes in dummy_application");
return recv_buf_->size();
}
static void handle_error(sec code) {
FAIL("handle_error called with " << CAF_ARG(code));
bool prepare_send() override {
MESSAGE("prepare_send called");
auto& buf = down->output_buffer();
auto data = as_bytes(make_span(hello_manager));
buf.insert(buf.end(), data.begin(), data.end());
return true;
}
template <class ParentPtr>
static void abort(ParentPtr, const error& reason) {
FAIL("abort called with " << CAF_ARG(reason));
bool done_sending() override {
MESSAGE("done_sending called");
return true;
}
private:
......@@ -127,8 +120,11 @@ private:
BEGIN_FIXTURE_SCOPE(fixture)
CAF_TEST(receive) {
auto mgr = make_socket_manager<dummy_application, stream_transport>(
recv_socket_guard.release(), &mpx, shared_recv_buf, shared_send_buf);
auto mock = mock_application::make(shared_recv_buf, shared_send_buf);
auto transport = net::stream_transport::make(recv_socket_guard.get(),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(),
std::move(transport));
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
......@@ -143,8 +139,11 @@ CAF_TEST(receive) {
}
CAF_TEST(send) {
auto mgr = make_socket_manager<dummy_application, stream_transport>(
recv_socket_guard.release(), &mpx, shared_recv_buf, shared_send_buf);
auto mock = mock_application::make(shared_recv_buf, shared_send_buf);
auto transport = net::stream_transport::make(recv_socket_guard.get(),
std::move(mock));
auto mgr = net::socket_manager::make(&mpx, recv_socket_guard.release(),
std::move(transport));
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
......
......@@ -27,18 +27,26 @@ using svec = std::vector<std::string>;
using string_consumer = typed_actor<result<void>(std::string)>;
struct app_t {
using input_tag = tag::stream_oriented;
class app_t : public net::stream_oriented::upper_layer {
public:
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<string_consumer>(down);
static auto make() {
return std::make_unique<app_t>();
}
static auto make(actor hdl) {
return std::make_unique<app_t>(std::move(hdl));
}
error init(net::socket_manager* mgr, net::stream_oriented::lower_layer* down,
const settings&) override {
this->down = down;
self = mgr->make_actor_shell<string_consumer>();
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
......@@ -51,41 +59,32 @@ struct app_t {
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;
bool prepare_send() override {
while (self->consume_message())
; // repeat
return !self->terminated();
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return self->try_block_mailbox();
void continue_reading() override {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
CAF_FAIL("continue_reading called");
bool done_sending() override {
return self->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf, byte_span) {
ptrdiff_t consume(byte_span buf, byte_span) override {
// Seek newline character.
constexpr auto nl = std::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), {});
auto sub_res = consume(buf.subspan(1), {});
return sub_res >= 0 ? sub_res + 1 : sub_res;
}
// Deserialize config value from received line.
......@@ -96,21 +95,19 @@ struct app_t {
if (auto parsed_res = config_value::parse(line)) {
val = std::move(*parsed_res);
} else {
down->abort_reason(std::move(parsed_res.error()));
return -1;
}
// Deserialize message from received dictionary.
config_value_reader reader{&val};
caf::message msg;
if (!reader.apply(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 {
[this](int32_t value) mutable {
++received_responses;
// Respond with the value as string.
auto str_response = std::to_string(value);
......@@ -121,16 +118,19 @@ struct app_t {
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
},
[down](error& err) mutable { down->abort_reason(std::move(err)); });
[this](error& err) mutable { self->quit(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, {});
auto sub_res = consume(sub_buf, {});
return sub_res >= 0 ? num_bytes + sub_res : sub_res;
}
return 0;
}
// Pointer to the layer below.
net::stream_oriented::lower_layer* down;
// Handle to the worker-under-test.
actor worker;
......@@ -201,17 +201,19 @@ constexpr std::string_view input = R"__(
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);
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make();
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
auto hdl = app.self.as_actor();
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"}));
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) {
......@@ -220,14 +222,15 @@ CAF_TEST(actor shells can send requests and receive responses) {
[](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);
auto fd = testee_socket_guard.release();
auto app_uptr = app_t::make(worker);
auto app = app_uptr.get();
auto transport = net::stream_transport::make(fd, std::move(app_uptr));
auto mgr = net::socket_manager::make(&mpx, fd, std::move(transport));
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(); });
run_while([&] { return app->consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
std::string_view expected_response = "246\n";
run_while([&] { return recv_buf.size() < expected_response.size(); });
......
......@@ -14,42 +14,46 @@ namespace {
using svec = std::vector<std::string>;
struct app_t {
class app_t : public net::web_socket::upper_layer {
public:
static auto make() {
return std::make_unique<app_t>();
}
std::string text_input;
caf::byte_buffer binary_input;
settings cfg;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings& init_cfg) {
error init(net::socket_manager*, net::web_socket::lower_layer*,
const settings& init_cfg) override {
cfg = init_cfg;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
bool prepare_send() override {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void continue_reading() override {
// nop
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, std::string_view text) {
ptrdiff_t consume_text(std::string_view text) override {
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) {
ptrdiff_t consume_binary(byte_span bytes) override {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
......@@ -83,32 +87,33 @@ auto key_to_bytes() {
}
auto make_handshake() {
net::web_socket::handshake result;
result.endpoint("/chat");
result.host("server.example.com");
result.key(key_to_bytes());
result.origin("http://example.com");
result.protocols("chat, superchat");
auto result = std::make_unique<net::web_socket::handshake>();
result->endpoint("/chat");
result->host("server.example.com");
result->key(key_to_bytes());
result->origin("http://example.com");
result->protocols("chat, superchat");
return result;
}
using mock_client_type = mock_stream_transport<net::web_socket::client<app_t>>;
} // namespace
SCENARIO("the client performs the WebSocket handshake on startup") {
GIVEN("valid WebSocket handshake data") {
WHEN("starting a WebSocket client") {
mock_client_type client{make_handshake()};
auto app = app_t::make();
auto ws = net::web_socket::client::make(make_handshake(), std::move(app));
auto& ws_state = *ws;
auto uut = mock_stream_transport::make(std::move(ws));
THEN("the client sends its HTTP request when initializing it") {
CHECK_EQ(client.init(), error{});
CHECK_EQ(client.output_as_str(), http_request);
CHECK_EQ(uut->init(), error{});
CHECK_EQ(uut->output_as_str(), http_request);
}
AND("the client waits for the server handshake and validates it") {
client.push(http_response);
CHECK_EQ(client.handle_input(),
uut->push(http_response);
CHECK_EQ(uut->handle_input(),
static_cast<ptrdiff_t>(http_response.size()));
CHECK(client.upper_layer.handshake_complete());
CHECK(ws_state.handshake_completed());
}
}
}
......
......@@ -21,42 +21,46 @@ namespace {
using svec = std::vector<std::string>;
struct app_t {
class app_t : public net::web_socket::upper_layer {
public:
std::string text_input;
byte_buffer binary_input;
settings cfg;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings& init_cfg) {
static auto make() {
return std::make_unique<app_t>();
}
error init(net::socket_manager*, net::web_socket::lower_layer*,
const settings& init_cfg) override {
cfg = init_cfg;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
bool prepare_send() override {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
bool done_sending() override {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
void continue_reading() override {
// nop
}
void abort(const error& reason) override {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, std::string_view text) {
ptrdiff_t consume_text(std::string_view text) override {
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) {
ptrdiff_t consume_binary(byte_span bytes) override {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
......@@ -65,9 +69,12 @@ struct app_t {
struct fixture {
fixture() {
using namespace caf::net;
ws = std::addressof(transport.upper_layer);
app = std::addressof(ws->upper_layer());
if (auto err = transport.init())
auto app_ptr = app_t::make();
app = app_ptr.get();
auto ws_ptr = net::web_socket::server::make(std::move(app_ptr));
ws = ws_ptr.get();
transport = mock_stream_transport::make(std::move(ws_ptr));
if (auto err = transport->init())
CAF_FAIL("failed to initialize mock transport: " << err);
rng.seed(0xD3ADC0D3);
}
......@@ -90,7 +97,7 @@ struct fixture {
void push(uint8_t opcode, const_byte_span bytes) {
byte_buffer frame;
rfc6455_append(opcode, bytes, frame);
transport.push(frame);
transport->push(frame);
}
void push(const_byte_span bytes) {
......@@ -101,9 +108,9 @@ struct fixture {
push(detail::rfc6455::text_frame, as_bytes(make_span(str)));
}
mock_stream_transport<net::web_socket::server<app_t>> transport;
std::unique_ptr<mock_stream_transport> transport;
net::web_socket::server<app_t>* ws;
net::web_socket::server* ws;
app_t* app;
......@@ -130,15 +137,15 @@ constexpr std::string_view opening_handshake
CAF_TEST_FIXTURE_SCOPE(web_socket_tests, fixture)
CAF_TEST(applications receive handshake data via config) {
transport.push(opening_handshake);
transport->push(opening_handshake);
{
auto consumed = transport.handle_input();
auto consumed = transport->handle_input();
if (consumed < 0)
CAF_FAIL("error handling input: " << transport.abort_reason());
CAF_FAIL("transport->handle_input failed");
CHECK_EQ(consumed, static_cast<ptrdiff_t>(opening_handshake.size()));
}
CHECK_EQ(transport.input.size(), 0u);
CHECK_EQ(transport.unconsumed(), 0u);
CHECK_EQ(transport->input.size(), 0u);
CHECK_EQ(transport->unconsumed(), 0u);
CHECK(ws->handshake_complete());
CHECK_SETTING("web-socket.method", "GET");
CHECK_SETTING("web-socket.path", "/chat");
......@@ -157,11 +164,11 @@ CAF_TEST(applications receive handshake data via config) {
}
CAF_TEST(the server responds with an HTTP response on success) {
transport.push(opening_handshake);
CHECK_EQ(transport.handle_input(),
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CHECK(ws->handshake_complete());
CHECK_EQ(transport.output_as_str(),
CHECK_EQ(transport->output_as_str(),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
......@@ -177,14 +184,14 @@ CAF_TEST(handshakes may arrive in chunks) {
bufs.emplace_back(i, i + chunk_size);
i += chunk_size;
bufs.emplace_back(i, opening_handshake.end());
transport.push(bufs[0]);
CHECK_EQ(transport.handle_input(), 0u);
transport->push(bufs[0]);
CHECK_EQ(transport->handle_input(), 0u);
CHECK(!ws->handshake_complete());
transport.push(bufs[1]);
CHECK_EQ(transport.handle_input(), 0u);
transport->push(bufs[1]);
CHECK_EQ(transport->handle_input(), 0u);
CHECK(!ws->handshake_complete());
transport.push(bufs[2]);
CHECK_EQ(static_cast<size_t>(transport.handle_input()),
transport->push(bufs[2]);
CHECK_EQ(static_cast<size_t>(transport->handle_input()),
opening_handshake.size());
CHECK(ws->handshake_complete());
}
......@@ -194,19 +201,19 @@ CAF_TEST(data may follow the handshake immediately) {
byte_buffer buf{hs_bytes.begin(), hs_bytes.end()};
rfc6455_append("Hello WebSocket!\n"sv, buf);
rfc6455_append("Bye WebSocket!\n"sv, buf);
transport.push(buf);
CHECK_EQ(transport.handle_input(), static_cast<ptrdiff_t>(buf.size()));
transport->push(buf);
CHECK_EQ(transport->handle_input(), static_cast<ptrdiff_t>(buf.size()));
CHECK(ws->handshake_complete());
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST(data may arrive later) {
transport.push(opening_handshake);
CHECK_EQ(transport.handle_input(),
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CHECK(ws->handshake_complete());
push("Hello WebSocket!\nBye WebSocket!\n"sv);
transport.handle_input();
transport->handle_input();
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
......
#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";
#include <string_view>
// clang-format off
const char* ca_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/ca.pem";
const char* cert_1_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/cert_1.pem";
const char* cert_2_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/cert_2.pem";
const char* key_1_enc_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/key_1_enc.pem";
const char* key_1_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/key_1.pem";
const char* key_2_pem_path = "@CMAKE_CURRENT_SOURCE_DIR@/test/net/ssl/key_2.pem";
// clang-format on
......@@ -2,9 +2,9 @@
#include <string_view>
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;
extern const char* ca_pem_path;
extern const char* cert_1_pem_path;
extern const char* cert_2_pem_path;
extern const char* key_1_enc_pem_path;
extern const char* key_1_pem_path;
extern const char* key_2_pem_path;
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