/** * @file WSFrame_client.h * @brief WebSocket frame helpers for the ImGui client (no MARTe2 dependency). * * Mirrors WSFrame.h from Source/Applications/StreamHub/ using standard C++ types. */ #pragma once #include #include namespace StreamHubClient { static const uint8_t WS_OPCODE_CONTINUATION = 0x00u; static const uint8_t WS_OPCODE_TEXT = 0x01u; static const uint8_t WS_OPCODE_BINARY = 0x02u; static const uint8_t WS_OPCODE_CLOSE = 0x08u; static const uint8_t WS_OPCODE_PING = 0x09u; static const uint8_t WS_OPCODE_PONG = 0x0Au; static const uint8_t WS_FIN_BIT = 0x80u; static const uint8_t WS_MASK_BIT = 0x80u; /** @brief Encode a WebSocket frame header (server→client, no masking). * @return Number of header bytes written (2, 4, or 10). */ inline uint32_t WSEncodeHeader(uint8_t* buf, uint8_t opcode, uint64_t payloadLen) { buf[0] = WS_FIN_BIT | opcode; if (payloadLen < 126u) { buf[1] = static_cast(payloadLen); return 2u; } else if (payloadLen < 65536u) { buf[1] = 126u; buf[2] = static_cast((payloadLen >> 8u) & 0xFFu); buf[3] = static_cast(payloadLen & 0xFFu); return 4u; } else { buf[1] = 127u; for (int i = 7; i >= 0; i--) { buf[2 + i] = static_cast(payloadLen & 0xFFu); payloadLen >>= 8u; } return 10u; } } struct WSFrameHeader { bool fin; uint8_t opcode; bool masked; uint64_t payloadLen; uint8_t maskKey[4]; uint32_t headerSize; }; inline bool WSParseHeader(const uint8_t* buf, uint32_t bufLen, WSFrameHeader& hdr) { if (bufLen < 2u) { return false; } hdr.fin = (buf[0] & WS_FIN_BIT) != 0u; hdr.opcode = buf[0] & 0x0Fu; hdr.masked = (buf[1] & WS_MASK_BIT) != 0u; uint32_t hdrSize = 2u; uint64_t plen = static_cast(buf[1] & 0x7Fu); if (plen == 126u) { if (bufLen < 4u) { return false; } plen = (static_cast(buf[2]) << 8u) | static_cast(buf[3]); hdrSize += 2u; } else if (plen == 127u) { if (bufLen < 10u) { return false; } plen = 0u; for (int i = 0; i < 8; i++) { plen = (plen << 8u) | static_cast(buf[2 + i]); } hdrSize += 8u; } if (hdr.masked) { if (bufLen < hdrSize + 4u) { return false; } hdr.maskKey[0] = buf[hdrSize]; hdr.maskKey[1] = buf[hdrSize + 1u]; hdr.maskKey[2] = buf[hdrSize + 2u]; hdr.maskKey[3] = buf[hdrSize + 3u]; hdrSize += 4u; } else { hdr.maskKey[0] = hdr.maskKey[1] = hdr.maskKey[2] = hdr.maskKey[3] = 0u; } hdr.payloadLen = plen; hdr.headerSize = hdrSize; return true; } inline void WSUnmask(uint8_t* payload, uint32_t len, const uint8_t mask[4]) { for (uint32_t i = 0u; i < len; i++) { payload[i] ^= mask[i % 4u]; } } /* ── SHA-1 (for WebSocket handshake) ──────────────────────────────────────── */ inline void WS_SHA1(const uint8_t* data, uint32_t len, uint8_t digest[20]) { /* FIPS 180-4 SHA-1 — sufficient for a 60-byte input (key+GUID) */ uint32_t h0 = 0x67452301u, h1 = 0xEFCDAB89u, h2 = 0x98BADCFEu, h3 = 0x10325476u, h4 = 0xC3D2E1F0u; auto rot32 = [](uint32_t v, int n) -> uint32_t { return (v << n) | (v >> (32 - n)); }; /* Build padded message */ uint32_t bitLen = len * 8u; uint32_t padLen = (len % 64u < 56u) ? (56u - len % 64u) : (120u - len % 64u); uint32_t msgLen = len + padLen + 8u; uint8_t* msg = new uint8_t[msgLen](); memcpy(msg, data, len); msg[len] = 0x80u; /* Big-endian bit-length at end */ msg[msgLen - 4] = static_cast((bitLen >> 24) & 0xFF); msg[msgLen - 3] = static_cast((bitLen >> 16) & 0xFF); msg[msgLen - 2] = static_cast((bitLen >> 8) & 0xFF); msg[msgLen - 1] = static_cast( bitLen & 0xFF); for (uint32_t blk = 0u; blk < msgLen; blk += 64u) { uint32_t w[80]; for (int i = 0; i < 16; i++) { w[i] = (static_cast(msg[blk + i*4]) << 24) | (static_cast(msg[blk + i*4+1]) << 16) | (static_cast(msg[blk + i*4+2]) << 8) | static_cast(msg[blk + i*4+3]); } for (int i = 16; i < 80; i++) { w[i] = rot32(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); } uint32_t a=h0,b=h1,c=h2,d=h3,e=h4,f,k,t; for (int i = 0; i < 80; i++) { if (i < 20) { f=( b & c)|((~b)& d); k=0x5A827999u; } else if (i < 40) { f= b ^ c ^ d; k=0x6ED9EBA1u; } else if (i < 60) { f=(b&c)|(b&d)|(c&d); k=0x8F1BBCDCu; } else { f= b ^ c ^ d; k=0xCA62C1D6u; } t = rot32(a,5) + f + e + k + w[i]; e=d; d=c; c=rot32(b,30); b=a; a=t; } h0+=a; h1+=b; h2+=c; h3+=d; h4+=e; } delete[] msg; auto put32 = [&](uint8_t* p, uint32_t v) { p[0]=v>>24; p[1]=(v>>16)&0xFF; p[2]=(v>>8)&0xFF; p[3]=v&0xFF; }; put32(digest, h0); put32(digest+4, h1); put32(digest+8, h2); put32(digest+12, h3); put32(digest+16, h4); } /* ── Base64 encode (for WebSocket handshake) ─────────────────────────────── */ inline uint32_t WS_Base64Encode(const uint8_t* in, uint32_t inLen, char* out) { static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; uint32_t o = 0; for (uint32_t i = 0; i < inLen; ) { uint32_t b = static_cast(in[i++]) << 16; int have = 1; if (i < inLen) { b |= static_cast(in[i++]) << 8; have++; } if (i < inLen) { b |= static_cast(in[i++]); have++; } out[o++] = tbl[(b >> 18) & 0x3F]; out[o++] = tbl[(b >> 12) & 0x3F]; out[o++] = (have >= 2) ? tbl[(b >> 6) & 0x3F] : '='; out[o++] = (have >= 3) ? tbl[ b & 0x3F] : '='; } out[o] = '\0'; return o; } } /* namespace StreamHubClient */