curl-fuzzer C++ reference
Loading...
Searching...
No Matches
pingpong_mock_server.h
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
10
11#ifndef PROTO_FUZZER_PINGPONG_MOCK_SERVER_H_
12#define PROTO_FUZZER_PINGPONG_MOCK_SERVER_H_
13
14#include <curl/curl.h>
15
16#include <cstddef>
17#include <memory>
18#include <string>
19#include <string_view>
20
21#include "curl_fuzzer.pb.h"
25
26namespace proto_fuzzer {
27
28class MockConnection;
29
45class PingpongMockServer : public MockServerBase, public IncomingDataObserver {
46 public:
48
49 PingpongMockServer(const PingpongMockServer&) = delete;
50 PingpongMockServer& operator=(const PingpongMockServer&) = delete;
51
52 // Bounded record of complete command lines sent by curl.
53 const std::string& control_transcript() const;
54
55 // Bounded record of the body most recently drained by a body phase. Peers
56 // without an upload direction keep this empty.
57 const std::string& uploaded_data() const;
58
59 // Number of opt-in multi-poll probes issued while the most recent transfer
60 // was active and its control socket had been handed to curl.
61 std::size_t active_pollset_probe_count() const;
62
63 // Number of opt-in probes where curl requested writable readiness while
64 // the peer held an incomplete control command. A positive count exposes a
65 // pending protocol send rather than a transient writable state.
66 std::size_t writable_pollset_probe_count() const;
67
68 protected:
69 explicit PingpongMockServer(MultiDrivePolicy drive_policy = MultiDrivePolicy::kPerform);
70
71 // Create the transport that carries the exchange. Overridden by TLS peers.
72 virtual std::unique_ptr<MockConnection> CreateConnection();
73
76 virtual void HandleLine(std::string_view line) = 0;
77
78 // Body phase control. Only valid while HandleLine() is running; the parser
79 // consumes the body before processing further command lines.
80
81 // Consume bytes through the first occurrence of `terminator`, capturing the
82 // body and the terminator itself. Used by SMTP's "\r\n.\r\n" end-of-body.
83 void BeginMarkerBody(std::string_view terminator);
84
85 // Consume exactly `length` bytes, capturing them, followed by a CRLF line
86 // terminator that is not captured. Used by IMAP's {N} literal uploads.
87 void BeginLiteralBody(std::size_t length);
88
89 // Consume exactly `length` bytes, capturing them, with no trailing
90 // terminator. Used by protocols such as RTSP whose announced entity body is
91 // followed immediately by the next request or by the peer's close.
92 void BeginExactBody(std::size_t length);
93
94 // Drain all subsequent carrier bytes without command parsing or capture.
95 // Plaintext upgrade peers use this after accepting STARTTLS because the
96 // following TLS handshake is intentionally opaque to the line parser.
97 void BeginOpaqueDrain();
98
101 virtual void HandleBodyComplete() {}
102
105 virtual void QueueShutdownReply() {}
106
109 virtual void ResetProtocolState() {}
110
111 curl_socket_t HandleOpenSocket(curlsocktype purpose = CURLSOCKTYPE_IPCXN,
112 struct curl_sockaddr* address = nullptr) override;
113
114 void RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) override;
115
116 void HandleDetachedMulti(CurlMultiPtr multi) override;
117
118 void ObserveIncomingData(const unsigned char* data, std::size_t size) override;
119
122 const curl::fuzzer::proto::Scenario* scenario() const { return scenario_; }
123
124 // Return the next command-aligned reply, or nullptr once the bounded script
125 // is exhausted. Empty entries still advance the cursor.
126 const std::string* NextControlReply();
127
128 // Queue one reply fragment, returning false only when the transport write
129 // failed. An empty fragment is a successful no-op.
130 bool WriteControlBytes(std::string_view bytes);
131
132 // Write the next scripted reply. Returns false when none remains.
133 bool WriteNextReply();
134
135 // Half-close the peer's write side after a protocol-level transport
136 // upgrade response. The existing socket remains readable so the bounded
137 // driver can drain client handshake bytes without parsing them as commands.
139
140 // First and second whitespace-delimited tokens of a command line, without
141 // interpreting any protocol-specific arguments.
142 static std::string_view FirstToken(std::string_view line);
143 static std::string_view SecondToken(std::string_view line);
144
145 // Compare an ASCII protocol token to an uppercase literal, ignoring case.
146 static bool TokenEquals(std::string_view token, std::string_view expected_uppercase);
147
148 private:
150 enum class BodyMode {
151 kCommand,
152 kMarker,
153 kLiteral,
154 kExact,
155 kOpaque,
156 };
157
158 // Observability must not grow with a future curl loop; these caps exceed the
159 // normalized upload and command budgets without affecting draining.
160 static constexpr std::size_t kMaxCapturedControlBytes = 64 * 1024;
161 static constexpr std::size_t kMaxCapturedUploadBytes = 2 * scenario_limits::kMaxUploadBytes;
162
163 void ResetForScenario(const curl::fuzzer::proto::Scenario& scenario);
164
165 static void ApplyScriptBackpressure(MockConnection* connection, const curl::fuzzer::proto::Connection& script);
166
167 bool ServiceControlConnection();
168
169 void FinishConnections();
170
171 // Retain a bounded prefix for assertions without affecting parser progress.
172 static void CapturePrefix(std::string_view source, std::size_t limit, std::string* destination);
173
175 const curl::fuzzer::proto::Scenario* scenario_;
177 std::size_t control_reply_count_;
179 std::size_t next_control_reply_;
181 std::string pending_control_bytes_;
183 std::string control_transcript_;
185 std::string uploaded_data_;
187 std::size_t active_pollset_probe_count_;
189 std::size_t writable_pollset_probe_count_;
191 std::string body_marker_;
193 std::size_t body_length_remaining_;
195 std::size_t body_terminator_remaining_;
197 BodyMode body_mode_;
199 curl_socket_t control_socket_;
201 bool control_opened_;
202};
203
204} // namespace proto_fuzzer
205
206#endif // PROTO_FUZZER_PINGPONG_MOCK_SERVER_H_
Receives bytes after any transport decoding.
Owns one half of a socketpair used to feed canned responses to libcurl.
Definition mock_server.h:29
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.
Definition curl_raii.h:58
Optional observer for application bytes consumed by mock peers.
MockServerBase — common plumbing shared by every protocol-specific mock server.
MultiDrivePolicy
Fixed policy for interpreting a scenario's multi-drive fields.
@ kPerform
Use the ordinary curl_multi_perform loop.
Shared complexity limits for structured scenarios.