aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/servers/wsframecodec.h
blob: 0a62e6c80c6db286e62206bffcb23cd2e073ffd8 (plain) (blame)
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