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

Add scaffold for socket abstractions

parent 8325b104
cmake_minimum_required(VERSION 2.8.12)
project(caf_net C CXX)
# get header files; only needed by CMake generators,
# e.g., for creating proper Xcode projects
file(GLOB_RECURSE LIBCAF_NET_HDRS "caf/*.hpp")
# list cpp files excluding platform-dependent files
set(LIBCAF_NET_SRCS
src/socket.cpp
)
add_custom_target(libcaf_net)
# build shared library if not compiling static only
if (NOT CAF_BUILD_STATIC_ONLY)
add_library(libcaf_net_shared SHARED ${LIBCAF_NET_SRCS} ${LIBCAF_NET_HDRS})
target_link_libraries(libcaf_net_shared ${CAF_EXTRA_LDFLAGS} ${CAF_LIBRARY_CORE})
target_include_directories(libcaf_net_shared PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
set_target_properties(libcaf_net_shared
PROPERTIES
SOVERSION ${CAF_VERSION}
VERSION ${CAF_VERSION}
OUTPUT_NAME caf_net
)
install(TARGETS libcaf_net_shared
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
)
add_dependencies(libcaf_net_shared libcaf_net)
endif ()
# build static library only if --build-static or --build-static-only was set
if (CAF_BUILD_STATIC_ONLY OR CAF_BUILD_STATIC)
add_library(libcaf_net_static STATIC ${LIBCAF_NET_HDRS} ${LIBCAF_NET_SRCS})
target_link_libraries(libcaf_net_static ${CAF_EXTRA_LDFLAGS} ${CAF_LIBRARY_CORE_STATIC})
target_include_directories(libcaf_net_static PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
set_target_properties(libcaf_net_static PROPERTIES OUTPUT_NAME caf_net_static)
install(TARGETS libcaf_net_static ARCHIVE DESTINATION lib)
add_dependencies(libcaf_net_static libcaf_net)
endif ()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/caf"
DESTINATION include
FILES_MATCHING PATTERN "*.hpp")
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <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)
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
// 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"
#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 <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>
#endif
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstddef>
#include <limits>
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
template <class Derived>
struct abstract_socket {
socket_id id;
constexpr abstract_socket(socket_id id) : id(id) {
// nop
}
constexpr abstract_socket(const Derived& other) : id(other.id) {
// nop
}
abstract_socket& operator=(const Derived& other) {
id = other.id;
return *this;
}
template <class Inspector>
friend typename Inspector::result_type inspect(Inspector& f, Derived& x) {
return f(x.id);
}
friend constexpr bool operator==(Derived x, Derived y) {
return x.id == y.id;
}
friend constexpr bool operator!=(Derived x, Derived y) {
return x.id != y.id;
}
friend constexpr bool operator<(Derived x, Derived y) {
return x.id < y.id;
}
};
} // namespace net
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <string>
#include <type_traits>
#include "caf/config.hpp"
#include "caf/fwd.hpp"
#include "caf/net/abstract_socket.hpp"
#include "caf/net/socket_id.hpp"
namespace caf {
namespace net {
/// An internal endpoint for sending or receiving data. Can be either a
/// ::network_socket or a ::pipe_socket.
struct socket : abstract_socket<socket> {
using super = abstract_socket<socket>;
using super::super;
};
/// Denotes the invalid socket.
constexpr auto invalid_socket = socket{invalid_socket_id};
/// Close socket `x`.
/// @relates socket
void close(socket x);
/// Returns the last socket error in this thread as an integer.
/// @relates socket
int last_socket_error();
/// Returns the last socket error as human-readable string.
/// @relates socket
std::string last_socket_error_as_string();
/// Returns a human-readable string for a given socket error.
/// @relates socket
std::string socket_error_as_string(int errcode);
/// Returns whether `errcode` indicates that an operation would block or return
/// nothing at the moment and can be tried again at a later point.
/// @relates socket
bool would_block_or_temporarily_unavailable(int errcode);
/// 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 child_process_inherit(socket x, bool new_value);
/// Enables or disables nonblocking I/O on `x`.
/// @relates socket
error nonblocking(socket x, bool new_value);
/// Convenience functions for checking the result of `recv` or `send`.
/// @relates socket
bool is_error(std::make_signed<size_t>::type res, bool is_nonblock);
} // namespace net
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstddef>
#include <limits>
#include "caf/config.hpp"
namespace caf {
namespace 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
} // namespace net
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/net/socket.hpp"
#include "caf/config.hpp"
#include "caf/detail/net_syscall.hpp"
#include "caf/detail/socket_sys_includes.hpp"
#include "caf/logger.hpp"
namespace caf {
namespace net {
#ifdef CAF_WINDOWS
void close(socket fd) {
closesocket(fd.id);
}
int last_socket_error() {
return WSAGetLastError();
}
std::string socket_error_as_string(int errcode) {
LPTSTR errorText = NULL;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, errcode, 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;
}
std::string last_socket_error_as_string() {
return socket_error_as_string(last_socket_error());
}
bool would_block_or_temporarily_unavailable(int errcode) {
return errcode == WSAEWOULDBLOCK || errcode == WSATRY_AGAIN;
}
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) {
::close(fd.id);
}
int last_socket_error() {
return errno;
}
std::string socket_error_as_string(int error_code) {
return strerror(error_code);
}
std::string last_socket_error_as_string() {
return strerror(errno);
}
bool would_block_or_temporarily_unavailable(int errcode) {
return errcode == EAGAIN || errcode == EWOULDBLOCK;
}
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
bool is_error(std::make_signed<size_t>::type res, bool is_nonblock) {
if (res < 0) {
auto err = last_socket_error();
return !is_nonblock || !would_block_or_temporarily_unavailable(err);
}
return false;
}
} // namespace net
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2019 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE socket
#include "caf/net/socket.hpp"
#include "caf/test/dsl.hpp"
using namespace caf;
using namespace caf::net;
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);
}
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