From c8909e4e8ca5627b6685224865f77ebd78637ccb Mon Sep 17 00:00:00 2001 From: Giulio Eulisse <10544+ktf@users.noreply.github.com> Date: Wed, 13 Jan 2021 15:44:21 +0100 Subject: [PATCH] DPL: introduce a WebSocketHandler * Minimal HTTP request handling * Simple request line * Headers * Chunked body * Minimal HTTP reply handling * Simple reply line * Headers * Chunked body * Calculate Sec-WebSockets-Accept correctly * Initial WebSocket frame decoder including: * multiple frames parsing --- Framework/Core/CMakeLists.txt | 4 + Framework/Core/src/Base64.cxx | 237 +++++++++++++++ Framework/Core/src/Base64.h | 22 ++ Framework/Core/src/HTTPParser.cxx | 372 ++++++++++++++++++++++++ Framework/Core/src/HTTPParser.h | 162 +++++++++++ Framework/Core/src/SHA1.cxx | 300 +++++++++++++++++++ Framework/Core/src/SHA1.h | 39 +++ Framework/Core/test/test_HTTPParser.cxx | 234 +++++++++++++++ 8 files changed, 1370 insertions(+) create mode 100644 Framework/Core/src/Base64.cxx create mode 100644 Framework/Core/src/Base64.h create mode 100644 Framework/Core/src/HTTPParser.cxx create mode 100644 Framework/Core/src/HTTPParser.h create mode 100644 Framework/Core/src/SHA1.cxx create mode 100644 Framework/Core/src/SHA1.h create mode 100644 Framework/Core/test/test_HTTPParser.cxx diff --git a/Framework/Core/CMakeLists.txt b/Framework/Core/CMakeLists.txt index eed8197f3b23e..a6252c175ef0a 100644 --- a/Framework/Core/CMakeLists.txt +++ b/Framework/Core/CMakeLists.txt @@ -60,6 +60,7 @@ o2_add_library(Framework src/ConfigurationOptionsRetriever.cxx src/FreePortFinder.cxx src/GraphvizHelpers.cxx + src/HTTPParser.cxx src/InputRecord.cxx src/InputSpec.cxx src/OutputSpec.cxx @@ -97,6 +98,8 @@ o2_add_library(Framework src/ExternalFairMQDeviceProxy.cxx src/HistogramRegistry.cxx src/StepTHn.cxx + src/SHA1.cxx + src/Base64.cxx test/TestClasses.cxx PRIVATE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/src PUBLIC_LINK_LIBRARIES AliceO2::Common @@ -157,6 +160,7 @@ foreach(t Graphviz GroupSlicer HistogramRegistry + HTTPParser IndexBuilder InfoLogger InputRecord diff --git a/Framework/Core/src/Base64.cxx b/Framework/Core/src/Base64.cxx new file mode 100644 index 0000000000000..b5644784dde84 --- /dev/null +++ b/Framework/Core/src/Base64.cxx @@ -0,0 +1,237 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include "Base64.h" + +#include +#include +#include + +#include +#include + +namespace o2::framework::internal +{ +static char encoder[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static char decoder[256]; +static int initialized; + +static void + base64_init_decoder() +{ + int i = 0; + + if (initialized) { + return; + } + + // -1 is used for error detection + memset(decoder, -1, sizeof decoder); + + for (; i < 64; decoder[(int)encoder[i]] = i, ++i) { + } + + initialized = 1; + + return; +} + +static int + base64_encsize(int size) +{ + return 4 * ((size + 2) / 3); +} + +int base64_encode(char* dest, int size, unsigned char* src, int slen) +{ + int dlen, i, j; + uint32_t a, b, c, triple; + + dlen = base64_encsize(slen); + + // Sanity checks + if (src == nullptr || dest == nullptr) { + return -1; + } + if (dlen + 1 > size) { + return -1; + } + if (slen == 0) { + if (size > 0) { + dest[0] = 0; + return 0; + } + return -1; + } + + for (i = 0, j = 0; i < slen;) { + a = src[i++]; + + // b and c may be off limit + b = i < slen ? src[i++] : 0; + c = i < slen ? src[i++] : 0; + + triple = (a << 16) + (b << 8) + c; + + dest[j++] = encoder[(triple >> 18) & 0x3F]; + dest[j++] = encoder[(triple >> 12) & 0x3F]; + dest[j++] = encoder[(triple >> 6) & 0x3F]; + dest[j++] = encoder[triple & 0x3F]; + } + + // Pad zeroes at the end + switch (slen % 3) { + case 1: + dest[j - 2] = '='; + case 2: + dest[j - 1] = '='; + } + + // Always add \0 + dest[j] = 0; + + return dlen; +} + +char* base64_enc_malloc(unsigned char* src, int slen) +{ + int size; + char* buffer; + + size = base64_encsize(slen) + 1; + + buffer = (char*)malloc(size); + if (buffer == nullptr) { + return nullptr; + } + + size = base64_encode(buffer, size, src, slen); + if (size == -1) { + free(buffer); + return nullptr; + } + + return buffer; +} + +static int + base64_decsize(char* src) +{ + int slen, size, i; + + if (src == nullptr) { + return 0; + } + + slen = strlen(src); + size = slen / 4 * 3; + + // Count pad chars + for (i = 0; src[slen - i - 1] == '='; ++i) { + } + + // Remove at most 2 bytes. + return size - (i >= 2 ? 2 : i); +} + +int base64_decode(unsigned char* dest, int size, char* src) +{ + int slen, dlen, padlen, i, j; + uint32_t a, b, c, d, triple; + + // Initialize decoder + base64_init_decoder(); + + // Sanity checks + if (src == nullptr || dest == nullptr) { + return -1; + } + + slen = strlen(src); + if (slen == 0) { + if (size > 0) { + dest[0] = 0; + return 0; + } + return -1; + } + + // Remove trailing pad characters. + for (padlen = 0; src[slen - padlen - 1] == '='; ++padlen) { + } + if (padlen > 2) { + slen -= padlen - 2; + } + if (slen % 4) { + return -1; + } + + dlen = base64_decsize(src); + + // Check buffer size + if (dlen + 1 > size) { + return -1; + } + + for (i = 0, j = 0; i < slen;) { + a = decoder[(int)src[i++]]; + b = decoder[(int)src[i++]]; + c = decoder[(int)src[i++]]; + d = decoder[(int)src[i++]]; + + // Sextet 3 and 4 may be zero at the end + if (i == slen) { + if (src[slen - 1] == '=') { + d = 0; + if (src[slen - 2] == '=') { + c = 0; + } + } + } + + // Non-Base64 char + if (a == -1 || b == -1 || c == -1 || d == -1) { + return -1; + } + + triple = (a << 18) + (b << 12) + (c << 6) + d; + + dest[j++] = (triple >> 16) & 0xFF; + dest[j++] = (triple >> 8) & 0xFF; + dest[j++] = triple & 0xFF; + } + + // Always add \0 + dest[j] = 0; + + return dlen; +} + +unsigned char* + base64_dec_malloc(char* src) +{ + int size; + unsigned char* buffer; + + size = base64_decsize(src) + 1; + + buffer = (unsigned char*)malloc(size); + if (buffer == nullptr) { + return nullptr; + } + + size = base64_decode(buffer, size, src); + if (size == -1) { + free(buffer); + return nullptr; + } + + return buffer; +} +} // namespace o2::framework::internal diff --git a/Framework/Core/src/Base64.h b/Framework/Core/src/Base64.h new file mode 100644 index 0000000000000..a9dc7d53c7258 --- /dev/null +++ b/Framework/Core/src/Base64.h @@ -0,0 +1,22 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FRAMEWORK_BASE64_H_ +#define O2_FRAMEWORK_BASE64_H_ + +namespace o2::framework::internal +{ +int base64_encode(char* dest, int size, unsigned char* src, int slen); +char* base64_enc_malloc(unsigned char* src, int slen); +int base64_decode(unsigned char* dest, int size, char* src); +unsigned char* base64_dec_malloc(char* src); +} // namespace o2::framework::internal + +#endif // O2_FRAMEWORK_BASE64_H_ diff --git a/Framework/Core/src/HTTPParser.cxx b/Framework/Core/src/HTTPParser.cxx new file mode 100644 index 0000000000000..da234412793f3 --- /dev/null +++ b/Framework/Core/src/HTTPParser.cxx @@ -0,0 +1,372 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "HTTPParser.h" +#include +#include "SHA1.h" +#include "Base64.h" + +using namespace o2::framework::internal; +namespace o2::framework +{ + +namespace +{ +/// WebSocket RFC requires XOR masking from the client +void memmask(char* dst, char const* src, size_t size, uint32_t mask) +{ + if (mask) { + char* m = (char*)&mask; + for (size_t len = 0; len < size; ++len) { + *dst++ = *src++ ^ m[len % 4]; + } + } else { + memcpy(dst, src, size); + } +} + +void memunmask(char* data, size_t size, uint32_t mask) +{ + char* m = (char*)&mask; + for (size_t len = 0; len < size; ++len) { + *data++ ^= m[len % 4]; + } +} +} // namespace + +void encode_websocket_frames(std::vector& outputs, char const* src, size_t size, WebSocketOpCode opcode, uint32_t mask) +{ + void* finalHeader; + size_t headerSize; + char* buffer = nullptr; + char* startPayload = nullptr; + std::vector result; + + if (size < 126) { + headerSize = sizeof(WebSocketFrameTiny); + buffer = (char*)malloc(headerSize + size); + WebSocketFrameTiny* header = (WebSocketFrameTiny*)buffer; + memset(buffer, 0, headerSize); + header->len = size; + } else if (size < 1 << 16) { + headerSize = sizeof(WebSocketFrameShort); + buffer = (char*)malloc(headerSize + size); + WebSocketFrameShort* header = (WebSocketFrameShort*)buffer; + memset(buffer, 0, headerSize); + header->len = 126; + header->len16 = size; + } else { + headerSize = sizeof(WebSocketFrameHuge); + buffer = (char*)malloc(headerSize + size); + WebSocketFrameHuge* header; + memset(buffer, 0, headerSize); + header->len = 127; + header->len64 = size; + } + size_t fullHeaderSize = (mask ? 4 : 0) + headerSize; + startPayload = buffer + fullHeaderSize; + WebSocketFrameTiny* header = (WebSocketFrameTiny*)buffer; + header->fin = 1; + header->opcode = (unsigned char)opcode; // binary or text for now + // Mask is right before payload. + if (mask) { + *((uint32_t*)(startPayload - 4)) = mask; + } + header->mask = mask ? 1 : 0; + memmask(startPayload, src, size, mask); + outputs.push_back(uv_buf_init(buffer, size + fullHeaderSize)); +} + +void decode_websocket(char* start, size_t size, WebSocketHandler& handler) +{ + char* cur = start; + while (cur - start < size) { + WebSocketFrameTiny* header = (WebSocketFrameTiny*)cur; + size_t payloadSize = 0; + size_t headerSize = 0; + if (header->len < 126) { + payloadSize = header->len; + headerSize = 2 + (header->mask ? 4 : 0); + } else if (header->len == 126) { + WebSocketFrameShort* headerSmall = (WebSocketFrameShort*)cur; + payloadSize = headerSmall->len16; + headerSize = 2 + 2 + (header->mask ? 4 : 0); + } else if (header->len == 127) { + WebSocketFrameHuge* headerSmall = (WebSocketFrameHuge*)cur; + payloadSize = headerSmall->len64; + headerSize = 2 + 8 + (header->mask ? 4 : 0); + } + if (header->mask) { + int32_t mask = *(int32_t*)(cur + headerSize - 4); + memunmask(cur, payloadSize, mask); + } + handler.frame(cur + headerSize, payloadSize); + cur += headerSize + payloadSize; + } +} + +std::string encode_websocket_handshake_request(const char* endpoint, const char* protocol, int version, char const* nonce, + std::vector> headers) +{ + char const* res = + "GET {} HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: {}\r\n" + "Sec-WebSocket-Protocol: {}\r\n" + "Sec-WebSocket-Version: {}\r\n" + "{}\r\n"; + std::string encodedHeaders; + for (auto [k, v] : headers) { + encodedHeaders += std::string(fmt::format("{}: {}\r\n", k, v)); + } + return fmt::format(res, endpoint, nonce, protocol, version, encodedHeaders); +} + +std::string HTTPParserHelpers::calculateAccept(const char* nonce) +{ + std::string reply = std::string(nonce) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char sha[20]; + SHA1(sha, reply.data(), reply.size()); + char base[64]; + base64_encode(base, 64, (unsigned char*)sha, 20); + return fmt::format("{}", base); +} + +std::string encode_websocket_handshake_reply(char const* nonce) +{ + char const* res = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: {}\r\n\r\n"; + return fmt::format(res, HTTPParserHelpers::calculateAccept(nonce)); +} + +void parse_http_request(char const* start, size_t size, HTTPParser* parser) +{ + enum HTTPState state = HTTPState::IN_START; + // Too short, let's try again... + if (size < 2) { + parser->remaining += std::string_view(start, size); + } + char const* cur = start; + char const* next = cur; + std::string_view lastToken(cur, 0); + std::string_view lastKey; + std::string_view lastValue; + std::string lastError; + if (parser->states.empty()) { + parser->states.push_back(HTTPState::IN_START); + } + char const* delimiters = nullptr; + char const* skippable = nullptr; + char const* separator = nullptr; + char const* spaces = "\t \v"; + char const* colon = ":"; + char const* newline = "\r\n"; + bool done = false; + + while (!done) { + HTTPState state = parser->states.back(); + parser->states.pop_back(); + switch (state) { + case HTTPState::IN_START: + parser->states.push_back(HTTPState::BEGIN_METHOD); + break; + case HTTPState::IN_START_REPLY: + parser->states.push_back(HTTPState::BEGIN_REPLY_VERSION); + break; + case HTTPState::BEGIN_REPLY_VERSION: + parser->states.push_back(HTTPState::END_REPLY_VERSION); + parser->states.push_back(HTTPState::IN_CAPTURE_DELIMITERS); + delimiters = spaces; + break; + case HTTPState::END_REPLY_VERSION: + parser->replyVersion(lastToken); + parser->states.push_back(HTTPState::BEGIN_REPLY_CODE); + parser->states.push_back(HTTPState::IN_SKIP_CHARS); + skippable = spaces; + break; + case HTTPState::BEGIN_REPLY_CODE: + parser->states.push_back(HTTPState::END_REPLY_CODE); + parser->states.push_back(HTTPState::IN_CAPTURE_DELIMITERS); + delimiters = spaces; + break; + case HTTPState::END_REPLY_CODE: + parser->replyCode(lastToken); + parser->states.push_back(HTTPState::BEGIN_REPLY_MESSAGE); + parser->states.push_back(HTTPState::IN_SKIP_CHARS); + skippable = spaces; + break; + case HTTPState::BEGIN_REPLY_MESSAGE: + parser->states.push_back(HTTPState::END_REPLY_MESSAGE); + parser->states.push_back(HTTPState::IN_CAPTURE_SEPARATOR); + separator = newline; + break; + case HTTPState::END_REPLY_MESSAGE: + parser->replyMessage(lastToken); + parser->states.push_back(HTTPState::BEGIN_HEADERS); + break; + case HTTPState::BEGIN_METHOD: + parser->states.push_back(HTTPState::END_METHOD); + parser->states.push_back(HTTPState::IN_CAPTURE_DELIMITERS); + delimiters = spaces; + break; + case HTTPState::END_METHOD: + parser->method(lastToken); + parser->states.push_back(HTTPState::BEGIN_TARGET); + parser->states.push_back(HTTPState::IN_SKIP_CHARS); + skippable = spaces; + break; + case HTTPState::BEGIN_TARGET: + parser->states.push_back(HTTPState::END_TARGET); + parser->states.push_back(HTTPState::IN_CAPTURE_DELIMITERS); + delimiters = spaces; + break; + case HTTPState::END_TARGET: + parser->target(lastToken); + parser->states.push_back(HTTPState::BEGIN_VERSION); + parser->states.push_back(HTTPState::IN_SKIP_CHARS); + skippable = spaces; + break; + case HTTPState::BEGIN_VERSION: + parser->states.push_back(HTTPState::END_VERSION); + parser->states.push_back(HTTPState::IN_CAPTURE_SEPARATOR); + separator = newline; + break; + case HTTPState::END_VERSION: + parser->version(lastToken); + parser->states.push_back(HTTPState::BEGIN_HEADERS); + break; + case HTTPState::BEGIN_HEADERS: + parser->states.push_back(HTTPState::BEGIN_HEADER); + break; + case HTTPState::BEGIN_HEADER: + parser->states.push_back(HTTPState::BEGIN_HEADER_KEY); + break; + case HTTPState::BEGIN_HEADER_KEY: + parser->states.push_back(HTTPState::END_HEADER_KEY); + parser->states.push_back(HTTPState::IN_CAPTURE_SEPARATOR); + separator = colon; + break; + case HTTPState::END_HEADER_KEY: + lastKey = lastToken; + parser->states.push_back(HTTPState::BEGIN_HEADER_VALUE); + parser->states.push_back(HTTPState::IN_SKIP_CHARS); + skippable = spaces; + break; + case HTTPState::BEGIN_HEADER_VALUE: + parser->states.push_back(HTTPState::END_HEADER_VALUE); + parser->states.push_back(HTTPState::IN_CAPTURE_SEPARATOR); + separator = newline; + break; + case HTTPState::END_HEADER_VALUE: + lastValue = lastToken; + parser->states.push_back(HTTPState::END_HEADER); + break; + case HTTPState::END_HEADER: + if (strncmp("\r\n", next, 2) == 0) { + parser->header(lastKey, lastValue); + parser->states.push_back(HTTPState::END_HEADERS); + next += 2; + cur = next; + } else { + parser->header(lastKey, lastValue); + parser->states.push_back(HTTPState::BEGIN_HEADER); + cur = next; + } + break; + case HTTPState::END_HEADERS: + parser->endHeaders(); + parser->states.push_back(HTTPState::BEGIN_BODY); + break; + case HTTPState::BEGIN_BODY: { + size_t bodySize = size - (cur - start); + parser->body(std::string_view(cur, bodySize)); + next = cur + bodySize; + cur = next; + parser->states.push_back(HTTPState::BEGIN_BODY); + parser->states.push_back(HTTPState::IN_DONE); + } break; + case HTTPState::IN_SKIP_CHARS: + while (true) { + if (next - start == size) { + parser->remaining += std::string_view(cur, next - cur); + } + if (strchr(skippable, *next)) { + next++; + continue; + } + cur = next; + break; + } + break; + case HTTPState::IN_SEPARATOR: + if (memcmp(separator, cur, strlen(separator)) != 0) { + parser->states.push_back(HTTPState::IN_ERROR); + break; + } + next += strlen(separator); + cur = next; + break; + case HTTPState::IN_CAPTURE_DELIMITERS: + while (true) { + if (next - start == size) { + parser->remaining += std::string_view(cur, next - cur); + } + if (strchr(delimiters, *next) == nullptr) { + next++; + continue; + } + lastToken = std::string_view(cur, next - cur); + cur = next; + break; + } + break; + case HTTPState::IN_CAPTURE_SEPARATOR: + while (true) { + if (next + strlen(separator) - start == size) { + parser->remaining += std::string_view(cur, next - cur); + } + if (memcmp(separator, next, strlen(separator)) != 0) { + next++; + continue; + } + lastToken = std::string_view(cur, next - cur); + next += strlen(separator); + cur = next; + break; + } + break; + case HTTPState::IN_DONE: + // The only case in which there can be a pending state when IN_DONE, is if + // we plan to resume processing. + if (parser->states.size() == 1 && parser->states.back() == HTTPState::BEGIN_BODY) { + done = true; + } else if (parser->states.empty()) { + done = true; + } else { + parser->states.push_back(HTTPState::IN_ERROR); + } + break; + case HTTPState::IN_ERROR: + parser->error = lastError; + parser->states.clear(); + done = true; + break; + default: + parser->states.push_back(HTTPState::IN_ERROR); + ; + ; + } + } +} +} // namespace o2::framework diff --git a/Framework/Core/src/HTTPParser.h b/Framework/Core/src/HTTPParser.h new file mode 100644 index 0000000000000..5de005d67cf4a --- /dev/null +++ b/Framework/Core/src/HTTPParser.h @@ -0,0 +1,162 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FRAMEWORK_HTTPPARSER_H_ +#define O2_FRAMEWORK_HTTPPARSER_H_ + +#include +#include +#include +#include +#include + +namespace o2::framework +{ + +struct __attribute__((__packed__)) WebSocketFrameTiny { + unsigned char fin : 1; + unsigned char rsv1 : 1; + unsigned char rsv2 : 1; + unsigned char rsv3 : 1; + unsigned char opcode : 4; + unsigned char mask : 1; + unsigned char len : 7; +}; + +struct __attribute__((__packed__)) WebSocketFrameShort { + unsigned char fin : 1; + unsigned char rsv1 : 1; + unsigned char rsv2 : 1; + unsigned char rsv3 : 1; + unsigned char opcode : 4; + unsigned char mask : 1; + unsigned char len : 7; + uint16_t len16; +}; + +struct __attribute__((__packed__)) WebSocketFrameHuge { + unsigned char fin : 1; + unsigned char rsv1 : 1; + unsigned char rsv2 : 1; + unsigned char rsv3 : 1; + unsigned char opcode : 4; + unsigned char mask : 1; + unsigned char len : 7; + uint64_t len64; +}; + +enum struct WebSocketFrameKind { + Tiny, + Short, + Huge +}; + +enum struct WebSocketOpCode : uint8_t { + Continuation = 0, + Text = 1, + Binary = 2, + Close = 8, + Ping = 9, + Pong = 10 +}; + +/// Encodes the request handshake for a given path / protocol / version. +/// @a path is the path of the websocket endpoint +/// @a protocol is the protocol required for the websocket connection +/// @a version is the protocol version +/// @a nonce is a unique randomly selected 16-byte value that has been base64-encoded. +/// @a headers with extra headers to be added to the request +std::string encode_websocket_handshake_request(const char* path, const char* protocol, int version, char const* nonce, + std::vector> headers = {}); + +/// Encodes the server reply for a given websocket connection +/// @a nonce the nonce of the request. +std::string encode_websocket_handshake_reply(char const* nonce); + +/// Encodes the buffer @a src which is @a size long to a number of buffers suitable to be sent via libuv. +/// If @a binary is provided the binary bit is set. +/// If @a mask is non zero, payload will be xored with the mask, as required by the WebSockets RFC +void encode_websocket_frames(std::vector& outputs, char const* src, size_t size, WebSocketOpCode opcode, uint32_t mask); + +/// An handler for a websocket message stream. +struct WebSocketHandler { + /// Invoked when all the headers are received. + virtual void headers(std::map const& headers){}; + /// FIXME: not implemented + virtual void beginFragmentation(){}; + /// Invoked when a frame it's parsed. Notice you do not own the data and you must + /// not free the memory. + virtual void frame(char const* frame, size_t s){}; + /// FIXME: not implemented + virtual void endFragmentation(){}; + /// FIXME: not implemented + virtual void control(char const* frame, size_t s){}; +}; + +/// Decoder for websocket data. For now we assume that the frame was not split. However multiple +/// frames might be present. +void decode_websocket(char* src, size_t size, WebSocketHandler& handler); + +enum struct HTTPState { + IN_START, + IN_START_REPLY, + BEGIN_REPLY_VERSION, + END_REPLY_VERSION, + BEGIN_REPLY_CODE, + END_REPLY_CODE, + BEGIN_REPLY_MESSAGE, + END_REPLY_MESSAGE, + BEGIN_METHOD, + END_METHOD, + BEGIN_TARGET, + END_TARGET, + BEGIN_VERSION, + END_VERSION, + BEGIN_HEADERS, + BEGIN_HEADER, + BEGIN_HEADER_KEY, + END_HEADER_KEY, + BEGIN_HEADER_VALUE, + END_HEADER_VALUE, + END_HEADER, + END_HEADERS, + BEGIN_BODY, + IN_DONE, + IN_ERROR, + IN_SKIP_CHARS, /// skip any "delimiters" char. + IN_CAPTURE_DELIMITERS, /// capture until any or the "delimiters" characters + IN_CAPTURE_SEPARATOR, /// capture until a specific "separator" + IN_SEPARATOR, /// skip a specific "separator" + IN_CHUNKED +}; + +struct HTTPParser { + std::string remaining; + std::string error; + std::vector states; + virtual void method(std::string_view const& s){}; + virtual void target(std::string_view const& s){}; + virtual void version(std::string_view const& s){}; + virtual void header(std::string_view const& k, std::string_view const& v){}; + virtual void endHeaders(){}; + virtual void body(std::string_view const& b){}; + virtual void replyVersion(std::string_view const& s){}; + virtual void replyCode(std::string_view const& s){}; + virtual void replyMessage(std::string_view const& s){}; +}; + +struct HTTPParserHelpers { + /// Helper to calculate the reply to a nonce + static std::string calculateAccept(const char* nonce); +}; + +void parse_http_request(char const* start, size_t size, HTTPParser* parser); +} // namespace o2::framework +#endif diff --git a/Framework/Core/src/SHA1.cxx b/Framework/Core/src/SHA1.cxx new file mode 100644 index 0000000000000..2d8529fa68167 --- /dev/null +++ b/Framework/Core/src/SHA1.cxx @@ -0,0 +1,300 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +/* +SHA-1 in C +By Steve Reid +100% Public Domain +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include +#include + +/* for uint32_t */ +#include + +#include "SHA1.h" + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +namespace o2::framework::internal +{ +void SHA1Transform( + uint32_t state[5], + const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + +/* SHA1Init - Initialize new context */ + +void SHA1Init( + SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +/* Run your data through this. */ + +void SHA1Update( + SHA1_CTX* context, + const unsigned char* data, + uint32_t len) +{ + uint32_t i; + + uint32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) { + context->count[1]++; + } + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } else { + i = 0; + } + memcpy(&context->buffer[j], &data[i], len - i); +} + +/* Add padding and return the message digest. */ + +void SHA1Final( + unsigned char digest[20], + SHA1_CTX* context) +{ + unsigned i; + + unsigned char finalcount[8]; + + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t} +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void SHA1( + char* hash_out, + const char* str, + int len) +{ + SHA1_CTX ctx; + unsigned int ii; + + SHA1Init(&ctx); + for (ii = 0; ii < len; ii += 1) { + SHA1Update(&ctx, (const unsigned char*)str + ii, 1); + } + SHA1Final((unsigned char*)hash_out, &ctx); + hash_out[20] = '\0'; +} +} // namespace o2::framework::internal diff --git a/Framework/Core/src/SHA1.h b/Framework/Core/src/SHA1.h new file mode 100644 index 0000000000000..587bab8811e2a --- /dev/null +++ b/Framework/Core/src/SHA1.h @@ -0,0 +1,39 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FRAMEWORK_SHA1_H_ +#define O2_FRAMEWORK_SHA1_H_ + +/* + Based on SHA-1 in C + By Steve Reid + 100% Public Domain + */ + +#include + +namespace o2::framework::internal +{ + +typedef struct +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); +void SHA1(char* hash_out, const char* str, int len); +} // namespace o2::framework::internal + +#endif // O2_FRAMEWORK_SHA1_H_ diff --git a/Framework/Core/test/test_HTTPParser.cxx b/Framework/Core/test/test_HTTPParser.cxx new file mode 100644 index 0000000000000..2b5fbd2d7e3d6 --- /dev/null +++ b/Framework/Core/test/test_HTTPParser.cxx @@ -0,0 +1,234 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#define BOOST_TEST_MODULE Test Framework HTTPParser +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include "../src/HTTPParser.h" +#include + +using namespace o2::framework; + +class DPLParser : public HTTPParser +{ + public: + std::string mMethod; + std::string mPath; + std::string mVersion; + std::string mBody; + std::map mHeaders; + void method(std::string_view const& m) override + { + mMethod = m; + } + void target(std::string_view const& p) override + { + mPath = p; + } + + void version(std::string_view const& v) override + { + mVersion = v; + } + void header(std::string_view const& k, std::string_view const& v) override + { + mHeaders[std::string(k)] = v; + } + void body(std::string_view const& b) override + { + mBody = b; + } +}; + +class DPLClientParser : public HTTPParser +{ + public: + std::string mReplyVersion; + std::string mReplyCode; + std::string mReplyMessage; + std::string mBody; + std::map mHeaders; + + void replyMessage(std::string_view const& s) override + { + mReplyMessage = s; + } + void replyCode(std::string_view const& s) override + { + mReplyCode = s; + } + void replyVersion(std::string_view const& s) override + { + mReplyVersion = s; + } + + void header(std::string_view const& k, std::string_view const& v) override + { + mHeaders[std::string(k)] = v; + } + void body(std::string_view const& b) override + { + mBody = b; + } +}; + +class TestWSHandler : public WebSocketHandler +{ + public: + std::vector mFrame; + std::vector mSize; + void frame(const char* f, size_t s) final + { + mFrame.push_back(f); + mSize.push_back(s); + } +}; + +BOOST_AUTO_TEST_CASE(HTTPParser1) +{ + { + const char* request = + "GET / HTTP/1.1\r\n" + "x-dpl-pid: 124679842\r\n\r\nCONTROL QUIT"; + DPLParser parser; + parse_http_request(request, strlen(request), &parser); + BOOST_REQUIRE_EQUAL(std::string(parser.mMethod), std::string("GET")); + BOOST_REQUIRE_EQUAL(std::string(parser.mPath), std::string("/")); + BOOST_REQUIRE_EQUAL(std::string(parser.mVersion), std::string("HTTP/1.1")); + BOOST_REQUIRE_EQUAL(parser.mHeaders.size(), 1); + } + { + const char* request = + "GET / HTTP/1.1\r\n" + "x-dpl-pid: 124679842\r\n" + "Somethingelse: cjnjsdnjks\r\n\r\nCONTROL QUIT"; + DPLParser parser; + parse_http_request(request, strlen(request), &parser); + BOOST_REQUIRE_EQUAL(std::string(parser.mMethod), std::string("GET")); + BOOST_REQUIRE_EQUAL(std::string(parser.mPath), std::string("/")); + BOOST_REQUIRE_EQUAL(std::string(parser.mVersion), std::string("HTTP/1.1")); + BOOST_REQUIRE_EQUAL(parser.mHeaders.size(), 2); + BOOST_REQUIRE_EQUAL(parser.mHeaders["x-dpl-pid"], "124679842"); + BOOST_REQUIRE_EQUAL(parser.mHeaders["Somethingelse"], "cjnjsdnjks"); + BOOST_REQUIRE_EQUAL(parser.mBody, "CONTROL QUIT"); + } + { + // handle continuations... + const char* request = + "GET / HTTP/1.1\r\n" + "x-dpl-pid: 124679842\r\n" + "Somethingelse: cjnjsdnjks\r\n\r\nCONTROL QUIT"; + const char* request2 = "FOO BAR"; + DPLParser parser; + parse_http_request(request, strlen(request), &parser); + BOOST_REQUIRE_EQUAL(std::string(parser.mMethod), std::string("GET")); + BOOST_REQUIRE_EQUAL(std::string(parser.mPath), std::string("/")); + BOOST_REQUIRE_EQUAL(std::string(parser.mVersion), std::string("HTTP/1.1")); + BOOST_REQUIRE_EQUAL(parser.mHeaders.size(), 2); + BOOST_REQUIRE_EQUAL(parser.mHeaders["x-dpl-pid"], "124679842"); + BOOST_REQUIRE_EQUAL(parser.mHeaders["Somethingelse"], "cjnjsdnjks"); + BOOST_REQUIRE_EQUAL(parser.mBody, "CONTROL QUIT"); + parse_http_request(request2, strlen(request2), &parser); + BOOST_REQUIRE_EQUAL(parser.mBody, "FOO BAR"); + } + + { + // WebSocket example + const char* request = + "GET /chat HTTP/1.1\r\n" + "Host: server.example.com\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" + "Sec-WebSocket-Protocol: chat, superchat\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Origin: http://example.com\r\n\r\n"; + + DPLParser parser; + parse_http_request(request, strlen(request), &parser); + BOOST_REQUIRE_EQUAL(std::string(parser.mMethod), std::string("GET")); + BOOST_REQUIRE_EQUAL(std::string(parser.mPath), std::string("/chat")); + BOOST_REQUIRE_EQUAL(std::string(parser.mVersion), std::string("HTTP/1.1")); + BOOST_REQUIRE_EQUAL(parser.mHeaders.size(), 7); + BOOST_REQUIRE_EQUAL(parser.mHeaders["Sec-WebSocket-Protocol"], "chat, superchat"); + } + { + // WebSocket example + const char* request = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; + + DPLClientParser parser; + parser.states.push_back(HTTPState::IN_START_REPLY); + parse_http_request(request, strlen(request), &parser); + BOOST_REQUIRE_EQUAL(std::string(parser.mReplyCode), std::string("101")); + BOOST_REQUIRE_EQUAL(std::string(parser.mReplyMessage), std::string("Switching Protocols")); + BOOST_REQUIRE_EQUAL(std::string(parser.mReplyVersion), std::string("HTTP/1.1")); + BOOST_REQUIRE_EQUAL(parser.mHeaders.size(), 3); + BOOST_REQUIRE_EQUAL(parser.mHeaders["Sec-WebSocket-Accept"], "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); + BOOST_REQUIRE_EQUAL(parser.mBody, ""); + } + { + // WebSocket frame encoding / decoding + char const* buffer = "hello websockets!"; + std::vector encoded; + encode_websocket_frames(encoded, buffer, strlen(buffer) + 1, WebSocketOpCode::Binary, 0); + BOOST_REQUIRE_EQUAL(encoded.size(), 1); + TestWSHandler handler; + BOOST_REQUIRE_EQUAL(encoded[0].len, strlen(buffer) + 1 + 2); // 1 for the 0, 2 for the header + decode_websocket(encoded[0].base, encoded[0].len, handler); + BOOST_REQUIRE_EQUAL(handler.mSize[0], strlen(buffer) + 1); + BOOST_REQUIRE_EQUAL(std::string(handler.mFrame[0], handler.mSize[0] - 1), std::string(buffer)); + } + { + // WebSocket multiple frame encoding / decoding + char const* buffer = "hello websockets!"; + std::vector encoded; + encode_websocket_frames(encoded, buffer, strlen(buffer) + 1, WebSocketOpCode::Binary, 0); + BOOST_REQUIRE_EQUAL(encoded.size(), 1); + char const* buffer2 = "and again."; + encode_websocket_frames(encoded, buffer2, strlen(buffer2) + 1, WebSocketOpCode::Binary, 0); + BOOST_REQUIRE_EQUAL(encoded.size(), 2); + char* multiBuffer = (char*)malloc(encoded[0].len + encoded[1].len + 4); + memcpy(multiBuffer, encoded[0].base, encoded[0].len); + memcpy(multiBuffer + encoded[0].len, encoded[1].base, encoded[1].len); + + TestWSHandler handler; + decode_websocket(multiBuffer, encoded[0].len + encoded[1].len, handler); + BOOST_REQUIRE_EQUAL(handler.mFrame.size(), 2); + BOOST_REQUIRE_EQUAL(handler.mSize.size(), 2); + BOOST_REQUIRE_EQUAL(std::string(handler.mFrame[0], handler.mSize[0] - 1), std::string(buffer)); + BOOST_REQUIRE_EQUAL(std::string(handler.mFrame[1], handler.mSize[1] - 1), std::string(buffer2)); + } + { + std::string checkRequest = + "GET /chat HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Protocol: myprotocol\r\n" + "Sec-WebSocket-Version: 13\r\n\r\n"; + std::string checkReply = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"; + int someSeed = 123; + std::string result = encode_websocket_handshake_request("/chat", "myprotocol", 13, "dGhlIHNhbXBsZSBub25jZQ=="); + BOOST_REQUIRE_EQUAL(result, checkRequest); + + std::string reply = encode_websocket_handshake_reply("dGhlIHNhbXBsZSBub25jZQ=="); + BOOST_CHECK_EQUAL(reply, checkReply); + } +}