Skip to content
Snippets Groups Projects
Commit 0090ae73 authored by Nicolas Pope's avatar Nicolas Pope
Browse files

Websocket implementation in C++

parent af04fc3c
No related branches found
No related tags found
No related merge requests found
#ifndef _FTL_NET_WS_INTERNAL_HPP_
#define _FTL_NET_WS_INTERNAL_HPP_
#include <stdint.h>
#include <cstddef>
#include <functional>
#include <ftl/uri.hpp>
using std::size_t;
namespace ftl {
namespace net {
/* Taken from easywsclient */
struct wsheader_type {
unsigned header_size;
bool fin;
bool mask;
enum opcode_type {
CONTINUATION = 0x0,
TEXT_FRAME = 0x1,
BINARY_FRAME = 0x2,
CLOSE = 8,
PING = 9,
PONG = 0xa,
} opcode;
int N0;
uint64_t N;
uint8_t masking_key[4];
};
/**
* Websocket dispatch parser. Given a raw socket buffer and its length, this
* function parses the websocket header and if valid and containing enough data
* in the buffer, it calls the passed function. If not enough data available
* to complete the dispatch, -1 is returned. Otherwise the total amount read
* from the buffer is returned.
*/
int ws_dispatch(const char *data, size_t len, std::function<void(const wsheader_type&,const char*,size_t)> d);
/**
* Websocket header constructor. Fills a buffer with the correct websocket
* header for a given opcode, mask setting and message length.
*/
int ws_prepare(wsheader_type::opcode_type, bool useMask, size_t len, char *buffer, size_t maxlen);
bool ws_connect(int sockfd, const ftl::URI &uri);
};
};
#endif // _FTL_NET_WS_INTERNAL_HPP_
/*
* Copyright 2019 Nicolas Pope.
*/
#define GLOG_NO_ABBREVIATED_SEVERITIES
#include <glog/logging.h>
#include <cstring>
#include <ftl/net/ws_internal.hpp>
#include <memory>
#ifndef WIN32
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#endif
#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#endif
#include <string>
#include <iostream>
using std::size_t;
using ftl::net::wsheader_type;
using ftl::URI;
using std::string;
/* Taken from easywsclient. */
int ftl::net::ws_dispatch(const char *data, size_t len, std::function<void(const wsheader_type&,const char*,size_t)> d) {
wsheader_type ws;
if (len < 2) return -1;
ws.fin = (data[0] & 0x80) == 0x80;
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
ws.mask = (data[1] & 0x80) == 0x80;
ws.N0 = (data[1] & 0x7f);
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
if (len < ws.header_size) return -1;
int i = 0;
if (ws.N0 < 126) {
ws.N = ws.N0;
i = 2;
} else if (ws.N0 == 126) {
ws.N = 0;
ws.N |= ((uint64_t) data[2]) << 8;
ws.N |= ((uint64_t) data[3]) << 0;
i = 4;
} else if (ws.N0 == 127) {
ws.N = 0;
ws.N |= ((uint64_t) data[2]) << 56;
ws.N |= ((uint64_t) data[3]) << 48;
ws.N |= ((uint64_t) data[4]) << 40;
ws.N |= ((uint64_t) data[5]) << 32;
ws.N |= ((uint64_t) data[6]) << 24;
ws.N |= ((uint64_t) data[7]) << 16;
ws.N |= ((uint64_t) data[8]) << 8;
ws.N |= ((uint64_t) data[9]) << 0;
i = 10;
}
if (ws.mask) {
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
} else {
ws.masking_key[0] = 0;
ws.masking_key[1] = 0;
ws.masking_key[2] = 0;
ws.masking_key[3] = 0;
}
if (len < ws.header_size+ws.N) return -1;
// Perform dispatch
d(ws, &data[ws.header_size], ws.N);
return ws.header_size+ws.N;
}
int ftl::net::ws_prepare(wsheader_type::opcode_type op, bool useMask, size_t len, char *data, size_t maxlen) {
// TODO:
// Masking key should (must) be derived from a high quality random
// number generator, to mitigate attacks on non-WebSocket friendly
// middleware:
const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 };
char *header = data;
size_t header_size = 2 + (len >= 126 ? 2 : 0) + (len >= 65536 ? 6 : 0) + (useMask ? 4 : 0);
if (header_size > maxlen) return -1;
memset(header, 0, header_size);
header[0] = 0x80 | op;
if (false) { }
else if (len < 126) {
header[1] = (len & 0xff) | (useMask ? 0x80 : 0);
if (useMask) {
header[2] = masking_key[0];
header[3] = masking_key[1];
header[4] = masking_key[2];
header[5] = masking_key[3];
}
} else if (len < 65536) {
header[1] = 126 | (useMask ? 0x80 : 0);
header[2] = (len >> 8) & 0xff;
header[3] = (len >> 0) & 0xff;
if (useMask) {
header[4] = masking_key[0];
header[5] = masking_key[1];
header[6] = masking_key[2];
header[7] = masking_key[3];
}
} else {
header[1] = 127 | (useMask ? 0x80 : 0);
header[2] = (len >> 56) & 0xff;
header[3] = (len >> 48) & 0xff;
header[4] = (len >> 40) & 0xff;
header[5] = (len >> 32) & 0xff;
header[6] = (len >> 24) & 0xff;
header[7] = (len >> 16) & 0xff;
header[8] = (len >> 8) & 0xff;
header[9] = (len >> 0) & 0xff;
if (useMask) {
header[10] = masking_key[0];
header[11] = masking_key[1];
header[12] = masking_key[2];
header[13] = masking_key[3];
}
}
return header_size;
}
bool ftl::net::ws_connect(int sockfd, const URI &uri) {
string http = "";
int status;
int i;
char line[256];
http += "GET "+uri.getPath()+" HTTP/1.1\r\n";
if (uri.getPort() == 80) {
http += "Host: "+uri.getHost()+"\r\n";
} else {
http += "Host: "+uri.getHost()+":"+std::to_string(uri.getPort())+"\r\n";
}
http += "Upgrade: websocket\r\n";
http += "Connection: Upgrade\r\n";
http += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n";
http += "Sec-WebSocket-Version: 13\r\n";
http += "\r\n";
int rc = ::send(sockfd, http.c_str(), http.length(), 0);
if (rc != http.length()) {
LOG(ERROR) << "Could not send Websocket http request...";
std::cout << http;
return false;
}
for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return false; } }
line[i] = 0;
if (i == 255) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", uri.getHost().c_str()); return false; }
if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", uri.getHost().c_str(), line); return false; }
// TODO: verify response headers,
while (true) {
for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return false; } }
if (line[0] == '\r' && line[1] == '\n') { break; }
}
return true;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment