14#include <curl/multi.h>
15#include <sys/select.h>
19#include "proto_fuzzer/allocation_fault_injection.h"
24namespace proto_fuzzer {
28constexpr long kSelectTimeoutUs = 1000;
35CURL** GetHandlesForCleanup(CURLM* multi) {
37 return curl_multi_get_handles(multi);
49 return static_cast<MockServerBase*
>(clientp)->
HandleOpenSocket(purpose, address);
63 return disposition == SocketSetupDisposition::kAlreadyConnected ? CURL_SOCKOPT_ALREADY_CONNECTED : CURL_SOCKOPT_OK;
76 drive_policy_(drive_policy) {}
99 curl_easy_setopt(easy, CURLOPT_OPENSOCKETDATA,
this);
101 curl_easy_setopt(easy, CURLOPT_SOCKOPTDATA,
this);
114 return purpose == CURLSOCKTYPE_ACCEPT ? SocketSetupDisposition::kNeedsSetup
115 : SocketSetupDisposition::kAlreadyConnected;
139 const curl::fuzzer::proto::ApiPlan* api_plan =
141 const bool use_multi_socket =
143 (api_plan !=
nullptr && api_plan->drive_mode() == curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET);
148 const auto& bp = scenario.connection().backpressure();
155 if (multi ==
nullptr) {
157 return CURLE_FAILED_INIT;
160 CURLcode transfer_result = CURLE_FAILED_INIT;
166 if (use_multi_socket && socket_driver.
Install(multi.get())) {
169 if (curl_multi_add_handle(multi.get(), easy) == CURLM_OK) {
173 if (api_plan !=
nullptr && api_plan->wake_multi()) {
177 long timeout_ms = -1;
178 (void)curl_multi_timeout(multi.get(), &timeout_ms);
179 (void)curl_multi_wakeup(multi.get());
182 RunLoop(multi.get(), easy, scenario);
190 int messages_remaining = 0;
191 CURLMsg* message =
nullptr;
192 while ((message = curl_multi_info_read(multi.get(), &messages_remaining)) !=
nullptr) {
193 if (message->msg == CURLMSG_DONE && message->easy_handle == easy) {
194 transfer_result = message->data.result;
202 CURL** handles = GetHandlesForCleanup(multi.get());
203 if (handles !=
nullptr) {
204 for (std::size_t index = 0; handles[index] !=
nullptr; ++index) {
205 CURL* handle = handles[index];
206 if (handle != easy && curl_multi_remove_handle(multi.get(), handle) == CURLM_OK) {
207 curl_easy_cleanup(handle);
214 curl_multi_remove_handle(multi.get(), easy);
219 if (use_multi_socket) {
226 std::fputs(
"curl-fuzzer: socket watch capacity exhausted\n", stderr);
229 return transfer_result;
260 const curl::fuzzer::proto::Scenario& scenario) {
287 (void)curl_easy_pause(easy, CURLPAUSE_CONT);
308 const auto& bp = scenario.connection().backpressure();
309 return bp.recv_buf_bytes() != 0 || bp.drain_limit() != 0;
326 *rc = curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd);
327 if (*rc != CURLM_OK) {
333 struct timeval timeout;
335 timeout.tv_usec = kSelectTimeoutUs;
336 return ::select(maxfd + 1, &readfds, &writefds, &excfds, &timeout);
348 (void)curl_multi_poll(multi,
nullptr, 0, 0, &numfds);
358 if (multi ==
nullptr || socket == CURL_SOCKET_BAD || socket < 0 || socket >= FD_SETSIZE) {
368 if (curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd) != CURLM_OK) {
371 return socket <= maxfd && FD_ISSET(socket, &writefds);
Owns one half of a socketpair used to feed canned responses to libcurl.
static bool UsesTimedDrive(const curl::fuzzer::proto::Scenario &scenario)
Treat a non-default BackpressureConfig as an explicit request for the slower, timed drive policy.
virtual void HandleDetachedMulti(CurlMultiPtr multi)
Dispose of or retain the multi after its easy handle has been removed.
virtual void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario)=0
Subclass hook invoked from DriveScenario.
virtual ConnectOnlyRunStats DriveConnectOnlyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario)
Establish a CONNECT_ONLY transport, then perform bounded direct I/O.
virtual void Install(CURL *easy)
Install the common OPENSOCKETFUNCTION / OPENSOCKETDATA / SOCKOPTFUNCTION callbacks on 'easy'.
CURLcode DriveScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario)
Run 'scenario' to completion on 'easy': allocate a multi, attach 'easy', delegate protocol-specific w...
std::size_t additional_handle_cleanup_count() const
int pending_recv_buf_bytes_
Cached SO_RCVBUF/SO_SNDBUF setting in bytes.
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
MockConnection * connection()
std::size_t pending_drain_limit_
Cached per-call DrainIncoming byte budget.
static int WaitOnMultiFdset(CURLM *multi, CURLMcode *rc)
Wait on curl's fdset with a short timeout.
std::size_t additional_handle_cleanup_count_
Number of application-created handles, excluding the caller's parent, successfully removed from the c...
static void ProbeMultiPollset(CURLM *multi)
Ask curl to construct and inspect its connection-filter pollset without waiting.
friend curl_socket_t MockServerBaseOpenSocketTrampoline(void *, curlsocktype, struct curl_sockaddr *)
C trampoline for CURLOPT_OPENSOCKETFUNCTION.
virtual curl_socket_t HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr *address)=0
Subclass hook invoked by the OPENSOCKET trampoline.
virtual void ObserveState()
Default no-op for peers without protocol-specific observations.
bool resume_response_
True only for an API-plan multi drive with its one-shot write callback.
void ResumeResponseIfRequested(CURL *easy)
Resume receive-side callback output at one bounded drive boundary when the dedicated API plan request...
virtual SocketSetupDisposition GetSocketSetupDisposition(curl_socket_t curlfd, curlsocktype purpose) const
Describe the socket returned by HandleOpenSocket without issuing native descriptor queries.
MultiSocketDriver * multi_socket_driver_
Non-owning pointer into DriveScenario's stack.
virtual CURLcode DriveEasyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario, bool use_events=false)
Run through the public easy entrypoint when a protocol mock can preload all peer work before curl tak...
friend int MockServerBaseSockOptTrampoline(void *, curl_socket_t, curlsocktype)
Translate explicit peer transport metadata into curl's sockopt callback contract.
virtual void ConfigureRequestData(ScenarioRequestData *request_data)
Bind protocol-specific work to the request-data callbacks after their per-scenario state has been con...
MultiSocketDriver * multi_socket_driver()
Expose the callback state installed for the current socket-action drive.
virtual ~MockServerBase()
Out-of-line destructor so MockConnection can stay forward-declared in the base header (its complete t...
static bool MultiPollsetWantsWrite(CURLM *multi, curl_socket_t socket)
Inspect one socket's current writable interest through curl's public multi fdset API.
void ApplyPendingBackpressure()
Apply the BackpressureConfig cached by DriveScenario to the new connection_.
MultiObserver multi_observer_
Observer receiving the live multi handle at the start of a drive.
Owns the socket and timer callback state required by libcurl's event-driven multi API.
bool Install(CURLM *multi)
Install socket/timer callbacks on a caller-owned multi before adding an easy handle.
bool watch_exhausted() const
Builds HTTP headers or TELNET options, MIME state, and upload callbacks whose storage or userdata lib...
Exclude a narrow harness-only operation from the allocation-failure plan.
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 ...
MockServerBase — common plumbing shared by every protocol-specific mock server.
SocketSetupDisposition
Tell curl whether an application-provided socket still needs its normal connect/options setup or alre...
MultiDrivePolicy
Fixed policy for interpreting a scenario's multi-drive fields.
@ kFromApiPlan
Read the multi-loop choice and probes from Scenario::api_plan.
@ kSocketAction
Use the callback-driven curl_multi_socket_action loop.
Bounded event-loop state for driving curl_multi_socket_action.
Results from the bounded CONNECT_ONLY public-API probe.
CURLcode connect_result
Result of establishing the connect-only transport.