diff --git a/src/server.c b/src/server.c index fd7316b..855feff 100644 --- a/src/server.c +++ b/src/server.c @@ -536,6 +536,12 @@ static void *handle_websocket(void *arg) pthread_exit(NULL); } + // Remove socket timeout for WebSocket (connections should stay open) + struct timeval ws_timeout; + ws_timeout.tv_sec = 0; // No timeout - wait indefinitely + ws_timeout.tv_usec = 0; + setsockopt(conn->socket_fd, SOL_SOCKET, SO_RCVTIMEO, &ws_timeout, sizeof(ws_timeout)); + log_event("WebSocket connection established"); uint8_t buffer[65536]; @@ -554,9 +560,13 @@ static void *handle_websocket(void *arg) if (bytes_received <= 0) { - ws_close_connection(conn, 1000); - free(conn); - pthread_exit(NULL); + if (bytes_received == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) + { + ws_close_connection(conn, 1000); + free(conn); + pthread_exit(NULL); + } + continue; } ws_frame_header_t header; @@ -578,7 +588,7 @@ static void *handle_websocket(void *arg) if (ws_is_valid_utf8(payload, header.payload_length)) { // Echo back the text message - ws_send_text(conn, (const char *)payload); + ws_send_frame(conn, WS_OPCODE_TEXT, payload, header.payload_length); log_event("WebSocket text frame received and echoed"); } else diff --git a/src/websocket.c b/src/websocket.c index 8e3ea30..226787c 100644 --- a/src/websocket.c +++ b/src/websocket.c @@ -219,6 +219,28 @@ int ws_parse_frame(const uint8_t *data, size_t len, ws_frame_header_t *header, u // Create WebSocket frame int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const uint8_t *payload, size_t payload_len) { + size_t header_size; + + // Calculate total frame size first + if (payload_len < 126) + { + header_size = 2; + } + else if (payload_len < 65536) + { + header_size = 4; + } + else + { + header_size = 10; + } + + // Check buffer size before writing anything + if (buffer_size < header_size + payload_len) + { + return -1; + } + size_t offset = 0; // First byte: FIN + opcode @@ -227,22 +249,16 @@ int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const u // Second byte: MASK + payload length if (payload_len < 126) { - if (buffer_size < offset + 1 + payload_len) - return -1; - buffer[offset++] = payload_len; + buffer[offset++] = (uint8_t)payload_len; } else if (payload_len < 65536) { - if (buffer_size < offset + 3 + payload_len) - return -1; buffer[offset++] = 126; buffer[offset++] = (payload_len >> 8) & 0xFF; buffer[offset++] = payload_len & 0xFF; } else { - if (buffer_size < offset + 9 + payload_len) - return -1; buffer[offset++] = 127; for (int i = 7; i >= 0; i--) { @@ -257,7 +273,7 @@ int ws_create_frame(uint8_t *buffer, size_t buffer_size, uint8_t opcode, const u offset += payload_len; } - return offset; + return (int)offset; } // Send WebSocket frame diff --git a/www/websocket-test.html b/www/websocket-test.html index f04877e..d75c2c2 100644 --- a/www/websocket-test.html +++ b/www/websocket-test.html @@ -11,333 +11,511 @@ box-sizing: border-box; } + :root { + --primary: #3b82f6; + --primary-hover: #2563eb; + --success: #10b981; + --danger: #ef4444; + --bg: #0f172a; + --surface: #1e293b; + --border: #334155; + --text: #f1f5f9; + --text-dim: #94a3b8; + } + body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + background: var(--bg); + color: var(--text); min-height: 100vh; - display: flex; - justify-content: center; - align-items: center; - padding: 20px; + padding: 1.5rem; } .container { - background: white; - border-radius: 15px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); - max-width: 600px; - width: 100%; - overflow: hidden; + max-width: 1200px; + margin: 0 auto; } - .header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 30px; + header { text-align: center; + margin-bottom: 2rem; } - .header h1 { - font-size: 28px; - margin-bottom: 10px; + h1 { + font-size: 2rem; + font-weight: 700; + margin-bottom: 0.5rem; + } + + .subtitle { + color: var(--text-dim); + font-size: 0.95rem; + } + + .grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + margin-bottom: 1.5rem; + } + + @media (max-width: 768px) { + .grid { + grid-template-columns: 1fr; + } + } + + .card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 0.75rem; + padding: 1.5rem; + } + + .card-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; } .status { - display: inline-block; - padding: 5px 15px; - border-radius: 20px; - font-size: 14px; - font-weight: bold; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 0.5rem; + margin-bottom: 1rem; } - .status.connected { - background: #10b981; + .status-dot { + width: 0.75rem; + height: 0.75rem; + border-radius: 50%; + background: var(--danger); + animation: pulse 2s infinite; } - .status.disconnected { - background: #ef4444; + .status-dot.connected { + background: var(--success); } - .content { - padding: 30px; - } - - .messages { - height: 300px; - overflow-y: auto; - border: 2px solid #e5e7eb; - border-radius: 10px; - padding: 15px; - margin-bottom: 20px; - background: #f9fafb; - } - - .message { - margin-bottom: 10px; - padding: 10px; - border-radius: 8px; - animation: fadeIn 0.3s; - } - - .message.sent { - background: #dbeafe; - margin-left: 20px; - } - - .message.received { - background: #d1fae5; - margin-right: 20px; - } - - .message.system { - background: #fee2e2; - text-align: center; - font-style: italic; - } - - .timestamp { - font-size: 11px; - color: #6b7280; - margin-bottom: 5px; + @keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } } .input-group { - display: flex; - gap: 10px; - } - - #messageInput { - flex: 1; - padding: 12px; - border: 2px solid #e5e7eb; - border-radius: 8px; - font-size: 14px; - } - - #messageInput:focus { - outline: none; - border-color: #667eea; - } - - button { - padding: 12px 30px; - border: none; - border-radius: 8px; - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: all 0.3s; - } - - #sendBtn { - background: #667eea; - color: white; - } - - #sendBtn:hover:not(:disabled) { - background: #5568d3; - transform: translateY(-2px); - } - - #sendBtn:disabled { - background: #9ca3af; - cursor: not-allowed; - } - - #connectBtn { - width: 100%; - margin-top: 15px; - } - - #connectBtn.connected { - background: #ef4444; - color: white; - } - - #connectBtn.disconnected { - background: #10b981; - color: white; - } - - @keyframes fadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - .controls { - margin-top: 20px; - padding-top: 20px; - border-top: 2px solid #e5e7eb; + margin-bottom: 1rem; } label { display: block; - margin-bottom: 8px; - color: #374151; - font-weight: 600; + margin-bottom: 0.4rem; + font-size: 0.9rem; + color: var(--text-dim); } - input[type="text"] { + input, select, textarea { width: 100%; - padding: 10px; - border: 2px solid #e5e7eb; - border-radius: 8px; - font-size: 14px; - margin-bottom: 15px; + padding: 0.65rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--border); + border-radius: 0.5rem; + color: var(--text); + font-size: 0.95rem; + transition: all 0.2s; + } + + input:focus, select:focus, textarea:focus { + outline: none; + border-color: var(--primary); + background: rgba(255, 255, 255, 0.08); + } + + textarea { + resize: vertical; + min-height: 80px; + font-family: 'Consolas', monospace; + } + + .btn { + padding: 0.65rem 1.25rem; + border: none; + border-radius: 0.5rem; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + display: inline-flex; + align-items: center; + gap: 0.5rem; + } + + .btn-primary { + background: var(--primary); + color: white; + } + + .btn-primary:hover:not(:disabled) { + background: var(--primary-hover); + } + + .btn-success { + background: var(--success); + color: white; + } + + .btn-danger { + background: var(--danger); + color: white; + } + + .btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .btn-block { + width: 100%; + justify-content: center; + } + + .btn-group { + display: flex; + gap: 0.5rem; + } + + .messages { + background: rgba(0, 0, 0, 0.2); + border-radius: 0.5rem; + padding: 1rem; + height: 400px; + overflow-y: auto; + margin-bottom: 1rem; + } + + .message { + margin-bottom: 0.75rem; + padding: 0.75rem; + border-radius: 0.5rem; + font-size: 0.9rem; + animation: fadeIn 0.3s; + } + + @keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } + } + + .message.sent { + background: rgba(59, 130, 246, 0.2); + border-left: 3px solid var(--primary); + } + + .message.received { + background: rgba(16, 185, 129, 0.2); + border-left: 3px solid var(--success); + } + + .message.system { + background: rgba(148, 163, 184, 0.15); + border-left: 3px solid var(--text-dim); + text-align: center; + } + + .message-time { + font-size: 0.75rem; + color: var(--text-dim); + margin-bottom: 0.25rem; + } + + .message-content { + word-break: break-word; + } + + .stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 1rem; + } + + .stat { + text-align: center; + padding: 1rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 0.5rem; + } + + .stat-value { + font-size: 1.75rem; + font-weight: 700; + color: var(--primary); + } + + .stat-label { + font-size: 0.8rem; + color: var(--text-dim); + margin-top: 0.25rem; + } + + .messages::-webkit-scrollbar { + width: 6px; + } + + .messages::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 3px; + } + + .messages::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; + } + + .messages::-webkit-scrollbar-thumb:hover { + background: var(--primary); }
Carbon Server
+