271 lines
8.2 KiB
C
271 lines
8.2 KiB
C
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#include "tinf.h"
|
||
|
#include "defl_static.h"
|
||
|
#include "conn.h"
|
||
|
#include "ws.h"
|
||
|
|
||
|
const static char http_get[] = "GET wss://%s%s HTTP/1.1\r\n";
|
||
|
const static char http_host[] = "Host: %s\r\n";
|
||
|
const static char http_origin[] = "Origin: https://%s\r\n";
|
||
|
const static char http_upgrade[] = "Upgrade: websocket\r\n";
|
||
|
const static char http_connection[] = "Connection: Upgrade\r\n";
|
||
|
const static char http_ws_key[] = "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Extensions: permessage-deflate\r\n";
|
||
|
const static char http_ws_version[] = "Sec-WebSocket-Version: 13\r\n";
|
||
|
|
||
|
#define WS_OPCODE_CONTINUATION 0x00
|
||
|
#define WS_OPCODE_TEXT 0x01
|
||
|
#define WS_OPCODE_BINARY 0x02
|
||
|
#define WS_OPCODE_CONECTION_CLOSE 0x08
|
||
|
#define WS_OPCODE_PING 0x09
|
||
|
#define WS_OPCODE_PONG 0x0A
|
||
|
|
||
|
int wsConnect(int socket, const char *host, const char *path, Bool *compression) {
|
||
|
char server_reply[1024];
|
||
|
char buffer[1024];
|
||
|
|
||
|
memset(server_reply, 0, sizeof(server_reply));
|
||
|
*compression = false;
|
||
|
|
||
|
memset(buffer, 0, sizeof(buffer));
|
||
|
sprintf(buffer, http_get, host, path);
|
||
|
ConnWrite(socket, buffer, strlen(buffer));
|
||
|
|
||
|
memset(buffer, 0, sizeof(buffer));
|
||
|
sprintf(buffer, http_host, host);
|
||
|
ConnWrite(socket, buffer, strlen(buffer));
|
||
|
|
||
|
memset(buffer, 0, sizeof(buffer));
|
||
|
sprintf(buffer, http_origin, host);
|
||
|
ConnWrite(socket, buffer, strlen(buffer));
|
||
|
|
||
|
ConnWrite(socket, http_upgrade, strlen(http_upgrade));
|
||
|
ConnWrite(socket, http_connection, strlen(http_connection));
|
||
|
ConnWrite(socket, http_ws_key, strlen(http_ws_key));
|
||
|
ConnWrite(socket, http_ws_version, strlen(http_ws_version));
|
||
|
ConnWrite(socket, "\r\n", 2);
|
||
|
|
||
|
if (ConnRead(socket, server_reply, 300) <= 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (strstr(server_reply, "HTTP/1.1 101 Switching Protocols") == NULL) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (strstr(server_reply, "permessage-deflate") != 0) {
|
||
|
*compression = true;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void wsInitFrame(WsFrame *frame) {
|
||
|
frame->finFlag = 0;
|
||
|
frame->maskingFlag = 0;
|
||
|
frame->opcode = 0;
|
||
|
frame->payloadLenght = 0;
|
||
|
frame->maskingMap = 0;
|
||
|
frame->payload = 0;
|
||
|
}
|
||
|
|
||
|
void wsCreateTextFrame(WsFrame *frame, const char *text) {
|
||
|
frame->finFlag = 1;
|
||
|
frame->maskingFlag = 1;
|
||
|
frame->opcode = WS_OPCODE_TEXT;
|
||
|
frame->payloadLenght = strlen(text);
|
||
|
frame->maskingMap = 0x00000000;
|
||
|
frame->payload = (uint8_t *)text;
|
||
|
}
|
||
|
|
||
|
void wsSendFrame(int socket, WsFrame *frame) {
|
||
|
uint8_t finOpcode = (frame->finFlag) ? frame->opcode | 0x80 : frame->opcode;
|
||
|
uint8_t maskPayloadLength = (frame->maskingFlag) ? frame->payloadLenght | 0x80 : frame->payloadLenght;
|
||
|
unsigned short sizePayload;
|
||
|
|
||
|
// todo support extended payloadLength
|
||
|
// The length of the "Payload data", in bytes: if 0-125, that is the
|
||
|
// payload length. If 126, the following 2 bytes interpreted as a
|
||
|
// 16-bit unsigned integer are the payload length. If 127, the
|
||
|
// following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||
|
// most significant bit MUST be 0) are the payload length. Multibyte
|
||
|
// length quantities are expressed in network byte order. Note that
|
||
|
// in all cases, the minimal number of bytes MUST be used to encode
|
||
|
// the length, for example, the length of a 124-byte-long string
|
||
|
// can't be encoded as the sequence 126, 0, 124. The payload length
|
||
|
// is the length of the "Extension data" + the length of the
|
||
|
// "Application data". The length of the "Extension data" may be
|
||
|
// zero, in which case the payload length is the length of the
|
||
|
// "Application data".
|
||
|
// FE = 126
|
||
|
if (frame->payloadLenght > 125) {
|
||
|
maskPayloadLength = 0xfe;
|
||
|
sizePayload = MAKEWORD(LL(frame->payloadLenght), LH(frame->payloadLenght));
|
||
|
}
|
||
|
|
||
|
|
||
|
ConnWrite(socket, &finOpcode, 1);
|
||
|
ConnWrite(socket, &maskPayloadLength, 1);
|
||
|
|
||
|
if (frame->payloadLenght > 125) {
|
||
|
sizePayload = _SWAPS(sizePayload);
|
||
|
ConnWrite(socket, &sizePayload, 2);
|
||
|
}
|
||
|
|
||
|
ConnWrite(socket, &frame->maskingMap , 4);
|
||
|
ConnWrite(socket, frame->payload, frame->payloadLenght);
|
||
|
}
|
||
|
|
||
|
void wsReceiveFrame(int socket, WsFrame *frame, Bool *timeout, int seconds) {
|
||
|
unsigned char inBuffer[2048];
|
||
|
int receivedTotal = 0;
|
||
|
int receivedLength = 0;
|
||
|
int i = 0, ret = 0;
|
||
|
|
||
|
*timeout = false;
|
||
|
memset(inBuffer, 0, sizeof(inBuffer));
|
||
|
|
||
|
while((ret = ConnReadBytesAvailable(socket)) == 0) {
|
||
|
i++;
|
||
|
sleep_ms(10);
|
||
|
if ((i > (seconds * 100)) && ret == 0) {
|
||
|
*timeout = true;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while((receivedLength = ConnRead(socket, inBuffer + receivedTotal, 2 - receivedTotal)) > 0 && receivedTotal < 2) {
|
||
|
receivedTotal += receivedLength;
|
||
|
}
|
||
|
|
||
|
frame->finFlag = (inBuffer[0] & 0x80) >> 7;
|
||
|
frame->opcode = inBuffer[0] & 0x0F;
|
||
|
frame->maskingFlag = inBuffer[1] & 0x80;
|
||
|
frame->payloadLenght = MAKEWORD(inBuffer[1], 0x00);
|
||
|
|
||
|
if (frame->payloadLenght > 125) {
|
||
|
receivedLength = ConnRead(socket, inBuffer, 2);
|
||
|
frame->payloadLenght = MAKEWORD(inBuffer[1], inBuffer[0]);
|
||
|
}
|
||
|
|
||
|
while((receivedLength = ConnRead(socket, inBuffer + receivedTotal, frame->payloadLenght + 2 - receivedTotal)) > 0 && receivedTotal < frame->payloadLenght) {
|
||
|
receivedTotal += receivedLength;
|
||
|
if ((receivedTotal - 2) == frame->payloadLenght) break;
|
||
|
}
|
||
|
|
||
|
memcpy(frame->payload, inBuffer + 2, receivedTotal - 2);
|
||
|
|
||
|
if (frame->opcode == WS_OPCODE_PING) {
|
||
|
wsSendPong(socket, frame);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned char gzipBuffer[2048];
|
||
|
void wsInflateFrame(WsFrame *frame) {
|
||
|
unsigned int outlen = 2048;
|
||
|
|
||
|
memset(gzipBuffer, 0, sizeof(gzipBuffer));
|
||
|
|
||
|
// https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-28#page-22
|
||
|
// 7.2.2 Decompression
|
||
|
frame->payload[frame->payloadLenght-1] = 0x01;
|
||
|
frame->payload[frame->payloadLenght] = 0x00;
|
||
|
frame->payload[frame->payloadLenght+1] = 0x00;
|
||
|
frame->payload[frame->payloadLenght+2] = 0xff;
|
||
|
frame->payload[frame->payloadLenght+3] = 0xff;
|
||
|
|
||
|
tinf_uncompress(gzipBuffer, &outlen, frame->payload, frame->payloadLenght+4);
|
||
|
|
||
|
memset(frame->payload, 0, frame->payloadLenght+3);
|
||
|
memcpy(frame->payload, gzipBuffer, outlen);
|
||
|
}
|
||
|
|
||
|
void wsDeflateFrame(WsFrame *frame) {
|
||
|
struct Outbuf out = {0};
|
||
|
memset(gzipBuffer, 0, sizeof(gzipBuffer));
|
||
|
|
||
|
//Deflate payload
|
||
|
zlib_start_block(&out);
|
||
|
tinf_compress(&out, frame->payload, frame->payloadLenght);
|
||
|
zlib_finish_block(&out);
|
||
|
|
||
|
memcpy(gzipBuffer, out.outbuf, out.outlen);
|
||
|
|
||
|
frame->finFlag = 0;
|
||
|
frame->opcode = 0xc1;
|
||
|
frame->payloadLenght = out.outlen;
|
||
|
frame->payload = (uint8_t *)gzipBuffer;
|
||
|
|
||
|
zlib_free_block(&out);
|
||
|
}
|
||
|
|
||
|
void wsSendPong(int socket, WsFrame *frame) {
|
||
|
frame->maskingFlag = 1;
|
||
|
frame->opcode = WS_OPCODE_PONG;
|
||
|
frame->payloadLenght = 0;
|
||
|
wsSendFrame(socket, frame);
|
||
|
}
|
||
|
|
||
|
void wsSendText(int socket, const char *text, Bool compression) {
|
||
|
WsFrame frame;
|
||
|
wsInitFrame(&frame);
|
||
|
wsCreateTextFrame(&frame, text);
|
||
|
if (compression) wsDeflateFrame(&frame);
|
||
|
wsSendFrame(socket, &frame);
|
||
|
}
|
||
|
|
||
|
void wsReceiveText(int socket, char *buffer, int bufferSize, Bool compression, Bool *timeout, int seconds) {
|
||
|
WsFrame frame;
|
||
|
wsInitFrame(&frame);
|
||
|
memset(buffer, 0, bufferSize);
|
||
|
frame.payload = (uint8_t *)buffer;
|
||
|
wsReceiveFrame(socket, &frame, timeout, seconds);
|
||
|
if (frame.opcode == WS_OPCODE_TEXT && compression) wsInflateFrame(&frame);
|
||
|
if (frame.opcode == WS_OPCODE_PONG) strcpy(buffer, "OPCODE_PING");
|
||
|
}
|
||
|
|
||
|
void hex_dump(const char *desc, const void *addr, const size_t len) {
|
||
|
int i;
|
||
|
unsigned char buff[17];
|
||
|
unsigned char *pc = (unsigned char*)addr;
|
||
|
|
||
|
// Output description if given.
|
||
|
if (desc != NULL)
|
||
|
printf ("%s:\n", desc);
|
||
|
|
||
|
// Process every byte in the data.
|
||
|
for (i = 0; i < len; i++) {
|
||
|
// Multiple of 16 means new line (with line offset).
|
||
|
|
||
|
if ((i % 16) == 0) {
|
||
|
// Just don't print ASCII for the zeroth line.
|
||
|
if (i != 0)
|
||
|
printf (" %s\n", buff);
|
||
|
|
||
|
// Output the offset.
|
||
|
printf (" %04x ", i);
|
||
|
}
|
||
|
|
||
|
// Now the hex code for the specific character.
|
||
|
printf (" %02x", pc[i]);
|
||
|
|
||
|
// And store a printable ASCII character for later.
|
||
|
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
|
||
|
buff[i % 16] = '.';
|
||
|
else
|
||
|
buff[i % 16] = pc[i];
|
||
|
buff[(i % 16) + 1] = '\0';
|
||
|
}
|
||
|
|
||
|
// Pad out last line if not exactly 16 characters.
|
||
|
while ((i % 16) != 0) {
|
||
|
printf (" ");
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
// And print the final ASCII bit.
|
||
|
printf (" %s\n", buff);
|
||
|
}
|