diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 892ea19..9bcd594 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=OFF -DCMAKE_PREFIX_PATH=${{ env.CMAKE_PREFIX_PATH }} -S ${{ github.workspace }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 6abc2b1..21efdca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,21 @@ 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" 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) 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" OFF) if (CMAKE_SYSTEM_NAME STREQUAL "NintendoWii" OR CMAKE_SYSTEM_NAME STREQUAL "NintendoGameCube") set(NETKIT_DKP ON) @@ -19,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() @@ -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) @@ -59,14 +63,11 @@ 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/native/native_sync_socket.cpp src/dns/cache.cpp - src/sock/openssl/ssl_sync_sock.cpp - src/sock/wolfssl/ssl_sync_sock.cpp src/http/predefined.cpp src/http/sync_client.cpp src/crypto/windows/certs.cpp @@ -74,8 +75,28 @@ 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/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 + src/socket/native/peer_helper.cpp + src/tcp/tcp_server.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_stream.cpp + 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 + src/stream/wolfssl/tls_stream.cpp ) +add_library(netkit::netkit ALIAS netkit) + set_target_properties(netkit PROPERTIES EXPORT_NAME netkit POSITION_INDEPENDENT_CODE ON @@ -95,40 +116,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) @@ -158,7 +146,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( @@ -204,6 +192,26 @@ 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 (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) + add_definitions(-DNOMINMAX) +endif() + if (WIN32) target_link_libraries(netkit PUBLIC ws2_32 iphlpapi dnsapi crypt32) elseif (UNIX AND NOT APPLE) @@ -249,7 +257,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/stream/wolfssl/user_settings.h DESTINATION include/wolfssl/wolfcrypt/ ) @@ -367,4 +375,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/README.md b/README.md index daaad5f..acb87b2 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,22 @@ 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 -- 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 @@ -24,13 +31,14 @@ Please note, this library is still work in progress. Usage is not recommended ye Still missing: -- Asynchronous I/O -- Schannel support for Windows +- Asynchronous I/O for non-Linux platforms + - Threaded fallback is available +- Schannel support for Windows (waste of time I reckon) - WebSocket abstraction ## Dependencies -- OpenSSL or WolfSSL (optional) +- WolfSSL (optional) - C++23 compiler - CMake @@ -38,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 @@ -87,4 +94,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/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/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/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/CMakeLists.txt b/examples/cpp/http_server/CMakeLists.txt index 7b35d59..b3a365c 100644 --- a/examples/cpp/http_server/CMakeLists.txt +++ b/examples/cpp/http_server/CMakeLists.txt @@ -1,17 +1,16 @@ -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 -) +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_param/css/index.css b/examples/cpp/http_server/css/index.css similarity index 93% rename from examples/cpp/http_server_param/css/index.css rename to examples/cpp/http_server/css/index.css index bafcf0b..af94006 100644 --- a/examples/cpp/http_server_param/css/index.css +++ b/examples/cpp/http_server/css/index.css @@ -1,16 +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; +* { + 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/files/bliss.png similarity index 100% rename from examples/cpp/http_server_param/files/bliss.png rename to examples/cpp/http_server/files/bliss.png diff --git a/examples/cpp/http_server_param/index.html b/examples/cpp/http_server/index.html similarity index 96% rename from examples/cpp/http_server_param/index.html rename to examples/cpp/http_server/index.html index 53334ab..e74f9e8 100644 --- a/examples/cpp/http_server_param/index.html +++ b/examples/cpp/http_server/index.html @@ -1,25 +1,25 @@ - - - - - - HTML test - - - - - Bliss -

HTML test

-

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

- + + + + + + 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/main.cpp b/examples/cpp/http_server/main.cpp index 52f49e3..a56528e 100644 --- a/examples/cpp/http_server/main.cpp +++ b/examples/cpp/http_server/main.cpp @@ -1,48 +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 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; -} +/** 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/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/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/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"; 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 f20e139..2f4ac87 100644 --- a/examples/cpp/socket/main.cpp +++ b/examples/cpp/socket/main.cpp @@ -4,37 +4,42 @@ * 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. - * @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_stream 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/examples/cpp/socket_async/.gitignore b/examples/cpp/socket_async/.gitignore new file mode 100644 index 0000000..8401570 --- /dev/null +++ b/examples/cpp/socket_async/.gitignore @@ -0,0 +1,2 @@ +*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 new file mode 100644 index 0000000..58fb60d --- /dev/null +++ b/examples/cpp/socket_async/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.11) + +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) + +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_async main.cpp) + +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 new file mode 100644 index 0000000..aa1c117 --- /dev/null +++ b/examples/cpp/socket_async/main.cpp @@ -0,0 +1,62 @@ +#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::tcp::async_tcp_stream sock(ctx, addr); + + co_await sock.connect(); + + constexpr std::string_view http_request = + "GET / HTTP/1.1\r\n" + "Host: google.com\r\n" + "Connection: close\r\n" + "\r\n"; + + 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; + + std::array buffer{}; + + while (true) { + auto received = co_await sock.read(buffer); + + 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( + reinterpret_cast(buffer.data()), + received.bytes + ); + } + + sock.close(); + + std::ofstream file("response.txt"); + + if (!file) + throw std::runtime_error("failed to open file"); + + file << response; + + std::cout << "Response written to response.txt\n"; +} + +int main() { + netkit::io::io_context ctx; + + ctx.spawn(request(ctx)); + ctx.run_until_idle(); + + return 0; +} diff --git a/examples/cpp/dkp_socket_ssl/.gitignore b/examples/cpp/socket_server/.gitignore similarity index 100% rename from examples/cpp/dkp_socket_ssl/.gitignore rename to examples/cpp/socket_server/.gitignore diff --git a/examples/cpp/socket_server/CMakeLists.txt b/examples/cpp/socket_server/CMakeLists.txt new file mode 100644 index 0000000..3b54d24 --- /dev/null +++ b/examples/cpp/socket_server/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.11) + +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) + +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_server main.cpp) + +target_link_libraries(socket_server PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_server/main.cpp b/examples/cpp/socket_server/main.cpp new file mode 100644 index 0000000..ef754b4 --- /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" + 1337, + netkit::sock::addr_type::ipv4 + }; + + netkit::tcp::tcp_server server{addr}; + + server.bind(); + server.listen(); + + std::cout << "server listening on port 1337\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/.gitignore b/examples/cpp/socket_server_async/.gitignore new file mode 100644 index 0000000..8401570 --- /dev/null +++ b/examples/cpp/socket_server_async/.gitignore @@ -0,0 +1,2 @@ +*build* +response.txt \ 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..6ba0e8d --- /dev/null +++ b/examples/cpp/socket_server_async/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.11) + +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) + +if (NOT TARGET netkit::netkit) + find_package(netkit REQUIRED) +endif() + +add_executable(socket_server_async main.cpp) + +target_link_libraries(socket_server_async PRIVATE netkit::netkit) \ No newline at end of file diff --git a/examples/cpp/socket_server_async/main.cpp b/examples/cpp/socket_server_async/main.cpp new file mode 100644 index 0000000..366d84f --- /dev/null +++ b/examples/cpp/socket_server_async/main.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +netkit::io::task<> +handle_client(netkit::io::io_context& ctx, std::unique_ptr client) { + std::array buffer{}; + + std::string response; + while (true) { + auto received = co_await client->read(buffer); + + if (received.status != netkit::stream::stream_status::success) { + throw std::runtime_error{"error"}; + } + + if (received.bytes == 0) { + std::cout << "client disconnected\n"; + break; + } + + response.append(reinterpret_cast(buffer.data()), received.bytes); + + co_await client->write_all(std::span(buffer.data(), received.bytes)); + + 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::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 server.accept(); + std::cerr << "client " << client->peer().get_ip() << " connected\n"; + 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/examples/cpp/socket_ssl/CMakeLists.txt b/examples/cpp/socket_ssl/CMakeLists.txt index 1f0ebca..ce3fc05 100644 --- a/examples/cpp/socket_ssl/CMakeLists.txt +++ b/examples/cpp/socket_ssl/CMakeLists.txt @@ -1,16 +1,16 @@ -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) +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 index 5c13ee1..2d4e0d7 100644 --- a/examples/cpp/socket_ssl/main.cpp +++ b/examples/cpp/socket_ssl/main.cpp @@ -1,57 +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 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; -} +/** 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/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/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/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..9c3c38f 100644 --- a/include/netkit/body/stream_body.hpp +++ b/include/netkit/body/stream_body.hpp @@ -1,33 +1,28 @@ #pragma once +#include + #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, - 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::basic_sync_sock& socket_; + stream::basic_stream& stream_; std::optional remaining_; std::string buffer_; std::string overflow_; 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/definitions.hpp b/include/netkit/definitions.hpp index 2e2cd94..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 @@ -26,6 +28,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/dns/sync_resolver.hpp b/include/netkit/dns/sync_resolver.hpp index fb5258b..4d686e4 100644 --- a/include/netkit/dns/sync_resolver.hpp +++ b/include/netkit/dns/sync_resolver.hpp @@ -11,15 +11,18 @@ */ #pragma once +#ifdef NETKIT_DNS + #include #include #include #include #include #include -#include -#include -#include +#include +#include +#include +#include #ifdef NETKIT_UNIX #include @@ -83,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(); + }; + - uint8_t _lenbuf[2] = { - static_cast(query.size() >> 8), - static_cast(query.size()) + 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); + + 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); @@ -212,4 +231,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..10d66ae 100644 --- a/include/netkit/http/basic_request_handler.hpp +++ b/include/netkit/http/basic_request_handler.hpp @@ -11,14 +11,18 @@ */ #pragma once +#ifdef NETKIT_HTTP + +#include #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; }; } + +#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 e1ff1eb..c68f1a8 100644 --- a/include/netkit/http/basic_sync_server.hpp +++ b/include/netkit/http/basic_sync_server.hpp @@ -11,9 +11,10 @@ */ #pragma once +#ifdef NETKIT_HTTP + #include #include -#include #include namespace netkit::http::server { @@ -28,3 +29,5 @@ namespace netkit::http::server { virtual void stop() = 0; }; } + +#endif 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/request_handler.hpp b/include/netkit/http/request_handler.hpp index 84e9be3..a0cbab4 100644 --- a/include/netkit/http/request_handler.hpp +++ b/include/netkit/http/request_handler.hpp @@ -11,132 +11,162 @@ */ #pragma once +#ifdef NETKIT_HTTP + +#include #include #include -#include - +#include #include #include #include #include -#include #include -#include +#include 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; } @@ -152,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; @@ -176,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" @@ -191,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; @@ -200,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); } @@ -241,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)) { @@ -427,34 +450,52 @@ 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; + } + } } } client_sock->close(); } }; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/include/netkit/http/sync_client.hpp b/include/netkit/http/sync_client.hpp index 93b04ab..9a93c43 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 #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..accd128 100644 --- a/include/netkit/http/sync_server.hpp +++ b/include/netkit/http/sync_server.hpp @@ -12,12 +12,15 @@ */ #pragma once -#include +#ifdef NETKIT_HTTP + #include #include -#include #include -#include +#include +#include +#include +#include namespace netkit::http::server { /** @@ -28,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 @@ -43,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(); @@ -84,3 +86,5 @@ namespace netkit::http::server { } }; } + +#endif diff --git a/include/netkit/io/basic_io_backend.hpp b/include/netkit/io/basic_io_backend.hpp new file mode 100644 index 0000000..9d2c60e --- /dev/null +++ b/include/netkit/io/basic_io_backend.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +#ifdef NETKIT_WINDOWS +#include +#endif + +namespace netkit::io { + +struct io_handle_state { + std::vector> read_waiters; + std::vector> write_waiters; +}; + +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(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; + virtual void poll() = 0; +}; + +} // namespace netkit::io \ No newline at end of file diff --git a/include/netkit/io/fallback/io_backend.hpp b/include/netkit/io/fallback/io_backend.hpp new file mode 100644 index 0000000..8e0bb07 --- /dev/null +++ b/include/netkit/io/fallback/io_backend.hpp @@ -0,0 +1,58 @@ +#pragma once + +// ReSharper disable once CppUnusedIncludeDirective +#include + +#if !defined(NETKIT_LINUX) || !defined(NETKIT_EPOLL) +#if !defined(NETKIT_WINDOWS) || !defined(NETKIT_WSAPOLL) + +#include +#include +#include +#include +#include +#include +#include + +namespace netkit::io { + +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(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; + }; + + 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 +#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..290196d --- /dev/null +++ b/include/netkit/io/io_awaitable.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +namespace netkit::io { + class io_context; // fw decl. + + struct io_awaitable { + io_backend& ctx; + io_handle_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 {} + }; +} \ No newline at end of file diff --git a/include/netkit/io/io_backend.hpp b/include/netkit/io/io_backend.hpp new file mode 100644 index 0000000..e91ec49 --- /dev/null +++ b/include/netkit/io/io_backend.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include +#include +#include \ 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..2bea742 --- /dev/null +++ b/include/netkit/io/io_context.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +namespace netkit::io { + +class NETKIT_API io_context { +public: + void run() { + running_ = true; + + while (running_) { + backend_.poll(!tasks_.empty() ? 0 : -1); + + cleanup_tasks(); + } + } + + void run_until_idle() { + running_ = true; + + while (running_ && !tasks_.empty()) { + backend_.poll(!tasks_.empty() ? 0 : -1); + + cleanup_tasks(); + } + } + + void stop() { + running_.store(false); + backend_.wake(); + } + + io_awaitable wait_readable(io_handle_t fd) { + return io_awaitable{backend_, fd, io_event::read}; + } + + io_awaitable wait_writable(io_handle_t 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(); } ); + } + + io_backend backend_; + std::atomic_bool running_ = false; + + std::vector> tasks_; +}; + +} \ No newline at end of file diff --git a/include/netkit/io/linux/io_backend.hpp b/include/netkit/io/linux/io_backend.hpp new file mode 100644 index 0000000..a595a73 --- /dev/null +++ b/include/netkit/io/linux/io_backend.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#if defined(NETKIT_LINUX) && defined(NETKIT_EPOLL) + +#include + +#include +#include +#include + +namespace netkit::io { + +class io_backend : public basic_io_backend { +public: + io_backend(); + ~io_backend() override; + + void wake() 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: + io_handle_t epoll_fd_; + bool running_ = true; + io_handle_t 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/task.hpp b/include/netkit/io/task.hpp new file mode 100644 index 0000000..e49611b --- /dev/null +++ b/include/netkit/io/task.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace netkit::io { + +template +class task; + +template +struct promise_return { + std::optional value; + + template + void return_value(U&& v) { + value.emplace(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 { + if (auto _continuation = h.promise().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_; +}; + +} \ No newline at end of file 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 4e5cefb..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 @@ -32,15 +34,18 @@ #include #include +// Platform headers +#include + // Socket headers -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include // HTTP headers #include @@ -63,4 +68,38 @@ #include #include +// io +#include +#include +#include +#include +#include + +// streams +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// tcp +#include +#include +#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 new file mode 100644 index 0000000..8901f2e --- /dev/null +++ b/include/netkit/platform/socket.hpp @@ -0,0 +1,290 @@ +/* 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 +#include +#include +#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; /* identical to EWOULDBLOCK */ + 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) { + 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); +} + +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/sock/basic_sync_sock.hpp b/include/netkit/sock/basic_sync_sock.hpp deleted file mode 100644 index 36c5924..0000000 --- a/include/netkit/sock/basic_sync_sock.hpp +++ /dev/null @@ -1,59 +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 basic_sync_sock.hpp - * @license MIT - * @note Part of the Netkit library. - * @brief Provides a basic interface for synchronous sockets. - */ -#pragma once - -#include -#include -#include - -namespace netkit::sock { - /** - * @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 { - public: - virtual ~basic_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 {}; }; - 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 {} - virtual void close() = 0; - [[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"}; - } - }; -} \ No newline at end of file diff --git a/include/netkit/sock/openssl/ssl_sync_sock.hpp b/include/netkit/sock/openssl/ssl_sync_sock.hpp deleted file mode 100644 index 063fbe1..0000000 --- a/include/netkit/sock/openssl/ssl_sync_sock.hpp +++ /dev/null @@ -1,96 +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 -#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/sock/sock_peer.hpp b/include/netkit/sock/sock_peer.hpp deleted file mode 100644 index 0e6e249..0000000 --- a/include/netkit/sock/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/sock/ssl_sync_sock.hpp b/include/netkit/sock/ssl_sync_sock.hpp deleted file mode 100644 index 06d8187..0000000 --- a/include/netkit/sock/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/sock/ssl_sync_sock_enum.hpp b/include/netkit/sock/ssl_sync_sock_enum.hpp deleted file mode 100644 index dee0420..0000000 --- a/include/netkit/sock/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/sock/sync_sock.hpp b/include/netkit/sock/sync_sock.hpp deleted file mode 100644 index 1d366b8..0000000 --- a/include/netkit/sock/sync_sock.hpp +++ /dev/null @@ -1,165 +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). - */ -#ifdef NETKIT_UNIX - sync_sock(const sock::addr& addr, sock::type t, opt opts = opt::no_reuse_addr|opt::no_delay|opt::blocking); - /** - * @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 -#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; - [[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/wolfssl/ssl_sync_sock.hpp b/include/netkit/sock/wolfssl/ssl_sync_sock.hpp deleted file mode 100644 index 4d506ab..0000000 --- a/include/netkit/sock/wolfssl/ssl_sync_sock.hpp +++ /dev/null @@ -1,106 +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 -#endif - -#include -#include -#include -#include - -#include -#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, - 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/sock/addr.hpp b/include/netkit/socket/addr.hpp similarity index 84% rename from include/netkit/sock/addr.hpp rename to include/netkit/socket/addr.hpp index 2181f06..d40c111 100644 --- a/include/netkit/sock/addr.hpp +++ b/include/netkit/socket/addr.hpp @@ -1,88 +1,104 @@ -/** 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); - - 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 + +#ifdef NETKIT_WINDOWS +#include +#else +#include +#include +#endif + +namespace netkit::sock { + using sockaddr_len = int; + + class NETKIT_API addr final { + std::filesystem::path path{}; + std::string hostname{}; + std::string ip{}; + int port{}; + addr_type type{addr_type::hostname}; + + sockaddr_storage sa_storage_{}; + + addr() = default; + + void prep_sa(); + 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 + 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. + */ + [[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; + + [[nodiscard]] const sockaddr* get_sa() const noexcept; + [[nodiscard]] sockaddr_len get_sa_len() const noexcept; + }; +} 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/socket/async_sock.hpp b/include/netkit/socket/async_sock.hpp new file mode 100644 index 0000000..0901462 --- /dev/null +++ b/include/netkit/socket/async_sock.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace netkit::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 new file mode 100644 index 0000000..4857b10 --- /dev/null +++ b/include/netkit/socket/native/basic_native_async_listener.hpp @@ -0,0 +1,28 @@ +#pragma once + +#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 bind(const addr& addr) = 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"}; + } + }; +} \ 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 new file mode 100644 index 0000000..f51a68c --- /dev/null +++ b/include/netkit/socket/native/basic_native_async_sock.hpp @@ -0,0 +1,55 @@ +/** 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_native_async_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a basic interface for asynchronous sockets. + */ +#pragma once + +#include +#include +#include +#include + +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_native_async_sock { + public: + virtual ~basic_native_async_sock() = default; + 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"}; + } + [[nodiscard]] virtual const addr& get_addr() const { + throw std::logic_error{"socket does not have an addr object"}; + } + [[nodiscard]] virtual addr get_peer() const { + throw std::logic_error{"socket does not have a peer"}; + }; + 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"}; + } + }; +} \ No newline at end of file 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..a110949 --- /dev/null +++ b/include/netkit/socket/native/basic_native_sync_listener.hpp @@ -0,0 +1,32 @@ +#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 bind(const addr& addr) = 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 new file mode 100644 index 0000000..157c7fe --- /dev/null +++ b/include/netkit/socket/native/basic_native_sync_sock.hpp @@ -0,0 +1,51 @@ +/** 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_native_sync_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides a basic interface for synchronous sockets. + */ +#pragma once + +#include +#include +#include + +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_native_sync_sock { + public: + virtual ~basic_native_sync_sock() = default; + 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"}; + } + [[nodiscard]] virtual const addr& get_addr() 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"}; + } + 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..5dabbb6 --- /dev/null +++ b/include/netkit/socket/native/native_async_listener.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#ifdef NETKIT_UNIX +#include +#elifdef NETKIT_WINDOWS +#include +#endif + +#include + +#include +#include +#include +#include + +namespace netkit::sock::native { + +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::reuse_addr | opt::no_blocking); + ~native_async_listener() override; + + void bind() override; + void bind(const addr& addr) 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_{}; + + opt opts_; + + bool bound_{false}; + bool listening_{false}; + + sockaddr_storage sa_storage_{}; +}; + +} \ 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 new file mode 100644 index 0000000..8e906b9 --- /dev/null +++ b/include/netkit/socket/native/native_async_sock.hpp @@ -0,0 +1,58 @@ +/** 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_async_sock.hpp + * @license MIT + * @note Part of the Netkit library. + * @brief Provides an asynchronous socket class implementing the basic_native_async_sock interface. + */ +#pragma once + +#include + +#include +#include +#include +#include + +#ifdef NETKIT_UNIX +#include +#elifdef NETKIT_WINDOWS +#include +#endif + +namespace netkit::sock::native { + class NETKIT_API native_async_sock : public basic_native_async_sock { + addr addr_; + type type_{}; + fd_t sockfd{}; + + bool bound{false}; + + 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); + 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; + 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; + 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 new file mode 100644 index 0000000..db1342d --- /dev/null +++ b/include/netkit/socket/native/native_sync_listener.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef NETKIT_UNIX +#include +#elifdef NETKIT_WINDOWS +#include +#endif + +#include + +namespace netkit::sock::native { +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; + 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_{}; + + bool bound_{false}; + bool listening_{false}; + + opt opts_; +}; + +} \ 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 new file mode 100644 index 0000000..a8b149e --- /dev/null +++ b/include/netkit/socket/native/native_sync_sock.hpp @@ -0,0 +1,84 @@ +/** 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_{}; + fd_t sockfd{}; + + bool bound{false}; + mutable std::string old_bytes; +#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::reuse_addr|opt::no_delay|opt::blocking); + /** + * @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::reuse_addr|opt::no_delay|opt::blocking); + ~native_sync_sock() override; + sock::addr& get_addr() override; + [[nodiscard]] const sock::addr& get_addr() const override; + void connect() 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; + 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. + */ + 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/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/async_socket_stream.hpp b/include/netkit/stream/async_socket_stream.hpp new file mode 100644 index 0000000..eaa49c6 --- /dev/null +++ b/include/netkit/stream/async_socket_stream.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +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; + [[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; + + std::optional get_addr() override { + return socket_->get_addr(); + } +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..d0dee8c --- /dev/null +++ b/include/netkit/stream/basic_async_stream.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace netkit::stream { + +class 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)); + } + + 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; + + 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 new file mode 100644 index 0000000..ca88b68 --- /dev/null +++ b/include/netkit/stream/basic_stream.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#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; + + stream_result read(void* data, std::size_t size) { + 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)); + } + + 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()) + ) + ); + } + + 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; + + 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; + + virtual std::optional get_addr() { + return {}; + } +}; + +} \ 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..17961a2 --- /dev/null +++ b/include/netkit/stream/memory_stream.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + +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)) {} + + stream_result read(std::span buffer) override { + if (read_pos_ >= buffer_.size()) { + return { + 0, + stream_status::eof + }; + } + + 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/socket_stream.hpp b/include/netkit/stream/socket_stream.hpp new file mode 100644 index 0000000..beed6a8 --- /dev/null +++ b/include/netkit/stream/socket_stream.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +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)) {} + + void connect(); + + [[nodiscard]] stream_result read(std::span buffer) override; + [[nodiscard]] stream_result write(std::span buffer) override; + + void close() noexcept override; + + [[nodiscard]] sock::addr peer() const; + + std::optional get_addr() override { + return socket_->get_addr(); + } +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 new file mode 100644 index 0000000..109e7f7 --- /dev/null +++ b/include/netkit/stream/stream_enum.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace netkit::stream { + enum class stream_status { + success, + closed, + error, + eof + }; + + struct stream_result { + std::size_t bytes{}; + stream_status status{stream_status::closed}; + }; +} \ No newline at end of file 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..ff4e779 --- /dev/null +++ b/include/netkit/stream/wolfssl/tls_stream.hpp @@ -0,0 +1,42 @@ +#pragma once + +#ifdef NETKIT_WOLFSSL + +#include +#include +#include +#include + +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 = {}); + + ~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/sock/wolfssl/user_settings.h b/include/netkit/stream/wolfssl/user_settings.h similarity index 93% rename from include/netkit/sock/wolfssl/user_settings.h rename to include/netkit/stream/wolfssl/user_settings.h index b866974..6bbc823 100644 --- a/include/netkit/sock/wolfssl/user_settings.h +++ b/include/netkit/stream/wolfssl/user_settings.h @@ -1,76 +1,77 @@ -#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 + * however, netkit cmakelists defines this if -DNETKIT_DEBUG and/or -DNETKIT_WOLFSSL_DEBUG +#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/include/netkit/tcp/async_tcp_server.hpp b/include/netkit/tcp/async_tcp_server.hpp new file mode 100644 index 0000000..c07a27f --- /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_stream; + +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/async_tcp_stream.hpp b/include/netkit/tcp/async_tcp_stream.hpp new file mode 100644 index 0000000..03c6553 --- /dev/null +++ b/include/netkit/tcp/async_tcp_stream.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +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; + + [[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_server.hpp b/include/netkit/tcp/tcp_server.hpp new file mode 100644 index 0000000..a5a3c92 --- /dev/null +++ b/include/netkit/tcp/tcp_server.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace netkit::tcp { + +class tcp_stream; + +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/include/netkit/tcp/tcp_stream.hpp b/include/netkit/tcp/tcp_stream.hpp new file mode 100644 index 0000000..9b9d817 --- /dev/null +++ b/include/netkit/tcp/tcp_stream.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +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)) {} + + ~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; + std::optional get_addr() override { + return stream_.get_addr(); + } + + stream::socket_stream& stream(); + +private: + stream::socket_stream stream_; +}; + +} \ No newline at end of file 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/body/stream_body.cpp b/src/body/stream_body.cpp index 33dfeb9..f824355 100644 --- a/src/body/stream_body.cpp +++ b/src/body/stream_body.cpp @@ -1,88 +1,103 @@ #include +#ifndef NOMINMAX #define NOMINMAX // some windows shit +#endif #include -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}; + return { + read_status::ok, + n + }; } \ 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..55dd63a 100644 --- a/src/http/sync_client.cpp +++ b/src/http/sync_client.cpp @@ -9,59 +9,67 @@ * @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 #include std::string netkit::http::client::sync_client::make_request(const std::string& request) const { 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; @@ -69,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; @@ -207,3 +201,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/io/fallback/io_backend.cpp b/src/io/fallback/io_backend.cpp new file mode 100644 index 0000000..b91b618 --- /dev/null +++ b/src/io/fallback/io_backend.cpp @@ -0,0 +1,106 @@ +#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_) { + 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(io_handle_t 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::vector pending; + + { + std::unique_lock lock(mutex_); + + if (waiters_.empty()) { + cv_.wait_for( + lock, + std::chrono::milliseconds(timeout_ms) + ); + } + + pending.swap(waiters_); + } + + for (auto& waiter : pending) { + if (waiter.handle) + waiter.handle.resume(); + } +} + + +void netkit::io::io_backend::poll() { + poll(-1); +} + +#endif +#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/io/linux/io_backend.cpp b/src/io/linux/io_backend.cpp new file mode 100644 index 0000000..70e2901 --- /dev/null +++ b/src/io/linux/io_backend.cpp @@ -0,0 +1,215 @@ +#include + +#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]; + + 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_state(fd, state); + } +} + +void netkit::io::io_backend::poll() { + this->poll(-1); +} + +#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/sock/openssl/ssl_sync_sock.cpp b/src/sock/openssl/ssl_sync_sock.cpp deleted file mode 100644 index 6be64f7..0000000 --- a/src/sock/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 -#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/sock/sock_peer.cpp b/src/sock/sock_peer.cpp deleted file mode 100644 index 95ab440..0000000 --- a/src/sock/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 diff --git a/src/sock/sync_sock.cpp b/src/sock/sync_sock.cpp deleted file mode 100644 index 89fb1d2..0000000 --- a/src/sock/sync_sock.cpp +++ /dev/null @@ -1,802 +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.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(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(this->sockfd); -#endif -} -netkit::sock::fd_t netkit::sock::sync_sock::native_handle() const { - return this->sockfd; -} \ No newline at end of file diff --git a/src/sock/wolfssl/ssl_sync_sock.cpp b/src/sock/wolfssl/ssl_sync_sock.cpp deleted file mode 100644 index 83e8045..0000000 --- a/src/sock/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/sock/addr.cpp b/src/socket/addr.cpp similarity index 71% rename from src/sock/addr.cpp rename to src/socket/addr.cpp index 3b14780..f4c8805 100644 --- a/src/sock/addr.cpp +++ b/src/socket/addr.cpp @@ -1,285 +1,399 @@ -/** 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 + +#ifdef NETKIT_DKP +#include +#include +#endif + +#if defined(NETKIT_UNIX) && !defined(NETKIT_DKP) +#include +#include +#include +#include +#endif + +#ifdef NETKIT_WINDOWS +#include +#include +#endif + +/* solely for use internally */ +#ifndef NETKIT_DKP +#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}; + } + + 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 NETKIT_WINDOWS + 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(); + } + + prep_sa(); +} + +#ifndef NETKIT_DKP +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 { + 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; +} + +const sockaddr* netkit::sock::addr::get_sa() const noexcept { + return reinterpret_cast(&sa_storage_); +} + +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 + 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, sockaddr_len 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 new file mode 100644 index 0000000..61b794b --- /dev/null +++ b/src/socket/native/native_async_listener.cpp @@ -0,0 +1,136 @@ +#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 { + platform::set_sock_opts(this->sockfd_, opts); +} + +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); + + if (!platform::valid_socket(sockfd_)) + throw socket_error("failed creating socket"); + + set_sock_opts(opts_); +} + +netkit::sock::native::native_async_listener::~native_async_listener() { + this->native_async_listener::close(); +} + +void netkit::sock::native::native_async_listener::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_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_async_listener::unbind() { + this->close(); +} + +void netkit::sock::native::native_async_listener::listen(int backlog) { + if (!bound_) throw socket_error("listener not bound"); + + if (platform::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); + + fd_t client_sockfd = platform::accept( + this->sockfd_, + reinterpret_cast(&client_addr), + &addr_len + ); + + if (platform::valid_socket(client_sockfd)) { + 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 (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: " + platform::last_error_message()); + } +} + +void netkit::sock::native::native_async_listener::close() noexcept { + if (platform::valid_socket(sockfd_)) { + platform::close_socket(sockfd_); + 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 new file mode 100644 index 0000000..5b27418 --- /dev/null +++ b/src/socket/native/native_async_socket.cpp @@ -0,0 +1,283 @@ +/** 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_async_socket.cpp + * @license MIT + * @note Part of the Netkit library. + * @brief Implementation of the asynchronous socket class. + */ + +#include +#include +#include + +#include +#include + +#ifdef NETKIT_UNIX +#include +#include +#include +#include +#include +#include +#elif defined(NETKIT_WINDOWS) +#include +#endif + +void netkit::sock::native::native_async_sock::set_sock_opts(opt opts) { + 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) { + + 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 = 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 (!platform::valid_socket(sockfd)) { + throw socket_error{"failed to create socket"}; + } + + this->native_async_sock::set_sock_opts(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 (!platform::valid_socket(sockfd)) { + throw socket_error{"invalid fd"}; + } + + this->native_async_sock::set_sock_opts(opts); +} + +netkit::sock::native::native_async_sock::~native_async_sock() { + this->native_async_sock::close(); +} + +netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() { + return this->addr_; +} + +const netkit::sock::addr& netkit::sock::native::native_async_sock::get_addr() const { + return this->addr_; +} + +netkit::io::task +netkit::sock::native::native_async_sock::connect() { + int ret = platform::connect(this->sockfd, addr_.get_sa(), addr_.get_sa_len()); + + if (ret == 0) { + co_return; + } + + 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); + + int error = 0; + 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"); + } + + if (error != 0) { + throw socket_error("failed to connect to server: " + std::to_string(error)); + } + + co_return; +} + +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) { + 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; + } + + if (sent == 0) { + co_return total_sent; + } + + if (err == platform::socket_err::would_block) { + co_await context_.wait_writable(sockfd); + continue; + } + + if (err == platform::socket_err::interrupted) { + continue; + } + + throw socket_error("failed to send: " + platform::last_error_message()); + } + + co_return total_sent; +} + +netkit::io::task +netkit::sock::native::native_async_sock::recv(void* buf, size_t size) { + for (;;) { + auto n = platform::recv(this->sockfd, static_cast(buf), size, 0); + + if (n > 0) { + co_return static_cast(n); + } + + if (n == 0) { + co_return 0; + } + + 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("recv failed: " + platform::last_error_message()); + } +} + +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; + } +} + +[[nodiscard]] netkit::sock::addr netkit::sock::native::native_async_sock::get_peer() const { + return native::get_peer(this->sockfd); +} + +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 new file mode 100644 index 0000000..33d3d93 --- /dev/null +++ b/src/socket/native/native_sync_listener.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include +#include +#include +#include + +#ifdef NETKIT_UNIX +#include +#include +#include +#include +#include +#include +#elif defined(NETKIT_WINDOWS) +#include +#endif + +void netkit::sock::native::native_sync_listener::set_sock_opts(opt opts) const { + netkit::platform::set_sock_opts(this->sockfd_, opts); +} + +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); + + if (!platform::valid_socket(sockfd_)) + throw socket_error("failed creating socket"); + + set_sock_opts(opts_); +} + +void netkit::sock::native::native_sync_listener::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_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"); + + if (platform::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); + + 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_); + } +#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 (platform::valid_socket(sockfd_)) { + platform::close_socket(sockfd_); + 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 new file mode 100644 index 0000000..095f0ba --- /dev/null +++ b/src/socket/native/native_sync_socket.cpp @@ -0,0 +1,171 @@ +/** 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 +#elif NETKIT_UNIX +#include +#ifndef NETKIT_DKP +#include +#else +#include +#include +#endif +#include +#include +#include +#include +#endif + +#include +#include + +void netkit::sock::native::native_sync_sock::connect() { + if (platform::connect(sockfd, addr_.get_sa(), addr_.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) { + platform::set_sock_opts(this->sockfd, opts); +} + +netkit::sock::native::native_sync_sock::native_sync_sock(const sock::addr& addr, sock::type t, opt opts) : addr_(addr), type_(t) { + 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 = 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 (!platform::valid_socket(sockfd)) + throw socket_error{"failed to create socket"}; + + this->native_sync_sock::set_sock_opts(opts); +} + +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 (!platform::valid_socket(sockfd)) + throw socket_error{"invalid fd"}; + + this->native_sync_sock::set_sock_opts(opts); +} + +netkit::sock::native::native_sync_sock::~native_sync_sock() { + this->native_sync_sock::close(); +} + +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_; +} + +std::size_t netkit::sock::native::native_sync_sock::send(const void* buf, size_t len) { + 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 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); +} + +[[nodiscard]] netkit::sock::addr netkit::sock::native::native_sync_sock::get_peer() const { + return native::get_peer(this->sockfd); +} + +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/socket/native/peer_helper.cpp b/src/socket/native/peer_helper.cpp new file mode 100644 index 0000000..1e8759b --- /dev/null +++ b/src/socket/native/peer_helper.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#ifdef NETKIT_UNIX +#include +#include +#include +#endif + +#include +#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); + + 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 + }; +#endif +} \ 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..2671b84 --- /dev/null +++ b/src/stream/async_socket_stream.cpp @@ -0,0 +1,36 @@ +#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/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 diff --git a/src/tcp/async_tcp_server.cpp b/src/tcp/async_tcp_server.cpp new file mode 100644 index 0000000..ee93568 --- /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/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_server.cpp b/src/tcp/tcp_server.cpp new file mode 100644 index 0000000..127da02 --- /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 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 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