1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include <zenhttp/websocket.h>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace zen {
/**
* Outcome of a frame parse attempt
*/
enum class WsFrameParseStatus : uint8_t
{
kNeedMoreData, // the buffer does not yet contain a full frame
kValid, // a complete, well-formed frame was parsed
kProtocolError // the frame violates RFC 6455; caller must close the connection (typically with 1002)
};
/**
* Result of attempting to parse a single WebSocket frame from a byte buffer
*/
struct WsFrameParseResult
{
WsFrameParseStatus Status = WsFrameParseStatus::kNeedMoreData;
bool IsValid = false; // true iff Status == kValid (convenience mirror)
size_t BytesConsumed = 0; // number of bytes consumed from the input buffer
WebSocketOpcode Opcode = WebSocketOpcode::kText;
bool Fin = false;
std::vector<uint8_t> Payload;
};
/**
* RFC 6455 WebSocket frame codec
*
* Provides static helpers for parsing client-to-server frames (which are
* always masked) and building server-to-client frames (which are never masked).
*/
struct WsFrameCodec
{
/**
* Try to parse one complete frame from the front of the buffer.
*
* Returns a result with Status == kNeedMoreData and BytesConsumed == 0 when
* there is not enough data yet. The caller should accumulate more data
* and retry.
*
* When RequireMask is true (server parsing client-to-server frames), a frame
* that is not masked is rejected with Status == kProtocolError per RFC 6455
* section 5.1 — the caller must close the connection with status 1002.
* When RequireMask is false (client parsing server-to-client frames), the
* mask bit is not validated.
*/
static WsFrameParseResult TryParseFrame(const uint8_t* Data, size_t Size, bool RequireMask = false);
/**
* Build a server-to-client frame (no masking)
*/
static std::vector<uint8_t> BuildFrame(WebSocketOpcode Opcode, std::span<const uint8_t> Payload);
/**
* Build a close frame with a status code and optional reason string
*/
static std::vector<uint8_t> BuildCloseFrame(uint16_t Code, std::string_view Reason = {});
/**
* Build a client-to-server frame (with masking per RFC 6455)
*/
static std::vector<uint8_t> BuildMaskedFrame(WebSocketOpcode Opcode, std::span<const uint8_t> Payload);
/**
* Build a masked close frame with status code and optional reason
*/
static std::vector<uint8_t> BuildMaskedCloseFrame(uint16_t Code, std::string_view Reason = {});
/**
* Compute the Sec-WebSocket-Accept value per RFC 6455 section 4.2.2
*
* accept = Base64(SHA1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
*/
static std::string ComputeAcceptKey(std::string_view ClientKey);
};
} // namespace zen
|