/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/mock_server_base.h" |
24 | | #include "proto_fuzzer/scenario_limits.h" |
25 | | |
26 | | namespace proto_fuzzer { |
27 | | |
28 | | class MockConnection { |
29 | | public: |
30 | | MockConnection(); |
31 | | ~MockConnection(); |
32 | | |
33 | | MockConnection(const MockConnection&) = delete; |
34 | | MockConnection& operator=(const MockConnection&) = delete; |
35 | | |
36 | | bool ok() const; |
37 | | curl_socket_t take_client_fd(); |
38 | | int server_fd() const; |
39 | | |
40 | | bool WriteAll(const unsigned char* data, std::size_t size); |
41 | | /// Drain bytes curl has written according to the configured per-call limit. |
42 | | /// @return number of bytes consumed during this call. |
43 | | std::size_t DrainIncoming(); |
44 | | void ReadAvailable(std::string* out); |
45 | | void ShutdownWrite(); |
46 | | |
47 | | /// Apply deterministic backpressure knobs. Set SO_RCVBUF on the server |
48 | | /// side (if recv_buf_bytes > 0) to cap how much curl can write before it |
49 | | /// short-writes / EAGAINs, and cap DrainIncoming()'s per-call byte budget |
50 | | /// (0 = unlimited). Must be called before any traffic for the recv_buf |
51 | | /// setting to matter. |
52 | | /// @param recv_buf_bytes SO_RCVBUF size in bytes, or 0 to leave default. |
53 | | /// @param drain_limit Max bytes drained per DrainIncoming call, 0 for unlimited. |
54 | | void ApplyBackpressure(int recv_buf_bytes, std::size_t drain_limit); |
55 | | |
56 | | private: |
57 | | int server_fd_; |
58 | | int client_fd_; |
59 | | std::size_t drain_limit_; |
60 | | }; |
61 | | |
62 | | /// @class proto_fuzzer::MockServer |
63 | | /// @brief HTTP in-process mock peer. Assigns one bounded response script to |
64 | | /// each socket curl opens, allowing redirects and authentication |
65 | | /// retries to progress without permitting an unbounded connection |
66 | | /// graph. WebSocketMockServer keeps its separate single-socket model. |
67 | | class MockServer : public MockServerBase { |
68 | | public: |
69 | | MockServer(); |
70 | | ~MockServer() override; |
71 | | |
72 | | /// Borrow the primary and bounded follow-on HTTP scripts from `scenario`. |
73 | | /// The caller must keep the scenario alive and unmodified until the current |
74 | | /// synchronous drive has finished. ScenarioRunner already provides exactly |
75 | | /// that lifetime, so retaining pointers avoids copying response bytes before |
76 | | /// curl has even requested the corresponding socket or chunk. |
77 | | void SetScripts(const curl::fuzzer::proto::Scenario& scenario); |
78 | | |
79 | | /// Deliver one queued response chunk. |
80 | | /// @return true when a chunk was consumed from the script. |
81 | | bool DeliverNextChunk(); |
82 | | bool has_more_chunks() const; |
83 | | |
84 | | protected: |
85 | | curl_socket_t HandleOpenSocket() override; |
86 | | void RunLoop(CURLM* multi, CURL* easy, const curl::fuzzer::proto::Scenario& scenario) override; |
87 | | |
88 | | private: |
89 | | /// One socket's borrowed response configuration plus its delivery cursor. |
90 | | /// The fixed script array is fully populated before curl runs, so both this |
91 | | /// object and its pointer into the caller-owned Scenario remain stable across |
92 | | /// callbacks. Counts record the exact runtime-visible prefix: raw chunks come |
93 | | /// first and structured frames consume only the remaining shared budget. |
94 | | struct ConnectionScript { |
95 | | const curl::fuzzer::proto::Connection* connection = nullptr; |
96 | | std::size_t raw_chunk_count = 0; |
97 | | std::size_t frame_chunk_count = 0; |
98 | | std::size_t next_chunk = 0; |
99 | | |
100 | | /// @return Number of raw and structured chunks visible to the runtime. |
101 | 73.1k | std::size_t chunk_count() const { return raw_chunk_count + frame_chunk_count; } |
102 | | }; |
103 | | |
104 | | /// Drain client request bytes from both the current socket and sockets curl |
105 | | /// has moved past. Keeping old peers responsive prevents a late write/close |
106 | | /// on a redirect source connection from stalling the new exchange. |
107 | | /// @return total bytes drained during this call. |
108 | | std::size_t DrainIncomingConnections(); |
109 | | |
110 | | std::array<ConnectionScript, scenario_limits::kMaxConnections> scripts_; |
111 | | std::size_t script_count_; |
112 | | std::size_t next_script_; |
113 | | ConnectionScript* active_script_; |
114 | | |
115 | | /// Old server halves must outlive their active role: libcurl owns the client |
116 | | /// fds and may close or briefly revisit them after opening the next socket. |
117 | | /// Destroying a MockConnection at that boundary would turn valid lifecycle |
118 | | /// traffic into harness-generated ECONNRESET/SIGPIPE behavior. |
119 | | std::vector<std::unique_ptr<MockConnection>> previous_connections_; |
120 | | }; |
121 | | |
122 | | } // namespace proto_fuzzer |
123 | | |
124 | | #endif // PROTO_FUZZER_MOCK_SERVER_H_ |