mirror of
https://github.com/nodemcu/nodemcu-firmware.git
synced 2025-02-06 21:18:25 +08:00
* espconn: remove unused espconn code, take 1 This is the easiest part of https://github.com/nodemcu/nodemcu-firmware/issues/3004 . It removes a bunch of functions that were never called in our tree. * espconn: De-orbit espconn_gethostbyname Further work on https://github.com/nodemcu/nodemcu-firmware/issues/3004 While here, remove `mqtt`'s charming DNS-retry logic (which is neither shared with nor duplicated in other modules) and update its :connect() return value behavior and documentation. * espconn: remove scary global pktinfo A write-only global! How about that. * net: remove deprecated methods All the TLS stuff moved over there a long time ago, and net_createUDPSocket should just do what it says on the tin. * espconn_secure: remove ESPCONN_SERVER support We can barely function as a TLS client; being a TLS server seems like a real stretch. This code was never called from Lua anyway. * espconn_secure: more code removal * espconn_secure: simplify ssl options structure There is nothing "ssl_packet" about this structure. Get rid of the terrifying "pbuffer" pointer. Squash two structure types together and eliminate an unused field. * espconn_secure: refactor mbedtls_msg_info_load Split out espconn_mbedtls_parse, which we can use as part of our effort towards addressing https://github.com/nodemcu/nodemcu-firmware/issues/3032 * espconn_secure: introduce TLS cert/key callbacks The new feature part of https://github.com/nodemcu/nodemcu-firmware/issues/3032 Subsequent work will remove the old mechanism. * tls: add deprecation warnings * luacheck: net.ifinfo is a thing now * tls: remove use of espconn->reverse * mqtt: stop using espconn->reverse Instead, just place the espconn structure itself at the top of the user data. This enlarges the structure somewhat but removes one more layer of dynamic heap usage and NULL checks. While here, simplify the code a bit. * mqtt: remove redundant pointer to connect_info Everywhere we have the mqtt_state_t we also have the lmqtt_userdata. * mqtt: doc fixes * mqtt: note bug * tls: allow :on(...,nil) to unregister a callback
877 lines
25 KiB
C
877 lines
25 KiB
C
/* Websocket client implementation
|
|
*
|
|
* Copyright (c) 2016 Luís Fonseca <miguelluisfonseca@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "osapi.h"
|
|
#include "user_interface.h"
|
|
#include "espconn.h"
|
|
#include "mem.h"
|
|
#include "limits.h"
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "websocketclient.h"
|
|
|
|
// Depends on 'crypto' module for sha1
|
|
#include "../crypto/digests.h"
|
|
#include "../crypto/mech.h"
|
|
|
|
#include "pm/swtimer.h"
|
|
|
|
#define PROTOCOL_SECURE "wss://"
|
|
#define PROTOCOL_INSECURE "ws://"
|
|
|
|
#define PORT_SECURE 443
|
|
#define PORT_INSECURE 80
|
|
#define PORT_MAX_VALUE 65535
|
|
|
|
#define WS_INIT_REQUEST "GET %s HTTP/1.1\r\n"\
|
|
"Host: %s:%d\r\n"
|
|
|
|
#define WS_INIT_REQUEST_LENGTH 30
|
|
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
#define WS_GUID_LENGTH 36
|
|
|
|
#define WS_HTTP_SWITCH_PROTOCOL_HEADER "HTTP/1.1 101"
|
|
#define WS_HTTP_SEC_WEBSOCKET_ACCEPT "Sec-WebSocket-Accept:"
|
|
|
|
#define WS_CONNECT_TIMEOUT_MS 10 * 1000
|
|
#define WS_PING_INTERVAL_MS 30 * 1000
|
|
#define WS_FORCE_CLOSE_TIMEOUT_MS 5 * 1000
|
|
#define WS_UNHEALTHY_THRESHOLD 2
|
|
|
|
#define WS_OPCODE_CONTINUATION 0x0
|
|
#define WS_OPCODE_TEXT 0x1
|
|
#define WS_OPCODE_BINARY 0x2
|
|
#define WS_OPCODE_CLOSE 0x8
|
|
#define WS_OPCODE_PING 0x9
|
|
#define WS_OPCODE_PONG 0xA
|
|
|
|
static const header_t DEFAULT_HEADERS[] = {
|
|
{"User-Agent", "ESP8266"},
|
|
{"Sec-WebSocket-Protocol", "chat"},
|
|
{0}
|
|
};
|
|
static const header_t *EMPTY_HEADERS = DEFAULT_HEADERS + sizeof(DEFAULT_HEADERS) / sizeof(header_t) - 1;
|
|
|
|
static char *cryptoSha1(char *data, unsigned int len) {
|
|
SHA1_CTX ctx;
|
|
SHA1Init(&ctx);
|
|
SHA1Update(&ctx, data, len);
|
|
|
|
uint8_t *digest = (uint8_t *) calloc(1,20);
|
|
SHA1Final(digest, &ctx);
|
|
return (char *) digest; // Requires free
|
|
}
|
|
|
|
static const char *bytes64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
static char *base64Encode(char *data, unsigned int len) {
|
|
int blen = (len + 2) / 3 * 4;
|
|
|
|
char *out = (char *) calloc(1,blen + 1);
|
|
out[blen] = '\0';
|
|
int j = 0, i;
|
|
for (i = 0; i < len; i += 3) {
|
|
int a = data[i];
|
|
int b = (i + 1 < len) ? data[i + 1] : 0;
|
|
int c = (i + 2 < len) ? data[i + 2] : 0;
|
|
out[j++] = bytes64[a >> 2];
|
|
out[j++] = bytes64[((a & 3) << 4) | (b >> 4)];
|
|
out[j++] = (i + 1 < len) ? bytes64[((b & 15) << 2) | (c >> 6)] : 61;
|
|
out[j++] = (i + 2 < len) ? bytes64[(c & 63)] : 61;
|
|
}
|
|
|
|
return out; // Requires free
|
|
}
|
|
|
|
static void generateSecKeys(char **key, char **expectedKey) {
|
|
char rndData[16];
|
|
int i;
|
|
for (i = 0; i < 16; i++) {
|
|
rndData[i] = (char) os_random();
|
|
}
|
|
|
|
*key = base64Encode(rndData, 16);
|
|
|
|
// expectedKey = b64(sha1(keyB64 + GUID))
|
|
char keyWithGuid[24 + WS_GUID_LENGTH];
|
|
memcpy(keyWithGuid, *key, 24);
|
|
memcpy(keyWithGuid + 24, WS_GUID, WS_GUID_LENGTH);
|
|
|
|
char *keyEncrypted = cryptoSha1(keyWithGuid, 24 + WS_GUID_LENGTH);
|
|
*expectedKey = base64Encode(keyEncrypted, 20);
|
|
|
|
os_free(keyEncrypted);
|
|
}
|
|
|
|
static char *_strcpy(char *dst, char *src) {
|
|
while(*dst++ = *src++);
|
|
return dst - 1;
|
|
}
|
|
|
|
static int headers_length(const header_t *headers) {
|
|
int length = 0;
|
|
for(; headers->key; headers++)
|
|
length += strlen(headers->key) + strlen(headers->value) + 4;
|
|
return length;
|
|
}
|
|
|
|
static char *sprintf_headers(char *buf, ...) {
|
|
char *dst = buf;
|
|
va_list args;
|
|
va_start(args, buf);
|
|
for(header_t *header_set = va_arg(args, header_t *); header_set; header_set = va_arg(args, header_t *))
|
|
for(header_t *header = header_set; header->key; header++) {
|
|
va_list args2;
|
|
va_start(args2, buf);
|
|
for(header_t *header_set2 = va_arg(args2, header_t *); header_set2; header_set2 = va_arg(args2, header_t *))
|
|
for(header_t *header2 = header_set2; header2->key; header2++) {
|
|
if(header == header2)
|
|
goto ok;
|
|
if(!strcasecmp(header->key, header2->key))
|
|
goto skip;
|
|
}
|
|
ok:
|
|
dst = _strcpy(dst, header->key);
|
|
dst = _strcpy(dst, ": ");
|
|
dst = _strcpy(dst, header->value);
|
|
dst = _strcpy(dst, "\r\n");
|
|
skip:;
|
|
}
|
|
dst = _strcpy(dst, "\r\n");
|
|
return dst;
|
|
}
|
|
|
|
static void ws_closeSentCallback(void *arg) {
|
|
NODE_DBG("ws_closeSentCallback \n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
if (ws == NULL) {
|
|
NODE_DBG("ws is unexpectly null\n");
|
|
return;
|
|
}
|
|
|
|
ws->knownFailureCode = -6;
|
|
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
}
|
|
|
|
static void ws_sendFrame(struct espconn *conn, int opCode, const char *data, unsigned short len) {
|
|
NODE_DBG("ws_sendFrame %d %d\n", opCode, len);
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
if (ws->connectionState == 4) {
|
|
NODE_DBG("already in closing state\n");
|
|
return;
|
|
} else if (ws->connectionState != 3) {
|
|
NODE_DBG("can't send message while not in a connected state\n");
|
|
return;
|
|
}
|
|
|
|
char *b = calloc(1,10 + len); // 10 bytes = worst case scenario for framming
|
|
if (b == NULL) {
|
|
NODE_DBG("Out of memory when receiving message, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -16;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
|
|
b[0] = 1 << 7; // has fin
|
|
b[0] += opCode;
|
|
b[1] = 1 << 7; // has mask
|
|
int bufOffset;
|
|
if (len < 126) {
|
|
b[1] += len;
|
|
bufOffset = 2;
|
|
} else if (len < 0x10000) {
|
|
b[1] += 126;
|
|
b[2] = len >> 8;
|
|
b[3] = len;
|
|
bufOffset = 4;
|
|
} else {
|
|
b[1] += 127;
|
|
b[2] = len >> 24;
|
|
b[3] = len >> 16;
|
|
b[4] = len >> 8;
|
|
b[5] = len;
|
|
bufOffset = 6;
|
|
}
|
|
|
|
// Random mask:
|
|
b[bufOffset] = (char) os_random();
|
|
b[bufOffset + 1] = (char) os_random();
|
|
b[bufOffset + 2] = (char) os_random();
|
|
b[bufOffset + 3] = (char) os_random();
|
|
bufOffset += 4;
|
|
|
|
// Copy data to buffer
|
|
memcpy(b + bufOffset, data, len);
|
|
|
|
// Apply mask to encode payload
|
|
int i;
|
|
for (i = 0; i < len; i++) {
|
|
b[bufOffset + i] ^= b[bufOffset - 4 + i % 4];
|
|
}
|
|
bufOffset += len;
|
|
|
|
NODE_DBG("b[0] = %d \n", b[0]);
|
|
NODE_DBG("b[1] = %d \n", b[1]);
|
|
NODE_DBG("b[2] = %d \n", b[2]);
|
|
NODE_DBG("b[3] = %d \n", b[3]);
|
|
NODE_DBG("b[4] = %d \n", b[4]);
|
|
NODE_DBG("b[5] = %d \n", b[5]);
|
|
NODE_DBG("b[6] = %d \n", b[6]);
|
|
NODE_DBG("b[7] = %d \n", b[7]);
|
|
NODE_DBG("b[8] = %d \n", b[8]);
|
|
NODE_DBG("b[9] = %d \n", b[9]);
|
|
|
|
NODE_DBG("sending message\n");
|
|
if (ws->isSecure)
|
|
espconn_secure_send(conn, (uint8_t *) b, bufOffset);
|
|
else
|
|
espconn_send(conn, (uint8_t *) b, bufOffset);
|
|
|
|
os_free(b);
|
|
}
|
|
|
|
static void ws_sendPingTimeout(void *arg) {
|
|
NODE_DBG("ws_sendPingTimeout \n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
if (ws->unhealthyPoints == WS_UNHEALTHY_THRESHOLD) {
|
|
// several pings were sent but no pongs nor messages
|
|
ws->knownFailureCode = -19;
|
|
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
|
|
ws_sendFrame(conn, WS_OPCODE_PING, NULL, 0);
|
|
ws->unhealthyPoints += 1;
|
|
}
|
|
|
|
static void ws_receiveCallback(void *arg, char *buf, unsigned short len) {
|
|
NODE_DBG("ws_receiveCallback %d \n", len);
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
ws->unhealthyPoints = 0; // received data, connection is healthy
|
|
os_timer_disarm(&ws->timeoutTimer); // reset ping check
|
|
os_timer_arm(&ws->timeoutTimer, WS_PING_INTERVAL_MS, true);
|
|
|
|
|
|
char *b = buf;
|
|
if (ws->frameBuffer != NULL) { // Append previous frameBuffer with new content
|
|
NODE_DBG("Appending new frameBuffer to old one \n");
|
|
|
|
ws->frameBuffer = realloc(ws->frameBuffer, ws->frameBufferLen + len);
|
|
if (ws->frameBuffer == NULL) {
|
|
NODE_DBG("Failed to allocate new framebuffer, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -8;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(ws->frameBuffer + ws->frameBufferLen, b, len);
|
|
|
|
ws->frameBufferLen += len;
|
|
|
|
len = ws->frameBufferLen;
|
|
b = ws->frameBuffer;
|
|
NODE_DBG("New frameBufferLen: %d\n", len);
|
|
}
|
|
|
|
while (b != NULL) { // several frames can be present, b pointer will be moved to the next frame
|
|
NODE_DBG("b[0] = %d \n", b[0]);
|
|
NODE_DBG("b[1] = %d \n", b[1]);
|
|
NODE_DBG("b[2] = %d \n", b[2]);
|
|
NODE_DBG("b[3] = %d \n", b[3]);
|
|
NODE_DBG("b[4] = %d \n", b[4]);
|
|
NODE_DBG("b[5] = %d \n", b[5]);
|
|
NODE_DBG("b[6] = %d \n", b[6]);
|
|
NODE_DBG("b[7] = %d \n", b[7]);
|
|
|
|
int isFin = b[0] & 0x80 ? 1 : 0;
|
|
int opCode = b[0] & 0x0f;
|
|
int hasMask = b[1] & 0x80 ? 1 : 0;
|
|
uint64_t payloadLength = b[1] & 0x7f;
|
|
int bufOffset = 2;
|
|
if (payloadLength == 126) {
|
|
payloadLength = (b[2] << 8) + b[3];
|
|
bufOffset = 4;
|
|
} else if (payloadLength == 127) { // this will clearly not hold in heap, abort??
|
|
payloadLength = (b[2] << 24) + (b[3] << 16) + (b[4] << 8) + b[5];
|
|
bufOffset = 6;
|
|
}
|
|
|
|
if (hasMask) {
|
|
int maskOffset = bufOffset;
|
|
bufOffset += 4;
|
|
|
|
int i;
|
|
for (i = 0; i < payloadLength; i++) {
|
|
b[bufOffset + i] ^= b[maskOffset + i % 4]; // apply mask to decode payload
|
|
}
|
|
}
|
|
|
|
if (payloadLength > len - bufOffset) {
|
|
NODE_DBG("INCOMPLETE Frame \n");
|
|
if (ws->frameBuffer == NULL) {
|
|
NODE_DBG("Allocing new frameBuffer \n");
|
|
ws->frameBuffer = calloc(1,len);
|
|
if (ws->frameBuffer == NULL) {
|
|
NODE_DBG("Failed to allocate framebuffer, disconnecting... \n");
|
|
|
|
ws->knownFailureCode = -9;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(ws->frameBuffer, b, len);
|
|
ws->frameBufferLen = len;
|
|
}
|
|
break; // since the buffer were already concat'ed, wait for the next receive
|
|
}
|
|
|
|
if (!isFin) {
|
|
NODE_DBG("PARTIAL frame! Should concat payload and later restore opcode\n");
|
|
if(ws->payloadBuffer == NULL) {
|
|
NODE_DBG("Allocing new payloadBuffer \n");
|
|
ws->payloadBuffer = calloc(1,payloadLength);
|
|
if (ws->payloadBuffer == NULL) {
|
|
NODE_DBG("Failed to allocate payloadBuffer, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -10;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(ws->payloadBuffer, b + bufOffset, payloadLength);
|
|
ws->frameBufferLen = payloadLength;
|
|
ws->payloadOriginalOpCode = opCode;
|
|
} else {
|
|
NODE_DBG("Appending new payloadBuffer to old one \n");
|
|
ws->payloadBuffer = realloc(ws->payloadBuffer, ws->payloadBufferLen + payloadLength);
|
|
if (ws->payloadBuffer == NULL) {
|
|
NODE_DBG("Failed to allocate new framebuffer, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -11;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(ws->payloadBuffer + ws->payloadBufferLen, b + bufOffset, payloadLength);
|
|
|
|
ws->payloadBufferLen += payloadLength;
|
|
}
|
|
} else {
|
|
char *payload;
|
|
if (opCode == WS_OPCODE_CONTINUATION) {
|
|
NODE_DBG("restoring original opcode\n");
|
|
if (ws->payloadBuffer == NULL) {
|
|
NODE_DBG("Got FIN continuation frame but didn't receive any beforehand, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -15;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
// concat buffer with payload
|
|
payload = calloc(1,ws->payloadBufferLen + payloadLength);
|
|
|
|
if (payload == NULL) {
|
|
NODE_DBG("Failed to allocate new framebuffer, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -12;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(payload, ws->payloadBuffer, ws->payloadBufferLen);
|
|
memcpy(payload + ws->payloadBufferLen, b + bufOffset, payloadLength);
|
|
|
|
os_free(ws->payloadBuffer); // free previous buffer
|
|
ws->payloadBuffer = NULL;
|
|
|
|
payloadLength += ws->payloadBufferLen;
|
|
ws->payloadBufferLen = 0;
|
|
|
|
opCode = ws->payloadOriginalOpCode;
|
|
ws->payloadOriginalOpCode = 0;
|
|
} else {
|
|
int extensionDataOffset = 0;
|
|
|
|
if (opCode == WS_OPCODE_CLOSE && payloadLength > 0) {
|
|
unsigned int reasonCode = b[bufOffset] << 8 + b[bufOffset + 1];
|
|
NODE_DBG("Closing due to: %d\n", reasonCode); // Must not be shown to client as per spec
|
|
extensionDataOffset += 2;
|
|
}
|
|
|
|
payload = calloc(1,payloadLength - extensionDataOffset + 1);
|
|
if (payload == NULL) {
|
|
NODE_DBG("Failed to allocate payload, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -13;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
|
|
memcpy(payload, b + bufOffset + extensionDataOffset, payloadLength - extensionDataOffset);
|
|
payload[payloadLength - extensionDataOffset] = '\0';
|
|
}
|
|
|
|
NODE_DBG("isFin %d \n", isFin);
|
|
NODE_DBG("opCode %d \n", opCode);
|
|
NODE_DBG("hasMask %d \n", hasMask);
|
|
NODE_DBG("payloadLength %d \n", payloadLength);
|
|
NODE_DBG("len %d \n", len);
|
|
NODE_DBG("bufOffset %d \n", bufOffset);
|
|
|
|
if (opCode == WS_OPCODE_CLOSE) {
|
|
NODE_DBG("Closing message: %s\n", payload); // Must not be shown to client as per spec
|
|
|
|
espconn_regist_sentcb(conn, ws_closeSentCallback);
|
|
ws_sendFrame(conn, WS_OPCODE_CLOSE, (const char *) (b + bufOffset), (unsigned short) payloadLength);
|
|
ws->connectionState = 4;
|
|
} else if (opCode == WS_OPCODE_PING) {
|
|
ws_sendFrame(conn, WS_OPCODE_PONG, (const char *) (b + bufOffset), (unsigned short) payloadLength);
|
|
} else if (opCode == WS_OPCODE_PONG) {
|
|
// ping alarm was already reset...
|
|
} else {
|
|
if (ws->onReceive) ws->onReceive(ws, payloadLength, payload, opCode);
|
|
}
|
|
os_free(payload);
|
|
}
|
|
|
|
bufOffset += payloadLength;
|
|
NODE_DBG("bufOffset %d \n", bufOffset);
|
|
if (bufOffset == len) { // (bufOffset > len) won't happen here because it's being checked earlier
|
|
b = NULL;
|
|
if (ws->frameBuffer != NULL) { // the last frame inside buffer was processed
|
|
os_free(ws->frameBuffer);
|
|
ws->frameBuffer = NULL;
|
|
ws->frameBufferLen = 0;
|
|
}
|
|
} else {
|
|
len -= bufOffset;
|
|
b += bufOffset; // move b to next frame
|
|
if (ws->frameBuffer != NULL) {
|
|
NODE_DBG("Reallocing frameBuffer to remove consumed frame\n");
|
|
|
|
ws->frameBuffer = realloc(ws->frameBuffer, ws->frameBufferLen + len);
|
|
if (ws->frameBuffer == NULL) {
|
|
NODE_DBG("Failed to allocate new frame buffer, disconnecting...\n");
|
|
|
|
ws->knownFailureCode = -14;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
memcpy(ws->frameBuffer + ws->frameBufferLen, b, len);
|
|
|
|
ws->frameBufferLen += len;
|
|
b = ws->frameBuffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ws_initReceiveCallback(void *arg, char *buf, unsigned short len) {
|
|
NODE_DBG("ws_initReceiveCallback %d \n", len);
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
// Check server is switch protocols
|
|
if (strstr(buf, WS_HTTP_SWITCH_PROTOCOL_HEADER) == NULL) {
|
|
NODE_DBG("Server is not switching protocols\n");
|
|
ws->knownFailureCode = -17;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
|
|
// Check server has valid sec key
|
|
if (strstr(buf, ws->expectedSecKey) == NULL) {
|
|
NODE_DBG("Server has invalid response\n");
|
|
ws->knownFailureCode = -7;
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(conn);
|
|
else
|
|
espconn_disconnect(conn);
|
|
return;
|
|
}
|
|
|
|
NODE_DBG("Server response is valid, it's now a websocket!\n");
|
|
|
|
os_timer_disarm(&ws->timeoutTimer);
|
|
os_timer_setfn(&ws->timeoutTimer, (os_timer_func_t *) ws_sendPingTimeout, conn);
|
|
SWTIMER_REG_CB(ws_sendPingTimeout, SWTIMER_RESUME)
|
|
os_timer_arm(&ws->timeoutTimer, WS_PING_INTERVAL_MS, true);
|
|
|
|
espconn_regist_recvcb(conn, ws_receiveCallback);
|
|
|
|
if (ws->onConnection) ws->onConnection(ws);
|
|
|
|
char *data = strstr(buf, "\r\n\r\n");
|
|
unsigned short dataLength = len - (data - buf) - 4;
|
|
|
|
NODE_DBG("dataLength = %d\n", len - (data - buf) - 4);
|
|
|
|
if (data != NULL && dataLength > 0) { // handshake already contained a frame
|
|
ws_receiveCallback(arg, data + 4, dataLength);
|
|
}
|
|
}
|
|
|
|
static void connect_callback(void *arg) {
|
|
NODE_DBG("Connected\n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
ws->connectionState = 3;
|
|
|
|
espconn_regist_recvcb(conn, ws_initReceiveCallback);
|
|
|
|
char *key;
|
|
generateSecKeys(&key, &ws->expectedSecKey);
|
|
|
|
header_t headers[] = {
|
|
{"Upgrade", "websocket"},
|
|
{"Connection", "Upgrade"},
|
|
{"Sec-WebSocket-Key", key},
|
|
{"Sec-WebSocket-Version", "13"},
|
|
{0}
|
|
};
|
|
|
|
const header_t *extraHeaders = ws->extraHeaders ? ws->extraHeaders : EMPTY_HEADERS;
|
|
|
|
char buf[WS_INIT_REQUEST_LENGTH + strlen(ws->path) + strlen(ws->hostname) +
|
|
headers_length(DEFAULT_HEADERS) + headers_length(headers) + headers_length(extraHeaders) + 2];
|
|
|
|
int len = os_sprintf(
|
|
buf,
|
|
WS_INIT_REQUEST,
|
|
ws->path,
|
|
ws->hostname,
|
|
ws->port
|
|
);
|
|
|
|
len = sprintf_headers(buf + len, headers, extraHeaders, DEFAULT_HEADERS, 0) - buf;
|
|
|
|
os_free(key);
|
|
NODE_DBG("request: %s", buf);
|
|
if (ws->isSecure)
|
|
espconn_secure_send(conn, (uint8_t *) buf, len);
|
|
else
|
|
espconn_send(conn, (uint8_t *) buf, len);
|
|
}
|
|
|
|
static void disconnect_callback(void *arg) {
|
|
NODE_DBG("disconnect_callback\n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
ws->connectionState = 4;
|
|
|
|
os_timer_disarm(&ws->timeoutTimer);
|
|
|
|
NODE_DBG("ws->hostname %d\n", ws->hostname);
|
|
os_free(ws->hostname);
|
|
NODE_DBG("ws->path %d\n ", ws->path);
|
|
os_free(ws->path);
|
|
|
|
if (ws->expectedSecKey != NULL) {
|
|
os_free(ws->expectedSecKey);
|
|
}
|
|
|
|
if (ws->frameBuffer != NULL) {
|
|
os_free(ws->frameBuffer);
|
|
}
|
|
|
|
if (ws->payloadBuffer != NULL) {
|
|
os_free(ws->payloadBuffer);
|
|
}
|
|
|
|
if (conn->proto.tcp != NULL) {
|
|
os_free(conn->proto.tcp);
|
|
}
|
|
|
|
NODE_DBG("conn %d\n", conn);
|
|
espconn_delete(conn);
|
|
|
|
NODE_DBG("freeing conn1 \n");
|
|
|
|
os_free(conn);
|
|
ws->conn = NULL;
|
|
|
|
if (ws->onFailure) {
|
|
if (ws->knownFailureCode) ws->onFailure(ws, ws->knownFailureCode);
|
|
else ws->onFailure(ws, -99);
|
|
}
|
|
}
|
|
|
|
static void ws_connectTimeout(void *arg) {
|
|
NODE_DBG("ws_connectTimeout\n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
ws->knownFailureCode = -18;
|
|
disconnect_callback(arg);
|
|
}
|
|
|
|
static void error_callback(void * arg, sint8 errType) {
|
|
NODE_DBG("error_callback %d\n", errType);
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
ws->knownFailureCode = ((int) errType) - 100;
|
|
disconnect_callback(arg);
|
|
}
|
|
|
|
static void dns_callback(const char *hostname, ip_addr_t *addr, void *arg) {
|
|
NODE_DBG("dns_callback\n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
if (ws->conn == NULL || ws->connectionState == 4) {
|
|
return;
|
|
}
|
|
|
|
if (addr == NULL) {
|
|
ws->knownFailureCode = -5;
|
|
disconnect_callback(arg);
|
|
return;
|
|
}
|
|
|
|
ws->connectionState = 2;
|
|
|
|
os_memcpy(conn->proto.tcp->remote_ip, addr, 4);
|
|
|
|
espconn_regist_connectcb(conn, connect_callback);
|
|
espconn_regist_disconcb(conn, disconnect_callback);
|
|
espconn_regist_reconcb(conn, error_callback);
|
|
|
|
// Set connection timeout timer
|
|
os_timer_disarm(&ws->timeoutTimer);
|
|
os_timer_setfn(&ws->timeoutTimer, (os_timer_func_t *) ws_connectTimeout, conn);
|
|
SWTIMER_REG_CB(ws_connectTimeout, SWTIMER_RESUME)
|
|
os_timer_arm(&ws->timeoutTimer, WS_CONNECT_TIMEOUT_MS, false);
|
|
|
|
if (ws->isSecure) {
|
|
NODE_DBG("secure connecting \n");
|
|
espconn_secure_connect(conn);
|
|
}
|
|
else {
|
|
NODE_DBG("insecure connecting \n");
|
|
espconn_connect(conn);
|
|
}
|
|
|
|
NODE_DBG("DNS found %s " IPSTR " \n", hostname, IP2STR(addr));
|
|
}
|
|
|
|
void ws_connect(ws_info *ws, const char *url) {
|
|
NODE_DBG("ws_connect called\n");
|
|
|
|
if (ws == NULL) {
|
|
NODE_DBG("ws_connect ws_info argument is null!");
|
|
return;
|
|
}
|
|
|
|
if (url == NULL) {
|
|
NODE_DBG("url is null!");
|
|
return;
|
|
}
|
|
|
|
// Extract protocol - either ws or wss
|
|
bool isSecure = strncasecmp(url, PROTOCOL_SECURE, strlen(PROTOCOL_SECURE)) == 0;
|
|
|
|
if (isSecure) {
|
|
url += strlen(PROTOCOL_SECURE);
|
|
} else {
|
|
if (strncasecmp(url, PROTOCOL_INSECURE, strlen(PROTOCOL_INSECURE)) != 0) {
|
|
NODE_DBG("Failed to extract protocol from: %s\n", url);
|
|
if (ws->onFailure) ws->onFailure(ws, -1);
|
|
return;
|
|
}
|
|
url += strlen(PROTOCOL_INSECURE);
|
|
}
|
|
|
|
// Extract path - it should start with '/'
|
|
char *path = strchr(url, '/');
|
|
|
|
// Extract hostname, possibly including port
|
|
char hostname[256];
|
|
if (path) {
|
|
if (path - url >= sizeof(hostname)) {
|
|
NODE_DBG("Hostname too large");
|
|
if (ws->onFailure) ws->onFailure(ws, -2);
|
|
return;
|
|
}
|
|
memcpy(hostname, url, path - url);
|
|
hostname[path - url] = '\0';
|
|
} else {
|
|
// no path found, assuming the url only refers to the hostname and possibly the port
|
|
memcpy(hostname, url, strlen(url));
|
|
hostname[strlen(url)] = '\0';
|
|
path = "/";
|
|
}
|
|
|
|
// Extract port from hostname, if available
|
|
char *portInHostname = strchr(hostname, ':');
|
|
int port;
|
|
if (portInHostname) {
|
|
port = atoi(portInHostname + 1);
|
|
if (port <= 0 || port > PORT_MAX_VALUE) {
|
|
NODE_DBG("Invalid port number\n");
|
|
if (ws->onFailure) ws->onFailure(ws, -3);
|
|
return;
|
|
}
|
|
hostname[strlen(hostname) - strlen(portInHostname)] = '\0'; // remove port from hostname
|
|
} else {
|
|
port = isSecure ? PORT_SECURE : PORT_INSECURE;
|
|
}
|
|
|
|
if (strlen(hostname) == 0) {
|
|
NODE_DBG("Failed to extract hostname\n");
|
|
if (ws->onFailure) ws->onFailure(ws, -4);
|
|
return;
|
|
}
|
|
|
|
NODE_DBG("secure protocol = %d\n", isSecure);
|
|
NODE_DBG("hostname = %s\n", hostname);
|
|
NODE_DBG("port = %d\n", port);
|
|
NODE_DBG("path = %s\n", path);
|
|
|
|
// Prepare internal ws_info
|
|
ws->connectionState = 1;
|
|
ws->isSecure = isSecure;
|
|
ws->hostname = strdup(hostname);
|
|
ws->port = port;
|
|
ws->path = strdup(path);
|
|
ws->expectedSecKey = NULL;
|
|
ws->knownFailureCode = 0;
|
|
ws->frameBuffer = NULL;
|
|
ws->frameBufferLen = 0;
|
|
ws->payloadBuffer = NULL;
|
|
ws->payloadBufferLen = 0;
|
|
ws->payloadOriginalOpCode = 0;
|
|
ws->unhealthyPoints = 0;
|
|
|
|
// Prepare espconn
|
|
struct espconn *conn = (struct espconn *) calloc(1,sizeof(struct espconn));
|
|
conn->type = ESPCONN_TCP;
|
|
conn->state = ESPCONN_NONE;
|
|
conn->proto.tcp = (esp_tcp *) calloc(1,sizeof(esp_tcp));
|
|
conn->proto.tcp->local_port = espconn_port();
|
|
conn->proto.tcp->remote_port = ws->port;
|
|
|
|
conn->reverse = ws;
|
|
ws->conn = conn;
|
|
|
|
// Attempt to resolve hostname address
|
|
ip_addr_t addr;
|
|
err_t result = dns_gethostbyname(hostname, &addr, dns_callback, conn);
|
|
|
|
if (result == ERR_INPROGRESS) {
|
|
NODE_DBG("DNS pending\n");
|
|
} else {
|
|
dns_callback(hostname, &addr, conn);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void ws_send(ws_info *ws, int opCode, const char *message, unsigned short length) {
|
|
NODE_DBG("ws_send\n");
|
|
ws_sendFrame(ws->conn, opCode, message, length);
|
|
}
|
|
|
|
static void ws_forceCloseTimeout(void *arg) {
|
|
NODE_DBG("ws_forceCloseTimeout\n");
|
|
struct espconn *conn = (struct espconn *) arg;
|
|
ws_info *ws = (ws_info *) conn->reverse;
|
|
|
|
if (ws->connectionState == 0 || ws->connectionState == 4) {
|
|
return;
|
|
}
|
|
|
|
if (ws->isSecure)
|
|
espconn_secure_disconnect(ws->conn);
|
|
else
|
|
espconn_disconnect(ws->conn);
|
|
}
|
|
|
|
void ws_close(ws_info *ws) {
|
|
NODE_DBG("ws_close\n");
|
|
|
|
if (ws->connectionState == 0 || ws->connectionState == 4) {
|
|
return;
|
|
}
|
|
|
|
ws->knownFailureCode = 0; // no error as user requested to close
|
|
if (ws->connectionState == 1) {
|
|
disconnect_callback(ws->conn);
|
|
} else {
|
|
ws_sendFrame(ws->conn, WS_OPCODE_CLOSE, NULL, 0);
|
|
|
|
os_timer_disarm(&ws->timeoutTimer);
|
|
os_timer_setfn(&ws->timeoutTimer, (os_timer_func_t *) ws_forceCloseTimeout, ws->conn);
|
|
SWTIMER_REG_CB(ws_forceCloseTimeout, SWTIMER_RESUME);
|
|
os_timer_arm(&ws->timeoutTimer, WS_FORCE_CLOSE_TIMEOUT_MS, false);
|
|
}
|
|
}
|