22namespace proto_fuzzer {
29 : MockServerBase(drive_policy),
31 control_reply_count_(0),
32 next_control_reply_(0),
33 active_pollset_probe_count_(0),
34 writable_pollset_probe_count_(0),
35 body_length_remaining_(0),
36 body_terminator_remaining_(0),
37 body_mode_(BodyMode::kCommand),
38 control_socket_(CURL_SOCKET_BAD),
39 control_opened_(false) {}
82 body_mode_ = BodyMode::kMarker;
83 body_marker_.assign(terminator.data(), terminator.size());
84 body_length_remaining_ = 0;
85 body_terminator_remaining_ = 0;
93 body_mode_ = BodyMode::kLiteral;
95 body_length_remaining_ = length;
96 body_terminator_remaining_ = 2;
104 body_mode_ = BodyMode::kExact;
105 body_marker_.clear();
106 body_length_remaining_ = length;
107 body_terminator_remaining_ = 0;
113 body_mode_ = BodyMode::kOpaque;
114 body_marker_.clear();
115 body_length_remaining_ = 0;
116 body_terminator_remaining_ = 0;
125 if (data ==
nullptr || size == 0) {
128 pending_control_bytes_.append(
reinterpret_cast<const char*
>(data), size);
139 if (scenario_ ==
nullptr || purpose != CURLSOCKTYPE_IPCXN || control_opened_) {
140 return CURL_SOCKET_BAD;
146 return CURL_SOCKET_BAD;
150 ApplyScriptBackpressure(
connection_.get(), scenario_->connection());
151 const std::string& greeting = scenario_->connection().initial_response();
152 if (!greeting.empty() &&
153 !
connection_->WriteAll(
reinterpret_cast<const unsigned char*
>(greeting.data()), greeting.size())) {
155 return CURL_SOCKET_BAD;
158 control_opened_ =
true;
160 return control_socket_;
167void PingpongMockServer::ResetForScenario(
const curl::fuzzer::proto::Scenario& scenario) {
171 static_cast<std::size_t
>(
scenario.connection().on_readable_size()));
172 next_control_reply_ = 0;
173 pending_control_bytes_.clear();
174 control_transcript_.clear();
175 uploaded_data_.clear();
176 active_pollset_probe_count_ = 0;
177 writable_pollset_probe_count_ = 0;
178 body_marker_.clear();
179 body_length_remaining_ = 0;
180 body_terminator_remaining_ = 0;
181 body_mode_ = BodyMode::kCommand;
182 control_socket_ = CURL_SOCKET_BAD;
183 control_opened_ =
false;
192void PingpongMockServer::ApplyScriptBackpressure(MockConnection* connection,
193 const curl::fuzzer::proto::Connection& script) {
198 const std::uint32_t int_max =
static_cast<std::uint32_t
>(std::numeric_limits<int>::max());
199 const auto& backpressure = script.backpressure();
200 const int receive_buffer =
static_cast<int>(std::min(backpressure.recv_buf_bytes(), int_max));
201 connection->ApplyBackpressure(receive_buffer,
static_cast<std::size_t
>(backpressure.drain_limit()));
209 if (scenario_ ==
nullptr || next_control_reply_ >= control_reply_count_) {
212 return &scenario_->connection().on_readable(
static_cast<int>(next_control_reply_++));
222 connection_->WriteAll(
reinterpret_cast<const unsigned char*
>(bytes.data()), bytes.size()));
248 std::size_t begin = 0;
249 while (begin < line.size() && (line[begin] ==
' ' || line[begin] ==
'\t')) {
253 std::size_t end = begin;
254 while (end < line.size() && line[end] !=
' ' && line[end] !=
'\t' && line[end] !=
'\r' && line[end] !=
'\n') {
257 return line.substr(begin, end - begin);
265 std::size_t position = 0;
266 while (position < line.size() && (line[position] ==
' ' || line[position] ==
'\t')) {
269 while (position < line.size() && line[position] !=
' ' && line[position] !=
'\t' && line[position] !=
'\r' &&
270 line[position] !=
'\n') {
273 while (position < line.size() && (line[position] ==
' ' || line[position] ==
'\t')) {
277 std::size_t end = position;
278 while (end < line.size() && line[end] !=
' ' && line[end] !=
'\t' && line[end] !=
'\r' && line[end] !=
'\n') {
281 return line.substr(position, end - position);
291 if (token.size() != expected_uppercase.size()) {
294 for (std::size_t index = 0; index < token.size(); ++index) {
295 unsigned char actual =
static_cast<unsigned char>(token[index]);
296 if (actual >=
'a' && actual <=
'z') {
297 actual =
static_cast<unsigned char>(actual -
'a' +
'A');
299 if (actual !=
static_cast<unsigned char>(expected_uppercase[index])) {
312void PingpongMockServer::CapturePrefix(std::string_view source, std::size_t limit, std::string* destination) {
313 if (destination ==
nullptr || destination->size() >= limit) {
316 const std::size_t available = limit - destination->size();
317 destination->append(source.data(), std::min(source.size(), available));
325bool PingpongMockServer::ServiceControlConnection() {
330 const std::size_t size_before_read = pending_control_bytes_.size();
331 const std::size_t transport_progress =
connection_->DrainIncoming();
332 bool made_progress = pending_control_bytes_.size() != size_before_read || transport_progress != 0;
334 std::size_t consumed = 0;
335 while (consumed < pending_control_bytes_.size()) {
336 if (body_mode_ == BodyMode::kOpaque) {
337 consumed = pending_control_bytes_.size();
338 made_progress =
true;
342 if (body_mode_ == BodyMode::kMarker) {
343 const std::size_t marker = pending_control_bytes_.find(body_marker_, consumed);
344 if (marker == std::string::npos) {
347 const std::size_t body_end = marker + body_marker_.size();
348 CapturePrefix(std::string_view(pending_control_bytes_.data() + consumed, body_end - consumed),
349 kMaxCapturedUploadBytes, &uploaded_data_);
351 body_mode_ = BodyMode::kCommand;
352 made_progress =
true;
357 if (body_mode_ == BodyMode::kExact) {
358 const std::size_t available = pending_control_bytes_.size() - consumed;
359 const std::size_t take = std::min(available, body_length_remaining_);
360 CapturePrefix(std::string_view(pending_control_bytes_.data() + consumed, take), kMaxCapturedUploadBytes,
363 body_length_remaining_ -= take;
365 made_progress =
true;
367 if (body_length_remaining_ == 0) {
368 body_mode_ = BodyMode::kCommand;
369 made_progress =
true;
376 if (body_mode_ == BodyMode::kLiteral) {
377 if (body_length_remaining_ != 0) {
378 const std::size_t available = pending_control_bytes_.size() - consumed;
379 const std::size_t take = std::min(available, body_length_remaining_);
380 CapturePrefix(std::string_view(pending_control_bytes_.data() + consumed, take), kMaxCapturedUploadBytes,
383 body_length_remaining_ -= take;
384 made_progress =
true;
388 const std::size_t available = pending_control_bytes_.size() - consumed;
389 if (body_terminator_remaining_ == 2 && available >= 2 && pending_control_bytes_[consumed] ==
'\r' &&
390 pending_control_bytes_[consumed + 1] ==
'\n') {
392 body_terminator_remaining_ = 0;
393 body_mode_ = BodyMode::kCommand;
394 made_progress =
true;
398 if (available >= 1) {
399 const char byte = pending_control_bytes_[consumed];
400 if (
byte ==
'\r' ||
byte ==
'\n') {
402 if (
byte ==
'\n' || body_terminator_remaining_ == 0) {
403 body_terminator_remaining_ = 0;
404 body_mode_ = BodyMode::kCommand;
407 body_terminator_remaining_ = 1;
409 made_progress =
true;
414 body_terminator_remaining_ = 0;
415 body_mode_ = BodyMode::kCommand;
421 const std::size_t newline = pending_control_bytes_.find(
'\n', consumed);
422 if (newline == std::string::npos) {
425 const std::size_t command_size = newline - consumed + 1;
426 const std::string_view command(pending_control_bytes_.data() + consumed, command_size);
427 CapturePrefix(command, kMaxCapturedControlBytes, &control_transcript_);
429 consumed += command_size;
430 made_progress =
true;
434 pending_control_bytes_.erase(0, consumed);
436 return made_progress;
442void PingpongMockServer::FinishConnections() {
457 (void)ServiceControlConnection();
471 int still_running = 1;
472 int idle_iterations = 0;
473 int drive_iterations = 0;
474 CURLMcode result = CURLM_OK;
476 const int running_before = still_running;
477 result = curl_multi_perform(multi, &still_running);
478 if (result != CURLM_OK) {
486 if (
scenario.probe_multi_pollset() && still_running && control_opened_) {
488 ++active_pollset_probe_count_;
489 const bool incomplete_command = body_mode_ == BodyMode::kCommand && !pending_control_bytes_.empty() &&
490 pending_control_bytes_.find(
'\n') == std::string::npos;
492 ++writable_pollset_probe_count_;
496 bool made_progress = still_running != running_before;
497 made_progress = ServiceControlConnection() || made_progress;
507 (void)ServiceControlConnection();
std::unique_ptr< MockConnection > connection_
The per-scenario MockConnection, lazily created by HandleOpenSocket().
MockConnection * connection()
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.
static constexpr int kMaxIdleIterations
Consecutive no-progress budget for ordinary scenarios.
static bool MultiPollsetWantsWrite(CURLM *multi, curl_socket_t socket)
Inspect one socket's current writable interest through curl's public multi fdset API.
curl_socket_t HandleOpenSocket(curlsocktype purpose=CURLSOCKTYPE_IPCXN, struct curl_sockaddr *address=nullptr) override
Create the single control socketpair and queue the server greeting.
void RunLoop(CURLM *multi, CURL *easy, const curl::fuzzer::proto::Scenario &scenario) override
Alternate curl transitions with immediate peer service until the transfer completes or deterministic ...
void ObserveIncomingData(const unsigned char *data, std::size_t size) override
Append decoded application bytes to the pending parser buffer.
void HandleDetachedMulti(CurlMultiPtr multi) override
Give curl's graceful shutdown one last chance to emit its teardown command while the peer still owns ...
const std::string & control_transcript() const
Expose the bounded command capture from the most recent drive so tests can verify protocol states wit...
void BeginExactBody(std::size_t length)
Enter exact-length body mode.
bool WriteNextReply()
Consume the next scripted reply.
const curl::fuzzer::proto::Scenario * scenario() const
Borrowed scenario for the synchronous drive, or nullptr outside RunLoop().
void BeginLiteralBody(std::size_t length)
Enter length-announced literal body mode.
std::size_t active_pollset_probe_count() const
Report how many requested zero-timeout multi-poll probes ran during the most recent drive.
virtual void HandleBodyComplete()
Called after a body phase reached its terminator and the parser returned to command mode.
static std::string_view SecondToken(std::string_view line)
Skip the first token and return the second whitespace-delimited token without allocating.
void BeginOpaqueDrain()
Enter an opaque drain after a transport upgrade.
virtual void HandleLine(std::string_view line)=0
Answer one complete, newline-terminated command line.
void ShutdownControlWrite()
Half-close the server-to-client direction while retaining the read half.
virtual std::unique_ptr< MockConnection > CreateConnection()
Create the default plaintext transport.
static std::string_view FirstToken(std::string_view line)
Remove optional leading horizontal whitespace and return the first token without allocating.
~PingpongMockServer() override
Release the server-owned socketpair half after curl has returned its client descriptor.
const std::string * NextControlReply()
Return the next command-aligned response within the shared response-count budget.
std::size_t writable_pollset_probe_count() const
Report how often curl retained writable interest while the peer held an incomplete control command.
virtual void QueueShutdownReply()
Queue protocol-specific teardown liveness bytes before the control socket is half-closed.
const std::string & uploaded_data() const
Expose a bounded prefix of bytes drained during the most recent body phase while continuing to drain ...
static bool TokenEquals(std::string_view token, std::string_view expected_uppercase)
Fold only ASCII lowercase command letters.
bool WriteControlBytes(std::string_view bytes)
Keep failed peer writes local to the mock.
void BeginMarkerBody(std::string_view terminator)
Enter marker-terminated body mode.
virtual void ResetProtocolState()
Reset protocol-specific parser state for a new drive.
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 ...
MultiDrivePolicy
Fixed policy for interpreting a scenario's multi-drive fields.
PingpongMockServer — shared line-oriented command/response transport for the mail-style protocol peer...
constexpr std::size_t kMaxResponseChunks
Enough response boundaries to exercise incremental parsers while keeping per-perform delivery work fi...