/src/curl_fuzzer/proto_fuzzer/mock_server_base.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
3 | | * |
4 | | * SPDX-License-Identifier: curl |
5 | | */ |
6 | | |
7 | | /// @file |
8 | | /// @brief MockServerBase — common plumbing shared by every protocol-specific |
9 | | /// mock server. Owns the MockConnection, installs the OPENSOCKET / |
10 | | /// SOCKOPT trampolines, and exposes a single DriveScenario entrypoint |
11 | | /// that each subclass specialises for its protocol. |
12 | | |
13 | | #ifndef PROTO_FUZZER_MOCK_SERVER_BASE_H_ |
14 | | #define PROTO_FUZZER_MOCK_SERVER_BASE_H_ |
15 | | |
16 | | #include <curl/curl.h> |
17 | | |
18 | | #include <cstddef> |
19 | | #include <functional> |
20 | | #include <memory> |
21 | | #include <utility> |
22 | | |
23 | | #include "curl_fuzzer.pb.h" |
24 | | #include "proto_fuzzer/curl_raii.h" |
25 | | |
26 | | namespace proto_fuzzer { |
27 | | |
28 | | class MockConnection; |
29 | | class MultiSocketDriver; |
30 | | class ScenarioRequestData; |
31 | | |
32 | | /// Tell curl whether an application-provided socket still needs its normal |
33 | | /// connect/options setup or already represents a connected stream transport. |
34 | | /// This is explicit peer metadata: querying the descriptor from a callback is |
35 | | /// both redundant and unavailable in some fuzzing sandboxes. |
36 | | enum class SocketSetupDisposition { |
37 | | kNeedsSetup, |
38 | | kAlreadyConnected, |
39 | | }; |
40 | | |
41 | | /// Results from the bounded CONNECT_ONLY public-API probe. Keeping byte counts |
42 | | /// makes focused tests distinguish a successful connection from actual |
43 | | /// curl_easy_send/curl_easy_recv execution. |
44 | | struct ConnectOnlyRunStats { |
45 | | CURLcode connect_result = CURLE_FAILED_INIT; ///< Result of establishing the connect-only transport. |
46 | | CURLcode send_result = CURLE_FAILED_INIT; ///< Final result returned by curl_easy_send. |
47 | | CURLcode recv_result = CURLE_FAILED_INIT; ///< Final result returned by curl_easy_recv. |
48 | | std::size_t sent_bytes = 0; ///< Total application bytes accepted by curl_easy_send. |
49 | | std::size_t received_bytes = 0; ///< Total application bytes returned by curl_easy_recv. |
50 | | }; |
51 | | |
52 | | /// Fixed policy for interpreting a scenario's multi-drive fields. |
53 | | enum class MultiDrivePolicy { |
54 | | kPerform, ///< Use the ordinary curl_multi_perform loop. |
55 | | kSocketAction, ///< Use the callback-driven curl_multi_socket_action loop. |
56 | | kFromApiPlan, ///< Read the multi-loop choice and probes from Scenario::api_plan. |
57 | | }; |
58 | | |
59 | | /// @class proto_fuzzer::MockServerBase |
60 | | /// @brief Abstract base for protocol-specific in-process mock servers. Owns a |
61 | | /// single MockConnection (the socketpair curl talks to) and dispatches |
62 | | /// the OPENSOCKET callback through a virtual HandleOpenSocket so the |
63 | | /// subclass can decide whether to write anything immediately. Each |
64 | | /// subclass overrides RunLoop to seed itself from the Scenario proto |
65 | | /// and run its own perform loop; the base's DriveScenario owns the |
66 | | /// curl_multi handle around that call. |
67 | | class MockServerBase { |
68 | | public: |
69 | | virtual ~MockServerBase(); |
70 | | |
71 | | MockServerBase(const MockServerBase&) = delete; |
72 | | MockServerBase& operator=(const MockServerBase&) = delete; |
73 | | |
74 | | virtual void Install(CURL* easy); |
75 | | |
76 | | virtual void ConfigureRequestData(ScenarioRequestData* request_data); |
77 | | |
78 | | CURLcode DriveScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario); |
79 | | |
80 | | virtual CURLcode DriveEasyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario, |
81 | | bool use_events = false); |
82 | | |
83 | | virtual ConnectOnlyRunStats DriveConnectOnlyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario); |
84 | | |
85 | | /// Callback type used to publish the driving multi handle to observers. |
86 | | using MultiObserver = std::function<void(CURLM*)>; |
87 | | |
88 | | /// Register an observer invoked with the live multi handle after 'easy' is |
89 | | /// attached and before the drive loop starts. The API lifecycle lane uses it |
90 | | /// so public API probes fired from inside callbacks can target the multi that |
91 | | /// actually owns the transfer. Passing an empty observer clears it. |
92 | | /// @param observer Callback receiving the live multi handle. |
93 | 5.79k | void SetMultiObserver(MultiObserver observer) { multi_observer_ = std::move(observer); } |
94 | | |
95 | | MockConnection* connection(); |
96 | | |
97 | | protected: |
98 | | explicit MockServerBase(MultiDrivePolicy drive_policy = MultiDrivePolicy::kPerform); |
99 | | |
100 | | virtual void HandleDetachedMulti(CurlMultiPtr multi); |
101 | | |
102 | | /// Subclass hook invoked by the OPENSOCKET trampoline. The subclass owns the |
103 | | /// decision to construct `connection_`, push any initial bytes, and hand the |
104 | | /// client fd back to libcurl. Passing curl's mutable destination through is |
105 | | /// essential for datagram protocols: TFTP must replace the nominal URL port |
106 | | /// with its per-scenario loopback request socket before curl records the |
107 | | /// address used by sendto(). Stream socketpair peers simply ignore it. |
108 | | /// @param purpose The role curl intends the new socket to serve. |
109 | | /// @param address Mutable destination and native socket description. |
110 | | /// @return the client-side fd to hand to libcurl, or CURL_SOCKET_BAD. |
111 | | virtual curl_socket_t HandleOpenSocket(curlsocktype purpose, struct curl_sockaddr* address) = 0; |
112 | | |
113 | | virtual SocketSetupDisposition GetSocketSetupDisposition(curl_socket_t curlfd, curlsocktype purpose) const; |
114 | | |
115 | | /// Subclass hook invoked from DriveScenario. Runs the protocol-specific |
116 | | /// perform loop against a caller-owned multi that already has 'easy' added. |
117 | | /// @param multi multi handle owned by DriveScenario; easy already added. |
118 | | /// @param easy the curl easy handle attached to the mock. |
119 | | /// @param scenario the Scenario proto to drive. |
120 | | virtual void RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) = 0; |
121 | | |
122 | | static int WaitOnMultiFdset(CURLM* multi, CURLMcode* rc); |
123 | | |
124 | | static void ProbeMultiPollset(CURLM* multi); |
125 | | |
126 | | MultiSocketDriver* multi_socket_driver(); |
127 | | |
128 | | std::size_t additional_handle_cleanup_count() const; |
129 | | |
130 | | void ResumeResponseIfRequested(CURL* easy); |
131 | | |
132 | | /// Hard operation budget for one scenario. This bounds cases that continue |
133 | | /// making tiny amounts of progress (for example a one-byte backpressure |
134 | | /// drain) without relying on wall-clock time. |
135 | | static constexpr int kMaxDriveIterations = 512; |
136 | | |
137 | | /// Consecutive no-progress budget for ordinary scenarios. Socketpair I/O is |
138 | | /// local and should settle immediately, so a handful of extra performs is |
139 | | /// enough to flush curl's state machine without sleeping. |
140 | | static constexpr int kMaxIdleIterations = 8; |
141 | | |
142 | | /// Explicit backpressure scenarios get a larger idle budget and may use the |
143 | | /// short timed wait above. This keeps timeout/error branches reachable while |
144 | | /// ensuring ordinary mutations never inherit their cost. |
145 | | static constexpr int kMaxTimedIdleIterations = 256; |
146 | | |
147 | | static bool UsesTimedDrive(const curl::fuzzer::proto::Scenario& scenario); |
148 | | |
149 | | void ApplyPendingBackpressure(); |
150 | | |
151 | | /// The per-scenario MockConnection, lazily created by HandleOpenSocket(). |
152 | | /// Subclasses read/write through this pointer inside their RunLoop. |
153 | | std::unique_ptr<MockConnection> connection_; |
154 | | |
155 | | /// Cached SO_RCVBUF/SO_SNDBUF setting in bytes. Populated by DriveScenario |
156 | | /// before the first curl_multi_perform so HandleOpenSocket can consult it |
157 | | /// when it constructs the MockConnection. |
158 | | int pending_recv_buf_bytes_; |
159 | | /// Cached per-call DrainIncoming byte budget. Same population timing as |
160 | | /// pending_recv_buf_bytes_; 0 means unlimited (legacy drain behaviour). |
161 | | std::size_t pending_drain_limit_; |
162 | | |
163 | | /// Non-owning pointer into DriveScenario's stack. That scope encloses easy |
164 | | /// removal and multi cleanup, the complete interval in which callbacks may |
165 | | /// still fire. |
166 | | MultiSocketDriver* multi_socket_driver_; |
167 | | |
168 | | /// Number of application-created handles, excluding the caller's parent, |
169 | | /// successfully removed from the current multi and cleaned up. |
170 | | std::size_t additional_handle_cleanup_count_; |
171 | | |
172 | | /// True only for an API-plan multi drive with its one-shot write callback. |
173 | | bool resume_response_; |
174 | | |
175 | | /// Observer receiving the live multi handle at the start of a drive. |
176 | | MultiObserver multi_observer_; |
177 | | |
178 | | private: |
179 | | /// Target-authorized source of multi execution behavior. |
180 | | MultiDrivePolicy drive_policy_; |
181 | | |
182 | | friend curl_socket_t MockServerBaseOpenSocketTrampoline(void*, curlsocktype, struct curl_sockaddr*); |
183 | | friend int MockServerBaseSockOptTrampoline(void*, curl_socket_t, curlsocktype); |
184 | | }; |
185 | | |
186 | | } // namespace proto_fuzzer |
187 | | |
188 | | #endif // PROTO_FUZZER_MOCK_SERVER_BASE_H_ |