curl-fuzzer C++ reference
Loading...
Searching...
No Matches
ws_frame.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3 *
4 * SPDX-License-Identifier: curl
5 */
6
9
11
12#include <cstddef>
13#include <cstdint>
14#include <string>
15
16namespace proto_fuzzer {
17
18namespace {
19
20// RFC 6455 §5.2 length-form selectors.
21constexpr std::uint32_t kLenFormAuto = 0;
22constexpr std::uint32_t kLenForm7 = 1;
23constexpr std::uint32_t kLenForm16 = 2;
24constexpr std::uint32_t kLenForm64 = 3;
25
30std::uint32_t ResolveLengthForm(std::uint32_t form, std::size_t payload_len) {
31 if (form == kLenForm7 || form == kLenForm16 || form == kLenForm64) {
32 return form;
33 }
34 if (payload_len < 126) {
35 return kLenForm7;
36 }
37 if (payload_len <= 0xFFFF) {
38 return kLenForm16;
39 }
40 return kLenForm64;
41}
42
44void AppendBigEndian(std::string* out, std::uint64_t value, std::size_t width) {
45 for (std::size_t i = 0; i < width; ++i) {
46 std::size_t shift = (width - 1 - i) * 8;
47 out->push_back(static_cast<char>((value >> shift) & 0xFF));
48 }
49}
50
51} // namespace
52
59std::string SerializeWebSocketFrame(const curl::fuzzer::proto::WebSocketFrame& frame) {
60 std::string out;
61 const std::string& payload = frame.payload();
62 const std::size_t payload_len = payload.size();
63
64 // Byte 0: FIN | RSV1 | RSV2 | RSV3 | opcode(4 bits).
65 std::uint8_t byte0 = 0;
66 if (frame.fin()) byte0 |= 0x80;
67 if (frame.rsv1()) byte0 |= 0x40;
68 if (frame.rsv2()) byte0 |= 0x20;
69 if (frame.rsv3()) byte0 |= 0x10;
70 byte0 |= static_cast<std::uint8_t>(frame.opcode() & 0x0F);
71 out.push_back(static_cast<char>(byte0));
72
73 // Byte 1: MASK bit | payload-length indicator (7 bits).
74 const std::uint32_t length_form = ResolveLengthForm(frame.length_form(), payload_len);
75 std::uint8_t byte1 = frame.masked() ? 0x80 : 0x00;
76 switch (length_form) {
77 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 byte1 |= static_cast<std::uint8_t>(payload_len & 0x7F);
83 out.push_back(static_cast<char>(byte1));
84 break;
85 case kLenForm16:
86 byte1 |= 126;
87 out.push_back(static_cast<char>(byte1));
88 AppendBigEndian(&out, static_cast<std::uint64_t>(payload_len & 0xFFFF), 2);
89 break;
90 case kLenForm64:
91 default:
92 byte1 |= 127;
93 out.push_back(static_cast<char>(byte1));
94 AppendBigEndian(&out, static_cast<std::uint64_t>(payload_len), 8);
95 break;
96 }
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 if (frame.masked()) {
103 const std::uint32_t key = frame.mask_key();
104 std::uint8_t key_bytes[4] = {
105 static_cast<std::uint8_t>((key >> 24) & 0xFF),
106 static_cast<std::uint8_t>((key >> 16) & 0xFF),
107 static_cast<std::uint8_t>((key >> 8) & 0xFF),
108 static_cast<std::uint8_t>(key & 0xFF),
109 };
110 for (unsigned char b : key_bytes) {
111 out.push_back(static_cast<char>(b));
112 }
113 out.reserve(out.size() + payload_len);
114 for (std::size_t i = 0; i < payload_len; ++i) {
115 out.push_back(static_cast<char>(static_cast<std::uint8_t>(payload[i]) ^ key_bytes[i & 0x3]));
116 }
117 } else {
118 out.append(payload);
119 }
120
121 return out;
122}
123
124} // namespace proto_fuzzer
std::string SerializeWebSocketFrame(const curl::fuzzer::proto::WebSocketFrame &frame)
Serialise a proto WebSocketFrame into RFC 6455 wire bytes.
Definition ws_frame.cc:59
Serialize a proto WebSocketFrame message into RFC 6455 wire bytes.