Coverage Report

Created: 2026-10-15 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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_