12#define CURL_ALLOW_OLD_MULTI_SOCKET
14#include <curl/multi.h>
31namespace proto_fuzzer {
37 TransferSession session;
38 bool attached =
false;
41constexpr int kMaxDriveIterations = 512;
42constexpr int kMaxIdleIterations = 8;
43constexpr curl_off_t kStateProbeReceiveRate = 1;
44constexpr long kStateProbeTimeoutMs = 20;
47 void operator()(FILE* file)
const {
48 if (file !=
nullptr) {
54using FilePtr = std::unique_ptr<FILE, FileCloser>;
59bool ApplyStateProbeOptions(CURL* easy, curl::fuzzer::proto::MultiStateProbeProfile profile, FILE* progress_sink) {
60 if (profile != curl::fuzzer::proto::MULTI_STATE_PROBE_PROGRESS_RATE_LIMIT || progress_sink ==
nullptr) {
63 return curl_easy_setopt(easy, CURLOPT_STDERR, progress_sink) == CURLE_OK &&
64 curl_easy_setopt(easy, CURLOPT_TIMEOUT_MS, kStateProbeTimeoutMs) == CURLE_OK &&
65 curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 0L) == CURLE_OK &&
66 curl_easy_setopt(easy, CURLOPT_MAX_RECV_SPEED_LARGE, kStateProbeReceiveRate) == CURLE_OK;
69std::size_t RuntimeTransferCount(
const curl::fuzzer::proto::MultiPlan& plan) {
74void RecordNotification(CURLM*,
unsigned int notification, CURL*,
void* userdata) {
76 if (stats ==
nullptr) {
79 if (notification == CURLMNOTIFY_INFO_READ) {
80 ++stats->info_read_notifications;
81 }
else if (notification == CURLMNOTIFY_EASY_DONE) {
82 ++stats->easy_done_notifications;
89class MultiTransferDriver {
91 MultiTransferDriver(CURLM* multi, std::array<TransferState, scenario_limits::kMaxMultiTransfers>& transfers,
92 std::size_t transfer_count, MultiTransferRunStats& stats)
93 : multi_(multi), transfers_(transfers), transfer_count_(transfer_count), stats_(stats) {}
98 bool DrainCompletionMessages() {
99 bool consumed =
false;
100 int messages_remaining = 0;
101 CURLMsg* message =
nullptr;
102 while ((message = curl_multi_info_read(multi_, &messages_remaining)) !=
nullptr) {
104 if (message->msg != CURLMSG_DONE) {
107 for (std::size_t index = 0; index < transfer_count_; ++index) {
108 TransferState& transfer = transfers_[index];
109 if (transfer.session.easy() == message->easy_handle) {
110 ++stats_.completion_messages;
121 bool ApplyAction(
const curl::fuzzer::proto::MultiAction& action) {
122 if (transfer_count_ == 0) {
125 TransferState& transfer = transfers_[
static_cast<std::size_t
>(action.transfer_selector()) % transfer_count_];
126 CURL* easy = transfer.session.easy();
127 if (easy ==
nullptr) {
131 switch (action.kind()) {
132 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_RECV:
133 return curl_easy_pause(easy, CURLPAUSE_RECV) == CURLE_OK;
134 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_SEND:
135 return curl_easy_pause(easy, CURLPAUSE_SEND) == CURLE_OK;
136 case curl::fuzzer::proto::MULTI_ACTION_PAUSE_ALL:
137 return curl_easy_pause(easy, CURLPAUSE_ALL) == CURLE_OK;
138 case curl::fuzzer::proto::MULTI_ACTION_RESUME:
139 return curl_easy_pause(easy, CURLPAUSE_CONT) == CURLE_OK;
140 case curl::fuzzer::proto::MULTI_ACTION_REMOVE:
141 if (transfer.attached && curl_multi_remove_handle(multi_, easy) == CURLM_OK) {
142 transfer.attached =
false;
146 case curl::fuzzer::proto::MULTI_ACTION_READD:
147 if (!transfer.attached && curl_multi_add_handle(multi_, easy) == CURLM_OK) {
148 transfer.attached =
true;
152 case curl::fuzzer::proto::MULTI_ACTION_NONE:
160 std::array<TransferState, scenario_limits::kMaxMultiTransfers>& transfers_;
161 const std::size_t transfer_count_;
162 MultiTransferRunStats& stats_;
167void ProbeMultiWaitApis(CURLM* multi) {
169 (void)curl_multi_poll(multi,
nullptr, 0, 0, &numfds);
170 (void)curl_multi_wait(multi,
nullptr, 0, 0, &numfds);
172 unsigned int waitfd_count = 0;
173 (void)curl_multi_waitfds(multi,
nullptr, 0, &waitfd_count);
174 std::array<curl_waitfd, scenario_limits::kMaxMultiTransfers * 2> waitfds{};
175 (void)curl_multi_waitfds(multi, waitfds.data(),
static_cast<unsigned int>(waitfds.size()), &waitfd_count);
184 (void)curl_multi_fdset(multi, &readfds, &writefds, &exceptfds, &maxfd);
185 long timeout_ms = -1;
186 (void)curl_multi_timeout(multi, &timeout_ms);
188 CURL** handles = curl_multi_get_handles(multi);
191 constexpr CURLMinfo_offt kOffsetInfo[] = {
192 CURLMINFO_XFERS_CURRENT, CURLMINFO_XFERS_RUNNING, CURLMINFO_XFERS_PENDING,
193 CURLMINFO_XFERS_DONE, CURLMINFO_XFERS_ADDED,
195 for (
const CURLMinfo_offt info : kOffsetInfo) {
196 curl_off_t value = 0;
197 (void)curl_multi_get_offt(multi, info, &value);
205void ProbeLegacyMultiSocketApis(CURLM* multi) {
206 int running_handles = 0;
208#pragma GCC diagnostic push
209#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
211 (void)curl_multi_socket(multi, CURL_SOCKET_TIMEOUT, &running_handles);
212 (void)curl_multi_socket_all(multi, &running_handles);
214#pragma GCC diagnostic pop
228 const auto& plan = scenario.multi_plan();
229 const std::size_t transfer_count = RuntimeTransferCount(plan);
232 FilePtr progress_sink;
233 if (plan.state_probe_profile() == curl::fuzzer::proto::MULTI_STATE_PROBE_PROGRESS_RATE_LIMIT) {
234 progress_sink.reset(std::tmpfile());
248 const long max_host_connections =
249 static_cast<long>(std::min<std::size_t>(plan.max_host_connections(), transfer_count));
250 const long max_total_connections =
251 static_cast<long>(std::min<std::size_t>(plan.max_total_connections(), transfer_count));
252 const long cache_size =
254 (void)curl_multi_setopt(multi.get(), CURLMOPT_MAX_HOST_CONNECTIONS, max_host_connections);
255 (void)curl_multi_setopt(multi.get(), CURLMOPT_MAX_TOTAL_CONNECTIONS, max_total_connections);
256 (void)curl_multi_setopt(multi.get(), CURLMOPT_MAXCONNECTS, cache_size);
257 (void)curl_multi_setopt(multi.get(), CURLMOPT_PIPELINING,
258 plan.multiplex() ?
static_cast<long>(CURLPIPE_MULTIPLEX) : 0L);
259 (void)curl_multi_setopt(multi.get(), CURLMOPT_NOTIFYFUNCTION, &RecordNotification);
260 (void)curl_multi_setopt(multi.get(), CURLMOPT_NOTIFYDATA, &stats);
261 (void)curl_multi_notify_enable(multi.get(), CURLMNOTIFY_INFO_READ);
262 (void)curl_multi_notify_enable(multi.get(), CURLMNOTIFY_EASY_DONE);
263 (void)curl_multi_notify_disable(multi.get(), CURLMNOTIFY_INFO_READ);
264 (void)curl_multi_notify_enable(multi.get(), CURLMNOTIFY_INFO_READ);
265 (void)curl_multi_notify_disable(multi.get(), CURLMNOTIFY_EASY_DONE);
266 (void)curl_multi_notify_enable(multi.get(), CURLMNOTIFY_EASY_DONE);
268 const bool socket_mode = plan.drive_mode() == curl::fuzzer::proto::MULTI_DRIVE_SOCKET;
269 const bool socket_driver_installed = socket_mode && socket_driver.
Install(multi.get());
270 std::array<TransferState, scenario_limits::kMaxMultiTransfers> transfers;
271 const std::string url =
"http://" + scenario.host_path();
272 for (std::size_t index = 0; index < transfer_count; ++index) {
273 TransferState& transfer = transfers[index];
274 if (!transfer.session.Initialize()) {
277 transfer.session.ApplyBaseline(scenario);
278 (void)curl_easy_setopt(transfer.session.easy(), CURLOPT_URL, url.c_str());
279 mock.
Install(transfer.session.easy());
281 if (ApplyStateProbeOptions(transfer.session.easy(), plan.state_probe_profile(), progress_sink.get())) {
285 if (curl_multi_add_handle(multi.get(), transfer.session.easy()) == CURLM_OK) {
286 transfer.attached =
true;
296 MultiTransferDriver driver(multi.get(), transfers, transfer_count, stats);
297 ProbeLegacyMultiSocketApis(multi.get());
300 std::size_t next_action = 0;
301 bool initial_action_changed =
false;
302 if (next_action < action_count) {
303 initial_action_changed = driver.ApplyAction(plan.actions(
static_cast<int>(next_action++)));
307 int running_handles = 0;
308 CURLMcode result = socket_driver_installed ? socket_driver.
Start(&running_handles)
309 : curl_multi_perform(multi.get(), &running_handles);
310 bool completion_progress = driver.DrainCompletionMessages();
311 ProbeMultiWaitApis(multi.get());
312 if (plan.wake_multi()) {
313 if (socket_driver_installed) {
316 (void)curl_multi_wakeup(multi.get());
320 int idle_iterations = initial_action_changed || completion_progress ? 0 : 1;
321 for (
int iteration = 0; result == CURLM_OK && iteration < kMaxDriveIterations; ++iteration) {
322 if (running_handles == 0 && next_action >= action_count) {
327 if (next_action < action_count) {
328 made_progress = driver.ApplyAction(plan.actions(
static_cast<int>(next_action++))) || made_progress;
332 made_progress =
true;
335 const int running_before = running_handles;
336 if (socket_driver_installed) {
341 result = curl_multi_perform(multi.get(), &running_handles);
342 made_progress = running_handles != running_before || made_progress;
344 made_progress = driver.DrainCompletionMessages() || made_progress;
348 }
else if (++idle_iterations >= kMaxIdleIterations) {
353 (void)driver.DrainCompletionMessages();
355 for (std::size_t index = 0; index < transfer_count; ++index) {
356 TransferState& transfer = transfers[index];
357 if (transfer.session.easy() ==
nullptr) {
360 (void)curl_easy_pause(transfer.session.easy(), CURLPAUSE_CONT);
361 if (transfer.attached) {
362 (void)curl_multi_remove_handle(multi.get(), transfer.session.easy());
363 transfer.attached =
false;
372 std::fputs(
"curl-fuzzer: socket watch capacity exhausted\n", stderr);
374 for (std::size_t index = 0; index < transfer_count; ++index) {
375 transfers[index].session.Close();
virtual void Install(CURL *easy)
Install the common OPENSOCKETFUNCTION / OPENSOCKETDATA / SOCKOPTFUNCTION callbacks on 'easy'.
virtual void ConfigureRequestData(ScenarioRequestData *request_data)
Bind protocol-specific work to the request-data callbacks after their per-scenario state has been con...
Orchestrates a bounded sequence of mock HTTP exchanges: installs socket callbacks on an easy handle,...
std::size_t opened_connection_count() const
Report how many peer sockets curl opened during the current script run.
bool ServiceConnections()
Service one deterministic application event-loop turn by draining all available request bytes and rel...
void SetKeepConnectionsOpen(bool keep_open)
Keep completed response sockets writable instead of half-closing them.
void SetScripts(const curl::fuzzer::proto::Scenario &scenario)
Build the complete borrowed script table before entering curl.
Owns the socket and timer callback state required by libcurl's event-driven multi API.
CURLMcode Start(int *running_handles)
Start libcurl's socket state machine through the documented timeout pseudo-socket without waiting for...
bool Install(CURLM *multi)
Install socket/timer callbacks on a caller-owned multi before adding an easy handle.
bool watch_exhausted() const
void ProbeControlApis()
Exercise the wakeup and timeout-query APIs from a valid live-multi state.
DriveResult DriveReady(int *running_handles)
Run one bounded, non-blocking event-loop turn: poll the callback-provided watch set with a zero timeo...
Common ownership types for independently cleaned-up curl handles.
std::unique_ptr< CURLM, CurlMultiDeleter > CurlMultiPtr
Unique ownership of a CURL multi handle.
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
Bounded event-loop state for driving curl_multi_socket_action.
MultiTransferRunStats RunMultiTransferScenario(const curl::fuzzer::proto::Scenario &scenario)
Drive two to four easy handles through one CURLM and one bounded in-process HTTP peer.
Bounded shared-multi orchestration for concurrent HTTP transfers.
std::size_t ApplyScenarioOptions(CURL *easy, const curl::fuzzer::proto::Scenario &scenario)
Apply the bounded runtime-visible prefix of Scenario.options.
Translates Scenario SetOption messages into curl_easy_setopt calls, plus the fixed baseline setopts t...
Owns pointer-valued HTTP headers, TELNET options, MIME state, and upload callback state derived from ...
Shared complexity limits for structured scenarios.
constexpr std::size_t kMinMultiTransfers
Two handles are the minimum that can enter shared-multi scheduling, while four align with the mock's ...
constexpr std::size_t kMaxMultiActions
Ordered lifecycle transitions remain useful in pairs (pause/resume and remove/re-add); sixteen permit...
constexpr std::size_t kMaxMultiTransfers
Maximum easy handles configured in one shared-multi fuzz iteration.
Result from one non-blocking event-loop turn.
bool made_progress
True when an action ran or callback/handle state changed.
CURLMcode code
libcurl's result for the last socket action in the turn.
Observable lifecycle counts returned to focused unit tests.
std::size_t added_handles
Easy handles successfully added to the shared multi handle.
std::size_t state_probe_handles
Easy handles that received the complete closed state-probe setup.
std::size_t opened_connections
Peer sockets opened while serving the concurrent transfers.
std::size_t configured_handles
Easy handles requested after runtime bounds are applied.
std::size_t actions_consumed
Ordered plan actions consumed, including intentional no-ops.
bool watch_exhausted
Socket readiness could not be represented in the fixed watch table.
Owns an easy handle and the backing storage retained by its options.