Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
HttpWebSocket.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../AsyncStreams/AsyncStreams.h"
5#include "../Common/Function.h"
6#include "../Common/Result.h"
7#include "../Common/Span.h"
8#include "HttpExport.h"
9#include "HttpParser.h"
10
11namespace SC
12{
13struct AsyncEventLoop;
14struct HttpAsyncClient;
15struct HttpAsyncClientRequest;
16struct HttpAsyncClientResponse;
17struct HttpConnection;
18struct HttpRequest;
19struct HttpResponse;
20
23
25enum class HttpWebSocketOpcode : uint8_t
26{
27 Continuation = 0x0,
28
29 Text = 0x1,
30 Binary = 0x2,
31 Close = 0x8,
32 Ping = 0x9,
33 Pong = 0xA,
34};
35
37enum class HttpWebSocketEndpointRole : uint8_t
38{
39 Client,
40 Server,
41};
42
44enum class HttpWebSocketSha1Mode : uint8_t
45{
50};
51
53struct SC_HTTP_EXPORT HttpWebSocketFrameHeaderView
54{
55 HttpWebSocketOpcode opcode = HttpWebSocketOpcode::Text;
56
57 bool fin = true;
58 bool masked = false;
59 uint64_t payloadLength = 0;
60 uint8_t maskKey[4] = {0, 0, 0, 0};
61
62 [[nodiscard]] bool isControlFrame() const;
63};
64
66struct SC_HTTP_EXPORT HttpWebSocketTransportView
67{
68 AsyncReadableStream* readableStream = nullptr;
69 AsyncWritableStream* writableStream = nullptr;
70 AsyncBuffersPool* buffersPool = nullptr;
71
72 void reset();
73
74 [[nodiscard]] bool isValid() const
75 {
76 return readableStream != nullptr and writableStream != nullptr and buffersPool != nullptr;
77 }
78};
79
82{
83 HttpParser::Method method = HttpParser::Method::HttpGET;
84
85 StringSpan version;
86 StringSpan upgrade;
87 StringSpan connection;
88 StringSpan secWebSocketKey;
89 StringSpan secWebSocketVersion;
90};
91
94{
95 uint32_t statusCode = 0;
96
97 StringSpan upgrade;
98 StringSpan connection;
99 StringSpan secWebSocketAccept;
100};
101
103struct SC_HTTP_EXPORT HttpWebSocketHandshakeResult
104{
105 enum class Status : uint8_t
106 {
107 Accepted,
108 BadRequest,
109 UnsupportedVersion,
110 };
111
112 Status status = Status::BadRequest;
113
114 [[nodiscard]] bool accepted() const { return status == Status::Accepted; }
115 [[nodiscard]] int httpStatusCode() const;
116};
117
119struct SC_HTTP_EXPORT HttpWebSocketHandshake
120{
121 static constexpr size_t ClientKeyLength = 24;
122 static constexpr size_t AcceptKeyLength = 28;
123 static constexpr size_t NonceLength = 16;
124
125 static Result createClientKey(Span<const uint8_t> nonce, Span<char> storage, StringSpan& key);
126 static Result validateClientKey(StringSpan key);
127 static Result computeAccept(StringSpan clientKey, Span<char> storage, StringSpan& accept,
128 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
129
130 static bool headerContainsToken(StringSpan headerValue, StringSpan token);
131
132 static HttpWebSocketHandshakeResult validateServerRequest(const HttpWebSocketServerHandshakeRequestView& request);
133 static HttpWebSocketHandshakeResult validateServerRequest(const HttpRequest& request,
135
136 static Result validateClientResponse(const HttpWebSocketClientHandshakeResponseView& response,
137 StringSpan expectedClientKey,
138 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
139 static Result validateClientResponse(const HttpAsyncClientResponse& response, StringSpan expectedClientKey,
140 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
141
142 static Result prepareClientRequest(HttpAsyncClientRequest& request, StringSpan clientKey);
143 static Result writeServerAccept(HttpResponse& response, StringSpan clientKey, Span<char> acceptStorage,
144 StringSpan& accept,
145 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
146 static Result acceptServerConnection(HttpConnection& connection, HttpWebSocketTransportView& transport,
147 Span<char> acceptStorage,
148 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
149 static Result rejectServerConnection(HttpResponse& response, const HttpWebSocketHandshakeResult& result);
150};
151
153struct SC_HTTP_EXPORT HttpWebSocketClientHandshake
154{
155 Function<void(HttpWebSocketTransportView&)> onConnected;
156 Function<void(Result)> onError;
157
158 Result connect(HttpAsyncClient& client, AsyncEventLoop& loop, StringSpan url, StringSpan clientKey,
160 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform);
161
162 private:
163 void onPrepareRequest(HttpAsyncClientRequest& request);
164 void onResponse(HttpAsyncClientResponse& response);
165 void onClientError(Result result);
166 void fail(Result result);
167
168 HttpAsyncClient* client = nullptr;
169 HttpWebSocketTransportView* transport = nullptr;
170 StringSpan clientKey;
171
172 HttpWebSocketSha1Mode sha1Mode = HttpWebSocketSha1Mode::Platform;
173};
174
176struct SC_HTTP_EXPORT HttpWebSocketFrameReader
177{
178 Function<Result(const HttpWebSocketFrameHeaderView&)> onFrameHeader;
179 Function<Result(Span<char>, bool)> onFramePayload;
180
181 void reset(HttpWebSocketEndpointRole endpointRole);
182 Result parse(Span<char> data, size_t& consumedBytes);
183
184 private:
185 enum class State : uint8_t
186 {
187 HeaderByte0,
188 HeaderByte1,
189 ExtendedLength,
190 MaskKey,
191 Payload,
192 };
193
194 Result onHeaderReady();
195 Result finishCurrentFrame();
196
197 HttpWebSocketEndpointRole endpointRole = HttpWebSocketEndpointRole::Client;
198 HttpWebSocketFrameHeaderView currentFrame;
199
200 State state = State::HeaderByte0;
201
202 bool fragmentedMessageInProgress = false;
203
204 uint8_t headerByte0 = 0;
205
206 uint8_t extendedLengthBytesExpected = 0;
207 uint8_t extendedLengthBytesRead = 0;
208 uint8_t maskBytesRead = 0;
209
210 uint64_t extendedLengthAccumulator = 0;
211 uint64_t payloadBytesRemaining = 0;
212 uint64_t payloadBytesConsumed = 0;
213};
214
216struct SC_HTTP_EXPORT HttpWebSocketFrameWriter
217{
218 void reset(HttpWebSocketEndpointRole endpointRole);
219
220 Result beginFrame(const HttpWebSocketFrameHeaderView& frame, Span<char> storage, Span<const char>& encodedHeader);
221 Result writePayload(Span<char> payload);
222 Result finishFrame();
223
224 private:
225 HttpWebSocketEndpointRole endpointRole = HttpWebSocketEndpointRole::Client;
226 HttpWebSocketFrameHeaderView currentFrame;
227
228 bool frameInProgress = false;
229 bool fragmentedMessageInProgress = false;
230
231 uint64_t payloadBytesRemaining = 0;
232 uint64_t payloadBytesWritten = 0;
233};
234
236struct SC_HTTP_EXPORT HttpWebSocketMessageAssembler
237{
238 Function<Result(HttpWebSocketOpcode, Span<const char>)> onMessage;
239
240 void reset(Span<char> messageStorage);
241
242 Result onFrameHeader(const HttpWebSocketFrameHeaderView& header);
243 Result onFramePayload(Span<char> payload, bool frameFinished);
244
245 [[nodiscard]] size_t getCurrentMessageSize() const { return messageSize; }
246
247 private:
248 Span<char> messageStorage;
249
250 HttpWebSocketFrameHeaderView currentFrame;
251 HttpWebSocketOpcode messageOpcode = HttpWebSocketOpcode::Text;
252
253 size_t messageSize = 0;
254
255 bool assemblingMessage = false;
256 bool ignoringFrame = false;
257};
258
260struct SC_HTTP_EXPORT HttpWebSocketEndpoint
261{
262 Function<Result(const HttpWebSocketFrameHeaderView&)> onFrameHeader;
263 Function<Result(HttpWebSocketOpcode, Span<char>, bool)> onDataFramePayload;
264 Function<Result(Span<char>)> onPing;
265 Function<Result(Span<char>)> onPong;
266 Function<Result(uint16_t, Span<char>)> onClose;
267
268 void reset(HttpWebSocketEndpointRole endpointRole);
269 void setAutomaticMaskKey(const uint8_t maskKey[4]);
270
271 Result receive(Span<char> data, size_t& consumedBytes);
272
273 Result sendFrame(const HttpWebSocketFrameHeaderView& header, Span<const char> payload, Span<char> storage,
274 Span<const char>& encodedFrame);
275 Result sendData(HttpWebSocketOpcode opcode, Span<const char> payload, bool fin, const uint8_t* maskKey,
276 Span<char> storage, Span<const char>& encodedFrame);
277 Result sendPing(Span<const char> payload, const uint8_t* maskKey, Span<char> storage,
278 Span<const char>& encodedFrame);
279 Result sendPong(Span<const char> payload, const uint8_t* maskKey, Span<char> storage,
280 Span<const char>& encodedFrame);
281 Result sendClose(uint16_t statusCode, Span<const char> reason, const uint8_t* maskKey, Span<char> storage,
282 Span<const char>& encodedFrame);
283
284 [[nodiscard]] bool hasPendingControlFrame() const { return pendingControlFrame.sizeInBytes() > 0; }
285 Result getPendingControlFrame(Span<const char>& frame) const;
286 void clearPendingControlFrame();
287
288 [[nodiscard]] bool hasCloseBeenSent() const { return closeSent; }
289 [[nodiscard]] bool hasCloseBeenReceived() const { return closeReceived; }
290
291 private:
292 Result onReaderFrameHeader(const HttpWebSocketFrameHeaderView& header);
293 Result onReaderPayload(Span<char> payload, bool frameFinished);
294 Result handleControlFrame(Span<char> payload);
295 Result queueAutomaticControl(HttpWebSocketOpcode opcode, Span<const char> payload);
296 Result applyOutgoingMask(HttpWebSocketFrameHeaderView& header, const uint8_t* maskKey) const;
297
298 HttpWebSocketEndpointRole endpointRole = HttpWebSocketEndpointRole::Client;
301 HttpWebSocketFrameHeaderView currentFrame;
302
303 char controlPayload[125] = {0};
304 size_t controlPayloadSize = 0;
305 char automaticControlStorage[2 + 8 + 4 + 125] = {0};
306 Span<const char> pendingControlFrame;
307 uint8_t automaticMaskKey[4] = {0, 0, 0, 0};
308
309 bool automaticMaskKeySet = false;
310 bool closeSent = false;
311 bool closeReceived = false;
312};
313
315struct SC_HTTP_EXPORT HttpWebSocketConnectionPump
316{
317 Function<Result(HttpWebSocketOpcode, Span<char>, bool)> onDataFramePayload;
318 Function<void()> onEnd;
319 Function<void(Result)> onError;
320
321 Result attach(const HttpWebSocketTransportView& transport, HttpWebSocketEndpointRole endpointRole);
322 void detach();
323
324 Result writeFrame(Span<const char> frame);
325 Result flushPendingControlFrame();
326
327 [[nodiscard]] bool isAttached() const { return transport.isValid(); }
328
329 HttpWebSocketEndpoint& getEndpoint() { return endpoint; }
330 const HttpWebSocketEndpoint& getEndpoint() const { return endpoint; }
331
332 void onData(AsyncBufferView::ID bufferID);
333 void onStreamEnd();
334
335 private:
336 Result onEndpointDataFrame(HttpWebSocketOpcode opcode, Span<char> payload, bool frameFinished);
337 void fail(Result result);
338
340 HttpWebSocketEndpoint endpoint;
341
342 bool dataListenerAdded = false;
343 bool endListenerAdded = false;
344 bool closeListenerAdded = false;
345};
346
348struct SC_HTTP_EXPORT HttpWebSocketHubClient
349{
351 bool active = false;
352
353 void reset();
354};
355
357struct SC_HTTP_EXPORT HttpWebSocketSmallHub
358{
359 Function<Result(size_t, Span<const char>)> onBroadcastFrame;
360
361 Result init(Span<HttpWebSocketHubClient> clientStorage);
362
363 Result join(const HttpWebSocketTransportView& transport, size_t& clientIndex);
364 Result leave(size_t clientIndex);
365
366 Result broadcastFrame(Span<const char> encodedFrame);
367 Result broadcastText(Span<const char> payload, Span<char> frameStorage);
368
369 [[nodiscard]] size_t getNumClients() const { return numClients; }
370 [[nodiscard]] size_t getCapacity() const { return clients.sizeInElements(); }
371 [[nodiscard]] bool isClientActive(size_t clientIndex) const;
372
373 private:
374 Span<HttpWebSocketHubClient> clients;
375 size_t numClients = 0;
376};
377
379} // namespace SC
HttpWebSocketEndpointRole
Identifies the local endpoint role to validate masking rules.
Definition HttpWebSocket.h:38
HttpWebSocketSha1Mode
Selects the SHA-1 implementation used by the RFC 6455 opening handshake.
Definition HttpWebSocket.h:45
HttpWebSocketOpcode
WebSocket frame opcode as defined by RFC 6455.
Definition HttpWebSocket.h:26
@ Platform
Use the platform provider, returning an error when no provider is available.
@ SelfContained
Use the allocation-free SHA-1 implementation embedded in Http.
Definition AsyncStreams.h:66
Holds a Span of AsyncBufferView (allocated by user) holding available memory for the streams.
Definition AsyncStreams.h:177
Asynchronous I/O (files, sockets, timers, processes, fs events, threads wake-up) (see Async) AsyncEve...
Definition Async.h:1506
Async source abstraction emitting data events in caller provided byte buffers.
Definition AsyncStreams.h:228
Async destination abstraction where bytes can be written to.
Definition AsyncStreams.h:364
Outgoing HTTP request sent by the client.
Definition HttpConnection.h:428
Incoming HTTP response received by the client.
Definition HttpConnection.h:259
Asynchronous HTTP/1.1 client using caller-provided fixed storage.
Definition HttpAsyncClient.h:85
Http connection abstraction holding both the incoming and outgoing messages in an HTTP transaction.
Definition HttpConnection.h:490
Method
Method of the current request / response.
Definition HttpParser.h:19
Incoming HTTP request received by the server.
Definition HttpConnection.h:233
Outgoing HTTP response sent by the server.
Definition HttpConnection.h:383
Normalized client-side WebSocket opening handshake response data.
Definition HttpWebSocket.h:94
Small client-side helper that upgrades an HttpAsyncClient connection to a WebSocket transport.
Definition HttpWebSocket.h:154
Optional stream pump that binds a WebSocket transport to an endpoint lifecycle.
Definition HttpWebSocket.h:316
Small frame-lifecycle helper for ping / pong, close, and explicit fixed-buffer send backpressure.
Definition HttpWebSocket.h:261
Parsed or to-be-written WebSocket frame header.
Definition HttpWebSocket.h:54
Incremental WebSocket frame reader operating on caller-owned mutable byte slices.
Definition HttpWebSocket.h:177
Incremental WebSocket frame writer operating on caller-owned header storage and payload buffers.
Definition HttpWebSocket.h:217
Outcome of validating a WebSocket opening handshake.
Definition HttpWebSocket.h:104
Dependency-free RFC 6455 opening handshake helpers.
Definition HttpWebSocket.h:120
Fixed-slot WebSocket hub client entry.
Definition HttpWebSocket.h:349
Optional caller-buffered message assembly helper for applications that want complete messages.
Definition HttpWebSocket.h:237
Normalized server-side WebSocket opening handshake data.
Definition HttpWebSocket.h:82
Small fixed-capacity broadcast helper for server-side WebSocket fan-out.
Definition HttpWebSocket.h:358
Minimal transport handoff shape for later HTTP upgrade integration.
Definition HttpWebSocket.h:67