DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
WebSocket.cpp
Go to the documentation of this file.
1#include "pch.h"
5#include <openssl/sha.h>
6#include <openssl/evp.h>
7#include <array>
8#include <vector>
9#include <string>
10#include <cstdint>
11
12namespace DotNetDupe {
13 namespace System {
14 namespace Net {
15 namespace WebSockets {
16
17 static void BuildWebSocketFrameHeader(uint8_t opcode, size_t len, std::vector<uint8_t>& frame) {
19 frame.push_back(opcode);
21 if (len <= 125) {
22 frame.push_back(static_cast<uint8_t>(len));
23 } else if (len <= 65535) {
24 frame.push_back(126);
25 frame.push_back(static_cast<uint8_t>((len >> 8) & 0xFF));
26 frame.push_back(static_cast<uint8_t>(len & 0xFF));
27 } else {
28 frame.push_back(127);
29 for (int i = 7; i >= 0; --i) frame.push_back(static_cast<uint8_t>((len >> (i * 8)) & 0xFF));
30 }
31 }
32
33 class WebSocket::Impl : public Object {
34 public:
36 WebSocketState m_eState;
38
40 : m_pStream(pStream), m_eState(WebSocketState::Open) {}
41
42 void CloseState() {
45 m_eState = WebSocketState::Closed;
46 }
47
48 bool ReadExtended16(uint64_t& payloadLen) {
50 uint8_t extLen[2] = { 0 };
51 int bytesRead = m_pStream->Read(reinterpret_cast<char*>(extLen), 0, 2);
52 if (bytesRead <= 0) {
53 throw WebSocketException(WebSocketError::ConnectionClosedPrematurely, "Unexpected EOF reading 16-bit extended length.");
54 }
55 payloadLen = (static_cast<uint64_t>(extLen[0]) << 8) | extLen[1];
56 return true;
57 }
58
59 bool ReadExtended64(uint64_t& payloadLen) {
61 uint8_t extLen[8] = { 0 };
62 int bytesRead = m_pStream->Read(reinterpret_cast<char*>(extLen), 0, 8);
63 if (bytesRead <= 0) {
64 throw WebSocketException(WebSocketError::ConnectionClosedPrematurely, "Unexpected EOF reading 64-bit extended length.");
65 }
66 payloadLen = 0;
67 for (int i = 0; i < 8; ++i) payloadLen = (payloadLen << 8) | extLen[i];
68 return true;
69 }
70
71 bool ReadFrameHeader(uint8_t& opcode, bool& masked, uint64_t& payloadLen) {
73 uint8_t header[2] = { 0 };
74 int bytesRead = 0;
75 try {
76 bytesRead = m_pStream->Read(reinterpret_cast<char*>(header), 0, 2);
77 } catch (const Exception& ex) {
79 }
80 if (bytesRead <= 0) return false;
82 opcode = header[0] & 0x0F;
83 masked = (header[1] & 0x80) != 0;
84 payloadLen = header[1] & 0x7F;
85 return true;
86 }
87
88 bool ReadExtendedLength(uint64_t& payloadLen) {
90 if (payloadLen == 126) return ReadExtended16(payloadLen);
91 if (payloadLen == 127) return ReadExtended64(payloadLen);
92 return true;
93 }
94
95 void ReadPayloadBytes(uint64_t payloadLen, std::vector<uint8_t>& payload) {
97 payload.resize(payloadLen, 0);
98 uint64_t totalRead = 0;
99 while (totalRead < payloadLen) {
100 int toRead = static_cast<int>(payloadLen - totalRead);
101 int bytesRead = m_pStream->Read(reinterpret_cast<char*>(payload.data() + totalRead), 0, toRead);
102 if (bytesRead <= 0) {
103 throw WebSocketException(WebSocketError::ConnectionClosedPrematurely, "Connection lost while reading payload bytes.");
104 }
105 totalRead += bytesRead;
106 }
107 }
108
109 void UnmaskPayload(const std::array<uint8_t, 4>& maskKey, uint64_t payloadLen, std::vector<uint8_t>& payload) {
111 for (uint64_t i = 0; i < payloadLen; ++i) {
112 payload[i] ^= maskKey[i % 4];
113 }
114 }
115
116 bool ReadMaskKeyAndPayload(bool masked, uint64_t payloadLen, std::vector<uint8_t>& payload) {
118 std::array<uint8_t, 4> maskKey = { 0 };
119 if (masked && m_pStream->Read(reinterpret_cast<char*>(maskKey.data()), 0, 4) <= 0) {
120 throw WebSocketException(WebSocketError::ConnectionClosedPrematurely, "Connection lost while reading mask key.");
121 }
122 ReadPayloadBytes(payloadLen, payload);
123 if (masked) UnmaskPayload(maskKey, payloadLen, payload);
124 return true;
125 }
126
127 bool WriteFrame(uint8_t opcode, const uint8_t* pData, size_t len) {
129 Threading::Lock<Threading::CriticalSection> lock(m_csLock);
130 if (m_pStream.IsNull() || m_eState != WebSocketState::Open) {
131 throw WebSocketException(WebSocketError::InvalidState, "WebSocket is not connected or already closed.");
132 }
134 std::vector<uint8_t> frame;
135 BuildWebSocketFrameHeader(opcode, len, frame);
136 if (pData != nullptr && len > 0) frame.insert(frame.end(), pData, pData + len);
138 try {
139 m_pStream->Write(reinterpret_cast<const char*>(frame.data()), 0, static_cast<int>(frame.size()));
140 } catch (const Exception& ex) {
142 }
143 return true;
144 }
145 };
146
148 : m_pImpl(SmartPointer<Impl>::NewShared(pStream)) {
149 }
150
153
154 WebSocketState WebSocket::GetState() const { return m_pImpl->m_eState; }
156 m_pImpl->m_eState = state;
157 }
158
161 if (secWebSocketKey.IsEmpty()) {
162 throw ArgumentException("secWebSocketKey cannot be empty.");
163 }
165 std::string key = secWebSocketKey.GetRawString();
166 std::string magic = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
167 unsigned char hash[SHA_DIGEST_LENGTH];
168 SHA1(reinterpret_cast<const unsigned char*>(magic.c_str()), magic.length(), hash);
170 char encoded[128] = { 0 };
171 EVP_EncodeBlock(reinterpret_cast<unsigned char*>(encoded), hash, SHA_DIGEST_LENGTH);
173 return String(encoded);
174 }
175
176 bool WebSocket::SendAsync(const String& message) {
178 std::string text = message.GetRawString();
179 return m_pImpl->WriteFrame(0x81, reinterpret_cast<const uint8_t*>(text.data()), text.length());
180 }
181
184 return m_pImpl->WriteFrame(0x82, data.GetData(), data.GetLength());
185 }
186
187 bool WebSocket::ReceiveText(String& outMessage) {
189 {
190 Threading::Lock<Threading::CriticalSection> lock(m_pImpl->m_csLock);
191 if (m_pImpl->m_pStream.IsNull() || m_pImpl->m_eState != WebSocketState::Open) {
192 throw WebSocketException(WebSocketError::InvalidState, "WebSocket is not connected or already closed.");
193 }
194 }
196 uint8_t opcode = 0; bool masked = false; uint64_t payloadLen = 0;
197 if (!m_pImpl->ReadFrameHeader(opcode, masked, payloadLen) || opcode == 0x08) {
198 m_pImpl->CloseState();
199 return false;
200 }
202 std::vector<uint8_t> payload;
203 m_pImpl->ReadExtendedLength(payloadLen);
204 m_pImpl->ReadMaskKeyAndPayload(masked, payloadLen, payload);
205 outMessage = String(std::string(payload.begin(), payload.end()).c_str());
207 return true;
208 }
209
212 Threading::Lock<Threading::CriticalSection> lock(m_pImpl->m_csLock);
213 if (m_pImpl->m_eState == WebSocketState::Open) {
215 uint8_t closeFrame[2] = { 0x88, 0x00 };
216 if (!m_pImpl->m_pStream.IsNull()) {
217 try {
218 m_pImpl->m_pStream->Write(reinterpret_cast<const char*>(closeFrame), 0, 2);
219 } catch (...) {
220 // Suppress stream write errors during close handshake
221 (void)0;
222 }
223 }
225 m_pImpl->m_eState = WebSocketState::Closed;
226 }
227 }
228
229 }
230 }
231 }
232}
Defines the exception thrown when an invalid argument is provided to a method.
Represents an RFC 6455 full-duplex WebSocket connection over a NetworkStream.
Exception thrown when an error occurs during RFC 6455 WebSocket communication or handshakes.
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
Definition Array.h:29
int GetLength() const
Gets the total number of elements in all dimensions of the Array.
Definition Array.h:142
T * GetData()
Gets a pointer to the contiguous internal element buffer.
Definition Array.h:146
WebSocketException(const String &sMessage)
Constructs a WebSocketException with a custom descriptive error message.
~WebSocket() override
Destructor for WebSocket.
void Close()
Transmits an RFC 6455 close frame (opcode 0x8) and marks the state as Closed.
bool SendBytes(const Array< uint8_t > &data)
Sends a complete binary message frame (opcode 0x2) to the remote peer.
bool SendAsync(const String &message)
Sends a complete UTF-8 text message frame (opcode 0x1) to the remote peer.
WebSocketState GetState() const
Gets the current operational state of the WebSocket.
static String ComputeSecWebSocketAccept(const String &secWebSocketKey)
Computes the RFC 6455 Sec-WebSocket-Accept handshake header from a client challenge key.
bool ReceiveText(String &outMessage)
Receives and decodes the next complete text message frame from the stream.
void SetState(WebSocketState state)
Sets the operational state of the WebSocket.
WebSocket(SmartPointer< Sockets::NetworkStream > pStream)
Constructs a WebSocket instance bound to the specified open network stream.
Supports all classes in the DotNetDupe class hierarchy.
Definition Object.h:18
A unified smart pointer that supports both unique and shared ownership semantics.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
const char * GetRawString() const
Definition String.cpp:230
Provides a re-entrant mutual exclusion primitive for thread synchronization.
Provides an RAII-style scoped lock wrapper around synchronization primitives.
Definition Lock.h:21
static void BuildWebSocketFrameHeader(uint8_t opcode, size_t len, std::vector< uint8_t > &frame)
Definition WebSocket.cpp:17
WebSocketState
Defines the operational states that a WebSocket connection can be in.
Definition WebSocket.h:23
@ Closed
The connection has cleanly closed.
Definition WebSocket.h:29
@ Open
The connection is open and ready for sending/receiving frames.
Definition WebSocket.h:26
@ NativeError
Underlying socket or OS network error.
@ ConnectionClosedPrematurely
Connection closed before frame payload was completely read.
@ InvalidState
Attempted operation on an inactive, closed, or aborted socket.