Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/ws_frame.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 SerializeWebSocketFrame.
9
10
#include "proto_fuzzer/ws_frame.h"
11
12
#include <cstddef>
13
#include <cstdint>
14
#include <string>
15
16
namespace proto_fuzzer {
17
18
namespace {
19
20
// RFC 6455 §5.2 length-form selectors.
21
constexpr std::uint32_t kLenFormAuto = 0;
22
constexpr std::uint32_t kLenForm7 = 1;
23
constexpr std::uint32_t kLenForm16 = 2;
24
constexpr std::uint32_t kLenForm64 = 3;
25
26
/// Pick a length encoding for 'payload_len' honouring an explicit override.
27
/// When 'form' names a shorter encoding than the payload actually needs, we
28
/// still emit the shorter form and let the decoder reject it — the point is
29
/// to exercise the error path.
30
53.4k
std::uint32_t ResolveLengthForm(std::uint32_t form, std::size_t payload_len) {
31
53.4k
  if (form == kLenForm7 || form == kLenForm16 || form == kLenForm64) {
32
5.25k
    return form;
33
5.25k
  }
34
48.2k
  if (payload_len < 126) {
35
44.5k
    return kLenForm7;
36
44.5k
  }
37
3.72k
  if (payload_len <= 0xFFFF) {
38
3.72k
    return kLenForm16;
39
3.72k
  }
40
0
  return kLenForm64;
41
3.72k
}
42
43
/// Append 'value' as big-endian bytes of width 'width' to 'out'.
44
7.44k
void AppendBigEndian(std::string* out, std::uint64_t value, std::size_t width) {
45
38.1k
  for (std::size_t i = 0; i < width; ++i) {
46
30.7k
    std::size_t shift = (width - 1 - i) * 8;
47
30.7k
    out->push_back(static_cast<char>((value >> shift) & 0xFF));
48
30.7k
  }
49
7.44k
}
50
51
}  // namespace
52
53
/// Serialise a proto WebSocketFrame into RFC 6455 wire bytes. No validation
54
/// is performed — invalid combinations (reserved bits set, oversized
55
/// length_form for a tiny payload, opcode > 15) round-trip to the decoder
56
/// unchanged, which is the point.
57
/// @param frame The WebSocketFrame proto message to render.
58
/// @return The serialised byte string, ready to push onto the mock socket.
59
53.4k
std::string SerializeWebSocketFrame(const curl::fuzzer::proto::WebSocketFrame& frame) {
60
53.4k
  std::string out;
61
53.4k
  const std::string& payload = frame.payload();
62
53.4k
  const std::size_t payload_len = payload.size();
63
64
  // Byte 0: FIN | RSV1 | RSV2 | RSV3 | opcode(4 bits).
65
53.4k
  std::uint8_t byte0 = 0;
66
53.4k
  if (frame.fin()) byte0 |= 0x80;
67
53.4k
  if (frame.rsv1()) byte0 |= 0x40;
68
53.4k
  if (frame.rsv2()) byte0 |= 0x20;
69
53.4k
  if (frame.rsv3()) byte0 |= 0x10;
70
53.4k
  byte0 |= static_cast<std::uint8_t>(frame.opcode() & 0x0F);
71
53.4k
  out.push_back(static_cast<char>(byte0));
72
73
  // Byte 1: MASK bit | payload-length indicator (7 bits).
74
53.4k
  const std::uint32_t length_form = ResolveLengthForm(frame.length_form(), payload_len);
75
53.4k
  std::uint8_t byte1 = frame.masked() ? 0x80 : 0x00;
76
53.4k
  switch (length_form) {
77
46.0k
    case kLenForm7:
78
      // Clamp the 7-bit length to the payload's actual size. If the payload
79
      // is > 125 bytes and the caller forced 7-bit form, low 7 bits of size
80
      // are what the decoder sees — which is exactly the malformed-frame
81
      // path we want to reach.
82
46.0k
      byte1 |= static_cast<std::uint8_t>(payload_len & 0x7F);
83
46.0k
      out.push_back(static_cast<char>(byte1));
84
46.0k
      break;
85
4.80k
    case kLenForm16:
86
4.80k
      byte1 |= 126;
87
4.80k
      out.push_back(static_cast<char>(byte1));
88
4.80k
      AppendBigEndian(&out, static_cast<std::uint64_t>(payload_len & 0xFFFF), 2);
89
4.80k
      break;
90
2.63k
    case kLenForm64:
91
2.63k
    default:
92
2.63k
      byte1 |= 127;
93
2.63k
      out.push_back(static_cast<char>(byte1));
94
2.63k
      AppendBigEndian(&out, static_cast<std::uint64_t>(payload_len), 8);
95
2.63k
      break;
96
53.4k
  }
97
98
  // Masking key (4 bytes) and XORed payload. Client-to-server frames must be
99
  // masked per spec; server-to-client frames must NOT — but we emit whatever
100
  // the scenario says, so the decoder's "masked server frame" error path is
101
  // reachable.
102
53.4k
  if (frame.masked()) {
103
12.5k
    const std::uint32_t key = frame.mask_key();
104
12.5k
    std::uint8_t key_bytes[4] = {
105
12.5k
        static_cast<std::uint8_t>((key >> 24) & 0xFF),
106
12.5k
        static_cast<std::uint8_t>((key >> 16) & 0xFF),
107
12.5k
        static_cast<std::uint8_t>((key >> 8) & 0xFF),
108
12.5k
        static_cast<std::uint8_t>(key & 0xFF),
109
12.5k
    };
110
50.2k
    for (unsigned char b : key_bytes) {
111
50.2k
      out.push_back(static_cast<char>(b));
112
50.2k
    }
113
12.5k
    out.reserve(out.size() + payload_len);
114
3.35M
    for (std::size_t i = 0; i < payload_len; ++i) {
115
3.33M
      out.push_back(static_cast<char>(static_cast<std::uint8_t>(payload[i]) ^ key_bytes[i & 0x3]));
116
3.33M
    }
117
40.9k
  } else {
118
40.9k
    out.append(payload);
119
40.9k
  }
120
121
53.4k
  return out;
122
53.4k
}
123
124
}  // namespace proto_fuzzer