12#include <curl/multi.h>
20namespace proto_fuzzer {
25constexpr std::size_t kMaxInspectedPushHeaders = 16;
27constexpr std::size_t kMaxAcceptedPushes = 1;
39 accept_push_ = accept_push;
40 accepted_push_count_ = 0;
41 pushed_body_bytes_ = 0;
42 (void)curl_multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
43 (void)curl_multi_setopt(multi, CURLMOPT_PUSHFUNCTION, &H2Runtime::PushCallback);
44 (void)curl_multi_setopt(multi, CURLMOPT_PUSHDATA,
this);
51 if (connection !=
nullptr) {
60void H2Runtime::ResetPlan(
const curl::fuzzer::proto::Http2Plan& plan, std::string scheme, std::string authority) {
61 plan_driver_.Reset(plan, std::move(scheme), std::move(authority));
62 stream_weight_after_headers_ = plan.stream_weight_after_headers();
63 post_headers_transition_applied_ =
false;
64 stream_weight_setopt_result_ = CURLE_FAILED_INIT;
65 stream_weight_egress_result_ = CURLE_FAILED_INIT;
73 if (stream_weight_after_headers_ == 0 || post_headers_transition_applied_ ||
74 plan_driver_.observed_request_count() == 0) {
77 post_headers_transition_applied_ =
true;
78 stream_weight_setopt_result_ =
79 curl_easy_setopt(easy, CURLOPT_STREAM_WEIGHT,
static_cast<long>(stream_weight_after_headers_));
84 stream_weight_egress_result_ = curl_easy_pause(easy, CURLPAUSE_CONT);
96 const std::function<
void()>& after_perform) {
97 int still_running = 1;
98 int idle_iterations = 0;
99 int drive_iterations = 0;
100 while (still_running && idle_iterations < max_idle_iterations && drive_iterations++ < max_drive_iterations) {
101 const int running_before = still_running;
102 if (curl_multi_perform(multi, &still_running) != CURLM_OK) {
106 if (!still_running) {
110 bool made_progress = still_running != running_before;
112 made_progress = connection->DrainIncoming() != 0 || made_progress;
114 if (plan_driver_.NextChunk(&chunk)) {
115 made_progress =
true;
116 if (!chunk.empty()) {
117 (void)connection->WriteAll(
reinterpret_cast<const unsigned char*
>(chunk.data()), chunk.size());
119 made_progress = connection->DrainIncoming() != 0 || made_progress;
134 upkeep_result_ = curl_easy_upkeep(easy);
135 if (connection !=
nullptr) {
139#if defined(PROTO_FUZZER_OBSERVE_STATE)
151 plan_driver_.has_pending_work()};
192int H2Runtime::PushCallback(CURL* , CURL* pushed, std::size_t header_count,
struct curl_pushheaders* headers,
194 auto* self =
static_cast<H2Runtime*
>(userdata);
195 if (self ==
nullptr) {
196 return CURL_PUSH_DENY;
199 ++self->push_callback_count_;
200 self->push_header_count_ += header_count;
201 const std::size_t inspected = std::min(header_count, kMaxInspectedPushHeaders);
202 for (std::size_t index = 0; index < inspected; ++index) {
203 (void)curl_pushheader_bynum(headers, index);
205 self->saw_push_path_ = curl_pushheader_byname(headers,
":path") !=
nullptr;
206 (void)curl_pushheader_byname(headers,
"x-fuzzer-missing");
208 if (!self->accept_push_ || self->accepted_push_count_ >= kMaxAcceptedPushes) {
209 return CURL_PUSH_DENY;
212 if (curl_easy_setopt(pushed, CURLOPT_WRITEFUNCTION, &H2Runtime::PushedWriteCallback) != CURLE_OK ||
213 curl_easy_setopt(pushed, CURLOPT_WRITEDATA, self) != CURLE_OK) {
214 return CURL_PUSH_DENY;
216 ++self->accepted_push_count_;
220std::size_t H2Runtime::PushedWriteCallback(
char* , std::size_t size, std::size_t nmemb,
void* userdata) {
221 auto* self =
static_cast<H2Runtime*
>(userdata);
222 const std::size_t maximum = std::numeric_limits<std::size_t>::max();
223 if (self ==
nullptr || (size != 0 && nmemb > maximum / size)) {
226 const std::size_t bytes = size * nmemb;
227 self->pushed_body_bytes_ += std::min(bytes, maximum - self->pushed_body_bytes_);
Protocol state shared by TLS and plaintext HTTP/2 origin peers.
bool post_headers_transition_applied() const
H2Runtime()
Construct detached protocol state without observing a connection.
std::size_t observed_request_count() const
void FinishTransfer(CURL *easy, MockConnection *connection)
Probe upkeep while curl's connection cache and the peer are still live.
std::size_t push_callback_count() const
void ResetPlan(const curl::fuzzer::proto::Http2Plan &plan, std::string scheme, std::string authority="tls.test")
Borrow a structured plan for the ensuing synchronous drive.
std::size_t observed_client_priority_count() const
void AttachConnection(MockConnection *connection)
Observe application bytes after the carrier has decoded its transport.
std::size_t accepted_push_count() const
CURLcode stream_weight_setopt_result() const
std::size_t observed_client_settings_count() const
void ApplyPostHeadersTransition(CURL *easy)
Apply the plan's one state transition only after a real request stream has been observed.
CURLcode stream_weight_egress_result() const
CURLcode upkeep_result() const
void DrivePlan(CURLM *multi, MockServer &server, int max_idle_iterations, int max_drive_iterations, const std::function< void()> &after_perform)
Alternate curl and one bounded H2 plan boundary.
std::size_t push_header_count() const
void PrepareMulti(CURLM *multi, bool accept_push)
Install the bounded push policy before curl emits its initial SETTINGS.
std::size_t pushed_body_bytes() const
bool saw_push_path() const
Owns one half of a socketpair used to feed canned responses to libcurl.
virtual std::size_t DrainIncoming()
Advance incoming transport work according to the configured per-call limit.
void SetIncomingDataObserver(IncomingDataObserver *observer)
Register a non-owning sink for bytes consumed from curl.
MockConnection * connection()
Orchestrates a bounded sequence of mock HTTP exchanges: installs socket callbacks on an easy handle,...
Shared HTTP/2 plan, push, and upkeep behavior for origin peers.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...