/src/curl_fuzzer/proto_fuzzer/mock_server.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 MockConnection and MockServer — the in-process peer that feeds |
9 | | /// canned response bytes to libcurl over a socketpair. |
10 | | |
11 | | #ifndef PROTO_FUZZER_MOCK_SERVER_H_ |
12 | | #define PROTO_FUZZER_MOCK_SERVER_H_ |
13 | | |
14 | | #include <curl/curl.h> |
15 | | |
16 | | #include <array> |
17 | | #include <cstddef> |
18 | | #include <memory> |
19 | | #include <string> |
20 | | #include <vector> |
21 | | |
22 | | #include "curl_fuzzer.pb.h" |
23 | | #include "proto_fuzzer/incoming_data_observer.h" |
24 | | #include "proto_fuzzer/mock_server_base.h" |
25 | | #include "proto_fuzzer/scenario_limits.h" |
26 | | |
27 | | namespace proto_fuzzer { |
28 | | |
29 | | class MockConnection { |
30 | | public: |
31 | | MockConnection(); |
32 | | virtual ~MockConnection(); |
33 | | |
34 | | MockConnection(const MockConnection&) = delete; |
35 | | MockConnection& operator=(const MockConnection&) = delete; |
36 | | |
37 | | virtual bool ok() const; |
38 | | curl_socket_t take_client_fd(); |
39 | | int server_fd() const; |
40 | | |
41 | | std::size_t client_send_buffer_size() const; |
42 | | |
43 | | bool EnsureClientSendBufferSize(std::size_t minimum); |
44 | | |
45 | | virtual bool WriteAll(const unsigned char* data, std::size_t size); |
46 | | virtual std::size_t DrainIncoming(); |
47 | | void SetIncomingDataObserver(IncomingDataObserver* observer); |
48 | | void ReadAvailable(std::string* out); |
49 | | virtual void ShutdownWrite(); |
50 | | |
51 | | void ApplyBackpressure(int recv_buf_bytes, std::size_t drain_limit); |
52 | | |
53 | | protected: |
54 | | void NotifyIncomingData(const unsigned char* data, std::size_t size); |
55 | | |
56 | | private: |
57 | | int server_fd_; |
58 | | int client_fd_; |
59 | | std::size_t drain_limit_; |
60 | | IncomingDataObserver* incoming_data_observer_; |
61 | | }; |
62 | | |
63 | | /// @class proto_fuzzer::MockServer |
64 | | /// @brief HTTP in-process mock peer. Assigns one bounded response script to |
65 | | /// each socket curl opens, allowing redirects and authentication |
66 | | /// retries to progress without permitting an unbounded connection |
67 | | /// graph. WebSocketMockServer keeps its separate single-socket model. |
68 | | class MockServer : public MockServerBase { |
69 | | public: |
70 | | explicit MockServer(MultiDrivePolicy drive_policy = MultiDrivePolicy::kPerform); |
71 | | ~MockServer() override; |
72 | | |
73 | | void SetScripts(const curl::fuzzer::proto::Scenario& scenario); |
74 | | |
75 | | void SetKeepConnectionsOpen(bool keep_open); |
76 | | |
77 | | CURLcode DriveEasyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario, |
78 | | bool use_events = false) override; |
79 | | |
80 | | ConnectOnlyRunStats DriveConnectOnlyScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) override; |
81 | | |
82 | | bool DeliverNextChunk(); |
83 | | bool has_more_chunks() const; |
84 | | |
85 | | bool ServiceConnections(); |
86 | | |
87 | | std::size_t opened_connection_count() const; |
88 | | |
89 | | protected: |
90 | | virtual std::unique_ptr<MockConnection> CreateConnection(); |
91 | | |
92 | | void ResetConnections(); |
93 | | |
94 | | virtual void ObserveActiveTransfer(CURL* easy); |
95 | | |
96 | | curl_socket_t HandleOpenSocket(curlsocktype purpose = CURLSOCKTYPE_IPCXN, |
97 | | struct curl_sockaddr* address = nullptr) override; |
98 | | void RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) override; |
99 | | |
100 | | private: |
101 | | /// One socket's borrowed response configuration plus its delivery cursor. |
102 | | /// The fixed script array is fully populated before curl runs, so both this |
103 | | /// object and its pointer into the caller-owned Scenario remain stable across |
104 | | /// callbacks. Counts record the exact runtime-visible prefix: raw chunks come |
105 | | /// first and structured frames consume only the remaining shared budget. |
106 | | struct ConnectionScript { |
107 | | const curl::fuzzer::proto::Connection* connection = nullptr; |
108 | | std::size_t raw_chunk_count = 0; |
109 | | std::size_t frame_chunk_count = 0; |
110 | | std::size_t next_chunk = 0; |
111 | | |
112 | | /// @return Number of raw and structured chunks visible to the runtime. |
113 | 3.01M | std::size_t chunk_count() const { return raw_chunk_count + frame_chunk_count; } |
114 | | }; |
115 | | |
116 | | std::size_t DrainIncomingConnections(); |
117 | | |
118 | | void RunSocketActionLoop(CURLM* multi, CURL* easy); |
119 | | |
120 | | std::array<ConnectionScript, scenario_limits::kMaxConnections> scripts_; |
121 | | std::size_t script_count_; |
122 | | std::size_t next_script_; |
123 | | ConnectionScript* active_script_; |
124 | | |
125 | | /// True only while HandleOpenSocket is preparing a synchronous easy drive. |
126 | | /// Every response chunk must be queued before the callback returns because |
127 | | /// curl_easy_perform does not yield control to the mock. |
128 | | bool preload_all_chunks_; |
129 | | |
130 | | /// Suppress response-side half-close after the final scripted chunk. This |
131 | | /// is false for every historical target and enabled only by MultiPlan. |
132 | | bool keep_connections_open_; |
133 | | |
134 | | /// Old server halves must outlive their active role: libcurl owns the client |
135 | | /// fds and may close or briefly revisit them after opening the next socket. |
136 | | /// Destroying a MockConnection at that boundary would turn valid lifecycle |
137 | | /// traffic into harness-generated ECONNRESET/SIGPIPE behavior. |
138 | | std::vector<std::unique_ptr<MockConnection>> previous_connections_; |
139 | | }; |
140 | | |
141 | | } // namespace proto_fuzzer |
142 | | |
143 | | #endif // PROTO_FUZZER_MOCK_SERVER_H_ |