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.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_