/* SPDX-License-Identifier: GPL-3.0-only */ #define _GNU_SOURCE #include #include #include #include #include #include #include typedef int esp_err_t; typedef void *httpd_handle_t; typedef int httpd_ws_type_t; #define ESP_OK 0 #define ESP_FAIL -1 #define ESP_ERR_INVALID_ARG -2 #define ESP_ERR_INVALID_STATE -3 #define ESP_ERR_INVALID_SIZE -4 #define HTTPD_SOCK_ERR_TIMEOUT -3 #define HTTPD_WS_FIN_BIT 0x80 #define HTTPD_WS_MASK_BIT 0x80 #define HTTPD_WS_LENGTH_BITS 0x7f #define HTTPD_WS_OPCODE_BITS 0x0f #define HTTPD_WS_TYPE_CLOSE 8 #define HTTPD_WS_TYPE_PING 9 #define HTTPD_WS_TYPE_PONG 10 #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define ESP_LOGW(...) ((void)0) #define ESP_LOGD(...) ((void)0) /* SDK_OPTIONS */ struct sock_db { httpd_handle_t handle; int fd; bool ws_handshake_done, ws_control_frames; unsigned char pending_data[32]; size_t pending_len; int (*recv_fn)(httpd_handle_t, int, char *, size_t, int); }; struct httpd_req_aux { struct sock_db *sd; bool ws_final; httpd_ws_type_t ws_type; unsigned char before[8], mask_key[4], after[8]; }; typedef struct { void *aux; httpd_handle_t handle; } httpd_req_t; typedef struct { bool final, fragmented; httpd_ws_type_t type; unsigned char *payload; size_t len; } httpd_ws_frame_t; static unsigned char *wire; static size_t position, limit, chunk; static int terminal_result, terminal_calls, sends, sent_type; static size_t sent_len; static unsigned char sent_payload[128]; static int receive(httpd_handle_t h, int fd, char *buf, size_t len, int flags) { (void)h; (void)fd; (void)flags; assert(len > 0); if (position == limit) { /* Any continuation past failed framing is an observable failure. */ assert(terminal_calls++ == 0); return terminal_result; } size_t n = MIN(len, MIN(chunk, limit - position)); memcpy(buf, wire + position, n); position += n; return (int)n; } static int httpd_req_to_sockfd(httpd_req_t *req) { (void)req; return 7; } static esp_err_t httpd_ws_send_frame_async(httpd_handle_t h, int fd, httpd_ws_frame_t *f) { (void)h; assert(fd == 7); assert(f->len <= sizeof(sent_payload)); sends++; sent_type = f->type; sent_len = f->len; if (f->len) memcpy(sent_payload, f->payload, f->len); return ESP_OK; } /* SDK_FUNCTIONS */ static struct sock_db sd; static struct httpd_req_aux aux; static httpd_req_t req; static unsigned char packet[32]; static size_t packet_len, header_len; static const unsigned char plain[] = {0, 0xff, 0x80}; static void setup(int encoding, int opcode, size_t available, int result, size_t split, size_t pending) { memset(&sd, 0, sizeof(sd)); memset(&aux, 0, sizeof(aux)); memset(aux.before, 0xa5, sizeof(aux.before)); memset(aux.mask_key, 0xcc, sizeof(aux.mask_key)); memset(aux.after, 0x5a, sizeof(aux.after)); sd.ws_handshake_done = true; sd.recv_fn = receive; aux.sd = &sd; aux.ws_type = 2; req.aux = &aux; size_t n = 0; packet[n++] = 0x80 | opcode; packet[n++] = 0x80 | (encoding ? (encoding == 2 ? 126 : 127) : sizeof(plain)); for (int i = 0; i < encoding; i++) packet[n++] = i == encoding - 1 ? sizeof(plain) : 0; for (int i = 0; i < 4; i++) packet[n++] = (unsigned char)(0x10 + i); header_len = n; for (size_t i = 0; i < sizeof(plain); i++) packet[n++] = plain[i] ^ (0x10 + i); packet_len = n; limit = MIN(available, n); position = pending; chunk = split; assert(pending <= limit && pending <= sizeof(sd.pending_data)); memcpy(sd.pending_data + sizeof(sd.pending_data) - pending, packet, pending); sd.pending_len = pending; /* Input ends exactly at an inaccessible page, including partial headers. */ long page = sysconf(_SC_PAGESIZE); wire = mmap(NULL, (size_t)page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); assert(wire != MAP_FAILED); assert(mprotect(wire + page, (size_t)page, PROT_NONE) == 0); wire += page - limit; memcpy(wire, packet, limit); terminal_result = result; terminal_calls = sends = 0; } static void finish(void) { for (size_t i = 0; i < 8; i++) assert(aux.before[i] == 0xa5 && aux.after[i] == 0x5a); long page = sysconf(_SC_PAGESIZE); assert(munmap(wire + limit - page, (size_t)page * 2) == 0); } static unsigned checks; static void failure(int encoding, int stage, size_t partial, int error, bool pending, int opcode) { /* stage: first byte, second byte, extended length, mask. */ size_t start = stage == 0 ? 0 : stage == 1 ? 1 : stage == 2 ? 2 : 2 + encoding; setup(encoding, opcode, start + partial, error, 1, pending ? start + partial : 0); unsigned char output[5]; memset(output, 0xab, sizeof(output)); httpd_ws_frame_t frame = {.payload = output + 1}; esp_err_t ret = httpd_ws_get_frame_type(&req); if (stage == 0) { assert(ret == ESP_OK && aux.ws_final && aux.ws_type == HTTPD_WS_TYPE_CLOSE); assert(frame.len == 0); } else if (opcode == 2) { assert(ret == ESP_OK && aux.ws_type == 2 && aux.ws_final); assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_FAIL); assert(frame.type == 2 && frame.final); assert(frame.len == (stage == 3 ? 3u : 0u)); } else { assert(ret == ESP_ERR_INVALID_STATE); assert(aux.ws_type == opcode && aux.ws_final); } assert(position == limit && terminal_calls == 1 && sends == 0); assert(sd.pending_len == 0 && sd.ws_handshake_done && !sd.ws_control_frames); assert(req.aux == &aux && aux.sd == &sd); for (size_t i = 0; i < sizeof(output); i++) assert(output[i] == 0xab); for (size_t i = 0; i < sizeof(aux.mask_key); i++) assert(aux.mask_key[i] == (stage == 3 && i < partial ? 0x10 + i : 0xcc)); finish(); checks++; } static void valid(int encoding, size_t split, size_t pending, int opcode, bool probe) { setup(encoding, opcode, 32, 0, split, pending); unsigned char output[5]; memset(output, 0xab, sizeof(output)); httpd_ws_frame_t frame = {.payload = output + 1}; assert(httpd_ws_get_frame_type(&req) == ESP_OK); if (opcode == 2) { if (probe) { assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK); assert(frame.len == 3 && position == header_len); assert(output[1] == 0xab); } assert(httpd_ws_recv_frame(&req, &frame, 3) == ESP_OK); assert(frame.len == 3 && frame.type == 2 && frame.final); assert(memcmp(output + 1, plain, 3) == 0 && sends == 0); } else { assert(sends == 1 && sent_type == (opcode == 9 ? 10 : 8)); assert(sent_len == (opcode == 9 ? 3u : 0u)); if (opcode == 9) assert(memcmp(sent_payload, plain, 3) == 0); } assert(output[0] == 0xab && output[4] == 0xab); assert(position == packet_len && sd.pending_len == 0 && terminal_calls == 0); finish(); checks++; } static void extended_length(int encoding, uint64_t length) { setup(encoding, 2, (size_t)(6 + encoding), 0, 1, 0); for (int i = 0; i < encoding; i++) wire[2 + i] = (unsigned char)(length >> (8 * (encoding - i - 1))); httpd_ws_frame_t frame = {0}; assert(httpd_ws_get_frame_type(&req) == ESP_OK); assert(httpd_ws_recv_frame(&req, &frame, 0) == ESP_OK); assert(frame.len == length && frame.type == 2 && frame.final); assert(position == header_len && sends == 0 && terminal_calls == 0); finish(); checks++; } int main(void) { const int errors[] = {-1, HTTPD_SOCK_ERR_TIMEOUT, 0}; const int encodings[] = {0, 2, 8}; const int opcodes[] = {2, 9, 8}; for (size_t e = 0; e < 3; e++) { int encoding = encodings[e]; for (size_t o = 0; o < 3; o++) { for (int stage = 0; stage < 4; stage++) { if (stage == 2 && encoding == 0) continue; size_t extent = stage < 2 ? 1 : stage == 2 ? (size_t)encoding : 4; for (size_t part = 0; part < extent; part++) for (size_t err = 0; err < 3; err++) for (int pending = 0; pending < 2; pending++) failure(encoding, stage, part, errors[err], pending, opcodes[o]); } for (size_t split = 1; split <= 16; split *= 2) for (size_t pending = 0; pending <= (size_t)(6 + encoding); pending++) valid(encoding, split, pending, opcodes[o], false); valid(encoding, 1, 0, opcodes[o], true); } } extended_length(2, 126); extended_length(2, 65535); extended_length(8, 65536); extended_length(8, UINT64_C(0x0102030405060708)); printf("WS actual full receive/blocking/pending functions: %u guard-page/canary cases PASS\n", checks); return 0; }