Skip to content

infra(test): add mock_quic_peer_loop for QUIC Initial loopback (Phase 2C of #1074)#1103

Merged
kcenon merged 1 commit into
developfrom
feat/issue-1074-phase-2c-quic-peer-loop
May 5, 2026
Merged

infra(test): add mock_quic_peer_loop for QUIC Initial loopback (Phase 2C of #1074)#1103
kcenon merged 1 commit into
developfrom
feat/issue-1074-phase-2c-quic-peer-loop

Conversation

@kcenon

@kcenon kcenon commented May 5, 2026

Copy link
Copy Markdown
Owner

What

Add tests/support/mock_quic_peer_loop.{h,cpp} plus one demo TEST_F. The peer receives one client QUIC Initial datagram, derives QUIC v1 initial keys from the client's original DCID (RFC 9001 Section 5.2), and replies with a header-protected server Initial whose payload contains a stub crypto_frame (type 0x06). This makes quic_socket::process_crypto_frame reachable from a hermetic test for the first time.

Change Type

  • Test infrastructure (no src/ behavioral changes)

Why

Part of #1074 (Phase 2C). Continues the work of #1075 (Phase 2A, mock_h2_server_peer) and #1102 (Phase 2B, mock_grpc_server_peer).

Without a peer that can pass the initial-keys gate at quic_socket.cpp:527, the existing mock_udp_peer::make_quic_initial_packet_stub cannot drive any frame-dispatch branch in process_frame -- the entire crypto_frame / stream_frame / ack_frame switch sits behind that gate. This is the structural blocker for the >=80% line / >=70% branch coverage targets in #1062 and #1065.

Related Issues

Where

File Change
tests/support/mock_quic_peer_loop.h new (+147)
tests/support/mock_quic_peer_loop.cpp new (+270)
tests/support/CMakeLists.txt +1 source line
tests/support/README.md +1 row in Files table
tests/unit/quic_socket_branch_test.cpp +46 (one new TEST_F + include)

No src/ changes. No public-API additions. Phase 2D (friend-test injection for quic_server::handle_packet / websocket_server::handle_new_connection) and Phase 2E (frame_injector) remain open under #1074.

How

Implementation Highlights

The peer mirrors the RAII shape of mock_h2_server_peer and mock_grpc_server_peer:

  • Shared io_context from hermetic_transport_fixture
  • Dedicated worker thread launched in constructor, joined in destructor
  • std::atomic<bool> state flags polled by tests via wait_for(...)
  • All four special members deleted
  • Hermetic: bound to 127.0.0.1:0 (kernel-assigned port), no DNS, no on-disk secrets

Crypto path reuses production helpers -- no HKDF/AEAD reimplementation:

  • internal::quic::initial_keys::derive(dcid) for v1 initial-secret derivation
  • internal::quic::packet_builder::build_initial() for long-header construction
  • internal::quic::packet_protection::protect() and protect_header() for AEAD + header protection

The reply payload is PADDING + crypto_frame(0x06) carrying 8 zero bytes. process_crypto_frame fires regardless of TLS parse outcome, which is the goal -- driving the branch, not completing the handshake.

One implementation note: packet_builder::build_initial() does not embed the payload-length varint, so the peer code computes ciphertext+tag size and inserts the varint manually before AEAD encryption. Reviewers may want to focus on this section in mock_quic_peer_loop.cpp.

Testing Done

  • New demo TEST_F: QuicSocketHermeticTransportTest::ProcessCryptoFrameReachableViaMockQuicPeerLoop
  • Asserts peer.initial_sent() and !peer.io_failed() after the client connects. The test's value is that the previously unreachable process_crypto_frame branch executes -- coverage tooling picks that up. The client exposes no observable post-handshake state from outside (the TLS handshake does not complete with a stub crypto frame), so the test asserts peer-side state.
  • Local result: ctest -R 'ProcessCryptoFrameReachableViaMockQuicPeerLoop' passes in 9 ms on macOS.

Test Plan for Reviewers

cmake -S . -B build -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build --target network_test_support quic_socket_branch_test -j
ctest --test-dir build -R 'ProcessCryptoFrameReachableViaMockQuicPeerLoop' --output-on-failure

Breaking Changes

None.

Rollback

Revert this PR. No data migration, no public-API impact.

Part of #1074

…2C of #1074)

Add server-side QUIC Initial echo peer to tests/support/. The peer
receives one client Initial datagram, derives QUIC v1 initial keys
from the original DCID (RFC 9001 Section 5.2), and replies with a
header-protected server Initial carrying a stub crypto_frame
(type 0x06). This makes quic_socket::process_crypto_frame reachable
from a hermetic test for the first time, unblocking coverage
expansion under #1062 / #1065.

Phase 2A (#1075) and 2B (#1102) shipped HTTP/2 and gRPC peers; this
follows the same RAII shape: shared io_context, dedicated worker
thread, atomic state flags polled via wait_for. Phase 2D (friend-
test injection) and 2E (frame_injector) remain open under #1074.

Files:
- tests/support/mock_quic_peer_loop.{h,cpp} (new)
- tests/support/CMakeLists.txt (+1)
- tests/support/README.md (+1)
- tests/unit/quic_socket_branch_test.cpp (+46) -- one demo TEST_F

Part of #1074
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Metric Value
Line Coverage 69.6%
Branch Coverage 34.6%
Target 80% lines / 70% branches
Coverage Details

Full HTML report is available as a build artifact.

@kcenon kcenon merged commit 792b7e4 into develop May 5, 2026
15 checks passed
@kcenon kcenon deleted the feat/issue-1074-phase-2c-quic-peer-loop branch May 5, 2026 22:49
kcenon added a commit that referenced this pull request May 6, 2026
…#1104)

Add friend-test injection so unit tests can drive two private
server-side methods previously unreachable from public APIs:

- messaging_quic_server::handle_packet
  (src/internal/experimental/quic_server.h:432)
- messaging_ws_server::handle_new_connection
  (src/internal/http/websocket_server.h:415)

Mechanism: new compile definition NETWORK_ENABLE_TEST_INJECTION
gated behind BUILD_TESTS in cmake/network_system_targets.cmake.
The two production headers gain a forward declaration block and
a single 'friend class' line, all wrapped in
'#if defined(NETWORK_ENABLE_TEST_INJECTION)'. When BUILD_TESTS=OFF
the production binary is byte-identical to the previous develop tip.

Probe types live entirely in tests/support/:
- network_test_friends.h forward-declares the probes
- quic_server_probe.{h,cpp} forwards to handle_packet
- ws_server_probe.{h,cpp} forwards to handle_new_connection

Demo tests verify end-to-end wiring:
- QuicServerProbeTest.HandlePacket{Empty,Garbage}BufferDoesNotCrash
- WsServerProbeTest.HandleNewConnectionEarlyReturnNoSessionManager
- WsServerProbeTest.HandleNewConnectionEmptySocketEarlyReturn

Per #1074 scope: no behavioral changes to src/. Phases 2A (#1075),
2A.2 (#1082), 2B (#1102), 2C (#1103) merged; 2E remains.

Part of #1074
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant