Coverage Report

Created: 2026-08-31 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/mock_server_base.cc
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 Implementation of MockServerBase — shared trampolines, shared
9
///        select() helper, DriveScenario multi-handle RAII, and the scheme
10
///        classifier that produces the right subclass for a Scenario.
11
12
#include "proto_fuzzer/mock_server_base.h"
13
14
#include <sys/select.h>
15
16
#include "proto_fuzzer/mock_server.h"
17
18
namespace proto_fuzzer {
19
20
namespace {
21
22
constexpr long kSelectTimeoutUs = 1000;  // 1 ms; explicit timing cases only.
23
24
/// @brief Noop function to satisfy CURLOPT_SOCKOPTFUNCTION.
25
/// @return CURL_SOCKOPT_ALREADY_CONNECTED: the socketpair is already connected.
26
9.66k
int SockOptTrampoline(void* /*clientp*/, curl_socket_t /*curlfd*/, curlsocktype /*purpose*/) {
27
9.66k
  return CURL_SOCKOPT_ALREADY_CONNECTED;
28
9.66k
}
29
30
}  // namespace
31
32
/// @brief C trampoline for CURLOPT_OPENSOCKETFUNCTION. Declared at namespace
33
///        scope so it can be a friend of MockServerBase.
34
/// @param clientp Pointer to the MockServerBase instance.
35
/// @return The client-side socket fd as a curl_socket_t.
36
curl_socket_t MockServerBaseOpenSocketTrampoline(void* clientp, curlsocktype /*purpose*/,
37
10.5k
                                                 struct curl_sockaddr* /*address*/) {
38
10.5k
  return static_cast<MockServerBase*>(clientp)->HandleOpenSocket();
39
10.5k
}
40
41
/// Default-construct an empty base instance with no connection.
42
10.1k
MockServerBase::MockServerBase() : connection_(nullptr), pending_recv_buf_bytes_(0), pending_drain_limit_(0) {}
43
44
/// Out-of-line destructor so MockConnection can stay forward-declared in the
45
/// base header (its complete type is only needed where unique_ptr is
46
/// instantiated for destruction).
47
10.1k
MockServerBase::~MockServerBase() = default;
48
49
/// @return the owned MockConnection, or nullptr if one has not been opened.
50
26.9k
MockConnection* MockServerBase::connection() { return connection_.get(); }
51
52
/// Install the common socket-callback trio. All subclasses share the same
53
/// trampoline; dispatch to the subclass happens through HandleOpenSocket().
54
10.1k
void MockServerBase::Install(CURL* easy) {
55
10.1k
  curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, &MockServerBaseOpenSocketTrampoline);
56
10.1k
  curl_easy_setopt(easy, CURLOPT_OPENSOCKETDATA, this);
57
10.1k
  curl_easy_setopt(easy, CURLOPT_SOCKOPTFUNCTION, &SockOptTrampoline);
58
10.1k
}
59
60
/// Allocate a multi, attach 'easy', delegate to the subclass RunLoop, consume
61
/// its completion message, and clean up. Failures in multi_init / add_handle
62
/// silently no-op: the fuzzer cares about what curl does when driven, not
63
/// about harness-level errors.
64
10.1k
void MockServerBase::DriveScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
65
  // Cache backpressure knobs so HandleOpenSocket can apply them the moment
66
  // connection_ exists. Both default to 0, which matches the legacy "drain
67
  // greedily, kernel-default buffers" behaviour exactly.
68
10.1k
  const auto& bp = scenario.connection().backpressure();
69
10.1k
  pending_recv_buf_bytes_ = static_cast<int>(bp.recv_buf_bytes());
70
10.1k
  pending_drain_limit_ = static_cast<std::size_t>(bp.drain_limit());
71
72
10.1k
  CURLM* multi = curl_multi_init();
73
10.1k
  if (multi == nullptr) {
74
0
    return;
75
0
  }
76
10.1k
  if (curl_multi_add_handle(multi, easy) == CURLM_OK) {
77
10.1k
    RunLoop(multi, easy, scenario);
78
79
    // Completion messages are the multi API's only durable record of the
80
    // transfer result. Consume them while the easy handle is still attached:
81
    // otherwise every scenario systematically skips curl_multi_info_read's
82
    // result path and removal discards the opportunity. With one easy handle
83
    // attached, this drain has at most one completion message regardless of
84
    // fuzzed response size or redirect count.
85
10.1k
    int messages_remaining = 0;
86
19.4k
    while (curl_multi_info_read(multi, &messages_remaining) != nullptr) {
87
9.26k
    }
88
89
10.1k
    curl_multi_remove_handle(multi, easy);
90
10.1k
  }
91
10.1k
  curl_multi_cleanup(multi);
92
10.1k
}
93
94
/// Hand the cached backpressure config to the connection. Safe to call when
95
/// connection_ is null (no-op) or when both knobs are 0 (ApplyBackpressure
96
/// itself is a no-op in that case).
97
1.93k
void MockServerBase::ApplyPendingBackpressure() {
98
1.93k
  if (connection_) {
99
1.93k
    connection_->ApplyBackpressure(pending_recv_buf_bytes_, pending_drain_limit_);
100
1.93k
  }
101
1.93k
}
102
103
/// Treat a non-default BackpressureConfig as an explicit request for the
104
/// slower, timed drive policy. Proto3 scalar defaults make this deterministic:
105
/// a present-but-empty message remains on the ordinary fast path.
106
/// @param scenario Scenario whose backpressure settings select the policy.
107
/// @return true when the scenario explicitly opted into socket backpressure.
108
2.21k
bool MockServerBase::UsesTimedDrive(const curl::fuzzer::proto::Scenario& scenario) {
109
2.21k
  const auto& bp = scenario.connection().backpressure();
110
2.21k
  return bp.recv_buf_bytes() != 0 || bp.drain_limit() != 0;
111
2.21k
}
112
113
/// Wait on curl's fdset with a short timeout. Returns select()'s result; on
114
/// error sets *rc to the corresponding CURLMcode.
115
14.5k
int MockServerBase::WaitOnMultiFdset(CURLM* multi, CURLMcode* rc) {
116
14.5k
  fd_set readfds;
117
14.5k
  fd_set writefds;
118
14.5k
  fd_set excfds;
119
14.5k
  FD_ZERO(&readfds);
120
14.5k
  FD_ZERO(&writefds);
121
14.5k
  FD_ZERO(&excfds);
122
14.5k
  int maxfd = -1;
123
14.5k
  *rc = curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd);
124
14.5k
  if (*rc != CURLM_OK) {
125
0
    return -1;
126
0
  }
127
14.5k
  if (maxfd < 0) {
128
0
    return 0;
129
0
  }
130
14.5k
  struct timeval timeout;
131
14.5k
  timeout.tv_sec = 0;
132
14.5k
  timeout.tv_usec = kSelectTimeoutUs;
133
14.5k
  return ::select(maxfd + 1, &readfds, &writefds, &excfds, &timeout);
134
14.5k
}
135
136
/// Exercise curl_multi_poll's pollset/filter traversal once without sleeping.
137
/// The result is deliberately ignored: this is an API/state probe, while the
138
/// protocol-specific perform loop remains the authority on transfer progress.
139
807
void MockServerBase::ProbeMultiPollset(CURLM* multi) {
140
807
  int numfds = 0;
141
807
  (void)curl_multi_poll(multi, nullptr, 0, 0, &numfds);
142
807
}
143
144
}  // namespace proto_fuzzer