Coverage Report

Created: 2026-09-01 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/target_policy.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 the per-binary proto mutation policies.
9
10
#include "proto_fuzzer/target_policy.h"
11
12
#include <algorithm>
13
#include <cstdint>
14
#include <string>
15
16
#include "proto_fuzzer/scenario_limits.h"
17
#include "proto_fuzzer/telnet_scenario.h"
18
19
namespace proto_fuzzer {
20
21
namespace {
22
23
// Linux raises smaller socket-buffer requests to an implementation minimum,
24
// so 2048 is both cheap and reliably small enough to exercise short writes.
25
constexpr std::uint32_t kDefaultBackpressureBufferBytes = 2048;
26
27
// Values outside these ranges do not create useful new socket behavior for
28
// the harness's bounded 4-16 KiB writes. Keeping them small also prevents a
29
// mutated uint32 recv size from overflowing the int accepted by setsockopt.
30
constexpr std::uint32_t kMinBackpressureBufferBytes = 2048;
31
constexpr std::uint32_t kMaxBackpressureBufferBytes = 4096;
32
constexpr std::uint32_t kMaxDrainBytesPerIteration = 1024;
33
34
/// Remove a repeated-field suffix that the runtime would ignore. Doing this
35
/// in LPM's postprocessor matters for speed as well as memory: otherwise later
36
/// mutations keep rediscovering and editing objects that cannot reach curl.
37
template <typename RepeatedField>
38
173k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
173k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
173k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
173k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::SetOption> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::SetOption>*, unsigned long)
Line
Count
Source
38
22.6k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
22.6k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
22.6k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
22.6k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >(google::protobuf::RepeatedPtrField<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >*, unsigned long)
Line
Count
Source
38
90.1k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
90.1k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
90.1k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
90.1k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimePart> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimePart>*, unsigned long)
Line
Count
Source
38
6.93k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
6.93k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
6.93k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
6.93k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimeDataPart> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::MimeDataPart>*, unsigned long)
Line
Count
Source
38
4.03k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
4.03k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
4.03k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
4.03k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::WebSocketFrame> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::WebSocketFrame>*, unsigned long)
Line
Count
Source
38
27.2k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
27.2k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
27.2k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
27.2k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::TrimRepeated<google::protobuf::RepeatedPtrField<curl::fuzzer::proto::Connection> >(google::protobuf::RepeatedPtrField<curl::fuzzer::proto::Connection>*, unsigned long)
Line
Count
Source
38
22.6k
void TrimRepeated(RepeatedField* field, std::size_t limit) {
39
22.6k
  const std::size_t size = static_cast<std::size_t>(field->size());
40
22.6k
  if (size > limit) {
41
0
    field->DeleteSubrange(static_cast<int>(limit), static_cast<int>(size - limit));
42
0
  }
43
22.6k
}
44
45
/// Bound strings passed to NUL-terminated metadata APIs. The runtime applies
46
/// the same prefix, so deleting the invisible suffix increases useful
47
/// mutation density without removing any behavior curl could observe.
48
71.2k
void TrimMetadata(std::string* value) {
49
71.2k
  if (value->size() > scenario_limits::kMaxMetadataBytes) {
50
1
    value->resize(scenario_limits::kMaxMetadataBytes);
51
1
  }
52
71.2k
}
53
54
/// Give the successful-TLS lane a hostname its fixed certificate can verify
55
/// while retaining the fuzz-controlled path, query, and fragment. Arbitrary
56
/// authorities remain covered by the compatibility and legacy HTTPS lanes;
57
/// spending this lane's mutations on URL failures would keep curl's peer-cert
58
/// and encrypted application-data paths dark.
59
3.73k
void CanonicalizeTlsAuthority(curl::fuzzer::proto::Scenario* scenario) {
60
3.73k
  const std::string& host_path = scenario->host_path();
61
3.73k
  const std::size_t suffix_start = host_path.find_first_of("/?#");
62
3.73k
  if (suffix_start == std::string::npos) {
63
2.93k
    scenario->set_host_path("tls.test/");
64
2.93k
    return;
65
2.93k
  }
66
798
  scenario->set_host_path("tls.test" + host_path.substr(suffix_start));
67
798
}
68
69
/// Give the TFTP lane a parseable filename-bearing URL while retaining the
70
/// fuzz-controlled path, query, and fragment. The UDP peer rewrites curl's
71
/// destination after URL parsing, so authority mutations cannot reach another
72
/// host; canonicalizing them here avoids spending most iterations on failures
73
/// before curl constructs a TFTP request. An explicit slash is preserved so
74
/// the missing-filename error remains reachable.
75
0
void CanonicalizeTftpAuthority(curl::fuzzer::proto::Scenario* scenario) {
76
0
  const std::string& host_path = scenario->host_path();
77
0
  const std::size_t suffix_start = host_path.find_first_of("/?#");
78
0
  if (suffix_start == std::string::npos) {
79
0
    scenario->set_host_path("tftp.test/file");
80
0
    return;
81
0
  }
82
0
  scenario->set_host_path("tftp.test" + host_path.substr(suffix_start));
83
0
}
84
85
/// Keep the FTP lane inside the same parseable authority while leaving every
86
/// path segment and wildcard under mutation control. CONNECT_TO already
87
/// confines networking, but rejecting malformed authorities before USER/PWD
88
/// would waste the control/data peer this target uniquely provides.
89
0
void CanonicalizeFtpAuthority(curl::fuzzer::proto::Scenario* scenario) {
90
0
  const std::string& host_path = scenario->host_path();
91
0
  const std::size_t suffix_start = host_path.find_first_of("/?#");
92
0
  if (suffix_start == std::string::npos) {
93
0
    scenario->set_host_path("ftp.test/file");
94
0
    return;
95
0
  }
96
0
  scenario->set_host_path("ftp.test" + host_path.substr(suffix_start));
97
0
}
98
99
template <typename RepeatedBytes>
100
62.9k
void BoundStringValues(RepeatedBytes* values, std::size_t count_limit, std::size_t value_limit) {
101
62.9k
  TrimRepeated(values, count_limit);
102
62.9k
  for (std::string& value : *values) {
103
34.5k
    if (value.size() > value_limit) {
104
0
      value.resize(value_limit);
105
0
    }
106
34.5k
  }
107
62.9k
}
108
109
template <typename RepeatedBytes>
110
40.2k
void BoundHeaderValues(RepeatedBytes* headers, std::size_t limit) {
111
40.2k
  BoundStringValues(headers, limit, scenario_limits::kMaxMetadataBytes);
112
40.2k
}
113
114
/// Keep one response script identical to the prefix MockServer and
115
/// WebSocketMockServer can deliver. Raw chunks take precedence over structured
116
/// frames, matching both runtime serializers.
117
27.2k
void BoundConnectionShape(curl::fuzzer::proto::Connection* connection) {
118
27.2k
  TrimRepeated(connection->mutable_on_readable(), scenario_limits::kMaxResponseChunks);
119
27.2k
  const std::size_t raw_count = static_cast<std::size_t>(connection->on_readable_size());
120
27.2k
  TrimRepeated(connection->mutable_server_frames(), scenario_limits::kMaxResponseChunks - raw_count);
121
27.2k
}
122
123
/// Apply the metadata/header limits shared by both MIME part message types.
124
template <typename Part>
125
17.5k
void BoundMimePartMetadata(Part* part) {
126
17.5k
  TrimMetadata(part->mutable_name());
127
17.5k
  TrimMetadata(part->mutable_filename());
128
17.5k
  TrimMetadata(part->mutable_content_type());
129
17.5k
  BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart);
130
17.5k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::BoundMimePartMetadata<curl::fuzzer::proto::MimePart>(curl::fuzzer::proto::MimePart*)
Line
Count
Source
125
7.44k
void BoundMimePartMetadata(Part* part) {
126
7.44k
  TrimMetadata(part->mutable_name());
127
7.44k
  TrimMetadata(part->mutable_filename());
128
7.44k
  TrimMetadata(part->mutable_content_type());
129
7.44k
  BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart);
130
7.44k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::BoundMimePartMetadata<curl::fuzzer::proto::MimeDataPart>(curl::fuzzer::proto::MimeDataPart*)
Line
Count
Source
125
10.1k
void BoundMimePartMetadata(Part* part) {
126
10.1k
  TrimMetadata(part->mutable_name());
127
10.1k
  TrimMetadata(part->mutable_filename());
128
10.1k
  TrimMetadata(part->mutable_content_type());
129
10.1k
  BoundHeaderValues(part->mutable_headers(), scenario_limits::kMaxMimeHeadersPerPart);
130
10.1k
}
131
132
10.1k
void BoundMimeLeaf(curl::fuzzer::proto::MimeDataPart* part) {
133
10.1k
  BoundMimePartMetadata(part);
134
10.1k
  if (part->data().size() > scenario_limits::kMaxMimeDataBytes) {
135
0
    part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes);
136
0
  }
137
10.1k
}
138
139
/// Mirror the runtime's shared top-level/nested part budget in the protobuf
140
/// itself. A simple per-list cap is insufficient because many bounded child
141
/// lists could still leave most of the message semantically dead.
142
3.46k
void BoundMimeShape(curl::fuzzer::proto::MimePost* post) {
143
3.46k
  TrimRepeated(post->mutable_parts(), scenario_limits::kMaxTopLevelMimeParts);
144
3.46k
  std::size_t remaining = scenario_limits::kMaxTotalMimeParts;
145
3.46k
  std::size_t retained_top_parts = 0;
146
147
10.9k
  while (retained_top_parts < static_cast<std::size_t>(post->parts_size()) && remaining != 0) {
148
7.44k
    auto* part = post->mutable_parts(static_cast<int>(retained_top_parts));
149
7.44k
    ++retained_top_parts;
150
7.44k
    --remaining;
151
7.44k
    BoundMimePartMetadata(part);
152
153
7.44k
    if (part->content_case() == curl::fuzzer::proto::MimePart::kData) {
154
1.26k
      if (part->data().size() > scenario_limits::kMaxMimeDataBytes) {
155
0
        part->mutable_data()->resize(scenario_limits::kMaxMimeDataBytes);
156
0
      }
157
1.26k
      continue;
158
1.26k
    }
159
6.17k
    if (part->content_case() != curl::fuzzer::proto::MimePart::kSubparts) {
160
2.14k
      continue;
161
2.14k
    }
162
163
4.03k
    auto* children = part->mutable_subparts()->mutable_parts();
164
4.03k
    TrimRepeated(children, std::min(scenario_limits::kMaxNestedMimeParts, remaining));
165
10.1k
    for (auto& child : *children) {
166
10.1k
      BoundMimeLeaf(&child);
167
10.1k
      --remaining;
168
10.1k
    }
169
4.03k
  }
170
171
3.46k
  TrimRepeated(post->mutable_parts(), retained_top_parts);
172
3.46k
}
173
174
/// Remove upload bytes and read steps the callback cannot observe. Clamping
175
/// individual limits also keeps mutations concentrated on short reads instead
176
/// of many distinct uint32 values that all collapse to the same 16 KiB cap.
177
void BoundUploadShape(curl::fuzzer::proto::UploadScript* upload, std::size_t data_limit, std::size_t read_step_limit,
178
3.29k
                      std::size_t read_size_limit) {
179
3.29k
  if (upload->data().size() > data_limit) {
180
0
    upload->mutable_data()->resize(data_limit);
181
0
  }
182
  // RepeatedField<uint32_t> lacks RepeatedPtrField's DeleteSubrange helper;
183
  // removing the ignored suffix from the end is constant-time per element and
184
  // preserves the mutation-significant prefix exactly.
185
3.29k
  while (static_cast<std::size_t>(upload->read_sizes_size()) > read_step_limit) {
186
0
    upload->mutable_read_sizes()->RemoveLast();
187
0
  }
188
6.86k
  for (int i = 0; i < upload->read_sizes_size(); ++i) {
189
3.57k
    if (upload->read_sizes(i) > read_size_limit) {
190
0
      upload->set_read_sizes(i, static_cast<std::uint32_t>(read_size_limit));
191
0
    }
192
3.57k
  }
193
3.29k
}
194
195
/// Trim a protobuf repeated scalar without depending on the container's
196
/// pointer-field-only DeleteSubrange API. Keeping the mutation-significant
197
/// prefix matches every runtime selector loop.
198
template <typename RepeatedScalar>
199
0
void TrimRepeatedScalar(RepeatedScalar* values, std::size_t limit) {
200
0
  while (static_cast<std::size_t>(values->size()) > limit) {
201
0
    values->RemoveLast();
202
0
  }
203
0
}
204
205
/// Keep API work proportional to the fixed descriptor tables used by the
206
/// runtime. Selector magnitudes stay mutation-controlled because the runtime
207
/// folds them into the relevant typed table; only suffixes it cannot execute
208
/// are dead and therefore removed here.
209
0
void BoundApiPlanShape(curl::fuzzer::proto::ApiPlan* plan) {
210
0
  TrimRepeatedScalar(plan->mutable_share_data_selectors(), scenario_limits::kMaxApiShareDataSelectors);
211
0
  TrimRepeatedScalar(plan->mutable_easy_info_selectors(), scenario_limits::kMaxApiInfoSelectors);
212
213
0
  switch (plan->drive_mode()) {
214
0
    case curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM:
215
0
    case curl::fuzzer::proto::API_DRIVE_MULTI_SOCKET:
216
0
      break;
217
0
    case curl::fuzzer::proto::API_DRIVE_EASY_PERFORM:
218
      // Wakeup is a multi-handle API and has no live object in easy mode.
219
      // Clearing it keeps every retained mutation observable.
220
0
      plan->set_wake_multi(false);
221
0
      break;
222
0
    default:
223
0
      plan->set_drive_mode(curl::fuzzer::proto::API_DRIVE_MULTI_PERFORM);
224
0
      break;
225
0
  }
226
0
}
227
228
/// Canonicalize all shape limits enforced by the runtime. This runs only in
229
/// fixed policy targets; the compatibility binary deliberately retains its
230
/// historical no-postprocessor semantics for existing OSS-Fuzz reproducers.
231
22.6k
void BoundScenarioShape(curl::fuzzer::proto::Scenario* scenario) {
232
22.6k
  TrimRepeated(scenario->mutable_options(), scenario_limits::kMaxOptions);
233
60.0k
  for (auto& option : *scenario->mutable_options()) {
234
60.0k
    if (option.value_case() == curl::fuzzer::proto::SetOption::kStringValue) {
235
18.5k
      TrimMetadata(option.mutable_string_value());
236
18.5k
    }
237
60.0k
  }
238
239
22.6k
  BoundHeaderValues(scenario->mutable_request_headers(), scenario_limits::kMaxRequestHeaders);
240
22.6k
  BoundStringValues(scenario->mutable_telnet_options(), scenario_limits::kMaxTelnetOptions,
241
22.6k
                    scenario_limits::kMaxTelnetOptionBytes);
242
22.6k
  if (scenario->has_mime_post()) {
243
3.46k
    BoundMimeShape(scenario->mutable_mime_post());
244
3.46k
  }
245
22.6k
  if (scenario->has_upload()) {
246
3.29k
    const bool telnet = scenario->scheme() == curl::fuzzer::proto::SCHEME_TELNET;
247
3.29k
    const std::size_t data_limit = telnet ? scenario_limits::kMaxTelnetUploadBytes : scenario_limits::kMaxUploadBytes;
248
3.29k
    const std::size_t read_step_limit =
249
3.29k
        telnet ? scenario_limits::kMaxTelnetUploadReadSteps : scenario_limits::kMaxUploadReadSteps;
250
3.29k
    const std::size_t read_size_limit =
251
3.29k
        telnet ? scenario_limits::kMaxTelnetUploadReadSize : scenario_limits::kMaxUploadReadSize;
252
3.29k
    BoundUploadShape(scenario->mutable_upload(), data_limit, read_step_limit, read_size_limit);
253
3.29k
  }
254
255
22.6k
  BoundConnectionShape(scenario->mutable_connection());
256
22.6k
  TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1);
257
22.6k
  for (auto& connection : *scenario->mutable_subsequent_connections()) {
258
4.57k
    BoundConnectionShape(&connection);
259
4.57k
  }
260
22.6k
}
261
262
/// Return whether an option belongs in the high-throughput HTTP lane. This is
263
/// deliberately an allowlist rather than a denylist: adding a new structured
264
/// option should expand deep coverage first, not silently make the fast lane
265
/// slower before its cost has been measured.
266
5.33k
bool IsCheapHttpOption(curl::fuzzer::proto::CurlOptionId option_id) {
267
5.33k
  switch (option_id) {
268
1.79k
    case curl::fuzzer::proto::CURLOPT_ACCEPT_ENCODING:
269
2.14k
    case curl::fuzzer::proto::CURLOPT_BUFFERSIZE:
270
2.21k
    case curl::fuzzer::proto::CURLOPT_CUSTOMREQUEST:
271
2.24k
    case curl::fuzzer::proto::CURLOPT_DISALLOW_USERNAME_IN_URL:
272
2.29k
    case curl::fuzzer::proto::CURLOPT_FAILONERROR:
273
2.38k
    case curl::fuzzer::proto::CURLOPT_FILETIME:
274
2.45k
    case curl::fuzzer::proto::CURLOPT_HEADER:
275
2.48k
    case curl::fuzzer::proto::CURLOPT_HTTP09_ALLOWED:
276
2.51k
    case curl::fuzzer::proto::CURLOPT_HTTP_CONTENT_DECODING:
277
2.55k
    case curl::fuzzer::proto::CURLOPT_HTTP_TRANSFER_DECODING:
278
4.06k
    case curl::fuzzer::proto::CURLOPT_HTTP_VERSION:
279
4.09k
    case curl::fuzzer::proto::CURLOPT_HTTPGET:
280
4.13k
    case curl::fuzzer::proto::CURLOPT_IGNORE_CONTENT_LENGTH:
281
4.44k
    case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE:
282
4.57k
    case curl::fuzzer::proto::CURLOPT_NOBODY:
283
4.59k
    case curl::fuzzer::proto::CURLOPT_PATH_AS_IS:
284
4.68k
    case curl::fuzzer::proto::CURLOPT_RANGE:
285
4.90k
    case curl::fuzzer::proto::CURLOPT_REQUEST_TARGET:
286
5.21k
    case curl::fuzzer::proto::CURLOPT_RESUME_FROM_LARGE:
287
5.26k
    case curl::fuzzer::proto::CURLOPT_TRANSFER_ENCODING:
288
5.33k
    case curl::fuzzer::proto::CURLOPT_USERAGENT:
289
5.33k
      return true;
290
291
0
    case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED:
292
0
    default:
293
0
      return false;
294
5.33k
  }
295
5.33k
}
296
297
/// Compact an option list before applying the general option-count bound.
298
/// Keeping a relevant option that appears after a long rejected prefix is
299
/// important for mutation density: bounding first would let unrelated options
300
/// crowd useful ones out of a protocol-specific lane.
301
template <typename Predicate>
302
22.6k
void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) {
303
22.6k
  auto* options = scenario->mutable_options();
304
22.6k
  int retained = 0;
305
82.7k
  for (int index = 0; index < options->size(); ++index) {
306
60.0k
    if (!predicate(options->Get(index).option_id())) {
307
0
      continue;
308
0
    }
309
60.0k
    if (retained != index) {
310
0
      options->SwapElements(retained, index);
311
0
    }
312
60.0k
    ++retained;
313
60.0k
  }
314
22.6k
  options->DeleteSubrange(retained, options->size() - retained);
315
22.6k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::RetainMatchingOptions<bool (*)(curl::fuzzer::proto::CurlOptionId)>(curl::fuzzer::proto::Scenario*, bool (*)(curl::fuzzer::proto::CurlOptionId))
Line
Count
Source
302
7.08k
void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) {
303
7.08k
  auto* options = scenario->mutable_options();
304
7.08k
  int retained = 0;
305
13.5k
  for (int index = 0; index < options->size(); ++index) {
306
6.46k
    if (!predicate(options->Get(index).option_id())) {
307
0
      continue;
308
0
    }
309
6.46k
    if (retained != index) {
310
0
      options->SwapElements(retained, index);
311
0
    }
312
6.46k
    ++retained;
313
6.46k
  }
314
7.08k
  options->DeleteSubrange(retained, options->size() - retained);
315
7.08k
}
target_policy.cc:void proto_fuzzer::(anonymous namespace)::RetainMatchingOptions<proto_fuzzer::(anonymous namespace)::RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario*)::$_0>(curl::fuzzer::proto::Scenario*, proto_fuzzer::(anonymous namespace)::RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario*)::$_0)
Line
Count
Source
302
15.5k
void RetainMatchingOptions(curl::fuzzer::proto::Scenario* scenario, Predicate predicate) {
303
15.5k
  auto* options = scenario->mutable_options();
304
15.5k
  int retained = 0;
305
69.1k
  for (int index = 0; index < options->size(); ++index) {
306
53.5k
    if (!predicate(options->Get(index).option_id())) {
307
0
      continue;
308
0
    }
309
53.5k
    if (retained != index) {
310
0
      options->SwapElements(retained, index);
311
0
    }
312
53.5k
    ++retained;
313
53.5k
  }
314
15.5k
  options->DeleteSubrange(retained, options->size() - retained);
315
15.5k
}
316
317
/// Keep the high-throughput HTTP lane free of options whose setup or state is
318
/// assigned to a deeper or protocol-specific target.
319
4.51k
void RetainCheapHttpOptions(curl::fuzzer::proto::Scenario* scenario) {
320
4.51k
  RetainMatchingOptions(scenario, &IsCheapHttpOption);
321
4.51k
}
322
323
/// Return whether a scalar option can influence TELNET without selecting an
324
/// incompatible transfer mode or introducing external state. Protocol-
325
/// specific negotiation preferences use Scenario.telnet_options instead.
326
1.13k
bool IsCheapTelnetOption(curl::fuzzer::proto::CurlOptionId option_id) {
327
1.13k
  switch (option_id) {
328
78
    case curl::fuzzer::proto::CURLOPT_CRLF:
329
548
    case curl::fuzzer::proto::CURLOPT_USERPWD:
330
638
    case curl::fuzzer::proto::CURLOPT_USERNAME:
331
796
    case curl::fuzzer::proto::CURLOPT_PASSWORD:
332
1.13k
    case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE:
333
1.13k
      return true;
334
335
0
    case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED:
336
0
    default:
337
0
      return false;
338
1.13k
  }
339
1.13k
}
340
341
/// Compact the TELNET option prefix so unrelated HTTP mutations cannot crowd
342
/// useful credentials, CRLF handling, and transfer-size controls out of the
343
/// fixed target's general option budget.
344
2.57k
void RetainCheapTelnetOptions(curl::fuzzer::proto::Scenario* scenario) {
345
2.57k
  RetainMatchingOptions(scenario, &IsCheapTelnetOption);
346
2.57k
}
347
348
/// Return whether an option can change a plaintext FTP transfer serviced by
349
/// the bounded control/data peer. Active mode and FTPS settings are omitted:
350
/// retaining them would select socket and TLS behavior this target does not
351
/// provide, turning otherwise-useful mutations into early setup failures.
352
0
bool IsFtpOption(curl::fuzzer::proto::CurlOptionId option_id) {
353
0
  switch (option_id) {
354
0
    case curl::fuzzer::proto::CURLOPT_APPEND:
355
0
    case curl::fuzzer::proto::CURLOPT_BUFFERSIZE:
356
0
    case curl::fuzzer::proto::CURLOPT_CRLF:
357
0
    case curl::fuzzer::proto::CURLOPT_CUSTOMREQUEST:
358
0
    case curl::fuzzer::proto::CURLOPT_DIRLISTONLY:
359
0
    case curl::fuzzer::proto::CURLOPT_FILETIME:
360
0
    case curl::fuzzer::proto::CURLOPT_FTP_ACCOUNT:
361
0
    case curl::fuzzer::proto::CURLOPT_FTP_ALTERNATIVE_TO_USER:
362
0
    case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS:
363
0
    case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD:
364
0
    case curl::fuzzer::proto::CURLOPT_FTP_SKIP_PASV_IP:
365
0
    case curl::fuzzer::proto::CURLOPT_FTP_USE_EPSV:
366
0
    case curl::fuzzer::proto::CURLOPT_FTP_USE_PRET:
367
0
    case curl::fuzzer::proto::CURLOPT_INFILESIZE_LARGE:
368
0
    case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE:
369
0
    case curl::fuzzer::proto::CURLOPT_NOBODY:
370
0
    case curl::fuzzer::proto::CURLOPT_PASSWORD:
371
0
    case curl::fuzzer::proto::CURLOPT_RANGE:
372
0
    case curl::fuzzer::proto::CURLOPT_RESUME_FROM_LARGE:
373
0
    case curl::fuzzer::proto::CURLOPT_TIMECONDITION:
374
0
    case curl::fuzzer::proto::CURLOPT_TIMEVALUE_LARGE:
375
0
    case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT:
376
0
    case curl::fuzzer::proto::CURLOPT_UPLOAD:
377
0
    case curl::fuzzer::proto::CURLOPT_UPLOAD_BUFFERSIZE:
378
0
    case curl::fuzzer::proto::CURLOPT_USERNAME:
379
0
    case curl::fuzzer::proto::CURLOPT_USERPWD:
380
0
    case curl::fuzzer::proto::CURLOPT_WILDCARDMATCH:
381
0
      return true;
382
383
0
    case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED:
384
0
    default:
385
0
      return false;
386
0
  }
387
0
}
388
389
/// Keep the FTP target's general option budget focused on states its passive
390
/// peer can actually advance.
391
0
void RetainFtpOptions(curl::fuzzer::proto::Scenario* scenario) { RetainMatchingOptions(scenario, &IsFtpOption); }
392
393
/// Return whether an option affects TFTP request construction, option
394
/// negotiation, transfer direction, or bounded body delivery. TFTP has no
395
/// connection reuse or stream-level controls, so retaining those settings
396
/// would add protobuf work without another state-machine edge in curl.
397
0
bool IsTftpOption(curl::fuzzer::proto::CurlOptionId option_id) {
398
0
  switch (option_id) {
399
0
    case curl::fuzzer::proto::CURLOPT_CRLF:
400
0
    case curl::fuzzer::proto::CURLOPT_INFILESIZE_LARGE:
401
0
    case curl::fuzzer::proto::CURLOPT_MAXFILESIZE_LARGE:
402
0
    case curl::fuzzer::proto::CURLOPT_NOBODY:
403
0
    case curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE:
404
0
    case curl::fuzzer::proto::CURLOPT_TFTP_NO_OPTIONS:
405
0
    case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT:
406
0
    case curl::fuzzer::proto::CURLOPT_UPLOAD:
407
0
      return true;
408
409
0
    case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED:
410
0
    default:
411
0
      return false;
412
0
  }
413
0
}
414
415
/// Keep the datagram lane from spending mutations on stream-only options.
416
0
void RetainTftpOptions(curl::fuzzer::proto::Scenario* scenario) { RetainMatchingOptions(scenario, &IsTftpOption); }
417
418
/// Decode an integral oneof locally before the generated option canonicalizer
419
/// runs. Protocol mode bounds are policy, not setopt mechanics: folding them
420
/// here keeps nearly every mutation on a real FTP/TFTP state while the shared
421
/// option layer remains unaware of protocol-specific numeric ranges.
422
0
std::uint64_t IntegralMutationValue(const curl::fuzzer::proto::SetOption& option) {
423
0
  switch (option.value_case()) {
424
0
    case curl::fuzzer::proto::SetOption::kUintValue:
425
0
      return option.uint_value();
426
0
    case curl::fuzzer::proto::SetOption::kBoolValue:
427
0
      return option.bool_value() ? 1U : 0U;
428
0
    case curl::fuzzer::proto::SetOption::kStringValue:
429
0
    case curl::fuzzer::proto::SetOption::VALUE_NOT_SET:
430
0
      return 0;
431
0
  }
432
0
  return 0;
433
0
}
434
435
/// Fold small FTP enums onto curl's documented domains so random uint64 values
436
/// do not overwhelmingly stop at setopt validation before issuing a command.
437
0
void CanonicalizeFtpOptionModes(curl::fuzzer::proto::Scenario* scenario) {
438
0
  for (auto& option : *scenario->mutable_options()) {
439
0
    switch (option.option_id()) {
440
0
      case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS:
441
0
        option.set_uint_value(IntegralMutationValue(option) % 3U);
442
0
        break;
443
0
      case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD:
444
0
        option.set_uint_value(IntegralMutationValue(option) % 4U);
445
0
        break;
446
0
      default:
447
0
        break;
448
0
    }
449
0
  }
450
0
}
451
452
/// TFTP accepts block sizes from 8 through 65464. Mapping zero or a mismatched
453
/// oneof to the default 512 preserves a common valid request, while saturating
454
/// other values retains both lower/upper parser boundaries under mutation.
455
0
void CanonicalizeTftpOptionModes(curl::fuzzer::proto::Scenario* scenario) {
456
0
  for (auto& option : *scenario->mutable_options()) {
457
0
    if (option.option_id() != curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE) {
458
0
      continue;
459
0
    }
460
0
    std::uint64_t value = IntegralMutationValue(option);
461
0
    if (value == 0) {
462
0
      value = 512;
463
0
    }
464
0
    option.set_uint_value(std::max<std::uint64_t>(8, std::min<std::uint64_t>(value, 65464)));
465
0
  }
466
0
}
467
468
/// Identify options introduced for FTP/TFTP so existing fixed lanes do not
469
/// silently inherit dead mutations when the shared generated manifest grows.
470
/// Generic options retained by the FTP/TFTP allowlists are deliberately absent
471
/// here because they remain useful to HTTP, WebSocket, API, or timing targets.
472
53.5k
bool IsFileTransferOnlyOption(curl::fuzzer::proto::CurlOptionId option_id) {
473
53.5k
  switch (option_id) {
474
0
    case curl::fuzzer::proto::CURLOPT_APPEND:
475
0
    case curl::fuzzer::proto::CURLOPT_DIRLISTONLY:
476
0
    case curl::fuzzer::proto::CURLOPT_FTP_ACCOUNT:
477
0
    case curl::fuzzer::proto::CURLOPT_FTP_ALTERNATIVE_TO_USER:
478
0
    case curl::fuzzer::proto::CURLOPT_FTP_CREATE_MISSING_DIRS:
479
0
    case curl::fuzzer::proto::CURLOPT_FTP_FILEMETHOD:
480
0
    case curl::fuzzer::proto::CURLOPT_FTP_SKIP_PASV_IP:
481
0
    case curl::fuzzer::proto::CURLOPT_FTP_USE_EPSV:
482
0
    case curl::fuzzer::proto::CURLOPT_FTP_USE_PRET:
483
0
    case curl::fuzzer::proto::CURLOPT_TFTP_BLKSIZE:
484
0
    case curl::fuzzer::proto::CURLOPT_TFTP_NO_OPTIONS:
485
0
    case curl::fuzzer::proto::CURLOPT_TRANSFERTEXT:
486
0
    case curl::fuzzer::proto::CURLOPT_WILDCARDMATCH:
487
0
      return true;
488
489
2.31k
    case curl::fuzzer::proto::CURL_OPTION_UNSPECIFIED:
490
53.5k
    default:
491
53.5k
      return false;
492
53.5k
  }
493
53.5k
}
494
495
/// Remove FTP/TFTP-only options while preserving the relative order of every
496
/// generic option an existing fixed target already consumed.
497
15.5k
void RemoveFileTransferOnlyOptions(curl::fuzzer::proto::Scenario* scenario) {
498
15.5k
  RetainMatchingOptions(
499
53.5k
      scenario, [](curl::fuzzer::proto::CurlOptionId option_id) { return !IsFileTransferOnlyOption(option_id); });
500
15.5k
}
501
502
/// Remove the stateful shapes assigned to the deep HTTP target. This happens
503
/// before BoundScenarioShape so a fast iteration never walks or normalizes a
504
/// MIME tree, upload script, or follow-on connection that it will discard.
505
/// Raw response chunks and request headers stay intact because they reach the
506
/// core HTTP parser cheaply and provide much of the legacy fuzzer's coverage.
507
4.51k
void RemoveDeepHttpShape(curl::fuzzer::proto::Scenario* scenario) {
508
4.51k
  scenario->clear_mime_post();
509
4.51k
  scenario->clear_upload();
510
4.51k
  scenario->clear_subsequent_connections();
511
512
4.51k
  auto* connection = scenario->mutable_connection();
513
4.51k
  connection->clear_server_frames();
514
4.51k
  connection->clear_manual_probes();
515
4.51k
  connection->clear_backpressure();
516
4.51k
}
517
518
/// Remove fields the single-socket WebSocket driver cannot consume. MIME also
519
/// changes the HTTP request away from a useful Upgrade handshake, so retaining
520
/// either shape in fixed WS lanes gives LPM mutation work with no WS coverage
521
/// payoff. The mixed compatibility target has no postprocessor and keeps its
522
/// historical behavior.
523
4.46k
void RemoveIgnoredWebSocketShape(curl::fuzzer::proto::Scenario* scenario) {
524
4.46k
  scenario->clear_subsequent_connections();
525
4.46k
  scenario->clear_mime_post();
526
4.46k
}
527
528
/// Remove fields whose only effect in a TELNET lane would be protobuf work or
529
/// unsafe socket timing. TELNET's curl driver owns the thread until the peer
530
/// closes, so response backpressure and follow-on sockets cannot be serviced
531
/// by the outer event loop. Raw response fragments and the bounded upload stay
532
/// mutation-controlled because the dedicated mock can preload and drain them.
533
2.57k
void RemoveNonTelnetShape(curl::fuzzer::proto::Scenario* scenario) {
534
2.57k
  scenario->clear_subsequent_connections();
535
2.57k
  scenario->clear_request_headers();
536
2.57k
  scenario->clear_mime_post();
537
538
2.57k
  auto* connection = scenario->mutable_connection();
539
2.57k
  connection->clear_server_frames();
540
2.57k
  connection->clear_manual_probes();
541
2.57k
  connection->clear_backpressure();
542
2.57k
}
543
544
/// Remove the TELNET-only list and pause outcome from fixed event-driven
545
/// targets. The compatibility target skips postprocessing, so the runtime
546
/// repeats the pause-to-EOF guard before installing callbacks.
547
20.1k
void RemoveTelnetOnlyShape(curl::fuzzer::proto::Scenario* scenario) {
548
20.1k
  scenario->clear_telnet_options();
549
20.1k
  if (scenario->has_upload() && scenario->upload().terminal() == curl::fuzzer::proto::UPLOAD_TERMINAL_PAUSE) {
550
0
    scenario->mutable_upload()->set_terminal(curl::fuzzer::proto::UPLOAD_TERMINAL_EOF);
551
0
  }
552
20.1k
}
553
554
/// Keep lifecycle work out of protocol-focused lanes. The API binary retains
555
/// this message explicitly; compatibility inputs have no postprocessor so
556
/// existing reproducers keep their historical serialized meaning.
557
22.6k
void RemoveApiOnlyShape(curl::fuzzer::proto::Scenario* scenario) { scenario->clear_api_plan(); }
558
559
/// Remove stream-driver controls that neither file-transfer peer interprets.
560
/// FTP consumes raw byte chunks as control/data replies, while TFTP preserves
561
/// them as individual datagrams; structured WebSocket frames, manual probes,
562
/// and event-loop backpressure therefore cannot affect either curl protocol.
563
0
void RemoveUnusedFileTransferConnectionShape(curl::fuzzer::proto::Connection* connection) {
564
0
  connection->clear_server_frames();
565
0
  connection->clear_manual_probes();
566
0
  connection->clear_backpressure();
567
0
}
568
569
/// Retain only the reusable shapes consumed by the FTP peer: one raw control
570
/// script, a bounded sequence of passive-data scripts, and optional upload
571
/// input. HTTP, TELNET, WebSocket, and public-API fields would otherwise absorb
572
/// mutations despite having no representation in an FTP exchange.
573
0
void RemoveIgnoredFtpShape(curl::fuzzer::proto::Scenario* scenario) {
574
0
  scenario->clear_request_headers();
575
0
  scenario->clear_mime_post();
576
0
  RemoveTelnetOnlyShape(scenario);
577
0
  RemoveApiOnlyShape(scenario);
578
579
0
  RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
580
0
  TrimRepeated(scenario->mutable_subsequent_connections(), scenario_limits::kMaxConnections - 1);
581
0
  for (auto& connection : *scenario->mutable_subsequent_connections()) {
582
0
    RemoveUnusedFileTransferConnectionShape(&connection);
583
0
  }
584
0
}
585
586
/// Retain the primary raw response script because its entries are the ordered
587
/// UDP datagrams seen by curl, plus optional upload input for WRQ. TFTP cannot
588
/// consume follow-on stream connections or any higher-level protocol shape.
589
0
void RemoveIgnoredTftpShape(curl::fuzzer::proto::Scenario* scenario) {
590
0
  scenario->clear_subsequent_connections();
591
0
  scenario->clear_request_headers();
592
0
  scenario->clear_mime_post();
593
0
  RemoveTelnetOnlyShape(scenario);
594
0
  RemoveApiOnlyShape(scenario);
595
0
  RemoveUnusedFileTransferConnectionShape(scenario->mutable_connection());
596
0
}
597
598
/// Preserve useful in-range mutations while folding ineffective extremes onto
599
/// meaningful boundaries. Zero remains special: it disables that individual
600
/// control and lets the other control provide the timing target's pressure.
601
8.10k
std::uint32_t CanonicalizeNonZero(std::uint32_t value, std::uint32_t minimum, std::uint32_t maximum) {
602
8.10k
  if (value == 0) {
603
2.74k
    return 0;
604
2.74k
  }
605
5.35k
  return std::max(minimum, std::min(value, maximum));
606
8.10k
}
607
608
/// Keep the timing target on the plaintext member of the protocol family.
609
/// TLS setup has its own cost profile and would obscure whether backpressure
610
/// mutations are exploring curl's send/receive state machines effectively.
611
3.70k
curl::fuzzer::proto::Scheme PlaintextScheme(curl::fuzzer::proto::Scheme scheme) {
612
3.70k
  switch (scheme) {
613
1.26k
    case curl::fuzzer::proto::SCHEME_WS:
614
1.26k
    case curl::fuzzer::proto::SCHEME_WSS:
615
1.26k
      return curl::fuzzer::proto::SCHEME_WS;
616
2.44k
    case curl::fuzzer::proto::SCHEME_HTTP:
617
2.44k
    case curl::fuzzer::proto::SCHEME_HTTPS:
618
2.44k
    case curl::fuzzer::proto::SCHEME_TELNET:
619
2.44k
    case curl::fuzzer::proto::SCHEME_FTP:
620
2.44k
    case curl::fuzzer::proto::SCHEME_TFTP:
621
2.44k
    case curl::fuzzer::proto::SCHEME_UNSPECIFIED:
622
2.44k
    default:
623
2.44k
      return curl::fuzzer::proto::SCHEME_HTTP;
624
3.70k
  }
625
3.70k
}
626
627
/// Remove timing controls from every connection the structured message can
628
/// carry. Clearing only the primary script would let a mutated redirect turn a
629
/// fixed fast lane into the timed drive loop after its second socket opens.
630
11.8k
void ClearAllBackpressure(curl::fuzzer::proto::Scenario* scenario) {
631
11.8k
  if (scenario->has_connection()) {
632
11.8k
    scenario->mutable_connection()->clear_backpressure();
633
11.8k
  }
634
11.8k
  for (auto& connection : *scenario->mutable_subsequent_connections()) {
635
4.00k
    connection.clear_backpressure();
636
4.00k
  }
637
11.8k
}
638
639
/// Clamp one explicitly pressure-bearing follow-on script to the same useful
640
/// ranges as the timing lane's primary connection. An absent configuration is
641
/// left absent so merely adding a redirect response does not add waits.
642
569
void CanonicalizeOptionalBackpressure(curl::fuzzer::proto::Connection* connection) {
643
569
  if (!connection->has_backpressure()) {
644
224
    return;
645
224
  }
646
345
  auto* backpressure = connection->mutable_backpressure();
647
345
  if (backpressure->recv_buf_bytes() == 0 && backpressure->drain_limit() != 0) {
648
0
    backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes);
649
345
  } else {
650
345
    backpressure->set_recv_buf_bytes(
651
345
        CanonicalizeNonZero(backpressure->recv_buf_bytes(), kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes));
652
345
  }
653
345
  backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration));
654
345
}
655
656
}  // namespace
657
658
/// Canonicalize the fields that determine which server and drive-loop policy
659
/// execute. Fast targets discard backpressure because one mutated non-zero
660
/// scalar otherwise opts an ordinary input into hundreds of timed waits. The
661
/// timing target does the inverse: it guarantees a non-default buffer setting
662
/// so its CPU allocation remains focused on the intentionally slower paths.
663
22.6k
void ApplyTargetPolicy(curl::fuzzer::proto::Scenario* scenario, TargetProfile profile) {
664
22.6k
  if (scenario == nullptr) {
665
0
    return;
666
0
  }
667
668
22.6k
  if (profile == TargetProfile::kCompatibility) {
669
    // The original target's existing corpus predates profile splitting. A
670
    // no-op here makes the type safe to pass around while its binary continues
671
    // to omit postprocessor registration altogether. The append-only FTP/TFTP
672
    // scheme values do not justify rewriting historical mixed-lane inputs.
673
0
    return;
674
0
  }
675
676
22.6k
  if (profile == TargetProfile::kFastTelnet) {
677
    // Set the scheme before general bounds so the TELNET-specific upload and
678
    // PAUSE budgets are selected rather than event-driven compatibility ones.
679
2.57k
    scenario->set_scheme(curl::fuzzer::proto::SCHEME_TELNET);
680
2.57k
    RemoveApiOnlyShape(scenario);
681
2.57k
    RemoveNonTelnetShape(scenario);
682
2.57k
    RetainCheapTelnetOptions(scenario);
683
2.57k
    BoundScenarioShape(scenario);
684
2.57k
    BoundTelnetResponse(scenario->mutable_connection());
685
2.57k
    return;
686
2.57k
  }
687
688
20.1k
  if (profile == TargetProfile::kFastHttp) {
689
4.51k
    scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
690
4.51k
    RemoveApiOnlyShape(scenario);
691
4.51k
    RemoveTelnetOnlyShape(scenario);
692
4.51k
    RemoveDeepHttpShape(scenario);
693
4.51k
    RetainCheapHttpOptions(scenario);
694
4.51k
    BoundScenarioShape(scenario);
695
4.51k
    return;
696
4.51k
  }
697
698
15.5k
  if (profile == TargetProfile::kFastFtp) {
699
0
    scenario->set_scheme(curl::fuzzer::proto::SCHEME_FTP);
700
0
    RemoveIgnoredFtpShape(scenario);
701
0
    RetainFtpOptions(scenario);
702
0
    CanonicalizeFtpOptionModes(scenario);
703
0
    BoundScenarioShape(scenario);
704
0
    CanonicalizeFtpAuthority(scenario);
705
0
    return;
706
0
  }
707
708
15.5k
  if (profile == TargetProfile::kFastTftp) {
709
0
    scenario->set_scheme(curl::fuzzer::proto::SCHEME_TFTP);
710
0
    RemoveIgnoredTftpShape(scenario);
711
0
    RetainTftpOptions(scenario);
712
0
    CanonicalizeTftpOptionModes(scenario);
713
0
    BoundScenarioShape(scenario);
714
0
    CanonicalizeTftpAuthority(scenario);
715
0
    return;
716
0
  }
717
718
  // Select the lane's scheme before applying scheme-sensitive upload bounds.
719
  // The scheme field is itself mutable, so bounding first could accidentally
720
  // give an HTTP/WS case TELNET's smaller payload budget merely because that
721
  // was the input's pre-policy value.
722
15.5k
  switch (profile) {
723
0
    case TargetProfile::kCompatibility:
724
0
      return;
725
4.94k
    case TargetProfile::kDeepHttp:
726
4.94k
      scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
727
4.94k
      break;
728
0
    case TargetProfile::kApi:
729
0
      scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTP);
730
0
      break;
731
3.73k
    case TargetProfile::kFastHttps:
732
3.73k
      scenario->set_scheme(curl::fuzzer::proto::SCHEME_HTTPS);
733
3.73k
      break;
734
0
    case TargetProfile::kFastWebSocket:
735
0
      scenario->set_scheme(curl::fuzzer::proto::SCHEME_WS);
736
0
      break;
737
3.20k
    case TargetProfile::kFastSecureWebSocket:
738
3.20k
      scenario->set_scheme(curl::fuzzer::proto::SCHEME_WSS);
739
3.20k
      break;
740
3.70k
    case TargetProfile::kTiming:
741
3.70k
      scenario->set_scheme(PlaintextScheme(scenario->scheme()));
742
3.70k
      break;
743
0
    case TargetProfile::kFastHttp:
744
0
    case TargetProfile::kFastTelnet:
745
0
    case TargetProfile::kFastFtp:
746
0
    case TargetProfile::kFastTftp:
747
      // Protocol-specific early-return paths selected their scheme above.
748
0
      return;
749
15.5k
  }
750
751
  // TELNET's retained slist and synchronous pause have no observable, safe
752
  // meaning in the fixed event-driven lanes. Clear them before walking the
753
  // general shape so only the compatibility and TELNET targets can retain
754
  // those values.
755
15.5k
  RemoveTelnetOnlyShape(scenario);
756
15.5k
  if (profile != TargetProfile::kApi) {
757
15.5k
    RemoveApiOnlyShape(scenario);
758
15.5k
  }
759
15.5k
  RemoveFileTransferOnlyOptions(scenario);
760
15.5k
  BoundScenarioShape(scenario);
761
762
15.5k
  switch (profile) {
763
0
    case TargetProfile::kCompatibility:
764
0
      return;
765
0
    case TargetProfile::kFastHttp:
766
      // Handled before the general bounds so discarded deep shapes are never
767
      // traversed on the fast path.
768
0
      return;
769
770
4.94k
    case TargetProfile::kDeepHttp:
771
4.94k
      ClearAllBackpressure(scenario);
772
4.94k
      return;
773
774
0
    case TargetProfile::kApi:
775
0
      ClearAllBackpressure(scenario);
776
0
      if (scenario->host_path().size() > scenario_limits::kMaxApiStringBytes) {
777
0
        scenario->mutable_host_path()->resize(scenario_limits::kMaxApiStringBytes);
778
0
      }
779
0
      if (scenario->has_api_plan()) {
780
0
        BoundApiPlanShape(scenario->mutable_api_plan());
781
0
      }
782
0
      return;
783
784
3.73k
    case TargetProfile::kFastHttps:
785
3.73k
      ClearAllBackpressure(scenario);
786
3.73k
      CanonicalizeTlsAuthority(scenario);
787
3.73k
      return;
788
789
0
    case TargetProfile::kFastWebSocket:
790
0
      ClearAllBackpressure(scenario);
791
0
      RemoveIgnoredWebSocketShape(scenario);
792
0
      return;
793
794
3.20k
    case TargetProfile::kFastSecureWebSocket:
795
3.20k
      ClearAllBackpressure(scenario);
796
3.20k
      RemoveIgnoredWebSocketShape(scenario);
797
3.20k
      return;
798
799
0
    case TargetProfile::kFastTelnet:
800
      // Handled before the general bounds so its protocol-specific limits are
801
      // selected from the start.
802
0
      return;
803
804
0
    case TargetProfile::kFastFtp:
805
0
    case TargetProfile::kFastTftp:
806
      // Their peers consume narrower raw-script shapes, pruned before general
807
      // bounds so ignored fields never tax these fast paths.
808
0
      return;
809
810
3.70k
    case TargetProfile::kTiming: {
811
3.70k
      if (scenario->scheme() == curl::fuzzer::proto::SCHEME_WS) {
812
1.26k
        RemoveIgnoredWebSocketShape(scenario);
813
1.26k
      }
814
3.70k
      auto* backpressure = scenario->mutable_connection()->mutable_backpressure();
815
3.70k
      if (backpressure->recv_buf_bytes() == 0) {
816
        // A drain limit alone cannot fill the default AF_UNIX buffer with the
817
        // harness's bounded upload. Always tighten the socket so this lane
818
        // represents real pressure, not merely selection of the timed loop.
819
0
        backpressure->set_recv_buf_bytes(kDefaultBackpressureBufferBytes);
820
3.70k
      } else {
821
3.70k
        backpressure->set_recv_buf_bytes(CanonicalizeNonZero(backpressure->recv_buf_bytes(),
822
3.70k
                                                             kMinBackpressureBufferBytes, kMaxBackpressureBufferBytes));
823
3.70k
      }
824
3.70k
      backpressure->set_drain_limit(CanonicalizeNonZero(backpressure->drain_limit(), 1, kMaxDrainBytesPerIteration));
825
3.70k
      for (auto& connection : *scenario->mutable_subsequent_connections()) {
826
569
        CanonicalizeOptionalBackpressure(&connection);
827
569
      }
828
3.70k
      return;
829
0
    }
830
15.5k
  }
831
15.5k
}
832
833
}  // namespace proto_fuzzer