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
8 changes: 4 additions & 4 deletions docs/PROTOCOL_SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ the cross-system summary in kcenon/common_system#684.
| HTTP/1.1 | (core `src/http`) | `production` | `http_parser_branch_test`, `http_server_test`, `http_client_test`, `http_error_coverage_test`, `http_types_test` | Routing, cookies, multipart, chunked encoding, gzip/deflate. |
| HTTP/2 | `network-http2` | `production` | `http2_client_branch_test`, `http2_server_branch_test`, `*_dispatcher_branch_test`, `http2_server_stream_test`, `http2_request_test` | Multiplexed streams, frame layer, server/client dispatch. HPACK Huffman is an experimental sub-surface (see below). |
| HPACK | `network-http2` | `production` | `hpack_branch_test`, `hpack_coverage_test`, `hpack_extra_coverage_test`, `test_http2_hpack_rfc7541` | RFC 7541 static/dynamic table, integer/string coding. Huffman coding is an experimental pass-through (see HPACK Huffman row). |
| QUIC | `network-quic` | `production` | 30+ unit tests: `quic_packet_branch_test`, `quic_connection_branch_test`, `quic_frame_branch_test`, `quic_loss_detector_test`, `quic_congestion_controller_test`, `quic_socket_branch_test`, `test_quic_e2e` | RFC 9000/9001/9002 core: packets, frames, streams, loss detection, congestion control, crypto, varint, transport params. Connection-stats/ALPN-result accessors on the experimental client surface are incomplete (see experimental rows). |
| QUIC | `network-quic` | `production` | 30+ unit tests: `quic_packet_branch_test`, `quic_connection_branch_test`, `quic_frame_branch_test`, `quic_loss_detector_test`, `quic_congestion_controller_test`, `quic_socket_branch_test`, `test_quic_e2e` | RFC 9000/9001/9002 core: packets, frames, streams, loss detection, congestion control, crypto, varint, transport params. The experimental client's ALPN accessor is wired; connection-stats reports live byte/packet counters, with loss/RTT/congestion-window fields pending recovery-stack integration (see experimental rows). |
| gRPC | `network-grpc` | `production` | `grpc_client_branch_test`, `grpc_client_extended_coverage_test`, `test_grpc_*`, `mock_grpc_server_peer` | Custom HTTP/2 transport + optional official `grpc++` wrapper (`NETWORK_ENABLE_GRPC_OFFICIAL`, OFF by default). |
| DTLS (secure UDP) | `network-udp` | `experimental` | `test_dtls_socket` | DTLS socket exists and is tested, but server-side session payload handling in `secure_messaging_udp_server::process_session_data` is not yet wired (data buffer unused, `// TODO: Use when DTLS handling is implemented`). |

Expand All @@ -38,8 +38,8 @@ classified with evidence and disposition.
| Location | Marker / behavior | Status | Disposition |
|----------|-------------------|--------|-------------|
| `src/protocols/http2/hpack.cpp` (huffman::encode/decode/encoded_size, ~L306/609/651) | Huffman coding is a documented pass-through stub (returns input as-is); non-Huffman HPACK paths are fully RFC 7541 compliant | `experimental` | Keep; covered by `test_http2_hpack.cpp` Huffman stub tests asserting the pass-through contract and by `hpack_branch_test` H-bit branch. Real Huffman tables tracked by follow-up. |
| `src/experimental/quic_client.cpp` `alpn_protocol()` (~L459) | Returns `std::nullopt`; ALPN negotiation result not retrieved from handshake | `experimental` | Header annotated as experimental. Internal header (`src/internal/experimental/`), not a public surface. Tracked by follow-up. |
| `src/experimental/quic_client.cpp` `stats()` (~L470) | Returns default `quic_connection_stats{}`; not wired to live connection | `experimental` | Header annotated as experimental. Tracked by follow-up. |
| `src/experimental/quic_client.cpp` `alpn_protocol()` | Returns the ALPN protocol negotiated during the handshake via `quic_socket::negotiated_alpn()` (`std::nullopt` until negotiated) | `production` | Wired in kcenon/network_system#1158. |
| `src/experimental/quic_client.cpp` `stats()` | Live `bytes_sent`/`bytes_received`/`packets_sent`/`packets_received` sourced from `quic_socket`; `packets_lost`/`smoothed_rtt`/`min_rtt`/`cwnd` remain zero pending QUIC recovery-stack integration | `experimental` | Partially wired in kcenon/network_system#1158; loss/RTT/congestion accounting tracked as follow-up. |
| `src/core/secure_messaging_udp_server.cpp` `process_session_data` (~L301) | `data` parameter `[[maybe_unused]]` pending DTLS payload handling | `experimental` | DTLS row above. Tracked by follow-up. |
| `src/internal/quic_socket.cpp` `process_ack_frame` (~L719) | Tracks largest-acked only; full retransmission-queue pruning deferred (`(void)f; // Placeholder`) | `experimental` | Functional minimal ACK handling; full loss-recovery integration in `src/protocols/quic/loss_detector.cpp`. Tracked by follow-up. |
| `src/tracing/exporters.cpp` (otlp_grpc/jaeger/zipkin, ~L547/560/573) | These exporters log "not implemented" and fall back to console/OTLP-HTTP | `experimental` | Tracing observability surface, not a network protocol. `otlp_http` exporter is production. README tracing note already says "use otlp_http". |
Expand Down Expand Up @@ -69,7 +69,7 @@ Rows classified `experimental` with a genuine implementation gap are tracked by
dedicated follow-up issues (see kcenon/network_system#1144 PR body for numbers):

- HPACK Huffman coding: replace the pass-through stub with RFC 7541 Huffman tables.
- Experimental QUIC client: wire `alpn_protocol()` and `stats()` to the live connection.
- Experimental QUIC client: integrate the QUIC recovery stack (loss detection, RTT estimation, congestion control) into the client socket so `stats()` reports live `packets_lost`, `smoothed_rtt`/`min_rtt`, and `cwnd`. Byte/packet counters and the ALPN accessor are already wired (kcenon/network_system#1158).
- DTLS server: implement `secure_messaging_udp_server::process_session_data` payload handling.

No rows were classified `remove`; the `libs/network-*` wrapper `.cpp` files are
Expand Down
33 changes: 29 additions & 4 deletions src/experimental/quic_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,17 @@ auto messaging_quic_client::set_alpn_protocols(

auto messaging_quic_client::alpn_protocol() const -> std::optional<std::string>
{
// TODO: Implement ALPN negotiation result retrieval
return std::nullopt;
auto local_socket = get_socket();
if (!local_socket)
{
return std::nullopt;
}
auto alpn = local_socket->negotiated_alpn();
if (alpn.empty())
{
return std::nullopt;
}
return alpn;
}

auto messaging_quic_client::is_early_data_accepted() const -> bool
Expand All @@ -467,8 +476,24 @@ auto messaging_quic_client::is_early_data_accepted() const -> bool

auto messaging_quic_client::stats() const -> quic_connection_stats
{
// TODO: Implement connection statistics retrieval
return quic_connection_stats{};
quic_connection_stats stats;

auto local_socket = get_socket();
if (local_socket)
{
// Live transport counters from the socket.
stats.bytes_sent = local_socket->bytes_sent();
stats.bytes_received = local_socket->bytes_received();
stats.packets_sent = local_socket->packets_sent();
stats.packets_received = local_socket->packets_received();

// NOTE: packets_lost, smoothed_rtt, min_rtt, and cwnd require the QUIC
// loss-detection / RTT-estimation / congestion-control stack, which is
// not yet integrated into the experimental client's socket. They remain
// zero until that stack is wired in (see kcenon/network_system#1158).
}

return stats;
}

auto messaging_quic_client::do_connect(std::string_view host,
Expand Down
11 changes: 11 additions & 0 deletions src/internal/quic_socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,11 @@ auto quic_socket::remote_connection_id() const -> const connection_id&
// Internal Methods
// =============================================================================

auto quic_socket::negotiated_alpn() const -> std::string
{
return crypto_.get_alpn();
}

auto quic_socket::do_receive() -> void
{
if (!is_receiving_.load())
Expand Down Expand Up @@ -471,6 +476,9 @@ auto quic_socket::do_receive() -> void

if (bytes_transferred > 0)
{
packets_received_.fetch_add(1, std::memory_order_relaxed);
bytes_received_.fetch_add(bytes_transferred,
std::memory_order_relaxed);
handle_packet(std::span(recv_buffer_.data(), bytes_transferred));
}

Expand Down Expand Up @@ -875,6 +883,9 @@ auto quic_socket::send_packet(encryption_level level,
auto self = shared_from_this();
auto buffer = std::make_shared<std::vector<uint8_t>>(std::move(protected_packet));

packets_sent_.fetch_add(1, std::memory_order_relaxed);
bytes_sent_.fetch_add(buffer->size(), std::memory_order_relaxed);

udp_socket_.async_send_to(
asio::buffer(*buffer),
remote_endpoint_,
Expand Down
40 changes: 40 additions & 0 deletions src/internal/quic_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,40 @@ namespace kcenon::network::internal
*/
auto socket() const -> const asio::ip::udp::socket& { return udp_socket_; }

// =====================================================================
// Connection statistics
// =====================================================================

/*!
* \brief Get the ALPN protocol negotiated during the handshake.
* \return Negotiated protocol string, or empty if none was negotiated.
*/
[[nodiscard]] auto negotiated_alpn() const -> std::string;

/*! \brief Total QUIC packets sent on this socket. */
[[nodiscard]] auto packets_sent() const noexcept -> uint64_t
{
return packets_sent_.load(std::memory_order_relaxed);
}

/*! \brief Total QUIC packets received on this socket. */
[[nodiscard]] auto packets_received() const noexcept -> uint64_t
{
return packets_received_.load(std::memory_order_relaxed);
}

/*! \brief Total bytes sent on this socket (protected packet sizes). */
[[nodiscard]] auto bytes_sent() const noexcept -> uint64_t
{
return bytes_sent_.load(std::memory_order_relaxed);
}

/*! \brief Total bytes received on this socket (datagram sizes). */
[[nodiscard]] auto bytes_received() const noexcept -> uint64_t
{
return bytes_received_.load(std::memory_order_relaxed);
}

private:
// =====================================================================
// Internal Methods
Expand Down Expand Up @@ -416,6 +450,12 @@ namespace kcenon::network::internal
//! Connection state
std::atomic<quic_connection_state> state_{quic_connection_state::idle};

//! Live transport counters surfaced through quic_connection_stats.
std::atomic<uint64_t> packets_sent_{0};
std::atomic<uint64_t> packets_received_{0};
std::atomic<uint64_t> bytes_sent_{0};
std::atomic<uint64_t> bytes_received_{0};

//! QUIC crypto handler
protocols::quic::quic_crypto crypto_;

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/experimental_quic_client_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ All rights reserved.

#define NETWORK_USE_EXPERIMENTAL
#include "internal/experimental/quic_client.h"
#include "internal/quic_socket.h"

#include <gtest/gtest.h>

#include <chrono>
#include <memory>
#include <string>
#include <vector>
Expand Down Expand Up @@ -207,6 +209,48 @@ TEST_F(ExperimentalQuicClientTest, StatsBeforeConnect)
EXPECT_EQ(stats.bytes_received, 0u);
}

// stats() and alpn_protocol() delegate to the live quic_socket accessors added
// for this change. A freshly constructed socket must report zeroed counters and
// no negotiated ALPN — the same defaults the client surfaces before connecting.
TEST(QuicSocketStatsAccessorsTest, StartAtDefaults)
{
asio::io_context io;
asio::ip::udp::socket udp(io, asio::ip::udp::v4());
auto sock = std::make_shared<kcenon::network::internal::quic_socket>(
std::move(udp), kcenon::network::internal::quic_role::client);

EXPECT_EQ(sock->packets_sent(), 0u);
EXPECT_EQ(sock->packets_received(), 0u);
EXPECT_EQ(sock->bytes_sent(), 0u);
EXPECT_EQ(sock->bytes_received(), 0u);
EXPECT_TRUE(sock->negotiated_alpn().empty());
}

// connect() emits the QUIC Initial packet, so the live send counters that
// stats() reports must advance. Driven directly on the socket with a
// single-threaded io_context for deterministic, leak-free teardown.
TEST(QuicSocketStatsAccessorsTest, ConnectIncrementsSentCounters)
{
asio::io_context io;
asio::ip::udp::socket udp(io, asio::ip::udp::v4());
auto sock = std::make_shared<kcenon::network::internal::quic_socket>(
std::move(udp), kcenon::network::internal::quic_role::client);

asio::ip::udp::endpoint server(asio::ip::make_address("127.0.0.1"), 59998);
auto result = sock->connect(server, "example.com");
io.run_for(std::chrono::milliseconds(200));
sock->stop_receive();
io.run_for(std::chrono::milliseconds(50));

if (result.is_err())
{
GTEST_SKIP() << "QUIC connect unavailable in this environment";
}
// The Initial packet counts as at least one sent packet / some bytes.
EXPECT_GT(sock->packets_sent(), 0u);
EXPECT_GT(sock->bytes_sent(), 0u);
}

TEST_F(ExperimentalQuicClientTest, EarlyDataNotAcceptedBeforeConnect)
{
auto client = std::make_shared<messaging_quic_client>("client");
Expand Down
Loading