Skip to content

Instantly share code, notes, and snippets.

@fecf
Created August 28, 2018 11:37
Show Gist options
  • Save fecf/b7bacbaff4a27219afb7ab3879b90830 to your computer and use it in GitHub Desktop.
Save fecf/b7bacbaff4a27219afb7ab3879b90830 to your computer and use it in GitHub Desktop.
bare minimum websocket server with c++/winrt
...
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Networking::Sockets;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Security::Cryptography;
using namespace winrt::Windows::Security::Cryptography::Core;
StreamSocketListener websocket_;
...
websocket_ = StreamSocketListener();
websocket_.BindServiceNameAsync(winrt::to_hstring(websocket_port)).get();
websocket_.ConnectionReceived(
[&](StreamSocketListener sender,
StreamSocketListenerConnectionReceivedEventArgs args) -> void {
StreamSocket& socket = args.Socket();
bool upgraded = false;
while (true) {
// Recv
constexpr std::size_t recv_buf_size = 8192;
Buffer buffer(recv_buf_size);
socket.InputStream()
.ReadAsync(buffer, recv_buf_size, InputStreamOptions::Partial)
.get();
std::size_t length = buffer.Length();
if (length == 0) {
std::wcout << L"connection closed? (received length == 0)"
<< std::endl;
return;
}
// Parse
std::wstring text{};
text.reserve(length);
DataReader reader = DataReader::FromBuffer(buffer);
if (upgraded) {
// Assume websocket frame
std::size_t payload_start = (16 + 32) / 8;
if (length < payload_start) {
std::wcout << L"not enough length." << std::endl;
continue;
}
std::vector<uint8_t> data(length);
reader.ReadBytes(data);
std::array<std::uint8_t, 8> header{};
::memcpy(&header, data.data(), 8);
// OpCode
unsigned char op = header[0] & 0x0f;
switch (op) {
case 0x0:
std::wcout << L"0x0 not supported concatenated frame."
<< std::endl;
continue;
case 0x1:
// got text frame
break;
case 0x2:
std::wcout << L"0x2 not supported binary frame." << std::endl;
continue;
case 0x8:
// std::wcout << L"0x8 closed." << std::endl;
// close socket
return;
case 0x9:
std::wcout << L"0x9 received ping." << std::endl;
// send pong
{
DataWriter writer{socket.OutputStream()};
writer.WriteByte(0xA);
writer.StoreAsync().get();
}
continue;
case 0xA:
std::wcout << L"0xA received pong." << std::endl;
continue;
default:
std::wcout << L"close due to unknown opcode." << std::endl;
// close socket
return;
}
// Text frame
bool fin = (header[0] >> 7);
if (!fin) {
std::wcout << L"not supported concatenated message."
<< std::endl;
}
// Masked
bool masked = (header[1] >> 7) & 0x01;
if (!masked) {
std::wcout << L"not masked." << std::endl;
break;
}
// Payload length and mask
unsigned int payload_len = header[1] & 0x7f;
std::array<std::uint8_t, 4> mask;
if (payload_len >= 126) {
if (payload_len == 126) {
::memcpy(&payload_len, data.data() + 2, 2);
::memcpy(mask.data(), data.data() + 4, 4);
} else {
::memcpy(&payload_len, data.data() + 2, 8);
::memcpy(mask.data(), data.data() + 10, 4);
payload_start += 6;
}
} else {
::memcpy(mask.data(), data.data() + 2, 4);
}
if (data.size() < payload_start + payload_len) {
std::wcout << L"not enough data size. excepted "
<< payload_start + payload_len << " actual "
<< data.size() << std::endl;
}
// Decode
std::vector<uint8_t> decoded;
decoded.resize(payload_len);
for (int i = 0; i < (int)payload_len; ++i) {
decoded[i] = data[i + payload_start] ^ mask[i % 4];
}
IBuffer decoded_buffer =
CryptographicBuffer::CreateFromByteArray(decoded);
winrt::hstring decoded_buffer_utf8 =
CryptographicBuffer::ConvertBinaryToString(
BinaryStringEncoding::Utf8, decoded_buffer);
// std::wcout << L"decoded message: " <<
// decoded_buffer_utf8.c_str() << std::endl;
} else {
// Assume plain-text
try {
winrt::hstring hstr = reader.ReadString((uint32_t)length);
text = hstr;
} catch (std::exception ex) {
std::wcout << L"exception occured (" << ex.what() << ")"
<< std::endl;
}
}
// std::wcout << L"--- received start --- " << std::endl;
// std::wcout << text << std::endl;
// std::wcout << L"--- received end --- " << std::endl;
// Handshake
std::match_results<std::wstring::const_iterator> match;
if (!upgraded && std::regex_search(text, std::wregex(L"^GET")) &&
std::regex_search(text, match,
std::wregex(L"Sec-WebSocket-Key: (.*)"))) {
if (match.size() == 2) {
std::wstringstream ss;
ss << L"HTTP/1.1 101 Switching Protocols" << std::endl;
ss << L"Upgrade: websocket" << std::endl;
ss << L"Connection: Upgrade" << std::endl;
// Generate Sec-WebSocket-Accept
const winrt::hstring guid =
L"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const winrt::hstring accept_src = match[1].str() + guid;
HashAlgorithmProvider hash_provider =
HashAlgorithmProvider::OpenAlgorithm(
HashAlgorithmNames::Sha1());
IBuffer accept_src_buf =
CryptographicBuffer::ConvertStringToBinary(
accept_src, BinaryStringEncoding::Utf8);
IBuffer accept_hash = hash_provider.HashData(accept_src_buf);
winrt::hstring accept_base64 =
CryptographicBuffer::EncodeToBase64String(accept_hash);
ss << L"Sec-WebSocket-Accept: " << std::wstring(accept_base64)
<< std::endl;
ss << std::endl;
DataWriter writer{socket.OutputStream()};
writer.WriteString(ss.str());
writer.StoreAsync().get();
upgraded = true;
}
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment