Initial public khodo snapshot
publish / publish (push) Successful in 31m41s

This commit is contained in:
khodowany
2026-09-04 19:30:11 -05:00
commit 67429eacd1
34 changed files with 8279 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <ligo/config.hpp>
#include <ligo/http.hpp>
#include <ligo/net.hpp>
#include <ligo/security.hpp>
#include <ligo/service.hpp>
#include <ligo/detail/errors.hpp>
#include <ligo/tcp.hpp>
#include <ligo/udp.hpp>
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <expected>
#include <chrono>
#include <cstdint>
#include <string>
#include <vector>
#include <boost/json/value.hpp>
#include <modulus.hpp>
#include <ligo/security.hpp>
namespace ligo {
enum class protocol {
HTTP,
TCP,
UDP,
};
struct udp_reliability_config {
std::chrono::milliseconds resend_interval{50};
std::uint32_t max_in_flight{64};
std::chrono::milliseconds session_timeout{5000};
};
struct http_config {
std::uint64_t max_body_size{16U * 1024U * 1024U};
};
struct endpoint_config {
std::string name{};
ligo::protocol protocol{ligo::protocol::TCP};
std::string bind{};
std::string connect{};
std::chrono::milliseconds timeout{5000};
ligo::security_config security{};
ligo::http_config http{};
ligo::udp_reliability_config udp{};
};
auto validate(ligo::endpoint_config const& config) -> std::expected<void, modulus::error>;
auto parse_endpoint_config(boost::json::value const& value) -> std::expected<ligo::endpoint_config, modulus::error>;
auto parse_endpoint_configs(boost::json::value const& value) -> std::expected<std::vector<ligo::endpoint_config>, modulus::error>;
auto load_endpoint_configs(std::string const& path) -> std::expected<std::vector<ligo::endpoint_config>, modulus::error>;
auto to_json(ligo::udp_reliability_config const& config) -> boost::json::value;
auto to_json(ligo::http_config const& config) -> boost::json::value;
auto to_json(ligo::security_config const& config) -> boost::json::value;
auto to_json(ligo::endpoint_config const& config) -> boost::json::value;
auto to_json(std::vector<ligo::endpoint_config> const& configs) -> boost::json::value;
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <expected>
#include <string>
#include <utility>
#include <modulus.hpp>
namespace ligo::detail {
constexpr std::int32_t error_ok{0};
constexpr std::int32_t error_invalid_argument{1};
constexpr std::int32_t error_not_found{2};
constexpr std::int32_t error_unavailable{3};
constexpr std::int32_t error_timeout{4};
constexpr std::int32_t error_internal{5};
auto make_error(std::int32_t code, std::string message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> modulus::error;
auto fail_void(modulus::error error) -> std::expected<void, modulus::error>;
template <typename VALUE>
auto fail(modulus::error error) -> std::expected<VALUE, modulus::error>;
template <typename VALUE>
auto fail(std::expected<void, modulus::error> const& result) -> std::expected<VALUE, modulus::error>;
template <typename VALUE, typename MESSAGE>
auto fail(std::int32_t code, MESSAGE&& message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> std::expected<VALUE, modulus::error>;
template <typename MESSAGE>
auto fail_void(std::int32_t code, MESSAGE&& message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> std::expected<void, modulus::error>;
}
template <typename VALUE>
auto ligo::detail::fail(modulus::error error) -> std::expected<VALUE, modulus::error> {
return modulus::fail<VALUE>(std::move(error));
}
template <typename VALUE>
auto ligo::detail::fail(std::expected<void, modulus::error> const& result) -> std::expected<VALUE, modulus::error> {
assert(!result);
return ligo::detail::fail<VALUE>(result.error());
}
template <typename VALUE, typename MESSAGE>
auto ligo::detail::fail(std::int32_t code, MESSAGE&& message, modulus::source_location location) -> std::expected<VALUE, modulus::error> {
return modulus::fail<VALUE>(ligo::detail::make_error(code, std::string{std::forward<MESSAGE>(message)}, location));
}
template <typename MESSAGE>
auto ligo::detail::fail_void(std::int32_t code, MESSAGE&& message, modulus::source_location location) -> std::expected<void, modulus::error> {
return modulus::fail<void>(ligo::detail::make_error(code, std::string{std::forward<MESSAGE>(message)}, location));
}
+242
View File
@@ -0,0 +1,242 @@
#pragma once
#include <expected>
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <set>
#include <vector>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/this_coro.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/beast/core.hpp>
#include <boost/system/error_code.hpp>
#include <modulus.hpp>
#include <ligo/detail/errors.hpp>
#include <ligo/detail/transport.hpp>
namespace ligo::detail {
auto cancel_session_stream(boost::beast::tcp_stream& stream) -> void;
template <typename NEXT_LAYER>
auto cancel_session_stream(boost::asio::ssl::stream<NEXT_LAYER>& stream) -> void;
template <typename S>
struct session_registry {
static auto cancel_waiters(std::vector<std::shared_ptr<boost::asio::steady_timer>> const& live) -> void;
auto add(std::shared_ptr<S> const& stream) -> void;
auto remove(std::shared_ptr<S> const& stream) -> void;
auto cancel_all() -> void;
auto empty() const -> std::bool_t;
auto wait_empty(std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto notify_waiters() -> void;
auto live_waiters_locked() -> std::vector<std::shared_ptr<boost::asio::steady_timer>>;
mutable std::mutex mutex{};
std::set<std::shared_ptr<S>> sessions{};
std::vector<std::weak_ptr<boost::asio::steady_timer>> waiters{};
};
template <typename S>
struct session_guard {
~session_guard();
auto release() -> void;
std::shared_ptr<ligo::detail::session_registry<S>> registry{};
std::shared_ptr<S> stream{};
};
template <typename S>
auto stop_accepting_server(std::shared_ptr<S> state) -> void;
template <typename S>
auto stop_accepting_server_now(std::shared_ptr<S> const& state) -> void;
template <typename S>
auto async_stop_accepting_server(std::shared_ptr<S> state, std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
}
template <typename NEXT_LAYER>
auto ligo::detail::cancel_session_stream(boost::asio::ssl::stream<NEXT_LAYER>& stream) -> void {
auto ignored = boost::system::error_code{};
stream.lowest_layer().cancel(ignored);
stream.lowest_layer().close(ignored);
}
template <typename S>
auto ligo::detail::session_registry<S>::cancel_waiters(std::vector<std::shared_ptr<boost::asio::steady_timer>> const& live) -> void {
for (auto& waiter : live) {
waiter->cancel();
}
}
template <typename S>
auto ligo::detail::session_registry<S>::add(std::shared_ptr<S> const& stream) -> void {
auto lock = std::scoped_lock{mutex};
sessions.insert(stream);
}
template <typename S>
auto ligo::detail::session_registry<S>::remove(std::shared_ptr<S> const& stream) -> void {
auto live = std::vector<std::shared_ptr<boost::asio::steady_timer>>{};
{
auto lock = std::scoped_lock{mutex};
sessions.erase(stream);
live = live_waiters_locked();
}
cancel_waiters(live);
}
template <typename S>
auto ligo::detail::session_registry<S>::cancel_all() -> void {
auto copy = std::vector<std::shared_ptr<S>>{};
{
auto lock = std::scoped_lock{mutex};
copy.assign(sessions.begin(), sessions.end());
}
for (auto& stream : copy) {
boost::asio::post(stream->get_executor(), [stream] {
ligo::detail::cancel_session_stream(*stream);
});
}
}
template <typename S>
auto ligo::detail::session_registry<S>::empty() const -> std::bool_t {
auto lock = std::scoped_lock{mutex};
return sessions.empty();
}
template <typename S>
auto ligo::detail::session_registry<S>::wait_empty(std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
auto valid_timeout = ligo::detail::validate_positive_timeout(timeout, "shutdown timeout");
if (!valid_timeout) {
co_return valid_timeout;
}
auto executor = co_await boost::asio::this_coro::executor;
if (empty()) {
co_return std::expected<void, modulus::error>{};
}
auto notify_timer = std::make_shared<boost::asio::steady_timer>(executor);
auto deadline_timer = std::make_shared<boost::asio::steady_timer>(executor);
auto timed_out = std::make_shared<std::atomic<std::bool_t>>(false);
{
auto lock = std::scoped_lock{mutex};
waiters.push_back(notify_timer);
}
deadline_timer->expires_after(timeout);
deadline_timer->async_wait([notify_timer, timed_out](boost::system::error_code const& error) {
if (!error) {
timed_out->store(true, std::memory_order_relaxed);
notify_timer->cancel();
}
});
for (;;) {
if (empty()) {
deadline_timer->cancel();
co_return std::expected<void, modulus::error>{};
}
if (timed_out->load(std::memory_order_relaxed)) {
co_return ligo::detail::fail_void(ligo::detail::error_timeout, "timed out waiting for sessions to drain");
}
notify_timer->expires_at(std::chrono::steady_clock::time_point::max());
auto [error] = co_await notify_timer->async_wait(boost::asio::as_tuple(boost::asio::use_awaitable));
if (error && error != boost::asio::error::operation_aborted) {
co_return ligo::detail::fail_void(ligo::detail::network_failure(error));
}
}
}
template <typename S>
auto ligo::detail::session_registry<S>::notify_waiters() -> void {
auto live = std::vector<std::shared_ptr<boost::asio::steady_timer>>{};
{
auto lock = std::scoped_lock{mutex};
live = live_waiters_locked();
}
cancel_waiters(live);
}
template <typename S>
auto ligo::detail::session_registry<S>::live_waiters_locked() -> std::vector<std::shared_ptr<boost::asio::steady_timer>> {
auto live = std::vector<std::shared_ptr<boost::asio::steady_timer>>{};
auto retained = std::vector<std::weak_ptr<boost::asio::steady_timer>>{};
for (auto& waiter : waiters) {
if (auto locked = waiter.lock()) {
live.push_back(locked);
retained.push_back(locked);
}
}
waiters = std::move(retained);
return live;
}
template <typename S>
ligo::detail::session_guard<S>::~session_guard() {
if (registry && stream) {
registry->remove(stream);
}
}
template <typename S>
auto ligo::detail::session_guard<S>::release() -> void {
if (registry && stream) {
registry->remove(stream);
stream.reset();
registry.reset();
}
}
template <typename S>
auto ligo::detail::stop_accepting_server(std::shared_ptr<S> state) -> void {
auto lock = std::scoped_lock{state->lifecycle_mutex};
if (state->stopped.exchange(true)) {
return;
}
state->sessions->cancel_all();
auto server_executor = state->strand;
boost::asio::post(server_executor, [state = std::move(state)] {
auto ignored = boost::system::error_code{};
state->acceptor->close(ignored);
});
}
template <typename S>
auto ligo::detail::stop_accepting_server_now(std::shared_ptr<S> const& state) -> void {
auto lock = std::scoped_lock{state->lifecycle_mutex};
if (state->stopped.exchange(true)) {
return;
}
state->sessions->cancel_all();
auto ignored = boost::system::error_code{};
state->acceptor->close(ignored);
}
template <typename S>
auto ligo::detail::async_stop_accepting_server(std::shared_ptr<S> state, std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
auto valid_timeout = ligo::detail::validate_positive_timeout(timeout, "shutdown timeout");
if (!valid_timeout) {
co_return valid_timeout;
}
auto should_stop = false;
{
auto lock = std::scoped_lock{state->lifecycle_mutex};
should_stop = !state->stopped.exchange(true);
if (should_stop) {
state->sessions->cancel_all();
}
}
if (should_stop) {
co_await boost::asio::post(state->strand, boost::asio::use_awaitable);
auto ignored = boost::system::error_code{};
state->acceptor->close(ignored);
}
co_return co_await state->sessions->wait_empty(timeout);
}
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <utility>
#include <modulus.hpp>
namespace ligo::detail {
class state_handle {
public:
state_handle() = default;
state_handle(ligo::detail::state_handle const&) = delete;
state_handle(ligo::detail::state_handle&& other) noexcept;
~state_handle() = default;
auto operator=(ligo::detail::state_handle const&) -> ligo::detail::state_handle& = delete;
auto operator=(ligo::detail::state_handle&& other) noexcept -> ligo::detail::state_handle&;
explicit operator std::bool_t() const noexcept;
template <typename S>
explicit state_handle(std::shared_ptr<S> state);
template <typename S>
auto as() const -> std::shared_ptr<S>;
private:
std::atomic<std::shared_ptr<void>> state_{};
};
class unique_state {
public:
unique_state() noexcept = default;
unique_state(ligo::detail::unique_state const&) = delete;
unique_state(ligo::detail::unique_state&&) noexcept = default;
~unique_state() = default;
auto operator=(ligo::detail::unique_state const&) -> ligo::detail::unique_state& = delete;
auto operator=(ligo::detail::unique_state&&) noexcept -> ligo::detail::unique_state& = default;
explicit operator std::bool_t() const noexcept;
template <typename STATE>
explicit unique_state(std::unique_ptr<STATE> state);
template <typename STATE>
auto as() const noexcept -> STATE*;
private:
static auto ignore_state(void* state) -> void;
template <typename STATE>
static auto delete_state(void* state) -> void;
std::unique_ptr<void, std::function<void(void*)>> state_{nullptr, ignore_state};
};
}
template <typename STATE>
ligo::detail::state_handle::state_handle(std::shared_ptr<STATE> state) : state_{std::shared_ptr<void>{std::move(state)}} {}
template <typename STATE>
auto ligo::detail::state_handle::as() const -> std::shared_ptr<STATE> {
return std::static_pointer_cast<STATE>(state_.load());
}
template <typename STATE>
ligo::detail::unique_state::unique_state(std::unique_ptr<STATE> state) : state_{state.release(), delete_state<STATE>} {}
template <typename STATE>
auto ligo::detail::unique_state::as() const noexcept -> STATE* {
return static_cast<STATE*>(state_.get());
}
template <typename STATE>
auto ligo::detail::unique_state::delete_state(void* state) -> void {
delete static_cast<STATE*>(state);
}
+117
View File
@@ -0,0 +1,117 @@
#pragma once
#include <expected>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/beast/core.hpp>
#include <boost/json/value.hpp>
#include <boost/system/error_code.hpp>
#include <modulus.hpp>
#include <ligo/detail/errors.hpp>
#include <ligo/service.hpp>
namespace ligo::detail {
struct endpoint_parts {
std::string host{"127.0.0.1"};
std::uint16_t port{};
};
auto status_name(std::int32_t code) -> std::string;
auto parse_status_code(std::string_view value) -> std::int32_t;
auto network_failure(boost::system::error_code const& error) -> modulus::error;
auto operation_failure(boost::system::error_code const& error, std::string timeout_message) -> modulus::error;
auto invalid_argument(std::string message) -> modulus::error;
auto unavailable(std::string message) -> modulus::error;
auto validate_positive_timeout(std::chrono::milliseconds timeout, std::string_view name) -> std::expected<void, modulus::error>;
auto report_error(std::function<void(modulus::error)> const& on_error, modulus::error error) -> void;
auto report_error(std::function<void(modulus::error)> const& on_error, std::expected<void, modulus::error> status) -> void;
auto response_envelope(std::expected<boost::json::value, modulus::error> result) -> boost::json::value;
auto serialize_response_envelope(std::expected<boost::json::value, modulus::error> result) -> std::string;
auto unwrap_response_envelope(boost::json::value value, std::string_view protocol_name) -> std::expected<boost::json::value, modulus::error>;
auto parse_port(std::string_view text) -> std::expected<std::uint16_t, modulus::error>;
auto parse_endpoint_text(std::string_view text, std::uint16_t fallback_port) -> std::expected<ligo::detail::endpoint_parts, modulus::error>;
auto parse_client_endpoint_text(std::string_view text) -> std::expected<ligo::detail::endpoint_parts, modulus::error>;
auto parse_server_endpoint_text(std::string_view text) -> std::expected<ligo::detail::endpoint_parts, modulus::error>;
auto start_tcp_acceptor(boost::asio::ip::tcp::acceptor& acceptor, std::string const& host, std::uint16_t port) -> std::expected<std::uint16_t, modulus::error>;
auto resolve_tcp_endpoints(boost::asio::any_io_executor executor, std::string const& host, std::uint16_t port, std::chrono::milliseconds timeout, std::string timeout_message) -> boost::asio::awaitable<std::expected<boost::asio::ip::tcp::resolver::results_type, modulus::error>>;
auto call_handler(ligo::service_router const& router, ligo::request_context context, boost::json::value payload) -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>>;
auto call_response_handler(ligo::service_router const& router, ligo::request_context context, boost::json::value payload) -> boost::asio::awaitable<std::expected<ligo::service_response, modulus::error>>;
struct stream_deadline {
std::shared_ptr<boost::asio::steady_timer> timer{};
std::shared_ptr<std::atomic<std::bool_t>> timed_out{};
std::shared_ptr<std::atomic<std::bool_t>> active{};
std::shared_ptr<boost::asio::cancellation_signal> cancellation{};
};
auto start_stream_deadline(boost::beast::tcp_stream& stream, std::chrono::milliseconds timeout) -> ligo::detail::stream_deadline;
auto start_stream_deadline(boost::asio::ip::tcp::socket& socket, std::chrono::milliseconds timeout) -> ligo::detail::stream_deadline;
template <typename NEXT_LAYER>
auto start_stream_deadline(boost::asio::ssl::stream<NEXT_LAYER>& stream, std::chrono::milliseconds timeout) -> ligo::detail::stream_deadline;
auto cancel_stream_deadline(ligo::detail::stream_deadline& deadline) -> void;
auto deadline_token(ligo::detail::stream_deadline& deadline) -> decltype(boost::asio::bind_cancellation_slot(std::declval<boost::asio::cancellation_slot>(), boost::asio::as_tuple(boost::asio::use_awaitable)));
auto operation_failure(boost::system::error_code const& error, ligo::detail::stream_deadline const& deadline, std::string timeout_message) -> modulus::error;
template <typename NEXT_LAYER>
auto async_tls_handshake(boost::asio::ssl::stream<NEXT_LAYER>& stream, boost::asio::ssl::stream_base::handshake_type type, std::chrono::milliseconds timeout, std::string timeout_message) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
template <typename NEXT_LAYER>
auto async_tls_shutdown(boost::asio::ssl::stream<NEXT_LAYER>& stream, std::chrono::milliseconds timeout, std::string timeout_message) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
}
template <typename NEXT_LAYER>
auto ligo::detail::start_stream_deadline(boost::asio::ssl::stream<NEXT_LAYER>& stream, std::chrono::milliseconds timeout) -> ligo::detail::stream_deadline {
auto deadline = ligo::detail::stream_deadline{.timer = std::make_shared<boost::asio::steady_timer>(stream.get_executor()), .timed_out = std::make_shared<std::atomic<std::bool_t>>(false), .active = std::make_shared<std::atomic<std::bool_t>>(true), .cancellation = std::make_shared<boost::asio::cancellation_signal>()};
deadline.timer->expires_after(timeout);
deadline.timer->async_wait([timer = deadline.timer, timed_out = deadline.timed_out, active = deadline.active, cancellation = deadline.cancellation](boost::system::error_code const& error) {
if (!error && active->exchange(false, std::memory_order_acq_rel)) {
timed_out->store(true, std::memory_order_release);
cancellation->emit(boost::asio::cancellation_type::terminal);
}
});
return deadline;
}
template <typename NEXT_LAYER>
auto ligo::detail::async_tls_handshake(boost::asio::ssl::stream<NEXT_LAYER>& stream, boost::asio::ssl::stream_base::handshake_type type, std::chrono::milliseconds timeout, std::string timeout_message) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
auto deadline = ligo::detail::start_stream_deadline(stream, timeout);
auto [error] = co_await stream.async_handshake(type, ligo::detail::deadline_token(deadline));
ligo::detail::cancel_stream_deadline(deadline);
if (error) {
co_return ligo::detail::fail_void(ligo::detail::operation_failure(error, deadline, std::move(timeout_message)));
}
co_return std::expected<void, modulus::error>{};
}
template <typename NEXT_LAYER>
auto ligo::detail::async_tls_shutdown(boost::asio::ssl::stream<NEXT_LAYER>& stream, std::chrono::milliseconds timeout, std::string timeout_message) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
auto deadline = ligo::detail::start_stream_deadline(stream, timeout);
auto [error] = co_await stream.async_shutdown(ligo::detail::deadline_token(deadline));
ligo::detail::cancel_stream_deadline(deadline);
if (error) {
co_return ligo::detail::fail_void(ligo::detail::operation_failure(error, deadline, std::move(timeout_message)));
}
co_return std::expected<void, modulus::error>{};
}
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include <expected>
#include <chrono>
#include <cstdint>
#include <functional>
#include <string>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/json/value.hpp>
#include <modulus.hpp>
#include <ligo/detail/state.hpp>
#include <ligo/config.hpp>
#include <ligo/security.hpp>
#include <ligo/service.hpp>
namespace ligo::http {
enum class method {
GET,
POST,
PUT,
PATCH,
DELETE,
};
struct endpoint {
std::string host{"127.0.0.1"};
std::uint16_t port{0};
};
struct request {
ligo::http::method method{ligo::http::method::POST};
std::string target{"/"};
boost::json::value payload{};
};
struct client_options {
std::chrono::milliseconds timeout{5000};
std::uint64_t max_body_size{16U * 1024U * 1024U};
ligo::security_config security{};
};
struct server_options {
std::chrono::milliseconds timeout{5000};
std::uint64_t max_body_size{16U * 1024U * 1024U};
std::function<void(modulus::error)> on_error{};
ligo::security_config security{};
};
class client {
public:
explicit client(ligo::http::endpoint endpoint, ligo::http::client_options options = {});
auto call(ligo::http::request const& request) const -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>>;
private:
ligo::http::endpoint endpoint_{};
ligo::http::client_options options_{};
};
class server {
public:
server(boost::asio::io_context& io, ligo::http::endpoint endpoint, ligo::service_router router, ligo::http::server_options options = {});
server(boost::asio::any_io_executor executor, ligo::http::endpoint endpoint, ligo::service_router router, ligo::http::server_options options = {});
server(ligo::http::server const&) = delete;
server(ligo::http::server&&) noexcept = default;
~server();
auto operator=(ligo::http::server const&) -> ligo::http::server& = delete;
auto operator=(ligo::http::server&&) noexcept -> ligo::http::server&;
auto start() -> std::expected<void, modulus::error>;
auto stop() -> void;
auto async_stop(std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto selected_port() const -> std::uint16_t;
private:
ligo::detail::state_handle state_{};
};
auto client_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::http::endpoint, modulus::error>;
auto server_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::http::endpoint, modulus::error>;
auto client_options_from_config(ligo::endpoint_config const& config) -> ligo::http::client_options;
auto server_options_from_config(ligo::endpoint_config const& config) -> ligo::http::server_options;
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <expected>
#include <chrono>
#include <cstddef>
#include <memory>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/system/error_code.hpp>
#include <modulus.hpp>
#include <ligo/detail/state.hpp>
#include <ligo/config.hpp>
#include <ligo/security.hpp>
#include <ligo/tcp.hpp>
#include <cstdint>
namespace ligo::net {
class byte_stream_state;
class byte_stream {
public:
byte_stream() = default;
byte_stream(ligo::net::byte_stream const&) = delete;
byte_stream(ligo::net::byte_stream&&) noexcept = default;
~byte_stream();
auto operator=(ligo::net::byte_stream const&) -> ligo::net::byte_stream& = delete;
auto operator=(ligo::net::byte_stream&&) noexcept -> ligo::net::byte_stream&;
static auto from_socket(ligo::tcp::socket_handle socket) -> std::expected<ligo::net::byte_stream, modulus::error>;
static auto from_tls_socket(ligo::tcp::tls_socket socket) -> std::expected<ligo::net::byte_stream, modulus::error>;
auto async_read_some(boost::asio::mutable_buffer buffer, std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<std::size_t, modulus::error>>;
auto async_write_some(boost::asio::const_buffer buffer, std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<std::size_t, modulus::error>>;
auto async_write_all(boost::asio::const_buffer buffer, std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto async_shutdown(std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto close(boost::system::error_code& error) -> void;
auto is_open() const -> std::bool_t;
private:
explicit byte_stream(std::shared_ptr<ligo::net::byte_stream_state> state);
std::shared_ptr<ligo::net::byte_stream_state> state_{};
};
class listener {
public:
listener() = default;
listener(ligo::net::listener const&) = delete;
listener(ligo::net::listener&&) noexcept = default;
~listener();
auto operator=(ligo::net::listener const&) -> ligo::net::listener& = delete;
auto operator=(ligo::net::listener&&) noexcept -> ligo::net::listener&;
static auto bind(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint) -> std::expected<ligo::net::listener, modulus::error>;
auto selected_port() const -> std::uint16_t;
auto async_accept() -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto async_accept_tls_configured(ligo::security_config security, std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
auto async_accept_tls_handshaken(ligo::security_config security, std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
auto close() -> void;
private:
explicit listener(ligo::detail::state_handle state);
ligo::detail::state_handle state_{};
};
auto connect_tcp(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}) -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto connect(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}) -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto connect(boost::asio::any_io_executor executor, ligo::endpoint_config const& config) -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto connect_tls_configured(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}, ligo::security_config security = {.mode = ligo::security_mode::TLS}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
auto connect_tls_handshaken(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}, ligo::security_config security = {.mode = ligo::security_mode::TLS}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <expected>
#include <memory>
#include <string>
#include <string_view>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <modulus.hpp>
namespace ligo {
enum class security_mode {
NONE,
TLS,
DTLS,
};
enum class verify_mode {
DEFAULTED,
NONE,
PEER,
};
struct security_config {
ligo::security_mode mode{ligo::security_mode::NONE};
ligo::verify_mode verify{ligo::verify_mode::DEFAULTED};
std::string peer_name{};
std::string ca_file{};
std::string certificate_chain_file{};
std::string private_key_file{};
std::shared_ptr<boost::asio::ssl::context> tls_context{};
};
auto make_tls_client_context(ligo::security_config const& config) -> std::expected<std::shared_ptr<boost::asio::ssl::context>, modulus::error>;
auto make_tls_server_context(ligo::security_config const& config) -> std::expected<std::shared_ptr<boost::asio::ssl::context>, modulus::error>;
auto apply_tls_client_verification(boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& stream, std::string const& host, ligo::security_config const& config) -> std::expected<void, modulus::error>;
auto configure_tls_client_stream(boost::asio::ssl::stream<boost::asio::ip::tcp::socket>& stream, std::string const& host, ligo::security_config const& config) -> std::expected<void, modulus::error>;
auto validate_tls_server_config(std::string_view protocol, ligo::security_config const& config) -> std::expected<void, modulus::error>;
auto validate_tls_server_context(std::shared_ptr<boost::asio::ssl::context> const& context) -> std::expected<void, modulus::error>;
auto unsupported_security(std::string_view protocol, ligo::security_mode mode) -> modulus::error;
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <expected>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <boost/asio/awaitable.hpp>
#include <boost/json/value.hpp>
#include <modulus.hpp>
namespace ligo {
struct request_context {
std::string peer{};
std::string method{};
std::string route{};
};
struct service_response {
std::string content_type{"application/json"};
std::string body{};
};
class service_router {
public:
service_router() = default;
service_router(ligo::service_router const& other);
service_router(ligo::service_router&& other);
~service_router() = default;
auto operator=(ligo::service_router const& other) -> ligo::service_router&;
auto operator=(ligo::service_router&& other) -> ligo::service_router&;
auto add_route(std::string method, std::string route, std::function<boost::asio::awaitable<std::expected<boost::json::value, modulus::error>>(ligo::request_context, boost::json::value)> handler) -> void;
auto add_response_route(std::string method, std::string route, std::function<boost::asio::awaitable<std::expected<ligo::service_response, modulus::error>>(ligo::request_context, boost::json::value)> handler) -> void;
auto call(ligo::request_context context, boost::json::value payload) const -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>>;
auto call_response(ligo::request_context context, boost::json::value payload) const -> boost::asio::awaitable<std::expected<ligo::service_response, modulus::error>>;
private:
mutable std::mutex mutex_{};
std::map<std::pair<std::string, std::string>, std::shared_ptr<std::function<boost::asio::awaitable<std::expected<ligo::service_response, modulus::error>>(ligo::request_context, boost::json::value)> const>> handlers_{};
};
auto json_response(boost::json::value payload) -> ligo::service_response;
auto text_response(std::string content_type, std::string body) -> ligo::service_response;
}
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include <expected>
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/json/value.hpp>
#include <boost/system/error_code.hpp>
#include <modulus.hpp>
#include <ligo/detail/state.hpp>
#include <ligo/config.hpp>
#include <ligo/security.hpp>
#include <ligo/service.hpp>
namespace ligo::tcp {
struct endpoint {
std::string host{"127.0.0.1"};
std::uint16_t port{0};
};
struct socket_handle {
socket_handle() = default;
socket_handle(ligo::tcp::socket_handle const&) = delete;
socket_handle(ligo::tcp::socket_handle&&) noexcept = default;
explicit socket_handle(boost::asio::ip::tcp::socket socket);
explicit socket_handle(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
auto operator=(ligo::tcp::socket_handle const&) -> ligo::tcp::socket_handle& = delete;
auto operator=(ligo::tcp::socket_handle&&) noexcept -> ligo::tcp::socket_handle& = default;
auto socket() -> std::expected<std::reference_wrapper<boost::asio::ip::tcp::socket>, modulus::error>;
auto socket() const -> std::expected<std::reference_wrapper<boost::asio::ip::tcp::socket const>, modulus::error>;
auto is_open() const -> std::bool_t;
auto close(boost::system::error_code& error) -> void;
auto release_socket() -> std::expected<boost::asio::ip::tcp::socket, modulus::error>;
private:
std::shared_ptr<boost::asio::ip::tcp::socket> socket_{};
};
struct tls_socket {
struct released_stream {
released_stream(std::shared_ptr<boost::asio::ssl::context> context, boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream);
std::shared_ptr<boost::asio::ssl::context> context{};
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream;
};
tls_socket() = default;
tls_socket(ligo::tcp::tls_socket const&) = delete;
tls_socket(ligo::tcp::tls_socket&&) noexcept = default;
tls_socket(std::shared_ptr<boost::asio::ssl::context> context, boost::asio::ip::tcp::socket socket);
auto operator=(ligo::tcp::tls_socket const&) -> ligo::tcp::tls_socket& = delete;
auto operator=(ligo::tcp::tls_socket&&) noexcept -> ligo::tcp::tls_socket& = default;
auto stream() -> std::expected<std::reference_wrapper<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>, modulus::error>;
auto stream() const -> std::expected<std::reference_wrapper<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> const>, modulus::error>;
auto context() -> std::expected<std::reference_wrapper<boost::asio::ssl::context>, modulus::error>;
auto context() const -> std::expected<std::reference_wrapper<boost::asio::ssl::context const>, modulus::error>;
auto is_open() const -> std::bool_t;
auto close(boost::system::error_code& error) -> void;
auto release_stream() -> std::expected<ligo::tcp::tls_socket::released_stream, modulus::error>;
private:
std::shared_ptr<boost::asio::ssl::context> context_{};
std::unique_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> stream_{};
};
struct request {
std::string route{};
boost::json::value payload{};
};
struct client_options {
std::chrono::milliseconds timeout{5000};
ligo::security_config security{};
};
struct server_options {
std::chrono::milliseconds timeout{5000};
std::function<void(modulus::error)> on_error{};
ligo::security_config security{};
};
class client {
public:
explicit client(ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {});
auto call(ligo::tcp::request const& request) const -> boost::asio::awaitable<std::expected<boost::json::value, modulus::error>>;
private:
ligo::tcp::endpoint endpoint_{};
ligo::tcp::client_options options_{};
};
class server {
public:
server(boost::asio::io_context& io, ligo::tcp::endpoint endpoint, ligo::service_router router, ligo::tcp::server_options options = {});
server(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::service_router router, ligo::tcp::server_options options = {});
server(ligo::tcp::server const&) = delete;
server(ligo::tcp::server&&) noexcept = default;
~server();
auto operator=(ligo::tcp::server const&) -> ligo::tcp::server& = delete;
auto operator=(ligo::tcp::server&&) noexcept -> ligo::tcp::server&;
auto start() -> std::expected<void, modulus::error>;
auto stop() -> void;
auto async_stop(std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto selected_port() const -> std::uint16_t;
private:
ligo::detail::state_handle state_{};
};
auto client_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::tcp::endpoint, modulus::error>;
auto server_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::tcp::endpoint, modulus::error>;
auto client_options_from_config(ligo::endpoint_config const& config) -> ligo::tcp::client_options;
auto server_options_from_config(ligo::endpoint_config const& config) -> ligo::tcp::server_options;
auto connect(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}) -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto connect(boost::asio::any_io_executor executor, ligo::endpoint_config const& config) -> boost::asio::awaitable<std::expected<ligo::tcp::socket_handle, modulus::error>>;
auto connect_tls_configured(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}, ligo::security_config security = {.mode = ligo::security_mode::TLS}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
auto connect_tls_handshaken(boost::asio::any_io_executor executor, ligo::tcp::endpoint endpoint, ligo::tcp::client_options options = {}, ligo::security_config security = {.mode = ligo::security_mode::TLS}) -> boost::asio::awaitable<std::expected<ligo::tcp::tls_socket, modulus::error>>;
inline tls_socket::released_stream::released_stream(std::shared_ptr<boost::asio::ssl::context> context, boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream) : context{std::move(context)}, stream{std::move(stream)} {}
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <expected>
#include <chrono>
#include <cstdint>
#include <memory>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/json/value.hpp>
#include <modulus.hpp>
#include <ligo/detail/state.hpp>
#include <ligo/config.hpp>
#include <ligo/security.hpp>
namespace ligo::udp {
struct endpoint {
boost::asio::ip::udp::endpoint local{boost::asio::ip::udp::v4(), 0};
boost::asio::ip::udp::endpoint peer{boost::asio::ip::address_v4::loopback(), 0};
};
struct message {
boost::json::value payload{};
};
struct client_options {
ligo::udp_reliability_config reliability{};
ligo::security_config security{};
std::uint64_t session_id{1};
};
struct server_options {
ligo::udp_reliability_config reliability{};
ligo::security_config security{};
std::uint64_t session_id{1};
};
class client {
public:
client(ligo::udp::client const&) = delete;
client(ligo::udp::client&&) noexcept = default;
~client();
auto operator=(ligo::udp::client const&) -> ligo::udp::client& = delete;
auto operator=(ligo::udp::client&&) noexcept -> ligo::udp::client&;
static auto open(boost::asio::any_io_executor executor, boost::asio::ip::udp::endpoint peer, ligo::udp::client_options options = {}) -> std::expected<ligo::udp::client, modulus::error>;
static auto open(boost::asio::io_context& io, boost::asio::ip::udp::endpoint peer, ligo::udp::client_options options = {}) -> std::expected<ligo::udp::client, modulus::error>;
static auto bind(boost::asio::any_io_executor executor, ligo::udp::endpoint endpoint, ligo::udp::client_options options = {}) -> std::expected<ligo::udp::client, modulus::error>;
static auto bind(boost::asio::io_context& io, ligo::udp::endpoint endpoint, ligo::udp::client_options options = {}) -> std::expected<ligo::udp::client, modulus::error>;
static auto adopt(std::unique_ptr<boost::asio::ip::udp::socket> socket, boost::asio::ip::udp::endpoint peer, ligo::udp::client_options options = {}) -> std::expected<ligo::udp::client, modulus::error>;
auto send(boost::json::value payload) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto send(ligo::udp::message message) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto receive() -> boost::asio::awaitable<std::expected<ligo::udp::message, modulus::error>>;
auto local_endpoint() const -> std::expected<boost::asio::ip::udp::endpoint, modulus::error>;
auto close() -> void;
auto async_close(std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
private:
explicit client(ligo::detail::state_handle state);
ligo::detail::state_handle state_{};
};
class server {
public:
server(ligo::udp::server const&) = delete;
server(ligo::udp::server&&) noexcept = default;
~server();
auto operator=(ligo::udp::server const&) -> ligo::udp::server& = delete;
auto operator=(ligo::udp::server&&) noexcept -> ligo::udp::server&;
static auto bind(boost::asio::any_io_executor executor, ligo::udp::endpoint endpoint, ligo::udp::server_options options = {}) -> std::expected<ligo::udp::server, modulus::error>;
static auto bind(boost::asio::io_context& io, ligo::udp::endpoint endpoint, ligo::udp::server_options options = {}) -> std::expected<ligo::udp::server, modulus::error>;
static auto adopt(std::unique_ptr<boost::asio::ip::udp::socket> socket, boost::asio::ip::udp::endpoint peer, ligo::udp::server_options options = {}) -> std::expected<ligo::udp::server, modulus::error>;
auto send(boost::json::value payload) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto send(ligo::udp::message message) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
auto receive() -> boost::asio::awaitable<std::expected<ligo::udp::message, modulus::error>>;
auto local_endpoint() const -> std::expected<boost::asio::ip::udp::endpoint, modulus::error>;
auto close() -> void;
auto async_close(std::chrono::milliseconds timeout = std::chrono::milliseconds{5000}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
private:
explicit server(ligo::detail::state_handle state);
ligo::detail::state_handle state_{};
};
auto client_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::udp::endpoint, modulus::error>;
auto server_endpoint_from_config(ligo::endpoint_config const& config) -> std::expected<ligo::udp::endpoint, modulus::error>;
auto client_options_from_config(ligo::endpoint_config const& config) -> ligo::udp::client_options;
auto server_options_from_config(ligo::endpoint_config const& config) -> ligo::udp::server_options;
}