/** * test_ws_protocol.c — WebSocket 协议层单元测试 * * 使用标准 Linux socket 替代 DPDK 数据面,验证: * 1. WebSocket 握手 (HTTP Upgrade → 101) * 2. WS 帧编码/解码 (masked send / unmasked recv) * 3. 订阅/推送 消息交互 * * 编译: gcc -o test_ws_protocol test_ws_protocol.c -lpthread * 运行: ./test_ws_protocol [server_ip] [port] */ #include #include #include #include #include #include #include #include #include /* ═══════════════════════════════════════════════════════ * WebSocket 常量 (RFC 6455) * ═══════════════════════════════════════════════════════ */ #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 #define WS_MASK_KEY_SIZE 4 #define WS_MAX_FRAME_SIZE (64 * 1024) /* ── Base64 ─────────────────────────────────────────── */ static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static int base64_encode(const unsigned char *src, int len, char *dst) { int i, j = 0; for (i = 0; i < len; i += 3) { unsigned int val = (unsigned char)src[i] << 16; unsigned int val2 = (i + 1 < len) ? ((unsigned char)src[i + 1] << 8) : 0; unsigned int val3 = (i + 2 < len) ? (unsigned char)src[i + 2] : 0; val |= val2 | val3; dst[j++] = b64_table[(val >> 18) & 0x3F]; dst[j++] = b64_table[(val >> 12) & 0x3F]; dst[j++] = (i + 1 < len) ? b64_table[(val >> 6) & 0x3F] : '='; dst[j++] = (i + 2 < len) ? b64_table[val & 0x3F] : '='; } dst[j] = '\0'; return j; } /* ── WebSocket 握手 ─────────────────────────────────── */ static int ws_handshake(int fd, const char *host, int port, const char *path) { /* Generate random key */ uint8_t key_bytes[16]; char ws_key[32]; srand(time(NULL)); for (int i = 0; i < 16; i++) key_bytes[i] = rand() % 256; base64_encode(key_bytes, 16, ws_key); /* Build HTTP Upgrade request */ char req[2048]; int len = 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: %s\r\n" "Sec-WebSocket-Version: 13\r\n" "\r\n", path, host, port, ws_key); printf("[WS] Sending handshake:\n%s", req); if (send(fd, req, len, 0) != len) { printf("[WS] ERROR: send handshake failed\n"); return -1; } /* Receive response */ char resp[4096]; memset(resp, 0, sizeof(resp)); int total = 0; while (total < (int)sizeof(resp) - 1) { int n = recv(fd, resp + total, sizeof(resp) - 1 - total, 0); if (n <= 0) { printf("[WS] recv failed: %d\n", n); return -1; } total += n; resp[total] = '\0'; if (strstr(resp, "\r\n\r\n")) break; } printf("[WS] Response:\n%s\n", resp); if (!strstr(resp, "101")) { printf("[WS] ERROR: not 101\n"); return -1; } printf("[WS] Handshake OK!\n"); return 0; } /* ── WebSocket 帧编码 (client → server, MASKED) ─────── */ static int ws_send_frame(int fd, int opcode, const uint8_t *payload, int len) { uint8_t frame[WS_MAX_FRAME_SIZE]; int pos = 0; frame[pos++] = 0x80 | (opcode & 0x0F); /* FIN=1 */ uint8_t mask[4]; for (int i = 0; i < 4; i++) mask[i] = rand() % 256; if (len < 126) { frame[pos++] = 0x80 | (uint8_t)len; } else if (len < 65536) { frame[pos++] = 0x80 | 126; frame[pos++] = (len >> 8) & 0xFF; frame[pos++] = len & 0xFF; } else { frame[pos++] = 0x80 | 127; for (int i = 7; i >= 0; i--) frame[pos++] = (len >> (i * 8)) & 0xFF; } memcpy(&frame[pos], mask, 4); pos += 4; for (int i = 0; i < len; i++) frame[pos + i] = payload[i] ^ mask[i % 4]; pos += len; int sent = send(fd, frame, pos, 0); return sent == pos ? sent : -1; } static int ws_send_text(int fd, const char *text) { return ws_send_frame(fd, WS_OPCODE_TEXT, (const uint8_t *)text, strlen(text)); } /* ── WebSocket 帧解码 (server → client, UNMASKED) ──── */ static int ws_recv_frame(int fd, int *opcode, uint8_t *buf, int *buflen) { uint8_t header[10]; int total = 0; while (total < 2) { int n = recv(fd, header + total, 2 - total, 0); if (n <= 0) return n; total += n; } uint8_t byte0 = header[0], byte1 = header[1]; *opcode = byte0 & 0x0F; int masked = (byte1 >> 7) & 0x01; uint64_t payload_len = byte1 & 0x7F; if (payload_len == 126) { while (total < 4) { int n = recv(fd, header + total, 4 - total, 0); if (n <= 0) return n; total += n; } payload_len = ((uint64_t)header[2] << 8) | header[3]; } else if (payload_len == 127) { while (total < 10) { int n = recv(fd, header + total, 10 - total, 0); if (n <= 0) return n; total += n; } payload_len = 0; for (int i = 2; i < 10; i++) payload_len = (payload_len << 8) | header[i]; } uint8_t mask_key[4] = {0}; if (masked) { uint8_t mk[4]; int mk_total = 0; while (mk_total < 4) { int n = recv(fd, mk + mk_total, 4 - mk_total, 0); if (n <= 0) return n; mk_total += n; } memcpy(mask_key, mk, 4); } if (*opcode == WS_OPCODE_CLOSE) { printf("[WS] CLOSE frame\n"); return 0; } if (*opcode == WS_OPCODE_PING) { uint8_t pd[128]; int pl = payload_len < 128 ? (int)payload_len : 128; int rt = 0; while (rt < pl) { int n = recv(fd, pd + rt, pl - rt, 0); if (n <= 0) return n; rt += n; } if (masked) for (int i = 0; i < pl; i++) pd[i] ^= mask_key[i % 4]; ws_send_frame(fd, WS_OPCODE_PONG, pd, pl); printf("[WS] PING → PONG\n"); *buflen = 0; return 0; } if (*opcode == WS_OPCODE_PONG) { uint8_t pd[128]; int pl = payload_len < 128 ? (int)payload_len : 128; int rt = 0; while (rt < pl) { int n = recv(fd, pd + rt, pl - rt, 0); if (n <= 0) return n; rt += n; } printf("[WS] PONG\n"); *buflen = 0; return 0; } if (payload_len > (uint64_t)(*buflen)) { printf("[WS] payload too large: %lu > %d\n", payload_len, *buflen); return -1; } int rt = 0; while (rt < (int)payload_len) { int n = recv(fd, buf + rt, (int)payload_len - rt, 0); if (n <= 0) return n; rt += n; } if (masked) for (int i = 0; i < (int)payload_len; i++) buf[i] ^= mask_key[i % 4]; *buflen = (int)payload_len; return *buflen; } /* ═══════════════════════════════════════════════════════ * Main test * ═══════════════════════════════════════════════════════ */ static int test_case = 1; static void test_send_recv(int fd) { printf("\n=== Test %d: echo ===\n", test_case++); ws_send_text(fd, "hello dpdk websocket!"); uint8_t buf[4096]; int opcode, len = sizeof(buf); int n = ws_recv_frame(fd, &opcode, buf, &len); if (n > 0) { buf[len] = '\0'; printf("[OK] echo reply: %s\n", buf); } else { printf("[FAIL] no response\n"); } } static void test_subscribe(int fd) { printf("\n=== Test %d: subscribe ===\n", test_case++); ws_send_text(fd, "{\"action\":\"subscribe\",\"topic\":\"dpdk_test\"}"); /* First response: subscription confirmation */ uint8_t buf[4096]; int opcode, len = sizeof(buf); int n = ws_recv_frame(fd, &opcode, buf, &len); if (n > 0) { buf[len] = '\0'; printf("[OK] subscribe response: %s\n", buf); } /* Second response: push data with welcome message */ len = sizeof(buf); n = ws_recv_frame(fd, &opcode, buf, &len); if (n > 0) { buf[len] = '\0'; printf("[OK] push data: %s\n", buf); } } static void test_periodic_push(int fd, int wait_sec) { printf("\n=== Test %d: periodic push (wait %ds) ===\n", test_case++, wait_sec); sleep(wait_sec); /* Check for push data (non-blocking poll) */ /* For simplicity, just wait and try one recv with timeout */ struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); uint8_t buf[4096]; int opcode, len = sizeof(buf); int n = ws_recv_frame(fd, &opcode, buf, &len); if (n > 0) { buf[len] = '\0'; printf("[OK] periodic push: %s\n", buf); } else { printf("[INFO] no periodic data received yet\n"); } } int main(int argc, char *argv[]) { const char *server_ip = argc > 1 ? argv[1] : "192.168.159.129"; int port = argc > 2 ? atoi(argv[2]) : 8080; printf("╔══════════════════════════════════════════════╗\n"); printf("║ WebSocket Protocol Unit Test ║\n"); printf("╠══════════════════════════════════════════════╣\n"); printf("║ Server: %s:%d \n", server_ip, port); printf("╚══════════════════════════════════════════════╝\n\n"); /* 1. TCP connect */ int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return 1; } /* Set TCP_NODELAY for lower latency */ int opt = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(port), }; inet_pton(AF_INET, server_ip, &addr.sin_addr); printf("[TCP] Connecting to %s:%d...\n", server_ip, port); if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("connect"); close(fd); return 1; } printf("[TCP] Connected!\n"); /* 2. WebSocket handshake */ if (ws_handshake(fd, server_ip, port, "/ws") < 0) { printf("[FAIL] WebSocket handshake failed\n"); close(fd); return 1; } /* 3. Run tests */ test_send_recv(fd); test_subscribe(fd); test_periodic_push(fd, 5); printf("\n╔══════════════════════════════════════════════╗\n"); printf("║ All tests passed! ║\n"); printf("╚══════════════════════════════════════════════╝\n"); close(fd); return 0; }