curl-fuzzer C++ reference
Loading...
Searching...
No Matches
multi_socket_driver.cc
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
9
11
12#include <poll.h>
13
14#include <array>
15#include <cstddef>
16#include <cstdint>
17
18namespace proto_fuzzer {
19
20namespace {
21
23short PollEventsForInterest(int interest) {
24 short events = 0;
25 if ((interest & CURL_POLL_IN) != 0) {
26 events |= POLLIN;
27 }
28 if ((interest & CURL_POLL_OUT) != 0) {
29 events |= POLLOUT;
30 }
31 return events;
32}
33
35int CurlEventsForPollResult(short events) {
36 int result = 0;
37 if ((events & (POLLIN | POLLHUP)) != 0) {
38 // A stream hangup remains readable until curl consumes EOF.
39 result |= CURL_CSELECT_IN;
40 }
41 if ((events & POLLOUT) != 0) {
42 result |= CURL_CSELECT_OUT;
43 }
44 if ((events & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
45 result |= CURL_CSELECT_ERR;
46 }
47 return result;
48}
49
50} // namespace
51
55 : multi_(nullptr), timeout_ms_(-1), timer_pending_(false), watch_exhausted_(false), generation_(0) {}
56
60
67bool MultiSocketDriver::Install(CURLM* multi) {
68 multi_ = multi;
69 if (multi_ == nullptr) {
70 return false;
71 }
72 return curl_multi_setopt(multi_, CURLMOPT_SOCKETFUNCTION, &MultiSocketDriver::SocketCallback) == CURLM_OK &&
73 curl_multi_setopt(multi_, CURLMOPT_SOCKETDATA, this) == CURLM_OK &&
74 curl_multi_setopt(multi_, CURLMOPT_TIMERFUNCTION, &MultiSocketDriver::TimerCallback) == CURLM_OK &&
75 curl_multi_setopt(multi_, CURLMOPT_TIMERDATA, this) == CURLM_OK;
76}
77
84CURLMcode MultiSocketDriver::Start(int* running_handles) {
85 if (multi_ == nullptr) {
86 return CURLM_BAD_HANDLE;
87 }
88 timer_pending_ = false;
89 return curl_multi_socket_action(multi_, CURL_SOCKET_TIMEOUT, 0, running_handles);
90}
91
102 DriveResult result;
103 if (multi_ == nullptr) {
104 result.code = CURLM_BAD_HANDLE;
105 return result;
106 }
107
108 std::array<struct pollfd, kMaxWatches> poll_fds{};
109 std::size_t poll_count = 0;
110 for (const Watch& watch : watches_) {
111 if (!watch.active) {
112 continue;
113 }
114 poll_fds[poll_count].fd = watch.socket;
115 poll_fds[poll_count].events = PollEventsForInterest(watch.interest);
116 ++poll_count;
117 }
118
119 const std::uint64_t generation_before = generation_;
120 const int running_before = running_handles == nullptr ? 0 : *running_handles;
121 bool action_dispatched = false;
122 if (poll_count != 0 && ::poll(poll_fds.data(), static_cast<nfds_t>(poll_count), 0) > 0) {
123 for (std::size_t index = 0; index < poll_count; ++index) {
124 if (running_handles != nullptr && *running_handles == 0) {
125 break;
126 }
127 // An earlier action can synchronously remove or repurpose any later fd
128 // in the snapshot. Revalidate it against the callback-owned table so a
129 // stale readiness notification never reaches curl under a new meaning.
130 Watch* current = FindWatch(poll_fds[index].fd);
131 if (current == nullptr) {
132 continue;
133 }
134 const int events = CurlEventsForPollResult(poll_fds[index].revents);
135 if (events == 0) {
136 continue;
137 }
138 result.code = curl_multi_socket_action(multi_, poll_fds[index].fd, events, running_handles);
139 action_dispatched = true;
140 // Process at most one snapshot entry. Its callbacks can repurpose an fd
141 // with the same numeric value and interest, which no post-hoc lookup can
142 // distinguish; the outer loop rebuilds readiness from fresh watches.
143 break;
144 }
145 }
146
147 if (result.code == CURLM_OK && timer_pending_ && timeout_ms_ == 0) {
148 // Clear first: the action may synchronously install another zero timer,
149 // which belongs to the next outer loop turn rather than recursive work.
150 timer_pending_ = false;
151 result.code = curl_multi_socket_action(multi_, CURL_SOCKET_TIMEOUT, 0, running_handles);
152 action_dispatched = true;
153 }
154
155 const int running_after = running_handles == nullptr ? 0 : *running_handles;
156 // A successful action can consume buffered protocol bytes without changing
157 // either callbacks or handle count, so dispatch itself is observable
158 // progress. The outer fixed operation cap still bounds permanently-ready
159 // sockets.
160 result.made_progress = action_dispatched || generation_ != generation_before || running_after != running_before;
161 return result;
162}
163
169 if (multi_ == nullptr) {
170 return;
171 }
172 long timeout_ms = -1;
173 (void)curl_multi_timeout(multi_, &timeout_ms);
174 (void)curl_multi_wakeup(multi_);
175}
176
179int MultiSocketDriver::SocketCallback(CURL* /*easy*/, curl_socket_t socket, int what, void* user_data,
180 void* socket_data) {
181 return static_cast<MultiSocketDriver*>(user_data)->UpdateSocket(socket, what, socket_data);
182}
183
187int MultiSocketDriver::TimerCallback(CURLM* /*multi*/, long timeout_ms, void* user_data) {
188 return static_cast<MultiSocketDriver*>(user_data)->UpdateTimer(timeout_ms);
189}
190
195int MultiSocketDriver::UpdateSocket(curl_socket_t socket, int what, void* socket_data) {
196 Watch* watch = FindWatch(socket);
197 if (what == CURL_POLL_REMOVE) {
198 if (watch != nullptr) {
199 (void)curl_multi_assign(multi_, socket, nullptr);
200 watch->active = false;
201 watch->socket = CURL_SOCKET_BAD;
202 watch->interest = CURL_POLL_NONE;
203 ++generation_;
204 }
205 return 0;
206 }
207
208 if (watch == nullptr) {
209 watch = FindFreeWatch();
210 if (watch == nullptr) {
211 watch_exhausted_ = true;
212 return -1;
213 }
214 watch->socket = socket;
215 watch->active = true;
216 (void)curl_multi_assign(multi_, socket, watch);
217 ++generation_;
218 } else if (socket_data != nullptr && socket_data != watch) {
219 // Reassert the stable association if an unusual transition supplied a
220 // different application pointer. Never dereference foreign socket_data.
221 (void)curl_multi_assign(multi_, socket, watch);
222 }
223
224 if (watch->interest != what) {
225 watch->interest = what;
226 ++generation_;
227 }
228 return 0;
229}
230
235int MultiSocketDriver::UpdateTimer(long timeout_ms) {
236 if (!timer_pending_ || timeout_ms_ != timeout_ms) {
237 ++generation_;
238 }
239 timeout_ms_ = timeout_ms;
240 timer_pending_ = timeout_ms >= 0;
241 return 0;
242}
243
246MultiSocketDriver::Watch* MultiSocketDriver::FindWatch(curl_socket_t socket) {
247 for (Watch& watch : watches_) {
248 if (watch.active && watch.socket == socket) {
249 return &watch;
250 }
251 }
252 return nullptr;
253}
254
258MultiSocketDriver::Watch* MultiSocketDriver::FindFreeWatch() {
259 for (Watch& watch : watches_) {
260 if (!watch.active) {
261 return &watch;
262 }
263 }
264 return nullptr;
265}
266
267} // namespace proto_fuzzer
Owns the socket and timer callback state required by libcurl's event-driven multi API.
CURLMcode Start(int *running_handles)
Start libcurl's socket state machine through the documented timeout pseudo-socket without waiting for...
bool Install(CURLM *multi)
Install socket/timer callbacks on a caller-owned multi before adding an easy handle.
MultiSocketDriver()
Construct detached callback state.
void ProbeControlApis()
Exercise the wakeup and timeout-query APIs from a valid live-multi state.
~MultiSocketDriver()
The owner deliberately destroys this after curl_multi_cleanup, so there is no callback deregistration...
DriveResult DriveReady(int *running_handles)
Run one bounded, non-blocking event-loop turn: poll the callback-provided watch set with a zero timeo...
Bounded event-loop state for driving curl_multi_socket_action.
Result from one non-blocking event-loop turn.
bool made_progress
True when an action ran or callback/handle state changed.
CURLMcode code
libcurl's result for the last socket action in the turn.