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