diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2a60a0a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,22 @@
+BasedOnStyle: LLVM
+BreakBeforeBraces: Attach
+
+ColumnLimit: 120 # Match GitHub UI
+
+UseTab: Always
+TabWidth: 4
+IndentWidth: 4
+AccessModifierOffset: -4
+ContinuationIndentWidth: 4
+NamespaceIndentation: All
+IndentCaseLabels: false
+
+PointerAlignment: Left
+AlwaysBreakTemplateDeclarations: Yes
+SpaceAfterTemplateKeyword: false
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortBlocksOnASingleLine: Always
+
+FixNamespaceComments: true
+ReflowComments: false
diff --git a/.github/actions/badge/action.yml b/.github/actions/badge/action.yml
new file mode 100644
index 0000000..5bb03d4
--- /dev/null
+++ b/.github/actions/badge/action.yml
@@ -0,0 +1,27 @@
+name: Regular badging sequence
+description: Publishes a badge based on the job status
+inputs:
+ category:
+ description: The subfolder where to group the badges
+ required: true
+ badges:
+ description: A json object of label => status for all badges
+ required: true
+ github_token:
+ description: The token to use to publish the changes
+ required: false
+ default: ${{ github.token }}
+runs:
+ using: composite
+ steps:
+ - run: |
+ node ./.github/actions/badge/write-json-object.js ${{ inputs.category }} '${{ inputs.badges }}'
+ shell: bash
+ - uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ inputs.github_token }}
+ publish_branch: badges
+ publish_dir: ./badges
+ keep_files: true
+ user_name: "github-actions[bot]"
+ user_email: "github-actions[bot]@users.noreply.github.com"
\ No newline at end of file
diff --git a/.github/actions/badge/write-json-object.js b/.github/actions/badge/write-json-object.js
new file mode 100644
index 0000000..2303fee
--- /dev/null
+++ b/.github/actions/badge/write-json-object.js
@@ -0,0 +1,17 @@
+const fs = require('fs');
+const category = process.argv[2];
+const status = JSON.parse(process.argv[3]);
+
+if (!fs.existsSync("./badges")) fs.mkdirSync("./badges");
+if (!fs.existsSync("./badges/" + category)) fs.mkdirSync("./badges/" + category);
+for (let e in status) {
+ const path = "./badges/" + category + "/" + e;
+ if (!fs.existsSync(path)) fs.mkdirSync(path);
+ const ok = status[e] == "success";
+ fs.writeFileSync(path + "/shields.json", JSON.stringify({
+ "schemaVersion": 1,
+ "label": e,
+ "message": ok ? "Passing" : "Failing",
+ "color": ok ? "brightgreen" : "red"
+ }));
+}
diff --git a/.github/actions/process-linting-results/action.yml b/.github/actions/process-linting-results/action.yml
new file mode 100644
index 0000000..a90bbab
--- /dev/null
+++ b/.github/actions/process-linting-results/action.yml
@@ -0,0 +1,26 @@
+name: Process Linting Results
+description: Add a comment to a pull request with when `git diff` present and save the changes as an artifact so they can be applied manually
+inputs:
+ linter_name:
+ description: The name of the tool to credit in the comment
+ required: true
+runs:
+ using: "composite"
+ steps:
+ - run: git add --update
+ shell: bash
+ - id: stage
+ #continue-on-error: true
+ uses: Thalhammer/patch-generator-action@v2
+
+ # Unfortunately the previous action reports a failure so nothing else can run
+ # partially a limitation on composite actions since `continue-on-error` is not
+ # yet supported
+ - if: steps.stage.outputs.result == 'dirty'
+ uses: actions-ecosystem/action-create-comment@v1
+ with:
+ github_token: ${{ github.token }}
+ body: |
+ Hello, @${{ github.actor }}! `${{ inputs.linter_name }}` had some concerns :scream:
+ - run: exit $(git status -uno -s | wc -l)
+ shell: bash
\ No newline at end of file
diff --git a/.github/logo.svg b/.github/logo.svg
new file mode 100644
index 0000000..be78f4e
--- /dev/null
+++ b/.github/logo.svg
@@ -0,0 +1,80 @@
+
+
+
+
diff --git a/.github/scripts/add-newline-if-missing.sh b/.github/scripts/add-newline-if-missing.sh
new file mode 100755
index 0000000..9f751ef
--- /dev/null
+++ b/.github/scripts/add-newline-if-missing.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+if [[ -f "$1" && -s "$1" ]]; then
+ if [[ -n "$(tail -c 1 "$1")" ]]; then
+ echo "Fixed missing newline in file $1"
+ sed -i -e '$a\' $1
+ fi
+fi
\ No newline at end of file
diff --git a/.github/workflows/compiler-support.yml b/.github/workflows/compiler-support.yml
new file mode 100644
index 0000000..50dafd3
--- /dev/null
+++ b/.github/workflows/compiler-support.yml
@@ -0,0 +1,128 @@
+name: Compiler Compatibility CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+
+jobs:
+ build:
+ strategy:
+ fail-fast: false
+ matrix:
+ compiler:
+ # GCC 13 on MacOS seems to be generally broken (https://github.com/actions/runner-images/issues/9997) and therefore disabled
+ - { tag: "ubuntu-2204_clang-13", name: "Ubuntu 22.04 Clang 13", cxx: "/usr/bin/clang++-13", cc: "/usr/bin/clang-13", runs-on: "ubuntu-22.04" }
+ - { tag: "ubuntu-2204_clang-14", name: "Ubuntu 22.04 Clang 14", cxx: "/usr/bin/clang++-14", cc: "/usr/bin/clang-14", runs-on: "ubuntu-22.04" }
+ - { tag: "ubuntu-2204_clang-15", name: "Ubuntu 22.04 Clang 15", cxx: "/usr/bin/clang++-15", cc: "/usr/bin/clang-15", runs-on: "ubuntu-22.04" }
+ - { tag: "ubuntu-2204_gcc-10", name: "Ubuntu 22.04 G++ 10", cxx: "/usr/bin/g++-10", cc: "/usr/bin/gcc-10", runs-on: "ubuntu-22.04" }
+ - { tag: "ubuntu-2204_gcc-11", name: "Ubuntu 22.04 G++ 11", cxx: "/usr/bin/g++-11", cc: "/usr/bin/gcc-11", runs-on: "ubuntu-22.04" }
+ - { tag: "ubuntu-2004_clang-12", name: "Ubuntu 20.04 Clang 12", cxx: "/usr/bin/clang++-12", cc: "/usr/bin/clang-12", runs-on: "ubuntu-20.04" }
+ - { tag: "ubuntu-2004_clang-11", name: "Ubuntu 20.04 Clang 11", cxx: "/usr/bin/clang++-11", cc: "/usr/bin/clang-11", runs-on: "ubuntu-20.04" }
+ - { tag: "ubuntu-2004_clang-10", name: "Ubuntu 20.04 Clang 10", cxx: "/usr/bin/clang++-10", cc: "/usr/bin/clang-10", runs-on: "ubuntu-20.04" }
+ - { tag: "ubuntu-2004_gcc-10", name: "Ubuntu 20.04 G++ 10", cxx: "/usr/bin/g++-10", cc: "/usr/bin/gcc-10", runs-on: "ubuntu-20.04" }
+ #- { tag: "windows-2022_msvc17", name: "Windows Server 2022 MSVC 17", cxx: "", cc: "", runs-on: "windows-2022" }
+ #- { tag: "windows-2019_msvc16", name: "Windows Server 2019 MSVC 16", cxx: "", cc: "", runs-on: "windows-2019" }
+ - { tag: "macos-12_gcc-12", name: "MacOS 12 G++ 12", cxx: "g++-12", cc: "gcc-12", runs-on: "macos-12" }
+ #- { tag: "macos-12_gcc-13", name: "MacOS 12 G++ 13", cxx: "g++-13", cc: "gcc-13", runs-on: "macos-12" }
+ - { tag: "macos-12_gcc-14", name: "MacOS 12 G++ 14", cxx: "g++-14", cc: "gcc-14", runs-on: "macos-12" }
+ - { tag: "macos-12_clang-15", name: "MacOS 12 Clang 15", cxx: "/usr/local/opt/llvm@15/bin/clang++", cc: "/usr/local/opt/llvm@15/bin/clang", runs-on: "macos-12" }
+ - { tag: "macos-13_gcc-12", name: "MacOS 13 G++ 12", cxx: "g++-12", cc: "gcc-12", runs-on: "macos-13" }
+ #- { tag: "macos-13_gcc-13", name: "MacOS 13 G++ 13", cxx: "g++-13", cc: "gcc-13", runs-on: "macos-13" }
+ - { tag: "macos-13_gcc-14", name: "MacOS 13 G++ 14", cxx: "g++-14", cc: "gcc-14", runs-on: "macos-13" }
+ - { tag: "macos-13_clang-15", name: "MacOS 13 Clang 15", cxx: "/usr/local/opt/llvm@15/bin/clang++", cc: "/usr/local/opt/llvm@15/bin/clang", runs-on: "macos-13" }
+ - { tag: "macos-14_gcc-12", name: "MacOS 14 G++ 12", cxx: "g++-12", cc: "gcc-12", runs-on: "macos-14" }
+ #- { tag: "macos-14_gcc-13", name: "MacOS 14 G++ 13", cxx: "g++-13", cc: "gcc-13", runs-on: "macos-14" }
+ - { tag: "macos-14_gcc-14", name: "MacOS 14 G++ 14", cxx: "g++-14", cc: "gcc-14", runs-on: "macos-14" }
+ - { tag: "macos-14_clang-15", name: "MacOS 14 Clang 15", cxx: "/opt/homebrew/Cellar/llvm@15/15.0.7/bin/clang++", cc: "/opt/homebrew/Cellar/llvm@15/15.0.7/bin/clang", runs-on: "macos-14" }
+ runs-on: ${{ matrix.compiler.runs-on }}
+ name: Compiler ${{ matrix.compiler.name }}
+ env:
+ CXX: ${{ matrix.compiler.cxx }}
+ CC: ${{ matrix.compiler.cc }}
+ outputs:
+ # Because github wants us to suffer we need to list out every output instead of using a matrix statement or some kind of dynamic setting
+ ubuntu-2204_clang-13: ${{ steps.status.outputs.ubuntu-2204_clang-13 }}
+ ubuntu-2204_clang-14: ${{ steps.status.outputs.ubuntu-2204_clang-14 }}
+ ubuntu-2204_clang-15: ${{ steps.status.outputs.ubuntu-2204_clang-15 }}
+ ubuntu-2204_gcc-10: ${{ steps.status.outputs.ubuntu-2204_gcc-10 }}
+ ubuntu-2204_gcc-11: ${{ steps.status.outputs.ubuntu-2204_gcc-11 }}
+ ubuntu-2004_clang-12: ${{ steps.status.outputs.ubuntu-2004_clang-12 }}
+ ubuntu-2004_clang-11: ${{ steps.status.outputs.ubuntu-2004_clang-11 }}
+ ubuntu-2004_clang-10: ${{ steps.status.outputs.ubuntu-2004_clang-10 }}
+ ubuntu-2004_gcc-10: ${{ steps.status.outputs.ubuntu-2004_gcc-10 }}
+ windows-2022_msvc17: ${{ steps.status.outputs.windows-2022_msvc17 }}
+ windows-2019_msvc16: ${{ steps.status.outputs.windows-2019_msvc16 }}
+ macos-12_gcc-12: ${{ steps.status.outputs.macos-12_gcc-12 }}
+ macos-12_gcc-13: ${{ steps.status.outputs.macos-12_gcc-13 }}
+ macos-12_gcc-14: ${{ steps.status.outputs.macos-12_gcc-14 }}
+ macos-12_clang-15: ${{ steps.status.outputs.macos-12_clang-15 }}
+ macos-13_gcc-12: ${{ steps.status.outputs.macos-13_gcc-12 }}
+ macos-13_gcc-13: ${{ steps.status.outputs.macos-13_gcc-13 }}
+ macos-13_gcc-14: ${{ steps.status.outputs.macos-13_gcc-14 }}
+ macos-13_clang-15: ${{ steps.status.outputs.macos-13_clang-15 }}
+ macos-14_gcc-12: ${{ steps.status.outputs.macos-14_gcc-12 }}
+ macos-14_gcc-13: ${{ steps.status.outputs.macos-14_gcc-13 }}
+ macos-14_gcc-14: ${{ steps.status.outputs.macos-14_gcc-14 }}
+ macos-14_clang-15: ${{ steps.status.outputs.macos-14_clang-15 }}
+ defaults:
+ run:
+ shell: bash -l {0}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Set LDFLAGS=-Wl,-ld_classic
+ if: contains(matrix.compiler.tag, 'macos-13') || contains(matrix.compiler.tag, 'macos-14')
+ run: echo "LDFLAGS=-Wl,-ld_classic" >> $GITHUB_ENV
+ # Ubuntu 22.04 container has libstdc++13 installed which is incompatible with clang < 15 in C++20
+ - name: Uninstall libstdc++-13-dev
+ if: (matrix.compiler.tag == 'ubuntu-2204_clang-14') || (matrix.compiler.tag == 'ubuntu-2204_clang-13')
+ run: |
+ sudo apt autoremove libstdc++-13-dev gcc-13 libgcc-13-dev
+ sudo apt install libstdc++-12-dev gcc-12 libgcc-12-dev
+ - name: Install liburing
+ # Ubuntu 22.04 can just pull liburing from apt
+ if: contains(matrix.compiler.tag, 'ubuntu-2204')
+ run: sudo apt install liburing-dev
+ - name: Install liburing
+ # Ubuntu 20.04 does not have liburing in apt, pull in deb files from 22.04 instead
+ if: contains(matrix.compiler.tag, 'ubuntu-2004')
+ run: |
+ wget -O /tmp/liburing2_2.1-2build1_amd64.deb http://mirrors.kernel.org/ubuntu/pool/main/libu/liburing/liburing2_2.1-2build1_amd64.deb
+ wget -O /tmp/liburing-dev_2.1-2build1_amd64.deb http://mirrors.kernel.org/ubuntu/pool/main/libu/liburing/liburing-dev_2.1-2build1_amd64.deb
+ sudo dpkg -i /tmp/liburing-dev_2.1-2build1_amd64.deb /tmp/liburing2_2.1-2build1_amd64.deb
+ - name: Configure
+ if: contains(matrix.compiler.tag, 'ubuntu')
+ run: cmake -S. -Bbuild -DASYNCPP_BUILD_TEST=ON -DASYNCPP_WITH_ASAN=ON -DASYNCPP_WITH_TSAN=OFF
+ - name: Configure
+ if: contains(matrix.compiler.tag, 'ubuntu') != true
+ run: cmake -S. -Bbuild -DASYNCPP_BUILD_TEST=ON -DASYNCPP_WITH_ASAN=OFF -DASYNCPP_WITH_TSAN=OFF
+ - name: Build
+ run: cmake --build build --config Debug
+ - name: Test
+ working-directory: ${{ github.workspace }}/build
+ if: contains(matrix.compiler.tag, 'windows') != true
+ run: ./asyncpp_io-test
+ - name: Test
+ if: contains(matrix.compiler.tag, 'windows')
+ working-directory: ${{ github.workspace }}/build
+ run: Debug/asyncpp_io-test.exe
+ - name: Update Result
+ id: status
+ if: ${{ always() }}
+ run: echo "${{ matrix.compiler.tag }}=${{ job.status }}" >> $GITHUB_OUTPUT
+
+ badge-upload:
+ if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && always() }}
+ needs: [build]
+ runs-on: ubuntu-20.04
+ name: Publish badges
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ - name: Publish Badges
+ uses: ./.github/actions/badge
+ with:
+ category: compiler
+ badges: ${{ toJson(needs.build.outputs) }}
+
\ No newline at end of file
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..ec85483
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,47 @@
+name: Lint CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+
+jobs:
+ clang-format:
+ runs-on: ubuntu-22.04
+ steps:
+ - run: |
+ echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" | sudo tee /etc/apt/sources.list.d/llvm.list
+ echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main" | sudo tee -a /etc/apt/sources.list.d/llvm.list
+ wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
+ sudo apt update && sudo apt-get install clang-format-17
+ shopt -s globstar
+ - uses: actions/checkout@v3
+ - run: find \( -name "*.h" -or -name "*.cpp" \) -exec clang-format-17 -i {} \;
+ - run: find \( -name "*.h" -or -name "*.cpp" \) -exec ./.github/scripts/add-newline-if-missing.sh {} \;
+ - uses: ./.github/actions/process-linting-results
+ with:
+ linter_name: clang-format
+
+ cmake-format:
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/setup-python@v4.3.0
+ with:
+ python-version: "3.x"
+ - run: |
+ pip install cmakelang
+ shopt -s globstar
+ - uses: actions/checkout@v3
+ - run: find \( -name "CMakeLists.txt" -or -name "*.cmake" \) -exec cmake-format -i {} \;
+ - uses: ./.github/actions/process-linting-results
+ with:
+ linter_name: cmake-format
+
+ line-ending:
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v3
+ - run: git add --renormalize .
+ - uses: ./.github/actions/process-linting-results
+ with:
+ linter_name: line-ending
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3457d1b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/build
+/.vscode
+# MSVC
+/.vs
+/out/Build
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..7b5a12d
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,107 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(AsyncppIO)
+find_package(Threads REQUIRED)
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ set(ASYNCPP_ENABLE_URING_DEFAULT ON)
+else()
+ set(ASYNCPP_ENABLE_URING_DEFAULT OFF)
+endif()
+
+option(ASYNCPP_BUILD_TEST "Enable test builds" ON)
+option(ASYNCPP_WITH_ASAN "Enable asan for test builds" ON)
+option(ASYNCPP_ENABLE_URING "Enable support for linux uring"
+ ${ASYNCPP_ENABLE_URING_DEFAULT})
+include(cmake/import_openssl.cmake)
+include(cmake/import_asyncpp.cmake)
+
+if(ASYNCPP_ENABLE_URING)
+ find_package(PkgConfig REQUIRED)
+ if(HUNTER_ENABLED)
+ # Workaround hunter hideing system libs
+ set(HUNTER_LIBPATH $ENV{PKG_CONFIG_LIBDIR})
+ unset(ENV{PKG_CONFIG_LIBDIR})
+ pkg_search_module(URING REQUIRED NO_CMAKE_PATH liburing uring)
+ set(ENV{PKG_CONFIG_LIBDIR} ${HUNTER_LIBPATH})
+ else()
+ pkg_search_module(URING REQUIRED NO_CMAKE_PATH liburing uring)
+ endif()
+endif()
+
+add_library(
+ asyncpp_io
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/address.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/dns.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/file.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_select.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine_uring.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/io_engine.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/io_service.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/socket.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/tls.cpp)
+target_link_libraries(asyncpp_io PUBLIC asyncpp OpenSSL::SSL Threads::Threads)
+target_include_directories(asyncpp_io
+ PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_compile_features(asyncpp_io PUBLIC cxx_std_20)
+
+if(ASYNCPP_ENABLE_URING)
+ target_link_libraries(asyncpp_io PRIVATE ${URING_LINK_LIBRARIES})
+ target_include_directories(asyncpp_io PRIVATE ${URING_INCLUDE_DIRS})
+ target_compile_definitions(asyncpp_io PRIVATE ASYNCPP_ENABLE_URING=1)
+endif()
+
+if(ASYNCPP_WITH_ASAN)
+ if(MSVC)
+ target_compile_options(asyncpp_io PRIVATE -fsanitize=address /Zi)
+ target_compile_definitions(asyncpp_io PRIVATE _DISABLE_VECTOR_ANNOTATION)
+ target_compile_definitions(asyncpp_io PRIVATE _DISABLE_STRING_ANNOTATION)
+ target_link_libraries(asyncpp_io PRIVATE libsancov.lib)
+ else()
+ target_compile_options(asyncpp_io PRIVATE -fsanitize=address)
+ target_link_libraries(asyncpp_io PRIVATE asan)
+ endif()
+endif()
+
+# G++ below 11 needs a flag
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.0")
+ target_compile_options(asyncpp_io PUBLIC -fcoroutines)
+ endif()
+endif()
+
+if(ASYNCPP_BUILD_TEST)
+ include(cmake/import_gtest.cmake)
+
+ add_executable(
+ asyncpp_io-test
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/address.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/dns.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/endpoint.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/file.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/network.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/so_compat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/socket.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/tls.cpp)
+ target_link_libraries(
+ asyncpp_io-test PRIVATE asyncpp_io GTest::gtest GTest::gtest_main
+ Threads::Threads)
+
+ if(ASYNCPP_WITH_ASAN)
+ message(STATUS "Building with asan enabled")
+
+ if(MSVC)
+ target_compile_options(asyncpp_io-test PRIVATE -fsanitize=address /Zi)
+ target_compile_definitions(asyncpp_io-test
+ PRIVATE _DISABLE_VECTOR_ANNOTATION)
+ target_compile_definitions(asyncpp_io-test
+ PRIVATE _DISABLE_STRING_ANNOTATION)
+ target_link_libraries(asyncpp_io-test PRIVATE libsancov.lib)
+ else()
+ target_compile_options(asyncpp_io-test PRIVATE -fsanitize=address)
+ target_link_libraries(asyncpp_io-test PRIVATE asan)
+ endif()
+ endif()
+
+ gtest_discover_tests(asyncpp_io-test)
+endif()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..00c1652
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Dominik Thalhammer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..367fea8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,6 @@
+# Async++ Network library
+This library provides a c++20 coroutine wrapper for networking and IO related functionality.
+It is an addition to [async++](https://github.com/asyncpp/asyncpp) which provides general coroutine tasks and support classes.
+
+This library is developed and tested on Ubuntu linux. There is also experimental support for MacOS.
+Windows is currently unsupported.
\ No newline at end of file
diff --git a/cmake/import_asyncpp.cmake b/cmake/import_asyncpp.cmake
new file mode 100644
index 0000000..5588a2b
--- /dev/null
+++ b/cmake/import_asyncpp.cmake
@@ -0,0 +1,10 @@
+if(TARGET asyncpp)
+ message(STATUS "Using existing asyncpp target.")
+else()
+ message(STATUS "Missing asyncpp, using Fetch to import it.")
+
+ include(FetchContent)
+ FetchContent_Declare(asyncpp
+ GIT_REPOSITORY "https://github.com/asyncpp/asyncpp.git")
+ FetchContent_MakeAvailable(asyncpp)
+endif()
diff --git a/cmake/import_gtest.cmake b/cmake/import_gtest.cmake
new file mode 100644
index 0000000..72ef41a
--- /dev/null
+++ b/cmake/import_gtest.cmake
@@ -0,0 +1,26 @@
+enable_testing()
+include(GoogleTest)
+
+if(TARGET GTest::gtest)
+ message(STATUS "Using existing GTest::gtest target.")
+else()
+ if(HUNTER_ENABLED)
+ hunter_add_package(GTest)
+ find_package(GTest CONFIG REQUIRED)
+ else()
+ include(FetchContent)
+ FetchContent_Declare(
+ googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG release-1.12.1)
+ if(WIN32)
+ set(gtest_force_shared_crt
+ ON
+ CACHE BOOL "" FORCE)
+ set(BUILD_GMOCK
+ OFF
+ CACHE BOOL "" FORCE)
+ endif()
+ FetchContent_MakeAvailable(googletest)
+ endif()
+endif()
diff --git a/cmake/import_openssl.cmake b/cmake/import_openssl.cmake
new file mode 100644
index 0000000..6f7b966
--- /dev/null
+++ b/cmake/import_openssl.cmake
@@ -0,0 +1,13 @@
+if(TARGET OpenSSL::SSL)
+ message(STATUS "Using existing OpenSSL::SSL target.")
+else()
+ if(HUNTER_ENABLED)
+ hunter_add_package(OpenSSL)
+ find_package(OpenSSL REQUIRED)
+ else()
+ find_package(OpenSSL)
+ if(NOT OPENSSL_FOUND)
+ message(FATAL_ERROR "Could not find OpenSSL and Hunter is disabled")
+ endif()
+ endif()
+endif()
diff --git a/include/asyncpp/io/address.h b/include/asyncpp/io/address.h
new file mode 100644
index 0000000..91af18f
--- /dev/null
+++ b/include/asyncpp/io/address.h
@@ -0,0 +1,532 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct sockaddr_storage;
+struct sockaddr_in;
+struct sockaddr_in6;
+struct sockaddr_un;
+namespace asyncpp::io {
+ enum class address_type {
+ ipv4,
+ ipv6,
+#ifndef _WIN32
+ uds
+#endif
+ };
+
+ class ipv4_address {
+ alignas(uint32_t) std::array m_data{};
+
+ public:
+ constexpr ipv4_address() noexcept {}
+ explicit constexpr ipv4_address(uint32_t nbo_addr, std::endian order = std::endian::big) noexcept
+ : m_data{static_cast(nbo_addr >> 24), static_cast(nbo_addr >> 16),
+ static_cast(nbo_addr >> 8), static_cast(nbo_addr >> 0)} {
+ if (order != std::endian::big) std::reverse(m_data.begin(), m_data.end());
+ }
+ explicit constexpr ipv4_address(std::span data, std::endian order = std::endian::big) noexcept
+ : m_data{data[0], data[1], data[2], data[3]} {
+ if (order != std::endian::big) std::reverse(m_data.begin(), m_data.end());
+ }
+ constexpr ipv4_address(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
+ std::endian order = std::endian::big) noexcept
+ : m_data{a, b, c, d} {
+ if (order != std::endian::big) std::reverse(m_data.begin(), m_data.end());
+ }
+ explicit ipv4_address(const sockaddr_storage& addr);
+ explicit ipv4_address(const sockaddr_in& addr) noexcept;
+
+ constexpr std::span data() const noexcept { return m_data; }
+ constexpr uint32_t integer(std::endian order = std::endian::big) const noexcept {
+ if (order == std::endian::big) {
+ return (std::uint32_t(m_data[0]) << 24) | (std::uint32_t(m_data[1]) << 16) |
+ (std::uint32_t(m_data[2]) << 8) | (std::uint32_t(m_data[3]) << 0);
+ } else {
+ return (std::uint32_t(m_data[0]) << 0) | (std::uint32_t(m_data[1]) << 8) |
+ (std::uint32_t(m_data[2]) << 16) | (std::uint32_t(m_data[3]) << 24);
+ }
+ }
+
+ constexpr bool is_any() const noexcept { return *this == any(); }
+ constexpr bool is_multicast() const noexcept { return (m_data[0] & 0xf0) == 0xe0; }
+ constexpr bool is_loopback() const noexcept { return m_data[0] == 127; }
+ constexpr bool is_private() const noexcept {
+ return m_data[0] == 10 || (m_data[0] == 172 && (m_data[1] & 0xf0) == 16) ||
+ (m_data[0] == 192 && m_data[1] == 168);
+ // FIXME: technically 0.0.0.0/8, 100.64.0.0/10, 198.18.0.0/15 and 169.254.0.0/16 are considered private as well,
+ // however those are usually not meant when talking about private/public ips. In particular 100.64.0.0/10 (carrier grade nat)
+ // can appear as a users "public" ip from the perspective of the user.
+ }
+
+ constexpr std::strong_ordering operator<=>(const ipv4_address& rhs) const noexcept = default;
+
+ std::string to_string() const {
+ char buf[16]{};
+ auto ptr = std::begin(buf);
+ for (auto& e : m_data) {
+ if (ptr != std::begin(buf)) *ptr++ = '.';
+ if (e >= 100) {
+ *ptr++ = '0' + (e / 100);
+ *ptr++ = '0' + ((e % 100) / 10);
+ *ptr++ = '0' + (e % 10);
+ } else if (e >= 10) {
+ *ptr++ = '0' + (e / 10);
+ *ptr++ = '0' + (e % 10);
+ } else {
+ *ptr++ = '0' + e;
+ }
+ }
+ return std::string(buf, ptr);
+ }
+ std::pair to_sockaddr() const noexcept;
+ std::pair to_sockaddr_in() const noexcept;
+
+ static constexpr ipv4_address loopback() noexcept { return ipv4_address(127, 0, 0, 1); }
+ static constexpr ipv4_address any() noexcept { return ipv4_address(0, 0, 0, 0); }
+ static constexpr std::optional parse(std::string_view str) noexcept {
+ constexpr auto parse_part = [](std::string_view::const_iterator& it, std::string_view::const_iterator end) {
+ if (it == end || (*it < '0' && *it > '9')) return -1;
+ int32_t result = 0;
+ while (*it >= '0' && *it <= '9') {
+ result = (result * 10) + (*it - '0');
+ it++;
+ }
+ return result;
+ };
+ auto it = str.begin();
+ auto p1 = parse_part(it, str.end());
+ if (p1 < 0 || p1 > 255 || it == str.end() || *it++ != '.') return std::nullopt;
+ auto p2 = parse_part(it, str.end());
+ if (p2 < 0 || p2 > 255 || it == str.end() || *it++ != '.') return std::nullopt;
+ auto p3 = parse_part(it, str.end());
+ if (p3 < 0 || p3 > 255 || it == str.end() || *it++ != '.') return std::nullopt;
+ auto p4 = parse_part(it, str.end());
+ if (p4 < 0 || p4 > 255 || it != str.end()) return std::nullopt;
+ return ipv4_address(p1, p2, p3, p4);
+ }
+ };
+
+ class ipv6_address {
+ alignas(uint64_t) std::array m_data{};
+
+ public:
+ constexpr ipv6_address() noexcept {}
+ explicit constexpr ipv6_address(std::span data,
+ std::endian order = std::endian::big) noexcept
+ : m_data{data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
+ data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]} {
+ if (order != std::endian::big) std::reverse(m_data.begin(), m_data.end());
+ }
+ constexpr ipv6_address(uint64_t a, uint64_t b) noexcept
+ : m_data{static_cast(a >> 56), static_cast(a >> 48),
+ static_cast(a >> 40), static_cast(a >> 32),
+ static_cast(a >> 24), static_cast(a >> 16),
+ static_cast(a >> 8), static_cast(a >> 0),
+ static_cast(b >> 56), static_cast(b >> 48),
+ static_cast(b >> 40), static_cast(b >> 32),
+ static_cast(b >> 24), static_cast(b >> 16),
+ static_cast(b >> 8), static_cast(b >> 0)} {}
+ constexpr ipv6_address(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, uint16_t g,
+ uint16_t h) noexcept
+ : m_data{static_cast(a >> 8), static_cast(a >> 0),
+ static_cast(b >> 8), static_cast(b >> 0),
+ static_cast(c >> 8), static_cast(c >> 0),
+ static_cast(d >> 8), static_cast(d >> 0),
+ static_cast(e >> 8), static_cast(e >> 0),
+ static_cast(f >> 8), static_cast(f >> 0),
+ static_cast(g >> 8), static_cast(g >> 0),
+ static_cast(h >> 8), static_cast(h >> 0)} {}
+ constexpr ipv6_address(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f, uint8_t g, uint8_t h,
+ uint8_t i, uint8_t j, uint8_t k, uint8_t l, uint8_t m, uint8_t n, uint8_t o,
+ uint8_t p) noexcept
+ : m_data{a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p} {}
+ explicit constexpr ipv6_address(std::span data) noexcept
+ : ipv6_address{data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]} {}
+ explicit constexpr ipv6_address(ipv4_address addr) noexcept
+ : ipv6_address(std::array{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, addr.data()[0],
+ addr.data()[1], addr.data()[2], addr.data()[3]}) {}
+ explicit ipv6_address(const sockaddr_storage& addr);
+ explicit ipv6_address(const sockaddr_in6& addr) noexcept;
+
+ constexpr std::span data() const noexcept { return m_data; }
+ constexpr std::span ipv4_data() const noexcept {
+ return std::span{&m_data[12], &m_data[16]};
+ }
+
+ constexpr uint64_t subnet_prefix() const noexcept {
+ return static_cast(m_data[0]) << 56 | static_cast(m_data[1]) << 48 |
+ static_cast(m_data[2]) << 40 | static_cast(m_data[3]) << 32 |
+ static_cast(m_data[4]) << 24 | static_cast(m_data[5]) << 16 |
+ static_cast(m_data[6]) << 8 | static_cast(m_data[7]);
+ }
+ constexpr uint64_t interface_identifier() const noexcept {
+ return static_cast(m_data[8]) << 56 | static_cast(m_data[9]) << 48 |
+ static_cast(m_data[10]) << 40 | static_cast(m_data[11]) << 32 |
+ static_cast(m_data[12]) << 24 | static_cast(m_data[13]) << 16 |
+ static_cast(m_data[14]) << 8 | static_cast(m_data[15]);
+ }
+
+ constexpr std::strong_ordering operator<=>(const ipv6_address& rhs) const noexcept = default;
+
+ constexpr bool is_any() const noexcept { return *this == any(); }
+ constexpr bool is_loopback() const noexcept { return *this == loopback(); }
+ constexpr bool is_multicast() const noexcept { return m_data[0] == 0xff; }
+ constexpr bool is_link_local() const noexcept { return m_data[0] == 0xfe && (m_data[1] & 0xc0) == 0x80; }
+ constexpr bool is_global() const noexcept {
+ return !(is_any() || is_loopback() || is_multicast() || is_link_local());
+ }
+ constexpr bool is_ipv4_mapped() const noexcept {
+ for (size_t i = 0; i < 10; i++)
+ if (m_data[i]) return false;
+ return m_data[10] == 0xff && m_data[11] == 0xff;
+ }
+ constexpr ipv4_address mapped_ipv4() const noexcept {
+ if (!is_ipv4_mapped()) return ipv4_address();
+ return ipv4_address(std::span(&m_data[12], &m_data[16]));
+ }
+
+ std::string to_string(bool full = false) const {
+ static constexpr const char* table = "0123456789abcdef";
+ // A ipv6 address is represented by 8 16bit blocks separated by a colon.
+ // Leading zeros in a block are suppressed, but a block may not be empty.
+ // If more than one zero blocks follow each other the longest one may be replaced
+ // by ::
+
+ // Search for the longest run of zero blocks
+ int zerorun_start = -1;
+ int zerorun_length = 0;
+ if (!full) {
+ for (int i = 0; i < m_data.size(); i += 2) {
+ if (m_data[i] == 0 && m_data[i + 1] == 0) {
+ int run_start = i;
+ for (; i < m_data.size() && m_data[i] == 0 && m_data[i + 1] == 0; i += 2)
+ ;
+ if (i - run_start > zerorun_length && i - run_start >= 4) {
+ zerorun_start = run_start;
+ zerorun_length = i - run_start;
+ }
+ }
+ }
+ }
+
+ std::string res;
+ for (int i = 0; i < m_data.size();) {
+ // This is the start of the zero run
+ if (i == zerorun_start) {
+ i += zerorun_length;
+ res += ':';
+ // if this is the end, append an extra colon, otherwise it is added by the next block
+ if (i >= m_data.size()) res += ':';
+ continue;
+ }
+ if (i != 0) res += ':';
+ if (full || m_data[i] & 0xf0) res += table[(m_data[i] >> 4) & 0xf];
+ if (full || m_data[i]) res += table[m_data[i] & 0xf];
+ if (full || m_data[i] || m_data[i + 1] & 0xf0) res += table[(m_data[i + 1] >> 4) & 0xf];
+ res += table[m_data[i + 1] & 0xf];
+ i += 2;
+ }
+ return res;
+ }
+ std::pair to_sockaddr() const noexcept;
+ std::pair to_sockaddr_in6() const noexcept;
+
+ static constexpr ipv6_address any() noexcept { return ipv6_address(); }
+ static constexpr ipv6_address loopback() noexcept { return ipv6_address(0, 0, 0, 0, 0, 0, 0, 1); }
+ static constexpr std::optional parse(std::string_view str) noexcept {
+ if (str.starts_with("[")) str.remove_prefix(1);
+ if (str.starts_with("]")) str.remove_suffix(1);
+ std::array buf{};
+ int idx = 0;
+ int dcidx = -1;
+ auto it = str.begin();
+ auto part_start = it;
+ bool is_v4_interop = false;
+ if (*it == ':') {
+ dcidx = idx++;
+ it++;
+ if (it == str.end() || *it != ':') return std::nullopt;
+ it++;
+ }
+ while (it != str.end()) {
+ part_start = it;
+ if (*it == ':') {
+ if (dcidx != -1) return std::nullopt;
+ dcidx = idx++;
+ it++;
+ } else {
+ while (it != str.end()) {
+ if (idx == 8) return std::nullopt;
+ if (*it != ':') {
+ if (*it >= '0' && *it <= '9')
+ buf[idx] = buf[idx] * 16 + (*it - '0');
+ else if (*it >= 'a' && *it <= 'f')
+ buf[idx] = buf[idx] * 16 + (*it - 'a') + 10;
+ else if (*it >= 'A' && *it <= 'F')
+ buf[idx] = buf[idx] * 16 + (*it - 'A') + 10;
+ else if (*it == '.') {
+ auto ip4 = ipv4_address::parse(std::string_view(part_start, str.end()));
+ if (!ip4) return std::nullopt;
+ auto data = ip4->data();
+ buf[idx++] = (static_cast(data[0]) << 8) | data[1];
+ if (idx >= 8) return std::nullopt;
+ buf[idx] = (static_cast(data[2]) << 8) | data[3];
+ it = str.end();
+ is_v4_interop = true;
+ continue;
+ } else
+ return std::nullopt;
+ it++;
+ } else {
+ if (std::distance(part_start, it) > 4) return std::nullopt;
+ it++;
+ if (it == str.end()) return std::nullopt;
+ break;
+ }
+ }
+ idx++;
+ }
+ }
+ if (dcidx != -1) {
+ const auto ncopy = idx - dcidx;
+ const auto dest = dcidx + ncopy - 1;
+ for (auto i = 0; i < ncopy; i++) {
+ buf[7 - i] = buf[dest - i];
+ buf[dest - i] = 0;
+ }
+ } else if (idx != 8)
+ return std::nullopt;
+ ipv6_address res{buf};
+ if (is_v4_interop && !res.is_ipv4_mapped()) return std::nullopt;
+ return res;
+ }
+ };
+
+#ifndef _WIN32
+ class uds_address {
+ std::array m_data{};
+ uint8_t m_len{};
+
+ public:
+ constexpr uds_address() noexcept {}
+ explicit constexpr uds_address(std::string_view path) noexcept {
+ m_len = (std::min)(path.size(), m_data.size() - 1);
+ for (size_t i = 0; i < m_len; i++)
+ m_data[i] = path[i];
+ for (size_t i = m_len; i < m_data.size(); i++)
+ m_data[i] = '\0';
+ if (m_len != 0 && m_data[0] == '@') m_data[0] = '\0';
+ }
+ explicit uds_address(const sockaddr_storage& addr, size_t len);
+ explicit uds_address(const sockaddr_un& addr, size_t len) noexcept;
+
+ constexpr std::span data() const noexcept { return {m_data.data(), m_len}; }
+
+ constexpr std::strong_ordering operator<=>(const uds_address& rhs) const noexcept = default;
+
+ constexpr bool is_unnamed() const noexcept { return m_len == 0; }
+ constexpr bool is_abstract() const noexcept { return m_len != 0 && m_data[0] == '\0'; }
+
+ std::string to_string() const {
+ std::string res{reinterpret_cast(m_data.data()), m_len};
+ if (!res.empty() && res[0] == '\0') res[0] = '@';
+ return res;
+ }
+ std::pair to_sockaddr() const noexcept;
+ std::pair to_sockaddr_un() const noexcept;
+
+ static constexpr std::optional parse(std::string_view str) noexcept {
+ if (str.size() > 108) return std::nullopt;
+ if (!str.empty() && std::accumulate(str.begin(), str.end(), 0ull) == 0) return std::nullopt;
+ if (!str.empty() && str[0] == '@' && std::accumulate(str.begin() + 1, str.end(), 0ull) == 0)
+ return std::nullopt;
+ if (!str.empty() && (str.front() == ' ' || str.back() == ' ')) return std::nullopt;
+ return uds_address(str);
+ }
+ };
+#endif
+
+ class address {
+ union {
+ ipv4_address m_ipv4{};
+ ipv6_address m_ipv6;
+#ifndef _WIN32
+ uds_address m_uds;
+#endif
+ };
+ address_type m_type{address_type::ipv4};
+
+ public:
+ constexpr address() noexcept {}
+ explicit constexpr address(ipv4_address addr) noexcept : m_ipv4(addr), m_type{address_type::ipv4} {}
+ explicit constexpr address(ipv6_address addr) noexcept {
+ if (addr.is_ipv4_mapped()) {
+ m_ipv4 = addr.mapped_ipv4();
+ m_type = address_type::ipv4;
+ } else {
+ m_ipv6 = addr;
+ m_type = address_type::ipv6;
+ }
+ }
+#ifndef _WIN32
+ explicit constexpr address(uds_address addr) noexcept : m_uds(addr), m_type{address_type::uds} {}
+#endif
+ explicit address(const sockaddr_storage& addr, size_t len);
+
+ constexpr address_type type() const noexcept { return m_type; }
+ constexpr bool is_ipv4() const noexcept { return m_type == address_type::ipv4; }
+ constexpr bool is_ipv6() const noexcept { return m_type == address_type::ipv6; }
+#ifndef _WIN32
+ constexpr bool is_uds() const noexcept { return m_type == address_type::uds; }
+#endif
+
+ constexpr ipv4_address ipv4() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4;
+ case address_type::ipv6: return {};
+#ifndef _WIN32
+ case address_type::uds: return {};
+#endif
+ }
+ return {};
+ }
+ constexpr ipv6_address ipv6() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4: return ipv6_address(m_ipv4);
+#ifndef _WIN32
+ case address_type::uds: return {};
+#endif
+ case address_type::ipv6: return m_ipv6;
+ }
+ return {};
+ }
+
+#ifndef _WIN32
+ constexpr uds_address uds() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4:
+ case address_type::ipv6: return {};
+ case address_type::uds: return m_uds;
+ }
+ return {};
+ }
+#endif
+
+ constexpr bool is_any() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4.is_any();
+ case address_type::ipv6: return m_ipv6.is_any();
+#ifndef _WIN32
+ case address_type::uds: return false;
+#endif
+ }
+ }
+ constexpr bool is_loopback() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4.is_loopback();
+ case address_type::ipv6: return m_ipv6.is_loopback();
+#ifndef _WIN32
+ case address_type::uds: return false;
+#endif
+ }
+ }
+
+ constexpr std::span bytes() const noexcept {
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4.data();
+ case address_type::ipv6: return m_ipv6.data();
+#ifndef _WIN32
+ case address_type::uds: return m_uds.data();
+#endif
+ }
+ }
+
+ constexpr std::strong_ordering operator<=>(const address& rhs) const noexcept {
+ auto order = m_type <=> rhs.m_type;
+ if (order != std::strong_ordering::equal) return order;
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4 <=> rhs.m_ipv4;
+ case address_type::ipv6: return m_ipv6 <=> rhs.m_ipv6;
+#ifndef _WIN32
+ case address_type::uds: return m_uds <=> rhs.m_uds;
+#endif
+ }
+ }
+ constexpr bool operator==(const address& rhs) const noexcept {
+ return (*this <=> rhs) == std::strong_ordering::equal;
+ }
+ constexpr bool operator!=(const address& rhs) const noexcept {
+ return (*this <=> rhs) != std::strong_ordering::equal;
+ }
+
+ std::string to_string(bool full = false) const {
+ switch (m_type) {
+ case address_type::ipv4: return m_ipv4.to_string();
+ case address_type::ipv6: return m_ipv6.to_string(full);
+#ifndef _WIN32
+ case address_type::uds: return m_uds.to_string();
+#endif
+ }
+ }
+ std::pair to_sockaddr() const noexcept;
+
+ static constexpr address any() noexcept { return address(ipv6_address::any()); }
+ static constexpr address loopback() noexcept { return address(ipv6_address::loopback()); }
+ static constexpr std::optional parse(std::string_view str) noexcept {
+ auto ip4 = ipv4_address::parse(str);
+ if (ip4) return address(*ip4);
+ auto ip6 = ipv6_address::parse(str);
+ if (ip6) return address(*ip6);
+ return std::nullopt;
+ }
+ };
+} // namespace asyncpp::io
+
+namespace std {
+ template<>
+ struct hash {
+ size_t operator()(const asyncpp::io::ipv4_address& x) const noexcept {
+ return std::hash{}(x.integer());
+ }
+ };
+ template<>
+ struct hash {
+ size_t operator()(const asyncpp::io::ipv6_address& x) const noexcept {
+ std::hash h{};
+ auto res = h(x.subnet_prefix());
+ return res ^ (h(x.interface_identifier()) + 0x9e3779b99e3779b9ull + (res << 6) + (res >> 2));
+ }
+ };
+#ifndef _WIN32
+ template<>
+ struct hash {
+ size_t operator()(const asyncpp::io::uds_address& x) const noexcept {
+ size_t res = 0;
+ for (auto e : x.data())
+ res = res ^ (e + 0x9e3779b99e3779b9ull + (res << 6) + (res >> 2));
+ return res;
+ }
+ };
+#endif
+ template<>
+ struct hash {
+ size_t operator()(const asyncpp::io::address& x) const noexcept {
+ size_t res;
+ switch (x.type()) {
+ case asyncpp::io::address_type::ipv4: res = std::hash{}(x.ipv4()); break;
+ case asyncpp::io::address_type::ipv6: res = std::hash{}(x.ipv6()); break;
+#ifndef _WIN32
+ case asyncpp::io::address_type::uds: res = std::hash{}(x.uds()); break;
+#endif
+ }
+ return res ^ (static_cast(x.type()) + 0x9e3779b99e3779b9ull + (res << 6) + (res >> 2));
+ }
+ };
+} // namespace std
diff --git a/include/asyncpp/io/buffer.h b/include/asyncpp/io/buffer.h
new file mode 100644
index 0000000..a18b978
--- /dev/null
+++ b/include/asyncpp/io/buffer.h
@@ -0,0 +1,31 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+
+namespace asyncpp::io {
+
+ template
+ requires(std::is_trivial_v)
+ inline void raw_set(void* ptr, std::type_identity_t val) noexcept {
+ memcpy(ptr, &val, sizeof(T));
+ if constexpr (std::endian::native != Endian)
+ std::reverse(static_cast(ptr), static_cast(ptr) + sizeof(T));
+ }
+
+ template
+ requires(std::is_trivial_v)
+ inline T raw_get(const void* ptr) noexcept {
+ T res;
+ memcpy(&res, ptr, sizeof(T));
+ if constexpr (std::endian::native != Endian) {
+ std::reverse(reinterpret_cast(&res), reinterpret_cast(&res) + sizeof(T));
+ }
+ return res;
+ }
+
+ using buffer = std::span;
+ using const_buffer = std::span;
+} // namespace asyncpp::io
diff --git a/include/asyncpp/io/detail/cancel_awaitable.h b/include/asyncpp/io/detail/cancel_awaitable.h
new file mode 100644
index 0000000..048380d
--- /dev/null
+++ b/include/asyncpp/io/detail/cancel_awaitable.h
@@ -0,0 +1,41 @@
+#pragma once
+#include
+#include
+#include
+
+#include
+
+namespace asyncpp::io::detail {
+ struct cancel_io_stop_callback {
+ io_engine::completion_data* m_data;
+ io_engine* m_engine;
+ void operator()() noexcept {
+ if (m_engine && m_data) m_engine->cancel(m_data);
+ }
+ };
+
+ template
+ class cancellable_awaitable {
+ T m_child;
+ asyncpp::stop_token m_stop_token;
+ std::optional> m_cancel_callback;
+
+ public:
+ template
+ cancellable_awaitable(asyncpp::stop_token st, Args&&... args) noexcept
+ : m_child(std::forward(args)...), m_stop_token{std::move(st)} {}
+ bool await_ready() const noexcept { return m_child.await_ready(); }
+ bool await_suspend(coroutine_handle<> hdl) {
+ if (m_stop_token.stop_requested()) {
+ m_child.m_completion.result = -ECANCELED;
+ return false;
+ }
+ auto res = m_child.await_suspend(hdl);
+ if (res)
+ m_cancel_callback.emplace(
+ m_stop_token, cancel_io_stop_callback{&m_child.m_completion, m_child.m_socket.service().engine()});
+ return res;
+ }
+ auto await_resume() { return m_child.await_resume(); }
+ };
+} // namespace asyncpp::io::detail
diff --git a/include/asyncpp/io/detail/io_engine.h b/include/asyncpp/io/detail/io_engine.h
new file mode 100644
index 0000000..5c7e9a3
--- /dev/null
+++ b/include/asyncpp/io/detail/io_engine.h
@@ -0,0 +1,56 @@
+#pragma once
+#include
+
+#include
+
+namespace asyncpp::io::detail {
+ class io_engine {
+ public:
+ using file_handle_t = int;
+ constexpr static file_handle_t invalid_file_handle = -1;
+ using socket_handle_t = int;
+ constexpr static socket_handle_t invalid_socket_handle = -1;
+ enum class fsync_flags { none, datasync };
+
+ struct completion_data {
+ // Info provided by caller
+ void (*callback)(void*);
+ void* userdata;
+
+ // Filled by io_engine
+ int result;
+
+ // Private data the engine can use to associate state
+ void* engine_state{};
+ };
+
+ public:
+ virtual ~io_engine() = default;
+
+ virtual std::string_view name() const noexcept = 0;
+
+ virtual size_t run(bool nowait = false) = 0;
+ virtual void wake() = 0;
+
+ // Networking api
+ virtual bool enqueue_connect(socket_handle_t socket, endpoint ep, completion_data* cd) = 0;
+ virtual bool enqueue_accept(socket_handle_t socket, completion_data* cd) = 0;
+ virtual bool enqueue_recv(socket_handle_t socket, void* buf, size_t len, completion_data* cd) = 0;
+ virtual bool enqueue_send(socket_handle_t socket, const void* buf, size_t len, completion_data* cd) = 0;
+ virtual bool enqueue_recv_from(socket_handle_t socket, void* buf, size_t len, endpoint* source,
+ completion_data* cd) = 0;
+ virtual bool enqueue_send_to(socket_handle_t socket, const void* buf, size_t len, endpoint dst,
+ completion_data* cd) = 0;
+
+ // Filesystem IO
+ virtual bool enqueue_readv(file_handle_t fd, void* buf, size_t len, off_t offset, completion_data* cd) = 0;
+ virtual bool enqueue_writev(file_handle_t fd, const void* buf, size_t len, off_t offset,
+ completion_data* cd) = 0;
+ virtual bool enqueue_fsync(file_handle_t fd, fsync_flags flags, completion_data* cd) = 0;
+
+ // Cancelation
+ virtual bool cancel(completion_data* cd) = 0;
+ };
+
+ std::unique_ptr create_io_engine();
+} // namespace asyncpp::io::detail
diff --git a/include/asyncpp/io/dns.h b/include/asyncpp/io/dns.h
new file mode 100644
index 0000000..664cd1b
--- /dev/null
+++ b/include/asyncpp/io/dns.h
@@ -0,0 +1,919 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include