241 lines
6.8 KiB
C
241 lines
6.8 KiB
C
#include "websocket.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <lwip/sockets.h>
|
|
#include <lwip/inet.h>
|
|
#include <esp_log.h>
|
|
|
|
static int recv_all(int fd, void* buf, size_t len) {
|
|
size_t total = 0;
|
|
char* p = (char*)buf;
|
|
while (total < len) {
|
|
int r = lwip_recv(fd, p + total, len - total, 0);
|
|
if (r <= 0) {
|
|
return -1;
|
|
}
|
|
total += r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t my_htons(uint16_t val) {
|
|
return (uint16_t)(((val & 0xff) << 8) | ((val & 0xff00) >> 8));
|
|
}
|
|
|
|
int ws_connect(const char* host, int port, const char* path, const char* device_id, const char* auth_key) {
|
|
int fd = lwip_socket(AF_INET, SOCK_STREAM, 0);
|
|
if (fd < 0) return -1;
|
|
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = my_htons(port);
|
|
addr.sin_addr.s_addr = ipaddr_addr(host);
|
|
|
|
if (lwip_connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
// Set socket receive timeout (e.g. 90 seconds) to prevent blocking indefinitely
|
|
struct timeval tv;
|
|
tv.tv_sec = 90;
|
|
tv.tv_usec = 0;
|
|
lwip_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
|
|
// Send HTTP upgrade handshake request
|
|
char req[1024];
|
|
snprintf(req, sizeof(req),
|
|
"GET %s HTTP/1.1\r\n"
|
|
"Host: %s:%d\r\n"
|
|
"Upgrade: websocket\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
|
"Sec-WebSocket-Version: 13\r\n"
|
|
"Authorization: Bearer %s\r\n"
|
|
"X-Device-ID: %s\r\n"
|
|
"\r\n",
|
|
path, host, port, auth_key, device_id);
|
|
|
|
if (lwip_send(fd, req, strlen(req), 0) < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
// Read HTTP response headers until we hit "\r\n\r\n"
|
|
char header_buf[1024];
|
|
size_t header_len = 0;
|
|
while (header_len < sizeof(header_buf) - 1) {
|
|
char c;
|
|
int r = lwip_recv(fd, &c, 1, 0);
|
|
if (r <= 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
header_buf[header_len++] = c;
|
|
header_buf[header_len] = '\0';
|
|
|
|
if (header_len >= 4 && strcmp(header_buf + header_len - 4, "\r\n\r\n") == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Verify HTTP 101 Switching Protocols response status
|
|
if (strstr(header_buf, "HTTP/1.1 101") == NULL && strstr(header_buf, "HTTP/1.0 101") == NULL) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
int ws_send(int fd, const uint8_t* data, size_t len, bool binary) {
|
|
uint8_t header[10];
|
|
size_t header_len = 0;
|
|
|
|
header[0] = binary ? 0x82 : 0x81;
|
|
|
|
if (len < 126) {
|
|
header[1] = 0x80 | (uint8_t)len;
|
|
header_len = 2;
|
|
} else {
|
|
header[1] = 0x80 | 126;
|
|
header[2] = (uint8_t)((len >> 8) & 0xFF);
|
|
header[3] = (uint8_t)(len & 0xFF);
|
|
header_len = 4;
|
|
}
|
|
|
|
// Use fixed client mask for performance: 0x12, 0x34, 0x56, 0x78
|
|
uint8_t mask[4] = { 0x12, 0x34, 0x56, 0x78 };
|
|
memcpy(header + header_len, mask, 4);
|
|
header_len += 4;
|
|
|
|
// Send WebSocket frame header
|
|
int sent = lwip_send(fd, header, header_len, 0);
|
|
if (sent < 0) return -1;
|
|
|
|
// Mask the payload
|
|
uint8_t* masked = malloc(len);
|
|
if (masked == NULL) return -1;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
masked[i] = data[i] ^ mask[i % 4];
|
|
}
|
|
|
|
// Send masked payload
|
|
sent = lwip_send(fd, masked, len, 0);
|
|
free(masked);
|
|
|
|
return sent >= 0 ? 0 : -1;
|
|
}
|
|
|
|
int ws_recv(int fd, int* out_opcode, uint8_t* payload, size_t max_len) {
|
|
uint8_t header[2];
|
|
if (recv_all(fd, header, 2) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
int opcode = header[0] & 0x0F;
|
|
if (out_opcode != NULL) {
|
|
*out_opcode = opcode;
|
|
}
|
|
|
|
int masked = (header[1] & 0x80) != 0;
|
|
size_t len = header[1] & 0x7F;
|
|
|
|
if (len == 126) {
|
|
uint8_t ext_len[2];
|
|
if (recv_all(fd, ext_len, 2) < 0) return -1;
|
|
len = ((size_t)ext_len[0] << 8) | ext_len[1];
|
|
} else if (len == 127) {
|
|
uint8_t ext_len[8];
|
|
if (recv_all(fd, ext_len, 8) < 0) return -1;
|
|
// Parse 64-bit length into size_t
|
|
len = ((size_t)ext_len[4] << 24) | ((size_t)ext_len[5] << 16) | ((size_t)ext_len[6] << 8) | ext_len[7];
|
|
}
|
|
|
|
if (masked) {
|
|
uint8_t mask[4];
|
|
if (recv_all(fd, mask, 4) < 0) return -1;
|
|
|
|
if (len > max_len) {
|
|
ESP_LOGE("websocket", "ws_recv overflow (masked): len=%u, max_len=%u", (unsigned)len, (unsigned)max_len);
|
|
// Buffer overflow, skip payload to align stream
|
|
size_t to_discard = len;
|
|
uint8_t discard_buf[256];
|
|
while (to_discard > 0) {
|
|
size_t chunk = to_discard < sizeof(discard_buf) ? to_discard : sizeof(discard_buf);
|
|
if (recv_all(fd, discard_buf, chunk) < 0) return -1;
|
|
to_discard -= chunk;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
if (recv_all(fd, payload, len) < 0) return -1;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
payload[i] ^= mask[i % 4];
|
|
}
|
|
} else {
|
|
if (len > max_len) {
|
|
ESP_LOGE("websocket", "ws_recv overflow (unmasked): len=%u, max_len=%u", (unsigned)len, (unsigned)max_len);
|
|
size_t to_discard = len;
|
|
uint8_t discard_buf[256];
|
|
while (to_discard > 0) {
|
|
size_t chunk = to_discard < sizeof(discard_buf) ? to_discard : sizeof(discard_buf);
|
|
if (recv_all(fd, discard_buf, chunk) < 0) return -1;
|
|
to_discard -= chunk;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
if (recv_all(fd, payload, len) < 0) return -1;
|
|
}
|
|
|
|
return (int)len;
|
|
}
|
|
|
|
void ws_close(int fd) {
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
int ws_send_pong(int fd, const uint8_t* payload, size_t len) {
|
|
uint8_t header[10];
|
|
size_t header_len = 0;
|
|
|
|
header[0] = 0x8A; // FIN | PONG (0x0A)
|
|
|
|
if (len < 126) {
|
|
header[1] = 0x80 | (uint8_t)len;
|
|
header_len = 2;
|
|
} else {
|
|
header[1] = 0x80 | 126;
|
|
header[2] = (uint8_t)((len >> 8) & 0xFF);
|
|
header[3] = (uint8_t)(len & 0xFF);
|
|
header_len = 4;
|
|
}
|
|
|
|
uint8_t mask[4] = { 0x12, 0x34, 0x56, 0x78 };
|
|
memcpy(header + header_len, mask, 4);
|
|
header_len += 4;
|
|
|
|
int sent = lwip_send(fd, header, header_len, 0);
|
|
if (sent < 0) return -1;
|
|
|
|
if (len > 0 && payload != NULL) {
|
|
uint8_t* masked = malloc(len);
|
|
if (masked == NULL) return -1;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
masked[i] = payload[i] ^ mask[i % 4];
|
|
}
|
|
sent = lwip_send(fd, masked, len, 0);
|
|
free(masked);
|
|
}
|
|
|
|
return sent >= 0 ? 0 : -1;
|
|
}
|