Coverage Report

Created: 2026-04-28 07:09

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 = 10 * 1000;  // 10 ms
23
24
/// @brief Noop function to satisfy CURLOPT_SOCKOPTFUNCTION.
25
/// @return CURL_SOCKOPT_ALREADY_CONNECTED: the socketpair is already connected.
26
2.97k
int SockOptTrampoline(void* /*clientp*/, curl_socket_t /*curlfd*/, curlsocktype /*purpose*/) {
27
2.97k
  return CURL_SOCKOPT_ALREADY_CONNECTED;
28
2.97k
}
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
3.25k
                                                 struct curl_sockaddr* /*address*/) {
38
3.25k
  return static_cast<MockServerBase*>(clientp)->HandleOpenSocket();
39
3.25k
}
40
41
/// Default-construct an empty base instance with no connection.
42
3.28k
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
3.28k
MockServerBase::~MockServerBase() = default;
48
49
/// @return the owned MockConnection, or nullptr if one has not been opened.
50
126
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
3.28k
void MockServerBase::Install(CURL* easy) {
55
3.28k
  curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, &MockServerBaseOpenSocketTrampoline);
56
3.28k
  curl_easy_setopt(easy, CURLOPT_OPENSOCKETDATA, this);
57
3.28k
  curl_easy_setopt(easy, CURLOPT_SOCKOPTFUNCTION, &SockOptTrampoline);
58
3.28k
}
59
60
/// Allocate a multi, attach 'easy', delegate to the subclass RunLoop, clean
61
/// up. Failures in multi_init / add_handle silently no-op: the fuzzer cares
62
/// about what curl does when driven, not about harness-level errors.
63
3.28k
void MockServerBase::DriveScenario(CURL* easy, const curl::fuzzer::proto::Scenario& scenario) {
64
  // Cache backpressure knobs so HandleOpenSocket can apply them the moment
65
  // connection_ exists. Both default to 0, which matches the legacy "drain
66
  // greedily, kernel-default buffers" behaviour exactly.
67
3.28k
  const auto& bp = scenario.connection().backpressure();
68
3.28k
  pending_recv_buf_bytes_ = static_cast<int>(bp.recv_buf_bytes());
69
3.28k
  pending_drain_limit_ = static_cast<std::size_t>(bp.drain_limit());
70
71
3.28k
  CURLM* multi = curl_multi_init();
72
3.28k
  if (multi == nullptr) {
73
0
    return;
74
0
  }
75
3.28k
  if (curl_multi_add_handle(multi, easy) == CURLM_OK) {
76
3.28k
    RunLoop(multi, easy, scenario);
77
3.28k
    curl_multi_remove_handle(multi, easy);
78
3.28k
  }
79
3.28k
  curl_multi_cleanup(multi);
80
3.28k
}
81
82
/// Hand the cached backpressure config to the connection. Safe to call when
83
/// connection_ is null (no-op) or when both knobs are 0 (ApplyBackpressure
84
/// itself is a no-op in that case).
85
2.97k
void MockServerBase::ApplyPendingBackpressure() {
86
2.97k
  if (connection_) {
87
2.97k
    connection_->ApplyBackpressure(pending_recv_buf_bytes_, pending_drain_limit_);
88
2.97k
  }
89
2.97k
}
90
91
/// Wait on curl's fdset with a short timeout. Returns select()'s result; on
92
/// error sets *rc to the corresponding CURLMcode.
93
64.3k
int MockServerBase::WaitOnMultiFdset(CURLM* multi, CURLMcode* rc) {
94
64.3k
  fd_set readfds;
95
64.3k
  fd_set writefds;
96
64.3k
  fd_set excfds;
97
64.3k
  FD_ZERO(&readfds);
98
64.3k
  FD_ZERO(&writefds);
99
64.3k
  FD_ZERO(&excfds);
100
64.3k
  int maxfd = -1;
101
64.3k
  *rc = curl_multi_fdset(multi, &readfds, &writefds, &excfds, &maxfd);
102
64.3k
  if (*rc != CURLM_OK) {
103
0
    return -1;
104
0
  }
105
64.3k
  if (maxfd < 0) {
106
0
    return 0;
107
0
  }
108
64.3k
  struct timeval timeout;
109
64.3k
  timeout.tv_sec = 0;
110
64.3k
  timeout.tv_usec = kSelectTimeoutUs;
111
64.3k
  return ::select(maxfd + 1, &readfds, &writefds, &excfds, &timeout);
112
64.3k
}
113
114
}  // namespace proto_fuzzer