Commit ad965456 authored by Dominik Charousset's avatar Dominik Charousset

Merge caf_net from the incubator

parents cfeb9105 c29a4903
# -- get header files for creating "proper" XCode projects ---------------------
file(GLOB_RECURSE CAF_NET_HEADERS "caf/*.hpp")
# -- add targets ---------------------------------------------------------------
caf_incubator_add_component(
net
DEPENDENCIES
PUBLIC
CAF::core
$<$<CXX_COMPILER_ID:MSVC>:ws2_32>
PRIVATE
CAF::internal
ENUM_TYPES
net.basp.connection_state
net.basp.ec
net.basp.message_type
net.http.method
net.http.status
net.operation
net.stream_transport_error
net.web_socket.status
HEADERS
${CAF_NET_HEADERS}
SOURCES
src/convert_ip_endpoint.cpp
src/datagram_socket.cpp
src/detail/rfc6455.cpp
src/header.cpp
src/host.cpp
src/ip.cpp
src/message_queue.cpp
src/multiplexer.cpp
src/net/abstract_actor_shell.cpp
src/net/actor_shell.cpp
src/net/http/header.cpp
src/net/http/method.cpp
src/net/http/status.cpp
src/net/http/v1.cpp
src/net/middleman.cpp
src/net/middleman_backend.cpp
src/net/packet_writer.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/worker.cpp
TEST_SOURCES
test/net-test.cpp
TEST_SUITES
accept_socket
convert_ip_endpoint
datagram_socket
detail.rfc6455
header
ip
multiplexer
net.actor_shell
net.consumer_adapter
net.http.server
net.length_prefix_framing
net.operation
net.producer_adapter
net.typed_actor_shell
net.web_socket.client
net.web_socket.handshake
net.web_socket.server
network_socket
pipe_socket
socket
socket_guard
stream_socket
stream_transport
tcp_sockets
udp_datagram_socket)
if(CAF_INC_ENABLE_TESTING AND TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
caf_incubator_add_test_suites(caf-net-test net.openssl_transport)
target_sources(caf-net-test PRIVATE test/net/openssl_transport_constants.cpp)
target_link_libraries(caf-net-test PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()
Protocol Layering
=================
Layering plays an important role in the design of the ``caf.net`` module. When
implementing a new protocol for ``caf.net``, this protocol should integrate
naturally with any number of other protocols. To enable this key property,
``caf.net`` establishes a set of conventions and abstract interfaces.
Concepts
--------
A *protocol layer* is a class that implements a single processing step in a
communication pipeline. Multiple layers are organized in a *protocol stack*.
Each layer may only communicate with its direct predecessor or successor in the
stack.
At the bottom of the protocol stack is usually a *transport layer*. For example,
a ``stream_transport`` manages a stream socket and provides access to input and
output buffers to the upper layer.
At the top of the protocol is an *application* that utilizes the lower layers
for communication via the network. Applications should only rely on the
*abstract interface type* when communicating with their lower layer. For
example, an application that processes a data stream should not implement
against a TCP socket interface directly. By programming against the abstract
interface types of ``caf.net``, users can instantiate an application with any
compatible protocol stack of their choosing. For example, a user may add extra
security by using application-level data encryption or combine a custom datagram
transport with protocol layers that establish ordering and reliability to
emulate a stream.
Abstract Interface Types
------------------------
By default, ``caf.net`` distinguishes between these abstract interface types:
* *stream*: A stream interface represents a sequence of Bytes, transmitted
reliable and in order.
* *datagram*: A datagram interface provides access to some basic transfer units
that may arrive out of order or not at all.
* *message*: A message interface provides access to high-level, structured data.
Messages consist of a header and a payload. A single message may span multiple
datagrams, for example.
Note that each interface type also depends on the *direction*, i.e., whether
talking to the upper or lower level. Incoming data always travels the protocol
stack *up*. Outgoing data always travels the protocol stack *down*.
..code-block:: C++
interface base [role: upper layer] {
/// Called whenever the underlying transport is ready to send, allowing the
/// upper layers to produce additional output data or use the event to read
/// from event queues, etc.
/// @returns `true` if the lower layers may proceed, `false` otherwise
/// (aborts execution).
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down);
/// Called whenever the underlying transport finished writing all buffered
/// data for output to query whether an upper layer still has pending events
/// or may produce output data on the next call to `prepare_send`.
/// @returns `true` if the underlying socket may get removed from the I/O
/// event loop, `false` otherwise.
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down);
/// When provided, the underlying transport calls this member function
/// before leaving `handle_read_event`. The primary use case for this
/// callback is flushing buffers.
template <class LowerLayerPtr>
[[optional]] void after_reading(LowerLayerPtr down);
}
interface base [role: lower layer] {
/// Returns whether the layer has output slots available.
bool can_send_more() const noexcept;
/// Returns the managed socket.
auto handle() const noexcept;
}
interface stream_oriented [role: upper layer] {
/// Called by the lower layer for cleaning up any state in case of an error.
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason);
/// Consumes bytes from the lower layer.
/// @param down Reference to the lower layer that received the data.
/// @param buffer Available bytes to read.
/// @param delta Bytes that arrived since last calling this function.
/// @returns The number of consumed bytes. May be zero if waiting for more
/// input or negative to signal an error.
/// @note When returning a negative value, clients should also call
/// `down.abort_reason(...)` with an appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer, byte_span delta);
}
interface stream_oriented [role: lower layer] {
/// Configures threshold for the next receive operations. Policies remain
/// active until calling this function again.
/// @warning Calling this function in `consume` invalidates both byte spans.
void configure_read(read_policy policy);
/// Prepares the layer for outgoing traffic, e.g., by allocating an output
/// buffer as necessary.
void begin_output();
/// Returns a reference to the output buffer. Users may only call this
/// function and write to the buffer between calling `begin_output()` and
/// `end_output()`.
byte_buffer& output_buffer();
/// Prepares written data for transfer, e.g., by flushing buffers or
/// registering sockets for write events.
void end_output();
/// Propagates an abort reason to the lower layers. After processing the
/// current read or write event, the lowest layer will call `abort` on its
/// upper layer.
void abort_reason(error reason);
/// Returns the last recent abort reason or `none` if no error occurred.
const error& abort_reason();
}
interface datagram_oriented [role: upper layer] {
/// Consumes a datagram from the lower layer.
/// @param down Reference to the lower layer that received the datagram.
/// @param buffer Payload of the received datagram.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
}
interface datagram_oriented [role: lower layer] {
/// Prepares the layer for an outgoing datagram, e.g., by allocating an
/// output buffer as necessary.
void begin_datagram();
/// Returns a reference to the buffer for assembling the current datagram.
/// Users may only call this function and write to the buffer between
/// calling `begin_datagram()` and `end_datagram()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& datagram_buffer();
/// Seals and prepares a datagram for transfer.
void end_datagram();
}
interface message_oriented [role: upper layer] {
/// Consumes a message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param buffer Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buffer);
}
interface message_oriented [role: lower layer] {
/// Prepares the layer for an outgoing message, e.g., by allocating an
/// output buffer as necessary.
void begin_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_message()` and `end_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& message_buffer();
/// Seals and prepares a message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_message();
}
interface mixed_message_oriented [role: upper layer] {
/// Consumes a binary message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param buffer Payload of the received message.
/// @returns The number of consumed bytes or a negative value to signal an
/// error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed bytes,
/// clients must also call `down.set_read_error(...)` with an
/// appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr down, byte_span buffer);
/// Consumes a text message from the lower layer.
/// @param down Reference to the lower layer that received the message.
/// @param text Payload of the received message. The encoding depends on the
/// application.
/// @returns The number of consumed characters or a negative value to signal
/// an error.
/// @note Discarded data is lost permanently.
/// @note When returning a negative value for the number of consumed
/// characters, clients must also call `down.set_read_error(...)` with
/// an appropriate error code.
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr down, string_view text);
}
interface mixed_message_oriented [role: lower layer] {
/// Prepares the layer for an outgoing binary message, e.g., by allocating
/// an output buffer as necessary.
void begin_binary_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_binary_message()` and `end_binary_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
byte_buffer& binary_message_buffer();
/// Seals and prepares a binary message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_binary_message();
/// Prepares the layer for an outgoing text message, e.g., by allocating
/// an output buffer as necessary.
void begin_text_message();
/// Returns a reference to the buffer for assembling the current message.
/// Users may only call this function and write to the buffer between
/// calling `begin_text_message()` and `end_text_message()`.
/// @note Lower layers may pre-fill the buffer, e.g., to prefix custom
/// headers.
std::vector<char>& text_message_buffer();
/// Seals and prepares a text message for transfer.
/// @note When returning `false`, clients must also call
/// `down.set_read_error(...)` with an appropriate error code.
template <class LowerLayerPtr>
bool end_text_message();
}
// 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/socket_sys_includes.hpp"
#include "caf/fwd.hpp"
namespace caf::detail {
void convert(const ip_endpoint& src, sockaddr_storage& dst);
error convert(const sockaddr_storage& src, ip_endpoint& dst);
} // namespace caf::detail
// 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::detail {
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&>()));
public:
static constexpr bool value = sfinae_result::value;
};
template <class T, class LowerLayerPtr>
constexpr bool has_after_reading_v = has_after_reading<T, LowerLayerPtr>::value;
} // namespace caf::detail
// 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/fwd.hpp"
#include "caf/net/fwd.hpp"
namespace caf::detail {
template <class T>
struct actor_shell_ptr_type_oracle;
template <>
struct actor_shell_ptr_type_oracle<actor> {
using type = net::actor_shell_ptr;
};
template <class... Sigs>
struct actor_shell_ptr_type_oracle<typed_actor<Sigs...>> {
using type = net::typed_actor_shell_ptr<Sigs...>;
};
template <class T>
using infer_actor_shell_ptr_type =
typename actor_shell_ptr_type_oracle<T>::type;
} // namespace caf::detail
// 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 <cstdio>
#include <cstdlib>
#include "caf/error.hpp"
#include "caf/sec.hpp"
/// Calls a C functions and returns an error if `var op rhs` returns `true`.
#define CAF_NET_SYSCALL(funname, var, op, rhs, expr) \
auto var = expr; \
if (var op rhs) \
return make_error(sec::network_syscall_failed, funname, \
last_socket_error_as_string())
/// Calls a C functions and calls exit() if `var op rhs` returns `true`.
#define CAF_NET_CRITICAL_SYSCALL(funname, var, op, rhs, expr) \
auto var = expr; \
if (var op rhs) { \
fprintf(stderr, "[FATAL] %s:%u: syscall failed: %s returned %s\n", \
__FILE__, __LINE__, funname, \
last_socket_error_as_string().c_str()); \
abort(); \
} \
static_cast<void>(0)
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include <cstdint>
#include <vector>
namespace caf::detail {
struct CAF_NET_EXPORT rfc6455 {
// -- member types -----------------------------------------------------------
using binary_buffer = std::vector<byte>;
struct header {
bool fin;
uint8_t opcode;
uint32_t mask_key;
uint64_t payload_len;
};
// -- constants --------------------------------------------------------------
static constexpr uint8_t continuation_frame = 0x00;
static constexpr uint8_t text_frame = 0x01;
static constexpr uint8_t binary_frame = 0x02;
static constexpr uint8_t connection_close = 0x08;
static constexpr uint8_t ping = 0x09;
static constexpr uint8_t pong = 0x0A;
// -- utility functions ------------------------------------------------------
static void mask_data(uint32_t key, span<char> data);
static void mask_data(uint32_t key, span<byte> data);
static void assemble_frame(uint32_t mask_key, span<const char> data,
binary_buffer& out);
static void assemble_frame(uint32_t mask_key, span<const byte> data,
binary_buffer& out);
static void assemble_frame(uint8_t opcode, uint32_t mask_key,
span<const byte> data, binary_buffer& out);
static ptrdiff_t decode_header(span<const byte> data, header& hdr);
};
} // namespace caf::detail
// 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/socket_sys_includes.hpp"
namespace caf::detail {
inline auto addr_of(sockaddr_in& what) -> decltype(what.sin_addr)& {
return what.sin_addr;
}
inline auto family_of(sockaddr_in& what) -> decltype(what.sin_family)& {
return what.sin_family;
}
inline auto port_of(sockaddr_in& what) -> decltype(what.sin_port)& {
return what.sin_port;
}
inline auto addr_of(sockaddr_in6& what) -> decltype(what.sin6_addr)& {
return what.sin6_addr;
}
inline auto family_of(sockaddr_in6& what) -> decltype(what.sin6_family)& {
return what.sin6_family;
}
inline auto port_of(sockaddr_in6& what) -> decltype(what.sin6_port)& {
return what.sin6_port;
}
} // namespace caf::detail
// 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/config.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
using setsockopt_ptr = const char*;
using getsockopt_ptr = char*;
using socket_send_ptr = const char*;
using socket_recv_ptr = char*;
using socket_size_type = int;
#else // CAF_WINDOWS
using setsockopt_ptr = const void*;
using getsockopt_ptr = void*;
using socket_send_ptr = const void*;
using socket_recv_ptr = void*;
using socket_size_type = unsigned;
#endif // CAF_WINDOWS
} // 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.
// This convenience header pulls in platform-specific headers for the C socket
// API. Do *not* include this header in other headers.
#pragma once
#include "caf/config.hpp"
// clang-format off
#ifdef CAF_WINDOWS
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif // CAF_WINDOWS
# ifndef NOMINMAX
# define NOMINMAX
# endif // NOMINMAX
# ifdef CAF_MINGW
# undef _WIN32_WINNT
# undef WINVER
# define _WIN32_WINNT WindowsVista
# define WINVER WindowsVista
# include <w32api.h>
# endif // CAF_MINGW
# include <windows.h>
# include <winsock2.h>
# include <ws2ipdef.h>
# include <ws2tcpip.h>
#else // CAF_WINDOWS
# include <sys/types.h>
# include <arpa/inet.h>
# include <cerrno>
# include <fcntl.h>
# include <netinet/in.h>
# include <netinet/ip.h>
# include <netinet/tcp.h>
# include <sys/socket.h>
# include <unistd.h>
# include <sys/uio.h>
#endif
// clang-format on
// 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/actor_traits.hpp"
#include "caf/callback.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/extend.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/local_actor.hpp"
#include "caf/mixin/requester.hpp"
#include "caf/mixin/sender.hpp"
#include "caf/net/fwd.hpp"
#include "caf/none.hpp"
#include "caf/policy/normal_messages.hpp"
namespace caf::net {
class CAF_NET_EXPORT abstract_actor_shell : public local_actor,
public non_blocking_actor_base {
public:
// -- member types -----------------------------------------------------------
using super = local_actor;
struct mailbox_policy {
using queue_type = intrusive::drr_queue<policy::normal_messages>;
using deficit_type = policy::normal_messages::deficit_type;
using mapped_type = policy::normal_messages::mapped_type;
using unique_pointer = policy::normal_messages::unique_pointer;
};
using mailbox_type = intrusive::fifo_inbox<mailbox_policy>;
using fallback_handler = unique_callback_ptr<result<message>(message&)>;
// -- constructors, destructors, and assignment operators --------------------
abstract_actor_shell(actor_config& cfg, socket_manager* owner);
~abstract_actor_shell() override;
// -- state modifiers --------------------------------------------------------
/// Detaches the shell from its owner and closes the mailbox.
void quit(error reason);
/// Overrides the default handler for unexpected messages.
template <class F>
void set_fallback(F f) {
fallback_ = make_type_erased_callback(std::move(f));
}
// -- mailbox access ---------------------------------------------------------
auto& mailbox() noexcept {
return mailbox_;
}
/// Dequeues and returns the next message from the mailbox or returns
/// `nullptr` if the mailbox is empty.
mailbox_element_ptr next_message();
/// Tries to put the mailbox into the `blocked` state, causing the next
/// enqueue to register the owning socket manager for write events.
bool try_block_mailbox();
// -- message processing -----------------------------------------------------
/// Dequeues and processes the next message from the mailbox.
/// @returns `true` if a message was dequeued and processed, `false` if the
/// mailbox was empty.
bool consume_message();
/// Adds a callback for a multiplexed response.
void add_multiplexed_response_handler(message_id response_id, behavior bhvr);
// -- overridden functions of abstract_actor ---------------------------------
using abstract_actor::enqueue;
bool enqueue(mailbox_element_ptr ptr, execution_unit* eu) override;
mailbox_element* peek_at_next_mailbox_element() override;
// -- overridden functions of local_actor ------------------------------------
void launch(execution_unit* eu, bool lazy, bool hide) override;
bool cleanup(error&& fail_state, execution_unit* host) override;
protected:
// Stores incoming actor messages.
mailbox_type mailbox_;
// Guards access to owner_.
std::mutex owner_mtx_;
// Points to the owning manager (nullptr after quit was called).
socket_manager* owner_;
// Handler for consuming messages from the mailbox.
behavior bhvr_;
// Handler for unexpected messages.
fallback_handler fallback_;
// Stores callbacks for multiplexed responses.
detail::unordered_flat_map<message_id, behavior> multiplexed_responses_;
};
} // 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/actor_proxy.hpp"
#include "caf/net/endpoint_manager.hpp"
namespace caf::net {
/// Implements a simple proxy forwarding all operations to a manager.
class actor_proxy_impl : public actor_proxy {
public:
using super = actor_proxy;
actor_proxy_impl(actor_config& cfg, endpoint_manager_ptr dst);
~actor_proxy_impl() override;
bool enqueue(mailbox_element_ptr what, execution_unit* context) override;
void kill_proxy(execution_unit* ctx, error rsn) override;
private:
endpoint_manager_ptr dst_;
};
} // 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/actor_traits.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/extend.hpp"
#include "caf/fwd.hpp"
#include "caf/mixin/requester.hpp"
#include "caf/mixin/sender.hpp"
#include "caf/net/abstract_actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/none.hpp"
namespace caf::net {
/// Enables socket managers to communicate with actors using dynamically typed
/// messaging.
class CAF_NET_EXPORT actor_shell
: public extend<abstract_actor_shell, actor_shell>::with<mixin::sender,
mixin::requester>,
public dynamically_typed_actor_base {
public:
// -- friends ----------------------------------------------------------------
friend class actor_shell_ptr;
// -- member types -----------------------------------------------------------
using super = extend<abstract_actor_shell,
actor_shell>::with<mixin::sender, mixin::requester>;
using signatures = none_t;
using behavior_type = behavior;
// -- constructors, destructors, and assignment operators --------------------
actor_shell(actor_config& cfg, socket_manager* owner);
~actor_shell() override;
// -- state modifiers --------------------------------------------------------
/// Overrides the callbacks for incoming messages.
template <class... Fs>
void set_behavior(Fs... fs) {
bhvr_ = behavior{std::move(fs)...};
}
// -- overridden functions of local_actor ------------------------------------
const char* name() const override;
};
/// An "owning" pointer to an actor shell in the sense that it calls `quit()` on
/// the shell when going out of scope.
class CAF_NET_EXPORT actor_shell_ptr {
public:
// -- friends ----------------------------------------------------------------
friend class socket_manager;
// -- member types -----------------------------------------------------------
using handle_type = actor;
using element_type = actor_shell;
// -- constructors, destructors, and assignment operators --------------------
constexpr actor_shell_ptr() noexcept {
// nop
}
constexpr actor_shell_ptr(std::nullptr_t) noexcept {
// nop
}
actor_shell_ptr(actor_shell_ptr&& other) noexcept = default;
actor_shell_ptr& operator=(actor_shell_ptr&& other) noexcept = default;
actor_shell_ptr(const actor_shell_ptr& other) = delete;
actor_shell_ptr& operator=(const actor_shell_ptr& other) = delete;
~actor_shell_ptr();
// -- smart pointer interface ------------------------------------------------
/// Returns an actor handle to the managed actor shell.
handle_type as_actor() const noexcept;
void detach(error reason);
element_type* get() const noexcept;
element_type* operator->() const noexcept {
return get();
}
element_type& operator*() const noexcept {
return *get();
}
bool operator!() const noexcept {
return !ptr_;
}
explicit operator bool() const noexcept {
return static_cast<bool>(ptr_);
}
private:
/// @pre `ptr != nullptr`
explicit actor_shell_ptr(strong_actor_ptr ptr) noexcept;
strong_actor_ptr ptr_;
};
} // namespace caf::net
// 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/actor_proxy_impl.hpp"
#include "caf/net/datagram_socket.hpp"
#include "caf/net/datagram_transport.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/host.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/pollset_updater.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_id.hpp"
#include "caf/net/socket_manager.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/net/transport_base.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/udp_datagram_socket.hpp"
// 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 <map>
#include <mutex>
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
#include "caf/net/basp/application.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/node_id.hpp"
namespace caf::net::backend {
/// Minimal backend for tcp communication.
class CAF_NET_EXPORT tcp : public middleman_backend {
public:
using peer_map = std::map<node_id, endpoint_manager_ptr>;
using emplace_return_type = std::pair<peer_map::iterator, bool>;
// -- constructors, destructors, and assignment operators --------------------
tcp(middleman& mm);
~tcp() override;
// -- interface functions ----------------------------------------------------
error init() override;
void stop() override;
expected<endpoint_manager_ptr> get_or_connect(const uri& locator) override;
endpoint_manager_ptr peer(const node_id& id) override;
void resolve(const uri& locator, const actor& listener) override;
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override;
void set_last_hop(node_id*) override;
// -- properties -------------------------------------------------------------
uint16_t port() const noexcept override;
template <class Handle>
expected<endpoint_manager_ptr>
emplace(const node_id& peer_id, Handle socket_handle) {
using transport_type = stream_transport<basp::application>;
if (auto err = nonblocking(socket_handle, true))
return err;
auto mpx = mm_.mpx();
basp::application app{proxies_};
auto mgr = make_endpoint_manager(
mpx, mm_.system(), transport_type{socket_handle, std::move(app)});
if (auto err = mgr->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << err);
return err;
}
mpx->register_reading(mgr);
emplace_return_type res;
{
const std::lock_guard<std::mutex> lock(lock_);
res = peers_.emplace(peer_id, std::move(mgr));
}
if (res.second)
return res.first->second;
else
return make_error(sec::runtime_error, "peer_id already exists");
}
private:
endpoint_manager_ptr get_peer(const node_id& id);
middleman& mm_;
peer_map peers_;
proxy_registry proxies_;
uint16_t listening_port_;
std::mutex lock_;
};
} // namespace caf::net::backend
// 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 <map>
#include "caf/detail/net_export.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/node_id.hpp"
namespace caf::net::backend {
/// Minimal backend for unit testing.
/// @warning this backend is *not* thread safe.
class CAF_NET_EXPORT test : public middleman_backend {
public:
// -- member types -----------------------------------------------------------
using peer_entry = std::pair<stream_socket, endpoint_manager_ptr>;
// -- constructors, destructors, and assignment operators --------------------
test(middleman& mm);
~test() override;
// -- interface functions ----------------------------------------------------
error init() override;
void stop() override;
endpoint_manager_ptr peer(const node_id& id) override;
expected<endpoint_manager_ptr> get_or_connect(const uri& locator) override;
void resolve(const uri& locator, const actor& listener) override;
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override;
void set_last_hop(node_id*) override;
// -- properties -------------------------------------------------------------
stream_socket socket(const node_id& peer_id) {
return get_peer(peer_id).first;
}
uint16_t port() const noexcept override;
peer_entry& emplace(const node_id& peer_id, stream_socket first,
stream_socket second);
private:
peer_entry& get_peer(const node_id& id);
middleman& mm_;
std::map<node_id, peer_entry> peers_;
proxy_registry proxies_;
};
} // namespace caf::net::backend
// 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 <cstdint>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "caf/actor_addr.hpp"
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/worker_hub.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/net/basp/message_type.hpp"
#include "caf/net/basp/worker.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/node_id.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/response_promise.hpp"
#include "caf/scoped_execution_unit.hpp"
#include "caf/unit.hpp"
namespace caf::net::basp {
/// An implementation of BASP as an application layer protocol.
class CAF_NET_EXPORT application {
public:
// -- member types -----------------------------------------------------------
using hub_type = detail::worker_hub<worker>;
struct test_tag {};
// -- constructors, destructors, and assignment operators --------------------
explicit application(proxy_registry& proxies);
// -- static utility functions -----------------------------------------------
static auto default_app_ids() {
return std::vector<std::string>{
to_string(defaults::middleman::app_identifier)};
}
// -- interface functions ----------------------------------------------------
template <class Parent>
error init(Parent& parent) {
// Initialize member variables.
system_ = &parent.system();
executor_.system_ptr(system_);
executor_.proxy_registry_ptr(&proxies_);
// Allow unit tests to run the application without endpoint manager.
if constexpr (!std::is_base_of<test_tag, Parent>::value)
manager_ = &parent.manager();
size_t workers;
if (auto workers_cfg = get_if<size_t>(&system_->config(),
"caf.middleman.workers"))
workers = *workers_cfg;
else
workers = std::min(3u, std::thread::hardware_concurrency() / 4u) + 1;
for (size_t i = 0; i < workers; ++i)
hub_->add_new_worker(*queue_, proxies_);
// Write handshake.
auto hdr = parent.next_header_buffer();
auto payload = parent.next_payload_buffer();
if (auto err = generate_handshake(payload))
return err;
to_bytes(header{message_type::handshake,
static_cast<uint32_t>(payload.size()), version},
hdr);
parent.write_packet(hdr, payload);
parent.transport().configure_read(receive_policy::exactly(header_size));
return none;
}
error write_message(packet_writer& writer,
std::unique_ptr<endpoint_manager_queue::message> ptr);
template <class Parent>
error handle_data(Parent& parent, byte_span bytes) {
static_assert(std::is_base_of<packet_writer, Parent>::value,
"parent must implement packet_writer");
size_t next_read_size = header_size;
if (auto err = handle(next_read_size, parent, bytes))
return err;
parent.transport().configure_read(receive_policy::exactly(next_read_size));
return none;
}
void resolve(packet_writer& writer, string_view path, const actor& listener);
static void new_proxy(packet_writer& writer, actor_id id);
void local_actor_down(packet_writer& writer, actor_id id, error reason);
template <class Parent>
void timeout(Parent&, const std::string&, uint64_t) {
// nop
}
void handle_error(sec) {
// nop
}
// -- utility functions ------------------------------------------------------
strong_actor_ptr resolve_local_path(string_view path);
// -- properties -------------------------------------------------------------
connection_state state() const noexcept {
return state_;
}
actor_system& system() const noexcept {
return *system_;
}
private:
// -- handling of incoming messages ------------------------------------------
error handle(size_t& next_read_size, packet_writer& writer, byte_span bytes);
error handle(packet_writer& writer, header hdr, byte_span payload);
error handle_handshake(packet_writer& writer, header hdr, byte_span payload);
error handle_actor_message(packet_writer& writer, header hdr,
byte_span payload);
error handle_resolve_request(packet_writer& writer, header rec_hdr,
byte_span received);
error handle_resolve_response(packet_writer& writer, header received_hdr,
byte_span received);
error handle_monitor_message(packet_writer& writer, header received_hdr,
byte_span received);
error handle_down_message(packet_writer& writer, header received_hdr,
byte_span received);
/// Writes the handshake payload to `buf_`.
error generate_handshake(byte_buffer& buf);
// -- member variables -------------------------------------------------------
/// Stores a pointer to the parent actor system.
actor_system* system_ = nullptr;
/// Stores the expected type of the next incoming message.
connection_state state_ = connection_state::await_handshake_header;
/// Caches the last header while waiting for the matching payload.
header hdr_;
/// Stores the ID of our peer.
node_id peer_id_;
/// Tracks which local actors our peer monitors.
std::unordered_set<actor_addr> monitored_actors_; // TODO: this is unused
/// Caches actor handles obtained via `resolve`.
std::unordered_map<uint64_t, actor> pending_resolves_;
/// Ascending ID generator for requests to our peer.
uint64_t next_request_id_ = 1;
/// Points to the factory object for generating proxies.
proxy_registry& proxies_;
/// Points to the endpoint manager that owns this applications.
endpoint_manager* manager_ = nullptr;
/// Provides pointers to the actor system as well as the registry,
/// serializers and deserializer.
scoped_execution_unit executor_;
std::unique_ptr<message_queue> queue_;
std::unique_ptr<hub_type> hub_;
};
} // namespace caf::net::basp
// 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/net/basp/application.hpp"
#include "caf/proxy_registry.hpp"
namespace caf::net::basp {
/// Factory for basp::application.
/// @relates doorman
class CAF_NET_EXPORT application_factory {
public:
using application_type = basp::application;
application_factory(proxy_registry& proxies) : proxies_(proxies) {
// nop
}
template <class Parent>
error init(Parent&) {
return none;
}
application_type make() const {
return application_type{proxies_};
}
private:
proxy_registry& proxies_;
};
} // namespace caf::net::basp
// 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 <cstdint>
#include <string>
#include <type_traits>
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
namespace caf::net::basp {
/// @addtogroup BASP
/// Stores the state of a connection in a `basp::application`.
enum class connection_state : uint8_t {
/// Initial state for any connection to wait for the peer's handshake.
await_handshake_header,
/// Indicates that the header for the peer's handshake arrived and BASP
/// requires the payload next.
await_handshake_payload,
/// Indicates that a connection is established and this node is waiting for
/// the next BASP header.
await_header,
/// Indicates that this node has received a header with non-zero payload and
/// is waiting for the data.
await_payload,
/// Indicates that the connection is about to shut down.
shutdown,
};
/// @relates connection_state
CAF_NET_EXPORT std::string to_string(connection_state x);
/// @relates connection_state
CAF_NET_EXPORT bool from_string(string_view, connection_state&);
/// @relates connection_state
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<connection_state>,
connection_state&);
/// @relates connection_state
template <class Inspector>
bool inspect(Inspector& f, connection_state& x) {
return default_enum_inspect(f, x);
}
/// @}
} // namespace caf::net::basp
// 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 <cstddef>
#include <cstdint>
namespace caf::net::basp {
/// @addtogroup BASP
/// The current BASP version.
/// @note BASP is not backwards compatible.
constexpr uint64_t version = 1;
/// Size of a BASP header in serialized form.
constexpr size_t header_size = 13;
/// @}
} // namespace caf::net::basp
// 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 <cstdint>
#include <string>
#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::basp {
/// BASP-specific error codes.
enum class ec : uint8_t {
invalid_magic_number = 1,
unexpected_number_of_bytes,
unexpected_payload,
missing_payload,
illegal_state,
invalid_handshake,
missing_handshake,
unexpected_handshake,
version_mismatch,
unimplemented = 10,
app_identifiers_mismatch,
invalid_payload,
invalid_scheme,
invalid_locator,
};
/// @relates ec
CAF_NET_EXPORT std::string to_string(ec x);
/// @relates ec
CAF_NET_EXPORT bool from_string(string_view, ec&);
/// @relates ec
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<ec>, ec&);
/// @relates ec
template <class Inspector>
bool inspect(Inspector& f, ec& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net::basp
CAF_ERROR_CODE_ENUM(caf::net::basp::ec)
// 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 <array>
#include <cstdint>
#include "caf/detail/comparable.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/message_type.hpp"
#include "caf/type_id.hpp"
namespace caf::net::basp {
/// @addtogroup BASP
/// The header of a Binary Actor System Protocol (BASP) message.
struct CAF_NET_EXPORT header : detail::comparable<header> {
// -- constructors, destructors, and assignment operators --------------------
constexpr header() noexcept
: type(message_type::handshake), payload_len(0), operation_data(0) {
// nop
}
constexpr header(message_type type, uint32_t payload_len,
uint64_t operation_data) noexcept
: type(type), payload_len(payload_len), operation_data(operation_data) {
// nop
}
header(const header&) noexcept = default;
header& operator=(const header&) noexcept = default;
// -- factory functions ------------------------------------------------------
/// @pre `bytes.size() == header_size`
static header from_bytes(span<const byte> bytes);
// -- comparison -------------------------------------------------------------
int compare(header other) const noexcept;
// -- member variables -------------------------------------------------------
/// Denotes the BASP operation and how `operation_data` gets interpreted.
message_type type;
/// Stores the size in bytes for the payload that follows this header.
uint32_t payload_len;
/// Stores type-specific information such as the BASP version in handshakes.
uint64_t operation_data;
};
/// Serializes a header to a byte representation.
/// @relates header
CAF_NET_EXPORT std::array<byte, header_size> to_bytes(header x);
/// Serializes a header to a byte representation.
/// @relates header
CAF_NET_EXPORT void to_bytes(header x, byte_buffer& buf);
/// @relates header
template <class Inspector>
bool inspect(Inspector& f, header& x) {
return f.object(x).fields(f.field("type", x.type),
f.field("payload_len", x.payload_len),
f.field("operation_data", x.operation_data));
}
/// @}
} // namespace caf::net::basp
namespace caf {
template <>
struct type_name<net::basp::header> {
static constexpr string_view value = "caf::net::basp::header";
};
} // namespace caf
// 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 <cstdint>
#include <mutex>
#include <vector>
#include "caf/actor_control_block.hpp"
#include "caf/fwd.hpp"
#include "caf/mailbox_element.hpp"
namespace caf::net::basp {
/// Enforces strict order of message delivery, i.e., deliver messages in the
/// same order as if they were deserialized by a single thread.
class message_queue {
public:
// -- member types -----------------------------------------------------------
/// Request for sending a message to an actor at a later time.
struct actor_msg {
uint64_t id;
strong_actor_ptr receiver;
mailbox_element_ptr content;
};
// -- constructors, destructors, and assignment operators --------------------
message_queue();
// -- mutators ---------------------------------------------------------------
/// Adds a new message to the queue or deliver it immediately if possible.
void push(execution_unit* ctx, uint64_t id, strong_actor_ptr receiver,
mailbox_element_ptr content);
/// Marks given ID as dropped, effectively skipping it without effect.
void drop(execution_unit* ctx, uint64_t id);
/// Returns the next ascending ID.
uint64_t new_id();
// -- member variables -------------------------------------------------------
/// Protects all other properties.
std::mutex lock;
/// The next available ascending ID. The counter is large enough to overflow
/// after roughly 600 years if we dispatch a message every microsecond.
uint64_t next_id;
/// The next ID that we can ship.
uint64_t next_undelivered;
/// Keeps messages in sorted order in case a message other than
/// `next_undelivered` gets ready first.
std::vector<actor_msg> pending;
};
} // namespace caf::net::basp
// 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 <cstdint>
#include <string>
#include <type_traits>
#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::basp {
/// @addtogroup BASP
/// Describes the first header field of a BASP message and determines the
/// interpretation of the other header fields.
enum class message_type : uint8_t {
/// Sends supported BASP version and node information to the server.
///
/// ![](client_handshake.png)
handshake = 0,
/// Transmits an actor-to-actor messages.
///
/// ![](direct_message.png)
actor_message = 1,
/// Tries to resolve a path on the receiving node.
///
/// ![](resolve_request.png)
resolve_request = 2,
/// Transmits the result of a path lookup.
///
/// ![](resolve_response.png)
resolve_response = 3,
/// Informs the receiving node that the sending node has created a proxy
/// instance for one of its actors. Causes the receiving node to attach a
/// functor to the actor that triggers a down_message on termination.
///
/// ![](monitor_message.png)
monitor_message = 4,
/// Informs the receiving node that it has a proxy for an actor that has been
/// terminated.
///
/// ![](down_message.png)
down_message = 5,
/// Used to generate periodic traffic between two nodes in order to detect
/// disconnects.
///
/// ![](heartbeat.png)
heartbeat = 6,
};
/// @relates message_type
CAF_NET_EXPORT std::string to_string(message_type x);
/// @relates message_type
CAF_NET_EXPORT bool from_string(string_view, message_type&);
/// @relates message_type
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<message_type>,
message_type&);
/// @relates message_type
template <class Inspector>
bool inspect(Inspector& f, message_type& x) {
return default_enum_inspect(f, x);
}
/// @}
} // namespace caf::net::basp
// 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 <vector>
#include "caf/actor_control_block.hpp"
#include "caf/actor_proxy.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/config.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/execution_unit.hpp"
#include "caf/logger.hpp"
#include "caf/message.hpp"
#include "caf/message_id.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/node_id.hpp"
namespace caf::net::basp {
template <class Subtype>
class remote_message_handler {
public:
void handle_remote_message(execution_unit* ctx) {
// Local variables.
auto& dref = static_cast<Subtype&>(*this);
auto& payload = dref.payload_;
auto& hdr = dref.hdr_;
auto& registry = dref.system_->registry();
auto& proxies = *dref.proxies_;
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
// Deserialize payload.
actor_id src_id = 0;
node_id src_node;
actor_id dst_id = 0;
std::vector<strong_actor_ptr> fwd_stack;
message content;
binary_deserializer source{ctx, payload};
if (!(source.apply(src_node) && source.apply(src_id) && source.apply(dst_id)
&& source.apply(fwd_stack) && source.apply(content))) {
CAF_LOG_ERROR(
"failed to deserialize payload:" << CAF_ARG(source.get_error()));
return;
}
// Sanity checks.
if (dst_id == 0)
return;
// Try to fetch the receiver.
auto dst_hdl = registry.get(dst_id);
if (dst_hdl == nullptr) {
CAF_LOG_DEBUG("no actor found for given ID, drop message");
return;
}
// Try to fetch the sender.
strong_actor_ptr src_hdl;
if (src_node != none && src_id != 0)
src_hdl = proxies.get_or_put(src_node, src_id);
// Ship the message.
auto ptr = make_mailbox_element(std::move(src_hdl),
make_message_id(hdr.operation_data),
std::move(fwd_stack), std::move(content));
dref.queue_->push(ctx, dref.msg_id_, std::move(dst_hdl), std::move(ptr));
}
};
} // namespace caf::net::basp
// 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 <atomic>
#include <cstdint>
#include "caf/byte_buffer.hpp"
#include "caf/config.hpp"
#include "caf/detail/abstract_worker.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/worker_hub.hpp"
#include "caf/fwd.hpp"
#include "caf/net/basp/header.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/net/basp/remote_message_handler.hpp"
#include "caf/net/fwd.hpp"
#include "caf/node_id.hpp"
#include "caf/resumable.hpp"
namespace caf::net::basp {
/// Deserializes payloads for BASP messages asynchronously.
class CAF_NET_EXPORT worker : public detail::abstract_worker,
public remote_message_handler<worker> {
public:
// -- friends ----------------------------------------------------------------
friend remote_message_handler<worker>;
// -- member types -----------------------------------------------------------
using super = detail::abstract_worker;
using scheduler_type = scheduler::abstract_coordinator;
using hub_type = detail::worker_hub<worker>;
// -- constructors, destructors, and assignment operators --------------------
/// Only the ::worker_hub has access to the construtor.
worker(hub_type& hub, message_queue& queue, proxy_registry& proxies);
~worker() override;
// -- management -------------------------------------------------------------
void launch(const node_id& last_hop, const basp::header& hdr,
span<const byte> payload);
// -- implementation of resumable --------------------------------------------
resume_result resume(execution_unit* ctx, size_t) override;
private:
// -- constants and assertions -----------------------------------------------
/// Stores how many bytes the "first half" of this object requires.
static constexpr size_t pointer_members_size
= sizeof(hub_type*) + sizeof(message_queue*) + sizeof(proxy_registry*)
+ sizeof(actor_system*);
static_assert(CAF_CACHE_LINE_SIZE > pointer_members_size,
"invalid cache line size");
// -- member variables -------------------------------------------------------
/// Points to our home hub.
hub_type* hub_;
/// Points to the queue for establishing strict ordering.
message_queue* queue_;
/// Points to our proxy registry / factory.
proxy_registry* proxies_;
/// Points to the parent system.
actor_system* system_;
/// Prevents false sharing when writing to `next`.
char pad_[CAF_CACHE_LINE_SIZE - pointer_members_size];
/// ID for local ordering.
uint64_t msg_id_;
/// Identifies the node that sent us `hdr_` and `payload_`.
node_id last_hop_;
/// The header for the next message. Either a direct_message or a
/// routed_message.
header hdr_;
/// Contains whatever this worker deserializes next.
byte_buffer payload_;
};
} // namespace caf::net::basp
// 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/logger.hpp"
#include "caf/net/make_endpoint_manager.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"
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 {
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 write_result = typename socket_manager::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) {
// nop
}
// -- interface functions ----------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
CAF_LOG_TRACE("");
owner_ = owner;
cfg_ = cfg;
if (auto err = factory_.init(owner, cfg))
return err;
down->register_reading();
return none;
}
template <class LowerLayerPtr>
read_result handle_read_event(LowerLayerPtr down) {
CAF_LOG_TRACE("");
if (auto x = accept(down->handle())) {
socket_manager_ptr child = factory_.make(*x, owner_->mpx_ptr());
if (!child) {
CAF_LOG_ERROR("factory failed to create a new child");
down->abort_reason(sec::runtime_error);
return read_result::stop;
}
if (auto err = child->init(cfg_)) {
CAF_LOG_ERROR("failed to initialize new child:" << err);
down->abort_reason(std::move(err));
return read_result::stop;
}
if (limit_ == 0) {
return read_result::again;
} else {
return ++accepted_ < limit_ ? read_result::again : read_result::stop;
}
} else {
CAF_LOG_ERROR("accept failed:" << x.error());
return read_result::stop;
}
}
template <class LowerLayerPtr>
static read_result handle_buffered_data(LowerLayerPtr) {
return read_result::again;
}
template <class LowerLayerPtr>
static read_result handle_continue_reading(LowerLayerPtr) {
return read_result::again;
}
template <class LowerLayerPtr>
write_result handle_write_event(LowerLayerPtr) {
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) {
CAF_LOG_ERROR("connection_acceptor aborts due to an error: " << reason);
factory_.abort(reason);
}
private:
factory_type factory_;
socket_manager* owner_;
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;
}
template <class Socket>
socket_manager_ptr make(Socket connected_socket, multiplexer* mpx) {
return f_(std::move(connected_socket), mpx);
}
void abort(const error&) {
// nop
}
private:
FunctionObject f_;
};
} // 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/async/consumer.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/ref_counted.hpp"
namespace caf::net {
/// Connects a socket manager to an asynchronous consumer resource. Whenever new
/// data becomes ready, the adapter registers the socket manager for writing.
template <class Buffer>
class consumer_adapter final : public ref_counted, public async::consumer {
public:
using buf_ptr = intrusive_ptr<Buffer>;
void on_producer_ready() override {
// nop
}
void on_producer_wakeup() override {
mgr_->mpx().schedule_fn([adapter = strong_this()] { //
adapter->on_wakeup();
});
}
void ref_consumer() const noexcept override {
this->ref();
}
void deref_consumer() const noexcept override {
this->deref();
}
template <class Policy, class Observer>
std::pair<bool, size_t> pull(Policy policy, size_t demand, Observer& dst) {
return buf_->pull(policy, demand, dst);
}
void cancel() {
buf_->cancel();
buf_ = nullptr;
}
bool has_data() const noexcept {
return buf_->has_data();
}
/// Tries to open the resource for reading.
/// @returns a connected adapter that reads from the resource on success,
/// `nullptr` otherwise.
template <class Resource>
static intrusive_ptr<consumer_adapter>
try_open(socket_manager* owner, Resource src) {
CAF_ASSERT(owner != nullptr);
if (auto buf = src.try_open()) {
using ptr_type = intrusive_ptr<consumer_adapter>;
auto adapter = ptr_type{new consumer_adapter(owner, buf), false};
buf->set_consumer(adapter);
return adapter;
} else {
return nullptr;
}
}
friend void intrusive_ptr_add_ref(const consumer_adapter* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const consumer_adapter* ptr) noexcept {
ptr->deref();
}
private:
consumer_adapter(socket_manager* owner, buf_ptr buf)
: mgr_(owner), buf_(std::move(buf)) {
// nop
}
auto strong_this() {
return intrusive_ptr{this};
}
void on_wakeup() {
if (buf_ && buf_->has_consumer_event()) {
mgr_->mpx().register_writing(mgr_);
}
}
intrusive_ptr<socket_manager> mgr_;
intrusive_ptr<Buffer> buf_;
};
template <class T>
using consumer_adapter_ptr = intrusive_ptr<consumer_adapter<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.
#pragma once
#include "caf/detail/net_export.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/variant.hpp"
namespace caf::net {
/// A datagram-oriented network communication endpoint.
struct CAF_NET_EXPORT datagram_socket : network_socket {
using super = network_socket;
using super::super;
};
/// Enables or disables `SIO_UDP_CONNRESET` error on `x`.
/// @relates datagram_socket
error CAF_NET_EXPORT allow_connreset(datagram_socket x, bool new_value);
/// Converts the result from I/O operation on a ::datagram_socket to either an
/// error code or a integer greater or equal to zero.
/// @relates datagram_socket
variant<size_t, sec> CAF_NET_EXPORT
check_datagram_socket_io_res(std::make_signed<size_t>::type 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 <deque>
#include <vector>
#include "caf/byte_buffer.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/transport_base.hpp"
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/sec.hpp"
namespace caf::net {
template <class Factory>
using datagram_transport_base
= transport_base<datagram_transport<Factory>,
transport_worker_dispatcher<Factory, ip_endpoint>,
udp_datagram_socket, Factory, ip_endpoint>;
/// Implements a udp_transport policy that manages a datagram socket.
template <class Factory>
class datagram_transport : public datagram_transport_base<Factory> {
public:
// Maximal UDP-packet size
static constexpr size_t max_datagram_size
= std::numeric_limits<uint16_t>::max();
// -- member types -----------------------------------------------------------
using factory_type = Factory;
using id_type = ip_endpoint;
using application_type = typename factory_type::application_type;
using super = datagram_transport_base<factory_type>;
using buffer_cache_type = typename super::buffer_cache_type;
// -- constructors, destructors, and assignment operators --------------------
datagram_transport(udp_datagram_socket handle, factory_type factory)
: super(handle, std::move(factory)) {
// nop
}
// -- public member functions ------------------------------------------------
error init(endpoint_manager& manager) override {
CAF_LOG_TRACE("");
if (auto err = super::init(manager))
return err;
prepare_next_read();
return none;
}
bool handle_read_event(endpoint_manager&) override {
CAF_LOG_TRACE(CAF_ARG(this->handle_.id));
for (size_t reads = 0; reads < this->max_consecutive_reads_; ++reads) {
auto ret = read(this->handle_, this->read_buf_);
if (auto res = get_if<std::pair<size_t, ip_endpoint>>(&ret)) {
auto& [num_bytes, ep] = *res;
CAF_LOG_DEBUG("received " << num_bytes << " bytes");
this->read_buf_.resize(num_bytes);
if (auto err = this->next_layer_.handle_data(*this, this->read_buf_,
std::move(ep))) {
CAF_LOG_ERROR("handle_data failed: " << err);
return false;
}
prepare_next_read();
} else {
auto err = get<sec>(ret);
if (err == sec::unavailable_or_would_block) {
break;
} else {
CAF_LOG_DEBUG("read failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
return false;
}
}
}
return true;
}
bool handle_write_event(endpoint_manager& manager) override {
CAF_LOG_TRACE(CAF_ARG2("socket", this->handle_.id)
<< CAF_ARG2("queue-size", packet_queue_.size()));
auto fetch_next_message = [&] {
if (auto msg = manager.next_message()) {
this->next_layer_.write_message(*this, std::move(msg));
return true;
}
return false;
};
do {
if (auto err = write_some())
return err == sec::unavailable_or_would_block;
} while (fetch_next_message());
return !packet_queue_.empty();
}
// TODO: remove this function. `resolve` should add workers when needed.
error add_new_worker(node_id node, id_type id) {
auto worker = this->next_layer_.add_new_worker(*this, node, id);
if (!worker)
return worker.error();
return none;
}
void write_packet(id_type id, span<byte_buffer*> buffers) override {
CAF_LOG_TRACE("");
CAF_ASSERT(!buffers.empty());
if (packet_queue_.empty())
this->manager().register_writing();
// By convention, the first buffer is a header buffer. Every other buffer is
// a payload buffer.
packet_queue_.emplace_back(id, buffers);
}
/// Helper struct for managing outgoing packets
struct packet {
id_type id;
buffer_cache_type bytes;
size_t size;
packet(id_type id, span<byte_buffer*> bufs) : id(id) {
size = 0;
for (auto buf : bufs) {
size += buf->size();
bytes.emplace_back(std::move(*buf));
}
}
std::vector<byte_buffer*> get_buffer_ptrs() {
std::vector<byte_buffer*> ptrs;
for (auto& buf : bytes)
ptrs.emplace_back(&buf);
return ptrs;
}
};
private:
// -- utility functions ------------------------------------------------------
void prepare_next_read() {
this->read_buf_.resize(max_datagram_size);
}
error write_some() {
// Helper function to sort empty buffers back into the right caches.
auto recycle = [&]() {
auto& front = packet_queue_.front();
auto& bufs = front.bytes;
auto it = bufs.begin();
if (this->header_bufs_.size() < this->header_bufs_.capacity()) {
it->clear();
this->header_bufs_.emplace_back(std::move(*it++));
}
for (; it != bufs.end()
&& this->payload_bufs_.size() < this->payload_bufs_.capacity();
++it) {
it->clear();
this->payload_bufs_.emplace_back(std::move(*it));
}
packet_queue_.pop_front();
};
// Write as many bytes as possible.
while (!packet_queue_.empty()) {
auto& packet = packet_queue_.front();
auto ptrs = packet.get_buffer_ptrs();
auto write_ret = write(this->handle_, ptrs, packet.id);
if (auto num_bytes = get_if<size_t>(&write_ret)) {
CAF_LOG_DEBUG(CAF_ARG(this->handle_.id) << CAF_ARG(*num_bytes));
CAF_LOG_WARNING_IF(*num_bytes < packet.size,
"packet was not sent completely");
recycle();
} else {
auto err = get<sec>(write_ret);
if (err != sec::unavailable_or_would_block) {
CAF_LOG_ERROR("write failed" << CAF_ARG(err));
this->next_layer_.handle_error(err);
}
return err;
}
}
return none;
}
std::deque<packet> packet_queue_;
};
} // 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 <cstddef>
#include <cstdint>
// -- hard-coded default values for various CAF options ------------------------
namespace caf::defaults::middleman {
/// 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
// 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 <cstddef>
#include <cstdint>
#include <memory>
#include "caf/actor.hpp"
#include "caf/actor_clock.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/intrusive/singly_linked.hpp"
#include "caf/mailbox_element.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/variant.hpp"
namespace caf::net {
/// Manages a communication endpoint.
class CAF_NET_EXPORT endpoint_manager : public socket_manager {
public:
// -- member types -----------------------------------------------------------
using super = socket_manager;
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager(socket handle, const multiplexer_ptr& parent,
actor_system& sys);
~endpoint_manager() override;
// -- properties -------------------------------------------------------------
actor_system& system() noexcept {
return sys_;
}
const actor_system_config& config() const noexcept;
// -- queue access -----------------------------------------------------------
bool at_end_of_message_queue();
endpoint_manager_queue::message_ptr next_message();
// -- event management -------------------------------------------------------
/// Resolves a path to a remote actor.
void resolve(uri locator, actor listener);
/// Enqueues a message to the endpoint.
void enqueue(mailbox_element_ptr msg, strong_actor_ptr receiver);
/// Enqueues an event to the endpoint.
template <class... Ts>
void enqueue_event(Ts&&... xs) {
enqueue(new endpoint_manager_queue::event(std::forward<Ts>(xs)...));
}
// -- pure virtual member functions ------------------------------------------
/// Initializes the manager before adding it to the multiplexer's event loop.
// virtual error init() = 0;
protected:
bool enqueue(endpoint_manager_queue::element* ptr);
/// Points to the hosting actor system.
actor_system& sys_;
/// Stores control events and outbound messages.
endpoint_manager_queue::type queue_;
/// Stores a proxy for interacting with the actor clock.
actor timeout_proxy_;
};
using endpoint_manager_ptr = intrusive_ptr<endpoint_manager>;
} // 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/abstract_actor.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_system.hpp"
#include "caf/detail/overload.hpp"
#include "caf/net/endpoint_manager.hpp"
namespace caf::net {
template <class Transport>
class endpoint_manager_impl : public endpoint_manager {
public:
// -- member types -----------------------------------------------------------
using super = endpoint_manager;
using transport_type = Transport;
using application_type = typename transport_type::application_type;
using read_result = typename super::read_result;
using write_result = typename super::write_result;
// -- constructors, destructors, and assignment operators --------------------
endpoint_manager_impl(const multiplexer_ptr& parent, actor_system& sys,
socket handle, Transport trans)
: super(handle, parent, sys), transport_(std::move(trans)) {
// nop
}
~endpoint_manager_impl() override {
// nop
}
// -- properties -------------------------------------------------------------
transport_type& transport() {
return transport_;
}
endpoint_manager_impl& manager() {
return *this;
}
// -- interface functions ----------------------------------------------------
error init() /*override*/ {
this->register_reading();
return transport_.init(*this);
}
read_result handle_read_event() override {
return transport_.handle_read_event(*this);
}
write_result handle_write_event() override {
if (!this->queue_.blocked()) {
this->queue_.fetch_more();
auto& q = std::get<0>(this->queue_.queue().queues());
do {
q.inc_deficit(q.total_task_size());
for (auto ptr = q.next(); ptr != nullptr; ptr = q.next()) {
auto f = detail::make_overload(
[&](endpoint_manager_queue::event::resolve_request& x) {
transport_.resolve(*this, x.locator, x.listener);
},
[&](endpoint_manager_queue::event::new_proxy& x) {
transport_.new_proxy(*this, x.peer, x.id);
},
[&](endpoint_manager_queue::event::local_actor_down& x) {
transport_.local_actor_down(*this, x.observing_peer, x.id,
std::move(x.reason));
},
[&](endpoint_manager_queue::event::timeout& x) {
transport_.timeout(*this, x.type, x.id);
});
visit(f, ptr->value);
}
} while (!q.empty());
}
if (!transport_.handle_write_event(*this)) {
if (this->queue_.blocked())
return write_result::stop;
else if (!(this->queue_.empty() && this->queue_.try_block()))
return write_result::again;
else
return write_result::stop;
}
return write_result::again;
}
void handle_error(sec code) override {
transport_.handle_error(code);
}
private:
transport_type transport_;
/// Stores the id for the next timeout.
uint64_t next_timeout_id_;
error err_;
};
} // 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 <string>
#include "caf/actor.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/serialized_size.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive/drr_queue.hpp"
#include "caf/intrusive/fifo_inbox.hpp"
#include "caf/intrusive/singly_linked.hpp"
#include "caf/intrusive/wdrr_fixed_multiplexed_queue.hpp"
#include "caf/mailbox_element.hpp"
#include "caf/unit.hpp"
#include "caf/variant.hpp"
namespace caf::net {
class CAF_NET_EXPORT endpoint_manager_queue {
public:
enum class element_type { event, message };
class element : public intrusive::singly_linked<element> {
public:
explicit element(element_type tag) : tag_(tag) {
// nop
}
virtual ~element();
virtual size_t task_size() const noexcept = 0;
element_type tag() const noexcept {
return tag_;
}
private:
element_type tag_;
};
using element_ptr = std::unique_ptr<element>;
class event final : public element {
public:
struct resolve_request {
uri locator;
actor listener;
};
struct new_proxy {
node_id peer;
actor_id id;
};
struct local_actor_down {
node_id observing_peer;
actor_id id;
error reason;
};
struct timeout {
std::string type;
uint64_t id;
};
event(uri locator, actor listener);
event(node_id peer, actor_id proxy_id);
event(node_id observing_peer, actor_id local_actor_id, error reason);
event(std::string type, uint64_t id);
~event() override;
size_t task_size() const noexcept override;
/// Holds the event data.
variant<resolve_request, new_proxy, local_actor_down, timeout> value;
};
using event_ptr = std::unique_ptr<event>;
struct event_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = event;
using unique_pointer = event_ptr;
using queue_type = intrusive::drr_queue<event_policy>;
constexpr event_policy(unit_t) {
// nop
}
static constexpr task_size_type task_size(const event&) noexcept {
return 1;
}
};
class message : public element {
public:
/// Original message to a remote actor.
mailbox_element_ptr msg;
/// ID of the receiving actor.
strong_actor_ptr receiver;
message(mailbox_element_ptr msg, strong_actor_ptr receiver);
~message() override;
size_t task_size() const noexcept override;
};
using message_ptr = std::unique_ptr<message>;
struct message_policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = message;
using unique_pointer = message_ptr;
using queue_type = intrusive::drr_queue<message_policy>;
constexpr message_policy(unit_t) {
// nop
}
static task_size_type task_size(const message& msg) noexcept {
return detail::serialized_size(msg.msg->content());
}
};
struct categorized {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = element;
using unique_pointer = element_ptr;
constexpr categorized(unit_t) {
// nop
}
template <class Queue>
deficit_type quantum(const Queue&, deficit_type x) const noexcept {
return x;
}
size_t id_of(const element& x) const noexcept {
return static_cast<size_t>(x.tag());
}
};
struct policy {
using deficit_type = size_t;
using task_size_type = size_t;
using mapped_type = element;
using unique_pointer = std::unique_ptr<element>;
using queue_type = intrusive::wdrr_fixed_multiplexed_queue<
categorized, event_policy::queue_type, message_policy::queue_type>;
task_size_type task_size(const message& x) const noexcept {
return x.task_size();
}
};
using type = intrusive::fifo_inbox<policy>;
};
} // 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 <memory>
#include "caf/intrusive_ptr.hpp"
#include "caf/type_id.hpp"
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;
template <class... Sigs>
class typed_actor_shell_ptr;
// -- enumerations -------------------------------------------------------------
enum class ec : uint8_t;
// -- classes ------------------------------------------------------------------
class actor_shell;
class actor_shell_ptr;
class endpoint_manager;
class middleman;
class middleman_backend;
class multiplexer;
class socket_manager;
// -- structs ------------------------------------------------------------------
struct network_socket;
struct pipe_socket;
struct socket;
struct stream_socket;
struct tcp_accept_socket;
struct tcp_stream_socket;
struct datagram_socket;
struct udp_datagram_socket;
// -- smart pointer aliases ----------------------------------------------------
using endpoint_manager_ptr = intrusive_ptr<endpoint_manager>;
using middleman_backend_ptr = std::unique_ptr<middleman_backend>;
using multiplexer_ptr = std::shared_ptr<multiplexer>;
using socket_manager_ptr = intrusive_ptr<socket_manager>;
using weak_multiplexer_ptr = std::weak_ptr<multiplexer>;
} // namespace caf::net
namespace caf::net::basp {
enum class ec : uint8_t;
} // namespace caf::net::basp
CAF_BEGIN_TYPE_ID_BLOCK(net_module, detail::net_module_begin)
CAF_ADD_TYPE_ID(net_module, (caf::net::basp::ec))
CAF_END_TYPE_ID_BLOCK(net_module)
static_assert(caf::id_block::net_module::end == caf::detail::net_module_end);
// 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
// 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 {
struct CAF_NET_EXPORT this_host {
/// Initializes the network subsystem.
static error startup();
/// Release any resources of the network subsystem.
static void cleanup();
};
} // 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
namespace caf::net::http {
/// Stores context information for a request. For HTTP/2, this is the stream ID.
struct context {};
} // 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/config_value.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/method.hpp"
#include "caf/net/http/status.hpp"
#include "caf/string_view.hpp"
#include "caf/uri.hpp"
#include <vector>
namespace caf::net::http {
class CAF_NET_EXPORT header {
public:
header() = default;
header(header&&) = default;
header& operator=(header&&) = default;
header(const header&);
header& operator=(const header&);
void assign(const header&);
http::method method() const noexcept {
return method_;
}
string_view path() const noexcept {
return uri_.path();
}
const uri::query_map& query() const noexcept {
return uri_.query();
}
string_view fragment() const noexcept {
return uri_.fragment();
}
string_view version() const noexcept {
return version_;
}
const header_fields_map& fields() const noexcept {
return fields_;
}
string_view field(string_view key) const noexcept {
if (auto i = fields_.find(key); i != fields_.end())
return i->second;
else
return {};
}
template <class T>
optional<T> field_as(string_view key) const noexcept {
if (auto i = fields_.find(key); i != fields_.end()) {
caf::config_value val{to_string(i->second)};
if (auto res = caf::get_as<T>(val))
return std::move(*res);
else
return {};
} else {
return {};
}
}
bool valid() const noexcept {
return !raw_.empty();
}
std::pair<status, string_view> parse(string_view raw);
bool chunked_transfer_encoding() const;
optional<size_t> content_length() const;
private:
std::vector<char> raw_;
http::method method_;
uri uri_;
string_view version_;
header_fields_map fields_;
};
} // 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/unordered_flat_map.hpp"
#include "caf/string_view.hpp"
namespace caf::net::http {
/// Convenience type alias for a key-value map storing header fields.
using header_fields_map = detail::unordered_flat_map<string_view, 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/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net::http {
/// Methods as defined by RFC 7231.
enum class method : uint8_t {
/// Requests transfer of a current selected representation for the target
/// resource.
get,
/// Identical to GET except that the server MUST NOT send a message body in
/// the response. The server SHOULD send the same header fields in response to
/// a HEAD request as it would have sent if the request had been a GET, except
/// that the payload header fields (Section 3.3) MAY be omitted.
head,
/// Requests that the target resource process the representation enclosed in
/// the request according to the resource's own specific semantics.
post,
/// Requests that the state of the target resource be created or replaced with
/// the state defined by the representation enclosed in the request message
/// payload.
put,
/// Requests that the origin server remove the association between the target
/// resource and its current functionality.
del,
/// Requests that the recipient establish a tunnel to the destination origin
/// server identified by the request-target and, if successful, thereafter
/// restrict its behavior to blind forwarding of packets, in both directions,
/// until the tunnel is closed.
connect,
/// Requests information about the communication options available for the
/// target resource, at either the origin server or an intervening
/// intermediary.
options,
/// Requests a remote, application-level loop-back of the request message.
trace,
};
/// @relates method
CAF_NET_EXPORT std::string to_string(method);
/// Converts @p x to the RFC string representation, i.e., all-uppercase.
/// @relates method
CAF_NET_EXPORT std::string to_rfc_string(method x);
/// @relates method
CAF_NET_EXPORT bool from_string(string_view, method&);
/// @relates method
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<method>, method&);
/// @relates method
template <class Inspector>
bool inspect(Inspector& f, method& x) {
return default_enum_inspect(f, x);
}
} // 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/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"
#include "caf/net/fwd.hpp"
#include "caf/net/http/context.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/status.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/pec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/hypertext_oriented.hpp"
#include "caf/tag/stream_oriented.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 {
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;
using context_type = context;
using header_type = header;
enum class mode {
read_header,
read_payload,
read_chunks,
};
// -- constants --------------------------------------------------------------
/// Default maximum size for incoming HTTP requests: 64KiB.
static constexpr uint32_t default_max_request_size = 65'536;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit server(Ts&&... xs) : upper_layer_(std::forward<Ts>(xs)...) {
// 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;
}
// -- 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 void suspend_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::stop());
}
template <class LowerLayerPtr>
static auto handle(LowerLayerPtr down) noexcept {
return down->handle();
}
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;
}
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;
}
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(byte{'\r'});
buf.emplace_back(byte{'\n'});
buf.insert(buf.end(), bytes.begin(), bytes.end());
buf.emplace_back(byte{'\r'});
buf.emplace_back(byte{'\n'});
return down->end_output();
}
template <class LowerLayerPtr>
bool send_end_of_chunks(LowerLayerPtr down, context) {
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();
}
template <class LowerLayerPtr>
void fin(LowerLayerPtr, context) {
// nop
}
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>
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_.prepare_send(this_layer_ptr(down));
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(max_request_size_));
}
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) {
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;
}
}
}
}
private:
// -- implementation details -------------------------------------------------
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_hypertext_oriented_layer_ptr(this, down);
}
template <class LowerLayerPtr>
void write_response(LowerLayerPtr down, status code, string_view content) {
down->begin_output();
v1::write_response(code, "text/plain", content, down->output_buffer());
down->end_output();
}
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;
}
// -- HTTP request processing ------------------------------------------------
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, 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_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
/// Buffer for re-using memory.
header hdr_;
/// Stores whether we are currently waiting for the payload.
mode mode_ = mode::read_header;
/// Stores the expected payload size when in read_payload mode.
size_t payload_len_ = 0;
/// Maximum size for incoming HTTP requests.
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
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net::http {
/// Status codes as defined by RFC 7231 and RFC 6585.
enum class status : uint16_t {
/// Indicates that the initial part of a request has been received and has not
/// yet been rejected by the server. The server intends to send a final
/// response after the request has been fully received and acted upon.
continue_request = 100,
/// Indicates that the server understands and is willing to comply with the
/// client's request for a change in the application protocol being used on
/// this connection.
switching_protocols = 101,
/// Indicates that the request has succeeded.
ok = 200,
/// Indicates that the request has been fulfilled and has resulted in one or
/// more new resources being created.
created = 201,
/// Indicates that the request has been accepted for processing, but the
/// processing has not been completed.
accepted = 202,
/// Indicates that the request was successful but the enclosed payload has
/// been modified from that of the origin server's 200 (OK) response by a
/// transforming proxy
non_authoritative_information = 203,
/// Indicates that the server has successfully fulfilled the request and that
/// there is no additional content to send in the response payload body.
no_content = 204,
/// Indicates that the server has fulfilled the request and desires that the
/// user agent reset the "document view".
reset_content = 205,
/// Indicates that the server is successfully fulfilling a range request for
/// the target resource by transferring one or more parts of the selected
/// representation that correspond to the satisfiable ranges found in the
/// request's Range header field.
partial_content = 206,
/// Indicates that the target resource has more than one representation and
/// information about the alternatives is being provided so that the user (or
/// user agent) can select a preferred representation.
multiple_choices = 300,
/// Indicates that the target resource has been assigned a new permanent URI.
moved_permanently = 301,
/// Indicates that the target resource resides temporarily under a different
/// URI.
found = 302,
/// Indicates that the server is redirecting the user agent to a different
/// resource, as indicated by a URI in the Location header field, which is
/// intended to provide an indirect response to the original request.
see_other = 303,
/// Indicates that a conditional GET or HEAD request has been received and
/// would have resulted in a 200 (OK) response if it were not for the fact
/// that the condition evaluated to false.
not_modified = 304,
/// Deprecated.
use_proxy = 305,
/// No longer valid.
temporary_redirect = 307,
/// Indicates that the server cannot or will not process the request due to
/// something that is perceived to be a client error (e.g., malformed request
/// syntax, invalid request message framing, or deceptive request routing).
bad_request = 400,
/// Indicates that the request has not been applied because it lacks valid
/// authentication credentials for the target resource.
unauthorized = 401,
/// Reserved for future use.
payment_required = 402,
/// Indicates that the server understood the request but refuses to authorize
/// it.
forbidden = 403,
/// Indicates that the origin server did not find a current representation for
/// the target resource or is not willing to disclose that one exists.
not_found = 404,
/// Indicates that the method received in the request-line is known by the
/// origin server but not supported by the target resource.
method_not_allowed = 405,
/// Indicates that the target resource does not have a current representation
/// that would be acceptable to the user agent, according to the proactive
/// negotiation header fields received in the request (Section 5.3), and the
/// server is unwilling to supply a default representation.
not_acceptable = 406,
/// Similar to 401 (Unauthorized), but it indicates that the client needs to
/// authenticate itself in order to use a proxy.
proxy_authentication_required = 407,
/// Indicates that the server did not receive a complete request message
/// within the time that it was prepared to wait.
request_timeout = 408,
/// Indicates that the request could not be completed due to a conflict with
/// the current state of the target resource. This code is used in situations
/// where the user might be able to resolve the conflict and resubmit the
/// request.
conflict = 409,
/// Indicates that access to the target resource is no longer available at the
/// origin server and that this condition is likely to be permanent.
gone = 410,
/// Indicates that the server refuses to accept the request without a defined
/// Content-Length.
length_required = 411,
/// Indicates that one or more conditions given in the request header fields
/// evaluated to false when tested on the server.
precondition_failed = 412,
/// Indicates that the server is refusing to process a request because the
/// request payload is larger than the server is willing or able to process.
payload_too_large = 413,
/// Indicates that the server is refusing to service the request because the
/// request-target is longer than the server is willing to interpret.
uri_too_long = 414,
/// Indicates that the origin server is refusing to service the request
/// because the payload is in a format not supported by this method on the
/// target resource.
unsupported_media_type = 415,
/// Indicates that none of the ranges in the request's Range header field
/// overlap the current extent of the selected resource or that the set of
/// ranges requested has been rejected due to invalid ranges or an excessive
/// request of small or overlapping ranges.
range_not_satisfiable = 416,
/// Indicates that the expectation given in the request's Expect header field
/// could not be met by at least one of the inbound servers.
expectation_failed = 417,
/// Indicates that the server refuses to perform the request using the current
/// protocol but might be willing to do so after the client upgrades to a
/// different protocol.
upgrade_required = 426,
/// Indicates that the origin server requires the request to be conditional.
precondition_required = 428,
/// Indicates that the user has sent too many requests in a given amount of
/// time ("rate limiting").
too_many_requests = 429,
/// Indicates that the server is unwilling to process the request because its
/// header fields are too large.
request_header_fields_too_large = 431,
/// Indicates that the server encountered an unexpected condition that
/// prevented it from fulfilling the request.
internal_server_error = 500,
/// Indicates that the server does not support the functionality required to
/// fulfill the request.
not_implemented = 501,
/// Indicates that the server, while acting as a gateway or proxy, received an
/// invalid response from an inbound server it accessed while attempting to
/// fulfill the request.
bad_gateway = 502,
/// Indicates that the server is currently unable to handle the request due to
/// a temporary overload or scheduled maintenance, which will likely be
/// alleviated after some delay.
service_unavailable = 503,
/// Indicates that the server, while acting as a gateway or proxy, did not
/// receive a timely response from an upstream server it needed to access in
/// order to complete the request.
gateway_timeout = 504,
/// Indicates that the server does not support, or refuses to support, the
/// major version of HTTP that was used in the request message.
http_version_not_supported = 505,
/// Indicates that the client needs to authenticate to gain network access.
network_authentication_required = 511,
};
/// Returns the recommended response phrase to a status code.
/// @relates status
CAF_NET_EXPORT string_view phrase(status);
/// @relates status
CAF_NET_EXPORT std::string to_string(status);
/// @relates status
CAF_NET_EXPORT bool from_string(string_view, status&);
/// @relates status
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<status>, status&);
/// @relates status
template <class Inspector>
bool inspect(Inspector& f, status& x) {
return default_enum_inspect(f, x);
}
} // 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/detail/net_export.hpp"
#include "caf/net/http/header_fields_map.hpp"
#include "caf/net/http/status.hpp"
#include "caf/string_view.hpp"
#include <utility>
namespace caf::net::http::v1 {
/// Tries splitting the given byte span into an HTTP header (`first`) and a
/// remainder (`second`). Returns an empty @ref string_view as `first` for
/// incomplete HTTP headers.
CAF_NET_EXPORT std::pair<string_view, byte_span> split_header(byte_span bytes);
/// Writes an HTTP header to the buffer.
CAF_NET_EXPORT void write_header(status code, const header_fields_map& fields,
byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields.
CAF_NET_EXPORT void write_response(status code, string_view content_type,
string_view content, byte_buffer& buf);
/// Writes a complete HTTP response to the buffer. Automatically sets
/// Content-Type and Content-Length header fields followed by the user-defined
/// @p fields.
CAF_NET_EXPORT void
write_response(status code, string_view content_type, string_view content,
const header_fields_map& fields, byte_buffer& buf);
} // namespace caf::net::http::v1
// 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, 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,
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, string_view content_type,
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,
string_view content_type, 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
// 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 <string>
#include <vector>
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
namespace caf::net::ip {
/// Returns all IP addresses of `host` (if any).
std::vector<ip_address> CAF_NET_EXPORT resolve(string_view host);
/// Returns all IP addresses of `host` (if any).
std::vector<ip_address> CAF_NET_EXPORT resolve(ip_address host);
/// Returns the IP addresses for a local endpoint, which is either an address,
/// an interface name, or the string "localhost".
std::vector<ip_address> CAF_NET_EXPORT local_addresses(string_view host);
/// Returns the IP addresses for a local endpoint address.
std::vector<ip_address> CAF_NET_EXPORT local_addresses(ip_address host);
/// Returns the hostname of this device.
std::string CAF_NET_EXPORT hostname();
} // namespace caf::net::ip
// 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 <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
/// of varying size. The framing uses 4 Bytes for the length prefix, but
/// messages (including the 4 Bytes for the length prefix) are limited to a
/// maximum size of INT32_MAX. This limitation comes from the POSIX API (recv)
/// on 32-bit platforms.
template <class UpperLayer>
class length_prefix_framing {
public:
using input_tag = tag::stream_oriented;
using output_tag = tag::message_oriented;
using length_prefix_type = uint32_t;
static constexpr size_t hdr_size = sizeof(uint32_t);
static constexpr size_t max_message_length = INT32_MAX - sizeof(uint32_t);
// -- 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, 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)));
}
private:
// -- implementation details -------------------------------------------------
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_message_oriented_layer_ptr(this, down);
}
// -- member variables -------------------------------------------------------
UpperLayer upper_layer_;
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
// 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/make_counted.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
namespace caf::net {
template <class Transport>
endpoint_manager_ptr make_endpoint_manager(const multiplexer_ptr& mpx,
actor_system& sys, Transport trans) {
using impl = endpoint_manager_impl<Transport>;
return make_counted<impl>(mpx, sys, std::move(trans));
}
} // 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/actor_system.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/net/consumer_adapter.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>
namespace caf::net {
/// Translates between a message-oriented transport and data flows.
///
/// The trait class converts between the native and the wire format:
///
/// ~~~
/// struct my_trait {
/// bool convert(const T& value, byte_buffer& bytes);
/// bool convert(const_byte_span bytes, T& value);
/// };
/// ~~~
template <class T, class Trait, class Tag = tag::message_oriented>
class message_flow_bridge : public tag::no_auto_reading {
public:
using input_tag = Tag;
using buffer_type = async::spsc_buffer<T>;
using consumer_resource_t = async::consumer_resource<T>;
using producer_resource_t = async::producer_resource<T>;
message_flow_bridge(consumer_resource_t in_res, producer_resource_t out_res,
Trait trait)
: in_res_(std::move(in_res)),
trait_(std::move(trait)),
out_res_(std::move(out_res)) {
// nop
}
explicit message_flow_bridge(Trait trait) : trait_(std::move(trait)) {
// 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;
}
if (in_res_) {
in_ = consumer_adapter<buffer_type>::try_open(mgr, in_res_);
in_res_ = nullptr;
}
if (out_res_) {
out_ = producer_adapter<buffer_type>::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");
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();
}
}
}
template <class LowerLayerPtr>
struct write_helper {
using bridge_type = message_flow_bridge;
bridge_type* bridge;
LowerLayerPtr down;
bool aborted = false;
size_t consumed = 0;
error err;
write_helper(bridge_type* bridge, LowerLayerPtr down)
: bridge(bridge), down(down) {
// nop
}
void on_next(span<const T> items) {
CAF_ASSERT(items.size() == 1);
for (const auto& item : items) {
if (!bridge->write(down, item)) {
aborted = true;
return;
}
}
}
void on_complete() {
// nop
}
void on_error(const error& x) {
err = x;
}
};
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
write_helper<LowerLayerPtr> helper{this, down};
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);
} else {
down->send_close_message();
}
in_ = nullptr;
} else if (helper.aborted) {
down->abort_reason(make_error(sec::conversion_failed));
in_->cancel();
in_ = nullptr;
return false;
} else if (consumed == 0) {
return true;
}
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return !in_ || !in_->has_data();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_LOG_TRACE(CAF_ARG(reason));
if (out_) {
if (reason == sec::socket_disconnected || reason == sec::disposed)
out_->close();
else
out_->abort(reason);
out_ = nullptr;
}
if (in_) {
in_->cancel();
in_ = nullptr;
}
}
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, string_view 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());
}
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;
/// Incoming messages, serialized to the socket.
consumer_adapter_ptr<buffer_type> in_;
/// Outgoing messages, deserialized from the socket.
producer_adapter_ptr<buffer_type> out_;
/// Converts between raw bytes and items.
Trait trait_;
/// Discarded after initialization.
consumer_resource_t in_res_;
/// Discarded after initialization.
producer_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/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
// 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 <chrono>
#include <set>
#include <string>
#include <thread>
#include "caf/actor_system.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/fwd.hpp"
#include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/scoped_actor.hpp"
namespace caf::net {
class CAF_NET_EXPORT middleman : public actor_system::module {
public:
// -- member types -----------------------------------------------------------
using module = actor_system::module;
using module_ptr = actor_system::module_ptr;
using middleman_backend_list = std::vector<middleman_backend_ptr>;
// -- static utility functions -----------------------------------------------
static void init_global_meta_objects();
// -- constructors, destructors, and assignment operators --------------------
explicit middleman(actor_system& sys);
~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;
void stop() override;
void init(actor_system_config&) override;
id_t id() const override;
void* subtype_ptr() override;
// -- factory functions ------------------------------------------------------
template <class... Ts>
static module* make(actor_system& sys, detail::type_list<Ts...> token) {
std::unique_ptr<middleman> result{new middleman(sys)};
if (sizeof...(Ts) > 0) {
result->backends_.reserve(sizeof...(Ts));
create_backends(*result, token);
}
return result.release();
}
/// Adds module-specific options to the config before loading the module.
static void add_module_options(actor_system_config& cfg);
// -- remoting ---------------------------------------------------------------
expected<endpoint_manager_ptr> connect(const uri& locator);
// Publishes an actor.
template <class Handle>
void publish(Handle whom, const std::string& path) {
// TODO: Currently, we can't get the interface from the registry. Either we
// change that, or we need to associate the handle with the interface.
system().registry().put(path, whom);
}
/// Resolves a path to a remote actor.
void resolve(const uri& locator, const actor& listener);
template <class Handle = actor, class Duration = std::chrono::seconds>
expected<Handle>
remote_actor(const uri& locator, Duration timeout = std::chrono::seconds(5)) {
scoped_actor self{sys_};
resolve(locator, self);
Handle handle;
error err;
self->receive(
[&handle](strong_actor_ptr& ptr, const std::set<std::string>&) {
// TODO: This cast is not type-safe.
handle = actor_cast<Handle>(std::move(ptr));
},
[&err](const error& e) { err = e; },
after(timeout) >>
[&err] {
err = make_error(sec::runtime_error,
"manager did not respond with a proxy.");
});
if (err)
return err;
if (handle)
return handle;
return make_error(sec::runtime_error, "cast to handle-type failed");
}
// -- properties -------------------------------------------------------------
actor_system& system() {
return sys_;
}
const actor_system_config& config() const noexcept {
return sys_.config();
}
multiplexer& mpx() noexcept {
return mpx_;
}
const multiplexer& mpx() const noexcept {
return mpx_;
}
multiplexer* mpx_ptr() noexcept {
return &mpx_;
}
const multiplexer* mpx_ptr() const noexcept {
return &mpx_;
}
middleman_backend* backend(string_view scheme) const noexcept;
expected<uint16_t> port(string_view scheme) const;
private:
// -- utility functions ------------------------------------------------------
static void create_backends(middleman&, detail::type_list<>) {
// End of recursion.
}
template <class T, class... Ts>
static void create_backends(middleman& mm, detail::type_list<T, Ts...>) {
mm.backends_.emplace_back(new T(mm));
create_backends(mm, detail::type_list<Ts...>{});
}
// -- member variables -------------------------------------------------------
/// Points to the parent system.
actor_system& sys_;
/// Stores the global socket I/O multiplexer.
multiplexer mpx_;
/// Stores all available backends for managing peers.
middleman_backend_list backends_;
/// Runs the multiplexer's event loop
std::thread mpx_thread_;
};
} // 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 <string>
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/fwd.hpp"
#include "caf/proxy_registry.hpp"
namespace caf::net {
/// Technology-specific backend for connecting to and managing peer
/// connections.
/// @relates middleman
class CAF_NET_EXPORT middleman_backend : public proxy_registry::backend {
public:
// -- constructors, destructors, and assignment operators --------------------
middleman_backend(std::string id);
virtual ~middleman_backend();
// -- interface functions ----------------------------------------------------
/// Initializes the backend.
virtual error init() = 0;
/// @returns The endpoint manager for `peer` on success, `nullptr` otherwise.
virtual endpoint_manager_ptr peer(const node_id& id) = 0;
/// Establishes a connection to a remote node.
virtual expected<endpoint_manager_ptr> get_or_connect(const uri& locator) = 0;
/// Resolves a path to a remote actor.
virtual void resolve(const uri& locator, const actor& listener) = 0;
virtual void stop() = 0;
// -- properties -------------------------------------------------------------
const std::string& id() const noexcept {
return id_;
}
virtual uint16_t port() const = 0;
private:
/// Stores the technology-specific identifier.
std::string id_;
};
/// @relates middleman_backend
using middleman_backend_ptr = std::unique_ptr<middleman_backend>;
} // 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/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
// 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 <memory>
#include <mutex>
#include <thread>
#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"
extern "C" {
struct pollfd;
} // extern "C"
namespace caf::net {
class pollset_updater;
/// Multiplexes any number of ::socket_manager objects with a ::socket.
class CAF_NET_EXPORT multiplexer {
public:
// -- member types -----------------------------------------------------------
struct poll_update {
short events = 0;
socket_manager_ptr mgr;
};
using poll_update_map = detail::unordered_flat_map<socket, poll_update>;
using pollfd_list = std::vector<pollfd>;
using manager_list = std::vector<socket_manager_ptr>;
// -- friends ----------------------------------------------------------------
friend class pollset_updater; // Needs access to the `do_*` functions.
// -- static utility functions -----------------------------------------------
/// Blocks the PIPE signal on the current thread when running on a POSIX
/// windows. Has no effect when running on Windows.
static void block_sigpipe();
// -- constructors, destructors, and assignment operators --------------------
/// @param parent Points to the owning middleman instance. May be `nullptr`
/// only for the purpose of unit testing if no @ref
/// socket_manager requires access to the @ref middleman or the
/// @ref actor_system.
explicit multiplexer(middleman* parent);
~multiplexer();
// -- initialization ---------------------------------------------------------
error init();
// -- properties -------------------------------------------------------------
/// Returns the number of currently active socket managers.
size_t num_socket_managers() const noexcept;
/// Returns the index of `mgr` in the pollset or `-1`.
ptrdiff_t index_of(const socket_manager_ptr& mgr);
/// Returns the index of `fd` in the pollset or `-1`.
ptrdiff_t index_of(socket fd);
/// Returns the owning @ref middleman instance.
middleman& owner();
/// Returns the enclosing @ref actor_system.
actor_system& system();
/// Computes the current mask for the manager. Mostly useful for testing.
operation mask_of(const socket_manager_ptr& mgr);
// -- thread-safe signaling --------------------------------------------------
/// Registers `mgr` for read events.
/// @thread-safe
void register_reading(const socket_manager_ptr& mgr);
/// Registers `mgr` for write events.
/// @thread-safe
void register_writing(const socket_manager_ptr& mgr);
/// Triggers a continue reading event for `mgr`.
/// @thread-safe
void continue_reading(const socket_manager_ptr& mgr);
/// Triggers a continue writing event for `mgr`.
/// @thread-safe
void continue_writing(const socket_manager_ptr& mgr);
/// Schedules a call to `mgr->handle_error(sec::discarded)`.
/// @thread-safe
void discard(const socket_manager_ptr& mgr);
/// Stops further reading by `mgr`.
/// @thread-safe
void shutdown_reading(const socket_manager_ptr& mgr);
/// Stops further writing by `mgr`.
/// @thread-safe
void shutdown_writing(const socket_manager_ptr& mgr);
/// Schedules an action for execution on this multiplexer.
/// @thread-safe
void schedule(const action& what);
/// Schedules an action for execution on this multiplexer.
/// @thread-safe
template <class F>
void schedule_fn(F f) {
schedule(make_action(std::move(f)));
}
/// Registers `mgr` for initialization in the multiplexer's thread.
/// @thread-safe
void init(const socket_manager_ptr& mgr);
/// Signals the multiplexer to initiate shutdown.
/// @thread-safe
void shutdown();
// -- control flow -----------------------------------------------------------
/// Polls I/O activity once and runs all socket event handlers that become
/// ready as a result.
bool poll_once(bool blocking);
/// Applies all pending updates.
void apply_updates();
/// Sets the thread ID to `std::this_thread::id()`.
void set_thread_id();
/// Polls until no socket event handler remains.
void run();
protected:
// -- utility functions ------------------------------------------------------
/// Handles an I/O event on given manager.
void handle(const socket_manager_ptr& mgr, short events, short revents);
/// Transfers socket ownership from one manager to another.
void do_handover(const socket_manager_ptr& mgr);
/// Returns a change entry for the socket at given index. Lazily creates a new
/// entry before returning if necessary.
poll_update& update_for(ptrdiff_t index);
/// Returns a change entry for the socket of the manager.
poll_update& update_for(const socket_manager_ptr& mgr);
/// Writes `opcode` and pointer to `mgr` the the pipe for handling an event
/// later via the pollset updater.
template <class T>
void write_to_pipe(uint8_t opcode, T* ptr);
/// @copydoc write_to_pipe
template <class Enum, class T>
std::enable_if_t<std::is_enum_v<Enum>> write_to_pipe(Enum opcode, T* ptr) {
write_to_pipe(static_cast<uint8_t>(opcode), ptr);
}
/// Queries the currently active event bitmask for `mgr`.
short active_mask_of(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for reading.
bool is_reading(const socket_manager_ptr& mgr);
/// Queries whether `mgr` is currently registered for writing.
bool is_writing(const socket_manager_ptr& mgr);
// -- member variables -------------------------------------------------------
/// Bookkeeping data for managed sockets.
pollfd_list pollset_;
/// Maps sockets to their owning managers by storing the managers in the same
/// order as their sockets appear in `pollset_`.
manager_list managers_;
/// Caches changes to the events mask of managed sockets until they can safely
/// take place.
poll_update_map updates_;
/// Stores the ID of the thread this multiplexer is running in. Set when
/// calling `init()`.
std::thread::id tid_;
/// Guards `write_handle_`.
std::mutex write_lock_;
/// Used for pushing updates to the multiplexer's thread.
pipe_socket write_handle_;
/// Points to the owning middleman.
middleman* owner_;
/// Signals whether shutdown has been requested.
bool shutting_down_ = false;
private:
// -- internal callbacks the pollset updater ---------------------------------
void do_shutdown();
void do_register_reading(const socket_manager_ptr& mgr);
void do_register_writing(const socket_manager_ptr& mgr);
void do_continue_reading(const socket_manager_ptr& mgr);
void do_continue_writing(const socket_manager_ptr& mgr);
void do_discard(const socket_manager_ptr& mgr);
void do_shutdown_reading(const socket_manager_ptr& mgr);
void do_shutdown_writing(const socket_manager_ptr& mgr);
void do_init(const socket_manager_ptr& mgr);
};
} // 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 <cstdint>
#include <string>
#include <system_error>
#include <type_traits>
#include "caf/config.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf::net {
/// A bidirectional network communication endpoint.
struct CAF_NET_EXPORT network_socket : socket {
using super = socket;
using super::super;
};
/// Enables or disables `SIGPIPE` events from `x`.
/// @relates network_socket
error CAF_NET_EXPORT allow_sigpipe(network_socket x, bool new_value);
/// Enables or disables `SIO_UDP_CONNRESET`error on `x`.
/// @relates network_socket
error CAF_NET_EXPORT allow_udp_connreset(network_socket x, bool new_value);
/// Get the socket buffer size for `x`.
/// @pre `x != invalid_socket`
/// @relates network_socket
expected<size_t> CAF_NET_EXPORT send_buffer_size(network_socket x);
/// Set the socket buffer size for `x`.
/// @relates network_socket
error CAF_NET_EXPORT send_buffer_size(network_socket x, size_t capacity);
/// Returns the locally assigned port of `x`.
/// @relates network_socket
expected<uint16_t> CAF_NET_EXPORT local_port(network_socket x);
/// Returns the locally assigned address of `x`.
/// @relates network_socket
expected<std::string> CAF_NET_EXPORT local_addr(network_socket x);
/// Returns the port used by the remote host of `x`.
/// @relates network_socket
expected<uint16_t> CAF_NET_EXPORT remote_port(network_socket x);
/// Returns the remote host address of `x`.
/// @relates network_socket
expected<std::string> CAF_NET_EXPORT remote_addr(network_socket x);
/// Closes the read channel for a socket.
/// @relates network_socket
void CAF_NET_EXPORT shutdown_read(network_socket x);
/// Closes the write channel for a socket.
/// @relates network_socket
void CAF_NET_EXPORT shutdown_write(network_socket x);
/// Closes the both read and write channel for a socket.
/// @relates network_socket
void CAF_NET_EXPORT shutdown(network_socket x);
} // 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/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/handshake_worker.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/tag/io_event_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
CAF_PUSH_WARNINGS
#include <openssl/err.h>
#include <openssl/ssl.h>
CAF_POP_WARNINGS
#include <memory>
#include <string>
#include <string_view>
// -- small wrappers to help working with OpenSSL ------------------------------
namespace caf::net::openssl {
struct deleter {
void operator()(SSL_CTX* ptr) const noexcept {
SSL_CTX_free(ptr);
}
void operator()(SSL* ptr) const noexcept {
SSL_free(ptr);
}
};
/// A smart pointer to an `SSL_CTX` structure.
/// @note technically, SSL structures are reference counted and we could use
/// `intrusive_ptr` instead. However, we have no need for shared ownership
/// semantics here and use `unique_ptr` for simplicity.
using ctx_ptr = std::unique_ptr<SSL_CTX, deleter>;
/// A smart pointer to an `SSL` structure.
/// @note technically, SSL structures are reference counted and we could use
/// `intrusive_ptr` instead. However, we have no need for shared ownership
/// semantics here and use `unique_ptr` for simplicity.
using conn_ptr = std::unique_ptr<SSL, deleter>;
/// Convenience function for creating an OpenSSL context for given method.
inline ctx_ptr make_ctx(const SSL_METHOD* method) {
if (auto ptr = SSL_CTX_new(method))
return ctx_ptr{ptr};
else
CAF_RAISE_ERROR("SSL_CTX_new failed");
}
/// Fetches a string representation for the last OpenSSL errors.
std::string fetch_error_str() {
auto cb = [](const char* cstr, size_t len, void* vptr) -> int {
auto& str = *reinterpret_cast<std::string*>(vptr);
if (str.empty()) {
str.assign(cstr, len);
} else {
str += "; ";
auto view = std::string_view{cstr, len};
str.insert(str.end(), view.begin(), view.end());
}
return 1;
};
std::string result;
ERR_print_errors_cb(cb, &result);
return result;
}
/// Loads the certificate into the SSL context.
inline error certificate_pem_file(const ctx_ptr& ctx, const std::string& path) {
auto cstr = path.c_str();
if (SSL_CTX_use_certificate_file(ctx.get(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Loads the private key into the SSL context.
inline error private_key_pem_file(const ctx_ptr& ctx, const std::string& path) {
auto cstr = path.c_str();
if (SSL_CTX_use_PrivateKey_file(ctx.get(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Convenience function for creating a new SSL structure from given context.
inline conn_ptr make_conn(const ctx_ptr& ctx) {
if (auto ptr = SSL_new(ctx.get()))
return conn_ptr{ptr};
else
CAF_RAISE_ERROR("SSL_new failed");
}
/// Convenience function for creating a new SSL structure from given context and
/// binding the given socket to it.
inline conn_ptr make_conn(const ctx_ptr& ctx, stream_socket fd) {
auto ptr = make_conn(ctx);
if (SSL_set_fd(ptr.get(), fd.id))
return ptr;
else
CAF_RAISE_ERROR("SSL_set_fd failed");
}
/// Manages an OpenSSL connection.
class policy {
public:
// -- constructors, destructors, and assignment operators --------------------
policy() = delete;
policy(const policy&) = delete;
policy& operator=(const policy&) = delete;
policy(policy&&) = default;
policy& operator=(policy&&) = default;
explicit policy(conn_ptr conn) : conn_(std::move(conn)) {
// nop
}
// -- factories --------------------------------------------------------------
/// Creates a policy from an SSL context and socket.
static policy make(const ctx_ptr& ctx, stream_socket fd) {
return policy{make_conn(ctx, fd)};
}
/// Creates a policy from an SSL method and socket.
static policy make(const SSL_METHOD* method, stream_socket fd) {
auto ctx = make_ctx(method);
return policy{make_conn(ctx, fd)};
}
// -- properties -------------------------------------------------------------
SSL* conn() {
return conn_.get();
}
// -- OpenSSL settings -------------------------------------------------------
/// Loads the certificate into the SSL connection object.
error certificate_pem_file(const std::string& path) {
auto cstr = path.c_str();
if (SSL_use_certificate_file(conn(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
/// Loads the private key into the SSL connection object.
error private_key_pem_file(const std::string& path) {
auto cstr = path.c_str();
if (SSL_use_PrivateKey_file(conn(), cstr, SSL_FILETYPE_PEM) > 0) {
return none;
} else {
return make_error(sec::invalid_argument, fetch_error_str());
}
}
// -- interface functions for the stream transport ---------------------------
/// Fetches a string representation for the last error.
std::string fetch_error_str() {
return openssl::fetch_error_str();
}
/// Reads data from the SSL connection into the buffer.
ptrdiff_t read(stream_socket, span<byte> buf) {
return SSL_read(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
/// Writes data from the buffer to the SSL connection.
ptrdiff_t write(stream_socket, span<const byte> buf) {
return SSL_write(conn_.get(), buf.data(), static_cast<int>(buf.size()));
}
/// Performs a TLS/SSL handshake with the server.
ptrdiff_t connect(stream_socket) {
return SSL_connect(conn_.get());
}
/// Waits for the client to performs a TLS/SSL handshake.
ptrdiff_t accept(stream_socket) {
return SSL_accept(conn_.get());
}
/// Returns the last SSL error.
stream_transport_error last_error(stream_socket fd, ptrdiff_t ret) {
switch (SSL_get_error(conn_.get(), static_cast<int>(ret))) {
case SSL_ERROR_NONE:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_CONNECT:
// For all of these, OpenSSL docs say to do the operation again later.
return stream_transport_error::temporary;
case SSL_ERROR_SYSCALL:
// Need to consult errno, which we just leave to the default policy.
return default_stream_transport_policy::last_error(fd, ret);
case SSL_ERROR_WANT_READ:
return stream_transport_error::want_read;
case SSL_ERROR_WANT_WRITE:
return stream_transport_error::want_write;
default:
// Errors like SSL_ERROR_WANT_X509_LOOKUP are technically temporary, but
// we do not configure any callbacks. So seeing this is a red flag.
return stream_transport_error::permanent;
}
}
/// Graceful shutdown.
void notify_close() {
SSL_shutdown(conn_.get());
}
/// Returns the number of bytes that are buffered internally and that are
/// available for immediate read.
size_t buffered() {
return static_cast<size_t>(SSL_pending(conn_.get()));
}
private:
/// Our SSL connection data.
openssl::conn_ptr conn_;
};
/// Asynchronously starts the TLS/SSL client handshake.
/// @param fd A connected stream socket.
/// @param mpx Pointer to the multiplexer for managing the asynchronous events.
/// @param pol The OpenSSL policy with properly configured SSL/TSL method.
/// @param on_success The callback for creating a @ref socket_manager after
/// successfully connecting to the server.
/// @param on_error The callback for unexpected errors.
/// @pre `fd != invalid_socket`
/// @pre `mpx != nullptr`
template <class Socket, class OnSuccess, class OnError>
void async_connect(Socket fd, multiplexer* mpx, policy pol,
OnSuccess on_success, OnError on_error) {
using res_t = decltype(on_success(fd, mpx, std::move(pol)));
using err_t = decltype(on_error(error{}));
static_assert(std::is_convertible_v<res_t, socket_manager_ptr>,
"on_success must return a socket_manager_ptr");
static_assert(std::is_same_v<err_t, void>,
"on_error may not return anything");
using factory_t = default_handshake_worker_factory<OnSuccess, OnError>;
using worker_t = handshake_worker<false, Socket, policy, factory_t>;
auto factory = factory_t{std::move(on_success), std::move(on_error)};
auto mgr = make_counted<worker_t>(fd, mpx, std::move(pol),
std::move(factory));
mpx->init(mgr);
}
/// Asynchronously starts the TLS/SSL server handshake.
/// @param fd A connected stream socket.
/// @param mpx Pointer to the multiplexer for managing the asynchronous events.
/// @param pol The OpenSSL policy with properly configured SSL/TSL method.
/// @param on_success The callback for creating a @ref socket_manager after
/// successfully connecting to the client.
/// @param on_error The callback for unexpected errors.
/// @pre `fd != invalid_socket`
/// @pre `mpx != nullptr`
template <class Socket, class OnSuccess, class OnError>
void async_accept(Socket fd, multiplexer* mpx, policy pol, OnSuccess on_success,
OnError on_error) {
using res_t = decltype(on_success(fd, mpx, std::move(pol)));
using err_t = decltype(on_error(error{}));
static_assert(std::is_convertible_v<res_t, socket_manager_ptr>,
"on_success must return a socket_manager_ptr");
static_assert(std::is_same_v<err_t, void>,
"on_error may not return anything");
using factory_t = default_handshake_worker_factory<OnSuccess, OnError>;
using worker_t = handshake_worker<true, Socket, policy, factory_t>;
auto factory = factory_t{std::move(on_success), std::move(on_error)};
auto mgr = make_counted<worker_t>(fd, mpx, std::move(pol),
std::move(factory));
mpx->init(mgr);
}
} // namespace caf::net::openssl
namespace caf::net {
/// Implements a stream_transport that manages a stream socket with encrypted
/// communication over OpenSSL.
template <class UpperLayer>
class openssl_transport
: public stream_transport_base<openssl::policy, UpperLayer> {
public:
// -- member types -----------------------------------------------------------
using super = stream_transport_base<openssl::policy, UpperLayer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit openssl_transport(openssl::conn_ptr conn, Ts&&... xs)
: super(openssl::policy{std::move(conn)}, std::forward<Ts>(xs)...) {
// nop
}
template <class... Ts>
openssl_transport(openssl::policy policy, Ts&&... xs)
: super(std::move(policy), std::forward<Ts>(xs)...) {
// nop
}
};
} // namespace caf::net
// 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 <cstdint>
#include <string>
#include <type_traits>
#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 {
/// Values for representing bitmask of I/O operations.
enum class operation {
none = 0b0000,
read = 0b0001,
write = 0b0010,
block_read = 0b0100,
block_write = 0b1000,
read_write = 0b0011,
read_only = 0b1001,
write_only = 0b0110,
shutdown = 0b1100,
};
/// @relates operation
[[nodiscard]] constexpr int to_integer(operation x) noexcept {
return static_cast<int>(x);
}
/// Adds the `read` flag to `x` unless the `block_read` bit is set.
/// @relates operation
[[nodiscard]] constexpr operation add_read_flag(operation x) noexcept {
if (auto bits = to_integer(x); !(bits & 0b0100))
return static_cast<operation>(bits | 0b0001);
else
return x;
}
/// Adds the `write` flag to `x` unless the `block_write` bit is set.
/// @relates operation
[[nodiscard]] constexpr operation add_write_flag(operation x) noexcept {
if (auto bits = to_integer(x); !(bits & 0b1000))
return static_cast<operation>(bits | 0b0010);
else
return x;
}
/// Removes the `read` flag from `x`.
/// @relates operation
[[nodiscard]] constexpr operation remove_read_flag(operation x) noexcept {
return static_cast<operation>(to_integer(x) & 0b1110);
}
/// Removes the `write` flag from `x`.
/// @relates operation
[[nodiscard]] constexpr operation remove_write_flag(operation x) noexcept {
return static_cast<operation>(to_integer(x) & 0b1101);
}
/// Adds the `block_read` flag to `x` and removes the `read` flag if present.
/// @relates operation
[[nodiscard]] constexpr operation block_reads(operation x) noexcept {
auto bits = to_integer(x) | 0b0100;
return static_cast<operation>(bits & 0b1110);
}
/// Adds the `block_write` flag to `x` and removes the `write` flag if present.
/// @relates operation
[[nodiscard]] constexpr operation block_writes(operation x) noexcept {
auto bits = to_integer(x) | 0b1000;
return static_cast<operation>(bits & 0b1101);
}
/// Returns whether the read flag is present in `x`.
/// @relates operation
[[nodiscard]] constexpr bool is_reading(operation x) noexcept {
return (to_integer(x) & 0b0001) == 0b0001;
}
/// Returns whether the write flag is present in `x`.
/// @relates operation
[[nodiscard]] constexpr bool is_writing(operation x) noexcept {
return (to_integer(x) & 0b0010) == 0b0010;
}
/// Returns `!is_reading(x) && !is_writing(x)`.
/// @relates operation
[[nodiscard]] constexpr bool is_idle(operation x) noexcept {
return (to_integer(x) & 0b0011) == 0b0000;
}
/// Returns whether the block read flag is present in `x`.
/// @relates operation
[[nodiscard]] constexpr bool is_read_blocked(operation x) noexcept {
return (to_integer(x) & 0b0100) == 0b0100;
}
/// Returns whether the block write flag is present in `x`.
/// @relates operation
[[nodiscard]] constexpr bool is_write_blocked(operation x) noexcept {
return (to_integer(x) & 0b1000) == 0b1000;
}
/// @relates operation
CAF_NET_EXPORT std::string to_string(operation x);
/// @relates operation
CAF_NET_EXPORT bool from_string(string_view, operation&);
/// @relates operation
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<operation>, operation&);
/// @relates operation
template <class Inspector>
bool inspect(Inspector& f, operation& x) {
return default_enum_inspect(f, x);
}
} // 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"
#include "caf/net/fwd.hpp"
#include "caf/span.hpp"
namespace caf::net {
/// Implements an interface for packet writing in application-layers.
class CAF_NET_EXPORT packet_writer {
public:
virtual ~packet_writer();
/// Returns a buffer for writing header information.
virtual byte_buffer next_header_buffer() = 0;
/// Returns a buffer for writing payload content.
virtual byte_buffer next_payload_buffer() = 0;
/// Convenience function to write a packet consisting of multiple buffers.
/// @param buffers all buffers for the packet. The first buffer is a header
/// buffer, the other buffers are payload buffer.
/// @warning this function takes ownership of `buffers`.
template <class... Ts>
void write_packet(Ts&... buffers) {
byte_buffer* bufs[] = {&buffers...};
write_impl(make_span(bufs, sizeof...(Ts)));
}
protected:
/// Implementing function for `write_packet`.
/// @param buffers a `span` containing all buffers of a packet.
virtual void write_impl(span<byte_buffer*> buffers) = 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/packet_writer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/span.hpp"
namespace caf::net {
/// Implements the interface for transport and application policies and
/// dispatches member functions either to `object` or `parent`.
template <class Object, class Parent>
class packet_writer_decorator final : public packet_writer {
public:
// -- member types -----------------------------------------------------------
using transport_type = typename Parent::transport_type;
using application_type = typename Parent::application_type;
// -- constructors, destructors, and assignment operators --------------------
packet_writer_decorator(Object& object, Parent& parent)
: object_(object), parent_(parent) {
// nop
}
// -- properties -------------------------------------------------------------
actor_system& system() {
return parent_.system();
}
transport_type& transport() {
return parent_.transport();
}
endpoint_manager& manager() {
return parent_.manager();
}
byte_buffer next_header_buffer() override {
return transport().next_header_buffer();
}
byte_buffer next_payload_buffer() override {
return transport().next_payload_buffer();
}
protected:
void write_impl(span<byte_buffer*> buffers) override {
parent_.write_packet(object_.id(), buffers);
}
private:
Object& object_;
Parent& parent_;
};
template <class Object, class Parent>
packet_writer_decorator<Object, Parent>
make_packet_writer_decorator(Object& object, Parent& parent) {
return {object, parent};
}
} // 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 <cstddef>
#include <system_error>
#include <utility>
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_id.hpp"
// Note: This API mostly wraps platform-specific functions that return ssize_t.
// We return ptrdiff_t instead, since only POSIX defines ssize_t and the two
// types are functionally equivalent.
namespace caf::net {
/// A unidirectional communication endpoint for inter-process communication.
struct CAF_NET_EXPORT pipe_socket : socket {
using super = socket;
using super::super;
};
/// Creates two connected sockets. The first socket is the read handle and the
/// second socket is the write handle.
/// @relates pipe_socket
expected<std::pair<pipe_socket, pipe_socket>> CAF_NET_EXPORT make_pipe();
/// Transmits data from `x` to its peer.
/// @param x Connected endpoint.
/// @param buf Memory region for reading the message to send.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates pipe_socket
ptrdiff_t CAF_NET_EXPORT write(pipe_socket x, span<const byte> buf);
/// Receives data from `x`.
/// @param x Connected endpoint.
/// @param buf Memory region for storing the received bytes.
/// @returns The number of received bytes on success, otherwise an error code.
/// @relates pipe_socket
ptrdiff_t CAF_NET_EXPORT read(pipe_socket x, span<byte> buf);
} // 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 <array>
#include <cstdint>
#include <mutex>
#include "caf/byte.hpp"
#include "caf/net/pipe_socket.hpp"
#include "caf/net/socket_manager.hpp"
namespace caf::net {
class pollset_updater : public socket_manager {
public:
// -- member types -----------------------------------------------------------
using super = socket_manager;
using msg_buf = std::array<byte, sizeof(intptr_t) + 1>;
enum class code : uint8_t {
register_reading,
continue_reading,
register_writing,
continue_writing,
init_manager,
discard_manager,
shutdown_reading,
shutdown_writing,
run_action,
shutdown,
};
// -- constructors, destructors, and assignment operators --------------------
pollset_updater(pipe_socket read_handle, multiplexer* parent);
~pollset_updater() override;
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
pipe_socket handle() const noexcept {
return socket_cast<pipe_socket>(handle_);
}
// -- interface functions ----------------------------------------------------
error init(const settings& config) override;
read_result handle_read_event() override;
read_result handle_buffered_data() override;
read_result handle_continue_reading() override;
write_result handle_write_event() override;
write_result handle_continue_writing() override;
void handle_error(sec code) override;
private:
msg_buf buf_;
size_t buf_size_ = 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 <memory>
#include <new>
#include "caf/async/producer.hpp"
#include "caf/flow/observer.hpp"
#include "caf/flow/subscription.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/ref_counted.hpp"
namespace caf::net {
/// Connects a socket manager to an asynchronous producer resource.
template <class Buffer>
class producer_adapter final : public ref_counted, public async::producer {
public:
using atomic_count = std::atomic<size_t>;
using buf_ptr = intrusive_ptr<Buffer>;
using value_type = typename Buffer::value_type;
void on_consumer_ready() override {
// nop
}
void on_consumer_cancel() override {
mgr_->mpx().schedule_fn([adapter = strong_this()] { //
adapter->on_cancel();
});
}
void on_consumer_demand(size_t) override {
mgr_->continue_reading();
}
void ref_producer() const noexcept override {
this->ref();
}
void deref_producer() const noexcept override {
this->deref();
}
/// Tries to open the resource for writing.
/// @returns a connected adapter that writes to the resource on success,
/// `nullptr` otherwise.
template <class Resource>
static intrusive_ptr<producer_adapter>
try_open(socket_manager* owner, Resource src) {
CAF_ASSERT(owner != nullptr);
if (auto buf = src.try_open()) {
using ptr_type = intrusive_ptr<producer_adapter>;
auto adapter = ptr_type{new producer_adapter(owner, buf), false};
buf->set_producer(adapter);
return adapter;
} else {
return nullptr;
}
}
/// Makes `item` available to the consumer.
/// @returns the remaining demand.
size_t push(const value_type& item) {
if (buf_) {
return buf_->push(item);
} else {
return 0;
}
}
/// Makes `items` available to the consumer.
/// @returns the remaining demand.
size_t push(span<const value_type> items) {
if (buf_) {
return buf_->push(items);
} else {
return 0;
}
}
void close() {
if (buf_) {
buf_->close();
buf_ = nullptr;
mgr_ = nullptr;
}
}
void abort(error reason) {
if (buf_) {
buf_->abort(std::move(reason));
buf_ = nullptr;
mgr_ = nullptr;
}
}
friend void intrusive_ptr_add_ref(const producer_adapter* ptr) noexcept {
ptr->ref();
}
friend void intrusive_ptr_release(const producer_adapter* ptr) noexcept {
ptr->deref();
}
private:
producer_adapter(socket_manager* owner, buf_ptr buf)
: mgr_(owner), buf_(std::move(buf)) {
// nop
}
void on_cancel() {
if (buf_) {
mgr_->mpx().shutdown_reading(mgr_);
buf_ = nullptr;
mgr_ = nullptr;
}
}
auto strong_this() {
return intrusive_ptr{this};
}
intrusive_ptr<socket_manager> mgr_;
intrusive_ptr<Buffer> buf_;
};
template <class T>
using producer_adapter_ptr = intrusive_ptr<producer_adapter<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.
#pragma once
#include <cstdint>
namespace caf::net {
struct receive_policy {
uint32_t min_size;
uint32_t max_size;
/// @pre `min_size > 0`
/// @pre `min_size <= max_size`
static constexpr receive_policy between(uint32_t min_size,
uint32_t max_size) {
return {min_size, max_size};
}
/// @pre `size > 0`
static constexpr receive_policy exactly(uint32_t size) {
return {size, size};
}
static constexpr receive_policy up_to(uint32_t max_size) {
return {1, max_size};
}
static constexpr receive_policy stop() {
return {0, 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 <string>
#include <system_error>
#include <type_traits>
#include "caf/config.hpp"
#include "caf/detail/comparable.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/socket_id.hpp"
namespace caf::net {
/// An internal endpoint for sending or receiving data. Can be either a
/// ::network_socket, ::pipe_socket, ::stream_socket, or ::datagram_socket.
struct CAF_NET_EXPORT socket : detail::comparable<socket> {
socket_id id;
constexpr socket() noexcept : id(invalid_socket_id) {
// nop
}
constexpr explicit socket(socket_id id) noexcept : id(id) {
// nop
}
constexpr socket(const socket& other) noexcept = default;
socket& operator=(const socket& other) noexcept = default;
constexpr signed_socket_id compare(socket other) const noexcept {
return static_cast<signed_socket_id>(id)
- static_cast<signed_socket_id>(other.id);
}
};
/// @relates socket
template <class Inspector>
bool inspect(Inspector& f, socket& x) {
return f.object(x).fields(f.field("id", x.id));
}
/// Denotes the invalid socket.
constexpr auto invalid_socket = socket{invalid_socket_id};
/// Converts between different socket types.
template <class To, class From>
To socket_cast(From x) {
return To{x.id};
}
/// Closes socket `x`.
/// @relates socket
void CAF_NET_EXPORT close(socket x);
/// Returns the last socket error in this thread as an integer.
/// @relates socket
std::errc CAF_NET_EXPORT last_socket_error();
/// Checks whether `last_socket_error()` would return an error code that
/// indicates a temporary error.
/// @returns `true` if `last_socket_error()` returned either
/// `std::errc::operation_would_block` or
/// `std::errc::resource_unavailable_try_again`, `false` otherwise.
/// @relates socket
bool CAF_NET_EXPORT last_socket_error_is_temporary();
/// Returns the last socket error as human-readable string.
/// @relates socket
std::string CAF_NET_EXPORT last_socket_error_as_string();
/// Queries whether `x` is a valid and connected socket by reading the socket
/// option `SO_ERROR`. Sets the last socket error in case the socket entered an
/// error state.
/// @returns `true` if no error is pending on `x`, `false` otherwise.
/// @relates socket
bool CAF_NET_EXPORT probe(socket x);
/// Sets x to be inherited by child processes if `new_value == true`
/// or not if `new_value == false`. Not implemented on Windows.
/// @relates socket
error CAF_NET_EXPORT child_process_inherit(socket x, bool new_value);
/// Enables or disables nonblocking I/O on `x`.
/// @relates socket
error CAF_NET_EXPORT nonblocking(socket x, bool new_value);
/// Disallows further reads from the socket.
/// @relates socket
error CAF_NET_EXPORT shutdown_read(socket x);
/// Disallows further writes to the socket.
/// @relates socket
error CAF_NET_EXPORT shutdown_write(socket x);
} // 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_id.hpp"
namespace caf::net {
/// Closes the guarded socket when destroyed.
template <class Socket>
class socket_guard {
public:
socket_guard() noexcept : sock_(invalid_socket_id) {
// nop
}
explicit socket_guard(Socket sock) noexcept : sock_(sock) {
// nop
}
socket_guard(socket_guard&& other) noexcept : sock_(other.release()) {
// nop
}
socket_guard(const socket_guard&) = delete;
socket_guard& operator=(socket_guard&& other) noexcept {
reset(other.release());
return *this;
}
socket_guard& operator=(const socket_guard&) = delete;
~socket_guard() {
if (sock_.id != invalid_socket_id)
close(sock_);
}
void reset(Socket x) noexcept {
if (sock_.id != invalid_socket_id)
close(sock_);
sock_ = x;
}
Socket release() noexcept {
auto sock = sock_;
sock_.id = invalid_socket_id;
return sock;
}
Socket socket() const noexcept {
return sock_;
}
private:
Socket sock_;
};
template <class Socket>
socket_guard<Socket> make_socket_guard(Socket sock) {
return socket_guard<Socket>{sock};
}
} // 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 <cstddef>
#include <limits>
#include <type_traits>
#include "caf/config.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
/// Platform-specific representation of a socket.
/// @relates socket
using socket_id = size_t;
/// Identifies the invalid socket.
constexpr socket_id invalid_socket_id = std::numeric_limits<socket_id>::max();
#else // CAF_WINDOWS
/// Platform-specific representation of a socket.
/// @relates socket
using socket_id = int;
/// Identifies the invalid socket.
constexpr socket_id invalid_socket_id = -1;
#endif // CAF_WINDOWS
/// Signed counterpart of `socket_id`.
/// @relates socket
using signed_socket_id = std::make_signed<socket_id>::type;
} // 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/actor.hpp"
#include "caf/actor_system.hpp"
#include "caf/callback.hpp"
#include "caf/detail/infer_actor_shell_ptr_type.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/fwd.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/typed_actor_shell.hpp"
#include "caf/ref_counted.hpp"
#include "caf/sec.hpp"
#include "caf/tag/io_event_oriented.hpp"
namespace caf::net {
/// Manages the lifetime of a single socket and handles any I/O events on it.
class CAF_NET_EXPORT socket_manager : public ref_counted {
public:
// -- member types -----------------------------------------------------------
/// 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,
};
/// 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,
};
/// Stores manager-related flags in a single block.
struct flags_t {
bool read_closed : 1;
bool write_closed : 1;
};
// -- constructors, destructors, and assignment operators --------------------
/// @pre `handle != invalid_socket`
/// @pre `mpx!= nullptr`
socket_manager(socket handle, multiplexer* mpx);
~socket_manager() override;
socket_manager(const socket_manager&) = delete;
socket_manager& operator=(const socket_manager&) = delete;
// -- properties -------------------------------------------------------------
/// Returns the managed socket.
socket handle() const {
return handle_;
}
/// @private
void handle(socket new_handle) {
handle_ = new_handle;
}
/// Returns a reference to the hosting @ref actor_system instance.
actor_system& system() noexcept;
/// Returns the owning @ref multiplexer instance.
multiplexer& mpx() noexcept {
return *mpx_;
}
/// Returns the owning @ref multiplexer instance.
const multiplexer& mpx() const noexcept {
return *mpx_;
}
/// Returns a pointer to the owning @ref multiplexer instance.
multiplexer* mpx_ptr() noexcept {
return mpx_;
}
/// Returns a pointer to the owning @ref multiplexer instance.
const multiplexer* mpx_ptr() const noexcept {
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;
}
/// Returns whether the manager closed write operations on the socket.
[[nodiscard]] bool write_closed() const noexcept {
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.
/// @thread-safe
void register_reading();
/// Registers the manager for write operations on the @ref multiplexer.
/// @thread-safe
void register_writing();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new demand in asynchronous
/// buffer that allow the manager to push available data downstream. The event
/// is a no-op if the manager is already registered for reading.
/// @thread-safe
void continue_reading();
/// Schedules a call to `handle_continue_reading` on the @ref multiplexer.
/// This mechanism allows users to signal changes in the environment to the
/// manager that allow it to make progress, e.g., new data for writing in an
/// asynchronous buffer. The event is a no-op if the manager is already
/// registered for writing.
/// @thread-safe
void continue_writing();
// -- callbacks for the multiplexer ------------------------------------------
/// Performs a handover to another manager after `handle_read_event` or
/// `handle_read_event` returned `handover`.
socket_manager_ptr do_handover();
// -- pure virtual member functions ------------------------------------------
/// Initializes the manager and its all of its sub-components.
virtual error init(const settings& config) = 0;
/// Called whenever the socket received new data.
virtual read_result handle_read_event() = 0;
/// Called after handovers to allow the manager to process any data that is
/// already buffered at the transport policy and thus would not trigger a read
/// event on the socket.
virtual read_result handle_buffered_data() = 0;
/// Restarts a socket manager that suspended reads. Calling this member
/// function on active managers is a no-op. This function also should read any
/// data buffered outside of the socket.
virtual read_result handle_continue_reading() = 0;
/// Called whenever the socket is allowed to send data.
virtual write_result handle_write_event() = 0;
/// Restarts a socket manager that suspended writes. Calling this member
/// function on active managers is a no-op.
virtual write_result handle_continue_writing() = 0;
/// Called when the remote side becomes unreachable due to an error.
/// @param code The error code as reported by the operating system.
virtual void handle_error(sec code) = 0;
/// Returns the new manager for the socket 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);
protected:
// -- protected member variables ---------------------------------------------
socket handle_;
multiplexer* mpx_;
private:
// -- private member variables -----------------------------------------------
error abort_reason_;
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_;
};
/// @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
// 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
// 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 <cstddef>
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/net/network_socket.hpp"
// Note: This API mostly wraps platform-specific functions that return ssize_t.
// We return ptrdiff_t instead, since only POSIX defines ssize_t and the two
// types are functionally equivalent.
namespace caf::net {
/// A connection-oriented network communication endpoint for bidirectional byte
/// streams.
struct CAF_NET_EXPORT stream_socket : network_socket {
using super = network_socket;
using super::super;
};
/// Creates two connected sockets to mimic network communication (usually for
/// testing purposes).
/// @relates stream_socket
expected<std::pair<stream_socket, stream_socket>>
CAF_NET_EXPORT make_stream_socket_pair();
/// Enables or disables keepalive on `x`.
/// @relates network_socket
error CAF_NET_EXPORT keepalive(stream_socket x, bool new_value);
/// Enables or disables Nagle's algorithm on `x`.
/// @relates stream_socket
error CAF_NET_EXPORT nodelay(stream_socket x, bool new_value);
/// Receives data from `x`.
/// @param x A connected endpoint.
/// @param buf Points to destination buffer.
/// @returns The number of received bytes on success, 0 if the socket is closed,
/// or -1 in case of an error.
/// @relates stream_socket
/// @post Either the functions returned a non-negative integer or the caller can
/// retrieve the error code by calling `last_socket_error()`.
ptrdiff_t CAF_NET_EXPORT read(stream_socket x, span<byte> buf);
/// Sends data to `x`.
/// @param x A connected endpoint.
/// @param buf Points to the message to send.
/// @returns The number of received bytes on success, 0 if the socket is closed,
/// or -1 in case of an error.
/// @relates stream_socket
/// @post either the result is a `sec` or a positive (non-zero) integer
ptrdiff_t CAF_NET_EXPORT write(stream_socket x, span<const byte> buf);
/// Transmits data from `x` to its peer.
/// @param x A connected endpoint.
/// @param bufs Points to the message to send, scattered across up to 10
/// buffers.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates stream_socket
/// @post either the result is a `sec` or a positive (non-zero) integer
/// @pre `bufs.size() < 10`
ptrdiff_t CAF_NET_EXPORT write(stream_socket x,
std::initializer_list<span<const byte>> bufs);
} // 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 <deque>
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/has_after_reading.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_oriented_layer_ptr.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport_error.hpp"
#include "caf/sec.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#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 {
public:
/// Reads data from the socket into the buffer.
static ptrdiff_t read(stream_socket x, span<byte> buf) {
return net::read(x, buf);
}
/// Writes data from the buffer to the socket.
static ptrdiff_t write(stream_socket x, span<const byte> buf) {
return net::write(x, buf);
}
/// 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;
}
/// 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;
}
/// 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;
}
/// Returns the number of bytes that are buffered internally and that
/// available for immediate read.
static constexpr size_t buffered() {
return 0;
}
};
/// Implements a stream_transport that manages a stream socket.
template <class Policy, class UpperLayer>
class stream_transport_base {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::io_event_oriented;
using output_tag = tag::stream_oriented;
using socket_type = stream_socket;
using read_result = typename socket_manager::read_result;
using write_result = typename socket_manager::write_result;
/// Bundles various flags into a single block of memory.
struct flags_t {
/// Stores whether we left a read handler due to want_write.
bool wanted_read_from_write_event : 1;
/// Stores whether we left a write handler due to want_read.
bool wanted_write_from_read_event : 1;
};
// -- constants --------------------------------------------------------------
static constexpr size_t default_buf_size = 4 * 1024; // 4 KiB
// -- 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);
}
// -- interface for stream_oriented_layer_ptr --------------------------------
template <class ParentPtr>
bool can_send_more(ParentPtr) const noexcept {
return write_buf_.size() < max_write_buf_size_;
}
template <class ParentPtr>
static socket_type handle(ParentPtr parent) noexcept {
return parent->handle();
}
template <class ParentPtr>
void begin_output(ParentPtr parent) {
if (write_buf_.empty())
parent->register_writing();
}
template <class ParentPtr>
byte_buffer& output_buffer(ParentPtr) {
return write_buf_;
}
template <class ParentPtr>
static constexpr void end_output(ParentPtr) {
// nop
}
template <class ParentPtr>
static void abort_reason(ParentPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
template <class ParentPtr>
static const error& abort_reason(ParentPtr parent) {
return parent->abort_reason();
}
template <class ParentPtr>
void configure_read(ParentPtr parent, receive_policy policy) {
if (policy.max_size > 0 && max_read_size_ == 0) {
parent->register_reading();
}
min_read_size_ = policy.min_size;
max_read_size_ = policy.max_size;
}
template <class ParentPtr>
bool stopped(ParentPtr) const noexcept {
return max_read_size_ == 0;
}
// -- properties -------------------------------------------------------------
auto& read_buffer() noexcept {
return read_buf_;
}
const auto& read_buffer() const noexcept {
return read_buf_;
}
auto& write_buffer() noexcept {
return write_buf_;
}
const auto& write_buffer() const noexcept {
return write_buf_;
}
auto& upper_layer() noexcept {
return upper_layer_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
}
// -- initialization ---------------------------------------------------------
template <class ParentPtr>
error init(socket_manager* owner, ParentPtr parent, const settings& config) {
namespace mm = defaults::middleman;
auto default_max_reads = static_cast<uint32_t>(mm::max_consecutive_reads);
max_consecutive_reads_ = get_or(config,
"caf.middleman.max-consecutive-reads",
default_max_reads);
auto sock = parent->handle();
if constexpr (std::is_base_of<tcp_stream_socket, decltype(sock)>::value) {
if (auto err = nodelay(sock, true)) {
CAF_LOG_ERROR("nodelay failed: " << err);
return err;
}
}
if (auto socket_buf_size = send_buffer_size(parent->handle())) {
max_write_buf_size_ = *socket_buf_size;
CAF_ASSERT(max_write_buf_size_ > 0);
write_buf_.reserve(max_write_buf_size_ * 2);
} else {
CAF_LOG_ERROR("send_buffer_size: " << socket_buf_size.error());
return std::move(socket_buf_size.error());
}
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
return upper_layer_.init(owner, this_layer_ptr, config);
}
// -- event callbacks --------------------------------------------------------
template <class ParentPtr>
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);
}
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;
}
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);
}
}
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);
}
}
template <class ParentPtr>
write_result handle_continue_writing(ParentPtr) {
// TODO: consider whether we need another callback for the upper layer.
// For now, we always return `again`, which triggers the write
// handler later.
return write_result::again;
}
template <class ParentPtr>
void abort(ParentPtr parent, const error& reason) {
auto this_layer_ptr = make_stream_oriented_layer_ptr(this, parent);
upper_layer_.abort(this_layer_ptr, reason);
}
private:
template <class ThisLayerPtr>
void after_reading([[maybe_unused]] ThisLayerPtr& this_layer_ptr) {
if constexpr (detail::has_after_reading_v<UpperLayer, ThisLayerPtr>)
upper_layer_.after_reading(this_layer_ptr);
}
flags_t flags;
/// Caches the config parameter for limiting max. socket operations.
uint32_t max_consecutive_reads_ = 0;
/// Caches the write buffer size of the socket.
uint32_t max_write_buf_size_ = 0;
/// Stores what the user has configured as read threshold.
uint32_t min_read_size_ = 0;
/// Stores what the user has configured as max. number of bytes to receive.
uint32_t max_read_size_ = 0;
/// Stores how many bytes are currently buffered, i.e., how many bytes from
/// `read_buf_` are filled with actual data.
size_t buffered_ = 0;
/// Stores the offset in `read_buf_` since last calling
/// `upper_layer_.consume`.
size_t delta_offset_ = 0;
/// Caches incoming data.
byte_buffer read_buf_;
/// Caches outgoing data.
byte_buffer write_buf_;
/// Processes incoming data and generates outgoing data.
UpperLayer upper_layer_;
/// Configures how we read and write to the socket.
Policy policy_;
};
/// 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 -----------------------------------------------------------
using super
= stream_transport_base<default_stream_transport_policy, UpperLayer>;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit stream_transport(Ts&&... xs)
: super(default_stream_transport_policy{}, std::forward<Ts>(xs)...) {
// nop
}
};
} // 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/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/fwd.hpp"
#include "caf/is_error_code_enum.hpp"
#include <cstdint>
#include <string>
#include <type_traits>
namespace caf::net {
enum class stream_transport_error {
/// Indicates that the transport should try again later.
temporary,
/// Indicates that the transport must read data before trying again.
want_read,
/// Indicates that the transport must write data before trying again.
want_write,
/// Indicates that the transport cannot resume this operation.
permanent,
};
/// @relates stream_transport_error
CAF_NET_EXPORT std::string to_string(stream_transport_error);
/// @relates stream_transport_error
CAF_NET_EXPORT bool from_string(string_view, stream_transport_error&);
/// @relates stream_transport_error
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<stream_transport_error>,
stream_transport_error&);
/// @relates stream_transport_error
template <class Inspector>
bool inspect(Inspector& f, stream_transport_error& x) {
return default_enum_inspect(f, x);
}
} // namespace caf::net
// 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/ip_endpoint.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/network_socket.hpp"
#include "caf/uri.hpp"
namespace caf::net {
/// Represents a TCP acceptor in listening mode.
struct CAF_NET_EXPORT tcp_accept_socket : network_socket {
using super = network_socket;
using super::super;
using connected_socket_type = tcp_stream_socket;
};
/// Creates a new TCP socket to accept connections on a given port.
/// @param node The endpoint to listen on and the filter for incoming addresses.
/// Passing the address `0.0.0.0` will accept incoming connection from any host.
/// Passing port 0 lets the OS choose the port.
/// @relates tcp_accept_socket
expected<tcp_accept_socket>
CAF_NET_EXPORT make_tcp_accept_socket(ip_endpoint node,
bool reuse_addr = false);
/// Creates a new TCP socket to accept connections on a given port.
/// @param node The endpoint to listen on and the filter for incoming addresses.
/// Passing the address `0.0.0.0` will accept incoming connection from any host.
/// Passing port 0 lets the OS choose the port.
/// @param reuse_addr Optionally sets the SO_REUSEADDR option on the socket.
/// @relates tcp_accept_socket
expected<tcp_accept_socket>
CAF_NET_EXPORT make_tcp_accept_socket(const uri::authority_type& node,
bool reuse_addr = false);
/// Accepts a connection on `x`.
/// @param x Listening endpoint.
/// @returns The socket that handles the accepted connection on success, an
/// error otherwise.
/// @relates tcp_accept_socket
expected<tcp_stream_socket> CAF_NET_EXPORT accept(tcp_accept_socket x);
} // 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/ip_endpoint.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/timespan.hpp"
#include "caf/uri.hpp"
namespace caf::net {
/// Represents a TCP connection.
struct CAF_NET_EXPORT tcp_stream_socket : stream_socket {
using super = stream_socket;
using super::super;
};
/// Creates a `tcp_stream_socket` connected to given remote node.
/// @param node Host and port of the remote node.
/// @param timeout Maximum waiting time on the connection before canceling it.
/// @returns The connected socket or an error.
/// @relates tcp_stream_socket
expected<tcp_stream_socket> CAF_NET_EXPORT //
make_connected_tcp_stream_socket(ip_endpoint node, timespan timeout = infinite);
/// Creates a `tcp_stream_socket` connected to @p auth.
/// @param auth Host and port of the remote node.
/// @param timeout Maximum waiting time on the connection before canceling it.
/// @returns The connected socket or an error.
/// @note The timeout applies to a *single* connection attempt. If the DNS
/// lookup for @p auth returns more than one possible IP address then the
/// @p timeout applies to each connection attempt individually. For
/// example, passing a timeout of one second with a DNS result of five
/// entries would mean this function can block up to five seconds if all
/// attempts time out.
/// @relates tcp_stream_socket
expected<tcp_stream_socket> CAF_NET_EXPORT //
make_connected_tcp_stream_socket(const uri::authority_type& auth,
timespan timeout = infinite);
/// Creates a `tcp_stream_socket` connected to given @p host and @p port.
/// @param host TCP endpoint for connecting to.
/// @param port TCP port of the server.
/// @param timeout Maximum waiting time on the connection before canceling it.
/// @returns The connected socket or an error.
/// @note The timeout applies to a *single* connection attempt. If the DNS
/// lookup for @p auth returns more than one possible IP address then the
/// @p timeout applies to each connection attempt individually. For
/// example, passing a timeout of one second with a DNS result of five
/// entries would mean this function can block up to five seconds if all
/// attempts time out.
/// @relates tcp_stream_socket
expected<tcp_stream_socket> CAF_NET_EXPORT //
make_connected_tcp_stream_socket(std::string host, uint16_t port,
timespan timeout = infinite);
} // 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 <stdexcept>
#include "caf/detail/net_export.hpp"
#include "caf/error.hpp"
#include "caf/net/host.hpp"
#include "caf/raise_error.hpp"
namespace {
struct CAF_NET_EXPORT host_fixture {
host_fixture() {
if (auto err = caf::net::this_host::startup())
CAF_RAISE_ERROR(std::logic_error, "this_host::startup failed");
}
~host_fixture() {
caf::net::this_host::cleanup();
}
};
} // namespace
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/overload.hpp"
#include "caf/fwd.hpp"
#include "caf/logger.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/receive_policy.hpp"
namespace caf::net {
/// Implements a base class for transports.
/// @tparam Transport The derived type of the transport implementation.
/// @tparam NextLayer The Following Layer. Either `transport_worker` or
/// `transport_worker_dispatcher`
/// @tparam Handle The type of the related socket_handle.
/// @tparam Application The type of the application used in this stack.
/// @tparam IdType The id type of the derived transport, must match the IdType
/// of `NextLayer`.
template <class Transport, class NextLayer, class Handle, class Application,
class IdType>
class transport_base {
public:
// -- member types -----------------------------------------------------------
using next_layer_type = NextLayer;
using handle_type = Handle;
using transport_type = Transport;
using application_type = Application;
using id_type = IdType;
using buffer_cache_type = std::vector<byte_buffer>;
// -- constructors, destructors, and assignment operators --------------------
transport_base(handle_type handle, application_type application)
: next_layer_(std::move(application)), handle_(handle), manager_(nullptr) {
// nop
}
// -- properties -------------------------------------------------------------
/// Returns the socket_handle of this transport.
handle_type handle() const noexcept {
return handle_;
}
/// Returns a reference to the `actor_system` of this transport.
/// @pre `init` must be called before calling this getter.
actor_system& system() {
return manager().system();
}
/// Returns a reference to the application of this transport.
application_type& application() {
return next_layer_.application();
}
/// Returns a reference to this transport.
transport_type& transport() {
return *reinterpret_cast<transport_type*>(this);
}
/// Returns a reference to the `endpoint_manager` of this transport.
/// @pre `init` must be called before calling this getter.
endpoint_manager& manager() {
CAF_ASSERT(manager_);
return *manager_;
}
// -- transport member functions ---------------------------------------------
/// Initializes this transport.
/// @param parent The `endpoint_manager` that manages this transport.
/// @returns `error` on failure, none on success.
virtual error init(endpoint_manager& parent) {
CAF_LOG_TRACE("");
manager_ = &parent;
auto& cfg = system().config();
max_consecutive_reads_ = get_or(this->system().config(),
"caf.middleman.max-consecutive-reads",
defaults::middleman::max_consecutive_reads);
auto max_header_bufs = get_or(cfg, "caf.middleman.max-header-buffers",
defaults::middleman::max_header_buffers);
header_bufs_.reserve(max_header_bufs);
auto max_payload_bufs = get_or(cfg, "caf.middleman.max-payload-buffers",
defaults::middleman::max_payload_buffers);
payload_bufs_.reserve(max_payload_bufs);
if (auto err = next_layer_.init(*this))
return err;
return none;
}
/// Resolves a remote actor using `locator` and sends the resolved actor to
/// listener on success - an `error` otherwise.
/// @param locator The `uri` of the remote actor.
/// @param listener The `actor_handle` which the result should be sent to.
auto resolve(endpoint_manager&, const uri& locator, const actor& listener) {
CAF_LOG_TRACE(CAF_ARG(locator) << CAF_ARG(listener));
auto f = detail::make_overload(
[&](auto& layer) -> decltype(layer.resolve(*this, locator, listener)) {
return layer.resolve(*this, locator, listener);
},
[&](auto& layer) -> decltype(layer.resolve(*this, locator.path(),
listener)) {
return layer.resolve(*this, locator.path(), listener);
});
f(next_layer_);
}
/// Gets called by an actor proxy after creation.
/// @param peer The `node_id` of the remote node.
/// @param id The id of the remote actor.
void new_proxy(endpoint_manager&, const node_id& peer, actor_id id) {
next_layer_.new_proxy(*this, peer, id);
}
/// Notifies the remote endpoint that the local actor is down.
/// @param peer The `node_id` of the remote endpoint.
/// @param id The `actor_id` of the remote actor.
/// @param reason The reason why the local actor has shut down.
void local_actor_down(endpoint_manager&, const node_id& peer, actor_id id,
error reason) {
next_layer_.local_actor_down(*this, peer, id, std::move(reason));
}
/// Callback for when an error occurs.
/// @param code The error code to handle.
void handle_error(sec code) {
next_layer_.handle_error(code);
}
// -- (pure) virtual functions -----------------------------------------------
/// Called by the endpoint manager when the transport can read data from its
/// socket.
virtual bool handle_read_event(endpoint_manager&) = 0;
/// Called by the endpoint manager when the transport can write data to its
/// socket.
virtual bool handle_write_event(endpoint_manager& parent) = 0;
/// Queues a packet scattered across multiple buffers to be sent via this
/// transport.
/// @param id The id of the destination endpoint.
/// @param buffers Pointers to the buffers that make up the packet content.
virtual void write_packet(id_type id, span<byte_buffer*> buffers) = 0;
// -- buffer management ------------------------------------------------------
/// Returns the next cached header buffer or creates a new one if no buffers
/// are cached.
byte_buffer next_header_buffer() {
return next_buffer_impl(header_bufs_);
}
/// Returns the next cached payload buffer or creates a new one if no buffers
/// are cached.
byte_buffer next_payload_buffer() {
return next_buffer_impl(payload_bufs_);
}
private:
// -- utility functions ------------------------------------------------------
static byte_buffer next_buffer_impl(buffer_cache_type& cache) {
if (cache.empty())
return {};
auto buf = std::move(cache.back());
cache.pop_back();
return buf;
}
protected:
next_layer_type next_layer_;
handle_type handle_;
buffer_cache_type header_bufs_;
buffer_cache_type payload_bufs_;
byte_buffer read_buf_;
endpoint_manager* manager_;
size_t max_consecutive_reads_;
};
} // 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/logger.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/unit.hpp"
namespace caf::net {
/// Implements a worker for transport protocols.
template <class Application, class IdType>
class transport_worker {
public:
// -- member types -----------------------------------------------------------
using id_type = IdType;
using application_type = Application;
// -- constructors, destructors, and assignment operators --------------------
explicit transport_worker(application_type application,
id_type id = id_type{})
: application_(std::move(application)), id_(std::move(id)) {
// nop
}
// -- properties -------------------------------------------------------------
application_type& application() noexcept {
return application_;
}
const application_type& application() const noexcept {
return application_;
}
const id_type& id() const noexcept {
return id_;
}
// -- member functions -------------------------------------------------------
template <class Parent>
error init(Parent& parent) {
auto writer = make_packet_writer_decorator(*this, parent);
return application_.init(writer);
}
template <class Parent>
error handle_data(Parent& parent, span<const byte> data) {
auto writer = make_packet_writer_decorator(*this, parent);
return application_.handle_data(writer, data);
}
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto writer = make_packet_writer_decorator(*this, parent);
if (auto err = application_.write_message(writer, std::move(msg)))
CAF_LOG_ERROR("write_message failed: " << err);
}
template <class Parent>
void resolve(Parent& parent, string_view path, const actor& listener) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.resolve(writer, path, listener);
}
template <class Parent>
void new_proxy(Parent& parent, const node_id&, actor_id id) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.new_proxy(writer, id);
}
template <class Parent>
void
local_actor_down(Parent& parent, const node_id&, actor_id id, error reason) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.local_actor_down(writer, id, std::move(reason));
}
template <class Parent>
void timeout(Parent& parent, std::string tag, uint64_t id) {
auto writer = make_packet_writer_decorator(*this, parent);
application_.timeout(writer, std::move(tag), id);
}
void handle_error(sec error) {
application_.handle_error(error);
}
private:
application_type application_;
id_type id_;
};
template <class Application, class IdType = unit_t>
using transport_worker_ptr
= std::shared_ptr<transport_worker<Application, IdType>>;
} // 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 <unordered_map>
#include "caf/logger.hpp"
#include "caf/net/endpoint_manager_queue.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/packet_writer_decorator.hpp"
#include "caf/net/transport_worker.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
namespace caf::net {
/// Implements a dispatcher that dispatches between transport and workers.
template <class Factory, class IdType>
class transport_worker_dispatcher {
public:
// -- member types -----------------------------------------------------------
using id_type = IdType;
using factory_type = Factory;
using application_type = typename factory_type::application_type;
using worker_type = transport_worker<application_type, id_type>;
using worker_ptr = transport_worker_ptr<application_type, id_type>;
// -- constructors, destructors, and assignment operators --------------------
explicit transport_worker_dispatcher(factory_type factory)
: factory_(std::move(factory)) {
// nop
}
// -- member functions -------------------------------------------------------
template <class Parent>
error init(Parent&) {
CAF_ASSERT(workers_by_id_.empty());
return none;
}
template <class Parent>
error handle_data(Parent& parent, span<const byte> data, id_type id) {
if (auto worker = find_worker(id))
return worker->handle_data(parent, data);
// TODO: Where to get node_id from here?
auto worker = add_new_worker(parent, node_id{}, id);
if (worker)
return (*worker)->handle_data(parent, data);
else
return std::move(worker.error());
}
template <class Parent>
void write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto receiver = msg->receiver;
if (!receiver)
return;
auto nid = receiver->node();
if (auto worker = find_worker(nid)) {
worker->write_message(parent, std::move(msg));
return;
}
// TODO: where to get id_type from here?
if (auto worker = add_new_worker(parent, nid, id_type{}))
(*worker)->write_message(parent, std::move(msg));
}
template <class Parent>
void resolve(Parent& parent, const uri& locator, const actor& listener) {
if (auto worker = find_worker(make_node_id(locator)))
worker->resolve(parent, locator.path(), listener);
else
anon_send(listener,
make_error(sec::runtime_error, "could not resolve node"));
}
template <class Parent>
void new_proxy(Parent& parent, const node_id& nid, actor_id id) {
if (auto worker = find_worker(nid))
worker->new_proxy(parent, nid, id);
}
template <class Parent>
void local_actor_down(Parent& parent, const node_id& nid, actor_id id,
error reason) {
if (auto worker = find_worker(nid))
worker->local_actor_down(parent, nid, id, std::move(reason));
}
void handle_error(sec error) {
for (const auto& p : workers_by_id_) {
auto worker = p.second;
worker->handle_error(error);
}
}
template <class Parent>
expected<worker_ptr>
add_new_worker(Parent& parent, node_id node, id_type id) {
CAF_LOG_TRACE(CAF_ARG(node) << CAF_ARG(id));
auto application = factory_.make();
auto worker = std::make_shared<worker_type>(std::move(application), id);
if (auto err = worker->init(parent))
return err;
workers_by_id_.emplace(std::move(id), worker);
workers_by_node_.emplace(std::move(node), worker);
return worker;
}
private:
worker_ptr find_worker(const node_id& nid) {
return find_worker_impl(workers_by_node_, nid);
}
worker_ptr find_worker(const id_type& id) {
return find_worker_impl(workers_by_id_, id);
}
template <class Key>
worker_ptr find_worker_impl(const std::unordered_map<Key, worker_ptr>& map,
const Key& key) {
if (map.count(key) == 0) {
CAF_LOG_DEBUG("could not find worker: " << CAF_ARG(key));
return nullptr;
}
return map.at(key);
}
// -- worker lookups ---------------------------------------------------------
std::unordered_map<id_type, worker_ptr> workers_by_id_;
std::unordered_map<node_id, worker_ptr> workers_by_node_;
std::unordered_map<uint64_t, worker_ptr> workers_by_timeout_id_;
factory_type factory_;
};
} // 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/actor_traits.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/detail/type_list.hpp"
#include "caf/extend.hpp"
#include "caf/fwd.hpp"
#include "caf/mixin/requester.hpp"
#include "caf/mixin/sender.hpp"
#include "caf/net/abstract_actor_shell.hpp"
#include "caf/net/fwd.hpp"
#include "caf/none.hpp"
#include "caf/typed_actor.hpp"
namespace caf::net {
/// Enables socket managers to communicate with actors using statically typed
/// messaging.
template <class... Sigs>
class typed_actor_shell
// clang-format off
: public extend<abstract_actor_shell, typed_actor_shell<Sigs...>>::template
with<mixin::sender, mixin::requester>,
public statically_typed_actor_base {
// clang-format on
public:
// -- friends ----------------------------------------------------------------
template <class...>
friend class typed_actor_shell_ptr;
// -- member types -----------------------------------------------------------
// clang-format off
using super =
typename extend<abstract_actor_shell, typed_actor_shell<Sigs...>>::template
with<mixin::sender, mixin::requester>;
// clang-format on
using signatures = detail::type_list<Sigs...>;
using behavior_type = typed_behavior<Sigs...>;
// -- constructors, destructors, and assignment operators --------------------
using super::super;
// -- state modifiers --------------------------------------------------------
/// Overrides the callbacks for incoming messages.
template <class... Fs>
void set_behavior(Fs... fs) {
auto new_bhvr = behavior_type{std::move(fs)...};
this->bhvr_ = std::move(new_bhvr.unbox());
}
// -- overridden functions of local_actor ------------------------------------
const char* name() const override {
return "caf.net.typed-actor-shell";
}
};
/// An "owning" pointer to an actor shell in the sense that it calls `quit()` on
/// the shell when going out of scope.
template <class... Sigs>
class typed_actor_shell_ptr {
public:
// -- friends ----------------------------------------------------------------
friend class socket_manager;
// -- member types -----------------------------------------------------------
using handle_type = typed_actor<Sigs...>;
using element_type = typed_actor_shell<Sigs...>;
// -- constructors, destructors, and assignment operators --------------------
constexpr typed_actor_shell_ptr() noexcept {
// nop
}
constexpr typed_actor_shell_ptr(std::nullptr_t) noexcept {
// nop
}
typed_actor_shell_ptr(typed_actor_shell_ptr&& other) noexcept = default;
typed_actor_shell_ptr&
operator=(typed_actor_shell_ptr&& other) noexcept = default;
typed_actor_shell_ptr(const typed_actor_shell_ptr& other) = delete;
typed_actor_shell_ptr& operator=(const typed_actor_shell_ptr& other) = delete;
~typed_actor_shell_ptr() {
if (auto ptr = get())
ptr->quit(exit_reason::normal);
}
// -- smart pointer interface ------------------------------------------------
/// Returns an actor handle to the managed actor shell.
handle_type as_actor() const noexcept {
return actor_cast<handle_type>(ptr_);
}
void detach(error reason) {
if (auto ptr = get()) {
ptr->quit(std::move(reason));
ptr_.release();
}
}
element_type* get() const noexcept {
if (ptr_) {
auto ptr = actor_cast<abstract_actor*>(ptr_);
return static_cast<element_type*>(ptr);
} else {
return nullptr;
}
}
element_type* operator->() const noexcept {
return get();
}
element_type& operator*() const noexcept {
return *get();
}
bool operator!() const noexcept {
return !ptr_;
}
explicit operator bool() const noexcept {
return static_cast<bool>(ptr_);
}
private:
/// @pre `ptr != nullptr`
explicit typed_actor_shell_ptr(strong_actor_ptr ptr) noexcept
: ptr_(std::move(ptr)) {
// nop
}
strong_actor_ptr ptr_;
};
} // namespace caf::net
namespace caf::detail {
template <class T>
struct typed_actor_shell_ptr_oracle;
template <class... Sigs>
struct typed_actor_shell_ptr_oracle<typed_actor<Sigs...>> {
using type = net::typed_actor_shell_ptr<Sigs...>;
};
} // namespace caf::detail
namespace caf::net {
template <class Handle>
using typed_actor_shell_ptr_t =
typename detail::typed_actor_shell_ptr_oracle<Handle>::type;
} // 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"
#include "caf/net/network_socket.hpp"
namespace caf::net {
/// A datagram-oriented network communication endpoint for bidirectional
/// byte transmission.
struct CAF_NET_EXPORT udp_datagram_socket : network_socket {
using super = network_socket;
using super::super;
};
/// Creates a `udp_datagram_socket` bound to given port.
/// @param ep ip_endpoint that contains the port to bind to. Pass port '0' to
/// bind to any unused port - The endpoint will be updated with the
/// specific port that was bound.
/// @returns The connected socket or an error.
/// @relates udp_datagram_socket
expected<std::pair<udp_datagram_socket, uint16_t>>
CAF_NET_EXPORT make_udp_datagram_socket(ip_endpoint ep,
bool reuse_addr = false);
/// Enables or disables `SIO_UDP_CONNRESET` error on `x`.
/// @relates udp_datagram_socket
error CAF_NET_EXPORT allow_connreset(udp_datagram_socket x, bool new_value);
/// Receives the next datagram on socket `x`.
/// @param x The UDP socket for receiving datagrams.
/// @param buf Writable output buffer.
/// @returns The number of received bytes and the sender as `ip_endpoint` on
/// success, an error code otherwise.
/// @relates udp_datagram_socket
/// @post buf was modified and the resulting integer represents the length of
/// the received datagram, even if it did not fit into the given buffer.
variant<std::pair<size_t, ip_endpoint>, sec>
CAF_NET_EXPORT read(udp_datagram_socket x, span<byte> buf);
/// Sends the content of `bufs` as a datagram to the endpoint `ep` on socket
/// `x`.
/// @param x The UDP socket for sending datagrams.
/// @param bufs Points to the datagram to send, scattered across up to 10
/// buffers.
/// @param ep The enpoint to send the datagram to.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates udp_datagram_socket
/// @pre `bufs.size() < 10`
variant<size_t, sec> CAF_NET_EXPORT write(udp_datagram_socket x,
span<byte_buffer*> bufs,
ip_endpoint ep);
/// Sends the content of `buf` as a datagram to the endpoint `ep` on socket `x`.
/// @param x The UDP socket for sending datagrams.
/// @param buf The buffer to send.
/// @param ep The enpoint to send the datagram to.
/// @returns The number of written bytes on success, otherwise an error code.
/// @relates udp_datagram_socket
variant<size_t, sec> CAF_NET_EXPORT write(udp_datagram_socket x,
span<const byte> buf, ip_endpoint ep);
/// Converts the result from I/O operation on a ::udp_datagram_socket to either
/// an error code or a non-zero positive integer.
/// @relates udp_datagram_socket
variant<size_t, sec> CAF_NET_EXPORT
check_udp_datagram_socket_io_res(std::make_signed<size_t>::type 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/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/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>
namespace caf::net::web_socket {
/// Implements the client part for the WebSocket Protocol as defined in RFC
/// 6455. Initially, the layer performs the WebSocket handshake. Once completed,
/// this layer decodes RFC 6455 frames and forwards binary and text messages to
/// `UpperLayer`.
template <class UpperLayer>
class client {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
// -- 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)));
}
// -- 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;
}
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_.upper_layer();
}
const auto& upper_layer() const noexcept {
return upper_layer_.upper_layer();
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return handshake_complete() ? upper_layer_.prepare_send(down) : true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return handshake_complete() ? upper_layer_.done_sending(down) : true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
if (handshake_complete())
upper_layer_.abort(down, reason);
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr down) {
if (handshake_complete())
upper_layer_.continue_reading(down);
}
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;
}
}
}
bool handshake_complete() const noexcept {
return handshake_ == nullptr;
}
private:
// -- HTTP response processing -----------------------------------------------
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, 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;
}
}
// -- member variables -------------------------------------------------------
/// Stores the WebSocket handshake data until the handshake completed.
std::unique_ptr<handshake> handshake_;
/// Stores the upper layer.
framing<UpperLayer> upper_layer_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
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, caf::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
// 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.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/rfc6455.hpp"
#include "caf/net/mixed_message_oriented_layer_ptr.hpp"
#include "caf/net/web_socket/status.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include <random>
#include <string_view>
#include <vector>
namespace caf::net::web_socket {
/// Implements the WebSocket framing protocol as defined in RFC-6455.
template <class UpperLayer>
class framing {
public:
// -- member types -----------------------------------------------------------
using binary_buffer = std::vector<byte>;
using text_buffer = std::vector<char>;
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
// -- constants --------------------------------------------------------------
/// Restricts the size of received frames (including header).
static constexpr size_t max_frame_size = INT32_MAX;
/// Stored as currently active opcode to mean "no opcode received yet".
static constexpr size_t nil_code = 0xFF;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit framing(Ts&&... xs) : upper_layer_(std::forward<Ts>(xs)...) {
// nop
}
// -- initialization ---------------------------------------------------------
template <class LowerLayerPtr>
error init(socket_manager* owner, LowerLayerPtr down, const settings& cfg) {
std::random_device rd;
rng_.seed(rd());
return upper_layer_.init(owner, this_layer_ptr(down), cfg);
}
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_;
}
const auto& upper_layer() const noexcept {
return upper_layer_;
}
/// When set to true, causes the layer to mask all outgoing frames with a
/// randomly chosen masking key (cf. RFC 6455, Section 5.3). Servers may set
/// this to false, whereas clients are required to always mask according to
/// the standard.
bool mask_outgoing_frames = true;
// -- interface for mixed_message_oriented_layer_ptr -------------------------
template <class LowerLayerPtr>
static bool can_send_more(LowerLayerPtr parent) noexcept {
return parent->can_send_more();
}
template <class LowerLayerPtr>
static auto handle(LowerLayerPtr parent) noexcept {
return parent->handle();
}
template <class LowerLayerPtr>
static void suspend_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::stop());
}
template <class LowerLayerPtr>
static constexpr void begin_binary_message(LowerLayerPtr) {
// nop
}
template <class LowerLayerPtr>
binary_buffer& binary_message_buffer(LowerLayerPtr) {
return binary_buf_;
}
template <class LowerLayerPtr>
bool end_binary_message(LowerLayerPtr down) {
ship_frame(down, binary_buf_);
return true;
}
template <class LowerLayerPtr>
static constexpr void begin_text_message(LowerLayerPtr) {
// nop
}
template <class LowerLayerPtr>
text_buffer& text_message_buffer(LowerLayerPtr) {
return text_buf_;
}
template <class LowerLayerPtr>
bool end_text_message(LowerLayerPtr down) {
ship_frame(down, text_buf_);
return true;
}
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr down) {
ship_close(down);
return true;
}
template <class LowerLayerPtr>
bool send_close_message(LowerLayerPtr down, status code, string_view desc) {
ship_close(down, static_cast<uint16_t>(code), desc);
return true;
}
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;
}
template <class LowerLayerPtr>
static void abort_reason(LowerLayerPtr parent, error reason) {
return parent->abort_reason(std::move(reason));
}
template <class LowerLayerPtr>
static const error& abort_reason(LowerLayerPtr parent) {
return parent->abort_reason();
}
// -- interface for the lower layer ------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return upper_layer_.prepare_send(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);
}
}
private:
// -- implementation details -------------------------------------------------
template <class LowerLayerPtr>
bool handle(LowerLayerPtr down, uint8_t opcode, byte_span payload) {
switch (opcode) {
case detail::rfc6455::text_frame: {
string_view text{reinterpret_cast<const char*>(payload.data()),
payload.size()};
return upper_layer_.consume_text(this_layer_ptr(down), text) >= 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();
}
template <class LowerLayerPtr>
void ship_close(LowerLayerPtr down, uint16_t code, string_view msg) {
uint32_t mask_key = 0;
std::vector<byte> payload;
payload.reserve(msg.size() + 2);
payload.push_back(static_cast<byte>((code & 0xFF00) >> 8));
payload.push_back(static_cast<byte>(code & 0x00FF));
for (auto c : msg)
payload.push_back(static_cast<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();
}
template <class LowerLayerPtr>
void ship_close(LowerLayerPtr down) {
uint32_t mask_key = 0;
byte payload[] = {
byte{0x03}, byte{0xE8}, // Error code 1000: normal close.
byte{'E'}, byte{'O'}, byte{'F'}, // "EOF" string as goodbye message.
};
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 LowerLayerPtr, class T>
void ship_frame(LowerLayerPtr down, std::vector<T>& buf) {
uint32_t mask_key = 0;
if (mask_outgoing_frames) {
mask_key = static_cast<uint32_t>(rng_());
detail::rfc6455::mask_data(mask_key, buf);
}
down->begin_output();
detail::rfc6455::assemble_frame(mask_key, buf, down->output_buffer());
down->end_output();
buf.clear();
}
template <class LowerLayerPtr>
auto this_layer_ptr(LowerLayerPtr down) {
return make_mixed_message_oriented_layer_ptr(this, down);
}
// -- member variables -------------------------------------------------------
// Buffer for assembling binary frames.
binary_buffer binary_buf_;
// Buffer for assembling text frames.
text_buffer text_buf_;
// 32-bit random number generator.
std::mt19937 rng_;
uint8_t opcode_ = nil_code;
// Assembles fragmented payloads.
binary_buffer payload_buf_;
// Next layer in the processing chain.
UpperLayer upper_layer_;
};
} // namespace caf::net::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 <string>
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/net_export.hpp"
#include "caf/dictionary.hpp"
#include "caf/string_view.hpp"
namespace caf::net::web_socket {
/// Wraps state and algorithms for the WebSocket client handshake as defined in
/// RFC 6455.
class CAF_NET_EXPORT handshake {
public:
// -- member types -----------------------------------------------------------
using key_type = std::array<byte, 16>;
// -- constants --------------------------------------------------------------
/// Maximum size for HTTP request and response messages. A handshake should
/// usually fit into 200-300 Bytes, so 2KB is more than enough.
static constexpr uint32_t max_http_size = 2048;
// -- constructors, destructors, and assignment operators --------------------
handshake() noexcept;
handshake(const handshake&) = default;
handshake(handshake&&) noexcept = default;
handshake& operator=(const handshake&) = default;
handshake& operator=(handshake&&) noexcept = default;
// -- properties -------------------------------------------------------------
/// Returns the 16-byte `key` for the opening handshake.
[[nodiscard]] const auto& key() const noexcept {
return key_;
}
/// Sets the 16-byte `key` for the opening handshake.
void key(key_type new_key) noexcept {
key_ = new_key;
}
/// Tries to assign a key from base64 input.
bool assign_key(string_view base64_key);
/// Returns the key for the response message, i.e., what the server puts into
/// the HTTP field `Sec-WebSocket-Accept`.
std::string response_key() const;
/// Returns all configured header fields, except the key.
[[nodiscard]] const auto& fields() const noexcept {
return fields_;
}
/// Checks whether at least one bit in the key is one. A default constructed
/// header object fills the key with zeros.
[[nodiscard]] bool has_valid_key() const noexcept;
/// Checks whether all mandatory fields were provided by the user.
[[nodiscard]] bool has_mandatory_fields() const noexcept;
/// Fills the key with pseudo-random numbers generated by `std::minstd_rand`
/// with a seed chosen from `std::random_device`.
void randomize_key();
/// Fills the key with pseudo-random numbers generated by `std::minstd_rand`,
/// initialized with `seed`.
void randomize_key(unsigned seed);
/// Sets a value for the mandatory WebSocket endpoint, i.e., the Request-URI
/// component of the GET method according to RFC 2616.
/// @param value Identifies the endpoint that should handle the request.
void endpoint(std::string value) {
fields_["_endpoint"] = std::move(value);
}
/// Sets a value for the mandatory `Host` field.
/// @param value The Internet host and port number of the resource being
/// requested, as obtained from the original URI given by the
/// user or referring resource.
void host(std::string value) {
fields_["_host"] = std::move(value);
}
/// Sets a value for the optional `Origin` field.
/// @param value Indicates where the GET request originates from. Usually only
/// sent by browser clients.
void origin(std::string value) {
fields_["Origin"] = std::move(value);
}
/// Sets a value for the optional `Sec-WebSocket-Protocol` field.
/// @param value A comma-separated list of values indicating which protocols
/// the client would like to speak, ordered by preference.
void protocols(std::string value) {
fields_["Sec-WebSocket-Protocol"] = std::move(value);
}
/// Sets a value for the optional `Sec-WebSocket-Extensions` field.
/// @param value A comma-separated list of values indicating which extensions
/// the client would like to speak, ordered by preference.
void extensions(std::string value) {
fields_["Sec-WebSocket-Extensions"] = std::move(value);
}
// -- HTTP generation and validation -----------------------------------------
/// Writes the HTTP 1.1 request message to `buf`.
/// @pre `has_mandatory_fields()`
void write_http_1_request(byte_buffer& buf) const;
/// Writes the HTTP 1.1 response message to `buf`.
/// @pre `has_valid_key()`
void write_http_1_response(byte_buffer& buf) const;
/// Checks whether the `http_response` contains a HTTP 1.1 response to the
/// generated HTTP GET request. A valid response contains:
/// - HTTP status code 101 (Switching Protocols).
/// - An `Upgrade` field with the value `websocket`.
/// - A `Connection` field with the value `Upgrade`.
/// - A `Sec-WebSocket-Accept` field with the value `response_key()`.
bool is_valid_http_1_response(string_view http_response) const;
private:
// -- utility ----------------------------------------------------------------
string_view lookup(string_view field_name) const noexcept;
// -- member variables -------------------------------------------------------
key_type key_;
dictionary<std::string> fields_;
};
} // 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/byte_span.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/connection_acceptor.hpp"
#include "caf/net/fwd.hpp"
#include "caf/net/http/header.hpp"
#include "caf/net/http/method.hpp"
#include "caf/net/http/status.hpp"
#include "caf/net/http/v1.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/web_socket/framing.hpp"
#include "caf/net/web_socket/handshake.hpp"
#include "caf/net/web_socket/status.hpp"
#include "caf/pec.hpp"
#include "caf/settings.hpp"
#include "caf/tag/mixed_message_oriented.hpp"
#include "caf/tag/stream_oriented.hpp"
#include <algorithm>
namespace caf::net::web_socket {
/// Implements the server part for the WebSocket Protocol as defined in RFC
/// 6455. Initially, the layer performs the WebSocket handshake. Once completed,
/// this layer decodes RFC 6455 frames and forwards binary and text messages to
/// `UpperLayer`.
template <class UpperLayer>
class server {
public:
// -- member types -----------------------------------------------------------
using input_tag = tag::stream_oriented;
using output_tag = tag::mixed_message_oriented;
// -- 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;
}
// -- initialization ---------------------------------------------------------
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;
}
// -- properties -------------------------------------------------------------
auto& upper_layer() noexcept {
return upper_layer_.upper_layer();
}
const auto& upper_layer() const noexcept {
return upper_layer_.upper_layer();
}
// -- role: upper layer ------------------------------------------------------
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.prepare_send(down) : true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr down) {
return handshake_complete_ ? upper_layer_.done_sending(down) : true;
}
template <class LowerLayerPtr>
static void continue_reading(LowerLayerPtr down) {
down->configure_read(receive_policy::up_to(handshake::max_http_size));
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr down, const error& reason) {
if (handshake_complete_)
upper_layer_.abort(down, reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span input, byte_span delta) {
using namespace caf::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;
}
}
}
bool handshake_complete() const noexcept {
return handshake_complete_;
}
private:
// -- HTTP request processing ------------------------------------------------
template <class LowerLayerPtr>
void write_response(LowerLayerPtr down, http::status code, string_view msg) {
down->begin_output();
http::v1::write_response(code, "text/plain", msg, down->output_buffer());
down->end_output();
}
template <class LowerLayerPtr>
bool handle_header(LowerLayerPtr down, 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", to_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, to_string(key), to_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;
}
template <class F>
void for_each_line(string_view input, F&& f) {
constexpr string_view eol{"\r\n"};
for (auto pos = input.begin();;) {
auto line_end = std::search(pos, input.end(), eol.begin(), eol.end());
if (line_end == input.end())
return;
f(string_view{pos, line_end});
pos = line_end + eol.size();
}
}
static string_view trim(string_view str) {
str.remove_prefix(std::min(str.find_first_not_of(' '), str.size()));
auto trim_pos = str.find_last_not_of(' ');
if (trim_pos != str.npos)
str.remove_suffix(str.size() - (trim_pos + 1));
return str;
}
/// Splits `str` at the first occurrence of `sep` into the head and the
/// remainder (excluding the separator).
static std::pair<string_view, string_view> split(string_view str,
string_view sep) {
auto i = std::search(str.begin(), str.end(), sep.begin(), sep.end());
if (i != str.end())
return {{str.begin(), i}, {i + sep.size(), str.end()}};
return {{str}, {}};
}
/// Convenience function for splitting twice.
static std::tuple<string_view, string_view, string_view>
split2(string_view str, string_view sep) {
auto [first, r1] = split(str, sep);
auto [second, third] = split(r1, sep);
return {first, second, third};
}
/// Stores whether the WebSocket handshake completed successfully.
bool handshake_complete_ = false;
/// Stores the upper layer.
framing<UpperLayer> upper_layer_;
/// Stores a pointer to the owning manager for the delayed initialization.
socket_manager* owner_ = nullptr;
/// Holds a copy of the settings in order to delay initialization of the upper
/// layer until the handshake completed. We also fill this dictionary with the
/// contents of the HTTP GET header.
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
namespace caf::detail {
template <class T, class Trait>
using on_request_result = expected<
std::tuple<async::consumer_resource<T>, // For the connection to read from.
async::producer_resource<T>, // For the connection to write to.
Trait>>; // For translating between native and wire format.
template <class T>
struct is_on_request_result : std::false_type {};
template <class T, class Trait>
struct is_on_request_result<on_request_result<T, Trait>> : std::true_type {};
template <class T>
struct on_request_trait;
template <class T, class ServerTrait>
struct on_request_trait<on_request_result<T, ServerTrait>> {
using value_type = T;
using trait_type = ServerTrait;
};
template <class OnRequest>
class ws_accept_trait {
public:
using on_request_r
= decltype(std::declval<OnRequest&>()(std::declval<const settings&>()));
static_assert(is_on_request_result<on_request_r>::value,
"OnRequest must return an on_request_result");
using on_request_t = on_request_trait<on_request_r>;
using value_type = typename on_request_t::value_type;
using decorated_trait = typename on_request_t::trait_type;
using consumer_resource_t = async::consumer_resource<value_type>;
using producer_resource_t = async::producer_resource<value_type>;
using in_out_tuple = std::tuple<consumer_resource_t, producer_resource_t>;
using init_res_t = expected<in_out_tuple>;
ws_accept_trait() = delete;
explicit ws_accept_trait(OnRequest on_request) : state_(on_request) {
// nop
}
ws_accept_trait(ws_accept_trait&&) = default;
ws_accept_trait& operator=(ws_accept_trait&&) = default;
init_res_t init(const settings& cfg) {
auto f = std::move(std::get<OnRequest>(state_));
if (auto res = f(cfg)) {
auto& [in, out, trait] = *res;
if (auto err = trait.init(cfg)) {
state_ = none;
return std::move(res.error());
} else {
state_ = std::move(trait);
return std::make_tuple(std::move(in), std::move(out));
}
} else {
state_ = none;
return std::move(res.error());
}
}
bool converts_to_binary(const value_type& x) {
return decorated().converts_to_binary(x);
}
bool convert(const value_type& x, byte_buffer& bytes) {
return decorated().convert(x, bytes);
}
bool convert(const value_type& x, std::vector<char>& text) {
return decorated().convert(x, text);
}
bool convert(const_byte_span bytes, value_type& x) {
return decorated().convert(bytes, x);
}
bool convert(string_view text, value_type& x) {
return decorated().convert(text, x);
}
private:
decorated_trait& decorated() {
return std::get<decorated_trait>(state_);
}
std::variant<none_t, OnRequest, decorated_trait> state_;
};
template <template <class> class Transport, class OnRequest>
class ws_acceptor_factory {
public:
explicit ws_acceptor_factory(OnRequest on_request)
: on_request_(std::move(on_request)) {
// nop
}
error init(net::socket_manager*, const settings&) {
return none;
}
template <class Socket>
net::socket_manager_ptr make(Socket fd, net::multiplexer* mpx) {
using trait_t = ws_accept_trait<OnRequest>;
using value_type = typename trait_t::value_type;
using app_t = net::message_flow_bridge<value_type, trait_t,
tag::mixed_message_oriented>;
using stack_t = Transport<net::web_socket::server<app_t>>;
return net::make_socket_manager<stack_t>(fd, mpx, trait_t{on_request_});
}
void abort(const error&) {
// nop
}
private:
OnRequest on_request_;
};
} // namespace caf::detail
namespace caf::net::web_socket {
/// Creates a WebSocket server on the connected socket `fd`.
/// @param mpx The multiplexer that takes ownership of the socket.
/// @param fd An accept socket in listening mode. For a TCP socket, this socket
/// must already listen to an address plus port.
/// @param on_request Function object for turning requests into a tuple
/// consisting of a consumer resource, a producer resource,
/// and a trait. These arguments get forwarded to
/// @ref make_server internally.
template <template <class> class Transport = stream_transport, class Socket,
class OnRequest>
void accept(multiplexer& mpx, Socket fd, OnRequest on_request,
size_t limit = 0) {
using factory = detail::ws_acceptor_factory<Transport, OnRequest>;
using impl = connection_acceptor<Socket, factory>;
auto ptr = make_socket_manager<impl>(std::move(fd), &mpx, limit,
factory{std::move(on_request)});
mpx.init(ptr);
}
} // 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/default_enum_inspect.hpp"
#include "caf/detail/net_export.hpp"
#include <cstdint>
#include <type_traits>
namespace caf::net::web_socket {
/// Status codes as defined by RFC 6455, Section 7.4.
enum class status : uint16_t {
/// Indicates a normal closure, meaning that the purpose for which the
/// connection was established has been fulfilled.
normal_close = 1000,
/// Indicates that an endpoint is "going away", such as a server going down or
/// a browser having navigated away from a page.
going_away = 1001,
/// Indicates that an endpoint is terminating the connection due to a protocol
/// error.
protocol_error = 1002,
/// Indicates that an endpoint is terminating the connection because it has
/// received a type of data it cannot accept (e.g., an endpoint that
/// understands only text data MAY send this if it receives a binary message).
invalid_data = 1003,
/// A reserved value and MUST NOT be set as a status code in a Close control
/// frame by an endpoint. It is designated for use in applications expecting
/// a status code to indicate that no status code was actually present.
no_status = 1005,
/// A reserved value and MUST NOT be set as a status code in a Close control
/// frame by an endpoint. It is designated for use in applications expecting
/// a status code to indicate that the connection was closed abnormally, e.g.,
/// without sending or receiving a Close control frame.
abnormal_exit = 1006,
/// Indicates that an endpoint is terminating the connection because it has
/// received data within a message that was not consistent with the type of
/// the message (e.g., non-UTF-8 [RFC3629] data within a text message).
inconsistent_data = 1007,
/// Indicates that an endpoint is terminating the connection because it has
/// received a message that violates its policy. This is a generic status
/// code that can be returned when there is no other more suitable status code
/// (e.g., 1003 or 1009) or if there is a need to hide specific details about
/// the policy.
policy_violation = 1008,
/// Indicates that an endpoint is terminating the connection because it has
/// received a message that is too big for it to process.
message_too_big = 1009,
/// Indicates that an endpoint (client) is terminating the connection because
/// it has expected the server to negotiate one or more extension, but the
/// server didn't return them in the response message of the WebSocket
/// handshake. The list of extensions that are needed SHOULD appear in the
/// /reason/ part of the Close frame. Note that this status code is not used
/// by the server, because it can fail the WebSocket handshake instead.
missing_extensions = 1010,
/// Indicates that a server is terminating the connection because it
/// encountered an unexpected condition that prevented it from fulfilling the
/// request.
unexpected_condition = 1011,
/// A reserved value and MUST NOT be set as a status code in a Close control
/// frame by an endpoint. It is designated for use in applications expecting
/// a status code to indicate that the connection was closed due to a failure
/// to perform a TLS handshake (e.g., the server certificate can't be
/// verified).
tls_handshake_failure = 1015,
};
/// @relates status
CAF_NET_EXPORT std::string to_string(status);
/// @relates status
CAF_NET_EXPORT bool from_string(string_view, status&);
/// @relates status
CAF_NET_EXPORT bool from_integer(std::underlying_type_t<status>, status&);
/// @relates status
template <class Inspector>
bool inspect(Inspector& f, status& x) {
return default_enum_inspect(f, x);
}
} // 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/net/web_socket/framing.hpp"
namespace caf::net {
template <class UpperLayer>
using web_socket_framing
[[deprecated("use caf::net::web_socket::framing instead")]]
= web_socket::framing<UpperLayer>;
} // 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/web_socket/server.hpp"
namespace caf::net {
template <class UpperLayer>
using web_socket_server
[[deprecated("use caf::net::web_socket::server instead")]]
= web_socket::server<UpperLayer>;
} // 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
namespace caf::tag {
/// Tags a layer that expects an HTTP-like transport.
struct hypertext_oriented {};
} // 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 io_event_oriented {};
} // 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 message_oriented {};
} // 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 mixed_message_oriented {};
} // 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 {
/// 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
// 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/actor_proxy_impl.hpp"
#include "caf/actor_system.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
namespace caf::net {
actor_proxy_impl::actor_proxy_impl(actor_config& cfg, endpoint_manager_ptr dst)
: super(cfg), dst_(std::move(dst)) {
CAF_ASSERT(dst_ != nullptr);
dst_->enqueue_event(node(), id());
}
actor_proxy_impl::~actor_proxy_impl() {
// nop
}
void actor_proxy_impl::enqueue(mailbox_element_ptr msg, execution_unit*) {
CAF_PUSH_AID(0);
CAF_ASSERT(msg != nullptr);
CAF_LOG_SEND_EVENT(msg);
dst_->enqueue(std::move(msg), ctrl());
}
void actor_proxy_impl::kill_proxy(execution_unit* ctx, error rsn) {
cleanup(std::move(rsn), ctx);
}
} // 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/detail/convert_ip_endpoint.hpp"
#include <cstring>
#include "caf/error.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv6_endpoint.hpp"
#include "caf/sec.hpp"
namespace caf::detail {
void convert(const ip_endpoint& src, sockaddr_storage& dst) {
memset(&dst, 0, sizeof(sockaddr_storage));
if (src.address().embeds_v4()) {
auto sockaddr4 = reinterpret_cast<sockaddr_in*>(&dst);
sockaddr4->sin_family = AF_INET;
sockaddr4->sin_port = ntohs(src.port());
sockaddr4->sin_addr.s_addr = src.address().embedded_v4().bits();
} else {
auto sockaddr6 = reinterpret_cast<sockaddr_in6*>(&dst);
sockaddr6->sin6_family = AF_INET6;
sockaddr6->sin6_port = ntohs(src.port());
memcpy(&sockaddr6->sin6_addr, src.address().bytes().data(),
src.address().bytes().size());
}
}
error convert(const sockaddr_storage& src, ip_endpoint& dst) {
if (src.ss_family == AF_INET) {
auto sockaddr4 = reinterpret_cast<const sockaddr_in&>(src);
ipv4_address ipv4_addr;
memcpy(ipv4_addr.data().data(), &sockaddr4.sin_addr, ipv4_addr.size());
dst = ip_endpoint{ipv4_addr, htons(sockaddr4.sin_port)};
} else if (src.ss_family == AF_INET6) {
auto sockaddr6 = reinterpret_cast<const sockaddr_in6&>(src);
ipv6_address ipv6_addr;
memcpy(ipv6_addr.bytes().data(), &sockaddr6.sin6_addr,
ipv6_addr.bytes().size());
dst = ip_endpoint{ipv6_addr, htons(sockaddr6.sin6_port)};
} else {
return sec::invalid_argument;
}
return none;
}
} // namespace caf::detail
// 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/datagram_socket.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/logger.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
error allow_connreset(datagram_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
DWORD bytes_returned = 0;
CAF_NET_SYSCALL("WSAIoctl", res, !=, 0,
WSAIoctl(x.id, _WSAIOW(IOC_VENDOR, 12), &new_value,
sizeof(new_value), NULL, 0, &bytes_returned, NULL,
NULL));
return none;
}
#else // CAF_WINDOWS
error allow_connreset(datagram_socket x, bool) {
if (x == invalid_socket)
return sec::socket_invalid;
// nop; SIO_UDP_CONNRESET only exists on Windows
return none;
}
#endif // CAF_WINDOWS
variant<size_t, sec>
check_datagram_socket_io_res(std::make_signed<size_t>::type res) {
if (res < 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(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.
#include "caf/detail/rfc6455.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/span.hpp"
#include <cstring>
namespace caf::detail {
void rfc6455::mask_data(uint32_t key, span<char> data) {
mask_data(key, as_writable_bytes(data));
}
void rfc6455::mask_data(uint32_t key, span<byte> data) {
auto no_key = to_network_order(key);
byte arr[4];
memcpy(arr, &no_key, 4);
size_t i = 0;
for (auto& x : data) {
x = x ^ arr[i];
i = (i + 1) % 4;
}
}
void rfc6455::assemble_frame(uint32_t mask_key, span<const char> data,
binary_buffer& out) {
assemble_frame(text_frame, mask_key, as_bytes(data), out);
}
void rfc6455::assemble_frame(uint32_t mask_key, span<const byte> data,
binary_buffer& out) {
assemble_frame(binary_frame, mask_key, data, out);
}
void rfc6455::assemble_frame(uint8_t opcode, uint32_t mask_key,
span<const byte> data, binary_buffer& out) {
// First 8 bits: FIN flag + opcode (we never fragment frames).
out.push_back(byte{static_cast<uint8_t>(0x80 | opcode)});
// Mask flag + payload length (7 bits, 7+16 bits, or 7+64 bits)
auto mask_bit = byte{static_cast<uint8_t>(mask_key == 0 ? 0x00 : 0x80)};
if (data.size() < 126) {
auto len = static_cast<uint8_t>(data.size());
out.push_back(mask_bit | byte{len});
} else if (data.size() < std::numeric_limits<uint16_t>::max()) {
auto len = static_cast<uint16_t>(data.size());
auto no_len = to_network_order(len);
byte len_data[2];
memcpy(len_data, &no_len, 2);
out.push_back(mask_bit | byte{126});
for (auto x : len_data)
out.push_back(x);
} else {
auto len = static_cast<uint64_t>(data.size());
auto no_len = to_network_order(len);
byte len_data[8];
memcpy(len_data, &no_len, 8);
out.push_back(mask_bit | byte{127});
out.insert(out.end(), len_data, len_data + 8);
}
// Masking key: 0 or 4 bytes.
if (mask_key != 0) {
auto no_key = to_network_order(mask_key);
byte key_data[4];
memcpy(key_data, &no_key, 4);
out.insert(out.end(), key_data, key_data + 4);
}
// Application data.
out.insert(out.end(), data.begin(), data.end());
}
ptrdiff_t rfc6455::decode_header(span<const byte> data, header& hdr) {
if (data.size() < 2)
return 0;
auto byte1 = to_integer<uint8_t>(data[0]);
auto byte2 = to_integer<uint8_t>(data[1]);
// Fetch FIN flag and opcode.
hdr.fin = (byte1 & 0x80) != 0;
hdr.opcode = byte1 & 0x0F;
// Decode mask bit and payload length field.
bool masked = (byte2 & 0x80) != 0;
auto len_field = byte2 & 0x7F;
size_t header_length;
if (len_field < 126) {
header_length = 2 + (masked ? 4 : 0);
hdr.payload_len = len_field;
} else if (len_field == 126) {
header_length = 4 + (masked ? 4 : 0);
} else {
header_length = 10 + (masked ? 4 : 0);
}
// Make sure we can read all the data we need.
if (data.size() < header_length)
return 0;
// Start decoding remaining header bytes.
const byte* p = data.data() + 2;
// Fetch payload size
if (len_field == 126) {
uint16_t no_len;
memcpy(&no_len, p, 2);
hdr.payload_len = from_network_order(no_len);
p += 2;
} else if (len_field == 127) {
uint64_t no_len;
memcpy(&no_len, p, 8);
hdr.payload_len = from_network_order(no_len);
p += 8;
}
// Fetch mask key.
if (masked) {
uint32_t no_key;
memcpy(&no_key, p, 4);
hdr.mask_key = from_network_order(no_key);
p += 4;
} else {
hdr.mask_key = 0;
}
// No extension bits allowed.
if (byte1 & 0x70)
return -1;
// Verify opcode and return number of consumed bytes.
switch (hdr.opcode) {
case continuation_frame:
case text_frame:
case binary_frame:
case connection_close:
case ping:
case pong:
return static_cast<ptrdiff_t>(header_length);
default:
return -1;
}
}
} // namespace caf::detail
// 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/endpoint_manager.hpp"
#include "caf/intrusive/inbox_result.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
endpoint_manager::endpoint_manager(socket handle, const multiplexer_ptr& parent,
actor_system& sys)
: super(handle, parent), sys_(sys), queue_(unit, unit, unit) {
queue_.try_block();
}
endpoint_manager::~endpoint_manager() {
// nop
}
// -- properties ---------------------------------------------------------------
const actor_system_config& endpoint_manager::config() const noexcept {
return sys_.config();
}
// -- queue access -------------------------------------------------------------
bool endpoint_manager::at_end_of_message_queue() {
return queue_.empty() && queue_.try_block();
}
endpoint_manager_queue::message_ptr endpoint_manager::next_message() {
if (queue_.blocked())
return nullptr;
queue_.fetch_more();
auto& q = std::get<1>(queue_.queue().queues());
auto ts = q.next_task_size();
if (ts == 0)
return nullptr;
q.inc_deficit(ts);
auto result = q.next();
if (queue_.empty())
queue_.try_block();
return result;
}
// -- event management ---------------------------------------------------------
void endpoint_manager::resolve(uri locator, actor listener) {
using intrusive::inbox_result;
using event_type = endpoint_manager_queue::event;
auto ptr = new event_type(std::move(locator), listener);
if (!enqueue(ptr))
anon_send(listener, resolve_atom_v, make_error(sec::request_receiver_down));
}
void endpoint_manager::enqueue(mailbox_element_ptr msg,
strong_actor_ptr receiver) {
using message_type = endpoint_manager_queue::message;
auto ptr = new message_type(std::move(msg), std::move(receiver));
enqueue(ptr);
}
bool endpoint_manager::enqueue(endpoint_manager_queue::element* ptr) {
switch (queue_.push_back(ptr)) {
case intrusive::inbox_result::success:
return true;
case intrusive::inbox_result::unblocked_reader: {
auto mpx = parent_.lock();
if (mpx) {
mpx->register_writing(this);
return true;
}
return false;
}
default:
return false;
}
}
} // 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/basp/header.hpp"
#include <cstring>
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/span.hpp"
namespace caf::net::basp {
namespace {
void to_bytes_impl(const header& x, byte* ptr) {
*ptr = static_cast<byte>(x.type);
auto payload_len = detail::to_network_order(x.payload_len);
memcpy(ptr + 1, &payload_len, sizeof(payload_len));
auto operation_data = detail::to_network_order(x.operation_data);
memcpy(ptr + 5, &operation_data, sizeof(operation_data));
}
} // namespace
int header::compare(header other) const noexcept {
auto x = to_bytes(*this);
auto y = to_bytes(other);
return memcmp(x.data(), y.data(), header_size);
}
header header::from_bytes(span<const byte> bytes) {
CAF_ASSERT(bytes.size() >= header_size);
header result;
auto ptr = bytes.data();
result.type = *reinterpret_cast<const message_type*>(ptr);
uint32_t payload_len = 0;
memcpy(&payload_len, ptr + 1, 4);
result.payload_len = detail::from_network_order(payload_len);
uint64_t operation_data;
memcpy(&operation_data, ptr + 5, 8);
result.operation_data = detail::from_network_order(operation_data);
return result;
}
std::array<byte, header_size> to_bytes(header x) {
std::array<byte, header_size> result{};
to_bytes_impl(x, result.data());
return result;
}
void to_bytes(header x, byte_buffer& buf) {
buf.resize(header_size);
to_bytes_impl(x, buf.data());
}
} // namespace caf::net::basp
// 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/host.hpp"
#include "caf/config.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/error.hpp"
#include "caf/message.hpp"
#include "caf/net/socket.hpp"
#include "caf/none.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
error this_host::startup() {
WSADATA WinsockData;
CAF_NET_SYSCALL("WSAStartup", result, !=, 0,
WSAStartup(MAKEWORD(2, 2), &WinsockData));
return none;
}
void this_host::cleanup() {
WSACleanup();
}
#else // CAF_WINDOWS
error this_host::startup() {
return none;
}
void this_host::cleanup() {
// nop
}
#endif // CAF_WINDOWS
} // 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/ip.hpp"
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
#include "caf/config.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/error.hpp"
#include "caf/ip_address.hpp"
#include "caf/ip_subnet.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/logger.hpp"
#include "caf/string_algorithms.hpp"
#include "caf/string_view.hpp"
#ifdef CAF_WINDOWS
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0600
# endif
# include <iphlpapi.h>
# include <winsock.h>
#else
# include <ifaddrs.h>
# include <net/if.h>
# include <netdb.h>
# include <sys/ioctl.h>
#endif
#ifndef HOST_NAME_MAX
# define HOST_NAME_MAX 255
#endif
namespace caf::net::ip {
namespace {
// Dummy port to resolve empty string with getaddrinfo.
constexpr string_view dummy_port = "42";
constexpr string_view localhost = "localhost";
void* fetch_in_addr(int family, sockaddr* addr) {
if (family == AF_INET)
return &reinterpret_cast<sockaddr_in*>(addr)->sin_addr;
return &reinterpret_cast<sockaddr_in6*>(addr)->sin6_addr;
}
int fetch_addr_str(char (&buf)[INET6_ADDRSTRLEN], sockaddr* addr) {
if (addr == nullptr)
return AF_UNSPEC;
auto family = addr->sa_family;
auto in_addr = fetch_in_addr(family, addr);
return (family == AF_INET || family == AF_INET6)
&& inet_ntop(family, in_addr, buf, INET6_ADDRSTRLEN) == buf
? family
: AF_UNSPEC;
}
#ifdef CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
using adapters_ptr = std::unique_ptr<IP_ADAPTER_ADDRESSES, void (*)(void*)>;
ULONG len = 0;
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr,
&len)
!= ERROR_BUFFER_OVERFLOW) {
CAF_LOG_ERROR("failed to get adapter addresses buffer length");
return;
}
auto adapters = adapters_ptr{
reinterpret_cast<IP_ADAPTER_ADDRESSES*>(::malloc(len)), free};
if (!adapters) {
CAF_LOG_ERROR("malloc failed");
return;
}
// TODO: The Microsoft WIN32 API example propopses to try three times, other
// examples online just perform the call once. If we notice the call to be
// unreliable, we might adapt that behavior.
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr,
adapters.get(), &len)
!= ERROR_SUCCESS) {
CAF_LOG_ERROR("failed to get adapter addresses");
return;
}
char ip_buf[INET6_ADDRSTRLEN];
char name_buf[HOST_NAME_MAX];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto* it = adapters.get(); it != nullptr; it = it->Next) {
memset(name_buf, 0, HOST_NAME_MAX);
WideCharToMultiByte(CP_ACP, 0, it->FriendlyName, wcslen(it->FriendlyName),
name_buf, HOST_NAME_MAX, nullptr, nullptr);
std::string name{name_buf};
for (auto* addr = it->FirstUnicastAddress; addr != nullptr;
addr = addr->Next) {
memset(ip_buf, 0, INET6_ADDRSTRLEN);
getnameinfo(addr->Address.lpSockaddr, addr->Address.iSockaddrLength,
ip_buf, sizeof(ip_buf), nullptr, 0, NI_NUMERICHOST);
ip_address ip;
if (!is_link_local && starts_with(ip_buf, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << ip_buf);
continue;
} else if (auto err = parse(ip_buf, ip))
continue;
f(name_buf, ip);
}
}
}
#else // CAF_WINDOWS
template <class F>
void for_each_adapter(F f, bool is_link_local = false) {
ifaddrs* tmp = nullptr;
if (getifaddrs(&tmp) != 0)
return;
std::unique_ptr<ifaddrs, decltype(freeifaddrs)*> addrs{tmp, freeifaddrs};
char buffer[INET6_ADDRSTRLEN];
std::vector<std::pair<std::string, ip_address>> interfaces;
for (auto i = addrs.get(); i != nullptr; i = i->ifa_next) {
auto family = fetch_addr_str(buffer, i->ifa_addr);
if (family != AF_UNSPEC) {
ip_address ip;
if (!is_link_local && starts_with(buffer, "fe80:")) {
CAF_LOG_DEBUG("skipping link-local address: " << buffer);
continue;
} else if (auto err = parse(buffer, ip)) {
CAF_LOG_ERROR("could not parse into ip address " << buffer);
continue;
}
f({i->ifa_name, strlen(i->ifa_name)}, ip);
}
}
}
#endif // CAF_WINDOWS
} // namespace
std::vector<ip_address> resolve(string_view host) {
addrinfo hint;
memset(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_STREAM;
hint.ai_family = AF_UNSPEC;
if (host.empty())
hint.ai_flags = AI_PASSIVE;
addrinfo* tmp = nullptr;
std::string host_str{host.begin(), host.end()};
if (getaddrinfo(host.empty() ? nullptr : host_str.c_str(),
host.empty() ? dummy_port.data() : nullptr, &hint, &tmp)
!= 0)
return {};
std::unique_ptr<addrinfo, decltype(freeaddrinfo)*> addrs{tmp, freeaddrinfo};
char buffer[INET6_ADDRSTRLEN];
std::vector<ip_address> results;
for (auto i = addrs.get(); i != nullptr; i = i->ai_next) {
auto family = fetch_addr_str(buffer, i->ai_addr);
if (family != AF_UNSPEC) {
ip_address ip;
if (auto err = parse(buffer, ip))
CAF_LOG_ERROR("could not parse IP address: " << buffer);
else
results.emplace_back(ip);
}
}
// TODO: Should we just prefer ipv6 or use a config option?
// std::stable_sort(std::begin(results), std::end(results),
// [](const ip_address& lhs, const ip_address& rhs) {
// return !lhs.embeds_v4() && rhs.embeds_v4();
// });
return results;
}
std::vector<ip_address> resolve(ip_address host) {
return resolve(to_string(host));
}
std::vector<ip_address> local_addresses(string_view host) {
ip_address host_ip;
std::vector<ip_address> results;
if (host.empty()) {
for_each_adapter(
[&](string_view, ip_address ip) { results.push_back(ip); });
} else if (host == localhost) {
auto v6_local = ip_address{{0}, {0x1}};
auto v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
for_each_adapter([&](string_view, ip_address ip) {
if (ip == v4_local || ip == v6_local)
results.push_back(ip);
});
} else if (auto err = parse(host, host_ip)) {
for_each_adapter([&](string_view iface, ip_address ip) {
if (iface == host)
results.push_back(ip);
});
} else {
return local_addresses(host_ip);
}
return results;
}
std::vector<ip_address> local_addresses(ip_address host) {
static auto v6_any = ip_address{{0}, {0}};
static auto v4_any = ip_address{make_ipv4_address(0, 0, 0, 0)};
if (host == v4_any || host == v6_any)
return resolve("");
auto link_local = ip_address({0xfe, 0x8, 0x0, 0x0}, {0x0, 0x0, 0x0, 0x0});
auto ll_prefix = ip_subnet(link_local, 10);
// Unless explicitly specified we are going to skip link-local addresses.
auto is_link_local = ll_prefix.contains(host);
std::vector<ip_address> results;
for_each_adapter(
[&](string_view, ip_address ip) {
if (host == ip)
results.push_back(ip);
},
is_link_local);
return results;
}
std::string hostname() {
char buf[HOST_NAME_MAX + 1];
buf[HOST_NAME_MAX] = '\0';
gethostname(buf, HOST_NAME_MAX);
return buf;
}
} // namespace caf::net::ip
// 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/basp/message_queue.hpp"
#include <iterator>
namespace caf::net::basp {
message_queue::message_queue() : next_id(0), next_undelivered(0) {
// nop
}
void message_queue::push(execution_unit* ctx, uint64_t id,
strong_actor_ptr receiver,
mailbox_element_ptr content) {
std::unique_lock<std::mutex> guard{lock};
CAF_ASSERT(id >= next_undelivered);
CAF_ASSERT(id < next_id);
auto first = pending.begin();
auto last = pending.end();
if (id == next_undelivered) {
// Dispatch current head.
if (receiver != nullptr)
receiver->enqueue(std::move(content), ctx);
auto next = id + 1;
// Check whether we can deliver more.
if (first == last || first->id != next) {
next_undelivered = next;
CAF_ASSERT(next_undelivered <= next_id);
return;
}
// Deliver everything until reaching a non-consecutive ID or the end.
auto i = first;
for (; i != last && i->id == next; ++i, ++next)
if (i->receiver != nullptr)
i->receiver->enqueue(std::move(i->content), ctx);
next_undelivered = next;
pending.erase(first, i);
CAF_ASSERT(next_undelivered <= next_id);
return;
}
// Get the insertion point.
auto pred = [&](const actor_msg& x) { return x.id >= id; };
pending.emplace(std::find_if(first, last, pred),
actor_msg{id, std::move(receiver), std::move(content)});
}
void message_queue::drop(execution_unit* ctx, uint64_t id) {
push(ctx, id, nullptr, nullptr);
}
uint64_t message_queue::new_id() {
std::unique_lock<std::mutex> guard{lock};
return next_id++;
}
} // namespace caf::net::basp
// 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/multiplexer.hpp"
#include "caf/action.hpp"
#include "caf/byte.hpp"
#include "caf/config.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/make_counted.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/operation.hpp"
#include "caf/net/pollset_updater.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/variant.hpp"
#include <algorithm>
#include <optional>
#ifndef CAF_WINDOWS
# include <poll.h>
# include <signal.h>
#else
# include "caf/detail/socket_sys_includes.hpp"
#endif // CAF_WINDOWS
namespace caf::net {
#ifndef POLLRDHUP
# define POLLRDHUP POLLHUP
#endif
#ifndef POLLPRI
# define POLLPRI POLLIN
#endif
namespace {
#ifdef CAF_WINDOWS
// From the MSDN: If the POLLPRI flag is set on a socket for the Microsoft
// Winsock provider, the WSAPoll function will fail.
const short input_mask = POLLIN;
#else
const short input_mask = POLLIN | POLLPRI;
#endif
const short error_mask = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
const short output_mask = POLLOUT;
// short to_bitmask(operation mask) {
// return static_cast<short>((is_reading(mask) ? input_mask : 0)
// | (is_writing(mask) ? output_mask : 0));
// }
operation to_operation(const socket_manager_ptr& mgr,
std::optional<short> mask) {
operation res = operation::none;
if (mgr->read_closed())
res = block_reads(res);
if (mgr->write_closed())
res = block_writes(res);
if (mask) {
if ((*mask & input_mask) != 0)
res = add_read_flag(res);
if ((*mask & output_mask) != 0)
res = add_write_flag(res);
}
return res;
}
} // namespace
// -- static utility functions -------------------------------------------------
#ifdef CAF_LINUX
void multiplexer::block_sigpipe() {
sigset_t sigpipe_mask;
sigemptyset(&sigpipe_mask);
sigaddset(&sigpipe_mask, SIGPIPE);
sigset_t saved_mask;
if (pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &saved_mask) == -1) {
perror("pthread_sigmask");
exit(1);
}
}
#else
void multiplexer::block_sigpipe() {
// nop
}
#endif
// -- constructors, destructors, and assignment operators ----------------------
multiplexer::multiplexer(middleman* owner) : owner_(owner) {
// nop
}
multiplexer::~multiplexer() {
// nop
}
// -- initialization -----------------------------------------------------------
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{}))
return err;
write_handle_ = pipe_handles->second;
pollset_.emplace_back(pollfd{pipe_handles->first.id, input_mask, 0});
managers_.emplace_back(updater);
return none;
}
// -- properties ---------------------------------------------------------------
size_t multiplexer::num_socket_managers() const noexcept {
return managers_.size();
}
ptrdiff_t multiplexer::index_of(const socket_manager_ptr& mgr) {
auto first = managers_.begin();
auto last = managers_.end();
auto i = std::find(first, last, mgr);
return i == last ? -1 : std::distance(first, i);
}
ptrdiff_t multiplexer::index_of(socket fd) {
auto first = pollset_.begin();
auto last = pollset_.end();
auto i = std::find_if(first, last, [fd](pollfd& x) { return x.fd == fd.id; });
return i == last ? -1 : std::distance(first, i);
}
middleman& multiplexer::owner() {
CAF_ASSERT(owner_ != nullptr);
return *owner_;
}
actor_system& multiplexer::system() {
return owner().system();
}
operation multiplexer::mask_of(const socket_manager_ptr& mgr) {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end())
return to_operation(mgr, i->second.events);
else if (auto index = index_of(mgr); index != -1)
return to_operation(mgr, pollset_[index].events);
else
return to_operation(mgr, std::nullopt);
}
// -- thread-safe signaling ----------------------------------------------------
void multiplexer::register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_register_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::register_reading, mgr.get());
}
}
void multiplexer::register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) {
do_register_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::register_writing, mgr.get());
}
}
void multiplexer::continue_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
write_to_pipe(pollset_updater::code::continue_reading, mgr.get());
}
void multiplexer::continue_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
CAF_ASSERT(mgr != nullptr);
if (std::this_thread::get_id() == tid_) {
do_continue_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::continue_writing, mgr.get());
}
}
void multiplexer::discard(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_discard(mgr);
} else {
write_to_pipe(pollset_updater::code::discard_manager, mgr.get());
}
}
void multiplexer::shutdown_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_shutdown_reading(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_reading, mgr.get());
}
}
void multiplexer::shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_shutdown_writing(mgr);
} else {
write_to_pipe(pollset_updater::code::shutdown_writing, mgr.get());
}
}
void multiplexer::schedule(const action& what) {
CAF_LOG_TRACE("");
write_to_pipe(pollset_updater::code::run_action, what.ptr());
}
void multiplexer::init(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (std::this_thread::get_id() == tid_) {
do_init(mgr);
} else {
write_to_pipe(pollset_updater::code::init_manager, mgr.get());
}
}
void multiplexer::shutdown() {
CAF_LOG_TRACE("");
// Note: there is no 'shortcut' when calling the function in the multiplexer's
// thread, because do_shutdown calls apply_updates. This must only be called
// from the pollset_updater.
CAF_LOG_DEBUG("push shutdown event to pipe");
write_to_pipe(pollset_updater::code::shutdown,
static_cast<socket_manager*>(nullptr));
}
// -- control flow -------------------------------------------------------------
bool multiplexer::poll_once(bool blocking) {
CAF_LOG_TRACE(CAF_ARG(blocking));
if (pollset_.empty())
return false;
// We'll call poll() until poll() succeeds or fails.
for (;;) {
int presult =
#ifdef CAF_WINDOWS
::WSAPoll(pollset_.data(), static_cast<ULONG>(pollset_.size()),
blocking ? -1 : 0);
#else
::poll(pollset_.data(), static_cast<nfds_t>(pollset_.size()),
blocking ? -1 : 0);
#endif
if (presult > 0) {
CAF_LOG_DEBUG("poll() on" << pollset_.size() << "sockets reported"
<< presult << "event(s)");
// Scan pollset for events.
CAF_LOG_DEBUG("scan pollset for socket events");
if (auto revents = pollset_[0].revents; revents != 0) {
// Index 0 is always the pollset updater. This is the only handler that
// is allowed to modify pollset_ and managers_. Since this may very well
// mess with the for loop below, we process this handler first.
auto mgr = managers_[0];
handle(mgr, pollset_[0].events, revents);
--presult;
}
for (size_t i = 1; i < pollset_.size() && presult > 0; ++i) {
if (auto revents = pollset_[i].revents; revents != 0) {
handle(managers_[i], pollset_[i].events, revents);
--presult;
}
}
apply_updates();
return true;
} else if (presult == 0) {
// No activity.
return false;
} else {
auto code = last_socket_error();
switch (code) {
case std::errc::interrupted: {
// A signal was caught. Simply try again.
CAF_LOG_DEBUG("received errc::interrupted, try again");
break;
}
case std::errc::not_enough_memory: {
CAF_LOG_ERROR("poll() failed due to insufficient memory");
// There's not much we can do other than try again in hope someone
// else releases memory.
break;
}
default: {
// Must not happen.
auto int_code = static_cast<int>(code);
auto msg = std::generic_category().message(int_code);
string_view prefix = "poll() failed: ";
msg.insert(msg.begin(), prefix.begin(), prefix.end());
CAF_CRITICAL(msg.c_str());
}
}
}
}
}
void multiplexer::apply_updates() {
CAF_LOG_DEBUG("apply" << updates_.size() << "updates");
if (!updates_.empty()) {
for (auto& [fd, update] : updates_) {
if (auto index = index_of(fd); index == -1) {
if (update.events != 0) {
pollfd new_entry{socket_cast<socket_id>(fd), update.events, 0};
pollset_.emplace_back(new_entry);
managers_.emplace_back(std::move(update.mgr));
}
} else if (update.events != 0) {
pollset_[index].events = update.events;
managers_[index].swap(update.mgr);
} else {
pollset_.erase(pollset_.begin() + index);
managers_.erase(managers_.begin() + index);
}
}
updates_.clear();
}
}
void multiplexer::set_thread_id() {
CAF_LOG_TRACE("");
tid_ = std::this_thread::get_id();
}
void multiplexer::run() {
CAF_LOG_TRACE("");
// On systems like Linux, we cannot disable sigpipe on the socket alone. We
// need to block the signal at thread level since some APIs (such as OpenSSL)
// are unsafe to call otherwise.
block_sigpipe();
while (!shutting_down_ || pollset_.size() > 1)
poll_once(true);
// Close the pipe to block any future event.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket) {
close(write_handle_);
write_handle_ = pipe_socket{};
}
}
// -- utility functions --------------------------------------------------------
void multiplexer::handle(const socket_manager_ptr& mgr,
[[maybe_unused]] short events, short revents) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id)
<< CAF_ARG(events) << CAF_ARG(revents));
CAF_ASSERT(mgr != nullptr);
bool checkerror = true;
// Note: we double-check whether the manager is actually reading because a
// previous action from the pipe may have called shutdown_reading.
if ((events & revents & input_mask) != 0) {
checkerror = false;
switch (mgr->handle_read_event()) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::stop:
update_for(mgr).events &= ~input_mask;
break;
case socket_manager::read_result::want_write:
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover(mgr);
return;
}
}
}
// Similar reasoning than before: double-check whether this event should still
// get dispatched.
if ((events & revents & output_mask) != 0) {
checkerror = false;
switch (mgr->handle_write_event()) {
default: // socket_manager::write_result::again
break;
case socket_manager::write_result::stop:
update_for(mgr).events &= ~output_mask;
break;
case socket_manager::write_result::want_read:
update_for(mgr).events = input_mask;
break;
case socket_manager::write_result::handover:
do_handover(mgr);
return;
}
}
if (checkerror && ((revents & error_mask) != 0)) {
if (revents & POLLNVAL)
mgr->handle_error(sec::socket_invalid);
else if (revents & POLLHUP)
mgr->handle_error(sec::socket_disconnected);
else
mgr->handle_error(sec::socket_operation_failed);
update_for(mgr).events = 0;
}
}
void multiplexer::do_handover(const socket_manager_ptr& mgr) {
// Make sure to override the manager pointer in the update. Updates are
// associated to sockets, so the new manager is likely to modify this update
// again. Hence, it *must not* point to the old manager.
auto& update = update_for(mgr);
update.events = 0;
auto new_mgr = mgr->do_handover(); // May alter the events mask.
if (new_mgr != nullptr) {
update.mgr = new_mgr;
// If the new manager registered itself for reading, make sure it processes
// whatever data is available in buffers outside of the socket that may not
// trigger read events.
if ((update.events & input_mask)) {
switch (mgr->handle_buffered_data()) {
default: // socket_manager::read_result::again
// Nothing to do, bitmask may remain unchanged.
break;
case socket_manager::read_result::stop:
update.events &= ~input_mask;
break;
case socket_manager::read_result::want_write:
update.events = output_mask;
break;
case socket_manager::read_result::handover: {
// Down the rabbit hole we go!
do_handover(new_mgr);
}
}
}
}
}
multiplexer::poll_update& multiplexer::update_for(ptrdiff_t index) {
auto fd = socket{pollset_[index].fd};
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second;
} else {
updates_.container().emplace_back(fd, poll_update{pollset_[index].events,
managers_[index]});
return updates_.container().back().second;
}
}
multiplexer::poll_update&
multiplexer::update_for(const socket_manager_ptr& mgr) {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second;
} else if (auto index = index_of(fd); index != -1) {
updates_.container().emplace_back(fd,
poll_update{pollset_[index].events, mgr});
return updates_.container().back().second;
} else {
updates_.container().emplace_back(fd, poll_update{0, mgr});
return updates_.container().back().second;
}
}
template <class T>
void multiplexer::write_to_pipe(uint8_t opcode, T* ptr) {
pollset_updater::msg_buf buf;
if (ptr)
intrusive_ptr_add_ref(ptr);
buf[0] = static_cast<byte>(opcode);
auto value = reinterpret_cast<intptr_t>(ptr);
memcpy(buf.data() + 1, &value, sizeof(intptr_t));
ptrdiff_t res = -1;
{ // Lifetime scope of guard.
std::lock_guard<std::mutex> guard{write_lock_};
if (write_handle_ != invalid_socket)
res = write(write_handle_, buf);
}
if (res <= 0 && ptr)
intrusive_ptr_release(ptr);
}
short multiplexer::active_mask_of(const socket_manager_ptr& mgr) {
auto fd = mgr->handle();
if (auto i = updates_.find(fd); i != updates_.end()) {
return i->second.events;
} else if (auto index = index_of(fd); index != -1) {
return pollset_[index].events;
} else {
return 0;
}
}
bool multiplexer::is_reading(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & input_mask) != 0;
}
bool multiplexer::is_writing(const socket_manager_ptr& mgr) {
return (active_mask_of(mgr) & output_mask) != 0;
}
// -- internal callbacks the pollset updater -----------------------------------
void multiplexer::do_shutdown() {
// Note: calling apply_updates here is only safe because we know that the
// pollset updater runs outside of the for-loop in run_once.
CAF_LOG_DEBUG("initiate shutdown");
shutting_down_ = true;
apply_updates();
// Skip the first manager (the pollset updater).
for (size_t i = 1; i < managers_.size(); ++i) {
auto& mgr = managers_[i];
mgr->close_read();
update_for(static_cast<ptrdiff_t>(i)).events &= ~input_mask;
}
apply_updates();
}
void multiplexer::do_register_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, no new reads are allowed.
if (shutting_down_)
mgr->close_read();
else if (!mgr->read_closed())
update_for(mgr).events |= input_mask;
}
void multiplexer::do_register_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
// When shutting down, we do allow managers to write whatever is currently
// pending but we make sure that all read channels are closed.
if (shutting_down_)
mgr->close_read();
if (!mgr->write_closed())
update_for(mgr).events |= output_mask;
}
void multiplexer::do_continue_reading(const socket_manager_ptr& mgr) {
if (!is_reading(mgr)) {
switch (mgr->handle_continue_reading()) {
default: // socket_manager::read_result::stop
update_for(mgr).events &= ~input_mask;
break;
case socket_manager::read_result::again:
update_for(mgr).events |= input_mask;
break;
case socket_manager::read_result::want_write:
update_for(mgr).events = output_mask;
break;
case socket_manager::read_result::handover: {
do_handover(mgr);
}
}
}
}
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);
}
}
}
}
void multiplexer::do_discard(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
mgr->handle_error(sec::disposed);
update_for(mgr).events = 0;
}
void multiplexer::do_shutdown_reading(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->read_closed()) {
mgr->close_read();
update_for(mgr).events &= ~input_mask;
}
}
void multiplexer::do_shutdown_writing(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_ && !mgr->write_closed()) {
mgr->close_write();
update_for(mgr).events &= ~output_mask;
}
}
void multiplexer::do_init(const socket_manager_ptr& mgr) {
CAF_LOG_TRACE(CAF_ARG2("socket", mgr->handle().id));
if (!shutting_down_) {
error err;
if (owner_)
err = mgr->init(content(system().config()));
else
err = mgr->init(settings{});
if (err) {
CAF_LOG_DEBUG("mgr->init failed: " << err);
// The socket manager should not register itself for any events if
// initialization fails. Purge any state just in case.
update_for(mgr).events = 0;
}
// Else: no update since the manager is supposed to call continue_reading
// and continue_writing as necessary.
}
}
} // 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/abstract_actor_shell.hpp"
#include "caf/callback.hpp"
#include "caf/config.hpp"
#include "caf/detail/default_invoke_result_visitor.hpp"
#include "caf/detail/sync_request_bouncer.hpp"
#include "caf/invoke_message_result.hpp"
#include "caf/logger.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
abstract_actor_shell::abstract_actor_shell(actor_config& cfg,
socket_manager* owner)
: super(cfg), mailbox_(policy::normal_messages{}), owner_(owner) {
mailbox_.try_block();
}
abstract_actor_shell::~abstract_actor_shell() {
// nop
}
// -- state modifiers ----------------------------------------------------------
void abstract_actor_shell::quit(error reason) {
cleanup(std::move(reason), nullptr);
}
// -- mailbox access -----------------------------------------------------------
mailbox_element_ptr abstract_actor_shell::next_message() {
if (!mailbox_.blocked()) {
mailbox_.fetch_more();
auto& q = mailbox_.queue();
if (q.total_task_size() > 0) {
q.inc_deficit(1);
return q.next();
}
}
return nullptr;
}
bool abstract_actor_shell::try_block_mailbox() {
return mailbox_.try_block();
}
// -- message processing -------------------------------------------------------
bool abstract_actor_shell::consume_message() {
CAF_LOG_TRACE("");
if (auto msg = next_message()) {
current_element_ = msg.get();
CAF_LOG_RECEIVE_EVENT(current_element_);
CAF_BEFORE_PROCESSING(this, *msg);
auto mid = msg->mid;
if (!mid.is_response()) {
detail::default_invoke_result_visitor<abstract_actor_shell> visitor{this};
if (auto result = bhvr_(msg->payload)) {
visitor(*result);
} else {
auto fallback_result = (*fallback_)(msg->payload);
visit(visitor, fallback_result);
}
} else if (auto i = multiplexed_responses_.find(mid);
i != multiplexed_responses_.end()) {
auto bhvr = std::move(i->second);
multiplexed_responses_.erase(i);
auto res = bhvr(msg->payload);
if (!res) {
CAF_LOG_DEBUG("got unexpected_response");
auto err_msg = make_message(
make_error(sec::unexpected_response, std::move(msg->payload)));
bhvr(err_msg);
}
}
CAF_AFTER_PROCESSING(this, invoke_message_result::consumed);
CAF_LOG_SKIP_OR_FINALIZE_EVENT(invoke_message_result::consumed);
return true;
}
return false;
}
void abstract_actor_shell::add_multiplexed_response_handler(
message_id response_id, behavior bhvr) {
if (bhvr.timeout() != infinite)
request_response_timeout(bhvr.timeout(), response_id);
multiplexed_responses_.emplace(response_id, std::move(bhvr));
}
// -- overridden functions of abstract_actor -----------------------------------
bool abstract_actor_shell::enqueue(mailbox_element_ptr ptr, execution_unit*) {
CAF_ASSERT(ptr != nullptr);
CAF_ASSERT(!getf(is_blocking_flag));
CAF_LOG_TRACE(CAF_ARG(*ptr));
CAF_LOG_SEND_EVENT(ptr);
auto mid = ptr->mid;
auto sender = ptr->sender;
auto collects_metrics = getf(abstract_actor::collects_metrics_flag);
if (collects_metrics) {
ptr->set_enqueue_time();
metrics_.mailbox_size->inc();
}
switch (mailbox().push_back(std::move(ptr))) {
case intrusive::inbox_result::unblocked_reader: {
CAF_LOG_ACCEPT_EVENT(true);
std::unique_lock<std::mutex> guard{owner_mtx_};
// The owner can only be null if this enqueue succeeds, then we close the
// mailbox and reset owner_ in cleanup() before acquiring the mutex here.
// Hence, the mailbox element has already been disposed and we can simply
// skip any further processing.
if (owner_)
owner_->mpx().register_writing(owner_);
return true;
}
case intrusive::inbox_result::success:
// Enqueued to a running actors' mailbox: nothing to do.
CAF_LOG_ACCEPT_EVENT(false);
return true;
default: { // intrusive::inbox_result::queue_closed
CAF_LOG_REJECT_EVENT();
home_system().base_metrics().rejected_messages->inc();
if (collects_metrics)
metrics_.mailbox_size->dec();
if (mid.is_request()) {
detail::sync_request_bouncer f{exit_reason()};
f(sender, mid);
}
return false;
}
}
}
mailbox_element* abstract_actor_shell::peek_at_next_mailbox_element() {
return mailbox().closed() || mailbox().blocked() ? nullptr : mailbox().peek();
}
// -- overridden functions of local_actor --------------------------------------
void abstract_actor_shell::launch(execution_unit*, bool, bool hide) {
CAF_PUSH_AID_FROM_PTR(this);
CAF_LOG_TRACE(CAF_ARG(hide));
CAF_ASSERT(!getf(is_blocking_flag));
if (!hide)
register_at_system();
}
bool abstract_actor_shell::cleanup(error&& fail_state, execution_unit* host) {
CAF_LOG_TRACE(CAF_ARG(fail_state));
// Clear mailbox.
if (!mailbox_.closed()) {
mailbox_.close();
detail::sync_request_bouncer bounce{fail_state};
auto dropped = mailbox_.queue().new_round(1000, bounce).consumed_items;
while (dropped > 0) {
if (getf(abstract_actor::collects_metrics_flag)) {
auto val = static_cast<int64_t>(dropped);
metrics_.mailbox_size->dec(val);
}
dropped = mailbox_.queue().new_round(1000, bounce).consumed_items;
}
}
// Detach from owner.
{
std::unique_lock<std::mutex> guard{owner_mtx_};
owner_ = nullptr;
}
// Dispatch to parent's `cleanup` function.
return super::cleanup(std::move(fail_state), host);
}
} // 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/actor_shell.hpp"
namespace caf::net {
// -- actor_shell --------------------------------------------------------------
actor_shell::actor_shell(actor_config& cfg, socket_manager* owner)
: super(cfg, owner) {
// nop
}
actor_shell::~actor_shell() {
// nop
}
const char* actor_shell::name() const {
return "caf.net.actor-shell";
}
// -- actor_shell_ptr ----------------------------------------------------------
actor_shell_ptr::actor_shell_ptr(strong_actor_ptr ptr) noexcept
: ptr_(std::move(ptr)) {
// nop
}
actor_shell_ptr::~actor_shell_ptr() {
if (auto ptr = get())
ptr->quit(exit_reason::normal);
}
actor_shell_ptr::handle_type actor_shell_ptr::as_actor() const noexcept {
return actor_cast<actor>(ptr_);
}
void actor_shell_ptr::detach(error reason) {
if (auto ptr = get()) {
ptr->quit(std::move(reason));
ptr_.release();
}
}
actor_shell_ptr::element_type* actor_shell_ptr::get() const noexcept {
if (ptr_) {
auto ptr = actor_cast<abstract_actor*>(ptr_);
return static_cast<actor_shell*>(ptr);
} else {
return 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/backend/tcp.hpp"
#include <mutex>
#include <string>
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/basp/application.hpp"
#include "caf/net/basp/application_factory.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/defaults.hpp"
#include "caf/net/doorman.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.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"
namespace caf::net::backend {
tcp::tcp(middleman& mm)
: middleman_backend("tcp"), mm_(mm), proxies_(mm.system(), *this) {
// nop
}
tcp::~tcp() {
// nop
}
error tcp::init() {
uint16_t conf_port = get_or<uint16_t>(mm_.system().config(),
"caf.middleman.tcp-port",
defaults::middleman::tcp_port);
ip_endpoint ep;
auto local_address = std::string("[::]:") + std::to_string(conf_port);
if (auto err = detail::parse(local_address, ep))
return err;
auto acceptor = make_tcp_accept_socket(ep, true);
if (!acceptor)
return acceptor.error();
auto acc_guard = make_socket_guard(*acceptor);
if (auto err = nonblocking(acc_guard.socket(), true))
return err;
auto port = local_port(*acceptor);
if (!port)
return port.error();
listening_port_ = *port;
CAF_LOG_INFO("doorman spawned on " << CAF_ARG(*port));
auto doorman_uri = make_uri("tcp://doorman");
if (!doorman_uri)
return doorman_uri.error();
auto& mpx = mm_.mpx();
auto mgr = make_endpoint_manager(
mpx, mm_.system(),
doorman{acc_guard.release(), basp::application_factory{proxies_}});
if (auto err = mgr->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << err);
return err;
}
return none;
}
void tcp::stop() {
for (const auto& p : peers_)
proxies_.erase(p.first);
peers_.clear();
}
expected<endpoint_manager_ptr> tcp::get_or_connect(const uri& locator) {
if (auto auth = locator.authority_only()) {
auto id = make_node_id(*auth);
if (auto ptr = peer(id))
return ptr;
auto host = locator.authority().host;
if (auto hostname = get_if<std::string>(&host)) {
for (const auto& addr : ip::resolve(*hostname)) {
ip_endpoint ep{addr, locator.authority().port};
auto sock = make_connected_tcp_stream_socket(ep);
if (!sock)
continue;
else
return emplace(id, *sock);
}
}
}
return sec::cannot_connect_to_node;
}
endpoint_manager_ptr tcp::peer(const node_id& id) {
return get_peer(id);
}
void tcp::resolve(const uri& locator, const actor& listener) {
if (auto p = get_or_connect(locator))
(*p)->resolve(locator, listener);
else
anon_send(listener, p.error());
}
strong_actor_ptr tcp::make_proxy(node_id nid, actor_id aid) {
using impl_type = actor_proxy_impl;
using hdl_type = strong_actor_ptr;
actor_config cfg;
return make_actor<impl_type, hdl_type>(aid, nid, &mm_.system(), cfg,
peer(nid));
}
void tcp::set_last_hop(node_id*) {
// nop
}
uint16_t tcp::port() const noexcept {
return listening_port_;
}
endpoint_manager_ptr tcp::get_peer(const node_id& id) {
const std::lock_guard<std::mutex> lock(lock_);
auto i = peers_.find(id);
if (i != peers_.end())
return i->second;
return nullptr;
}
} // namespace caf::net::backend
// 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/backend/test.hpp"
#include "caf/expected.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/basp/application.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/raise_error.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
namespace caf::net::backend {
test::test(middleman& mm)
: middleman_backend("test"), mm_(mm), proxies_(mm.system(), *this) {
// nop
}
test::~test() {
// nop
}
error test::init() {
return none;
}
void test::stop() {
for (const auto& p : peers_)
proxies_.erase(p.first);
peers_.clear();
}
endpoint_manager_ptr test::peer(const node_id& id) {
return get_peer(id).second;
}
expected<endpoint_manager_ptr> test::get_or_connect(const uri& locator) {
if (auto ptr = peer(make_node_id(*locator.authority_only())))
return ptr;
return make_error(sec::runtime_error,
"connecting not implemented in test backend");
}
void test::resolve(const uri& locator, const actor& listener) {
auto id = locator.authority_only();
if (id)
peer(make_node_id(*id))->resolve(locator, listener);
else
anon_send(listener, error(basp::ec::invalid_locator));
}
strong_actor_ptr test::make_proxy(node_id nid, actor_id aid) {
using impl_type = actor_proxy_impl;
using hdl_type = strong_actor_ptr;
actor_config cfg;
return make_actor<impl_type, hdl_type>(aid, nid, &mm_.system(), cfg,
peer(nid));
}
void test::set_last_hop(node_id*) {
// nop
}
uint16_t test::port() const noexcept {
return 0;
}
test::peer_entry& test::emplace(const node_id& peer_id, stream_socket first,
stream_socket second) {
using transport_type = stream_transport<basp::application>;
if (auto err = nonblocking(second, true))
CAF_LOG_ERROR("nonblocking failed: " << err);
auto mpx = mm_.mpx();
basp::application app{proxies_};
auto mgr = make_endpoint_manager(mpx, mm_.system(),
transport_type{second, std::move(app)});
if (auto err = mgr->init()) {
CAF_LOG_ERROR("mgr->init() failed: " << err);
CAF_RAISE_ERROR("mgr->init() failed");
}
mpx->register_reading(mgr);
auto& result = peers_[peer_id];
result = std::make_pair(first, std::move(mgr));
return result;
}
test::peer_entry& test::get_peer(const node_id& id) {
auto i = peers_.find(id);
if (i != peers_.end())
return i->second;
auto sockets = make_stream_socket_pair();
if (!sockets) {
CAF_LOG_ERROR("make_stream_socket_pair failed: " << sockets.error());
CAF_RAISE_ERROR("make_stream_socket_pair failed");
}
return emplace(id, sockets->first, sockets->second);
}
} // namespace caf::net::backend
// 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/basp/application.hpp"
#include <vector>
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/defaults.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/detail/parse.hpp"
#include "caf/error.hpp"
#include "caf/logger.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/no_stages.hpp"
#include "caf/none.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
#include "caf/string_algorithms.hpp"
namespace caf::net::basp {
application::application(proxy_registry& proxies)
: proxies_(proxies), queue_{new message_queue}, hub_{new hub_type} {
// nop
}
error application::write_message(
packet_writer& writer, std::unique_ptr<endpoint_manager_queue::message> ptr) {
CAF_ASSERT(ptr != nullptr);
CAF_ASSERT(ptr->msg != nullptr);
CAF_LOG_TRACE(CAF_ARG2("content", ptr->msg->content()));
const auto& src = ptr->msg->sender;
const auto& dst = ptr->receiver;
if (dst == nullptr) {
// TODO: valid?
return none;
}
auto payload_buf = writer.next_payload_buffer();
binary_serializer sink{system(), payload_buf};
if (src != nullptr) {
auto src_id = src->id();
system().registry().put(src_id, src);
if (!sink.apply_objects(src->node(), src_id, dst->id(), ptr->msg->stages))
return sink.get_error();
} else {
if (!sink.apply_objects(node_id{}, actor_id{0}, dst->id(),
ptr->msg->stages))
return sink.get_error();
}
if (!sink.apply_objects(ptr->msg->content()))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::actor_message,
static_cast<uint32_t>(payload_buf.size()),
ptr->msg->mid.integer_value()},
hdr);
writer.write_packet(hdr, payload_buf);
return none;
}
void application::resolve(packet_writer& writer, string_view path,
const actor& listener) {
CAF_LOG_TRACE(CAF_ARG(path) << CAF_ARG(listener));
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (!sink.apply_objects(path)) {
CAF_LOG_ERROR("unable to serialize path:" << sink.get_error());
return;
}
auto req_id = next_request_id_++;
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::resolve_request,
static_cast<uint32_t>(payload.size()), req_id},
hdr);
writer.write_packet(hdr, payload);
pending_resolves_.emplace(req_id, listener);
}
void application::new_proxy(packet_writer& writer, actor_id id) {
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::monitor_message, 0, static_cast<uint64_t>(id)},
hdr);
writer.write_packet(hdr);
}
void application::local_actor_down(packet_writer& writer, actor_id id,
error reason) {
auto payload = writer.next_payload_buffer();
binary_serializer sink{system(), payload};
if (!sink.apply_objects(reason))
CAF_RAISE_ERROR("unable to serialize an error");
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
static_cast<uint32_t>(payload.size()),
static_cast<uint64_t>(id)},
hdr);
writer.write_packet(hdr, payload);
}
strong_actor_ptr application::resolve_local_path(string_view path) {
CAF_LOG_TRACE(CAF_ARG(path));
// We currently support two path formats: `id/<actor_id>` and `name/<atom>`.
static constexpr string_view id_prefix = "id/";
if (starts_with(path, id_prefix)) {
path.remove_prefix(id_prefix.size());
actor_id aid;
if (auto err = detail::parse(path, aid))
return nullptr;
return system().registry().get(aid);
}
static constexpr string_view name_prefix = "name/";
if (starts_with(path, name_prefix)) {
path.remove_prefix(name_prefix.size());
std::string name;
if (auto err = detail::parse(path, name))
return nullptr;
return system().registry().get(name);
}
return nullptr;
}
error application::handle(size_t& next_read_size, packet_writer& writer,
byte_span bytes) {
CAF_LOG_TRACE(CAF_ARG(state_) << CAF_ARG2("bytes.size", bytes.size()));
switch (state_) {
case connection_state::await_handshake_header: {
if (bytes.size() != header_size)
return ec::unexpected_number_of_bytes;
hdr_ = header::from_bytes(bytes);
if (hdr_.type != message_type::handshake)
return ec::missing_handshake;
if (hdr_.operation_data != version)
return ec::version_mismatch;
if (hdr_.payload_len == 0)
return ec::missing_payload;
state_ = connection_state::await_handshake_payload;
next_read_size = hdr_.payload_len;
return none;
}
case connection_state::await_handshake_payload: {
if (auto err = handle_handshake(writer, hdr_, bytes))
return err;
state_ = connection_state::await_header;
return none;
}
case connection_state::await_header: {
if (bytes.size() != header_size)
return ec::unexpected_number_of_bytes;
hdr_ = header::from_bytes(bytes);
if (hdr_.payload_len == 0)
return handle(writer, hdr_, byte_span{});
next_read_size = hdr_.payload_len;
state_ = connection_state::await_payload;
return none;
}
case connection_state::await_payload: {
if (bytes.size() != hdr_.payload_len)
return ec::unexpected_number_of_bytes;
state_ = connection_state::await_header;
return handle(writer, hdr_, bytes);
}
default:
return ec::illegal_state;
}
}
error application::handle(packet_writer& writer, header hdr,
byte_span payload) {
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
switch (hdr.type) {
case message_type::handshake:
return ec::unexpected_handshake;
case message_type::actor_message:
return handle_actor_message(writer, hdr, payload);
case message_type::resolve_request:
return handle_resolve_request(writer, hdr, payload);
case message_type::resolve_response:
return handle_resolve_response(writer, hdr, payload);
case message_type::monitor_message:
return handle_monitor_message(writer, hdr, payload);
case message_type::down_message:
return handle_down_message(writer, hdr, payload);
case message_type::heartbeat:
return none;
default:
return ec::unimplemented;
}
}
error application::handle_handshake(packet_writer&, header hdr,
byte_span payload) {
CAF_LOG_TRACE(CAF_ARG(hdr) << CAF_ARG2("payload.size", payload.size()));
if (hdr.type != message_type::handshake)
return ec::missing_handshake;
if (hdr.operation_data != version)
return ec::version_mismatch;
node_id peer_id;
std::vector<std::string> app_ids;
binary_deserializer source{&executor_, payload};
if (!source.apply_objects(peer_id, app_ids))
return source.get_error();
if (!peer_id || app_ids.empty())
return ec::invalid_handshake;
auto ids = get_or(system().config(), "caf.middleman.app-identifiers",
basp::application::default_app_ids());
auto predicate = [=](const std::string& x) {
return std::find(ids.begin(), ids.end(), x) != ids.end();
};
if (std::none_of(app_ids.begin(), app_ids.end(), predicate))
return ec::app_identifiers_mismatch;
peer_id_ = std::move(peer_id);
state_ = connection_state::await_header;
return none;
}
error application::handle_actor_message(packet_writer&, header hdr,
byte_span payload) {
auto worker = hub_->pop();
if (worker != nullptr) {
CAF_LOG_DEBUG("launch BASP worker for deserializing an actor_message");
worker->launch(node_id{}, hdr, payload);
} else {
CAF_LOG_DEBUG(
"out of BASP workers, continue deserializing an actor_message");
// If no worker is available then we have no other choice than to take
// the performance hit and deserialize in this thread.
struct handler : remote_message_handler<handler> {
handler(message_queue* queue, proxy_registry* proxies,
actor_system* system, node_id last_hop, basp::header& hdr,
byte_span payload)
: queue_(queue),
proxies_(proxies),
system_(system),
last_hop_(std::move(last_hop)),
hdr_(hdr),
payload_(payload) {
msg_id_ = queue_->new_id();
}
message_queue* queue_;
proxy_registry* proxies_;
actor_system* system_;
node_id last_hop_;
basp::header& hdr_;
byte_span payload_;
uint64_t msg_id_;
};
handler f{queue_.get(), &proxies_, system_, node_id{}, hdr, payload};
f.handle_remote_message(&executor_);
}
return none;
}
error application::handle_resolve_request(packet_writer& writer, header rec_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(rec_hdr) << CAF_ARG2("received.size", received.size()));
CAF_ASSERT(rec_hdr.type == message_type::resolve_request);
size_t path_size = 0;
binary_deserializer source{&executor_, received};
if (!source.begin_sequence(path_size))
return source.get_error();
// We expect the received buffer to contain the path only.
if (path_size != source.remaining())
return ec::invalid_payload;
auto remainder = source.remainder();
string_view path{reinterpret_cast<const char*>(remainder.data()),
remainder.size()};
// Write result.
auto result = resolve_local_path(path);
actor_id aid;
std::set<std::string> ifs;
if (result) {
aid = result->id();
system().registry().put(aid, result);
} else {
aid = 0;
}
// TODO: figure out how to obtain messaging interface.
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (!sink.apply_objects(aid, ifs))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::resolve_response,
static_cast<uint32_t>(payload.size()),
rec_hdr.operation_data},
hdr);
writer.write_packet(hdr, payload);
return none;
}
error application::handle_resolve_response(packet_writer&, header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
CAF_ASSERT(received_hdr.type == message_type::resolve_response);
auto i = pending_resolves_.find(received_hdr.operation_data);
if (i == pending_resolves_.end()) {
CAF_LOG_ERROR("received unknown ID in resolve_response message");
return none;
}
auto guard = detail::make_scope_guard([&] { pending_resolves_.erase(i); });
actor_id aid;
std::set<std::string> ifs;
binary_deserializer source{&executor_, received};
if (!source.apply_objects(aid, ifs)) {
anon_send(i->second, sec::remote_lookup_failed);
return source.get_error();
}
if (aid == 0) {
anon_send(i->second, strong_actor_ptr{nullptr}, std::move(ifs));
return none;
}
anon_send(i->second, proxies_.get_or_put(peer_id_, aid), std::move(ifs));
return none;
}
error application::handle_monitor_message(packet_writer& writer,
header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
if (!received.empty())
return ec::unexpected_payload;
auto aid = static_cast<actor_id>(received_hdr.operation_data);
auto hdl = system().registry().get(aid);
if (hdl != nullptr) {
endpoint_manager_ptr mgr = manager_;
auto nid = peer_id_;
hdl->get()->attach_functor([mgr, nid, aid](error reason) mutable {
mgr->enqueue_event(std::move(nid), aid, std::move(reason));
});
} else {
error reason = exit_reason::unknown;
auto payload = writer.next_payload_buffer();
binary_serializer sink{&executor_, payload};
if (!sink.apply_objects(reason))
return sink.get_error();
auto hdr = writer.next_header_buffer();
to_bytes(header{message_type::down_message,
static_cast<uint32_t>(payload.size()),
received_hdr.operation_data},
hdr);
writer.write_packet(hdr, payload);
}
return none;
}
error application::handle_down_message(packet_writer&, header received_hdr,
byte_span received) {
CAF_LOG_TRACE(CAF_ARG(received_hdr)
<< CAF_ARG2("received.size", received.size()));
error reason;
binary_deserializer source{&executor_, received};
if (!source.apply_objects(reason))
return source.get_error();
proxies_.erase(peer_id_, received_hdr.operation_data, std::move(reason));
return none;
}
error application::generate_handshake(byte_buffer& buf) {
binary_serializer sink{&executor_, buf};
if (!sink.apply_objects(system().node(),
get_or(system().config(),
"caf.middleman.app-identifiers",
application::default_app_ids())))
return sink.get_error();
return none;
}
} // namespace caf::net::basp
// 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/endpoint_manager_queue.hpp"
namespace caf::net {
endpoint_manager_queue::element::~element() {
// nop
}
endpoint_manager_queue::event::event(uri locator, actor listener)
: element(element_type::event),
value(resolve_request{std::move(locator), std::move(listener)}) {
// nop
}
endpoint_manager_queue::event::event(node_id peer, actor_id proxy_id)
: element(element_type::event), value(new_proxy{peer, proxy_id}) {
// nop
}
endpoint_manager_queue::event::event(node_id observing_peer,
actor_id local_actor_id, error reason)
: element(element_type::event),
value(local_actor_down{observing_peer, local_actor_id, std::move(reason)}) {
// nop
}
endpoint_manager_queue::event::event(std::string tag, uint64_t id)
: element(element_type::event), value(timeout{std::move(tag), id}) {
// nop
}
endpoint_manager_queue::event::~event() {
// nop
}
size_t endpoint_manager_queue::event::task_size() const noexcept {
return 1;
}
endpoint_manager_queue::message::message(mailbox_element_ptr msg,
strong_actor_ptr receiver)
: element(element_type::message),
msg(std::move(msg)),
receiver(std::move(receiver)) {
// nop
}
size_t endpoint_manager_queue::message::task_size() const noexcept {
return message_policy::task_size(*this);
}
endpoint_manager_queue::message::~message() {
// nop
}
} // namespace caf::net
#include "caf/net/http/header.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
namespace caf::net::http {
namespace {
constexpr string_view eol = "\r\n";
template <class F>
bool for_each_line(string_view input, F&& f) {
for (auto pos = input.begin();;) {
auto line_end = std::search(pos, input.end(), eol.begin(), eol.end());
if (line_end == input.end() || std::distance(pos, input.end()) == 2) {
// Reaching the end or hitting the last empty line tells us we're done.
return true;
}
if (!f(string_view{pos, line_end}))
return false;
pos = line_end + eol.size();
}
return true;
}
string_view trim(string_view str) {
str.remove_prefix(std::min(str.find_first_not_of(' '), str.size()));
auto trim_pos = str.find_last_not_of(' ');
if (trim_pos != str.npos)
str.remove_suffix(str.size() - (trim_pos + 1));
return str;
}
/// Splits `str` at the first occurrence of `sep` into the head and the
/// remainder (excluding the separator).
static std::pair<string_view, string_view> split(string_view str,
string_view sep) {
auto i = std::search(str.begin(), str.end(), sep.begin(), sep.end());
if (i != str.end())
return {{str.begin(), i}, {i + sep.size(), str.end()}};
return {{str}, {}};
}
/// Convenience function for splitting twice.
static std::tuple<string_view, string_view, string_view>
split2(string_view str, string_view sep) {
auto [first, r1] = split(str, sep);
auto [second, third] = split(r1, sep);
return {first, second, third};
}
/// @pre `y` is all lowercase
bool case_insensitive_eq(string_view x, string_view y) {
return std::equal(x.begin(), x.end(), y.begin(), y.end(),
[](char a, char b) { return tolower(a) == b; });
}
} // namespace
header::header(const header& other) {
assign(other);
}
header& header::operator=(const header& other) {
assign(other);
return *this;
}
void header::assign(const header& other) {
auto remap = [](const char* base, string_view src, const char* new_base) {
auto offset = std::distance(base, src.data());
return string_view{new_base + offset, src.size()};
};
method_ = other.method_;
uri_ = other.uri_;
if (other.valid()) {
raw_.assign(other.raw_.begin(), other.raw_.end());
auto base = other.raw_.data();
auto new_base = raw_.data();
version_ = remap(base, other.version_, new_base);
auto& fields = fields_.container();
auto& other_fields = other.fields_.container();
fields.resize(other_fields.size());
for (size_t index = 0; index < fields.size(); ++index) {
fields[index].first = remap(base, other_fields[index].first, new_base);
fields[index].second = remap(base, other_fields[index].second, new_base);
}
} else {
raw_.clear();
version_ = string_view{};
fields_.clear();
}
}
std::pair<status, string_view> header::parse(string_view raw) {
CAF_LOG_TRACE(CAF_ARG(raw));
// Sanity checking and copying of the raw input.
using namespace literals;
if (raw.empty()) {
raw_.clear();
return {status::bad_request, "Empty header."};
};
raw_.assign(raw.begin(), raw.end());
// Parse the first line, i.e., "METHOD REQUEST-URI VERSION".
auto [first_line, remainder] = split(string_view{raw_.data(), raw_.size()},
eol);
auto [method_str, request_uri_str, version] = split2(first_line, " ");
// The path must be absolute.
if (request_uri_str.empty() || request_uri_str.front() != '/') {
CAF_LOG_DEBUG("Malformed Request-URI: expected an absolute path.");
raw_.clear();
return {status::bad_request,
"Malformed Request-URI: expected an absolute path."};
}
// The path must form a valid URI when prefixing a scheme. We don't actually
// care about the scheme, so just use "foo" here for the validation step.
if (auto res = make_uri("nil://host" + to_string(request_uri_str))) {
uri_ = std::move(*res);
} else {
CAF_LOG_DEBUG("Failed to parse URI" << request_uri_str << "->"
<< res.error());
raw_.clear();
return {status::bad_request, "Malformed Request-URI."};
}
// Verify and store the method.
if (case_insensitive_eq(method_str, "get")) {
method_ = method::get;
} else if (case_insensitive_eq(method_str, "head")) {
method_ = method::head;
} else if (case_insensitive_eq(method_str, "post")) {
method_ = method::post;
} else if (case_insensitive_eq(method_str, "put")) {
method_ = method::put;
} else if (case_insensitive_eq(method_str, "delete")) {
method_ = method::del;
} else if (case_insensitive_eq(method_str, "connect")) {
method_ = method::connect;
} else if (case_insensitive_eq(method_str, "options")) {
method_ = method::options;
} else if (case_insensitive_eq(method_str, "trace")) {
method_ = method::trace;
} else {
CAF_LOG_DEBUG("Invalid HTTP method.");
raw_.clear();
return {status::bad_request, "Invalid HTTP method."};
}
// Store the remaining header fields.
version_ = version;
fields_.clear();
bool ok = for_each_line(remainder, [this](string_view line) {
if (auto sep = std::find(line.begin(), line.end(), ':');
sep != line.end()) {
auto key = trim({line.begin(), sep});
auto val = trim({sep + 1, line.end()});
if (!key.empty()) {
fields_.emplace(key, val);
return true;
}
}
return false;
});
if (ok) {
return {status::ok, "OK"};
} else {
raw_.clear();
version_ = string_view{};
fields_.clear();
return {status::bad_request, "Malformed header fields."};
}
}
bool header::chunked_transfer_encoding() const {
return field("Transfer-Encoding").find("chunked") != string_view::npos;
}
optional<size_t> header::content_length() const {
return field_as<size_t>("Content-Length");
}
} // namespace caf::net::http
#include "caf/net/http/method.hpp"
namespace caf::net::http {
std::string to_rfc_string(method x) {
using namespace caf::literals;
switch (x) {
case method::get:
return "GET";
case method::head:
return "HEAD";
case method::post:
return "POST";
case method::put:
return "PUT";
case method::del:
return "DELETE";
case method::connect:
return "CONNECT";
case method::options:
return "OPTIONS";
case method::trace:
return "TRACE";
default:
return "INVALID";
}
}
} // namespace caf::net::http
#include "caf/net/http/status.hpp"
namespace caf::net::http {
string_view phrase(status code) {
using namespace caf::literals;
switch (code) {
case status::continue_request:
return "Continue"_sv;
case status::switching_protocols:
return "Switching Protocols"_sv;
case status::ok:
return "OK"_sv;
case status::created:
return "Created"_sv;
case status::accepted:
return "Accepted"_sv;
case status::non_authoritative_information:
return "Non-Authoritative Information"_sv;
case status::no_content:
return "No Content"_sv;
case status::reset_content:
return "Reset Content"_sv;
case status::partial_content:
return "Partial Content"_sv;
case status::multiple_choices:
return "Multiple Choices"_sv;
case status::moved_permanently:
return "Moved Permanently"_sv;
case status::found:
return "Found"_sv;
case status::see_other:
return "See Other"_sv;
case status::not_modified:
return "Not Modified"_sv;
case status::use_proxy:
return "Use Proxy"_sv;
case status::temporary_redirect:
return "Temporary Redirect"_sv;
case status::bad_request:
return "Bad Request"_sv;
case status::unauthorized:
return "Unauthorized"_sv;
case status::payment_required:
return "Payment Required"_sv;
case status::forbidden:
return "Forbidden"_sv;
case status::not_found:
return "Not Found"_sv;
case status::method_not_allowed:
return "Method Not Allowed"_sv;
case status::not_acceptable:
return "Not Acceptable"_sv;
case status::proxy_authentication_required:
return "Proxy Authentication Required"_sv;
case status::request_timeout:
return "Request Timeout"_sv;
case status::conflict:
return "Conflict"_sv;
case status::gone:
return "Gone"_sv;
case status::length_required:
return "Length Required"_sv;
case status::precondition_failed:
return "Precondition Failed"_sv;
case status::payload_too_large:
return "Payload Too Large"_sv;
case status::uri_too_long:
return "URI Too Long"_sv;
case status::unsupported_media_type:
return "Unsupported Media Type"_sv;
case status::range_not_satisfiable:
return "Range Not Satisfiable"_sv;
case status::expectation_failed:
return "Expectation Failed"_sv;
case status::upgrade_required:
return "Upgrade Required"_sv;
case status::precondition_required:
return "Precondition Required"_sv;
case status::too_many_requests:
return "Too Many Requests"_sv;
case status::request_header_fields_too_large:
return "Request Header Fields Too Large"_sv;
case status::internal_server_error:
return "Internal Server Error"_sv;
case status::not_implemented:
return "Not Implemented"_sv;
case status::bad_gateway:
return "Bad Gateway"_sv;
case status::service_unavailable:
return "Service Unavailable"_sv;
case status::gateway_timeout:
return "Gateway Timeout"_sv;
case status::http_version_not_supported:
return "HTTP Version Not Supported"_sv;
case status::network_authentication_required:
return "Network Authentication Required"_sv;
default:
return "Unrecognized";
}
}
} // namespace caf::net::http
#include "caf/net/http/v1.hpp"
#include <array>
#include <string_view>
using namespace std::literals;
namespace caf::net::http::v1 {
namespace {
struct writer {
byte_buffer* buf;
};
writer& operator<<(writer& out, char x) {
out.buf->push_back(static_cast<byte>(x));
return out;
}
writer& operator<<(writer& out, string_view str) {
auto bytes = as_bytes(make_span(str));
out.buf->insert(out.buf->end(), bytes.begin(), bytes.end());
return out;
}
writer& operator<<(writer& out, std::string_view str) {
auto bytes = as_bytes(make_span(str));
out.buf->insert(out.buf->end(), bytes.begin(), bytes.end());
return out;
}
writer& operator<<(writer& out, const std::string& str) {
return out << string_view{str};
}
} // namespace
std::pair<string_view, byte_span> split_header(byte_span bytes) {
std::array<byte, 4> end_of_header{{
byte{'\r'},
byte{'\n'},
byte{'\r'},
byte{'\n'},
}};
if (auto i = std::search(bytes.begin(), bytes.end(), end_of_header.begin(),
end_of_header.end());
i == bytes.end()) {
return {string_view{}, bytes};
} else {
auto offset = static_cast<size_t>(std::distance(bytes.begin(), i));
offset += end_of_header.size();
return {string_view{reinterpret_cast<const char*>(bytes.begin()), offset},
bytes.subspan(offset)};
}
}
void write_header(status code, const header_fields_map& fields,
byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 "sv << std::to_string(static_cast<int>(code)) << ' '
<< phrase(code) << "\r\n"sv;
for (auto& [key, val] : fields)
out << key << ": "sv << val << "\r\n"sv;
out << "\r\n"sv;
}
void write_response(status code, string_view content_type, string_view content,
byte_buffer& buf) {
header_fields_map fields;
write_response(code, content_type, content, fields, buf);
writer out{&buf};
out << content;
}
void write_response(status code, string_view content_type, string_view content,
const header_fields_map& fields, byte_buffer& buf) {
writer out{&buf};
out << "HTTP/1.1 "sv << std::to_string(static_cast<int>(code)) << ' '
<< phrase(code) << "\r\n"sv;
out << "Content-Type: "sv << content_type << "\r\n"sv;
out << "Content-Length: "sv << std::to_string(content.size()) << "\r\n"sv;
for (auto& [key, val] : fields)
out << key << ": "sv << val << "\r\n"sv;
out << "\r\n"sv;
out << content;
}
} // namespace caf::net::http::v1
// 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/middleman.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/detail/set_thread_name.hpp"
#include "caf/expected.hpp"
#include "caf/init_global_meta_objects.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/middleman_backend.hpp"
#include "caf/raise_error.hpp"
#include "caf/sec.hpp"
#include "caf/send.hpp"
#include "caf/uri.hpp"
namespace caf::net {
void middleman::init_global_meta_objects() {
caf::init_global_meta_objects<id_block::net_module>();
}
middleman::middleman(actor_system& sys) : sys_(sys), mpx_(this) {
// nop
}
middleman::~middleman() {
// nop
}
void middleman::start() {
if (!get_or(config(), "caf.middleman.manual-multiplexing", false)) {
mpx_thread_ = std::thread{[this] {
CAF_SET_LOGGER_SYS(&sys_);
detail::set_thread_name("caf.net.mpx");
sys_.thread_started();
mpx_.set_thread_id();
mpx_.run();
sys_.thread_terminates();
}};
} else {
mpx_.set_thread_id();
}
}
void middleman::stop() {
for (const auto& backend : backends_)
backend->stop();
mpx_.shutdown();
if (mpx_thread_.joinable())
mpx_thread_.join();
else
mpx_.run();
}
void middleman::init(actor_system_config& cfg) {
if (auto err = mpx_.init()) {
CAF_LOG_ERROR("mpx_.init() failed: " << err);
CAF_RAISE_ERROR("mpx_.init() failed");
}
if (auto node_uri = get_if<uri>(&cfg, "caf.middleman.this-node")) {
auto this_node = make_node_id(std::move(*node_uri));
sys_.node_.swap(this_node);
} else {
// CAF_RAISE_ERROR("no valid entry for caf.middleman.this-node found");
}
for (auto& backend : backends_)
if (auto err = backend->init()) {
CAF_LOG_ERROR("failed to initialize backend: " << err);
CAF_RAISE_ERROR("failed to initialize backend");
}
}
middleman::module::id_t middleman::id() const {
return module::network_manager;
}
void* middleman::subtype_ptr() {
return this;
}
void middleman::add_module_options(actor_system_config& cfg) {
config_option_adder{cfg.custom_options(), "caf.middleman"}
.add<std::vector<std::string>>("app-identifiers",
"valid application identifiers of this node")
.add<uri>("this-node", "locator of this CAF node")
.add<size_t>("max-consecutive-reads",
"max. number of consecutive reads per broker")
.add<bool>("manual-multiplexing",
"disables background activity of the multiplexer")
.add<size_t>("workers", "number of deserialization workers")
.add<timespan>("heartbeat-interval", "interval of heartbeat messages")
.add<timespan>("connection-timeout",
"max. time between messages before declaring a node dead "
"(disabled if 0, ignored if heartbeats are disabled)")
.add<std::string>("network-backend", "legacy option");
}
expected<endpoint_manager_ptr> middleman::connect(const uri& locator) {
if (auto ptr = backend(locator.scheme()))
return ptr->get_or_connect(locator);
else
return basp::ec::invalid_scheme;
}
void middleman::resolve(const uri& locator, const actor& listener) {
auto ptr = backend(locator.scheme());
if (ptr != nullptr)
ptr->resolve(locator, listener);
else
anon_send(listener, error{basp::ec::invalid_scheme});
}
middleman_backend* middleman::backend(string_view scheme) const noexcept {
auto predicate = [&](const middleman_backend_ptr& ptr) {
return ptr->id() == scheme;
};
auto i = std::find_if(backends_.begin(), backends_.end(), predicate);
if (i != backends_.end())
return i->get();
return nullptr;
}
expected<uint16_t> middleman::port(string_view scheme) const {
if (auto ptr = backend(scheme))
return ptr->port();
else
return basp::ec::invalid_scheme;
}
} // 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/middleman_backend.hpp"
namespace caf::net {
middleman_backend::middleman_backend(std::string id) : id_(std::move(id)) {
// nop
}
middleman_backend::~middleman_backend() {
// nop
}
} // 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/packet_writer.hpp"
namespace caf::net {
packet_writer::~packet_writer() {
// nop
}
} // 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/web_socket/handshake.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <random>
#include <tuple>
#include "caf/config.hpp"
#include "caf/detail/base64.hpp"
#include "caf/hash/sha1.hpp"
#include "caf/string_algorithms.hpp"
namespace caf::net::web_socket {
handshake::handshake() noexcept {
key_.fill(byte{0});
}
bool handshake::has_valid_key() const noexcept {
auto non_zero = [](byte x) { return x != byte{0}; };
return std::any_of(key_.begin(), key_.end(), non_zero);
}
bool handshake::assign_key(string_view base64_key) {
// Base 64 produces character groups of size 4. This means our key has to use
// six groups, but the last two characters are always padding.
if (base64_key.size() == 24 && ends_with(base64_key, "==")) {
std::vector<byte> buf;
buf.reserve(18);
if (detail::base64::decode(base64_key, buf)) {
CAF_ASSERT(buf.size() == 16);
key_type bytes;
std::copy(buf.begin(), buf.end(), bytes.begin());
key(bytes);
return true;
}
}
return false;
}
std::string handshake::response_key() const {
// For details on the (convoluted) algorithm see RFC 6455.
auto str = detail::base64::encode(key_);
str += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
auto sha = hash::sha1::compute(str);
str.clear();
detail::base64::encode(sha, str);
return str;
}
void handshake::randomize_key() {
std::random_device rd;
randomize_key(rd());
}
void handshake::randomize_key(unsigned seed) {
std::minstd_rand rng{seed};
std::uniform_int_distribution<> f{0, 255};
for (auto& x : key_)
x = static_cast<byte>(f(rng));
}
bool handshake::has_mandatory_fields() const noexcept {
return fields_.contains("_endpoint") && fields_.contains("_host");
}
// -- HTTP generation and validation -------------------------------------------
namespace {
struct writer {
byte_buffer* buf;
};
writer& operator<<(writer& out, string_view str) {
auto bytes = as_bytes(make_span(str));
out.buf->insert(out.buf->end(), bytes.begin(), bytes.end());
return out;
}
template <class F>
auto operator<<(writer& out, F&& f) -> decltype(f(out)) {
return f(out);
}
} // namespace
void handshake::write_http_1_request(byte_buffer& buf) const {
auto encoded_key = [this](auto& out) -> decltype(auto) {
detail::base64::encode(key_, *out.buf);
return out;
};
writer out{&buf};
out << "GET " << lookup("_endpoint") << " HTTP/1.1\r\n"
<< "Host: " << lookup("_host") << "\r\n"
<< "Upgrade: websocket\r\n"
<< "Connection: Upgrade\r\n"
<< "Sec-WebSocket-Version: 13\r\n"
<< "Sec-WebSocket-Key: " << encoded_key << "\r\n";
for (auto& [key, val] : fields_)
if (key[0] != '_')
out << key << ": " << val << "\r\n";
out << "\r\n";
}
void handshake::write_http_1_response(byte_buffer& buf) const {
writer out{&buf};
out << "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: "
<< response_key() << "\r\n\r\n";
}
namespace {
template <class F>
void for_each_http_line(string_view lines, F&& f) {
using namespace caf::literals;
auto nl = "\r\n"_sv;
for (;;) {
if (auto pos = lines.find(nl); pos != string_view::npos) {
auto line = string_view{lines.data(), pos};
if (!line.empty())
f(string_view{lines.data(), pos});
lines.remove_prefix(pos + 2);
} else {
return;
}
}
}
// Splits `str` at the first occurrence of `sep` into the head and the
// remainder (excluding the separator).
std::pair<string_view, string_view> split(string_view str, string_view sep) {
auto i = std::search(str.begin(), str.end(), sep.begin(), sep.end());
if (i != str.end())
return {{str.begin(), i}, {i + sep.size(), str.end()}};
return {{str}, {}};
}
// Convenience function for splitting twice.
std::tuple<string_view, string_view, string_view> split2(string_view str,
string_view sep) {
auto [first, r1] = split(str, sep);
auto [second, third] = split(r1, sep);
return {first, second, third};
}
void trim(string_view& str) {
auto non_whitespace = [](char c) { return !isspace(c); };
if (std::any_of(str.begin(), str.end(), non_whitespace)) {
while (str.front() == ' ')
str.remove_prefix(1);
while (str.back() == ' ')
str.remove_suffix(1);
} else {
str = string_view{};
}
}
bool lowercase_equal(string_view x, string_view y) {
if (x.size() != y.size()) {
return false;
} else {
for (size_t index = 0; index < x.size(); ++index)
if (tolower(x[index]) != tolower(y[index]))
return false;
return true;
}
}
struct response_checker {
string_view ws_key;
bool has_status_101 = false;
bool has_upgrade_field = false;
bool has_connection_field = false;
bool has_ws_accept_field = false;
response_checker(string_view key) : ws_key(key) {
// nop
}
bool ok() const noexcept {
return has_status_101 && has_upgrade_field && has_connection_field
&& has_ws_accept_field;
}
void operator()(string_view line) noexcept {
if (starts_with(line, "HTTP/1")) {
string_view code;
std::tie(std::ignore, code, std::ignore) = split2(line, " ");
has_status_101 = code == "101";
} else {
auto [field, value] = split(line, ":");
trim(field);
trim(value);
if (field == "Upgrade")
has_upgrade_field = lowercase_equal(value, "websocket");
else if (field == "Connection")
has_connection_field = lowercase_equal(value, "upgrade");
else if (field == "Sec-WebSocket-Accept")
has_ws_accept_field = value == ws_key;
}
}
};
} // namespace
bool handshake::is_valid_http_1_response(string_view http_response) const {
auto seed = detail::base64::encode(key_);
seed += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
auto response_key_sha = hash::sha1::compute(seed);
auto response_key = detail::base64::encode(response_key_sha);
response_checker checker{response_key};
for_each_http_line(http_response, checker);
return checker.ok();
}
// -- utility ------------------------------------------------------------------
string_view handshake::lookup(string_view field_name) const noexcept {
if (auto i = fields_.find(field_name); i != fields_.end())
return i->second;
else
return string_view{};
}
} // 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/network_socket.hpp"
#include <cstdint>
#include "caf/config.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/error.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/sec.hpp"
#include "caf/variant.hpp"
namespace {
uint16_t port_of(sockaddr_in& what) {
return what.sin_port;
}
uint16_t port_of(sockaddr_in6& what) {
return what.sin6_port;
}
uint16_t port_of(sockaddr& what) {
switch (what.sa_family) {
case AF_INET:
return port_of(reinterpret_cast<sockaddr_in&>(what));
case AF_INET6:
return port_of(reinterpret_cast<sockaddr_in6&>(what));
default:
break;
}
CAF_CRITICAL("invalid protocol family");
}
} // namespace
namespace caf::net {
#if defined(CAF_MACOS) || defined(CAF_IOS) || defined(CAF_BSD)
# define CAF_HAS_NOSIGPIPE_SOCKET_FLAG
#endif
#ifdef CAF_WINDOWS
error allow_sigpipe(network_socket x, bool) {
if (x == invalid_socket)
return make_error(sec::network_syscall_failed, "setsockopt",
"invalid socket");
return none;
}
error allow_udp_connreset(network_socket x, bool new_value) {
DWORD bytes_returned = 0;
CAF_NET_SYSCALL("WSAIoctl", res, !=, 0,
WSAIoctl(x.id, _WSAIOW(IOC_VENDOR, 12), &new_value,
sizeof(new_value), NULL, 0, &bytes_returned, NULL,
NULL));
return none;
}
#else // CAF_WINDOWS
error allow_sigpipe(network_socket x, [[maybe_unused]] bool new_value) {
# ifdef CAF_HAS_NOSIGPIPE_SOCKET_FLAG
int value = new_value ? 0 : 1;
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, SOL_SOCKET, SO_NOSIGPIPE, &value,
static_cast<unsigned>(sizeof(value))));
# else // CAF_HAS_NO_SIGPIPE_SOCKET_FLAG
if (x == invalid_socket)
return make_error(sec::network_syscall_failed, "setsockopt",
"invalid socket");
# endif // CAF_HAS_NO_SIGPIPE_SOCKET_FLAG
return none;
}
error allow_udp_connreset(network_socket x, bool) {
// SIO_UDP_CONNRESET only exists on Windows
if (x == invalid_socket)
return make_error(sec::network_syscall_failed, "WSAIoctl",
"invalid socket");
return none;
}
#endif // CAF_WINDOWS
expected<size_t> send_buffer_size(network_socket x) {
int size = 0;
socket_size_type ret_size = sizeof(size);
CAF_NET_SYSCALL("getsockopt", res, !=, 0,
getsockopt(x.id, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<getsockopt_ptr>(&size),
&ret_size));
return static_cast<size_t>(size);
}
error send_buffer_size(network_socket x, size_t capacity) {
auto new_value = static_cast<int>(capacity);
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<setsockopt_ptr>(&new_value),
static_cast<socket_size_type>(sizeof(int))));
return none;
}
expected<std::string> local_addr(network_socket x) {
sockaddr_storage st;
socket_size_type st_len = sizeof(st);
sockaddr* sa = reinterpret_cast<sockaddr*>(&st);
CAF_NET_SYSCALL("getsockname", tmp1, !=, 0, getsockname(x.id, sa, &st_len));
char addr[INET6_ADDRSTRLEN]{0};
switch (sa->sa_family) {
case AF_INET:
return inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in*>(sa)->sin_addr,
addr, sizeof(addr));
case AF_INET6:
return inet_ntop(AF_INET6,
&reinterpret_cast<sockaddr_in6*>(sa)->sin6_addr, addr,
sizeof(addr));
default:
break;
}
return make_error(sec::invalid_protocol_family, "local_addr", sa->sa_family);
}
expected<uint16_t> local_port(network_socket x) {
sockaddr_storage st;
auto st_len = static_cast<socket_size_type>(sizeof(st));
CAF_NET_SYSCALL("getsockname", tmp, !=, 0,
getsockname(x.id, reinterpret_cast<sockaddr*>(&st), &st_len));
return ntohs(port_of(reinterpret_cast<sockaddr&>(st)));
}
expected<std::string> remote_addr(network_socket x) {
sockaddr_storage st;
socket_size_type st_len = sizeof(st);
sockaddr* sa = reinterpret_cast<sockaddr*>(&st);
CAF_NET_SYSCALL("getpeername", tmp, !=, 0, getpeername(x.id, sa, &st_len));
char addr[INET6_ADDRSTRLEN]{0};
switch (sa->sa_family) {
case AF_INET:
return inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in*>(sa)->sin_addr,
addr, sizeof(addr));
case AF_INET6:
return inet_ntop(AF_INET6,
&reinterpret_cast<sockaddr_in6*>(sa)->sin6_addr, addr,
sizeof(addr));
default:
break;
}
return make_error(sec::invalid_protocol_family, "remote_addr", sa->sa_family);
}
expected<uint16_t> remote_port(network_socket x) {
sockaddr_storage st;
socket_size_type st_len = sizeof(st);
CAF_NET_SYSCALL("getpeername", tmp, !=, 0,
getpeername(x.id, reinterpret_cast<sockaddr*>(&st), &st_len));
return ntohs(port_of(reinterpret_cast<sockaddr&>(st)));
}
void shutdown_read(network_socket x) {
::shutdown(x.id, 0);
}
void shutdown_write(network_socket x) {
::shutdown(x.id, 1);
}
void shutdown(network_socket x) {
::shutdown(x.id, 2);
}
} // 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/pipe_socket.hpp"
#include <cstdio>
#include <utility>
#include "caf/byte.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/message.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
expected<std::pair<pipe_socket, pipe_socket>> make_pipe() {
// Windows has no support for unidirectional pipes. Emulate pipes by using a
// pair of regular TCP sockets with read/write channels closed appropriately.
if (auto result = make_stream_socket_pair()) {
shutdown_write(result->first);
shutdown_read(result->second);
return std::make_pair(socket_cast<pipe_socket>(result->first),
socket_cast<pipe_socket>(result->second));
} else {
return std::move(result.error());
}
}
ptrdiff_t write(pipe_socket x, span<const byte> buf) {
// On Windows, a pipe consists of two stream sockets.
return write(socket_cast<stream_socket>(x), buf);
}
ptrdiff_t read(pipe_socket x, span<byte> buf) {
// On Windows, a pipe consists of two stream sockets.
return read(socket_cast<stream_socket>(x), buf);
}
#else // CAF_WINDOWS
expected<std::pair<pipe_socket, pipe_socket>> make_pipe() {
socket_id pipefds[2];
if (pipe(pipefds) != 0)
return make_error(sec::network_syscall_failed, "pipe",
last_socket_error_as_string());
auto guard = detail::make_scope_guard([&] {
close(socket{pipefds[0]});
close(socket{pipefds[1]});
});
// Note: for pipe2 it is better to avoid races by setting CLOEXEC (but not on
// POSIX).
if (auto err = child_process_inherit(socket{pipefds[0]}, false))
return err;
if (auto err = child_process_inherit(socket{pipefds[1]}, false))
return err;
guard.disable();
return std::make_pair(pipe_socket{pipefds[0]}, pipe_socket{pipefds[1]});
}
ptrdiff_t write(pipe_socket x, span<const byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::write(x.id, reinterpret_cast<socket_send_ptr>(buf.data()),
buf.size());
}
ptrdiff_t read(pipe_socket x, span<byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::read(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()),
buf.size());
}
#endif // CAF_WINDOWS
} // 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/pollset_updater.hpp"
#include <cstring>
#include "caf/action.hpp"
#include "caf/actor_system.hpp"
#include "caf/logger.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/sec.hpp"
#include "caf/span.hpp"
#include "caf/variant.hpp"
namespace caf::net {
// -- constructors, destructors, and assignment operators ----------------------
pollset_updater::pollset_updater(pipe_socket read_handle, multiplexer* parent)
: super(read_handle, parent) {
// nop
}
pollset_updater::~pollset_updater() {
// nop
}
// -- interface functions ------------------------------------------------------
error pollset_updater::init(const settings&) {
CAF_LOG_TRACE("");
return nonblocking(handle(), true);
}
pollset_updater::read_result pollset_updater::handle_read_event() {
CAF_LOG_TRACE("");
auto as_mgr = [](intptr_t ptr) {
return intrusive_ptr{reinterpret_cast<socket_manager*>(ptr), false};
};
auto run_action = [](intptr_t ptr) {
auto f = action{intrusive_ptr{reinterpret_cast<action::impl*>(ptr), false}};
f.run();
};
for (;;) {
CAF_ASSERT((buf_.size() - buf_size_) > 0);
auto num_bytes = read(handle(), make_span(buf_.data() + buf_size_,
buf_.size() - buf_size_));
if (num_bytes > 0) {
buf_size_ += static_cast<size_t>(num_bytes);
if (buf_.size() == buf_size_) {
buf_size_ = 0;
auto opcode = static_cast<uint8_t>(buf_[0]);
intptr_t ptr;
memcpy(&ptr, buf_.data() + 1, sizeof(intptr_t));
switch (static_cast<code>(opcode)) {
case code::register_reading:
mpx_->do_register_reading(as_mgr(ptr));
break;
case code::continue_reading:
mpx_->do_continue_reading(as_mgr(ptr));
break;
case code::register_writing:
mpx_->do_register_writing(as_mgr(ptr));
break;
case code::continue_writing:
mpx_->do_continue_writing(as_mgr(ptr));
break;
case code::init_manager:
mpx_->do_init(as_mgr(ptr));
break;
case code::discard_manager:
mpx_->do_discard(as_mgr(ptr));
break;
case code::shutdown_reading:
mpx_->do_shutdown_reading(as_mgr(ptr));
break;
case code::shutdown_writing:
mpx_->do_shutdown_writing(as_mgr(ptr));
break;
case code::run_action:
run_action(ptr);
break;
case code::shutdown:
CAF_ASSERT(ptr == 0);
mpx_->do_shutdown();
break;
default:
CAF_LOG_ERROR("opcode not recognized: " << CAF_ARG(opcode));
break;
}
}
} else if (num_bytes == 0) {
CAF_LOG_DEBUG("pipe closed, assume shutdown");
return read_result::stop;
} else if (last_socket_error_is_temporary()) {
return read_result::again;
} else {
return read_result::stop;
}
}
}
pollset_updater::read_result pollset_updater::handle_buffered_data() {
return read_result::again;
}
pollset_updater::read_result pollset_updater::handle_continue_reading() {
return read_result::again;
}
pollset_updater::write_result pollset_updater::handle_write_event() {
return write_result::stop;
}
pollset_updater::write_result pollset_updater::handle_continue_writing() {
return write_result::stop;
}
void pollset_updater::handle_error(sec) {
// nop
}
} // 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/socket.hpp"
#include <system_error>
#include "caf/config.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/logger.hpp"
#include "caf/sec.hpp"
#include "caf/variant.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
void close(socket fd) {
CAF_LOG_DEBUG("close" << CAF_ARG2("socket", fd.id));
closesocket(fd.id);
}
# define ERRC_CASE(wsa_code, stl_code) \
case wsa_code: \
return errc::stl_code
std::errc last_socket_error() {
// Unfortunately, MS does not define errc consistent with the WSA error
// codes. Hence, we cannot simply use static_cast<> but have to perform a
// switch-case.
using std::errc;
int wsa_code = WSAGetLastError();
switch (wsa_code) {
ERRC_CASE(WSA_INVALID_HANDLE, invalid_argument);
ERRC_CASE(WSA_NOT_ENOUGH_MEMORY, not_enough_memory);
ERRC_CASE(WSA_INVALID_PARAMETER, invalid_argument);
ERRC_CASE(WSAEINTR, interrupted);
ERRC_CASE(WSAEBADF, bad_file_descriptor);
ERRC_CASE(WSAEACCES, permission_denied);
ERRC_CASE(WSAEFAULT, bad_address);
ERRC_CASE(WSAEINVAL, invalid_argument);
ERRC_CASE(WSAEMFILE, too_many_files_open);
ERRC_CASE(WSAEWOULDBLOCK, operation_would_block);
ERRC_CASE(WSAEINPROGRESS, operation_in_progress);
ERRC_CASE(WSAEALREADY, connection_already_in_progress);
ERRC_CASE(WSAENOTSOCK, not_a_socket);
ERRC_CASE(WSAEDESTADDRREQ, destination_address_required);
ERRC_CASE(WSAEMSGSIZE, message_size);
ERRC_CASE(WSAEPROTOTYPE, wrong_protocol_type);
ERRC_CASE(WSAENOPROTOOPT, no_protocol_option);
ERRC_CASE(WSAEPROTONOSUPPORT, protocol_not_supported);
// Windows returns this error code if the *type* argument to socket() is
// invalid. POSIX returns EINVAL.
ERRC_CASE(WSAESOCKTNOSUPPORT, invalid_argument);
ERRC_CASE(WSAEOPNOTSUPP, operation_not_supported);
// Windows returns this error code if the *protocol* argument to socket() is
// invalid. POSIX returns EINVAL.
ERRC_CASE(WSAEPFNOSUPPORT, invalid_argument);
ERRC_CASE(WSAEAFNOSUPPORT, address_family_not_supported);
ERRC_CASE(WSAEADDRINUSE, address_in_use);
ERRC_CASE(WSAEADDRNOTAVAIL, address_not_available);
ERRC_CASE(WSAENETDOWN, network_down);
ERRC_CASE(WSAENETUNREACH, network_unreachable);
ERRC_CASE(WSAENETRESET, network_reset);
ERRC_CASE(WSAECONNABORTED, connection_aborted);
ERRC_CASE(WSAECONNRESET, connection_reset);
ERRC_CASE(WSAENOBUFS, no_buffer_space);
ERRC_CASE(WSAEISCONN, already_connected);
ERRC_CASE(WSAENOTCONN, not_connected);
// Windows returns this error code when writing to a socket with closed
// output channel. POSIX returns EPIPE.
ERRC_CASE(WSAESHUTDOWN, broken_pipe);
ERRC_CASE(WSAETIMEDOUT, timed_out);
ERRC_CASE(WSAECONNREFUSED, connection_refused);
ERRC_CASE(WSAELOOP, too_many_symbolic_link_levels);
ERRC_CASE(WSAENAMETOOLONG, filename_too_long);
ERRC_CASE(WSAEHOSTUNREACH, host_unreachable);
ERRC_CASE(WSAENOTEMPTY, directory_not_empty);
ERRC_CASE(WSANOTINITIALISED, network_down);
ERRC_CASE(WSAEDISCON, already_connected);
ERRC_CASE(WSAENOMORE, not_connected);
ERRC_CASE(WSAECANCELLED, operation_canceled);
ERRC_CASE(WSATRY_AGAIN, resource_unavailable_try_again);
ERRC_CASE(WSANO_RECOVERY, state_not_recoverable);
}
fprintf(stderr, "[FATAL] %s:%u: unrecognized WSA error code (%d)\n", __FILE__,
__LINE__, wsa_code);
abort();
}
bool last_socket_error_is_temporary() {
switch (WSAGetLastError()) {
case WSATRY_AGAIN:
case WSAEINPROGRESS:
case WSAEWOULDBLOCK:
return true;
default:
return false;
}
}
std::string last_socket_error_as_string() {
int wsa_code = WSAGetLastError();
LPTSTR errorText = NULL;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, wsa_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &errorText, 0, nullptr);
std::string result;
if (errorText != nullptr) {
result = errorText;
// Release memory allocated by FormatMessage().
LocalFree(errorText);
}
return result;
}
bool would_block_or_temporarily_unavailable(int errcode) {
return errcode == WSAEWOULDBLOCK || errcode == WSATRY_AGAIN;
}
bool probe(socket x) {
auto err = 0;
auto len = static_cast<socklen_t>(sizeof(err));
auto err_ptr = reinterpret_cast<char*>(&err);
if (getsockopt(x.id, SOL_SOCKET, SO_ERROR, err_ptr, &len) == 0) {
WSASetLastError(err);
return err == 0;
} else {
return false;
}
}
error child_process_inherit(socket x, bool) {
// TODO: possible to implement via SetHandleInformation?
if (x == invalid_socket)
return make_error(sec::network_syscall_failed, "ioctlsocket",
"invalid socket");
return none;
}
error nonblocking(socket x, bool new_value) {
u_long mode = new_value ? 1 : 0;
CAF_NET_SYSCALL("ioctlsocket", res, !=, 0, ioctlsocket(x.id, FIONBIO, &mode));
return none;
}
#else // CAF_WINDOWS
void close(socket fd) {
CAF_LOG_DEBUG("close" << CAF_ARG2("socket", fd.id));
::close(fd.id);
}
std::errc last_socket_error() {
// TODO: Linux and macOS both have some non-POSIX error codes that should get
// mapped accordingly.
return static_cast<std::errc>(errno);
}
bool last_socket_error_is_temporary() {
switch (errno) {
case EAGAIN:
case EINPROGRESS:
# if EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
# endif
return true;
default:
return false;
}
}
std::string last_socket_error_as_string() {
return strerror(errno);
}
bool would_block_or_temporarily_unavailable(int errcode) {
return errcode == EAGAIN || errcode == EWOULDBLOCK;
}
bool probe(socket x) {
auto err = 0;
auto len = static_cast<socklen_t>(sizeof(err));
if (getsockopt(x.id, SOL_SOCKET, SO_ERROR, &err, &len) == 0) {
errno = err;
return err == 0;
} else {
return false;
}
}
error child_process_inherit(socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
// read flags for x
CAF_NET_SYSCALL("fcntl", rf, ==, -1, fcntl(x.id, F_GETFD));
// calculate and set new flags
auto wf = !new_value ? rf | FD_CLOEXEC : rf & ~(FD_CLOEXEC);
CAF_NET_SYSCALL("fcntl", set_res, ==, -1, fcntl(x.id, F_SETFD, wf));
return none;
}
error nonblocking(socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
// read flags for x
CAF_NET_SYSCALL("fcntl", rf, ==, -1, fcntl(x.id, F_GETFL, 0));
// calculate and set new flags
auto wf = new_value ? (rf | O_NONBLOCK) : (rf & (~(O_NONBLOCK)));
CAF_NET_SYSCALL("fcntl", set_res, ==, -1, fcntl(x.id, F_SETFL, wf));
return none;
}
#endif // CAF_WINDOWS
error shutdown_read(socket x) {
CAF_NET_SYSCALL("shutdown", res, !=, 0, shutdown(x.id, 0));
return caf::none;
}
error shutdown_write(socket x) {
CAF_NET_SYSCALL("shutdown", res, !=, 0, shutdown(x.id, 1));
return caf::none;
}
} // 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/socket_manager.hpp"
#include "caf/config.hpp"
#include "caf/net/actor_shell.hpp"
#include "caf/net/multiplexer.hpp"
namespace caf::net {
socket_manager::socket_manager(socket handle, multiplexer* mpx)
: handle_(handle), mpx_(mpx) {
CAF_ASSERT(handle_ != invalid_socket);
CAF_ASSERT(mpx_ != nullptr);
memset(&flags_, 0, sizeof(flags_t));
}
socket_manager::~socket_manager() {
close(handle_);
}
actor_system& socket_manager::system() noexcept {
CAF_ASSERT(mpx_ != nullptr);
return mpx_->system();
}
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;
}
void socket_manager::abort_reason(error reason) noexcept {
abort_reason_ = std::move(reason);
flags_.read_closed = true;
flags_.write_closed = true;
}
void socket_manager::register_reading() {
mpx_->register_reading(this);
}
void socket_manager::continue_reading() {
mpx_->continue_reading(this);
}
void socket_manager::register_writing() {
mpx_->register_writing(this);
}
void socket_manager::continue_writing() {
mpx_->continue_writing(this);
}
socket_manager_ptr socket_manager::do_handover() {
flags_.read_closed = true;
flags_.write_closed = true;
auto hdl = handle_;
handle_ = invalid_socket;
if (auto ptr = make_next_manager(hdl)) {
return ptr;
} else {
close(hdl);
return nullptr;
}
}
socket_manager_ptr socket_manager::make_next_manager(socket) {
return {};
}
} // 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/stream_socket.hpp"
#include "caf/byte.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/logger.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/span.hpp"
#include "caf/variant.hpp"
#ifdef CAF_POSIX
# include <sys/uio.h>
#endif
namespace caf::net {
#ifdef CAF_WINDOWS
constexpr int no_sigpipe_io_flag = 0;
/**************************************************************************\
* Based on work of others; *
* original header: *
* *
* Copyright 2007, 2010 by Nathan C. Myers <ncm@cantrip.org> *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* *
* Redistributions of source code must retain the above copyright notice, *
* this list of conditions and the following disclaimer. *
* *
* Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* *
* The name of the author must not be used to endorse or promote products *
* derived from this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR *
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT *
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY *
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE *
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
\**************************************************************************/
expected<std::pair<stream_socket, stream_socket>> make_stream_socket_pair() {
auto addrlen = static_cast<int>(sizeof(sockaddr_in));
socket_id socks[2] = {invalid_socket_id, invalid_socket_id};
CAF_NET_SYSCALL("socket", listener, ==, invalid_socket_id,
::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
union {
sockaddr_in inaddr;
sockaddr addr;
} a;
memset(&a, 0, sizeof(a));
a.inaddr.sin_family = AF_INET;
a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.inaddr.sin_port = 0;
// makes sure all sockets are closed in case of an error
auto guard = detail::make_scope_guard([&] {
auto e = WSAGetLastError();
close(socket{listener});
close(socket{socks[0]});
close(socket{socks[1]});
WSASetLastError(e);
});
// bind listener to a local port
int reuse = 1;
CAF_NET_SYSCALL("setsockopt", tmp1, !=, 0,
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<char*>(&reuse),
static_cast<int>(sizeof(reuse))));
CAF_NET_SYSCALL("bind", tmp2, !=, 0,
bind(listener, &a.addr, static_cast<int>(sizeof(a.inaddr))));
// Read the port in use: win32 getsockname may only set the port number
// (http://msdn.microsoft.com/library/ms738543.aspx).
memset(&a, 0, sizeof(a));
CAF_NET_SYSCALL("getsockname", tmp3, !=, 0,
getsockname(listener, &a.addr, &addrlen));
a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
a.inaddr.sin_family = AF_INET;
// set listener to listen mode
CAF_NET_SYSCALL("listen", tmp5, !=, 0, listen(listener, 1));
// create read-only end of the pipe
DWORD flags = 0;
CAF_NET_SYSCALL("WSASocketW", read_fd, ==, invalid_socket_id,
WSASocketW(AF_INET, SOCK_STREAM, 0, nullptr, 0, flags));
CAF_NET_SYSCALL("connect", tmp6, !=, 0,
connect(read_fd, &a.addr,
static_cast<int>(sizeof(a.inaddr))));
// get write-only end of the pipe
CAF_NET_SYSCALL("accept", write_fd, ==, invalid_socket_id,
accept(listener, nullptr, nullptr));
close(socket{listener});
guard.disable();
return std::make_pair(stream_socket{read_fd}, stream_socket{write_fd});
}
error keepalive(stream_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
char value = new_value ? 1 : 0;
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, SOL_SOCKET, SO_KEEPALIVE, &value,
static_cast<int>(sizeof(value))));
return none;
}
#else // CAF_WINDOWS
# if defined(CAF_MACOS) || defined(CAF_IOS) || defined(CAF_BSD)
constexpr int no_sigpipe_io_flag = 0;
# else
constexpr int no_sigpipe_io_flag = MSG_NOSIGNAL;
# endif
expected<std::pair<stream_socket, stream_socket>> make_stream_socket_pair() {
int sockets[2];
CAF_NET_SYSCALL("socketpair", spair_res, !=, 0,
socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
return std::make_pair(stream_socket{sockets[0]}, stream_socket{sockets[1]});
}
error keepalive(stream_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
int value = new_value ? 1 : 0;
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, SOL_SOCKET, SO_KEEPALIVE, &value,
static_cast<unsigned>(sizeof(value))));
return none;
}
#endif // CAF_WINDOWS
error nodelay(stream_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
int flag = new_value ? 1 : 0;
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<setsockopt_ptr>(&flag),
static_cast<socket_size_type>(sizeof(flag))));
return none;
}
ptrdiff_t read(stream_socket x, span<byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::recv(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()), buf.size(),
no_sigpipe_io_flag);
}
ptrdiff_t write(stream_socket x, span<const byte> buf) {
CAF_LOG_TRACE(CAF_ARG2("socket", x.id) << CAF_ARG2("bytes", buf.size()));
return ::send(x.id, reinterpret_cast<socket_send_ptr>(buf.data()), buf.size(),
no_sigpipe_io_flag);
}
#ifdef CAF_WINDOWS
ptrdiff_t write(stream_socket x, std::initializer_list<span<const byte>> bufs) {
CAF_ASSERT(bufs.size() < 10);
WSABUF buf_array[10];
auto convert = [](span<const byte> buf) {
auto data = const_cast<byte*>(buf.data());
return WSABUF{static_cast<ULONG>(buf.size()),
reinterpret_cast<CHAR*>(data)};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
DWORD bytes_sent = 0;
auto res = WSASend(x.id, buf_array, static_cast<DWORD>(bufs.size()),
&bytes_sent, 0, nullptr, nullptr);
return (res == 0) ? bytes_sent : -1;
}
#else // CAF_WINDOWS
ptrdiff_t write(stream_socket x, std::initializer_list<span<const byte>> bufs) {
CAF_ASSERT(bufs.size() < 10);
iovec buf_array[10];
auto convert = [](span<const byte> buf) {
return iovec{const_cast<byte*>(buf.data()), buf.size()};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
return writev(x.id, buf_array, static_cast<int>(bufs.size()));
}
#endif // CAF_WINDOWS
} // 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/tcp_accept_socket.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/sockaddr_members.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/ip_address.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/logger.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/sec.hpp"
namespace caf::net {
namespace {
error set_inaddr_any(socket, sockaddr_in& sa) {
sa.sin_addr.s_addr = INADDR_ANY;
return none;
}
error set_inaddr_any(socket x, sockaddr_in6& sa) {
sa.sin6_addr = in6addr_any;
// Also accept ipv4 connections on this socket.
int off = 0;
CAF_NET_SYSCALL("setsockopt", res, !=, 0,
setsockopt(x.id, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<setsockopt_ptr>(&off),
static_cast<socket_size_type>(sizeof(off))));
return none;
}
template <int Family>
expected<tcp_accept_socket> new_tcp_acceptor_impl(uint16_t port,
const char* addr,
bool reuse_addr, bool any) {
static_assert(Family == AF_INET || Family == AF_INET6, "invalid family");
CAF_LOG_TRACE(CAF_ARG(port) << ", addr = " << (addr ? addr : "nullptr"));
int socktype = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
socktype |= SOCK_CLOEXEC;
#endif
CAF_NET_SYSCALL("socket", fd, ==, -1, ::socket(Family, socktype, 0));
tcp_accept_socket sock{fd};
// sguard closes the socket in case of exception
auto sguard = make_socket_guard(tcp_accept_socket{fd});
if (auto err = child_process_inherit(sock, false))
return err;
if (reuse_addr) {
int on = 1;
CAF_NET_SYSCALL("setsockopt", tmp1, !=, 0,
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<setsockopt_ptr>(&on),
static_cast<socket_size_type>(sizeof(on))));
}
using sockaddr_type =
typename std::conditional<Family == AF_INET, sockaddr_in,
sockaddr_in6>::type;
sockaddr_type sa;
memset(&sa, 0, sizeof(sockaddr_type));
detail::family_of(sa) = Family;
if (any)
if (auto err = set_inaddr_any(sock, sa))
return err;
CAF_NET_SYSCALL("inet_pton", tmp, !=, 1,
inet_pton(Family, addr, &detail::addr_of(sa)));
detail::port_of(sa) = htons(port);
CAF_NET_SYSCALL("bind", res, !=, 0,
bind(fd, reinterpret_cast<sockaddr*>(&sa),
static_cast<socket_size_type>(sizeof(sa))));
return sguard.release();
}
} // namespace
expected<tcp_accept_socket> make_tcp_accept_socket(ip_endpoint node,
bool reuse_addr) {
CAF_LOG_TRACE(CAF_ARG(node));
auto addr = to_string(node.address());
bool is_v4 = node.address().embeds_v4();
bool is_zero = is_v4 ? node.address().embedded_v4().bits() == 0
: node.address().zero();
auto make_acceptor = is_v4 ? new_tcp_acceptor_impl<AF_INET>
: new_tcp_acceptor_impl<AF_INET6>;
if (auto p = make_acceptor(node.port(), addr.c_str(), reuse_addr, is_zero)) {
auto sock = socket_cast<tcp_accept_socket>(*p);
auto sguard = make_socket_guard(sock);
CAF_NET_SYSCALL("listen", tmp, !=, 0, listen(sock.id, SOMAXCONN));
CAF_LOG_DEBUG(CAF_ARG(sock.id));
return sguard.release();
} else {
CAF_LOG_WARNING("could not create tcp socket:" << CAF_ARG(node)
<< CAF_ARG(p.error()));
return make_error(sec::cannot_open_port, "tcp socket creation failed",
to_string(node), std::move(p.error()));
}
}
expected<tcp_accept_socket>
make_tcp_accept_socket(const uri::authority_type& node, bool reuse_addr) {
if (auto ip = get_if<ip_address>(&node.host))
return make_tcp_accept_socket(ip_endpoint{*ip, node.port}, reuse_addr);
auto host = get<std::string>(node.host);
auto addrs = ip::local_addresses(host);
if (addrs.empty())
return make_error(sec::cannot_open_port, "no local interface available",
to_string(node));
for (auto& addr : addrs) {
if (auto sock = make_tcp_accept_socket(ip_endpoint{addr, node.port},
reuse_addr))
return *sock;
}
return make_error(sec::cannot_open_port, "tcp socket creation failed",
to_string(node));
}
expected<tcp_stream_socket> accept(tcp_accept_socket x) {
auto sock = ::accept(x.id, nullptr, nullptr);
if (sock == net::invalid_socket_id) {
auto err = net::last_socket_error();
if (err != std::errc::operation_would_block
&& err != std::errc::resource_unavailable_try_again) {
return caf::make_error(sec::unavailable_or_would_block);
}
return caf::make_error(sec::socket_operation_failed, "tcp accept failed");
}
return tcp_stream_socket{sock};
}
} // 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/tcp_stream_socket.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/sockaddr_members.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/logger.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/sec.hpp"
#include "caf/variant.hpp"
#include <algorithm>
#ifndef CAF_WINDOWS
# include <poll.h>
#endif
#ifdef CAF_WINDOWS
# define POLL_FN ::WSAPoll
#else
# define POLL_FN ::poll
#endif
namespace caf::net {
namespace {
bool connect_with_timeout(stream_socket fd, const sockaddr* addr,
socklen_t addrlen, timespan timeout) {
namespace sc = std::chrono;
CAF_LOG_TRACE(CAF_ARG(fd.id) << CAF_ARG(timeout));
// Set to non-blocking or fail.
if (auto err = nonblocking(fd, true))
return false;
// Calculate deadline and define a lambda for getting the relative time in ms.
auto deadline = sc::steady_clock::now() + timeout;
auto ms_until_deadline = [deadline] {
auto t = sc::steady_clock::now();
auto ms_count = sc::duration_cast<sc::milliseconds>(deadline - t).count();
return std::max(static_cast<int>(ms_count), 0);
};
// Call connect() once and see if it succeeds. Otherwise enter a poll()-loop.
if (connect(fd.id, addr, addrlen) == 0) {
// Done! Try restoring the socket to blocking and return.
if (auto err = nonblocking(fd, false))
return false;
else
return true;
} else if (!last_socket_error_is_temporary()) {
// Hard error. No need to restore the socket to blocking since we are going
// to close it.
return false;
} else {
// Loop until the reaching the deadline.
pollfd pollset[1];
pollset[0].fd = fd.id;
pollset[0].events = POLLOUT;
auto ms = ms_until_deadline();
do {
auto pres = POLL_FN(pollset, 1, ms);
if (pres > 0) {
// Check that the socket really is ready to go by reading SO_ERROR.
if (probe(fd)) {
// Done! Try restoring the socket to blocking and return.
if (auto err = nonblocking(fd, false))
return false;
else
return true;
} else {
return false;
}
} else if (pres < 0 && !last_socket_error_is_temporary()) {
return false;
}
// Else: timeout or EINTR. Try-again.
ms = ms_until_deadline();
} while (ms > 0);
}
// No need to restore the socket to blocking since we are going to close it.
return false;
}
template <int Family>
bool ip_connect(stream_socket fd, std::string host, uint16_t port,
timespan timeout) {
CAF_LOG_TRACE("Family =" << (Family == AF_INET ? "AF_INET" : "AF_INET6")
<< CAF_ARG(fd.id) << CAF_ARG(host) << CAF_ARG(port)
<< CAF_ARG(timeout));
static_assert(Family == AF_INET || Family == AF_INET6, "invalid family");
using sockaddr_type =
typename std::conditional<Family == AF_INET, sockaddr_in,
sockaddr_in6>::type;
sockaddr_type sa;
memset(&sa, 0, sizeof(sockaddr_type));
if (inet_pton(Family, host.c_str(), &detail::addr_of(sa)) == 1) {
detail::family_of(sa) = Family;
detail::port_of(sa) = htons(port);
using sa_ptr = const sockaddr*;
if (timeout == infinite) {
return ::connect(fd.id, reinterpret_cast<sa_ptr>(&sa), sizeof(sa)) == 0;
} else {
return connect_with_timeout(fd, reinterpret_cast<sa_ptr>(&sa), sizeof(sa),
timeout);
}
} else {
CAF_LOG_DEBUG("inet_pton failed to parse"
<< host << "for family"
<< (Family == AF_INET ? "AF_INET" : "AF_INET6"));
return false;
}
}
} // namespace
expected<tcp_stream_socket> make_connected_tcp_stream_socket(ip_endpoint node,
timespan timeout) {
CAF_LOG_TRACE(CAF_ARG(node) << CAF_ARG(timeout));
CAF_LOG_DEBUG_IF(timeout == infinite, "try to connect to TCP node" << node);
CAF_LOG_DEBUG_IF(timeout != infinite, "try to connect to TCP node"
<< node << "with timeout" << timeout);
auto proto = node.address().embeds_v4() ? AF_INET : AF_INET6;
int socktype = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
socktype |= SOCK_CLOEXEC;
#endif
CAF_NET_SYSCALL("socket", fd, ==, -1, ::socket(proto, socktype, 0));
tcp_stream_socket sock{fd};
if (auto err = child_process_inherit(sock, false))
return err;
auto sguard = make_socket_guard(sock);
if (proto == AF_INET6) {
if (ip_connect<AF_INET6>(sock, to_string(node.address()), node.port(),
timeout)) {
CAF_LOG_INFO("established TCP connection to IPv6 node"
<< to_string(node));
return sguard.release();
}
} else if (ip_connect<AF_INET>(sock, to_string(node.address().embedded_v4()),
node.port(), timeout)) {
CAF_LOG_INFO("established TCP connection to IPv4 node" << to_string(node));
return sguard.release();
}
CAF_LOG_INFO("failed to connect to" << node);
return make_error(sec::cannot_connect_to_node);
}
expected<tcp_stream_socket>
make_connected_tcp_stream_socket(const uri::authority_type& node,
timespan timeout) {
CAF_LOG_TRACE(CAF_ARG(node) << CAF_ARG(timeout));
auto port = node.port;
if (port == 0)
return make_error(sec::cannot_connect_to_node, "port is zero");
std::vector<ip_address> addrs;
if (auto str = get_if<std::string>(&node.host))
addrs = ip::resolve(*str);
else if (auto addr = get_if<ip_address>(&node.host))
addrs.push_back(*addr);
if (addrs.empty())
return make_error(sec::cannot_connect_to_node, "empty authority");
for (auto& addr : addrs) {
auto ep = ip_endpoint{addr, port};
if (auto sock = make_connected_tcp_stream_socket(ep, timeout))
return *sock;
}
return make_error(sec::cannot_connect_to_node, to_string(node));
}
expected<tcp_stream_socket> make_connected_tcp_stream_socket(std::string host,
uint16_t port,
timespan timeout) {
CAF_LOG_TRACE(CAF_ARG(host) << CAF_ARG(port) << CAF_ARG(timeout));
uri::authority_type auth;
auth.host = std::move(host);
auth.port = port;
return make_connected_tcp_stream_socket(auth, timeout);
}
} // 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/udp_datagram_socket.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/convert_ip_endpoint.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_aliases.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/expected.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/logger.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/span.hpp"
namespace caf::net {
#ifdef CAF_WINDOWS
error allow_connreset(udp_datagram_socket x, bool new_value) {
CAF_LOG_TRACE(CAF_ARG(x) << CAF_ARG(new_value));
DWORD bytes_returned = 0;
CAF_NET_SYSCALL("WSAIoctl", res, !=, 0,
WSAIoctl(x.id, _WSAIOW(IOC_VENDOR, 12), &new_value,
sizeof(new_value), NULL, 0, &bytes_returned, NULL,
NULL));
return none;
}
#else // CAF_WINDOWS
error allow_connreset(udp_datagram_socket x, bool) {
if (socket_cast<net::socket>(x) == invalid_socket)
return sec::socket_invalid;
// nop; SIO_UDP_CONNRESET only exists on Windows
return none;
}
#endif // CAF_WINDOWS
expected<std::pair<udp_datagram_socket, uint16_t>>
make_udp_datagram_socket(ip_endpoint ep, bool reuse_addr) {
CAF_LOG_TRACE(CAF_ARG(ep));
sockaddr_storage addr = {};
detail::convert(ep, addr);
CAF_NET_SYSCALL("socket", fd, ==, invalid_socket_id,
::socket(addr.ss_family, SOCK_DGRAM, 0));
udp_datagram_socket sock{fd};
auto sguard = make_socket_guard(sock);
socklen_t len = (addr.ss_family == AF_INET) ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
if (reuse_addr) {
int on = 1;
CAF_NET_SYSCALL("setsockopt", tmp1, !=, 0,
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<setsockopt_ptr>(&on),
static_cast<socket_size_type>(sizeof(on))));
}
CAF_NET_SYSCALL("bind", err, !=, 0,
::bind(sock.id, reinterpret_cast<sockaddr*>(&addr), len));
CAF_NET_SYSCALL("getsockname", erro, !=, 0,
getsockname(sock.id, reinterpret_cast<sockaddr*>(&addr),
&len));
CAF_LOG_DEBUG(CAF_ARG(sock.id));
auto port = addr.ss_family == AF_INET
? reinterpret_cast<sockaddr_in*>(&addr)->sin_port
: reinterpret_cast<sockaddr_in6*>(&addr)->sin6_port;
return std::make_pair(sguard.release(), ntohs(port));
}
variant<std::pair<size_t, ip_endpoint>, sec> read(udp_datagram_socket x,
span<byte> buf) {
sockaddr_storage addr = {};
socklen_t len = sizeof(sockaddr_storage);
auto res = ::recvfrom(x.id, reinterpret_cast<socket_recv_ptr>(buf.data()),
buf.size(), 0, reinterpret_cast<sockaddr*>(&addr),
&len);
auto ret = check_udp_datagram_socket_io_res(res);
if (auto num_bytes = get_if<size_t>(&ret)) {
CAF_LOG_INFO_IF(*num_bytes == 0, "Received empty datagram");
CAF_LOG_WARNING_IF(*num_bytes > buf.size(),
"recvfrom cut of message, only received "
<< CAF_ARG(buf.size()) << " of " << CAF_ARG(num_bytes)
<< " bytes");
ip_endpoint ep;
if (auto err = detail::convert(addr, ep)) {
CAF_ASSERT(err.category() == type_id_v<sec>);
return static_cast<sec>(err.code());
}
return std::pair<size_t, ip_endpoint>(*num_bytes, ep);
} else {
return get<sec>(ret);
}
}
variant<size_t, sec> write(udp_datagram_socket x, span<const byte> buf,
ip_endpoint ep) {
sockaddr_storage addr = {};
detail::convert(ep, addr);
auto len = static_cast<socklen_t>(
ep.address().embeds_v4() ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
auto res = ::sendto(x.id, reinterpret_cast<socket_send_ptr>(buf.data()),
buf.size(), 0, reinterpret_cast<sockaddr*>(&addr), len);
auto ret = check_udp_datagram_socket_io_res(res);
if (auto num_bytes = get_if<size_t>(&ret))
return *num_bytes;
else
return get<sec>(ret);
}
#ifdef CAF_WINDOWS
variant<size_t, sec> write(udp_datagram_socket x, span<byte_buffer*> bufs,
ip_endpoint ep) {
CAF_ASSERT(bufs.size() < 10);
WSABUF buf_array[10];
auto convert = [](byte_buffer* buf) {
return WSABUF{static_cast<ULONG>(buf->size()),
reinterpret_cast<CHAR*>(buf->data())};
};
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
sockaddr_storage addr = {};
detail::convert(ep, addr);
auto len = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
DWORD bytes_sent = 0;
auto res = WSASendTo(x.id, buf_array, static_cast<DWORD>(bufs.size()),
&bytes_sent, 0, reinterpret_cast<sockaddr*>(&addr), len,
nullptr, nullptr);
if (res != 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(bytes_sent);
}
#else // CAF_WINDOWS
variant<size_t, sec> write(udp_datagram_socket x, span<byte_buffer*> bufs,
ip_endpoint ep) {
CAF_ASSERT(bufs.size() < 10);
auto convert = [](byte_buffer* buf) {
return iovec{buf->data(), buf->size()};
};
sockaddr_storage addr = {};
detail::convert(ep, addr);
iovec buf_array[10];
std::transform(bufs.begin(), bufs.end(), std::begin(buf_array), convert);
msghdr message = {};
memset(&message, 0, sizeof(msghdr));
message.msg_name = &addr;
message.msg_namelen = ep.address().embeds_v4() ? sizeof(sockaddr_in)
: sizeof(sockaddr_in6);
message.msg_iov = buf_array;
message.msg_iovlen = static_cast<int>(bufs.size());
auto res = sendmsg(x.id, &message, 0);
return check_udp_datagram_socket_io_res(res);
}
#endif // CAF_WINDOWS
variant<size_t, sec>
check_udp_datagram_socket_io_res(std::make_signed<size_t>::type res) {
if (res < 0) {
auto code = last_socket_error();
if (code == std::errc::operation_would_block
|| code == std::errc::resource_unavailable_try_again)
return sec::unavailable_or_would_block;
return sec::socket_operation_failed;
}
return static_cast<size_t>(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.
#include "caf/net/basp/worker.hpp"
#include "caf/actor_system.hpp"
#include "caf/byte.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/proxy_registry.hpp"
#include "caf/scheduler/abstract_coordinator.hpp"
namespace caf::net::basp {
// -- constructors, destructors, and assignment operators ----------------------
worker::worker(hub_type& hub, message_queue& queue, proxy_registry& proxies)
: hub_(&hub), queue_(&queue), proxies_(&proxies), system_(&proxies.system()) {
CAF_IGNORE_UNUSED(pad_);
}
worker::~worker() {
// nop
}
// -- management ---------------------------------------------------------------
void worker::launch(const node_id& last_hop, const basp::header& hdr,
span<const byte> payload) {
msg_id_ = queue_->new_id();
last_hop_ = last_hop;
memcpy(&hdr_, &hdr, sizeof(basp::header));
payload_.assign(payload.begin(), payload.end());
ref();
system_->scheduler().enqueue(this);
}
// -- implementation of resumable ----------------------------------------------
resumable::resume_result worker::resume(execution_unit* ctx, size_t) {
ctx->proxy_registry_ptr(proxies_);
handle_remote_message(ctx);
hub_->push(this);
return resumable::awaiting_message;
}
} // namespace caf::net::basp
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.tcp_accept_socket
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/uri.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/test/host_fixture.hpp"
using namespace caf;
using namespace caf::net;
using namespace std::literals::string_literals;
namespace {
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
auth.port = 0;
auth.host = "0.0.0.0"s;
}
uri::authority_type auth;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(doorman_tests, fixture)
CAF_TEST(tcp connect) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(socket_cast<network_socket>(acceptor)));
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
uri::authority_type dst;
dst.port = port;
dst.host = "localhost"s;
auto conn = make_socket_guard(unbox(make_connected_tcp_stream_socket(dst)));
auto accepted = make_socket_guard(unbox(accept(acceptor)));
CAF_MESSAGE("accepted connection");
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE basp.application
#include "caf/net/basp/application.hpp"
#include "caf/test/dsl.hpp"
#include <vector>
#include "caf/byte_buffer.hpp"
#include "caf/forwarding_actor_proxy.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/packet_writer.hpp"
#include "caf/none.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
#define REQUIRE_OK(statement) \
if (auto err = statement) \
CAF_FAIL("failed to serialize data: " << err);
namespace {
struct config : actor_system_config {
config() {
net::middleman::add_module_options(*this);
}
};
struct fixture : test_coordinator_fixture<config>,
proxy_registry::backend,
basp::application::test_tag,
public packet_writer {
fixture() : proxies(sys, *this), app(proxies) {
REQUIRE_OK(app.init(*this));
uri mars_uri;
REQUIRE_OK(parse("tcp://mars", mars_uri));
mars = make_node_id(mars_uri);
}
template <class... Ts>
byte_buffer to_buf(const Ts&... xs) {
byte_buffer buf;
binary_serializer sink{system(), buf};
REQUIRE_OK(sink(xs...));
return buf;
}
template <class... Ts>
void set_input(const Ts&... xs) {
input = to_buf(xs...);
}
void handle_handshake() {
CAF_CHECK_EQUAL(app.state(),
basp::connection_state::await_handshake_header);
auto payload = to_buf(mars, basp::application::default_app_ids());
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()),
basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(),
basp::connection_state::await_handshake_payload);
REQUIRE_OK(app.handle_data(*this, payload));
}
void consume_handshake() {
if (output.size() < basp::header_size)
CAF_FAIL("BASP application did not write a handshake header");
auto hdr = basp::header::from_bytes(output);
if (hdr.type != basp::message_type::handshake || hdr.payload_len == 0
|| hdr.operation_data != basp::version)
CAF_FAIL("invalid handshake header");
node_id nid;
std::vector<std::string> app_ids;
binary_deserializer source{sys, output};
source.skip(basp::header_size);
if (auto err = source(nid, app_ids))
CAF_FAIL("unable to deserialize payload: " << err);
if (source.remaining() > 0)
CAF_FAIL("trailing bytes after reading payload");
output.clear();
}
actor_system& system() {
return sys;
}
fixture& transport() {
return *this;
}
endpoint_manager& manager() {
CAF_FAIL("unexpected function call");
}
byte_buffer next_payload_buffer() override {
return {};
}
byte_buffer next_header_buffer() override {
return {};
}
template <class... Ts>
void configure_read(Ts...) {
// nop
}
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override {
using impl_type = forwarding_actor_proxy;
using hdl_type = strong_actor_ptr;
actor_config cfg;
return make_actor<impl_type, hdl_type>(aid, nid, &sys, cfg, self);
}
void set_last_hop(node_id*) override {
// nop
}
protected:
void write_impl(span<byte_buffer*> buffers) override {
for (auto buf : buffers)
output.insert(output.end(), buf->begin(), buf->end());
}
byte_buffer input;
byte_buffer output;
node_id mars;
proxy_registry proxies;
basp::application app;
};
} // namespace
#define MOCK(kind, op, ...) \
do { \
auto payload = to_buf(__VA_ARGS__); \
set_input(basp::header{kind, static_cast<uint32_t>(payload.size()), op}); \
if (auto err = app.handle_data(*this, input)) \
CAF_FAIL("application-under-test failed to process header: " << err); \
if (auto err = app.handle_data(*this, payload)) \
CAF_FAIL("application-under-test failed to process payload: " << err); \
} while (false)
#define RECEIVE(msg_type, op_data, ...) \
do { \
binary_deserializer source{sys, output}; \
basp::header hdr; \
if (auto err = source(hdr, __VA_ARGS__)) \
CAF_FAIL("failed to receive data: " << err); \
if (source.remaining() != 0) \
CAF_FAIL("unable to read entire message, " << source.remaining() \
<< " bytes left in buffer"); \
CAF_CHECK_EQUAL(hdr.type, msg_type); \
CAF_CHECK_EQUAL(hdr.operation_data, op_data); \
output.clear(); \
} while (false)
CAF_TEST_FIXTURE_SCOPE(application_tests, fixture)
CAF_TEST(missing handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::heartbeat, 0, 0});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::missing_handshake);
}
CAF_TEST(version mismatch) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::handshake, 0, 0});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::version_mismatch);
}
CAF_TEST(missing payload in handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
set_input(basp::header{basp::message_type::handshake, 0, basp::version});
CAF_CHECK_EQUAL(app.handle_data(*this, input), basp::ec::missing_payload);
}
CAF_TEST(invalid handshake) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
node_id no_nid;
std::vector<std::string> no_ids;
auto payload = to_buf(no_nid, no_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_payload);
CAF_CHECK_EQUAL(app.handle_data(*this, payload), basp::ec::invalid_handshake);
}
CAF_TEST(app identifier mismatch) {
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_header);
std::vector<std::string> wrong_ids{"YOLO!!!"};
auto payload = to_buf(mars, wrong_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_handshake_payload);
CAF_CHECK_EQUAL(app.handle_data(*this, payload),
basp::ec::app_identifiers_mismatch);
}
CAF_TEST(repeated handshake) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
node_id no_nid;
std::vector<std::string> no_ids;
auto payload = to_buf(no_nid, no_ids);
set_input(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
CAF_CHECK_EQUAL(app.handle_data(*this, input), none);
CAF_CHECK_EQUAL(app.handle_data(*this, payload),
basp::ec::unexpected_handshake);
}
CAF_TEST(actor message) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
CAF_REQUIRE_EQUAL(self->mailbox().size(), 0u);
MOCK(basp::message_type::actor_message, make_message_id().integer_value(),
mars, actor_id{42}, self->id(), std::vector<strong_actor_ptr>{},
make_message("hello world!"));
expect((monitor_atom, strong_actor_ptr), from(_).to(self));
expect((std::string), from(_).to(self).with("hello world!"));
}
CAF_TEST(resolve request without result) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, std::string{"foo/bar"});
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, 0u);
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on id with result) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
auto path = "id/" + std::to_string(self->id());
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on name with result) {
handle_handshake();
consume_handshake();
sys.registry().put("foo", self);
std::string path = "name/foo";
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve response with invalid actor handle) {
handle_handshake();
consume_handshake();
app.resolve(*this, "foo/bar", self);
std::string path;
RECEIVE(basp::message_type::resolve_request, 1u, path);
CAF_CHECK_EQUAL(path, "foo/bar");
actor_id aid = 0;
std::set<std::string> ifs;
MOCK(basp::message_type::resolve_response, 1u, aid, ifs);
self->receive([&](strong_actor_ptr& hdl, std::set<std::string>& hdl_ifs) {
CAF_CHECK_EQUAL(hdl, nullptr);
CAF_CHECK_EQUAL(ifs, hdl_ifs);
});
}
CAF_TEST(resolve response with valid actor handle) {
handle_handshake();
consume_handshake();
app.resolve(*this, "foo/bar", self);
std::string path;
RECEIVE(basp::message_type::resolve_request, 1u, path);
CAF_CHECK_EQUAL(path, "foo/bar");
actor_id aid = 42;
std::set<std::string> ifs;
MOCK(basp::message_type::resolve_response, 1u, aid, ifs);
self->receive([&](strong_actor_ptr& hdl, std::set<std::string>& hdl_ifs) {
CAF_REQUIRE(hdl != nullptr);
CAF_CHECK_EQUAL(ifs, hdl_ifs);
CAF_CHECK_EQUAL(hdl->id(), aid);
});
}
CAF_TEST(heartbeat message) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
auto bytes = to_bytes(basp::header{basp::message_type::heartbeat, 0, 0});
set_input(bytes);
REQUIRE_OK(app.handle_data(*this, input));
CAF_CHECK_EQUAL(app.state(), basp::connection_state::await_header);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE convert_ip_endpoint
#include "caf/detail/convert_ip_endpoint.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include <cstring>
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/ipv4_endpoint.hpp"
#include "caf/ipv6_endpoint.hpp"
using namespace caf;
using namespace caf::detail;
namespace {
struct fixture : host_fixture {
fixture() : host_fixture() {
memset(&sockaddr4_src, 0, sizeof(sockaddr_storage));
memset(&sockaddr6_src, 0, sizeof(sockaddr_storage));
auto sockaddr6_ptr = reinterpret_cast<sockaddr_in6*>(&sockaddr6_src);
sockaddr6_ptr->sin6_family = AF_INET6;
sockaddr6_ptr->sin6_port = htons(23);
sockaddr6_ptr->sin6_addr = in6addr_loopback;
auto sockaddr4_ptr = reinterpret_cast<sockaddr_in*>(&sockaddr4_src);
sockaddr4_ptr->sin_family = AF_INET;
sockaddr4_ptr->sin_port = htons(23);
sockaddr4_ptr->sin_addr.s_addr = INADDR_LOOPBACK;
}
sockaddr_storage sockaddr6_src;
sockaddr_storage sockaddr4_src;
sockaddr_storage dst;
ip_endpoint ep_src;
ip_endpoint ep_dst;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(convert_ip_endpoint_tests, fixture)
CAF_TEST(sockaddr_in6 roundtrip) {
ip_endpoint tmp;
CAF_MESSAGE("converting sockaddr_in6 to ip_endpoint");
CAF_CHECK_EQUAL(convert(sockaddr6_src, tmp), none);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in6");
convert(tmp, dst);
CAF_CHECK_EQUAL(memcmp(&sockaddr6_src, &dst, sizeof(sockaddr_storage)), 0);
}
CAF_TEST(ipv6_endpoint roundtrip) {
sockaddr_storage tmp = {};
if (auto err = detail::parse("[::1]:55555", ep_src))
CAF_FAIL("unable to parse input: " << err);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in6");
convert(ep_src, tmp);
CAF_MESSAGE("converting sockaddr_in6 to ip_endpoint");
CAF_CHECK_EQUAL(convert(tmp, ep_dst), none);
CAF_CHECK_EQUAL(ep_src, ep_dst);
}
CAF_TEST(sockaddr_in4 roundtrip) {
ip_endpoint tmp;
CAF_MESSAGE("converting sockaddr_in to ip_endpoint");
CAF_CHECK_EQUAL(convert(sockaddr4_src, tmp), none);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in");
convert(tmp, dst);
CAF_CHECK_EQUAL(memcmp(&sockaddr4_src, &dst, sizeof(sockaddr_storage)), 0);
}
CAF_TEST(ipv4_endpoint roundtrip) {
sockaddr_storage tmp = {};
if (auto err = detail::parse("127.0.0.1:55555", ep_src))
CAF_FAIL("unable to parse input: " << err);
CAF_MESSAGE("converting ip_endpoint to sockaddr_in");
convert(ep_src, tmp);
CAF_MESSAGE("converting sockaddr_in to ip_endpoint");
CAF_CHECK_EQUAL(convert(tmp, ep_dst), none);
CAF_CHECK_EQUAL(ep_src, ep_dst);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE datagram_socket
#include "caf/net/datagram_socket.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST(invalid_socket) {
datagram_socket x;
CAF_CHECK_NOT_EQUAL(allow_connreset(x, true), none);
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE datagram_transport
#include "caf/net/datagram_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/endpoint_manager_impl.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
using namespace caf::net::ip;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
constexpr string_view hello_manager = "hello manager!";
class dummy_application_factory;
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() : shared_buf(std::make_shared<byte_buffer>(1024)) {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
mpx->set_thread_id();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto addresses = local_addresses("localhost");
CAF_CHECK(!addresses.empty());
ep = ip_endpoint(*addresses.begin(), 0);
auto send_pair = unbox(make_udp_datagram_socket(ep));
send_socket = send_pair.first;
auto receive_pair = unbox(make_udp_datagram_socket(ep));
recv_socket = receive_pair.first;
ep.port(receive_pair.second);
CAF_MESSAGE("sending message to " << CAF_ARG(ep));
if (auto err = nonblocking(recv_socket, true))
CAF_FAIL("nonblocking() returned an error: " << err);
}
~fixture() {
close(send_socket);
close(recv_socket);
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
error read_from_socket(udp_datagram_socket sock, byte_buffer& buf) {
uint8_t receive_attempts = 0;
variant<std::pair<size_t, ip_endpoint>, sec> read_ret;
do {
read_ret = read(sock, buf);
if (auto read_res = get_if<std::pair<size_t, ip_endpoint>>(&read_ret)) {
buf.resize(read_res->first);
} else if (get<sec>(read_ret) != sec::unavailable_or_would_block) {
return make_error(get<sec>(read_ret), "read failed");
}
if (++receive_attempts > 100)
return make_error(sec::runtime_error,
"too many unavailable_or_would_blocks");
} while (read_ret.index() != 0);
return none;
}
multiplexer_ptr mpx;
byte_buffer_ptr shared_buf;
ip_endpoint ep;
udp_datagram_socket send_socket;
udp_datagram_socket recv_socket;
};
class dummy_application {
public:
explicit dummy_application(byte_buffer_ptr rec_buf)
: rec_buf_(std::move(rec_buf)){
// nop
};
template <class Parent>
error init(Parent&) {
return none;
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto payload_buf = parent.next_payload_buffer();
binary_serializer sink{parent.system(), payload_buf};
if (auto err = sink(msg->msg->payload))
CAF_FAIL("serializing failed: " << err);
parent.write_packet(payload_buf);
return none;
}
template <class Parent>
error handle_data(Parent&, span<const byte> data) {
rec_buf_->clear();
rec_buf_->insert(rec_buf_->begin(), data.begin(), data.end());
return none;
}
template <class Parent>
void resolve(Parent& parent, string_view path, const actor& listener) {
actor_id aid = 42;
auto uri = unbox(make_uri("test:/id/42"));
auto nid = make_node_id(uri);
actor_config cfg;
endpoint_manager_ptr ptr{&parent.manager()};
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(
aid, nid, &parent.system(), cfg, std::move(ptr));
anon_send(listener, resolve_atom_v, std::string{path.begin(), path.end()},
p);
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
template <class Parent>
void timeout(Parent&, const std::string&, uint64_t) {
// nop
}
void handle_error(sec sec) {
CAF_FAIL("handle_error called: " << to_string(sec));
}
private:
byte_buffer_ptr rec_buf_;
};
class dummy_application_factory {
public:
using application_type = dummy_application;
explicit dummy_application_factory(byte_buffer_ptr buf)
: buf_(std::move(buf)) {
// nop
}
dummy_application make() {
return dummy_application{buf_};
}
private:
byte_buffer_ptr buf_;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(datagram_transport_tests, fixture)
CAF_TEST(receive) {
using transport_type = datagram_transport<dummy_application_factory>;
if (auto err = nonblocking(recv_socket, true))
CAF_FAIL("nonblocking() returned an error: " << err);
auto mgr = make_endpoint_manager(
mpx, sys,
transport_type{recv_socket, dummy_application_factory{shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
transport.configure_read(net::receive_policy::exactly(hello_manager.size()));
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_CHECK_EQUAL(write(send_socket, as_bytes(make_span(hello_manager)), ep),
hello_manager.size());
CAF_MESSAGE("wrote " << hello_manager.size() << " bytes.");
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(shared_buf->data()),
shared_buf->size()),
hello_manager);
}
CAF_TEST(resolve and proxy communication) {
using transport_type = datagram_transport<dummy_application_factory>;
byte_buffer recv_buf(1024);
auto uri = unbox(make_uri("test:/id/42"));
auto mgr = make_endpoint_manager(
mpx, sys,
transport_type{send_socket, dummy_application_factory{shared_buf}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto mgr_impl = mgr.downcast<endpoint_manager_impl<transport_type>>();
CAF_CHECK(mgr_impl != nullptr);
auto& transport = mgr_impl->transport();
CAF_CHECK_EQUAL(transport.add_new_worker(make_node_id(uri), ep), none);
run();
mgr->resolve(uri, self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
CAF_MESSAGE("got a proxy, send a message to it");
self->send(actor_cast<actor>(p), "hello proxy!");
},
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
CAF_CHECK_EQUAL(read_from_socket(recv_socket, recv_buf), none);
CAF_MESSAGE("receive buffer contains " << recv_buf.size() << " bytes");
message msg;
binary_deserializer source{sys, recv_buf};
CAF_CHECK_EQUAL(source(msg), none);
if (msg.match_elements<std::string>())
CAF_CHECK_EQUAL(msg.get_as<std::string>(0), "hello proxy!");
else
CAF_ERROR("expected a string, got: " << to_string(msg));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE detail.rfc6455
#include "caf/detail/rfc6455.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include <cstdint>
#include <initializer_list>
#include <vector>
using namespace caf;
namespace {
struct fixture {
using impl = detail::rfc6455;
auto bytes(std::initializer_list<uint8_t> xs) {
std::vector<byte> result;
for (auto x : xs)
result.emplace_back(static_cast<byte>(x));
return result;
}
template <class T>
auto take(const T& xs, size_t num_bytes) {
auto n = std::min(xs.size(), num_bytes);
return std::vector<typename T::value_type>{xs.begin(), xs.begin() + n};
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(rfc6455_tests, fixture)
CAF_TEST(masking) {
auto key = uint32_t{0xDEADC0DE};
auto data = bytes({0x12, 0x34, 0x45, 0x67, 0x89, 0x9A});
auto masked_data = data;
CAF_MESSAGE("masking XORs the repeated key to data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, bytes({
0x12 ^ 0xDE,
0x34 ^ 0xAD,
0x45 ^ 0xC0,
0x67 ^ 0xDE,
0x89 ^ 0xDE,
0x9A ^ 0xAD,
}));
CAF_MESSAGE("masking masked data again gives the original data");
impl::mask_data(key, masked_data);
CAF_CHECK_EQUAL(masked_data, data);
}
CAF_TEST(no mask key plus small data) {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
0x82, // FIN + binary frame opcode
0x04, // data size = 4
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 2);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus small data) {
std::vector<uint8_t> data{0x12, 0x34, 0x45, 0x67};
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(out, bytes({
0x82, // FIN + binary frame opcode
0x84, // MASKED + data size = 4
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0x12, 0x34, 0x45, 0x67, // masked data
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 6);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus medium data) {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 8),
bytes({
0x82, // FIN + binary frame opcode
0x7E, // 126 -> uint16 size
0x00, 0x7E, // data size = 126
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 4);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus medium data) {
std::vector<uint8_t> data;
data.insert(data.end(), 126, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 12),
bytes({
0x82, // FIN + binary frame opcode
0xFE, // MASKED + 126 -> uint16 size
0x00, 0x7E, // data size = 126
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 8);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(no mask key plus large data) {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0, as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 14),
bytes({
0x82, // FIN + binary frame opcode
0x7F, // 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 65536
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 10);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0u);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST(valid mask key plus large data) {
std::vector<uint8_t> data;
data.insert(data.end(), 65536, 0xFF);
std::vector<byte> out;
impl::assemble_frame(impl::binary_frame, 0xDEADC0DE,
as_bytes(make_span(data)), out);
CAF_CHECK_EQUAL(take(out, 18),
bytes({
0x82, // FIN + binary frame opcode
0xFF, // MASKED + 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 65536
0xDE, 0xAD, 0xC0, 0xDE, // mask key,
0xFF, 0xFF, 0xFF, 0xFF, // first 4 masked bytes
}));
impl::header hdr;
CAF_CHECK_EQUAL(impl::decode_header(out, hdr), 14);
CAF_CHECK_EQUAL(hdr.fin, true);
CAF_CHECK_EQUAL(hdr.mask_key, 0xDEADC0DEu);
CAF_CHECK_EQUAL(hdr.opcode, impl::binary_frame);
CAF_CHECK_EQUAL(hdr.payload_len, data.size());
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.doorman
#include "caf/net/doorman.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/error.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/ip.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/uri.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/test/host_fixture.hpp"
using namespace caf;
using namespace caf::net;
using namespace std::literals::string_literals;
namespace {
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
mpx->set_thread_id();
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auth.port = 0;
auth.host = "0.0.0.0"s;
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
multiplexer_ptr mpx;
uri::authority_type auth;
};
class dummy_application {
public:
template <class Parent>
error init(Parent&) {
return none;
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> msg) {
auto payload_buf = parent.next_payload_buffer();
binary_serializer sink{parent.system(), payload_buf};
if (auto err = sink(msg->msg->payload))
CAF_FAIL("serializing failed: " << err);
parent.write_packet(payload_buf);
return none;
}
template <class Parent>
error handle_data(Parent&, span<const byte>) {
return none;
}
template <class Parent>
void resolve(Parent&, string_view path, const actor& listener) {
anon_send(listener, resolve_atom_v,
"the resolved path is still "
+ std::string(path.begin(), path.end()));
}
template <class Parent>
void timeout(Parent&, const std::string&, uint64_t) {
// nop
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
void handle_error(sec) {
// nop
}
};
class dummy_application_factory {
public:
using application_type = dummy_application;
template <class Parent>
error init(Parent&) {
return none;
}
application_type make() const {
return dummy_application{};
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(doorman_tests, fixture)
CAF_TEST(doorman accept) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(socket_cast<network_socket>(acceptor)));
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
auto mgr = make_endpoint_manager(
mpx, sys,
doorman<dummy_application_factory>{acceptor_guard.release(),
dummy_application_factory{}});
CAF_CHECK_EQUAL(mgr->init(), none);
auto before = mpx->num_socket_managers();
CAF_CHECK_EQUAL(before, 2u);
uri::authority_type dst;
dst.port = port;
dst.host = "localhost"s;
CAF_MESSAGE("connecting to doorman on: " << dst);
auto conn = make_socket_guard(unbox(make_connected_tcp_stream_socket(dst)));
CAF_MESSAGE("waiting for connection");
while (mpx->num_socket_managers() != before + 1)
run();
CAF_MESSAGE("connected");
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE endpoint_manager
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/node_id.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
string_view hello_manager{"hello manager!"};
string_view hello_test{"hello test!"};
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
mpx = std::make_shared<multiplexer>();
mpx->set_thread_id();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
if (mpx->num_socket_managers() != 1)
CAF_FAIL("mpx->num_socket_managers() != 1");
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
multiplexer_ptr mpx;
};
class dummy_application {
// nop
};
class dummy_transport {
public:
using application_type = dummy_application;
dummy_transport(stream_socket handle, byte_buffer_ptr data)
: handle_(handle), data_(data), read_buf_(1024) {
// nop
}
stream_socket handle() {
return handle_;
}
template <class Manager>
error init(Manager& manager) {
auto test_bytes = as_bytes(make_span(hello_test));
buf_.insert(buf_.end(), test_bytes.begin(), test_bytes.end());
manager.register_writing();
return none;
}
template <class Manager>
bool handle_read_event(Manager&) {
auto num_bytes = read(handle_, read_buf_);
if (num_bytes > 0) {
data_->insert(data_->end(), read_buf_.begin(),
read_buf_.begin() + num_bytes);
return true;
}
return num_bytes < 0 && last_socket_error_is_temporary();
}
template <class Manager>
bool handle_write_event(Manager& mgr) {
for (auto x = mgr.next_message(); x != nullptr; x = mgr.next_message()) {
binary_serializer sink{mgr.system(), buf_};
if (auto err = sink(x->msg->payload))
CAF_FAIL("serializing failed: " << err);
}
auto num_bytes = write(handle_, buf_);
if (num_bytes > 0) {
buf_.erase(buf_.begin(), buf_.begin() + num_bytes);
return buf_.size() > 0;
}
return num_bytes < 0 && last_socket_error_is_temporary();
}
void handle_error(sec) {
// nop
}
template <class Manager>
void resolve(Manager& mgr, const uri& locator, const actor& listener) {
actor_id aid = 42;
auto hid = string_view("0011223344556677889900112233445566778899");
auto nid = unbox(make_node_id(42, hid));
actor_config cfg;
auto p = make_actor<actor_proxy_impl, strong_actor_ptr>(aid, nid,
&mgr.system(), cfg,
&mgr);
std::string path{locator.path().begin(), locator.path().end()};
anon_send(listener, resolve_atom_v, std::move(path), p);
}
template <class Manager>
void timeout(Manager&, const std::string&, uint64_t) {
// nop
}
template <class Parent>
void new_proxy(Parent&, const node_id&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, const node_id&, actor_id, error) {
// nop
}
private:
stream_socket handle_;
byte_buffer_ptr data_;
byte_buffer read_buf_;
byte_buffer buf_;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(send and receive) {
byte_buffer read_buf(1024);
auto buf = std::make_shared<byte_buffer>();
auto sockets = unbox(make_stream_socket_pair());
CAF_CHECK_EQUAL(nonblocking(sockets.second, true), none);
CAF_CHECK_LESS(read(sockets.second, read_buf), 0);
CAF_CHECK(last_socket_error_is_temporary());
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
auto mgr = make_endpoint_manager(mpx, sys,
dummy_transport{sockets.first, buf});
CAF_CHECK_EQUAL(mgr->mask(), operation::none);
CAF_CHECK_EQUAL(mgr->init(), none);
CAF_CHECK_EQUAL(mgr->mask(), operation::read_write);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
CAF_CHECK_EQUAL(write(sockets.second, as_bytes(make_span(hello_manager))),
hello_manager.size());
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(buf->data()),
buf->size()),
hello_manager);
CAF_CHECK_EQUAL(read(sockets.second, read_buf), hello_test.size());
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(read_buf.data()),
hello_test.size()),
hello_test);
}
CAF_TEST(resolve and proxy communication) {
byte_buffer read_buf(1024);
auto buf = std::make_shared<byte_buffer>();
auto sockets = unbox(make_stream_socket_pair());
CAF_CHECK_EQUAL(nonblocking(sockets.second, true), none);
auto guard = detail::make_scope_guard([&] { close(sockets.second); });
auto mgr = make_endpoint_manager(mpx, sys,
dummy_transport{sockets.first, buf});
CAF_CHECK_EQUAL(mgr->init(), none);
CAF_CHECK_EQUAL(mgr->mask(), operation::read_write);
run();
CAF_CHECK_EQUAL(read(sockets.second, read_buf), hello_test.size());
mgr->resolve(unbox(make_uri("test:id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
CAF_MESSAGE("got a proxy, send a message to it");
self->send(actor_cast<actor>(p), "hello proxy!");
},
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
auto read_res = read(sockets.second, read_buf);
if (read_res <= 0) {
std::string msg = "socket closed";
if (read_res < 0)
msg = last_socket_error_as_string();
CAF_ERROR("read() failed: " << msg);
return;
}
read_buf.resize(static_cast<size_t>(read_res));
CAF_MESSAGE("receive buffer contains " << read_buf.size() << " bytes");
message msg;
binary_deserializer source{sys, read_buf};
CAF_CHECK_EQUAL(source(msg), none);
if (msg.match_elements<std::string>())
CAF_CHECK_EQUAL(msg.get_as<std::string>(0), "hello proxy!");
else
CAF_ERROR("expected a string, got: " << to_string(msg));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE basp.header
#include "caf/net/basp/header.hpp"
#include "net-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/deep_to_string.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST(serialization) {
basp::header x{basp::message_type::handshake, 42, 4};
byte_buffer buf;
{
binary_serializer sink{nullptr, buf};
CHECK(sink.apply(x));
}
CHECK_EQ(buf.size(), basp::header_size);
auto buf2 = to_bytes(x);
REQUIRE_EQ(buf.size(), buf2.size());
CHECK(std::equal(buf.begin(), buf.end(), buf2.begin()));
basp::header y;
{
binary_deserializer source{nullptr, buf};
CHECK(source.apply(y));
}
CHECK_EQ(x, y);
auto z = basp::header::from_bytes(buf);
CHECK_EQ(x, z);
CHECK_EQ(y, z);
}
CAF_TEST(to_string) {
basp::header x{basp::message_type::handshake, 42, 4};
CHECK_EQ(
deep_to_string(x),
"caf::net::basp::header(caf::net::basp::message_type::handshake, 42, 4)");
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE ip
#include "caf/net/ip.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/ip_address.hpp"
#include "caf/ipv4_address.hpp"
using namespace caf;
using namespace caf::net;
namespace {
struct fixture : host_fixture {
fixture() : v6_local{{0}, {0x1}} {
v4_local = ip_address{make_ipv4_address(127, 0, 0, 1)};
v4_any_addr = ip_address{make_ipv4_address(0, 0, 0, 0)};
}
bool contains(ip_address x) {
return std::count(addrs.begin(), addrs.end(), x) > 0;
}
ip_address v4_any_addr;
ip_address v6_any_addr;
ip_address v4_local;
ip_address v6_local;
std::vector<ip_address> addrs;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(ip_tests, fixture)
CAF_TEST(resolve localhost) {
addrs = ip::resolve("localhost");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_local) || contains(v6_local));
}
CAF_TEST(resolve any) {
addrs = ip::resolve("");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
}
CAF_TEST(local addresses localhost) {
addrs = ip::local_addresses("localhost");
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_local) || contains(v6_local));
}
CAF_TEST(local addresses any) {
addrs = ip::local_addresses("0.0.0.0");
auto tmp = ip::local_addresses("::");
addrs.insert(addrs.end(), tmp.begin(), tmp.end());
CAF_CHECK(!addrs.empty());
CAF_CHECK(contains(v4_any_addr) || contains(v6_any_addr));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE multiplexer
#include "caf/net/multiplexer.hpp"
#include "net-test.hpp"
#include <new>
#include <tuple>
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using shared_atomic_count = std::shared_ptr<std::atomic<size_t>>;
class dummy_manager : public socket_manager {
public:
// -- constructors, destructors, and assignment operators --------------------
dummy_manager(stream_socket handle, multiplexer* parent, std::string name,
shared_atomic_count count)
: socket_manager(handle, parent), name(std::move(name)), count_(count) {
MESSAGE("created new dummy manager");
++*count_;
rd_buf_.resize(1024);
}
~dummy_manager() {
MESSAGE("destroyed dummy manager");
--*count_;
}
// -- properties -------------------------------------------------------------
stream_socket handle() const noexcept {
return socket_cast<stream_socket>(handle_);
}
// -- testing DSL ------------------------------------------------------------
void send(string_view x) {
auto x_bytes = as_bytes(make_span(x));
wr_buf_.insert(wr_buf_.end(), x_bytes.begin(), x_bytes.end());
}
std::string receive() {
std::string result(reinterpret_cast<char*>(rd_buf_.data()), rd_buf_pos_);
rd_buf_pos_ = 0;
return result;
}
// -- interface functions ----------------------------------------------------
error init(const settings&) override {
return none;
}
read_result handle_read_event() override {
if (trigger_handover) {
MESSAGE(name << " triggered a handover");
return read_result::handover;
}
if (read_capacity() < 1024)
rd_buf_.resize(rd_buf_.size() + 2048);
auto num_bytes = read(handle(),
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()) {
return read_result::again;
} else {
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 (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_);
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()
? 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;
}
// --
bool trigger_handover = false;
intrusive_ptr<dummy_manager> next;
std::string name;
private:
byte* read_position_begin() {
return rd_buf_.data() + rd_buf_pos_;
}
byte* read_position_end() {
return rd_buf_.data() + rd_buf_.size();
}
size_t read_capacity() const {
return rd_buf_.size() - rd_buf_pos_;
}
shared_atomic_count count_;
size_t rd_buf_pos_ = 0;
byte_buffer wr_buf_;
byte_buffer rd_buf_;
};
using dummy_manager_ptr = intrusive_ptr<dummy_manager>;
struct fixture : host_fixture {
fixture() : mpx(nullptr) {
manager_count = std::make_shared<std::atomic<size_t>>(0);
mpx.set_thread_id();
}
~fixture() {
mpx.shutdown();
exhaust();
REQUIRE_EQ(*manager_count, 0u);
}
void exhaust() {
mpx.apply_updates();
while (mpx.poll_once(false))
; // Repeat.
}
void apply_updates() {
mpx.apply_updates();
}
auto make_manager(stream_socket fd, std::string name) {
return make_counted<dummy_manager>(fd, &mpx, std::move(name),
manager_count);
}
void init() {
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
exhaust();
}
shared_atomic_count manager_count;
multiplexer mpx;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("the multiplexer has no socket managers after default construction") {
GIVEN("a default constructed multiplexer") {
WHEN("querying the number of socket managers") {
THEN("the result is 0") {
CHECK_EQ(mpx.num_socket_managers(), 0u);
}
}
}
}
SCENARIO("the multiplexer constructs the pollset updater while initializing") {
GIVEN("an initialized multiplexer") {
WHEN("querying the number of socket managers") {
THEN("the result is 1") {
CHECK_EQ(mpx.num_socket_managers(), 0u);
CHECK_EQ(mpx.init(), none);
exhaust();
CHECK_EQ(mpx.num_socket_managers(), 1u);
}
}
}
}
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();
apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 3u);
THEN("the multiplexer runs callbacks on socket activity") {
alice->send("Hello Bob!");
alice->register_writing();
exhaust();
CHECK_EQ(bob->receive(), "Hello Bob!");
}
}
}
}
SCENARIO("a multiplexer terminates its thread after shutting down") {
GIVEN("a multiplexer running in its own thread and some socket managers") {
init();
auto go_time = std::make_shared<barrier>(2);
auto mpx_thread = std::thread{[this, go_time] {
mpx.set_thread_id();
go_time->arrive_and_wait();
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();
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());
}
}
}
}
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()
#define CAF_TEST_NO_MAIN
#include "caf/test/unit_test_impl.hpp"
#include "caf/init_global_meta_objects.hpp"
#include "caf/net/middleman.hpp"
int main(int argc, char** argv) {
using namespace caf;
net::middleman::init_global_meta_objects();
core::init_global_meta_objects();
return test::main(argc, argv);
}
#pragma once
#include "caf/error.hpp"
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/string_view.hpp"
#include "caf/tag/stream_oriented.hpp"
#include "caf/test/bdd_dsl.hpp"
template <class UpperLayer>
class mock_stream_transport {
public:
// -- member types -----------------------------------------------------------
using output_tag = caf::tag::stream_oriented;
// -- constructors, destructors, and assignment operators --------------------
template <class... Ts>
explicit mock_stream_transport(Ts&&... xs)
: upper_layer(std::forward<Ts>(xs)...) {
// nop
}
// -- interface for the upper layer ------------------------------------------
void begin_output() {
// nop
}
auto& output_buffer() {
return output;
}
constexpr void end_output() {
// nop
}
constexpr caf::net::socket handle() noexcept {
return caf::net::invalid_socket;
}
bool can_send_more() const noexcept {
return true;
}
const caf::error& abort_reason() {
return abort_reason_;
}
void abort_reason(caf::error reason) {
abort_reason_ = std::move(reason);
}
bool stopped() const noexcept {
return max_read_size == 0;
}
void configure_read(caf::net::receive_policy policy) {
min_read_size = policy.min_size;
max_read_size = policy.max_size;
}
// -- initialization ---------------------------------------------------------
caf::error init(const caf::settings& config) {
return upper_layer.init(nullptr, this, config);
}
caf::error init() {
caf::settings config;
return init(config);
}
// -- buffer management ------------------------------------------------------
void push(caf::span<const caf::byte> bytes) {
input.insert(input.begin(), bytes.begin(), bytes.end());
}
void push(caf::string_view str) {
push(caf::as_bytes(caf::make_span(str)));
}
size_t unconsumed() const noexcept {
return read_buf_.size();
}
caf::string_view output_as_str() const noexcept {
return {reinterpret_cast<const char*>(output.data()), output.size()};
}
// -- event callbacks --------------------------------------------------------
ptrdiff_t handle_input() {
ptrdiff_t result = 0;
while (max_read_size > 0) {
CAF_ASSERT(max_read_size > static_cast<size_t>(read_size_));
size_t len = max_read_size - static_cast<size_t>(read_size_);
CAF_LOG_DEBUG(CAF_ARG2("available capacity:", len));
auto num_bytes = std::min(input.size(), len);
if (num_bytes == 0)
return result;
auto delta_offset = static_cast<ptrdiff_t>(read_buf_.size());
read_buf_.insert(read_buf_.end(), input.begin(),
input.begin() + num_bytes);
input.erase(input.begin(), input.begin() + num_bytes);
read_size_ += static_cast<ptrdiff_t>(num_bytes);
if (static_cast<size_t>(read_size_) < min_read_size)
return result;
auto delta = make_span(read_buf_.data() + delta_offset,
read_size_ - delta_offset);
auto consumed = upper_layer.consume(this, caf::make_span(read_buf_),
delta);
if (consumed > 0) {
result += static_cast<ptrdiff_t>(consumed);
read_buf_.erase(read_buf_.begin(), read_buf_.begin() + consumed);
read_size_ -= consumed;
} else if (consumed < 0) {
if (!abort_reason_)
abort_reason_ = caf::sec::runtime_error;
upper_layer.abort(this, abort_reason_);
return -1;
}
}
return result;
}
// -- member variables -------------------------------------------------------
UpperLayer upper_layer;
std::vector<caf::byte> output;
std::vector<caf::byte> input;
uint32_t min_read_size = 0;
uint32_t max_read_size = 0;
private:
std::vector<caf::byte> read_buf_;
ptrdiff_t read_size_ = 0;
caf::error abort_reason_;
};
// Drop-in replacement for std::barrier (based on the TS API as of 2020).
class barrier {
public:
explicit barrier(ptrdiff_t num_threads)
: num_threads_(num_threads), count_(0) {
// 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_; });
}
}
private:
ptrdiff_t num_threads_;
std::mutex mx_;
std::atomic<ptrdiff_t> count_;
std::condition_variable cv_;
};
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.actor_shell
#include "caf/net/actor_shell.hpp"
#include "net-test.hpp"
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_transport.hpp"
using namespace caf;
using namespace std::string_literals;
namespace {
using svec = std::vector<std::string>;
struct app_t {
using input_tag = tag::stream_oriented;
app_t() = default;
explicit app_t(actor hdl) : worker(std::move(hdl)) {
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr down, const settings&) {
self = mgr->make_actor_shell(down);
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
});
self->set_fallback([](message& msg) -> result<message> {
CAF_FAIL("unexpected message: " << msg);
return make_error(sec::unexpected_message);
});
down->configure_read(net::receive_policy::up_to(2048));
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
while (self->consume_message()) {
// We set abort_reason in our response handlers in case of an error.
if (down->abort_reason())
return false;
// else: repeat.
}
return true;
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return self->try_block_mailbox();
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf, byte_span) {
// Seek newline character.
constexpr auto nl = byte{'\n'};
if (auto i = std::find(buf.begin(), buf.end(), nl); i != buf.end()) {
// Skip empty lines.
if (i == buf.begin()) {
consumed_bytes += 1;
auto sub_res = consume(down, buf.subspan(1), {});
return sub_res >= 0 ? sub_res + 1 : sub_res;
}
auto num_bytes = std::distance(buf.begin(), i) + 1;
string_view line{reinterpret_cast<const char*>(buf.data()),
static_cast<size_t>(num_bytes) - 1};
CAF_MESSAGE("deserialize config value from : " << line);
config_value val;
if (auto parsed_res = config_value::parse(line)) {
val = std::move(*parsed_res);
} else {
down->abort_reason(std::move(parsed_res.error()));
return -1;
}
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 {
++received_responses;
// Respond with the value as string.
auto str_response = std::to_string(value);
str_response += '\n';
down->begin_output();
auto& buf = down->output_buffer();
auto bytes = as_bytes(make_span(str_response));
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
},
[down](error& err) mutable { down->abort_reason(std::move(err)); });
// Try consuming more from the buffer.
consumed_bytes += static_cast<size_t>(num_bytes);
auto sub_buf = buf.subspan(num_bytes);
auto sub_res = consume(down, sub_buf, {});
return sub_res >= 0 ? num_bytes + sub_res : sub_res;
}
return 0;
}
// Handle to the worker-under-test.
actor worker;
// Lines received as asynchronous messages.
std::vector<std::string> lines;
// Actor shell representing this app.
net::actor_shell_ptr self;
// Counts how many bytes we've consumed in total.
size_t consumed_bytes = 0;
// Counts how many response messages we've received from the worker.
size_t received_responses = 0;
};
struct fixture : host_fixture, test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) {
mpx.set_thread_id();
if (auto err = mpx.init())
CAF_FAIL("mpx.init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second);
if (auto err = nonblocking(self_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
if (auto err = nonblocking(testee_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
}
template <class Predicate>
void run_while(Predicate predicate) {
if (!predicate())
return;
for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates();
mpx.poll_once(false);
byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0)
recv_buf.insert(recv_buf.end(), tmp, tmp + bytes);
if (!predicate())
return;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
CAF_FAIL("reached max repeat rate without meeting the predicate");
}
void send(string_view str) {
auto res = write(self_socket_guard.socket(), as_bytes(make_span(str)));
if (res != static_cast<ptrdiff_t>(str.size()))
CAF_FAIL("expected write() to return " << str.size() << ", got: " << res);
}
net::middleman mm;
net::multiplexer mpx;
net::socket_guard<net::stream_socket> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard;
std::vector<byte> recv_buf;
};
constexpr std::string_view input = R"__(
[{"@type": "int32_t", "value": 123}]
)__";
} // namespace
CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) {
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
auto hdl = app.self.as_actor();
anon_send(hdl, "line 1");
anon_send(hdl, "line 2");
anon_send(hdl, "line 3");
run_while([&] { return app.lines.size() != 3; });
CAF_CHECK_EQUAL(app.lines, svec({"line 1", "line 2", "line 3"}));
}
CAF_TEST(actor shells can send requests and receive responses) {
auto worker = sys.spawn([] {
return behavior{
[](int32_t value) { return value * 2; },
};
});
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx,
worker);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
send(input);
run_while([&] { return app.consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
string_view expected_response = "246\n";
run_while([&] { return recv_buf.size() < expected_response.size(); });
string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.backend.tcp
#include "caf/net/backend/tcp.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include <string>
#include <thread>
#include "caf/actor_system_config.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
using namespace std::literals::string_literals;
namespace {
behavior dummy_actor(event_based_actor*) {
return {
// nop
};
}
struct earth_node {
uri operator()() {
return unbox(make_uri("tcp://earth"));
}
};
struct mars_node {
uri operator()() {
return unbox(make_uri("tcp://mars"));
}
};
template <class Node>
struct config : actor_system_config {
config() {
Node this_node;
put(content, "caf.middleman.this-node", this_node());
load<middleman, backend::tcp>();
}
};
class planet_driver {
public:
virtual ~planet_driver() = default;
virtual bool handle_io_event() = 0;
};
template <class Node>
class planet : public test_coordinator_fixture<config<Node>> {
public:
planet(planet_driver& driver)
: mm(this->sys.network_manager()), mpx(mm.mpx()), driver_(driver) {
mpx->set_thread_id();
}
node_id id() const {
return this->sys.node();
}
bool handle_io_event() override {
return driver_.handle_io_event();
}
net::middleman& mm;
multiplexer_ptr mpx;
private:
planet_driver& driver_;
};
struct fixture : host_fixture, planet_driver {
fixture() : earth(*this), mars(*this) {
earth.run();
mars.run();
CAF_REQUIRE_EQUAL(earth.mpx->num_socket_managers(), 2);
CAF_REQUIRE_EQUAL(mars.mpx->num_socket_managers(), 2);
}
bool handle_io_event() override {
return earth.mpx->poll_once(false) || mars.mpx->poll_once(false);
}
void run() {
earth.run();
}
planet<earth_node> earth;
planet<mars_node> mars;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(tcp_backend_tests, fixture)
CAF_TEST(doorman accept) {
auto backend = earth.mm.backend("tcp");
CAF_CHECK(backend);
uri::authority_type auth;
auth.host = "localhost"s;
auth.port = backend->port();
CAF_MESSAGE("trying to connect to earth at " << CAF_ARG(auth));
auto sock = make_connected_tcp_stream_socket(auth);
CAF_CHECK(sock);
auto guard = make_socket_guard(*sock);
int runs = 0;
while (earth.mpx->num_socket_managers() < 3) {
if (++runs >= 5)
CAF_FAIL("doorman did not create endpoint_manager");
run();
}
CAF_CHECK_EQUAL(earth.mpx->num_socket_managers(), 3);
}
CAF_TEST(connect) {
uri::authority_type auth;
auth.host = "0.0.0.0"s;
auth.port = 0;
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto acc_guard = make_socket_guard(acceptor);
auto port = unbox(local_port(acc_guard.socket()));
auto uri_str = std::string("tcp://localhost:") + std::to_string(port);
CAF_MESSAGE("connecting to " << CAF_ARG(uri_str));
CAF_CHECK(earth.mm.connect(*make_uri(uri_str)));
auto sock = unbox(accept(acc_guard.socket()));
auto sock_guard = make_socket_guard(sock);
handle_io_event();
CAF_CHECK_EQUAL(earth.mpx->num_socket_managers(), 3);
}
CAF_TEST(publish) {
auto dummy = earth.sys.spawn(dummy_actor);
auto path = "dummy"s;
CAF_MESSAGE("publishing actor " << CAF_ARG(path));
earth.mm.publish(dummy, path);
CAF_MESSAGE("check registry for " << CAF_ARG(path));
CAF_CHECK_NOT_EQUAL(earth.sys.registry().get(path), nullptr);
}
CAF_TEST(resolve) {
using std::chrono::milliseconds;
using std::chrono::seconds;
auto sockets = unbox(make_stream_socket_pair());
auto earth_be = reinterpret_cast<net::backend::tcp*>(earth.mm.backend("tcp"));
CAF_CHECK(earth_be->emplace(mars.id(), sockets.first));
auto mars_be = reinterpret_cast<net::backend::tcp*>(mars.mm.backend("tcp"));
CAF_CHECK(mars_be->emplace(earth.id(), sockets.second));
handle_io_event();
CAF_CHECK_EQUAL(earth.mpx->num_socket_managers(), 3);
CAF_CHECK_EQUAL(mars.mpx->num_socket_managers(), 3);
auto dummy = earth.sys.spawn(dummy_actor);
earth.mm.publish(dummy, "dummy"s);
auto locator = unbox(make_uri("tcp://earth/name/dummy"s));
CAF_MESSAGE("resolve " << CAF_ARG(locator));
mars.mm.resolve(locator, mars.self);
mars.run();
earth.run();
mars.run();
mars.self->receive(
[](strong_actor_ptr& ptr, const std::set<std::string>&) {
CAF_MESSAGE("resolved actor!");
CAF_CHECK_NOT_EQUAL(ptr, nullptr);
},
[](const error& err) {
CAF_FAIL("got error while resolving: " << CAF_ARG(err));
},
after(std::chrono::seconds(0)) >>
[] { CAF_FAIL("manager did not respond with a proxy."); });
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.basp.message_queue
#include "caf/net/basp/message_queue.hpp"
#include "caf/test/dsl.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_system.hpp"
#include "caf/behavior.hpp"
using namespace caf;
namespace {
behavior testee_impl() {
return {
[](ok_atom, int) {
// nop
},
};
}
struct fixture : test_coordinator_fixture<> {
net::basp::message_queue queue;
strong_actor_ptr testee;
fixture() {
auto hdl = sys.spawn<lazy_init>(testee_impl);
testee = actor_cast<strong_actor_ptr>(hdl);
}
void acquire_ids(size_t num) {
for (size_t i = 0; i < num; ++i)
queue.new_id();
}
void push(int msg_id) {
queue.push(nullptr, static_cast<uint64_t>(msg_id), testee,
make_mailbox_element(self->ctrl(), make_message_id(), {},
ok_atom_v, msg_id));
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(message_queue_tests, fixture)
CAF_TEST(default construction) {
CAF_CHECK_EQUAL(queue.next_id, 0u);
CAF_CHECK_EQUAL(queue.next_undelivered, 0u);
CAF_CHECK_EQUAL(queue.pending.size(), 0u);
}
CAF_TEST(ascending IDs) {
CAF_CHECK_EQUAL(queue.new_id(), 0u);
CAF_CHECK_EQUAL(queue.new_id(), 1u);
CAF_CHECK_EQUAL(queue.new_id(), 2u);
CAF_CHECK_EQUAL(queue.next_undelivered, 0u);
}
CAF_TEST(push order 0 - 1 - 2) {
acquire_ids(3);
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
push(2);
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 0 - 2 - 1) {
acquire_ids(3);
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 1 - 0 - 2) {
acquire_ids(3);
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
push(2);
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 1 - 2 - 0) {
acquire_ids(3);
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 2 - 0 - 1) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
push(1);
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(push order 2 - 1 - 0) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
push(1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 1));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST(dropping) {
acquire_ids(3);
push(2);
disallow((ok_atom, int), from(self).to(testee));
queue.drop(nullptr, 1);
disallow((ok_atom, int), from(self).to(testee));
push(0);
expect((ok_atom, int), from(self).to(testee).with(_, 0));
expect((ok_atom, int), from(self).to(testee).with(_, 2));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.basp.ping_pong
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/backend/test.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
using namespace caf;
using namespace caf::net;
namespace {
struct earth_node {
uri operator()() {
return unbox(make_uri("test://earth"));
}
};
struct mars_node {
uri operator()() {
return unbox(make_uri("test://mars"));
}
};
template <class Node>
struct config : actor_system_config {
config() {
Node this_node;
put(content, "caf.middleman.this-node", this_node());
load<middleman, backend::test>();
}
};
class planet_driver {
public:
virtual ~planet_driver() {
// nop
}
virtual bool consume_message() = 0;
virtual bool handle_io_event() = 0;
virtual bool trigger_timeout() = 0;
};
template <class Node>
class planet : public test_coordinator_fixture<config<Node>> {
public:
planet(planet_driver& driver)
: mpx(*this->sys.network_manager().mpx()), driver_(driver) {
mpx.set_thread_id();
}
net::backend::test& backend() {
auto& mm = this->sys.network_manager();
return *dynamic_cast<net::backend::test*>(mm.backend("test"));
}
node_id id() const {
return this->sys.node();
}
bool consume_message() override {
return driver_.consume_message();
}
bool handle_io_event() override {
return driver_.handle_io_event();
}
bool trigger_timeout() override {
return driver_.trigger_timeout();
}
actor resolve(string_view locator) {
auto hdl = actor_cast<actor>(this->self);
this->sys.network_manager().resolve(unbox(make_uri(locator)), hdl);
this->run();
actor result;
this->self->receive(
[&](strong_actor_ptr& ptr, const std::set<std::string>&) {
CAF_MESSAGE("resolved " << locator);
result = actor_cast<actor>(std::move(ptr));
});
return result;
}
multiplexer& mpx;
private:
planet_driver& driver_;
};
behavior ping_actor(event_based_actor* self, actor pong, size_t num_pings,
std::shared_ptr<size_t> count) {
CAF_MESSAGE("num_pings: " << num_pings);
self->send(pong, ping_atom_v, 1);
return {
[=](pong_atom, int value) {
CAF_MESSAGE("received `pong_atom`");
if (++*count >= num_pings) {
CAF_MESSAGE("received " << num_pings << " pings, call self->quit");
self->quit();
}
return caf::make_result(ping_atom_v, value + 1);
},
};
}
behavior pong_actor(event_based_actor* self) {
CAF_MESSAGE("pong actor started");
self->set_down_handler([=](down_msg& dm) {
CAF_MESSAGE("received down_msg{" << to_string(dm.reason) << "}");
self->quit(dm.reason);
});
return {
[=](ping_atom, int value) {
CAF_MESSAGE("received `ping_atom` from " << self->current_sender());
if (self->current_sender() == self->ctrl())
abort();
self->monitor(self->current_sender());
// set next behavior
self->become(
[](ping_atom, int val) { return caf::make_result(pong_atom_v, val); });
// reply to 'ping'
return caf::make_result(pong_atom_v, value);
},
};
}
struct fixture : host_fixture, planet_driver {
fixture() : earth(*this), mars(*this) {
auto sockets = unbox(make_stream_socket_pair());
earth.backend().emplace(mars.id(), sockets.first, sockets.second);
mars.backend().emplace(earth.id(), sockets.second, sockets.first);
run();
}
bool consume_message() override {
return earth.sched.try_run_once() || mars.sched.try_run_once();
}
bool handle_io_event() override {
return earth.mpx.poll_once(false) || mars.mpx.poll_once(false);
}
bool trigger_timeout() override {
return earth.sched.trigger_timeout() || mars.sched.trigger_timeout();
}
void run() {
earth.run();
}
planet<earth_node> earth;
planet<mars_node> mars;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(ping_pong_tests, fixture)
CAF_TEST(full setup) {
auto pong = earth.sys.spawn(pong_actor);
run();
earth.sys.registry().put("pong", pong);
auto remote_pong = mars.resolve("test://earth/name/pong");
auto count = std::make_shared<size_t>(0);
auto ping = mars.sys.spawn(ping_actor, remote_pong, 10, count);
run();
anon_send_exit(pong, exit_reason::kill);
anon_send_exit(ping, exit_reason::kill);
CAF_CHECK_EQUAL(*count, 10u);
run();
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.basp.worker
#include "caf/net/basp/worker.hpp"
#include "caf/test/dsl.hpp"
#include "caf/actor_cast.hpp"
#include "caf/actor_control_block.hpp"
#include "caf/actor_system.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/basp/message_queue.hpp"
#include "caf/proxy_registry.hpp"
using namespace caf;
namespace {
behavior testee_impl() {
return {
[](ok_atom) {
// nop
},
};
}
class mock_actor_proxy : public actor_proxy {
public:
explicit mock_actor_proxy(actor_config& cfg) : actor_proxy(cfg) {
// nop
}
void enqueue(mailbox_element_ptr, execution_unit*) override {
CAF_FAIL("mock_actor_proxy::enqueue called");
}
void kill_proxy(execution_unit*, error) override {
// nop
}
};
class mock_proxy_registry_backend : public proxy_registry::backend {
public:
mock_proxy_registry_backend(actor_system& sys) : sys_(sys) {
// nop
}
strong_actor_ptr make_proxy(node_id nid, actor_id aid) override {
actor_config cfg;
return make_actor<mock_actor_proxy, strong_actor_ptr>(aid, nid, &sys_, cfg);
}
void set_last_hop(node_id*) override {
// nop
}
private:
actor_system& sys_;
};
struct fixture : test_coordinator_fixture<> {
detail::worker_hub<net::basp::worker> hub;
net::basp::message_queue queue;
mock_proxy_registry_backend proxies_backend;
proxy_registry proxies;
node_id last_hop;
actor testee;
fixture() : proxies_backend(sys), proxies(sys, proxies_backend) {
auto tmp = make_node_id(123, "0011223344556677889900112233445566778899");
last_hop = unbox(std::move(tmp));
testee = sys.spawn<lazy_init>(testee_impl);
sys.registry().put(testee.id(), testee);
}
~fixture() {
sys.registry().erase(testee.id());
}
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(worker_tests, fixture)
CAF_TEST(deliver serialized message) {
CAF_MESSAGE("create the BASP worker");
CAF_REQUIRE_EQUAL(hub.peek(), nullptr);
hub.add_new_worker(queue, proxies);
CAF_REQUIRE_NOT_EQUAL(hub.peek(), nullptr);
auto w = hub.pop();
CAF_MESSAGE("create a fake message + BASP header");
byte_buffer payload;
std::vector<strong_actor_ptr> stages;
binary_serializer sink{sys, payload};
if (auto err = sink(node_id{}, self->id(), testee.id(), stages,
make_message(ok_atom_v)))
CAF_FAIL("unable to serialize message: " << err);
net::basp::header hdr{net::basp::message_type::actor_message,
static_cast<uint32_t>(payload.size()),
make_message_id().integer_value()};
CAF_MESSAGE("launch worker");
w->launch(last_hop, hdr, payload);
sched.run_once();
expect((ok_atom), from(_).to(testee));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.consumer_adapter
#include "caf/net/consumer_adapter.hpp"
#include "net-test.hpp"
#include "caf/async/spsc_buffer.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/scheduled_actor/flow.hpp"
using namespace caf;
namespace {
class reader {
public:
reader(net::stream_socket fd, size_t n) : sg_(fd) {
buf_.resize(n);
}
auto fd() {
return sg_.socket();
}
void read_some() {
if (rd_pos_ < buf_.size()) {
auto res = read(fd(), make_span(buf_).subspan(rd_pos_));
if (res > 0) {
rd_pos_ += static_cast<size_t>(res);
MESSAGE(rd_pos_ << " bytes received");
} else if (res < 0 && !net::last_socket_error_is_temporary()) {
FAIL("failed to read: " << net::last_socket_error_as_string());
}
}
}
size_t remaining() const noexcept {
return buf_.size() - rd_pos_;
}
bool done() const noexcept {
return remaining() == 0;
}
auto& buf() const {
return buf_;
}
private:
size_t rd_pos_ = 0;
std::vector<byte> buf_;
net::socket_guard<net::stream_socket> sg_;
};
class app_t {
public:
using input_tag = tag::stream_oriented;
using resource_type = async::consumer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
using adapter_ptr = net::consumer_adapter_ptr<buffer_type>;
using adapter_type = adapter_ptr::element_type;
explicit app_t(resource_type input) : input(std::move(input)) {
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings&) {
if (auto ptr = adapter_type::try_open(mgr, std::move(input))) {
adapter = std::move(ptr);
return none;
} else {
FAIL("unable to open the resource");
}
}
template <class LowerLayerPtr>
struct send_helper {
app_t* thisptr;
LowerLayerPtr down;
bool on_next_called;
bool aborted;
void on_next(span<const int32_t> items) {
REQUIRE_EQ(items.size(), 1u);
auto val = items[0];
thisptr->written_values.emplace_back(val);
auto offset = thisptr->written_bytes.size();
binary_serializer sink{nullptr, thisptr->written_bytes};
if (!sink.apply(val))
FAIL("sink.apply failed: " << sink.get_error());
auto bytes = make_span(thisptr->written_bytes).subspan(offset);
down->begin_output();
auto& buf = down->output_buffer();
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
}
void on_complete() {
// nop
}
void on_error(const error&) {
aborted = true;
}
};
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
if (done)
return true;
auto helper = send_helper<LowerLayerPtr>{this, down, false, false};
while (down->can_send_more()) {
auto [ok, consumed] = adapter->pull(async::delay_errors, 1, helper);
if (!ok) {
MESSAGE("adapter signaled end-of-buffer");
done = true;
break;
} else if (consumed == 0) {
break;
}
}
MESSAGE(written_bytes.size() << " bytes written");
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return done || !adapter->has_data();
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr, byte_span, byte_span) {
FAIL("app::consume called: unexpected data");
}
bool done = false;
std::vector<int32_t> written_values;
std::vector<byte> written_bytes;
adapter_ptr adapter;
resource_type input;
};
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() : mm(sys) {
mm.mpx().set_thread_id();
if (auto err = mm.mpx().init())
CAF_FAIL("mpx.init() failed: " << err);
}
bool handle_io_event() override {
return mm.mpx().poll_once(false);
}
net::middleman mm;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("subscriber adapters wake up idle socket managers") {
GIVEN("an actor pushing into a buffer resource") {
static constexpr size_t num_items = 79;
auto [rd, wr] = async::make_spsc_buffer_resource<int32_t>(8, 2);
sys.spawn([wr{wr}](event_based_actor* self) {
self->make_observable().repeat(42).take(num_items).subscribe(wr);
});
WHEN("draining the buffer resource and sending its items over a socket") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
if (auto err = nonblocking(fd1, true))
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();
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the reader receives all items before the connection closes") {
auto remaining = num_items * sizeof(int32_t);
reader rd{fd2, remaining};
while (!rd.done()) {
if (auto new_val = rd.remaining(); remaining != new_val) {
remaining = new_val;
MESSAGE("want " << remaining << " more bytes");
}
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(rd.buf().size(), num_items * sizeof(int32_t));
CHECK_EQ(app.written_bytes, rd.buf());
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.http.server
#include "caf/net/http/server.hpp"
#include "net-test.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
namespace {
struct app_t {
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;
}
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;
}
string_view field(string_view key) {
if (auto i = hdr.fields().find(key); i != hdr.fields().end())
return i->second;
else
return {};
}
string_view param(string_view key) {
auto& qm = hdr.query();
if (auto i = qm.find(key); i != qm.end())
return i->second;
else
return {};
}
};
using mock_server_type = mock_stream_transport<net::http::server<app_t>>;
} // namespace
BEGIN_FIXTURE_SCOPE(host_fixture)
SCENARIO("the server parses HTTP GET requests into header fields") {
GIVEN("valid HTTP GET request") {
string_view req = "GET /foo/bar?user=foo&pw=bar HTTP/1.1\r\n"
"Host: localhost:8090\r\n"
"User-Agent: AwesomeLib/1.0\r\n"
"Accept-Encoding: gzip\r\n\r\n";
string_view res = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 12\r\n"
"\r\n"
"Hello world!";
WHEN("sending it to an HTTP server") {
mock_server_type serv;
CHECK_EQ(serv.init(), 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(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");
}
AND("the server properly formats a response from the application layer") {
CHECK_EQ(serv.output_as_str(), res);
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.length_prefix_framing
#include "caf/net/length_prefix_framing.hpp"
#include "net-test.hpp"
#include <cctype>
#include <numeric>
#include <vector>
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/byte_span.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/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;
namespace {
using string_list = std::vector<std::string>;
template <bool EnableSuspend>
struct app {
using input_tag = tag::message_oriented;
template <class LowerLayerPtr>
caf::error init(net::socket_manager*, LowerLayerPtr, const settings&) {
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error&) {
// nop
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf) {
auto printable = [](byte x) { return ::isprint(static_cast<uint8_t>(x)); };
if (CHECK(std::all_of(buf.begin(), buf.end(), printable))) {
auto str_buf = reinterpret_cast<char*>(buf.data());
inputs.emplace_back(std::string{str_buf, buf.size()});
if constexpr (EnableSuspend)
if (inputs.back() == "pause")
down->suspend_reading();
std::string response = "ok ";
response += std::to_string(inputs.size());
auto response_bytes = as_bytes(make_span(response));
down->begin_message();
auto& buf = down->message_buffer();
buf.insert(buf.end(), response_bytes.begin(), response_bytes.end());
CHECK(down->end_message());
return static_cast<ptrdiff_t>(buf.size());
} else {
return -1;
}
}
std::vector<std::string> inputs;
};
void encode(byte_buffer& buf, string_view msg) {
using detail::to_network_order;
auto prefix = to_network_order(static_cast<uint32_t>(msg.size()));
auto prefix_bytes = as_bytes(make_span(&prefix, 1));
buf.insert(buf.end(), prefix_bytes.begin(), prefix_bytes.end());
auto bytes = as_bytes(make_span(msg));
buf.insert(buf.end(), bytes.begin(), bytes.end());
}
auto decode(byte_buffer& buf) {
auto printable = [](byte x) { return ::isprint(static_cast<uint8_t>(x)); };
string_list result;
auto input = make_span(buf);
while (!input.empty()) {
auto [msg_size, msg] = net::length_prefix_framing<app<false>>::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)) {
CAF_FAIL("cannot decode buffer: unprintable characters found in message");
} else {
auto str = std::string{reinterpret_cast<char*>(msg.data()), msg_size};
result.emplace_back(std::move(str));
input = msg.subspan(msg_size);
}
}
return result;
}
} // namespace
BEGIN_FIXTURE_SCOPE(host_fixture)
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{});
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");
}
auto response = string_view{reinterpret_cast<char*>(uut.output.data()),
uut.output.size()};
CHECK_EQ(decode(uut.output), string_list({"ok 1", "ok 2"}));
}
}
}
}
SCENARIO("calling suspend_reading removes message apps temporarily") {
using namespace std::literals;
GIVEN("a length_prefix_framing with an app that consumes strings") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer = std::thread{[fd1{fd1}] {
auto guard = make_socket_guard(fd1);
std::vector<std::string_view> inputs{"first", "second", "pause", "third",
"fourth"};
byte_buffer wr_buf;
byte_buffer rd_buf;
rd_buf.resize(512);
for (auto input : inputs) {
wr_buf.clear();
encode(wr_buf, input);
write(fd1, wr_buf);
read(fd1, rd_buf);
}
}};
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
mpx.apply_updates();
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);
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");
}
THEN("users can resume it via continue_reading ") {
mgr->continue_reading();
mpx.apply_updates();
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");
}
}
}
writer.join();
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.openssl_transport
#include "caf/net/openssl_transport.hpp"
#include "net-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/span.hpp"
#include <filesystem>
#include <random>
// Note: these constants are defined in openssl_transport_constants.cpp.
extern std::string_view ca_pem;
extern std::string_view cert_1_pem;
extern std::string_view cert_2_pem;
extern std::string_view key_1_enc_pem;
extern std::string_view key_1_pem;
extern std::string_view key_2_pem;
using namespace caf;
using namespace caf::net;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
struct fixture : host_fixture {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture() {
multiplexer::block_sigpipe();
OPENSSL_init_ssl(OPENSSL_INIT_SSL_DEFAULT, nullptr);
// Make a directory name with 8 random (hex) character suffix.
std::string dir_name = "caf-net-test-";
std::random_device rd;
std::minstd_rand rng{rd()};
std::uniform_int_distribution<int> dist(0, 255);
for (int i = 0; i < 4; ++i)
detail::append_hex(dir_name, static_cast<uint8_t>(dist(rng)));
// Create the directory under /tmp (or its equivalent on non-POSIX).
namespace fs = std::filesystem;
tmp_dir = fs::temp_directory_path() / dir_name;
if (!fs::create_directory(tmp_dir)) {
std::cerr << "*** failed to create " << tmp_dir.string() << "\n";
abort();
}
// Create the .pem files on disk.
write_file("ca.pem", ca_pem);
write_file("cert.1.pem", cert_1_pem);
write_file("cert.2.pem", cert_1_pem);
write_file("key.1.enc.pem", key_1_enc_pem);
write_file("key.1.pem", key_1_pem);
write_file("key.2.pem", key_1_pem);
}
template <class SocketPair>
SocketPair no_sigpipe(SocketPair pair) {
auto [first, second] = pair;
if (auto err = allow_sigpipe(first, false))
FAIL("allow_sigpipe failed: " << err);
if (auto err = allow_sigpipe(second, false))
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 {
public:
using input_tag = tag::stream_oriented;
explicit dummy_app(std::shared_ptr<bool> done, byte_buffer_ptr recv_buf)
: done_(std::move(done)), recv_buf_(std::move(recv_buf)) {
// nop
}
~dummy_app() {
*done_ = true;
}
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
MESSAGE("initialize dummy app");
parent->configure_read(receive_policy::exactly(4));
parent->begin_output();
auto& buf = parent->output_buffer();
caf::binary_serializer out{nullptr, buf};
static_cast<void>(out.apply(10));
parent->end_output();
return none;
}
template <class ParentPtr>
bool prepare_send(ParentPtr) {
return true;
}
template <class ParentPtr>
bool done_sending(ParentPtr) {
return true;
}
template <class ParentPtr>
void continue_reading(ParentPtr) {
// nop
}
template <class ParentPtr>
size_t consume(ParentPtr down, span<const byte> data, span<const byte>) {
MESSAGE("dummy app received " << data.size() << " bytes");
// Store the received bytes.
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
// Respond with the same data and return.
down->begin_output();
auto& out = down->output_buffer();
out.insert(out.end(), data.begin(), data.end());
down->end_output();
return static_cast<ptrdiff_t>(data.size());
}
template <class ParentPtr>
void abort(ParentPtr, const error& reason) {
MESSAGE("dummy_app::abort called: " << reason);
*done_ = true;
}
private:
std::shared_ptr<bool> done_;
byte_buffer_ptr recv_buf_;
};
// Simulates a remote SSL server.
void dummy_tls_server(stream_socket fd, std::string cert_file,
std::string key_file) {
namespace ssl = caf::net::openssl;
multiplexer::block_sigpipe();
// Make sure we close our socket.
auto guard = detail::make_scope_guard([fd] { close(fd); });
// Get and configure our SSL context.
auto ctx = ssl::make_ctx(TLS_server_method());
if (auto err = ssl::certificate_pem_file(ctx, cert_file)) {
std::cerr << "*** certificate_pem_file failed: " << ssl::fetch_error_str();
return;
}
if (auto err = ssl::private_key_pem_file(ctx, key_file)) {
std::cerr << "*** private_key_pem_file failed: " << ssl::fetch_error_str();
return;
}
// Perform SSL handshake.
auto f = net::openssl::policy::make(std::move(ctx), fd);
if (f.accept(fd) <= 0) {
std::cerr << "*** accept failed: " << ssl::fetch_error_str();
return;
}
// Do some ping-pong messaging.
for (int i = 0; i < 4; ++i) {
byte_buffer buf;
buf.resize(4);
f.read(fd, buf);
f.write(fd, buf);
}
// Graceful shutdown.
f.notify_close();
}
// Simulates a remote SSL client.
void dummy_tls_client(stream_socket fd) {
multiplexer::block_sigpipe();
// Make sure we close our socket.
auto guard = detail::make_scope_guard([fd] { close(fd); });
// Perform SSL handshake.
auto f = net::openssl::policy::make(TLS_client_method(), fd);
if (f.connect(fd) <= 0) {
ERR_print_errors_fp(stderr);
return;
}
// Do some ping-pong messaging.
for (int i = 0; i < 4; ++i) {
byte_buffer buf;
buf.resize(4);
f.read(fd, buf);
f.write(fd, buf);
}
// Graceful shutdown.
f.notify_close();
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("openssl::async_connect performs the client handshake") {
GIVEN("a connection to a TLS server") {
auto [serv_fd, client_fd] = 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>;
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto done = std::make_shared<bool>(false);
auto buf = std::make_shared<byte_buffer>();
auto make_manager = [done, buf](stream_socket fd, multiplexer* ptr,
net::openssl::policy policy) {
return make_socket_manager<stack_t>(fd, ptr, std::move(policy), done,
buf);
};
auto on_connect_error = [](const error& reason) {
FAIL("connect failed: " << reason);
};
net::openssl::async_connect(client_fd, &mpx,
net::openssl::policy::make(SSLv23_method(),
client_fd),
make_manager, on_connect_error);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
int32_t value = 0;
static_cast<void>(src.apply(value));
CHECK_EQ(value, 10);
}
}
}
}
server.join();
}
}
SCENARIO("openssl::async_accept performs the server handshake") {
GIVEN("a socket that is connected to a client") {
auto [serv_fd, client_fd] = no_sigpipe(unbox(make_stream_socket_pair()));
if (auto err = net::nonblocking(serv_fd, true))
FAIL("net::nonblocking failed: " << err);
std::thread client{dummy_tls_client, client_fd};
WHEN("acting as the OpenSSL server") {
THEN("openssl::async_accept transparently calls SSL_accept") {
using stack_t = openssl_transport<dummy_app>;
net::multiplexer mpx{nullptr};
mpx.set_thread_id();
auto done = std::make_shared<bool>(false);
auto buf = std::make_shared<byte_buffer>();
auto make_manager = [done, buf](stream_socket fd, multiplexer* ptr,
net::openssl::policy policy) {
return make_socket_manager<stack_t>(fd, ptr, std::move(policy), done,
buf);
};
auto on_accept_error = [](const error& reason) {
FAIL("accept failed: " << reason);
};
auto ssl = net::openssl::policy::make(TLS_server_method(), serv_fd);
if (auto err = ssl.certificate_pem_file(abs_path("cert.1.pem")))
FAIL("certificate_pem_file failed: " << err);
if (auto err = ssl.private_key_pem_file(abs_path("key.1.pem")))
FAIL("privat_key_pem_file failed: " << err);
net::openssl::async_accept(serv_fd, &mpx, std::move(ssl), make_manager,
on_accept_error);
mpx.apply_updates();
while (!*done)
mpx.poll_once(true);
if (CHECK_EQ(buf->size(), 16u)) { // 4x 32-bit integers
caf::binary_deserializer src{nullptr, *buf};
for (int i = 0; i < 4; ++i) {
int32_t value = 0;
static_cast<void>(src.apply(value));
CHECK_EQ(value, 10);
}
}
}
}
client.join();
}
}
END_FIXTURE_SCOPE()
#include <string_view>
std::string_view ca_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDmzCCAoOgAwIBAgIJAPLZ3e3WR0LLMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV\n"
"BAYTAlVTMQswCQYDVQQIDAJDQTERMA8GA1UEBwwIQmVya2VsZXkxIzAhBgNVBAoM\n"
"GkFDTUUgU2lnbmluZyBBdXRob3JpdHkgSW5jMRAwDgYDVQQDDAdmb28uYmFyMB4X\n"
"DTE3MDQyMTIzMjM0OFoXDTQyMDQyMTIzMjM0OFowZDELMAkGA1UEBhMCVVMxCzAJ\n"
"BgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEjMCEGA1UECgwaQUNNRSBTaWdu\n"
"aW5nIEF1dGhvcml0eSBJbmMxEDAOBgNVBAMMB2Zvby5iYXIwggEiMA0GCSqGSIb3\n"
"DQEBAQUAA4IBDwAwggEKAoIBAQC6ah79JvrN3LtcPzc9bX5THdzfidWncSmowotG\n"
"SZA3gcIhlsYD3P3RCaUR9g+f2Z/l0l7ciKgWetpNtN9hRBbg5/9tFzSpCb/Y0SSG\n"
"mwtHHovEqN2MWV+Od/MUcYSlL6MmPjSDc8Ls5NSniTr9OBE9J1jm72AsuzHasjPQ\n"
"D84TlWeTSs0HW3H5VxDb15xWYFnmgBo0JylDWj0+VWI+G41Xr7Ubu9699lWSFYF9\n"
"FCtdjzM5e1CGZOMvqUbUBus38BhUAdQ4fE7Dwnn8seKh+7HpJ70omIgqG87e4DBo\n"
"HbnMAkZaekk8+LBl0Hfu8c66Utw9mNoMIlFf/AMlJyLDIpNxAgMBAAGjUDBOMB0G\n"
"A1UdDgQWBBRc6Cbyshtny6jFWZtd/cEUUfMQ3DAfBgNVHSMEGDAWgBRc6Cbyshtn\n"
"y6jFWZtd/cEUUfMQ3DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCY\n"
"numHau9XYH5h4R2CoMdnKPMGk6V7UZZdbidLLcE4roQrYhnBdyhT69b/ySJK2Ee4\n"
"mt8T+E0wcg3k8Pr3aJEJA8eYYaJTqZvvv+TwuMBPjmE2rYSIpgMZv2tRD3XWMaQu\n"
"duLbwkclfejQHDD26xNXsxuU+WNB5kuvtNAg0oKFyFdNKElLQEcjyYzfxmCF4YX5\n"
"WmElijr1Tzuzd59rWPqC/tVIsh42vQ+P6g8Y1PDmo8eTUFveZ+wcr/eEPW6IOMrg\n"
"OW7tATcrgzNuXZ1umiuGgAPuIVqPfr9ssZHBqi9UOK9L/8MQrnOxecNUpPohcTFR\n"
"vq+Zqu15QV9T4BVWKHv0\n"
"-----END CERTIFICATE-----\n";
std::string_view cert_1_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDOjCCAiICCQDz7oMOR7Wm7jANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV\n"
"UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F\n"
"IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0\n"
"MjEyMzI2MzhaGA80NzU1MDMxOTIzMjYzOFowWDELMAkGA1UEBhMCVVMxCzAJBgNV\n"
"BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl\n"
"MRIwEAYDVQQDDAkxLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQDHobccAQQqbZANdOdx852W/nUzGcwpurOi8zbh9yCxMwnFMogW9AsqKEnd\n"
"sypV6Ah/cIz45PAgCdEg+1pc2DG7+E0+QlV4ChNwCDuk+FSWB6pqMTCdZcLeIwlA\n"
"GPp6Ow9v40dW7IFpDetFKXEo6kqEzR5P58Q0a6KpCtpsSMqhk57Py83wB9gPA1vp\n"
"s77kN7D5CI3oay86TA5j5nfFMT1X/77Hs24csW6CLnW/OD4f1RK79UgPd/kpPKQ1\n"
"jNq+hsR7NZTcfrAF1hcfScxnKaznO7WopSt1k75NqLdnSN1GIci2GpiXYKtXZ9l5\n"
"TErv2Oucpw/u+a/wjKlXjrgLL9lfAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKuW\n"
"yKA2uuiNc9MKU+yVbNaP8kPaMb/wMvVaFG8FFFpCTZ0MFMLsqRpeqtj7gMK/gaJC\n"
"CQm4EyadjzfWFYDLkHzm6b7gI8digvvhjr/C2RJ5Qxr2P0iFP1buGq0CqnF20XgQ\n"
"Q+ecS43CZ77CfKfS6ZLPmAZMAwgFLImVyo5mkaTECo3+9oCnjDYBapvXLJqCJRhk\n"
"NosoTmGCV0HecWN4l38ojnXd44aSktQIND9iCLus3S6++nFnX5DHGZiv6/SnSO/6\n"
"+Op7nV0A6zKVcMOYQ0SGZPD8UQs5wDJgrR9LY29Ox5QBwu/5NqyvNSrMQaTop5vb\n"
"wkMInaq5lLxEYQDSLBc=\n"
"-----END CERTIFICATE-----\n";
std::string_view cert_2_pem
= "-----BEGIN CERTIFICATE-----\n"
"MIIDOjCCAiICCQDz7oMOR7Wm7TANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV\n"
"UzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCEJlcmtlbGV5MSMwIQYDVQQKDBpBQ01F\n"
"IFNpZ25pbmcgQXV0aG9yaXR5IEluYzEQMA4GA1UEAwwHZm9vLmJhcjAgFw0xNzA0\n"
"MjEyMzI2MzNaGA80NzU1MDMxOTIzMjYzM1owWDELMAkGA1UEBhMCVVMxCzAJBgNV\n"
"BAgMAkNBMREwDwYDVQQHDAhCZXJrZWxleTEVMBMGA1UECgwMQUNNRSBTZXJ2aWNl\n"
"MRIwEAYDVQQDDAkyLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
"AoIBAQDG9fAvW9qnhjGRmLpA++RvOHaesu7NiUQvxf2F6gF2rLJV0/+DSA/PztEv\n"
"1WJaGhgJSaEqUjaHk3HY2EKlbGXEPh1mxqgPZD5plGlu4ddTwutxCxxQiFIBH+3N\n"
"MYRjJvDN7ozJoi4uRiK0QQdDWAqWJs5hMOJqeWd6MCgmVXSP6pj5/omGROktbHzD\n"
"9jJhAW9fnYFg6k+7cGN5kLmjqqnGhJkNtgom6uW9j73S9OpU/9Er2aZme6/PrujI\n"
"qYFBV81TJK2vmonWUITxfQjk9JVJYhBdHamGTxUqVBbuRcbAqdImV9yx4LoGh55u\n"
"L6xnsW4i0n1o1k+bh03NgwPz12O3AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJmN\n"
"yCdInFIeEwomE6m+Su82BWBzkztOfMG9iRE+1aGuC8EQ8kju5NNMmWQcuKetNh0s\n"
"hJVdY6LXh27O0ZUllhQ/ig9c+dYFh6AHoZU7WjiNKIyWuyl4IAOkQ4IEdsBvst+l\n"
"0rafcdJjUpqNOMWeyg6x1s+gUD5o+ZLCZGCdkCW3fZbKgF52L+vmsSRiJg2JkYZW\n"
"8BPNNsroHZw2UXnLvRqUXCMf1hnOrlx/B0a0Q46hD4NQvl+OzlKaxfR2L2USmJ8M\n"
"XZvT6+i8fWvkGv18iunm23Yu+8Zf08wTXnbqXvmMda5upAYLmwD0YKIVYC3ycihh\n"
"mkYCYI6PVeH63a2/zxw=\n"
"-----END CERTIFICATE-----\n";
std::string_view key_1_enc_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"Proc-Type: 4,ENCRYPTED\n"
"DEK-Info: DES-EDE3-CBC,4FEF2D042C3D3463\n"
"\n"
"S3uHh5KH2Vsxzhw+B8zKsZNUG7CjMAvYfZwUptDnCms2dqxdfyzioxiRVNTN2MBb\n"
"nFxMKZayl5Xjr+04LhUpCAKdmvTfqGmWRFVSa8ol1hvqN/Vmg3a11B+3MD9pHXKu\n"
"ipgHMDTmE34DsDun6cXZ8W2k9ZSSJ20Y7qhcb4lTYUZuWq+94+Bup684COZytbyG\n"
"Eb6tOvVs/N6KeFhvd4vBL4YgTR3aY/vu1cHQQJeHvSYFtGKTKr7qpsfFfDFB7ExM\n"
"bqt2qbiUq1s+uZ0hlDjs+/TCZpC9Wuqpl9m05FrZhboubBRK9pw3wRVNvrpfwunH\n"
"2/E4Ta8/YaxypUwtk4saiPofKcJwvYx3Te38OHKKXgVKlein1p1e6eJNZt3qJaPd\n"
"BEBAR11iWstE+ghPS2WmrwT17cDqNA8wG5CRM9unYVWRMtO8Nl98OYars0vAXOgd\n"
"TI8bHDx6OHm2sJnkc56HAfu6hMdTbKzZXsWyOqLXUU79ioNmTvbMQUO0F+0VKDrv\n"
"3LikXewv69aOhhk+M96ng8KpWUW4z2kbDReiFuWQfjtiFNyEdS9wQIElpq5gU98r\n"
"EAeZoT6s8O0fkE/tt374ZJREEe9YEkMFcv06c96GPkMKnScncOFKRa1w0AblGxOM\n"
"vjICMLIk3CesFNWNzJk4MQu5ZVerJsz27UFocjNUvT6iM10rDCeU55W71bIPWRSb\n"
"SZL3JUi1gOeORrv3jAEamOd6YRlvPRYQjpDENg/qnx+bdXp5S3qHeHFSkRRHBCfy\n"
"UcYUba5hZJVDyBJXC4dkc2Ftcna149PZP1aKe9r//ZGa25PVZl1Mg6HhLROSJrEN\n"
"D2lqPMYcBDuSiZ6QK5ZHh2gsXJPktDgMhw4ZNIfIIqIpMDJ7rV4tQXXwB3d+iZte\n"
"/Ib/y1Mn8fZOo448nBqagpNFvU70eg7WkA4rzgiS3VzFYosP3g48PlZ07bEZ0awy\n"
"9lMGE0OKJv6x8EkVY0xHDxujzD5WVFEkanpGo8bWl1ofBrsnnDsFrvvRkDRsQbrG\n"
"WZkRqn7zLaGKOUirfKjfktajvPOAKMsvbl9KXAfp88sOtTZuYppCivZ/2qnzQ8xg\n"
"9/fuGfpAPXYfUu44+7dhrbvxJzrPp+GQRLUb1e3KA2MLgVak+yV1TIKdDGYek5vM\n"
"xDEmLLgOQqsE/OAcdXD2b/qIVYEmFPdut6N9K0m65AoqrN5nY6r0BzuC7usCgxnu\n"
"K6Z7Y2L6Ax4DjnKt21RRcjIVLLpea7KXnMiTogCYJ7+zmD82fE3i1xCDQs7R9b4T\n"
"tZhnkGfQS2RjOp3WaiV5rcmOKXDK3kYE+ezD0wbiVFDgB2jNdR7duOFqzW/WuPac\n"
"hsIPYn7VmjLB3W4p7PBf7OQ1fIiQ6IofeMvu8I8IOVRfC7eYDCJ0QKsHI8uA6j9T\n"
"vOCfjsZcpjygtN1Xy3uXIEZUVJJq/DF5t2Lxn95wDG9VfstJQR3KuDbPwYAWOYCh\n"
"W6gYu6HftIr3OlIYKurtSUFM4+iargMUaBVOUQ4XWLN8LzoyHNL3fsiGKn1afBV/\n"
"J3Qaqrn/YBGCAc2SOUCRl3F8yfWjmidXy6rQIzrjpF8aX2QSflB+fw==\n"
"-----END RSA PRIVATE KEY-----\n";
std::string_view key_1_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEogIBAAKCAQEAx6G3HAEEKm2QDXTncfOdlv51MxnMKbqzovM24fcgsTMJxTKI\n"
"FvQLKihJ3bMqVegIf3CM+OTwIAnRIPtaXNgxu/hNPkJVeAoTcAg7pPhUlgeqajEw\n"
"nWXC3iMJQBj6ejsPb+NHVuyBaQ3rRSlxKOpKhM0eT+fENGuiqQrabEjKoZOez8vN\n"
"8AfYDwNb6bO+5Dew+QiN6GsvOkwOY+Z3xTE9V/++x7NuHLFugi51vzg+H9USu/VI\n"
"D3f5KTykNYzavobEezWU3H6wBdYXH0nMZyms5zu1qKUrdZO+Tai3Z0jdRiHIthqY\n"
"l2CrV2fZeUxK79jrnKcP7vmv8IypV464Cy/ZXwIDAQABAoIBAC0Y7jmoTR2clJ9F\n"
"modWhnI215kMqd9/atdT5EEVx8/f/MQMj0vII8GJSm6H6/duLIVFksMjTM+gCBtQ\n"
"TPCOcmXJSQHYkGBGvm9fnMG+y7T81FWa+SWFeIkgFxXgzqzQLMOU72fGk9F8sHp2\n"
"Szb3/o+TmtZoQB2rdxqC9ibiJsxrG5IBVKkzlSPv3POkPXwSb1HcETqrTwefuioj\n"
"WMuMrqtm5Y3HddJ5l4JEF5VA3KrsfXWl3JLHH0UViemVahiNjXQAVTKAXIL1PHAV\n"
"J2MCEvlpA7sIgXREbmvPvZUTkt3pIqhVjZVJ7tHiSnSecqNTbuxcocnhKhZrHNtC\n"
"v2zYKHkCgYEA6cAIhz0qOGDycZ1lf9RSWw0RO1hO8frATMQNVoFVuJJCVL22u96u\n"
"0FvJ0JGyYbjthULnlOKyRe7DUL5HRLVS4D7vvKCrgwDmsJp1VFxMdASUdaBfq6aX\n"
"oKLUW4q7kC2lQcmK/PVRYwp2GQSx8bodWe+DtXUY/GcN03znY8mhSB0CgYEA2qJK\n"
"1GSZsm6kFbDek3BiMMHfO+X819owB2FmXiH+GQckyIZu9xA3HWrkOWTqwglEvzfO\n"
"qzFF96E9iEEtseAxhcM8gPvfFuXiUj9t2nH/7SzMnVGikhtYi0p6jrgHmscc4NBx\n"
"AOUA15kYEFOGqpZfl2uuKqgHidrHdGkJzzSUBqsCgYAVCjb6TVQejQNlnKBFOExN\n"
"a8iwScuZVlO21TLKJYwct/WGgSkQkgO0N37b6jFfQHEIvLPxn9IiH1KvUuFBWvzh\n"
"uGiF1wR5HzykitKizEgJbVwbllrmLXGagO2Sa9NkL+efG1AKYt53hrqIl/aYZoM7\n"
"1CZL0AV2uqPw9F4zijOdNQKBgH1WmvWGMsKjQTgaLI9z1ybCjjqlj70jHXOtt+Tx\n"
"Md2hRcobn5PN3PrlY68vlpHkhF/nG3jzB3x+GGt7ijm2IE3h7la3jl5vLb8fE9gu\n"
"kJykmSz7Nurx+GHqMbaN8/Ycfga4GIB9yGzRHIWHjOVQzb5eAfv8Vk4GeV/YM8Jx\n"
"Dwd/AoGAILn8pVC9dIFac2BDOFU5y9ZvMmZAvwRxh9vEWewNvkzg27vdYc+rCHNm\n"
"I7H0S/RqfqVeo0ApE5PQ8Sll6RvxN/mbSQo9YeCDGQ1r1rNe4Vs12GAYXAbE4ipf\n"
"BTdqMbieumB/zL97iK5baHUFEJ4VRtLQhh/SOXgew/BF8ccpilI=\n"
"-----END RSA PRIVATE KEY-----\n";
std::string_view key_2_pem
= "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpQIBAAKCAQEAxvXwL1vap4YxkZi6QPvkbzh2nrLuzYlEL8X9heoBdqyyVdP/\n"
"g0gPz87RL9ViWhoYCUmhKlI2h5Nx2NhCpWxlxD4dZsaoD2Q+aZRpbuHXU8LrcQsc\n"
"UIhSAR/tzTGEYybwze6MyaIuLkYitEEHQ1gKlibOYTDianlnejAoJlV0j+qY+f6J\n"
"hkTpLWx8w/YyYQFvX52BYOpPu3BjeZC5o6qpxoSZDbYKJurlvY+90vTqVP/RK9mm\n"
"Znuvz67oyKmBQVfNUyStr5qJ1lCE8X0I5PSVSWIQXR2phk8VKlQW7kXGwKnSJlfc\n"
"seC6Boeebi+sZ7FuItJ9aNZPm4dNzYMD89djtwIDAQABAoIBAQDDaWquGRl40GR/\n"
"C/JjQQPr+RkIZdYGKXu/MEcA8ATf+l5tzfp3hp+BCzCKOpqOxHI3LQoN9xF3t2lq\n"
"AX3z27NYO2nFN/h4pYxnRk0Hiulia1+zd6YnsrxYPnPhxXCxsd1xZYsBvzh8WoZb\n"
"ZEMt8Zr0PskUzF6VFQh9Ci9k9ym07ooo/KqP4wjXsm/JK1ueOCTpRtabrBI1icrV\n"
"iTaw1JEGqlTAQ92vg3pXqSG5yy69Krt7miZZtiOA5mJ90VrHtlNSgp31AOcVv/Ve\n"
"/LMIwJp9EzTN+4ipT7AKPeJAoeVqpFjQk+2cW44zJ7xyzw73pTs5ErxkEIhQOp4M\n"
"ak2iMg4BAoGBAOivDZSaOcTxEB3oKxYvN/jL9eU2Io9wdZwAZdYQpkgc8lkM9elW\n"
"2rbHIwifkDxQnZbl3rXM8xmjA4c5PSCUYdPnLvx6nsUJrWTG0RjakHRliSLthNEC\n"
"LpL9MR1aQblyz1D/ulWTFOCNvHU7m3XI3RVJEQWu3qQ5pCndzT56wXjnAoGBANrl\n"
"zKvR9o2SONU8SDIcMzXrO2647Z8yXn4Kz1WhWojhRQQ1V3VOLm8gBwv8bPtc7LmE\n"
"MSX5MIcxRoHu7D98d53hd+K/ZGYV2h/638qaIEgZDf2oa8QylBgvoGljoy1DH8nN\n"
"KKOgksqWK0AAEkP0+S4IFugTxHVanw8JUkV0gVSxAoGBANIRUGJrxmHt/M3zUArs\n"
"QE0G3o28DQGQ1y0rEsVrLKQINid9UvoBpt3C9PcRD2fUpCGakDFzwbnQeRv46h3i\n"
"uFtV6Q6aKYLcFMXZ1ObqU+Yx0NhOtUz4+lFL8q58UL/7Tf3jkjc13XBJpe31DYoN\n"
"+MMBvzNxR6HeRD5j96tDqi3bAoGAT57SqZS/l5MeNQGuSPvU7MHZZlbBp+xMTpBk\n"
"BgOgyLUXw4Ybf8GmRiliJsv0YCHWwUwCDIvtSN91hAGB0T3WzIiccM+pFzDPnF5G\n"
"VI1nPJJQcnl2aXD0SS/ZqzvguK/3uhFzvMDFZAbnSGo+OpW6pTGwE05NYVpLDM8Z\n"
"K8ZK3KECgYEApNoI5Mr5tmtjq4sbZrgQq6cMlfkIj9gUubOzFCryUb6NaB38Xqkp\n"
"2N3/jqdkR+5ZiKOYhsYj+Iy6U3jyqiEl9VySYTfEIfP/ky1CD0a8/EVC9HR4iG8J\n"
"im6G7/osaSBYAZctryLqVJXObTelgEy/EFwW9jW8HVph/G+ljmHOmuQ=\n"
"-----END RSA PRIVATE KEY-----\n";
#define CAF_SUITE net.operation
#include "caf/net/operation.hpp"
#include "net-test.hpp"
using namespace caf;
using namespace caf::net;
SCENARIO("add_read_flag adds the read bit unless block_read prevents it") {
GIVEN("a valid operation enum value") {
WHEN("calling add_read_flag") {
THEN("the read bit is set unless the block_read flag is present") {
auto add_rd = [](auto x) { return add_read_flag(x); };
CHECK_EQ(add_rd(operation::none), operation::read);
CHECK_EQ(add_rd(operation::read), operation::read);
CHECK_EQ(add_rd(operation::write), operation::read_write);
CHECK_EQ(add_rd(operation::block_read), operation::block_read);
CHECK_EQ(add_rd(operation::block_write), operation::read_only);
CHECK_EQ(add_rd(operation::read_write), operation::read_write);
CHECK_EQ(add_rd(operation::read_only), operation::read_only);
CHECK_EQ(add_rd(operation::write_only), operation::write_only);
CHECK_EQ(add_rd(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("add_write_flag adds the write bit unless block_write prevents it") {
GIVEN("a valid operation enum value") {
WHEN("calling add_write_flag") {
THEN("the write bit is set unless the block_write flag is present") {
auto add_wr = [](auto x) { return add_write_flag(x); };
CHECK_EQ(add_wr(operation::none), operation::write);
CHECK_EQ(add_wr(operation::read), operation::read_write);
CHECK_EQ(add_wr(operation::write), operation::write);
CHECK_EQ(add_wr(operation::block_read), operation::write_only);
CHECK_EQ(add_wr(operation::block_write), operation::block_write);
CHECK_EQ(add_wr(operation::read_write), operation::read_write);
CHECK_EQ(add_wr(operation::read_only), operation::read_only);
CHECK_EQ(add_wr(operation::write_only), operation::write_only);
CHECK_EQ(add_wr(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("remove_read_flag erases the read flag") {
GIVEN("a valid operation enum value") {
WHEN("calling remove_read_flag") {
THEN("the read bit is removed if present") {
auto del_rd = [](auto x) { return remove_read_flag(x); };
CHECK_EQ(del_rd(operation::none), operation::none);
CHECK_EQ(del_rd(operation::read), operation::none);
CHECK_EQ(del_rd(operation::write), operation::write);
CHECK_EQ(del_rd(operation::block_read), operation::block_read);
CHECK_EQ(del_rd(operation::block_write), operation::block_write);
CHECK_EQ(del_rd(operation::read_write), operation::write);
CHECK_EQ(del_rd(operation::read_only), operation::block_write);
CHECK_EQ(del_rd(operation::write_only), operation::write_only);
CHECK_EQ(del_rd(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("remove_write_flag erases the write flag") {
GIVEN("a valid operation enum value") {
WHEN("calling remove_write_flag") {
THEN("the write bit is removed if present") {
auto del_wr = [](auto x) { return remove_write_flag(x); };
CHECK_EQ(del_wr(operation::none), operation::none);
CHECK_EQ(del_wr(operation::read), operation::read);
CHECK_EQ(del_wr(operation::write), operation::none);
CHECK_EQ(del_wr(operation::block_read), operation::block_read);
CHECK_EQ(del_wr(operation::block_write), operation::block_write);
CHECK_EQ(del_wr(operation::read_write), operation::read);
CHECK_EQ(del_wr(operation::read_only), operation::read_only);
CHECK_EQ(del_wr(operation::write_only), operation::block_read);
CHECK_EQ(del_wr(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("block_reads removes the read flag and sets the block read flag") {
GIVEN("a valid operation enum value") {
WHEN("calling block_reads") {
THEN("the read bit is removed if present and block_read is set") {
auto block_rd = [](auto x) { return block_reads(x); };
CHECK_EQ(block_rd(operation::none), operation::block_read);
CHECK_EQ(block_rd(operation::read), operation::block_read);
CHECK_EQ(block_rd(operation::write), operation::write_only);
CHECK_EQ(block_rd(operation::block_read), operation::block_read);
CHECK_EQ(block_rd(operation::block_write), operation::shutdown);
CHECK_EQ(block_rd(operation::read_write), operation::write_only);
CHECK_EQ(block_rd(operation::read_only), operation::shutdown);
CHECK_EQ(block_rd(operation::write_only), operation::write_only);
CHECK_EQ(block_rd(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("block_writes removes the write flag and sets the block write flag") {
GIVEN("a valid operation enum value") {
WHEN("calling block_writes") {
THEN("the write bit is removed if present and block_write is set") {
auto block_wr = [](auto x) { return block_writes(x); };
CHECK_EQ(block_wr(operation::none), operation::block_write);
CHECK_EQ(block_wr(operation::read), operation::read_only);
CHECK_EQ(block_wr(operation::write), operation::block_write);
CHECK_EQ(block_wr(operation::block_read), operation::shutdown);
CHECK_EQ(block_wr(operation::block_write), operation::block_write);
CHECK_EQ(block_wr(operation::read_write), operation::read_only);
CHECK_EQ(block_wr(operation::read_only), operation::read_only);
CHECK_EQ(block_wr(operation::write_only), operation::shutdown);
CHECK_EQ(block_wr(operation::shutdown), operation::shutdown);
}
}
}
}
SCENARIO("is-predicates check whether certain flags are present") {
GIVEN("a valid operation enum value") {
WHEN("calling is_reading") {
THEN("the predicate returns true if the read bit is present") {
CHECK_EQ(is_reading(operation::none), false);
CHECK_EQ(is_reading(operation::read), true);
CHECK_EQ(is_reading(operation::write), false);
CHECK_EQ(is_reading(operation::block_read), false);
CHECK_EQ(is_reading(operation::block_write), false);
CHECK_EQ(is_reading(operation::read_write), true);
CHECK_EQ(is_reading(operation::read_only), true);
CHECK_EQ(is_reading(operation::write_only), false);
CHECK_EQ(is_reading(operation::shutdown), false);
}
}
WHEN("calling is_writing") {
THEN("the predicate returns true if the write bit is present") {
CHECK_EQ(is_writing(operation::none), false);
CHECK_EQ(is_writing(operation::read), false);
CHECK_EQ(is_writing(operation::write), true);
CHECK_EQ(is_writing(operation::block_read), false);
CHECK_EQ(is_writing(operation::block_write), false);
CHECK_EQ(is_writing(operation::read_write), true);
CHECK_EQ(is_writing(operation::read_only), false);
CHECK_EQ(is_writing(operation::write_only), true);
CHECK_EQ(is_writing(operation::shutdown), false);
}
}
WHEN("calling is_idle") {
THEN("the predicate returns true when neither reading nor writing") {
CHECK_EQ(is_idle(operation::none), true);
CHECK_EQ(is_idle(operation::read), false);
CHECK_EQ(is_idle(operation::write), false);
CHECK_EQ(is_idle(operation::block_read), true);
CHECK_EQ(is_idle(operation::block_write), true);
CHECK_EQ(is_idle(operation::read_write), false);
CHECK_EQ(is_idle(operation::read_only), false);
CHECK_EQ(is_idle(operation::write_only), false);
CHECK_EQ(is_idle(operation::shutdown), true);
}
}
WHEN("calling is_read_blocked") {
THEN("the predicate returns when blocking reads") {
CHECK_EQ(is_read_blocked(operation::none), false);
CHECK_EQ(is_read_blocked(operation::read), false);
CHECK_EQ(is_read_blocked(operation::write), false);
CHECK_EQ(is_read_blocked(operation::block_read), true);
CHECK_EQ(is_read_blocked(operation::block_write), false);
CHECK_EQ(is_read_blocked(operation::read_write), false);
CHECK_EQ(is_read_blocked(operation::read_only), false);
CHECK_EQ(is_read_blocked(operation::write_only), true);
CHECK_EQ(is_read_blocked(operation::shutdown), true);
}
}
WHEN("calling is_write_blocked") {
THEN("the predicate returns when blocking writes") {
CHECK_EQ(is_write_blocked(operation::none), false);
CHECK_EQ(is_write_blocked(operation::read), false);
CHECK_EQ(is_write_blocked(operation::write), false);
CHECK_EQ(is_write_blocked(operation::block_read), false);
CHECK_EQ(is_write_blocked(operation::block_write), true);
CHECK_EQ(is_write_blocked(operation::read_write), false);
CHECK_EQ(is_write_blocked(operation::read_only), true);
CHECK_EQ(is_write_blocked(operation::write_only), false);
CHECK_EQ(is_write_blocked(operation::shutdown), true);
}
}
}
}
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.producer_adapter
#include "caf/net/producer_adapter.hpp"
#include "net-test.hpp"
#include "caf/detail/network_order.hpp"
#include "caf/net/length_prefix_framing.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/scheduled_actor/flow.hpp"
#include "caf/tag/message_oriented.hpp"
using namespace caf;
namespace {
class writer {
public:
explicit writer(net::stream_socket fd) : sg_(fd) {
// nop
}
auto fd() {
return sg_.socket();
}
byte_buffer encode(string_view msg) {
using detail::to_network_order;
auto prefix = to_network_order(static_cast<uint32_t>(msg.size()));
auto prefix_bytes = as_bytes(make_span(&prefix, 1));
byte_buffer buf;
buf.insert(buf.end(), prefix_bytes.begin(), prefix_bytes.end());
auto bytes = as_bytes(make_span(msg));
buf.insert(buf.end(), bytes.begin(), bytes.end());
return buf;
}
void write(string_view msg) {
auto buf = encode(msg);
if (net::write(fd(), buf) < 0)
FAIL("failed to write: " << net::last_socket_error_as_string());
}
private:
net::socket_guard<net::stream_socket> sg_;
};
class app_t {
public:
using input_tag = tag::message_oriented;
using resource_type = async::producer_resource<int32_t>;
using buffer_type = resource_type::buffer_type;
using adapter_ptr = net::producer_adapter_ptr<buffer_type>;
using adapter_type = adapter_ptr::element_type;
explicit app_t(resource_type output) : output_(std::move(output)) {
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr, const settings&) {
if (auto ptr = adapter_type::try_open(mgr, std::move(output_))) {
adapter_ = std::move(ptr);
return none;
} else {
FAIL("unable to open the resource");
}
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
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) {
auto val = int32_t{0};
auto str = string_view{reinterpret_cast<char*>(buf.data()), buf.size()};
if (auto err = detail::parse(str, val))
FAIL("unable to parse input: " << err);
++received_messages;
if (auto capacity_left = adapter_->push(val); capacity_left == 0) {
down->suspend_reading();
}
return static_cast<ptrdiff_t>(buf.size());
}
size_t received_messages = 0;
adapter_ptr adapter_;
resource_type output_;
};
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() : mm(sys) {
if (auto err = mm.mpx().init())
CAF_FAIL("mpx.init() failed: " << err);
}
bool handle_io_event() override {
mm.mpx().apply_updates();
return mm.mpx().poll_once(false);
}
net::middleman mm;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("publisher adapters suspend reads if the buffer becomes full") {
GIVEN("an actor reading from a buffer resource") {
static constexpr size_t num_items = 13;
std::vector<int32_t> outputs;
auto [rd, wr] = async::make_spsc_buffer_resource<int32_t>(8, 2);
sys.spawn([rd{rd}, &outputs](event_based_actor* self) {
self //
->make_observable()
.from_resource(rd)
.for_each([&outputs](int32_t x) { outputs.emplace_back(x); });
});
WHEN("a producer reads from a socket and publishes to the buffer") {
auto [fd1, fd2] = unbox(net::make_stream_socket_pair());
auto writer_thread = std::thread{[fd1{fd1}] {
writer out{fd1};
for (size_t i = 0; i < num_items; ++i)
out.write(std::to_string(i));
}};
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));
if (auto err = mgr->init(content(cfg)))
FAIL("mgr->init() failed: " << err);
THEN("the actor receives all items from the writer (socket)") {
while (outputs.size() < num_items)
run();
auto ls = [](auto... xs) { return std::vector<int32_t>{xs...}; };
CHECK_EQ(outputs, ls(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
}
writer_thread.join();
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.typed_actor_shell
#include "caf/net/typed_actor_shell.hpp"
#include "net-test.hpp"
#include "caf/byte.hpp"
#include "caf/byte_span.hpp"
#include "caf/callback.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/typed_actor.hpp"
using namespace caf;
using namespace std::string_literals;
namespace {
using svec = std::vector<std::string>;
using string_consumer = typed_actor<result<void>(std::string)>;
struct app_t {
using input_tag = tag::stream_oriented;
app_t() = default;
explicit app_t(actor hdl) : worker(std::move(hdl)) {
// nop
}
template <class LowerLayerPtr>
error init(net::socket_manager* mgr, LowerLayerPtr down, const settings&) {
self = mgr->make_actor_shell<string_consumer>(down);
self->set_behavior([this](std::string& line) {
CAF_MESSAGE("received an asynchronous message: " << line);
lines.emplace_back(std::move(line));
});
self->set_fallback([](message& msg) -> result<message> {
CAF_FAIL("unexpected message: " << msg);
return make_error(sec::unexpected_message);
});
down->configure_read(net::receive_policy::up_to(2048));
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr down) {
while (self->consume_message()) {
// We set abort_reason in our response handlers in case of an error.
if (down->abort_reason())
return false;
// else: repeat.
}
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return self->try_block_mailbox();
}
template <class LowerLayerPtr>
void continue_reading(LowerLayerPtr) {
CAF_FAIL("continue_reading called");
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume(LowerLayerPtr down, byte_span buf, byte_span) {
// Seek newline character.
constexpr auto nl = byte{'\n'};
if (auto i = std::find(buf.begin(), buf.end(), nl); i != buf.end()) {
// Skip empty lines.
if (i == buf.begin()) {
consumed_bytes += 1;
auto sub_res = consume(down, buf.subspan(1), {});
return sub_res >= 0 ? sub_res + 1 : sub_res;
}
// Deserialize config value from received line.
auto num_bytes = std::distance(buf.begin(), i) + 1;
string_view line{reinterpret_cast<const char*>(buf.data()),
static_cast<size_t>(num_bytes) - 1};
config_value val;
if (auto parsed_res = config_value::parse(line)) {
val = std::move(*parsed_res);
} else {
down->abort_reason(std::move(parsed_res.error()));
return -1;
}
// 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 {
++received_responses;
// Respond with the value as string.
auto str_response = std::to_string(value);
str_response += '\n';
down->begin_output();
auto& buf = down->output_buffer();
auto bytes = as_bytes(make_span(str_response));
buf.insert(buf.end(), bytes.begin(), bytes.end());
down->end_output();
},
[down](error& err) mutable { down->abort_reason(std::move(err)); });
// Try consuming more from the buffer.
consumed_bytes += static_cast<size_t>(num_bytes);
auto sub_buf = buf.subspan(num_bytes);
auto sub_res = consume(down, sub_buf, {});
return sub_res >= 0 ? num_bytes + sub_res : sub_res;
}
return 0;
}
// Handle to the worker-under-test.
actor worker;
// Lines received as asynchronous messages.
std::vector<std::string> lines;
// Actor shell representing this app.
net::typed_actor_shell_ptr_t<string_consumer> self;
// Counts how many bytes we've consumed in total.
size_t consumed_bytes = 0;
// Counts how many response messages we've received from the worker.
size_t received_responses = 0;
};
struct fixture : host_fixture, test_coordinator_fixture<> {
fixture() : mm(sys), mpx(&mm) {
mpx.set_thread_id();
if (auto err = mpx.init())
CAF_FAIL("mpx.init() failed: " << err);
auto sockets = unbox(net::make_stream_socket_pair());
self_socket_guard.reset(sockets.first);
testee_socket_guard.reset(sockets.second);
if (auto err = nonblocking(self_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
if (auto err = nonblocking(testee_socket_guard.socket(), true))
CAF_FAIL("nonblocking returned an error: " << err);
}
template <class Predicate>
void run_while(Predicate predicate) {
if (!predicate())
return;
for (size_t i = 0; i < 1000; ++i) {
mpx.apply_updates();
mpx.poll_once(false);
byte tmp[1024];
auto bytes = read(self_socket_guard.socket(), make_span(tmp, 1024));
if (bytes > 0)
recv_buf.insert(recv_buf.end(), tmp, tmp + bytes);
if (!predicate())
return;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
CAF_FAIL("reached max repeat rate without meeting the predicate");
}
void send(string_view str) {
auto res = write(self_socket_guard.socket(), as_bytes(make_span(str)));
if (res != static_cast<ptrdiff_t>(str.size()))
CAF_FAIL("expected write() to return " << str.size() << ", got: " << res);
}
net::middleman mm;
net::multiplexer mpx;
net::socket_guard<net::stream_socket> self_socket_guard;
net::socket_guard<net::stream_socket> testee_socket_guard;
std::vector<byte> recv_buf;
};
constexpr std::string_view input = R"__(
[ { "@type" : "int32_t", value: 123 } ]
)__";
} // namespace
CAF_TEST_FIXTURE_SCOPE(actor_shell_tests, fixture)
CAF_TEST(actor shells expose their mailbox to their owners) {
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
auto hdl = app.self.as_actor();
anon_send(hdl, "line 1");
anon_send(hdl, "line 2");
anon_send(hdl, "line 3");
run_while([&] { return app.lines.size() != 3; });
CAF_CHECK_EQUAL(app.lines, svec({"line 1", "line 2", "line 3"}));
}
CAF_TEST(actor shells can send requests and receive responses) {
auto worker = sys.spawn([] {
return behavior{
[](int32_t value) { return value * 2; },
};
});
auto sck = testee_socket_guard.release();
auto mgr = net::make_socket_manager<app_t, net::stream_transport>(sck, &mpx,
worker);
if (auto err = mgr->init(content(cfg)))
CAF_FAIL("mgr->init() failed: " << err);
auto& app = mgr->top_layer();
send(input);
run_while([&] { return app.consumed_bytes != input.size(); });
expect((int32_t), to(worker).with(123));
string_view expected_response = "246\n";
run_while([&] { return recv_buf.size() < expected_response.size(); });
string_view received_response{reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()};
CAF_CHECK_EQUAL(received_response, expected_response);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.web_socket.client
#include "caf/net/web_socket/client.hpp"
#include "net-test.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
namespace {
using svec = std::vector<std::string>;
struct app_t {
std::string text_input;
std::vector<byte> binary_input;
settings cfg;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings& init_cfg) {
cfg = init_cfg;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, string_view text) {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr, byte_span bytes) {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
constexpr auto key = "the sample nonce"_sv;
constexpr auto http_request = "GET /chat HTTP/1.1\r\n"
"Host: server.example.com\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Origin: http://example.com\r\n"
"Sec-WebSocket-Protocol: chat, superchat\r\n"
"\r\n"_sv;
constexpr auto http_response
= "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
"\r\n"_sv;
auto key_to_bytes() {
net::web_socket::handshake::key_type bytes;
auto in = as_bytes(make_span(key));
std::copy(in.begin(), in.end(), bytes.begin());
return 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");
return result;
}
using mock_client_type = mock_stream_transport<net::web_socket::client<app_t>>;
} // namespace
BEGIN_FIXTURE_SCOPE(host_fixture)
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()};
THEN("the client sends its HTTP request when initializing it") {
CHECK_EQ(client.init(), error{});
CHECK_EQ(client.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(),
static_cast<ptrdiff_t>(http_response.size()));
CHECK(client.upper_layer.handshake_complete());
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.web_socket.flow
#include "caf/net/web_socket/flow.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
namespace {
struct fixture {};
} // namespace
CAF_TEST_FIXTURE_SCOPE(flow_tests, fixture)
CAF_TEST(todo) {
// implement me
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.web_socket.handshake
#include "caf/net/web_socket/handshake.hpp"
#include "net-test.hpp"
#include <algorithm>
using namespace caf::literals;
using namespace caf::net;
using namespace caf;
namespace {
constexpr auto key = "the sample nonce"_sv;
constexpr auto http_request = "GET /chat HTTP/1.1\r\n"
"Host: server.example.com\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Origin: http://example.com\r\n"
"Sec-WebSocket-Protocol: chat, superchat\r\n"
"\r\n"_sv;
constexpr auto http_response
= "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
"\r\n"_sv;
struct fixture {
byte_buffer& buf() {
return bytes;
}
string_view str() {
return {reinterpret_cast<char*>(bytes.data()), bytes.size()};
}
auto key_to_bytes() {
web_socket::handshake::key_type bytes;
auto in = as_bytes(make_span(key));
std::copy(in.begin(), in.end(), bytes.begin());
return bytes;
}
byte_buffer bytes;
};
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
SCENARIO("handshake generates HTTP GET requests according to RFC 6455") {
GIVEN("a request header object with endpoint, origin and protocol") {
web_socket::handshake uut;
uut.endpoint("/chat");
uut.host("server.example.com");
uut.key(key_to_bytes());
uut.origin("http://example.com");
uut.protocols("chat, superchat");
WHEN("when generating the HTTP handshake") {
THEN("CAF creates output according to RFC 6455 and omits empty fields") {
uut.write_http_1_request(buf());
CHECK_EQ(str(), http_request);
}
}
}
}
SCENARIO("handshake objects validate HTTP response headers") {
GIVEN("a request header object with predefined key") {
web_socket::handshake uut;
uut.endpoint("/chat");
uut.key(key_to_bytes());
WHEN("presenting a HTTP response with proper Sec-WebSocket-Accept") {
THEN("the object recognizes the response as valid") {
CHECK(uut.is_valid_http_1_response(http_response));
CHECK(!uut.is_valid_http_1_response("HTTP/1.1 101 Bogus\r\n"));
}
}
}
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE net.web_socket.server
#include "caf/net/web_socket/server.hpp"
#include "net-test.hpp"
#include "caf/byte_span.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
using namespace caf;
using namespace caf::literals;
using namespace std::literals::string_literals;
namespace {
using svec = std::vector<std::string>;
struct app_t {
std::string text_input;
std::vector<byte> binary_input;
settings cfg;
template <class LowerLayerPtr>
error init(net::socket_manager*, LowerLayerPtr, const settings& init_cfg) {
cfg = init_cfg;
return none;
}
template <class LowerLayerPtr>
bool prepare_send(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
bool done_sending(LowerLayerPtr) {
return true;
}
template <class LowerLayerPtr>
void abort(LowerLayerPtr, const error& reason) {
CAF_FAIL("app::abort called: " << reason);
}
template <class LowerLayerPtr>
ptrdiff_t consume_text(LowerLayerPtr, string_view text) {
text_input.insert(text_input.end(), text.begin(), text.end());
return static_cast<ptrdiff_t>(text.size());
}
template <class LowerLayerPtr>
ptrdiff_t consume_binary(LowerLayerPtr, byte_span bytes) {
binary_input.insert(binary_input.end(), bytes.begin(), bytes.end());
return static_cast<ptrdiff_t>(bytes.size());
}
};
struct fixture : host_fixture {
fixture() {
using namespace caf::net;
ws = std::addressof(transport.upper_layer);
app = std::addressof(ws->upper_layer());
if (auto err = transport.init())
CAF_FAIL("failed to initialize mock transport: " << err);
rng.seed(0xD3ADC0D3);
}
void rfc6455_append(uint8_t opcode, span<const byte> bytes,
std::vector<byte>& out) {
std::vector<byte> payload{bytes.begin(), bytes.end()};
auto key = static_cast<uint32_t>(rng());
detail::rfc6455::mask_data(key, payload);
detail::rfc6455::assemble_frame(opcode, key, payload, out);
}
void rfc6455_append(span<const byte> bytes, std::vector<byte>& out) {
rfc6455_append(detail::rfc6455::binary_frame, bytes, out);
}
void rfc6455_append(string_view text, std::vector<byte>& out) {
rfc6455_append(detail::rfc6455::text_frame, as_bytes(make_span(text)), out);
}
void push(uint8_t opcode, span<const byte> bytes) {
std::vector<byte> frame;
rfc6455_append(opcode, bytes, frame);
transport.push(frame);
}
void push(span<const byte> bytes) {
push(detail::rfc6455::binary_frame, bytes);
}
void push(string_view str) {
push(detail::rfc6455::text_frame, as_bytes(make_span(str)));
}
mock_stream_transport<net::web_socket::server<app_t>> transport;
net::web_socket::server<app_t>* ws;
app_t* app;
std::minstd_rand rng;
};
constexpr auto opening_handshake
= "GET /chat?room=lounge HTTP/1.1\r\n"
"Host: server.example.com\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Origin: http://example.com\r\n"
"Sec-WebSocket-Protocol: chat, superchat\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n"_sv;
} // namespace
#define CHECK_SETTING(key, expected_value) \
if (CAF_CHECK(holds_alternative<std::string>(app->cfg, key))) \
CAF_CHECK_EQUAL(get<std::string>(app->cfg, key), expected_value);
CAF_TEST_FIXTURE_SCOPE(web_socket_tests, fixture)
CAF_TEST(applications receive handshake data via config) {
transport.push(opening_handshake);
{
auto consumed = transport.handle_input();
if (consumed < 0)
CAF_FAIL("error handling input: " << transport.abort_reason());
CAF_CHECK_EQUAL(consumed, static_cast<ptrdiff_t>(opening_handshake.size()));
}
CAF_CHECK_EQUAL(transport.input.size(), 0u);
CAF_CHECK_EQUAL(transport.unconsumed(), 0u);
CAF_CHECK(ws->handshake_complete());
CHECK_SETTING("web-socket.method", "GET");
CHECK_SETTING("web-socket.path", "chat");
CHECK_SETTING("web-socket.http-version", "HTTP/1.1");
CHECK_SETTING("web-socket.fields.Host", "server.example.com");
CHECK_SETTING("web-socket.fields.Upgrade", "websocket");
CHECK_SETTING("web-socket.fields.Connection", "Upgrade");
CHECK_SETTING("web-socket.fields.Origin", "http://example.com");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Protocol", "chat, superchat");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Version", "13");
CHECK_SETTING("web-socket.fields.Sec-WebSocket-Key",
"dGhlIHNhbXBsZSBub25jZQ==");
using str_map = std::map<std::string, std::string>;
if (auto query = get_as<str_map>(app->cfg, "web-socket.query");
CAF_CHECK(query))
CAF_CHECK_EQUAL(*query, str_map({{"room"s, "lounge"s}}));
}
CAF_TEST(the server responds with an HTTP response on success) {
transport.push(opening_handshake);
CAF_CHECK_EQUAL(transport.handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CAF_CHECK(ws->handshake_complete());
CAF_CHECK(transport.output_as_str(),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n");
}
CAF_TEST(handshakes may arrive in chunks) {
svec bufs;
size_t chunk_size = opening_handshake.size() / 3;
auto i = opening_handshake.begin();
bufs.emplace_back(i, i + chunk_size);
i += chunk_size;
bufs.emplace_back(i, i + chunk_size);
i += chunk_size;
bufs.emplace_back(i, opening_handshake.end());
transport.push(bufs[0]);
CAF_CHECK_EQUAL(transport.handle_input(), 0u);
CAF_CHECK(!ws->handshake_complete());
transport.push(bufs[1]);
CAF_CHECK_EQUAL(transport.handle_input(), 0u);
CAF_CHECK(!ws->handshake_complete());
transport.push(bufs[2]);
CAF_CHECK_EQUAL(static_cast<size_t>(transport.handle_input()),
opening_handshake.size());
CAF_CHECK(ws->handshake_complete());
}
CAF_TEST(data may follow the handshake immediately) {
auto hs_bytes = as_bytes(make_span(opening_handshake));
std::vector<byte> buf{hs_bytes.begin(), hs_bytes.end()};
rfc6455_append("Hello WebSocket!\n"_sv, buf);
rfc6455_append("Bye WebSocket!\n"_sv, buf);
transport.push(buf);
CAF_CHECK_EQUAL(transport.handle_input(), static_cast<ptrdiff_t>(buf.size()));
CAF_CHECK(ws->handshake_complete());
CAF_CHECK_EQUAL(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST(data may arrive later) {
transport.push(opening_handshake);
CAF_CHECK_EQUAL(transport.handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
CAF_CHECK(ws->handshake_complete());
push("Hello WebSocket!\nBye WebSocket!\n"_sv);
transport.handle_input();
CAF_CHECK_EQUAL(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE network_socket
#include "caf/net/network_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST_FIXTURE_SCOPE(network_socket_tests, host_fixture)
CAF_TEST(invalid socket) {
network_socket x;
CAF_CHECK_EQUAL(allow_udp_connreset(x, true), sec::network_syscall_failed);
CAF_CHECK_EQUAL(send_buffer_size(x), sec::network_syscall_failed);
CAF_CHECK_EQUAL(local_port(x), sec::network_syscall_failed);
CAF_CHECK_EQUAL(local_addr(x), sec::network_syscall_failed);
CAF_CHECK_EQUAL(remote_port(x), sec::network_syscall_failed);
CAF_CHECK_EQUAL(remote_addr(x), sec::network_syscall_failed);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE pipe_socket
#include "caf/net/pipe_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST_FIXTURE_SCOPE(pipe_socket_tests, host_fixture)
CAF_TEST(send and receive) {
byte_buffer send_buf{byte(1), byte(2), byte(3), byte(4),
byte(5), byte(6), byte(7), byte(8)};
byte_buffer receive_buf;
receive_buf.resize(100);
pipe_socket rd_sock;
pipe_socket wr_sock;
std::tie(rd_sock, wr_sock) = unbox(make_pipe());
CAF_CHECK_EQUAL(static_cast<size_t>(write(wr_sock, send_buf)),
send_buf.size());
CAF_CHECK_EQUAL(static_cast<size_t>(read(rd_sock, receive_buf)),
send_buf.size());
CAF_CHECK(std::equal(send_buf.begin(), send_buf.end(), receive_buf.begin()));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE socket
#include "caf/net/socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
using namespace caf::net;
CAF_TEST_FIXTURE_SCOPE(socket_tests, host_fixture)
CAF_TEST(invalid socket) {
auto x = invalid_socket;
CAF_CHECK_EQUAL(x.id, invalid_socket_id);
CAF_CHECK_EQUAL(child_process_inherit(x, true), sec::network_syscall_failed);
CAF_CHECK_EQUAL(nonblocking(x, true), sec::network_syscall_failed);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE socket_guard
#include "caf/net/socket_guard.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/socket_id.hpp"
using namespace caf;
using namespace caf::net;
namespace {
constexpr socket_id dummy_id = 13;
struct dummy_socket {
dummy_socket(socket_id id, bool& closed) : id(id), closed(closed) {
// nop
}
dummy_socket(const dummy_socket&) = default;
dummy_socket& operator=(const dummy_socket& other) {
id = other.id;
closed = other.closed;
return *this;
}
socket_id id;
bool& closed;
};
void close(dummy_socket x) {
x.id = invalid_socket_id;
x.closed = true;
}
struct fixture {
fixture() : closed{false}, sock{dummy_id, closed} {
// nop
}
bool closed;
dummy_socket sock;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(socket_guard_tests, fixture)
CAF_TEST(cleanup) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
}
CAF_CHECK(sock.closed);
}
CAF_TEST(reset) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
guard.release();
CAF_CHECK_EQUAL(guard.socket().id, invalid_socket_id);
guard.reset(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
}
CAF_CHECK_EQUAL(sock.closed, true);
}
CAF_TEST(release) {
{
auto guard = make_socket_guard(sock);
CAF_CHECK_EQUAL(guard.socket().id, dummy_id);
guard.release();
CAF_CHECK_EQUAL(guard.socket().id, invalid_socket_id);
}
CAF_CHECK_EQUAL(sock.closed, false);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE basp.stream_application
#include "caf/net/basp/application.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include <vector>
#include "caf/byte_buffer.hpp"
#include "caf/net/backend/test.hpp"
#include "caf/net/basp/connection_state.hpp"
#include "caf/net/basp/constants.hpp"
#include "caf/net/basp/ec.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/middleman.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/net/stream_transport.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
#define REQUIRE_OK(statement) \
if (auto err = statement) \
CAF_FAIL(err);
namespace {
using transport_type = stream_transport<basp::application>;
struct config : actor_system_config {
config() {
put(content, "caf.middleman.this-node", unbox(make_uri("test:earth")));
load<middleman, backend::test>();
}
};
struct fixture : host_fixture, test_coordinator_fixture<config> {
fixture() : mars(make_node_id(unbox(make_uri("test:mars")))) {
auto& mm = sys.network_manager();
mm.mpx()->set_thread_id();
auto backend = dynamic_cast<backend::test*>(mm.backend("test"));
auto mgr = backend->peer(mars);
auto& dref = dynamic_cast<socket_manager_impl<transport_type>&>(*mgr);
app = &dref.transport().application();
sock = backend->socket(mars);
}
bool handle_io_event() override {
auto mpx = sys.network_manager().mpx();
return mpx->poll_once(false);
}
template <class... Ts>
byte_buffer to_buf(const Ts&... xs) {
byte_buffer buf;
binary_serializer sink{system(), buf};
REQUIRE_OK(sink(xs...));
return buf;
}
template <class... Ts>
void mock(const Ts&... xs) {
auto buf = to_buf(xs...);
if (write(sock, buf) != static_cast<ptrdiff_t>(buf.size()))
CAF_FAIL("unable to write " << buf.size() << " bytes");
run();
}
void handle_handshake() {
CAF_CHECK_EQUAL(app->state(),
basp::connection_state::await_handshake_header);
auto payload = to_buf(mars, basp::application::default_app_ids());
mock(basp::header{basp::message_type::handshake,
static_cast<uint32_t>(payload.size()), basp::version});
CAF_CHECK_EQUAL(app->state(),
basp::connection_state::await_handshake_payload);
write(sock, payload);
run();
}
void consume_handshake() {
byte_buffer buf(basp::header_size);
if (read(sock, buf) != basp::header_size)
CAF_FAIL("unable to read " << basp::header_size << " bytes");
auto hdr = basp::header::from_bytes(buf);
if (hdr.type != basp::message_type::handshake || hdr.payload_len == 0
|| hdr.operation_data != basp::version)
CAF_FAIL("invalid handshake header");
buf.resize(hdr.payload_len);
if (read(sock, buf) != static_cast<ptrdiff_t>(hdr.payload_len))
CAF_FAIL("unable to read " << hdr.payload_len << " bytes");
node_id nid;
std::vector<std::string> app_ids;
binary_deserializer source{sys, buf};
if (auto err = source(nid, app_ids))
CAF_FAIL("unable to deserialize payload: " << err);
if (source.remaining() > 0)
CAF_FAIL("trailing bytes after reading payload");
}
actor_system& system() {
return sys;
}
node_id mars;
stream_socket sock;
basp::application* app;
unit_t no_payload;
};
} // namespace
#define MOCK(kind, op, ...) \
do { \
CAF_MESSAGE("mock " << kind); \
if (!std::is_same<decltype(std::make_tuple(__VA_ARGS__)), \
std::tuple<unit_t>>::value) { \
auto payload = to_buf(__VA_ARGS__); \
mock(basp::header{kind, static_cast<uint32_t>(payload.size()), op}); \
write(sock, payload); \
} else { \
mock(basp::header{kind, 0, op}); \
} \
run(); \
} while (false)
#define RECEIVE(msg_type, op_data, ...) \
do { \
CAF_MESSAGE("receive " << msg_type); \
byte_buffer buf(basp::header_size); \
if (read(sock, buf) != static_cast<ptrdiff_t>(basp::header_size)) \
CAF_FAIL("unable to read " << basp::header_size << " bytes"); \
auto hdr = basp::header::from_bytes(buf); \
CAF_CHECK_EQUAL(hdr.type, msg_type); \
CAF_CHECK_EQUAL(hdr.operation_data, op_data); \
if (!std::is_same<decltype(std::make_tuple(__VA_ARGS__)), \
std::tuple<unit_t>>::value) { \
buf.resize(hdr.payload_len); \
if (read(sock, buf) != static_cast<ptrdiff_t>(hdr.payload_len)) \
CAF_FAIL("unable to read " << hdr.payload_len << " bytes"); \
binary_deserializer source{sys, buf}; \
if (auto err = source(__VA_ARGS__)) \
CAF_FAIL("failed to receive data: " << err); \
} else { \
if (hdr.payload_len != 0) \
CAF_FAIL("unexpected payload"); \
} \
} while (false)
CAF_TEST_FIXTURE_SCOPE(application_tests, fixture)
CAF_TEST(actor message and down message) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
CAF_REQUIRE_EQUAL(self->mailbox().size(), 0u);
MOCK(basp::message_type::actor_message, make_message_id().integer_value(),
mars, actor_id{42}, self->id(), std::vector<strong_actor_ptr>{},
make_message("hello world!"));
MOCK(basp::message_type::monitor_message, 42u, no_payload);
strong_actor_ptr proxy;
self->receive([&](const std::string& str) {
CAF_CHECK_EQUAL(str, "hello world!");
proxy = self->current_sender();
CAF_REQUIRE_NOT_EQUAL(proxy, nullptr);
self->monitor(proxy);
});
MOCK(basp::message_type::down_message, 42u,
error{exit_reason::user_shutdown});
expect((down_msg),
from(_).to(self).with(down_msg{actor_cast<actor_addr>(proxy),
exit_reason::user_shutdown}));
}
CAF_TEST(resolve request without result) {
handle_handshake();
consume_handshake();
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, std::string{"foo/bar"});
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, 0u);
CAF_CHECK(ifs.empty());
}
CAF_TEST(resolve request on id with result) {
handle_handshake();
consume_handshake();
sys.registry().put(self->id(), self);
auto path = "id/" + std::to_string(self->id());
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
MOCK(basp::message_type::resolve_request, 42, path);
CAF_CHECK_EQUAL(app->state(), basp::connection_state::await_header);
actor_id aid;
std::set<std::string> ifs;
RECEIVE(basp::message_type::resolve_response, 42u, aid, ifs);
CAF_CHECK_EQUAL(aid, self->id());
CAF_CHECK(ifs.empty());
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE stream_socket
#include "caf/net/stream_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
byte operator"" _b(unsigned long long x) {
return static_cast<byte>(x);
}
} // namespace
CAF_TEST_FIXTURE_SCOPE(network_socket_simple_tests, host_fixture)
CAF_TEST(invalid socket) {
stream_socket x;
CAF_CHECK_EQUAL(keepalive(x, true), sec::network_syscall_failed);
CAF_CHECK_EQUAL(nodelay(x, true), sec::network_syscall_failed);
CAF_CHECK_EQUAL(allow_sigpipe(x, true), sec::network_syscall_failed);
}
CAF_TEST_FIXTURE_SCOPE_END()
namespace {
struct fixture : host_fixture {
fixture() : rd_buf(124) {
std::tie(first, second) = unbox(make_stream_socket_pair());
CAF_REQUIRE_EQUAL(nonblocking(first, true), caf::none);
CAF_REQUIRE_EQUAL(nonblocking(second, true), caf::none);
CAF_REQUIRE_NOT_EQUAL(unbox(send_buffer_size(first)), 0u);
CAF_REQUIRE_NOT_EQUAL(unbox(send_buffer_size(second)), 0u);
}
~fixture() {
close(first);
close(second);
}
stream_socket first;
stream_socket second;
byte_buffer rd_buf;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(network_socket_tests, fixture)
CAF_TEST(read on empty sockets) {
CAF_CHECK_LESS(read(first, rd_buf), 0);
CAF_CHECK(last_socket_error_is_temporary());
CAF_CHECK_LESS(read(second, rd_buf), 0);
CAF_CHECK(last_socket_error_is_temporary());
}
CAF_TEST(transfer data from first to second socket) {
byte_buffer wr_buf{1_b, 2_b, 4_b, 8_b, 16_b, 32_b, 64_b};
CAF_MESSAGE("transfer data from first to second socket");
CAF_CHECK_EQUAL(static_cast<size_t>(write(first, wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(static_cast<size_t>(read(second, rd_buf)), wr_buf.size());
CAF_CHECK(std::equal(wr_buf.begin(), wr_buf.end(), rd_buf.begin()));
rd_buf.assign(rd_buf.size(), byte(0));
}
CAF_TEST(transfer data from second to first socket) {
byte_buffer wr_buf{1_b, 2_b, 4_b, 8_b, 16_b, 32_b, 64_b};
CAF_CHECK_EQUAL(static_cast<size_t>(write(second, wr_buf)), wr_buf.size());
CAF_CHECK_EQUAL(static_cast<size_t>(read(first, rd_buf)), wr_buf.size());
CAF_CHECK(std::equal(wr_buf.begin(), wr_buf.end(), rd_buf.begin()));
}
CAF_TEST(shut down first socket and observe shutdown on the second one) {
close(first);
CAF_CHECK_EQUAL(read(second, rd_buf), 0);
first.id = invalid_socket_id;
}
CAF_TEST(transfer data using multiple buffers) {
byte_buffer wr_buf_1{1_b, 2_b, 4_b};
byte_buffer wr_buf_2{8_b, 16_b, 32_b, 64_b};
byte_buffer full_buf;
full_buf.insert(full_buf.end(), wr_buf_1.begin(), wr_buf_1.end());
full_buf.insert(full_buf.end(), wr_buf_2.begin(), wr_buf_2.end());
CAF_CHECK_EQUAL(static_cast<size_t>(
write(second, {make_span(wr_buf_1), make_span(wr_buf_2)})),
full_buf.size());
CAF_CHECK_EQUAL(static_cast<size_t>(read(first, rd_buf)), full_buf.size());
CAF_CHECK(std::equal(full_buf.begin(), full_buf.end(), rd_buf.begin()));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE stream_transport
#include "caf/net/stream_transport.hpp"
#include "net-test.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/socket_guard.hpp"
#include "caf/net/socket_manager.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
constexpr string_view hello_manager = "hello manager!";
struct fixture : test_coordinator_fixture<>, host_fixture {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
fixture()
: mpx(nullptr),
recv_buf(1024),
shared_recv_buf{std::make_shared<byte_buffer>()},
shared_send_buf{std::make_shared<byte_buffer>()} {
mpx.set_thread_id();
mpx.apply_updates();
if (auto err = mpx.init())
FAIL("mpx.init failed: " << err);
REQUIRE_EQ(mpx.num_socket_managers(), 1u);
auto sockets = unbox(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))
FAIL("nonblocking returned an error: " << err);
}
bool handle_io_event() override {
return mpx.poll_once(false);
}
settings config;
multiplexer mpx;
byte_buffer recv_buf;
socket_guard<stream_socket> send_socket_guard;
socket_guard<stream_socket> recv_socket_guard;
byte_buffer_ptr shared_recv_buf;
byte_buffer_ptr shared_send_buf;
};
class dummy_application {
public:
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
using input_tag = tag::stream_oriented;
explicit dummy_application(byte_buffer_ptr recv_buf, byte_buffer_ptr send_buf)
: recv_buf_(std::move(recv_buf)),
send_buf_(std::move(send_buf)){
// nop
};
~dummy_application() = default;
template <class ParentPtr>
error init(socket_manager*, ParentPtr parent, const settings&) {
parent->configure_read(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;
}
template <class ParentPtr>
bool done_sending(ParentPtr) {
MESSAGE("done_sending called");
return true;
}
template <class ParentPtr>
void continue_reading(ParentPtr) {
FAIL("continue_reading called");
}
template <class ParentPtr>
size_t consume(ParentPtr, span<const byte> data, span<const byte>) {
recv_buf_->clear();
recv_buf_->insert(recv_buf_->begin(), data.begin(), data.end());
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));
}
template <class ParentPtr>
static void abort(ParentPtr, const error& reason) {
FAIL("abort called with " << CAF_ARG(reason));
}
private:
byte_buffer_ptr recv_buf_;
byte_buffer_ptr send_buf_;
};
} // namespace
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);
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
CHECK_EQ(static_cast<size_t>(write(send_socket_guard.socket(),
as_bytes(make_span(hello_manager)))),
hello_manager.size());
MESSAGE("wrote " << hello_manager.size() << " bytes.");
run();
CHECK_EQ(string_view(reinterpret_cast<char*>(shared_recv_buf->data()),
shared_recv_buf->size()),
hello_manager);
}
CAF_TEST(send) {
auto mgr = make_socket_manager<dummy_application, stream_transport>(
recv_socket_guard.release(), &mpx, shared_recv_buf, shared_send_buf);
CHECK_EQ(mgr->init(config), none);
mpx.apply_updates();
CHECK_EQ(mpx.num_socket_managers(), 2u);
mgr->register_writing();
mpx.apply_updates();
while (handle_io_event())
;
recv_buf.resize(hello_manager.size());
auto res = read(send_socket_guard.socket(), make_span(recv_buf));
MESSAGE("received " << res << " bytes");
recv_buf.resize(res);
CHECK_EQ(string_view(reinterpret_cast<char*>(recv_buf.data()),
recv_buf.size()),
hello_manager);
}
END_FIXTURE_SCOPE()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE string_application
#include "caf/net/stream_transport.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/endpoint_manager.hpp"
#include "caf/net/make_endpoint_manager.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/net/stream_socket.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
using namespace caf::policy;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
struct fixture : test_coordinator_fixture<>, host_fixture {
fixture() {
mpx = std::make_shared<multiplexer>();
if (auto err = mpx->init())
CAF_FAIL("mpx->init failed: " << err);
mpx->set_thread_id();
}
bool handle_io_event() override {
return mpx->poll_once(false);
}
multiplexer_ptr mpx;
};
struct string_application_header {
uint32_t payload;
};
/// @relates header
template <class Inspector>
bool inspect(Inspector& f, string_application_header& x) {
return f.fields(f.field("payload", x.payload));
}
class string_application {
public:
using header_type = string_application_header;
string_application(byte_buffer_ptr buf) : buf_(std::move(buf)) {
// nop
}
template <class Parent>
error init(Parent&) {
return none;
}
template <class Parent>
void handle_packet(Parent& parent, header_type&, span<const byte> payload) {
binary_deserializer source{parent.system(), payload};
message msg;
if (auto err = msg.load(source))
CAF_FAIL("unable to deserialize message: " << err);
if (!msg.match_elements<std::string>())
CAF_FAIL("unexpected message: " << msg);
auto& str = msg.get_as<std::string>(0);
auto bytes = as_bytes(make_span(str));
buf_->insert(buf_->end(), bytes.begin(), bytes.end());
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> ptr) {
// Ignore proxy announcement messages.
if (ptr->msg == nullptr)
return none;
auto header_buf = parent.next_header_buffer();
auto payload_buf = parent.next_payload_buffer();
binary_serializer payload_sink{parent.system(), payload_buf};
if (auto err = payload_sink(ptr->msg->payload))
CAF_FAIL("serializing failed: " << err);
binary_serializer header_sink{parent.system(), header_buf};
if (auto err
= header_sink(header_type{static_cast<uint32_t>(payload_buf.size())}))
CAF_FAIL("serializing failed: " << err);
parent.write_packet(header_buf, payload_buf);
return none;
}
private:
byte_buffer_ptr buf_;
};
template <class Base, class Subtype>
class stream_string_application : public Base {
public:
using header_type = typename Base::header_type;
stream_string_application(byte_buffer_ptr buf)
: Base(std::move(buf)), await_payload_(false) {
// nop
}
template <class Parent>
error init(Parent& parent) {
parent.transport().configure_read(
net::receive_policy::exactly(sizeof(header_type)));
return Base::init(parent);
}
template <class Parent>
error handle_data(Parent& parent, span<const byte> data) {
if (await_payload_) {
Base::handle_packet(parent, header_, data);
await_payload_ = false;
} else {
if (data.size() != sizeof(header_type))
CAF_FAIL("");
binary_deserializer source{nullptr, data};
if (auto err = source(header_))
CAF_FAIL("serializing failed: " << err);
if (header_.payload == 0)
Base::handle_packet(parent, header_, span<const byte>{});
else
parent.transport().configure_read(
net::receive_policy::exactly(header_.payload));
await_payload_ = true;
}
return none;
}
template <class Parent>
void resolve(Parent& parent, string_view path, actor listener) {
actor_id aid = 42;
auto hid = string_view("0011223344556677889900112233445566778899");
auto nid = unbox(make_node_id(aid, hid));
actor_config cfg;
auto sys = &parent.system();
auto mgr = &parent.manager();
auto p = make_actor<net::actor_proxy_impl, strong_actor_ptr>(aid, nid, sys,
cfg, mgr);
anon_send(listener, resolve_atom_v, std::string{path.begin(), path.end()},
p);
}
template <class Parent>
void timeout(Parent&, const std::string&, uint64_t) {
// nop
}
template <class Parent>
void new_proxy(Parent&, actor_id) {
// nop
}
template <class Parent>
void local_actor_down(Parent&, actor_id, error) {
// nop
}
void handle_error(sec sec) {
CAF_FAIL("handle_error called: " << CAF_ARG(sec));
}
private:
header_type header_;
bool await_payload_;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(receive) {
using application_type
= extend<string_application>::with<stream_string_application>;
using transport_type = stream_transport<application_type>;
byte_buffer read_buf(1024);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 1u);
auto buf = std::make_shared<byte_buffer>();
auto sockets = unbox(make_stream_socket_pair());
CAF_CHECK_EQUAL(nonblocking(sockets.second, true), none);
CAF_CHECK_LESS(read(sockets.second, read_buf), 0);
CAF_CHECK(last_socket_error_is_temporary());
CAF_MESSAGE("adding both endpoint managers");
auto mgr1 = make_endpoint_manager(
mpx, sys, transport_type{sockets.first, application_type{buf}});
CAF_CHECK_EQUAL(mgr1->init(), none);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 2u);
auto mgr2 = make_endpoint_manager(
mpx, sys, transport_type{sockets.second, application_type{buf}});
CAF_CHECK_EQUAL(mgr2->init(), none);
CAF_CHECK_EQUAL(mpx->num_socket_managers(), 3u);
CAF_MESSAGE("resolve actor-proxy");
mgr1->resolve(unbox(make_uri("test:/id/42")), self);
run();
self->receive(
[&](resolve_atom, const std::string&, const strong_actor_ptr& p) {
CAF_MESSAGE("got a proxy, send a message to it");
self->send(actor_cast<actor>(p), "hello proxy!");
},
after(std::chrono::seconds(0)) >>
[&] { CAF_FAIL("manager did not respond with a proxy."); });
run();
CAF_CHECK_EQUAL(string_view(reinterpret_cast<char*>(buf->data()),
buf->size()),
"hello proxy!");
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE tcp_sockets
#include "caf/net/tcp_accept_socket.hpp"
#include "caf/net/tcp_stream_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/net/socket_guard.hpp"
using namespace caf;
using namespace caf::net;
namespace {
// TODO: switch to std::operator""s when switching to C++14
std::string operator"" _s(const char* str, size_t size) {
return std::string(str, size);
}
struct fixture : host_fixture {
fixture() {
auth.port = 0;
auth.host = "0.0.0.0"_s;
}
uri::authority_type auth;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(tcp_sockets_tests, fixture)
CAF_TEST(open tcp port) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(acceptor));
CAF_CHECK_NOT_EQUAL(port, 0);
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
}
CAF_TEST(tcp connect) {
auto acceptor = unbox(make_tcp_accept_socket(auth, false));
auto port = unbox(local_port(acceptor));
CAF_CHECK_NOT_EQUAL(port, 0);
auto acceptor_guard = make_socket_guard(acceptor);
CAF_MESSAGE("opened acceptor on port " << port);
uri::authority_type dst;
dst.port = port;
dst.host = "localhost"_s;
CAF_MESSAGE("connecting to localhost");
auto conn = unbox(make_connected_tcp_stream_socket(dst));
auto conn_guard = make_socket_guard(conn);
CAF_CHECK_NOT_EQUAL(conn, invalid_socket);
auto accepted = unbox(accept(acceptor));
auto accepted_guard = make_socket_guard(conn);
CAF_CHECK_NOT_EQUAL(accepted, invalid_socket);
CAF_MESSAGE("connected");
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE transport_worker
#include "caf/net/transport_worker.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/make_actor.hpp"
#include "caf/net/actor_proxy_impl.hpp"
#include "caf/net/multiplexer.hpp"
#include "caf/span.hpp"
using namespace caf;
using namespace caf::net;
namespace {
constexpr string_view hello_test = "hello test!";
struct application_result {
bool initialized;
byte_buffer data_buffer;
std::string resolve_path;
actor resolve_listener;
std::string timeout_value;
uint64_t timeout_id;
sec err;
};
struct transport_result {
byte_buffer packet_buffer;
ip_endpoint ep;
};
class dummy_application {
public:
dummy_application(std::shared_ptr<application_result> res)
: res_(std::move(res)){
// nop
};
~dummy_application() = default;
template <class Parent>
error init(Parent&) {
res_->initialized = true;
return none;
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> ptr) {
auto payload_buf = parent.next_payload_buffer();
binary_serializer sink(parent.system(), payload_buf);
if (!sink.apply_objects(ptr->msg->content()))
CAF_FAIL("failed to serialize content: " << sink.get_error());
CAF_MESSAGE("before sending: " << CAF_ARG(ptr->msg->content()));
parent.write_packet(payload_buf);
return none;
}
template <class Parent>
error handle_data(Parent&, span<const byte> data) {
auto& buf = res_->data_buffer;
buf.clear();
buf.insert(buf.begin(), data.begin(), data.end());
return none;
}
template <class Parent>
void resolve(Parent&, string_view path, const actor& listener) {
res_->resolve_path.assign(path.begin(), path.end());
res_->resolve_listener = listener;
}
template <class Parent>
void timeout(Parent&, std::string value, uint64_t id) {
res_->timeout_value = std::move(value);
res_->timeout_id = id;
}
void handle_error(sec err) {
res_->err = err;
}
private:
std::shared_ptr<application_result> res_;
};
class dummy_transport {
public:
using transport_type = dummy_transport;
using application_type = dummy_application;
dummy_transport(actor_system& sys, std::shared_ptr<transport_result> res)
: sys_(sys), res_(std::move(res)) {
// nop
}
void write_packet(ip_endpoint ep, span<byte_buffer*> buffers) {
res_->ep = ep;
auto& packet_buf = res_->packet_buffer;
packet_buf.clear();
for (auto buf : buffers)
packet_buf.insert(packet_buf.end(), buf->begin(), buf->end());
}
actor_system& system() {
return sys_;
}
transport_type& transport() {
return *this;
}
byte_buffer next_header_buffer() {
return {};
}
byte_buffer next_payload_buffer() {
return {};
}
private:
actor_system& sys_;
std::shared_ptr<transport_result> res_;
};
struct fixture : test_coordinator_fixture<>, host_fixture {
using worker_type = transport_worker<dummy_application, ip_endpoint>;
fixture()
: mpx(nullptr),
transport_results{std::make_shared<transport_result>()},
application_results{std::make_shared<application_result>()},
transport(sys, transport_results),
worker{dummy_application{application_results}} {
if (auto err = mpx.init())
CAF_FAIL("mpx.init failed: " << err);
if (auto err = parse("[::1]:12345", ep))
CAF_FAIL("parse returned an error: " << err);
worker = worker_type{dummy_application{application_results}, ep};
}
bool handle_io_event() override {
return mpx.poll_once(false);
}
multiplexer mpx;
std::shared_ptr<transport_result> transport_results;
std::shared_ptr<application_result> application_results;
dummy_transport transport;
worker_type worker;
ip_endpoint ep;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(endpoint_manager_tests, fixture)
CAF_TEST(construction and initialization) {
CAF_CHECK_EQUAL(worker.init(transport), none);
CAF_CHECK_EQUAL(application_results->initialized, true);
}
CAF_TEST(handle_data) {
auto test_span = as_bytes(make_span(hello_test));
CAF_CHECK_EQUAL(worker.handle_data(transport, test_span), none);
auto& buf = application_results->data_buffer;
string_view result{reinterpret_cast<char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(result, hello_test);
}
CAF_TEST(write_message) {
std::string hello_test{"hello world!"};
actor act;
auto strong_actor = actor_cast<strong_actor_ptr>(act);
mailbox_element::forwarding_stack stack;
auto msg = make_message(hello_test);
auto elem = make_mailbox_element(strong_actor, make_message_id(12345), stack,
msg);
using message_type = endpoint_manager_queue::message;
auto message = detail::make_unique<message_type>(std::move(elem), nullptr);
worker.write_message(transport, std::move(message));
auto& buf = transport_results->packet_buffer;
binary_deserializer source{sys, buf};
caf::message received_msg;
CAF_CHECK(source.apply_objects(received_msg));
CAF_MESSAGE(CAF_ARG(received_msg));
auto received_str = received_msg.get_as<std::string>(0);
string_view result{received_str};
CAF_CHECK_EQUAL(result, hello_test);
CAF_CHECK_EQUAL(transport_results->ep, ep);
}
CAF_TEST(resolve) {
worker.resolve(transport, "foo", self);
CAF_CHECK_EQUAL(application_results->resolve_path, "foo");
CAF_CHECK_EQUAL(application_results->resolve_listener, self);
}
CAF_TEST(timeout) {
worker.timeout(transport, "bar", 42u);
CAF_CHECK_EQUAL(application_results->timeout_value, "bar");
CAF_CHECK_EQUAL(application_results->timeout_id, 42u);
}
CAF_TEST(handle_error) {
worker.handle_error(sec::feature_disabled);
CAF_CHECK_EQUAL(application_results->err, sec::feature_disabled);
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE transport_worker_dispatcher
#include "caf/net/transport_worker_dispatcher.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/make_actor.hpp"
#include "caf/monitorable_actor.hpp"
#include "caf/node_id.hpp"
#include "caf/uri.hpp"
using namespace caf;
using namespace caf::net;
namespace {
using byte_buffer_ptr = std::shared_ptr<byte_buffer>;
constexpr string_view hello_test = "hello_test";
struct dummy_actor : public monitorable_actor {
dummy_actor(actor_config& cfg) : monitorable_actor(cfg) {
// nop
}
void enqueue(mailbox_element_ptr, execution_unit*) override {
// nop
}
void setup_metrics() {
// nop
}
};
class dummy_application {
public:
dummy_application(byte_buffer_ptr rec_buf, uint8_t id)
: rec_buf_(std::move(rec_buf)),
id_(id){
// nop
};
~dummy_application() = default;
template <class Parent>
error init(Parent&) {
rec_buf_->push_back(static_cast<byte>(id_));
return none;
}
template <class Parent>
error write_message(Parent& parent,
std::unique_ptr<endpoint_manager_queue::message> ptr) {
rec_buf_->push_back(static_cast<byte>(id_));
auto data = ptr->msg->content().get_as<byte_buffer>(0);
parent.write_packet(data);
return none;
}
template <class Parent>
error handle_data(Parent&, span<const byte>) {
rec_buf_->push_back(static_cast<byte>(id_));
return none;
}
template <class Manager>
void resolve(Manager&, const std::string&, actor) {
rec_buf_->push_back(static_cast<byte>(id_));
}
template <class Transport>
void timeout(Transport&, const std::string&, uint64_t) {
rec_buf_->push_back(static_cast<byte>(id_));
}
void handle_error(sec) {
rec_buf_->push_back(static_cast<byte>(id_));
}
private:
byte_buffer_ptr rec_buf_;
uint8_t id_;
};
struct dummy_application_factory {
public:
using application_type = dummy_application;
dummy_application_factory(byte_buffer_ptr buf)
: buf_(std::move(buf)), application_cnt_(0) {
// nop
}
dummy_application make() {
return dummy_application{buf_, application_cnt_++};
}
private:
byte_buffer_ptr buf_;
uint8_t application_cnt_;
};
struct dummy_transport {
using transport_type = dummy_transport;
using factory_type = dummy_application_factory;
using application_type = dummy_application;
dummy_transport(actor_system& sys, byte_buffer_ptr buf)
: sys_(sys), buf_(std::move(buf)) {
// nop
}
template <class IdType>
void write_packet(IdType, span<byte_buffer*> buffers) {
for (auto buf : buffers)
buf_->insert(buf_->end(), buf->begin(), buf->end());
}
actor_system& system() {
return sys_;
}
transport_type& transport() {
return *this;
}
byte_buffer next_header_buffer() {
return {};
}
byte_buffer next_payload_buffer() {
return {};
}
private:
actor_system& sys_;
byte_buffer_ptr buf_;
};
struct testdata {
testdata(uint8_t worker_id, node_id id, ip_endpoint ep)
: worker_id(worker_id), nid(std::move(id)), ep(ep) {
// nop
}
uint8_t worker_id;
node_id nid;
ip_endpoint ep;
};
// TODO: switch to std::operator""s when switching to C++14
ip_endpoint operator"" _ep(const char* cstr, size_t cstr_len) {
ip_endpoint ep;
string_view str(cstr, cstr_len);
if (auto err = parse(str, ep))
CAF_FAIL("parse returned error: " << err);
return ep;
}
uri operator"" _u(const char* cstr, size_t cstr_len) {
uri result;
string_view str{cstr, cstr_len};
auto err = parse(str, result);
if (err)
CAF_FAIL("error while parsing " << str << ": " << to_string(err));
return result;
}
struct fixture : host_fixture {
using dispatcher_type
= transport_worker_dispatcher<dummy_application_factory, ip_endpoint>;
fixture()
: buf{std::make_shared<byte_buffer>()},
dispatcher{dummy_application_factory{buf}},
dummy{sys, buf} {
add_new_workers();
}
std::unique_ptr<net::endpoint_manager_queue::message>
make_dummy_message(node_id nid) {
actor_id aid = 42;
auto test_span = as_bytes(make_span(hello_test));
byte_buffer payload(test_span.begin(), test_span.end());
actor_config cfg;
auto p = make_actor<dummy_actor, strong_actor_ptr>(aid, nid, &sys, cfg);
auto receiver = actor_cast<strong_actor_ptr>(p);
if (!receiver)
CAF_FAIL("failed to cast receiver to a strong_actor_ptr");
mailbox_element::forwarding_stack stack;
auto elem = make_mailbox_element(nullptr, make_message_id(12345),
std::move(stack), make_message(payload));
return detail::make_unique<endpoint_manager_queue::message>(std::move(elem),
receiver);
}
bool contains(byte x) {
return std::count(buf->begin(), buf->end(), x) > 0;
}
void add_new_workers() {
for (auto& data : test_data) {
auto worker = dispatcher.add_new_worker(dummy, data.nid, data.ep);
if (!worker)
CAF_FAIL("add_new_worker returned an error: " << worker.error());
}
buf->clear();
}
void test_write_message(testdata& testcase) {
auto msg = make_dummy_message(testcase.nid);
if (!msg->receiver)
CAF_FAIL("receiver is null");
CAF_MESSAGE(CAF_ARG(msg));
dispatcher.write_message(dummy, std::move(msg));
}
actor_system_config cfg{};
actor_system sys{cfg};
byte_buffer_ptr buf;
dispatcher_type dispatcher;
dummy_transport dummy;
std::vector<testdata> test_data{
{0, make_node_id("http:file"_u), "[::1]:1"_ep},
{1, make_node_id("http:file?a=1&b=2"_u), "[fe80::2:34]:12345"_ep},
{2, make_node_id("http:file#42"_u), "[1234::17]:4444"_ep},
{3, make_node_id("http:file?a=1&b=2#42"_u), "[2332::1]:12"_ep},
};
};
#define CHECK_HANDLE_DATA(testcase) \
CAF_CHECK_EQUAL( \
dispatcher.handle_data(dummy, span<const byte>{}, testcase.ep), none); \
CAF_CHECK_EQUAL(buf->size(), 1u); \
CAF_CHECK_EQUAL(static_cast<byte>(testcase.worker_id), buf->at(0)); \
buf->clear();
#define CHECK_WRITE_MESSAGE(testcase) \
test_write_message(testcase); \
CAF_CHECK_EQUAL(buf->size(), hello_test.size() + 1u); \
CAF_CHECK_EQUAL(static_cast<byte>(testcase.worker_id), buf->at(0)); \
CAF_CHECK_EQUAL( \
memcmp(buf->data() + 1, hello_test.data(), hello_test.size()), 0); \
buf->clear();
} // namespace
CAF_TEST_FIXTURE_SCOPE(transport_worker_dispatcher_test, fixture)
CAF_TEST(init) {
dispatcher_type dispatcher{dummy_application_factory{buf}};
CAF_CHECK_EQUAL(dispatcher.init(dummy), none);
}
CAF_TEST(handle_data) {
CHECK_HANDLE_DATA(test_data.at(0));
CHECK_HANDLE_DATA(test_data.at(1));
CHECK_HANDLE_DATA(test_data.at(2));
CHECK_HANDLE_DATA(test_data.at(3));
}
CAF_TEST(write_message write_packet) {
CHECK_WRITE_MESSAGE(test_data.at(0));
CHECK_WRITE_MESSAGE(test_data.at(1));
CHECK_WRITE_MESSAGE(test_data.at(2));
CHECK_WRITE_MESSAGE(test_data.at(3));
}
CAF_TEST(resolve) {
// TODO think of a test for this
}
CAF_TEST(handle_error) {
dispatcher.handle_error(sec::unavailable_or_would_block);
CAF_CHECK_EQUAL(buf->size(), 4u);
CAF_CHECK(contains(byte(0)));
CAF_CHECK(contains(byte(1)));
CAF_CHECK(contains(byte(2)));
CAF_CHECK(contains(byte(3)));
}
CAF_TEST_FIXTURE_SCOPE_END()
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE udp_datagram_socket
#include "caf/net/udp_datagram_socket.hpp"
#include "caf/net/test/host_fixture.hpp"
#include "caf/test/dsl.hpp"
#include "caf/binary_serializer.hpp"
#include "caf/byte_buffer.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/ip_address.hpp"
#include "caf/ip_endpoint.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/net/ip.hpp"
using namespace caf;
using namespace caf::net;
using namespace caf::net::ip;
namespace {
constexpr string_view hello_test = "Hello test!";
struct fixture : host_fixture {
fixture() : host_fixture(), buf(1024) {
addresses = local_addresses("localhost");
CAF_CHECK(!addresses.empty());
ep = ip_endpoint(*addresses.begin(), 0);
auto send_pair = unbox(make_udp_datagram_socket(ep));
send_socket = send_pair.first;
auto receive_pair = unbox(make_udp_datagram_socket(ep));
receive_socket = receive_pair.first;
ep.port(receive_pair.second);
}
~fixture() {
close(send_socket);
close(receive_socket);
}
std::vector<ip_address> addresses;
actor_system_config cfg;
actor_system sys{cfg};
ip_endpoint ep;
udp_datagram_socket send_socket;
udp_datagram_socket receive_socket;
byte_buffer buf;
};
error read_from_socket(udp_datagram_socket sock, byte_buffer& buf) {
uint8_t receive_attempts = 0;
variant<std::pair<size_t, ip_endpoint>, sec> read_ret;
do {
read_ret = read(sock, buf);
if (auto read_res = get_if<std::pair<size_t, ip_endpoint>>(&read_ret)) {
buf.resize(read_res->first);
} else if (get<sec>(read_ret) != sec::unavailable_or_would_block) {
return make_error(get<sec>(read_ret), "read failed");
}
if (++receive_attempts > 100)
return make_error(sec::runtime_error,
"too many unavailable_or_would_blocks");
} while (read_ret.index() != 0);
return none;
}
struct header {
header(size_t payload_size) : payload_size(payload_size) {
// nop
}
header() : header(0) {
// nop
}
template <class Inspector>
friend bool inspect(Inspector& f, header& x) {
return f.object(x).fields(f.field("payload_size", x.payload_size));
}
size_t payload_size;
};
} // namespace
CAF_TEST_FIXTURE_SCOPE(udp_datagram_socket_test, fixture)
CAF_TEST(socket creation) {
ip_endpoint ep;
CAF_CHECK_EQUAL(parse("0.0.0.0:0", ep), none);
auto ret = make_udp_datagram_socket(ep);
if (!ret)
CAF_FAIL("socket creation failed: " << ret.error());
CAF_CHECK_EQUAL(local_port(ret->first), ret->second);
}
CAF_TEST(read / write using span<byte>) {
if (auto err = nonblocking(socket_cast<net::socket>(receive_socket), true))
CAF_FAIL("setting socket to nonblocking failed: " << err);
auto read_res = read(receive_socket, buf);
if (CAF_CHECK(holds_alternative<sec>(read_res)))
CAF_CHECK(get<sec>(read_res) == sec::unavailable_or_would_block);
CAF_MESSAGE("sending data to " << to_string(ep));
auto write_res = write(send_socket, as_bytes(make_span(hello_test)), ep);
if (CAF_CHECK(holds_alternative<size_t>(write_res)))
CAF_CHECK_EQUAL(get<size_t>(write_res), hello_test.size());
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
string_view received{reinterpret_cast<const char*>(buf.data()), buf.size()};
CAF_CHECK_EQUAL(received, hello_test);
}
CAF_TEST(read / write using span<byte_buffer*>) {
// generate header and payload in separate buffers
header hdr{hello_test.size()};
byte_buffer hdr_buf;
binary_serializer sink(sys, hdr_buf);
if (!sink.apply(hdr))
CAF_FAIL("failed to serialize payload: " << sink.get_error());
auto bytes = as_bytes(make_span(hello_test));
byte_buffer payload_buf(bytes.begin(), bytes.end());
auto packet_size = hdr_buf.size() + payload_buf.size();
std::vector<byte_buffer*> bufs{&hdr_buf, &payload_buf};
auto write_res = write(send_socket, bufs, ep);
if (CAF_CHECK(holds_alternative<size_t>(write_res)))
CAF_CHECK_EQUAL(get<size_t>(write_res), packet_size);
// receive both as one single packet.
buf.resize(packet_size);
CAF_CHECK_EQUAL(read_from_socket(receive_socket, buf), none);
CAF_CHECK_EQUAL(buf.size(), packet_size);
binary_deserializer source(nullptr, buf);
header recv_hdr;
if (!source.apply(recv_hdr))
CAF_FAIL("failed to deserialize header: " << source.get_error());
CAF_CHECK_EQUAL(hdr.payload_size, recv_hdr.payload_size);
string_view received{reinterpret_cast<const char*>(buf.data())
+ sizeof(header),
buf.size() - sizeof(header)};
CAF_CHECK_EQUAL(received, hello_test);
}
CAF_TEST_FIXTURE_SCOPE_END()
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