Symbian platform (C++)
SDK native APIs, runtime and tooling
Loading...
Searching...
No Matches
framing.h
Go to the documentation of this file.
1// Copyright 2026 The A11 Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Extracted from A11's Http2WebSocketChannel. Helpers and ParseFrames are
16// verbatim, except the thread annotation: the SDK endpoint has one owner.
17// WriteFrame only replaces the unavailable transport/RNG/lock boundary and
18// accepts string_view to avoid a payload copy.
19#ifndef SYMBIAN_WEBSOCKET_FRAMING_H_
20#define SYMBIAN_WEBSOCKET_FRAMING_H_
21#include <cstddef>
22#include <cstdint>
23#include <cstring>
24#include <limits>
25#include <optional>
26#include <string>
27#include <string_view>
28#include <vector>
29
30#include "absl/status/status.h"
32
34constexpr std::uint8_t kContinuation = 0x0;
35constexpr std::uint8_t kText = 0x1;
36constexpr std::uint8_t kBinary = 0x2;
37constexpr std::uint8_t kClose = 0x8;
38constexpr std::uint8_t kPing = 0x9;
39constexpr std::uint8_t kPong = 0xa;
40
41void AppendBigEndian16(std::string* output, std::uint16_t value) {
42 output->push_back(static_cast<char>((value >> 8U) & 0xffU));
43 output->push_back(static_cast<char>(value & 0xffU));
44}
45
46void AppendBigEndian64(std::string* output, std::uint64_t value) {
47 for (int shift = 56; shift >= 0; shift -= 8) {
48 output->push_back(static_cast<char>((value >> shift) & 0xffU));
49 }
50}
51
67void MaskInPlace(char* data, size_t length, const char (&mask)[4]) {
68 std::uint32_t word = 0;
69 std::memcpy(&word, mask, sizeof(word));
70 size_t index = 0;
71 for (; index + sizeof(word) <= length; index += sizeof(word)) {
72 std::uint32_t chunk = 0;
73 std::memcpy(&chunk, data + index, sizeof(chunk));
74 chunk ^= word;
75 std::memcpy(data + index, &chunk, sizeof(chunk));
76 }
77 for (; index < length; ++index) {
78 data[index] ^= mask[index % 4];
79 }
80}
81
82std::uint16_t ReadBigEndian16(std::string_view input, size_t offset) {
83 return static_cast<std::uint16_t>(
84 (static_cast<std::uint16_t>(static_cast<unsigned char>(input[offset]))
85 << 8U) |
86 static_cast<std::uint16_t>(
87 static_cast<unsigned char>(input[offset + 1])));
88}
89
90std::uint64_t ReadBigEndian64(std::string_view input, size_t offset) {
91 std::uint64_t result = 0;
92 for (size_t index = 0; index < sizeof(result); ++index) {
93 result = (result << 8U) | static_cast<unsigned char>(input[offset + index]);
94 }
95 return result;
96}
97
98class Framing {
99 public:
100 Framing(Role role, size_t maximum)
101 : role_(role), max_message_size_(maximum) {}
102
103 struct ParsedActions {
104 std::vector<std::string> messages;
105 std::vector<std::string> pongs;
106 std::optional<std::string> close;
107 };
108
109 absl::Status ParseFrames(ParsedActions* actions) {
110 size_t consumed = 0;
111 while (input_.size() - consumed >= 2) {
112 const auto first = static_cast<unsigned char>(input_[consumed]);
113 const auto second = static_cast<unsigned char>(input_[consumed + 1]);
114 const bool final = (first & 0x80U) != 0;
115 const std::uint8_t opcode = first & 0x0fU;
116 if ((first & 0x70U) != 0) {
117 return absl::InvalidArgumentError("WebSocket RSV bits are not zero");
118 }
119 const bool masked = (second & 0x80U) != 0;
120 if (masked != (role_ == Role::kServer)) {
121 return absl::InvalidArgumentError(
122 role_ == Role::kServer
123 ? "WebSocket client frame is not masked"
124 : "WebSocket server frame is unexpectedly masked");
125 }
126
127 std::uint64_t payload_size = second & 0x7fU;
128 size_t header_size = 2;
129 if (payload_size == 126) {
130 if (input_.size() - consumed < 4) {
131 break;
132 }
133 payload_size = ReadBigEndian16(input_, consumed + 2);
134 header_size = 4;
135 } else if (payload_size == 127) {
136 if (input_.size() - consumed < 10) {
137 break;
138 }
139 payload_size = ReadBigEndian64(input_, consumed + 2);
140 if ((payload_size & (std::uint64_t{1} << 63U)) != 0) {
141 return absl::InvalidArgumentError("WebSocket length is invalid");
142 }
143 header_size = 10;
144 }
145 if (payload_size > max_message_size_ ||
146 payload_size > std::numeric_limits<size_t>::max()) {
147 return absl::OutOfRangeError(
148 "WebSocket frame exceeds max_message_size");
149 }
150 const bool control = opcode >= kClose;
151 if (control && (!final || payload_size > 125)) {
152 return absl::InvalidArgumentError(
153 "WebSocket control frame must be final and at most 125 bytes");
154 }
155 const size_t mask_size = masked ? 4 : 0;
156 const size_t full_size =
157 header_size + mask_size + static_cast<size_t>(payload_size);
158 if (input_.size() - consumed < full_size) {
159 break;
160 }
161
162 const size_t mask_offset = consumed + header_size;
163 const size_t payload_offset = mask_offset + mask_size;
164 // Copied out before the payload is taken, because taking it may move the
165 // buffer the mask lives in.
166 char mask[4] = {0, 0, 0, 0};
167 if (masked) {
168 std::memcpy(mask, input_.data() + mask_offset, sizeof(mask));
169 }
170 std::string payload;
171 bool taken = false;
172 if (consumed == 0 && payload_offset + payload_size == input_.size()) {
173 // The buffer holds exactly this one frame and nothing before it, which
174 // is the common case for a message that arrived in its own TCP read.
175 payload = std::move(input_);
176 input_.clear();
177 payload.erase(0, payload_offset);
178 taken = true;
179 } else {
180 payload =
181 input_.substr(payload_offset, static_cast<size_t>(payload_size));
182 }
183 if (masked) {
184 MaskInPlace(payload.data(), payload.size(), mask);
185 }
186 // Taking the buffer already consumed it: there is nothing left to skip
187 // past and nothing left to erase, and leaving `consumed` set would make
188 // the loop's `input_.size() - consumed` underflow on the next pass.
189 consumed = taken ? 0 : consumed + full_size;
190
191 if (opcode == kPing) {
192 actions->pongs.push_back(std::move(payload));
193 } else if (opcode == kPong) {
194 continue;
195 } else if (opcode == kClose) {
196 if (payload.size() == 1) {
197 return absl::InvalidArgumentError(
198 "WebSocket close code is truncated");
199 }
200 actions->close = std::move(payload);
201 break;
202 } else if (opcode == kText) {
203 return absl::InvalidArgumentError(
204 "A11 binary channel received a text WebSocket frame");
205 } else if (opcode == kBinary) {
206 if (fragment_opcode_.has_value()) {
207 return absl::InvalidArgumentError(
208 "WebSocket data frame interrupted a fragmented message");
209 }
210 if (final) {
211 actions->messages.push_back(std::move(payload));
212 } else {
213 fragment_opcode_ = opcode;
214 fragmented_ = std::move(payload);
215 }
216 } else if (opcode == kContinuation) {
217 if (!fragment_opcode_.has_value()) {
218 return absl::InvalidArgumentError(
219 "WebSocket continuation has no initial frame");
220 }
221 if (fragmented_.size() + payload.size() > max_message_size_) {
222 return absl::OutOfRangeError(
223 "Fragmented WebSocket message exceeds max_message_size");
224 }
225 fragmented_.append(payload);
226 if (final) {
227 actions->messages.push_back(std::move(fragmented_));
228 fragmented_.clear();
229 fragment_opcode_.reset();
230 }
231 } else {
232 return absl::InvalidArgumentError("WebSocket opcode is unsupported");
233 }
234 }
235 if (consumed != 0) {
236 input_.erase(0, consumed);
237 }
238 return absl::OkStatus();
239 }
240
241 std::string WriteFrame(std::uint8_t opcode, std::string_view payload,
242 std::uint32_t masking_key) {
243 const bool masked = role_ == Role::kClient;
244 std::string frame;
245 frame.reserve(payload.size() + 14);
246 frame.push_back(static_cast<char>(0x80U | opcode));
247 const std::uint8_t mask_flag = masked ? 0x80U : 0;
248 if (payload.size() <= 125) {
249 frame.push_back(static_cast<char>(mask_flag | payload.size()));
250 } else if (payload.size() <= std::numeric_limits<std::uint16_t>::max()) {
251 frame.push_back(static_cast<char>(mask_flag | 126U));
252 AppendBigEndian16(&frame, static_cast<std::uint16_t>(payload.size()));
253 } else {
254 frame.push_back(static_cast<char>(mask_flag | 127U));
255 AppendBigEndian64(&frame, payload.size());
256 }
257 if (masked) {
258 const auto key = masking_key;
259 const char mask[4] = {
260 static_cast<char>((key >> 24U) & 0xffU),
261 static_cast<char>((key >> 16U) & 0xffU),
262 static_cast<char>((key >> 8U) & 0xffU),
263 static_cast<char>(key & 0xffU),
264 };
265 frame.append(mask, sizeof(mask));
266 const size_t body = frame.size();
267 frame.append(payload);
268 MaskInPlace(frame.data() + body, payload.size(), mask);
269 } else {
270 frame.append(payload);
271 }
272 return frame;
273 }
274
275 const Role role_;
276 const size_t max_message_size_;
277 std::string input_;
278 std::optional<std::uint8_t> fragment_opcode_;
279 std::string fragmented_;
280};
281} // namespace symbian::websocket::internal
282#endif
Definition framing.h:33
constexpr std::uint8_t kBinary
Definition framing.h:36
void AppendBigEndian16(std::string *output, std::uint16_t value)
Definition framing.h:41
constexpr std::uint8_t kText
Definition framing.h:35
std::uint64_t ReadBigEndian64(std::string_view input, size_t offset)
Definition framing.h:90
constexpr std::uint8_t kPing
Definition framing.h:38
void MaskInPlace(char *data, size_t length, const char(&mask)[4])
XORs length bytes at data with a repeating 4-byte WebSocket mask.
Definition framing.h:67
constexpr std::uint8_t kPong
Definition framing.h:39
void AppendBigEndian64(std::string *output, std::uint64_t value)
Definition framing.h:46
constexpr std::uint8_t kContinuation
Definition framing.h:34
constexpr std::uint8_t kClose
Definition framing.h:37
std::uint16_t ReadBigEndian16(std::string_view input, size_t offset)
Definition framing.h:82
Role
Definition websocket.h:20
uint32_t offset
Definition exports.cc:70
std::string_view payload
Definition sis.cc:139
libusb_context * value
Definition usb.cc:35
std::vector< unsigned char > data
Definition usb.cc:292