curl-fuzzer C++ reference
Loading...
Searching...
No Matches
mock_server.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <fcntl.h>
13#include <string.h>
14#include <sys/select.h>
15#include <sys/socket.h>
16#include <sys/types.h>
17#include <unistd.h>
18
19#include <algorithm>
20#include <cerrno>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <string>
25#include <string_view>
26#include <vector>
27
31
32namespace proto_fuzzer {
33
34// curl's debug-only event-based easy entrypoint is intentionally used by the
35// API coverage lane. curl-fuzzer builds every bundled curl with ENABLE_DEBUG
36// and visible symbols, matching curl's own command-line tool declaration.
39extern "C" CURLcode curl_easy_perform_ev(CURL* easy);
40
41namespace {
42
43constexpr int kMaxConnectOnlyIoIterations = 16;
44constexpr std::size_t kConnectOnlyBufferSize = 1024;
45
46// fd_set can only represent file descriptors < FD_SETSIZE. Reject any pair that couldn't participate in select()
47// without memory corruption.
48bool FdFitsInFdSet(int fd) { return fd >= 0 && fd < FD_SETSIZE; }
49
50} // namespace
51
55
58MockConnection::MockConnection() : server_fd_(-1), client_fd_(-1), drain_limit_(0), incoming_data_observer_(nullptr) {
59 int fds[2];
60
61 if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
62 return;
63 }
64
65 // The fds must be small enough to fit in an fd_set for select(). If not, close them and fail the constructor.
66 if (!FdFitsInFdSet(fds[0]) || !FdFitsInFdSet(fds[1])) {
67 close(fds[0]);
68 close(fds[1]);
69 return;
70 }
71
72 // Set the server-side fd non-blocking so we can write to it without risk of hanging the fuzzer.
73 int flags = fcntl(fds[0], F_GETFL, 0);
74 if (flags < 0 || fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) < 0) {
75 close(fds[0]);
76 close(fds[1]);
77 return;
78 }
79
80 // Success: store the file descriptors.
81 server_fd_ = fds[0];
82 client_fd_ = fds[1];
83}
84
89MockConnection::MockConnection(int adopted_server_fd)
90 : server_fd_(adopted_server_fd), client_fd_(-1), drain_limit_(0), incoming_data_observer_(nullptr) {
91 if (server_fd_ < 0) {
92 return;
93 }
94 const int flags = fcntl(server_fd_, F_GETFL, 0);
95 if (!FdFitsInFdSet(server_fd_) || flags < 0 || fcntl(server_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
96 close(server_fd_);
97 server_fd_ = -1;
98 }
99}
100
103 if (server_fd_ >= 0) {
104 close(server_fd_);
105 }
106 if (client_fd_ >= 0) {
107 close(client_fd_);
108 }
109}
110
112bool MockConnection::ok() const { return server_fd_ >= 0; }
113
115int MockConnection::server_fd() const { return server_fd_; }
116
124 if (client_fd_ < 0) {
125 return 0;
126 }
127 int size = 0;
128 socklen_t length = sizeof(size);
129 if (getsockopt(client_fd_, SOL_SOCKET, SO_SNDBUF, &size, &length) != 0 || size <= 0) {
130 return 0;
131 }
132 return static_cast<std::size_t>(size);
133}
134
142 if (client_send_buffer_size() >= minimum) {
143 return true;
144 }
145 if (client_fd_ < 0 || minimum > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
146 return false;
147 }
148 const int requested = static_cast<int>(minimum);
149 if (setsockopt(client_fd_, SOL_SOCKET, SO_SNDBUF, &requested, sizeof(requested)) != 0) {
150 return false;
151 }
152 return client_send_buffer_size() >= minimum;
153}
154
160 if (client_fd_ < 0) {
161 return false;
162 }
163 const int flags = fcntl(client_fd_, F_GETFL, 0);
164 if (flags < 0 || fcntl(client_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
165 return false;
166 }
167
168 static constexpr unsigned char kPadding[4096] = {};
169 bool full = false;
170 while (true) {
171 const ssize_t written = ::send(client_fd_, kPadding, sizeof(kPadding), MSG_NOSIGNAL);
172 if (written > 0) {
173 continue;
174 }
175 full = written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK);
176 break;
177 }
178 if (fcntl(client_fd_, F_SETFL, flags) < 0) {
179 return false;
180 }
181 return full;
182}
183
188 int fd = client_fd_;
189 client_fd_ = -1;
190 return static_cast<curl_socket_t>(fd);
191}
192
201bool MockConnection::WriteAll(const unsigned char* data, std::size_t size) {
202 if (server_fd_ < 0) {
203 return false;
204 }
205 std::size_t written = 0;
206 while (written < size) {
207 ssize_t n = ::send(server_fd_, data + written, size - written, MSG_NOSIGNAL);
208 if (n <= 0) {
209 return false;
210 }
211 written += static_cast<std::size_t>(n);
212 }
213 return true;
214}
215
226 if (server_fd_ < 0) {
227 return 0;
228 }
229 unsigned char scratch[4096];
230 std::size_t drained = 0;
231 while (drain_limit_ == 0 || drained < drain_limit_) {
232 std::size_t want = sizeof(scratch);
233 if (drain_limit_ != 0) {
234 const std::size_t remaining = drain_limit_ - drained;
235 if (remaining < want) {
236 want = remaining;
237 }
238 }
239 ssize_t n = ::read(server_fd_, scratch, want);
240 if (n <= 0) {
241 break;
242 }
243 NotifyIncomingData(scratch, static_cast<std::size_t>(n));
244 drained += static_cast<std::size_t>(n);
245 }
246 return drained;
247}
248
253void MockConnection::SetIncomingDataObserver(IncomingDataObserver* observer) { incoming_data_observer_ = observer; }
254
258void MockConnection::NotifyIncomingData(const unsigned char* data, std::size_t size) {
259 if (incoming_data_observer_ != nullptr) {
260 incoming_data_observer_->ObserveIncomingData(data, size);
261 }
262}
263
272void MockConnection::ApplyBackpressure(int recv_buf_bytes, std::size_t drain_limit) {
273 if (recv_buf_bytes > 0) {
274 if (server_fd_ >= 0) {
275 (void)setsockopt(server_fd_, SOL_SOCKET, SO_RCVBUF, &recv_buf_bytes, sizeof(recv_buf_bytes));
276 }
277 if (client_fd_ >= 0) {
278 (void)setsockopt(client_fd_, SOL_SOCKET, SO_SNDBUF, &recv_buf_bytes, sizeof(recv_buf_bytes));
279 }
280 }
281 drain_limit_ = drain_limit;
282}
283
288void MockConnection::ReadAvailable(std::string* out) {
289 if (server_fd_ < 0 || out == nullptr) {
290 return;
291 }
292 unsigned char scratch[4096];
293 while (true) {
294 ssize_t n = ::read(server_fd_, scratch, sizeof(scratch));
295 if (n <= 0) {
296 break;
297 }
298 out->append(reinterpret_cast<const char*>(scratch), static_cast<std::size_t>(n));
299 }
300}
301
304 if (server_fd_ < 0) {
305 return;
306 }
307 ::shutdown(server_fd_, SHUT_WR);
308}
309
314
320 : MockServerBase(drive_policy),
321 script_count_(0),
322 next_script_(0),
323 active_script_(nullptr),
324 preload_all_chunks_(false),
325 keep_connections_open_(false) {}
326
329MockServer::~MockServer() = default;
330
340void MockServer::SetScripts(const curl::fuzzer::proto::Scenario& scenario) {
342 script_count_ = 0;
343 next_script_ = 0;
344
345 const auto append_script = [this](const curl::fuzzer::proto::Connection& connection) {
346 ConnectionScript& script = scripts_[script_count_++];
347 script.connection = &connection;
348 script.raw_chunk_count = std::min<std::size_t>(scenario_limits::kMaxResponseChunks, connection.on_readable_size());
349 const std::size_t frame_budget = scenario_limits::kMaxResponseChunks - script.raw_chunk_count;
350 script.frame_chunk_count = std::min<std::size_t>(frame_budget, connection.server_frames_size());
351 script.next_chunk = 0;
352 };
353
354 append_script(scenario.connection());
355 const std::size_t subsequent_count = std::min<std::size_t>(
356 scenario_limits::kMaxConnections - 1, static_cast<std::size_t>(scenario.subsequent_connections_size()));
357 for (std::size_t i = 0; i < subsequent_count; ++i) {
358 append_script(scenario.subsequent_connections(static_cast<int>(i)));
359 }
360}
361
366void MockServer::SetKeepConnectionsOpen(bool keep_open) { keep_connections_open_ = keep_open; }
367
370 return active_script_ != nullptr && active_script_->next_chunk < active_script_->chunk_count();
371}
372
377std::unique_ptr<MockConnection> MockServer::CreateConnection() { return std::make_unique<MockConnection>(); }
378
384 active_script_ = nullptr;
385 connection_.reset();
386 previous_connections_.clear();
387}
388
394
404curl_socket_t MockServer::HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* address) {
405 (void)purpose;
406 (void)address;
407 if (next_script_ >= script_count_) {
408 // Refusing a fifth socket keeps redirect loops bounded even if curl's own
409 // redirect limit is mutated upward or an authentication scheme retries.
410 return CURL_SOCKET_BAD;
411 }
412
413 if (connection_) {
414 // libcurl owns the corresponding client fd, so retain the server half
415 // until the easy handle leaves the multi instead of closing it at the
416 // moment a redirect or retry opens its replacement.
417 previous_connections_.push_back(std::move(connection_));
418 }
419
420 active_script_ = &scripts_[next_script_++];
421 const curl::fuzzer::proto::Connection& script_connection = *active_script_->connection;
423 if (!connection_ || !connection_->ok()) {
424 connection_.reset();
425 active_script_ = nullptr;
426 return CURL_SOCKET_BAD;
427 }
428
429 // Target policies clamp/clear these values before execution. Saturating the
430 // compatibility lane's raw uint32 avoids implementation-defined narrowing
431 // while preserving its ability to request any representable socket size.
432 const std::uint32_t int_max = static_cast<std::uint32_t>(std::numeric_limits<int>::max());
433 const auto& backpressure = script_connection.backpressure();
434 const int recv_buf_bytes = static_cast<int>(std::min(backpressure.recv_buf_bytes(), int_max));
435 connection_->ApplyBackpressure(recv_buf_bytes, static_cast<std::size_t>(backpressure.drain_limit()));
436
437 const std::string& initial_response = script_connection.initial_response();
438 if (!initial_response.empty()) {
439 if (!connection_->WriteAll(reinterpret_cast<const unsigned char*>(initial_response.data()),
440 initial_response.size())) {
441 connection_.reset();
442 active_script_ = nullptr;
443 return CURL_SOCKET_BAD;
444 }
445 }
446 if (preload_all_chunks_) {
447 while (has_more_chunks()) {
448 (void)DeliverNextChunk();
449 }
450 }
451 if (active_script_->chunk_count() == 0 && !keep_connections_open_) {
452 connection_->ShutdownWrite();
453 }
454 return connection_->take_client_fd();
455}
456
471CURLcode MockServer::DriveEasyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario, bool use_events) {
472 SetScripts(scenario);
473 preload_all_chunks_ = true;
474 (void)curl_easy_setopt(easy, CURLOPT_TIMEOUT_MS, 50L);
475 (void)curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, 50L);
476 // CONNECT_ONLY makes curl's timeleft check report "no limit" for the whole
477 // post-connect phase, so the timeouts above stop being enforced; value 2
478 // additionally skips the connect-only shortcut and runs a complete transfer.
479 // Clearing it is not a coverage loss: the multi-driven WebSocket lanes still
480 // exercise CONNECT_ONLY under their own iteration budget. Zero rather than
481 // one because CONNECT_ONLY=1 also disables the timeout and stays bounded only
482 // through a curl-internal shortcut this harness should not depend on.
483 (void)curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 0L);
484 const CURLcode result = use_events ? curl_easy_perform_ev(easy) : curl_easy_perform(easy);
485 preload_all_chunks_ = false;
486 return result;
487}
488
496ConnectOnlyRunStats MockServer::DriveConnectOnlyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
498 SetScripts(scenario);
499 preload_all_chunks_ = true;
500 (void)curl_easy_setopt(easy, CURLOPT_TIMEOUT_MS, 50L);
501 (void)curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT_MS, 50L);
502 (void)curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1L);
503 stats.connect_result = curl_easy_perform(easy);
504 preload_all_chunks_ = false;
505 if (stats.connect_result != CURLE_OK || connection_ == nullptr) {
506 return stats;
507 }
508
509 constexpr std::string_view kFallbackRequest = "GET / HTTP/1.0\r\n\r\n";
510 const std::string& upload = scenario.upload().data();
511 const std::string_view source = upload.empty() ? kFallbackRequest : std::string_view(upload);
512 const std::string_view outgoing = source.substr(0, scenario_limits::kMaxApiStringBytes);
513 stats.send_result = CURLE_OK;
514 for (int iteration = 0; stats.sent_bytes < outgoing.size() && iteration < kMaxConnectOnlyIoIterations; ++iteration) {
515 std::size_t sent = 0;
516 stats.send_result =
517 curl_easy_send(easy, outgoing.data() + stats.sent_bytes, outgoing.size() - stats.sent_bytes, &sent);
518 stats.sent_bytes += sent;
519 (void)connection_->DrainIncoming();
520 if (stats.send_result != CURLE_OK && stats.send_result != CURLE_AGAIN) {
521 break;
522 }
523 }
524
525 std::array<unsigned char, kConnectOnlyBufferSize> incoming{};
526 for (int iteration = 0; iteration < kMaxConnectOnlyIoIterations; ++iteration) {
527 std::size_t received = 0;
528 stats.recv_result = curl_easy_recv(easy, incoming.data(), incoming.size(), &received);
529 stats.received_bytes += received;
530 if ((stats.recv_result != CURLE_OK && stats.recv_result != CURLE_AGAIN) ||
531 (stats.recv_result == CURLE_OK && received == 0)) {
532 break;
533 }
534 }
535 return stats;
536}
537
542 if (!connection_ || !has_more_chunks()) {
543 return false;
544 }
545 connection_->DrainIncoming();
546 const std::size_t chunk_index = active_script_->next_chunk++;
547 const auto& script_connection = *active_script_->connection;
548 if (chunk_index < active_script_->raw_chunk_count) {
549 const std::string& chunk = script_connection.on_readable(static_cast<int>(chunk_index));
550 if (!chunk.empty()) {
551 connection_->WriteAll(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
552 }
553 } else {
554 // HTTP scenarios historically accept structured WebSocket frames as raw
555 // response bytes after every on_readable chunk. Serialize only the frame
556 // curl is about to receive: follow-on scripts and capped suffix frames may
557 // never be consumed, so eagerly materialising all of them wastes mutations.
558 const std::size_t frame_index = chunk_index - active_script_->raw_chunk_count;
559 const std::string chunk = SerializeWebSocketFrame(script_connection.server_frames(static_cast<int>(frame_index)));
560 if (!chunk.empty()) {
561 connection_->WriteAll(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
562 }
563 }
564 if (!has_more_chunks() && !keep_connections_open_) {
565 connection_->ShutdownWrite();
566 }
567 return true;
568}
569
575std::size_t MockServer::DrainIncomingConnections() {
576 std::size_t drained = 0;
577 for (const auto& previous : previous_connections_) {
578 drained += previous->DrainIncoming();
579 }
580 if (connection_) {
581 drained += connection_->DrainIncoming();
582 }
583 return drained;
584}
585
595 bool made_progress = DrainIncomingConnections() != 0;
596 if (has_more_chunks()) {
597 made_progress = DeliverNextChunk() || made_progress;
598 }
599 return made_progress;
600}
601
604std::size_t MockServer::opened_connection_count() const { return next_script_; }
605
614void MockServer::RunSocketActionLoop(CURLM* multi, CURL* easy) {
616 if (driver == nullptr) {
617 return;
618 }
619
620 int still_running = 1;
621 CURLMcode rc = driver->Start(&still_running);
624 int idle_iterations = 0;
625 int drive_iterations = 0;
626 while (rc == CURLM_OK && still_running && idle_iterations < kMaxIdleIterations &&
627 drive_iterations++ < kMaxDriveIterations) {
628 bool made_progress = ServiceConnections();
629 const MultiSocketDriver::DriveResult drive_result = driver->DriveReady(&still_running);
630 rc = drive_result.code;
633 made_progress = drive_result.made_progress || made_progress;
634 if (made_progress) {
635 idle_iterations = 0;
636 } else {
637 ++idle_iterations;
638 }
639 }
640}
641
646bool MockServer::SupportsRepeatedPollsetProbe() const { return false; }
647
654void MockServer::RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
655 SetScripts(scenario);
656
657 if (multi_socket_driver() != nullptr) {
658 RunSocketActionLoop(multi, easy);
659 return;
660 }
661
662 int still_running = 1;
663 int idle_iterations = 0;
664 int drive_iterations = 0;
665 bool pollset_probed = false;
666 // Each newly opened socket can carry its own pressure settings. Inspect only
667 // the borrowed, runtime-bounded scripts: ignored repeated protobuf entries must
668 // not opt a fast transfer into timed waits.
669 const bool timed_drive =
670 std::any_of(scripts_.begin(), scripts_.begin() + script_count_, [](const ConnectionScript& script) {
671 const auto& backpressure = script.connection->backpressure();
672 return backpressure.recv_buf_bytes() != 0 || backpressure.drain_limit() != 0;
673 });
674 const int idle_limit = timed_drive ? kMaxTimedIdleIterations : kMaxIdleIterations;
675 const bool repeated_pollset_probe = scenario.probe_multi_pollset() && SupportsRepeatedPollsetProbe();
676 CURLMcode rc = CURLM_OK;
677
678 while (still_running && idle_iterations < idle_limit && drive_iterations++ < kMaxDriveIterations) {
679 bool made_progress = false;
680 const int running_before = still_running;
681 rc = curl_multi_perform(multi, &still_running);
682 if (rc != CURLM_OK) {
683 break;
684 }
687 made_progress = still_running != running_before;
688 if (still_running && ((timed_drive && !pollset_probed) || repeated_pollset_probe)) {
689 // Probe only after curl has built the socket/filter chain. Timing cases
690 // need one zero-timeout poll; an explicit protocol opt-in probes each
691 // bounded perform turn so filters installed after a SOCKS handshake
692 // (notably HAProxy) are observed while active. Default fast paths remain
693 // free of a per-input polling syscall.
694 ProbeMultiPollset(multi);
695 pollset_probed = true;
696 }
697 if (!still_running) {
698 break;
699 }
700
701 // Always drain whatever curl has written. Under backpressure the kernel
702 // recv buffer would otherwise stay full — curl short-writes, the mock
703 // never consumes, and the transfer wedges until the drive budget. With
704 // drain_limit set this still honours the per-tick byte budget.
705 made_progress = ServiceConnections() || made_progress;
706
707 if (made_progress) {
708 idle_iterations = 0;
709 continue;
710 }
711
712 // Ordinary socketpair scenarios never sleep: repeated no-progress
713 // performs are enough to settle curl's local state machine. Only an
714 // explicit BackpressureConfig opts into short waits so timeout-related
715 // behaviour remains fuzzable without taxing every corpus entry.
716 if (timed_drive) {
717 (void)WaitOnMultiFdset(multi, &rc);
718 if (rc != CURLM_OK) {
719 break;
720 }
721 }
722 ++idle_iterations;
723 }
724}
725
726} // namespace proto_fuzzer
Receives bytes after any transport decoding.
bool EnsureClientSendBufferSize(std::size_t minimum)
Ensure curl's endpoint reports at least minimum bytes of send buffer, requesting a larger SO_SNDBUF w...
virtual bool WriteAll(const unsigned char *data, std::size_t size)
Write 'size' bytes from 'data' to the server fd, looping until the whole buffer is sent or a short/fa...
void ReadAvailable(std::string *out)
Non-blocking read: append whatever bytes are currently available on the server fd to 'out'.
virtual void ShutdownWrite()
Signal end-of-response to libcurl by half-closing the write side.
virtual bool ok() const
void NotifyIncomingData(const unsigned char *data, std::size_t size)
Forward one application-data fragment to the optional observer.
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()
Construct a non-blocking AF_UNIX/SOCK_STREAM socketpair.
std::size_t client_send_buffer_size() const
Query the capacity curl's endpoint reports for queued client writes while this object still owns that...
virtual ~MockConnection()
Close the server-side fd (and the client-side fd if it was never handed off via take_client_fd()).
bool FillClientSendBuffer()
Saturate curl's endpoint before ownership is transferred.
void ApplyBackpressure(int recv_buf_bytes, std::size_t drain_limit)
Apply deterministic backpressure knobs before traffic begins.
curl_socket_t take_client_fd()
Hand the client-side fd to libcurl.
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
static int WaitOnMultiFdset(CURLM *multi, CURLMcode *rc)
Wait on curl's fdset with a short timeout.
static constexpr int kMaxTimedIdleIterations
Explicit backpressure scenarios get a larger idle budget and may use the short timed wait above.
static void ProbeMultiPollset(CURLM *multi)
Ask curl to construct and inspect its connection-filter pollset without waiting.
static constexpr int kMaxDriveIterations
Hard operation budget for one scenario.
void ResumeResponseIfRequested(CURL *easy)
Resume receive-side callback output at one bounded drive boundary when the dedicated API plan request...
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
MultiSocketDriver * multi_socket_driver()
Expose the callback state installed for the current socket-action drive.
CURLcode DriveEasyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario, bool use_events=false) override
Preload all bounded response bytes from inside OPENSOCKETFUNCTION, where the mock still owns both soc...
void ResetConnections()
Release all current and retired socketpairs before resetting transport- specific state.
ConnectOnlyRunStats DriveConnectOnlyScenario(CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Establish only the HTTP transport, then pass bounded application bytes through curl's public direct-I...
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...
~MockServer() override
Default destructor; current and previous MockConnections clean up their server-side descriptors only ...
void SetKeepConnectionsOpen(bool keep_open)
Keep completed response sockets writable instead of half-closing them.
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Seed the mock from the scenario, then drive the perform loop until curl is done or a deterministic op...
void SetScripts(const curl::fuzzer::proto::Scenario &scenario)
Build the complete borrowed script table before entering curl.
virtual std::unique_ptr< MockConnection > CreateConnection()
Construct the transport used for one HTTP exchange.
virtual void ObserveActiveTransfer(CURL *easy)
Observe curl while its connection filters are still attached.
virtual bool SupportsRepeatedPollsetProbe() const
Ordinary HTTP and compatibility peers retain the historical perform-only loop even if an append-only ...
MockServer(MultiDrivePolicy drive_policy=MultiDrivePolicy::kPerform)
Construct an idle MockServer with no scripted responses or open peers.
curl_socket_t HandleOpenSocket(curlsocktype purpose=CURLSOCKTYPE_IPCXN, struct curl_sockaddr *address=nullptr) override
Called by the OPENSOCKETFUNCTION trampoline in the base class.
bool DeliverNextChunk()
Push the next queued chunk.
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...
DriveResult DriveReady(int *running_handles)
Run one bounded, non-blocking event-loop turn: poll the callback-provided watch set with a zero timeo...
CURLcode curl_easy_perform_ev(CURL *easy)
MockConnection and MockServer — the in-process peer that feeds canned response bytes to libcurl over ...
MultiDrivePolicy
Fixed policy for interpreting a scenario's multi-drive fields.
Bounded event-loop state for driving curl_multi_socket_action.
Shared complexity limits for structured scenarios.
constexpr std::size_t kMaxResponseChunks
Enough response boundaries to exercise incremental parsers while keeping per-perform delivery work fi...
constexpr std::size_t kMaxConnections
Four sockets cover redirect/auth flows without permitting mutation-sized connection chains to dominat...
constexpr std::size_t kMaxApiStringBytes
URL escaping can expand input threefold.
Results from the bounded CONNECT_ONLY public-API probe.
CURLcode connect_result
Result of establishing the connect-only transport.
CURLcode recv_result
Final result returned by curl_easy_recv.
std::size_t received_bytes
Total application bytes returned by curl_easy_recv.
std::size_t sent_bytes
Total application bytes accepted by curl_easy_send.
CURLcode send_result
Final result returned by curl_easy_send.
CURLMcode code
libcurl's result for the last socket action in the turn.
std::string SerializeWebSocketFrame(const curl::fuzzer::proto::WebSocketFrame &frame)
Serialise a proto WebSocketFrame into RFC 6455 wire bytes.
Definition ws_frame.cc:59
Serialize a proto WebSocketFrame message into RFC 6455 wire bytes.