Created
August 28, 2018 11:37
-
-
Save fecf/b7bacbaff4a27219afb7ab3879b90830 to your computer and use it in GitHub Desktop.
bare minimum websocket server with c++/winrt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
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