From 3030ad10294d7edd71529fa614608423192ea48e Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Sat, 25 Jul 2026 22:21:11 +0200 Subject: [PATCH 01/34] implement async i/o and async_sock for linux --- CMakeLists.txt | 3 + README.md | 11 +- examples/cpp/socket/main.cpp | 2 +- examples/cpp/socket_async/.gitignore | 1 + examples/cpp/socket_async/CMakeLists.txt | 15 + examples/cpp/socket_async/main.cpp | 57 +++ examples/cpp/socket_async/response.txt | 19 + examples/cpp/socket_server_async/.gitignore | 1 + .../cpp/socket_server_async/CMakeLists.txt | 15 + examples/cpp/socket_server_async/main.cpp | 55 +++ examples/cpp/socket_server_async/response.txt | 19 + include/netkit/definitions.hpp | 4 + include/netkit/io/basic_io_context.hpp | 187 ++++++++ include/netkit/io/io_awaitable.hpp | 31 ++ include/netkit/io/io_context.hpp | 66 +++ include/netkit/io/task.hpp | 159 ++++++ include/netkit/netkit.hpp | 8 + include/netkit/sock/addr.hpp | 1 + include/netkit/sock/async_sock.hpp | 64 +++ include/netkit/sock/basic_async_sock.hpp | 62 +++ include/netkit/sock/sync_sock.hpp | 5 +- src/io/basic_io_context.cpp | 55 +++ src/io/io_awaitable.cpp | 6 + src/sock/async_sock.cpp | 454 ++++++++++++++++++ src/sock/sync_sock.cpp | 4 +- 25 files changed, 1295 insertions(+), 9 deletions(-) create mode 100644 examples/cpp/socket_async/.gitignore create mode 100644 examples/cpp/socket_async/CMakeLists.txt create mode 100644 examples/cpp/socket_async/main.cpp create mode 100644 examples/cpp/socket_async/response.txt create mode 100644 examples/cpp/socket_server_async/.gitignore create mode 100644 examples/cpp/socket_server_async/CMakeLists.txt create mode 100644 examples/cpp/socket_server_async/main.cpp create mode 100644 examples/cpp/socket_server_async/response.txt create mode 100644 include/netkit/io/basic_io_context.hpp create mode 100644 include/netkit/io/io_awaitable.hpp create mode 100644 include/netkit/io/io_context.hpp create mode 100644 include/netkit/io/task.hpp create mode 100644 include/netkit/sock/async_sock.hpp create mode 100644 include/netkit/sock/basic_async_sock.hpp create mode 100644 src/io/basic_io_context.cpp create mode 100644 src/io/io_awaitable.cpp create mode 100644 src/sock/async_sock.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6abc2b1..6931735 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,9 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/http/multipart.cpp src/http/multipart_reader.cpp src/body/multipart_part_body.cpp + src/io/io_awaitable.cpp + src/io/basic_io_context.cpp + src/sock/async_sock.cpp ) set_target_properties(netkit PROPERTIES diff --git a/README.md b/README.md index daaad5f..ff02a00 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,18 @@ C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. +## Warning + Please note, this library is still work in progress. Usage is not recommended yet, aside from tests. Contributions and bug reports are much appreciated! +If you still intend to use the library, there are two ways to go. You can either use the unstable master branch directly which may change its API entirely. + +The other alternative is to use the latest release, which doesn't have a changing API but will make migration later potentially time consuming. + ## Features -- Binding, connecting, sending, receiving and closing synchronous TCP/UDP sockets +- OS-independent socket abstractions, both synchronous and asynchronous +- Higher-level socket abstractions - HTTP/1.0 and HTTP/1.1 body parser, including headers and body. - IPv4 and IPv6 support - TCP and UDP support @@ -24,7 +31,7 @@ Please note, this library is still work in progress. Usage is not recommended ye Still missing: -- Asynchronous I/O +- Asynchronous I/O for non-Linux platforms - Schannel support for Windows - WebSocket abstraction diff --git a/examples/cpp/socket/main.cpp b/examples/cpp/socket/main.cpp index f20e139..ddd8e74 100644 --- a/examples/cpp/socket/main.cpp +++ b/examples/cpp/socket/main.cpp @@ -4,7 +4,7 @@ * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. * - * @file main.c + * @file main.cpp * @license MIT * @note Example code using the Netkit library. * @note See examples/socket_ssl for a TLS/SSL version of this example. diff --git a/examples/cpp/socket_async/.gitignore b/examples/cpp/socket_async/.gitignore new file mode 100644 index 0000000..3c34c2f --- /dev/null +++ b/examples/cpp/socket_async/.gitignore @@ -0,0 +1 @@ +*build* \ No newline at end of file diff --git a/examples/cpp/socket_async/CMakeLists.txt b/examples/cpp/socket_async/CMakeLists.txt new file mode 100644 index 0000000..1948c66 --- /dev/null +++ b/examples/cpp/socket_async/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.11) + +project(netkit-example LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(netkit) + +add_executable( + netkit-example + main.cpp +) +target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/socket_async/main.cpp b/examples/cpp/socket_async/main.cpp new file mode 100644 index 0000000..f71eccc --- /dev/null +++ b/examples/cpp/socket_async/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +netkit::io::task request(netkit::io::io_context& ctx) { + netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; + + netkit::sock::async_sock sock{ctx, addr, netkit::sock::type::tcp}; + + co_await sock.connect(); + + constexpr std::string_view request = + "GET / HTTP/1.1\r\n" + "Host: google.com\r\n" + "Connection: close\r\n" + "\r\n"; + + co_await sock.send( + request.data(), + request.size() + ); + + std::string response; + + char buffer[8192]; + while (true) { + auto received = co_await sock.recv(buffer, sizeof(buffer)); + + if (received == 0) + break; + + response.append( + buffer, + received + ); + } + + sock.close(); + + std::ofstream file("response.txt"); + + if (!file) { + std::cerr << "Failed to open file\n"; + co_return; + } + + file << response; + + std::cout << "Response written to response.txt\n"; +} + +int main() { + netkit::io::io_context ctx; + ctx.spawn(request(ctx)); + ctx.run(); +} diff --git a/examples/cpp/socket_async/response.txt b/examples/cpp/socket_async/response.txt new file mode 100644 index 0000000..3631989 --- /dev/null +++ b/examples/cpp/socket_async/response.txt @@ -0,0 +1,19 @@ +HTTP/1.1 301 Moved Permanently +Location: http://www.google.com/ +Content-Type: text/html; charset=UTF-8 +Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-pzHBJy4TWzR1elN8aKNKdA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp +Date: Sat, 25 Jul 2026 16:26:31 GMT +Expires: Mon, 24 Aug 2026 16:26:31 GMT +Cache-Control: public, max-age=2592000 +Server: gws +Content-Length: 219 +X-XSS-Protection: 0 +X-Frame-Options: SAMEORIGIN +Connection: close + + +301 Moved +

301 Moved

+The document has moved +here. + diff --git a/examples/cpp/socket_server_async/.gitignore b/examples/cpp/socket_server_async/.gitignore new file mode 100644 index 0000000..3c34c2f --- /dev/null +++ b/examples/cpp/socket_server_async/.gitignore @@ -0,0 +1 @@ +*build* \ No newline at end of file diff --git a/examples/cpp/socket_server_async/CMakeLists.txt b/examples/cpp/socket_server_async/CMakeLists.txt new file mode 100644 index 0000000..1948c66 --- /dev/null +++ b/examples/cpp/socket_server_async/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.11) + +project(netkit-example LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(netkit) + +add_executable( + netkit-example + main.cpp +) +target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/socket_server_async/main.cpp b/examples/cpp/socket_server_async/main.cpp new file mode 100644 index 0000000..0f02c9f --- /dev/null +++ b/examples/cpp/socket_server_async/main.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +netkit::io::task<> +handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { + char buffer[8192]; + + while (true) { + auto received = co_await client->recv(buffer, sizeof(buffer)); + + if (received == 0) { + std::cout << "client disconnected\n"; + break; + } + + std::cout << buffer << "\n"; + + co_await client->send(buffer, received); + + client->close(); + } +} + +netkit::io::task<> +request(netkit::io::io_context& ctx) { + netkit::sock::addr addr{ + "localhost", + 1337, + netkit::sock::addr_type::hostname + }; + + netkit::sock::async_sock sock{ + ctx, + addr, + netkit::sock::type::tcp, + netkit::sock::opt::reuse_addr | netkit::sock::opt::no_delay | netkit::sock::opt::no_blocking + }; + + sock.bind(); + sock.listen(); + + // ReSharper disable once CppDFAEndlessLoop + while (true) { + auto client = co_await sock.accept(); + ctx.spawn(handle_client(ctx, std::move(client))); + } +} + +int main() { + netkit::io::io_context ctx; + ctx.spawn(request(ctx)); + ctx.run(); +} diff --git a/examples/cpp/socket_server_async/response.txt b/examples/cpp/socket_server_async/response.txt new file mode 100644 index 0000000..53d06f7 --- /dev/null +++ b/examples/cpp/socket_server_async/response.txt @@ -0,0 +1,19 @@ +HTTP/1.1 301 Moved Permanently +Location: http://www.google.com/ +Content-Type: text/html; charset=UTF-8 +Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-rtIMGGI5Lruf_cH3ypsLQQ' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp +Date: Sat, 25 Jul 2026 16:31:38 GMT +Expires: Mon, 24 Aug 2026 16:31:38 GMT +Cache-Control: public, max-age=2592000 +Server: gws +Content-Length: 219 +X-XSS-Protection: 0 +X-Frame-Options: SAMEORIGIN +Connection: close + + +301 Moved +

301 Moved

+The document has moved +here. + diff --git a/include/netkit/definitions.hpp b/include/netkit/definitions.hpp index 2e2cd94..d0c0840 100644 --- a/include/netkit/definitions.hpp +++ b/include/netkit/definitions.hpp @@ -26,6 +26,10 @@ #define NETKIT_DKP 1 #endif +#if defined(__linux) || defined(linux) +#define NETKIT_LINUX 1 +#endif + #ifndef NETKIT_FALLBACK_IPV4_DNS_1 #define NETKIT_FALLBACK_IPV4_DNS_1 "8.8.8.8" #endif diff --git a/include/netkit/io/basic_io_context.hpp b/include/netkit/io/basic_io_context.hpp new file mode 100644 index 0000000..dce550b --- /dev/null +++ b/include/netkit/io/basic_io_context.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include + +#ifdef NETKIT_LINUX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace netkit::io { + +struct fd_state { + std::vector> read_waiters; + std::vector> write_waiters; +}; + +enum class io_event { read, write }; + +class basic_io_context { +public: + basic_io_context() { + epoll_fd_ = epoll_create1(0); + + if (epoll_fd_ == -1) + throw std::runtime_error("epoll_create1 failed"); + + wake_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + + if (wake_fd_ == -1) + throw std::runtime_error("eventfd failed"); + + + epoll_event ev{}; + ev.events = EPOLLIN; + ev.data.fd = wake_fd_; + + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, wake_fd_, &ev) == -1) { + throw std::runtime_error( + "failed to add wake fd" + ); + } + } + + ~basic_io_context() { + if (wake_fd_ != -1) + close(wake_fd_); + + if (epoll_fd_ != -1) + close(epoll_fd_); + } + + void wake() { + uint64_t value = 1; + + write(wake_fd_, &value, sizeof(value)); + } + + void update_epoll(int fd, const fd_state& state) { + epoll_event ev{}; + ev.data.fd = fd; + ev.events = 0; + + if (!state.read_waiters.empty()) + ev.events |= EPOLLIN; + + if (!state.write_waiters.empty()) + ev.events |= EPOLLOUT; + + if (ev.events == 0) { + if (registered_fds_.contains(fd)) { + epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr); + registered_fds_.erase(fd); + } + return; + } + + int ret; + + if (!registered_fds_.contains(fd)) { + ret = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev); + + if (ret == -1) + throw std::runtime_error(std::strerror(errno)); + + registered_fds_.insert(fd); + } + else { + ret = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ev); + + if (ret == -1) + throw std::runtime_error(std::strerror(errno)); + } + } + + void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) { + auto& state = fd_map_[fd]; + + if (ev == io_event::read) + state.read_waiters.push_back(h); + else + state.write_waiters.push_back(h); + + update_epoll(fd, state); + } + + void run() { + constexpr int MAX_EVENTS = 64; + epoll_event events[MAX_EVENTS]; + + while (running_) { + int n = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); + + if (n == -1) { + if (errno == EINTR) continue; + throw std::runtime_error("epoll_wait failed"); + } + + for (int i = 0; i < n; ++i) { + int fd = events[i].data.fd; + + auto it = fd_map_.find(fd); + if (it == fd_map_.end()) continue; + + auto& state = it->second; + + if (events[i].events & EPOLLIN) { + auto waiters = std::move(state.read_waiters); + state.read_waiters.clear(); + + for (auto h : waiters) + h.resume(); + } + + if (events[i].events & EPOLLOUT) { + auto waiters = std::move(state.write_waiters); + state.write_waiters.clear(); + + for (auto h : waiters) + h.resume(); + } + + if (events[i].events & (EPOLLERR | EPOLLHUP)) { + auto read_waiters = std::move(state.read_waiters); + auto write_waiters = std::move(state.write_waiters); + + state.read_waiters.clear(); + state.write_waiters.clear(); + + for (auto h : read_waiters) h.resume(); + for (auto h : write_waiters) h.resume(); + + update_epoll(fd, state); + continue; + } + + update_epoll(fd, state); + } + } + } + + void stop() { + running_ = false; + } + + void poll(int timeout_ms = -1); + +private: + int epoll_fd_; + bool running_ = true; + int wake_fd_ = -1; + + std::unordered_map fd_map_; + std::unordered_set registered_fds_; +}; + +} // namespace netkit::io + +#endif \ No newline at end of file diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp new file mode 100644 index 0000000..d3de734 --- /dev/null +++ b/include/netkit/io/io_awaitable.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include +#include + +#ifdef NETKIT_LINUX + +namespace netkit::io { + class io_context; // fw decl. + + struct io_awaitable { + basic_io_context& ctx; + sock::fd_t fd; + io_event ev; + + [[nodiscard]] bool await_ready() const noexcept { + return false; + } + + void await_suspend(std::coroutine_handle<> h) { + ctx.register_waiter(fd, ev, h); + } + + void await_resume() noexcept {} + }; +} + +#endif \ No newline at end of file diff --git a/include/netkit/io/io_context.hpp b/include/netkit/io/io_context.hpp new file mode 100644 index 0000000..780016a --- /dev/null +++ b/include/netkit/io/io_context.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#ifdef NETKIT_LINUX + +namespace netkit::io { + +class io_context { +public: + void run() { + running_ = true; + + while (running_) { + backend_.poll(-1); + + cleanup_tasks(); + } + } + + void run_until_idle() { + running_ = true; + + while (running_ && !tasks_.empty()) { + backend_.poll(-1); + + cleanup_tasks(); + } + } + + void stop() { + running_.store(false); + backend_.wake(); + } + + io_awaitable wait_readable(int fd) { + return io_awaitable{backend_, fd, io_event::read}; + } + + io_awaitable wait_writable(int fd) { + return io_awaitable{backend_, fd, io_event::write}; + } + + void spawn(task&& t) { + tasks_.push_back(std::move(t)); + tasks_.back().resume(); + } +private: + void cleanup_tasks() { + std::erase_if(tasks_, [](const auto& task) { return task.done(); } ); + } + + basic_io_context backend_; + std::atomic_bool running_ = false; + + std::vector> tasks_; +}; + +} + +#endif \ No newline at end of file diff --git a/include/netkit/io/task.hpp b/include/netkit/io/task.hpp new file mode 100644 index 0000000..6070841 --- /dev/null +++ b/include/netkit/io/task.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#ifdef NETKIT_LINUX + +namespace netkit::io { + +template +class task; + +template +struct promise_return { + T value; + + template + void return_value(U&& v) { + value = std::forward(v); + } +}; + +template<> +struct promise_return { + void return_void() {} +}; + +template +class task { +public: + struct promise_type : promise_return { + std::exception_ptr exception; + std::coroutine_handle<> continuation{}; + + task get_return_object() { + return task{ + handle_type::from_promise(*this) + }; + } + + std::suspend_always initial_suspend() noexcept { + return {}; + } + + auto final_suspend() noexcept { + struct final_awaiter { + bool await_ready() noexcept { + return false; + } + + std::coroutine_handle<> await_suspend(handle_type h) noexcept { + auto continuation = h.promise().continuation; + + if (continuation) + return continuation; + + return std::noop_coroutine(); + } + + void await_resume() noexcept {} + }; + + return final_awaiter{}; + } + + void unhandled_exception() { + exception = std::current_exception(); + } + }; + + using handle_type = std::coroutine_handle; + + class awaiter { + public: + explicit awaiter(handle_type h) : handle_(std::exchange(h, {})) {} + + [[nodiscard]] bool await_ready() const noexcept { + return handle_.done(); + } + + void await_suspend(std::coroutine_handle<> caller) { + handle_.promise().continuation = caller; + handle_.resume(); + } + + auto await_resume() { + if (handle_.promise().exception) + std::rethrow_exception( + handle_.promise().exception + ); + + if constexpr (std::is_void_v) { + handle_.destroy(); + handle_ = {}; + } + else { + auto value = std::move( + handle_.promise().value + ); + + handle_.destroy(); + handle_ = {}; + + return value; + } + } + private: + handle_type handle_; + }; + + auto operator co_await() && { + return awaiter{ + std::exchange(handle_, {}) + }; + } + + void resume() { + if (handle_ && !handle_.done()) { + handle_.resume(); + } + } + + [[nodiscard]] bool done() const noexcept { + return !handle_ || handle_.done(); + } + + task(task&& other) noexcept : handle_(std::exchange(other.handle_, {})) {} + + task& operator=(task&& other) noexcept { + if (this != &other) { + destroy(); + handle_ = std::exchange(other.handle_, {}); + } + + return *this; + } + + ~task() { + destroy(); + } +private: + explicit task(handle_type h) : handle_(h) {} + + void destroy() { + if (handle_) + handle_.destroy(); + } + + handle_type handle_; +}; + +} + +#endif \ No newline at end of file diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 4e5cefb..c201508 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include #include @@ -63,4 +65,10 @@ #include #include +// io +#include +#include +#include +#include + #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/sock/addr.hpp b/include/netkit/sock/addr.hpp index 2181f06..78261b4 100644 --- a/include/netkit/sock/addr.hpp +++ b/include/netkit/sock/addr.hpp @@ -25,6 +25,7 @@ namespace netkit::sock { int port{}; addr_type type{addr_type::hostname}; friend addr get_peer(fd_t); + friend addr get_peer_async(fd_t); // jank addr() = default; public: diff --git a/include/netkit/sock/async_sock.hpp b/include/netkit/sock/async_sock.hpp new file mode 100644 index 0000000..17fbb44 --- /dev/null +++ b/include/netkit/sock/async_sock.hpp @@ -0,0 +1,64 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file async_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides an asynchronous socket class implementing the basic_async_sock interface. + */ +#pragma once + +#include + +#ifdef NETKIT_LINUX + +#include + +#include +#include +#include + +#include + +namespace netkit::sock { + constexpr fd_t INVALID_SOCKET = -1; + + class NETKIT_API async_sock : public basic_async_sock { + addr addr_; + type type_{}; + fd_t sockfd{INVALID_SOCKET}; + + sockaddr_storage sa_storage{}; + bool bound{false}; + + [[nodiscard]] const sockaddr* get_sa() const; + [[nodiscard]] socklen_t get_sa_len() const; + void prep_sa(); + + io::io_context& context_; + public: + async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); + async_sock(io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); + ~async_sock() override; + sock::addr& get_addr() override; + [[nodiscard]] const sock::addr& get_addr() const override; + netkit::io::task connect() override; + + void bind() override; + void unbind() override; + void listen(int backlog) override; + void listen() override; + [[nodiscard]] netkit::io::task> accept() override; + netkit::io::task send(const void* buf, size_t len) override; + [[nodiscard]] netkit::io::task recv(void* buf, std::size_t size) override; + void close() override; + [[nodiscard]] sock::addr get_peer() const override; + [[nodiscard]] fd_t native_handle() const override; + void set_sock_opts(opt opts) override; + }; +} + +#endif \ No newline at end of file diff --git a/include/netkit/sock/basic_async_sock.hpp b/include/netkit/sock/basic_async_sock.hpp new file mode 100644 index 0000000..d18b088 --- /dev/null +++ b/include/netkit/sock/basic_async_sock.hpp @@ -0,0 +1,62 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file basic_async_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a basic interface for asynchronous sockets. + */ +#pragma once + +#include + +#ifdef NETKIT_LINUX + +#include +#include +#include +#include + +namespace netkit::sock { + /** + * @brief A class that represents an asynchronous socket. + * @note This class is an abstract base class and should not be instantiated directly. + * @note Use the async_sock class instead. + */ + class NETKIT_API basic_async_sock { + public: + virtual ~basic_async_sock() = default; + virtual netkit::io::task connect() = 0; + virtual void bind() = 0; + virtual void unbind() = 0; + virtual void listen() = 0; + virtual void listen(int backlog) = 0; + virtual netkit::io::task> accept() = 0; + virtual netkit::io::task send(const void* buf, std::size_t len) = 0; + [[nodiscard]] virtual netkit::io::task recv(void* buf, std::size_t len) = 0; + virtual void close() = 0; + + /* ... */ + virtual addr& get_addr() { + throw std::logic_error{"socket does not have an addr object"}; + } + [[nodiscard]] virtual const addr& get_addr() const { + throw std::logic_error{"socket does not have an addr object"}; + } + virtual void clear_overflow_bytes() const {} + [[nodiscard]] virtual addr get_peer() const { + throw std::logic_error{"socket does not have a peer"}; + }; + [[nodiscard]] virtual fd_t native_handle() const { + throw std::logic_error{"socket does not have a native handle"}; + } + virtual void set_sock_opts(opt opts) { + throw std::logic_error{"socket does not have opts to set"}; + } + }; +} + +#endif \ No newline at end of file diff --git a/include/netkit/sock/sync_sock.hpp b/include/netkit/sock/sync_sock.hpp index 1d366b8..ecfbde4 100644 --- a/include/netkit/sock/sync_sock.hpp +++ b/include/netkit/sock/sync_sock.hpp @@ -54,8 +54,8 @@ namespace netkit::sock { * @param t The socket type (tcp, udp, unix). * @param opts The socket options (reuse_addr, no_reuse_addr). */ -#ifdef NETKIT_UNIX sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); +#ifdef NETKIT_UNIX /** * @brief Constructs a sync_sock object from an existing file descriptor. * @param existing_fd The existing file descriptor. @@ -64,9 +64,6 @@ namespace netkit::sock { * @param opts The socket options (reuse_addr, no_reuse_addr). */ sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); -#endif -#ifdef NETKIT_WINDOWS - sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); #endif ~sync_sock() override; sock::addr& get_addr() override; diff --git a/src/io/basic_io_context.cpp b/src/io/basic_io_context.cpp new file mode 100644 index 0000000..79f5f14 --- /dev/null +++ b/src/io/basic_io_context.cpp @@ -0,0 +1,55 @@ +#include + +#ifdef NETKIT_LINUX +void netkit::io::basic_io_context::poll(int timeout_ms) +{ + epoll_event events[64]; + + int n = epoll_wait( + epoll_fd_, + events, + 64, + timeout_ms + ); + + for (int i = 0; i < n; ++i) { + int fd = events[i].data.fd; + + if (fd == wake_fd_) { + uint64_t value; + + read(wake_fd_, &value, sizeof(value)); + + continue; + } + + + auto it = fd_map_.find(fd); + + if (it == fd_map_.end()) + continue; + + auto& state = it->second; + + if (events[i].events & EPOLLIN) { + auto waiters = std::move(state.read_waiters); + state.read_waiters.clear(); + + for (auto h : waiters) { + h.resume(); + } + } + + if (events[i].events & EPOLLOUT) { + auto waiters = std::move(state.write_waiters); + state.write_waiters.clear(); + + for (auto h : waiters) { + h.resume(); + } + } + + update_epoll(fd, state); + } +} +#endif \ No newline at end of file diff --git a/src/io/io_awaitable.cpp b/src/io/io_awaitable.cpp new file mode 100644 index 0000000..44656b7 --- /dev/null +++ b/src/io/io_awaitable.cpp @@ -0,0 +1,6 @@ +#include +#include + +#ifdef NETKIT_LINUX +// TODO: move method definitions here +#endif \ No newline at end of file diff --git a/src/sock/async_sock.cpp b/src/sock/async_sock.cpp new file mode 100644 index 0000000..1e3171f --- /dev/null +++ b/src/sock/async_sock.cpp @@ -0,0 +1,454 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol + * abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file async_sock.cpp + * @license MIT + * @note Part of the Netkit library. + * @brief Implementation of the asynchronous socket class. + */ + +#include +#include +#include + +#include +#include + +#ifdef NETKIT_LINUX + +#include +#include +#include +#include +#include +#include + +namespace netkit::sock { +netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { + sockaddr_storage addr_storage{}; + socklen_t addr_len = sizeof(addr_storage); + + if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { + throw netkit::socket_error("getpeername() failed: " + std::string(strerror(errno))); + } + + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = 0; + + if (addr_storage.ss_family == AF_INET) { + auto* addr_in = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else if (addr_storage.ss_family == AF_INET6) { + auto* addr_in6 = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in6->sin6_port); + } else { + throw netkit::ip_error("unsupported address family"); + } + + netkit::sock::addr addr{}; + addr.ip = ip_str; + addr.port = port; + addr.type = (addr_storage.ss_family == AF_INET) ? netkit::sock::addr_type::ipv4 : netkit::sock::addr_type::ipv6; + + return addr; +} +} + +const sockaddr* netkit::sock::async_sock::get_sa() const { + return reinterpret_cast(&sa_storage); +} + +socklen_t netkit::sock::async_sock::get_sa_len() const { + if (addr_.is_ipv4()) return sizeof(sockaddr_in); + if (addr_.is_ipv6()) return sizeof(sockaddr_in6); +#ifndef NETKIT_DKP + if (addr_.is_file_path()) { + const auto& path = addr_.get_path(); + return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); + } +#endif + + throw netkit::socket_error("invalid address type"); +} + +void netkit::sock::async_sock::prep_sa() { + memset(&sa_storage, 0, sizeof(sa_storage)); + + if (addr_.is_ipv4()) { + auto* sa4 = reinterpret_cast(&sa_storage); + sa4->sin_family = AF_INET; + sa4->sin_port = htons(addr_.get_port()); + if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { + throw parsing_error("invalid IPv4 address"); + } + } else if (addr_.is_ipv6()) { + auto* sa6 = reinterpret_cast(&sa_storage); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(addr_.get_port()); + + std::string ip = addr_.get_ip(); + unsigned long scope = 0; + + auto pos = ip.find('%'); + if (pos != std::string::npos) { + scope = std::stoul(ip.substr(pos + 1)); + ip = ip.substr(0, pos); // strip %scope before inet_pton + } + + if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { + throw parsing_error("invalid IPv6 address"); + } + + if (scope != 0) { + sa6->sin6_scope_id = scope; + } + } else if (addr_.is_file_path()) { + auto* sa_un = reinterpret_cast(&sa_storage); + sa_un->sun_family = AF_UNIX; + const auto& path = addr_.get_path().string(); + if (path.size() >= sizeof(sa_un->sun_path)) { + throw socket_error("UNIX socket path too long"); + } + std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); + } else { + throw ip_error("invalid address type"); + } +} + +void netkit::sock::async_sock::set_sock_opts(opt opts) { + if (opts & opt::reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & opt::no_reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & opt::no_delay) { + ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & opt::keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & opt::no_keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & opt::no_blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +} + +netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts) + : addr_(addr), type_(t), context_(ctx) { + this->sockfd = -1; + + if (!addr.is_file_path()) { + if (addr.get_ip().empty()) { + throw socket_error("IP address/file path is empty"); + } + } + + if (t != type::uds) { + this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, + t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + } else { + this->sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); + } + + if (this->sockfd < 0) { + throw socket_error("failed to create socket"); + } + + if (this->sockfd >= 0) { + this->async_sock::set_sock_opts(opts); + } else { + throw socket_error("cannot set options on invalid socket"); + } + + this->prep_sa(); +} + +netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, int existing_fd, const sock::addr& peer, sock::type t, opt opts) + : addr_(peer), type_(t), sockfd(existing_fd), context_(ctx) { + if (sockfd < 0) throw socket_error("invalid fd"); + if (this->sockfd >= 0) { + this->async_sock::set_sock_opts(opts); + } else { + throw socket_error("cannot set options on invalid socket"); + } + + this->prep_sa(); +} + +netkit::sock::async_sock::~async_sock() { + if (this->sockfd == -1) { + return; + } + if (::close(this->sockfd) < 0) { + ; + } +} + +netkit::sock::addr& netkit::sock::async_sock::get_addr() { + return this->addr_; +} + +const netkit::sock::addr& netkit::sock::async_sock::get_addr() const { + return this->addr_; +} + +netkit::io::task netkit::sock::async_sock::connect() { + int ret = ::connect( + this->sockfd, + this->get_sa(), + this->get_sa_len() + ); + + if (ret == 0) { + co_return; + } + + + if (errno != EINPROGRESS) { + throw netkit::socket_error( + "failed to connect to server" + ); + } + + co_await this->context_.wait_writable(this->sockfd); + + int error = 0; + socklen_t len = sizeof(error); + + if (getsockopt( + this->sockfd, + SOL_SOCKET, + SO_ERROR, + &error, + &len + ) < 0) { + throw netkit::socket_error( + "getsockopt(SO_ERROR) failed" + ); + } + + if (error != 0) { + errno = error; + + throw netkit::socket_error( + "failed to connect to server" + ); + } + + co_return; +} + +void netkit::sock::async_sock::bind() { + auto ret = ::bind( + this->sockfd, + this->get_sa(), + this->get_sa_len() + ); + + if (ret < 0) { + throw socket_error("failed to bind socket"); + } + + this->bound = true; +} + +void netkit::sock::async_sock::unbind() { + if (this->bound) { + if (::close(this->sockfd) < 0) { + throw socket_error("failed to unbind socket"); + } + this->bound = false; + } +} + +void netkit::sock::async_sock::listen(int backlog) { + if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { + throw socket_error("failed to listen on socket"); + } +} + +void netkit::sock::async_sock::listen() { + this->listen(-1); +} + +netkit::io::task> +netkit::sock::async_sock::accept() { + while (true) { + sockaddr_storage client_addr{}; + socklen_t addr_len = sizeof(client_addr); + + // TODO: use fcntl and get bsd support + // it is just a waste of time right now since we are using epoll anyway + fd_t client_sockfd = ::accept4( + this->sockfd, + reinterpret_cast(&client_addr), + &addr_len, + SOCK_NONBLOCK + ); + + if (client_sockfd >= 0) { + if (this->type_ == type::uds) { + co_return std::make_unique( + this->context_, + client_sockfd, + sock::addr( + reinterpret_cast(&client_addr)->sun_path + ), + this->type_ + ); + } + + auto peer = sock::get_peer(client_sockfd); + + co_return std::make_unique( + this->context_, + client_sockfd, + peer, + this->type_ + ); + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + co_await this->context_.wait_readable(this->sockfd); + continue; + } + + throw socket_error( + "failed to accept connection: " + + std::string(strerror(errno)) + ); + } +} + +netkit::io::task netkit::sock::async_sock::send(const void* buf, size_t len) { + size_t total_sent = 0; + const char* data = static_cast(buf); + + while (total_sent < len) { + ssize_t sent = ::send( + this->sockfd, + data + total_sent, + len - total_sent, + 0 + ); + + if (sent > 0) { + total_sent += sent; + continue; + } + + if (sent == 0) { + co_return total_sent; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + co_await this->context_.wait_writable( + this->sockfd + ); + + continue; + } + + throw socket_error( + "failed to send: " + + std::string(strerror(errno)) + ); + } + + co_return total_sent; +} + +netkit::io::task netkit::sock::async_sock::recv(void* buf, size_t size) { + for (;;) { + auto n = ::recv( + this->sockfd, + static_cast(buf), + size, + 0 + ); + + if (n > 0) { + co_return static_cast(n); + } + + if (n == 0) { + co_return 0; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + co_await context_.wait_readable(sockfd); + continue; + } + + if (errno == EINTR) + continue; + + throw socket_error( + "recv failed: " + std::string(strerror(errno)) + ); + } +} + +void netkit::sock::async_sock::close() { + if (this->sockfd == -1) + return; + + ::close(this->sockfd); + this->sockfd = -1; +} + +[[nodiscard]] netkit::sock::addr netkit::sock::async_sock::get_peer() const { +#ifdef NETKIT_DKP + if (!this->has_peer) { + throw netkit::socket_error("peer not known"); + } + + char ip_str[INET6_ADDRSTRLEN]{}; + uint16_t port = 0; + + if (this->peer_addr.ss_family == AF_INET) { + auto* addr_in = (sockaddr_in*)&this->peer_addr; + inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else { + throw netkit::ip_error("unsupported address family (Wii = IPv4 only)"); + } + + return netkit::sock::addr{ + ip_str, + port, netkit::sock::addr_type::ipv4 + }; +#else + return sock::get_peer(this->sockfd); +#endif +} +netkit::sock::fd_t netkit::sock::async_sock::native_handle() const { + return this->sockfd; +} + +#endif \ No newline at end of file diff --git a/src/sock/sync_sock.cpp b/src/sock/sync_sock.cpp index 89fb1d2..533e11b 100644 --- a/src/sock/sync_sock.cpp +++ b/src/sock/sync_sock.cpp @@ -44,7 +44,7 @@ #ifndef NETKIT_DKP namespace netkit::sock { -netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { +netkit::sock::addr get_peer_async(netkit::sock::fd_t sockfd) { sockaddr_storage addr_storage{}; socklen_t addr_len = sizeof(addr_storage); @@ -794,7 +794,7 @@ void netkit::sock::sync_sock::close() { port, netkit::sock::addr_type::ipv4 }; #else - return sock::get_peer(this->sockfd); + return sock::get_peer_async(this->sockfd); #endif } netkit::sock::fd_t netkit::sock::sync_sock::native_handle() const { From dd319cff5e44c5bff29f118bef3ad5b375c685ac Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Sat, 25 Jul 2026 23:33:53 +0200 Subject: [PATCH 02/34] delegate existing socket abstractions to underlying native classes new abstractions will be written on top of these, which is why they've been stripped down. notes: - probably will not compile on anything but linux - does not compile unless HTTP, DNS, WOLFSSL and OPENSSL are disabled these pains will stay until the socket abstractions have been rewritten. --- CMakeLists.txt | 24 +- include/netkit/body/multipart_part_body.hpp | 3 +- include/netkit/body/stream_body.hpp | 9 +- include/netkit/dns/sync_resolver.hpp | 10 +- include/netkit/http/basic_request_handler.hpp | 2 +- include/netkit/http/basic_sync_server.hpp | 2 +- include/netkit/http/request_handler.hpp | 7 +- include/netkit/http/sync_client.hpp | 9 +- include/netkit/http/sync_server.hpp | 6 +- include/netkit/io/io_awaitable.hpp | 5 +- include/netkit/netkit.hpp | 20 +- include/netkit/sock/sync_sock.hpp | 162 -- include/netkit/{sock => socket}/addr.hpp | 185 +-- include/netkit/{sock => socket}/addr_type.hpp | 188 +-- .../native/basic_native_async_sock.hpp} | 12 +- .../native/basic_native_sync_sock.hpp} | 22 +- .../native/native_async_sock.hpp} | 26 +- .../netkit/socket/native/native_sync_sock.hpp | 112 ++ .../openssl/ssl_sync_sock.hpp | 14 +- include/netkit/{sock => socket}/sock_peer.hpp | 34 +- .../netkit/{sock => socket}/ssl_sync_sock.hpp | 2 +- .../{sock => socket}/ssl_sync_sock_enum.hpp | 0 .../wolfssl/ssl_sync_sock.hpp | 9 +- .../{sock => socket}/wolfssl/user_settings.h | 150 +- src/body/stream_body.cpp | 6 +- src/c/sock/addr.cpp | 2 +- src/c/sock/ssl_sync_sock.cpp | 4 +- src/c/sock/sync_sock.cpp | 2 +- src/http/sync_client.cpp | 11 +- src/{sock => socket}/addr.cpp | 569 +++---- .../native/native_async_socket.cpp} | 64 +- .../native/native_sync_socket.cpp} | 1344 +++++++---------- .../openssl/ssl_sync_sock.cpp | 4 +- src/{sock => socket}/sock_peer.cpp | 46 +- .../wolfssl/ssl_sync_sock.cpp | 0 35 files changed, 1386 insertions(+), 1679 deletions(-) delete mode 100644 include/netkit/sock/sync_sock.hpp rename include/netkit/{sock => socket}/addr.hpp (90%) rename include/netkit/{sock => socket}/addr_type.hpp (96%) rename include/netkit/{sock/basic_async_sock.hpp => socket/native/basic_native_async_sock.hpp} (86%) rename include/netkit/{sock/basic_sync_sock.hpp => socket/native/basic_native_sync_sock.hpp} (63%) rename include/netkit/{sock/async_sock.hpp => socket/native/native_async_sock.hpp} (67%) create mode 100644 include/netkit/socket/native/native_sync_sock.hpp rename include/netkit/{sock => socket}/openssl/ssl_sync_sock.hpp (95%) rename include/netkit/{sock => socket}/sock_peer.hpp (89%) rename include/netkit/{sock => socket}/ssl_sync_sock.hpp (83%) rename include/netkit/{sock => socket}/ssl_sync_sock_enum.hpp (100%) rename include/netkit/{sock => socket}/wolfssl/ssl_sync_sock.hpp (96%) rename include/netkit/{sock => socket}/wolfssl/user_settings.h (94%) rename src/{sock => socket}/addr.cpp (96%) rename src/{sock/async_sock.cpp => socket/native/native_async_socket.cpp} (81%) rename src/{sock/sync_sock.cpp => socket/native/native_sync_socket.cpp} (53%) rename src/{sock => socket}/openssl/ssl_sync_sock.cpp (99%) rename src/{sock => socket}/sock_peer.cpp (87%) rename src/{sock => socket}/wolfssl/ssl_sync_sock.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6931735..b8e4ba6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ option(NETKIT_ENABLE_C_TESTS "Enable C building tests" OFF) option(NETKIT_ENABLE_WINDOWS_CERTSTORE "Enable getting CA certificates from the Windows store" ON) option(NETKIT_ENABLE_FALLBACK_CA "Enable fallback CA certficate (required for DevkitPro)" ON) option(NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER "Enable using netkit's DNS resolver instead of the system resolver" OFF) +option(NETKIT_ENABLE_HTTP "Enable netkit's HTTP abstractions" ON) +option(NETKIT_ENABLE_DNS "Enable netkit's DNS features" ON) option(NETKIT_WOLFSSL_DEBUG "Enable WolfSSL debugging" OFF) option(NETKIT_BUILD_SHARED "Build netkit as a shared library" ON) option(NETKIT_USE_SYSTEM_WOLFSSL "Use system WolfSSL if found (not recommended)" OFF) @@ -59,14 +61,14 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/network/ip_list.cpp src/dns/record_type.cpp src/dns/nameserver_list.cpp - src/sock/addr.cpp + src/socket/addr.cpp src/dns/response_parser.cpp src/dns/query_builder.cpp - src/sock/sock_peer.cpp - src/sock/sync_sock.cpp + src/socket/sock_peer.cpp + src/socket/native/native_sync_socket.cpp src/dns/cache.cpp - src/sock/openssl/ssl_sync_sock.cpp - src/sock/wolfssl/ssl_sync_sock.cpp + src/socket/openssl/ssl_sync_sock.cpp + src/socket/wolfssl/ssl_sync_sock.cpp src/http/predefined.cpp src/http/sync_client.cpp src/crypto/windows/certs.cpp @@ -76,7 +78,7 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/body/multipart_part_body.cpp src/io/io_awaitable.cpp src/io/basic_io_context.cpp - src/sock/async_sock.cpp + src/socket/native/native_async_socket.cpp ) set_target_properties(netkit PROPERTIES @@ -207,6 +209,14 @@ if (NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER) target_compile_definitions(netkit PUBLIC NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER) endif() +if (NETKIT_ENABLE_HTTP) + target_compile_definitions(netkit PUBLIC NETKIT_HTTP) +endif() + +if (NETKIT_ENABLE_DNS) + target_compile_definitions(netkit PUBLIC NETKIT_DNS) +endif() + if (WIN32) target_link_libraries(netkit PUBLIC ws2_32 iphlpapi dnsapi crypt32) elseif (UNIX AND NOT APPLE) @@ -252,7 +262,7 @@ install(TARGETS netkit if (NETKIT_DKP AND NETKIT_ENABLE_WOLFSSL) install( FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/netkit/sock/wolfssl/user_settings.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/netkit/socket/wolfssl/user_settings.h DESTINATION include/wolfssl/wolfcrypt/ ) diff --git a/include/netkit/body/multipart_part_body.hpp b/include/netkit/body/multipart_part_body.hpp index ab477b0..06b5254 100644 --- a/include/netkit/body/multipart_part_body.hpp +++ b/include/netkit/body/multipart_part_body.hpp @@ -1,8 +1,7 @@ #pragma once -#include #include -#include +#include namespace netkit::body { class multipart_part_body : public basic_body { diff --git a/include/netkit/body/stream_body.hpp b/include/netkit/body/stream_body.hpp index 6a522e1..3a2ed15 100644 --- a/include/netkit/body/stream_body.hpp +++ b/include/netkit/body/stream_body.hpp @@ -1,18 +1,17 @@ #pragma once +#include #include #include +#include #include #include -#include -#include - namespace netkit::body { class NETKIT_API stream_body : public basic_body { public: - stream_body(sock::basic_sync_sock& socket, + stream_body(sock::native::basic_native_sync_sock& socket, std::optional length, std::string initial = {}) : socket_(socket), @@ -27,7 +26,7 @@ class NETKIT_API stream_body : public basic_body { } private: - sock::basic_sync_sock& socket_; + sock::native::basic_native_sync_sock& socket_; std::optional remaining_; std::string buffer_; std::string overflow_; diff --git a/include/netkit/dns/sync_resolver.hpp b/include/netkit/dns/sync_resolver.hpp index fb5258b..57f4455 100644 --- a/include/netkit/dns/sync_resolver.hpp +++ b/include/netkit/dns/sync_resolver.hpp @@ -11,15 +11,17 @@ */ #pragma once +#ifdef NETKIT_DNS + +#include #include #include #include #include #include #include -#include -#include -#include +#include +#include #ifdef NETKIT_UNIX #include @@ -212,4 +214,6 @@ namespace netkit::dns { }; } +#endif + #endif \ No newline at end of file diff --git a/include/netkit/http/basic_request_handler.hpp b/include/netkit/http/basic_request_handler.hpp index 24dca7a..fff2d5e 100644 --- a/include/netkit/http/basic_request_handler.hpp +++ b/include/netkit/http/basic_request_handler.hpp @@ -11,8 +11,8 @@ */ #pragma once +#include <../socket/native/basic_native_sync_sock.hpp> #include -#include namespace netkit::http::server { template diff --git a/include/netkit/http/basic_sync_server.hpp b/include/netkit/http/basic_sync_server.hpp index e1ff1eb..37457c4 100644 --- a/include/netkit/http/basic_sync_server.hpp +++ b/include/netkit/http/basic_sync_server.hpp @@ -11,9 +11,9 @@ */ #pragma once +#include <../socket/native/native_sync_sock.hpp> #include #include -#include #include namespace netkit::http::server { diff --git a/include/netkit/http/request_handler.hpp b/include/netkit/http/request_handler.hpp index 84e9be3..8a19556 100644 --- a/include/netkit/http/request_handler.hpp +++ b/include/netkit/http/request_handler.hpp @@ -11,17 +11,16 @@ */ #pragma once +#include <../socket/native/native_sync_sock.hpp> #include #include -#include - +#include #include #include #include #include -#include #include -#include +#include namespace netkit::http::server { template diff --git a/include/netkit/http/sync_client.hpp b/include/netkit/http/sync_client.hpp index 93b04ab..c91e46e 100644 --- a/include/netkit/http/sync_client.hpp +++ b/include/netkit/http/sync_client.hpp @@ -11,9 +11,11 @@ */ #pragma once -#include -#include +#ifdef NETKIT_HTTP + +#include <../socket/native/native_sync_sock.hpp> #include +#include namespace netkit::http::client { /** @@ -181,5 +183,6 @@ namespace netkit::http::client { return BP(ret).parse(); } }; - } + +#endif diff --git a/include/netkit/http/sync_server.hpp b/include/netkit/http/sync_server.hpp index f838fdd..447a6f6 100644 --- a/include/netkit/http/sync_server.hpp +++ b/include/netkit/http/sync_server.hpp @@ -12,12 +12,12 @@ */ #pragma once -#include #include #include -#include #include -#include +#include +#include +#include namespace netkit::http::server { /** diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index d3de734..475b892 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -1,10 +1,9 @@ #pragma once -#include -#include - #include #include +#include +#include #ifdef NETKIT_LINUX diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index c201508..828076b 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -33,16 +33,16 @@ #include // Socket headers -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // HTTP headers #include diff --git a/include/netkit/sock/sync_sock.hpp b/include/netkit/sock/sync_sock.hpp deleted file mode 100644 index ecfbde4..0000000 --- a/include/netkit/sock/sync_sock.hpp +++ /dev/null @@ -1,162 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sync_sock.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a synchronous socket class implementing the basic_sync_sock interface. - */ -#pragma once - -#include - -#include - -#ifdef NETKIT_WINDOWS -#include -#include -#elif NETKIT_UNIX -#include -#endif - -#include -#include -#include - -namespace netkit::sock { - class NETKIT_API sync_sock : public basic_sync_sock { - addr addr_; - type type_{}; -#ifdef NETKIT_WINDOWS - fd_t sockfd{INVALID_SOCKET}; -#else - fd_t sockfd{-1}; -#endif - sockaddr_storage sa_storage{}; - bool bound{false}; - mutable std::string old_bytes; - - [[nodiscard]] const sockaddr* get_sa() const; - [[nodiscard]] socklen_t get_sa_len() const; - void prep_sa(); - -#ifdef NETKIT_DKP - sockaddr_storage peer_addr{}; - bool has_peer{false}; -#endif - public: - /** - * @brief Constructs a sync_sock object. - * @param addr The socket address to bind to. - * @param t The socket type (tcp, udp, unix). - * @param opts The socket options (reuse_addr, no_reuse_addr). - */ - sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); -#ifdef NETKIT_UNIX - /** - * @brief Constructs a sync_sock object from an existing file descriptor. - * @param existing_fd The existing file descriptor. - * @param peer The peer address of the socket. - * @param t The socket type (tcp, udp, unix). - * @param opts The socket options (reuse_addr, no_reuse_addr). - */ - sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); -#endif - ~sync_sock() override; - sock::addr& get_addr() override; - [[nodiscard]] const sock::addr& get_addr() const override; - void connect() override; - /** - * @brief Bind the socket to the address. - */ - void bind() override; - /** - * @brief Unbind the socket from the address. - */ - void unbind() override; - /** - * @brief Listen for incoming connections. - * @param backlog The maximum number of pending connections. - * @note Very barebones, use with care. - */ - void listen(int backlog) override; - /** - * @brief Listen for incoming connections with default backlog. - * @note Uses SOMAXCONN as the default backlog value. - */ - void listen() override; - /** - * @brief Accept a connection from a client. - * @return sock_handle The socket handle for the accepted connection. - */ - [[nodiscard]] std::unique_ptr accept() override; - /** - * @brief Send data to the server. - * @param buf The data to send. - * @param len The length of the data. - * @return The number of bytes sent. - */ - int send(const void* buf, size_t len) override; - /** - * @brief Send a string to the server. - * @param buf The string to send. - */ - void send(const std::string& buf) override; - /** - * @brief Returns bytes that were read, further than the requested length (as defined by the eof parameter in recv()). - * @note This does NOT need to be called if you intend to call recv() again, as recv() prepends these bytes automatically. - * @note Call clear_overflow_bytes() after calling, if you do not want recv() to use these bytes again. - * @return std::string of overflow bytes. - */ - [[nodiscard]] std::string overflow_bytes() const override; - /** - * @brief Clear the overflow bytes buffer. - * @note This does NOT need to be called if you intend to call recv() again, as recv() prepends these bytes automatically. - */ - void clear_overflow_bytes() const override; - /** - * @brief Receive data from the server. - * @param timeout_seconds The timeout in seconds (-1 means wait indefinitely until match is found) - * @param match The substring to look for in received data. - * @param eof The number of bytes to read before considering the match complete. - * @return The received data as a sock_recv_result object. - */ - [[nodiscard]] recv_result recv(int timeout_seconds, const std::string& match, size_t eof) override; - /** - * @brief Receive data from the server. - * @note This is a recv() implementation that behaves like a Unix recv() call would. - * @return The received data as a sock_recv_result object. - */ - [[nodiscard]] recv_result recv() override; - - /* @brief Receive data from the server. - * @param timeout_seconds The timeout in seconds (-1 means wait indefinitely). - * @return The received data as a sock_recv_result - */ - [[nodiscard]] recv_result recv(int timeout_seconds) override; - /** - * @brief Receive data from the server until a specific match is found. - * @param timeout_seconds The timeout in seconds (-1 means wait indefinitely). - * @param match The substring to look for in received data. - * @return The received data as a sock_recv_result object. - */ - [[nodiscard]] recv_result recv(int timeout_seconds, const std::string& match) override; - /** - * @brief Receive data from the server until a specific match is found or a certain amount of data is received. - * @param timeout_seconds The timeout in seconds (-1 means wait indefinitely). - * @param eof The number of bytes to read before considering the match complete. - * @return The received data as a sock_recv_result object. - */ - [[nodiscard]] recv_result recv(int timeout_seconds, size_t eof) override; - /** - * @brief Close the socket. - */ - void close() override; - [[nodiscard]] sock::addr get_peer() const override; - [[nodiscard]] fd_t native_handle() const override; - void set_sock_opts(opt opts) override; - }; -} \ No newline at end of file diff --git a/include/netkit/sock/addr.hpp b/include/netkit/socket/addr.hpp similarity index 90% rename from include/netkit/sock/addr.hpp rename to include/netkit/socket/addr.hpp index 78261b4..c2faaba 100644 --- a/include/netkit/sock/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -1,89 +1,96 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sock_addr.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a class representing a socket address, which can be an IP address (IPv4 or IPv6), hostname, or file path. - * @see netkit::sock::sock_addr - * @see netkit::sock::basic_sync_sock - */ -#pragma once - -#include -#include -#include - -namespace netkit::sock { - class NETKIT_API addr final { - std::filesystem::path path{}; - std::string hostname{}; - std::string ip{}; - int port{}; - addr_type type{addr_type::hostname}; - friend addr get_peer(fd_t); - friend addr get_peer_async(fd_t); // jank - - addr() = default; - public: - /** - * @brief Constructs a sock_addr object. - * @param hostname The hostname or IP address to resolve. - * @param port The port to use. - * @param t The address type (ipv4, ipv6, hostname_ipv4, hostname_ipv6). - */ - addr(const std::string& hostname, int port, addr_type t); -#ifndef NETKIT_DKP - /** - * @brief Constructs a sock_addr object for a file path. - * @param path The file path to use. - * @throws parsing_error if the path does not exist. - */ - explicit addr(std::filesystem::path path); -#endif - /** - * @brief Check whether the address is IPv4 or IPv6. - * @return True if the address is IPv4, false if it is IPv6 or invalid. - */ - [[nodiscard]] bool is_ipv4() const noexcept; - /** - * @brief Check whether the address is IPv6. - * @return True if the address is IPv6, false if it is IPv4 or invalid. - */ - [[nodiscard]] bool is_ipv6() const noexcept; - /** - * @brief Check whether the address is a file path. - * @return True if the address is a file path, false if it is an IP address, hostname or invalid. - */ - [[nodiscard]] bool is_file_path() const noexcept; - /** - * @brief Get the stored IP address. - * @return The stored IP address. - */ - [[nodiscard]] std::string get_ip() const; - /** - * @brief Get the stored file path. - * @return The stored file path. - */ - [[nodiscard]] std::filesystem::path get_path() const; - /** - * @brief Get the stored hostname. - * @return The stored hostname. - */ - [[nodiscard]] std::string get_hostname() const; - /** - * @brief Get the stored port. - * @return The stored port. - */ - [[nodiscard]] int get_port() const; - /** - * @brief Get the stored type. - * @return The stored type. - */ - [[nodiscard]] addr_type get_type() const; - ~addr() = default; - }; -} +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file addr.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a class representing a socket address, which can be an IP address (IPv4 or IPv6), hostname, or file path. + * @see netkit::sock::addr + * @see netkit::sock::basic_sync_sock + */ +#pragma once + +#include +#include +#include + +namespace netkit::sock { + class addr; + + namespace native { + addr get_peer_async(fd_t fd); + addr get_peer(fd_t fd); + } + + class NETKIT_API addr final { + std::filesystem::path path{}; + std::string hostname{}; + std::string ip{}; + int port{}; + addr_type type{addr_type::hostname}; + friend addr native::get_peer(fd_t); + friend addr native::get_peer_async(fd_t); // jank + + addr() = default; + public: + /** + * @brief Constructs a sock_addr object. + * @param hostname The hostname or IP address to resolve. + * @param port The port to use. + * @param t The address type (ipv4, ipv6, hostname_ipv4, hostname_ipv6). + */ + addr(const std::string& hostname, int port, addr_type t); +#ifndef NETKIT_DKP + /** + * @brief Constructs a sock_addr object for a file path. + * @param path The file path to use. + * @throws parsing_error if the path does not exist. + */ + explicit addr(std::filesystem::path path); +#endif + /** + * @brief Check whether the address is IPv4 or IPv6. + * @return True if the address is IPv4, false if it is IPv6 or invalid. + */ + [[nodiscard]] bool is_ipv4() const noexcept; + /** + * @brief Check whether the address is IPv6. + * @return True if the address is IPv6, false if it is IPv4 or invalid. + */ + [[nodiscard]] bool is_ipv6() const noexcept; + /** + * @brief Check whether the address is a file path. + * @return True if the address is a file path, false if it is an IP address, hostname or invalid. + */ + [[nodiscard]] bool is_file_path() const noexcept; + /** + * @brief Get the stored IP address. + * @return The stored IP address. + */ + [[nodiscard]] std::string get_ip() const; + /** + * @brief Get the stored file path. + * @return The stored file path. + */ + [[nodiscard]] std::filesystem::path get_path() const; + /** + * @brief Get the stored hostname. + * @return The stored hostname. + */ + [[nodiscard]] std::string get_hostname() const; + /** + * @brief Get the stored port. + * @return The stored port. + */ + [[nodiscard]] int get_port() const; + /** + * @brief Get the stored type. + * @return The stored type. + */ + [[nodiscard]] addr_type get_type() const; + ~addr() = default; + }; +} diff --git a/include/netkit/sock/addr_type.hpp b/include/netkit/socket/addr_type.hpp similarity index 96% rename from include/netkit/sock/addr_type.hpp rename to include/netkit/socket/addr_type.hpp index 90568b7..dc22151 100644 --- a/include/netkit/sock/addr_type.hpp +++ b/include/netkit/socket/addr_type.hpp @@ -1,94 +1,94 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file addr_type.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides common socket types, options, and related enums and structs. - */ -#pragma once - -#include -#include - -#ifdef NETKIT_WINDOWS -#include -#endif - -namespace netkit::sock { - /** - * @brief Socket file descriptor type. - * @note This is a typedef for int, but can be changed to a different type if needed. - */ -#ifdef NETKIT_WINDOWS - using fd_t = SOCKET; -#elifdef NETKIT_UNIX - using fd_t = int; -#endif - - enum class addr_type { - ipv4 = 0, /* IPv4 address */ - ipv6 = 1, /* IPv6 address */ - hostname_ipv4 = 2, /* Hostname; resolve to IPv4 address */ - hostname_ipv6 = 3, /* Hostname; resolve to IPv6 address */ - hostname = 4, /* Hostname; resolve to IPv4 address */ - filename = 5 /* File path; used for Unix domain sockets */ - }; - - /** - * @brief Socket types. - */ - enum class type { - tcp, /* TCP socket */ - udp, /* UDP socket */ -#ifndef NETKIT_DKP - uds, /* UNIX domain socket */ -#endif - }; - /** - * @brief Socket options. - * @note These options can be used with the sync_sock class to set socket options. - */ - enum class opt { - reuse_addr = 1 << 0, /* Reuse address option */ - no_reuse_addr = 1 << 1, /* Do not reuse address option */ - no_delay = 1 << 2, /* Disable Nagle's algorithm (TCP_NODELAY) */ - keep_alive = 1 << 3, /* Enable keep-alive option */ - no_keep_alive = 1 << 4, /* Disable keep-alive option */ - no_blocking = 1 << 5, /* Set socket to non-blocking mode. Not necessarily supported. */ - blocking = 1 << 6, /* Set socket to blocking mode */ - }; - - /** - * @brief Socket receive status. - * @note This enum is used to indicate the status of a socket receive operation. - */ - enum class recv_status { - success, - timeout, - closed, - error - }; - - /** - * @brief Result of a socket receive operation. - * @note This struct contains the result data and the status of the receive operation. - */ - struct recv_result { - std::string data{}; - recv_status status{recv_status::success}; - }; - - inline opt operator|(opt lhs, opt rhs) { - using T = std::underlying_type_t; - return static_cast(static_cast(lhs) | static_cast(rhs)); - } - - inline bool operator&(opt lhs, opt rhs) { - using T = std::underlying_type_t; - return static_cast(lhs) & static_cast(rhs); - } -} +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file addr_type.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides common socket types, options, and related enums and structs. + */ +#pragma once + +#include +#include + +#ifdef NETKIT_WINDOWS +#include +#endif + +namespace netkit::sock { + /** + * @brief Socket file descriptor type. + * @note This is a typedef for int, but can be changed to a different type if needed. + */ +#ifdef NETKIT_WINDOWS + using fd_t = SOCKET; +#elifdef NETKIT_UNIX + using fd_t = int; +#endif + + enum class addr_type { + ipv4 = 0, /* IPv4 address */ + ipv6 = 1, /* IPv6 address */ + hostname_ipv4 = 2, /* Hostname; resolve to IPv4 address */ + hostname_ipv6 = 3, /* Hostname; resolve to IPv6 address */ + hostname = 4, /* Hostname; resolve to IPv4 address */ + filename = 5 /* File path; used for Unix domain sockets */ + }; + + /** + * @brief Socket types. + */ + enum class type { + tcp, /* TCP socket */ + udp, /* UDP socket */ +#ifndef NETKIT_DKP + uds, /* UNIX domain socket */ +#endif + }; + /** + * @brief Socket options. + * @note These options can be used with the sync_sock class to set socket options. + */ + enum class opt { + reuse_addr = 1 << 0, /* Reuse address option */ + no_reuse_addr = 1 << 1, /* Do not reuse address option */ + no_delay = 1 << 2, /* Disable Nagle's algorithm (TCP_NODELAY) */ + keep_alive = 1 << 3, /* Enable keep-alive option */ + no_keep_alive = 1 << 4, /* Disable keep-alive option */ + no_blocking = 1 << 5, /* Set socket to non-blocking mode. Not necessarily supported. */ + blocking = 1 << 6, /* Set socket to blocking mode */ + }; + + /** + * @brief Socket receive status. + * @note This enum is used to indicate the status of a socket receive operation. + */ + enum class recv_status { + success, + timeout, + closed, + error + }; + + /** + * @brief Result of a socket receive operation. + * @note This struct contains the result data and the status of the receive operation. + */ + struct recv_result { + std::string data{}; + recv_status status{recv_status::success}; + }; + + inline opt operator|(opt lhs, opt rhs) { + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline bool operator&(opt lhs, opt rhs) { + using T = std::underlying_type_t; + return static_cast(lhs) & static_cast(rhs); + } +} diff --git a/include/netkit/sock/basic_async_sock.hpp b/include/netkit/socket/native/basic_native_async_sock.hpp similarity index 86% rename from include/netkit/sock/basic_async_sock.hpp rename to include/netkit/socket/native/basic_native_async_sock.hpp index d18b088..080e92d 100644 --- a/include/netkit/sock/basic_async_sock.hpp +++ b/include/netkit/socket/native/basic_native_async_sock.hpp @@ -16,25 +16,25 @@ #ifdef NETKIT_LINUX #include -#include -#include #include +#include +#include -namespace netkit::sock { +namespace netkit::sock::native { /** * @brief A class that represents an asynchronous socket. * @note This class is an abstract base class and should not be instantiated directly. * @note Use the async_sock class instead. */ - class NETKIT_API basic_async_sock { + class NETKIT_API basic_native_async_sock { public: - virtual ~basic_async_sock() = default; + virtual ~basic_native_async_sock() = default; virtual netkit::io::task connect() = 0; virtual void bind() = 0; virtual void unbind() = 0; virtual void listen() = 0; virtual void listen(int backlog) = 0; - virtual netkit::io::task> accept() = 0; + virtual netkit::io::task> accept() = 0; virtual netkit::io::task send(const void* buf, std::size_t len) = 0; [[nodiscard]] virtual netkit::io::task recv(void* buf, std::size_t len) = 0; virtual void close() = 0; diff --git a/include/netkit/sock/basic_sync_sock.hpp b/include/netkit/socket/native/basic_native_sync_sock.hpp similarity index 63% rename from include/netkit/sock/basic_sync_sock.hpp rename to include/netkit/socket/native/basic_native_sync_sock.hpp index 36c5924..d7dceba 100644 --- a/include/netkit/sock/basic_sync_sock.hpp +++ b/include/netkit/socket/native/basic_native_sync_sock.hpp @@ -12,32 +12,26 @@ #pragma once #include -#include -#include +#include +#include -namespace netkit::sock { +namespace netkit::sock::native { /** * @brief A class that represents a synchronous socket. * @note This class is an abstract base class and should not be instantiated directly. * @note Use the sync_sock class instead. */ - class NETKIT_API basic_sync_sock { + class NETKIT_API basic_native_sync_sock { public: - virtual ~basic_sync_sock() = default; + virtual ~basic_native_sync_sock() = default; virtual void connect() = 0; virtual void bind() = 0; virtual void unbind() = 0; virtual void listen(int backlog) = 0; virtual void listen() = 0; - [[nodiscard]] virtual std::unique_ptr accept() = 0; - virtual int send(const void* buf, size_t len) = 0; - virtual void send(const std::string& buf) = 0; - [[nodiscard]] virtual recv_result recv(int timeout_seconds) = 0; - [[nodiscard]] virtual recv_result recv(int timeout_seconds, const std::string& match) = 0; - [[nodiscard]] virtual recv_result recv(int timeout_seconds, const std::string& match, size_t eof) = 0; - [[nodiscard]] virtual recv_result recv(int timeout_seconds, size_t eof) = 0; - [[nodiscard]] virtual recv_result recv() = 0; - [[nodiscard]] virtual std::string overflow_bytes() const { return {}; }; + [[nodiscard]] virtual std::unique_ptr accept() = 0; + virtual std::size_t send(const void* buf, std::size_t len) = 0; + [[nodiscard]] virtual std::size_t recv(void* buf, std::size_t len) = 0; virtual addr& get_addr() { throw std::logic_error{"socket does not have an addr object"}; } diff --git a/include/netkit/sock/async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp similarity index 67% rename from include/netkit/sock/async_sock.hpp rename to include/netkit/socket/native/native_async_sock.hpp index 17fbb44..8610f6c 100644 --- a/include/netkit/sock/async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -4,10 +4,10 @@ * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. * - * @file async_sock.hpp + * @file native_async_sock.hpp * @license MIT * @note Part of the Netkit library. - * @brief Provides an asynchronous socket class implementing the basic_async_sock interface. + * @brief Provides an asynchronous socket class implementing the basic_native_async_sock interface. */ #pragma once @@ -15,18 +15,16 @@ #ifdef NETKIT_LINUX -#include - -#include -#include -#include - #include +#include +#include +#include +#include -namespace netkit::sock { +namespace netkit::sock::native { constexpr fd_t INVALID_SOCKET = -1; - class NETKIT_API async_sock : public basic_async_sock { + class NETKIT_API native_async_sock : public basic_native_async_sock { addr addr_; type type_{}; fd_t sockfd{INVALID_SOCKET}; @@ -40,9 +38,9 @@ namespace netkit::sock { io::io_context& context_; public: - async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); - async_sock(io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); - ~async_sock() override; + native_async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); + native_async_sock(io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); + ~native_async_sock() override; sock::addr& get_addr() override; [[nodiscard]] const sock::addr& get_addr() const override; netkit::io::task connect() override; @@ -51,7 +49,7 @@ namespace netkit::sock { void unbind() override; void listen(int backlog) override; void listen() override; - [[nodiscard]] netkit::io::task> accept() override; + [[nodiscard]] netkit::io::task> accept() override; netkit::io::task send(const void* buf, size_t len) override; [[nodiscard]] netkit::io::task recv(void* buf, std::size_t size) override; void close() override; diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp new file mode 100644 index 0000000..5ff4b23 --- /dev/null +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -0,0 +1,112 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file native_sync_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a synchronous socket class implementing the basic_native_sync_sock interface. + */ +#pragma once + +#include + +#include + +#ifdef NETKIT_WINDOWS +#include +#include +#elif NETKIT_UNIX +#include +#endif + +#include +#include +#include + +namespace netkit::sock::native { + class NETKIT_API native_sync_sock : public basic_native_sync_sock { + addr addr_; + type type_{}; +#ifdef NETKIT_WINDOWS + fd_t sockfd{INVALID_SOCKET}; +#else + fd_t sockfd{-1}; +#endif + sockaddr_storage sa_storage{}; + bool bound{false}; + mutable std::string old_bytes; + + [[nodiscard]] const sockaddr* get_sa() const; + [[nodiscard]] socklen_t get_sa_len() const; + void prep_sa(); + +#ifdef NETKIT_DKP + sockaddr_storage peer_addr{}; + bool has_peer{false}; +#endif + public: + /** + * @brief Constructs a sync_sock object. + * @param addr The socket address to bind to. + * @param t The socket type (tcp, udp, unix). + * @param opts The socket options (reuse_addr, no_reuse_addr). + */ + native_sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); +#ifdef NETKIT_UNIX + /** + * @brief Constructs a sync_sock object from an existing file descriptor. + * @param existing_fd The existing file descriptor. + * @param peer The peer address of the socket. + * @param t The socket type (tcp, udp, unix). + * @param opts The socket options (reuse_addr, no_reuse_addr). + */ + native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); +#endif + ~native_sync_sock() override; + sock::addr& get_addr() override; + [[nodiscard]] const sock::addr& get_addr() const override; + void connect() override; + /** + * @brief Bind the socket to the address. + */ + void bind() override; + /** + * @brief Unbind the socket from the address. + */ + void unbind() override; + /** + * @brief Listen for incoming connections. + * @param backlog The maximum number of pending connections. + * @note Very barebones, use with care. + */ + void listen(int backlog) override; + /** + * @brief Listen for incoming connections with default backlog. + * @note Uses SOMAXCONN as the default backlog value. + */ + void listen() override; + /** + * @brief Accept a connection from a client. + * @return sock_handle The socket handle for the accepted connection. + */ + [[nodiscard]] std::unique_ptr accept() override; + /** + * @brief Send data to the server. + * @param buf The data to send. + * @param len The length of the data. + * @return The number of bytes sent. + */ + std::size_t send(const void* buf, std::size_t len) override; + [[nodiscard]] std::size_t recv(void* buf, std::size_t len) override; + /** + * @brief Close the socket. + */ + void close() override; + [[nodiscard]] sock::addr get_peer() const override; + [[nodiscard]] fd_t native_handle() const override; + void set_sock_opts(opt opts) override; + }; +} \ No newline at end of file diff --git a/include/netkit/sock/openssl/ssl_sync_sock.hpp b/include/netkit/socket/openssl/ssl_sync_sock.hpp similarity index 95% rename from include/netkit/sock/openssl/ssl_sync_sock.hpp rename to include/netkit/socket/openssl/ssl_sync_sock.hpp index 063fbe1..f2584d1 100644 --- a/include/netkit/sock/openssl/ssl_sync_sock.hpp +++ b/include/netkit/socket/openssl/ssl_sync_sock.hpp @@ -15,16 +15,14 @@ #ifdef NETKIT_OPENSSL -#include -#include -#include -#include - -#include -#include - +#include <../native/basic_native_sync_sock.hpp> #include #include +#include +#include +#include +#include +#include namespace netkit::sock { diff --git a/include/netkit/sock/sock_peer.hpp b/include/netkit/socket/sock_peer.hpp similarity index 89% rename from include/netkit/sock/sock_peer.hpp rename to include/netkit/socket/sock_peer.hpp index 0e6e249..831ccb4 100644 --- a/include/netkit/sock/sock_peer.hpp +++ b/include/netkit/socket/sock_peer.hpp @@ -1,18 +1,18 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sock_peer.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a function to get the peer address of a socket. - */ -#pragma once - -#include - -namespace netkit::sock { - addr get_peer(fd_t sockfd); +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file sock_peer.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a function to get the peer address of a socket. + */ +#pragma once + +#include + +namespace netkit::sock { + addr get_peer(fd_t sockfd); } \ No newline at end of file diff --git a/include/netkit/sock/ssl_sync_sock.hpp b/include/netkit/socket/ssl_sync_sock.hpp similarity index 83% rename from include/netkit/sock/ssl_sync_sock.hpp rename to include/netkit/socket/ssl_sync_sock.hpp index 06d8187..252daff 100644 --- a/include/netkit/sock/ssl_sync_sock.hpp +++ b/include/netkit/socket/ssl_sync_sock.hpp @@ -5,7 +5,7 @@ #endif #if defined(NETKIT_OPENSSL) -#include +#include #elif defined(NETKIT_WOLFSSL) #include #endif \ No newline at end of file diff --git a/include/netkit/sock/ssl_sync_sock_enum.hpp b/include/netkit/socket/ssl_sync_sock_enum.hpp similarity index 100% rename from include/netkit/sock/ssl_sync_sock_enum.hpp rename to include/netkit/socket/ssl_sync_sock_enum.hpp diff --git a/include/netkit/sock/wolfssl/ssl_sync_sock.hpp b/include/netkit/socket/wolfssl/ssl_sync_sock.hpp similarity index 96% rename from include/netkit/sock/wolfssl/ssl_sync_sock.hpp rename to include/netkit/socket/wolfssl/ssl_sync_sock.hpp index 4d506ab..b7b6274 100644 --- a/include/netkit/sock/wolfssl/ssl_sync_sock.hpp +++ b/include/netkit/socket/wolfssl/ssl_sync_sock.hpp @@ -18,13 +18,12 @@ #include #endif -#include -#include -#include -#include - +#include <../native/basic_native_sync_sock.hpp> #include #include +#include +#include +#include namespace netkit::sock { #ifdef NETKIT_WOLFSSL diff --git a/include/netkit/sock/wolfssl/user_settings.h b/include/netkit/socket/wolfssl/user_settings.h similarity index 94% rename from include/netkit/sock/wolfssl/user_settings.h rename to include/netkit/socket/wolfssl/user_settings.h index b866974..f190d13 100644 --- a/include/netkit/sock/wolfssl/user_settings.h +++ b/include/netkit/socket/wolfssl/user_settings.h @@ -1,76 +1,76 @@ -#pragma once - -#include -#include -#include - -#define NO_WRITEV -#define SINGLE_THREADED -#define DEVKITPRO -#define SIZEOF_LONG_LONG 8 -#define BIG_ENDIAN_ORDER -#define NO_FILESYSTEM - -#define NO_DEV_URANDOM -#define NO_DEV_RANDOM -#define CUSTOM_RAND_GENERATE_SEED gen_seed - -static int gen_seed(unsigned char* output, unsigned int sz) { - static uint32_t state = 0; - if (state == 0) { - state = (uint32_t)gettime(); - state ^= (uintptr_t)&state; - state ^= (uintptr_t)&output; - state ^= state << 13; - state ^= state >> 17; - state ^= state << 5; - } - - for (unsigned int i = 0; i < sz; i++) { - state ^= state << 13; - state ^= state >> 17; - state ^= state << 5; - - if ((i & 7) == 0) - state ^= (uint32_t)gettime(); - - output[i] = state & 0xFF; - } - - return 0; -} - -#define HAVE_HASHDRBG -#define OPENSSL_COEXIST - -/* uncomment if wolfssl debugging is desired -#define DEBUG_WOLFSSL -*/ -#define WOLFSSL_ALT_CERT_CHAINS - -#define WOLFSSL_SNI -#define HAVE_SNI -#define HAVE_TLS_EXTENSIONS - -#define WOLFSSL_TLS10 -#define WOLFSSL_TLS11 -#define WOLFSSL_TLS12 -#define WOLFSSL_TLS13 - -#define HAVE_SUPPORTED_CURVES -#define HAVE_ECC -#define HAVE_ECC384 -#define HAVE_HKDF -#define HAVE_AESGCM -#define HAVE_AEAD -#define WC_RSA_PSS -#define HAVE_RSA -#define HAVE_FFDHE_4096 - -#define TFM_TIMING_RESISTANT -#define ECC_TIMING_RESISTANT -#define WC_RSA_BLINDING - -#ifndef GEKKO -#define GEKKO +#pragma once + +#include +#include +#include + +#define NO_WRITEV +#define SINGLE_THREADED +#define DEVKITPRO +#define SIZEOF_LONG_LONG 8 +#define BIG_ENDIAN_ORDER +#define NO_FILESYSTEM + +#define NO_DEV_URANDOM +#define NO_DEV_RANDOM +#define CUSTOM_RAND_GENERATE_SEED gen_seed + +static int gen_seed(unsigned char* output, unsigned int sz) { + static uint32_t state = 0; + if (state == 0) { + state = (uint32_t)gettime(); + state ^= (uintptr_t)&state; + state ^= (uintptr_t)&output; + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + } + + for (unsigned int i = 0; i < sz; i++) { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + + if ((i & 7) == 0) + state ^= (uint32_t)gettime(); + + output[i] = state & 0xFF; + } + + return 0; +} + +#define HAVE_HASHDRBG +#define OPENSSL_COEXIST + +/* uncomment if wolfssl debugging is desired +#define DEBUG_WOLFSSL +*/ +#define WOLFSSL_ALT_CERT_CHAINS + +#define WOLFSSL_SNI +#define HAVE_SNI +#define HAVE_TLS_EXTENSIONS + +#define WOLFSSL_TLS10 +#define WOLFSSL_TLS11 +#define WOLFSSL_TLS12 +#define WOLFSSL_TLS13 + +#define HAVE_SUPPORTED_CURVES +#define HAVE_ECC +#define HAVE_ECC384 +#define HAVE_HKDF +#define HAVE_AESGCM +#define HAVE_AEAD +#define WC_RSA_PSS +#define HAVE_RSA +#define HAVE_FFDHE_4096 + +#define TFM_TIMING_RESISTANT +#define ECC_TIMING_RESISTANT +#define WC_RSA_BLINDING + +#ifndef GEKKO +#define GEKKO #endif \ No newline at end of file diff --git a/src/body/stream_body.cpp b/src/body/stream_body.cpp index 33dfeb9..3a96fa0 100644 --- a/src/body/stream_body.cpp +++ b/src/body/stream_body.cpp @@ -2,6 +2,8 @@ #define NOMINMAX // some windows shit #include +#ifdef NETKIT_HTTP + netkit::body::read_result netkit::body::stream_body::read(char* out, std::size_t max_bytes) noexcept { if (max_bytes == 0) return {read_status::ok, 0}; @@ -85,4 +87,6 @@ netkit::body::read_result netkit::body::stream_body::read(char* out, std::size_t return {read_status::ok, n}; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/src/c/sock/addr.cpp b/src/c/sock/addr.cpp index ed3f0bb..3f1f72c 100644 --- a/src/c/sock/addr.cpp +++ b/src/c/sock/addr.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include struct netkit_sock_addr { std::unique_ptr impl; diff --git a/src/c/sock/ssl_sync_sock.cpp b/src/c/sock/ssl_sync_sock.cpp index 01a88ec..a247c72 100644 --- a/src/c/sock/ssl_sync_sock.cpp +++ b/src/c/sock/ssl_sync_sock.cpp @@ -1,11 +1,11 @@ #if defined(NETKIT_OPENSSL) || defined(NETKIT_WOLFSSL) -#include #include +#include #include #include #include -#include +#include struct netkit_sync_sock { std::unique_ptr impl; diff --git a/src/c/sock/sync_sock.cpp b/src/c/sock/sync_sock.cpp index 464c4e6..868ffbb 100644 --- a/src/c/sock/sync_sock.cpp +++ b/src/c/sock/sync_sock.cpp @@ -1,9 +1,9 @@ +#include <../../../include/netkit/socket/native/native_sync_sock.hpp> #include #include #include #include #include -#include struct netkit_sync_sock { std::unique_ptr impl; diff --git a/src/http/sync_client.cpp b/src/http/sync_client.cpp index 8e5f17e..d050cb6 100644 --- a/src/http/sync_client.cpp +++ b/src/http/sync_client.cpp @@ -9,12 +9,13 @@ * @note Part of the Netkit library. * @brief Implementation of the synchronous HTTP client class. */ -#include -#include -#include +#ifdef NETKIT_HTTP + #include +#include #include - +#include +#include #include std::string netkit::http::client::sync_client::make_request(const std::string& request) const { @@ -207,3 +208,5 @@ netkit::http::method netkit::http::client::sync_client::get_method() const { netkit::http::version netkit::http::client::sync_client::get_version() const { return this->v; } + +#endif \ No newline at end of file diff --git a/src/sock/addr.cpp b/src/socket/addr.cpp similarity index 96% rename from src/sock/addr.cpp rename to src/socket/addr.cpp index 3b14780..4e7780d 100644 --- a/src/sock/addr.cpp +++ b/src/socket/addr.cpp @@ -1,285 +1,286 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file addr.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the sock_addr class. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef NETKIT_DKP -#include -#include -#endif - -#if defined(NETKIT_UNIX) && !defined(NETKIT_DKP) -#include -#include -#include -#endif - -/* solely for use internally */ -#ifndef NETKIT_DKP -#ifdef NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER -[[nodiscard]] static netkit::network::ip_list get_a_aaaa_from_hostname(const std::string& hostname) { - if (hostname == "localhost") { - return {NETKIT_LOCALHOST_IPV4, NETKIT_LOCALHOST_IPV6}; - } - - auto nameservers = netkit::dns::get_nameservers(); - - if (nameservers.contains_ipv4() == false && nameservers.contains_ipv6() == false) { - nameservers = { - {NETKIT_FALLBACK_IPV4_DNS_1, NETKIT_FALLBACK_IPV4_DNS_2}, - {NETKIT_FALLBACK_IPV6_DNS_1, NETKIT_FALLBACK_IPV6_DNS_2}, - }; - } - - netkit::dns::sync_resolver resolver(nameservers); - - auto records = resolver.query_records(hostname, netkit::dns::record_type::A); - auto records_v6 = resolver.query_records(hostname, netkit::dns::record_type::AAAA); - - std::string v4{}; - std::string v6{}; - - records.insert(records.end(), records_v6.begin(), records_v6.end()); - - for (const auto& rec : records) { - std::visit([&v4, &v6](T0&& data) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - v4 = data.ip.get_ipv4(); - } else if constexpr (std::is_same_v) { - v6 = data.ip.get_ipv6(); - } - }, rec.data); - } - - if (v4.empty() && v6.empty()) { - throw netkit::dns_error("no valid A or AAAA records found for hostname: " + hostname); - } - - return {v4, v6}; -} -#else -[[nodiscard]] static netkit::network::ip_list get_a_aaaa_from_hostname(const std::string& hostname) { - if (hostname == "localhost") { - return {NETKIT_LOCALHOST_IPV4, NETKIT_LOCALHOST_IPV6}; - } - - addrinfo hints{}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - addrinfo* result = nullptr; - - int res = getaddrinfo(hostname.c_str(), nullptr, &hints, &result); - if (res != 0) { -#ifdef _WIN32 - throw netkit::dns_error("getaddrinfo failed: " + std::to_string(res)); -#else - throw netkit::dns_error(gai_strerror(res)); -#endif - } - - std::string v4{}; - std::string v6{}; - - for (addrinfo* ptr = result; ptr != nullptr; ptr = ptr->ai_next) { - char buffer[INET6_ADDRSTRLEN] = {0}; - - if (ptr->ai_family == AF_INET) { - auto* ipv4 = reinterpret_cast(ptr->ai_addr); - inet_ntop(AF_INET, &ipv4->sin_addr, buffer, sizeof(buffer)); - v4 = buffer; - } - else if (ptr->ai_family == AF_INET6) { - auto* ipv6 = reinterpret_cast(ptr->ai_addr); - inet_ntop(AF_INET6, &ipv6->sin6_addr, buffer, sizeof(buffer)); - v6 = buffer; - } - } - - freeaddrinfo(result); - - if (v4.empty() && v6.empty()) { - throw netkit::dns_error("no valid A or AAAA records found for hostname: " + hostname); - } - - return {v4, v6}; -} -#endif -#endif - -netkit::sock::addr::addr(const std::string& hostname, int port, addr_type t) : - hostname(hostname), port(port), type(t) { - -#ifdef NETKIT_DKP - static std::once_flag flag; - std::call_once(flag, [] { - s32 ret; - - char localip[16] = {0}; - char gateway[16] = {0}; - char netmask[16] = {0}; - - ret = if_config ( localip, netmask, gateway, true, 20); - if (ret < 0) { - throw socket_error("failed to get local network interface address"); - } - }); -#elif NETKIT_WINDOWS - static std::once_flag wsa_flag; - - std::call_once(wsa_flag, [] { - WSADATA wsa; - int res = WSAStartup(MAKEWORD(2, 2), &wsa); - if (res != 0) { - throw netkit::socket_error("WSAStartup failed"); - } - }); -#endif - -#ifndef NETKIT_DKP - const auto resolve_host = [](const std::string& h, bool t) -> std::string { - auto ip_list = get_a_aaaa_from_hostname(h); - auto ip = t ? ip_list.get_ipv6() : ip_list.get_ipv4(); - - return ip; - }; - - if (type == addr_type::hostname) { - auto ip6 = resolve_host(hostname, true); - auto ip4 = resolve_host(hostname, false); - - if (!ip6.empty() && netkit::network::usable_ipv6_address_exists()) { - ip = ip6; - type = addr_type::ipv6; - } else if (!ip4.empty()) { - ip = ip4; - type = addr_type::ipv4; - } else { - throw ip_error("sock_addr(): could not resolve hostname"); - } - } else if (type == addr_type::hostname_ipv4) { - ip = resolve_host(hostname, false); - type = netkit::sock::addr_type::ipv4; -#else - if (type == addr_type::hostname || type == addr_type::hostname_ipv4) { - netkit::network::ip_list result; - hostent* host = gethostbyname(hostname.c_str()); - - if (!host) { - throw netkit::dns_error("failed to resolve hostname"); - } - - if (host->h_addrtype != AF_INET) { - throw netkit::dns_error("not an IPv4 result"); - } - - for (int i = 0; host->h_addr_list[i] != nullptr; i++) { - in_addr addr{}; - memcpy(&addr, host->h_addr_list[i], sizeof(addr)); - - const char* _ip = inet_ntoa(addr); - if (_ip) - result.set_ipv4(_ip); - - break; - } - - ip = result.get_ipv4(); - type = netkit::sock::addr_type::ipv4; -#endif -#ifndef NETKIT_DKP - } else if (type == addr_type::hostname_ipv6) { - ip = resolve_host(hostname, true); - type = netkit::sock::addr_type::ipv6; -#endif - } else if (type == addr_type::ipv4 || type == addr_type::ipv6) { - ip = hostname; - } else { - throw ip_error("sock_addr(): invalid address type"); - } - - if (ip.empty()) { - throw ip_error("sock_addr(): could not resolve hostname or invalid IP address"); - } - - if (!network::is_ipv4(ip) && !network::is_ipv6(ip)) { - throw parsing_error("sock_addr(): invalid address type (constructor)"); - } - - if (this->hostname == ip) { - this->hostname.clear(); - } -} - -#ifndef NETKIT_DKP -netkit::sock::addr::addr(std::filesystem::path path) : path(std::move(path)), type(addr_type::filename) {} -#endif - -bool netkit::sock::addr::is_ipv4() const noexcept { - return type == addr_type::ipv4; -} - -bool netkit::sock::addr::is_ipv6() const noexcept { - return type == addr_type::ipv6; -} - -bool netkit::sock::addr::is_file_path() const noexcept { - return type == addr_type::filename; -} - -std::string netkit::sock::addr::get_ip() const { - if (type == addr_type::filename) { - throw parsing_error("sock_addr(): cannot get IP from a file path"); - } - - return this->ip; -} - -[[nodiscard]] std::filesystem::path netkit::sock::addr::get_path() const { - if (type != addr_type::filename) { - throw parsing_error("sock_addr(): cannot get path from an IP address or hostname"); - } - return this->path; -} - -std::string netkit::sock::addr::get_hostname() const { - if (hostname.empty()) { - throw parsing_error("hostname is empty, use get_ip() instead"); - } - if (type == addr_type::filename) { - throw parsing_error("sock_addr(): cannot get hostname from a file path"); - } - return hostname; -} - -int netkit::sock::addr::get_port() const { - if (type == addr_type::filename) { - throw parsing_error("sock_addr(): cannot get port from a file path"); - } - - return port; -} - -netkit::sock::addr_type netkit::sock::addr::get_type() const { - return type; +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file addr.cpp + * @license MIT + * @note Part of the Netkit library. + * @brief Implementation of the sock_addr class. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef NETKIT_DKP +#include +#include +#endif + +#if defined(NETKIT_UNIX) && !defined(NETKIT_DKP) +#include +#include +#include +#endif + +/* solely for use internally */ +#ifndef NETKIT_DKP +#ifdef NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER +[[nodiscard]] static netkit::network::ip_list get_a_aaaa_from_hostname(const std::string& hostname) { + if (hostname == "localhost") { + return {NETKIT_LOCALHOST_IPV4, NETKIT_LOCALHOST_IPV6}; + } + + auto nameservers = netkit::dns::get_nameservers(); + + if (nameservers.contains_ipv4() == false && nameservers.contains_ipv6() == false) { + nameservers = { + {NETKIT_FALLBACK_IPV4_DNS_1, NETKIT_FALLBACK_IPV4_DNS_2}, + {NETKIT_FALLBACK_IPV6_DNS_1, NETKIT_FALLBACK_IPV6_DNS_2}, + }; + } + + netkit::dns::sync_resolver resolver(nameservers); + + auto records = resolver.query_records(hostname, netkit::dns::record_type::A); + auto records_v6 = resolver.query_records(hostname, netkit::dns::record_type::AAAA); + + std::string v4{}; + std::string v6{}; + + records.insert(records.end(), records_v6.begin(), records_v6.end()); + + for (const auto& rec : records) { + std::visit([&v4, &v6](T0&& data) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + v4 = data.ip.get_ipv4(); + } else if constexpr (std::is_same_v) { + v6 = data.ip.get_ipv6(); + } + }, rec.data); + } + + if (v4.empty() && v6.empty()) { + throw netkit::dns_error("no valid A or AAAA records found for hostname: " + hostname); + } + + return {v4, v6}; +} +#else +[[nodiscard]] static netkit::network::ip_list get_a_aaaa_from_hostname(const std::string& hostname) { + if (hostname == "localhost") { + return {NETKIT_LOCALHOST_IPV4, NETKIT_LOCALHOST_IPV6}; + } + + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + addrinfo* result = nullptr; + + int res = getaddrinfo(hostname.c_str(), nullptr, &hints, &result); + if (res != 0) { +#ifdef _WIN32 + throw netkit::dns_error("getaddrinfo failed: " + std::to_string(res)); +#else + throw netkit::dns_error(gai_strerror(res)); +#endif + } + + std::string v4{}; + std::string v6{}; + + for (addrinfo* ptr = result; ptr != nullptr; ptr = ptr->ai_next) { + char buffer[INET6_ADDRSTRLEN] = {0}; + + if (ptr->ai_family == AF_INET) { + auto* ipv4 = reinterpret_cast(ptr->ai_addr); + inet_ntop(AF_INET, &ipv4->sin_addr, buffer, sizeof(buffer)); + v4 = buffer; + } + else if (ptr->ai_family == AF_INET6) { + auto* ipv6 = reinterpret_cast(ptr->ai_addr); + inet_ntop(AF_INET6, &ipv6->sin6_addr, buffer, sizeof(buffer)); + v6 = buffer; + } + } + + freeaddrinfo(result); + + if (v4.empty() && v6.empty()) { + throw netkit::dns_error("no valid A or AAAA records found for hostname: " + hostname); + } + + return {v4, v6}; +} +#endif +#endif + +netkit::sock::addr::addr(const std::string& hostname, int port, addr_type t) : + hostname(hostname), port(port), type(t) { + +#ifdef NETKIT_DKP + static std::once_flag flag; + std::call_once(flag, [] { + s32 ret; + + char localip[16] = {0}; + char gateway[16] = {0}; + char netmask[16] = {0}; + + ret = if_config ( localip, netmask, gateway, true, 20); + if (ret < 0) { + throw socket_error("failed to get local network interface address"); + } + }); +#elif NETKIT_WINDOWS + static std::once_flag wsa_flag; + + std::call_once(wsa_flag, [] { + WSADATA wsa; + int res = WSAStartup(MAKEWORD(2, 2), &wsa); + if (res != 0) { + throw netkit::socket_error("WSAStartup failed"); + } + }); +#endif + +#ifndef NETKIT_DKP + const auto resolve_host = [](const std::string& h, bool t) -> std::string { + auto ip_list = get_a_aaaa_from_hostname(h); + auto ip = t ? ip_list.get_ipv6() : ip_list.get_ipv4(); + + return ip; + }; + + if (type == addr_type::hostname) { + auto ip6 = resolve_host(hostname, true); + auto ip4 = resolve_host(hostname, false); + + if (!ip6.empty() && netkit::network::usable_ipv6_address_exists()) { + ip = ip6; + type = addr_type::ipv6; + } else if (!ip4.empty()) { + ip = ip4; + type = addr_type::ipv4; + } else { + throw ip_error("sock_addr(): could not resolve hostname"); + } + } else if (type == addr_type::hostname_ipv4) { + ip = resolve_host(hostname, false); + type = netkit::sock::addr_type::ipv4; +#else + if (type == addr_type::hostname || type == addr_type::hostname_ipv4) { + netkit::network::ip_list result; + hostent* host = gethostbyname(hostname.c_str()); + + if (!host) { + throw netkit::dns_error("failed to resolve hostname"); + } + + if (host->h_addrtype != AF_INET) { + throw netkit::dns_error("not an IPv4 result"); + } + + for (int i = 0; host->h_addr_list[i] != nullptr; i++) { + in_addr addr{}; + memcpy(&addr, host->h_addr_list[i], sizeof(addr)); + + const char* _ip = inet_ntoa(addr); + if (_ip) + result.set_ipv4(_ip); + + break; + } + + ip = result.get_ipv4(); + type = netkit::sock::addr_type::ipv4; +#endif +#ifndef NETKIT_DKP + } else if (type == addr_type::hostname_ipv6) { + ip = resolve_host(hostname, true); + type = netkit::sock::addr_type::ipv6; +#endif + } else if (type == addr_type::ipv4 || type == addr_type::ipv6) { + ip = hostname; + } else { + throw ip_error("sock_addr(): invalid address type"); + } + + if (ip.empty()) { + throw ip_error("sock_addr(): could not resolve hostname or invalid IP address"); + } + + if (!network::is_ipv4(ip) && !network::is_ipv6(ip)) { + throw parsing_error("sock_addr(): invalid address type (constructor)"); + } + + if (this->hostname == ip) { + this->hostname.clear(); + } +} + +#ifndef NETKIT_DKP +netkit::sock::addr::addr(std::filesystem::path path) : path(std::move(path)), type(addr_type::filename) {} +#endif + +bool netkit::sock::addr::is_ipv4() const noexcept { + return type == addr_type::ipv4; +} + +bool netkit::sock::addr::is_ipv6() const noexcept { + return type == addr_type::ipv6; +} + +bool netkit::sock::addr::is_file_path() const noexcept { + return type == addr_type::filename; +} + +std::string netkit::sock::addr::get_ip() const { + if (type == addr_type::filename) { + throw parsing_error("sock_addr(): cannot get IP from a file path"); + } + + return this->ip; +} + +[[nodiscard]] std::filesystem::path netkit::sock::addr::get_path() const { + if (type != addr_type::filename) { + throw parsing_error("sock_addr(): cannot get path from an IP address or hostname"); + } + return this->path; +} + +std::string netkit::sock::addr::get_hostname() const { + if (hostname.empty()) { + throw parsing_error("hostname is empty, use get_ip() instead"); + } + if (type == addr_type::filename) { + throw parsing_error("sock_addr(): cannot get hostname from a file path"); + } + return hostname; +} + +int netkit::sock::addr::get_port() const { + if (type == addr_type::filename) { + throw parsing_error("sock_addr(): cannot get port from a file path"); + } + + return port; +} + +netkit::sock::addr_type netkit::sock::addr::get_type() const { + return type; } \ No newline at end of file diff --git a/src/sock/async_sock.cpp b/src/socket/native/native_async_socket.cpp similarity index 81% rename from src/sock/async_sock.cpp rename to src/socket/native/native_async_socket.cpp index 1e3171f..8d4d871 100644 --- a/src/sock/async_sock.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -11,12 +11,10 @@ * @brief Implementation of the asynchronous socket class. */ -#include -#include +#include #include - +#include #include -#include #ifdef NETKIT_LINUX @@ -27,8 +25,8 @@ #include #include -namespace netkit::sock { -netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { +namespace netkit::sock::native { +netkit::sock::addr get_peer_async(netkit::sock::fd_t sockfd) { sockaddr_storage addr_storage{}; socklen_t addr_len = sizeof(addr_storage); @@ -60,11 +58,11 @@ netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { } } -const sockaddr* netkit::sock::async_sock::get_sa() const { +const sockaddr* netkit::sock::native::native_async_sock::get_sa() const { return reinterpret_cast(&sa_storage); } -socklen_t netkit::sock::async_sock::get_sa_len() const { +socklen_t netkit::sock::native::native_async_sock::get_sa_len() const { if (addr_.is_ipv4()) return sizeof(sockaddr_in); if (addr_.is_ipv6()) return sizeof(sockaddr_in6); #ifndef NETKIT_DKP @@ -77,7 +75,7 @@ socklen_t netkit::sock::async_sock::get_sa_len() const { throw netkit::socket_error("invalid address type"); } -void netkit::sock::async_sock::prep_sa() { +void netkit::sock::native::native_async_sock::prep_sa() { memset(&sa_storage, 0, sizeof(sa_storage)); if (addr_.is_ipv4()) { @@ -121,7 +119,7 @@ void netkit::sock::async_sock::prep_sa() { } } -void netkit::sock::async_sock::set_sock_opts(opt opts) { +void netkit::sock::native::native_async_sock::set_sock_opts(opt opts) { if (opts & opt::reuse_addr) { ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); } else if (opts & opt::no_reuse_addr) { @@ -158,7 +156,7 @@ void netkit::sock::async_sock::set_sock_opts(opt opts) { } } -netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts) +netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t), context_(ctx) { this->sockfd = -1; @@ -180,7 +178,7 @@ netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, const sock::ad } if (this->sockfd >= 0) { - this->async_sock::set_sock_opts(opts); + this->native_async_sock::set_sock_opts(opts); } else { throw socket_error("cannot set options on invalid socket"); } @@ -188,11 +186,11 @@ netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, const sock::ad this->prep_sa(); } -netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, int existing_fd, const sock::addr& peer, sock::type t, opt opts) +netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, int existing_fd, const sock::addr& peer, sock::type t, opt opts) : addr_(peer), type_(t), sockfd(existing_fd), context_(ctx) { if (sockfd < 0) throw socket_error("invalid fd"); if (this->sockfd >= 0) { - this->async_sock::set_sock_opts(opts); + this->native_async_sock::set_sock_opts(opts); } else { throw socket_error("cannot set options on invalid socket"); } @@ -200,7 +198,7 @@ netkit::sock::async_sock::async_sock(netkit::io::io_context& ctx, int existing_f this->prep_sa(); } -netkit::sock::async_sock::~async_sock() { +netkit::sock::native::native_async_sock::~native_async_sock() { if (this->sockfd == -1) { return; } @@ -209,15 +207,15 @@ netkit::sock::async_sock::~async_sock() { } } -netkit::sock::addr& netkit::sock::async_sock::get_addr() { +netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() { return this->addr_; } -const netkit::sock::addr& netkit::sock::async_sock::get_addr() const { +const netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() const { return this->addr_; } -netkit::io::task netkit::sock::async_sock::connect() { +netkit::io::task netkit::sock::native::native_async_sock::connect() { int ret = ::connect( this->sockfd, this->get_sa(), @@ -263,7 +261,7 @@ netkit::io::task netkit::sock::async_sock::connect() { co_return; } -void netkit::sock::async_sock::bind() { +void netkit::sock::native::native_async_sock::bind() { auto ret = ::bind( this->sockfd, this->get_sa(), @@ -277,7 +275,7 @@ void netkit::sock::async_sock::bind() { this->bound = true; } -void netkit::sock::async_sock::unbind() { +void netkit::sock::native::native_async_sock::unbind() { if (this->bound) { if (::close(this->sockfd) < 0) { throw socket_error("failed to unbind socket"); @@ -286,18 +284,18 @@ void netkit::sock::async_sock::unbind() { } } -void netkit::sock::async_sock::listen(int backlog) { +void netkit::sock::native::native_async_sock::listen(int backlog) { if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { throw socket_error("failed to listen on socket"); } } -void netkit::sock::async_sock::listen() { +void netkit::sock::native::native_async_sock::listen() { this->listen(-1); } -netkit::io::task> -netkit::sock::async_sock::accept() { +netkit::io::task> +netkit::sock::native::native_async_sock::accept() { while (true) { sockaddr_storage client_addr{}; socklen_t addr_len = sizeof(client_addr); @@ -313,7 +311,7 @@ netkit::sock::async_sock::accept() { if (client_sockfd >= 0) { if (this->type_ == type::uds) { - co_return std::make_unique( + co_return std::make_unique( this->context_, client_sockfd, sock::addr( @@ -323,9 +321,9 @@ netkit::sock::async_sock::accept() { ); } - auto peer = sock::get_peer(client_sockfd); + auto peer = sock::native::get_peer_async(client_sockfd); - co_return std::make_unique( + co_return std::make_unique( this->context_, client_sockfd, peer, @@ -345,7 +343,7 @@ netkit::sock::async_sock::accept() { } } -netkit::io::task netkit::sock::async_sock::send(const void* buf, size_t len) { +netkit::io::task netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { size_t total_sent = 0; const char* data = static_cast(buf); @@ -383,7 +381,7 @@ netkit::io::task netkit::sock::async_sock::send(const void* buf, si co_return total_sent; } -netkit::io::task netkit::sock::async_sock::recv(void* buf, size_t size) { +netkit::io::task netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { for (;;) { auto n = ::recv( this->sockfd, @@ -414,7 +412,7 @@ netkit::io::task netkit::sock::async_sock::recv(void* buf, size_t s } } -void netkit::sock::async_sock::close() { +void netkit::sock::native::native_async_sock::close() { if (this->sockfd == -1) return; @@ -422,7 +420,7 @@ void netkit::sock::async_sock::close() { this->sockfd = -1; } -[[nodiscard]] netkit::sock::addr netkit::sock::async_sock::get_peer() const { +[[nodiscard]] netkit::sock::addr netkit::sock::native::native_async_sock::get_peer() const { #ifdef NETKIT_DKP if (!this->has_peer) { throw netkit::socket_error("peer not known"); @@ -444,10 +442,10 @@ void netkit::sock::async_sock::close() { port, netkit::sock::addr_type::ipv4 }; #else - return sock::get_peer(this->sockfd); + return sock::native::get_peer_async(this->sockfd); #endif } -netkit::sock::fd_t netkit::sock::async_sock::native_handle() const { +netkit::sock::fd_t netkit::sock::native::native_async_sock::native_handle() const { return this->sockfd; } diff --git a/src/sock/sync_sock.cpp b/src/socket/native/native_sync_socket.cpp similarity index 53% rename from src/sock/sync_sock.cpp rename to src/socket/native/native_sync_socket.cpp index 533e11b..1fe0f8c 100644 --- a/src/sock/sync_sock.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -1,802 +1,544 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sync_sock.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the synchronous socket class. - */ -#include -#include -#include -#include -#include - -#ifdef NETKIT_WINDOWS -#include -#include -#include -#elif NETKIT_UNIX -#include -#ifndef NETKIT_DKP -#include -#else -#include -#include -#endif -#include -#include -#include -#include -#endif - -#include -#include - -#ifdef NETKIT_DKP -#define NETKIT_SELECT ::net_select -#else -#define NETKIT_SELECT select -#endif - -#ifndef NETKIT_DKP -namespace netkit::sock { -netkit::sock::addr get_peer_async(netkit::sock::fd_t sockfd) { - sockaddr_storage addr_storage{}; - socklen_t addr_len = sizeof(addr_storage); - - if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { - throw netkit::socket_error("getpeername() failed: " + std::string(strerror(errno))); - } - - char ip_str[INET6_ADDRSTRLEN] = {0}; - uint16_t port = 0; - - if (addr_storage.ss_family == AF_INET) { - auto* addr_in = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else if (addr_storage.ss_family == AF_INET6) { - auto* addr_in6 = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in6->sin6_port); - } else { - throw netkit::ip_error("unsupported address family"); - } - - netkit::sock::addr addr{}; - addr.ip = ip_str; - addr.port = port; - addr.type = (addr_storage.ss_family == AF_INET) ? netkit::sock::addr_type::ipv4 : netkit::sock::addr_type::ipv6; - - return addr; -} -} -#endif - -const sockaddr* netkit::sock::sync_sock::get_sa() const { - return reinterpret_cast(&sa_storage); -} - -socklen_t netkit::sock::sync_sock::get_sa_len() const { - if (addr_.is_ipv4()) return sizeof(sockaddr_in); - if (addr_.is_ipv6()) return sizeof(sockaddr_in6); -#ifndef NETKIT_DKP - if (addr_.is_file_path()) { - const auto& path = addr_.get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); - } -#endif - - throw netkit::socket_error("invalid address type"); -} - -void netkit::sock::sync_sock::prep_sa() { - memset(&sa_storage, 0, sizeof(sa_storage)); - - if (addr_.is_ipv4()) { - auto* sa4 = reinterpret_cast(&sa_storage); - sa4->sin_family = AF_INET; - sa4->sin_port = htons(addr_.get_port()); - if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { - throw parsing_error("invalid IPv4 address"); - } - } else if (addr_.is_ipv6()) { - auto* sa6 = reinterpret_cast(&sa_storage); - sa6->sin6_family = AF_INET6; - sa6->sin6_port = htons(addr_.get_port()); - - std::string ip = addr_.get_ip(); - unsigned long scope = 0; - - auto pos = ip.find('%'); - if (pos != std::string::npos) { - scope = std::stoul(ip.substr(pos + 1)); - ip = ip.substr(0, pos); // strip %scope before inet_pton - } - - if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { - throw parsing_error("invalid IPv6 address"); - } - - if (scope != 0) { - sa6->sin6_scope_id = scope; - } -#ifndef NETKIT_DKP - } else if (addr_.is_file_path()) { - auto* sa_un = reinterpret_cast(&sa_storage); - sa_un->sun_family = AF_UNIX; - const auto& path = addr_.get_path().string(); - if (path.size() >= sizeof(sa_un->sun_path)) { - throw socket_error("UNIX socket path too long"); - } - std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); -#endif - } else { - throw ip_error("invalid address type"); - } -} -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::set_sock_opts(opt opts) { - if (opts & opt::reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); - } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); - } - if (opts & opt::no_delay) { - ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); - } - if (opts & opt::keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); - } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); - } - if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::set_sock_opts(opt opts) { - if (opts & opt::reuse_addr) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_REUSEADDR"); - } - } else if (opts & opt::no_reuse_addr) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_REUSEADDR"); - } - } - if ((opts & opt::no_delay) && type_ == type::tcp) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set TCP_NODELAY"); - } - } - if (opts & opt::keep_alive) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_KEEPALIVE"); - } - } else if (opts & opt::no_keep_alive) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_KEEPALIVE"); - } - } - if (opts & opt::no_blocking) { - u_long mode = 1; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - u_long mode = 0; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } -} -#endif - -#ifdef NETKIT_UNIX -netkit::sock::sync_sock::sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { - this->sockfd = -1; - - if (!addr.is_file_path()) { - if (addr.get_ip().empty()) { - throw socket_error("IP address/file path is empty"); - } - } - -#ifdef NETKIT_DKP - this->sockfd = ::socket(AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, IPPROTO_IP); -#else - if (t != type::uds) { - this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, - t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); - } else { - this->sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); - } -#endif - - if (this->sockfd < 0) { - throw socket_error("failed to create socket"); - } - - if (this->sockfd >= 0) { - this->sync_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } - - this->prep_sa(); -} - -netkit::sock::sync_sock::sync_sock(int existing_fd, const sock::addr& peer, sock::type t, opt opts) - : addr_(peer), type_(t), sockfd(existing_fd) { - if (sockfd < 0) throw socket_error("invalid fd"); - if (this->sockfd >= 0) { - this->sync_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } - - this->prep_sa(); -} -#endif -#ifdef NETKIT_WINDOWS -netkit::sock::sync_sock::sync_sock(const sock::addr& in_addr, sock::type t, opt opts) - : addr_(in_addr), type_(t) { - - if (this->addr_.get_ip().empty() && !this->addr_.is_file_path()) { - throw socket_error("IP address or file path is empty"); - } - - int domain = AF_UNIX; - int sock_type = SOCK_STREAM; - int protocol = 0; - - if (t != type::uds) { - domain = this->addr_.is_ipv6() ? AF_INET6 : AF_INET; - sock_type = (t == type::tcp) ? SOCK_STREAM : SOCK_DGRAM; - protocol = (t == type::tcp) ? IPPROTO_TCP : IPPROTO_UDP; - } else { - protocol = 0; - } - - this->sockfd = socket(domain, sock_type, protocol); - if (this->sockfd == INVALID_SOCKET) { - throw socket_error("Failed to create socket"); - } - - this->sync_sock::set_sock_opts(opts); - this->prep_sa(); -} -#endif -#ifdef NETKIT_UNIX -netkit::sock::sync_sock::~sync_sock() { - if (this->sockfd == -1) { - return; - } - if (::close(this->sockfd) < 0) { - ; - } -} -#endif -#ifdef NETKIT_WINDOWS -netkit::sock::sync_sock::~sync_sock() { - if (this->sockfd == INVALID_SOCKET) { - return; - } - - if (::closesocket(this->sockfd) == SOCKET_ERROR) { - return; - } - - this->sockfd = INVALID_SOCKET; -} -#endif - -netkit::sock::addr& netkit::sock::sync_sock::get_addr() { - return this->addr_; -} - -const netkit::sock::addr& netkit::sock::sync_sock::get_addr() const { - return this->addr_; -} -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::connect() { - if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) < 0) { - throw netkit::socket_error("failed to connect to server"); - } - -#ifdef NETKIT_DKP - std::memcpy(&this->peer_addr, this->get_sa(), this->get_sa_len()); - this->has_peer = true; -#endif -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::connect() { - if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) == SOCKET_ERROR) { - throw socket_error("failed to connect to server"); - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::bind() { - this->bound = true; - - auto ret = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); - - if (ret < 0) { - throw socket_error("failed to bind socket: " + std::to_string(ret)); - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::bind() { - this->bound = true; - - int result = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); - - if (result == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to bind socket, error code: " + std::to_string(err)); - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::unbind() { - if (this->bound) { - if (::close(this->sockfd) < 0) { - throw socket_error("failed to unbind socket"); - } - this->bound = false; - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::unbind() { - if (this->bound) { - if (::closesocket(this->sockfd) == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to close socket, error code: " + std::to_string(err)); - } - this->bound = false; - this->sockfd = INVALID_SOCKET; - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::listen(int backlog) { - if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { - throw socket_error("failed to listen on socket"); - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::listen(int backlog) { - if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to listen socket, error code: " + std::to_string(err)); - } -} -#endif -void netkit::sock::sync_sock::listen() { - listen(-1); -} -#ifdef NETKIT_UNIX -std::unique_ptr netkit::sock::sync_sock::accept() { - sockaddr_storage client_addr{}; - socklen_t addr_len = sizeof(client_addr); - - int client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); - if (client_sockfd < 0) { - throw socket_error("failed to accept connection: " + std::string(strerror(errno))); - } - -#ifndef NETKIT_DKP - if (this->type_ == type::uds) { - return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); - } - - auto peer = sock::get_peer(client_sockfd); - return std::make_unique(client_sockfd, peer, this->type_); -#else // fuck this code - char ip_str[INET6_ADDRSTRLEN]{}; - uint16_t port = 0; - - if (client_addr.ss_family == AF_INET) { - auto* addr_in = reinterpret_cast(&client_addr); - inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else { - throw ip_error("unsupported address family"); - } - - sock::addr peer{ - ip_str, - port, - addr_type::ipv4 - }; - - auto sock_ptr = std::make_unique(client_sockfd, peer, this->type_); - - std::memcpy(&sock_ptr->peer_addr, &client_addr, addr_len); - sock_ptr->has_peer = true; - - return sock_ptr; - -#endif - -} -#endif -#ifdef NETKIT_WINDOWS -std::unique_ptr netkit::sock::sync_sock::accept() { - sockaddr_storage client_addr{}; - int addr_len = sizeof(client_addr); - - SOCKET client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); - if (client_sockfd == INVALID_SOCKET) { - int err = WSAGetLastError(); - throw socket_error("failed to accept connection, error code: " + std::to_string(err)); - } - - auto peer = sock::get_peer(client_sockfd); - auto handle = std::make_unique(peer, this->type_); - handle->sockfd = client_sockfd; - - return handle; -} -#endif -#ifdef NETKIT_UNIX -int netkit::sock::sync_sock::send(const void* buf, size_t len) { - size_t total_sent = 0; - const char* data = static_cast(buf); - - while (total_sent < len) { - ssize_t sent = ::send(this->sockfd, data + total_sent, len - total_sent, 0); - if (sent <= 0) { - return static_cast(sent); - } - total_sent += sent; - } - - return static_cast(total_sent); -} -#endif -#ifdef NETKIT_WINDOWS -int netkit::sock::sync_sock::send(const void* buf, size_t len) { - size_t total_sent = 0; - const char* data = static_cast(buf); - - while (total_sent < len) { - int ret = ::send(this->sockfd, data + total_sent, static_cast(len - total_sent), 0); - if (ret == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("send() failed, error code: " + std::to_string(err)); - } - if (ret == 0) { - break; - } - total_sent += ret; - } - - return static_cast(total_sent); -} -#endif -void netkit::sock::sync_sock::send(const std::string& buf) { - static_cast(this->send(buf.c_str(), buf.length())); -} - -std::string netkit::sock::sync_sock::overflow_bytes() const { - return old_bytes; -} - -void netkit::sock::sync_sock::clear_overflow_bytes() const { - old_bytes.clear(); -} -#ifdef NETKIT_UNIX -netkit::sock::recv_result netkit::sock::sync_sock::recv(const int timeout_seconds, const std::string& match, size_t eof) { - std::string data = old_bytes; - old_bytes.clear(); - - if (eof != 0 && data.size() >= eof) { - if (data.size() > eof) { - old_bytes = data.substr(eof); - data.resize(eof); - } - return {data, recv_status::success}; - } - - if (!match.empty()) { - size_t pos = data.find(match); - if (pos != std::string::npos) { - old_bytes = data.substr(pos + match.size()); - data.resize(pos + match.size()); - return {data, recv_status::success}; - } - } - - auto start = std::chrono::steady_clock::now(); - - while (true) { - auto elapsed = std::chrono::steady_clock::now() - start; - auto remaining = std::chrono::seconds(timeout_seconds) - elapsed; - if (remaining <= std::chrono::seconds(0) && timeout_seconds != -1) { - return {data, recv_status::timeout}; - } - - timeval tv{}; - tv.tv_sec = std::chrono::duration_cast(remaining).count(); - tv.tv_usec = 0; - - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(this->sockfd, &readfds); - - - if (this->sockfd < 0) throw socket_error("invalid socket descriptor"); - int ret = NETKIT_SELECT(this->sockfd + 1, &readfds, nullptr, nullptr, - timeout_seconds == -1 ? nullptr : &tv); - if (ret < 0) throw socket_error("select() failed"); - if (ret == 0) return {data, recv_status::timeout}; - - if (FD_ISSET(this->sockfd, &readfds)) { - size_t bytes_to_read = 8192; - if (eof != 0 && data.size() + bytes_to_read > eof) { - bytes_to_read = eof - data.size(); - } - - char buf[8192]; - ssize_t received = ::recv(this->sockfd, buf, bytes_to_read, 0); - if (received < 0) { - if (errno == EINTR) continue; - throw socket_error("recv() failed"); - } - if (received == 0) return {data, recv_status::closed}; - - data.append(buf, static_cast(received)); - - if (eof != 0 && data.length() > eof) { - old_bytes = data.substr(eof); - data.resize(eof); - } - if (eof != 0 && data.length() >= eof) { - return {data, recv_status::success}; - } - - if (!match.empty()) { - size_t pos = data.find(match); - if (pos != std::string::npos) { - old_bytes = data.substr(pos + match.size()); - data.resize(pos + match.size()); - return {data, recv_status::success}; - } - } - } - } -} - -netkit::sock::recv_result netkit::sock::sync_sock::recv() { - for (;;) { - char buf[8192]; - ssize_t n = ::recv(this->sockfd, buf, sizeof(buf), 0); - if (n > 0) - return {{buf, buf + n}, recv_status::success}; - if (n == 0) - return {{}, recv_status::closed}; - if (errno == EINTR) - continue; - throw netkit::socket_error("recv failed"); - } -} -#endif -#ifdef NETKIT_WINDOWS -netkit::sock::recv_result netkit::sock::sync_sock::recv(const int timeout_seconds, const std::string& match, size_t eof) { - std::string data = old_bytes; - old_bytes.clear(); - - if (eof != 0 && data.size() >= eof) { - if (data.size() > eof) { - old_bytes = data.substr(eof); - data.resize(eof); - } - return {data, recv_status::success}; - } - - if (!match.empty()) { - size_t pos = data.find(match); - if (pos != std::string::npos) { - old_bytes = data.substr(pos + match.size()); - data.resize(pos + match.size()); - return {data, recv_status::success}; - } - } - - auto start = std::chrono::steady_clock::now(); - - while (true) { - auto elapsed = std::chrono::steady_clock::now() - start; - auto remaining = std::chrono::seconds(timeout_seconds) - elapsed; - if (timeout_seconds == -1) { - remaining = std::chrono::hours(24 * 365 * 100); - } - if (remaining <= std::chrono::seconds(0) && timeout_seconds != -1) { - return {data, recv_status::timeout}; - } - - timeval tv{}; - tv.tv_sec = static_cast(std::chrono::duration_cast(remaining).count()); - tv.tv_usec = 0; - - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(this->sockfd, &readfds); - - if (this->sockfd < 0) throw socket_error("invalid socket descriptor"); - - int ret = ::select(0, &readfds, nullptr, nullptr, timeout_seconds == -1 ? nullptr : &tv); - if (ret == SOCKET_ERROR) { - throw socket_error("select() failed"); - } - if (ret == 0) { - return {data, recv_status::timeout}; - } - - if (FD_ISSET(this->sockfd, &readfds)) { - size_t bytes_to_read = 8192; - if (eof != 0 && data.size() + bytes_to_read > eof) { - bytes_to_read = eof - data.size(); - } - - char buf[8192]; - int received = ::recv(this->sockfd, buf, static_cast(bytes_to_read), 0); - if (received == SOCKET_ERROR) { - int err = WSAGetLastError(); - if (err == WSAEINTR) continue; - throw socket_error("recv() failed"); - } - if (received == 0) { - return {data, recv_status::closed}; - } - - data.append(buf, static_cast(received)); - - if (eof != 0 && data.length() > eof) { - old_bytes = data.substr(eof); - data.resize(eof); - } - if (eof != 0 && data.length() >= eof) { - return {data, recv_status::success}; - } - - if (!match.empty()) { - size_t pos = data.find(match); - if (pos != std::string::npos) { - old_bytes = data.substr(pos + match.size()); - data.resize(pos + match.size()); - return {data, recv_status::success}; - } - } - } - } -} - -netkit::sock::recv_result netkit::sock::sync_sock::recv() { - constexpr size_t buffer_size = 8192; - char buf[buffer_size]; - - for (;;) { - int n = ::recv(this->sockfd, buf, static_cast(buffer_size), 0); - if (n > 0) { - return {std::string(buf, buf + n), recv_status::success}; - } else if (n == 0) { - return {{}, recv_status::closed}; - } else { - int err = WSAGetLastError(); - if (err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAEINPROGRESS) { - continue; - } - throw std::runtime_error("recv failed: WSA error " + std::to_string(err)); - } - } -} -#endif - -netkit::sock::recv_result netkit::sock::sync_sock::recv(const int timeout_seconds) { - return recv(timeout_seconds, "", 0); -} - -netkit::sock::recv_result netkit::sock::sync_sock::recv(const int timeout_seconds, const std::string& match) { - return this->recv(timeout_seconds, match, 0); -} - -netkit::sock::recv_result netkit::sock::sync_sock::recv(const int timeout_seconds, size_t eof) { - return this->recv(timeout_seconds, "", eof); -} - -#ifdef NETKIT_UNIX -void netkit::sock::sync_sock::close() { - if (this->sockfd == -1) { - return; - } - - (void)::close(this->sockfd); - this->sockfd = -1; -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::sync_sock::close() { - if (this->sockfd == INVALID_SOCKET) { - return; - } - - ::shutdown(this->sockfd, SD_BOTH); - - if (::closesocket(this->sockfd) != 0) { - ; - } - - sockfd = INVALID_SOCKET; -} -#endif -[[nodiscard]] netkit::sock::addr netkit::sock::sync_sock::get_peer() const { -#ifdef NETKIT_DKP - if (!this->has_peer) { - throw netkit::socket_error("peer not known"); - } - - char ip_str[INET6_ADDRSTRLEN]{}; - uint16_t port = 0; - - if (this->peer_addr.ss_family == AF_INET) { - auto* addr_in = (sockaddr_in*)&this->peer_addr; - inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else { - throw netkit::ip_error("unsupported address family (Wii = IPv4 only)"); - } - - return netkit::sock::addr{ - ip_str, - port, netkit::sock::addr_type::ipv4 - }; -#else - return sock::get_peer_async(this->sockfd); -#endif -} -netkit::sock::fd_t netkit::sock::sync_sock::native_handle() const { - return this->sockfd; +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file native_sync_socket.cpp + * @license MIT + * @note Part of the Netkit library. + * @brief Implementation of the synchronous socket class. + */ +#include +#include +#include +#include +#include + +#ifdef NETKIT_WINDOWS +#include +#include +#include +#elif NETKIT_UNIX +#include +#ifndef NETKIT_DKP +#include +#else +#include +#include +#endif +#include +#include +#include +#include +#endif + +#include + +#include + +#ifdef NETKIT_DKP +#define NETKIT_SELECT ::net_select +#else +#define NETKIT_SELECT select +#endif + +#ifndef NETKIT_DKP +namespace netkit::sock::native { +netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { + sockaddr_storage addr_storage{}; + socklen_t addr_len = sizeof(addr_storage); + + if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { + throw netkit::socket_error("getpeername() failed: " + std::string(strerror(errno))); + } + + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = 0; + + if (addr_storage.ss_family == AF_INET) { + auto* addr_in = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else if (addr_storage.ss_family == AF_INET6) { + auto* addr_in6 = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in6->sin6_port); + } else { + throw netkit::ip_error("unsupported address family"); + } + + netkit::sock::addr addr{}; + addr.ip = ip_str; + addr.port = port; + addr.type = (addr_storage.ss_family == AF_INET) ? netkit::sock::addr_type::ipv4 : netkit::sock::addr_type::ipv6; + + return addr; +} +} +#endif + +const sockaddr* netkit::sock::native::native_sync_sock::get_sa() const { + return reinterpret_cast(&sa_storage); +} + +socklen_t netkit::sock::native::native_sync_sock::get_sa_len() const { + if (addr_.is_ipv4()) return sizeof(sockaddr_in); + if (addr_.is_ipv6()) return sizeof(sockaddr_in6); +#ifndef NETKIT_DKP + if (addr_.is_file_path()) { + const auto& path = addr_.get_path(); + return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); + } +#endif + + throw netkit::socket_error("invalid address type"); +} + +void netkit::sock::native::native_sync_sock::prep_sa() { + memset(&sa_storage, 0, sizeof(sa_storage)); + + if (addr_.is_ipv4()) { + auto* sa4 = reinterpret_cast(&sa_storage); + sa4->sin_family = AF_INET; + sa4->sin_port = htons(addr_.get_port()); + if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { + throw parsing_error("invalid IPv4 address"); + } + } else if (addr_.is_ipv6()) { + auto* sa6 = reinterpret_cast(&sa_storage); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(addr_.get_port()); + + std::string ip = addr_.get_ip(); + unsigned long scope = 0; + + auto pos = ip.find('%'); + if (pos != std::string::npos) { + scope = std::stoul(ip.substr(pos + 1)); + ip = ip.substr(0, pos); // strip %scope before inet_pton + } + + if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { + throw parsing_error("invalid IPv6 address"); + } + + if (scope != 0) { + sa6->sin6_scope_id = scope; + } +#ifndef NETKIT_DKP + } else if (addr_.is_file_path()) { + auto* sa_un = reinterpret_cast(&sa_storage); + sa_un->sun_family = AF_UNIX; + const auto& path = addr_.get_path().string(); + if (path.size() >= sizeof(sa_un->sun_path)) { + throw socket_error("UNIX socket path too long"); + } + std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); +#endif + } else { + throw ip_error("invalid address type"); + } +} +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { + if (opts & opt::reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & opt::no_reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & opt::no_delay) { + ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & opt::keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & opt::no_keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & opt::no_blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +} +#endif +#ifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { + if (opts & opt::reuse_addr) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set SO_REUSEADDR"); + } + } else if (opts & opt::no_reuse_addr) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to clear SO_REUSEADDR"); + } + } + if ((opts & opt::no_delay) && type_ == type::tcp) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set TCP_NODELAY"); + } + } + if (opts & opt::keep_alive) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set SO_KEEPALIVE"); + } + } else if (opts & opt::no_keep_alive) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to clear SO_KEEPALIVE"); + } + } + if (opts & opt::no_blocking) { + u_long mode = 1; + if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + u_long mode = 0; + if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +} +#endif + +#ifdef NETKIT_UNIX +netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { + this->sockfd = -1; + + if (!addr.is_file_path()) { + if (addr.get_ip().empty()) { + throw socket_error("IP address/file path is empty"); + } + } + +#ifdef NETKIT_DKP + this->sockfd = ::socket(AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, IPPROTO_IP); +#else + if (t != type::uds) { + this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, + t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + } else { + this->sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); + } +#endif + + if (this->sockfd < 0) { + throw socket_error("failed to create socket"); + } + + if (this->sockfd >= 0) { + this->native_sync_sock::set_sock_opts(opts); + } else { + throw socket_error("cannot set options on invalid socket"); + } + + this->prep_sa(); +} + +netkit::sock::native::native_sync_sock::native_sync_sock(int existing_fd, const sock::addr& peer, sock::type t, opt opts) + : addr_(peer), type_(t), sockfd(existing_fd) { + if (sockfd < 0) throw socket_error("invalid fd"); + if (this->sockfd >= 0) { + this->native_sync_sock::set_sock_opts(opts); + } else { + throw socket_error("cannot set options on invalid socket"); + } + + this->prep_sa(); +} +#endif +#ifdef NETKIT_WINDOWS +netkit::sock::native::native_sync_sock::sync_sock(const sock::addr& in_addr, sock::type t, opt opts) + : addr_(in_addr), type_(t) { + + if (this->addr_.get_ip().empty() && !this->addr_.is_file_path()) { + throw socket_error("IP address or file path is empty"); + } + + int domain = AF_UNIX; + int sock_type = SOCK_STREAM; + int protocol = 0; + + if (t != type::uds) { + domain = this->addr_.is_ipv6() ? AF_INET6 : AF_INET; + sock_type = (t == type::tcp) ? SOCK_STREAM : SOCK_DGRAM; + protocol = (t == type::tcp) ? IPPROTO_TCP : IPPROTO_UDP; + } else { + protocol = 0; + } + + this->sockfd = socket(domain, sock_type, protocol); + if (this->sockfd == INVALID_SOCKET) { + throw socket_error("Failed to create socket"); + } + + this->sync_sock::set_sock_opts(opts); + this->prep_sa(); +} +#endif +#ifdef NETKIT_UNIX +netkit::sock::native::native_sync_sock::~native_sync_sock() { + if (this->sockfd == -1) { + return; + } + if (::close(this->sockfd) < 0) { + ; + } +} +#endif +#ifdef NETKIT_WINDOWS +netkit::sock::native::native_sync_sock::~native_sync_sock() { + if (this->sockfd == INVALID_SOCKET) { + return; + } + + if (::closesocket(this->sockfd) == SOCKET_ERROR) { + return; + } + + this->sockfd = INVALID_SOCKET; +} +#endif + +netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() { + return this->addr_; +} + +const netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() const { + return this->addr_; +} +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::connect() { + if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) < 0) { + throw netkit::socket_error("failed to connect to server"); + } + +#ifdef NETKIT_DKP + std::memcpy(&this->peer_addr, this->get_sa(), this->get_sa_len()); + this->has_peer = true; +#endif +} +#endif +#ifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::connect() { + if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) == SOCKET_ERROR) { + throw socket_error("failed to connect to server"); + } +} +#endif +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::bind() { + this->bound = true; + + auto ret = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); + + if (ret < 0) { + throw socket_error("failed to bind socket: " + std::to_string(ret)); + } +} +#endif +#ifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::bind() { + this->bound = true; + + int result = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); + + if (result == SOCKET_ERROR) { + int err = WSAGetLastError(); + throw socket_error("failed to bind socket, error code: " + std::to_string(err)); + } +} +#endif +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::unbind() { + if (this->bound) { + if (::close(this->sockfd) < 0) { + throw socket_error("failed to unbind socket"); + } + this->bound = false; + } +} +#endif +#ifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::unbind() { + if (this->bound) { + if (::closesocket(this->sockfd) == SOCKET_ERROR) { + int err = WSAGetLastError(); + throw socket_error("failed to close socket, error code: " + std::to_string(err)); + } + this->bound = false; + this->sockfd = INVALID_SOCKET; + } +} +#endif +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::listen(int backlog) { + if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { + throw socket_error("failed to listen on socket"); + } +} +#endif +#ifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::listen(int backlog) { + if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) == SOCKET_ERROR) { + int err = WSAGetLastError(); + throw socket_error("failed to listen socket, error code: " + std::to_string(err)); + } +} +#endif +void netkit::sock::native::native_sync_sock::listen() { + listen(-1); +} +#ifdef NETKIT_UNIX +std::unique_ptr netkit::sock::native::native_sync_sock::accept() { + sockaddr_storage client_addr{}; + socklen_t addr_len = sizeof(client_addr); + + int client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); + if (client_sockfd < 0) { + throw socket_error("failed to accept connection: " + std::string(strerror(errno))); + } + +#ifndef NETKIT_DKP + if (this->type_ == type::uds) { + return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); + } + + auto peer = sock::get_peer(client_sockfd); + return std::make_unique(client_sockfd, peer, this->type_); +#else // fuck this code + char ip_str[INET6_ADDRSTRLEN]{}; + uint16_t port = 0; + + if (client_addr.ss_family == AF_INET) { + auto* addr_in = reinterpret_cast(&client_addr); + inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else { + throw ip_error("unsupported address family"); + } + + sock::addr peer{ + ip_str, + port, + addr_type::ipv4 + }; + + auto sock_ptr = std::make_unique(client_sockfd, peer, this->type_); + + std::memcpy(&sock_ptr->peer_addr, &client_addr, addr_len); + sock_ptr->has_peer = true; + + return sock_ptr; + +#endif + +} +#endif +#ifdef NETKIT_WINDOWS +std::unique_ptr netkit::sock::native::native_sync_sock::accept() { + sockaddr_storage client_addr{}; + int addr_len = sizeof(client_addr); + + SOCKET client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); + if (client_sockfd == INVALID_SOCKET) { + int err = WSAGetLastError(); + throw socket_error("failed to accept connection, error code: " + std::to_string(err)); + } + + auto peer = sock::get_peer(client_sockfd); + auto handle = std::make_unique(peer, this->type_); + handle->sockfd = client_sockfd; + + return handle; +} +#endif + +std::size_t netkit::sock::native::native_sync_sock::send(const void* buf, size_t len) { + return ::send(this->sockfd, buf, len, 0); +} + +std::size_t netkit::sock::native::native_sync_sock::recv(void* buf, std::size_t len) { + return ::recv(this->sockfd, buf, len, 0); +} + +#ifdef NETKIT_UNIX +void netkit::sock::native::native_sync_sock::close() { + if (this->sockfd == -1) { + return; + } + + (void)::close(this->sockfd); + this->sockfd = -1; +} +#elifdef NETKIT_WINDOWS +void netkit::sock::native::native_sync_sock::close() { + if (this->sockfd == INVALID_SOCKET) { + return; + } + + ::shutdown(this->sockfd, SD_BOTH); + + if (::closesocket(this->sockfd) != 0) { + ; + } + + sockfd = INVALID_SOCKET; +} +#endif +[[nodiscard]] netkit::sock::addr netkit::sock::native::native_sync_sock::get_peer() const { +#ifdef NETKIT_DKP + if (!this->has_peer) { + throw netkit::socket_error("peer not known"); + } + + char ip_str[INET6_ADDRSTRLEN]{}; + uint16_t port = 0; + + if (this->peer_addr.ss_family == AF_INET) { + auto* addr_in = (sockaddr_in*)&this->peer_addr; + inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else { + throw netkit::ip_error("unsupported address family (Wii = IPv4 only)"); + } + + return netkit::sock::addr{ + ip_str, + port, netkit::sock::addr_type::ipv4 + }; +#else + return native::get_peer(this->sockfd); +#endif +} +netkit::sock::fd_t netkit::sock::native::native_sync_sock::native_handle() const { + return this->sockfd; } \ No newline at end of file diff --git a/src/sock/openssl/ssl_sync_sock.cpp b/src/socket/openssl/ssl_sync_sock.cpp similarity index 99% rename from src/sock/openssl/ssl_sync_sock.cpp rename to src/socket/openssl/ssl_sync_sock.cpp index 6be64f7..fa120e0 100644 --- a/src/sock/openssl/ssl_sync_sock.cpp +++ b/src/socket/openssl/ssl_sync_sock.cpp @@ -11,9 +11,9 @@ */ #ifdef NETKIT_OPENSSL +#include <../../../include/netkit/socket/native/native_sync_sock.hpp> #include -#include -#include +#include #ifdef NETKIT_ENABLE_FALLBACK_CA #include #endif diff --git a/src/sock/sock_peer.cpp b/src/socket/sock_peer.cpp similarity index 87% rename from src/sock/sock_peer.cpp rename to src/socket/sock_peer.cpp index 95ab440..efa1c4d 100644 --- a/src/sock/sock_peer.cpp +++ b/src/socket/sock_peer.cpp @@ -1,24 +1,24 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sock_peer.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the function to get the peer address of a socket. - */ -#include -#include -#include -#include -#include -#ifdef NETKIT_UNIX -#include -#include -#include -#elifdef NETKIT_WINDOWS -#include -#include +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file sock_peer.cpp + * @license MIT + * @note Part of the Netkit library. + * @brief Implementation of the function to get the peer address of a socket. + */ +#include +#include +#include +#include +#include +#ifdef NETKIT_UNIX +#include +#include +#include +#elifdef NETKIT_WINDOWS +#include +#include #endif \ No newline at end of file diff --git a/src/sock/wolfssl/ssl_sync_sock.cpp b/src/socket/wolfssl/ssl_sync_sock.cpp similarity index 100% rename from src/sock/wolfssl/ssl_sync_sock.cpp rename to src/socket/wolfssl/ssl_sync_sock.cpp From f69aebb337cb05b6587afc89e86509adce5b6917 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Sun, 26 Jul 2026 00:34:20 +0200 Subject: [PATCH 03/34] introduce concept of streams. will be used to build socket abstractions --- include/netkit/netkit.hpp | 13 +++-- include/netkit/socket/addr.hpp | 2 +- include/netkit/socket/async_sock.hpp | 7 +++ include/netkit/socket/sync_sock.hpp | 7 +++ include/netkit/stream/basic_stream.hpp | 17 ++++++ include/netkit/stream/memory_stream.hpp | 71 +++++++++++++++++++++++++ include/netkit/stream/stream_enum.hpp | 14 +++++ 7 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 include/netkit/socket/async_sock.hpp create mode 100644 include/netkit/socket/sync_sock.hpp create mode 100644 include/netkit/stream/basic_stream.hpp create mode 100644 include/netkit/stream/memory_stream.hpp create mode 100644 include/netkit/stream/stream_enum.hpp diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 828076b..cb3e698 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -35,14 +35,16 @@ // Socket headers #include #include -#include +#include #include #include #include #include #include -#include -#include +#include +#include +#include +#include // HTTP headers #include @@ -71,4 +73,9 @@ #include #include +// streams +#include +#include +#include + #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/socket/addr.hpp b/include/netkit/socket/addr.hpp index c2faaba..471e54c 100644 --- a/include/netkit/socket/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -49,7 +49,7 @@ namespace netkit::sock { * @param path The file path to use. * @throws parsing_error if the path does not exist. */ - explicit addr(std::filesystem::path path); + explicit addr(std::filesystem::path path); #endif /** * @brief Check whether the address is IPv4 or IPv6. diff --git a/include/netkit/socket/async_sock.hpp b/include/netkit/socket/async_sock.hpp new file mode 100644 index 0000000..6bcae46 --- /dev/null +++ b/include/netkit/socket/async_sock.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace netkit::sock { + using async_sock = native::native_async_sock; +} \ No newline at end of file diff --git a/include/netkit/socket/sync_sock.hpp b/include/netkit/socket/sync_sock.hpp new file mode 100644 index 0000000..bb345b8 --- /dev/null +++ b/include/netkit/socket/sync_sock.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace netkit::sock { + using sync_sock = netkit::sock::native::native_sync_sock; +} \ No newline at end of file diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp new file mode 100644 index 0000000..abef3dd --- /dev/null +++ b/include/netkit/stream/basic_stream.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace netkit::stream { + +class basic_stream { +public: + virtual ~basic_stream() = default; + + virtual stream_result read(std::span buffer) = 0; + virtual stream_result write(std::span buffer) = 0; + + virtual void close() noexcept = 0; +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/memory_stream.hpp b/include/netkit/stream/memory_stream.hpp new file mode 100644 index 0000000..5d00949 --- /dev/null +++ b/include/netkit/stream/memory_stream.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +namespace netkit::stream { + +class memory_stream : public basic_stream { +public: + memory_stream() = default; + + explicit memory_stream(std::vector data) : buffer_(std::move(data)) {} + + stream_result read(std::span buffer) override { + if (read_pos_ >= buffer_.size()) { + return { + 0, + stream_status::closed + }; + } + + auto available = buffer_.size() - read_pos_; + auto amount = std::min( + available, + buffer.size() + ); + + std::copy_n( + buffer_.data() + read_pos_, + amount, + buffer.data() + ); + + read_pos_ += amount; + + return { + amount, + stream_status::success + }; + } + + stream_result write(std::span buffer) override { + buffer_.insert( + buffer_.end(), + buffer.begin(), + buffer.end() + ); + + return { + buffer.size(), + stream_status::success + }; + } + + void close() noexcept override { + closed_ = true; + } + + [[nodiscard]] std::span data() const noexcept { + return buffer_; + } + +private: + std::vector buffer_; + std::size_t read_pos_{0}; + bool closed_{false}; +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/stream_enum.hpp b/include/netkit/stream/stream_enum.hpp new file mode 100644 index 0000000..605b808 --- /dev/null +++ b/include/netkit/stream/stream_enum.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace netkit::stream { + enum class stream_status { + success, + closed, + error + }; + + struct stream_result { + std::size_t bytes{}; + stream_status status{stream_status::closed}; + }; +} \ No newline at end of file From b158ef6ff1213d6f2b2a99dd2f4d2bb051dcd053 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Sun, 26 Jul 2026 18:09:15 +0200 Subject: [PATCH 04/34] introduce tcp_connection class which will allow tcp clients to be made --- CMakeLists.txt | 4 +- examples/cpp/socket/main.cpp | 25 +- include/netkit/body/stream_body.hpp | 18 +- include/netkit/http/basic_request_handler.hpp | 4 + include/netkit/http/basic_sync_server.hpp | 4 + include/netkit/http/request_handler.hpp | 6 +- include/netkit/http/sync_server.hpp | 4 + include/netkit/netkit.hpp | 6 +- include/netkit/socket/addr.hpp | 9 - .../native/basic_native_async_listener.hpp | 33 +++ .../socket/native/basic_native_async_sock.hpp | 15 +- .../native/basic_native_sync_listener.hpp | 31 +++ .../socket/native/basic_native_sync_sock.hpp | 15 +- .../socket/native/native_async_listener.hpp | 59 +++++ .../socket/native/native_async_sock.hpp | 11 +- .../socket/native/native_sync_listener.hpp | 54 +++++ .../netkit/socket/native/native_sync_sock.hpp | 31 +-- include/netkit/socket/native/peer_helper.hpp | 7 + include/netkit/socket/sock_peer.hpp | 18 -- .../netkit/socket/wolfssl/ssl_sync_sock.hpp | 8 +- include/netkit/stream/basic_stream.hpp | 6 + include/netkit/stream/memory_stream.hpp | 2 +- include/netkit/stream/socket_stream.hpp | 88 +++++++ include/netkit/stream/stream_enum.hpp | 3 +- include/netkit/tcp/tcp_connection.hpp | 120 ++++++++++ src/body/stream_body.cpp | 105 +++++---- src/socket/native/native_async_listener.cpp | 222 ++++++++++++++++++ src/socket/native/native_async_socket.cpp | 164 ++----------- src/socket/native/native_sync_listener.cpp | 202 ++++++++++++++++ src/socket/native/native_sync_socket.cpp | 196 +--------------- src/socket/native/peer_helper.cpp | 39 +++ src/socket/sock_peer.cpp | 24 -- 32 files changed, 1011 insertions(+), 522 deletions(-) create mode 100644 include/netkit/socket/native/basic_native_async_listener.hpp create mode 100644 include/netkit/socket/native/basic_native_sync_listener.hpp create mode 100644 include/netkit/socket/native/native_async_listener.hpp create mode 100644 include/netkit/socket/native/native_sync_listener.hpp create mode 100644 include/netkit/socket/native/peer_helper.hpp delete mode 100644 include/netkit/socket/sock_peer.hpp create mode 100644 include/netkit/stream/socket_stream.hpp create mode 100644 include/netkit/tcp/tcp_connection.hpp create mode 100644 src/socket/native/native_async_listener.cpp create mode 100644 src/socket/native/native_sync_listener.cpp create mode 100644 src/socket/native/peer_helper.cpp delete mode 100644 src/socket/sock_peer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b8e4ba6..0f925f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,6 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/socket/addr.cpp src/dns/response_parser.cpp src/dns/query_builder.cpp - src/socket/sock_peer.cpp src/socket/native/native_sync_socket.cpp src/dns/cache.cpp src/socket/openssl/ssl_sync_sock.cpp @@ -79,6 +78,9 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/io/io_awaitable.cpp src/io/basic_io_context.cpp src/socket/native/native_async_socket.cpp + src/socket/native/native_sync_listener.cpp + src/socket/native/native_async_listener.cpp + src/socket/native/peer_helper.cpp ) set_target_properties(netkit PROPERTIES diff --git a/examples/cpp/socket/main.cpp b/examples/cpp/socket/main.cpp index ddd8e74..35a7827 100644 --- a/examples/cpp/socket/main.cpp +++ b/examples/cpp/socket/main.cpp @@ -8,33 +8,38 @@ * @license MIT * @note Example code using the Netkit library. * @note See examples/socket_ssl for a TLS/SSL version of this example. - * @brief A lower-level example demonstrating the usage of sync_sock to make a simple HTTP request. */ #include #include #include -#include +#include int main() { - netkit::sock::sock_addr addr("google.com", 80, netkit::sock::sock_addr_type::hostname); - netkit::sock::sync_sock sock(addr, netkit::sock::sock_type::tcp); + netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; + netkit::tcp::tcp_connection connector{addr}; - sock.connect(); + connector.connect(); constexpr std::string_view request = "GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"; - sock.send(request.data()); + auto write_result = connector.write_all(request); - std::string response = sock.recv(-1).data; + if (write_result.status != netkit::stream::stream_status::success) { + throw std::runtime_error{"write failed"}; + } - sock.close(); + auto response = connector.read_all_string(); + + connector.close(); std::ofstream file("response.txt"); + if (file.is_open()) { - file << response; + file.write(response.data(), response.size()); file.close(); } else { std::cerr << "Failed to open file" << std::endl; } + std::cout << "Response written to response.txt" << std::endl; -} +} \ No newline at end of file diff --git a/include/netkit/body/stream_body.hpp b/include/netkit/body/stream_body.hpp index 3a2ed15..9c3c38f 100644 --- a/include/netkit/body/stream_body.hpp +++ b/include/netkit/body/stream_body.hpp @@ -1,9 +1,11 @@ #pragma once -#include +#include + #include #include #include +#include #include #include @@ -11,22 +13,16 @@ namespace netkit::body { class NETKIT_API stream_body : public basic_body { public: - stream_body(sock::native::basic_native_sync_sock& socket, - std::optional length, - std::string initial = {}) - : socket_(socket), + stream_body(stream::basic_stream& stream, std::optional length, std::string initial = {}) + : stream_(stream), remaining_(length), buffer_(std::move(initial)) {} - read_result read(char* buffer, std::size_t max_bytes) noexcept override; - - [[nodiscard]] std::optional size() const override { - return remaining_; - } + read_result read(char* out, std::size_t max_bytes) noexcept override; private: - sock::native::basic_native_sync_sock& socket_; + stream::basic_stream& stream_; std::optional remaining_; std::string buffer_; std::string overflow_; diff --git a/include/netkit/http/basic_request_handler.hpp b/include/netkit/http/basic_request_handler.hpp index fff2d5e..eade6b7 100644 --- a/include/netkit/http/basic_request_handler.hpp +++ b/include/netkit/http/basic_request_handler.hpp @@ -11,6 +11,8 @@ */ #pragma once +#ifdef NETKIT_HTTP + #include <../socket/native/basic_native_sync_sock.hpp> #include @@ -22,3 +24,5 @@ namespace netkit::http::server { virtual ~basic_request_handler() = default; }; } + +#endif \ No newline at end of file diff --git a/include/netkit/http/basic_sync_server.hpp b/include/netkit/http/basic_sync_server.hpp index 37457c4..8d29b42 100644 --- a/include/netkit/http/basic_sync_server.hpp +++ b/include/netkit/http/basic_sync_server.hpp @@ -11,6 +11,8 @@ */ #pragma once +#ifdef NETKIT_HTTP + #include <../socket/native/native_sync_sock.hpp> #include #include @@ -28,3 +30,5 @@ namespace netkit::http::server { virtual void stop() = 0; }; } + +#endif diff --git a/include/netkit/http/request_handler.hpp b/include/netkit/http/request_handler.hpp index 8a19556..8c651bf 100644 --- a/include/netkit/http/request_handler.hpp +++ b/include/netkit/http/request_handler.hpp @@ -11,6 +11,8 @@ */ #pragma once +#ifdef NETKIT_HTTP + #include <../socket/native/native_sync_sock.hpp> #include #include @@ -456,4 +458,6 @@ namespace netkit::http::server { client_sock->close(); } }; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/include/netkit/http/sync_server.hpp b/include/netkit/http/sync_server.hpp index 447a6f6..21eecc5 100644 --- a/include/netkit/http/sync_server.hpp +++ b/include/netkit/http/sync_server.hpp @@ -12,6 +12,8 @@ */ #pragma once +#ifdef NETKIT_HTTP + #include #include #include @@ -84,3 +86,5 @@ namespace netkit::http::server { } }; } + +#endif diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index cb3e698..cac32d1 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include #include #include +#include // HTTP headers #include @@ -77,5 +77,9 @@ #include #include #include +#include + +// tcp +#include #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/socket/addr.hpp b/include/netkit/socket/addr.hpp index 471e54c..86a701f 100644 --- a/include/netkit/socket/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -18,21 +18,12 @@ #include namespace netkit::sock { - class addr; - - namespace native { - addr get_peer_async(fd_t fd); - addr get_peer(fd_t fd); - } - class NETKIT_API addr final { std::filesystem::path path{}; std::string hostname{}; std::string ip{}; int port{}; addr_type type{addr_type::hostname}; - friend addr native::get_peer(fd_t); - friend addr native::get_peer_async(fd_t); // jank addr() = default; public: diff --git a/include/netkit/socket/native/basic_native_async_listener.hpp b/include/netkit/socket/native/basic_native_async_listener.hpp new file mode 100644 index 0000000..83b60cc --- /dev/null +++ b/include/netkit/socket/native/basic_native_async_listener.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#ifdef NETKIT_LINUX + +#include +#include +#include +#include +#include + +namespace netkit::sock::native { + class NETKIT_API basic_native_async_listener { + public: + virtual ~basic_native_async_listener() = default; + virtual void bind() = 0; + virtual void unbind() = 0; + virtual void listen() = 0; + virtual void listen(int backlog) = 0; + virtual netkit::io::task> accept() = 0; + virtual void close() noexcept = 0; + + [[nodiscard]] virtual const addr& get_local_endpoint() const { + throw std::logic_error{"socket does not have an addr object"}; + } + [[nodiscard]] virtual fd_t native_handle() const { + throw std::logic_error{"socket does not have a native handle"}; + } + }; +} + +#endif \ No newline at end of file diff --git a/include/netkit/socket/native/basic_native_async_sock.hpp b/include/netkit/socket/native/basic_native_async_sock.hpp index 080e92d..c4f7d8d 100644 --- a/include/netkit/socket/native/basic_native_async_sock.hpp +++ b/include/netkit/socket/native/basic_native_async_sock.hpp @@ -30,32 +30,25 @@ namespace netkit::sock::native { public: virtual ~basic_native_async_sock() = default; virtual netkit::io::task connect() = 0; - virtual void bind() = 0; - virtual void unbind() = 0; - virtual void listen() = 0; - virtual void listen(int backlog) = 0; - virtual netkit::io::task> accept() = 0; virtual netkit::io::task send(const void* buf, std::size_t len) = 0; [[nodiscard]] virtual netkit::io::task recv(void* buf, std::size_t len) = 0; - virtual void close() = 0; + virtual void close() noexcept = 0; - /* ... */ virtual addr& get_addr() { throw std::logic_error{"socket does not have an addr object"}; } [[nodiscard]] virtual const addr& get_addr() const { throw std::logic_error{"socket does not have an addr object"}; } - virtual void clear_overflow_bytes() const {} [[nodiscard]] virtual addr get_peer() const { throw std::logic_error{"socket does not have a peer"}; }; - [[nodiscard]] virtual fd_t native_handle() const { - throw std::logic_error{"socket does not have a native handle"}; - } virtual void set_sock_opts(opt opts) { throw std::logic_error{"socket does not have opts to set"}; } + [[nodiscard]] virtual fd_t native_handle() const { + throw std::logic_error{"socket does not have a native handle"}; + } }; } diff --git a/include/netkit/socket/native/basic_native_sync_listener.hpp b/include/netkit/socket/native/basic_native_sync_listener.hpp new file mode 100644 index 0000000..e3174e8 --- /dev/null +++ b/include/netkit/socket/native/basic_native_sync_listener.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include +#include +#include + +namespace netkit::sock::native { + class NETKIT_API basic_native_sync_listener { + public: + virtual ~basic_native_sync_listener() = default; + + virtual void bind() = 0; + virtual void unbind() = 0; + + virtual void listen(int backlog) = 0; + virtual void listen() = 0; + + [[nodiscard]] virtual std::unique_ptr accept() = 0; + + [[nodiscard]] virtual const addr& get_local_endpoint() const { + throw std::logic_error{"socket does not have an addr object"}; + } + virtual void close() noexcept = 0; + + [[nodiscard]] virtual fd_t native_handle() const { + throw std::logic_error{"socket does not have a native handle"}; + } + }; +} \ No newline at end of file diff --git a/include/netkit/socket/native/basic_native_sync_sock.hpp b/include/netkit/socket/native/basic_native_sync_sock.hpp index d7dceba..933b43c 100644 --- a/include/netkit/socket/native/basic_native_sync_sock.hpp +++ b/include/netkit/socket/native/basic_native_sync_sock.hpp @@ -24,12 +24,7 @@ namespace netkit::sock::native { class NETKIT_API basic_native_sync_sock { public: virtual ~basic_native_sync_sock() = default; - virtual void connect() = 0; - virtual void bind() = 0; - virtual void unbind() = 0; - virtual void listen(int backlog) = 0; - virtual void listen() = 0; - [[nodiscard]] virtual std::unique_ptr accept() = 0; + virtual void connect() = 0; virtual std::size_t send(const void* buf, std::size_t len) = 0; [[nodiscard]] virtual std::size_t recv(void* buf, std::size_t len) = 0; virtual addr& get_addr() { @@ -38,16 +33,14 @@ namespace netkit::sock::native { [[nodiscard]] virtual const addr& get_addr() const { throw std::logic_error{"socket does not have an addr object"}; } - virtual void clear_overflow_bytes() const {} - virtual void close() = 0; - [[nodiscard]] virtual addr get_peer() const { - throw std::logic_error{"socket does not have a peer"}; - }; + virtual void close() noexcept = 0; [[nodiscard]] virtual fd_t native_handle() const { throw std::logic_error{"socket does not have a native handle"}; } virtual void set_sock_opts(opt opts) { throw std::logic_error{"socket does not have opts to set"}; } + [[nodiscard]] virtual addr get_peer() const = 0; + }; } \ No newline at end of file diff --git a/include/netkit/socket/native/native_async_listener.hpp b/include/netkit/socket/native/native_async_listener.hpp new file mode 100644 index 0000000..259e17b --- /dev/null +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace netkit::sock::native { +#ifndef NETKIT_WINDOWS +constexpr fd_t INVALID_SOCKET = -1; +#endif + +class NETKIT_API native_async_listener : public basic_native_async_listener { +public: + native_async_listener(io::io_context& ctx, const addr& address, type t = type::tcp, opt opts = opt::no_reuse_addr | opt::no_blocking); + ~native_async_listener() override; + + void bind() override; + void unbind() override; + void listen(int backlog) override; + void listen() override; + + [[nodiscard]] io::task> accept() override; + + void close() noexcept override; + + [[nodiscard]] const addr& get_local_endpoint() const override; + + [[nodiscard]] fd_t native_handle() const override; + void set_sock_opts(opt opts) const; + +private: + io::io_context& context_; + + addr addr_; + type type_; + + fd_t sockfd_{INVALID_SOCKET}; + + opt opts_; + + bool bound_{false}; + bool listening_{false}; + + sockaddr_storage sa_storage_{}; + + void prep_sa(); + + [[nodiscard]] + const sockaddr* get_sa() const; + + [[nodiscard]] + socklen_t get_sa_len() const; +}; + +} \ No newline at end of file diff --git a/include/netkit/socket/native/native_async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp index 8610f6c..1739326 100644 --- a/include/netkit/socket/native/native_async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -22,12 +22,10 @@ #include namespace netkit::sock::native { - constexpr fd_t INVALID_SOCKET = -1; - class NETKIT_API native_async_sock : public basic_native_async_sock { addr addr_; type type_{}; - fd_t sockfd{INVALID_SOCKET}; + fd_t sockfd{-1}; sockaddr_storage sa_storage{}; bool bound{false}; @@ -45,14 +43,9 @@ namespace netkit::sock::native { [[nodiscard]] const sock::addr& get_addr() const override; netkit::io::task connect() override; - void bind() override; - void unbind() override; - void listen(int backlog) override; - void listen() override; - [[nodiscard]] netkit::io::task> accept() override; netkit::io::task send(const void* buf, size_t len) override; [[nodiscard]] netkit::io::task recv(void* buf, std::size_t size) override; - void close() override; + void close() noexcept override; [[nodiscard]] sock::addr get_peer() const override; [[nodiscard]] fd_t native_handle() const override; void set_sock_opts(opt opts) override; diff --git a/include/netkit/socket/native/native_sync_listener.hpp b/include/netkit/socket/native/native_sync_listener.hpp new file mode 100644 index 0000000..2a2c50f --- /dev/null +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace netkit::sock::native { +#ifndef NETKIT_WINDOWS +constexpr fd_t INVALID_SOCKET = -1; +#endif + +class NETKIT_API native_sync_listener + : public basic_native_sync_listener +{ +public: + void set_sock_opts(opt opts) const; + native_sync_listener(const addr& address, type t = type::tcp, opt opts = opt::no_reuse_addr); + ~native_sync_listener() override = default; + + void bind() override; + void unbind() override; + + void listen(int backlog) override; + void listen() override; + + std::unique_ptr accept() override; + + void close() noexcept override; + + [[nodiscard]] const addr& get_local_endpoint() const override; + + [[nodiscard]] fd_t native_handle() const override; +private: + addr addr_; + type type_; + + fd_t sockfd_{INVALID_SOCKET}; + + bool bound_{false}; + bool listening_{false}; + + opt opts_; + + sockaddr_storage sa_storage_{}; + + void prep_sa(); + + [[nodiscard]] + const sockaddr* get_sa() const; + + [[nodiscard]] + socklen_t get_sa_len() const; +}; + +} \ No newline at end of file diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp index 5ff4b23..a63b112 100644 --- a/include/netkit/socket/native/native_sync_sock.hpp +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -42,7 +42,6 @@ namespace netkit::sock::native { [[nodiscard]] const sockaddr* get_sa() const; [[nodiscard]] socklen_t get_sa_len() const; void prep_sa(); - #ifdef NETKIT_DKP sockaddr_storage peer_addr{}; bool has_peer{false}; @@ -68,31 +67,7 @@ namespace netkit::sock::native { ~native_sync_sock() override; sock::addr& get_addr() override; [[nodiscard]] const sock::addr& get_addr() const override; - void connect() override; - /** - * @brief Bind the socket to the address. - */ - void bind() override; - /** - * @brief Unbind the socket from the address. - */ - void unbind() override; - /** - * @brief Listen for incoming connections. - * @param backlog The maximum number of pending connections. - * @note Very barebones, use with care. - */ - void listen(int backlog) override; - /** - * @brief Listen for incoming connections with default backlog. - * @note Uses SOMAXCONN as the default backlog value. - */ - void listen() override; - /** - * @brief Accept a connection from a client. - * @return sock_handle The socket handle for the accepted connection. - */ - [[nodiscard]] std::unique_ptr accept() override; + void connect() override; /** * @brief Send data to the server. * @param buf The data to send. @@ -104,9 +79,9 @@ namespace netkit::sock::native { /** * @brief Close the socket. */ - void close() override; - [[nodiscard]] sock::addr get_peer() const override; + void close() noexcept override; [[nodiscard]] fd_t native_handle() const override; void set_sock_opts(opt opts) override; + [[nodiscard]] addr get_peer() const override; }; } \ No newline at end of file diff --git a/include/netkit/socket/native/peer_helper.hpp b/include/netkit/socket/native/peer_helper.hpp new file mode 100644 index 0000000..bfa047d --- /dev/null +++ b/include/netkit/socket/native/peer_helper.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace netkit::sock::native { + [[nodiscard]] netkit::sock::addr get_peer(fd_t fd); +} \ No newline at end of file diff --git a/include/netkit/socket/sock_peer.hpp b/include/netkit/socket/sock_peer.hpp deleted file mode 100644 index 831ccb4..0000000 --- a/include/netkit/socket/sock_peer.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sock_peer.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a function to get the peer address of a socket. - */ -#pragma once - -#include - -namespace netkit::sock { - addr get_peer(fd_t sockfd); -} \ No newline at end of file diff --git a/include/netkit/socket/wolfssl/ssl_sync_sock.hpp b/include/netkit/socket/wolfssl/ssl_sync_sock.hpp index b7b6274..f58ee44 100644 --- a/include/netkit/socket/wolfssl/ssl_sync_sock.hpp +++ b/include/netkit/socket/wolfssl/ssl_sync_sock.hpp @@ -14,9 +14,9 @@ #pragma once #ifdef NETKIT_WOLFSSL + #include #include -#endif #include <../native/basic_native_sync_sock.hpp> #include @@ -26,7 +26,6 @@ #include namespace netkit::sock { -#ifdef NETKIT_WOLFSSL class NETKIT_API ssl_sync_sock : public basic_sync_sock { public: explicit ssl_sync_sock(std::unique_ptr underlying, @@ -101,5 +100,6 @@ namespace netkit::sock { static void throw_ssl_error(const std::string& msg); }; -#endif -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index abef3dd..3f32ba5 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -2,6 +2,8 @@ #include +#include + namespace netkit::stream { class basic_stream { @@ -11,6 +13,10 @@ class basic_stream { virtual stream_result read(std::span buffer) = 0; virtual stream_result write(std::span buffer) = 0; + stream_result read(void* data, std::size_t size) { + return this->read(std::span(static_cast(data), size)); + } + virtual void close() noexcept = 0; }; diff --git a/include/netkit/stream/memory_stream.hpp b/include/netkit/stream/memory_stream.hpp index 5d00949..98d1e2b 100644 --- a/include/netkit/stream/memory_stream.hpp +++ b/include/netkit/stream/memory_stream.hpp @@ -17,7 +17,7 @@ class memory_stream : public basic_stream { if (read_pos_ >= buffer_.size()) { return { 0, - stream_status::closed + stream_status::eof }; } diff --git a/include/netkit/stream/socket_stream.hpp b/include/netkit/stream/socket_stream.hpp new file mode 100644 index 0000000..e7ca20c --- /dev/null +++ b/include/netkit/stream/socket_stream.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +namespace netkit::stream { + +class socket_stream : public basic_stream { +public: + explicit socket_stream(std::unique_ptr socket) + : socket_(std::move(socket)) {} + + void connect() { + if (!socket_) + throw std::logic_error("socket closed"); + + socket_->connect(); + } + + [[nodiscard]] stream_result read(std::span buffer) override { + if (!socket_) + throw std::logic_error("socket closed"); + + auto result = socket_->recv( + buffer.data(), + buffer.size() + ); + + if (result == 0) { + return { + 0, + stream_status::eof + }; + } + + if (result < 0) { + return { + 0, + stream_status::error + }; + } + + return { + static_cast(result), + stream_status::success + }; + } + + [[nodiscard]] stream_result write(std::span buffer) override { + if (!socket_) + throw std::logic_error("socket closed"); + + auto result = socket_->send( + buffer.data(), + buffer.size() + ); + + if (result < 0) { + return { + 0, + stream_status::error + }; + } + + return { + static_cast(result), + stream_status::success + }; + } + + void close() noexcept override { + if (socket_) { + socket_->close(); + socket_.reset(); + } + } + + [[nodiscard]] sock::addr peer() const { + if (!socket_) + throw std::logic_error("socket closed"); + + return socket_->get_peer(); + } +private: + std::unique_ptr socket_; +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/stream_enum.hpp b/include/netkit/stream/stream_enum.hpp index 605b808..4e24d4b 100644 --- a/include/netkit/stream/stream_enum.hpp +++ b/include/netkit/stream/stream_enum.hpp @@ -4,7 +4,8 @@ namespace netkit::stream { enum class stream_status { success, closed, - error + error, + eof }; struct stream_result { diff --git a/include/netkit/tcp/tcp_connection.hpp b/include/netkit/tcp/tcp_connection.hpp new file mode 100644 index 0000000..9430bea --- /dev/null +++ b/include/netkit/tcp/tcp_connection.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace netkit::tcp { + +class tcp_connection { +public: + tcp_connection(const sock::addr& addr) : stream_(std::make_unique(addr, sock::type::tcp)) {} + + ~tcp_connection() { + this->close(); + } + + void connect() { + stream_.connect(); + } + + stream::stream_result read(std::span buffer) { + return stream_.read(buffer); + } + + stream::stream_result write(std::span buffer) { + return stream_.write(buffer); + } + + stream::stream_result write_all(std::span buffer) { + std::size_t total = 0; + + while (total < buffer.size()) { + auto result = this->write(buffer.subspan(total)); + + if (result.status != stream::stream_status::success) + return { + result.bytes + total, + result.status + }; + + total += result.bytes; + } + + return { + total, + stream::stream_status::success + }; + } + + stream::stream_result write_all(std::string_view data) { + return write_all( + std::as_bytes( + std::span(data.data(), data.size()) + ) + ); + } + + std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024) { + std::vector result; + + std::size_t total = 0; + + std::array buffer{}; + + while (total < max_bytes) { + auto res = read(buffer); + + if (res.status == stream::stream_status::eof) + break; + + if (res.status != stream::stream_status::success) + throw std::runtime_error("read failed"); + + auto amount = std::min(res.bytes, max_bytes - total); + + result.insert( + result.end(), + buffer.begin(), + buffer.begin() + amount + ); + + total += amount; + + if (amount != res.bytes) + throw std::length_error("read_all exceeded maximum size"); + } + + return result; + } + + std::string read_all_string() { + auto data = this->read_all(); + + return { + reinterpret_cast(data.data()), + data.size() + }; + } + + void close() noexcept { + stream_.close(); + } + + [[nodiscard]] sock::addr peer() const { + return stream_.peer(); + } + + stream::socket_stream& stream() { + return stream_; + } + +private: + stream::socket_stream stream_; +}; + +} \ No newline at end of file diff --git a/src/body/stream_body.cpp b/src/body/stream_body.cpp index 3a96fa0..0852fb1 100644 --- a/src/body/stream_body.cpp +++ b/src/body/stream_body.cpp @@ -2,91 +2,100 @@ #define NOMINMAX // some windows shit #include -#ifdef NETKIT_HTTP - -netkit::body::read_result netkit::body::stream_body::read(char* out, std::size_t max_bytes) noexcept { +netkit::body::read_result +netkit::body::stream_body::read(char* out, std::size_t max_bytes) noexcept { if (max_bytes == 0) return {read_status::ok, 0}; if (remaining_ && *remaining_ == 0) return {read_status::eof, 0}; - std::size_t total = 0; - if (!buffer_.empty()) { - std::size_t n = std::min({ + auto consume = [&](std::string& src) -> std::optional { + if (src.empty()) + return std::nullopt; + + auto n = std::min({ max_bytes, - buffer_.size(), - remaining_.value_or(buffer_.size()) + src.size(), + remaining_.value_or(src.size()) }); - std::memcpy(out, buffer_.data(), n); + std::memcpy( + out, + src.data(), + n + ); - buffer_.erase(0, n); + src.erase(0, n); if (remaining_) *remaining_ -= n; - return {read_status::ok, n}; - } + return n; + }; - if (!overflow_.empty()) { - std::size_t n = std::min({ - max_bytes, - overflow_.size(), - remaining_.value_or(overflow_.size()) - }); + if (auto n = consume(buffer_)) + return {read_status::ok, *n}; - std::memcpy(out, overflow_.data(), n); + if (auto n = consume(overflow_)) + return {read_status::ok, *n}; - overflow_.erase(0, n); // could optimize later - total += n; + std::array temp; - if (remaining_) - *remaining_ -= n; - - return {read_status::ok, n}; - } - - - std::size_t want = max_bytes; + auto want = std::min( + max_bytes, + temp.size() + ); if (remaining_) - want = std::min(want, *remaining_); - + want = std::min( + want, + *remaining_ + ); - auto result = socket_.recv(3, "", want); + auto result = stream_.read( + std::span( + temp.data(), + want + ) + ); - if (result.status == sock::recv_status::closed) + if (result.status == stream::stream_status::closed) return {read_status::eof, 0}; - if (result.status == sock::recv_status::timeout) - return {read_status::timeout, 0}; - - if (result.status != sock::recv_status::success) + if (result.status != stream::stream_status::success) return {read_status::error, 0}; - if (result.data.empty()) - return {read_status::timeout, 0}; + if (result.bytes == 0) + return {read_status::eof, 0}; + auto n = std::min( + max_bytes, + result.bytes + ); - std::size_t n = std::min(max_bytes, result.data.size()); + std::memcpy( + out, + temp.data(), + n + ); - std::memcpy(out, result.data.data(), n); if (remaining_) *remaining_ -= n; - if (n < result.data.size()) { + if (n < result.bytes) { overflow_.assign( - result.data.data() + n, - result.data.size() - n + reinterpret_cast(temp.data() + n), + result.bytes - n ); } - return {read_status::ok, n}; -} - -#endif \ No newline at end of file + return { + read_status::ok, + n + }; +} \ No newline at end of file diff --git a/src/socket/native/native_async_listener.cpp b/src/socket/native/native_async_listener.cpp new file mode 100644 index 0000000..33b8f9c --- /dev/null +++ b/src/socket/native/native_async_listener.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void netkit::sock::native::native_async_listener::set_sock_opts(opt opts) const { + if (opts & opt::reuse_addr) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & opt::no_reuse_addr) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & opt::no_delay) { + ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & opt::keep_alive) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & opt::no_keep_alive) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & opt::no_blocking) { + int flags = fcntl(this->sockfd_, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd_); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd_, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(this->sockfd_); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + int flags = fcntl(this->sockfd_, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd_); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd_, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(this->sockfd_); + throw socket_error("failed to set socket to blocking mode"); + } + } +} + +const sockaddr* netkit::sock::native::native_async_listener::get_sa() const { + return reinterpret_cast(&sa_storage_); +} + +socklen_t netkit::sock::native::native_async_listener::get_sa_len() const { + if (addr_.is_ipv4()) return sizeof(sockaddr_in); + if (addr_.is_ipv6()) return sizeof(sockaddr_in6); +#ifndef NETKIT_DKP + if (addr_.is_file_path()) { + const auto& path = addr_.get_path(); + return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); + } +#endif + + throw netkit::socket_error("invalid address type"); +} + +void netkit::sock::native::native_async_listener::prep_sa() { + memset(&sa_storage_, 0, sizeof(sa_storage_)); + + if (addr_.is_ipv4()) { + auto* sa4 = reinterpret_cast(&sa_storage_); + sa4->sin_family = AF_INET; + sa4->sin_port = htons(addr_.get_port()); + if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { + throw parsing_error("invalid IPv4 address"); + } + } else if (addr_.is_ipv6()) { + auto* sa6 = reinterpret_cast(&sa_storage_); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(addr_.get_port()); + + std::string ip = addr_.get_ip(); + unsigned long scope = 0; + + auto pos = ip.find('%'); + if (pos != std::string::npos) { + scope = std::stoul(ip.substr(pos + 1)); + ip = ip.substr(0, pos); // strip %scope before inet_pton + } + + if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { + throw parsing_error("invalid IPv6 address"); + } + + if (scope != 0) { + sa6->sin6_scope_id = scope; + } +#ifndef NETKIT_DKP + } else if (addr_.is_file_path()) { + auto* sa_un = reinterpret_cast(&sa_storage_); + sa_un->sun_family = AF_UNIX; + const auto& path = addr_.get_path().string(); + if (path.size() >= sizeof(sa_un->sun_path)) { + throw socket_error("UNIX socket path too long"); + } + std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); +#endif + } else { + throw ip_error("invalid address type"); + } +} + +netkit::sock::native::native_async_listener::native_async_listener(io::io_context& ctx, const addr& address, type t, opt opts) : context_(ctx), addr_(address), type_(t), opts_(opts) { + sockfd_ = ::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); + + if (sockfd_ == INVALID_SOCKET) + throw socket_error("failed creating socket"); + + set_sock_opts(opts_); + + prep_sa(); +} + +netkit::sock::native::native_async_listener::~native_async_listener() { + this->native_async_listener::close(); +} + +void netkit::sock::native::native_async_listener::bind() { + if (::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + throw socket_error( + "bind failed" + ); + } + + bound_ = true; +} + +void netkit::sock::native::native_async_listener::unbind() { + this->close(); +} + +void netkit::sock::native::native_async_listener::listen(int backlog) { + if (!bound_) throw socket_error("listener not bound"); + + if (::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { + throw socket_error("failed to listen on socket"); + } + + listening_ = true; +} + +void netkit::sock::native::native_async_listener::listen() { + this->listen(-1); +} + +netkit::io::task> +netkit::sock::native::native_async_listener::accept() { + while (true) { + sockaddr_storage client_addr{}; + socklen_t addr_len = sizeof(client_addr); + + // TODO: use fcntl and get bsd support + // it is just a waste of time right now since we are using epoll anyway + fd_t client_sockfd = ::accept4( + this->sockfd_, + reinterpret_cast(&client_addr), + &addr_len, + SOCK_NONBLOCK + ); + + if (client_sockfd >= 0) { + if (this->type_ == type::uds) { + co_return std::make_unique( + this->context_, + client_sockfd, + sock::addr( + reinterpret_cast(&client_addr)->sun_path + ), + this->type_ + ); + } + + auto peer = sock::native::get_peer(client_sockfd); + + co_return std::make_unique( + this->context_, + client_sockfd, + peer, + this->type_ + ); + } + + if (errno == EAGAIN || errno == EWOULDBLOCK) { + co_await this->context_.wait_readable(this->sockfd_); + continue; + } + + throw socket_error( + "failed to accept connection: " + + std::string(strerror(errno)) + ); + } +} + +void netkit::sock::native::native_async_listener::close() noexcept { + if (sockfd_ != INVALID_SOCKET) { + ::close(sockfd_); + sockfd_ = INVALID_SOCKET; + } + + this->bound_ = this->listening_ = false; +} + +netkit::sock::fd_t netkit::sock::native::native_async_listener::native_handle() const{ + return sockfd_; +} + +const netkit::sock::addr& netkit::sock::native::native_async_listener::get_local_endpoint() const { + return addr_; +} + diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index 8d4d871..cb034af 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -5,7 +5,7 @@ * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. * - * @file async_sock.cpp + * @file native_async_socket.cpp * @license MIT * @note Part of the Netkit library. * @brief Implementation of the asynchronous socket class. @@ -18,46 +18,15 @@ #ifdef NETKIT_LINUX -#include -#include -#include +#include + #include #include +#include +#include +#include #include -namespace netkit::sock::native { -netkit::sock::addr get_peer_async(netkit::sock::fd_t sockfd) { - sockaddr_storage addr_storage{}; - socklen_t addr_len = sizeof(addr_storage); - - if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { - throw netkit::socket_error("getpeername() failed: " + std::string(strerror(errno))); - } - - char ip_str[INET6_ADDRSTRLEN] = {0}; - uint16_t port = 0; - - if (addr_storage.ss_family == AF_INET) { - auto* addr_in = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else if (addr_storage.ss_family == AF_INET6) { - auto* addr_in6 = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in6->sin6_port); - } else { - throw netkit::ip_error("unsupported address family"); - } - - netkit::sock::addr addr{}; - addr.ip = ip_str; - addr.port = port; - addr.type = (addr_storage.ss_family == AF_INET) ? netkit::sock::addr_type::ipv4 : netkit::sock::addr_type::ipv6; - - return addr; -} -} - const sockaddr* netkit::sock::native::native_async_sock::get_sa() const { return reinterpret_cast(&sa_storage); } @@ -226,11 +195,8 @@ netkit::io::task netkit::sock::native::native_async_sock::connect() { co_return; } - if (errno != EINPROGRESS) { - throw netkit::socket_error( - "failed to connect to server" - ); + throw netkit::socket_error("failed to connect to server"); } co_await this->context_.wait_writable(this->sockfd); @@ -238,111 +204,19 @@ netkit::io::task netkit::sock::native::native_async_sock::connect() { int error = 0; socklen_t len = sizeof(error); - if (getsockopt( - this->sockfd, - SOL_SOCKET, - SO_ERROR, - &error, - &len - ) < 0) { - throw netkit::socket_error( - "getsockopt(SO_ERROR) failed" - ); + if (getsockopt(this->sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + throw netkit::socket_error("getsockopt(SO_ERROR) failed"); } if (error != 0) { errno = error; - throw netkit::socket_error( - "failed to connect to server" - ); + throw netkit::socket_error("failed to connect to server"); } co_return; } -void netkit::sock::native::native_async_sock::bind() { - auto ret = ::bind( - this->sockfd, - this->get_sa(), - this->get_sa_len() - ); - - if (ret < 0) { - throw socket_error("failed to bind socket"); - } - - this->bound = true; -} - -void netkit::sock::native::native_async_sock::unbind() { - if (this->bound) { - if (::close(this->sockfd) < 0) { - throw socket_error("failed to unbind socket"); - } - this->bound = false; - } -} - -void netkit::sock::native::native_async_sock::listen(int backlog) { - if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { - throw socket_error("failed to listen on socket"); - } -} - -void netkit::sock::native::native_async_sock::listen() { - this->listen(-1); -} - -netkit::io::task> -netkit::sock::native::native_async_sock::accept() { - while (true) { - sockaddr_storage client_addr{}; - socklen_t addr_len = sizeof(client_addr); - - // TODO: use fcntl and get bsd support - // it is just a waste of time right now since we are using epoll anyway - fd_t client_sockfd = ::accept4( - this->sockfd, - reinterpret_cast(&client_addr), - &addr_len, - SOCK_NONBLOCK - ); - - if (client_sockfd >= 0) { - if (this->type_ == type::uds) { - co_return std::make_unique( - this->context_, - client_sockfd, - sock::addr( - reinterpret_cast(&client_addr)->sun_path - ), - this->type_ - ); - } - - auto peer = sock::native::get_peer_async(client_sockfd); - - co_return std::make_unique( - this->context_, - client_sockfd, - peer, - this->type_ - ); - } - - if (errno == EAGAIN || errno == EWOULDBLOCK) { - co_await this->context_.wait_readable(this->sockfd); - continue; - } - - throw socket_error( - "failed to accept connection: " + - std::string(strerror(errno)) - ); - } -} - netkit::io::task netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { size_t total_sent = 0; const char* data = static_cast(buf); @@ -372,10 +246,7 @@ netkit::io::task netkit::sock::native::native_async_sock::send(cons continue; } - throw socket_error( - "failed to send: " + - std::string(strerror(errno)) - ); + throw socket_error("failed to send: " + std::string(strerror(errno))); } co_return total_sent; @@ -383,12 +254,7 @@ netkit::io::task netkit::sock::native::native_async_sock::send(cons netkit::io::task netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { for (;;) { - auto n = ::recv( - this->sockfd, - static_cast(buf), - size, - 0 - ); + auto n = ::recv(this->sockfd, buf, size, 0); if (n > 0) { co_return static_cast(n); @@ -412,7 +278,7 @@ netkit::io::task netkit::sock::native::native_async_sock::recv(void } } -void netkit::sock::native::native_async_sock::close() { +void netkit::sock::native::native_async_sock::close() noexcept { if (this->sockfd == -1) return; @@ -434,7 +300,7 @@ void netkit::sock::native::native_async_sock::close() { inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); port = ntohs(addr_in->sin_port); } else { - throw netkit::ip_error("unsupported address family (Wii = IPv4 only)"); + throw netkit::ip_error("unsupported address family"); } return netkit::sock::addr{ @@ -442,7 +308,7 @@ void netkit::sock::native::native_async_sock::close() { port, netkit::sock::addr_type::ipv4 }; #else - return sock::native::get_peer_async(this->sockfd); + return native::get_peer(this->sockfd); #endif } netkit::sock::fd_t netkit::sock::native::native_async_sock::native_handle() const { diff --git a/src/socket/native/native_sync_listener.cpp b/src/socket/native/native_sync_listener.cpp new file mode 100644 index 0000000..43c3499 --- /dev/null +++ b/src/socket/native/native_sync_listener.cpp @@ -0,0 +1,202 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { + if (opts & opt::reuse_addr) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & opt::no_reuse_addr) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & opt::no_delay) { + ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & opt::keep_alive) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & opt::no_keep_alive) { + ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & opt::no_blocking) { + int flags = fcntl(this->sockfd_, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd_); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd_, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(this->sockfd_); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + int flags = fcntl(this->sockfd_, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd_); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd_, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(this->sockfd_); + throw socket_error("failed to set socket to blocking mode"); + } + } +} + +const sockaddr* netkit::sock::native::native_sync_listener::get_sa() const { + return reinterpret_cast(&sa_storage_); +} + +socklen_t netkit::sock::native::native_sync_listener::get_sa_len() const { + if (addr_.is_ipv4()) return sizeof(sockaddr_in); + if (addr_.is_ipv6()) return sizeof(sockaddr_in6); +#ifndef NETKIT_DKP + if (addr_.is_file_path()) { + const auto& path = addr_.get_path(); + return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); + } +#endif + + throw netkit::socket_error("invalid address type"); +} + +void netkit::sock::native::native_sync_listener::prep_sa() { + memset(&sa_storage_, 0, sizeof(sa_storage_)); + + if (addr_.is_ipv4()) { + auto* sa4 = reinterpret_cast(&sa_storage_); + sa4->sin_family = AF_INET; + sa4->sin_port = htons(addr_.get_port()); + if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { + throw parsing_error("invalid IPv4 address"); + } + } else if (addr_.is_ipv6()) { + auto* sa6 = reinterpret_cast(&sa_storage_); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(addr_.get_port()); + + std::string ip = addr_.get_ip(); + unsigned long scope = 0; + + auto pos = ip.find('%'); + if (pos != std::string::npos) { + scope = std::stoul(ip.substr(pos + 1)); + ip = ip.substr(0, pos); // strip %scope before inet_pton + } + + if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { + throw parsing_error("invalid IPv6 address"); + } + + if (scope != 0) { + sa6->sin6_scope_id = scope; + } +#ifndef NETKIT_DKP + } else if (addr_.is_file_path()) { + auto* sa_un = reinterpret_cast(&sa_storage_); + sa_un->sun_family = AF_UNIX; + const auto& path = addr_.get_path().string(); + if (path.size() >= sizeof(sa_un->sun_path)) { + throw socket_error("UNIX socket path too long"); + } + std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); +#endif + } else { + throw ip_error("invalid address type"); + } +} + +netkit::sock::native::native_sync_listener::native_sync_listener(const addr& address, type t, opt opts) +: addr_(address), type_(t), opts_(opts) { + sockfd_ = ::socket( + addr_.is_ipv6() + ? AF_INET6 + : AF_INET, + SOCK_STREAM, + 0 + ); + + if (sockfd_ < 0) + throw socket_error( + "failed creating socket" + ); + + set_sock_opts(opts_); + prep_sa(); +} + +void netkit::sock::native::native_sync_listener::bind() { + if (::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + throw socket_error( + "bind failed" + ); + } + + bound_ = true; +} + +void netkit::sock::native::native_sync_listener::unbind() { + this->close(); +} + +void netkit::sock::native::native_sync_listener::listen(int backlog) { + if (!bound_) throw socket_error("listener not bound"); + + if (::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { + throw socket_error("failed to listen on socket"); + } + + listening_ = true; +} + +void netkit::sock::native::native_sync_listener::listen() { + this->listen(-1); +} + +std::unique_ptr +netkit::sock::native::native_sync_listener::accept() { + sockaddr_storage client_addr{}; + socklen_t addr_len = sizeof(client_addr); + + int client_sockfd = ::accept(this->sockfd_, reinterpret_cast(&client_addr), &addr_len); + if (client_sockfd < 0) { + throw socket_error("failed to accept connection: " + std::string(std::strerror(errno))); + } + + if (this->type_ == type::uds) { + return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); + } + + if (this->type_ == type::uds) { + return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); + } + + auto peer = native::get_peer(client_sockfd); + return std::make_unique(client_sockfd, peer, this->type_); +} + +void netkit::sock::native::native_sync_listener::close() noexcept { + if (sockfd_ != INVALID_SOCKET) { + ::close(sockfd_); + sockfd_ = INVALID_SOCKET; + } + + this->bound_ = this->listening_ = false; +} + +netkit::sock::fd_t netkit::sock::native::native_sync_listener::native_handle() const{ + return sockfd_; +} + +const netkit::sock::addr& netkit::sock::native::native_sync_listener::get_local_endpoint() const { + return addr_; +} + diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index 1fe0f8c..144b3a7 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -10,10 +10,10 @@ * @brief Implementation of the synchronous socket class. */ #include +#include #include #include #include -#include #ifdef NETKIT_WINDOWS #include @@ -43,41 +43,6 @@ #define NETKIT_SELECT select #endif -#ifndef NETKIT_DKP -namespace netkit::sock::native { -netkit::sock::addr get_peer(netkit::sock::fd_t sockfd) { - sockaddr_storage addr_storage{}; - socklen_t addr_len = sizeof(addr_storage); - - if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { - throw netkit::socket_error("getpeername() failed: " + std::string(strerror(errno))); - } - - char ip_str[INET6_ADDRSTRLEN] = {0}; - uint16_t port = 0; - - if (addr_storage.ss_family == AF_INET) { - auto* addr_in = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else if (addr_storage.ss_family == AF_INET6) { - auto* addr_in6 = reinterpret_cast(&addr_storage); - inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); - port = ntohs(addr_in6->sin6_port); - } else { - throw netkit::ip_error("unsupported address family"); - } - - netkit::sock::addr addr{}; - addr.ip = ip_str; - addr.port = port; - addr.type = (addr_storage.ss_family == AF_INET) ? netkit::sock::addr_type::ipv4 : netkit::sock::addr_type::ipv6; - - return addr; -} -} -#endif - const sockaddr* netkit::sock::native::native_sync_sock::get_sa() const { return reinterpret_cast(&sa_storage); } @@ -140,6 +105,13 @@ void netkit::sock::native::native_sync_sock::prep_sa() { throw ip_error("invalid address type"); } } + +void netkit::sock::native::native_sync_sock::connect() { + if (::connect(sockfd, get_sa(), get_sa_len()) < 0) { + throw socket_error("connect failed: " + std::string(strerror(errno))); + } +} + #ifdef NETKIT_UNIX void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { if (opts & opt::reuse_addr) { @@ -335,152 +307,6 @@ netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() { const netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() const { return this->addr_; } -#ifdef NETKIT_UNIX -void netkit::sock::native::native_sync_sock::connect() { - if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) < 0) { - throw netkit::socket_error("failed to connect to server"); - } - -#ifdef NETKIT_DKP - std::memcpy(&this->peer_addr, this->get_sa(), this->get_sa_len()); - this->has_peer = true; -#endif -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::connect() { - if (::connect(this->sockfd, this->get_sa(), this->get_sa_len()) == SOCKET_ERROR) { - throw socket_error("failed to connect to server"); - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::native::native_sync_sock::bind() { - this->bound = true; - - auto ret = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); - - if (ret < 0) { - throw socket_error("failed to bind socket: " + std::to_string(ret)); - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::bind() { - this->bound = true; - - int result = ::bind(this->sockfd, this->get_sa(), this->get_sa_len()); - - if (result == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to bind socket, error code: " + std::to_string(err)); - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::native::native_sync_sock::unbind() { - if (this->bound) { - if (::close(this->sockfd) < 0) { - throw socket_error("failed to unbind socket"); - } - this->bound = false; - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::unbind() { - if (this->bound) { - if (::closesocket(this->sockfd) == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to close socket, error code: " + std::to_string(err)); - } - this->bound = false; - this->sockfd = INVALID_SOCKET; - } -} -#endif -#ifdef NETKIT_UNIX -void netkit::sock::native::native_sync_sock::listen(int backlog) { - if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) < 0) { - throw socket_error("failed to listen on socket"); - } -} -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::listen(int backlog) { - if (::listen(this->sockfd, backlog == -1 ? SOMAXCONN : backlog) == SOCKET_ERROR) { - int err = WSAGetLastError(); - throw socket_error("failed to listen socket, error code: " + std::to_string(err)); - } -} -#endif -void netkit::sock::native::native_sync_sock::listen() { - listen(-1); -} -#ifdef NETKIT_UNIX -std::unique_ptr netkit::sock::native::native_sync_sock::accept() { - sockaddr_storage client_addr{}; - socklen_t addr_len = sizeof(client_addr); - - int client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); - if (client_sockfd < 0) { - throw socket_error("failed to accept connection: " + std::string(strerror(errno))); - } - -#ifndef NETKIT_DKP - if (this->type_ == type::uds) { - return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); - } - - auto peer = sock::get_peer(client_sockfd); - return std::make_unique(client_sockfd, peer, this->type_); -#else // fuck this code - char ip_str[INET6_ADDRSTRLEN]{}; - uint16_t port = 0; - - if (client_addr.ss_family == AF_INET) { - auto* addr_in = reinterpret_cast(&client_addr); - inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else { - throw ip_error("unsupported address family"); - } - - sock::addr peer{ - ip_str, - port, - addr_type::ipv4 - }; - - auto sock_ptr = std::make_unique(client_sockfd, peer, this->type_); - - std::memcpy(&sock_ptr->peer_addr, &client_addr, addr_len); - sock_ptr->has_peer = true; - - return sock_ptr; - -#endif - -} -#endif -#ifdef NETKIT_WINDOWS -std::unique_ptr netkit::sock::native::native_sync_sock::accept() { - sockaddr_storage client_addr{}; - int addr_len = sizeof(client_addr); - - SOCKET client_sockfd = ::accept(this->sockfd, reinterpret_cast(&client_addr), &addr_len); - if (client_sockfd == INVALID_SOCKET) { - int err = WSAGetLastError(); - throw socket_error("failed to accept connection, error code: " + std::to_string(err)); - } - - auto peer = sock::get_peer(client_sockfd); - auto handle = std::make_unique(peer, this->type_); - handle->sockfd = client_sockfd; - - return handle; -} -#endif std::size_t netkit::sock::native::native_sync_sock::send(const void* buf, size_t len) { return ::send(this->sockfd, buf, len, 0); @@ -491,7 +317,7 @@ std::size_t netkit::sock::native::native_sync_sock::recv(void* buf, std::size_t } #ifdef NETKIT_UNIX -void netkit::sock::native::native_sync_sock::close() { +void netkit::sock::native::native_sync_sock::close() noexcept { if (this->sockfd == -1) { return; } @@ -500,7 +326,7 @@ void netkit::sock::native::native_sync_sock::close() { this->sockfd = -1; } #elifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::close() { +void netkit::sock::native::native_sync_sock::close() noexcept { if (this->sockfd == INVALID_SOCKET) { return; } @@ -536,7 +362,7 @@ void netkit::sock::native::native_sync_sock::close() { port, netkit::sock::addr_type::ipv4 }; #else - return native::get_peer(this->sockfd); + return netkit::sock::native::get_peer(this->sockfd); #endif } netkit::sock::fd_t netkit::sock::native::native_sync_sock::native_handle() const { diff --git a/src/socket/native/peer_helper.cpp b/src/socket/native/peer_helper.cpp new file mode 100644 index 0000000..d02d394 --- /dev/null +++ b/src/socket/native/peer_helper.cpp @@ -0,0 +1,39 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +netkit::sock::addr netkit::sock::native::get_peer(fd_t sockfd) { + sockaddr_storage addr_storage{}; + socklen_t addr_len = sizeof(addr_storage); + + if (getpeername(sockfd, reinterpret_cast(&addr_storage), &addr_len) < 0) { + throw netkit::socket_error("getpeername() failed: " + std::string(std::strerror(errno))); + } + + char ip_str[INET6_ADDRSTRLEN] = {0}; + uint16_t port = 0; + + if (addr_storage.ss_family == AF_INET) { + auto* addr_in = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else if (addr_storage.ss_family == AF_INET6) { + auto* addr_in6 = reinterpret_cast(&addr_storage); + inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, sizeof(ip_str)); + port = ntohs(addr_in6->sin6_port); + } else { + throw netkit::ip_error("unsupported address family"); + } + + return netkit::sock::addr{ + ip_str, + port, + (addr_storage.ss_family == AF_INET) ? sock::addr_type::ipv4 : sock::addr_type::ipv6 + }; +} \ No newline at end of file diff --git a/src/socket/sock_peer.cpp b/src/socket/sock_peer.cpp deleted file mode 100644 index efa1c4d..0000000 --- a/src/socket/sock_peer.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file sock_peer.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the function to get the peer address of a socket. - */ -#include -#include -#include -#include -#include -#ifdef NETKIT_UNIX -#include -#include -#include -#elifdef NETKIT_WINDOWS -#include -#include -#endif \ No newline at end of file From a174eb94cda47d4b086256208805b887e7d3eb28 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Sun, 26 Jul 2026 23:39:54 +0200 Subject: [PATCH 05/34] introduce async_tcp... classes this should complete the basic tcp classes --- CMakeLists.txt | 7 ++ examples/cpp/socket_async/main.cpp | 39 ++++--- examples/cpp/socket_async/response.txt | 6 +- examples/cpp/socket_server/.gitignore | 1 + examples/cpp/socket_server/CMakeLists.txt | 15 +++ examples/cpp/socket_server/main.cpp | 51 +++++++++ examples/cpp/socket_server_async/main.cpp | 49 ++++---- include/netkit/netkit.hpp | 5 + .../socket/native/native_async_listener.hpp | 2 +- .../socket/native/native_async_sock.hpp | 4 +- .../socket/native/native_sync_listener.hpp | 2 +- .../netkit/socket/native/native_sync_sock.hpp | 4 +- include/netkit/stream/async_socket_stream.hpp | 22 ++++ include/netkit/stream/basic_async_stream.hpp | 28 +++++ include/netkit/stream/basic_stream.hpp | 4 + include/netkit/stream/socket_stream.hpp | 73 +----------- include/netkit/stream/stream_enum.hpp | 2 + include/netkit/tcp/async_tcp_connection.hpp | 35 ++++++ include/netkit/tcp/async_tcp_server.hpp | 36 ++++++ include/netkit/tcp/tcp_connection.hpp | 105 ++---------------- include/netkit/tcp/tcp_server.hpp | 31 ++++++ src/stream/async_socket_stream.cpp | 37 ++++++ src/stream/socket_stream.cpp | 73 ++++++++++++ src/tcp/async_tcp_connection.cpp | 96 ++++++++++++++++ src/tcp/async_tcp_server.cpp | 37 ++++++ src/tcp/tcp_connection.cpp | 99 +++++++++++++++++ src/tcp/tcp_server.cpp | 39 +++++++ 27 files changed, 691 insertions(+), 211 deletions(-) create mode 100644 examples/cpp/socket_server/.gitignore create mode 100644 examples/cpp/socket_server/CMakeLists.txt create mode 100644 examples/cpp/socket_server/main.cpp create mode 100644 include/netkit/stream/async_socket_stream.hpp create mode 100644 include/netkit/stream/basic_async_stream.hpp create mode 100644 include/netkit/tcp/async_tcp_connection.hpp create mode 100644 include/netkit/tcp/async_tcp_server.hpp create mode 100644 include/netkit/tcp/tcp_server.hpp create mode 100644 src/stream/async_socket_stream.cpp create mode 100644 src/stream/socket_stream.cpp create mode 100644 src/tcp/async_tcp_connection.cpp create mode 100644 src/tcp/async_tcp_server.cpp create mode 100644 src/tcp/tcp_connection.cpp create mode 100644 src/tcp/tcp_server.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f925f1..21ccf91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,13 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/socket/native/native_sync_listener.cpp src/socket/native/native_async_listener.cpp src/socket/native/peer_helper.cpp + src/tcp/tcp_server.cpp + src/tcp/tcp_connection.cpp + src/stream/socket_stream.cpp + src/stream/socket_stream.cpp + src/stream/async_socket_stream.cpp + src/tcp/async_tcp_connection.cpp + src/tcp/async_tcp_server.cpp ) set_target_properties(netkit PROPERTIES diff --git a/examples/cpp/socket_async/main.cpp b/examples/cpp/socket_async/main.cpp index f71eccc..0ca7327 100644 --- a/examples/cpp/socket_async/main.cpp +++ b/examples/cpp/socket_async/main.cpp @@ -3,36 +3,40 @@ #include #include -netkit::io::task request(netkit::io::io_context& ctx) { +netkit::io::task<> +request(netkit::io::io_context& ctx) { netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; - - netkit::sock::async_sock sock{ctx, addr, netkit::sock::type::tcp}; + netkit::tcp::async_tcp_connection sock(ctx, addr); co_await sock.connect(); - constexpr std::string_view request = + constexpr std::string_view http_request = "GET / HTTP/1.1\r\n" "Host: google.com\r\n" "Connection: close\r\n" "\r\n"; - co_await sock.send( - request.data(), - request.size() - ); + auto write_result = co_await sock.write_all(http_request); + + if (write_result.status != netkit::stream::stream_status::success) + throw std::runtime_error("write failed"); std::string response; - char buffer[8192]; + std::array buffer{}; + while (true) { - auto received = co_await sock.recv(buffer, sizeof(buffer)); + auto received = co_await sock.read(buffer); - if (received == 0) + if (received.status == netkit::stream::stream_status::eof) break; + if (received.status != netkit::stream::stream_status::success) + throw std::runtime_error("read failed"); + response.append( - buffer, - received + reinterpret_cast(buffer.data()), + received.bytes ); } @@ -40,10 +44,8 @@ netkit::io::task request(netkit::io::io_context& ctx) { std::ofstream file("response.txt"); - if (!file) { - std::cerr << "Failed to open file\n"; - co_return; - } + if (!file) + throw std::runtime_error("failed to open file"); file << response; @@ -52,6 +54,7 @@ netkit::io::task request(netkit::io::io_context& ctx) { int main() { netkit::io::io_context ctx; + ctx.spawn(request(ctx)); - ctx.run(); + ctx.run_until_idle(); } diff --git a/examples/cpp/socket_async/response.txt b/examples/cpp/socket_async/response.txt index 3631989..7c85951 100644 --- a/examples/cpp/socket_async/response.txt +++ b/examples/cpp/socket_async/response.txt @@ -1,9 +1,9 @@ HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ Content-Type: text/html; charset=UTF-8 -Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-pzHBJy4TWzR1elN8aKNKdA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp -Date: Sat, 25 Jul 2026 16:26:31 GMT -Expires: Mon, 24 Aug 2026 16:26:31 GMT +Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-1phIRS7D711BKE5J3groVw' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp +Date: Sun, 26 Jul 2026 18:32:22 GMT +Expires: Tue, 25 Aug 2026 18:32:22 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 219 diff --git a/examples/cpp/socket_server/.gitignore b/examples/cpp/socket_server/.gitignore new file mode 100644 index 0000000..3c34c2f --- /dev/null +++ b/examples/cpp/socket_server/.gitignore @@ -0,0 +1 @@ +*build* \ No newline at end of file diff --git a/examples/cpp/socket_server/CMakeLists.txt b/examples/cpp/socket_server/CMakeLists.txt new file mode 100644 index 0000000..1948c66 --- /dev/null +++ b/examples/cpp/socket_server/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.11) + +project(netkit-example LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(netkit) + +add_executable( + netkit-example + main.cpp +) +target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/socket_server/main.cpp b/examples/cpp/socket_server/main.cpp new file mode 100644 index 0000000..def4f95 --- /dev/null +++ b/examples/cpp/socket_server/main.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include +#include + +[[noreturn]] int main() { + netkit::sock::addr addr{ + "0.0.0.0", // or simply "localhost" + 8080, + netkit::sock::addr_type::ipv4 + }; + + netkit::tcp::tcp_server server{addr}; + + server.bind(); + server.listen(); + + std::cout << "server listening on port 8080\n"; + + while (true) { + auto client = server.accept(); + + std::cout << "client connected from " << client->peer().get_ip() << "\n"; + + // we can read with client->read() + // in a http context, you might want to read until you find \r\n\r\n + // and then parse the headers. then you'd continue reading. + + constexpr std::string_view response = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 12\r\n" + "Connection: close\r\n" + "\r\n" + "Hello world!"; + + auto result = client->write_all(response); + + if (result.status != netkit::stream::stream_status::success) { + std::cerr << "failed to send response\n"; + } + + client->close(); + + std::cout << "client closed\n"; + } + + + // we will never actually reach this + server.close(); +} \ No newline at end of file diff --git a/examples/cpp/socket_server_async/main.cpp b/examples/cpp/socket_server_async/main.cpp index 0f02c9f..0c8a9bd 100644 --- a/examples/cpp/socket_server_async/main.cpp +++ b/examples/cpp/socket_server_async/main.cpp @@ -4,46 +4,49 @@ #include netkit::io::task<> -handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { - char buffer[8192]; +handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { + std::array buffer{}; + std::string response; while (true) { - auto received = co_await client->recv(buffer, sizeof(buffer)); + auto received = co_await client->read(buffer); - if (received == 0) { + if (received.status != netkit::stream::stream_status::success) { + throw std::runtime_error{"error"}; + } + + if (received.bytes == 0) { std::cout << "client disconnected\n"; break; } - std::cout << buffer << "\n"; + response.append(reinterpret_cast(buffer.data()), received.bytes); - co_await client->send(buffer, received); + co_await client->write_all(std::span(buffer.data(), received.bytes)); - client->close(); + break; } + + std::cerr << "client " << client->peer().get_ip() << " disconnected\n"; + + client->close(); } netkit::io::task<> request(netkit::io::io_context& ctx) { - netkit::sock::addr addr{ - "localhost", - 1337, - netkit::sock::addr_type::hostname - }; - - netkit::sock::async_sock sock{ - ctx, - addr, - netkit::sock::type::tcp, - netkit::sock::opt::reuse_addr | netkit::sock::opt::no_delay | netkit::sock::opt::no_blocking - }; - - sock.bind(); - sock.listen(); + netkit::sock::addr addr{"localhost", 1337, netkit::sock::addr_type::hostname}; + + netkit::tcp::async_tcp_server server{ctx, addr}; + + server.bind(); + server.listen(); + + std::cerr << "server listening on port " << addr.get_port() << "\n"; // ReSharper disable once CppDFAEndlessLoop while (true) { - auto client = co_await sock.accept(); + auto client = co_await server.accept(); + std::cerr << "client " << client->peer().get_ip() << " connected\n"; ctx.spawn(handle_client(ctx, std::move(client))); } } diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index cac32d1..ce25cf2 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -78,8 +78,13 @@ #include #include #include +#include +#include // tcp #include +#include +#include +#include #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/socket/native/native_async_listener.hpp b/include/netkit/socket/native/native_async_listener.hpp index 259e17b..06a66d7 100644 --- a/include/netkit/socket/native/native_async_listener.hpp +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -15,7 +15,7 @@ constexpr fd_t INVALID_SOCKET = -1; class NETKIT_API native_async_listener : public basic_native_async_listener { public: - native_async_listener(io::io_context& ctx, const addr& address, type t = type::tcp, opt opts = opt::no_reuse_addr | opt::no_blocking); + native_async_listener(io::io_context& ctx, const addr& address, type t = type::tcp, opt opts = opt::reuse_addr | opt::no_blocking); ~native_async_listener() override; void bind() override; diff --git a/include/netkit/socket/native/native_async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp index 1739326..3df7dbf 100644 --- a/include/netkit/socket/native/native_async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -36,8 +36,8 @@ namespace netkit::sock::native { io::io_context& context_; public: - native_async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); - native_async_sock(io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay); + native_async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::no_blocking); + native_async_sock(io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::no_blocking); ~native_async_sock() override; sock::addr& get_addr() override; [[nodiscard]] const sock::addr& get_addr() const override; diff --git a/include/netkit/socket/native/native_sync_listener.hpp b/include/netkit/socket/native/native_sync_listener.hpp index 2a2c50f..a72ed45 100644 --- a/include/netkit/socket/native/native_sync_listener.hpp +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -13,7 +13,7 @@ class NETKIT_API native_sync_listener { public: void set_sock_opts(opt opts) const; - native_sync_listener(const addr& address, type t = type::tcp, opt opts = opt::no_reuse_addr); + native_sync_listener(const addr& address, type t = type::tcp, opt opts = opt::reuse_addr|opt::blocking); ~native_sync_listener() override = default; void bind() override; diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp index a63b112..c547c24 100644 --- a/include/netkit/socket/native/native_sync_sock.hpp +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -53,7 +53,7 @@ namespace netkit::sock::native { * @param t The socket type (tcp, udp, unix). * @param opts The socket options (reuse_addr, no_reuse_addr). */ - native_sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); + native_sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::blocking); #ifdef NETKIT_UNIX /** * @brief Constructs a sync_sock object from an existing file descriptor. @@ -62,7 +62,7 @@ namespace netkit::sock::native { * @param t The socket type (tcp, udp, unix). * @param opts The socket options (reuse_addr, no_reuse_addr). */ - native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); + native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::blocking); #endif ~native_sync_sock() override; sock::addr& get_addr() override; diff --git a/include/netkit/stream/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp new file mode 100644 index 0000000..7fad8e9 --- /dev/null +++ b/include/netkit/stream/async_socket_stream.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace netkit::stream { + +class NETKIT_API async_socket_stream : public basic_async_stream { +public: + explicit async_socket_stream(std::unique_ptr socket) : socket_(std::move(socket)) {} + + [[nodiscard]] io::task<> connect() const; + [[nodiscard]] io::task read(std::span buffer) override; + [[nodiscard]] io::task write(std::span buffer) override; + void close() noexcept override; + + [[nodiscard]] sock::addr peer() const; +private: + std::unique_ptr socket_; +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/basic_async_stream.hpp b/include/netkit/stream/basic_async_stream.hpp new file mode 100644 index 0000000..ed58498 --- /dev/null +++ b/include/netkit/stream/basic_async_stream.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include + +namespace netkit::stream { + +class NETKIT_API basic_async_stream { +public: + virtual ~basic_async_stream() = default; + + virtual io::task read(std::span buffer) = 0; + virtual io::task write(std::span buffer) = 0; + + virtual io::task read(void* data, std::size_t size) { + co_return co_await read(std::span(static_cast(data), size) ); + } + + virtual io::task write(const void* data, std::size_t size) { + co_return co_await write(std::span(static_cast(data), size)); + } + + virtual void close() noexcept = 0; +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index 3f32ba5..a0e6d0d 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -17,6 +17,10 @@ class basic_stream { return this->read(std::span(static_cast(data), size)); } + stream_result write(const void* data, std::size_t size) { + return this->write(std::span(static_cast(data), size)); + } + virtual void close() noexcept = 0; }; diff --git a/include/netkit/stream/socket_stream.hpp b/include/netkit/stream/socket_stream.hpp index e7ca20c..d1b1d71 100644 --- a/include/netkit/stream/socket_stream.hpp +++ b/include/netkit/stream/socket_stream.hpp @@ -10,77 +10,14 @@ class socket_stream : public basic_stream { explicit socket_stream(std::unique_ptr socket) : socket_(std::move(socket)) {} - void connect() { - if (!socket_) - throw std::logic_error("socket closed"); + void connect(); - socket_->connect(); - } + [[nodiscard]] stream_result read(std::span buffer) override; + [[nodiscard]] stream_result write(std::span buffer) override; - [[nodiscard]] stream_result read(std::span buffer) override { - if (!socket_) - throw std::logic_error("socket closed"); + void close() noexcept override; - auto result = socket_->recv( - buffer.data(), - buffer.size() - ); - - if (result == 0) { - return { - 0, - stream_status::eof - }; - } - - if (result < 0) { - return { - 0, - stream_status::error - }; - } - - return { - static_cast(result), - stream_status::success - }; - } - - [[nodiscard]] stream_result write(std::span buffer) override { - if (!socket_) - throw std::logic_error("socket closed"); - - auto result = socket_->send( - buffer.data(), - buffer.size() - ); - - if (result < 0) { - return { - 0, - stream_status::error - }; - } - - return { - static_cast(result), - stream_status::success - }; - } - - void close() noexcept override { - if (socket_) { - socket_->close(); - socket_.reset(); - } - } - - [[nodiscard]] sock::addr peer() const { - if (!socket_) - throw std::logic_error("socket closed"); - - return socket_->get_peer(); - } + [[nodiscard]] sock::addr peer() const; private: std::unique_ptr socket_; }; diff --git a/include/netkit/stream/stream_enum.hpp b/include/netkit/stream/stream_enum.hpp index 4e24d4b..109e7f7 100644 --- a/include/netkit/stream/stream_enum.hpp +++ b/include/netkit/stream/stream_enum.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace netkit::stream { enum class stream_status { success, diff --git a/include/netkit/tcp/async_tcp_connection.hpp b/include/netkit/tcp/async_tcp_connection.hpp new file mode 100644 index 0000000..84eba5a --- /dev/null +++ b/include/netkit/tcp/async_tcp_connection.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace netkit::tcp { + +class async_tcp_connection { +public: + async_tcp_connection(io::io_context& ctx, const sock::addr& addr) : stream_(std::make_unique(ctx, addr, sock::type::tcp)) {} + async_tcp_connection(std::unique_ptr socket) : stream_(std::move(socket)) {} + ~async_tcp_connection(); + + netkit::io::task<> connect() const; + netkit::io::task read(std::span buffer); + netkit::io::task write(std::span buffer); + netkit::io::task write_all(std::span buffer); + netkit::io::task write_all(std::string_view data); + netkit::io::task> read_all(std::size_t max_bytes = 16 * 1024 * 1024); + netkit::io::task read_all_string(); + + void close() noexcept; + [[nodiscard]] sock::addr peer() const; + + stream::async_socket_stream& stream(); + +private: + stream::async_socket_stream stream_; +}; + +} \ No newline at end of file diff --git a/include/netkit/tcp/async_tcp_server.hpp b/include/netkit/tcp/async_tcp_server.hpp new file mode 100644 index 0000000..32a8d08 --- /dev/null +++ b/include/netkit/tcp/async_tcp_server.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include + +namespace netkit::tcp { + +class async_tcp_connection; + +class async_tcp_server { +public: + async_tcp_server( + io::io_context& ctx, + sock::addr addr + ); + + ~async_tcp_server(); + + void bind(); + + void listen(); + void listen(int backlog); + + io::task> accept(); + + void close() noexcept; + + [[nodiscard]] const sock::addr& get_local_endpoint() const noexcept; +private: + sock::addr addr_; + std::unique_ptr listener_; +}; + +} \ No newline at end of file diff --git a/include/netkit/tcp/tcp_connection.hpp b/include/netkit/tcp/tcp_connection.hpp index 9430bea..f7e1fe8 100644 --- a/include/netkit/tcp/tcp_connection.hpp +++ b/include/netkit/tcp/tcp_connection.hpp @@ -13,105 +13,24 @@ namespace netkit::tcp { class tcp_connection { public: tcp_connection(const sock::addr& addr) : stream_(std::make_unique(addr, sock::type::tcp)) {} + tcp_connection(std::unique_ptr socket) : stream_(std::move(socket)) {} - ~tcp_connection() { - this->close(); - } + ~tcp_connection(); - void connect() { - stream_.connect(); - } + void connect(); - stream::stream_result read(std::span buffer) { - return stream_.read(buffer); - } + stream::stream_result read(std::span buffer); + stream::stream_result write(std::span buffer); + stream::stream_result write_all(std::span buffer); + stream::stream_result write_all(std::string_view data); + std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024); + std::string read_all_string(); - stream::stream_result write(std::span buffer) { - return stream_.write(buffer); - } + void close() noexcept; - stream::stream_result write_all(std::span buffer) { - std::size_t total = 0; + [[nodiscard]] sock::addr peer() const; - while (total < buffer.size()) { - auto result = this->write(buffer.subspan(total)); - - if (result.status != stream::stream_status::success) - return { - result.bytes + total, - result.status - }; - - total += result.bytes; - } - - return { - total, - stream::stream_status::success - }; - } - - stream::stream_result write_all(std::string_view data) { - return write_all( - std::as_bytes( - std::span(data.data(), data.size()) - ) - ); - } - - std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024) { - std::vector result; - - std::size_t total = 0; - - std::array buffer{}; - - while (total < max_bytes) { - auto res = read(buffer); - - if (res.status == stream::stream_status::eof) - break; - - if (res.status != stream::stream_status::success) - throw std::runtime_error("read failed"); - - auto amount = std::min(res.bytes, max_bytes - total); - - result.insert( - result.end(), - buffer.begin(), - buffer.begin() + amount - ); - - total += amount; - - if (amount != res.bytes) - throw std::length_error("read_all exceeded maximum size"); - } - - return result; - } - - std::string read_all_string() { - auto data = this->read_all(); - - return { - reinterpret_cast(data.data()), - data.size() - }; - } - - void close() noexcept { - stream_.close(); - } - - [[nodiscard]] sock::addr peer() const { - return stream_.peer(); - } - - stream::socket_stream& stream() { - return stream_; - } + stream::socket_stream& stream(); private: stream::socket_stream stream_; diff --git a/include/netkit/tcp/tcp_server.hpp b/include/netkit/tcp/tcp_server.hpp new file mode 100644 index 0000000..0b9eb0b --- /dev/null +++ b/include/netkit/tcp/tcp_server.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace netkit::tcp { + +class tcp_connection; + +class tcp_server { +public: + tcp_server(sock::addr addr); + + ~tcp_server(); + + void bind(); + void listen(); + void listen(int backlog); + + std::unique_ptr accept(); + + void close() noexcept; + + const sock::addr& get_local_endpoint() const noexcept; +private: + sock::addr addr_; + std::unique_ptr listener_; +}; + +} \ No newline at end of file diff --git a/src/stream/async_socket_stream.cpp b/src/stream/async_socket_stream.cpp new file mode 100644 index 0000000..4bc38c0 --- /dev/null +++ b/src/stream/async_socket_stream.cpp @@ -0,0 +1,37 @@ +#include +#include +#include + +netkit::io::task +netkit::stream::async_socket_stream::connect() const { + co_await socket_->connect(); +} + +netkit::io::task +netkit::stream::async_socket_stream::read(std::span buffer) { + auto result = co_await socket_->recv(buffer.data(), buffer.size()); + + if (result == 0) { + co_return stream_result{ 0, stream_status::eof }; + } + + co_return stream_result{ result, stream_status::success }; +} + +netkit::io::task +netkit::stream::async_socket_stream::write(std::span buffer) { + auto result = co_await socket_->send(buffer.data(), buffer.size()); + co_return stream_result{ result, result == 0 ? stream_status::eof : stream_status::success }; +} + + +void netkit::stream::async_socket_stream::close() noexcept { + if (socket_) { + socket_->close(); + socket_.reset(); + } +} + +netkit::sock::addr netkit::stream::async_socket_stream::peer() const { + return socket_->get_peer(); +} \ No newline at end of file diff --git a/src/stream/socket_stream.cpp b/src/stream/socket_stream.cpp new file mode 100644 index 0000000..e10379e --- /dev/null +++ b/src/stream/socket_stream.cpp @@ -0,0 +1,73 @@ +#include + +void netkit::stream::socket_stream::connect() { + if (!socket_) + throw std::logic_error("socket closed"); + + socket_->connect(); +} + +netkit::stream::stream_result netkit::stream::socket_stream::read(std::span buffer) { + if (!socket_) + throw std::logic_error("socket closed"); + + auto result = socket_->recv( + buffer.data(), + buffer.size() + ); + + if (result == 0) { + return { + 0, + stream_status::eof + }; + } + + if (result < 0) { + return { + 0, + stream_status::error + }; + } + + return { + static_cast(result), + stream_status::success + }; +} + +netkit::stream::stream_result netkit::stream::socket_stream::write(std::span buffer) { + if (!socket_) + throw std::logic_error("socket closed"); + + auto result = socket_->send( + buffer.data(), + buffer.size() + ); + + if (result < 0) { + return { + 0, + stream_status::error + }; + } + + return { + static_cast(result), + stream_status::success + }; +} + +void netkit::stream::socket_stream::close() noexcept { + if (socket_) { + socket_->close(); + socket_.reset(); + } +} + +netkit::sock::addr netkit::stream::socket_stream::peer() const { + if (!socket_) + throw std::logic_error("socket closed"); + + return socket_->get_peer(); +} \ No newline at end of file diff --git a/src/tcp/async_tcp_connection.cpp b/src/tcp/async_tcp_connection.cpp new file mode 100644 index 0000000..d3bc5f4 --- /dev/null +++ b/src/tcp/async_tcp_connection.cpp @@ -0,0 +1,96 @@ +#include + +netkit::tcp::async_tcp_connection::~async_tcp_connection() { + this->close(); +} + +netkit::io::task netkit::tcp::async_tcp_connection::connect() const { + co_await stream_.connect(); +} + +netkit::io::task +netkit::tcp::async_tcp_connection::read(std::span buffer) { + co_return co_await stream_.read(buffer); +} + +netkit::io::task +netkit::tcp::async_tcp_connection::write(std::span buffer) { + co_return co_await stream_.write(buffer); +} + +netkit::io::task +netkit::tcp::async_tcp_connection::write_all(std::span buffer) { + std::size_t total = 0; + + while (total < buffer.size()) { + auto result = co_await write( + buffer.subspan(total) + ); + + if (result.status != stream::stream_status::success) + co_return result; + + total += result.bytes; + } + + co_return stream::stream_result{ + total, + stream::stream_status::success + }; +} + +netkit::io::task +netkit::tcp::async_tcp_connection::write_all(std::string_view data) { + co_return co_await this->write_all(std::as_bytes(std::span(data.data(), data.size()))); +} + +netkit::io::task> +netkit::tcp::async_tcp_connection::read_all(std::size_t max_bytes) { + std::vector result; + + std::array buffer{}; + + std::size_t total = 0; + + while (total < max_bytes) { + auto res = co_await read(buffer); + + if (res.status == stream::stream_status::eof) + break; + + if (res.status != stream::stream_status::success) + throw std::runtime_error("read failed"); + + result.insert( + result.end(), + buffer.begin(), + buffer.begin() + res.bytes + ); + + total += res.bytes; + } + + co_return result; +} + +netkit::io::task +netkit::tcp::async_tcp_connection::read_all_string() { + auto data = co_await this->read_all(); + + co_return std::string{ + reinterpret_cast(data.data()), + data.size() + }; +} + +void netkit::tcp::async_tcp_connection::close() noexcept { + stream_.close(); +} + +netkit::sock::addr netkit::tcp::async_tcp_connection::peer() const { + return stream_.peer(); +} + +netkit::stream::async_socket_stream& netkit::tcp::async_tcp_connection::stream() { + return stream_; +} \ No newline at end of file diff --git a/src/tcp/async_tcp_server.cpp b/src/tcp/async_tcp_server.cpp new file mode 100644 index 0000000..69dac46 --- /dev/null +++ b/src/tcp/async_tcp_server.cpp @@ -0,0 +1,37 @@ +#include +#include +#include + +netkit::tcp::async_tcp_server::async_tcp_server(io::io_context& ctx, netkit::sock::addr addr) + : addr_(std::move(addr)), listener_(std::make_unique(ctx, addr_, sock::type::tcp)) {} + +netkit::tcp::async_tcp_server::~async_tcp_server() { + this->close(); +} + +void netkit::tcp::async_tcp_server::bind() { + listener_->bind(); +} + +void netkit::tcp::async_tcp_server::listen(int backlog) { + listener_->listen(backlog); +} + +void netkit::tcp::async_tcp_server::listen() { + listener_->listen(-1); +} + +netkit::io::task> +netkit::tcp::async_tcp_server::accept() { + auto socket = co_await listener_->accept(); + co_return std::make_unique(std::move(socket)); +} + +void netkit::tcp::async_tcp_server::close() noexcept { + if (listener_) + listener_->close(); +} + +const netkit::sock::addr& netkit::tcp::async_tcp_server::get_local_endpoint() const noexcept { + return listener_->get_local_endpoint(); +} \ No newline at end of file diff --git a/src/tcp/tcp_connection.cpp b/src/tcp/tcp_connection.cpp new file mode 100644 index 0000000..332ccf7 --- /dev/null +++ b/src/tcp/tcp_connection.cpp @@ -0,0 +1,99 @@ +#include + +netkit::tcp::tcp_connection::~tcp_connection() { + this->close(); +} + +void netkit::tcp::tcp_connection::connect() { + stream_.connect(); +} + +netkit::stream::stream_result netkit::tcp::tcp_connection::read(std::span buffer) { + return stream_.read(buffer); +} + +netkit::stream::stream_result netkit::tcp::tcp_connection::write(std::span buffer) { + return stream_.write(buffer); +} + +netkit::stream::stream_result netkit::tcp::tcp_connection::write_all(std::span buffer) { + std::size_t total = 0; + + while (total < buffer.size()) { + auto result = this->write(buffer.subspan(total)); + + if (result.status != stream::stream_status::success) + return { + result.bytes + total, + result.status + }; + + total += result.bytes; + } + + return { + total, netkit::stream::stream_status::success + }; +} + +netkit::stream::stream_result netkit::tcp::tcp_connection::write_all(std::string_view data) { + return write_all( + std::as_bytes( + std::span(data.data(), data.size()) + ) + ); +} + +std::vector netkit::tcp::tcp_connection::read_all(std::size_t max_bytes) { + std::vector result; + + std::size_t total = 0; + + std::array buffer{}; + + while (total < max_bytes) { + auto res = read(buffer); + + if (res.status == netkit::stream::stream_status::eof) + break; + + if (res.status != netkit::stream::stream_status::success) + throw std::runtime_error("read failed"); + + auto amount = std::min(res.bytes, max_bytes - total); + + result.insert( + result.end(), + buffer.begin(), + buffer.begin() + amount + ); + + total += amount; + + if (amount != res.bytes) + throw std::length_error("read_all exceeded maximum size"); + } + + return result; +} + +std::string netkit::tcp::tcp_connection::read_all_string() { + auto data = this->read_all(); + + return { + reinterpret_cast(data.data()), + data.size() + }; +} + +void netkit::tcp::tcp_connection::close() noexcept { + stream_.close(); +} + +netkit::sock::addr netkit::tcp::tcp_connection::peer() const { + return stream_.peer(); +} + +netkit::stream::socket_stream& netkit::tcp::tcp_connection::stream() { + return stream_; +} \ No newline at end of file diff --git a/src/tcp/tcp_server.cpp b/src/tcp/tcp_server.cpp new file mode 100644 index 0000000..4bab832 --- /dev/null +++ b/src/tcp/tcp_server.cpp @@ -0,0 +1,39 @@ +#include +#include + +netkit::tcp::tcp_server::tcp_server(netkit::sock::addr addr) + : addr_(std::move(addr)), listener_(std::make_unique(addr_, sock::type::tcp)) {} + +netkit::tcp::tcp_server::~tcp_server() { + this->close(); +} + +void netkit::tcp::tcp_server::bind() { + listener_->bind(); +} + +void netkit::tcp::tcp_server::listen(int backlog) { + listener_->listen(backlog); +} + +void netkit::tcp::tcp_server::listen() { + listener_->listen(-1); +} + +std::unique_ptr +netkit::tcp::tcp_server::accept() { + auto socket = listener_->accept(); + + return std::make_unique( + std::move(socket) + ); +} + +void netkit::tcp::tcp_server::close() noexcept { + if (listener_) + listener_->close(); +} + +const netkit::sock::addr& netkit::tcp::tcp_server::get_local_endpoint() const noexcept { + return listener_->get_local_endpoint(); +} \ No newline at end of file From fb31b9b288bd637f45c5f54cc86b2338d6ee9bc2 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Mon, 27 Jul 2026 00:03:20 +0200 Subject: [PATCH 06/34] remove linux guards in some io headers --- include/netkit/io/io_awaitable.hpp | 6 +----- include/netkit/io/io_context.hpp | 6 +----- include/netkit/io/task.hpp | 8 +------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index 475b892..6f2a98c 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -5,8 +5,6 @@ #include #include -#ifdef NETKIT_LINUX - namespace netkit::io { class io_context; // fw decl. @@ -25,6 +23,4 @@ namespace netkit::io { void await_resume() noexcept {} }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/include/netkit/io/io_context.hpp b/include/netkit/io/io_context.hpp index 780016a..fe84873 100644 --- a/include/netkit/io/io_context.hpp +++ b/include/netkit/io/io_context.hpp @@ -7,8 +7,6 @@ #include -#ifdef NETKIT_LINUX - namespace netkit::io { class io_context { @@ -61,6 +59,4 @@ class io_context { std::vector> tasks_; }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/include/netkit/io/task.hpp b/include/netkit/io/task.hpp index 6070841..d3786d8 100644 --- a/include/netkit/io/task.hpp +++ b/include/netkit/io/task.hpp @@ -6,10 +6,6 @@ #include #include -#include - -#ifdef NETKIT_LINUX - namespace netkit::io { template @@ -154,6 +150,4 @@ class task { handle_type handle_; }; -} - -#endif \ No newline at end of file +} \ No newline at end of file From 697916574e8191744fe47a7bea39d3e342b52868 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Mon, 27 Jul 2026 01:36:21 +0200 Subject: [PATCH 07/34] move linux specifics --- .idea/vcs.xml | 3 ++ CMakeLists.txt | 2 +- examples/cpp/socket_server_async/.gitignore | 3 +- include/netkit/io/basic_io_backend.hpp | 27 +++++++++++++ include/netkit/io/io_awaitable.hpp | 4 +- include/netkit/io/io_context.hpp | 9 ++--- .../io_backend.hpp} | 40 ++++++++----------- include/netkit/netkit.hpp | 2 +- .../io_backend.cpp} | 14 ++++--- 9 files changed, 66 insertions(+), 38 deletions(-) create mode 100644 include/netkit/io/basic_io_backend.hpp rename include/netkit/io/{basic_io_context.hpp => linux/io_backend.hpp} (85%) rename src/io/{basic_io_context.cpp => linux/io_backend.cpp} (80%) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index c8397c9..4940fff 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,8 @@ + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ccf91..9ddacde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/http/multipart_reader.cpp src/body/multipart_part_body.cpp src/io/io_awaitable.cpp - src/io/basic_io_context.cpp + src/io/linux/io_backend.cpp src/socket/native/native_async_socket.cpp src/socket/native/native_sync_listener.cpp src/socket/native/native_async_listener.cpp diff --git a/examples/cpp/socket_server_async/.gitignore b/examples/cpp/socket_server_async/.gitignore index 3c34c2f..8401570 100644 --- a/examples/cpp/socket_server_async/.gitignore +++ b/examples/cpp/socket_server_async/.gitignore @@ -1 +1,2 @@ -*build* \ No newline at end of file +*build* +response.txt \ No newline at end of file diff --git a/include/netkit/io/basic_io_backend.hpp b/include/netkit/io/basic_io_backend.hpp new file mode 100644 index 0000000..dd45095 --- /dev/null +++ b/include/netkit/io/basic_io_backend.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace netkit::io { + +struct io_handle_state { + std::vector> read_waiters; + std::vector> write_waiters; +}; + +enum class io_event { read, write }; + +class basic_io_backend { +public: + virtual ~basic_io_backend() = default; + virtual void wake() = 0; + virtual void update_state(int fd, const io_handle_state& state) = 0; + virtual void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) = 0; + virtual void run() = 0; + virtual void stop() = 0; + virtual void poll(int timeout_ms) = 0; + virtual void poll() = 0; +}; + +} // namespace netkit::io \ No newline at end of file diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index 6f2a98c..aaa9b4f 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -2,14 +2,14 @@ #include #include -#include +#include #include namespace netkit::io { class io_context; // fw decl. struct io_awaitable { - basic_io_context& ctx; + io_backend& ctx; sock::fd_t fd; io_event ev; diff --git a/include/netkit/io/io_context.hpp b/include/netkit/io/io_context.hpp index fe84873..d8dc53d 100644 --- a/include/netkit/io/io_context.hpp +++ b/include/netkit/io/io_context.hpp @@ -1,11 +1,10 @@ #pragma once -#include +#include +#include #include -#include #include - -#include +#include namespace netkit::io { @@ -53,7 +52,7 @@ class io_context { std::erase_if(tasks_, [](const auto& task) { return task.done(); } ); } - basic_io_context backend_; + io_backend backend_; std::atomic_bool running_ = false; std::vector> tasks_; diff --git a/include/netkit/io/basic_io_context.hpp b/include/netkit/io/linux/io_backend.hpp similarity index 85% rename from include/netkit/io/basic_io_context.hpp rename to include/netkit/io/linux/io_backend.hpp index dce550b..8715b13 100644 --- a/include/netkit/io/basic_io_context.hpp +++ b/include/netkit/io/linux/io_backend.hpp @@ -4,6 +4,8 @@ #ifdef NETKIT_LINUX +#include + #include #include #include @@ -18,16 +20,9 @@ namespace netkit::io { -struct fd_state { - std::vector> read_waiters; - std::vector> write_waiters; -}; - -enum class io_event { read, write }; - -class basic_io_context { +class io_backend : public basic_io_backend { public: - basic_io_context() { + io_backend() { epoll_fd_ = epoll_create1(0); if (epoll_fd_ == -1) @@ -50,7 +45,7 @@ class basic_io_context { } } - ~basic_io_context() { + ~io_backend() override { if (wake_fd_ != -1) close(wake_fd_); @@ -58,13 +53,13 @@ class basic_io_context { close(epoll_fd_); } - void wake() { + void wake() override { uint64_t value = 1; write(wake_fd_, &value, sizeof(value)); } - void update_epoll(int fd, const fd_state& state) { + void update_state(int fd, const io_handle_state& state) override { epoll_event ev{}; ev.data.fd = fd; ev.events = 0; @@ -92,8 +87,7 @@ class basic_io_context { throw std::runtime_error(std::strerror(errno)); registered_fds_.insert(fd); - } - else { + } else { ret = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ev); if (ret == -1) @@ -101,7 +95,7 @@ class basic_io_context { } } - void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) { + void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) override { auto& state = fd_map_[fd]; if (ev == io_event::read) @@ -109,10 +103,10 @@ class basic_io_context { else state.write_waiters.push_back(h); - update_epoll(fd, state); + update_state(fd, state); } - void run() { + void run() override { constexpr int MAX_EVENTS = 64; epoll_event events[MAX_EVENTS]; @@ -158,27 +152,27 @@ class basic_io_context { for (auto h : read_waiters) h.resume(); for (auto h : write_waiters) h.resume(); - update_epoll(fd, state); + update_state(fd, state); continue; } - update_epoll(fd, state); + update_state(fd, state); } } } - void stop() { + void stop() override { running_ = false; } - void poll(int timeout_ms = -1); - + void poll(int timeout_ms) override; + void poll() override; private: int epoll_fd_; bool running_ = true; int wake_fd_ = -1; - std::unordered_map fd_map_; + std::unordered_map fd_map_; std::unordered_set registered_fds_; }; diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index ce25cf2..84624be 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -68,7 +68,7 @@ #include // io -#include +#include #include #include #include diff --git a/src/io/basic_io_context.cpp b/src/io/linux/io_backend.cpp similarity index 80% rename from src/io/basic_io_context.cpp rename to src/io/linux/io_backend.cpp index 79f5f14..764f382 100644 --- a/src/io/basic_io_context.cpp +++ b/src/io/linux/io_backend.cpp @@ -1,8 +1,8 @@ -#include +#include #ifdef NETKIT_LINUX -void netkit::io::basic_io_context::poll(int timeout_ms) -{ + +void netkit::io::io_backend::poll(int timeout_ms) { epoll_event events[64]; int n = epoll_wait( @@ -23,7 +23,6 @@ void netkit::io::basic_io_context::poll(int timeout_ms) continue; } - auto it = fd_map_.find(fd); if (it == fd_map_.end()) @@ -49,7 +48,12 @@ void netkit::io::basic_io_context::poll(int timeout_ms) } } - update_epoll(fd, state); + update_state(fd, state); } } + +void netkit::io::io_backend::poll() { + this->poll(-1); +} + #endif \ No newline at end of file From 7c5beade28e029df4917d3cc98c770d3324d705c Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Mon, 27 Jul 2026 17:22:01 +0200 Subject: [PATCH 08/34] add fallback for when epoll() is not available --- CMakeLists.txt | 6 + examples/cpp/socket_async/response.txt | 6 +- include/netkit/io/fallback/io_backend.hpp | 54 ++++++++ include/netkit/io/io_awaitable.hpp | 2 +- include/netkit/io/io_backend.hpp | 4 + include/netkit/io/linux/io_backend.hpp | 161 ++-------------------- include/netkit/netkit.hpp | 1 + src/io/fallback/io_backend.cpp | 106 ++++++++++++++ src/io/linux/io_backend.cpp | 158 ++++++++++++++++++++- 9 files changed, 341 insertions(+), 157 deletions(-) create mode 100644 include/netkit/io/fallback/io_backend.hpp create mode 100644 include/netkit/io/io_backend.hpp create mode 100644 src/io/fallback/io_backend.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ddacde..344d76e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ option(NETKIT_ENABLE_C_TESTS "Enable C building tests" OFF) option(NETKIT_ENABLE_WINDOWS_CERTSTORE "Enable getting CA certificates from the Windows store" ON) option(NETKIT_ENABLE_FALLBACK_CA "Enable fallback CA certficate (required for DevkitPro)" ON) option(NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER "Enable using netkit's DNS resolver instead of the system resolver" OFF) +option(NETKIT_ENABLE_EPOLL "Enable using epoll backend (Linux only)" ON) option(NETKIT_ENABLE_HTTP "Enable netkit's HTTP abstractions" ON) option(NETKIT_ENABLE_DNS "Enable netkit's DNS features" ON) option(NETKIT_WOLFSSL_DEBUG "Enable WolfSSL debugging" OFF) @@ -88,6 +89,7 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/stream/async_socket_stream.cpp src/tcp/async_tcp_connection.cpp src/tcp/async_tcp_server.cpp + src/io/fallback/io_backend.cpp ) set_target_properties(netkit PROPERTIES @@ -226,6 +228,10 @@ if (NETKIT_ENABLE_DNS) target_compile_definitions(netkit PUBLIC NETKIT_DNS) endif() +if (NETKIT_ENABLE_EPOLL) + target_compile_definitions(netkit PUBLIC NETKIT_EPOLL) +endif() + if (WIN32) target_link_libraries(netkit PUBLIC ws2_32 iphlpapi dnsapi crypt32) elseif (UNIX AND NOT APPLE) diff --git a/examples/cpp/socket_async/response.txt b/examples/cpp/socket_async/response.txt index 7c85951..27cdf09 100644 --- a/examples/cpp/socket_async/response.txt +++ b/examples/cpp/socket_async/response.txt @@ -1,9 +1,9 @@ HTTP/1.1 301 Moved Permanently Location: http://www.google.com/ Content-Type: text/html; charset=UTF-8 -Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-1phIRS7D711BKE5J3groVw' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp -Date: Sun, 26 Jul 2026 18:32:22 GMT -Expires: Tue, 25 Aug 2026 18:32:22 GMT +Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-_Z6KSerqCBOWipbdNOKQwA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp +Date: Mon, 27 Jul 2026 13:20:49 GMT +Expires: Wed, 26 Aug 2026 13:20:49 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 219 diff --git a/include/netkit/io/fallback/io_backend.hpp b/include/netkit/io/fallback/io_backend.hpp new file mode 100644 index 0000000..cbec0cd --- /dev/null +++ b/include/netkit/io/fallback/io_backend.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) + +#include +#include +#include +#include +#include +#include + +namespace netkit::io { + +class io_backend : public basic_io_backend { +public: + io_backend(std::size_t threads = 4); + ~io_backend() override; + + void wake() override; + + void update_state(int, const io_handle_state&) override {} + + void register_waiter(int fd, io_event event, std::coroutine_handle<> h) override; + + void run() override; + void stop() override; + + void poll(int timeout_ms) override; + void poll() override; + +private: + struct waiter { + int fd{}; + io_event event{}; + std::coroutine_handle<> handle; + }; + + void worker(); + + std::atomic running_{true}; + + std::mutex mutex_; + std::condition_variable cv_; + + std::queue queue_; + std::vector workers_; + std::vector waiters_; +}; + +} // namespace netkit::io + +#endif \ No newline at end of file diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index aaa9b4f..de532d8 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include namespace netkit::io { diff --git a/include/netkit/io/io_backend.hpp b/include/netkit/io/io_backend.hpp new file mode 100644 index 0000000..54025de --- /dev/null +++ b/include/netkit/io/io_backend.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include +#include \ No newline at end of file diff --git a/include/netkit/io/linux/io_backend.hpp b/include/netkit/io/linux/io_backend.hpp index 8715b13..0f0b3dd 100644 --- a/include/netkit/io/linux/io_backend.hpp +++ b/include/netkit/io/linux/io_backend.hpp @@ -2,169 +2,26 @@ #include -#ifdef NETKIT_LINUX +#if defined(NETKIT_LINUX) && defined(NETKIT_EPOLL) #include #include -#include -#include -#include -#include -#include #include #include -#include - -#include namespace netkit::io { class io_backend : public basic_io_backend { public: - io_backend() { - epoll_fd_ = epoll_create1(0); - - if (epoll_fd_ == -1) - throw std::runtime_error("epoll_create1 failed"); - - wake_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); - - if (wake_fd_ == -1) - throw std::runtime_error("eventfd failed"); - - - epoll_event ev{}; - ev.events = EPOLLIN; - ev.data.fd = wake_fd_; - - if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, wake_fd_, &ev) == -1) { - throw std::runtime_error( - "failed to add wake fd" - ); - } - } - - ~io_backend() override { - if (wake_fd_ != -1) - close(wake_fd_); - - if (epoll_fd_ != -1) - close(epoll_fd_); - } - - void wake() override { - uint64_t value = 1; - - write(wake_fd_, &value, sizeof(value)); - } - - void update_state(int fd, const io_handle_state& state) override { - epoll_event ev{}; - ev.data.fd = fd; - ev.events = 0; - - if (!state.read_waiters.empty()) - ev.events |= EPOLLIN; - - if (!state.write_waiters.empty()) - ev.events |= EPOLLOUT; - - if (ev.events == 0) { - if (registered_fds_.contains(fd)) { - epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr); - registered_fds_.erase(fd); - } - return; - } - - int ret; - - if (!registered_fds_.contains(fd)) { - ret = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev); - - if (ret == -1) - throw std::runtime_error(std::strerror(errno)); - - registered_fds_.insert(fd); - } else { - ret = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ev); - - if (ret == -1) - throw std::runtime_error(std::strerror(errno)); - } - } - - void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) override { - auto& state = fd_map_[fd]; - - if (ev == io_event::read) - state.read_waiters.push_back(h); - else - state.write_waiters.push_back(h); - - update_state(fd, state); - } - - void run() override { - constexpr int MAX_EVENTS = 64; - epoll_event events[MAX_EVENTS]; - - while (running_) { - int n = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); - - if (n == -1) { - if (errno == EINTR) continue; - throw std::runtime_error("epoll_wait failed"); - } - - for (int i = 0; i < n; ++i) { - int fd = events[i].data.fd; - - auto it = fd_map_.find(fd); - if (it == fd_map_.end()) continue; - - auto& state = it->second; - - if (events[i].events & EPOLLIN) { - auto waiters = std::move(state.read_waiters); - state.read_waiters.clear(); - - for (auto h : waiters) - h.resume(); - } - - if (events[i].events & EPOLLOUT) { - auto waiters = std::move(state.write_waiters); - state.write_waiters.clear(); - - for (auto h : waiters) - h.resume(); - } - - if (events[i].events & (EPOLLERR | EPOLLHUP)) { - auto read_waiters = std::move(state.read_waiters); - auto write_waiters = std::move(state.write_waiters); - - state.read_waiters.clear(); - state.write_waiters.clear(); - - for (auto h : read_waiters) h.resume(); - for (auto h : write_waiters) h.resume(); - - update_state(fd, state); - continue; - } - - update_state(fd, state); - } - } - } - - void stop() override { - running_ = false; - } - + io_backend(); + ~io_backend() override; + + void wake() override; + void update_state(int fd, const io_handle_state& state) override; + void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) override; + void run() override; + void stop() override; void poll(int timeout_ms) override; void poll() override; private: diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 84624be..43014cc 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -69,6 +69,7 @@ // io #include +#include #include #include #include diff --git a/src/io/fallback/io_backend.cpp b/src/io/fallback/io_backend.cpp new file mode 100644 index 0000000..5fcdf11 --- /dev/null +++ b/src/io/fallback/io_backend.cpp @@ -0,0 +1,106 @@ +#include + +#if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) + +void netkit::io::io_backend::worker() { + while (running_) { + waiter task; + + { + std::unique_lock lock(mutex_); + + cv_.wait(lock, [&] { + return !queue_.empty() || !running_; + }); + + if (!running_) + return; + + task = queue_.front(); + queue_.pop(); + } + + if (task.handle) + task.handle.resume(); + } +} + +netkit::io::io_backend::io_backend(std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + workers_.emplace_back( + [this] { + worker(); + } + ); + } +} + +netkit::io::io_backend::~io_backend() { + this->io_backend::stop(); + + for (auto& thread : workers_) { + if (thread.joinable()) + thread.join(); + } +} + +void netkit::io::io_backend::wake() { + cv_.notify_all(); +} + +void netkit::io::io_backend::register_waiter(int fd, io_event event, std::coroutine_handle<> handle) { + std::lock_guard lock(mutex_); + + waiters_.push_back({ + fd, + event, + handle + }); + + cv_.notify_one(); +} + + +void netkit::io::io_backend::run() { + while (running_) { + poll(); + } +} + +void netkit::io::io_backend::stop() { + running_ = false; + cv_.notify_all(); +} + +void netkit::io::io_backend::poll(int timeout_ms) { + std::unique_lock lock(mutex_); + + if (waiters_.empty()) { + if (timeout_ms < 0) { + cv_.wait(lock); + } else { + cv_.wait_for( + lock, + std::chrono::milliseconds(timeout_ms) + ); + } + } + + std::vector pending; + pending.swap(waiters_); + + lock.unlock(); + + + for (auto& waiter : pending) { + if (waiter.handle) + waiter.handle.resume(); + } +} + + +void netkit::io::io_backend::poll() { + poll(-1); +} + +#endif \ No newline at end of file diff --git a/src/io/linux/io_backend.cpp b/src/io/linux/io_backend.cpp index 764f382..70e2901 100644 --- a/src/io/linux/io_backend.cpp +++ b/src/io/linux/io_backend.cpp @@ -1,6 +1,162 @@ #include -#ifdef NETKIT_LINUX +#if defined(NETKIT_LINUX) && defined(NETKIT_EPOLL) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +netkit::io::io_backend::io_backend() { + epoll_fd_ = epoll_create1(0); + + if (epoll_fd_ == -1) + throw std::runtime_error("epoll_create1 failed"); + + wake_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + + if (wake_fd_ == -1) + throw std::runtime_error("eventfd failed"); + + epoll_event ev{}; + ev.events = EPOLLIN; + ev.data.fd = wake_fd_; + + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, wake_fd_, &ev) == -1) { + throw std::runtime_error("failed to add wake fd"); + } +} + +netkit::io::io_backend::~io_backend() { + if (wake_fd_ != -1) + close(wake_fd_); + + if (epoll_fd_ != -1) + close(epoll_fd_); +} + +void netkit::io::io_backend::wake() { + uint64_t value = 1; + + write(wake_fd_, &value, sizeof(value)); +} + +void netkit::io::io_backend::update_state(int fd, const io_handle_state& state) { + epoll_event ev{}; + ev.data.fd = fd; + ev.events = 0; + + if (!state.read_waiters.empty()) + ev.events |= EPOLLIN; + + if (!state.write_waiters.empty()) + ev.events |= EPOLLOUT; + + if (ev.events == 0) { + if (registered_fds_.contains(fd)) { + epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, nullptr); + registered_fds_.erase(fd); + } + return; + } + + int ret; + + if (!registered_fds_.contains(fd)) { + ret = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev); + + if (ret == -1) + throw std::runtime_error(std::strerror(errno)); + + registered_fds_.insert(fd); + } else { + ret = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ev); + + if (ret == -1) + throw std::runtime_error(std::strerror(errno)); + } +} + +void netkit::io::io_backend::register_waiter(int fd, io_event ev, std::coroutine_handle<> h) { + auto& state = fd_map_[fd]; + + if (ev == io_event::read) + state.read_waiters.push_back(h); + else + state.write_waiters.push_back(h); + + update_state(fd, state); +} + +void netkit::io::io_backend::run() { + constexpr int MAX_EVENTS = 64; + epoll_event events[MAX_EVENTS]; + + while (running_) { + int n = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1); + + if (n == -1) { + if (errno == EINTR) + continue; + throw std::runtime_error("epoll_wait failed"); + } + + for (int i = 0; i < n; ++i) { + int fd = events[i].data.fd; + + auto it = fd_map_.find(fd); + if (it == fd_map_.end()) + continue; + + auto& state = it->second; + + if (events[i].events & EPOLLIN) { + auto waiters = std::move(state.read_waiters); + state.read_waiters.clear(); + + for (auto h : waiters) + h.resume(); + } + + if (events[i].events & EPOLLOUT) { + auto waiters = std::move(state.write_waiters); + state.write_waiters.clear(); + + for (auto h : waiters) + h.resume(); + } + + if (events[i].events & (EPOLLERR | EPOLLHUP)) { + auto read_waiters = std::move(state.read_waiters); + auto write_waiters = std::move(state.write_waiters); + + state.read_waiters.clear(); + state.write_waiters.clear(); + + for (auto h : read_waiters) + h.resume(); + for (auto h : write_waiters) + h.resume(); + + update_state(fd, state); + continue; + } + + update_state(fd, state); + } + } +} + +void netkit::io::io_backend::stop() { + running_ = false; +} void netkit::io::io_backend::poll(int timeout_ms) { epoll_event events[64]; From 77d45f488d2e8847817a7db8595d4a17762be982 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Mon, 27 Jul 2026 21:03:46 +0200 Subject: [PATCH 09/34] rename tcp_connection to tcp_stream --- CMakeLists.txt | 4 +- README.md | 5 +- examples/cpp/socket/main.cpp | 2 +- examples/cpp/socket_async/main.cpp | 2 +- examples/cpp/socket_server_async/main.cpp | 2 +- include/netkit/netkit.hpp | 6 +- include/netkit/stream/async_socket_stream.hpp | 1 + include/netkit/stream/basic_async_stream.hpp | 63 ++++++++++++ include/netkit/stream/basic_stream.hpp | 73 ++++++++++++++ include/netkit/tcp/async_tcp_connection.hpp | 35 ------- include/netkit/tcp/async_tcp_server.hpp | 4 +- include/netkit/tcp/async_tcp_stream.hpp | 30 ++++++ include/netkit/tcp/tcp_connection.hpp | 39 -------- include/netkit/tcp/tcp_server.hpp | 4 +- include/netkit/tcp/tcp_stream.hpp | 35 +++++++ src/tcp/async_tcp_connection.cpp | 96 ------------------ src/tcp/async_tcp_server.cpp | 6 +- src/tcp/async_tcp_stream.cpp | 31 ++++++ src/tcp/tcp_connection.cpp | 99 ------------------- src/tcp/tcp_server.cpp | 6 +- src/tcp/tcp_stream.cpp | 29 ++++++ 21 files changed, 283 insertions(+), 289 deletions(-) delete mode 100644 include/netkit/tcp/async_tcp_connection.hpp create mode 100644 include/netkit/tcp/async_tcp_stream.hpp delete mode 100644 include/netkit/tcp/tcp_connection.hpp create mode 100644 include/netkit/tcp/tcp_stream.hpp delete mode 100644 src/tcp/async_tcp_connection.cpp create mode 100644 src/tcp/async_tcp_stream.cpp delete mode 100644 src/tcp/tcp_connection.cpp create mode 100644 src/tcp/tcp_stream.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 344d76e..c510376 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,11 +83,11 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/socket/native/native_async_listener.cpp src/socket/native/peer_helper.cpp src/tcp/tcp_server.cpp - src/tcp/tcp_connection.cpp + src/tcp/tcp_stream.cpp src/stream/socket_stream.cpp src/stream/socket_stream.cpp src/stream/async_socket_stream.cpp - src/tcp/async_tcp_connection.cpp + src/tcp/async_tcp_stream.cpp src/tcp/async_tcp_server.cpp src/io/fallback/io_backend.cpp ) diff --git a/README.md b/README.md index ff02a00..767cfc2 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ The other alternative is to use the latest release, which doesn't have a changin Still missing: - Asynchronous I/O for non-Linux platforms -- Schannel support for Windows + - Threaded fallback is available +- Schannel support for Windows (waste of time I reckon) - WebSocket abstraction ## Dependencies @@ -94,4 +95,4 @@ Netkit can also be statically linked, and for users of DevkitPro it will be auto This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. -Copyright (c) 2025-2026 Jacob Nilsson +Copyright (c) 2025-2026 Jacob Nilsson \ No newline at end of file diff --git a/examples/cpp/socket/main.cpp b/examples/cpp/socket/main.cpp index 35a7827..08e4978 100644 --- a/examples/cpp/socket/main.cpp +++ b/examples/cpp/socket/main.cpp @@ -16,7 +16,7 @@ int main() { netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; - netkit::tcp::tcp_connection connector{addr}; + netkit::tcp::tcp_stream connector{addr}; connector.connect(); diff --git a/examples/cpp/socket_async/main.cpp b/examples/cpp/socket_async/main.cpp index 0ca7327..2909897 100644 --- a/examples/cpp/socket_async/main.cpp +++ b/examples/cpp/socket_async/main.cpp @@ -6,7 +6,7 @@ netkit::io::task<> request(netkit::io::io_context& ctx) { netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; - netkit::tcp::async_tcp_connection sock(ctx, addr); + netkit::tcp::async_tcp_stream sock(ctx, addr); co_await sock.connect(); diff --git a/examples/cpp/socket_server_async/main.cpp b/examples/cpp/socket_server_async/main.cpp index 0c8a9bd..366d84f 100644 --- a/examples/cpp/socket_server_async/main.cpp +++ b/examples/cpp/socket_server_async/main.cpp @@ -4,7 +4,7 @@ #include netkit::io::task<> -handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { +handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { std::array buffer{}; std::string response; diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 43014cc..199bdb8 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -83,9 +83,9 @@ #include // tcp -#include -#include -#include #include +#include +#include +#include #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/stream/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp index 7fad8e9..a17a3cb 100644 --- a/include/netkit/stream/async_socket_stream.hpp +++ b/include/netkit/stream/async_socket_stream.hpp @@ -12,6 +12,7 @@ class NETKIT_API async_socket_stream : public basic_async_stream { [[nodiscard]] io::task<> connect() const; [[nodiscard]] io::task read(std::span buffer) override; [[nodiscard]] io::task write(std::span buffer) override; + void close() noexcept override; [[nodiscard]] sock::addr peer() const; diff --git a/include/netkit/stream/basic_async_stream.hpp b/include/netkit/stream/basic_async_stream.hpp index ed58498..46a2829 100644 --- a/include/netkit/stream/basic_async_stream.hpp +++ b/include/netkit/stream/basic_async_stream.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace netkit::stream { @@ -22,6 +23,68 @@ class NETKIT_API basic_async_stream { co_return co_await write(std::span(static_cast(data), size)); } + io::task + write_all(std::span buffer) { + std::size_t total = 0; + + while (total < buffer.size()) { + auto result = co_await write( + buffer.subspan(total) + ); + + if (result.status != stream::stream_status::success) + co_return result; + + total += result.bytes; + } + + co_return stream::stream_result{ + total, + stream::stream_status::success + }; + } + + io::task write_all(std::string_view data) { + co_return co_await this->write_all(std::as_bytes(std::span(data.data(), data.size()))); + } + + io::task> read_all(std::size_t max_bytes = 16 * 1024 * 1024) { + std::vector result; + + std::array buffer{}; + + std::size_t total = 0; + + while (total < max_bytes) { + auto res = co_await read(buffer); + + if (res.status == stream::stream_status::eof) + break; + + if (res.status != stream::stream_status::success) + throw std::runtime_error("read failed"); + + result.insert( + result.end(), + buffer.begin(), + buffer.begin() + res.bytes + ); + + total += res.bytes; + } + + co_return result; + } + + io::task read_all_string() { + auto data = co_await this->read_all(); + + co_return std::string{ + reinterpret_cast(data.data()), + data.size() + }; + } + virtual void close() noexcept = 0; }; diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index a0e6d0d..381de34 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include namespace netkit::stream { @@ -21,6 +24,76 @@ class basic_stream { return this->write(std::span(static_cast(data), size)); } + stream_result write_all(std::span buffer) { + std::size_t total = 0; + + while (total < buffer.size()) { + auto result = this->write(buffer.subspan(total)); + + if (result.status != stream_status::success) + return { + result.bytes + total, + result.status + }; + + total += result.bytes; + } + + return { + total, stream_status::success + }; + } + + stream_result write_all(std::string_view data) { + return write_all( + std::as_bytes( + std::span(data.data(), data.size()) + ) + ); + } + + std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024) { + std::vector result; + + std::size_t total = 0; + + std::array buffer{}; + + while (total < max_bytes) { + auto res = read(buffer); + + if (res.status == stream_status::eof) + break; + + if (res.status != stream_status::success) + throw std::runtime_error("read failed"); + + auto amount = std::min(res.bytes, max_bytes - total); + + result.insert( + result.end(), + buffer.begin(), + buffer.begin() + amount + ); + + total += amount; + + if (amount != res.bytes) + throw std::length_error("read_all exceeded maximum size"); + } + + return result; + } + + std::string read_all_string() { + auto data = this->read_all(); + + return { + reinterpret_cast(data.data()), + data.size() + }; + } + virtual void close() noexcept = 0; }; diff --git a/include/netkit/tcp/async_tcp_connection.hpp b/include/netkit/tcp/async_tcp_connection.hpp deleted file mode 100644 index 84eba5a..0000000 --- a/include/netkit/tcp/async_tcp_connection.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -namespace netkit::tcp { - -class async_tcp_connection { -public: - async_tcp_connection(io::io_context& ctx, const sock::addr& addr) : stream_(std::make_unique(ctx, addr, sock::type::tcp)) {} - async_tcp_connection(std::unique_ptr socket) : stream_(std::move(socket)) {} - ~async_tcp_connection(); - - netkit::io::task<> connect() const; - netkit::io::task read(std::span buffer); - netkit::io::task write(std::span buffer); - netkit::io::task write_all(std::span buffer); - netkit::io::task write_all(std::string_view data); - netkit::io::task> read_all(std::size_t max_bytes = 16 * 1024 * 1024); - netkit::io::task read_all_string(); - - void close() noexcept; - [[nodiscard]] sock::addr peer() const; - - stream::async_socket_stream& stream(); - -private: - stream::async_socket_stream stream_; -}; - -} \ No newline at end of file diff --git a/include/netkit/tcp/async_tcp_server.hpp b/include/netkit/tcp/async_tcp_server.hpp index 32a8d08..c07a27f 100644 --- a/include/netkit/tcp/async_tcp_server.hpp +++ b/include/netkit/tcp/async_tcp_server.hpp @@ -7,7 +7,7 @@ namespace netkit::tcp { -class async_tcp_connection; +class async_tcp_stream; class async_tcp_server { public: @@ -23,7 +23,7 @@ class async_tcp_server { void listen(); void listen(int backlog); - io::task> accept(); + io::task> accept(); void close() noexcept; diff --git a/include/netkit/tcp/async_tcp_stream.hpp b/include/netkit/tcp/async_tcp_stream.hpp new file mode 100644 index 0000000..d9e9c2b --- /dev/null +++ b/include/netkit/tcp/async_tcp_stream.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace netkit::tcp { + +class async_tcp_stream : public stream::basic_async_stream { +public: + async_tcp_stream(io::io_context& ctx, const sock::addr& addr) : stream_(std::make_unique(ctx, addr, sock::type::tcp)) {} + async_tcp_stream(std::unique_ptr socket) : stream_(std::move(socket)) {} + ~async_tcp_stream() override; + + [[nodiscard]] netkit::io::task<> connect() const; + netkit::io::task read(std::span buffer) override; + netkit::io::task write(std::span buffer) override; + + void close() noexcept override; + [[nodiscard]] sock::addr peer() const; + + stream::async_socket_stream& stream(); +private: + stream::async_socket_stream stream_; +}; + +} \ No newline at end of file diff --git a/include/netkit/tcp/tcp_connection.hpp b/include/netkit/tcp/tcp_connection.hpp deleted file mode 100644 index f7e1fe8..0000000 --- a/include/netkit/tcp/tcp_connection.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace netkit::tcp { - -class tcp_connection { -public: - tcp_connection(const sock::addr& addr) : stream_(std::make_unique(addr, sock::type::tcp)) {} - tcp_connection(std::unique_ptr socket) : stream_(std::move(socket)) {} - - ~tcp_connection(); - - void connect(); - - stream::stream_result read(std::span buffer); - stream::stream_result write(std::span buffer); - stream::stream_result write_all(std::span buffer); - stream::stream_result write_all(std::string_view data); - std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024); - std::string read_all_string(); - - void close() noexcept; - - [[nodiscard]] sock::addr peer() const; - - stream::socket_stream& stream(); - -private: - stream::socket_stream stream_; -}; - -} \ No newline at end of file diff --git a/include/netkit/tcp/tcp_server.hpp b/include/netkit/tcp/tcp_server.hpp index 0b9eb0b..a5a3c92 100644 --- a/include/netkit/tcp/tcp_server.hpp +++ b/include/netkit/tcp/tcp_server.hpp @@ -6,7 +6,7 @@ namespace netkit::tcp { -class tcp_connection; +class tcp_stream; class tcp_server { public: @@ -18,7 +18,7 @@ class tcp_server { void listen(); void listen(int backlog); - std::unique_ptr accept(); + std::unique_ptr accept(); void close() noexcept; diff --git a/include/netkit/tcp/tcp_stream.hpp b/include/netkit/tcp/tcp_stream.hpp new file mode 100644 index 0000000..ec58e36 --- /dev/null +++ b/include/netkit/tcp/tcp_stream.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace netkit::tcp { + +class tcp_stream : public stream::basic_stream { +public: + tcp_stream(const sock::addr& addr) : stream_(std::make_unique(addr, sock::type::tcp)) {} + tcp_stream(std::unique_ptr socket) : stream_(std::move(socket)) {} + + ~tcp_stream() override; + + void connect(); + + stream::stream_result read(std::span buffer) override; + stream::stream_result write(std::span buffer) override; + + void close() noexcept override; + + [[nodiscard]] sock::addr peer() const; + + stream::socket_stream& stream(); + +private: + stream::socket_stream stream_; +}; + +} \ No newline at end of file diff --git a/src/tcp/async_tcp_connection.cpp b/src/tcp/async_tcp_connection.cpp deleted file mode 100644 index d3bc5f4..0000000 --- a/src/tcp/async_tcp_connection.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include - -netkit::tcp::async_tcp_connection::~async_tcp_connection() { - this->close(); -} - -netkit::io::task netkit::tcp::async_tcp_connection::connect() const { - co_await stream_.connect(); -} - -netkit::io::task -netkit::tcp::async_tcp_connection::read(std::span buffer) { - co_return co_await stream_.read(buffer); -} - -netkit::io::task -netkit::tcp::async_tcp_connection::write(std::span buffer) { - co_return co_await stream_.write(buffer); -} - -netkit::io::task -netkit::tcp::async_tcp_connection::write_all(std::span buffer) { - std::size_t total = 0; - - while (total < buffer.size()) { - auto result = co_await write( - buffer.subspan(total) - ); - - if (result.status != stream::stream_status::success) - co_return result; - - total += result.bytes; - } - - co_return stream::stream_result{ - total, - stream::stream_status::success - }; -} - -netkit::io::task -netkit::tcp::async_tcp_connection::write_all(std::string_view data) { - co_return co_await this->write_all(std::as_bytes(std::span(data.data(), data.size()))); -} - -netkit::io::task> -netkit::tcp::async_tcp_connection::read_all(std::size_t max_bytes) { - std::vector result; - - std::array buffer{}; - - std::size_t total = 0; - - while (total < max_bytes) { - auto res = co_await read(buffer); - - if (res.status == stream::stream_status::eof) - break; - - if (res.status != stream::stream_status::success) - throw std::runtime_error("read failed"); - - result.insert( - result.end(), - buffer.begin(), - buffer.begin() + res.bytes - ); - - total += res.bytes; - } - - co_return result; -} - -netkit::io::task -netkit::tcp::async_tcp_connection::read_all_string() { - auto data = co_await this->read_all(); - - co_return std::string{ - reinterpret_cast(data.data()), - data.size() - }; -} - -void netkit::tcp::async_tcp_connection::close() noexcept { - stream_.close(); -} - -netkit::sock::addr netkit::tcp::async_tcp_connection::peer() const { - return stream_.peer(); -} - -netkit::stream::async_socket_stream& netkit::tcp::async_tcp_connection::stream() { - return stream_; -} \ No newline at end of file diff --git a/src/tcp/async_tcp_server.cpp b/src/tcp/async_tcp_server.cpp index 69dac46..ee93568 100644 --- a/src/tcp/async_tcp_server.cpp +++ b/src/tcp/async_tcp_server.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include netkit::tcp::async_tcp_server::async_tcp_server(io::io_context& ctx, netkit::sock::addr addr) : addr_(std::move(addr)), listener_(std::make_unique(ctx, addr_, sock::type::tcp)) {} @@ -21,10 +21,10 @@ void netkit::tcp::async_tcp_server::listen() { listener_->listen(-1); } -netkit::io::task> +netkit::io::task> netkit::tcp::async_tcp_server::accept() { auto socket = co_await listener_->accept(); - co_return std::make_unique(std::move(socket)); + co_return std::make_unique(std::move(socket)); } void netkit::tcp::async_tcp_server::close() noexcept { diff --git a/src/tcp/async_tcp_stream.cpp b/src/tcp/async_tcp_stream.cpp new file mode 100644 index 0000000..252712c --- /dev/null +++ b/src/tcp/async_tcp_stream.cpp @@ -0,0 +1,31 @@ +#include + +netkit::tcp::async_tcp_stream::~async_tcp_stream() { + this->async_tcp_stream::close(); +} + +netkit::io::task netkit::tcp::async_tcp_stream::connect() const { + co_await stream_.connect(); +} + +netkit::io::task +netkit::tcp::async_tcp_stream::read(std::span buffer) { + co_return co_await stream_.read(buffer); +} + +netkit::io::task +netkit::tcp::async_tcp_stream::write(std::span buffer) { + co_return co_await stream_.write(buffer); +} + +void netkit::tcp::async_tcp_stream::close() noexcept { + stream_.close(); +} + +netkit::sock::addr netkit::tcp::async_tcp_stream::peer() const { + return stream_.peer(); +} + +netkit::stream::async_socket_stream& netkit::tcp::async_tcp_stream::stream() { + return stream_; +} \ No newline at end of file diff --git a/src/tcp/tcp_connection.cpp b/src/tcp/tcp_connection.cpp deleted file mode 100644 index 332ccf7..0000000 --- a/src/tcp/tcp_connection.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include - -netkit::tcp::tcp_connection::~tcp_connection() { - this->close(); -} - -void netkit::tcp::tcp_connection::connect() { - stream_.connect(); -} - -netkit::stream::stream_result netkit::tcp::tcp_connection::read(std::span buffer) { - return stream_.read(buffer); -} - -netkit::stream::stream_result netkit::tcp::tcp_connection::write(std::span buffer) { - return stream_.write(buffer); -} - -netkit::stream::stream_result netkit::tcp::tcp_connection::write_all(std::span buffer) { - std::size_t total = 0; - - while (total < buffer.size()) { - auto result = this->write(buffer.subspan(total)); - - if (result.status != stream::stream_status::success) - return { - result.bytes + total, - result.status - }; - - total += result.bytes; - } - - return { - total, netkit::stream::stream_status::success - }; -} - -netkit::stream::stream_result netkit::tcp::tcp_connection::write_all(std::string_view data) { - return write_all( - std::as_bytes( - std::span(data.data(), data.size()) - ) - ); -} - -std::vector netkit::tcp::tcp_connection::read_all(std::size_t max_bytes) { - std::vector result; - - std::size_t total = 0; - - std::array buffer{}; - - while (total < max_bytes) { - auto res = read(buffer); - - if (res.status == netkit::stream::stream_status::eof) - break; - - if (res.status != netkit::stream::stream_status::success) - throw std::runtime_error("read failed"); - - auto amount = std::min(res.bytes, max_bytes - total); - - result.insert( - result.end(), - buffer.begin(), - buffer.begin() + amount - ); - - total += amount; - - if (amount != res.bytes) - throw std::length_error("read_all exceeded maximum size"); - } - - return result; -} - -std::string netkit::tcp::tcp_connection::read_all_string() { - auto data = this->read_all(); - - return { - reinterpret_cast(data.data()), - data.size() - }; -} - -void netkit::tcp::tcp_connection::close() noexcept { - stream_.close(); -} - -netkit::sock::addr netkit::tcp::tcp_connection::peer() const { - return stream_.peer(); -} - -netkit::stream::socket_stream& netkit::tcp::tcp_connection::stream() { - return stream_; -} \ No newline at end of file diff --git a/src/tcp/tcp_server.cpp b/src/tcp/tcp_server.cpp index 4bab832..127da02 100644 --- a/src/tcp/tcp_server.cpp +++ b/src/tcp/tcp_server.cpp @@ -1,5 +1,5 @@ #include -#include +#include netkit::tcp::tcp_server::tcp_server(netkit::sock::addr addr) : addr_(std::move(addr)), listener_(std::make_unique(addr_, sock::type::tcp)) {} @@ -20,11 +20,11 @@ void netkit::tcp::tcp_server::listen() { listener_->listen(-1); } -std::unique_ptr +std::unique_ptr netkit::tcp::tcp_server::accept() { auto socket = listener_->accept(); - return std::make_unique( + return std::make_unique( std::move(socket) ); } diff --git a/src/tcp/tcp_stream.cpp b/src/tcp/tcp_stream.cpp new file mode 100644 index 0000000..5ae2b87 --- /dev/null +++ b/src/tcp/tcp_stream.cpp @@ -0,0 +1,29 @@ +#include + +netkit::tcp::tcp_stream::~tcp_stream() { + this->tcp_stream::close(); +} + +void netkit::tcp::tcp_stream::connect() { + stream_.connect(); +} + +netkit::stream::stream_result netkit::tcp::tcp_stream::read(std::span buffer) { + return stream_.read(buffer); +} + +netkit::stream::stream_result netkit::tcp::tcp_stream::write(std::span buffer) { + return stream_.write(buffer); +} + +void netkit::tcp::tcp_stream::close() noexcept { + stream_.close(); +} + +netkit::sock::addr netkit::tcp::tcp_stream::peer() const { + return stream_.peer(); +} + +netkit::stream::socket_stream& netkit::tcp::tcp_stream::stream() { + return stream_; +} \ No newline at end of file From c56b3c4213043551d72788a4c4df86771170c9a5 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Mon, 27 Jul 2026 23:09:39 +0200 Subject: [PATCH 10/34] some windows refactoring --- include/netkit/body/basic_body.hpp | 2 +- include/netkit/http/multipart_reader.hpp | 2 +- include/netkit/http/sync_client.hpp | 2 +- include/netkit/io/fallback/io_backend.hpp | 1 + include/netkit/io/io_awaitable.hpp | 2 +- include/netkit/socket/async_sock.hpp | 2 +- .../native/basic_native_async_listener.hpp | 8 +- .../socket/native/basic_native_async_sock.hpp | 8 +- .../socket/native/native_async_listener.hpp | 7 + .../socket/native/native_async_sock.hpp | 16 +- .../socket/native/native_sync_listener.hpp | 7 + .../netkit/socket/native/native_sync_sock.hpp | 2 - include/netkit/stream/async_socket_stream.hpp | 4 +- include/netkit/stream/basic_async_stream.hpp | 4 +- include/netkit/tcp/async_tcp_stream.hpp | 1 + src/body/stream_body.cpp | 2 + src/socket/addr.cpp | 6 +- src/socket/native/native_async_listener.cpp | 117 ++++++++- src/socket/native/native_async_socket.cpp | 222 +++++++++++++++--- src/socket/native/native_sync_listener.cpp | 67 +++++- src/socket/native/native_sync_socket.cpp | 35 +-- src/socket/native/peer_helper.cpp | 9 +- 22 files changed, 439 insertions(+), 87 deletions(-) diff --git a/include/netkit/body/basic_body.hpp b/include/netkit/body/basic_body.hpp index 3018473..cb6c50c 100644 --- a/include/netkit/body/basic_body.hpp +++ b/include/netkit/body/basic_body.hpp @@ -12,7 +12,7 @@ #include namespace netkit::body { - enum class NETKIT_API read_status { + enum class read_status { ok, eof, error, diff --git a/include/netkit/http/multipart_reader.hpp b/include/netkit/http/multipart_reader.hpp index b6e3d8b..5e81ca3 100644 --- a/include/netkit/http/multipart_reader.hpp +++ b/include/netkit/http/multipart_reader.hpp @@ -17,7 +17,7 @@ #include namespace netkit::http::utility { - enum class NETKIT_API multipart_state { + enum class multipart_state { boundary, headers, data, diff --git a/include/netkit/http/sync_client.hpp b/include/netkit/http/sync_client.hpp index c91e46e..9a93c43 100644 --- a/include/netkit/http/sync_client.hpp +++ b/include/netkit/http/sync_client.hpp @@ -13,7 +13,7 @@ #ifdef NETKIT_HTTP -#include <../socket/native/native_sync_sock.hpp> +#include #include #include diff --git a/include/netkit/io/fallback/io_backend.hpp b/include/netkit/io/fallback/io_backend.hpp index cbec0cd..0421ade 100644 --- a/include/netkit/io/fallback/io_backend.hpp +++ b/include/netkit/io/fallback/io_backend.hpp @@ -1,5 +1,6 @@ #pragma once +// ReSharper disable once CppUnusedIncludeDirective #include #if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index de532d8..07ed36a 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -10,7 +10,7 @@ namespace netkit::io { struct io_awaitable { io_backend& ctx; - sock::fd_t fd; + int fd; io_event ev; [[nodiscard]] bool await_ready() const noexcept { diff --git a/include/netkit/socket/async_sock.hpp b/include/netkit/socket/async_sock.hpp index 6bcae46..0901462 100644 --- a/include/netkit/socket/async_sock.hpp +++ b/include/netkit/socket/async_sock.hpp @@ -3,5 +3,5 @@ #include namespace netkit::sock { - using async_sock = native::native_async_sock; + using async_sock = netkit::sock::native::native_async_sock; } \ No newline at end of file diff --git a/include/netkit/socket/native/basic_native_async_listener.hpp b/include/netkit/socket/native/basic_native_async_listener.hpp index 83b60cc..423efad 100644 --- a/include/netkit/socket/native/basic_native_async_listener.hpp +++ b/include/netkit/socket/native/basic_native_async_listener.hpp @@ -1,9 +1,5 @@ #pragma once -#include - -#ifdef NETKIT_LINUX - #include #include #include @@ -28,6 +24,4 @@ namespace netkit::sock::native { throw std::logic_error{"socket does not have a native handle"}; } }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/include/netkit/socket/native/basic_native_async_sock.hpp b/include/netkit/socket/native/basic_native_async_sock.hpp index c4f7d8d..d62873a 100644 --- a/include/netkit/socket/native/basic_native_async_sock.hpp +++ b/include/netkit/socket/native/basic_native_async_sock.hpp @@ -11,10 +11,6 @@ */ #pragma once -#include - -#ifdef NETKIT_LINUX - #include #include #include @@ -50,6 +46,4 @@ namespace netkit::sock::native { throw std::logic_error{"socket does not have a native handle"}; } }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/include/netkit/socket/native/native_async_listener.hpp b/include/netkit/socket/native/native_async_listener.hpp index 06a66d7..8f69495 100644 --- a/include/netkit/socket/native/native_async_listener.hpp +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -1,6 +1,13 @@ #pragma once +#include + +#ifdef NETKIT_UNIX #include +#elifdef NETKIT_WINDOWS +#include +#endif + #include #include diff --git a/include/netkit/socket/native/native_async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp index 3df7dbf..1b65ff3 100644 --- a/include/netkit/socket/native/native_async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -13,19 +13,25 @@ #include -#ifdef NETKIT_LINUX - #include #include #include #include + +#ifdef NETKIT_UNIX #include +#elifdef NETKIT_WINDOWS +#include +#endif namespace netkit::sock::native { +#ifndef NETKIT_WINDOWS +constexpr fd_t INVALID_SOCKET = -1; +#endif class NETKIT_API native_async_sock : public basic_native_async_sock { addr addr_; type type_{}; - fd_t sockfd{-1}; + fd_t sockfd{INVALID_SOCKET}; sockaddr_storage sa_storage{}; bool bound{false}; @@ -50,6 +56,4 @@ namespace netkit::sock::native { [[nodiscard]] fd_t native_handle() const override; void set_sock_opts(opt opts) override; }; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/include/netkit/socket/native/native_sync_listener.hpp b/include/netkit/socket/native/native_sync_listener.hpp index a72ed45..e0208c1 100644 --- a/include/netkit/socket/native/native_sync_listener.hpp +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -1,6 +1,13 @@ #pragma once +#include + +#ifdef NETKIT_UNIX #include +#elifdef NETKIT_WINDOWS +#include +#endif + #include namespace netkit::sock::native { diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp index c547c24..8998ccf 100644 --- a/include/netkit/socket/native/native_sync_sock.hpp +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -54,7 +54,6 @@ namespace netkit::sock::native { * @param opts The socket options (reuse_addr, no_reuse_addr). */ native_sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::blocking); -#ifdef NETKIT_UNIX /** * @brief Constructs a sync_sock object from an existing file descriptor. * @param existing_fd The existing file descriptor. @@ -63,7 +62,6 @@ namespace netkit::sock::native { * @param opts The socket options (reuse_addr, no_reuse_addr). */ native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::blocking); -#endif ~native_sync_sock() override; sock::addr& get_addr() override; [[nodiscard]] const sock::addr& get_addr() const override; diff --git a/include/netkit/stream/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp index a17a3cb..04c1d62 100644 --- a/include/netkit/stream/async_socket_stream.hpp +++ b/include/netkit/stream/async_socket_stream.hpp @@ -1,7 +1,9 @@ #pragma once -#include +#include #include +#include +#include namespace netkit::stream { diff --git a/include/netkit/stream/basic_async_stream.hpp b/include/netkit/stream/basic_async_stream.hpp index 46a2829..5234a21 100644 --- a/include/netkit/stream/basic_async_stream.hpp +++ b/include/netkit/stream/basic_async_stream.hpp @@ -5,10 +5,12 @@ #include #include +#include +#include namespace netkit::stream { -class NETKIT_API basic_async_stream { +class basic_async_stream { public: virtual ~basic_async_stream() = default; diff --git a/include/netkit/tcp/async_tcp_stream.hpp b/include/netkit/tcp/async_tcp_stream.hpp index d9e9c2b..ec08a04 100644 --- a/include/netkit/tcp/async_tcp_stream.hpp +++ b/include/netkit/tcp/async_tcp_stream.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/src/body/stream_body.cpp b/src/body/stream_body.cpp index 0852fb1..f824355 100644 --- a/src/body/stream_body.cpp +++ b/src/body/stream_body.cpp @@ -1,5 +1,7 @@ #include +#ifndef NOMINMAX #define NOMINMAX // some windows shit +#endif #include netkit::body::read_result diff --git a/src/socket/addr.cpp b/src/socket/addr.cpp index 4e7780d..2b575d3 100644 --- a/src/socket/addr.cpp +++ b/src/socket/addr.cpp @@ -9,7 +9,6 @@ * @note Part of the Netkit library. * @brief Implementation of the sock_addr class. */ -#include #include #include #include @@ -32,6 +31,11 @@ #include #include #include +#include +#endif + +#ifdef NETKIT_WINDOWS +#include #endif /* solely for use internally */ diff --git a/src/socket/native/native_async_listener.cpp b/src/socket/native/native_async_listener.cpp index 33b8f9c..e9ee9e2 100644 --- a/src/socket/native/native_async_listener.cpp +++ b/src/socket/native/native_async_listener.cpp @@ -1,17 +1,26 @@ -#include #include -#include -#include -#include +#include + #include #include #include #include + +#ifdef NETKIT_WINDOWS +#include +#include +#include +#elif NETKIT_UNIX +#include #include #include -#include +#include +#include +#include +#endif void netkit::sock::native::native_async_listener::set_sock_opts(opt opts) const { +#ifdef NETKIT_UNIX if (opts & opt::reuse_addr) { ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); } else if (opts & opt::no_reuse_addr) { @@ -46,6 +55,54 @@ void netkit::sock::native::native_async_listener::set_sock_opts(opt opts) const throw socket_error("failed to set socket to blocking mode"); } } +#elifdef NETKIT_WINDOWS + if (opts & opt::reuse_addr) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set SO_REUSEADDR"); + } + } else if (opts & opt::no_reuse_addr) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to clear SO_REUSEADDR"); + } + } + if ((opts & opt::no_delay) && type_ == type::tcp) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set TCP_NODELAY"); + } + } + if (opts & opt::keep_alive) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set SO_KEEPALIVE"); + } + } else if (opts & opt::no_keep_alive) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to clear SO_KEEPALIVE"); + } + } + if (opts & opt::no_blocking) { + u_long mode = 1; + if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + u_long mode = 0; + if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set socket to blocking mode"); + } + } +#endif } const sockaddr* netkit::sock::native::native_async_listener::get_sa() const { @@ -154,6 +211,7 @@ void netkit::sock::native::native_async_listener::listen() { this->listen(-1); } +#ifdef NETKIT_UNIX netkit::io::task> netkit::sock::native::native_async_listener::accept() { while (true) { @@ -202,6 +260,55 @@ netkit::sock::native::native_async_listener::accept() { ); } } +#elifdef NETKIT_WINDOWS +netkit::io::task> +netkit::sock::native::native_async_listener::accept() { + while (true) { + sockaddr_storage client_addr{}; + int addr_len = sizeof(client_addr); + + SOCKET client_sockfd = ::accept( + this->sockfd_, + reinterpret_cast(&client_addr), + &addr_len + ); + + if (client_sockfd != INVALID_SOCKET) { + + // set non-blocking + u_long mode = 1; + ioctlsocket(client_sockfd, FIONBIO, &mode); + +#ifdef NETKIT_WINDOWS + if (this->type_ == type::uds) { + closesocket(client_sockfd); + throw socket_error("unix domain sockets not supported on win32 platform"); + } +#endif + + auto peer = sock::native::get_peer(client_sockfd); + + co_return std::make_unique( + this->context_, + client_sockfd, + peer, + this->type_ + ); + } + + int err = WSAGetLastError(); + + if (err == WSAEWOULDBLOCK) { + co_await this->context_.wait_readable(this->sockfd_); + continue; + } + + throw socket_error( + "failed to accept connection: " + std::to_string(err) + ); + } +} +#endif void netkit::sock::native::native_async_listener::close() noexcept { if (sockfd_ != INVALID_SOCKET) { diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index cb034af..fac078c 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -14,18 +14,21 @@ #include #include #include +#include +#include #include -#ifdef NETKIT_LINUX - #include +#ifdef NETKIT_UNIX #include -#include #include #include #include -#include +#elifdef NETKIT_WINDOWS +#include +#include +#endif const sockaddr* netkit::sock::native::native_async_sock::get_sa() const { return reinterpret_cast(&sa_storage); @@ -89,40 +92,89 @@ void netkit::sock::native::native_async_sock::prep_sa() { } void netkit::sock::native::native_async_sock::set_sock_opts(opt opts) { +#ifdef NETKIT_UNIX + if (opts & opt::reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & opt::no_reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & opt::no_delay) { + ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & opt::keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & opt::no_keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & opt::no_blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + int flags = fcntl(this->sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(this->sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(this->sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +#elifdef NETKIT_WINDOWS if (opts & opt::reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + BOOL optval = TRUE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set SO_REUSEADDR"); + } } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + BOOL optval = FALSE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to clear SO_REUSEADDR"); + } } - if (opts & opt::no_delay) { - ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + if ((opts & opt::no_delay) && type_ == type::tcp) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set TCP_NODELAY"); + } } if (opts & opt::keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + BOOL optval = TRUE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to set SO_KEEPALIVE"); + } } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + BOOL optval = FALSE; + if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd); + throw socket_error("failed to clear SO_KEEPALIVE"); + } } if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd); + u_long mode = 1; + if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd); throw socket_error("failed to set socket to non-blocking mode"); } } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd); + u_long mode = 0; + if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd); throw socket_error("failed to set socket to blocking mode"); } } +#endif } netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts) @@ -155,7 +207,7 @@ netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_contex this->prep_sa(); } -netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, int existing_fd, const sock::addr& peer, sock::type t, opt opts) +netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) : addr_(peer), type_(t), sockfd(existing_fd), context_(ctx) { if (sockfd < 0) throw socket_error("invalid fd"); if (this->sockfd >= 0) { @@ -184,6 +236,7 @@ const netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() co return this->addr_; } +#ifdef NETKIT_UNIX netkit::io::task netkit::sock::native::native_async_sock::connect() { int ret = ::connect( this->sockfd, @@ -216,7 +269,50 @@ netkit::io::task netkit::sock::native::native_async_sock::connect() { co_return; } +#elifdef NETKIT_WINDOWS +netkit::io::task +netkit::sock::native::native_async_sock::connect() { + int ret = ::connect( + this->sockfd, + this->get_sa(), + this->get_sa_len() + ); + + if (ret == 0) { + co_return; + } + + int err = WSAGetLastError(); + + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) { + throw netkit::socket_error("failed to connect to server"); + } + + co_await this->context_.wait_writable(this->sockfd); + + int error = 0; + int len = sizeof(error); + if (getsockopt( + this->sockfd, + SOL_SOCKET, + SO_ERROR, + reinterpret_cast(&error), + &len + ) == SOCKET_ERROR) { + throw netkit::socket_error("getsockopt(SO_ERROR) failed"); + } + + if (error != 0) { + throw netkit::socket_error( + "failed to connect to server: " + std::to_string(error) + ); + } + + co_return; +} +#endif +#ifdef NETKIT_UNIX netkit::io::task netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { size_t total_sent = 0; const char* data = static_cast(buf); @@ -251,7 +347,43 @@ netkit::io::task netkit::sock::native::native_async_sock::send(cons co_return total_sent; } +#elifdef NETKIT_WINDOWS +netkit::io::task +netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { + size_t total_sent = 0; + const char* data = static_cast(buf); + while (total_sent < len) { + int sent = ::send( + this->sockfd, + data + total_sent, + static_cast(len - total_sent), + 0 + ); + + if (sent > 0) { + total_sent += sent; + continue; + } + + if (sent == 0) { + co_return total_sent; + } + + int err = WSAGetLastError(); + + if (err == WSAEWOULDBLOCK) { + co_await this->context_.wait_writable(this->sockfd); + continue; + } + + throw socket_error("failed to send: " + std::to_string(err)); + } + + co_return total_sent; +} +#endif +#ifdef NETKIT_UNIX netkit::io::task netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { for (;;) { auto n = ::recv(this->sockfd, buf, size, 0); @@ -277,6 +409,42 @@ netkit::io::task netkit::sock::native::native_async_sock::recv(void ); } } +#elifdef NETKIT_WINDOWS +netkit::io::task +netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { + for (;;) { + int n = ::recv( + this->sockfd, + static_cast(buf), + static_cast(size), + 0 + ); + + if (n > 0) { + co_return static_cast(n); + } + + if (n == 0) { + co_return 0; + } + + int err = WSAGetLastError(); + + if (err == WSAEWOULDBLOCK) { + co_await context_.wait_readable(sockfd); + continue; + } + + if (err == WSAEINTR) { + continue; + } + + throw socket_error( + "recv failed: " + std::to_string(err) + ); + } +} +#endif void netkit::sock::native::native_async_sock::close() noexcept { if (this->sockfd == -1) @@ -313,6 +481,4 @@ void netkit::sock::native::native_async_sock::close() noexcept { } netkit::sock::fd_t netkit::sock::native::native_async_sock::native_handle() const { return this->sockfd; -} - -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/socket/native/native_sync_listener.cpp b/src/socket/native/native_sync_listener.cpp index 43c3499..b0e5220 100644 --- a/src/socket/native/native_sync_listener.cpp +++ b/src/socket/native/native_sync_listener.cpp @@ -1,20 +1,25 @@ -#include - -#include #include #include #include -#include -#include #include #include #include -#include +#include + +#ifdef NETKIT_UNIX +#include +#include +#include #include #include +#elifdef NETKIT_WINDOWS +#include +#endif + #include void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { +#ifdef NETKIT_UNIX if (opts & opt::reuse_addr) { ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); } else if (opts & opt::no_reuse_addr) { @@ -49,6 +54,54 @@ void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { throw socket_error("failed to set socket to blocking mode"); } } +#elifdef NETKIT_WINDOWS + if (opts & opt::reuse_addr) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set SO_REUSEADDR"); + } + } else if (opts & opt::no_reuse_addr) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to clear SO_REUSEADDR"); + } + } + if ((opts & opt::no_delay) && type_ == type::tcp) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set TCP_NODELAY"); + } + } + if (opts & opt::keep_alive) { + BOOL optval = TRUE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set SO_KEEPALIVE"); + } + } else if (opts & opt::no_keep_alive) { + BOOL optval = FALSE; + if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to clear SO_KEEPALIVE"); + } + } + if (opts & opt::no_blocking) { + u_long mode = 1; + if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & opt::blocking) { + u_long mode = 0; + if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(this->sockfd_); + throw socket_error("failed to set socket to blocking mode"); + } + } +#endif } const sockaddr* netkit::sock::native::native_sync_listener::get_sa() const { @@ -166,7 +219,7 @@ netkit::sock::native::native_sync_listener::accept() { sockaddr_storage client_addr{}; socklen_t addr_len = sizeof(client_addr); - int client_sockfd = ::accept(this->sockfd_, reinterpret_cast(&client_addr), &addr_len); + auto client_sockfd = ::accept(this->sockfd_, reinterpret_cast(&client_addr), &addr_len); if (client_sockfd < 0) { throw socket_error("failed to accept connection: " + std::string(std::strerror(errno))); } diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index 144b3a7..a7d0338 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -213,7 +213,7 @@ netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, #ifdef NETKIT_DKP this->sockfd = ::socket(AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, IPPROTO_IP); -#else +#elifndef NETKIT_WINDOWS if (t != type::uds) { this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); @@ -234,21 +234,26 @@ netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, this->prep_sa(); } +#endif -netkit::sock::native::native_sync_sock::native_sync_sock(int existing_fd, const sock::addr& peer, sock::type t, opt opts) - : addr_(peer), type_(t), sockfd(existing_fd) { - if (sockfd < 0) throw socket_error("invalid fd"); - if (this->sockfd >= 0) { - this->native_sync_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } +netkit::sock::native::native_sync_sock::native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) + : addr_(peer), type_(t), sockfd(existing_fd) { + if (sockfd < 0) throw socket_error("invalid fd"); +#ifdef NETKIT_WINDOWS + if (this->sockfd != INVALID_SOCKET) { +#else + if (this->sockfd >= 0) { +#endif + this->native_sync_sock::set_sock_opts(opts); + } else { + throw socket_error("cannot set options on invalid socket"); + } - this->prep_sa(); + this->prep_sa(); } -#endif + #ifdef NETKIT_WINDOWS -netkit::sock::native::native_sync_sock::sync_sock(const sock::addr& in_addr, sock::type t, opt opts) +netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& in_addr, sock::type t, opt opts) : addr_(in_addr), type_(t) { if (this->addr_.get_ip().empty() && !this->addr_.is_file_path()) { @@ -272,7 +277,7 @@ netkit::sock::native::native_sync_sock::sync_sock(const sock::addr& in_addr, soc throw socket_error("Failed to create socket"); } - this->sync_sock::set_sock_opts(opts); + this->native_sync_sock::set_sock_opts(opts); this->prep_sa(); } #endif @@ -309,11 +314,11 @@ const netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() con } std::size_t netkit::sock::native::native_sync_sock::send(const void* buf, size_t len) { - return ::send(this->sockfd, buf, len, 0); + return ::send(this->sockfd, static_cast(buf), len, 0); } std::size_t netkit::sock::native::native_sync_sock::recv(void* buf, std::size_t len) { - return ::recv(this->sockfd, buf, len, 0); + return ::recv(this->sockfd, static_cast(buf), len, 0); } #ifdef NETKIT_UNIX diff --git a/src/socket/native/peer_helper.cpp b/src/socket/native/peer_helper.cpp index d02d394..0a12016 100644 --- a/src/socket/native/peer_helper.cpp +++ b/src/socket/native/peer_helper.cpp @@ -1,10 +1,15 @@ +#include +#include #include +#ifdef NETKIT_UNIX #include -#include #include -#include #include +#endif + +#include +#include #include #include From 5962e11824e483575c7fd50fa06614f82052fa5e Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Wed, 29 Jul 2026 02:25:29 +0200 Subject: [PATCH 11/34] example refactoring, remove examples that don't build also refactor windows code and work towards socket implementations without preprocessor cancer --- .idea/vcs.xml | 3 - CMakeLists.txt | 13 + examples/cpp/CMakeLists.txt | 6 + examples/cpp/buffer_body/CMakeLists.txt | 15 +- examples/cpp/dkp_socket_ssl/.gitignore | 1 - examples/cpp/dkp_socket_ssl/CMakeLists.txt | 18 - examples/cpp/dkp_socket_ssl/main.cpp | 78 ----- examples/cpp/file_body/CMakeLists.txt | 15 +- examples/cpp/file_server/.gitignore | 2 - examples/cpp/file_server/CMakeLists.txt | 17 - examples/cpp/file_server/main.cpp | 123 ------- examples/cpp/http_client/.gitignore | 1 - examples/cpp/http_client/CMakeLists.txt | 16 - examples/cpp/http_client/main.cpp | 34 -- examples/cpp/http_server/.gitignore | 1 - examples/cpp/http_server/CMakeLists.txt | 17 - examples/cpp/http_server/main.cpp | 48 --- examples/cpp/http_server_param/.gitignore | 1 - examples/cpp/http_server_param/CMakeLists.txt | 17 - examples/cpp/http_server_param/css/index.css | 16 - .../cpp/http_server_param/files/bliss.png | Bin 109857 -> 0 bytes examples/cpp/http_server_param/index.html | 25 -- examples/cpp/http_server_param/main.cpp | 103 ------ examples/cpp/multipart_parsing/CMakeLists.txt | 15 +- examples/cpp/network_interface/CMakeLists.txt | 15 +- examples/cpp/socket/CMakeLists.txt | 15 +- examples/cpp/socket/main.cpp | 2 +- examples/cpp/socket_async/.gitignore | 3 +- examples/cpp/socket_async/CMakeLists.txt | 15 +- examples/cpp/socket_async/main.cpp | 2 + examples/cpp/socket_async/response.txt | 19 -- examples/cpp/socket_server/CMakeLists.txt | 15 +- .../cpp/socket_server_async/CMakeLists.txt | 15 +- examples/cpp/socket_ssl/.gitignore | 1 - examples/cpp/socket_ssl/CMakeLists.txt | 16 - examples/cpp/socket_ssl/main.cpp | 57 ---- examples/cpp/stream_body/.gitignore | 1 - examples/cpp/stream_body/CMakeLists.txt | 15 - examples/cpp/stream_body/main.cpp | 47 --- examples/cpp/uds_server/.gitignore | 1 - examples/cpp/uds_server/CMakeLists.txt | 15 - examples/cpp/uds_server/main.cpp | 23 -- include/netkit/definitions.hpp | 2 + include/netkit/io/basic_io_backend.hpp | 16 +- include/netkit/io/fallback/io_backend.hpp | 10 +- include/netkit/io/io_awaitable.hpp | 2 +- include/netkit/io/io_backend.hpp | 3 +- include/netkit/io/io_context.hpp | 13 +- include/netkit/io/linux/io_backend.hpp | 12 +- include/netkit/io/windows/io_backend.hpp | 56 ++++ include/netkit/netkit.hpp | 3 + include/netkit/platform/socket.hpp | 276 ++++++++++++++++ include/netkit/tcp/async_tcp_stream.hpp | 2 +- src/io/fallback/io_backend.cpp | 24 +- src/io/windows/io_backend.cpp | 198 +++++++++++ src/socket/native/native_async_listener.cpp | 89 +---- src/socket/native/native_async_socket.cpp | 310 +++--------------- src/socket/native/native_sync_listener.cpp | 86 +---- src/socket/native/native_sync_socket.cpp | 102 +----- src/socket/native/peer_helper.cpp | 24 ++ src/stream/async_socket_stream.cpp | 1 - 61 files changed, 751 insertions(+), 1340 deletions(-) create mode 100644 examples/cpp/CMakeLists.txt delete mode 100644 examples/cpp/dkp_socket_ssl/.gitignore delete mode 100644 examples/cpp/dkp_socket_ssl/CMakeLists.txt delete mode 100644 examples/cpp/dkp_socket_ssl/main.cpp delete mode 100644 examples/cpp/file_server/.gitignore delete mode 100644 examples/cpp/file_server/CMakeLists.txt delete mode 100644 examples/cpp/file_server/main.cpp delete mode 100644 examples/cpp/http_client/.gitignore delete mode 100644 examples/cpp/http_client/CMakeLists.txt delete mode 100644 examples/cpp/http_client/main.cpp delete mode 100644 examples/cpp/http_server/.gitignore delete mode 100644 examples/cpp/http_server/CMakeLists.txt delete mode 100644 examples/cpp/http_server/main.cpp delete mode 100644 examples/cpp/http_server_param/.gitignore delete mode 100644 examples/cpp/http_server_param/CMakeLists.txt delete mode 100644 examples/cpp/http_server_param/css/index.css delete mode 100644 examples/cpp/http_server_param/files/bliss.png delete mode 100644 examples/cpp/http_server_param/index.html delete mode 100644 examples/cpp/http_server_param/main.cpp delete mode 100644 examples/cpp/socket_async/response.txt delete mode 100644 examples/cpp/socket_ssl/.gitignore delete mode 100644 examples/cpp/socket_ssl/CMakeLists.txt delete mode 100644 examples/cpp/socket_ssl/main.cpp delete mode 100644 examples/cpp/stream_body/.gitignore delete mode 100644 examples/cpp/stream_body/CMakeLists.txt delete mode 100644 examples/cpp/stream_body/main.cpp delete mode 100644 examples/cpp/uds_server/.gitignore delete mode 100644 examples/cpp/uds_server/CMakeLists.txt delete mode 100644 examples/cpp/uds_server/main.cpp create mode 100644 include/netkit/io/windows/io_backend.hpp create mode 100644 include/netkit/platform/socket.hpp create mode 100644 src/io/windows/io_backend.cpp diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 4940fff..c8397c9 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,8 +2,5 @@ - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c510376..dc9c07e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,13 @@ option(NETKIT_ENABLE_WINDOWS_CERTSTORE "Enable getting CA certificates from the option(NETKIT_ENABLE_FALLBACK_CA "Enable fallback CA certficate (required for DevkitPro)" ON) option(NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER "Enable using netkit's DNS resolver instead of the system resolver" OFF) option(NETKIT_ENABLE_EPOLL "Enable using epoll backend (Linux only)" ON) +option(NETKIT_ENABLE_WSAPOLL "Enable using wsapoll backend (Windows only)" ON) option(NETKIT_ENABLE_HTTP "Enable netkit's HTTP abstractions" ON) option(NETKIT_ENABLE_DNS "Enable netkit's DNS features" ON) option(NETKIT_WOLFSSL_DEBUG "Enable WolfSSL debugging" OFF) option(NETKIT_BUILD_SHARED "Build netkit as a shared library" ON) option(NETKIT_USE_SYSTEM_WOLFSSL "Use system WolfSSL if found (not recommended)" OFF) +option(NETKIT_BUILD_EXAMPLES "Build netkit's examples" ON) if (CMAKE_SYSTEM_NAME STREQUAL "NintendoWii" OR CMAKE_SYSTEM_NAME STREQUAL "NintendoGameCube") set(NETKIT_DKP ON) @@ -90,8 +92,11 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/tcp/async_tcp_stream.cpp src/tcp/async_tcp_server.cpp src/io/fallback/io_backend.cpp + src/io/windows/io_backend.cpp ) +add_library(netkit::netkit ALIAS netkit) + set_target_properties(netkit PROPERTIES EXPORT_NAME netkit POSITION_INDEPENDENT_CODE ON @@ -232,6 +237,10 @@ if (NETKIT_ENABLE_EPOLL) target_compile_definitions(netkit PUBLIC NETKIT_EPOLL) endif() +if (NETKIT_ENABLE_WSAPOLL) + target_compile_definitions(netkit PUBLIC NETKIT_WSAPOLL) +endif() + if (WIN32) target_link_libraries(netkit PUBLIC ws2_32 iphlpapi dnsapi crypt32) elseif (UNIX AND NOT APPLE) @@ -395,4 +404,8 @@ if (NETKIT_ENABLE_C_BINDINGS) endif() else() message(STATUS "C bindings disabled") +endif() + +if (NETKIT_BUILD_EXAMPLES) + add_subdirectory(examples/cpp) endif() \ No newline at end of file diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt new file mode 100644 index 0000000..7dc2d6e --- /dev/null +++ b/examples/cpp/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB children RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *) +foreach(child ${children}) + if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${child}) + add_subdirectory(${child}) + endif() +endforeach() \ No newline at end of file diff --git a/examples/cpp/buffer_body/CMakeLists.txt b/examples/cpp/buffer_body/CMakeLists.txt index 1948c66..f526bbb 100644 --- a/examples/cpp/buffer_body/CMakeLists.txt +++ b/examples/cpp/buffer_body/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(buffer_body LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(buffer_body main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(buffer_body PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/dkp_socket_ssl/.gitignore b/examples/cpp/dkp_socket_ssl/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/dkp_socket_ssl/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/dkp_socket_ssl/CMakeLists.txt b/examples/cpp/dkp_socket_ssl/CMakeLists.txt deleted file mode 100644 index 59b9732..0000000 --- a/examples/cpp/dkp_socket_ssl/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) - -target_link_libraries(netkit-example PRIVATE netkit::netkit) - -ogc_create_dol(netkit-example) diff --git a/examples/cpp/dkp_socket_ssl/main.cpp b/examples/cpp/dkp_socket_ssl/main.cpp deleted file mode 100644 index bc2f491..0000000 --- a/examples/cpp/dkp_socket_ssl/main.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.cpp - * @license MIT - * @note Example code using the Netkit library. - * @note Only functional if Netkit was built with OpenSSL support. - * @note See examples/socket/main.cpp for a non-SSL/TLS version. - * @brief A lower-level example demonstrating the usage of sync_sock to make a simple HTTP request, with SSL/TLS. - */ -#include -#include -#include -#include -#include -#include - -int main() { - VIDEO_Init(); - WII_Initialize(); - - const auto rmode = VIDEO_GetPreferredMode(nullptr); - const auto xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); - - console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ); - - VIDEO_Configure(rmode); - VIDEO_SetNextFramebuffer(xfb); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - VIDEO_WaitVSync(); - - if (rmode->viTVMode&VI_NON_INTERLACE) { - VIDEO_WaitVSync(); - } - - netkit::sock::addr addr("www.google.com", 443, netkit::sock::addr_type::hostname); - std::unique_ptr _sock = std::make_unique( - addr, netkit::sock::type::tcp); - - netkit::sock::ssl_sync_sock sock((std::move(_sock)), - netkit::sock::mode::client, - netkit::sock::version::TLS_1_2, - netkit::sock::verification::peer - ); - - sock.connect(); - sock.perform_handshake(); - - constexpr std::string_view request = "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"; - std::string response; - - int sent = sock.send(request.data(), request.size()); - - while (true) { - auto res = sock.recv(6); - - response += res.data; - - if (res.status == netkit::sock::recv_status::closed) - break; - - if (res.status == netkit::sock::recv_status::timeout) - break; - - if (res.status == netkit::sock::recv_status::error) - throw std::runtime_error("recv failed"); - } - - std::cout << response << std::flush; - - while (true) {}; - - return EXIT_SUCCESS; -} diff --git a/examples/cpp/file_body/CMakeLists.txt b/examples/cpp/file_body/CMakeLists.txt index 1948c66..1956156 100644 --- a/examples/cpp/file_body/CMakeLists.txt +++ b/examples/cpp/file_body/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(file_body LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(file_body main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(file_body PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/file_server/.gitignore b/examples/cpp/file_server/.gitignore deleted file mode 100644 index c7b0e1b..0000000 --- a/examples/cpp/file_server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*build* -cmake-* \ No newline at end of file diff --git a/examples/cpp/file_server/CMakeLists.txt b/examples/cpp/file_server/CMakeLists.txt deleted file mode 100644 index 881f003..0000000 --- a/examples/cpp/file_server/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -include_directories(/usr/local/include) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/file_server/main.cpp b/examples/cpp/file_server/main.cpp deleted file mode 100644 index 4a9cce9..0000000 --- a/examples/cpp/file_server/main.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.c - * @license MIT - * @note Example code using the Netkit library. - * @brief A simple HTTP file server that lists files in a directory and serves them over HTTP. - */ -#include -#include -#include - -enum class EntryType { - File, - Directory, - Symlink, -}; - -struct Entry { - std::string name{}; - std::string full_path{}; - EntryType type{EntryType::File}; -}; - -std::vector get_entries_in_directory(const std::string& directory) { - std::filesystem::path dir{directory}; - std::vector entries; - if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { - std::cerr << "Directory does not exist or is not a directory: " << directory << std::endl; - return entries; - } - for (const auto& entry : std::filesystem::directory_iterator(dir)) { - Entry e; - e.name = entry.path().filename().string(); - e.full_path = entry.path().string(); - if (std::filesystem::is_regular_file(entry)) { - e.type = EntryType::File; - } else if (std::filesystem::is_directory(entry)) { - e.type = EntryType::Directory; - } else if (std::filesystem::is_symlink(entry)) { - e.type = EntryType::Symlink; - } - entries.push_back(e); - } - return entries; -} - -std::string root_directory{"/"}; -std::string current_directory{"/"}; - -int main() { - netkit::http::server::sync_server server( - netkit::http::server::server_settings{ - .port = 1337, - .enable_session = false, - .session_directory = "./sessions", - .session_cookie_name = "netkit-test", - .trust_x_forwarded_for = false, - }, - [](const netkit::http::server::request& request) -> netkit::http::server::response { - netkit::http::server::response response; - response.http_status = 200; - response.content_type = "text/html"; - - std::string body = ""; - body += "

Directory Listing

"; - body += "
    "; - - if (request.endpoint != "/") { - body += "
  • ..
  • "; - } else { - current_directory = root_directory; - } - - if (request.endpoint == "/$previous") { - // Go up one directory - if (current_directory != root_directory) { - current_directory = current_directory.substr(0, current_directory.find_last_of('/')); - if (current_directory.empty()) { - current_directory = root_directory; - } - } - } else if (request.endpoint != "/" && request.endpoint.at(0) == '/') { - auto fod = root_directory + request.endpoint.substr(1); - fod = netkit::utility::url_decode(fod); - - if (std::filesystem::is_directory(fod)) { - if ((std::filesystem::status(fod).permissions() & - std::filesystem::perms::owner_read) != std::filesystem::perms::none) - { - current_directory = fod; - } - } else { - // serve the file - std::filesystem::path file_path{fod}; - if (std::filesystem::exists(file_path) && std::filesystem::is_regular_file(file_path)) { - response.body = netkit::utility::read_file(file_path.string()); - response.content_type = netkit::utility::get_appropriate_content_type(file_path.filename().string()); - return response; - } else { - response.http_status = 404; - response.body = "

    404 Not Found

    "; - return response; - } - } - } - - auto entries = get_entries_in_directory(current_directory); - for (const auto& entry : entries) { - body += "
  • " + entry.name + "
  • "; - } - - body += "
"; - response.body = body; - - return response; - }); - - server.run(); -} diff --git a/examples/cpp/http_client/.gitignore b/examples/cpp/http_client/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/http_client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/http_client/CMakeLists.txt b/examples/cpp/http_client/CMakeLists.txt deleted file mode 100644 index 1f0ebca..0000000 --- a/examples/cpp/http_client/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) - -target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/http_client/main.cpp b/examples/cpp/http_client/main.cpp deleted file mode 100644 index c8929ad..0000000 --- a/examples/cpp/http_client/main.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.cpp - * @license MIT - * @note Example code using the Netkit library. - * @note If netkit was built with OpenSSL support, HTTPS requests will be made. - * @brief A simple example demonstrating the usage of http::client::sync_client() to make a simple HTTP request. - */ -#include -#include -#include - -void thin_http_abstraction() { - auto http_abstr = netkit::http::client::sync_client("www.google.com", "/", 443, - netkit::http::method::GET, netkit::http::version::HTTP_1_1); - - http_abstr.set_connection("Close"); - http_abstr.set_user_agent("netkit-client/1.0"); - http_abstr.set_header("Accept", "application/json"); - - const auto& ref = http_abstr.get(); - for (const auto& it : ref.headers) { - std::cerr << it.first << ": " << it.second << std::endl; - } - std::cout << ref.body << std::endl; -} - -int main() { - thin_http_abstraction(); -} diff --git a/examples/cpp/http_server/.gitignore b/examples/cpp/http_server/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/http_server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/http_server/CMakeLists.txt b/examples/cpp/http_server/CMakeLists.txt deleted file mode 100644 index 7b35d59..0000000 --- a/examples/cpp/http_server/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE - netkit::netkit -) diff --git a/examples/cpp/http_server/main.cpp b/examples/cpp/http_server/main.cpp deleted file mode 100644 index 52f49e3..0000000 --- a/examples/cpp/http_server/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.c - * @license MIT - * @note Example code using the Netkit library. - * @brief A simple HTTP server that responds with "Hello, World!" to any request. - */ -#include -#include -#include - -int main() { - constexpr int port = 8081; - std::cout << "Starting HTTP server on port " << port << "...\n"; - netkit::http::server::sync_server server( - netkit::http::server::server_settings{ - .port = port, - .enable_session = false, - .session_directory = "./sessions", - .session_cookie_name = "netkit-test", - .trust_x_forwarded_for = false, - }, - [](const netkit::http::server::request& req) -> netkit::http::server::response { - netkit::http::server::response res; - res.http_status = 200; - res.body = "Hello, World!"; - res.content_type = "text/html"; - res.headers.push_back({"X-Test-Header", "TestValue"}); - - std::cout << "Received request from: " << req.ip_address << "\n" - << "Endpoint: " << req.endpoint << "\n" - << "Method: " << req.method << "\n" - << "User-Agent: " << req.user_agent << "\n" - << "Body: " << req.body << "\n"; - return res; - }); - - std::cout << "Server started on port 8080" << ".\n" - << "Press Ctrl+C to stop the server.\n"; - - server.run(); - - return 0; -} diff --git a/examples/cpp/http_server_param/.gitignore b/examples/cpp/http_server_param/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/http_server_param/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/http_server_param/CMakeLists.txt b/examples/cpp/http_server_param/CMakeLists.txt deleted file mode 100644 index 3b560c5..0000000 --- a/examples/cpp/http_server_param/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE - netkit::netkit -) diff --git a/examples/cpp/http_server_param/css/index.css b/examples/cpp/http_server_param/css/index.css deleted file mode 100644 index bafcf0b..0000000 --- a/examples/cpp/http_server_param/css/index.css +++ /dev/null @@ -1,16 +0,0 @@ -* { - font-family: Arial, sans-serif; - margin: 0; - padding: 0; - text-align: center; - box-sizing: border-box; -} - -button { - padding: 50px; -} - -h1 { - color: #333; - margin-top: 20px; -} \ No newline at end of file diff --git a/examples/cpp/http_server_param/files/bliss.png b/examples/cpp/http_server_param/files/bliss.png deleted file mode 100644 index 22ceab5a9011d39a46d1df9e08ee7037c1c9d8be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109857 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 - - - - - HTML test - - - - - Bliss -

HTML test

-

This is a simple HTML file to test the HTTP server.

- - \ No newline at end of file diff --git a/examples/cpp/http_server_param/main.cpp b/examples/cpp/http_server_param/main.cpp deleted file mode 100644 index efc8708..0000000 --- a/examples/cpp/http_server_param/main.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.c - * @license MIT - * @note Example code using the Netkit library. - * @brief Slightly more advanced/useful HTTP file server that serves files from a specified index file and directory. - */ -#include -#include -#include - -struct server_settings { - int port{8080}; - std::string index_file{"./index.html"}; -}; - -int main(int argc, char** argv) { - server_settings settings{}; - std::vector args(argv, argv + argc); - for (int i{0}; i < args.size(); ++i) { - if (args[i] == "--port" && i + 1 < args.size()) { - settings.port = std::stoi(args[i + 1]); - ++i; - } else if (args[i] == "--index-file" && i + 1 < args.size()) { - settings.index_file = args[i + 1]; - ++i; - } else if (args[i] == "--help" || args[i] == "-h") { - std::cout << "Usage: " << args[0] << " [--port ] [--index-file ] [--help|-h]\n" - << " --port Specify the port to run the HTTP server on (default: 80)\n" - << " --index-file Specify the path to the index HTML file (default: ./index.html)\n" - << " --help, -h Show this help message\n"; - return 0; - } - } - - std::cout << "Starting HTTP server on port " << settings.port << "...\n"; - netkit::http::server::sync_server server( - netkit::http::server::server_settings{ - .port = settings.port, - .enable_session = false, - .session_directory = "./sessions", - .session_cookie_name = "netkit-server", - .trust_x_forwarded_for = false, - }, - [&settings](const netkit::http::server::request& req) -> netkit::http::server::response { - netkit::http::server::response res; - std::string parent_path = std::filesystem::path(settings.index_file).parent_path().string(); - if (!std::filesystem::is_directory(parent_path)) { - parent_path = "."; - } - - std::string body_{}; - if (req.body) { - body_ = req.body->read_all(); - } - - if (req.endpoint.find("..") != std::string::npos) { - res.http_status = 403; - res.body = std::make_unique("

403 Forbidden

"); - res.content_type = "text/html"; - res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); - return res; - } - - if ((req.endpoint == "/" || req.endpoint.empty()) && std::filesystem::is_regular_file(settings.index_file)) { - res.http_status = 200; - //res.body = netkit::utility::read_file(settings.index_file); - res.body = std::make_unique(settings.index_file); - res.content_type = netkit::utility::get_appropriate_content_type(settings.index_file); - res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); - } else if (std::filesystem::is_regular_file(std::filesystem::path(parent_path) / req.endpoint.substr(1))) { - std::string file_path = std::filesystem::path(parent_path) / req.endpoint.substr(1); - res.http_status = 200; - //res.body = netkit::utility::read_file(file_path); - res.body = std::make_unique(file_path); - res.content_type = netkit::utility::get_appropriate_content_type(file_path); - res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); - res.headers.push_back({"Content-Disposition", "inline"}); - } else { - res.http_status = 404; - res.body = std::make_unique("

404 Not Found

"); - res.content_type = "text/html"; - res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); - } - - std::cout << "Received request from: " << req.ip_address << "\n" - << "Endpoint: " << req.endpoint << "\n" - << "Method: " << req.method << "\n" - << "User-Agent: " << req.user_agent << "\n" - << "Body: " << body_ << "\n"; - - return res; - }); - - std::cout << "Server started on port " << settings.port << ".\n" - << "Press Ctrl+C to stop the server.\n"; - - server.run(); -} diff --git a/examples/cpp/multipart_parsing/CMakeLists.txt b/examples/cpp/multipart_parsing/CMakeLists.txt index 1948c66..d7d0e6a 100644 --- a/examples/cpp/multipart_parsing/CMakeLists.txt +++ b/examples/cpp/multipart_parsing/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(multipart_parsing LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(multipart_parsing main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(multipart_parsing PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/network_interface/CMakeLists.txt b/examples/cpp/network_interface/CMakeLists.txt index 001a9f4..356c9a1 100644 --- a/examples/cpp/network_interface/CMakeLists.txt +++ b/examples/cpp/network_interface/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(network_interface LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(network_interface main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(network_interface PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket/CMakeLists.txt b/examples/cpp/socket/CMakeLists.txt index 001a9f4..86c1cc8 100644 --- a/examples/cpp/socket/CMakeLists.txt +++ b/examples/cpp/socket/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(socket LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(socket PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket/main.cpp b/examples/cpp/socket/main.cpp index 08e4978..2f4ac87 100644 --- a/examples/cpp/socket/main.cpp +++ b/examples/cpp/socket/main.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include int main() { netkit::sock::addr addr{"google.com", 80, netkit::sock::addr_type::hostname}; diff --git a/examples/cpp/socket_async/.gitignore b/examples/cpp/socket_async/.gitignore index 3c34c2f..8401570 100644 --- a/examples/cpp/socket_async/.gitignore +++ b/examples/cpp/socket_async/.gitignore @@ -1 +1,2 @@ -*build* \ No newline at end of file +*build* +response.txt \ No newline at end of file diff --git a/examples/cpp/socket_async/CMakeLists.txt b/examples/cpp/socket_async/CMakeLists.txt index 1948c66..58fb60d 100644 --- a/examples/cpp/socket_async/CMakeLists.txt +++ b/examples/cpp/socket_async/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(socket_async LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_async main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(socket_async PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_async/main.cpp b/examples/cpp/socket_async/main.cpp index 2909897..aa1c117 100644 --- a/examples/cpp/socket_async/main.cpp +++ b/examples/cpp/socket_async/main.cpp @@ -57,4 +57,6 @@ int main() { ctx.spawn(request(ctx)); ctx.run_until_idle(); + + return 0; } diff --git a/examples/cpp/socket_async/response.txt b/examples/cpp/socket_async/response.txt deleted file mode 100644 index 27cdf09..0000000 --- a/examples/cpp/socket_async/response.txt +++ /dev/null @@ -1,19 +0,0 @@ -HTTP/1.1 301 Moved Permanently -Location: http://www.google.com/ -Content-Type: text/html; charset=UTF-8 -Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-_Z6KSerqCBOWipbdNOKQwA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp -Date: Mon, 27 Jul 2026 13:20:49 GMT -Expires: Wed, 26 Aug 2026 13:20:49 GMT -Cache-Control: public, max-age=2592000 -Server: gws -Content-Length: 219 -X-XSS-Protection: 0 -X-Frame-Options: SAMEORIGIN -Connection: close - - -301 Moved -

301 Moved

-The document has moved -
here. - diff --git a/examples/cpp/socket_server/CMakeLists.txt b/examples/cpp/socket_server/CMakeLists.txt index 1948c66..3b54d24 100644 --- a/examples/cpp/socket_server/CMakeLists.txt +++ b/examples/cpp/socket_server/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(socket_server LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_server main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(socket_server PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_server_async/CMakeLists.txt b/examples/cpp/socket_server_async/CMakeLists.txt index 1948c66..6ba0e8d 100644 --- a/examples/cpp/socket_server_async/CMakeLists.txt +++ b/examples/cpp/socket_server_async/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.11) -project(netkit-example LANGUAGES CXX) +project(socket_server_async LANGUAGES CXX) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(netkit) +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_server_async main.cpp) -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) +target_link_libraries(socket_server_async PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_ssl/.gitignore b/examples/cpp/socket_ssl/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/socket_ssl/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/socket_ssl/CMakeLists.txt b/examples/cpp/socket_ssl/CMakeLists.txt deleted file mode 100644 index 1f0ebca..0000000 --- a/examples/cpp/socket_ssl/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) - -target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/socket_ssl/main.cpp b/examples/cpp/socket_ssl/main.cpp deleted file mode 100644 index 5c13ee1..0000000 --- a/examples/cpp/socket_ssl/main.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.cpp - * @license MIT - * @note Example code using the Netkit library. - * @note Only functional if Netkit was built with OpenSSL support. - * @note See examples/socket/main.cpp for a non-SSL/TLS version. - * @brief A lower-level example demonstrating the usage of sync_sock to make a simple HTTP request, with SSL/TLS. - */ -#include -#include -#include -#include - -int main() { - netkit::sock::addr addr("google.com", 443, netkit::sock::addr_type::hostname); - std::unique_ptr _sock = std::make_unique( - addr, netkit::sock::type::tcp); - - netkit::sock::ssl_sync_sock sock((std::move(_sock)), - netkit::sock::mode::client, - netkit::sock::version::TLS_1_2, - netkit::sock::verification::peer - ); - - sock.connect(); - sock.perform_handshake(); - - constexpr std::string_view request = "GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"; - std::string response; - - int sent = sock.send(request.data(), request.size()); - - while (true) { - auto res = sock.recv(6); - - response += res.data; - - if (res.status == netkit::sock::recv_status::closed) - break; - - if (res.status == netkit::sock::recv_status::timeout) - break; - - if (res.status == netkit::sock::recv_status::error) - std::cout << "error" << "\n"; - continue; - } - - std::cout << response << std::flush; - - return EXIT_SUCCESS; -} diff --git a/examples/cpp/stream_body/.gitignore b/examples/cpp/stream_body/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/stream_body/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/stream_body/CMakeLists.txt b/examples/cpp/stream_body/CMakeLists.txt deleted file mode 100644 index 1948c66..0000000 --- a/examples/cpp/stream_body/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/stream_body/main.cpp b/examples/cpp/stream_body/main.cpp deleted file mode 100644 index 2507d17..0000000 --- a/examples/cpp/stream_body/main.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file main.c - * @license MIT - * @note Example code using the Netkit library. - * @note See examples/socket_ssl for a TLS/SSL version of this example. - * @brief A simple example demonstrating stream_body - */ -#include -#include -#include -#include -#include -#include -#include - -int main() { - netkit::sock::addr addr("www.google.com", 80, netkit::sock::addr_type::hostname); - netkit::sock::sync_sock sock(addr, netkit::sock::type::tcp); - - sock.connect(); - - constexpr std::string_view request = "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n"; - - sock.send(request.data()); - - netkit::body::stream_body body(sock, std::nullopt); - - while (true) { - char buf[4] = {0}; - - auto result = body.read(buf, 3); - - if (result.get_status() == netkit::body::read_status::eof) - break; - - buf[result.get_bytes_read()] = '\0'; - - std::cout << buf << "\n"; - } - - return 0; -} diff --git a/examples/cpp/uds_server/.gitignore b/examples/cpp/uds_server/.gitignore deleted file mode 100644 index 3c34c2f..0000000 --- a/examples/cpp/uds_server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*build* \ No newline at end of file diff --git a/examples/cpp/uds_server/CMakeLists.txt b/examples/cpp/uds_server/CMakeLists.txt deleted file mode 100644 index 001a9f4..0000000 --- a/examples/cpp/uds_server/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.11) - -project(netkit-example LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -find_package(netkit) - -add_executable( - netkit-example - main.cpp -) -target_link_libraries(netkit-example PRIVATE netkit::netkit) diff --git a/examples/cpp/uds_server/main.cpp b/examples/cpp/uds_server/main.cpp deleted file mode 100644 index b00fa1d..0000000 --- a/examples/cpp/uds_server/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include -#include -#include - -int main() { - std::filesystem::remove("/tmp/test.sock"); - - netkit::sock::addr addr("/tmp/test.sock"); - netkit::sock::sync_sock sock(addr, netkit::sock::type::uds); - - sock.bind(); - sock.listen(); - - while (true) { - auto rec = sock.accept(); - auto buffer = rec->recv().data; - std::cout << buffer << "\n"; - } - - return 0; -} diff --git a/include/netkit/definitions.hpp b/include/netkit/definitions.hpp index d0c0840..7238ea2 100644 --- a/include/netkit/definitions.hpp +++ b/include/netkit/definitions.hpp @@ -11,6 +11,8 @@ */ #pragma once +#include + #if defined(__APPLE__) #define NETKIT_MACOS 1 #endif diff --git a/include/netkit/io/basic_io_backend.hpp b/include/netkit/io/basic_io_backend.hpp index dd45095..9d2c60e 100644 --- a/include/netkit/io/basic_io_backend.hpp +++ b/include/netkit/io/basic_io_backend.hpp @@ -1,8 +1,14 @@ #pragma once +#include + #include #include +#ifdef NETKIT_WINDOWS +#include +#endif + namespace netkit::io { struct io_handle_state { @@ -12,12 +18,18 @@ struct io_handle_state { enum class io_event { read, write }; +#ifdef NETKIT_WINDOWS +typedef SOCKET io_handle_t; +#else +typedef int io_handle_t; +#endif + class basic_io_backend { public: virtual ~basic_io_backend() = default; virtual void wake() = 0; - virtual void update_state(int fd, const io_handle_state& state) = 0; - virtual void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) = 0; + virtual void update_state(io_handle_t fd, const io_handle_state& state) = 0; + virtual void register_waiter(io_handle_t fd, io_event ev, std::coroutine_handle<> h) = 0; virtual void run() = 0; virtual void stop() = 0; virtual void poll(int timeout_ms) = 0; diff --git a/include/netkit/io/fallback/io_backend.hpp b/include/netkit/io/fallback/io_backend.hpp index 0421ade..56cb1e5 100644 --- a/include/netkit/io/fallback/io_backend.hpp +++ b/include/netkit/io/fallback/io_backend.hpp @@ -4,6 +4,7 @@ #include #if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) +#if !defined(NETKIT_WINDOWS) || !defined(NETKIT_WSAPOLL) #include #include @@ -14,16 +15,16 @@ namespace netkit::io { -class io_backend : public basic_io_backend { +class NETKIT_API io_backend : public basic_io_backend { public: io_backend(std::size_t threads = 4); ~io_backend() override; void wake() override; - void update_state(int, const io_handle_state&) override {} + void update_state(io_handle_t, const io_handle_state&) override {} - void register_waiter(int fd, io_event event, std::coroutine_handle<> h) override; + void register_waiter(io_handle_t fd, io_event event, std::coroutine_handle<> h) override; void run() override; void stop() override; @@ -33,7 +34,7 @@ class io_backend : public basic_io_backend { private: struct waiter { - int fd{}; + io_handle_t fd{}; io_event event{}; std::coroutine_handle<> handle; }; @@ -52,4 +53,5 @@ class io_backend : public basic_io_backend { } // namespace netkit::io +#endif #endif \ No newline at end of file diff --git a/include/netkit/io/io_awaitable.hpp b/include/netkit/io/io_awaitable.hpp index 07ed36a..290196d 100644 --- a/include/netkit/io/io_awaitable.hpp +++ b/include/netkit/io/io_awaitable.hpp @@ -10,7 +10,7 @@ namespace netkit::io { struct io_awaitable { io_backend& ctx; - int fd; + io_handle_t fd; io_event ev; [[nodiscard]] bool await_ready() const noexcept { diff --git a/include/netkit/io/io_backend.hpp b/include/netkit/io/io_backend.hpp index 54025de..e91ec49 100644 --- a/include/netkit/io/io_backend.hpp +++ b/include/netkit/io/io_backend.hpp @@ -1,4 +1,5 @@ #pragma once #include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/include/netkit/io/io_context.hpp b/include/netkit/io/io_context.hpp index d8dc53d..2bea742 100644 --- a/include/netkit/io/io_context.hpp +++ b/include/netkit/io/io_context.hpp @@ -1,20 +1,19 @@ #pragma once #include -#include -#include +#include #include #include namespace netkit::io { -class io_context { +class NETKIT_API io_context { public: void run() { running_ = true; while (running_) { - backend_.poll(-1); + backend_.poll(!tasks_.empty() ? 0 : -1); cleanup_tasks(); } @@ -24,7 +23,7 @@ class io_context { running_ = true; while (running_ && !tasks_.empty()) { - backend_.poll(-1); + backend_.poll(!tasks_.empty() ? 0 : -1); cleanup_tasks(); } @@ -35,11 +34,11 @@ class io_context { backend_.wake(); } - io_awaitable wait_readable(int fd) { + io_awaitable wait_readable(io_handle_t fd) { return io_awaitable{backend_, fd, io_event::read}; } - io_awaitable wait_writable(int fd) { + io_awaitable wait_writable(io_handle_t fd) { return io_awaitable{backend_, fd, io_event::write}; } diff --git a/include/netkit/io/linux/io_backend.hpp b/include/netkit/io/linux/io_backend.hpp index 0f0b3dd..a595a73 100644 --- a/include/netkit/io/linux/io_backend.hpp +++ b/include/netkit/io/linux/io_backend.hpp @@ -18,19 +18,19 @@ class io_backend : public basic_io_backend { ~io_backend() override; void wake() override; - void update_state(int fd, const io_handle_state& state) override; - void register_waiter(int fd, io_event ev, std::coroutine_handle<> h) override; + void update_state(io_handle_t fd, const io_handle_state& state) override; + void register_waiter(io_handle_t fd, io_event ev, std::coroutine_handle<> h) override; void run() override; void stop() override; void poll(int timeout_ms) override; void poll() override; private: - int epoll_fd_; + io_handle_t epoll_fd_; bool running_ = true; - int wake_fd_ = -1; + io_handle_t wake_fd_ = -1; - std::unordered_map fd_map_; - std::unordered_set registered_fds_; + std::unordered_map fd_map_; + std::unordered_set registered_fds_; }; } // namespace netkit::io diff --git a/include/netkit/io/windows/io_backend.hpp b/include/netkit/io/windows/io_backend.hpp new file mode 100644 index 0000000..b8cce26 --- /dev/null +++ b/include/netkit/io/windows/io_backend.hpp @@ -0,0 +1,56 @@ +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include + +#if defined(NETKIT_WINDOWS) && defined(NETKIT_EPOLL) + +#include +#include +#include +#include +#include +#include + +namespace netkit::io { + +class NETKIT_API io_backend : public basic_io_backend { +public: + io_backend(); + ~io_backend() override; + + void wake() override; + + void update_state(io_handle_t, const io_handle_state&) override {} + + void register_waiter( + io_handle_t fd, + io_event event, + std::coroutine_handle<> h + ) override; + + void run() override; + void stop() override; + + void poll(int timeout_ms) override; + void poll() override; + +private: + struct waiter { + io_handle_t fd{}; + io_event event{}; + std::coroutine_handle<> handle; + }; + + SOCKET wake_read_{INVALID_SOCKET}; + SOCKET wake_write_{INVALID_SOCKET}; + + std::atomic_bool running_{true}; + + std::mutex mutex_; + std::vector waiters_; +}; + +} // namespace netkit::io + +#endif \ No newline at end of file diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 199bdb8..0a75acf 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -32,6 +32,9 @@ #include #include +// Platform headers +#include + // Socket headers #include #include diff --git a/include/netkit/platform/socket.hpp b/include/netkit/platform/socket.hpp new file mode 100644 index 0000000..d7a562b --- /dev/null +++ b/include/netkit/platform/socket.hpp @@ -0,0 +1,276 @@ +/* There would normally be a useful file header here, but I am going to use the header + * to complain, instead: + * + * This file only has to exist because Microsoft Windows is the most retarded operating system + * that has ever existed. In (somewhat) recent years, they've implemented tons of features that were essentially + * backports from the Unix socket implementation, such as support for + * UDS sockets (in typical Microsoft fashion, it's half-assed.) + * + * Instead of doing this properly, i.e. copying it in such a way that code written for Unix systems + * just works, in true Microsoft fashion, they ALWAYS have to fuck it up their own shitty Windows-isms, + * like defining a special unsigned SOCKET type. Oh, we're so special, we have to do it in our own shitty + * fucking way, so that consumers writing cross-platform code have to pollute their code with #ifdef cancer. + * + * On Unix systems, we have the concept of a file descriptor, which we store using a signed integer. + * This makes checking the validity of a file descriptor trivial; just check whether it is greater than or equal to 0. + * + * On Windows however, we don't have file descriptors, but we still have socket identifiers, and instead of being + * signed, they're unsigned, so we have to check if it's equal to INVALID_SOCKET which, further, means that + * in order to support both operating systems, we have to have a bunch of shitty helper functions. + * + * That's what this header is for -- abstracting away a few of Microsoft's many horrible design choices. + * Frankly, having to support Windows is a nuisance more than anything. + * + * Linux/*BSD/macOS users, count your blessings; at least you don't have to deal with this utter piece of shit. + */ +#pragma once + +#include + +#include +#include +#include + +#ifdef NETKIT_WINDOWS +#include +#else +#include +#endif + +namespace netkit::platform { + +#ifdef NETKIT_WINDOWS +typedef SOCKET socket_t; +typedef int socket_result; +typedef int socket_length_t; + +inline constexpr socket_t invalid_socket = INVALID_SOCKET; +#else +typedef int socket_t; +typedef ssize_t socket_result; +typedef socklen_t socket_length_t; + +inline constexpr socket_t invalid_socket = -1; +#endif + +enum class socket_err { + none, + would_block, + in_progress, + interrupted, + connection_refused, + timed_out, + not_connected, + unknown +}; + +inline bool valid_socket(socket_t s) noexcept { +#ifdef NETKIT_WINDOWS + return s != invalid_socket; +#else + return s >= 0; +#endif +} + +inline void close_socket(socket_t s) noexcept { +#ifdef NETKIT_WINDOWS + closesocket(s); +#else + close(s); +#endif +} + +inline void set_sock_opts(socket_t sockfd, sock::opt opts) { +#ifdef NETKIT_UNIX + if (opts & netkit::sock::opt::reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); + } else if (opts & netkit::sock::opt::no_reuse_addr) { + ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); + } + if (opts & netkit::sock::opt::no_delay) { + ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); + } + if (opts & netkit::sock::opt::keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); + } else if (opts & netkit::sock::opt::no_keep_alive) { + ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); + } + if (opts & netkit::sock::opt::no_blocking) { + int flags = fcntl(sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { + ::close(sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & netkit::sock::opt::blocking) { + int flags = fcntl(sockfd, F_GETFL, 0); + if (flags < 0) { + ::close(sockfd); + throw socket_error("failed to get socket flags"); + } + if (fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { + ::close(sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +#elifdef NETKIT_WINDOWS + if (opts & netkit::sock::opt::reuse_addr) { + BOOL optval = TRUE; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to set SO_REUSEADDR"); + } + } else if (opts & netkit::sock::opt::no_reuse_addr) { + BOOL optval = FALSE; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to clear SO_REUSEADDR"); + } + } + if ((opts & netkit::sock::opt::no_delay)) { + BOOL optval = TRUE; + if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to set TCP_NODELAY"); + } + } + if (opts & netkit::sock::opt::keep_alive) { + BOOL optval = TRUE; + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to set SO_KEEPALIVE"); + } + } else if (opts & netkit::sock::opt::no_keep_alive) { + BOOL optval = FALSE; + if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to clear SO_KEEPALIVE"); + } + } + if (opts & netkit::sock::opt::no_blocking) { + u_long mode = 1; + if (ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to set socket to non-blocking mode"); + } + } else if (opts & netkit::sock::opt::blocking) { + u_long mode = 0; + if (ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(sockfd); + throw socket_error("failed to set socket to blocking mode"); + } + } +#endif +} + +inline socket_err last_socket_error() { +#ifdef NETKIT_WINDOWS + + switch (WSAGetLastError()) { + case 0: return socket_err::none; + case WSAEWOULDBLOCK: return socket_err::would_block; + case WSAEINPROGRESS: return socket_err::in_progress; + case WSAEINTR: return socket_err::interrupted; + case WSAECONNREFUSED: return socket_err::connection_refused; + case WSAETIMEDOUT: return socket_err::timed_out; + case WSAENOTCONN: return socket_err::not_connected; + default: return socket_err::unknown; + } + +#else + switch (errno) { + case 0: return socket_err::none; + case EWOULDBLOCK: + case EAGAIN: return socket_err::would_block; + case EINPROGRESS: return socket_err::in_progress; + case EINTR: return socket_err::interrupted; + case ECONNREFUSED: return socket_err::connection_refused; + case ETIMEDOUT: return socket_err::timed_out; + case ENOTCONN: return socket_err::not_connected; + default: return socket_err::unknown; + } +#endif +} + +#ifdef NETKIT_WINDOWS +inline std::string last_error_message() { + int err = WSAGetLastError(); + char* msg = nullptr; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + err, + 0, + (LPSTR)&msg, + 0, + nullptr + ); + + std::string result = msg ? msg : "unknown"; + if (msg) LocalFree(msg); + return result; +} +#else +inline std::string last_error_message() { + return std::strerror(errno); +} +#endif + +inline socket_t socket(int domain, int type, int protocol) { +#ifdef NETKIT_WINDOWS + if (domain == AF_UNIX) { + throw socket_error{"unix domain sockets are not supported in win32 platform"}; + } +#endif + return ::socket(domain, type, protocol); +} + +inline socket_result send(socket_t sock,const void* buffer,size_t length,int flags) { +#ifdef NETKIT_WINDOWS + if (length > INT_MAX) + length = INT_MAX; +#endif + + return ::send( + sock, +#ifdef NETKIT_WINDOWS + static_cast(buffer), + static_cast(length), +#else + buffer, + length, +#endif + flags + ); +} + +inline socket_result recv(socket_t sock, void* buffer, std::size_t length, int flags = 0) { +#ifdef NETKIT_WINDOWS + return ::recv(sock, static_cast(buffer), static_cast(length), flags); +#else + return ::recv(sock, buffer, length, flags); +#endif +} + +// this is identical, but let's stick with the theme, shall we? +inline socket_t accept(socket_t sock, sockaddr* addr, socket_length_t* length) { + return ::accept(sock, addr, length); +} + +inline int connect(socket_t sock, const sockaddr* addr, socket_length_t length) { + return ::connect(sock, addr, length); +} + +inline int bind(socket_t sock, const sockaddr* addr, socket_length_t length) { + return ::bind(sock, addr, length); +} + +inline int listen(socket_t sock, int backlog) { + return ::listen(sock, backlog); +} + +} diff --git a/include/netkit/tcp/async_tcp_stream.hpp b/include/netkit/tcp/async_tcp_stream.hpp index ec08a04..87325a7 100644 --- a/include/netkit/tcp/async_tcp_stream.hpp +++ b/include/netkit/tcp/async_tcp_stream.hpp @@ -10,7 +10,7 @@ namespace netkit::tcp { -class async_tcp_stream : public stream::basic_async_stream { +class NETKIT_API async_tcp_stream : public stream::basic_async_stream { public: async_tcp_stream(io::io_context& ctx, const sock::addr& addr) : stream_(std::make_unique(ctx, addr, sock::type::tcp)) {} async_tcp_stream(std::unique_ptr socket) : stream_(std::move(socket)) {} diff --git a/src/io/fallback/io_backend.cpp b/src/io/fallback/io_backend.cpp index 5fcdf11..b91b618 100644 --- a/src/io/fallback/io_backend.cpp +++ b/src/io/fallback/io_backend.cpp @@ -1,6 +1,9 @@ +#include +#include #include #if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) +#if !defined(NETKIT_WINDOWS) || !defined(NETKIT_WSAPOLL) void netkit::io::io_backend::worker() { while (running_) { @@ -48,7 +51,7 @@ void netkit::io::io_backend::wake() { cv_.notify_all(); } -void netkit::io::io_backend::register_waiter(int fd, io_event event, std::coroutine_handle<> handle) { +void netkit::io::io_backend::register_waiter(io_handle_t fd, io_event event, std::coroutine_handle<> handle) { std::lock_guard lock(mutex_); waiters_.push_back({ @@ -73,24 +76,20 @@ void netkit::io::io_backend::stop() { } void netkit::io::io_backend::poll(int timeout_ms) { - std::unique_lock lock(mutex_); + std::vector pending; + + { + std::unique_lock lock(mutex_); - if (waiters_.empty()) { - if (timeout_ms < 0) { - cv_.wait(lock); - } else { + if (waiters_.empty()) { cv_.wait_for( lock, std::chrono::milliseconds(timeout_ms) ); } - } - - std::vector pending; - pending.swap(waiters_); - - lock.unlock(); + pending.swap(waiters_); + } for (auto& waiter : pending) { if (waiter.handle) @@ -103,4 +102,5 @@ void netkit::io::io_backend::poll() { poll(-1); } +#endif #endif \ No newline at end of file diff --git a/src/io/windows/io_backend.cpp b/src/io/windows/io_backend.cpp new file mode 100644 index 0000000..b45b16f --- /dev/null +++ b/src/io/windows/io_backend.cpp @@ -0,0 +1,198 @@ +#include + +#include +#include +#include + +#if defined(NETKIT_WINDOWS) && defined(NETKIT_WSAPOLL) + +void netkit::io::io_backend::register_waiter(io_handle_t fd, io_event event, std::coroutine_handle<> h) { + std::lock_guard lock(mutex_); + + waiters_.push_back({ + fd, + event, + h + }); +} + +// TODO: move WSAStartup() to a single place, so that we don't call it multiple times. +netkit::io::io_backend::io_backend() { + WSADATA data{}; + + if (WSAStartup(MAKEWORD(2,2), &data) != 0) + throw socket_error("WSAStartup failed"); + + SOCKET listener = socket( + AF_INET, + SOCK_DGRAM, + IPPROTO_UDP + ); + + if (listener == INVALID_SOCKET) + throw socket_error("failed to create wake socket"); + + + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind( + listener, + reinterpret_cast(&addr), + sizeof(addr) + ) == SOCKET_ERROR) { + closesocket(listener); + throw socket_error("failed to bind wake socket"); + } + + + int len = sizeof(addr); + + if (getsockname( + listener, + reinterpret_cast(&addr), + &len + ) == SOCKET_ERROR) { + closesocket(listener); + throw socket_error("failed to get wake address"); + } + + wake_read_ = listener; + + wake_write_ = socket( + AF_INET, + SOCK_DGRAM, + IPPROTO_UDP + ); + + if (wake_write_ == INVALID_SOCKET) { + closesocket(wake_read_); + throw socket_error("failed to create wake sender"); + } + + if (connect( + wake_write_, + reinterpret_cast(&addr), + sizeof(addr) + ) == SOCKET_ERROR) { + closesocket(wake_read_); + closesocket(wake_write_); + + throw socket_error("failed to connect wake socket"); + } +} + +netkit::io::io_backend::~io_backend() { + io_backend::stop(); + + if (wake_read_ != INVALID_SOCKET) + closesocket(wake_read_); + + if (wake_write_ != INVALID_SOCKET) + closesocket(wake_write_); + + WSACleanup(); +} + +void netkit::io::io_backend::wake() { + char byte = 1; + + send( + wake_write_, + &byte, + 1, + 0 + ); +} + + + +void netkit::io::io_backend::poll(int timeout_ms) { + std::vector waiters; + + { + std::lock_guard lock(mutex_); + waiters = waiters_; + } + + if (waiters.empty()) { + Sleep(timeout_ms); + return; + } + + std::vector fds; + + fds.reserve(waiters.size()); + + for (auto& waiter : waiters) { + WSAPOLLFD fd{}; + + fd.fd = waiter.fd; + + if (waiter.event == io_event::read) + fd.events = POLLRDNORM; + + else if (waiter.event == io_event::write) + fd.events = POLLWRNORM; + + fds.push_back(fd); + } + + int result = WSAPoll( + fds.data(), + static_cast(fds.size()), + timeout_ms + ); + + if (result <= 0) + return; + + std::vector> ready; + + { + std::lock_guard lock(mutex_); + + for (std::size_t i = 0; i < fds.size(); i++) { + auto revents = fds[i].revents; + + if (revents == 0) + continue; + + auto it = std::ranges::find_if(waiters_, + [&](const waiter& w) { + return w.fd == waiters[i].fd && + w.handle == waiters[i].handle; + } + ); + + if (it != waiters_.end()) { + ready.push_back(it->handle); + waiters_.erase(it); + } + } + } + + for (auto handle : ready) { + if (handle) + handle.resume(); + } +} + +void netkit::io::io_backend::poll() { + this->poll(-1); +} + +void netkit::io::io_backend::run() { + while (running_) { + poll(-1); + } +} + +void netkit::io::io_backend::stop() { + running_ = false; + wake(); +} + +#endif \ No newline at end of file diff --git a/src/socket/native/native_async_listener.cpp b/src/socket/native/native_async_listener.cpp index e9ee9e2..b5d7440 100644 --- a/src/socket/native/native_async_listener.cpp +++ b/src/socket/native/native_async_listener.cpp @@ -1,10 +1,11 @@ -#include -#include +#include +#include #include #include #include #include +#include #ifdef NETKIT_WINDOWS #include @@ -20,89 +21,7 @@ #endif void netkit::sock::native::native_async_listener::set_sock_opts(opt opts) const { -#ifdef NETKIT_UNIX - if (opts & opt::reuse_addr) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); - } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); - } - if (opts & opt::no_delay) { - ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); - } - if (opts & opt::keep_alive) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); - } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); - } - if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd_, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd_); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd_, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd_); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd_, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd_); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd_, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd_); - throw socket_error("failed to set socket to blocking mode"); - } - } -#elifdef NETKIT_WINDOWS - if (opts & opt::reuse_addr) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set SO_REUSEADDR"); - } - } else if (opts & opt::no_reuse_addr) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to clear SO_REUSEADDR"); - } - } - if ((opts & opt::no_delay) && type_ == type::tcp) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set TCP_NODELAY"); - } - } - if (opts & opt::keep_alive) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set SO_KEEPALIVE"); - } - } else if (opts & opt::no_keep_alive) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to clear SO_KEEPALIVE"); - } - } - if (opts & opt::no_blocking) { - u_long mode = 1; - if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - u_long mode = 0; - if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set socket to blocking mode"); - } - } -#endif + platform::set_sock_opts(this->sockfd_, opts); } const sockaddr* netkit::sock::native::native_async_listener::get_sa() const { diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index fac078c..7c01e48 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -15,17 +15,18 @@ #include #include #include -#include #include #include +#include #ifdef NETKIT_UNIX #include #include #include #include -#elifdef NETKIT_WINDOWS +#include +#elif defined(NETKIT_WINDOWS) #include #include #endif @@ -92,94 +93,11 @@ void netkit::sock::native::native_async_sock::prep_sa() { } void netkit::sock::native::native_async_sock::set_sock_opts(opt opts) { -#ifdef NETKIT_UNIX - if (opts & opt::reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); - } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); - } - if (opts & opt::no_delay) { - ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); - } - if (opts & opt::keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); - } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); - } - if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } -#elifdef NETKIT_WINDOWS - if (opts & opt::reuse_addr) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_REUSEADDR"); - } - } else if (opts & opt::no_reuse_addr) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_REUSEADDR"); - } - } - if ((opts & opt::no_delay) && type_ == type::tcp) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set TCP_NODELAY"); - } - } - if (opts & opt::keep_alive) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_KEEPALIVE"); - } - } else if (opts & opt::no_keep_alive) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_KEEPALIVE"); - } - } - if (opts & opt::no_blocking) { - u_long mode = 1; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - u_long mode = 0; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } -#endif + platform::set_sock_opts(this->sockfd, opts); } netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t), context_(ctx) { - this->sockfd = -1; if (!addr.is_file_path()) { if (addr.get_ip().empty()) { @@ -188,44 +106,33 @@ netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_contex } if (t != type::uds) { - this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, + this->sockfd = platform::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); } else { - this->sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); - } - - if (this->sockfd < 0) { - throw socket_error("failed to create socket"); + this->sockfd = platform::socket(AF_UNIX, SOCK_STREAM, 0); } - if (this->sockfd >= 0) { - this->native_async_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } + if (!platform::valid_socket(sockfd)) { + throw socket_error{"failed to create socket"}; + } + this->native_async_sock::set_sock_opts(opts); this->prep_sa(); } netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) : addr_(peer), type_(t), sockfd(existing_fd), context_(ctx) { - if (sockfd < 0) throw socket_error("invalid fd"); - if (this->sockfd >= 0) { - this->native_async_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } + if (!platform::valid_socket(sockfd)) { + throw socket_error{"invalid fd"}; + } + + this->native_async_sock::set_sock_opts(opts); this->prep_sa(); } netkit::sock::native::native_async_sock::~native_async_sock() { - if (this->sockfd == -1) { - return; - } - if (::close(this->sockfd) < 0) { - ; - } + this->native_async_sock::close(); } netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() { @@ -236,43 +143,9 @@ const netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() co return this->addr_; } -#ifdef NETKIT_UNIX -netkit::io::task netkit::sock::native::native_async_sock::connect() { - int ret = ::connect( - this->sockfd, - this->get_sa(), - this->get_sa_len() - ); - - if (ret == 0) { - co_return; - } - - if (errno != EINPROGRESS) { - throw netkit::socket_error("failed to connect to server"); - } - - co_await this->context_.wait_writable(this->sockfd); - - int error = 0; - socklen_t len = sizeof(error); - - if (getsockopt(this->sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { - throw netkit::socket_error("getsockopt(SO_ERROR) failed"); - } - - if (error != 0) { - errno = error; - - throw netkit::socket_error("failed to connect to server"); - } - - co_return; -} -#elifdef NETKIT_WINDOWS netkit::io::task netkit::sock::native::native_async_sock::connect() { - int ret = ::connect( + int ret = platform::connect( this->sockfd, this->get_sa(), this->get_sa_len() @@ -282,10 +155,9 @@ netkit::sock::native::native_async_sock::connect() { co_return; } - int err = WSAGetLastError(); - - if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) { - throw netkit::socket_error("failed to connect to server"); + if (platform::last_socket_error() != platform::socket_err::would_block && + platform::last_socket_error() != platform::socket_err::in_progress) { + throw netkit::socket_error{"failed to connect to server"}; } co_await this->context_.wait_writable(this->sockfd); @@ -293,38 +165,31 @@ netkit::sock::native::native_async_sock::connect() { int error = 0; int len = sizeof(error); - if (getsockopt( - this->sockfd, - SOL_SOCKET, - SO_ERROR, - reinterpret_cast(&error), - &len - ) == SOCKET_ERROR) { - throw netkit::socket_error("getsockopt(SO_ERROR) failed"); - } + if (getsockopt(this->sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) { + throw socket_error("getsockopt(SO_ERROR) failed"); + } if (error != 0) { - throw netkit::socket_error( - "failed to connect to server: " + std::to_string(error) - ); + throw socket_error("failed to connect to server: " + std::to_string(error)); } co_return; } -#endif -#ifdef NETKIT_UNIX + netkit::io::task netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { size_t total_sent = 0; const char* data = static_cast(buf); while (total_sent < len) { - ssize_t sent = ::send( + auto sent = platform::send( this->sockfd, data + total_sent, len - total_sent, 0 ); + auto err = platform::last_socket_error(); + if (sent > 0) { total_sent += sent; continue; @@ -334,91 +199,25 @@ netkit::io::task netkit::sock::native::native_async_sock::send(cons co_return total_sent; } - if (errno == EAGAIN || errno == EWOULDBLOCK) { - co_await this->context_.wait_writable( - this->sockfd - ); - - continue; - } - - throw socket_error("failed to send: " + std::string(strerror(errno))); - } - - co_return total_sent; -} -#elifdef NETKIT_WINDOWS -netkit::io::task -netkit::sock::native::native_async_sock::send(const void* buf, size_t len) { - size_t total_sent = 0; - const char* data = static_cast(buf); - - while (total_sent < len) { - int sent = ::send( - this->sockfd, - data + total_sent, - static_cast(len - total_sent), - 0 - ); - - if (sent > 0) { - total_sent += sent; + if (err == platform::socket_err::would_block) { + co_await context_.wait_writable(sockfd); continue; } - if (sent == 0) { - co_return total_sent; - } - - int err = WSAGetLastError(); - - if (err == WSAEWOULDBLOCK) { - co_await this->context_.wait_writable(this->sockfd); + if (err == platform::socket_err::interrupted) { continue; } - throw socket_error("failed to send: " + std::to_string(err)); + throw socket_error("failed to send: " + platform::last_error_message()); } co_return total_sent; } -#endif -#ifdef NETKIT_UNIX -netkit::io::task netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { - for (;;) { - auto n = ::recv(this->sockfd, buf, size, 0); - - if (n > 0) { - co_return static_cast(n); - } - - if (n == 0) { - co_return 0; - } - if (errno == EAGAIN || errno == EWOULDBLOCK) { - co_await context_.wait_readable(sockfd); - continue; - } - - if (errno == EINTR) - continue; - - throw socket_error( - "recv failed: " + std::string(strerror(errno)) - ); - } -} -#elifdef NETKIT_WINDOWS netkit::io::task netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { for (;;) { - int n = ::recv( - this->sockfd, - static_cast(buf), - static_cast(size), - 0 - ); + auto n = platform::recv(this->sockfd, static_cast(buf), size, 0); if (n > 0) { co_return static_cast(n); @@ -428,57 +227,32 @@ netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { co_return 0; } - int err = WSAGetLastError(); + auto err = platform::last_socket_error(); - if (err == WSAEWOULDBLOCK) { + if (err == platform::socket_err::would_block) { co_await context_.wait_readable(sockfd); continue; } - if (err == WSAEINTR) { + if (err == platform::socket_err::interrupted) { continue; } - throw socket_error( - "recv failed: " + std::to_string(err) - ); + throw socket_error("recv failed: " + platform::last_error_message()); } } -#endif void netkit::sock::native::native_async_sock::close() noexcept { - if (this->sockfd == -1) - return; - - ::close(this->sockfd); - this->sockfd = -1; + if (platform::valid_socket(this->sockfd)) { + platform::close_socket(this->sockfd); + this->sockfd = platform::invalid_socket; + } } [[nodiscard]] netkit::sock::addr netkit::sock::native::native_async_sock::get_peer() const { -#ifdef NETKIT_DKP - if (!this->has_peer) { - throw netkit::socket_error("peer not known"); - } - - char ip_str[INET6_ADDRSTRLEN]{}; - uint16_t port = 0; - - if (this->peer_addr.ss_family == AF_INET) { - auto* addr_in = (sockaddr_in*)&this->peer_addr; - inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else { - throw netkit::ip_error("unsupported address family"); - } - - return netkit::sock::addr{ - ip_str, - port, netkit::sock::addr_type::ipv4 - }; -#else return native::get_peer(this->sockfd); -#endif } + netkit::sock::fd_t netkit::sock::native::native_async_sock::native_handle() const { return this->sockfd; } \ No newline at end of file diff --git a/src/socket/native/native_sync_listener.cpp b/src/socket/native/native_sync_listener.cpp index b0e5220..aa79ee8 100644 --- a/src/socket/native/native_sync_listener.cpp +++ b/src/socket/native/native_sync_listener.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -19,89 +21,7 @@ #include void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { -#ifdef NETKIT_UNIX - if (opts & opt::reuse_addr) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); - } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); - } - if (opts & opt::no_delay) { - ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); - } - if (opts & opt::keep_alive) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); - } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); - } - if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd_, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd_); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd_, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd_); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd_, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd_); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd_, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd_); - throw socket_error("failed to set socket to blocking mode"); - } - } -#elifdef NETKIT_WINDOWS - if (opts & opt::reuse_addr) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set SO_REUSEADDR"); - } - } else if (opts & opt::no_reuse_addr) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to clear SO_REUSEADDR"); - } - } - if ((opts & opt::no_delay) && type_ == type::tcp) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set TCP_NODELAY"); - } - } - if (opts & opt::keep_alive) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set SO_KEEPALIVE"); - } - } else if (opts & opt::no_keep_alive) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd_, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to clear SO_KEEPALIVE"); - } - } - if (opts & opt::no_blocking) { - u_long mode = 1; - if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - u_long mode = 0; - if (ioctlsocket(this->sockfd_, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd_); - throw socket_error("failed to set socket to blocking mode"); - } - } -#endif + netkit::platform::set_sock_opts(this->sockfd_, opts); } const sockaddr* netkit::sock::native::native_sync_listener::get_sa() const { diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index a7d0338..6a45b34 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -1,5 +1,6 @@ /** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol + * abstractions. * * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. @@ -9,11 +10,13 @@ * @note Part of the Netkit library. * @brief Implementation of the synchronous socket class. */ -#include -#include +#include + #include #include #include +#include +#include #ifdef NETKIT_WINDOWS #include @@ -37,12 +40,6 @@ #include -#ifdef NETKIT_DKP -#define NETKIT_SELECT ::net_select -#else -#define NETKIT_SELECT select -#endif - const sockaddr* netkit::sock::native::native_sync_sock::get_sa() const { return reinterpret_cast(&sa_storage); } @@ -112,94 +109,9 @@ void netkit::sock::native::native_sync_sock::connect() { } } -#ifdef NETKIT_UNIX void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { - if (opts & opt::reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opts, sizeof(opts)); - } else if (opts & opt::no_reuse_addr) { - ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, nullptr, 0); - } - if (opts & opt::no_delay) { - ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opts, sizeof(opts)); - } - if (opts & opt::keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &opts, sizeof(opts)); - } else if (opts & opt::no_keep_alive) { - ::setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, nullptr, 0); - } - if (opts & opt::no_blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - int flags = fcntl(this->sockfd, F_GETFL, 0); - if (flags < 0) { - ::close(this->sockfd); - throw socket_error("failed to get socket flags"); - } - if (fcntl(this->sockfd, F_SETFL, flags & ~O_NONBLOCK) < 0) { - ::close(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } + netkit::platform::set_sock_opts(this->sockfd, opts); } -#endif -#ifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { - if (opts & opt::reuse_addr) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_REUSEADDR"); - } - } else if (opts & opt::no_reuse_addr) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_REUSEADDR"); - } - } - if ((opts & opt::no_delay) && type_ == type::tcp) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set TCP_NODELAY"); - } - } - if (opts & opt::keep_alive) { - BOOL optval = TRUE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set SO_KEEPALIVE"); - } - } else if (opts & opt::no_keep_alive) { - BOOL optval = FALSE; - if (setsockopt(this->sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&optval), sizeof(optval)) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to clear SO_KEEPALIVE"); - } - } - if (opts & opt::no_blocking) { - u_long mode = 1; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to non-blocking mode"); - } - } else if (opts & opt::blocking) { - u_long mode = 0; - if (ioctlsocket(this->sockfd, FIONBIO, &mode) == SOCKET_ERROR) { - closesocket(this->sockfd); - throw socket_error("failed to set socket to blocking mode"); - } - } -} -#endif #ifdef NETKIT_UNIX netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { diff --git a/src/socket/native/peer_helper.cpp b/src/socket/native/peer_helper.cpp index 0a12016..1e8759b 100644 --- a/src/socket/native/peer_helper.cpp +++ b/src/socket/native/peer_helper.cpp @@ -12,8 +12,31 @@ #include #include #include +#include netkit::sock::addr netkit::sock::native::get_peer(fd_t sockfd) { +#ifdef NETKIT_DKP + if (!this->has_peer) { + throw netkit::socket_error("peer not known"); + } + + char ip_str[INET6_ADDRSTRLEN]{}; + uint16_t port = 0; + + if (this->peer_addr.ss_family == AF_INET) { + auto* addr_in = (sockaddr_in*)&this->peer_addr; + inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); + port = ntohs(addr_in->sin_port); + } else { + throw netkit::ip_error("unsupported address family"); + } + + return netkit::sock::addr{ + ip_str, + port, + netkit::sock::addr_type::ipv4 + }; +#else sockaddr_storage addr_storage{}; socklen_t addr_len = sizeof(addr_storage); @@ -41,4 +64,5 @@ netkit::sock::addr netkit::sock::native::get_peer(fd_t sockfd) { port, (addr_storage.ss_family == AF_INET) ? sock::addr_type::ipv4 : sock::addr_type::ipv6 }; +#endif } \ No newline at end of file diff --git a/src/stream/async_socket_stream.cpp b/src/stream/async_socket_stream.cpp index 4bc38c0..2671b84 100644 --- a/src/stream/async_socket_stream.cpp +++ b/src/stream/async_socket_stream.cpp @@ -24,7 +24,6 @@ netkit::stream::async_socket_stream::write(std::span buffer) { co_return stream_result{ result, result == 0 ? stream_status::eof : stream_status::success }; } - void netkit::stream::async_socket_stream::close() noexcept { if (socket_) { socket_->close(); From 372346b52f242d2526532fdd0cefe9e8c8d69659 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Wed, 29 Jul 2026 15:53:46 +0200 Subject: [PATCH 12/34] fix all current socket types for windows --- examples/cpp/socket_server/main.cpp | 6 +- include/netkit/platform/socket.hpp | 5 - src/socket/native/native_async_listener.cpp | 89 ++--------- src/socket/native/native_sync_listener.cpp | 46 ++---- src/socket/native/native_sync_socket.cpp | 159 ++++---------------- 5 files changed, 60 insertions(+), 245 deletions(-) diff --git a/examples/cpp/socket_server/main.cpp b/examples/cpp/socket_server/main.cpp index def4f95..ef754b4 100644 --- a/examples/cpp/socket_server/main.cpp +++ b/examples/cpp/socket_server/main.cpp @@ -1,13 +1,13 @@ #include #include -#include +#include #include [[noreturn]] int main() { netkit::sock::addr addr{ "0.0.0.0", // or simply "localhost" - 8080, + 1337, netkit::sock::addr_type::ipv4 }; @@ -16,7 +16,7 @@ server.bind(); server.listen(); - std::cout << "server listening on port 8080\n"; + std::cout << "server listening on port 1337\n"; while (true) { auto client = server.accept(); diff --git a/include/netkit/platform/socket.hpp b/include/netkit/platform/socket.hpp index d7a562b..47dc838 100644 --- a/include/netkit/platform/socket.hpp +++ b/include/netkit/platform/socket.hpp @@ -221,11 +221,6 @@ inline std::string last_error_message() { #endif inline socket_t socket(int domain, int type, int protocol) { -#ifdef NETKIT_WINDOWS - if (domain == AF_UNIX) { - throw socket_error{"unix domain sockets are not supported in win32 platform"}; - } -#endif return ::socket(domain, type, protocol); } diff --git a/src/socket/native/native_async_listener.cpp b/src/socket/native/native_async_listener.cpp index b5d7440..12eb144 100644 --- a/src/socket/native/native_async_listener.cpp +++ b/src/socket/native/native_async_listener.cpp @@ -88,13 +88,12 @@ void netkit::sock::native::native_async_listener::prep_sa() { } netkit::sock::native::native_async_listener::native_async_listener(io::io_context& ctx, const addr& address, type t, opt opts) : context_(ctx), addr_(address), type_(t), opts_(opts) { - sockfd_ = ::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); + sockfd_ = platform::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); - if (sockfd_ == INVALID_SOCKET) + if (!platform::valid_socket(sockfd_)) throw socket_error("failed creating socket"); set_sock_opts(opts_); - prep_sa(); } @@ -103,10 +102,8 @@ netkit::sock::native::native_async_listener::~native_async_listener() { } void netkit::sock::native::native_async_listener::bind() { - if (::bind(sockfd_, get_sa(), get_sa_len()) < 0) { - throw socket_error( - "bind failed" - ); + if (platform::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + throw socket_error("bind failed"); } bound_ = true; @@ -119,7 +116,7 @@ void netkit::sock::native::native_async_listener::unbind() { void netkit::sock::native::native_async_listener::listen(int backlog) { if (!bound_) throw socket_error("listener not bound"); - if (::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { + if (platform::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { throw socket_error("failed to listen on socket"); } @@ -130,23 +127,19 @@ void netkit::sock::native::native_async_listener::listen() { this->listen(-1); } -#ifdef NETKIT_UNIX netkit::io::task> netkit::sock::native::native_async_listener::accept() { while (true) { sockaddr_storage client_addr{}; socklen_t addr_len = sizeof(client_addr); - // TODO: use fcntl and get bsd support - // it is just a waste of time right now since we are using epoll anyway - fd_t client_sockfd = ::accept4( + fd_t client_sockfd = platform::accept( this->sockfd_, reinterpret_cast(&client_addr), - &addr_len, - SOCK_NONBLOCK + &addr_len ); - if (client_sockfd >= 0) { + if (platform::valid_socket(client_sockfd)) { if (this->type_ == type::uds) { co_return std::make_unique( this->context_, @@ -168,77 +161,23 @@ netkit::sock::native::native_async_listener::accept() { ); } - if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (platform::last_socket_error() == platform::socket_err::would_block) { co_await this->context_.wait_readable(this->sockfd_); continue; } - throw socket_error( - "failed to accept connection: " + - std::string(strerror(errno)) - ); + throw socket_error("failed to accept connection: " + platform::last_error_message()); } } -#elifdef NETKIT_WINDOWS -netkit::io::task> -netkit::sock::native::native_async_listener::accept() { - while (true) { - sockaddr_storage client_addr{}; - int addr_len = sizeof(client_addr); - - SOCKET client_sockfd = ::accept( - this->sockfd_, - reinterpret_cast(&client_addr), - &addr_len - ); - - if (client_sockfd != INVALID_SOCKET) { - - // set non-blocking - u_long mode = 1; - ioctlsocket(client_sockfd, FIONBIO, &mode); - -#ifdef NETKIT_WINDOWS - if (this->type_ == type::uds) { - closesocket(client_sockfd); - throw socket_error("unix domain sockets not supported on win32 platform"); - } -#endif - - auto peer = sock::native::get_peer(client_sockfd); - - co_return std::make_unique( - this->context_, - client_sockfd, - peer, - this->type_ - ); - } - - int err = WSAGetLastError(); - - if (err == WSAEWOULDBLOCK) { - co_await this->context_.wait_readable(this->sockfd_); - continue; - } - - throw socket_error( - "failed to accept connection: " + std::to_string(err) - ); - } -} -#endif void netkit::sock::native::native_async_listener::close() noexcept { - if (sockfd_ != INVALID_SOCKET) { - ::close(sockfd_); - sockfd_ = INVALID_SOCKET; + if (platform::valid_socket(sockfd_)) { + platform::close_socket(sockfd_); + this->bound_ = this->listening_ = false; } - - this->bound_ = this->listening_ = false; } -netkit::sock::fd_t netkit::sock::native::native_async_listener::native_handle() const{ +netkit::sock::fd_t netkit::sock::native::native_async_listener::native_handle() const { return sockfd_; } diff --git a/src/socket/native/native_sync_listener.cpp b/src/socket/native/native_sync_listener.cpp index aa79ee8..f116806 100644 --- a/src/socket/native/native_sync_listener.cpp +++ b/src/socket/native/native_sync_listener.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include #include @@ -14,7 +13,8 @@ #include #include #include -#elifdef NETKIT_WINDOWS +#include +#elif defined(NETKIT_WINDOWS) #include #endif @@ -89,28 +89,18 @@ void netkit::sock::native::native_sync_listener::prep_sa() { netkit::sock::native::native_sync_listener::native_sync_listener(const addr& address, type t, opt opts) : addr_(address), type_(t), opts_(opts) { - sockfd_ = ::socket( - addr_.is_ipv6() - ? AF_INET6 - : AF_INET, - SOCK_STREAM, - 0 - ); - - if (sockfd_ < 0) - throw socket_error( - "failed creating socket" - ); + sockfd_ = platform::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); + + if (!platform::valid_socket(sockfd_)) + throw socket_error("failed creating socket"); set_sock_opts(opts_); prep_sa(); } void netkit::sock::native::native_sync_listener::bind() { - if (::bind(sockfd_, get_sa(), get_sa_len()) < 0) { - throw socket_error( - "bind failed" - ); + if (platform::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + throw socket_error("bind failed"); } bound_ = true; @@ -123,7 +113,7 @@ void netkit::sock::native::native_sync_listener::unbind() { void netkit::sock::native::native_sync_listener::listen(int backlog) { if (!bound_) throw socket_error("listener not bound"); - if (::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { + if (platform::listen(this->sockfd_, backlog == -1 ? SOMAXCONN : backlog) < 0) { throw socket_error("failed to listen on socket"); } @@ -139,30 +129,26 @@ netkit::sock::native::native_sync_listener::accept() { sockaddr_storage client_addr{}; socklen_t addr_len = sizeof(client_addr); - auto client_sockfd = ::accept(this->sockfd_, reinterpret_cast(&client_addr), &addr_len); - if (client_sockfd < 0) { + auto client_sockfd = platform::accept(this->sockfd_, reinterpret_cast(&client_addr), &addr_len); + if (!platform::valid_socket(client_sockfd)) { throw socket_error("failed to accept connection: " + std::string(std::strerror(errno))); } +#ifndef NETKIT_DKP if (this->type_ == type::uds) { return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); } - - if (this->type_ == type::uds) { - return std::make_unique(client_sockfd, sock::addr(reinterpret_cast(&client_addr)->sun_path), this->type_); - } +#endif auto peer = native::get_peer(client_sockfd); return std::make_unique(client_sockfd, peer, this->type_); } void netkit::sock::native::native_sync_listener::close() noexcept { - if (sockfd_ != INVALID_SOCKET) { - ::close(sockfd_); - sockfd_ = INVALID_SOCKET; + if (platform::valid_socket(sockfd_)) { + platform::close_socket(sockfd_); + this->bound_ = this->listening_ = false; } - - this->bound_ = this->listening_ = false; } netkit::sock::fd_t netkit::sock::native::native_sync_listener::native_handle() const{ diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index 6a45b34..f250286 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -37,7 +37,6 @@ #endif #include - #include const sockaddr* netkit::sock::native::native_sync_sock::get_sa() const { @@ -104,18 +103,17 @@ void netkit::sock::native::native_sync_sock::prep_sa() { } void netkit::sock::native::native_sync_sock::connect() { - if (::connect(sockfd, get_sa(), get_sa_len()) < 0) { - throw socket_error("connect failed: " + std::string(strerror(errno))); + if (platform::connect(sockfd, get_sa(), get_sa_len()) < 0) { + throw socket_error("connect failed: " + platform::last_error_message()); } } void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { - netkit::platform::set_sock_opts(this->sockfd, opts); + platform::set_sock_opts(this->sockfd, opts); } -#ifdef NETKIT_UNIX netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { - this->sockfd = -1; + this->sockfd = -1; if (!addr.is_file_path()) { if (addr.get_ip().empty()) { @@ -123,99 +121,33 @@ netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, } } -#ifdef NETKIT_DKP - this->sockfd = ::socket(AF_INET, t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, IPPROTO_IP); -#elifndef NETKIT_WINDOWS - if (t != type::uds) { - this->sockfd = ::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, - t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); - } else { - this->sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); - } -#endif - - if (this->sockfd < 0) { - throw socket_error("failed to create socket"); - } + if (t != type::uds) { + this->sockfd = platform::socket(addr.is_ipv6() ? AF_INET6 : AF_INET, + t == type::tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + } else { + this->sockfd = platform::socket(AF_UNIX, SOCK_STREAM, 0); + } - if (this->sockfd >= 0) { - this->native_sync_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } + if (!platform::valid_socket(sockfd)) + throw socket_error{"failed to create socket"}; - this->prep_sa(); + this->native_sync_sock::set_sock_opts(opts); + this->prep_sa(); } -#endif netkit::sock::native::native_sync_sock::native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) : addr_(peer), type_(t), sockfd(existing_fd) { - if (sockfd < 0) throw socket_error("invalid fd"); -#ifdef NETKIT_WINDOWS - if (this->sockfd != INVALID_SOCKET) { -#else - if (this->sockfd >= 0) { -#endif - this->native_sync_sock::set_sock_opts(opts); - } else { - throw socket_error("cannot set options on invalid socket"); - } + if (!platform::valid_socket(sockfd)) + throw socket_error{"invalid fd"}; + + this->native_sync_sock::set_sock_opts(opts); this->prep_sa(); } -#ifdef NETKIT_WINDOWS -netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& in_addr, sock::type t, opt opts) - : addr_(in_addr), type_(t) { - - if (this->addr_.get_ip().empty() && !this->addr_.is_file_path()) { - throw socket_error("IP address or file path is empty"); - } - - int domain = AF_UNIX; - int sock_type = SOCK_STREAM; - int protocol = 0; - - if (t != type::uds) { - domain = this->addr_.is_ipv6() ? AF_INET6 : AF_INET; - sock_type = (t == type::tcp) ? SOCK_STREAM : SOCK_DGRAM; - protocol = (t == type::tcp) ? IPPROTO_TCP : IPPROTO_UDP; - } else { - protocol = 0; - } - - this->sockfd = socket(domain, sock_type, protocol); - if (this->sockfd == INVALID_SOCKET) { - throw socket_error("Failed to create socket"); - } - - this->native_sync_sock::set_sock_opts(opts); - this->prep_sa(); -} -#endif -#ifdef NETKIT_UNIX -netkit::sock::native::native_sync_sock::~native_sync_sock() { - if (this->sockfd == -1) { - return; - } - if (::close(this->sockfd) < 0) { - ; - } -} -#endif -#ifdef NETKIT_WINDOWS netkit::sock::native::native_sync_sock::~native_sync_sock() { - if (this->sockfd == INVALID_SOCKET) { - return; - } - - if (::closesocket(this->sockfd) == SOCKET_ERROR) { - return; - } - - this->sockfd = INVALID_SOCKET; + this->native_sync_sock::close(); } -#endif netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() { return this->addr_; @@ -226,62 +158,25 @@ const netkit::sock::addr& netkit::sock::native::native_sync_sock::get_addr() con } std::size_t netkit::sock::native::native_sync_sock::send(const void* buf, size_t len) { - return ::send(this->sockfd, static_cast(buf), len, 0); + return platform::send(this->sockfd, static_cast(buf), len, 0); } std::size_t netkit::sock::native::native_sync_sock::recv(void* buf, std::size_t len) { - return ::recv(this->sockfd, static_cast(buf), len, 0); + return platform::recv(this->sockfd, static_cast(buf), len, 0); } -#ifdef NETKIT_UNIX void netkit::sock::native::native_sync_sock::close() noexcept { - if (this->sockfd == -1) { - return; - } + if (!platform::valid_socket(this->sockfd)) { + return; + } - (void)::close(this->sockfd); - this->sockfd = -1; + platform::close_socket(this->sockfd); } -#elifdef NETKIT_WINDOWS -void netkit::sock::native::native_sync_sock::close() noexcept { - if (this->sockfd == INVALID_SOCKET) { - return; - } - ::shutdown(this->sockfd, SD_BOTH); - - if (::closesocket(this->sockfd) != 0) { - ; - } - - sockfd = INVALID_SOCKET; -} -#endif [[nodiscard]] netkit::sock::addr netkit::sock::native::native_sync_sock::get_peer() const { -#ifdef NETKIT_DKP - if (!this->has_peer) { - throw netkit::socket_error("peer not known"); - } - - char ip_str[INET6_ADDRSTRLEN]{}; - uint16_t port = 0; - - if (this->peer_addr.ss_family == AF_INET) { - auto* addr_in = (sockaddr_in*)&this->peer_addr; - inet_ntop(AF_INET, &addr_in->sin_addr, ip_str, sizeof(ip_str)); - port = ntohs(addr_in->sin_port); - } else { - throw netkit::ip_error("unsupported address family (Wii = IPv4 only)"); - } - - return netkit::sock::addr{ - ip_str, - port, netkit::sock::addr_type::ipv4 - }; -#else - return netkit::sock::native::get_peer(this->sockfd); -#endif + return native::get_peer(this->sockfd); } + netkit::sock::fd_t netkit::sock::native::native_sync_sock::native_handle() const { return this->sockfd; } \ No newline at end of file From 7b4f2c75a39289a1fd340f32597d401df9a6c0df Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Wed, 29 Jul 2026 23:15:48 +0200 Subject: [PATCH 13/34] add udp abstractions --- CMakeLists.txt | 2 + .../netkit/datagram/async_socket_datagram.hpp | 36 ++++ .../netkit/datagram/basic_async_datagram.hpp | 23 +++ include/netkit/datagram/basic_datagram.hpp | 20 +++ include/netkit/datagram/socket_datagram.hpp | 36 ++++ include/netkit/io/task.hpp | 12 +- include/netkit/netkit.hpp | 10 ++ include/netkit/platform/socket.hpp | 16 ++ include/netkit/socket/addr.hpp | 10 ++ .../native/basic_native_async_listener.hpp | 1 + .../socket/native/basic_native_async_sock.hpp | 8 +- .../native/basic_native_sync_listener.hpp | 1 + .../socket/native/basic_native_sync_sock.hpp | 7 +- .../socket/native/native_async_listener.hpp | 10 +- .../socket/native/native_async_sock.hpp | 14 +- .../socket/native/native_sync_listener.hpp | 15 +- .../netkit/socket/native/native_sync_sock.hpp | 12 +- include/netkit/udp/async_udp_datagram.hpp | 38 ++++ include/netkit/udp/udp_datagram.hpp | 28 +++ src/socket/addr.cpp | 115 +++++++++++- src/socket/native/native_async_listener.cpp | 81 ++------- src/socket/native/native_async_socket.cpp | 165 ++++++++++-------- src/socket/native/native_sync_listener.cpp | 81 ++------- src/socket/native/native_sync_socket.cpp | 127 +++++++------- src/udp/async_udp_datagram.cpp | 30 ++++ src/udp/udp_datagram.cpp | 29 +++ 26 files changed, 612 insertions(+), 315 deletions(-) create mode 100644 include/netkit/datagram/async_socket_datagram.hpp create mode 100644 include/netkit/datagram/basic_async_datagram.hpp create mode 100644 include/netkit/datagram/basic_datagram.hpp create mode 100644 include/netkit/datagram/socket_datagram.hpp create mode 100644 include/netkit/udp/async_udp_datagram.hpp create mode 100644 include/netkit/udp/udp_datagram.hpp create mode 100644 src/udp/async_udp_datagram.cpp create mode 100644 src/udp/udp_datagram.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dc9c07e..99ce3dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,8 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/tcp/async_tcp_server.cpp src/io/fallback/io_backend.cpp src/io/windows/io_backend.cpp + src/udp/udp_datagram.cpp + src/udp/async_udp_datagram.cpp ) add_library(netkit::netkit ALIAS netkit) diff --git a/include/netkit/datagram/async_socket_datagram.hpp b/include/netkit/datagram/async_socket_datagram.hpp new file mode 100644 index 0000000..56d632c --- /dev/null +++ b/include/netkit/datagram/async_socket_datagram.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace netkit::datagram { + +class async_socket_datagram : public basic_async_datagram { +public: + explicit async_socket_datagram(std::unique_ptr socket) + : sock_(std::move(socket)) {} + + io::task + send_to(std::span buffer, const sock::addr& to) override { + co_return co_await sock_->sendto(buffer.data(), buffer.size(), to); + } + + io::task> + recv_from(std::span buffer) override { + co_return co_await sock_->recvfrom(buffer.data(), buffer.size()); + } + + void close() noexcept override { + sock_->close(); + } + + platform::socket_t native_handle() const noexcept { + return sock_->native_handle(); + } + +private: + std::unique_ptr sock_; +}; + +} \ No newline at end of file diff --git a/include/netkit/datagram/basic_async_datagram.hpp b/include/netkit/datagram/basic_async_datagram.hpp new file mode 100644 index 0000000..8c3506d --- /dev/null +++ b/include/netkit/datagram/basic_async_datagram.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include + +namespace netkit::datagram { + +class basic_async_datagram { +public: + virtual ~basic_async_datagram() = default; + + virtual io::task + send_to(std::span buffer, const sock::addr& to) = 0; + + virtual io::task> + recv_from(std::span buffer) = 0; + + virtual void close() noexcept = 0; +}; + +} \ No newline at end of file diff --git a/include/netkit/datagram/basic_datagram.hpp b/include/netkit/datagram/basic_datagram.hpp new file mode 100644 index 0000000..e4f32d2 --- /dev/null +++ b/include/netkit/datagram/basic_datagram.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include + +namespace netkit::datagram { + +class basic_datagram { +public: + virtual ~basic_datagram() = default; + + virtual std::size_t send_to(std::span buffer, const sock::addr& to) = 0; + virtual std::pair recv_from(std::span buffer) = 0; + + virtual void close() noexcept = 0; +}; + +} \ No newline at end of file diff --git a/include/netkit/datagram/socket_datagram.hpp b/include/netkit/datagram/socket_datagram.hpp new file mode 100644 index 0000000..b589ae9 --- /dev/null +++ b/include/netkit/datagram/socket_datagram.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +namespace netkit::datagram { + +class socket_datagram : public basic_datagram { +public: + explicit socket_datagram(std::unique_ptr socket) + : sock_(std::move(socket)) {} + + std::size_t + send_to(std::span buffer, const sock::addr& to) override { + return sock_->sendto(buffer.data(), buffer.size(), to); + } + + std::pair + recv_from(std::span buffer) override { + return sock_->recvfrom(buffer.data(), buffer.size()); + } + + void close() noexcept override { + sock_->close(); + } + + platform::socket_t native_handle() const noexcept { + return sock_->native_handle(); + } + +private: + std::unique_ptr sock_; +}; + +} \ No newline at end of file diff --git a/include/netkit/io/task.hpp b/include/netkit/io/task.hpp index d3786d8..e0c3c4a 100644 --- a/include/netkit/io/task.hpp +++ b/include/netkit/io/task.hpp @@ -13,11 +13,11 @@ class task; template struct promise_return { - T value; + std::optional value; template void return_value(U&& v) { - value = std::forward(v); + value.emplace(std::forward(v)); } }; @@ -50,10 +50,8 @@ class task { } std::coroutine_handle<> await_suspend(handle_type h) noexcept { - auto continuation = h.promise().continuation; - - if (continuation) - return continuation; + if (auto _continuation = h.promise().continuation) + return _continuation; return std::noop_coroutine(); } @@ -96,7 +94,7 @@ class task { } else { auto value = std::move( - handle_.promise().value + *handle_.promise().value ); handle_.destroy(); diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index 0a75acf..d60f4c5 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -91,4 +91,14 @@ #include #include +// datagram +#include +#include +#include +#include + +// udp +#include +#include + #pragma message ("Use of netkit.hpp directly is discouraged for all uses, except test code.") \ No newline at end of file diff --git a/include/netkit/platform/socket.hpp b/include/netkit/platform/socket.hpp index 47dc838..9bb4a05 100644 --- a/include/netkit/platform/socket.hpp +++ b/include/netkit/platform/socket.hpp @@ -268,4 +268,20 @@ inline int listen(socket_t sock, int backlog) { return ::listen(sock, backlog); } +inline socket_result recvfrom(socket_t sock, void* buffer, size_t length, int flags, sockaddr* addr, socklen_t* addrlen) { +#ifdef NETKIT_WINDOWS + return ::recvfrom(sock, static_cast(buffer), static_cast(length), flags, addr, addrlen); +#else + return ::recvfrom(sock, buffer, length, flags, addr, addrlen); +#endif +} + +inline socket_result sendto(socket_t sock, const void* buffer, size_t length, int flags, const sockaddr* addr, socklen_t addrlen) { +#ifdef NETKIT_WINDOWS + return ::sendto(sock, static_cast(buffer), static_cast(length), flags, addr, addrlen); +#else + return ::sendto(sock, buffer, length, flags, addr, addrlen); +#endif +} + } diff --git a/include/netkit/socket/addr.hpp b/include/netkit/socket/addr.hpp index 86a701f..1d4d0bd 100644 --- a/include/netkit/socket/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -18,6 +18,8 @@ #include namespace netkit::sock { + using sockaddr_len = int; + class NETKIT_API addr final { std::filesystem::path path{}; std::string hostname{}; @@ -25,7 +27,11 @@ namespace netkit::sock { int port{}; addr_type type{addr_type::hostname}; + sockaddr_storage sa_storage_{}; + addr() = default; + + void prep_sa(); public: /** * @brief Constructs a sock_addr object. @@ -42,6 +48,7 @@ namespace netkit::sock { */ explicit addr(std::filesystem::path path); #endif + addr(const sockaddr* sa, sockaddr_len len); /** * @brief Check whether the address is IPv4 or IPv6. * @return True if the address is IPv4, false if it is IPv6 or invalid. @@ -83,5 +90,8 @@ namespace netkit::sock { */ [[nodiscard]] addr_type get_type() const; ~addr() = default; + + [[nodiscard]] const sockaddr* get_sa() const noexcept; + [[nodiscard]] sockaddr_len get_sa_len() const noexcept; }; } diff --git a/include/netkit/socket/native/basic_native_async_listener.hpp b/include/netkit/socket/native/basic_native_async_listener.hpp index 423efad..4857b10 100644 --- a/include/netkit/socket/native/basic_native_async_listener.hpp +++ b/include/netkit/socket/native/basic_native_async_listener.hpp @@ -11,6 +11,7 @@ namespace netkit::sock::native { public: virtual ~basic_native_async_listener() = default; virtual void bind() = 0; + virtual void bind(const addr& addr) = 0; virtual void unbind() = 0; virtual void listen() = 0; virtual void listen(int backlog) = 0; diff --git a/include/netkit/socket/native/basic_native_async_sock.hpp b/include/netkit/socket/native/basic_native_async_sock.hpp index d62873a..f51a68c 100644 --- a/include/netkit/socket/native/basic_native_async_sock.hpp +++ b/include/netkit/socket/native/basic_native_async_sock.hpp @@ -4,7 +4,7 @@ * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. * - * @file basic_async_sock.hpp + * @file basic_native_async_sock.hpp * @license MIT * @note Part of the Netkit library. * @brief Provides a basic interface for asynchronous sockets. @@ -28,8 +28,14 @@ namespace netkit::sock::native { virtual netkit::io::task connect() = 0; virtual netkit::io::task send(const void* buf, std::size_t len) = 0; [[nodiscard]] virtual netkit::io::task recv(void* buf, std::size_t len) = 0; + virtual netkit::io::task> recvfrom(void* buf, std::size_t len) = 0; + virtual netkit::io::task sendto(const void* buf, std::size_t len, const addr& dest) = 0; virtual void close() noexcept = 0; + virtual void bind() = 0; + virtual void bind(const addr& addr) = 0; + virtual void unbind() noexcept = 0; + virtual addr& get_addr() { throw std::logic_error{"socket does not have an addr object"}; } diff --git a/include/netkit/socket/native/basic_native_sync_listener.hpp b/include/netkit/socket/native/basic_native_sync_listener.hpp index e3174e8..a110949 100644 --- a/include/netkit/socket/native/basic_native_sync_listener.hpp +++ b/include/netkit/socket/native/basic_native_sync_listener.hpp @@ -12,6 +12,7 @@ namespace netkit::sock::native { virtual ~basic_native_sync_listener() = default; virtual void bind() = 0; + virtual void bind(const addr& addr) = 0; virtual void unbind() = 0; virtual void listen(int backlog) = 0; diff --git a/include/netkit/socket/native/basic_native_sync_sock.hpp b/include/netkit/socket/native/basic_native_sync_sock.hpp index 933b43c..157c7fe 100644 --- a/include/netkit/socket/native/basic_native_sync_sock.hpp +++ b/include/netkit/socket/native/basic_native_sync_sock.hpp @@ -4,7 +4,7 @@ * Copyright (c) 2025-2026 Jacob Nilsson * Licensed under the MIT License. * - * @file basic_sync_sock.hpp + * @file basic_native_sync_sock.hpp * @license MIT * @note Part of the Netkit library. * @brief Provides a basic interface for synchronous sockets. @@ -27,6 +27,11 @@ namespace netkit::sock::native { virtual void connect() = 0; virtual std::size_t send(const void* buf, std::size_t len) = 0; [[nodiscard]] virtual std::size_t recv(void* buf, std::size_t len) = 0; + virtual std::pair recvfrom(void* buf, size_t size) = 0; + virtual std::size_t sendto(const void* buf, std::size_t len, const addr& dest) = 0; + virtual void bind() = 0; + virtual void bind(const addr& addr) = 0; + virtual void unbind() noexcept = 0; virtual addr& get_addr() { throw std::logic_error{"socket does not have an addr object"}; } diff --git a/include/netkit/socket/native/native_async_listener.hpp b/include/netkit/socket/native/native_async_listener.hpp index 8f69495..03a9739 100644 --- a/include/netkit/socket/native/native_async_listener.hpp +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -26,7 +26,9 @@ class NETKIT_API native_async_listener : public basic_native_async_listener { ~native_async_listener() override; void bind() override; + void bind(const addr& addr) override; void unbind() override; + void listen(int backlog) override; void listen() override; @@ -53,14 +55,6 @@ class NETKIT_API native_async_listener : public basic_native_async_listener { bool listening_{false}; sockaddr_storage sa_storage_{}; - - void prep_sa(); - - [[nodiscard]] - const sockaddr* get_sa() const; - - [[nodiscard]] - socklen_t get_sa_len() const; }; } \ No newline at end of file diff --git a/include/netkit/socket/native/native_async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp index 1b65ff3..1d68c4b 100644 --- a/include/netkit/socket/native/native_async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -33,13 +33,8 @@ constexpr fd_t INVALID_SOCKET = -1; type type_{}; fd_t sockfd{INVALID_SOCKET}; - sockaddr_storage sa_storage{}; bool bound{false}; - [[nodiscard]] const sockaddr* get_sa() const; - [[nodiscard]] socklen_t get_sa_len() const; - void prep_sa(); - io::io_context& context_; public: native_async_sock(io::io_context& ctx, const sock::addr& addr, sock::type t, opt opts = opt::reuse_addr|opt::no_delay|opt::no_blocking); @@ -49,11 +44,18 @@ constexpr fd_t INVALID_SOCKET = -1; [[nodiscard]] const sock::addr& get_addr() const override; netkit::io::task connect() override; + void bind() override; + void bind(const addr& addr) override; + void unbind() noexcept override; + netkit::io::task send(const void* buf, size_t len) override; [[nodiscard]] netkit::io::task recv(void* buf, std::size_t size) override; void close() noexcept override; [[nodiscard]] sock::addr get_peer() const override; [[nodiscard]] fd_t native_handle() const override; - void set_sock_opts(opt opts) override; + netkit::io::task> recvfrom(void* buf, size_t size) override; + netkit::io::task sendto(const void* buf, std::size_t len, const addr& dest) override; + + void set_sock_opts(opt opts) override; }; } \ No newline at end of file diff --git a/include/netkit/socket/native/native_sync_listener.hpp b/include/netkit/socket/native/native_sync_listener.hpp index e0208c1..d2ceb65 100644 --- a/include/netkit/socket/native/native_sync_listener.hpp +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -15,15 +15,14 @@ namespace netkit::sock::native { constexpr fd_t INVALID_SOCKET = -1; #endif -class NETKIT_API native_sync_listener - : public basic_native_sync_listener -{ +class NETKIT_API native_sync_listener : public basic_native_sync_listener { public: void set_sock_opts(opt opts) const; native_sync_listener(const addr& address, type t = type::tcp, opt opts = opt::reuse_addr|opt::blocking); ~native_sync_listener() override = default; void bind() override; + void bind(const addr& addr) override; void unbind() override; void listen(int backlog) override; @@ -46,16 +45,6 @@ class NETKIT_API native_sync_listener bool listening_{false}; opt opts_; - - sockaddr_storage sa_storage_{}; - - void prep_sa(); - - [[nodiscard]] - const sockaddr* get_sa() const; - - [[nodiscard]] - socklen_t get_sa_len() const; }; } \ No newline at end of file diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp index 8998ccf..aaa9b3b 100644 --- a/include/netkit/socket/native/native_sync_sock.hpp +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -35,13 +35,8 @@ namespace netkit::sock::native { #else fd_t sockfd{-1}; #endif - sockaddr_storage sa_storage{}; bool bound{false}; mutable std::string old_bytes; - - [[nodiscard]] const sockaddr* get_sa() const; - [[nodiscard]] socklen_t get_sa_len() const; - void prep_sa(); #ifdef NETKIT_DKP sockaddr_storage peer_addr{}; bool has_peer{false}; @@ -74,6 +69,13 @@ namespace netkit::sock::native { */ std::size_t send(const void* buf, std::size_t len) override; [[nodiscard]] std::size_t recv(void* buf, std::size_t len) override; + std::pair recvfrom(void* buf, size_t size) override; + std::size_t sendto(const void* buf, std::size_t len, const addr& dest) override; + + void bind() override; + void bind(const addr& addr) override; + void unbind() noexcept override; + /** * @brief Close the socket. */ diff --git a/include/netkit/udp/async_udp_datagram.hpp b/include/netkit/udp/async_udp_datagram.hpp new file mode 100644 index 0000000..3d3cd72 --- /dev/null +++ b/include/netkit/udp/async_udp_datagram.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include +#include +#include + +namespace netkit::udp { + +class async_udp_datagram : public datagram::basic_async_datagram { +public: + async_udp_datagram( + io::io_context& ctx, + sock::addr addr + ); + + void bind() const; + + io::task + send_to( + std::span buffer, + const sock::addr& dest + ); + + io::task> + recv_from( + std::span buffer + ); + + void close() noexcept; + +private: + sock::addr addr_; + std::unique_ptr sock_; +}; + +} \ No newline at end of file diff --git a/include/netkit/udp/udp_datagram.hpp b/include/netkit/udp/udp_datagram.hpp new file mode 100644 index 0000000..79de752 --- /dev/null +++ b/include/netkit/udp/udp_datagram.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +namespace netkit::udp { + +class udp_datagram : public datagram::basic_datagram { +public: + explicit udp_datagram(sock::addr addr); + udp_datagram(); + + void bind() const; + + std::size_t send_to(std::span buffer, const sock::addr& dest) override; + + std::pair recv_from(std::span buffer) override; + + void close() noexcept override; + +private: + sock::addr addr_; + std::unique_ptr sock_; +}; + +} \ No newline at end of file diff --git a/src/socket/addr.cpp b/src/socket/addr.cpp index 2b575d3..415e443 100644 --- a/src/socket/addr.cpp +++ b/src/socket/addr.cpp @@ -36,11 +36,12 @@ #ifdef NETKIT_WINDOWS #include +#include #endif /* solely for use internally */ #ifndef NETKIT_DKP -#ifdef NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER +#if defined(NETKIT_ENABLE_SOCK_CUSTOM_RESOLVER) && defined(NETKIT_DNS) [[nodiscard]] static netkit::network::ip_list get_a_aaaa_from_hostname(const std::string& hostname) { if (hostname == "localhost") { return {NETKIT_LOCALHOST_IPV4, NETKIT_LOCALHOST_IPV6}; @@ -96,7 +97,7 @@ int res = getaddrinfo(hostname.c_str(), nullptr, &hints, &result); if (res != 0) { -#ifdef _WIN32 +#ifdef NETKIT_WINDOWS throw netkit::dns_error("getaddrinfo failed: " + std::to_string(res)); #else throw netkit::dns_error(gai_strerror(res)); @@ -234,10 +235,14 @@ netkit::sock::addr::addr(const std::string& hostname, int port, addr_type t) : if (this->hostname == ip) { this->hostname.clear(); } + + prep_sa(); } #ifndef NETKIT_DKP -netkit::sock::addr::addr(std::filesystem::path path) : path(std::move(path)), type(addr_type::filename) {} +netkit::sock::addr::addr(std::filesystem::path path) : path(std::move(path)), type(addr_type::filename) { + prep_sa(); +} #endif bool netkit::sock::addr::is_ipv4() const noexcept { @@ -287,4 +292,108 @@ int netkit::sock::addr::get_port() const { netkit::sock::addr_type netkit::sock::addr::get_type() const { return type; +} + +const sockaddr* netkit::sock::addr::get_sa() const noexcept { + return reinterpret_cast(&sa_storage_); +} + +socklen_t netkit::sock::addr::get_sa_len() const noexcept { + if (this->is_ipv4()) return sizeof(sockaddr_in); + if (this->is_ipv6()) return sizeof(sockaddr_in6); +#ifndef NETKIT_DKP + if (this->is_file_path()) { + const auto& file_path = this->get_path(); + return static_cast(offsetof(sockaddr_un, sun_path) + file_path.string().size() + 1); + } +#endif + + return 0; +} + +void netkit::sock::addr::prep_sa() { + memset(&sa_storage_, 0, sizeof(sa_storage_)); + + if (this->is_ipv4()) { + auto* sa4 = reinterpret_cast(&sa_storage_); + sa4->sin_family = AF_INET; + sa4->sin_port = htons(this->get_port()); + if (inet_pton(AF_INET, this->get_ip().c_str(), &sa4->sin_addr) <= 0) { + throw parsing_error("invalid IPv4 address"); + } + } else if (this->is_ipv6()) { + auto* sa6 = reinterpret_cast(&sa_storage_); + sa6->sin6_family = AF_INET6; + sa6->sin6_port = htons(this->get_port()); + + std::string ip_addr = this->get_ip(); + unsigned long scope = 0; + + auto pos = ip.find('%'); + if (pos != std::string::npos) { + scope = std::stoul(ip_addr.substr(pos + 1)); + ip_addr = ip_addr.substr(0, pos); // strip %scope before inet_pton + } + + if (inet_pton(AF_INET6, ip_addr.c_str(), &sa6->sin6_addr) <= 0) { + throw parsing_error("invalid IPv6 address"); + } + + if (scope != 0) { + sa6->sin6_scope_id = scope; + } + } else if (this->is_file_path()) { + auto* sa_un = reinterpret_cast(&sa_storage_); + sa_un->sun_family = AF_UNIX; + + const auto& f_path = this->get_path().string(); + if (f_path.size() >= sizeof(sa_un->sun_path)) { + throw socket_error("UNIX socket path too long"); + } + std::memcpy(sa_un->sun_path, f_path.c_str(), f_path.size() + 1); + } else { + throw ip_error("invalid address type"); + } +} + +netkit::sock::addr::addr(const sockaddr* sa, socklen_t len) { + switch (sa->sa_family) { + case AF_INET: { + auto* sa4 = reinterpret_cast(sa); + + char _ip[INET_ADDRSTRLEN]{}; + if (!inet_ntop(AF_INET, &sa4->sin_addr, _ip, sizeof(_ip))) { + throw socket_error("inet_ntop failed"); + } + + ip = _ip; + port = ntohs(sa4->sin_port); + type = addr_type::ipv4; + break; + } + + case AF_INET6: { + auto* sa6 = reinterpret_cast(sa); + + char _ip[INET6_ADDRSTRLEN]{}; + if (!inet_ntop(AF_INET6, &sa6->sin6_addr, _ip, sizeof(_ip))) { + throw socket_error("inet_ntop failed"); + } + + ip = _ip; + port = ntohs(sa6->sin6_port); + + if (sa6->sin6_scope_id != 0) { + ip += "%" + std::to_string(sa6->sin6_scope_id); + } + + type = addr_type::ipv6; + break; + } + + default: + throw socket_error("unsupported address family"); + } + + this->prep_sa(); } \ No newline at end of file diff --git a/src/socket/native/native_async_listener.cpp b/src/socket/native/native_async_listener.cpp index 12eb144..61b794b 100644 --- a/src/socket/native/native_async_listener.cpp +++ b/src/socket/native/native_async_listener.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #ifdef NETKIT_WINDOWS #include @@ -18,75 +17,13 @@ #include #include #include +#include #endif void netkit::sock::native::native_async_listener::set_sock_opts(opt opts) const { platform::set_sock_opts(this->sockfd_, opts); } -const sockaddr* netkit::sock::native::native_async_listener::get_sa() const { - return reinterpret_cast(&sa_storage_); -} - -socklen_t netkit::sock::native::native_async_listener::get_sa_len() const { - if (addr_.is_ipv4()) return sizeof(sockaddr_in); - if (addr_.is_ipv6()) return sizeof(sockaddr_in6); -#ifndef NETKIT_DKP - if (addr_.is_file_path()) { - const auto& path = addr_.get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); - } -#endif - - throw netkit::socket_error("invalid address type"); -} - -void netkit::sock::native::native_async_listener::prep_sa() { - memset(&sa_storage_, 0, sizeof(sa_storage_)); - - if (addr_.is_ipv4()) { - auto* sa4 = reinterpret_cast(&sa_storage_); - sa4->sin_family = AF_INET; - sa4->sin_port = htons(addr_.get_port()); - if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { - throw parsing_error("invalid IPv4 address"); - } - } else if (addr_.is_ipv6()) { - auto* sa6 = reinterpret_cast(&sa_storage_); - sa6->sin6_family = AF_INET6; - sa6->sin6_port = htons(addr_.get_port()); - - std::string ip = addr_.get_ip(); - unsigned long scope = 0; - - auto pos = ip.find('%'); - if (pos != std::string::npos) { - scope = std::stoul(ip.substr(pos + 1)); - ip = ip.substr(0, pos); // strip %scope before inet_pton - } - - if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { - throw parsing_error("invalid IPv6 address"); - } - - if (scope != 0) { - sa6->sin6_scope_id = scope; - } -#ifndef NETKIT_DKP - } else if (addr_.is_file_path()) { - auto* sa_un = reinterpret_cast(&sa_storage_); - sa_un->sun_family = AF_UNIX; - const auto& path = addr_.get_path().string(); - if (path.size() >= sizeof(sa_un->sun_path)) { - throw socket_error("UNIX socket path too long"); - } - std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); -#endif - } else { - throw ip_error("invalid address type"); - } -} - netkit::sock::native::native_async_listener::native_async_listener(io::io_context& ctx, const addr& address, type t, opt opts) : context_(ctx), addr_(address), type_(t), opts_(opts) { sockfd_ = platform::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); @@ -94,7 +31,6 @@ netkit::sock::native::native_async_listener::native_async_listener(io::io_contex throw socket_error("failed creating socket"); set_sock_opts(opts_); - prep_sa(); } netkit::sock::native::native_async_listener::~native_async_listener() { @@ -102,10 +38,23 @@ netkit::sock::native::native_async_listener::~native_async_listener() { } void netkit::sock::native::native_async_listener::bind() { - if (platform::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + if (platform::bind(sockfd_, addr_.get_sa(), addr_.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + bound_ = true; +} + +void netkit::sock::native::native_async_listener::bind(const addr& addr) { + if (bound_) { + throw socket_error{"bind failed"}; + } + + if (platform::bind(sockfd_, addr.get_sa(), addr.get_sa_len()) < 0) { throw socket_error("bind failed"); } + addr_ = addr; bound_ = true; } diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index 7c01e48..f325c7d 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include @@ -28,70 +27,8 @@ #include #elif defined(NETKIT_WINDOWS) #include -#include #endif -const sockaddr* netkit::sock::native::native_async_sock::get_sa() const { - return reinterpret_cast(&sa_storage); -} - -socklen_t netkit::sock::native::native_async_sock::get_sa_len() const { - if (addr_.is_ipv4()) return sizeof(sockaddr_in); - if (addr_.is_ipv6()) return sizeof(sockaddr_in6); -#ifndef NETKIT_DKP - if (addr_.is_file_path()) { - const auto& path = addr_.get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); - } -#endif - - throw netkit::socket_error("invalid address type"); -} - -void netkit::sock::native::native_async_sock::prep_sa() { - memset(&sa_storage, 0, sizeof(sa_storage)); - - if (addr_.is_ipv4()) { - auto* sa4 = reinterpret_cast(&sa_storage); - sa4->sin_family = AF_INET; - sa4->sin_port = htons(addr_.get_port()); - if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { - throw parsing_error("invalid IPv4 address"); - } - } else if (addr_.is_ipv6()) { - auto* sa6 = reinterpret_cast(&sa_storage); - sa6->sin6_family = AF_INET6; - sa6->sin6_port = htons(addr_.get_port()); - - std::string ip = addr_.get_ip(); - unsigned long scope = 0; - - auto pos = ip.find('%'); - if (pos != std::string::npos) { - scope = std::stoul(ip.substr(pos + 1)); - ip = ip.substr(0, pos); // strip %scope before inet_pton - } - - if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { - throw parsing_error("invalid IPv6 address"); - } - - if (scope != 0) { - sa6->sin6_scope_id = scope; - } - } else if (addr_.is_file_path()) { - auto* sa_un = reinterpret_cast(&sa_storage); - sa_un->sun_family = AF_UNIX; - const auto& path = addr_.get_path().string(); - if (path.size() >= sizeof(sa_un->sun_path)) { - throw socket_error("UNIX socket path too long"); - } - std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); - } else { - throw ip_error("invalid address type"); - } -} - void netkit::sock::native::native_async_sock::set_sock_opts(opt opts) { platform::set_sock_opts(this->sockfd, opts); } @@ -117,7 +54,6 @@ netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_contex } this->native_async_sock::set_sock_opts(opts); - this->prep_sa(); } netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_context& ctx, fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) @@ -128,7 +64,6 @@ netkit::sock::native::native_async_sock::native_async_sock(netkit::io::io_contex } this->native_async_sock::set_sock_opts(opts); - this->prep_sa(); } netkit::sock::native::native_async_sock::~native_async_sock() { @@ -145,11 +80,7 @@ const netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() co netkit::io::task netkit::sock::native::native_async_sock::connect() { - int ret = platform::connect( - this->sockfd, - this->get_sa(), - this->get_sa_len() - ); + int ret = platform::connect(this->sockfd, addr_.get_sa(), addr_.get_sa_len()); if (ret == 0) { co_return; @@ -246,6 +177,7 @@ void netkit::sock::native::native_async_sock::close() noexcept { if (platform::valid_socket(this->sockfd)) { platform::close_socket(this->sockfd); this->sockfd = platform::invalid_socket; + this->bound = false; } } @@ -255,4 +187,97 @@ void netkit::sock::native::native_async_sock::close() noexcept { netkit::sock::fd_t netkit::sock::native::native_async_sock::native_handle() const { return this->sockfd; +} + +netkit::io::task> +netkit::sock::native::native_async_sock::recvfrom(void* buf, size_t size) { + for (;;) { + sockaddr_storage sa{}; + socklen_t sa_len = sizeof(sa); + + auto n = platform::recvfrom( + this->sockfd, + buf, + size, + 0, + reinterpret_cast(&sa), + &sa_len + ); + + if (n >= 0) { + addr from(reinterpret_cast(&sa), sa_len); + + co_return std::pair { + static_cast(n), + std::move(from) + }; + } + + auto err = platform::last_socket_error(); + + if (err == platform::socket_err::would_block) { + co_await context_.wait_readable(sockfd); + continue; + } + + if (err == platform::socket_err::interrupted) { + continue; + } + + throw socket_error( + "recvfrom failed: " + platform::last_error_message() + ); + } +} + +netkit::io::task +netkit::sock::native::native_async_sock::sendto(const void* buf, std::size_t len, const addr& dest) { + while (true) { + co_await context_.wait_writable(sockfd); + + auto ret = platform::sendto( + sockfd, + static_cast(buf), + static_cast(len), + 0, + dest.get_sa(), + dest.get_sa_len() + ); + + if (!platform::valid_socket(ret)) { + auto err = platform::last_socket_error(); + + if (err == platform::socket_err::would_block) + continue; + + throw socket_error("sendto failed: " + platform::last_error_message()); + } + + co_return static_cast(ret); + } +} + +void netkit::sock::native::native_async_sock::bind() { + if (platform::bind(sockfd, addr_.get_sa(), addr_.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + bound = true; +} + +void netkit::sock::native::native_async_sock::bind(const addr& addr) { + if (bound) { + throw socket_error{"bind failed"}; + } + + if (platform::bind(sockfd, addr.get_sa(), addr.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + addr_ = addr; + bound = true; +} + +void netkit::sock::native::native_async_sock::unbind() noexcept { + this->close(); } \ No newline at end of file diff --git a/src/socket/native/native_sync_listener.cpp b/src/socket/native/native_sync_listener.cpp index f116806..33d3d93 100644 --- a/src/socket/native/native_sync_listener.cpp +++ b/src/socket/native/native_sync_listener.cpp @@ -18,75 +18,10 @@ #include #endif -#include - void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { netkit::platform::set_sock_opts(this->sockfd_, opts); } -const sockaddr* netkit::sock::native::native_sync_listener::get_sa() const { - return reinterpret_cast(&sa_storage_); -} - -socklen_t netkit::sock::native::native_sync_listener::get_sa_len() const { - if (addr_.is_ipv4()) return sizeof(sockaddr_in); - if (addr_.is_ipv6()) return sizeof(sockaddr_in6); -#ifndef NETKIT_DKP - if (addr_.is_file_path()) { - const auto& path = addr_.get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); - } -#endif - - throw netkit::socket_error("invalid address type"); -} - -void netkit::sock::native::native_sync_listener::prep_sa() { - memset(&sa_storage_, 0, sizeof(sa_storage_)); - - if (addr_.is_ipv4()) { - auto* sa4 = reinterpret_cast(&sa_storage_); - sa4->sin_family = AF_INET; - sa4->sin_port = htons(addr_.get_port()); - if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { - throw parsing_error("invalid IPv4 address"); - } - } else if (addr_.is_ipv6()) { - auto* sa6 = reinterpret_cast(&sa_storage_); - sa6->sin6_family = AF_INET6; - sa6->sin6_port = htons(addr_.get_port()); - - std::string ip = addr_.get_ip(); - unsigned long scope = 0; - - auto pos = ip.find('%'); - if (pos != std::string::npos) { - scope = std::stoul(ip.substr(pos + 1)); - ip = ip.substr(0, pos); // strip %scope before inet_pton - } - - if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { - throw parsing_error("invalid IPv6 address"); - } - - if (scope != 0) { - sa6->sin6_scope_id = scope; - } -#ifndef NETKIT_DKP - } else if (addr_.is_file_path()) { - auto* sa_un = reinterpret_cast(&sa_storage_); - sa_un->sun_family = AF_UNIX; - const auto& path = addr_.get_path().string(); - if (path.size() >= sizeof(sa_un->sun_path)) { - throw socket_error("UNIX socket path too long"); - } - std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); -#endif - } else { - throw ip_error("invalid address type"); - } -} - netkit::sock::native::native_sync_listener::native_sync_listener(const addr& address, type t, opt opts) : addr_(address), type_(t), opts_(opts) { sockfd_ = platform::socket(addr_.is_ipv6() ? AF_INET6 : AF_INET, SOCK_STREAM, 0); @@ -95,11 +30,10 @@ netkit::sock::native::native_sync_listener::native_sync_listener(const addr& add throw socket_error("failed creating socket"); set_sock_opts(opts_); - prep_sa(); } void netkit::sock::native::native_sync_listener::bind() { - if (platform::bind(sockfd_, get_sa(), get_sa_len()) < 0) { + if (platform::bind(sockfd_, addr_.get_sa(), addr_.get_sa_len()) < 0) { throw socket_error("bind failed"); } @@ -110,6 +44,19 @@ void netkit::sock::native::native_sync_listener::unbind() { this->close(); } +void netkit::sock::native::native_sync_listener::bind(const addr& addr) { + if (bound_) { + throw socket_error{"bind failed"}; + } + + if (platform::bind(sockfd_, addr.get_sa(), addr.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + addr_ = addr; + bound_ = true; +} + void netkit::sock::native::native_sync_listener::listen(int backlog) { if (!bound_) throw socket_error("listener not bound"); diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index f250286..6dad52f 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -12,7 +12,6 @@ */ #include -#include #include #include #include @@ -21,7 +20,6 @@ #ifdef NETKIT_WINDOWS #include #include -#include #elif NETKIT_UNIX #include #ifndef NETKIT_DKP @@ -39,71 +37,8 @@ #include #include -const sockaddr* netkit::sock::native::native_sync_sock::get_sa() const { - return reinterpret_cast(&sa_storage); -} - -socklen_t netkit::sock::native::native_sync_sock::get_sa_len() const { - if (addr_.is_ipv4()) return sizeof(sockaddr_in); - if (addr_.is_ipv6()) return sizeof(sockaddr_in6); -#ifndef NETKIT_DKP - if (addr_.is_file_path()) { - const auto& path = addr_.get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + path.string().size() + 1); - } -#endif - - throw netkit::socket_error("invalid address type"); -} - -void netkit::sock::native::native_sync_sock::prep_sa() { - memset(&sa_storage, 0, sizeof(sa_storage)); - - if (addr_.is_ipv4()) { - auto* sa4 = reinterpret_cast(&sa_storage); - sa4->sin_family = AF_INET; - sa4->sin_port = htons(addr_.get_port()); - if (inet_pton(AF_INET, addr_.get_ip().c_str(), &sa4->sin_addr) <= 0) { - throw parsing_error("invalid IPv4 address"); - } - } else if (addr_.is_ipv6()) { - auto* sa6 = reinterpret_cast(&sa_storage); - sa6->sin6_family = AF_INET6; - sa6->sin6_port = htons(addr_.get_port()); - - std::string ip = addr_.get_ip(); - unsigned long scope = 0; - - auto pos = ip.find('%'); - if (pos != std::string::npos) { - scope = std::stoul(ip.substr(pos + 1)); - ip = ip.substr(0, pos); // strip %scope before inet_pton - } - - if (inet_pton(AF_INET6, ip.c_str(), &sa6->sin6_addr) <= 0) { - throw parsing_error("invalid IPv6 address"); - } - - if (scope != 0) { - sa6->sin6_scope_id = scope; - } -#ifndef NETKIT_DKP - } else if (addr_.is_file_path()) { - auto* sa_un = reinterpret_cast(&sa_storage); - sa_un->sun_family = AF_UNIX; - const auto& path = addr_.get_path().string(); - if (path.size() >= sizeof(sa_un->sun_path)) { - throw socket_error("UNIX socket path too long"); - } - std::memcpy(sa_un->sun_path, path.c_str(), path.size() + 1); -#endif - } else { - throw ip_error("invalid address type"); - } -} - void netkit::sock::native::native_sync_sock::connect() { - if (platform::connect(sockfd, get_sa(), get_sa_len()) < 0) { + if (platform::connect(sockfd, addr_.get_sa(), addr_.get_sa_len()) < 0) { throw socket_error("connect failed: " + platform::last_error_message()); } } @@ -132,7 +67,6 @@ netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, throw socket_error{"failed to create socket"}; this->native_sync_sock::set_sock_opts(opts); - this->prep_sa(); } netkit::sock::native::native_sync_sock::native_sync_sock(fd_t existing_fd, const sock::addr& peer, sock::type t, opt opts) @@ -142,7 +76,6 @@ netkit::sock::native::native_sync_sock::native_sync_sock(fd_t existing_fd, const throw socket_error{"invalid fd"}; this->native_sync_sock::set_sock_opts(opts); - this->prep_sa(); } netkit::sock::native::native_sync_sock::~native_sync_sock() { @@ -165,11 +98,44 @@ std::size_t netkit::sock::native::native_sync_sock::recv(void* buf, std::size_t return platform::recv(this->sockfd, static_cast(buf), len, 0); } +std::pair +netkit::sock::native::native_sync_sock::recvfrom(void* buf, size_t size) { + sockaddr_storage sa{}; + socklen_t sa_len = sizeof(sa); + + auto ret = platform::recvfrom(this->sockfd, buf, size, 0, reinterpret_cast(&sa), &sa_len); + + if (ret < 0) { + throw socket_error("recvfrom failed: " + platform::last_error_message()); + } + + addr from(reinterpret_cast(&sa), sa_len); + + return { + static_cast(ret), + std::move(from) + }; +} + + +std::size_t +netkit::sock::native::native_sync_sock::sendto(const void* buf, std::size_t len, const addr& dest) { + auto ret = platform::sendto(this->sockfd, static_cast(buf), static_cast(len), 0, dest.get_sa(), dest.get_sa_len()); + + if (ret < 0) { + throw socket_error("sendto failed: " + platform::last_error_message()); + } + + return static_cast(ret); +} + void netkit::sock::native::native_sync_sock::close() noexcept { if (!platform::valid_socket(this->sockfd)) { return; } + this->bound = false; + platform::close_socket(this->sockfd); } @@ -179,4 +145,29 @@ void netkit::sock::native::native_sync_sock::close() noexcept { netkit::sock::fd_t netkit::sock::native::native_sync_sock::native_handle() const { return this->sockfd; +} + +void netkit::sock::native::native_sync_sock::bind() { + if (platform::bind(sockfd, addr_.get_sa(), addr_.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + bound = true; +} + +void netkit::sock::native::native_sync_sock::bind(const addr& addr) { + if (bound) { + throw socket_error{"bind failed"}; + } + + if (platform::bind(sockfd, addr.get_sa(), addr.get_sa_len()) < 0) { + throw socket_error("bind failed"); + } + + addr_ = addr; + bound = true; +} + +void netkit::sock::native::native_sync_sock::unbind() noexcept { + this->close(); } \ No newline at end of file diff --git a/src/udp/async_udp_datagram.cpp b/src/udp/async_udp_datagram.cpp new file mode 100644 index 0000000..a2b7896 --- /dev/null +++ b/src/udp/async_udp_datagram.cpp @@ -0,0 +1,30 @@ +#include +#include + +netkit::udp::async_udp_datagram::async_udp_datagram(netkit::io::io_context& ctx, sock::addr addr) +: addr_(addr), sock_(std::make_unique(ctx, addr, sock::type::udp)) +{} + +void netkit::udp::async_udp_datagram::bind() const { + sock_->bind(addr_); +} + +netkit::io::task netkit::udp::async_udp_datagram::send_to(std::span buffer, const sock::addr& dest) { + return sock_->sendto( + buffer.data(), + buffer.size(), + dest + ); +} + +netkit::io::task> +netkit::udp::async_udp_datagram::recv_from(std::span buffer) { + return sock_->recvfrom( + buffer.data(), + buffer.size() + ); +} + +void netkit::udp::async_udp_datagram::close() noexcept { + sock_->close(); +} \ No newline at end of file diff --git a/src/udp/udp_datagram.cpp b/src/udp/udp_datagram.cpp new file mode 100644 index 0000000..1ed0282 --- /dev/null +++ b/src/udp/udp_datagram.cpp @@ -0,0 +1,29 @@ +#include +#include + +netkit::udp::udp_datagram::udp_datagram(sock::addr addr) +: addr_(addr), sock_(std::make_unique(addr, sock::type::udp)) +{} + +void netkit::udp::udp_datagram::bind() const { + sock_->bind(addr_); +} + +std::size_t netkit::udp::udp_datagram::send_to(std::span buffer, const sock::addr& dest) { + return sock_->sendto( + buffer.data(), + buffer.size(), + dest + ); +} + +std::pair +netkit::udp::udp_datagram::recv_from(std::span buffer) { + return sock_->recvfrom( + buffer.data(), + buffer.size() + ); +} +void netkit::udp::udp_datagram::close() noexcept { + sock_->close(); +} \ No newline at end of file From 5eccaadeba9a49dbd26bb88680335356d9ce60c8 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Thu, 30 Jul 2026 16:56:05 +0200 Subject: [PATCH 14/34] fix dns socket usage --- CMakeLists.txt | 2 +- SECURITY.md | 4 +- include/netkit/dns/sync_resolver.hpp | 135 ++++++++++-------- include/netkit/socket/wolfssl/user_settings.h | 1 + 4 files changed, 81 insertions(+), 61 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99ce3dd..5a339d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(netkit LANGUAGES C CXX) option(NETKIT_ENABLE_OPENSSL "Enable OpenSSL support in netkit" OFF) option(NETKIT_ENABLE_WOLFSSL "Enable WolfSSL support in netkit" ON) option(NETKIT_ENABLE_TESTS "Enable building tests" OFF) -option(NETKIT_ENABLE_C_BINDINGS "Enable experimental C bindings for netkit" ON) +option(NETKIT_ENABLE_C_BINDINGS "Enable experimental C bindings for netkit" OFF) option(NETKIT_ENABLE_C_TESTS "Enable C building tests" OFF) option(NETKIT_ENABLE_WINDOWS_CERTSTORE "Enable getting CA certificates from the Windows store" ON) option(NETKIT_ENABLE_FALLBACK_CA "Enable fallback CA certficate (required for DevkitPro)" ON) diff --git a/SECURITY.md b/SECURITY.md index d07f956..f744bc9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,5 @@ # Reporting a Vulnerability -Please report vulnerabilities to me through email. You can email me at `contact@jacobnilsson.com`. +As of now, before the v1.0.0 release, we will discuss vulnerabilities in the open, as we don't expect +anyone to use this in security critical applications. Please file an issue on the GitHub repository, +or pull request if you have a fix. \ No newline at end of file diff --git a/include/netkit/dns/sync_resolver.hpp b/include/netkit/dns/sync_resolver.hpp index 57f4455..4d686e4 100644 --- a/include/netkit/dns/sync_resolver.hpp +++ b/include/netkit/dns/sync_resolver.hpp @@ -13,7 +13,6 @@ #ifdef NETKIT_DNS -#include #include #include #include @@ -22,6 +21,8 @@ #include #include #include +#include +#include #ifdef NETKIT_UNIX #include @@ -85,79 +86,95 @@ namespace netkit::dns { std::vector all_records; - auto send_udp = [&query](const std::string &server, - netkit::sock::addr_type family) -> std::optional > { - netkit::sock::addr addr(server, 53, family); - netkit::sock::sync_sock sock( - addr, - netkit::sock::type::udp, - netkit::sock::opt::blocking | - netkit::sock::opt::no_delay - ); - - sock.connect(); + auto send_udp = [&query](const std::string& server, netkit::sock::addr_type family) -> std::optional> { + netkit::sock::addr addr(server, 53, family); - sock.send(query.data(), query.size()); + netkit::udp::udp_datagram sock(addr); - auto resp = sock.recv(2, 4096).data; + std::array buffer{}; - if (resp.size() < 12) - return std::nullopt; + sock.send_to( + std::as_bytes(std::span(query)), + addr + ); - uint16_t flags = (resp[2] << 8) | resp[3]; + auto [size, from] = sock.recv_from(buffer); - if (flags & 0x0200) - return std::nullopt; + if (size < 12) + return std::nullopt; - return std::vector(resp.begin(), resp.end()); - }; + uint16_t flags = + (static_cast(buffer[2]) << 8) | + static_cast(buffer[3]); + if (flags & 0x0200) + return std::nullopt; - auto send_tcp = [&](const std::string& server, netkit::sock::addr_type family) -> std::optional> { - netkit::sock::addr addr(server, 53, family); - netkit::sock::sync_sock sock( - addr, - netkit::sock::type::tcp, - netkit::sock::opt::blocking | - netkit::sock::opt::no_delay + return std::vector( + reinterpret_cast(buffer.data()), + reinterpret_cast(buffer.data()) + size ); - sock.connect(); + }; + + + auto send_tcp = [&query](const std::string& server, netkit::sock::addr_type family) -> std::optional> { + netkit::sock::addr addr(server, 53, family); + netkit::tcp::tcp_stream sock(addr); - uint8_t _lenbuf[2] = { - static_cast(query.size() >> 8), - static_cast(query.size()) + sock.connect(); + + std::array lenbuf{ + std::byte(query.size() >> 8), + std::byte(query.size() & 0xFF) }; - sock.send(reinterpret_cast(_lenbuf), 2); - sock.send(reinterpret_cast(query.data()), query.size()); + if (sock.write(lenbuf).status != stream::stream_status::success) + return std::nullopt; - std::string lenbuf; - while (lenbuf.size() < 2) { - auto chunk = sock.recv(2, 2 - lenbuf.size()).data; - if (chunk.empty()) - return std::nullopt; - lenbuf += chunk; - } + if (sock.write(std::as_bytes(std::span(query))).status != stream::stream_status::success) + return std::nullopt; - uint16_t resp_len = - (static_cast(lenbuf[0]) << 8) | - static_cast(lenbuf[1]); - if (resp_len == 0) - return std::nullopt; - - std::string resp; - resp.reserve(resp_len); - - while (resp.size() < resp_len) { - size_t to_read = resp_len - resp.size(); - auto chunk = sock.recv(2, to_read).data; - if (chunk.empty()) - return std::nullopt; - resp += chunk; - } + std::array resp_len_buf{}; + std::size_t total = 0; - return std::vector(resp.begin(), resp.end()); - }; + while (total < 2) { + auto res = sock.read( + std::span(resp_len_buf).subspan(total) + ); + + if (res.status != stream::stream_status::success || res.bytes == 0) + return std::nullopt; + + total += res.bytes; + } + + uint16_t resp_len = + (static_cast(std::to_integer(resp_len_buf[0])) << 8) | + std::to_integer(resp_len_buf[1]); + + if (resp_len == 0) + return std::nullopt; + + std::vector resp(resp_len); + + total = 0; + + while (total < resp_len) { + auto res = sock.read( + std::span(resp).subspan(total) + ); + + if (res.status != stream::stream_status::success || res.bytes == 0) + return std::nullopt; + + total += res.bytes; + } + + return std::vector( + reinterpret_cast(resp.data()), + reinterpret_cast(resp.data()) + resp.size() + ); + }; auto try_server = [&](const std::string& server, netkit::sock::addr_type family) -> bool { auto udp_resp = send_udp(server, family); diff --git a/include/netkit/socket/wolfssl/user_settings.h b/include/netkit/socket/wolfssl/user_settings.h index f190d13..6bbc823 100644 --- a/include/netkit/socket/wolfssl/user_settings.h +++ b/include/netkit/socket/wolfssl/user_settings.h @@ -44,6 +44,7 @@ static int gen_seed(unsigned char* output, unsigned int sz) { #define OPENSSL_COEXIST /* uncomment if wolfssl debugging is desired + * however, netkit cmakelists defines this if -DNETKIT_DEBUG and/or -DNETKIT_WOLFSSL_DEBUG #define DEBUG_WOLFSSL */ #define WOLFSSL_ALT_CERT_CHAINS From 0fdd32c450f9e1d5952f4db7965fa2060f9e3ddc Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Thu, 30 Jul 2026 21:34:41 +0200 Subject: [PATCH 15/34] implement back support for tls/ssl this commit also removes support for the openssl backend. rationale: before, we had two backends for tls/ssl. the obvious reason to use openssl is that it's well known and available on almost all desktop and server systems. however, it's huge, does far more than we need it to do, does not function on embedded/devkitpro meaning we need the wolfssl backend regardless, and having two backends means we have to keep two backends around which may diverge and cause more unnecessary work. --- CMakeLists.txt | 47 +- README.md | 7 +- examples/cpp/socket_ssl/.gitignore | 1 + examples/cpp/socket_ssl/CMakeLists.txt | 16 + examples/cpp/socket_ssl/main.cpp | 53 ++ include/netkit/netkit.hpp | 9 +- .../netkit/socket/openssl/ssl_sync_sock.hpp | 94 --- include/netkit/socket/ssl_sync_sock.hpp | 11 - include/netkit/socket/ssl_sync_sock_enum.hpp | 19 - .../netkit/socket/wolfssl/ssl_sync_sock.hpp | 105 --- include/netkit/stream/async_socket_stream.hpp | 4 + include/netkit/stream/basic_async_stream.hpp | 4 + include/netkit/stream/basic_stream.hpp | 10 +- include/netkit/stream/socket_stream.hpp | 4 + include/netkit/stream/tls_stream.hpp | 5 + include/netkit/stream/tls_stream_enum.hpp | 18 + include/netkit/stream/wolfssl/tls_stream.hpp | 39 ++ .../wolfssl/user_settings.h | 0 include/netkit/tcp/tcp_stream.hpp | 3 + src/socket/native/native_sync_socket.cpp | 2 - src/socket/openssl/ssl_sync_sock.cpp | 561 --------------- src/socket/wolfssl/ssl_sync_sock.cpp | 636 ------------------ src/stream/wolfssl/tls_stream.cpp | 234 +++++++ 23 files changed, 400 insertions(+), 1482 deletions(-) create mode 100644 examples/cpp/socket_ssl/.gitignore create mode 100644 examples/cpp/socket_ssl/CMakeLists.txt create mode 100644 examples/cpp/socket_ssl/main.cpp delete mode 100644 include/netkit/socket/openssl/ssl_sync_sock.hpp delete mode 100644 include/netkit/socket/ssl_sync_sock.hpp delete mode 100644 include/netkit/socket/ssl_sync_sock_enum.hpp delete mode 100644 include/netkit/socket/wolfssl/ssl_sync_sock.hpp create mode 100644 include/netkit/stream/tls_stream.hpp create mode 100644 include/netkit/stream/tls_stream_enum.hpp create mode 100644 include/netkit/stream/wolfssl/tls_stream.hpp rename include/netkit/{socket => stream}/wolfssl/user_settings.h (100%) delete mode 100644 src/socket/openssl/ssl_sync_sock.cpp delete mode 100644 src/socket/wolfssl/ssl_sync_sock.cpp create mode 100644 src/stream/wolfssl/tls_stream.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a339d9..c6323c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.28) project(netkit LANGUAGES C CXX) -option(NETKIT_ENABLE_OPENSSL "Enable OpenSSL support in netkit" OFF) option(NETKIT_ENABLE_WOLFSSL "Enable WolfSSL support in netkit" ON) option(NETKIT_ENABLE_TESTS "Enable building tests" OFF) option(NETKIT_ENABLE_C_BINDINGS "Enable experimental C bindings for netkit" OFF) @@ -24,10 +23,6 @@ else() set(NETKIT_DKP OFF) endif() -if (NETKIT_DKP) - set(NETKIT_ENABLE_OPENSSL OFF CACHE BOOL "" FORCE) -endif() - if (NETKIT_BUILD_SHARED) set(NETKIT_LIB_TYPE SHARED) else() @@ -69,8 +64,6 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/dns/query_builder.cpp src/socket/native/native_sync_socket.cpp src/dns/cache.cpp - src/socket/openssl/ssl_sync_sock.cpp - src/socket/wolfssl/ssl_sync_sock.cpp src/http/predefined.cpp src/http/sync_client.cpp src/crypto/windows/certs.cpp @@ -95,6 +88,7 @@ add_library(netkit ${NETKIT_LIB_TYPE} src/io/windows/io_backend.cpp src/udp/udp_datagram.cpp src/udp/async_udp_datagram.cpp + src/stream/wolfssl/tls_stream.cpp ) add_library(netkit::netkit ALIAS netkit) @@ -118,40 +112,7 @@ target_include_directories(netkit $ ) -if (NETKIT_ENABLE_OPENSSL AND NETKIT_ENABLE_WOLFSSL) - message(STATUS "NETKIT_ENABLE_WOLFSSL AND NETKIT_ENABLE_OPENSSL cannot be used simultaneously.") - set(NETKIT_ENABLE_OPENSSL Off) -endif() - -if (NETKIT_ENABLE_OPENSSL) - find_package(OpenSSL) - - if (NOT NETKIT_BUILD_SHARED) - set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) - endif() - - if (NOT OpenSSL_FOUND) - message(STATUS "OpenSSL not found. You can install it via system package manager or vcpkg.") - - if (DEFINED ENV{VCPKG_ROOT}) - set(CMAKE_TOOLCHAIN_FILE - "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "" - ) - find_package(OpenSSL REQUIRED) - else() - message(STATUS "Not using VCPKG") - endif() - endif() - - if (OpenSSL_FOUND) - message(STATUS "OpenSSL enabled in netkit") - target_compile_definitions(netkit PUBLIC NETKIT_OPENSSL) - target_link_libraries(netkit PUBLIC OpenSSL::SSL OpenSSL::Crypto) - else() - message(FATAL_ERROR "OpenSSL requested but not found.") - endif() -elseif (NETKIT_ENABLE_WOLFSSL) +if (NETKIT_ENABLE_WOLFSSL) find_package(wolfSSL QUIET) if (NOT NETKIT_BUILD_SHARED) @@ -181,7 +142,7 @@ elseif (NETKIT_ENABLE_WOLFSSL) WOLFSSL_USER_SETTINGS_FILE="user_settings.h" ) - include_directories(${CMAKE_SOURCE_DIR}/include/netkit/sock/wolfssl) + include_directories(${CMAKE_SOURCE_DIR}/include/netkit/stream/wolfssl) endif() FetchContent_Declare( @@ -288,7 +249,7 @@ install(TARGETS netkit if (NETKIT_DKP AND NETKIT_ENABLE_WOLFSSL) install( FILES - ${CMAKE_CURRENT_SOURCE_DIR}/include/netkit/socket/wolfssl/user_settings.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/netkit/stream/wolfssl/user_settings.h DESTINATION include/wolfssl/wolfcrypt/ ) diff --git a/README.md b/README.md index 767cfc2..9a1a28b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The other alternative is to use the latest release, which doesn't have a changin - HTTP/1.0 and HTTP/1.1 body parser, including headers and body. - IPv4 and IPv6 support - TCP and UDP support -- TLS/SSL sockets and HTTP abstraction (OpenSSL or WolfSSL integration) +- TLS/SSL sockets and HTTP abstraction (WolfSSL integration) - DNS resolution - Network interface enumeration - Exceptions for errors @@ -38,7 +38,7 @@ Still missing: ## Dependencies -- OpenSSL or WolfSSL (optional) +- WolfSSL (optional) - C++23 compiler - CMake @@ -46,8 +46,7 @@ Still missing: netkit's CMakeLists.txt offers multiple options: -- NETKIT_ENABLE_OPENSSL: Enable OpenSSL-backed SSL/TLS (not compatible with DevkitPro) -- NETKIT_ENABLE_WOLFSSL: Enable WolfSSL-backed SSL/TLS (cannot be used with NETKIT_ENABLE_OPENSSL) +- NETKIT_ENABLE_WOLFSSL: Enable WolfSSL-backed SSL/TLS - NETKIT_ENABLE_TESTS: Enable Catch2 tests for the main C++ library - NETKIT_ENABLE_C_BINDINGS: Enable C bindings for netkit - NETKIT_ENABLE_C_TESTS: Enable tests for netkit C bindings diff --git a/examples/cpp/socket_ssl/.gitignore b/examples/cpp/socket_ssl/.gitignore new file mode 100644 index 0000000..3c34c2f --- /dev/null +++ b/examples/cpp/socket_ssl/.gitignore @@ -0,0 +1 @@ +*build* \ No newline at end of file diff --git a/examples/cpp/socket_ssl/CMakeLists.txt b/examples/cpp/socket_ssl/CMakeLists.txt new file mode 100644 index 0000000..ce3fc05 --- /dev/null +++ b/examples/cpp/socket_ssl/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.11) + +project(socket-ssl LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket-ssl main.cpp) + +target_link_libraries(socket-ssl PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_ssl/main.cpp b/examples/cpp/socket_ssl/main.cpp new file mode 100644 index 0000000..2d4e0d7 --- /dev/null +++ b/examples/cpp/socket_ssl/main.cpp @@ -0,0 +1,53 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file main.cpp + * @license MIT + * @note Example code using the Netkit library. + * @note See examples/socket_ssl for a TLS/SSL version of this example. + */ +#include +#include +#include +#include +#include + +int main() { + netkit::sock::addr addr{"google.com", 443, netkit::sock::addr_type::hostname}; + std::unique_ptr connector_ = std::make_unique(addr); + + connector_->connect(); + + netkit::stream::tls_stream connector(std::move(connector_), + netkit::stream::version::TLS_1_1, + netkit::stream::verification::none + ); + + connector.perform_handshake(); + + constexpr std::string_view request = "GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n"; + + auto write_result = connector.write_all(request); + + if (write_result.status != netkit::stream::stream_status::success) { + throw std::runtime_error{"write failed"}; + } + + auto response = connector.read_all_string(); + + connector.close(); + + std::ofstream file("response.txt"); + + if (file.is_open()) { + file.write(response.data(), response.size()); + file.close(); + } else { + std::cerr << "Failed to open file" << std::endl; + } + + std::cout << "Response written to response.txt" << std::endl; +} \ No newline at end of file diff --git a/include/netkit/netkit.hpp b/include/netkit/netkit.hpp index d60f4c5..3ba636f 100644 --- a/include/netkit/netkit.hpp +++ b/include/netkit/netkit.hpp @@ -12,6 +12,8 @@ */ #pragma once +#define NETKIT_NETKIT + // Essential headers #include #include @@ -39,10 +41,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -84,6 +82,9 @@ #include #include #include +#include +#include +#include // tcp #include diff --git a/include/netkit/socket/openssl/ssl_sync_sock.hpp b/include/netkit/socket/openssl/ssl_sync_sock.hpp deleted file mode 100644 index f2584d1..0000000 --- a/include/netkit/socket/openssl/ssl_sync_sock.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file ssl_sync_sock.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a synchronous SSL/TLS socket class wrapping a basic synchronous socket. - * @see netkit::sock::basic_sync_sock - * @see netkit::sock::sync_sock - */ -#pragma once - -#ifdef NETKIT_OPENSSL - -#include <../native/basic_native_sync_sock.hpp> -#include -#include -#include -#include -#include -#include -#include - -namespace netkit::sock { - -class NETKIT_API ssl_sync_sock : public basic_sync_sock { -public: - explicit ssl_sync_sock(std::unique_ptr underlying, - mode ssl_mode, version ssl_version = version::TLS_1_2, - verification ssl_verification = verification::peer, - std::string cert_path = "", - std::string key_path = ""); - ~ssl_sync_sock() override; - void connect() override; - void bind() override; - void unbind() override; - void listen(int backlog) override; - void listen() override; - - bool is_secure() const; - - std::unique_ptr accept() override; - std::unique_ptr accept_explicit_ssl(); - int send(const void* buf, size_t len) override; - void send(const std::string& buf) override; - recv_result recv(int timeout_seconds) override; - recv_result recv(int timeout_seconds, const std::string& match) override; - recv_result recv(int timeout_seconds, const std::string& match, size_t eof) override; - recv_result recv(int timeout_seconds, size_t eof) override; - recv_result recv() override; - std::string overflow_bytes() const override; - void clear_overflow_bytes() const override; - void close() override; - void perform_handshake(); - [[nodiscard]] netkit::sock::addr get_peer() const override; - addr& get_addr() override; - const addr& get_addr() const override; -private: - mutable std::string overflow_; - mutable std::mutex state_mtx_; - - std::unique_ptr underlying_sock_; - mode ssl_mode_; - version version_; - verification verification_; - std::string cert_path_; - std::string key_path_; - - SSL_CTX* ctx_ = nullptr; - SSL* ssl_ = nullptr; - - BIO* read_bio_ = nullptr; - BIO* write_bio_ = nullptr; - - bool handshake_complete_ = false; - bool read_eof_ = false; - mutable bool transport_eof_ = false; - - static void init_openssl_once(); - void create_ssl_context(); - void create_ssl_object(); - void create_bio(); - void drain_write_bio() const; - void feed_read_bio_blocking() const; - void ensure_ready() const; - recv_result recv_internal(int, const std::string* match, size_t eof) const; - static void throw_ssl_error(const std::string& msg); -}; -} - -#endif // NETKIT_OPENSSL \ No newline at end of file diff --git a/include/netkit/socket/ssl_sync_sock.hpp b/include/netkit/socket/ssl_sync_sock.hpp deleted file mode 100644 index 252daff..0000000 --- a/include/netkit/socket/ssl_sync_sock.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#if defined(NETKIT_ENABLE_OPENSSL) && defined(NETKIT_ENABLE_WOLFSSL) -#error "Only one SSL backend can be enabled at compile time" -#endif - -#if defined(NETKIT_OPENSSL) -#include -#elif defined(NETKIT_WOLFSSL) -#include -#endif \ No newline at end of file diff --git a/include/netkit/socket/ssl_sync_sock_enum.hpp b/include/netkit/socket/ssl_sync_sock_enum.hpp deleted file mode 100644 index dee0420..0000000 --- a/include/netkit/socket/ssl_sync_sock_enum.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -namespace netkit::sock { - enum class NETKIT_API mode { - client, - server - }; - - enum class NETKIT_API version { - TLS_1_1, - TLS_1_2, - TLS_1_3 - }; - - enum class NETKIT_API verification { - peer, - none - }; -} \ No newline at end of file diff --git a/include/netkit/socket/wolfssl/ssl_sync_sock.hpp b/include/netkit/socket/wolfssl/ssl_sync_sock.hpp deleted file mode 100644 index f58ee44..0000000 --- a/include/netkit/socket/wolfssl/ssl_sync_sock.hpp +++ /dev/null @@ -1,105 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file ssl_sync_sock.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a synchronous SSL/TLS socket class wrapping a basic synchronous socket. - * @see netkit::sock::basic_sync_sock - * @see netkit::sock::sync_sock - */ -#pragma once - -#ifdef NETKIT_WOLFSSL - -#include -#include - -#include <../native/basic_native_sync_sock.hpp> -#include -#include -#include -#include -#include - -namespace netkit::sock { - class NETKIT_API ssl_sync_sock : public basic_sync_sock { - public: - explicit ssl_sync_sock(std::unique_ptr underlying, - mode ssl_mode, - version ssl_version = version::TLS_1_2, - verification ssl_verification = verification::peer, - std::string cert_path = "", - std::string key_path = ""); - - ~ssl_sync_sock() override; - - void connect() override; - void bind() override; - void unbind() override; - void listen(int backlog) override; - void listen() override; - - bool is_secure() const; - - [[nodiscard]] std::unique_ptr accept() override; - [[nodiscard]] std::unique_ptr accept_explicit_ssl(); - - int send(const void* buf, size_t len) override; - void send(const std::string& buf) override; - - [[nodiscard]] recv_result recv(int timeout_seconds) override; - [[nodiscard]] recv_result recv(int timeout_seconds, const std::string& match) override; - [[nodiscard]] recv_result recv(int timeout_seconds, const std::string& match, size_t eof) override; - [[nodiscard]] recv_result recv(int timeout_seconds, size_t eof) override; - [[nodiscard]] recv_result recv() override; - - std::string overflow_bytes() const override; - void clear_overflow_bytes() const override; - - void close() override; - void perform_handshake(); - - [[nodiscard]] netkit::sock::addr get_peer() const override; - addr& get_addr() override; - const addr& get_addr() const override; - private: - mutable std::string overflow_; - mutable std::mutex state_mtx_; - - std::unique_ptr underlying_sock_; - - mode ssl_mode_; - version version_; - verification verification_; - - std::string cert_path_; - std::string key_path_; - std::string ca_path_; - - WOLFSSL_CTX* ctx_ = nullptr; - WOLFSSL* ssl_ = nullptr; - - bool handshake_complete_ = false; - mutable bool read_eof_ = false; - bool transport_eof_ = false; - - static void init_wolfssl_once(); - - void create_ssl_context(); - void create_ssl_object(); - - void ensure_ready() const; - - recv_result recv_internal(int timeout, - const std::string* match, - size_t eof) const; - - static void throw_ssl_error(const std::string& msg); - }; -} - -#endif \ No newline at end of file diff --git a/include/netkit/stream/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp index 04c1d62..55a43d0 100644 --- a/include/netkit/stream/async_socket_stream.hpp +++ b/include/netkit/stream/async_socket_stream.hpp @@ -18,6 +18,10 @@ class NETKIT_API async_socket_stream : public basic_async_stream { void close() noexcept override; [[nodiscard]] sock::addr peer() const; + + std::optional get_addr() override { + return socket_->get_addr(); + } private: std::unique_ptr socket_; }; diff --git a/include/netkit/stream/basic_async_stream.hpp b/include/netkit/stream/basic_async_stream.hpp index 5234a21..df097a0 100644 --- a/include/netkit/stream/basic_async_stream.hpp +++ b/include/netkit/stream/basic_async_stream.hpp @@ -88,6 +88,10 @@ class basic_async_stream { } virtual void close() noexcept = 0; + + virtual std::optional get_addr() { + return {}; + } }; } \ No newline at end of file diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index 381de34..9e4d42d 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -1,11 +1,11 @@ #pragma once +#include #include - #include -#include -#include #include +#include +#include namespace netkit::stream { @@ -95,6 +95,10 @@ class basic_stream { } virtual void close() noexcept = 0; + + virtual std::optional get_addr() { + return {}; + } }; } \ No newline at end of file diff --git a/include/netkit/stream/socket_stream.hpp b/include/netkit/stream/socket_stream.hpp index d1b1d71..516bc34 100644 --- a/include/netkit/stream/socket_stream.hpp +++ b/include/netkit/stream/socket_stream.hpp @@ -18,6 +18,10 @@ class socket_stream : public basic_stream { void close() noexcept override; [[nodiscard]] sock::addr peer() const; + + std::optional get_addr() override { + return socket_->get_addr(); + } private: std::unique_ptr socket_; }; diff --git a/include/netkit/stream/tls_stream.hpp b/include/netkit/stream/tls_stream.hpp new file mode 100644 index 0000000..8d4bc15 --- /dev/null +++ b/include/netkit/stream/tls_stream.hpp @@ -0,0 +1,5 @@ +#pragma once + +#define NETKIT_TLS_STREAM + +#include \ No newline at end of file diff --git a/include/netkit/stream/tls_stream_enum.hpp b/include/netkit/stream/tls_stream_enum.hpp new file mode 100644 index 0000000..56315be --- /dev/null +++ b/include/netkit/stream/tls_stream_enum.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace netkit::stream { + +enum class NETKIT_API version { + TLS_1_1, + TLS_1_2, + TLS_1_3 +}; + +enum class NETKIT_API verification { + peer, + none +}; + +} \ No newline at end of file diff --git a/include/netkit/stream/wolfssl/tls_stream.hpp b/include/netkit/stream/wolfssl/tls_stream.hpp new file mode 100644 index 0000000..62af88b --- /dev/null +++ b/include/netkit/stream/wolfssl/tls_stream.hpp @@ -0,0 +1,39 @@ +#pragma once + +#ifdef NETKIT_WOLFSSL + +#include +#include +#include +#include + +namespace netkit::stream { +class tls_stream : public netkit::stream::basic_stream { +public: + explicit tls_stream(std::unique_ptr stream, + version ver = version::TLS_1_2, verification verif = verification::peer, + const std::string& ca_cert = {}); + + ~tls_stream() override; + + void perform_handshake() const; + + netkit::stream::stream_result read(std::span buffer) override; + netkit::stream::stream_result write(std::span buffer) override; + + void close() noexcept override; + +private: + std::unique_ptr stream_; + version version_; + verification verification_; + + std::string ca_cert_; + + WOLFSSL_CTX* ctx_{}; + WOLFSSL* ssl_{}; +}; + +} + +#endif \ No newline at end of file diff --git a/include/netkit/socket/wolfssl/user_settings.h b/include/netkit/stream/wolfssl/user_settings.h similarity index 100% rename from include/netkit/socket/wolfssl/user_settings.h rename to include/netkit/stream/wolfssl/user_settings.h diff --git a/include/netkit/tcp/tcp_stream.hpp b/include/netkit/tcp/tcp_stream.hpp index ec58e36..4b9e753 100644 --- a/include/netkit/tcp/tcp_stream.hpp +++ b/include/netkit/tcp/tcp_stream.hpp @@ -25,6 +25,9 @@ class tcp_stream : public stream::basic_stream { void close() noexcept override; [[nodiscard]] sock::addr peer() const; + std::optional get_addr() override { + return stream_.get_addr(); + } stream::socket_stream& stream(); diff --git a/src/socket/native/native_sync_socket.cpp b/src/socket/native/native_sync_socket.cpp index 6dad52f..095f0ba 100644 --- a/src/socket/native/native_sync_socket.cpp +++ b/src/socket/native/native_sync_socket.cpp @@ -48,8 +48,6 @@ void netkit::sock::native::native_sync_sock::set_sock_opts(opt opts) { } netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { - this->sockfd = -1; - if (!addr.is_file_path()) { if (addr.get_ip().empty()) { throw socket_error("IP address/file path is empty"); diff --git a/src/socket/openssl/ssl_sync_sock.cpp b/src/socket/openssl/ssl_sync_sock.cpp deleted file mode 100644 index fa120e0..0000000 --- a/src/socket/openssl/ssl_sync_sock.cpp +++ /dev/null @@ -1,561 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file ssl_sync_sock.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the synchronous SSL/TLS socket class using OpenSSL. - */ -#ifdef NETKIT_OPENSSL - -#include <../../../include/netkit/socket/native/native_sync_sock.hpp> -#include -#include -#ifdef NETKIT_ENABLE_FALLBACK_CA -#include -#endif - -#ifdef NETKIT_WINDOWS -#include -#include -#endif - -#include -#include -#include - -#include -#include - -template -std::unique_ptr unique_dynamic_cast(std::unique_ptr base) -{ - T* ptr = dynamic_cast(base.get()); - - if (!ptr) - return {}; - - base.release(); - return std::unique_ptr(ptr); -} - -netkit::sock::ssl_sync_sock::ssl_sync_sock(std::unique_ptr underlying, - mode ssl_mode, version ssl_version, - verification ssl_verification, - std::string cert_path, - std::string key_path) - : underlying_sock_(std::move(underlying)), - ssl_mode_(ssl_mode), - version_(ssl_version), - verification_(ssl_verification), - cert_path_(std::move(cert_path)), key_path_(std::move(key_path)) -{ - init_openssl_once(); - create_ssl_context(); - create_ssl_object(); - create_bio(); - - if (ssl_mode_ == mode::client) { - auto underlying_hostname = underlying_sock_->get_addr().get_hostname(); - if (underlying_hostname.empty()) { - throw std::runtime_error{"empty hostname"}; - } - SSL_set_tlsext_host_name(ssl_, underlying_hostname.c_str()); - } -} - -netkit::sock::ssl_sync_sock::~ssl_sync_sock() { - this->ssl_sync_sock::close(); -} - -void netkit::sock::ssl_sync_sock::connect() { - if (ssl_mode_ != mode::client) - throw std::runtime_error("connect() only valid for client mode"); - - underlying_sock_->connect(); -} - -void netkit::sock::ssl_sync_sock::bind() { - underlying_sock_->bind(); -} - -void netkit::sock::ssl_sync_sock::unbind() { - underlying_sock_->unbind(); -} - -void netkit::sock::ssl_sync_sock::listen(int backlog) { - underlying_sock_->listen(backlog); -} - -void netkit::sock::ssl_sync_sock::listen() { - underlying_sock_->listen(); -} - -bool netkit::sock::ssl_sync_sock::is_secure() const { - return ssl_ && SSL_is_init_finished(ssl_); -} - -std::unique_ptr netkit::sock::ssl_sync_sock::accept() { - auto client = underlying_sock_->accept(); - - auto ssl_client = std::make_unique( - std::move(client), - mode::server, - version_, - verification_, - cert_path_, - key_path_); - - ssl_client->perform_handshake(); - - return ssl_client; -} - -std::unique_ptr netkit::sock::ssl_sync_sock::accept_explicit_ssl() { - auto accepted = accept(); - return unique_dynamic_cast(std::move(accepted)); -} - -int netkit::sock::ssl_sync_sock::send(const void* buf, size_t len) { - ensure_ready(); - - size_t offset = 0; - while (offset < len) { - int ret = SSL_write( - ssl_, - static_cast(buf) + offset, - static_cast(len - offset)); - - drain_write_bio(); - - if (ret > 0) { - offset += ret; - continue; - } - - int err = SSL_get_error(ssl_, ret); - if (err == SSL_ERROR_WANT_READ) { - feed_read_bio_blocking(); - } else if (err == SSL_ERROR_WANT_WRITE) { - // retry - } else { - throw_ssl_error("SSL_write failed"); - } - } - - return static_cast(len); -} - -void netkit::sock::ssl_sync_sock::send(const std::string& buf) { - static_cast(send(buf.data(), buf.size())); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds) { - return recv_internal(timeout_seconds, nullptr, 0); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, const std::string& match) { - return recv_internal(timeout_seconds, &match, 0); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, const std::string& match, size_t eof) { - return recv_internal(timeout_seconds, &match, eof); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, size_t eof) { - return recv_internal(timeout_seconds, nullptr, eof); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv() { - for (;;) { - char buf[8192]; - - int n = SSL_read(ssl_, buf, sizeof(buf)); - - if (n > 0) { - return {{buf, buf + n}, recv_status::success}; - } - - switch (int err = SSL_get_error(ssl_, n)) { - case SSL_ERROR_ZERO_RETURN: - return {{}, recv_status::closed}; - - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - continue; - - default: - throw netkit::socket_error( - "SSL_read failed: " + std::to_string(err) - ); - } - } -} - -std::string netkit::sock::ssl_sync_sock::overflow_bytes() const { - return overflow_; -} - -void netkit::sock::ssl_sync_sock::clear_overflow_bytes() const { - overflow_.clear(); -} - -netkit::sock::addr netkit::sock::ssl_sync_sock::get_peer() const { - return underlying_sock_->get_peer(); -} - -void netkit::sock::ssl_sync_sock::close() { - std::scoped_lock lk(state_mtx_); - - if (ssl_) { - SSL_shutdown(ssl_); - SSL_free(ssl_); - ssl_ = nullptr; - } - - if (ctx_) { - SSL_CTX_free(ctx_); - ctx_ = nullptr; - } - - if (underlying_sock_) { - underlying_sock_->close(); - } -} - -void netkit::sock::ssl_sync_sock::perform_handshake() { - while (!SSL_is_init_finished(ssl_)) { - int ret = SSL_do_handshake(ssl_); - drain_write_bio(); - - if (ret == 1) - break; - - int err = SSL_get_error(ssl_, ret); - if (err == SSL_ERROR_WANT_READ) { - feed_read_bio_blocking(); - } else if (err == SSL_ERROR_WANT_WRITE) { - continue; - } else { - throw_ssl_error("TLS handshake failed"); - } - } - - handshake_complete_ = true; -} - -void netkit::sock::ssl_sync_sock::init_openssl_once() { - static bool initialized = false; - static std::mutex m; - std::scoped_lock lk(m); - if (!initialized) { - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - initialized = true; - } -} - -void netkit::sock::ssl_sync_sock::create_ssl_context() { - const SSL_METHOD* method = (ssl_mode_ == mode::client) - ? TLS_client_method() - : TLS_server_method(); - ctx_ = SSL_CTX_new(method); - if (!ctx_) throw_ssl_error("SSL_CTX_new failed"); - - long version{}; - - switch (version_) { - case version::TLS_1_1: - version = TLS1_1_VERSION; - break; - case version::TLS_1_2: - version = TLS1_2_VERSION; - break; - case version::TLS_1_3: - version = TLS1_3_VERSION; - break; - } - - SSL_CTX_set_min_proto_version(ctx_, version); - - int verification{}; - - switch (verification_) { - case verification::none: - verification = SSL_VERIFY_NONE; - break; - case verification::peer: - verification = SSL_VERIFY_PEER; - break; - } - - if (ssl_mode_ == mode::server) { - if (SSL_CTX_use_certificate_file(ctx_, cert_path_.c_str(), SSL_FILETYPE_PEM) <= 0) - throw_ssl_error("Failed to load certificate"); - if (SSL_CTX_use_PrivateKey_file(ctx_, key_path_.c_str(), SSL_FILETYPE_PEM) <= 0) - throw_ssl_error("Failed to load private key"); - } else { - SSL_CTX_set_verify(ctx_, verification, nullptr); - SSL_CTX_set_verify_depth(ctx_, 10); - SSL_CTX_set_default_verify_paths(ctx_); - - if (const char* ca_path = std::getenv("SSL_CERT_FILE")) { - if (!SSL_CTX_load_verify_locations(ctx_, ca_path, nullptr)) { - throw std::runtime_error{"failed to load ca bundle from environment variable (SSL_CERT_FILE=" + std::string(ca_path) + ")"}; - } - } - - if (!cert_path_.empty()) { - if (SSL_CTX_load_verify_locations(ctx_, cert_path_.c_str(), nullptr) != 1) { - BIO* bio = BIO_new_mem_buf(cert_path_.data(), static_cast(cert_path_.size())); - if (!bio) throw std::runtime_error("failed to create BIO"); - - while (true) { - X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); - if (!cert) break; - if (X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx_), cert) != 1) { - X509_free(cert); - BIO_free(bio); - throw std::runtime_error("failed to add certificate to store"); - } - X509_free(cert); - } - BIO_free(bio); - } - } - - const auto has_usable_certs = [](const SSL_CTX* ctx) -> bool { - if (!ctx) return false; - - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - if (!store) return false; - - STACK_OF(X509_OBJECT)* objs = X509_STORE_get0_objects(store); - if (!objs) return false; - - for (int i = 0; i < sk_X509_OBJECT_num(objs); ++i) { - X509_OBJECT* obj = sk_X509_OBJECT_value(objs, i); - if (!obj) continue; - - if (X509_OBJECT_get_type(obj) == X509_LU_X509) { - return true; - } - } - - return false; - }; -#ifdef NETKIT_WINDOWS -#ifdef NETKIT_ENABLE_WINDOWS_CERTSTORE - const auto get_localappdata = []() -> std::filesystem::path { - const std::string folder_name = "netkit"; - - std::filesystem::path base_path; - - char appdata[MAX_PATH]; - DWORD len = GetEnvironmentVariableA("LOCALAPPDATA", appdata, sizeof(appdata)); - if (len > 0) { - base_path = appdata; - } else { - base_path = std::filesystem::temp_directory_path(); - } - base_path /= folder_name; - - std::filesystem::create_directories(base_path); - return base_path; - }; - - std::filesystem::path path = (get_localappdata() / "ca-bundle.pem").string(); - if (!has_usable_certs(ctx_) && crypto::windows::is_outdated(path.wstring())) { - std::filesystem::remove(path); - if (!crypto::windows::export_certs(path.wstring())) { - throw std::runtime_error("failed to export certificates"); - } - } - - const std::string path_ = path.string(); - if (!SSL_CTX_load_verify_locations(ctx_, path_.c_str(), nullptr)) { - throw std::runtime_error{"failed to load certificate location"}; - } -#endif -#endif - -#ifdef NETKIT_ENABLE_FALLBACK_CA - auto load_ca_bundle = [](SSL_CTX* ctx, std::string_view pem) -> bool { - BIO* bio = BIO_new_mem_buf(pem.data(), static_cast(pem.size())); - if (!bio) return false; - - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - if (!store) { - BIO_free(bio); - return false; - } - - bool any_loaded = false; - - while (true) { - X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); - if (!cert) break; - - if (X509_STORE_add_cert(store, cert) == 1) { - any_loaded = true; - } - - X509_free(cert); - } - - BIO_free(bio); - return any_loaded; - }; - - if (!has_usable_certs(ctx_) && verification == SSL_VERIFY_PEER) { - if (!load_ca_bundle(ctx_, crypto::fallback_ca)) { - throw std::runtime_error("failed to load certificates"); - } - } -#endif - - X509_VERIFY_PARAM_set1_host(SSL_CTX_get0_param(ctx_), - underlying_sock_->get_addr().get_hostname().c_str(), - 0); - } -} - -void netkit::sock::ssl_sync_sock::create_ssl_object() { - ssl_ = SSL_new(ctx_); - if (!ssl_) throw_ssl_error("SSL_new failed"); -} - -void netkit::sock::ssl_sync_sock::create_bio() { - read_bio_ = BIO_new(BIO_s_mem()); - BIO_set_mem_eof_return(read_bio_, -1); - write_bio_ = BIO_new(BIO_s_mem()); - if (!read_bio_ || !write_bio_) - throw_ssl_error("Failed to create memory BIOs"); - - SSL_set_bio(ssl_, read_bio_, write_bio_); - - if (ssl_mode_ == mode::client) { - SSL_set_connect_state(ssl_); - } else { - SSL_set_accept_state(ssl_); - } -} - -void netkit::sock::ssl_sync_sock::drain_write_bio() const { - char buf[4096]; - int n; - - while ((n = BIO_read(write_bio_, buf, sizeof(buf))) > 0) { - underlying_sock_->send(buf, n); - } -} - -void netkit::sock::ssl_sync_sock::feed_read_bio_blocking() const { - auto res = underlying_sock_->recv(); - - if (res.status == sock::recv_status::closed) { - BIO_set_mem_eof_return(read_bio_, -1); - transport_eof_ = true; - return; - } - - if (res.status != sock::recv_status::success) - throw std::runtime_error("Socket read failed"); - - if (!res.data.empty()) { - int written = BIO_write( - read_bio_, - res.data.data(), - static_cast(res.data.size())); - if (written <= 0) - throw_ssl_error("BIO_write failed"); - } -} - -void netkit::sock::ssl_sync_sock::ensure_ready() const { - if (!ssl_) throw std::runtime_error("SSL socket closed"); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv_internal(int, const std::string* match, size_t eof) const { - ensure_ready(); - sock::recv_result result; - - if (!overflow_.empty()) { - result.data = std::exchange(overflow_, ""); - return result; - } - - while (true) { - char buf[4096]; - int ret = SSL_read(ssl_, buf, sizeof(buf)); - drain_write_bio(); - - if (ret > 0) { - result.data.append(buf, ret); - } else { - int err = SSL_get_error(ssl_, ret); - - if (err == SSL_ERROR_WANT_READ) { - if (transport_eof_) { - result.status = sock::recv_status::closed; - break; - } - - const_cast(this)->feed_read_bio_blocking(); - continue; - } else if (err == SSL_ERROR_SYSCALL) { - if (ret == 0) { - result.status = sock::recv_status::closed; - } else { - result.status = sock::recv_status::error; - } - break; - } else if (err == SSL_ERROR_WANT_WRITE) { - continue; - } else if (err == SSL_ERROR_ZERO_RETURN) { - result.status = sock::recv_status::closed; - break; - } else { - result.status = sock::recv_status::error; - break; - } - } - - if (match && !match->empty()) { - auto pos = result.data.find(*match); - if (pos != std::string::npos) { - overflow_ = result.data.substr(pos + match->size()); - result.data.resize(pos + match->size()); - break; - } - } - - if (eof && result.data.size() >= eof) { - overflow_ = result.data.substr(eof); - result.data.resize(eof); - break; - } - } - - return result; -} - -void netkit::sock::ssl_sync_sock::throw_ssl_error(const std::string& msg) { - char buf[256]; - ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); - throw std::runtime_error(msg + ": " + buf); -} - -netkit::sock::addr& netkit::sock::ssl_sync_sock::get_addr() { - return this->underlying_sock_->get_addr(); -} - -const netkit::sock::addr& netkit::sock::ssl_sync_sock::get_addr() const { - return this->underlying_sock_->get_addr(); -} - -#endif \ No newline at end of file diff --git a/src/socket/wolfssl/ssl_sync_sock.cpp b/src/socket/wolfssl/ssl_sync_sock.cpp deleted file mode 100644 index 83e8045..0000000 --- a/src/socket/wolfssl/ssl_sync_sock.cpp +++ /dev/null @@ -1,636 +0,0 @@ -/** netkit - * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. - * - * Copyright (c) 2025-2026 Jacob Nilsson - * Licensed under the MIT License. - * - * @file ssl_sync_sock.cpp - * @license MIT - * @note Part of the Netkit library. - * @brief Implementation of the synchronous SSL/TLS socket class using WolfSSL. - */ -#ifdef NETKIT_WOLFSSL -#include -#include -#include -#include -#include - -#ifdef NETKIT_ENABLE_FALLBACK_CA -#include -#endif - -#ifdef NETKIT_WINDOWS -#include -#include -#endif - -#include -#include - -template -std::unique_ptr unique_dynamic_cast(std::unique_ptr base) -{ - T* ptr = dynamic_cast(base.get()); - - if (!ptr) - return {}; - - base.release(); - return std::unique_ptr(ptr); -} - -netkit::sock::ssl_sync_sock::ssl_sync_sock(std::unique_ptr underlying, - mode ssl_mode, version ssl_version, - verification ssl_verification, - std::string cert_path, - std::string key_path) - : underlying_sock_(std::move(underlying)), - ssl_mode_(ssl_mode), - version_(ssl_version), - verification_(ssl_verification), - cert_path_(std::move(cert_path)), key_path_(std::move(key_path)) -{ - init_wolfssl_once(); - create_ssl_context(); - create_ssl_object(); -} - -netkit::sock::ssl_sync_sock::~ssl_sync_sock() { - ssl_sync_sock::close(); -} - -void netkit::sock::ssl_sync_sock::connect() { - if (ssl_mode_ != mode::client) - throw std::runtime_error("connect() only valid for client mode"); - - underlying_sock_->connect(); - - // hack, until we have async_sock - underlying_sock_->set_sock_opts(opt::no_blocking); -} - -void netkit::sock::ssl_sync_sock::bind() { - underlying_sock_->bind(); -} - -void netkit::sock::ssl_sync_sock::unbind() { - underlying_sock_->unbind(); -} - -void netkit::sock::ssl_sync_sock::listen(int backlog) { - underlying_sock_->listen(backlog); -} - -void netkit::sock::ssl_sync_sock::listen() { - underlying_sock_->listen(); -} - -bool netkit::sock::ssl_sync_sock::is_secure() const { - return ssl_ && handshake_complete_; -} - -std::unique_ptr netkit::sock::ssl_sync_sock::accept() { - auto client = underlying_sock_->accept(); - - auto ssl_client = std::make_unique( - std::move(client), - mode::server, - version_, - verification_, - cert_path_, - key_path_); - - ssl_client->perform_handshake(); - - return ssl_client; -} -std::unique_ptr netkit::sock::ssl_sync_sock::accept_explicit_ssl() { - auto accepted = accept(); - return unique_dynamic_cast(std::move(accepted)); -} - -int netkit::sock::ssl_sync_sock::send(const void* buf, size_t len) { - ensure_ready(); - - int ret = wolfSSL_write( - ssl_, - buf, - static_cast(len) - ); - - if (ret <= 0) { - int err = wolfSSL_get_error(ssl_, ret); - throw std::runtime_error( - "wolfSSL_write failed: " + std::to_string(err) - ); - } - - return ret; -} - -void netkit::sock::ssl_sync_sock::send(const std::string& buf) { - static_cast(send(buf.data(), buf.size())); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds) { - return recv_internal(timeout_seconds, nullptr, 0); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, const std::string& match) { - return recv_internal(timeout_seconds, &match, 0); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, const std::string& match, size_t eof) { - return recv_internal(timeout_seconds, &match, eof); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv(int timeout_seconds, size_t eof) { - return recv_internal(timeout_seconds, nullptr, eof); -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv() { - for (;;) { - char buf[8192]; - - int n = wolfSSL_read(ssl_, buf, sizeof(buf)); - - if (n > 0) { - return {{buf, buf + n}, recv_status::success}; - } - - switch (int err = wolfSSL_get_error(ssl_, n)) { - case WOLFSSL_ERROR_ZERO_RETURN: - return {{}, recv_status::closed}; - - case WOLFSSL_ERROR_WANT_READ: - case WOLFSSL_ERROR_WANT_WRITE: - continue; - - default: - throw netkit::socket_error( - "wolfSSL_read failed: " + std::to_string(err) - ); - } - } -} - -std::string netkit::sock::ssl_sync_sock::overflow_bytes() const { - return overflow_; -} - -void netkit::sock::ssl_sync_sock::clear_overflow_bytes() const { - overflow_.clear(); -} - -netkit::sock::addr netkit::sock::ssl_sync_sock::get_peer() const { - return underlying_sock_->get_peer(); -} -netkit::sock::addr& netkit::sock::ssl_sync_sock::get_addr() { - return underlying_sock_->get_addr(); -} -const netkit::sock::addr& netkit::sock::ssl_sync_sock::get_addr() const { - return underlying_sock_->get_addr(); -} - -void netkit::sock::ssl_sync_sock::close() { - std::lock_guard lock(state_mtx_); - - if (ssl_) { - wolfSSL_shutdown(ssl_); - wolfSSL_free(ssl_); - ssl_ = nullptr; - } - - if (ctx_) { - wolfSSL_CTX_free(ctx_); - ctx_ = nullptr; - } - - if (underlying_sock_) { - underlying_sock_->close(); - } -} - -#ifdef NETKIT_DKP -void netkit::sock::ssl_sync_sock::perform_handshake() { - int ret; - - if (ssl_mode_ == mode::client) { - ret = wolfSSL_connect(ssl_); - } else - ret = wolfSSL_accept(ssl_); - - if (ret != WOLFSSL_SUCCESS) { -#ifdef NETKIT_WOLFSSL_DEBUG - int err = wolfSSL_get_error(ssl_, ret); - - std::cerr << "wolfSSL_connect/accept ret=" - << ret - << " err=" - << err - << "\n"; -#endif - - throw_ssl_error("TLS handshake failed"); - } - - handshake_complete_ = true; -} -#else -void netkit::sock::ssl_sync_sock::perform_handshake() { - while (true) { - int ret = ssl_mode_ == mode::client - ? wolfSSL_connect(ssl_) - : wolfSSL_accept(ssl_); - - if (ret == WOLFSSL_SUCCESS) { - handshake_complete_ = true; - return; - } - - int err = wolfSSL_get_error(ssl_, ret); - - if (err == WOLFSSL_ERROR_WANT_READ || - err == WOLFSSL_ERROR_WANT_WRITE) { - std::this_thread::yield(); - continue; - } - - throw_ssl_error("TLS handshake failed"); - } -} -#endif - -void netkit::sock::ssl_sync_sock::init_wolfssl_once() { - static std::once_flag flag; - std::call_once(flag, []() { - wolfSSL_Init(); -#if defined(NETKIT_WOLFSSL_DEBUG) - wolfSSL_Debugging_ON(); - wolfSSL_SetLoggingCb([](const int level, const char* msg) { -#ifdef NETKIT_DKP - SYS_Report("[wolfSSL:%d] %s\n", level, msg); -#else - std::cerr << msg << "\n"; -#endif - }); - wolfSSL_SetAllocators( - [](size_t sz) -> void* { - void* p = malloc(sz); - printf("malloc(%zu) = %p\n", sz, p); - return p; - }, - [](void* p) { - printf("free(%p)\n", p); - free(p); - }, - [](void* p, size_t sz) -> void* { - void* np = realloc(p, sz); - printf("realloc(%p, %zu) = %p\n", p, sz, np); - return np; - } - ); -#endif - }); -} - -void netkit::sock::ssl_sync_sock::create_ssl_context() { - WOLFSSL_METHOD* method = - (ssl_mode_ == mode::client) - ? wolfTLS_client_method() - : wolfTLS_server_method(); - - if (!method) { - throw_ssl_error("wolfTLS method initialization failed"); - } - - ctx_ = wolfSSL_CTX_new(method); - if (!ctx_) { - throw_ssl_error("wolfSSL_CTX_new failed"); - } - - switch (version_) { - case version::TLS_1_2: - wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_2); - break; - - case version::TLS_1_3: - wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_3); - break; - - case version::TLS_1_1: - wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_1); - break; - } - - if (const char* env = std::getenv("NETKIT_SSL_VERIFY")) { - std::string value(env); - - if (value == "none" || value == "disable" || value == "false") { - verification_ = verification::none; - } - - if (value == "peer" || value == "verify" || value == "enable" || value == "true") { - verification_ = verification::peer; - } - } - - int verify_mode = (verification_ == verification::peer) - ? WOLFSSL_VERIFY_PEER - : WOLFSSL_VERIFY_NONE; - - wolfSSL_CTX_set_verify(ctx_, verify_mode, nullptr); - - bool loaded_ca = false; - -#ifndef NETKIT_DKP - if (!ca_path_.empty()) { - loaded_ca = wolfSSL_CTX_load_verify_locations( - ctx_, - ca_path_.c_str(), - nullptr - ) == SSL_SUCCESS; - } - -#ifdef NETKIT_WINDOWS -#ifdef NETKIT_ENABLE_WINDOWS_CERTSTORE - const auto get_localappdata = []() -> std::filesystem::path { - const std::string folder_name = "netkit"; - - std::filesystem::path base_path; - - char appdata[MAX_PATH]; - DWORD len = GetEnvironmentVariableA("LOCALAPPDATA", appdata, sizeof(appdata)); - if (len > 0) { - base_path = appdata; - } else { - base_path = std::filesystem::temp_directory_path(); - } - base_path /= folder_name; - - std::filesystem::create_directories(base_path); - return base_path; - }; - - std::filesystem::path path = (get_localappdata() / "ca-bundle.pem").string(); - if (!loaded_ca && crypto::windows::is_outdated(path.wstring())) { - std::filesystem::remove(path); - if (!crypto::windows::export_certs(path.wstring())) { - throw std::runtime_error("failed to export certificates"); - } - } - - const std::string path_ = path.string(); - if (wolfSSL_CTX_load_verify_locations(ctx_, path_.c_str(), nullptr)) { - loaded_ca = true; - } - -#endif -#endif - - if (!loaded_ca && this->ssl_mode_ == mode::client) { - loaded_ca = - wolfSSL_CTX_load_system_CA_certs(ctx_) == SSL_SUCCESS; - } -#endif - -#ifdef NETKIT_ENABLE_FALLBACK_CA - if (!loaded_ca && this->ssl_mode_ == mode::client) { - loaded_ca = wolfSSL_CTX_load_verify_buffer( - ctx_, - reinterpret_cast(crypto::fallback_ca.data()), - static_cast(crypto::fallback_ca.size()), - WOLFSSL_FILETYPE_PEM - ); - } -#endif - - if (!loaded_ca && verification_ == verification::peer && this->ssl_mode_ == mode::client) { - throw std::runtime_error( - "No trusted CA certificates available" - ); - } - -#ifndef NETKIT_DKP - if (!cert_path_.empty() && this->ssl_mode_ == mode::server) { - if (wolfSSL_CTX_use_certificate_file(ctx_, cert_path_.c_str(), SSL_FILETYPE_PEM) != SSL_SUCCESS) - throw_ssl_error("Failed to load cert"); - } - - if (!key_path_.empty() && this->ssl_mode_ == mode::server) { - if (wolfSSL_CTX_use_PrivateKey_file(ctx_, key_path_.c_str(), SSL_FILETYPE_PEM) != SSL_SUCCESS) - throw_ssl_error("Failed to load key"); - } -#endif - -#ifndef NETKIT_WINDOWS - wolfSSL_CTX_SetIOSend(ctx_, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int { - auto* self = static_cast(ctx); - - int ret = self->underlying_sock_->send( - buf, - static_cast(sz) - ); - - if (ret < 0) - return WOLFSSL_CBIO_ERR_GENERAL; - - return ret; - }); - - wolfSSL_CTX_SetIORecv(ctx_, - [](WOLFSSL*, char* buf, int sz, void* ctx) -> int { - auto* self = static_cast(ctx); - - ssize_t n = ::recv( - self->underlying_sock_->native_handle(), - buf, - sz, - MSG_DONTWAIT - ); - - if (n > 0) - return static_cast(n); - - if (n == 0) - return WOLFSSL_CBIO_ERR_CONN_CLOSE; - - if (errno == EAGAIN || errno == EWOULDBLOCK) - return WOLFSSL_CBIO_ERR_WANT_READ; - - if (errno == EINTR) - return WOLFSSL_CBIO_ERR_WANT_READ; - - return WOLFSSL_CBIO_ERR_GENERAL; - }); -#else - wolfSSL_CTX_SetIOSend(ctx_, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int { - auto* self = static_cast(ctx); - - int ret = self->underlying_sock_->send( - buf, - static_cast(sz) - ); - - if (ret < 0) { - int err = WSAGetLastError(); - - if (err == WSAEWOULDBLOCK) - return WOLFSSL_CBIO_ERR_WANT_WRITE; - - if (err == WSAEINTR) - return WOLFSSL_CBIO_ERR_WANT_WRITE; - - return WOLFSSL_CBIO_ERR_GENERAL; - } - - return ret; - }); - - wolfSSL_CTX_SetIORecv(ctx_, - [](WOLFSSL*, char* buf, int sz, void* ctx) -> int { - auto* self = static_cast(ctx); - - int ret = ::recv( - self->underlying_sock_->native_handle(), - buf, - sz, - 0 - ); - - if (ret > 0) - return ret; - - if (ret == 0) - return WOLFSSL_CBIO_ERR_CONN_CLOSE; - - int err = WSAGetLastError(); - - if (err == WSAEWOULDBLOCK) - return WOLFSSL_CBIO_ERR_WANT_READ; - - if (err == WSAEINTR) - return WOLFSSL_CBIO_ERR_WANT_READ; - - return WOLFSSL_CBIO_ERR_GENERAL; - }); -#endif -} - -void netkit::sock::ssl_sync_sock::create_ssl_object() { - ssl_ = wolfSSL_new(ctx_); - if (!ssl_) { - throw_ssl_error("Failed to create WOLFSSL object"); - } - auto hostname = this->underlying_sock_->get_addr().get_hostname(); - if (hostname.empty()) { - throw std::runtime_error{"get_hostname() empty"}; - } - - wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, hostname.data(), hostname.length()); - - wolfSSL_SetIOWriteCtx(ssl_, this); - wolfSSL_SetIOReadCtx(ssl_, this); - - wolfSSL_check_domain_name( - ssl_, - hostname.c_str() - ); -} - -void netkit::sock::ssl_sync_sock::ensure_ready() const { - if (!ssl_) { - throw std::runtime_error("SSL not initialized"); - } - - if (!handshake_complete_) { - const_cast(this)->perform_handshake(); - } -} - -netkit::sock::recv_result netkit::sock::ssl_sync_sock::recv_internal(int timeout_seconds, const std::string* match, size_t eof) const -{ - std::lock_guard lock(state_mtx_); - ensure_ready(); - - std::string data = overflow_; - overflow_.clear(); - - auto start = std::chrono::steady_clock::now(); - - char buf[8192]; - - while (true) { - if (timeout_seconds != -1) { - auto elapsed = std::chrono::steady_clock::now() - start; - if (elapsed >= std::chrono::seconds(timeout_seconds)) { - return {data, recv_status::timeout}; - } - } - - int ret = wolfSSL_read( - ssl_, - buf, - sizeof(buf) - ); - - if (ret > 0) { - data.append(buf, static_cast(ret)); - - if (eof != 0 && data.size() >= eof) { - if (data.size() > eof) { - overflow_ = data.substr(eof); - data.resize(eof); - } - - return {data, recv_status::success}; - } - - if (match && !match->empty()) { - auto pos = data.find(*match); - - if (pos != std::string::npos) { - overflow_ = data.substr(pos + match->size()); - data.resize(pos + match->size()); - - return {data, recv_status::success}; - } - } - - continue; - } - - - int err = wolfSSL_get_error(ssl_, ret); - if (err == WOLFSSL_ERROR_WANT_READ || - err == WOLFSSL_ERROR_WANT_WRITE) - { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - - if (err == WOLFSSL_ERROR_ZERO_RETURN || - err == WOLFSSL_ERROR_SYSCALL || - err == -397) - { - if (!data.empty()) - return {data, recv_status::success}; - - return {"", recv_status::closed}; - } - - return {data, recv_status::error}; - } -} - -void netkit::sock::ssl_sync_sock::throw_ssl_error(const std::string& msg) { - int err = wolfSSL_get_error(nullptr, 0); - - char buffer[256]; - wolfSSL_ERR_error_string(err, buffer); - - throw std::runtime_error(msg + " (wolfSSL err=" + std::to_string(err) + ", " + buffer + ")"); -} - -#endif \ No newline at end of file diff --git a/src/stream/wolfssl/tls_stream.cpp b/src/stream/wolfssl/tls_stream.cpp new file mode 100644 index 0000000..1d74edb --- /dev/null +++ b/src/stream/wolfssl/tls_stream.cpp @@ -0,0 +1,234 @@ +#ifdef NETKIT_WOLFSSL + +#include +#include +#include + +#ifdef NETKIT_WOLFSSL_DEBUG +#include +#endif + +#include +#include +#include + +netkit::stream::tls_stream::tls_stream(std::unique_ptr stream, version ver, verification verif, const std::string& ca_cert) +: stream_(std::move(stream)), version_(ver), verification_(verif), ca_cert_(ca_cert) { + static std::once_flag flag; + std::call_once(flag, []() { + wolfSSL_Init(); +#if defined(NETKIT_WOLFSSL_DEBUG) + wolfSSL_Debugging_ON(); + wolfSSL_SetLoggingCb([](const int level, const char* msg) { +#ifdef NETKIT_DKP + SYS_Report("[wolfSSL:%d] %s\n", level, msg); +#else + std::cerr << msg << "\n"; +#endif + }); + wolfSSL_SetAllocators( + [](size_t sz) -> void* { + void* p = malloc(sz); + printf("malloc(%zu) = %p\n", sz, p); + return p; + }, + [](void* p) { + printf("free(%p)\n", p); + free(p); + }, + [](void* p, size_t sz) -> void* { + void* np = realloc(p, sz); + printf("realloc(%p, %zu) = %p\n", p, sz, np); + return np; + } + ); +#endif + }); + + ctx_ = wolfSSL_CTX_new(wolfTLS_client_method()); + + if (!ctx_) + throw std::runtime_error("wolfSSL_CTX_new failed"); + + // TODO: maybe we shouldn't store the version in the class? haven't yet decided on this + switch (version_) { + case version::TLS_1_2: + wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_2); + break; + + case version::TLS_1_3: + wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_3); + break; + + case version::TLS_1_1: + wolfSSL_CTX_SetMinVersion(ctx_, WOLFSSL_TLSV1_1); + break; + } + + int verify_mode = (verification_ == verification::peer) ? WOLFSSL_VERIFY_PEER : WOLFSSL_VERIFY_NONE; + wolfSSL_CTX_set_verify(ctx_, verify_mode, nullptr); + + bool loaded_ca = false; + if (this->ca_cert_.empty()) { + loaded_ca = wolfSSL_CTX_load_verify_buffer(ctx_, + reinterpret_cast(crypto::fallback_ca.data()), + static_cast(crypto::fallback_ca.size()), WOLFSSL_FILETYPE_PEM); + } +#ifndef NETKIT_DKP +#ifdef NETKIT_WINDOWS +#ifdef NETKIT_ENABLE_WINDOWS_CERTSTORE + const auto get_localappdata = []() -> std::filesystem::path { + const std::string folder_name = "netkit"; + + std::filesystem::path base_path; + + char appdata[MAX_PATH]; + DWORD len = GetEnvironmentVariableA("LOCALAPPDATA", appdata, sizeof(appdata)); + if (len > 0) { + base_path = appdata; + } else { + base_path = std::filesystem::temp_directory_path(); + } + base_path /= folder_name; + + std::filesystem::create_directories(base_path); + return base_path; + }; + + std::filesystem::path path = (get_localappdata() / "ca-bundle.pem").string(); + if (!loaded_ca && crypto::windows::is_outdated(path.wstring())) { + std::filesystem::remove(path); + if (!crypto::windows::export_certs(path.wstring())) { + throw std::runtime_error("failed to export certificates"); + } + } + + const std::string path_ = path.string(); + if (!loaded_ca && wolfSSL_CTX_load_verify_locations(ctx_, path_.c_str(), nullptr)) { + loaded_ca = true; + } + +#endif +#endif + if (!loaded_ca) { + loaded_ca = wolfSSL_CTX_load_system_CA_certs(ctx_) == SSL_SUCCESS; + } +#endif + +#ifdef NETKIT_ENABLE_FALLBACK_CA + if (!loaded_ca) { + loaded_ca = wolfSSL_CTX_load_verify_buffer( + ctx_, + reinterpret_cast(crypto::fallback_ca.data()), + crypto::fallback_ca.size(), + WOLFSSL_FILETYPE_PEM + ); + } +#endif + + if (!loaded_ca && verification_ == verification::peer) { + throw std::runtime_error( + "No trusted CA certificates available" + ); + } + + wolfSSL_SetIORecv(ctx_, + [](WOLFSSL* ssl, char* buf, int sz, void* ctx) -> int { + auto* self = static_cast(ctx); + + auto res = self->stream_->read(std::span(reinterpret_cast(buf), sz) ); + + if (res.status != netkit::stream::stream_status::success) + return WOLFSSL_CBIO_ERR_GENERAL; + + if (res.bytes == 0) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + + return static_cast(res.bytes); + } + ); + + wolfSSL_SetIOSend(ctx_, + [](WOLFSSL* ssl, char* buf, int sz, void* ctx) -> int { + auto* self = static_cast(ctx); + + auto res = self->stream_->write(std::span(reinterpret_cast(buf), sz) ); + + if (res.status != netkit::stream::stream_status::success) + return WOLFSSL_CBIO_ERR_GENERAL; + + return static_cast(res.bytes); + } + ); + + ssl_ = wolfSSL_new(ctx_); + + if (!ssl_) + throw std::runtime_error("wolfSSL_new failed"); + + wolfSSL_SetIOReadCtx(ssl_, this); + wolfSSL_SetIOWriteCtx(ssl_, this); + + std::string hostname; + if (this->stream_ && this->stream_->get_addr().has_value()) { + hostname = this->stream_->get_addr()->get_hostname(); + wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, hostname.data(), hostname.length()); + wolfSSL_check_domain_name(ssl_, hostname.c_str()); + } +} + +void netkit::stream::tls_stream::perform_handshake() const { + int ret = wolfSSL_connect(ssl_); + + if (ret != SSL_SUCCESS) { + int err = wolfSSL_get_error(ssl_, ret); + + throw std::runtime_error("TLS handshake failed: " + std::to_string(err)); + } +} + +netkit::stream::stream_result netkit::stream::tls_stream::read(std::span buffer) { + for (;;) { + int ret = wolfSSL_read(ssl_, buffer.data(), static_cast(buffer.size())); + + if (ret > 0) + return { static_cast(ret), stream_status::success }; + + int err = wolfSSL_get_error(ssl_, ret); + + if (err == WOLFSSL_ERROR_ZERO_RETURN) + return { 0, stream_status::eof }; + + if (err == WOLFSSL_ERROR_WANT_READ || err == WOLFSSL_ERROR_WANT_WRITE) + continue; + + return { 0, stream_status::eof }; + } +} + +netkit::stream::stream_result netkit::stream::tls_stream::write(std::span buffer) { + int ret = wolfSSL_write(ssl_, buffer.data(), static_cast(buffer.size())); + + if (ret > 0) { + return { static_cast(ret), stream_status::success }; + } + + return { 0, stream_status::error }; +} + +void netkit::stream::tls_stream::close() noexcept { + if (ssl_) + wolfSSL_shutdown(ssl_); + + stream_->close(); +} + +netkit::stream::tls_stream::~tls_stream() { + if (ssl_) + wolfSSL_free(ssl_); + + if (ctx_) + wolfSSL_CTX_free(ctx_); +} + +#endif \ No newline at end of file From 49c885416667a9f2eff799d9d42b82d45e214f31 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 03:28:49 +0200 Subject: [PATCH 16/34] http fixes --- README.md | 2 +- examples/cpp/http_server/.gitignore | 1 + examples/cpp/http_server/CMakeLists.txt | 16 + examples/cpp/http_server/main.cpp | 101 ++++++ include/netkit/http/basic_request_handler.hpp | 4 +- include/netkit/http/basic_sync_server.hpp | 1 - include/netkit/http/request_handler.hpp | 318 ++++++++++-------- include/netkit/http/sync_server.hpp | 6 +- include/netkit/stream/async_socket_stream.hpp | 3 + include/netkit/stream/basic_stream.hpp | 5 + include/netkit/stream/memory_stream.hpp | 3 + include/netkit/stream/socket_stream.hpp | 3 + include/netkit/stream/wolfssl/tls_stream.hpp | 3 + include/netkit/tcp/async_tcp_stream.hpp | 2 + include/netkit/tcp/tcp_stream.hpp | 3 + src/http/sync_client.cpp | 73 ++-- 16 files changed, 357 insertions(+), 187 deletions(-) create mode 100644 examples/cpp/http_server/.gitignore create mode 100644 examples/cpp/http_server/CMakeLists.txt create mode 100644 examples/cpp/http_server/main.cpp diff --git a/README.md b/README.md index 9a1a28b..acb87b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Please note, this library is still work in progress. Usage is not recommended ye If you still intend to use the library, there are two ways to go. You can either use the unstable master branch directly which may change its API entirely. -The other alternative is to use the latest release, which doesn't have a changing API but will make migration later potentially time consuming. +The other alternative is to use the latest release, which doesn't have a changing API but will make migration later potentially time-consuming. ## Features diff --git a/examples/cpp/http_server/.gitignore b/examples/cpp/http_server/.gitignore new file mode 100644 index 0000000..3c34c2f --- /dev/null +++ b/examples/cpp/http_server/.gitignore @@ -0,0 +1 @@ +*build* \ No newline at end of file diff --git a/examples/cpp/http_server/CMakeLists.txt b/examples/cpp/http_server/CMakeLists.txt new file mode 100644 index 0000000..b3a365c --- /dev/null +++ b/examples/cpp/http_server/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.11) + +project(http_server LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(http_server main.cpp) + +target_link_libraries(http_server PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/http_server/main.cpp b/examples/cpp/http_server/main.cpp new file mode 100644 index 0000000..a56528e --- /dev/null +++ b/examples/cpp/http_server/main.cpp @@ -0,0 +1,101 @@ +/** netkit + * C++23 cross-platform networking toolkit library providing safe Unix-style sockets and protocol abstractions. + * + * Copyright (c) 2025-2026 Jacob Nilsson + * Licensed under the MIT License. + * + * @file main.c + * @license MIT + * @note Example code using the Netkit library. + * @brief Slightly more advanced/useful HTTP file server that serves files from a specified index file and directory. + */ +#include +#include +#include + +struct server_settings { + int port{8080}; + std::string index_file{"./index.html"}; +}; + +int main(int argc, char** argv) { + server_settings settings{}; + std::vector args(argv, argv + argc); + for (int i{0}; i < args.size(); ++i) { + if (args[i] == "--port" && i + 1 < args.size()) { + settings.port = std::stoi(args[i + 1]); + ++i; + } else if (args[i] == "--index-file" && i + 1 < args.size()) { + settings.index_file = args[i + 1]; + ++i; + } else if (args[i] == "--help" || args[i] == "-h") { + std::cout << "Usage: " << args[0] << " [--port ] [--index-file ] [--help|-h]\n" + << " --port Specify the port to run the HTTP server on (default: 80)\n" + << " --index-file Specify the path to the index HTML file (default: ./index.html)\n" + << " --help, -h Show this help message\n"; + return 0; + } + } + + std::cout << "Starting HTTP server on port " << settings.port << "...\n"; + netkit::http::server::sync_server server( + netkit::http::server::server_settings{ + .port = settings.port, + .enable_session = false, + .session_directory = "./sessions", + .session_cookie_name = "netkit-server", + .trust_x_forwarded_for = false, + }, + [&settings](const netkit::http::server::request& req) -> netkit::http::server::response { + netkit::http::server::response res; + std::string parent_path = std::filesystem::path(settings.index_file).parent_path().string(); + if (!std::filesystem::is_directory(parent_path)) { + parent_path = "."; + } + + std::string body_{}; + if (req.body) { + body_ = req.body->read_all(); + } + + if (req.endpoint.find("..") != std::string::npos) { + res.http_status = 403; + res.body = std::make_unique("

403 Forbidden

"); + res.content_type = "text/html"; + res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); + return res; + } + + if ((req.endpoint == "/" || req.endpoint.empty()) && std::filesystem::is_regular_file(settings.index_file)) { + res.http_status = 200; + res.body = std::make_unique(settings.index_file); + res.content_type = netkit::utility::get_appropriate_content_type(settings.index_file); + res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); + } else if (std::filesystem::is_regular_file(std::filesystem::path(parent_path) / req.endpoint.substr(1))) { + std::string file_path = (std::filesystem::path(parent_path) / req.endpoint.substr(1)).string(); + res.http_status = 200; + res.body = std::make_unique(file_path); + res.content_type = netkit::utility::get_appropriate_content_type(file_path); + res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); + res.headers.push_back({"Content-Disposition", "inline"}); + } else { + res.http_status = 404; + res.body = std::make_unique("

404 Not Found

"); + res.content_type = "text/html"; + res.headers.push_back({"X-Server", "netkit-http-server/1.0"}); + } + + std::cout << "Received request from: " << req.ip_address << "\n" + << "Endpoint: " << req.endpoint << "\n" + << "Method: " << req.method << "\n" + << "User-Agent: " << req.user_agent << "\n" + << "Body: " << body_ << "\n"; + + return res; + }); + + std::cout << "Server started on port " << settings.port << ".\n" + << "Press Ctrl+C to stop the server.\n"; + + server.run(); +} \ No newline at end of file diff --git a/include/netkit/http/basic_request_handler.hpp b/include/netkit/http/basic_request_handler.hpp index eade6b7..10d66ae 100644 --- a/include/netkit/http/basic_request_handler.hpp +++ b/include/netkit/http/basic_request_handler.hpp @@ -13,14 +13,14 @@ #ifdef NETKIT_HTTP -#include <../socket/native/basic_native_sync_sock.hpp> +#include #include namespace netkit::http::server { template class basic_request_handler { public: - virtual void handle(std::unique_ptr&, server_settings&, const request_callback&) const = 0; + virtual void handle(std::unique_ptr&, server_settings&, const request_callback&) const = 0; virtual ~basic_request_handler() = default; }; } diff --git a/include/netkit/http/basic_sync_server.hpp b/include/netkit/http/basic_sync_server.hpp index 8d29b42..c68f1a8 100644 --- a/include/netkit/http/basic_sync_server.hpp +++ b/include/netkit/http/basic_sync_server.hpp @@ -13,7 +13,6 @@ #ifdef NETKIT_HTTP -#include <../socket/native/native_sync_sock.hpp> #include #include #include diff --git a/include/netkit/http/request_handler.hpp b/include/netkit/http/request_handler.hpp index 8c651bf..a0cbab4 100644 --- a/include/netkit/http/request_handler.hpp +++ b/include/netkit/http/request_handler.hpp @@ -13,7 +13,7 @@ #ifdef NETKIT_HTTP -#include <../socket/native/native_sync_sock.hpp> +#include #include #include #include @@ -27,117 +27,146 @@ namespace netkit::http::server { template class request_handler : public basic_request_handler<> { - static std::vector get_cookies_from_request(const std::string& cookie_header) { - std::vector cookies; - std::string cookie_str = cookie_header + ";"; - - while (cookie_str.find(';') != std::string::npos) { - std::string cookie = cookie_str.substr(0, cookie_str.find(';')); - cookie_str = cookie_str.substr(cookie_str.find(';') + 1); - - std::string name = cookie.substr(0, cookie.find('=')); - std::string value = cookie.substr(cookie.find('=') + 1); - - if (!name.empty() && !value.empty()) { - if (name.front() == ' ') { - name = name.substr(1); - } - cookies.push_back({name, value}); - } - } - - return cookies; - } - - static std::unordered_map default_read_from_session_file(const std::string& f) { - std::unordered_map session; - - std::ifstream file(f); - - if (!file.good()) { - file.close(); - return {}; - } - - if (!file.is_open()) { - throw std::runtime_error("failed to open session file (read_from_session_file()): " + f); - } - - std::string line{}; - while (std::getline(file, line)) { - if (line.find('=') != std::string::npos) { - std::string key = line.substr(0, line.find('=')); - std::string value = line.substr(line.find('=') + 1); - - session[key] = value; - } - } - - file.close(); - - return session; - } - - static void default_write_to_session_file(const std::string& f, const std::unordered_map& session) { - auto directory = std::filesystem::path(f).parent_path(); - if (!std::filesystem::exists(directory)) { - std::filesystem::create_directories(directory); - } - std::ofstream file(f, std::ios::trunc); - - if (!file.is_open() || !file.good()) { - throw std::runtime_error("failed to open session file (write_to_session_file()): " + f); - } - - for (const auto& it : session) { - file << it.first << "=" << it.second << "\n"; - } - - file.close(); - } - - [[nodiscard]] static std::unordered_map get_headers(const std::string& header_part) { - std::unordered_map headers_map; - std::istringstream hs(header_part); - std::string l{}; - while (std::getline(hs, l) && l != "\r") { - if (l.back() == '\r') l.pop_back(); - auto cpos = l.find(':'); - if (cpos != std::string::npos) { - auto key = l.substr(0, cpos); - auto value = l.substr(cpos + 1); - auto trim = [](std::string& s) { - s.erase(0, s.find_first_not_of(" \t")); - s.erase(s.find_last_not_of(" \t") + 1); - }; - trim(key); - trim(value); - headers_map[key] = value; - } - } - - return headers_map; - } - - struct status_line { - std::string method{"GET"}; - std::string path{"/"}; - std::string http_version{"HTTP/1.1"}; - }; - - status_line get_status_line(const std::string& header_part) const { - status_line line{}; - std::istringstream hs(header_part); - std::string first_line{}; - if (std::getline(hs, first_line)) { - if (first_line.back() == '\r') first_line.pop_back(); - std::istringstream line_ss(first_line); - line_ss >> line.method >> line.path >> line.http_version; - } - return line; + static std::vector get_cookies_from_request(const std::string& cookie_header) { + std::vector cookies; + std::string cookie_str = cookie_header + ";"; + + while (cookie_str.find(';') != std::string::npos) { + std::string cookie = cookie_str.substr(0, cookie_str.find(';')); + cookie_str = cookie_str.substr(cookie_str.find(';') + 1); + + std::string name = cookie.substr(0, cookie.find('=')); + std::string value = cookie.substr(cookie.find('=') + 1); + + if (!name.empty() && !value.empty()) { + if (name.front() == ' ') { + name = name.substr(1); + } + cookies.push_back({name, value}); + } + } + + return cookies; + } + + static std::unordered_map default_read_from_session_file(const std::string& f) { + std::unordered_map session; + + std::ifstream file(f); + + if (!file.good()) { + file.close(); + return {}; + } + + if (!file.is_open()) { + throw std::runtime_error("failed to open session file (read_from_session_file()): " + f); + } + + std::string line{}; + while (std::getline(file, line)) { + if (line.find('=') != std::string::npos) { + std::string key = line.substr(0, line.find('=')); + std::string value = line.substr(line.find('=') + 1); + + session[key] = value; + } + } + + file.close(); + + return session; + } + + static void default_write_to_session_file(const std::string& f, const std::unordered_map& session) { + auto directory = std::filesystem::path(f).parent_path(); + if (!std::filesystem::exists(directory)) { + std::filesystem::create_directories(directory); + } + std::ofstream file(f, std::ios::trunc); + + if (!file.is_open() || !file.good()) { + throw std::runtime_error("failed to open session file (write_to_session_file()): " + f); + } + + for (const auto& it : session) { + file << it.first << "=" << it.second << "\n"; + } + + file.close(); + } + + [[nodiscard]] static std::unordered_map get_headers(const std::string& header_part) { + std::unordered_map headers_map; + std::istringstream hs(header_part); + std::string l{}; + while (std::getline(hs, l) && l != "\r") { + if (l.back() == '\r') l.pop_back(); + auto cpos = l.find(':'); + if (cpos != std::string::npos) { + auto key = l.substr(0, cpos); + auto value = l.substr(cpos + 1); + auto trim = [](std::string& s) { + s.erase(0, s.find_first_not_of(" \t")); + s.erase(s.find_last_not_of(" \t") + 1); + }; + trim(key); + trim(value); + headers_map[key] = value; + } + } + + return headers_map; + } + + struct status_line { + std::string method{"GET"}; + std::string path{"/"}; + std::string http_version{"HTTP/1.1"}; + }; + + status_line get_status_line(const std::string& header_part) const { + status_line line{}; + std::istringstream hs(header_part); + std::string first_line{}; + if (std::getline(hs, first_line)) { + if (first_line.back() == '\r') first_line.pop_back(); + std::istringstream line_ss(first_line); + line_ss >> line.method >> line.path >> line.http_version; + } + return line; + } + + mutable std::string overflow_bytes{}; + std::string read_until(std::unique_ptr& client_sock, const std::string& delimiter) const { + std::string ret; + char buffer[4096]; + + while (true) { + const auto [bytes, status] = client_sock->read(buffer, sizeof(buffer)); + + if (status == stream::stream_status::error) { + throw socket_error{"error occurred"}; + } + + ret.append(buffer, bytes); + + int pos = ret.find_first_of(delimiter); + if (pos != std::string::npos) { + overflow_bytes = ret.substr(pos); + ret = ret.substr(0, pos); + break; + } + + if (status == stream::stream_status::eof) { + break; + } + } + + return ret; } public: - void handle(std::unique_ptr& client_sock, server_settings& settings, const request_callback& callback) const override { + void handle(std::unique_ptr& client_sock, server_settings& settings, const request_callback& callback) const override { if (!client_sock) { return; } @@ -153,7 +182,7 @@ namespace netkit::http::server { } request req{}; - std::string headers = client_sock->recv(5, "\r\n\r\n").data; + std::string headers = read_until(client_sock, "\r\n\r\n"); const auto headers_vec = get_headers(headers); if (headers.empty()) { return; @@ -177,14 +206,14 @@ namespace netkit::http::server { break; } } else if (line.starts_with("Expect:") && line.find("100-continue") != std::string::npos) { - client_sock->send("HTTP/1.1 100 Continue\r\n\r\n"); + client_sock->write_all("HTTP/1.1 100 Continue\r\n\r\n"); } else if (line.starts_with("Expect:") && line.find("100-continue") == std::string::npos) { std::string response = "HTTP/1.1 417 Expectation Failed\r\n" "Content-Length: 0\r\n" "Connection: close\r\n" "\r\n"; - client_sock->send(response); + client_sock->write_all(response); return; } else if (line.starts_with("Upgrade:") && line.find("websocket") != std::string::npos) { std::string response = "HTTP/1.1 426 Upgrade Required\r\n" @@ -192,7 +221,7 @@ namespace netkit::http::server { "Connection: close\r\n" "\r\n"; - client_sock->send(response); + client_sock->write_all(response); return; } else if (line.starts_with("Connection:") && line.find("close") != std::string::npos) { close = true; @@ -201,26 +230,19 @@ namespace netkit::http::server { // TODO: implement streaming for chunked if (is_chunked && (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "DELETE")) { - std::string chunked = client_sock->overflow_bytes(); - client_sock->clear_overflow_bytes(); - - while (chunked.find("0\r\n\r\n") == std::string::npos) { - auto res = client_sock->recv(5, "", 0); // no eof - if (res.status == sock::recv_status::closed) break; - if (res.status == sock::recv_status::timeout) close = true; - if (res.data.empty()) continue; - chunked += res.data; - } + std::string chunked = overflow_bytes; + overflow_bytes.clear(); + chunked = read_until(client_sock, "0\r\n\r\n"); std::string decoded = netkit::utility::decode_chunked(chunked); req.headers = get_headers(headers); req.body = std::make_unique(decoded); } else if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "DELETE") { - std::string initial = client_sock->overflow_bytes(); - client_sock->clear_overflow_bytes(); - req.headers = get_headers(headers); + std::string initial = overflow_bytes; + overflow_bytes.clear(); - req.body = std::make_unique(*client_sock, content_length, std::move(initial)); + req.headers = get_headers(headers); + req.body = std::make_unique(*client_sock, content_length, std::move(initial)); } else { req.headers = get_headers(headers); } @@ -242,7 +264,7 @@ namespace netkit::http::server { }(); if (req.ip_address.empty()) { - req.ip_address = client_sock->get_peer().get_ip(); + req.ip_address = client_sock->peer().get_ip(); } if (!netkit::network::is_ipv4(req.ip_address) && !netkit::network::is_ipv6(req.ip_address)) { @@ -428,30 +450,46 @@ namespace netkit::http::server { header_section << "Content-Length: " << response.body->size().value_or(0) << "\r\n"; header_section << "\r\n"; - client_sock->send(header_section.str()); + client_sock->write_all(header_section.str()); char buf[4096]; while (true) { auto result = response.body->read(buf, sizeof(buf)); - if (result.get_status() == netkit::body::read_status::error) - break; + using status_t = netkit::body::read_status; - if (result.get_status() == netkit::body::read_status::timeout) + switch (result.get_status()) { + case status_t::error: + throw std::runtime_error("Body read error"); + + case status_t::timeout: continue; - auto bytes = result.get_bytes_read(); + case status_t::ok: + case status_t::eof: { + auto bytes = result.get_bytes_read(); + + if (bytes > 0) { + std::size_t total_sent = 0; + + while (total_sent < bytes) { + auto [sent, write_status] = + client_sock->write_all(buf + total_sent, bytes - total_sent); - if (bytes > 0) { - int sbytes = client_sock->send(buf, bytes); - if (sbytes != bytes) { - throw std::runtime_error{"Only sent" + std::to_string(sbytes) + " bytes out of " + std::to_string(bytes) + " to send"}; + if (write_status == netkit::stream::stream_status::error) + throw std::runtime_error("Socket write error"); + + total_sent += sent; + } } - } - if (result.get_status() == netkit::body::read_status::eof) + if (result.get_status() == status_t::eof) + return; // done + break; + } + } } } diff --git a/include/netkit/http/sync_server.hpp b/include/netkit/http/sync_server.hpp index 21eecc5..accd128 100644 --- a/include/netkit/http/sync_server.hpp +++ b/include/netkit/http/sync_server.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace netkit::http::server { @@ -30,7 +31,7 @@ namespace netkit::http::server { bool running = true; server_settings settings; std::function callback; - std::unique_ptr sock; + std::unique_ptr sock; public: /** * @brief Constructor for the server class @@ -45,8 +46,7 @@ namespace netkit::http::server { } sock::addr addr = {"localhost", settings.port, netkit::sock::addr_type::hostname}; - this->sock = std::make_unique(addr, netkit::sock::type::tcp, - netkit::sock::opt::reuse_addr|netkit::sock::opt::no_delay|netkit::sock::opt::blocking); + this->sock = std::make_unique(addr); try { sock->bind(); diff --git a/include/netkit/stream/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp index 55a43d0..eaa49c6 100644 --- a/include/netkit/stream/async_socket_stream.hpp +++ b/include/netkit/stream/async_socket_stream.hpp @@ -9,6 +9,9 @@ namespace netkit::stream { class NETKIT_API async_socket_stream : public basic_async_stream { public: + using basic_async_stream::write; + using basic_async_stream::read; + explicit async_socket_stream(std::unique_ptr socket) : socket_(std::move(socket)) {} [[nodiscard]] io::task<> connect() const; diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index 9e4d42d..998fb4c 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -52,6 +53,10 @@ class basic_stream { ); } + stream_result write_all(const void* data, std::size_t size) { + return write_all(std::span(static_cast(data), size)); + } + std::vector read_all(std::size_t max_bytes = 16 * 1024 * 1024) { std::vector result; diff --git a/include/netkit/stream/memory_stream.hpp b/include/netkit/stream/memory_stream.hpp index 98d1e2b..17961a2 100644 --- a/include/netkit/stream/memory_stream.hpp +++ b/include/netkit/stream/memory_stream.hpp @@ -9,6 +9,9 @@ namespace netkit::stream { class memory_stream : public basic_stream { public: + using basic_stream::write; + using basic_stream::read; + memory_stream() = default; explicit memory_stream(std::vector data) : buffer_(std::move(data)) {} diff --git a/include/netkit/stream/socket_stream.hpp b/include/netkit/stream/socket_stream.hpp index 516bc34..beed6a8 100644 --- a/include/netkit/stream/socket_stream.hpp +++ b/include/netkit/stream/socket_stream.hpp @@ -7,6 +7,9 @@ namespace netkit::stream { class socket_stream : public basic_stream { public: + using basic_stream::write; + using basic_stream::read; + explicit socket_stream(std::unique_ptr socket) : socket_(std::move(socket)) {} diff --git a/include/netkit/stream/wolfssl/tls_stream.hpp b/include/netkit/stream/wolfssl/tls_stream.hpp index 62af88b..ff4e779 100644 --- a/include/netkit/stream/wolfssl/tls_stream.hpp +++ b/include/netkit/stream/wolfssl/tls_stream.hpp @@ -10,6 +10,9 @@ namespace netkit::stream { class tls_stream : public netkit::stream::basic_stream { public: + using basic_stream::write; + using basic_stream::read; + explicit tls_stream(std::unique_ptr stream, version ver = version::TLS_1_2, verification verif = verification::peer, const std::string& ca_cert = {}); diff --git a/include/netkit/tcp/async_tcp_stream.hpp b/include/netkit/tcp/async_tcp_stream.hpp index 87325a7..03c6553 100644 --- a/include/netkit/tcp/async_tcp_stream.hpp +++ b/include/netkit/tcp/async_tcp_stream.hpp @@ -12,6 +12,8 @@ namespace netkit::tcp { class NETKIT_API async_tcp_stream : public stream::basic_async_stream { public: + using basic_async_stream::write; + using basic_async_stream::read; async_tcp_stream(io::io_context& ctx, const sock::addr& addr) : stream_(std::make_unique(ctx, addr, sock::type::tcp)) {} async_tcp_stream(std::unique_ptr socket) : stream_(std::move(socket)) {} ~async_tcp_stream() override; diff --git a/include/netkit/tcp/tcp_stream.hpp b/include/netkit/tcp/tcp_stream.hpp index 4b9e753..9b9d817 100644 --- a/include/netkit/tcp/tcp_stream.hpp +++ b/include/netkit/tcp/tcp_stream.hpp @@ -12,6 +12,9 @@ namespace netkit::tcp { class tcp_stream : public stream::basic_stream { public: + using basic_stream::write; + using basic_stream::read; + tcp_stream(const sock::addr& addr) : stream_(std::make_unique(addr, sock::type::tcp)) {} tcp_stream(std::unique_ptr socket) : stream_(std::move(socket)) {} diff --git a/src/http/sync_client.cpp b/src/http/sync_client.cpp index d050cb6..55dd63a 100644 --- a/src/http/sync_client.cpp +++ b/src/http/sync_client.cpp @@ -14,7 +14,8 @@ #include #include #include -#include +#include +#include #include #include @@ -22,47 +23,53 @@ std::string netkit::http::client::sync_client::make_request(const std::string& r sock::addr addr(hostname, port, sock::addr_type::hostname); #if defined(NETKIT_SSL) - using variant_sock = std::variant; + using variant_sock = std::variant; #else - using variant_sock = std::variant; + using variant_sock = std::variant; #endif std::optional sock{std::nullopt}; -#if defined(NETKIT_OPENSSL) || defined(NETKIT_WOLFSSL) +#if defined(NETKIT_SSL) if (port == 443) { - auto tcp_sock = std::make_unique(addr, netkit::sock::type::tcp); - sock.emplace(std::in_place_type, - std::move(tcp_sock), - netkit::sock::mode::client); + auto tcp_sock = std::make_unique(addr); + tcp_sock->connect(); + sock.emplace(std::in_place_type, + std::move(tcp_sock)); + std::get(*sock).perform_handshake(); } else { - sock.emplace(netkit::sock::sync_sock(addr, netkit::sock::type::tcp)); - std::get(*sock).connect(); + sock.emplace(std::in_place_type, addr); + std::get(*sock).connect(); } #else - sock.emplace(sock::sync_sock(addr, sock::type::tcp)); - std::get(*sock).connect(); + sock.emplace(std::in_place_type, addr); + std::get(*sock).connect(); #endif auto& s = *sock; - std::visit([](auto& sckt){ sckt.connect(); }, s); - std::visit([&](auto& sckt) { sckt.send(request.data(), request.size()); }, s); + + const auto write_data = [&](const std::string& data) { + std::visit([&](auto& socket) { + socket.write_all(data); + }, s); + }; + + const auto recv_data = [&]() -> std::string { + std::string ret; + std::visit([&](auto& socket) { + ret = socket.read_all_string(); + }, s); + return ret; + }; + + write_data(request); std::string raw; std::string s_headers; + while (true) { - auto result = std::visit([&](auto& sckt) { return sckt.recv(timeout, "\r\n\r\n", 0); }, s); - if (result.status == sock::recv_status::timeout) { - throw std::runtime_error("timeout while reading headers"); - } - if (result.status == sock::recv_status::closed) { - throw std::runtime_error("connection closed during headers"); - } - if (result.data.empty()) { - throw std::runtime_error("empty recv data unexpectedly"); - } + raw += recv_data(); - raw += result.data; - if (auto pos = raw.find("\r\n\r\n"); pos != std::string::npos) { + if (auto pos = raw.find_first_of("\r\n\r\n"); pos != std::string::npos) { s_headers = raw.substr(0, pos + 4); raw = raw.substr(pos + 4); break; @@ -70,36 +77,22 @@ std::string netkit::http::client::sync_client::make_request(const std::string& r } bool is_chunked = false; - std::size_t content_length = 0; std::istringstream header_stream(s_headers); std::string line; while (std::getline(header_stream, line) && line != "\r") { if (line.starts_with("Transfer-Encoding:") && line.find("chunked") != std::string::npos) { is_chunked = true; - } else if (line.starts_with("Content-Length:")) { - content_length = std::stoul(line.substr(15)); } } - std::string s_body; if (is_chunked) { std::string chunked_data = std::move(raw); - while (chunked_data.find("0\r\n\r\n") == std::string::npos) { - std::string chunk = std::visit([&](auto& sckt) { return sckt.recv(timeout, 8192); }, s).data; - if (chunk.empty()) throw std::runtime_error("connection closed during chunked body"); - chunked_data += chunk; - } s_body = utility::decode_chunked(chunked_data); } else { s_body = std::move(raw); - while (s_body.size() < content_length) { - auto res = std::visit([&](auto& sckt) { return sckt.recv(30, "", 0); }, s); - if (res.data.empty()) break; - s_body += res.data; - } } return s_headers + s_body; From b6ee0e3c73591fddb3faa264181e67b369c2a8ca Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 03:30:27 +0200 Subject: [PATCH 17/34] update github ci --- .github/workflows/cmake-multi-platform.yml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 892ea19..faece23 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -32,23 +32,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install OpenSSL - run: | - if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then - sudo apt update - sudo apt install -y libssl-dev libwolfssl-dev - echo "CMAKE_PREFIX_PATH=/usr" >> $GITHUB_ENV - elif [[ "${{ matrix.os }}" == "macos-latest" ]]; then - brew update - brew install openssl - OPENSSL_DIR=$(brew --prefix openssl) - echo "CMAKE_PREFIX_PATH=${OPENSSL_DIR}" >> $GITHUB_ENV - elif [[ "${{ matrix.os }}" == "windows-latest" ]]; then - choco install openssl.light -y - echo "CMAKE_PREFIX_PATH=C:/Program Files/OpenSSL-Win64" >> $GITHUB_ENV - fi - shell: bash - - name: Set reusable strings id: strings shell: bash @@ -61,6 +44,7 @@ jobs: -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DNETKIT_ENABLE_TESTS=ON -DCMAKE_PREFIX_PATH=${{ env.CMAKE_PREFIX_PATH }} -S ${{ github.workspace }} From 8d6eab588d89b5076477b84739ac84d1566a32d3 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:24:47 +0200 Subject: [PATCH 18/34] unistd include on unix only --- src/socket/native/native_async_socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index f325c7d..6b9d646 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #elif defined(NETKIT_WINDOWS) #include #endif From 93273cbbde74a83665c61c6dba214d62b3664272 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:27:40 +0200 Subject: [PATCH 19/34] unix includes --- include/netkit/socket/addr.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/netkit/socket/addr.hpp b/include/netkit/socket/addr.hpp index 1d4d0bd..d40c111 100644 --- a/include/netkit/socket/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -17,6 +17,13 @@ #include #include +#ifdef NETKIT_WINDOWS +#include +#else +#include +#include +#endif + namespace netkit::sock { using sockaddr_len = int; From 29193b5276ff12f8ac5ebde5456b1f16436d9201 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:28:57 +0200 Subject: [PATCH 20/34] ci: disable catch2 tests --- .github/workflows/cmake-multi-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index faece23..9bcd594 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -44,7 +44,7 @@ jobs: -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - -DNETKIT_ENABLE_TESTS=ON + -DNETKIT_ENABLE_TESTS=OFF -DCMAKE_PREFIX_PATH=${{ env.CMAKE_PREFIX_PATH }} -S ${{ github.workspace }} From 00c98e717e16fece2adcad3d7febbe732b2213f8 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:33:01 +0200 Subject: [PATCH 21/34] declaration/definition signature mismatch fix --- src/socket/addr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket/addr.cpp b/src/socket/addr.cpp index 415e443..c37d954 100644 --- a/src/socket/addr.cpp +++ b/src/socket/addr.cpp @@ -298,7 +298,7 @@ const sockaddr* netkit::sock::addr::get_sa() const noexcept { return reinterpret_cast(&sa_storage_); } -socklen_t netkit::sock::addr::get_sa_len() const noexcept { +netkit::sock::sockaddr_len netkit::sock::addr::get_sa_len() const noexcept { if (this->is_ipv4()) return sizeof(sockaddr_in); if (this->is_ipv6()) return sizeof(sockaddr_in6); #ifndef NETKIT_DKP From fb05f14469ac9b9794b6d8adbd3fd8e1eb1e389f Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:40:38 +0200 Subject: [PATCH 22/34] include more headers as needed --- include/netkit/stream/basic_stream.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index 998fb4c..8288492 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -1,12 +1,13 @@ #pragma once -#include #include #include #include #include #include #include +#include +#include namespace netkit::stream { From 2c16f8ea67637e7a7298c8a79220f4ed47ecab53 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:50:36 +0200 Subject: [PATCH 23/34] windows crap --- CMakeLists.txt | 4 ++++ include/netkit/stream/basic_stream.hpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6323c7..f02ada3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,10 @@ if (NETKIT_ENABLE_WSAPOLL) target_compile_definitions(netkit PUBLIC NETKIT_WSAPOLL) endif() +if (WIN32) + add_definitions(-DNOMINMAX) +endif() + if (WIN32) target_link_libraries(netkit PUBLIC ws2_32 iphlpapi dnsapi crypt32) elseif (UNIX AND NOT APPLE) diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index 8288492..b83988c 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -74,7 +74,7 @@ class basic_stream { if (res.status != stream_status::success) throw std::runtime_error("read failed"); - auto amount = std::min(res.bytes, max_bytes - total); + auto amount = (std::min)(res.bytes, max_bytes - total); result.insert( result.end(), From 1ef6afd3ea72a18d763b6a509c33414c2ef78b65 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 18:57:25 +0200 Subject: [PATCH 24/34] more... --- include/netkit/io/task.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/netkit/io/task.hpp b/include/netkit/io/task.hpp index e0c3c4a..e49611b 100644 --- a/include/netkit/io/task.hpp +++ b/include/netkit/io/task.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace netkit::io { From 0cafd9c5e556192f914a071d2e0775cfaab21e33 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:05:06 +0200 Subject: [PATCH 25/34] more... --- include/netkit/stream/basic_async_stream.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/netkit/stream/basic_async_stream.hpp b/include/netkit/stream/basic_async_stream.hpp index df097a0..d0dee8c 100644 --- a/include/netkit/stream/basic_async_stream.hpp +++ b/include/netkit/stream/basic_async_stream.hpp @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include namespace netkit::stream { From dddeb5d6d6b4281f25427015026cbd0a2b9294e4 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:11:47 +0200 Subject: [PATCH 26/34] more complete shit for windows --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f02ada3..21efdca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ option(NETKIT_ENABLE_DNS "Enable netkit's DNS features" ON) option(NETKIT_WOLFSSL_DEBUG "Enable WolfSSL debugging" OFF) option(NETKIT_BUILD_SHARED "Build netkit as a shared library" ON) option(NETKIT_USE_SYSTEM_WOLFSSL "Use system WolfSSL if found (not recommended)" OFF) -option(NETKIT_BUILD_EXAMPLES "Build netkit's examples" ON) +option(NETKIT_BUILD_EXAMPLES "Build netkit's examples" OFF) if (CMAKE_SYSTEM_NAME STREQUAL "NintendoWii" OR CMAKE_SYSTEM_NAME STREQUAL "NintendoGameCube") set(NETKIT_DKP ON) @@ -41,6 +41,10 @@ if (NOT NETKIT_BUILD_SHARED AND WIN32) ) endif() +if (WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) From 18b402a59c1c0a8272d31133515d884ff12d2672 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:12:42 +0200 Subject: [PATCH 27/34] dont explicitly include unistd in network_interface example (its useless anyway) --- examples/cpp/network_interface/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/cpp/network_interface/main.cpp b/examples/cpp/network_interface/main.cpp index fb4be41..1e00573 100644 --- a/examples/cpp/network_interface/main.cpp +++ b/examples/cpp/network_interface/main.cpp @@ -10,7 +10,6 @@ * @brief A clone of the 'ifconfig' command that lists network interfaces and their addresses. */ #include -#include #include const std::string RESET = "\033[0m"; From b1a796ac37a2d01ef6387664259301e8ae5f227f Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:19:13 +0200 Subject: [PATCH 28/34] more shit --- src/socket/addr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socket/addr.cpp b/src/socket/addr.cpp index c37d954..f4c8805 100644 --- a/src/socket/addr.cpp +++ b/src/socket/addr.cpp @@ -304,7 +304,7 @@ netkit::sock::sockaddr_len netkit::sock::addr::get_sa_len() const noexcept { #ifndef NETKIT_DKP if (this->is_file_path()) { const auto& file_path = this->get_path(); - return static_cast(offsetof(sockaddr_un, sun_path) + file_path.string().size() + 1); + return static_cast(offsetof(sockaddr_un, sun_path) + file_path.string().size() + 1); } #endif @@ -356,7 +356,7 @@ void netkit::sock::addr::prep_sa() { } } -netkit::sock::addr::addr(const sockaddr* sa, socklen_t len) { +netkit::sock::addr::addr(const sockaddr* sa, sockaddr_len len) { switch (sa->sa_family) { case AF_INET: { auto* sa4 = reinterpret_cast(sa); From e5ed7471c5e81573a4ee94054958223005ddcb92 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:22:42 +0200 Subject: [PATCH 29/34] commit these --- examples/cpp/http_server/css/index.css | 16 +++++++++++++++ examples/cpp/http_server/files/bliss.png | Bin 0 -> 109857 bytes examples/cpp/http_server/index.html | 25 +++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 examples/cpp/http_server/css/index.css create mode 100644 examples/cpp/http_server/files/bliss.png create mode 100644 examples/cpp/http_server/index.html diff --git a/examples/cpp/http_server/css/index.css b/examples/cpp/http_server/css/index.css new file mode 100644 index 0000000..af94006 --- /dev/null +++ b/examples/cpp/http_server/css/index.css @@ -0,0 +1,16 @@ +* { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + text-align: center; + box-sizing: border-box; +} + +button { + padding: 50px; +} + +h1 { + color: #333; + margin-top: 20px; +} \ No newline at end of file diff --git a/examples/cpp/http_server/files/bliss.png b/examples/cpp/http_server/files/bliss.png new file mode 100644 index 0000000000000000000000000000000000000000..22ceab5a9011d39a46d1df9e08ee7037c1c9d8be GIT binary patch literal 109857 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 + + + + + HTML test + + + + + Bliss +

HTML test

+

This is a simple HTML file to test the HTTP server.

+ + \ No newline at end of file From f847723d5b1f13acc9aab1cfa2cddf4d5be4b288 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 19:55:54 +0200 Subject: [PATCH 30/34] linux fixes --- include/netkit/platform/socket.hpp | 3 +++ include/netkit/stream/basic_stream.hpp | 1 + 2 files changed, 4 insertions(+) diff --git a/include/netkit/platform/socket.hpp b/include/netkit/platform/socket.hpp index 9bb4a05..9b31735 100644 --- a/include/netkit/platform/socket.hpp +++ b/include/netkit/platform/socket.hpp @@ -34,7 +34,10 @@ #ifdef NETKIT_WINDOWS #include #else +#include #include +#include +#include #endif namespace netkit::platform { diff --git a/include/netkit/stream/basic_stream.hpp b/include/netkit/stream/basic_stream.hpp index b83988c..ca88b68 100644 --- a/include/netkit/stream/basic_stream.hpp +++ b/include/netkit/stream/basic_stream.hpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace netkit::stream { From 8100ef4517a1306aa58d3331fab5a6ca5dc6fbae Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 20:04:15 +0200 Subject: [PATCH 31/34] linux fixes 2 --- include/netkit/platform/socket.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/netkit/platform/socket.hpp b/include/netkit/platform/socket.hpp index 9b31735..8901f2e 100644 --- a/include/netkit/platform/socket.hpp +++ b/include/netkit/platform/socket.hpp @@ -187,7 +187,7 @@ inline socket_err last_socket_error() { switch (errno) { case 0: return socket_err::none; case EWOULDBLOCK: - case EAGAIN: return socket_err::would_block; + //case EAGAIN: return socket_err::would_block; /* identical to EWOULDBLOCK */ case EINPROGRESS: return socket_err::in_progress; case EINTR: return socket_err::interrupted; case ECONNREFUSED: return socket_err::connection_refused; From e44b3af2d0dea8790185fabb66d25124b2df934e Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 20:08:28 +0200 Subject: [PATCH 32/34] linux fixes 3 --- src/socket/native/native_async_socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket/native/native_async_socket.cpp b/src/socket/native/native_async_socket.cpp index 6b9d646..5b27418 100644 --- a/src/socket/native/native_async_socket.cpp +++ b/src/socket/native/native_async_socket.cpp @@ -94,7 +94,7 @@ netkit::sock::native::native_async_sock::connect() { co_await this->context_.wait_writable(this->sockfd); int error = 0; - int len = sizeof(error); + socklen_t len = sizeof(error); if (getsockopt(this->sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len) < 0) { throw socket_error("getsockopt(SO_ERROR) failed"); From 4364765937341626ab89e808e9cb0e18df37931f Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 20:18:16 +0200 Subject: [PATCH 33/34] linux fixes 4 --- include/netkit/socket/native/native_async_listener.hpp | 5 +---- include/netkit/socket/native/native_async_sock.hpp | 5 +---- include/netkit/socket/native/native_sync_listener.hpp | 6 +----- include/netkit/socket/native/native_sync_sock.hpp | 7 ++----- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/include/netkit/socket/native/native_async_listener.hpp b/include/netkit/socket/native/native_async_listener.hpp index 03a9739..5dabbb6 100644 --- a/include/netkit/socket/native/native_async_listener.hpp +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -16,9 +16,6 @@ #include namespace netkit::sock::native { -#ifndef NETKIT_WINDOWS -constexpr fd_t INVALID_SOCKET = -1; -#endif class NETKIT_API native_async_listener : public basic_native_async_listener { public: @@ -47,7 +44,7 @@ class NETKIT_API native_async_listener : public basic_native_async_listener { addr addr_; type type_; - fd_t sockfd_{INVALID_SOCKET}; + fd_t sockfd_{}; opt opts_; diff --git a/include/netkit/socket/native/native_async_sock.hpp b/include/netkit/socket/native/native_async_sock.hpp index 1d68c4b..8e906b9 100644 --- a/include/netkit/socket/native/native_async_sock.hpp +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -25,13 +25,10 @@ #endif namespace netkit::sock::native { -#ifndef NETKIT_WINDOWS -constexpr fd_t INVALID_SOCKET = -1; -#endif class NETKIT_API native_async_sock : public basic_native_async_sock { addr addr_; type type_{}; - fd_t sockfd{INVALID_SOCKET}; + fd_t sockfd{}; bool bound{false}; diff --git a/include/netkit/socket/native/native_sync_listener.hpp b/include/netkit/socket/native/native_sync_listener.hpp index d2ceb65..db1342d 100644 --- a/include/netkit/socket/native/native_sync_listener.hpp +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -11,10 +11,6 @@ #include namespace netkit::sock::native { -#ifndef NETKIT_WINDOWS -constexpr fd_t INVALID_SOCKET = -1; -#endif - class NETKIT_API native_sync_listener : public basic_native_sync_listener { public: void set_sock_opts(opt opts) const; @@ -39,7 +35,7 @@ class NETKIT_API native_sync_listener : public basic_native_sync_listener { addr addr_; type type_; - fd_t sockfd_{INVALID_SOCKET}; + fd_t sockfd_{}; bool bound_{false}; bool listening_{false}; diff --git a/include/netkit/socket/native/native_sync_sock.hpp b/include/netkit/socket/native/native_sync_sock.hpp index aaa9b3b..a8b149e 100644 --- a/include/netkit/socket/native/native_sync_sock.hpp +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -30,11 +30,8 @@ namespace netkit::sock::native { class NETKIT_API native_sync_sock : public basic_native_sync_sock { addr addr_; type type_{}; -#ifdef NETKIT_WINDOWS - fd_t sockfd{INVALID_SOCKET}; -#else - fd_t sockfd{-1}; -#endif + fd_t sockfd{}; + bool bound{false}; mutable std::string old_bytes; #ifdef NETKIT_DKP From b4f00f9acdb7a1934ec8077b123897810bc78068 Mon Sep 17 00:00:00 2001 From: Jacob Nilsson Date: Fri, 31 Jul 2026 20:22:07 +0200 Subject: [PATCH 34/34] macos fix --- include/netkit/io/fallback/io_backend.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/netkit/io/fallback/io_backend.hpp b/include/netkit/io/fallback/io_backend.hpp index 56cb1e5..8e0bb07 100644 --- a/include/netkit/io/fallback/io_backend.hpp +++ b/include/netkit/io/fallback/io_backend.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace netkit::io {