Coverage Report

Created: 2026-09-14 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/http3_mock_server.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief ngtcp2 QUIC transport and nghttp3 peer implementation.
9
10
#include "proto_fuzzer/http3_mock_server.h"
11
12
#include <arpa/inet.h>
13
#include <fcntl.h>
14
#include <nghttp3/nghttp3.h>
15
#include <ngtcp2/ngtcp2.h>
16
#include <ngtcp2/ngtcp2_crypto.h>
17
#include <ngtcp2/ngtcp2_crypto_ossl.h>
18
#include <openssl/err.h>
19
#include <openssl/pem.h>
20
#include <openssl/ssl.h>
21
#include <sys/socket.h>
22
#include <unistd.h>
23
24
#include <algorithm>
25
#include <array>
26
#include <cerrno>
27
#include <chrono>
28
#include <climits>
29
#include <cstddef>
30
#include <cstdint>
31
#include <cstring>
32
#include <memory>
33
#include <string>
34
#include <utility>
35
#include <vector>
36
37
#include "proto_fuzzer/multi_socket_driver.h"
38
#include "proto_fuzzer/scenario_limits.h"
39
#include "proto_fuzzer/tls_test_credentials.h"
40
41
namespace proto_fuzzer {
42
43
namespace {
44
45
constexpr std::int64_t kNoStreamId = -1;
46
constexpr std::size_t kMaxDatagramsPerTurn = 64;
47
constexpr std::size_t kServerConnectionIdBytes = 18;
48
constexpr std::size_t kReceiveBufferBytes = 64 * 1024;
49
// One request plus the six HTTP/3 critical streams leave room for every
50
// bounded action to create a fresh raw unidirectional stream.
51
constexpr std::size_t kMaxStreams = scenario_limits::kMaxHttp3Actions + 8;
52
constexpr std::size_t kMaxWritesPerTurn = 64;
53
constexpr std::size_t kMaxPendingPlaintextBytes = 64 * 1024;
54
constexpr int kHttp3IdleIterations = 32;
55
// QUIC's first two bits select one of these wire widths. The remaining bits
56
// encode the integer itself.
57
constexpr std::array<std::size_t, 4> kQuicVarintByteWidths = {1, 2, 4, 8};
58
constexpr unsigned int kQuicVarintTagBits = 2;
59
constexpr unsigned int kQuicVarintTagShift = CHAR_BIT - kQuicVarintTagBits;
60
static_assert(CHAR_BIT == 8, "QUIC varints require 8-bit bytes");
61
62
enum StreamRoleIndex : std::size_t {
63
  kResponseStream = 0,
64
  kControlStream = 1,
65
  kQpackEncoderStream = 2,
66
  kQpackDecoderStream = 3,
67
  kStreamRoleCount = 4,
68
};
69
70
/// Keep server-side OpenSSL failures from changing error classification in
71
/// curl's OpenSSL client, which executes on the same thread.
72
class OpenSslErrorQueueGuard {
73
 public:
74
164k
  OpenSslErrorQueueGuard() { ERR_clear_error(); }
75
164k
  ~OpenSslErrorQueueGuard() { ERR_clear_error(); }
76
77
  OpenSslErrorQueueGuard(const OpenSslErrorQueueGuard&) = delete;
78
  OpenSslErrorQueueGuard& operator=(const OpenSslErrorQueueGuard&) = delete;
79
};
80
81
/// Configure callback-created descriptors explicitly: curl's requested
82
/// SOCK_NONBLOCK and SOCK_CLOEXEC flags do not constrain an application fd.
83
13.6k
bool ConfigureSocket(int fd) {
84
13.6k
  if (fd < 0) {
85
0
    return false;
86
0
  }
87
13.6k
  const int descriptor_flags = ::fcntl(fd, F_GETFD, 0);
88
13.6k
  if (descriptor_flags < 0 || ::fcntl(fd, F_SETFD, descriptor_flags | FD_CLOEXEC) < 0) {
89
0
    return false;
90
0
  }
91
13.6k
  const int status_flags = ::fcntl(fd, F_GETFL, 0);
92
13.6k
  return status_flags >= 0 && ::fcntl(fd, F_SETFL, status_flags | O_NONBLOCK) == 0;
93
13.6k
}
94
95
/// Bind one private IPv4 UDP endpoint to an ephemeral loopback port.
96
6.80k
int OpenLoopbackSocket(struct sockaddr_in* bound_address) {
97
6.80k
  if (bound_address == nullptr) {
98
0
    return -1;
99
0
  }
100
101
6.80k
  const int fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
102
6.80k
  if (fd < 0 || !ConfigureSocket(fd)) {
103
0
    if (fd >= 0) {
104
0
      (void)::close(fd);
105
0
    }
106
0
    return -1;
107
0
  }
108
109
6.80k
  struct sockaddr_in requested = {};
110
6.80k
  requested.sin_family = AF_INET;
111
6.80k
  requested.sin_port = htons(0);
112
6.80k
  requested.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
113
6.80k
  if (::bind(fd, reinterpret_cast<const struct sockaddr*>(&requested), sizeof(requested)) != 0) {
114
0
    (void)::close(fd);
115
0
    return -1;
116
0
  }
117
118
6.80k
  socklen_t length = sizeof(*bound_address);
119
6.80k
  std::memset(bound_address, 0, sizeof(*bound_address));
120
6.80k
  if (::getsockname(fd, reinterpret_cast<struct sockaddr*>(bound_address), &length) != 0 ||
121
6.80k
      length != sizeof(*bound_address) || bound_address->sin_family != AF_INET) {
122
0
    (void)::close(fd);
123
0
    return -1;
124
0
  }
125
6.80k
  return fd;
126
6.80k
}
127
128
/// Replace all destination metadata curl can retain with the private listener.
129
6.80k
bool RewriteDestination(struct curl_sockaddr* address, const struct sockaddr_in& destination) {
130
6.80k
  if (address == nullptr || sizeof(destination) > sizeof(address->addr)) {
131
0
    return false;
132
0
  }
133
6.80k
  address->family = AF_INET;
134
6.80k
  address->socktype = SOCK_DGRAM;
135
6.80k
  address->protocol = IPPROTO_UDP;
136
6.80k
  address->addrlen = sizeof(destination);
137
6.80k
  std::memset(&address->addr, 0, sizeof(address->addr));
138
6.80k
  std::memcpy(&address->addr, &destination, sizeof(destination));
139
6.80k
  return true;
140
6.80k
}
141
142
/// Select only the final RFC HTTP/3 ALPN identifier.
143
int SelectHttp3Alpn(SSL* /*ssl*/, const unsigned char** selected, unsigned char* selected_length,
144
6.80k
                    const unsigned char* client_protocols, unsigned int client_protocols_length, void* /*userdata*/) {
145
6.80k
  static constexpr unsigned char server_protocols[] = {2, 'h', '3'};
146
6.80k
  unsigned char* match = nullptr;
147
6.80k
  unsigned char match_length = 0;
148
6.80k
  const int result = SSL_select_next_proto(&match, &match_length, server_protocols, sizeof(server_protocols),
149
6.80k
                                           client_protocols, client_protocols_length);
150
6.80k
  if (result != OPENSSL_NPN_NEGOTIATED) {
151
0
    return SSL_TLSEXT_ERR_ALERT_FATAL;
152
0
  }
153
6.80k
  *selected = match;
154
6.80k
  *selected_length = match_length;
155
6.80k
  return SSL_TLSEXT_ERR_OK;
156
6.80k
}
157
158
/// Append one profile-selected certificate without transferring a malformed
159
/// object into the context on parse failure.
160
378
bool AddExtraChainCertificate(SSL_CTX* context, const char* certificate_pem) {
161
378
  BIO* certificate_bio = BIO_new_mem_buf(certificate_pem, -1);
162
378
  if (certificate_bio == nullptr) {
163
0
    return false;
164
0
  }
165
378
  X509* certificate = PEM_read_bio_X509(certificate_bio, nullptr, nullptr, nullptr);
166
378
  BIO_free(certificate_bio);
167
378
  if (certificate == nullptr) {
168
0
    return false;
169
0
  }
170
378
  if (SSL_CTX_add_extra_chain_cert(context, certificate) != 1) {
171
0
    X509_free(certificate);
172
0
    return false;
173
0
  }
174
378
  return true;
175
378
}
176
177
/// Parse the checked-in key and profile-selected chain entirely in memory.
178
7.04k
bool LoadCredentials(SSL_CTX* context, curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain) {
179
7.04k
  BIO* certificate_bio = BIO_new_mem_buf(tls_test_credentials::kCertificatePem, -1);
180
7.04k
  BIO* key_bio = BIO_new_mem_buf(tls_test_credentials::kPrivateKeyPem, -1);
181
7.04k
  if (certificate_bio == nullptr || key_bio == nullptr) {
182
0
    BIO_free(certificate_bio);
183
0
    BIO_free(key_bio);
184
0
    return false;
185
0
  }
186
187
7.04k
  X509* certificate = PEM_read_bio_X509(certificate_bio, nullptr, nullptr, nullptr);
188
7.04k
  EVP_PKEY* key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr);
189
7.04k
  BIO_free(certificate_bio);
190
7.04k
  BIO_free(key_bio);
191
7.04k
  if (certificate == nullptr || key == nullptr) {
192
0
    X509_free(certificate);
193
0
    EVP_PKEY_free(key);
194
0
    return false;
195
0
  }
196
197
7.04k
  const bool loaded = SSL_CTX_use_certificate(context, certificate) == 1 && SSL_CTX_use_PrivateKey(context, key) == 1 &&
198
7.04k
                      SSL_CTX_check_private_key(context) == 1;
199
7.04k
  X509_free(certificate);
200
7.04k
  EVP_PKEY_free(key);
201
7.04k
  if (!loaded) {
202
0
    return false;
203
0
  }
204
205
7.04k
  if (certificate_chain == curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_ALL_KEY_TYPES) {
206
126
    return AddExtraChainCertificate(context, tls_test_credentials::kRsaCertificatePem) &&
207
126
           AddExtraChainCertificate(context, tls_test_credentials::kDsaCertificatePem) &&
208
126
           AddExtraChainCertificate(context, tls_test_credentials::kDhCertificatePem);
209
126
  }
210
6.91k
  return true;
211
7.04k
}
212
213
/// Create a TLS 1.3 context for ngtcp2's OpenSSL crypto adapter.
214
7.04k
SSL_CTX* CreateTlsContext(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain) {
215
7.04k
  OpenSslErrorQueueGuard error_guard;
216
7.04k
  SSL_CTX* context = SSL_CTX_new(TLS_server_method());
217
7.04k
  if (context == nullptr || !LoadCredentials(context, certificate_chain)) {
218
0
    SSL_CTX_free(context);
219
0
    return nullptr;
220
0
  }
221
222
7.04k
  if (SSL_CTX_set_min_proto_version(context, TLS1_3_VERSION) != 1 ||
223
7.04k
      SSL_CTX_set_max_proto_version(context, TLS1_3_VERSION) != 1) {
224
0
    SSL_CTX_free(context);
225
0
    return nullptr;
226
0
  }
227
7.04k
  (void)SSL_CTX_set_options(context, SSL_OP_NO_COMPRESSION);
228
7.04k
  SSL_CTX_set_alpn_select_cb(context, &SelectHttp3Alpn, nullptr);
229
7.04k
  return context;
230
7.04k
}
231
232
/// Encode one QUIC variable-length integer in its shortest representation.
233
3.81k
void AppendQuicVarint(std::string* output, std::uint64_t value) {
234
3.81k
  value &= scenario_limits::kMaxQuicVarint;
235
3.84k
  for (std::size_t encoding = 0; encoding < kQuicVarintByteWidths.size(); ++encoding) {
236
3.84k
    const std::size_t byte_width = kQuicVarintByteWidths[encoding];
237
3.84k
    const unsigned int value_bits = static_cast<unsigned int>(byte_width * CHAR_BIT - kQuicVarintTagBits);
238
3.84k
    if (value >= (std::uint64_t{1} << value_bits)) {
239
35
      continue;
240
35
    }
241
242
7.69k
    for (std::size_t byte = byte_width; byte != 0; --byte) {
243
3.88k
      const unsigned int shift = static_cast<unsigned int>((byte - 1) * CHAR_BIT);
244
3.88k
      unsigned char encoded_byte = static_cast<unsigned char>(value >> shift);
245
3.88k
      if (byte == byte_width) {
246
3.81k
        encoded_byte |= static_cast<unsigned char>(encoding << kQuicVarintTagShift);
247
3.81k
      }
248
3.88k
      output->push_back(static_cast<char>(encoded_byte));
249
3.88k
    }
250
3.81k
    return;
251
3.84k
  }
252
3.81k
}
253
254
/// Normalize a header name again at the runtime boundary. Fixed H3 targets
255
/// already run the postprocessor; this also keeps direct unit-test scenarios
256
/// safe to pass to nghttp3.
257
1.50k
std::string NormalizeHeaderName(const std::string& source) {
258
1.50k
  std::string result = source.substr(0, scenario_limits::kMaxHttp3HeaderNameBytes);
259
1.50k
  if (result.empty()) {
260
0
    return "x-fuzz";
261
0
  }
262
29.8k
  for (char& byte : result) {
263
29.8k
    const unsigned char value = static_cast<unsigned char>(byte);
264
29.8k
    const bool lower = value >= 'a' && value <= 'z';
265
29.8k
    const bool upper = value >= 'A' && value <= 'Z';
266
29.8k
    const bool digit = value >= '0' && value <= '9';
267
29.8k
    const bool punctuation = value == '!' || value == '#' || value == '$' || value == '%' || value == '&' ||
268
24.4k
                             value == '\'' || value == '*' || value == '+' || value == '-' || value == '.' ||
269
13.8k
                             value == '^' || value == '_' || value == '`' || value == '|' || value == '~';
270
29.8k
    if (upper) {
271
0
      byte = static_cast<char>(value - 'A' + 'a');
272
29.8k
    } else if (!lower && !digit && !punctuation) {
273
0
      byte = '-';
274
0
    }
275
29.8k
  }
276
1.50k
  return result;
277
1.50k
}
278
279
/// Remove control bytes that would make a structured field value malformed.
280
1.50k
std::string NormalizeHeaderValue(const std::string& source) {
281
1.50k
  std::string result = source.substr(0, scenario_limits::kMaxHttp3HeaderValueBytes);
282
93.9k
  for (char& byte : result) {
283
93.9k
    const unsigned char value = static_cast<unsigned char>(byte);
284
93.9k
    if ((value < 0x20U && value != '\t') || value == 0x7fU) {
285
0
      byte = ' ';
286
0
    }
287
93.9k
  }
288
1.50k
  return result;
289
1.50k
}
290
291
/// Return ngtcp2's nanosecond-resolution monotonic timestamp.
292
369k
ngtcp2_tstamp QuicNow() {
293
369k
  const auto now = std::chrono::steady_clock::now().time_since_epoch();
294
369k
  return static_cast<ngtcp2_tstamp>(std::chrono::duration_cast<std::chrono::nanoseconds>(now).count());
295
369k
}
296
297
}  // namespace
298
299
/// Private transport and HTTP/3 state. Keeping ngtcp2, OpenSSL, and nghttp3
300
/// out of the public header lets other proto targets compile without their
301
/// include trees.
302
class Http3MockServerImpl {
303
 public:
304
  explicit Http3MockServerImpl(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
305
7.04k
      : context_(CreateTlsContext(certificate_chain)), crypto_connection_ref_{&GetQuicConnection, this} {
306
7.04k
    role_stream_ids_.fill(kNoStreamId);
307
7.04k
    streams_.reserve(kMaxStreams);
308
7.04k
    retained_writes_.reserve(kMaxWritesPerTurn);
309
7.04k
  }
310
311
7.04k
  ~Http3MockServerImpl() {
312
7.04k
    ResetPeer();
313
7.04k
    OpenSslErrorQueueGuard error_guard;
314
7.04k
    SSL_CTX_free(context_);
315
7.04k
  }
316
317
  Http3MockServerImpl(const Http3MockServerImpl&) = delete;
318
  Http3MockServerImpl& operator=(const Http3MockServerImpl&) = delete;
319
320
6.80k
  curl_socket_t OpenSocket(curlsocktype purpose, struct curl_sockaddr* address) {
321
6.80k
    if (purpose != CURLSOCKTYPE_IPCXN || address == nullptr || socket_opened_ || context_ == nullptr) {
322
2
      return CURL_SOCKET_BAD;
323
2
    }
324
6.80k
    socket_opened_ = true;
325
326
6.80k
    struct sockaddr_in listener_address = {};
327
6.80k
    server_fd_ = OpenLoopbackSocket(&listener_address);
328
6.80k
    if (server_fd_ < 0 || !RewriteDestination(address, listener_address)) {
329
0
      ResetPeer();
330
0
      return CURL_SOCKET_BAD;
331
0
    }
332
6.80k
    server_port_ = ntohs(listener_address.sin_port);
333
6.80k
    local_address_ = listener_address;
334
335
6.80k
    const int client_fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
336
6.80k
    if (client_fd < 0 || !ConfigureSocket(client_fd)) {
337
0
      if (client_fd >= 0) {
338
0
        (void)::close(client_fd);
339
0
      }
340
0
      ResetPeer();
341
0
      return CURL_SOCKET_BAD;
342
0
    }
343
6.80k
    return static_cast<curl_socket_t>(client_fd);
344
6.80k
  }
345
346
7.04k
  void BeginScenario(const curl::fuzzer::proto::Scenario& scenario) {
347
7.04k
    ResetPeer();
348
7.04k
    scenario_ = &scenario;
349
7.04k
    default_response_.Clear();
350
7.04k
    default_response_.set_status_code(200);
351
7.04k
    default_response_.set_finish_stream(true);
352
7.04k
  }
353
354
137k
  std::size_t DrivePeerTurn() {
355
137k
    if (server_fd_ < 0 || transport_failed_) {
356
832
      return 0;
357
832
    }
358
359
136k
    OpenSslErrorQueueGuard error_guard;
360
136k
    std::size_t progress = HandleExpiry();
361
136k
    if (transport_failed_) {
362
0
      return progress;
363
0
    }
364
136k
    progress += ReadDatagrams();
365
136k
    if (quic_connection_ == nullptr || transport_failed_) {
366
26
      return progress;
367
26
    }
368
369
136k
    if (handshake_complete_ && !close_requested_ && !close_sent_) {
370
122k
      progress += CreateServerStreams();
371
122k
      progress += DriveApplicationOutput();
372
122k
    }
373
136k
    progress += FlushTransportPackets();
374
136k
    return progress;
375
136k
  }
376
377
0
  bool handshake_complete() const { return handshake_complete_; }
378
379
0
  bool request_headers_received() const { return request_headers_received_; }
380
381
0
  std::size_t executed_action_count() const { return next_action_; }
382
383
0
  std::uint16_t server_port() const { return server_port_; }
384
385
 private:
386
  // ngtcp2 acknowledges QUIC writes by stream offset, while nghttp3 accepts
387
  // acknowledgement only for its own serialized bytes. Track those regions
388
  // separately from deliberately raw script writes.
389
  struct StreamState {
390
    struct ManagedWriteRange {
391
      std::uint64_t begin = 0;
392
      std::uint64_t end = 0;
393
    };
394
395
    std::int64_t id = kNoStreamId;
396
    std::uint64_t write_offset = 0;
397
    std::vector<ManagedWriteRange> managed_write_ranges;
398
    std::vector<std::uint64_t> managed_fin_offsets;
399
    std::size_t next_managed_ack_range = 0;
400
    std::size_t next_managed_fin_offset = 0;
401
  };
402
403
  // Queue at most one application write at a time. The byte string is kept in
404
  // retained_writes_ because ngtcp2 may submit only a prefix on each turn.
405
  struct PendingWrite {
406
    std::int64_t stream_id = kNoStreamId;
407
    const std::string* bytes = nullptr;
408
    std::size_t offset = 0;
409
    bool finish_stream = false;
410
    bool nghttp3_managed = false;
411
    bool completes_action = false;
412
413
288k
    bool active() const { return stream_id != kNoStreamId; }
414
415
38.7k
    void Clear() {
416
38.7k
      stream_id = kNoStreamId;
417
38.7k
      bytes = nullptr;
418
38.7k
      offset = 0;
419
38.7k
      finish_stream = false;
420
38.7k
      nghttp3_managed = false;
421
38.7k
      completes_action = false;
422
38.7k
    }
423
  };
424
425
  // nghttp3 pulls response bytes through a callback. This state lets that
426
  // callback preserve the scenario's chunk boundaries and final-stream intent.
427
  struct ResponseBodyState {
428
    std::vector<std::string> chunks;
429
    std::size_t next_chunk = 0;
430
    bool finish_stream = false;
431
    bool has_trailers = false;
432
  };
433
434
  enum class WriteResult {
435
    kComplete,
436
    kBlocked,
437
    kFailed,
438
  };
439
440
  // Tear down in protocol-layer order: HTTP/3 retains references into QUIC,
441
  // and OpenSSL's app data points back to this object until it is cleared.
442
14.0k
  void ResetPeer() {
443
14.0k
    OpenSslErrorQueueGuard error_guard;
444
14.0k
    pending_write_.Clear();
445
14.0k
    nghttp3_conn_del(http3_connection_);
446
14.0k
    http3_connection_ = nullptr;
447
14.0k
    ngtcp2_conn_del(quic_connection_);
448
14.0k
    quic_connection_ = nullptr;
449
14.0k
    if (tls_connection_ != nullptr) {
450
6.80k
      SSL_set_app_data(tls_connection_, nullptr);
451
6.80k
      SSL_free(tls_connection_);
452
6.80k
      tls_connection_ = nullptr;
453
6.80k
    }
454
14.0k
    ngtcp2_crypto_ossl_ctx_del(crypto_context_);
455
14.0k
    crypto_context_ = nullptr;
456
14.0k
    streams_.clear();
457
14.0k
    retained_writes_.clear();
458
14.0k
    if (server_fd_ >= 0) {
459
6.80k
      (void)::close(server_fd_);
460
6.80k
      server_fd_ = -1;
461
6.80k
    }
462
463
14.0k
    scenario_ = nullptr;
464
14.0k
    role_stream_ids_.fill(kNoStreamId);
465
14.0k
    std::memset(&local_address_, 0, sizeof(local_address_));
466
14.0k
    std::memset(&remote_address_, 0, sizeof(remote_address_));
467
14.0k
    ngtcp2_path_storage_zero(&path_storage_);
468
14.0k
    response_body_.chunks.clear();
469
14.0k
    response_body_.next_chunk = 0;
470
14.0k
    response_body_.finish_stream = false;
471
14.0k
    response_body_.has_trailers = false;
472
14.0k
    next_action_ = 0;
473
14.0k
    server_port_ = 0;
474
14.0k
    socket_opened_ = false;
475
14.0k
    handshake_complete_ = false;
476
14.0k
    request_headers_received_ = false;
477
14.0k
    server_streams_bound_ = false;
478
14.0k
    bootstrap_drained_ = false;
479
14.0k
    waiting_for_h3_drain_ = false;
480
14.0k
    waiting_drain_completes_action_ = false;
481
14.0k
    final_response_submitted_ = false;
482
14.0k
    default_response_started_ = false;
483
14.0k
    http3_failed_ = false;
484
14.0k
    transport_failed_ = false;
485
14.0k
    close_requested_ = false;
486
14.0k
    close_sent_ = false;
487
14.0k
    close_error_code_ = 0;
488
14.0k
    random_counter_ = 0;
489
14.0k
  }
490
491
  // ngtcp2 calls into this group while it owns QUIC parsing and encryption.
492
  // Each callback bridges a transport event into the separately-owned nghttp3
493
  // connection or records enough state for a later application turn.
494
306k
  static ngtcp2_conn* GetQuicConnection(ngtcp2_crypto_conn_ref* connection_ref) {
495
306k
    auto* self = static_cast<Http3MockServerImpl*>(connection_ref->user_data);
496
306k
    return self == nullptr ? nullptr : self->quic_connection_;
497
306k
  }
498
499
6.71k
  static int HandshakeCompleted(ngtcp2_conn* /*connection*/, void* user_data) {
500
6.71k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
501
6.71k
    if (self == nullptr) {
502
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
503
0
    }
504
6.71k
    self->handshake_complete_ = true;
505
6.71k
    return 0;
506
6.71k
  }
507
508
13.6k
  static int ReceiveTransmitKey(ngtcp2_conn* /*connection*/, ngtcp2_encryption_level level, void* user_data) {
509
13.6k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
510
13.6k
    if (self == nullptr) {
511
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
512
0
    }
513
13.6k
    if (level != NGTCP2_ENCRYPTION_LEVEL_1RTT || self->server_streams_bound_) {
514
6.80k
      return 0;
515
6.80k
    }
516
    // HTTP/3 application streams become valid only after 1-RTT keys exist.
517
    // Bind its critical streams before allowing script output onto the peer.
518
6.80k
    if (!self->CreateHttp3Connection()) {
519
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
520
0
    }
521
6.80k
    (void)self->CreateServerStreams();
522
6.80k
    return self->server_streams_bound_ ? 0 : NGTCP2_ERR_CALLBACK_FAILURE;
523
6.80k
  }
524
525
  static void GenerateRandomBytes(std::uint8_t* destination, std::size_t length,
526
27.2k
                                  const ngtcp2_rand_ctx* random_context) {
527
27.2k
    if (random_context == nullptr) {
528
0
      std::memset(destination, 0, length);
529
0
      return;
530
0
    }
531
27.2k
    auto* self = static_cast<Http3MockServerImpl*>(random_context->native_handle);
532
27.2k
    if (self == nullptr) {
533
0
      std::memset(destination, 0, length);
534
0
      return;
535
0
    }
536
27.2k
    self->FillRandom(destination, length);
537
27.2k
  }
538
539
  static int GetNewConnectionId(ngtcp2_conn* /*connection*/, ngtcp2_cid* connection_id,
540
                                ngtcp2_stateless_reset_token* reset_token, std::size_t connection_id_length,
541
6.80k
                                void* user_data) {
542
6.80k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
543
6.80k
    if (self == nullptr || connection_id == nullptr || reset_token == nullptr ||
544
6.80k
        connection_id_length > sizeof(connection_id->data)) {
545
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
546
0
    }
547
6.80k
    connection_id->datalen = connection_id_length;
548
6.80k
    self->FillRandom(connection_id->data, connection_id_length);
549
6.80k
    self->FillRandom(reset_token->data, sizeof(reset_token->data));
550
6.80k
    return 0;
551
6.80k
  }
552
553
26.8k
  static int OpenRemoteStream(ngtcp2_conn* /*connection*/, std::int64_t stream_id, void* user_data) {
554
26.8k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
555
26.8k
    if (self == nullptr || self->streams_.size() >= kMaxStreams) {
556
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
557
0
    }
558
26.8k
    StreamState state;
559
26.8k
    state.id = stream_id;
560
26.8k
    self->streams_.push_back(state);
561
26.8k
    if (self->role_stream_ids_[kResponseStream] == kNoStreamId && ngtcp2_is_bidi_stream(stream_id)) {
562
6.71k
      self->role_stream_ids_[kResponseStream] = stream_id;
563
6.71k
    }
564
26.8k
    return 0;
565
26.8k
  }
566
567
  // ngtcp2 presents decrypted stream bytes; nghttp3 parses the HTTP/3 layer.
568
  // Extending QUIC's flow-control windows by the consumed amount keeps only
569
  // parsed bytes eligible for further client transmission.
570
  static int ReceiveQuicStreamData(ngtcp2_conn* connection, std::uint32_t flags, std::int64_t stream_id,
571
                                   std::uint64_t /*offset*/, const std::uint8_t* data, std::size_t length,
572
28.9k
                                   void* user_data, void* /*stream_user_data*/) {
573
28.9k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
574
28.9k
    if (self == nullptr || self->http3_connection_ == nullptr || self->http3_failed_) {
575
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
576
0
    }
577
28.9k
    const nghttp3_ssize consumed =
578
28.9k
        nghttp3_conn_read_stream2(self->http3_connection_, stream_id, data, length, flags & NGTCP2_STREAM_DATA_FLAG_FIN,
579
28.9k
                                  ngtcp2_conn_get_timestamp(connection));
580
28.9k
    if (consumed < 0) {
581
26
      self->http3_failed_ = true;
582
26
      return NGTCP2_ERR_CALLBACK_FAILURE;
583
26
    }
584
28.9k
    ngtcp2_conn_extend_max_stream_offset(connection, stream_id, static_cast<std::uint64_t>(consumed));
585
28.9k
    ngtcp2_conn_extend_max_offset(connection, static_cast<std::uint64_t>(consumed));
586
28.9k
    return 0;
587
28.9k
  }
588
589
  // A transport acknowledgement covers both raw fuzz bytes and nghttp3 output.
590
  // Report only intersecting nghttp3-managed ranges back to nghttp3; otherwise
591
  // its write offsets would advance for bytes it never generated.
592
  static int AckedStreamDataOffset(ngtcp2_conn* /*connection*/, std::int64_t stream_id, std::uint64_t offset,
593
20.9k
                                   std::uint64_t length, void* user_data, void* /*stream_user_data*/) {
594
20.9k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
595
20.9k
    if (self == nullptr || self->http3_connection_ == nullptr || self->http3_failed_) {
596
0
      return 0;
597
0
    }
598
599
20.9k
    StreamState* stream = self->FindStream(stream_id);
600
20.9k
    if (stream == nullptr || offset > UINT64_MAX - length) {
601
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
602
0
    }
603
604
20.9k
    if (length == 0) {
605
6
      if (stream->next_managed_fin_offset < stream->managed_fin_offsets.size() &&
606
0
          stream->managed_fin_offsets[stream->next_managed_fin_offset] == offset) {
607
0
        ++stream->next_managed_fin_offset;
608
0
        if (nghttp3_conn_add_ack_offset(self->http3_connection_, stream_id, 0) != 0) {
609
0
          self->http3_failed_ = true;
610
0
          return NGTCP2_ERR_CALLBACK_FAILURE;
611
0
        }
612
0
      }
613
6
      return 0;
614
6
    }
615
616
20.9k
    const std::uint64_t end = offset + length;
617
20.9k
    std::uint64_t managed_length = 0;
618
41.4k
    for (std::size_t index = stream->next_managed_ack_range; index < stream->managed_write_ranges.size(); ++index) {
619
20.7k
      const StreamState::ManagedWriteRange& range = stream->managed_write_ranges[index];
620
20.7k
      if (range.end <= offset) {
621
0
        stream->next_managed_ack_range = index + 1;
622
0
        continue;
623
0
      }
624
20.7k
      if (range.begin >= end) {
625
234
        break;
626
234
      }
627
20.5k
      managed_length += std::min(range.end, end) - std::max(range.begin, offset);
628
20.5k
      if (range.end <= end) {
629
20.5k
        stream->next_managed_ack_range = index + 1;
630
20.5k
      }
631
20.5k
    }
632
633
20.9k
    if (managed_length != 0 && nghttp3_conn_add_ack_offset(self->http3_connection_, stream_id, managed_length) != 0) {
634
0
      self->http3_failed_ = true;
635
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
636
0
    }
637
20.9k
    return 0;
638
20.9k
  }
639
640
  // Keep nghttp3's stream lifecycle synchronized with QUIC resets, STOP_SENDING
641
  // events, and close error-code flags.
642
  static int CloseQuicStream(ngtcp2_conn* /*connection*/, std::uint32_t flags, std::int64_t stream_id,
643
                             std::uint64_t receive_error_code, std::uint64_t transmit_error_code, void* user_data,
644
131
                             void* /*stream_user_data*/) {
645
131
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
646
131
    if (self == nullptr || self->http3_connection_ == nullptr || self->http3_failed_) {
647
0
      return 0;
648
0
    }
649
131
    std::uint32_t http3_flags = NGHTTP3_STREAM_CLOSE_FLAG_NONE;
650
131
    if ((flags & NGTCP2_STREAM_CLOSE2_FLAG_RX_APP_ERROR_CODE_SET) != 0) {
651
0
      http3_flags |= NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET;
652
0
    }
653
131
    if ((flags & NGTCP2_STREAM_CLOSE2_FLAG_TX_APP_ERROR_CODE_SET) != 0) {
654
112
      http3_flags |= NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET;
655
112
    }
656
131
    const int result = nghttp3_conn_close_stream2(self->http3_connection_, http3_flags, stream_id, receive_error_code,
657
131
                                                  transmit_error_code);
658
131
    if (result != 0 && result != NGHTTP3_ERR_STREAM_NOT_FOUND) {
659
0
      self->http3_failed_ = true;
660
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
661
0
    }
662
131
    return 0;
663
131
  }
664
665
  static int ResetQuicStream(ngtcp2_conn* /*connection*/, std::int64_t stream_id, std::uint64_t /*final_size*/,
666
0
                             std::uint64_t /*application_error_code*/, void* user_data, void* /*stream_user_data*/) {
667
0
    return ShutdownHttp3StreamRead(stream_id, user_data);
668
0
  }
669
670
  static int StopSendingQuicStream(ngtcp2_conn* /*connection*/, std::int64_t stream_id,
671
                                   std::uint64_t /*application_error_code*/, void* user_data,
672
0
                                   void* /*stream_user_data*/) {
673
0
    return ShutdownHttp3StreamRead(stream_id, user_data);
674
0
  }
675
676
0
  static int ShutdownHttp3StreamRead(std::int64_t stream_id, void* user_data) {
677
0
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
678
0
    if (self == nullptr || self->http3_connection_ == nullptr || self->http3_failed_) {
679
0
      return 0;
680
0
    }
681
0
    const int result = nghttp3_conn_shutdown_stream_read(self->http3_connection_, stream_id);
682
0
    if (result != 0 && result != NGHTTP3_ERR_STREAM_NOT_FOUND) {
683
0
      self->http3_failed_ = true;
684
0
      return NGTCP2_ERR_CALLBACK_FAILURE;
685
0
    }
686
0
    return 0;
687
0
  }
688
689
  // Create HTTP/3 once the QUIC handshake can carry application traffic.
690
  // Dynamic QPACK is intentionally disabled so scenarios cannot retain table
691
  // state beyond the bounded peer lifetime.
692
6.80k
  bool CreateHttp3Connection() {
693
6.80k
    if (http3_connection_ != nullptr) {
694
0
      return true;
695
0
    }
696
6.80k
    nghttp3_callbacks callbacks = {};
697
6.80k
    callbacks.recv_data = &ReceiveRequestData;
698
6.80k
    callbacks.deferred_consume = &DeferredConsumeRequestData;
699
6.80k
    callbacks.end_headers = &EndRequestHeaders;
700
6.80k
    callbacks.end_stream = &EndRequestStream;
701
6.80k
    nghttp3_settings settings;
702
6.80k
    nghttp3_settings_default(&settings);
703
6.80k
    settings.qpack_max_dtable_capacity = 0;
704
6.80k
    settings.qpack_encoder_max_dtable_capacity = 0;
705
6.80k
    settings.qpack_blocked_streams = 0;
706
6.80k
    if (nghttp3_conn_server_new(&http3_connection_, &callbacks, &settings, nullptr, this) != 0) {
707
0
      http3_failed_ = true;
708
0
      return false;
709
0
    }
710
6.80k
    nghttp3_conn_set_max_concurrent_streams(http3_connection_, kMaxStreams);
711
6.80k
    return true;
712
6.80k
  }
713
714
  // QUIC needs unpredictable-looking connection IDs and reset tokens, but
715
  // fuzzing needs deterministic reruns. This local generator is not used for
716
  // cryptographic secrecy.
717
47.6k
  void FillRandom(std::uint8_t* destination, std::size_t length) {
718
47.6k
    std::uint64_t state = ++random_counter_ * UINT64_C(0x9e3779b97f4a7c15);
719
523k
    for (std::size_t index = 0; index < length; ++index) {
720
476k
      state ^= state >> 12U;
721
476k
      state ^= state << 25U;
722
476k
      state ^= state >> 27U;
723
476k
      destination[index] = static_cast<std::uint8_t>((state * UINT64_C(0x2545f4914f6cdd1d)) >> 56U);
724
476k
    }
725
47.6k
  }
726
727
  // The first valid Initial establishes the sole remote endpoint. Construct
728
  // all three protocol layers here because ngtcp2's crypto callbacks need a
729
  // live server-side SSL object while processing this packet.
730
6.80k
  bool InitializeConnection(const ngtcp2_pkt_hd& header, const struct sockaddr_in& remote_address) {
731
6.80k
    if (quic_connection_ != nullptr || header.type != NGTCP2_PKT_INITIAL) {
732
0
      return false;
733
0
    }
734
735
6.80k
    remote_address_ = remote_address;
736
6.80k
    ngtcp2_path_storage_init(&path_storage_, reinterpret_cast<const ngtcp2_sockaddr*>(&local_address_),
737
6.80k
                             sizeof(local_address_), reinterpret_cast<const ngtcp2_sockaddr*>(&remote_address_),
738
6.80k
                             sizeof(remote_address_), nullptr);
739
740
6.80k
    ngtcp2_callbacks callbacks = {};
741
6.80k
    callbacks.recv_client_initial = ngtcp2_crypto_recv_client_initial_cb;
742
6.80k
    callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb;
743
6.80k
    callbacks.handshake_completed = &HandshakeCompleted;
744
6.80k
    callbacks.encrypt = ngtcp2_crypto_encrypt_cb;
745
6.80k
    callbacks.decrypt = ngtcp2_crypto_decrypt_cb;
746
6.80k
    callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb;
747
6.80k
    callbacks.recv_stream_data = &ReceiveQuicStreamData;
748
6.80k
    callbacks.acked_stream_data_offset = &AckedStreamDataOffset;
749
6.80k
    callbacks.stream_open = &OpenRemoteStream;
750
6.80k
    callbacks.stream_reset = &ResetQuicStream;
751
6.80k
    callbacks.rand = &GenerateRandomBytes;
752
6.80k
    callbacks.update_key = ngtcp2_crypto_update_key_cb;
753
6.80k
    callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb;
754
6.80k
    callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb;
755
6.80k
    callbacks.stream_stop_sending = &StopSendingQuicStream;
756
6.80k
    callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb;
757
6.80k
    callbacks.recv_tx_key = &ReceiveTransmitKey;
758
6.80k
    callbacks.get_new_connection_id2 = &GetNewConnectionId;
759
6.80k
    callbacks.get_path_challenge_data2 = ngtcp2_crypto_get_path_challenge_data2_cb;
760
6.80k
    callbacks.stream_close2 = &CloseQuicStream;
761
762
6.80k
    ngtcp2_settings settings;
763
6.80k
    ngtcp2_settings_default(&settings);
764
6.80k
    settings.initial_ts = QuicNow();
765
6.80k
    settings.rand_ctx.native_handle = this;
766
6.80k
    settings.no_pmtud = 1;
767
6.80k
    settings.max_tx_udp_payload_size = packet_buffer_.size();
768
769
6.80k
    ngtcp2_transport_params parameters;
770
6.80k
    ngtcp2_transport_params_default(&parameters);
771
6.80k
    parameters.initial_max_stream_data_bidi_local = kMaxPendingPlaintextBytes;
772
6.80k
    parameters.initial_max_stream_data_bidi_remote = kMaxPendingPlaintextBytes;
773
6.80k
    parameters.initial_max_stream_data_uni = kMaxPendingPlaintextBytes;
774
6.80k
    parameters.initial_max_data = kMaxPendingPlaintextBytes * 4U;
775
6.80k
    parameters.initial_max_streams_bidi = kMaxStreams;
776
6.80k
    parameters.initial_max_streams_uni = kMaxStreams;
777
6.80k
    parameters.max_idle_timeout = 5U * NGTCP2_SECONDS;
778
6.80k
    parameters.active_connection_id_limit = 2;
779
6.80k
    parameters.original_dcid = header.dcid;
780
6.80k
    parameters.original_dcid_present = 1;
781
782
6.80k
    ngtcp2_cid server_cid = {};
783
6.80k
    server_cid.datalen = kServerConnectionIdBytes;
784
6.80k
    FillRandom(server_cid.data, server_cid.datalen);
785
6.80k
    if (ngtcp2_conn_server_new(&quic_connection_, &header.scid, &server_cid, &path_storage_.path, header.version,
786
6.80k
                               &callbacks, &settings, &parameters, nullptr, this) != 0) {
787
0
      return false;
788
0
    }
789
790
6.80k
    if (ngtcp2_crypto_ossl_ctx_new(&crypto_context_, nullptr) != 0) {
791
0
      return false;
792
0
    }
793
6.80k
    tls_connection_ = SSL_new(context_);
794
6.80k
    if (tls_connection_ == nullptr) {
795
0
      return false;
796
0
    }
797
6.80k
    ngtcp2_crypto_ossl_ctx_set_ssl(crypto_context_, tls_connection_);
798
6.80k
    if (ngtcp2_crypto_ossl_configure_server_session(tls_connection_) != 0) {
799
0
      return false;
800
0
    }
801
6.80k
    SSL_set_app_data(tls_connection_, &crypto_connection_ref_);
802
6.80k
    SSL_set_accept_state(tls_connection_);
803
6.80k
    ngtcp2_conn_set_tls_native_handle(quic_connection_, crypto_context_);
804
6.80k
    return true;
805
6.80k
  }
806
807
  // Drain a bounded datagram batch. Before connection setup, accept only a
808
  // valid QUIC Initial; afterwards, ignore packets from other UDP endpoints.
809
136k
  std::size_t ReadDatagrams() {
810
136k
    std::size_t progress = 0;
811
192k
    for (std::size_t packet = 0; packet < kMaxDatagramsPerTurn; ++packet) {
812
192k
      struct sockaddr_in remote_address = {};
813
192k
      socklen_t remote_length = sizeof(remote_address);
814
192k
      const ssize_t received = ::recvfrom(server_fd_, receive_buffer_.data(), receive_buffer_.size(), 0,
815
192k
                                          reinterpret_cast<struct sockaddr*>(&remote_address), &remote_length);
816
192k
      if (received < 0) {
817
136k
        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
818
0
          transport_failed_ = true;
819
0
        }
820
136k
        break;
821
136k
      }
822
55.8k
      if (received == 0 || remote_length != sizeof(remote_address) || remote_address.sin_family != AF_INET) {
823
0
        continue;
824
0
      }
825
55.8k
      ++progress;
826
827
55.8k
      if (quic_connection_ == nullptr) {
828
6.80k
        ngtcp2_pkt_hd header = {};
829
6.80k
        if (ngtcp2_accept(&header, receive_buffer_.data(), static_cast<std::size_t>(received)) != 0) {
830
0
          continue;
831
0
        }
832
6.80k
        if (!InitializeConnection(header, remote_address)) {
833
0
          transport_failed_ = true;
834
0
          return progress;
835
0
        }
836
49.0k
      } else if (remote_address.sin_port != remote_address_.sin_port ||
837
49.0k
                 remote_address.sin_addr.s_addr != remote_address_.sin_addr.s_addr) {
838
0
        continue;
839
0
      }
840
841
55.8k
      ngtcp2_pkt_info packet_info = {};
842
55.8k
      const int result = ngtcp2_conn_read_pkt(quic_connection_, &path_storage_.path, &packet_info,
843
55.8k
                                              receive_buffer_.data(), static_cast<std::size_t>(received), QuicNow());
844
55.8k
      if (result != 0) {
845
26
        transport_failed_ = true;
846
26
        return progress;
847
26
      }
848
55.8k
    }
849
136k
    return progress;
850
136k
  }
851
852
  // ngtcp2 timers can make handshake, loss-recovery, or close packets ready
853
  // even when curl has not sent a new UDP datagram this turn.
854
136k
  std::size_t HandleExpiry() {
855
136k
    if (quic_connection_ == nullptr) {
856
6.80k
      return 0;
857
6.80k
    }
858
129k
    const ngtcp2_tstamp now = QuicNow();
859
129k
    if (ngtcp2_conn_get_expiry2(quic_connection_) > now) {
860
120k
      return 0;
861
120k
    }
862
8.65k
    const int result = ngtcp2_conn_handle_expiry(quic_connection_, now);
863
8.65k
    if (result != 0) {
864
0
      transport_failed_ = true;
865
0
    }
866
8.65k
    return 1;
867
129k
  }
868
869
  // A nonblocking socket can defer a packet without making the peer invalid.
870
  // Hard send errors are terminal because no later turn can repair the path.
871
43.1k
  bool SendPacket(const ngtcp2_path& path, const std::uint8_t* data, std::size_t length) {
872
43.1k
    const ngtcp2_addr* destination = &path.remote;
873
43.1k
    if (destination->addr == nullptr || destination->addrlen == 0) {
874
0
      destination = &path_storage_.path.remote;
875
0
    }
876
43.1k
    const ssize_t sent = ::sendto(server_fd_, data, length, 0,
877
43.1k
                                  reinterpret_cast<const struct sockaddr*>(destination->addr), destination->addrlen);
878
43.1k
    if (sent == static_cast<ssize_t>(length)) {
879
43.1k
      return true;
880
43.1k
    }
881
0
    if (sent < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
882
0
      return false;
883
0
    }
884
0
    transport_failed_ = true;
885
0
    return false;
886
0
  }
887
888
  // Serialize all QUIC control traffic after application writes. A requested
889
  // CONNECTION_CLOSE takes priority and is emitted at most once.
890
136k
  std::size_t FlushTransportPackets() {
891
136k
    if (quic_connection_ == nullptr || transport_failed_ || close_sent_) {
892
0
      return 0;
893
0
    }
894
895
136k
    if (close_requested_) {
896
2.30k
      ngtcp2_ccerr error;
897
2.30k
      ngtcp2_ccerr_default(&error);
898
2.30k
      ngtcp2_ccerr_set_application_error(&error, close_error_code_, nullptr, 0);
899
2.30k
      ngtcp2_path_storage output_path;
900
2.30k
      ngtcp2_path_storage_zero(&output_path);
901
2.30k
      ngtcp2_pkt_info packet_info = {};
902
2.30k
      const ngtcp2_tstamp now = QuicNow();
903
2.30k
      const ngtcp2_ssize written = ngtcp2_conn_write_connection_close(
904
2.30k
          quic_connection_, &output_path.path, &packet_info, packet_buffer_.data(), packet_buffer_.size(), &error, now);
905
2.30k
      if (written < 0) {
906
0
        transport_failed_ = true;
907
0
        return 0;
908
0
      }
909
2.30k
      close_sent_ = true;
910
2.30k
      if (written == 0) {
911
0
        return 1;
912
0
      }
913
2.30k
      ngtcp2_conn_update_pkt_tx_time(quic_connection_, now);
914
2.30k
      (void)SendPacket(output_path.path, packet_buffer_.data(), static_cast<std::size_t>(written));
915
2.30k
      return 1;
916
2.30k
    }
917
918
133k
    std::size_t progress = 0;
919
151k
    for (std::size_t packet = 0; packet < kMaxWritesPerTurn; ++packet) {
920
151k
      ngtcp2_path_storage output_path;
921
151k
      ngtcp2_path_storage_zero(&output_path);
922
151k
      ngtcp2_pkt_info packet_info = {};
923
151k
      const ngtcp2_tstamp now = QuicNow();
924
151k
      const ngtcp2_ssize written = ngtcp2_conn_write_pkt(quic_connection_, &output_path.path, &packet_info,
925
151k
                                                         packet_buffer_.data(), packet_buffer_.size(), now);
926
151k
      if (written < 0) {
927
0
        transport_failed_ = true;
928
0
        break;
929
0
      }
930
151k
      if (written == 0) {
931
133k
        break;
932
133k
      }
933
17.7k
      ngtcp2_conn_update_pkt_tx_time(quic_connection_, now);
934
17.7k
      if (!SendPacket(output_path.path, packet_buffer_.data(), static_cast<std::size_t>(written))) {
935
0
        break;
936
0
      }
937
17.7k
      ++progress;
938
17.7k
    }
939
133k
    return progress;
940
136k
  }
941
942
  // HTTP/3 requires one control stream and two QPACK streams before it can
943
  // send response metadata. Their stream IDs are retained by role so script
944
  // actions can target them without exposing transport-chosen IDs.
945
129k
  std::size_t CreateServerStreams() {
946
129k
    if (server_streams_bound_ || quic_connection_ == nullptr || http3_connection_ == nullptr || http3_failed_) {
947
122k
      return 0;
948
122k
    }
949
950
6.80k
    std::size_t progress = 0;
951
6.80k
    const std::array<StreamRoleIndex, 3> roles = {kControlStream, kQpackEncoderStream, kQpackDecoderStream};
952
20.4k
    for (const StreamRoleIndex role : roles) {
953
20.4k
      if (role_stream_ids_[role] != kNoStreamId) {
954
0
        continue;
955
0
      }
956
20.4k
      const std::int64_t stream_id = OpenUnidirectionalStream();
957
20.4k
      if (stream_id == kNoStreamId) {
958
0
        return progress;
959
0
      }
960
20.4k
      role_stream_ids_[role] = stream_id;
961
20.4k
      ++progress;
962
20.4k
    }
963
964
6.80k
    if (role_stream_ids_[kControlStream] == kNoStreamId || role_stream_ids_[kQpackEncoderStream] == kNoStreamId ||
965
6.80k
        role_stream_ids_[kQpackDecoderStream] == kNoStreamId) {
966
0
      return progress;
967
0
    }
968
6.80k
    if (nghttp3_conn_bind_qpack_streams(http3_connection_, role_stream_ids_[kQpackEncoderStream],
969
6.80k
                                        role_stream_ids_[kQpackDecoderStream]) != 0 ||
970
6.80k
        nghttp3_conn_bind_control_stream(http3_connection_, role_stream_ids_[kControlStream]) != 0) {
971
0
      http3_failed_ = true;
972
0
      return progress;
973
0
    }
974
6.80k
    server_streams_bound_ = true;
975
6.80k
    return progress + 1;
976
6.80k
  }
977
978
21.4k
  std::int64_t OpenUnidirectionalStream() {
979
21.4k
    if (quic_connection_ == nullptr || streams_.size() >= kMaxStreams) {
980
0
      return kNoStreamId;
981
0
    }
982
21.4k
    std::int64_t stream_id = kNoStreamId;
983
21.4k
    if (ngtcp2_conn_open_uni_stream(quic_connection_, &stream_id, nullptr) != 0) {
984
0
      return kNoStreamId;
985
0
    }
986
987
21.4k
    StreamState state;
988
21.4k
    state.id = stream_id;
989
21.4k
    streams_.push_back(state);
990
21.4k
    return stream_id;
991
21.4k
  }
992
993
  // Advance protocol-generated output before one scenario action. This keeps
994
  // QPACK/control bytes causally ahead of raw writes and makes each turn's
995
  // work bounded even when the client is blocked.
996
122k
  std::size_t DriveApplicationOutput() {
997
122k
    std::size_t progress = 0;
998
999
122k
    if (pending_write_.active()) {
1000
3.43k
      const bool completes_action = pending_write_.completes_action;
1001
3.43k
      const WriteResult result = FlushPendingWrite(&progress);
1002
3.43k
      if (result == WriteResult::kBlocked) {
1003
131
        return progress;
1004
131
      }
1005
3.30k
      if (result == WriteResult::kFailed && transport_failed_) {
1006
0
        return progress;
1007
0
      }
1008
3.30k
      if (completes_action) {
1009
3.29k
        ++next_action_;
1010
3.29k
        return progress + 1;
1011
3.29k
      }
1012
14
      if (result == WriteResult::kFailed && waiting_for_h3_drain_) {
1013
0
        waiting_for_h3_drain_ = false;
1014
0
        if (waiting_drain_completes_action_) {
1015
0
          ++next_action_;
1016
0
        }
1017
0
        return progress + 1;
1018
0
      }
1019
14
    }
1020
1021
119k
    if (!server_streams_bound_) {
1022
0
      return progress;
1023
0
    }
1024
119k
    if (!bootstrap_drained_) {
1025
6.68k
      if (http3_failed_ || PumpNghttp3(&progress)) {
1026
6.68k
        bootstrap_drained_ = true;
1027
6.68k
        ++progress;
1028
6.68k
      }
1029
6.68k
      return progress;
1030
6.68k
    }
1031
112k
    if (waiting_for_h3_drain_) {
1032
1.22k
      if (http3_failed_ || PumpNghttp3(&progress)) {
1033
1.21k
        waiting_for_h3_drain_ = false;
1034
1.21k
        if (waiting_drain_completes_action_) {
1035
1.21k
          ++next_action_;
1036
1.21k
        }
1037
1.21k
        waiting_drain_completes_action_ = false;
1038
1.21k
        ++progress;
1039
1.21k
      }
1040
1.22k
      return progress;
1041
1.22k
    }
1042
1043
    // Reading a request can produce QPACK decoder instructions independently
1044
    // of a response action. Keep those ahead of script-controlled raw writes.
1045
111k
    if (!http3_failed_ && !PumpNghttp3(&progress)) {
1046
0
      return progress;
1047
0
    }
1048
111k
    if (transport_failed_) {
1049
0
      return progress;
1050
0
    }
1051
1052
111k
    const std::size_t action_count =
1053
111k
        scenario_ == nullptr
1054
111k
            ? 0
1055
111k
            : std::min<std::size_t>(scenario_limits::kMaxHttp3Actions, scenario_->http3_plan().actions_size());
1056
111k
    if (next_action_ >= action_count) {
1057
      // An empty H3 plan still needs one successful response after a request.
1058
103k
      if (action_count == 0 && !default_response_started_ && request_headers_received_) {
1059
0
        default_response_started_ = true;
1060
0
        if (SubmitStructuredResponse(default_response_)) {
1061
0
          waiting_for_h3_drain_ = true;
1062
0
          waiting_drain_completes_action_ = false;
1063
0
          ++progress;
1064
0
        }
1065
0
      }
1066
103k
      return progress;
1067
103k
    }
1068
1069
7.73k
    const auto& action = scenario_->http3_plan().actions(static_cast<int>(next_action_));
1070
7.73k
    if (action.has_structured_response()) {
1071
1.27k
      if (!request_headers_received_) {
1072
0
        return progress;
1073
0
      }
1074
1.27k
      if (SubmitStructuredResponse(action.structured_response())) {
1075
1.21k
        waiting_for_h3_drain_ = true;
1076
1.21k
        waiting_drain_completes_action_ = true;
1077
1.21k
      } else {
1078
61
        ++next_action_;
1079
61
      }
1080
1.27k
      return progress + 1;
1081
1.27k
    }
1082
1083
6.45k
    if (action.has_stream_write()) {
1084
1.00k
      const auto& write = action.stream_write();
1085
1.00k
      const std::int64_t stream_id = StreamForRole(write.role());
1086
1.00k
      if (stream_id == kNoStreamId) {
1087
0
        return progress;
1088
0
      }
1089
1.00k
      const std::size_t length = std::min<std::size_t>(scenario_limits::kMaxHttp3RawWriteBytes, write.data().size());
1090
1.00k
      QueueWrite(stream_id, write.data().substr(0, length), write.finish_stream(), false, true);
1091
1.00k
      return progress + 1;
1092
1.00k
    }
1093
1094
5.45k
    if (action.has_open_unidirectional_stream()) {
1095
1.02k
      const auto& open = action.open_unidirectional_stream();
1096
1.02k
      const std::int64_t stream_id = OpenUnidirectionalStream();
1097
1.02k
      if (stream_id == kNoStreamId) {
1098
0
        ++next_action_;
1099
0
        return progress + 1;
1100
0
      }
1101
1.02k
      const std::size_t length = std::min<std::size_t>(scenario_limits::kMaxHttp3RawWriteBytes, open.data().size());
1102
1.02k
      QueueWrite(stream_id, open.data().substr(0, length), open.finish_stream(), false, true);
1103
1.02k
      return progress + 1;
1104
1.02k
    }
1105
1106
4.43k
    if (action.has_stream_reset()) {
1107
855
      const auto& reset = action.stream_reset();
1108
855
      const std::int64_t stream_id = StreamForRole(reset.role());
1109
855
      if (stream_id == kNoStreamId) {
1110
0
        return progress;
1111
0
      }
1112
855
      (void)ngtcp2_conn_shutdown_stream_write(quic_connection_, 0, stream_id,
1113
855
                                              reset.application_error_code() & scenario_limits::kMaxQuicVarint);
1114
855
      ++next_action_;
1115
855
      return progress + 1;
1116
855
    }
1117
1118
3.57k
    if (action.has_goaway()) {
1119
1.27k
      const std::int64_t control_stream_id = StreamForRole(curl::fuzzer::proto::HTTP3_STREAM_CONTROL);
1120
1.27k
      if (control_stream_id == kNoStreamId) {
1121
0
        return progress;
1122
0
      }
1123
      // HTTP/3 GOAWAY is a raw control-stream frame so its identifier remains
1124
      // directly mutation-controlled instead of being selected by nghttp3.
1125
1.27k
      std::string payload;
1126
1.27k
      AppendQuicVarint(&payload, action.goaway().id());
1127
1.27k
      std::string frame;
1128
1.27k
      AppendQuicVarint(&frame, 0x07U);
1129
1.27k
      AppendQuicVarint(&frame, payload.size());
1130
1.27k
      frame.append(payload);
1131
1.27k
      QueueWrite(control_stream_id, std::move(frame), false, false, true);
1132
1.27k
      return progress + 1;
1133
1.27k
    }
1134
1135
2.30k
    if (action.has_connection_close()) {
1136
2.30k
      close_error_code_ = action.connection_close().application_error_code() & scenario_limits::kMaxQuicVarint;
1137
2.30k
      close_requested_ = true;
1138
2.30k
      ++next_action_;
1139
2.30k
      return progress + 1;
1140
2.30k
    }
1141
1142
0
    ++next_action_;
1143
0
    return progress + 1;
1144
2.30k
  }
1145
1146
  // Retain script and nghttp3 bytes until ngtcp2 accepts their full prefix.
1147
  // Only one pending write exists, preserving ordering between action types.
1148
  void QueueWrite(std::int64_t stream_id, std::string bytes, bool finish_stream, bool nghttp3_managed,
1149
24.6k
                  bool completes_action) {
1150
24.6k
    retained_writes_.push_back(std::make_unique<std::string>(std::move(bytes)));
1151
24.6k
    pending_write_.stream_id = stream_id;
1152
24.6k
    pending_write_.bytes = retained_writes_.back().get();
1153
24.6k
    pending_write_.offset = 0;
1154
24.6k
    pending_write_.finish_stream = finish_stream;
1155
24.6k
    pending_write_.nghttp3_managed = nghttp3_managed;
1156
24.6k
    pending_write_.completes_action = completes_action;
1157
24.6k
  }
1158
1159
  // Packetize one prefix through ngtcp2. For nghttp3-managed output, record
1160
  // the exact submitted offsets so its acknowledgement accounting can exclude
1161
  // interleaved raw writes on the same QUIC stream.
1162
24.8k
  WriteResult FlushPendingWrite(std::size_t* progress) {
1163
24.8k
    if (!pending_write_.active()) {
1164
0
      return WriteResult::kComplete;
1165
0
    }
1166
1167
24.8k
    if (pending_write_.bytes == nullptr || pending_write_.offset > pending_write_.bytes->size()) {
1168
0
      pending_write_.Clear();
1169
0
      return WriteResult::kFailed;
1170
0
    }
1171
1172
24.8k
    const std::size_t remaining = pending_write_.bytes->size() - pending_write_.offset;
1173
24.8k
    if (remaining == 0 && !pending_write_.finish_stream) {
1174
1.77k
      pending_write_.Clear();
1175
1.77k
      ++*progress;
1176
1.77k
      return WriteResult::kComplete;
1177
1.77k
    }
1178
1179
23.0k
    ngtcp2_vec vector = {};
1180
23.0k
    vector.base =
1181
23.0k
        reinterpret_cast<std::uint8_t*>(const_cast<char*>(pending_write_.bytes->data() + pending_write_.offset));
1182
23.0k
    vector.len = remaining;
1183
23.0k
    const std::size_t vector_count = remaining == 0 ? 0 : 1;
1184
23.0k
    const std::uint32_t flags =
1185
23.0k
        pending_write_.finish_stream ? NGTCP2_WRITE_STREAM_FLAG_FIN : NGTCP2_WRITE_STREAM_FLAG_NONE;
1186
23.0k
    ngtcp2_path_storage output_path;
1187
23.0k
    ngtcp2_path_storage_zero(&output_path);
1188
23.0k
    ngtcp2_pkt_info packet_info = {};
1189
23.0k
    ngtcp2_ssize submitted = -1;
1190
23.0k
    const ngtcp2_tstamp now = QuicNow();
1191
23.0k
    const ngtcp2_ssize packet_length = ngtcp2_conn_writev_stream(
1192
23.0k
        quic_connection_, &output_path.path, &packet_info, packet_buffer_.data(), packet_buffer_.size(), &submitted,
1193
23.0k
        flags, pending_write_.stream_id, vector_count == 0 ? nullptr : &vector, vector_count, now);
1194
23.0k
    if (packet_length == NGTCP2_ERR_STREAM_DATA_BLOCKED) {
1195
0
      return WriteResult::kBlocked;
1196
0
    }
1197
23.0k
    if (packet_length == NGTCP2_ERR_STREAM_SHUT_WR || packet_length == NGTCP2_ERR_STREAM_NOT_FOUND) {
1198
46
      pending_write_.Clear();
1199
46
      return WriteResult::kFailed;
1200
46
    }
1201
23.0k
    if (packet_length < 0) {
1202
0
      transport_failed_ = true;
1203
0
      pending_write_.Clear();
1204
0
      return WriteResult::kFailed;
1205
0
    }
1206
23.0k
    if (packet_length == 0) {
1207
0
      return WriteResult::kBlocked;
1208
0
    }
1209
23.0k
    ngtcp2_conn_update_pkt_tx_time(quic_connection_, now);
1210
23.0k
    (void)SendPacket(output_path.path, packet_buffer_.data(), static_cast<std::size_t>(packet_length));
1211
23.0k
    ++*progress;
1212
23.0k
    if (transport_failed_) {
1213
0
      pending_write_.Clear();
1214
0
      return WriteResult::kFailed;
1215
0
    }
1216
1217
23.0k
    if (submitted >= 0) {
1218
23.0k
      StreamState* stream = FindStream(pending_write_.stream_id);
1219
23.0k
      if (stream == nullptr || static_cast<std::uint64_t>(submitted) > UINT64_MAX - stream->write_offset) {
1220
0
        transport_failed_ = true;
1221
0
        pending_write_.Clear();
1222
0
        return WriteResult::kFailed;
1223
0
      }
1224
23.0k
      const std::uint64_t write_begin = stream->write_offset;
1225
23.0k
      stream->write_offset += static_cast<std::uint64_t>(submitted);
1226
23.0k
      if (pending_write_.nghttp3_managed) {
1227
21.4k
        if (nghttp3_conn_add_write_offset(http3_connection_, pending_write_.stream_id,
1228
21.4k
                                          static_cast<std::uint64_t>(submitted)) != 0) {
1229
0
          http3_failed_ = true;
1230
0
          pending_write_.Clear();
1231
0
          return WriteResult::kFailed;
1232
0
        }
1233
21.4k
        if (submitted != 0) {
1234
21.4k
          stream->managed_write_ranges.push_back({write_begin, stream->write_offset});
1235
21.4k
        } else if (pending_write_.finish_stream && pending_write_.offset == pending_write_.bytes->size()) {
1236
0
          stream->managed_fin_offsets.push_back(write_begin);
1237
0
        }
1238
21.4k
      }
1239
23.0k
      pending_write_.offset += static_cast<std::size_t>(submitted);
1240
23.0k
    }
1241
23.0k
    if (submitted < 0 || pending_write_.offset != pending_write_.bytes->size()) {
1242
145
      return WriteResult::kBlocked;
1243
145
    }
1244
22.8k
    pending_write_.Clear();
1245
22.8k
    return WriteResult::kComplete;
1246
23.0k
  }
1247
1248
  // Let nghttp3 serialize response/control bytes, then feed each generated
1249
  // vector through the same QUIC write path as raw scenario data. Stop when a
1250
  // raw action is pending so it cannot be reordered behind generated output.
1251
119k
  bool PumpNghttp3(std::size_t* progress) {
1252
119k
    if (http3_connection_ == nullptr || http3_failed_) {
1253
0
      return true;
1254
0
    }
1255
1256
140k
    for (std::size_t operation = 0; operation < kMaxWritesPerTurn; ++operation) {
1257
140k
      if (pending_write_.active()) {
1258
21.3k
        if (!pending_write_.nghttp3_managed) {
1259
0
          return false;
1260
0
        }
1261
21.3k
        const WriteResult result = FlushPendingWrite(progress);
1262
21.3k
        if (result == WriteResult::kBlocked) {
1263
14
          return false;
1264
14
        }
1265
21.3k
        if (result == WriteResult::kFailed) {
1266
0
          http3_failed_ = true;
1267
0
          return true;
1268
0
        }
1269
21.3k
      }
1270
1271
140k
      std::array<nghttp3_vec, 16> vectors;
1272
140k
      std::int64_t stream_id = -1;
1273
140k
      int finish_stream = 0;
1274
140k
      const nghttp3_ssize vector_count =
1275
140k
          nghttp3_conn_writev_stream(http3_connection_, &stream_id, &finish_stream, vectors.data(), vectors.size());
1276
140k
      if (vector_count < 0) {
1277
0
        http3_failed_ = true;
1278
0
        return true;
1279
0
      }
1280
140k
      if (vector_count == 0 && stream_id == -1) {
1281
119k
        return true;
1282
119k
      }
1283
1284
21.3k
      StreamState* stream = FindStream(stream_id);
1285
21.3k
      if (stream == nullptr) {
1286
0
        http3_failed_ = true;
1287
0
        return true;
1288
0
      }
1289
1290
21.3k
      std::string bytes;
1291
21.3k
      if (vector_count > 0) {
1292
21.3k
        std::size_t total = 0;
1293
43.1k
        for (nghttp3_ssize index = 0; index < vector_count; ++index) {
1294
21.7k
          if (vectors[static_cast<std::size_t>(index)].len > kMaxPendingPlaintextBytes - total) {
1295
0
            http3_failed_ = true;
1296
0
            return true;
1297
0
          }
1298
21.7k
          total += vectors[static_cast<std::size_t>(index)].len;
1299
21.7k
        }
1300
21.3k
        bytes.reserve(total);
1301
43.1k
        for (nghttp3_ssize index = 0; index < vector_count; ++index) {
1302
21.7k
          const nghttp3_vec& vector = vectors[static_cast<std::size_t>(index)];
1303
21.7k
          bytes.append(reinterpret_cast<const char*>(vector.base), vector.len);
1304
21.7k
        }
1305
21.3k
      }
1306
1307
21.3k
      QueueWrite(stream->id, std::move(bytes), finish_stream != 0, true, false);
1308
21.3k
    }
1309
0
    return false;
1310
119k
  }
1311
1312
  // Convert a bounded schema response into nghttp3-owned fields and pull-based
1313
  // body state. Interim responses do not consume the single final response.
1314
1.27k
  bool SubmitStructuredResponse(const curl::fuzzer::proto::Http3Response& response) {
1315
1.27k
    if (http3_connection_ == nullptr || http3_failed_ || role_stream_ids_[kResponseStream] == kNoStreamId) {
1316
0
      return false;
1317
0
    }
1318
1319
1.27k
    std::uint32_t status_code = response.status_code();
1320
1.27k
    if (status_code < 100U || status_code > 599U) {
1321
0
      status_code = 100U + status_code % 500U;
1322
0
    }
1323
1.27k
    std::vector<std::pair<std::string, std::string>> fields;
1324
1.27k
    fields.reserve(1 + std::min<std::size_t>(scenario_limits::kMaxHttp3Headers, response.response_headers_size()));
1325
1.27k
    fields.emplace_back(":status", std::to_string(status_code));
1326
1.27k
    AppendHeaders(&fields, response.response_headers(), scenario_limits::kMaxHttp3Headers);
1327
1.27k
    const std::vector<nghttp3_nv> name_values = MakeNameValues(fields);
1328
1.27k
    const std::int64_t response_stream_id = static_cast<std::int64_t>(role_stream_ids_[kResponseStream]);
1329
1330
1.27k
    if (status_code < 200U) {
1331
372
      return nghttp3_conn_submit_info(http3_connection_, response_stream_id, name_values.data(), name_values.size()) ==
1332
372
             0;
1333
372
    }
1334
901
    if (final_response_submitted_) {
1335
61
      return false;
1336
61
    }
1337
1338
840
    response_body_.chunks.clear();
1339
840
    response_body_.next_chunk = 0;
1340
840
    response_body_.finish_stream = response.finish_stream();
1341
840
    response_body_.has_trailers = response.response_trailers_size() != 0;
1342
840
    std::size_t body_bytes = 0;
1343
840
    const std::size_t body_chunks =
1344
840
        std::min<std::size_t>(scenario_limits::kMaxHttp3BodyChunks, response.body_chunks_size());
1345
840
    response_body_.chunks.reserve(body_chunks);
1346
1.06k
    for (std::size_t index = 0; index < body_chunks && body_bytes < scenario_limits::kMaxHttp3BodyBytes; ++index) {
1347
228
      const std::string& source = response.body_chunks(static_cast<int>(index));
1348
228
      const std::size_t length = std::min(source.size(), scenario_limits::kMaxHttp3BodyBytes - body_bytes);
1349
228
      response_body_.chunks.emplace_back(source.data(), length);
1350
228
      body_bytes += length;
1351
228
    }
1352
1353
840
    nghttp3_data_reader reader = {};
1354
840
    reader.read_data = &ReadResponseBody;
1355
840
    if (nghttp3_conn_submit_response(http3_connection_, response_stream_id, name_values.data(), name_values.size(),
1356
840
                                     &reader) != 0) {
1357
0
      return false;
1358
0
    }
1359
840
    final_response_submitted_ = true;
1360
1361
840
    if (response_body_.has_trailers) {
1362
142
      std::vector<std::pair<std::string, std::string>> trailers;
1363
142
      trailers.reserve(std::min<std::size_t>(scenario_limits::kMaxHttp3Trailers, response.response_trailers_size()));
1364
142
      AppendHeaders(&trailers, response.response_trailers(), scenario_limits::kMaxHttp3Trailers);
1365
142
      const std::vector<nghttp3_nv> trailer_values = MakeNameValues(trailers);
1366
142
      if (nghttp3_conn_submit_trailers(http3_connection_, response_stream_id, trailer_values.data(),
1367
142
                                       trailer_values.size()) != 0) {
1368
0
        http3_failed_ = true;
1369
0
      }
1370
142
    }
1371
840
    return true;
1372
840
  }
1373
1374
  template <typename RepeatedHeaders>
1375
  static void AppendHeaders(std::vector<std::pair<std::string, std::string>>* destination,
1376
1.41k
                            const RepeatedHeaders& source, std::size_t count_limit) {
1377
1.41k
    std::size_t remaining = scenario_limits::kMaxHttp3HeaderBytes;
1378
1.41k
    const std::size_t count = std::min<std::size_t>(count_limit, source.size());
1379
2.91k
    for (std::size_t index = 0; index < count && remaining != 0; ++index) {
1380
1.50k
      std::string name = NormalizeHeaderName(source.Get(static_cast<int>(index)).name());
1381
1.50k
      std::string value = NormalizeHeaderValue(source.Get(static_cast<int>(index)).value());
1382
1.50k
      if (name.size() > remaining) {
1383
0
        name.resize(remaining);
1384
0
        value.clear();
1385
1.50k
      } else if (value.size() > remaining - name.size()) {
1386
0
        value.resize(remaining - name.size());
1387
0
      }
1388
1.50k
      remaining -= name.size() + value.size();
1389
1.50k
      destination->emplace_back(std::move(name), std::move(value));
1390
1.50k
    }
1391
1.41k
  }
1392
1393
1.41k
  static std::vector<nghttp3_nv> MakeNameValues(const std::vector<std::pair<std::string, std::string>>& fields) {
1394
1.41k
    std::vector<nghttp3_nv> name_values;
1395
1.41k
    name_values.reserve(fields.size());
1396
2.77k
    for (const auto& field : fields) {
1397
2.77k
      nghttp3_nv name_value = {};
1398
2.77k
      name_value.name = reinterpret_cast<std::uint8_t*>(const_cast<char*>(field.first.data()));
1399
2.77k
      name_value.value = reinterpret_cast<std::uint8_t*>(const_cast<char*>(field.second.data()));
1400
2.77k
      name_value.namelen = field.first.size();
1401
2.77k
      name_value.valuelen = field.second.size();
1402
2.77k
      name_value.flags = NGHTTP3_NV_FLAG_NONE;
1403
2.77k
      name_values.push_back(name_value);
1404
2.77k
    }
1405
1.41k
    return name_values;
1406
1.41k
  }
1407
1408
65.3k
  StreamState* FindStream(std::int64_t stream_id) {
1409
148k
    for (StreamState& stream : streams_) {
1410
148k
      if (stream.id == stream_id) {
1411
65.3k
        return &stream;
1412
65.3k
      }
1413
148k
    }
1414
0
    return nullptr;
1415
65.3k
  }
1416
1417
3.12k
  std::int64_t StreamForRole(curl::fuzzer::proto::Http3StreamRole role) {
1418
3.12k
    const int role_value = static_cast<int>(role);
1419
3.12k
    if (role_value < 0 || role_value >= static_cast<int>(kStreamRoleCount)) {
1420
0
      return kNoStreamId;
1421
0
    }
1422
3.12k
    return role_stream_ids_[static_cast<std::size_t>(role_value)];
1423
3.12k
  }
1424
1425
  // nghttp3 callbacks replenish QUIC flow-control credit only as application
1426
  // bytes are consumed, then discover the first request stream for responses.
1427
  static int ReceiveRequestData(nghttp3_conn* /*connection*/, std::int64_t stream_id, const std::uint8_t* /*data*/,
1428
1.66k
                                std::size_t data_length, void* user_data, void* /*stream_user_data*/) {
1429
1.66k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
1430
1.66k
    if (self != nullptr && self->quic_connection_ != nullptr) {
1431
1.66k
      ngtcp2_conn_extend_max_stream_offset(self->quic_connection_, stream_id, data_length);
1432
1.66k
      ngtcp2_conn_extend_max_offset(self->quic_connection_, data_length);
1433
1.66k
    }
1434
1.66k
    return 0;
1435
1.66k
  }
1436
1437
  static int DeferredConsumeRequestData(nghttp3_conn* /*connection*/, std::int64_t stream_id, std::size_t consumed,
1438
0
                                        void* user_data, void* /*stream_user_data*/) {
1439
0
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
1440
0
    if (self != nullptr && self->quic_connection_ != nullptr) {
1441
0
      ngtcp2_conn_extend_max_stream_offset(self->quic_connection_, stream_id, consumed);
1442
0
      ngtcp2_conn_extend_max_offset(self->quic_connection_, consumed);
1443
0
    }
1444
0
    return 0;
1445
0
  }
1446
1447
  static int EndRequestHeaders(nghttp3_conn* /*connection*/, std::int64_t stream_id, int /*finish_stream*/,
1448
6.67k
                               void* user_data, void* /*stream_user_data*/) {
1449
6.67k
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
1450
6.67k
    if (self != nullptr) {
1451
6.67k
      if (self->role_stream_ids_[kResponseStream] == kNoStreamId) {
1452
0
        self->role_stream_ids_[kResponseStream] = stream_id;
1453
0
        if (self->FindStream(stream_id) == nullptr && self->streams_.size() < kMaxStreams) {
1454
0
          StreamState state;
1455
0
          state.id = stream_id;
1456
0
          self->streams_.push_back(state);
1457
0
        }
1458
0
      }
1459
6.67k
      if (self->role_stream_ids_[kResponseStream] == stream_id) {
1460
6.67k
        self->request_headers_received_ = true;
1461
6.67k
      }
1462
6.67k
    }
1463
6.67k
    return 0;
1464
6.67k
  }
1465
1466
  static int EndRequestStream(nghttp3_conn* /*connection*/, std::int64_t /*stream_id*/, void* /*user_data*/,
1467
6.62k
                              void* /*stream_user_data*/) {
1468
6.62k
    return 0;
1469
6.62k
  }
1470
1471
  // nghttp3 pulls one schema body chunk per call. EOF may keep the stream open
1472
  // for trailers or a deliberate no-FIN response.
1473
  static nghttp3_ssize ReadResponseBody(nghttp3_conn* /*connection*/, std::int64_t /*stream_id*/, nghttp3_vec* vectors,
1474
                                        std::size_t vector_count, std::uint32_t* flags, void* user_data,
1475
958
                                        void* /*stream_user_data*/) {
1476
958
    auto* self = static_cast<Http3MockServerImpl*>(user_data);
1477
958
    if (self == nullptr || vectors == nullptr || vector_count == 0 || flags == nullptr) {
1478
0
      return NGHTTP3_ERR_CALLBACK_FAILURE;
1479
0
    }
1480
1481
992
    while (self->response_body_.next_chunk < self->response_body_.chunks.size() &&
1482
228
           self->response_body_.chunks[self->response_body_.next_chunk].empty()) {
1483
34
      ++self->response_body_.next_chunk;
1484
34
    }
1485
958
    if (self->response_body_.next_chunk == self->response_body_.chunks.size()) {
1486
764
      *flags = NGHTTP3_DATA_FLAG_EOF;
1487
764
      if (!self->response_body_.finish_stream && !self->response_body_.has_trailers) {
1488
93
        *flags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
1489
93
      }
1490
764
      return 0;
1491
764
    }
1492
1493
194
    const std::string& chunk = self->response_body_.chunks[self->response_body_.next_chunk++];
1494
194
    vectors[0].base = reinterpret_cast<std::uint8_t*>(const_cast<char*>(chunk.data()));
1495
194
    vectors[0].len = chunk.size();
1496
194
    *flags = NGHTTP3_DATA_FLAG_NONE;
1497
194
    if (self->response_body_.next_chunk == self->response_body_.chunks.size()) {
1498
76
      *flags |= NGHTTP3_DATA_FLAG_EOF;
1499
76
      if (!self->response_body_.finish_stream && !self->response_body_.has_trailers) {
1500
34
        *flags |= NGHTTP3_DATA_FLAG_NO_END_STREAM;
1501
34
      }
1502
76
    }
1503
194
    return 1;
1504
958
  }
1505
1506
  // Layer-owned transport state. The destruction order in ResetPeer mirrors
1507
  // these dependencies: HTTP/3, QUIC, TLS adapter, then TLS context/socket.
1508
  SSL_CTX* context_ = nullptr;
1509
  ngtcp2_conn* quic_connection_ = nullptr;
1510
  SSL* tls_connection_ = nullptr;
1511
  ngtcp2_crypto_ossl_ctx* crypto_context_ = nullptr;
1512
  ngtcp2_crypto_conn_ref crypto_connection_ref_;
1513
  nghttp3_conn* http3_connection_ = nullptr;
1514
  const curl::fuzzer::proto::Scenario* scenario_ = nullptr;
1515
  std::vector<StreamState> streams_;
1516
  std::vector<std::unique_ptr<std::string>> retained_writes_;
1517
  std::array<std::int64_t, kStreamRoleCount> role_stream_ids_;
1518
  PendingWrite pending_write_;
1519
  ResponseBodyState response_body_;
1520
  curl::fuzzer::proto::Http3Response default_response_;
1521
  ngtcp2_path_storage path_storage_ = {};
1522
  struct sockaddr_in local_address_ = {};
1523
  struct sockaddr_in remote_address_ = {};
1524
  std::array<std::uint8_t, kReceiveBufferBytes> receive_buffer_ = {};
1525
  std::array<std::uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> packet_buffer_ = {};
1526
  std::uint64_t random_counter_ = 0;
1527
  std::uint64_t close_error_code_ = 0;
1528
  std::size_t next_action_ = 0;
1529
  int server_fd_ = -1;
1530
  std::uint16_t server_port_ = 0;
1531
  bool socket_opened_ = false;
1532
  bool handshake_complete_ = false;
1533
  bool request_headers_received_ = false;
1534
  bool server_streams_bound_ = false;
1535
  bool bootstrap_drained_ = false;
1536
  bool waiting_for_h3_drain_ = false;
1537
  bool waiting_drain_completes_action_ = false;
1538
  bool final_response_submitted_ = false;
1539
  bool default_response_started_ = false;
1540
  bool http3_failed_ = false;
1541
  bool transport_failed_ = false;
1542
  bool close_requested_ = false;
1543
  bool close_sent_ = false;
1544
};
1545
1546
0
Http3MockServer::Http3MockServer() : Http3MockServer(curl::fuzzer::proto::TLS_CERTIFICATE_CHAIN_DEFAULT_EC) {}
1547
1548
Http3MockServer::Http3MockServer(curl::fuzzer::proto::TlsCertificateChainProfile certificate_chain)
1549
7.04k
    : impl_(new Http3MockServerImpl(certificate_chain)) {}
1550
1551
7.04k
Http3MockServer::~Http3MockServer() = default;
1552
1553
7.04k
void Http3MockServer::Install(CURL* easy) {
1554
  // Curl trusts the checked-in server certificate directly and must use HTTP/3
1555
  // rather than silently falling back to an HTTP/1 or HTTP/2 code path.
1556
7.04k
  MockServerBase::Install(easy);
1557
7.04k
  struct curl_blob trust_anchor = {const_cast<char*>(tls_test_credentials::kCertificatePem),
1558
7.04k
                                   sizeof(tls_test_credentials::kCertificatePem) - 1, CURL_BLOB_NOCOPY};
1559
7.04k
  (void)curl_easy_setopt(easy, CURLOPT_CAINFO_BLOB, &trust_anchor);
1560
7.04k
  (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
1561
7.04k
  (void)curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
1562
7.04k
  (void)curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
1563
7.04k
}
1564
1565
0
bool Http3MockServer::handshake_complete() const { return impl_ != nullptr && impl_->handshake_complete(); }
1566
1567
0
bool Http3MockServer::request_headers_received() const { return impl_ != nullptr && impl_->request_headers_received(); }
1568
1569
0
std::size_t Http3MockServer::executed_action_count() const {
1570
0
  return impl_ == nullptr ? 0 : impl_->executed_action_count();
1571
0
}
1572
1573
0
std::uint16_t Http3MockServer::server_port() const { return impl_ == nullptr ? 0 : impl_->server_port(); }
1574
1575
6.80k
curl_socket_t Http3MockServer::HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* address) {
1576
6.80k
  return impl_ == nullptr ? CURL_SOCKET_BAD : impl_->OpenSocket(purpose, address);
1577
6.80k
}
1578
1579
SocketSetupDisposition Http3MockServer::GetSocketSetupDisposition(curl_socket_t /*curlfd*/,
1580
6.80k
                                                                  curlsocktype /*purpose*/) const {
1581
6.80k
  return SocketSetupDisposition::kNeedsSetup;
1582
6.80k
}
1583
1584
7.04k
void Http3MockServer::RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
1585
7.04k
  (void)easy;
1586
7.04k
  if (impl_ == nullptr) {
1587
0
    return;
1588
0
  }
1589
7.04k
  impl_->BeginScenario(scenario);
1590
1591
7.04k
  int still_running = 1;
1592
7.04k
  int idle_iterations = 0;
1593
7.04k
  if (MultiSocketDriver* driver = multi_socket_driver(); driver != nullptr) {
1594
    // Alternate curl readiness processing with one bounded peer turn. Neither
1595
    // side blocks, so an idle limit prevents a malformed case spinning forever.
1596
7.04k
    CURLMcode result = driver->Start(&still_running);
1597
141k
    for (int iteration = 0; result == CURLM_OK && iteration < kMaxDriveIterations && still_running != 0; ++iteration) {
1598
137k
      std::size_t progress = impl_->DrivePeerTurn();
1599
137k
      const MultiSocketDriver::DriveResult drive_result = driver->DriveReady(&still_running);
1600
137k
      result = drive_result.code;
1601
137k
      progress += drive_result.made_progress ? 1U : 0U;
1602
137k
      if (progress != 0) {
1603
35.1k
        idle_iterations = 0;
1604
101k
      } else if (++idle_iterations >= kHttp3IdleIterations) {
1605
2.81k
        break;
1606
2.81k
      }
1607
137k
    }
1608
7.04k
    return;
1609
7.04k
  }
1610
1611
  // The fallback exercises curl's public multi-perform API while preserving
1612
  // the same peer-first/idle-limit behavior as the socket-action driver.
1613
0
  for (int iteration = 0; iteration < kMaxDriveIterations && still_running != 0; ++iteration) {
1614
0
    const int running_before = still_running;
1615
0
    const CURLMcode result = curl_multi_perform(multi, &still_running);
1616
0
    if (result != CURLM_OK) {
1617
0
      break;
1618
0
    }
1619
1620
0
    std::size_t progress = still_running == running_before ? 0U : 1U;
1621
0
    progress += impl_->DrivePeerTurn();
1622
0
    if (progress != 0) {
1623
0
      idle_iterations = 0;
1624
0
    } else if (++idle_iterations >= kHttp3IdleIterations) {
1625
0
      break;
1626
0
    }
1627
0
  }
1628
0
}
1629
1630
}  // namespace proto_fuzzer