Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions .github/workflows/toolchain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ jobs:
-DLAGHU_BUILD_PROFILE=FULL
cmake --build "$RUNNER_TEMP/laghu-full" --target \
laghu_crypto_provider_test laghu_idna_test laghu_password_auth_test \
laghu_password_auth_failure_token_test laghu_http2_test laghu_http2_contract_test
laghu_password_auth_failure_token_test laghu_http2_test laghu_http2_contract_test \
laghu_http3_test laghu_http3_contract_test
ctest --test-dir "$RUNNER_TEMP/laghu-full" --output-on-failure \
-R '^laghu\.(crypto\.provider|adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract))$'
-R '^laghu\.(crypto\.provider|adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract|http3|http3\.contract))$'
- name: Verify system dynamic IDNA and password adapters
if: matrix.name == 'gcc-14-libstdc++'
run: |
Expand Down Expand Up @@ -81,12 +82,12 @@ jobs:
-DCMAKE_CXX_COMPILER="${{ matrix.compiler }}" \
-DCMAKE_CXX_FLAGS="${{ matrix.cxxflags }}" \
-DLAGHU_BUILD_PROFILE=CUSTOM \
'-DLAGHU_FEATURES=idna;password_auth;http2'
'-DLAGHU_FEATURES=idna;password_auth;http2;http3'
cmake --build "$RUNNER_TEMP/laghu-clang-adapters" --target \
laghu_idna_test laghu_password_auth_test laghu_password_auth_failure_token_test \
laghu_http2_test laghu_http2_contract_test
laghu_http2_test laghu_http2_contract_test laghu_http3_test laghu_http3_contract_test
ctest --test-dir "$RUNNER_TEMP/laghu-clang-adapters" --output-on-failure \
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract)$'
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract|http3|http3\.contract)$'
- name: Verify LibreSSL crypto adapter
if: matrix.name == 'gcc-14-libstdc++'
run: |
Expand Down Expand Up @@ -141,12 +142,12 @@ jobs:
cmake -S . -B "$RUNNER_TEMP/laghu-adapters" -G Ninja \
-DCMAKE_CXX_COMPILER=clang++ \
-DLAGHU_BUILD_PROFILE=CUSTOM \
'-DLAGHU_FEATURES=idna;password_auth;http2'
'-DLAGHU_FEATURES=idna;password_auth;http2;http3'
cmake --build "$RUNNER_TEMP/laghu-adapters" --target \
laghu_idna_test laghu_password_auth_test laghu_password_auth_failure_token_test \
laghu_http2_test laghu_http2_contract_test
laghu_http2_test laghu_http2_contract_test laghu_http3_test laghu_http3_contract_test
ctest --test-dir "$RUNNER_TEMP/laghu-adapters" --output-on-failure \
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract)$'
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract|http3|http3\.contract)$'
- uses: actions/upload-artifact@v7
with:
name: toolchain-capabilities-macos-appleclang
Expand Down Expand Up @@ -176,12 +177,12 @@ jobs:
cmake -S . -B /tmp/laghu-adapters -G Ninja \
-DCMAKE_CXX_COMPILER=clang++ \
-DLAGHU_BUILD_PROFILE=CUSTOM \
'-DLAGHU_FEATURES=idna;password_auth;http2'
'-DLAGHU_FEATURES=idna;password_auth;http2;http3'
cmake --build /tmp/laghu-adapters --target \
laghu_idna_test laghu_password_auth_test laghu_password_auth_failure_token_test \
laghu_http2_test laghu_http2_contract_test
laghu_http2_test laghu_http2_contract_test laghu_http3_test laghu_http3_contract_test
ctest --test-dir /tmp/laghu-adapters --output-on-failure \
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract)$'
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract|http3|http3\.contract)$'
linux-aarch64-cross:
runs-on: ubuntu-24.04
steps:
Expand All @@ -197,7 +198,7 @@ jobs:
run: |
cmake --preset linux-aarch64-gcc \
-DLAGHU_BUILD_PROFILE=CUSTOM \
'-DLAGHU_FEATURES=tls;http2'
'-DLAGHU_FEATURES=tls;http2;http3'
cmake --build build/linux-aarch64-gcc --target laghu_verify_toolchain
- name: Verify ARM64 archive
run: |
Expand All @@ -207,6 +208,10 @@ jobs:
aarch64-linux-gnu-ar t build/linux-aarch64-gcc/liblaghu_crypto.a
test -f build/linux-aarch64-gcc/liblaghu_http2.a
aarch64-linux-gnu-ar t build/linux-aarch64-gcc/liblaghu_http2.a
test -f build/linux-aarch64-gcc/liblaghu_quic.a
aarch64-linux-gnu-ar t build/linux-aarch64-gcc/liblaghu_quic.a
test -f build/linux-aarch64-gcc/liblaghu_http3.a
aarch64-linux-gnu-ar t build/linux-aarch64-gcc/liblaghu_http3.a
linux-clang-asan-ubsan:
runs-on: ubuntu-24.04
steps:
Expand Down Expand Up @@ -242,12 +247,12 @@ jobs:
-DCMAKE_CXX_FLAGS=-stdlib=libc++ \
-DLAGHU_SANITIZER_PROFILE=ASAN_UBSAN \
-DLAGHU_BUILD_PROFILE=CUSTOM \
'-DLAGHU_FEATURES=idna;password_auth;http2'
'-DLAGHU_FEATURES=idna;password_auth;http2;http3'
cmake --build "$RUNNER_TEMP/laghu-asan-adapters" --target \
laghu_idna_test laghu_password_auth_test laghu_password_auth_failure_token_test \
laghu_http2_test laghu_http2_contract_test
laghu_http2_test laghu_http2_contract_test laghu_http3_test laghu_http3_contract_test
ctest --test-dir "$RUNNER_TEMP/laghu-asan-adapters" --output-on-failure \
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract)$'
-R '^laghu\.adapters\.(idna|password_auth|password_auth\.failure_token|http2|http2\.contract|http3|http3\.contract)$'
- name: Replay binary-envelope fuzz corpus under ASan and UBSan
run: |
cmake -S . -B "$RUNNER_TEMP/laghu-fuzz" -G Ninja \
Expand Down
48 changes: 48 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,33 @@ if(NOT laghu_http2_feature_index EQUAL -1)
endif()
endif()

list(FIND LAGHU_EFFECTIVE_FEATURES http3 laghu_http3_feature_index)
if(NOT laghu_http3_feature_index EQUAL -1)
add_library(laghu_quic STATIC
src/adapters/quic.cpp
$<TARGET_OBJECTS:laghu_dependency_logging_impl>)
laghu_apply_first_party_contract(laghu_quic)
laghu_configure_api_consumer(laghu_quic adapters)
target_include_directories(laghu_quic PRIVATE "${CMAKE_SOURCE_DIR}/src/adapters/private")
laghu_register_subsystem_target(laghu_quic adapters)
laghu_link_subsystems(laghu_quic laghu_core)
target_link_libraries(laghu_quic PRIVATE laghu_feature_http3)

add_library(laghu_http3 STATIC
src/adapters/http3.cpp
$<TARGET_OBJECTS:laghu_dependency_logging_impl>)
laghu_apply_first_party_contract(laghu_http3)
laghu_configure_api_consumer(laghu_http3 adapters)
target_include_directories(laghu_http3 PRIVATE "${CMAKE_SOURCE_DIR}/src/adapters/private")
laghu_register_subsystem_target(laghu_http3 adapters)
laghu_link_subsystems(laghu_http3 laghu_core)
target_link_libraries(laghu_http3 PRIVATE laghu_feature_http3)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set_source_files_properties(src/adapters/quic.cpp src/adapters/http3.cpp PROPERTIES
COMPILE_OPTIONS "-Wno-old-style-cast;-Wno-unsafe-buffer-usage;-Wno-zero-as-null-pointer-constant")
endif()
endif()

add_executable(laghu_core_smoke src/core/smoke.cpp)
laghu_apply_first_party_contract(laghu_core_smoke)
laghu_configure_api_consumer(laghu_core_smoke core)
Expand Down Expand Up @@ -383,6 +410,23 @@ if(TARGET laghu_http2)
laghu_configure_api_consumer(laghu_http2_contract_test adapters)
laghu_add_native_test(laghu.adapters.http2.contract laghu_http2_contract_test)
endif()
if(TARGET laghu_quic)
add_executable(laghu_http3_test tests/adapters/http3.cpp)
laghu_apply_first_party_contract(laghu_http3_test)
laghu_configure_api_consumer(laghu_http3_test adapters)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set_source_files_properties(tests/adapters/http3.cpp PROPERTIES
COMPILE_OPTIONS -Wno-unsafe-buffer-usage)
endif()
target_link_libraries(laghu_http3_test PRIVATE
laghu_quic laghu_http3 laghu_test_support)
laghu_add_native_test(laghu.adapters.http3 laghu_http3_test)

add_executable(laghu_http3_contract_test tests/adapters/http3_contract.cpp)
laghu_apply_first_party_contract(laghu_http3_contract_test)
laghu_configure_api_consumer(laghu_http3_contract_test adapters)
laghu_add_native_test(laghu.adapters.http3.contract laghu_http3_contract_test)
endif()
laghu_write_fuzz_target_registry()
laghu_add_fuzz_validation_tests()

Expand Down Expand Up @@ -568,6 +612,10 @@ endif()
if(TARGET laghu_http2_test)
list(APPEND laghu_verify_targets laghu_http2 laghu_http2_test laghu_http2_contract_test)
endif()
if(TARGET laghu_http3_test)
list(APPEND laghu_verify_targets
laghu_quic laghu_http3 laghu_http3_test laghu_http3_contract_test)
endif()
laghu_collect_fuzz_targets(laghu_fuzz_verify_targets)
list(APPEND laghu_verify_targets ${laghu_fuzz_verify_targets})
foreach(target IN ITEMS laghu_sanitizer_heap_misuse_fixture
Expand Down
10 changes: 10 additions & 0 deletions cmake/LaghuBuildIdentity.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ function(laghu_build_identity_input_hashes output)
src/adapters/contract/laghu/adapters/http2.hpp
src/adapters/http2.cpp)
endif()
list(FIND LAGHU_EFFECTIVE_FEATURES http3 http3_feature_index)
if(NOT http3_feature_index EQUAL -1)
list(APPEND inputs
src/adapters/contract/laghu/adapters/http3.hpp
src/adapters/contract/laghu/adapters/native_memory.hpp
src/adapters/contract/laghu/adapters/quic.hpp
src/adapters/http3.cpp
src/adapters/quic.cpp
src/adapters/private/laghu/adapters/internal/arena_memory.hpp)
endif()
set(entries)
foreach(input IN LISTS inputs)
set(path "${CMAKE_SOURCE_DIR}/${input}")
Expand Down
7 changes: 7 additions & 0 deletions cmake/LaghuDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,13 @@ function(laghu_acquire_vendored_cmake_dependency id private_target)
endif()
add_library("${private_target}" INTERFACE)
target_link_libraries("${private_target}" INTERFACE "${vendored_target}")
if(id STREQUAL ngtcp2 OR id STREQUAL nghttp3)
# These upstream targets publish only their installed include directory.
# Laghu consumes them directly from FetchContent's source/build trees.
target_include_directories("${private_target}" SYSTEM INTERFACE
"${${content_name}_SOURCE_DIR}/lib/includes"
"${${content_name}_BINARY_DIR}/lib/includes")
endif()
if(LAGHU_DEPENDENCY_LINK_MODE STREQUAL STATIC)
add_dependencies("${private_target}" "${static_proof_target}")
endif()
Expand Down
100 changes: 100 additions & 0 deletions src/adapters/contract/laghu/adapters/http3.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: AGPL-3.0-only
#pragma once

#include <cstddef>
#include <cstdint>
#include <span>
#include <type_traits>
#include <utility>

#include <laghu/adapters/dependency.hpp>
#include <laghu/adapters/native_memory.hpp>
#include <laghu/core/bounded_arena.hpp>
#include <laghu/core/views.hpp>

namespace laghu::adapters {

enum class Http3Role : std::uint8_t { client, server };
enum class Http3EventKind : std::uint8_t {
headers_begin, header, headers_end, data, stream_end, reset, stop_sending,
shutdown, qpack_failure,
};

struct Http3Header final { core::ByteView name{}; core::ByteView value{}; };
struct Http3Event final {
// All views are valid only for the duration of the sink callback.
Http3EventKind kind{Http3EventKind::data};
std::int64_t stream_id{-1};
core::ByteView name{};
core::ByteView value{};
core::ByteView data{};
std::uint64_t application_error{};
};
using Http3EventWrite = bool (*)(void*, const Http3Event&) noexcept;
struct Http3EventSink final { void* context{}; Http3EventWrite write{}; };

struct Http3Limits final {
std::uint64_t maximum_field_section_bytes{};
std::uint64_t qpack_table_capacity{};
std::uint64_t qpack_blocked_streams{};
};

struct Http3Output final {
// The byte view remains valid only until the next operation on this session.
std::int64_t stream_id{-1};
core::ByteView bytes{};
bool fin{};
};

class Http3Session final {
public:
Http3Session(const Http3Session&) = delete;
Http3Session& operator=(const Http3Session&) = delete;
Http3Session(Http3Session&& other) noexcept;
Http3Session& operator=(Http3Session&& other) noexcept;
~Http3Session();

// The memory pool and its arena must outlive the session. The arena cannot
// be reset while this session holds its pin.
[[nodiscard]] static core::Result<Http3Session> create(
Http3Role role, NativeMemoryPool& memory,
Http3Limits limits, Http3EventSink events = {},
DependencyLogSink log_sink = {}) noexcept;
[[nodiscard]] core::Result<void> bind_streams(std::int64_t control,
std::int64_t qpack_encoder,
std::int64_t qpack_decoder) noexcept;
[[nodiscard]] core::Result<std::size_t> receive(std::int64_t stream_id,
core::ByteView data,
bool fin) noexcept;
[[nodiscard]] core::Result<Http3Output> next_output() noexcept;
[[nodiscard]] core::Result<void> mark_output_written(std::int64_t stream_id,
std::size_t bytes) noexcept;
[[nodiscard]] core::Result<void> acknowledge_stream_data(std::int64_t stream_id,
std::uint64_t bytes) noexcept;
[[nodiscard]] core::Result<void> submit_request(
std::int64_t stream_id, std::span<const Http3Header> headers) noexcept;
[[nodiscard]] core::Result<void> submit_response(
std::int64_t stream_id, std::span<const Http3Header> headers) noexcept;
[[nodiscard]] core::Result<void> close_stream(std::int64_t stream_id,
std::uint64_t code) noexcept;
[[nodiscard]] core::Result<void> shutdown() noexcept;

private:
Http3Session(void* connection, void* state, const core::BoundedArena& arena,
std::uint64_t generation, core::ArenaPin pin) noexcept
: connection_(connection), state_(state), arena_(&arena), generation_(generation),
pin_(std::move(pin)) {}
[[nodiscard]] core::Result<void> require_valid() const noexcept;
void release() noexcept;
void move_from(Http3Session&& other) noexcept;
void* connection_{};
void* state_{};
const core::BoundedArena* arena_{};
std::uint64_t generation_{};
core::ArenaPin pin_{};
};

static_assert(std::is_trivially_copyable_v<Http3Header>);
static_assert(!std::is_copy_constructible_v<Http3Session>);

} // namespace laghu::adapters
34 changes: 34 additions & 0 deletions src/adapters/contract/laghu/adapters/native_memory.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0-only
#pragma once

#include <cstdint>

#include <laghu/core/bounded_arena.hpp>

namespace laghu::adapters {

namespace internal {
struct ArenaMemoryAccess;
}

// Caller-owned reusable storage for native dependency allocations. The pool
// must outlive every adapter session that uses it.
class NativeMemoryPool final {
public:
constexpr NativeMemoryPool(core::WorkerId worker, core::BoundedArena& arena) noexcept
: arena_(&arena), worker_(worker), generation_(arena.generation()) {}

NativeMemoryPool(const NativeMemoryPool&) = delete;
NativeMemoryPool& operator=(const NativeMemoryPool&) = delete;
NativeMemoryPool(NativeMemoryPool&&) = delete;
NativeMemoryPool& operator=(NativeMemoryPool&&) = delete;

private:
friend struct internal::ArenaMemoryAccess;
core::BoundedArena* arena_{};
core::WorkerId worker_;
std::uint64_t generation_{};
void* free_list_{};
};

} // namespace laghu::adapters
Loading
Loading