#include "websocket.h" #include #include #include #include #include #include #include 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; }