5#include <openssl/sha.h>
6#include <openssl/evp.h>
19 frame.push_back(opcode);
22 frame.push_back(
static_cast<uint8_t
>(len));
23 }
else if (len <= 65535) {
25 frame.push_back(
static_cast<uint8_t
>((len >> 8) & 0xFF));
26 frame.push_back(
static_cast<uint8_t
>(len & 0xFF));
29 for (
int i = 7; i >= 0; --i) frame.push_back(
static_cast<uint8_t
>((len >> (i * 8)) & 0xFF));
33 class WebSocket::Impl :
public Object {
48 bool ReadExtended16(uint64_t& payloadLen) {
50 uint8_t extLen[2] = { 0 };
51 int bytesRead = m_pStream->Read(
reinterpret_cast<char*
>(extLen), 0, 2);
55 payloadLen = (
static_cast<uint64_t
>(extLen[0]) << 8) | extLen[1];
59 bool ReadExtended64(uint64_t& payloadLen) {
61 uint8_t extLen[8] = { 0 };
62 int bytesRead = m_pStream->Read(
reinterpret_cast<char*
>(extLen), 0, 8);
67 for (
int i = 0; i < 8; ++i) payloadLen = (payloadLen << 8) | extLen[i];
71 bool ReadFrameHeader(uint8_t& opcode,
bool& masked, uint64_t& payloadLen) {
73 uint8_t header[2] = { 0 };
76 bytesRead = m_pStream->Read(
reinterpret_cast<char*
>(header), 0, 2);
77 }
catch (
const Exception& ex) {
80 if (bytesRead <= 0)
return false;
82 opcode = header[0] & 0x0F;
83 masked = (header[1] & 0x80) != 0;
84 payloadLen = header[1] & 0x7F;
88 bool ReadExtendedLength(uint64_t& payloadLen) {
90 if (payloadLen == 126)
return ReadExtended16(payloadLen);
91 if (payloadLen == 127)
return ReadExtended64(payloadLen);
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) {
105 totalRead += bytesRead;
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];
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) {
122 ReadPayloadBytes(payloadLen, payload);
123 if (masked) UnmaskPayload(maskKey, payloadLen, payload);
127 bool WriteFrame(uint8_t opcode,
const uint8_t* pData,
size_t len) {
129 Threading::Lock<Threading::CriticalSection> lock(m_csLock);
134 std::vector<uint8_t> frame;
136 if (pData !=
nullptr && len > 0) frame.insert(frame.end(), pData, pData + len);
139 m_pStream->Write(
reinterpret_cast<const char*
>(frame.data()), 0,
static_cast<int>(frame.size()));
140 }
catch (
const Exception& ex) {
156 m_pImpl->m_eState = state;
161 if (secWebSocketKey.
IsEmpty()) {
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);
179 return m_pImpl->WriteFrame(0x81,
reinterpret_cast<const uint8_t*
>(text.data()), text.length());
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();
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());
215 uint8_t closeFrame[2] = { 0x88, 0x00 };
216 if (!m_pImpl->m_pStream.IsNull()) {
218 m_pImpl->m_pStream->Write(
reinterpret_cast<const char*
>(closeFrame), 0, 2);
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...
int GetLength() const
Gets the total number of elements in all dimensions of the Array.
T * GetData()
Gets a pointer to the contiguous internal element buffer.
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.
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.
String()
Initializes a new instance of the String class to an empty string.
const char * GetRawString() const
Provides a re-entrant mutual exclusion primitive for thread synchronization.
Provides an RAII-style scoped lock wrapper around synchronization primitives.
static void BuildWebSocketFrameHeader(uint8_t opcode, size_t len, std::vector< uint8_t > &frame)
WebSocketState
Defines the operational states that a WebSocket connection can be in.
@ Closed
The connection has cleanly closed.
@ Open
The connection is open and ready for sending/receiving frames.
@ 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.