| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #ifndef NETDATA_WEBSOCKET_INTERNAL_H |
| 4 | #define NETDATA_WEBSOCKET_INTERNAL_H |
| 5 | |
| 6 | #include "websocket.h" |
| 7 | |
| 8 | // Maximum number of WebSocket threads |
| 9 | #define WEBSOCKET_MAX_THREADS 2 |
| 10 | |
| 11 | #define WORKERS_WEBSOCKET_POLL 0 |
| 12 | #define WORKERS_WEBSOCKET_CMD_READ 1 |
| 13 | #define WORKERS_WEBSOCKET_CMD_EXIT 2 |
| 14 | #define WORKERS_WEBSOCKET_CMD_ADD 3 |
| 15 | #define WORKERS_WEBSOCKET_CMD_DEL 4 |
| 16 | #define WORKERS_WEBSOCKET_CMD_BROADCAST 5 |
| 17 | #define WORKERS_WEBSOCKET_CMD_UNKNOWN 6 |
| 18 | #define WORKERS_WEBSOCKET_SOCK_RECEIVE 7 |
| 19 | #define WORKERS_WEBSOCKET_SOCK_SEND 8 |
| 20 | #define WORKERS_WEBSOCKET_SOCK_ERROR 9 |
| 21 | #define WORKERS_WEBSOCKET_CLIENT_TIMEOUT 10 |
| 22 | #define WORKERS_WEBSOCKET_SEND_PING 11 |
| 23 | #define WORKERS_WEBSOCKET_CLIENT_STUCK 12 |
| 24 | |
| 25 | #define WORKERS_WEBSOCKET_INCOMPLETE_FRAME 13 |
| 26 | #define WORKERS_WEBSOCKET_COMPLETE_FRAME 14 |
| 27 | #define WORKERS_WEBSOCKET_MESSAGE 15 |
| 28 | #define WORKERS_WEBSOCKET_MSG_PING 16 |
| 29 | #define WORKERS_WEBSOCKET_MSG_PONG 17 |
| 30 | #define WORKERS_WEBSOCKET_MSG_CLOSE 18 |
| 31 | #define WORKERS_WEBSOCKET_MSG_INVALID 19 |
| 32 | |
| 33 | // Forward declaration for thread structure |
| 34 | struct websocket_thread; |
| 35 | |
| 36 | #include "websocket-compression.h" |
| 37 | |
| 38 | // WebSocket protocol constants |
| 39 | #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
| 40 | |
| 41 | // WebSocket frame constants |
| 42 | #define WS_FIN 0x80 // Final frame bit |
| 43 | #define WS_RSV1 0x40 // Reserved bit 1 (used for compression) |
| 44 | #define WS_MASK 0x80 // Mask bit |
| 45 | |
| 46 | // Frame size limits for protection against DoS and browser compatibility |
| 47 | #define WS_MAX_INCOMING_FRAME_SIZE (20ULL * 1024 * 1024) // 20MB max incoming frame size (browsers have ~16MiB) |
| 48 | #define WS_MAX_OUTGOING_FRAME_SIZE (4ULL * 1024 * 1024) // 4MB max outgoing frame size for browser compatibility |
| 49 | #define WS_MAX_DECOMPRESSED_SIZE (200ULL * 1024 * 1024) // 200MB max inbound uncompressed message |
| 50 | #define WS_DEBUG_DUMP_BYTES 32 // max payload bytes captured in debug hex dumps |
| 51 | |
| 52 | // WebSocket timeout constants (in seconds) |
| 53 | #define WS_PERIODIC_PING_INTERVAL 60 // Send periodic ping every 60 seconds |
| 54 | #define WS_IDLE_CHECK_INTERVAL 120 // Check if client is idle after 120 seconds |
| 55 | #define WS_INACTIVITY_TIMEOUT 1800 // Disconnect after 30 minutes (1800 seconds) of inactivity |
| 56 | #define WS_CLOSING_STATE_TIMEOUT 5 // Force close if stuck in closing state for 5 seconds |
| 57 | |
| 58 | // WebSocket frame header structure - used for processing frame headers |
| 59 | typedef struct websocket_frame_header { |
| 60 | unsigned char fin:1; |
| 61 | unsigned char rsv1:1; |
| 62 | unsigned char rsv2:1; |
| 63 | unsigned char rsv3:1; |
| 64 | unsigned char opcode:4; |
| 65 | unsigned char mask:1; |
| 66 | unsigned char len:7; |
| 67 | |
| 68 | unsigned char mask_key[4]; // Masking key (if present) |
| 69 | size_t frame_size; // Size of the entire frame |
| 70 | size_t header_size; // Size of the header |
| 71 | size_t payload_length; // Length of the payload data |
| 72 | void *payload; // Pointer to the payload data |
| 73 | } WEBSOCKET_FRAME_HEADER; |
| 74 | |
| 75 | // Buffer for message data (used for reassembly of fragmented messages) |
| 76 | typedef struct websocket_buffer { |
| 77 | char *data; // Buffer holding message data |
| 78 | size_t length; // Current buffer length |
| 79 | size_t size; // Allocated buffer size |
| 80 | } WS_BUF; |
| 81 | |
| 82 | // Forward declaration for client structure |
| 83 | struct websocket_server_client; |
| 84 | |
| 85 | // Function prototypes for buffer handling |
| 86 | |
| 87 | // Message and payload processing functions |
| 88 | void websocket_client_message_reset(struct websocket_server_client *wsc); |
| 89 | bool websocket_client_process_message(struct websocket_server_client *wsc); |
| 90 | bool websocket_client_decompress_message(struct websocket_server_client *wsc); |
| 91 | |
| 92 | // Additional helper functions |
| 93 | bool websocket_frame_is_control_opcode(WEBSOCKET_OPCODE opcode); |
| 94 | bool websocket_validate_utf8(const char *data, size_t length); |
| 95 | |
| 96 | #include "websocket-buffer.h" |
| 97 | |
| 98 | // WebSocket connection context - full structure definition |
| 99 | struct websocket_server_client { |
| 100 | WEBSOCKET_STATE state; |
| 101 | ND_SOCK sock; // Socket with SSL abstraction |
| 102 | uint32_t id; // Unique client ID |
| 103 | size_t max_message_size; |
| 104 | size_t max_outbound_frame_size; // Maximum size of outgoing frames for this client |
| 105 | time_t connected_t; // Connection timestamp |
| 106 | time_t last_activity_t; // Last activity timestamp |
| 107 | |
| 108 | // Buffer for I/O data |
| 109 | struct circular_buffer in_buffer; // Incoming raw data (circular buffer) |
| 110 | struct circular_buffer out_buffer; // Outgoing raw data (circular buffer) |
| 111 | size_t next_frame_size; // The size of the next complete frame to read |
| 112 | |
| 113 | // Connection info |
| 114 | char client_ip[INET6_ADDRSTRLEN]; |
| 115 | char client_port[NI_MAXSERV]; |
| 116 | WEBSOCKET_PROTOCOL protocol; // The negotiated subprotocol |
| 117 | |
| 118 | // Authentication info |
| 119 | USER_AUTH user_auth; // Authentication information copied from web_client |
| 120 | |
| 121 | // Thread management |
| 122 | struct websocket_thread *wth; // The thread handling this client |
| 123 | struct websocket_server_client *prev; // Linked list for thread's client management |
| 124 | struct websocket_server_client *next; // Linked list for thread's client management |
| 125 | |
| 126 | // Message processing state |
| 127 | WS_BUF payload; // Pre-allocated buffer for message data |
| 128 | WS_BUF u_payload; // Pre-allocated buffer for uncompressed message data |
| 129 | WS_BUF c_payload; // Pre-allocated buffer for outbound compressed data |
| 130 | WEBSOCKET_OPCODE opcode; // Current message opcode |
| 131 | bool is_compressed; // Whether the current message is compressed |
| 132 | bool message_complete; // Whether the current message is complete |
| 133 | size_t message_id; // Sequential ID for messages, starting from 0 |
| 134 | size_t frame_id; // Sequential ID for frames within current message |
| 135 | |
| 136 | // Compression state |
| 137 | WEBSOCKET_COMPRESSION_CTX compression; |
| 138 | |
| 139 | // Connection closing state |
| 140 | bool flush_and_remove_client; // Flag to indicate we're just flushing buffer before close |
| 141 | |
| 142 | // Protocol handler callbacks |
| 143 | void (*on_connect)(struct websocket_server_client *wsc); // Called when a client is successfully connected |
| 144 | void (*on_message)(struct websocket_server_client *wsc, const char *message, size_t length, WEBSOCKET_OPCODE opcode); // Called when a message is received |
| 145 | void (*on_close)(struct websocket_server_client *wsc, WEBSOCKET_CLOSE_CODE code, const char *reason); // Called BEFORE sending close frame |
| 146 | void (*on_disconnect)(struct websocket_server_client *wsc); // Called when a client is disconnected |
| 147 | |
| 148 | // User data for application use |
| 149 | void *user_data; |
| 150 | }; |
| 151 | |
| 152 | // Forward declarations for websocket client |
| 153 | typedef struct websocket_server_client WS_CLIENT; |
| 154 | |
| 155 | // WebSocket thread structure |
| 156 | typedef struct websocket_thread { |
| 157 | size_t id; // Thread ID |
| 158 | pid_t tid; |
| 159 | |
| 160 | struct { |
| 161 | ND_THREAD *thread; // Thread handle |
| 162 | bool running; // Thread running status |
| 163 | SPINLOCK spinlock; // Thread spinlock |
| 164 | }; |
| 165 | |
| 166 | size_t clients_current; // Current number of clients in the thread |
| 167 | SPINLOCK clients_spinlock; // Spinlock for client operations |
| 168 | struct websocket_server_client *clients; // Head of the clients double-linked list |
| 169 | |
| 170 | nd_poll_t *ndpl; // Poll instance |
| 171 | |
| 172 | struct { |
| 173 | int pipe[2]; // Command pipe [0] = read, [1] = write |
| 174 | char *buffer; // Reusable scratch buffer for command payloads |
| 175 | size_t buffer_size; |
| 176 | } cmd; |
| 177 | |
| 178 | } WEBSOCKET_THREAD; |
| 179 | |
| 180 | // Global array of WebSocket threads |
| 181 | extern WEBSOCKET_THREAD websocket_threads[WEBSOCKET_MAX_THREADS]; |
| 182 | |
| 183 | // Define JudyL typed structure for WebSocket clients |
| 184 | DEFINE_JUDYL_TYPED(WS_CLIENTS, struct websocket_server_client *); |
| 185 | |
| 186 | // WebSocket thread commands |
| 187 | #define WEBSOCKET_THREAD_CMD_EXIT 1 |
| 188 | #define WEBSOCKET_THREAD_CMD_ADD_CLIENT 2 |
| 189 | #define WEBSOCKET_THREAD_CMD_REMOVE_CLIENT 3 |
| 190 | #define WEBSOCKET_THREAD_CMD_BROADCAST 4 |
| 191 | |
| 192 | // Buffer size definitions for WebSocket operations |
| 193 | #define WEBSOCKET_RECEIVE_BUFFER_SIZE 4096 // Size used for network read operations |
| 194 | |
| 195 | // Initial buffer sizes |
| 196 | #define WEBSOCKET_IN_BUFFER_INITIAL_SIZE 8192UL // Initial size for incoming data buffer |
| 197 | #define WEBSOCKET_OUT_BUFFER_INITIAL_SIZE 16384UL // Initial size for outgoing data buffer |
| 198 | #define WEBSOCKET_PAYLOAD_INITIAL_SIZE 8192UL // Initial size for message payload buffer |
| 199 | #define WEBSOCKET_UNPACKED_INITIAL_SIZE 16384UL // Initial size for uncompressed message buffer |
| 200 | |
| 201 | // Maximum buffer sizes to protect against memory exhaustion |
| 202 | #define WEBSOCKET_IN_BUFFER_MAX_SIZE (20UL * 1024 * 1024) // 10MiB max for incoming data buffer |
| 203 | #define WEBSOCKET_OUT_BUFFER_MAX_SIZE (20UL * 1024 * 1024) // 10MiB max for outgoing data buffer |
| 204 | |
| 205 | NEVERNULL |
| 206 | WS_CLIENT *websocket_client_create(void); |
| 207 | |
| 208 | // Thread management |
| 209 | void websocket_threads_init(void); |
| 210 | void websocket_threads_join(void); |
| 211 | bool websocket_thread_send_command(WEBSOCKET_THREAD *wth, uint8_t cmd, uint32_t id); |
| 212 | bool websocket_thread_send_broadcast(WEBSOCKET_THREAD *wth, WEBSOCKET_OPCODE opcode, const char *message); |
| 213 | void websocket_thread(void *ptr); |
| 214 | void websocket_thread_enqueue_client(WEBSOCKET_THREAD *wth, struct websocket_server_client *wsc); |
| 215 | bool websocket_thread_update_client_poll_flags(struct websocket_server_client *wsc); |
| 216 | |
| 217 | // Client registry internals |
| 218 | void websocket_client_free(WS_CLIENT *wsc); |
| 219 | bool websocket_client_register(struct websocket_server_client *wsc); |
| 220 | void websocket_client_unregister(struct websocket_server_client *wsc); |
| 221 | struct websocket_server_client *websocket_client_find_by_id(size_t id); |
| 222 | |
| 223 | // Utility functions |
| 224 | // Validates a WebSocket close code according to RFC 6455 |
| 225 | bool websocket_validate_close_code(uint16_t code); |
| 226 | void websocket_debug(WS_CLIENT *wsc, const char *format, ...); |
| 227 | void websocket_info(WS_CLIENT *wsc, const char *format, ...); |
| 228 | void websocket_error(WS_CLIENT *wsc, const char *format, ...); |
| 229 | void websocket_dump_debug(WS_CLIENT *wsc, const char *payload, size_t payload_length, const char *format, ...); |
| 230 | |
| 231 | // Frame processing result codes |
| 232 | typedef enum { |
| 233 | WS_FRAME_ERROR = -1, // Processing error occurred |
| 234 | WS_FRAME_COMPLETE = 0, // Frame processing completed successfully |
| 235 | WS_FRAME_NEED_MORE_DATA = 1, // Need more data to complete frame processing |
| 236 | WS_FRAME_MESSAGE_READY = 2 // A complete message is ready to be processed |
| 237 | } WEBSOCKET_FRAME_RESULT; |
| 238 | |
| 239 | // Centralized protocol validation functions |
| 240 | void websocket_protocol_exception(WS_CLIENT *wsc, WEBSOCKET_CLOSE_CODE reason_code, const char *reason_txt); |
| 241 | |
| 242 | // Protocol receiver functions - websocket-protocol-rcv.c |
| 243 | ssize_t websocket_protocol_got_data(WS_CLIENT *wsc, char *data, size_t length); |
| 244 | |
| 245 | // Payload sender - breaks large messages into multiple frames |
| 246 | int websocket_protocol_send_payload( |
| 247 | WS_CLIENT *wsc, const char *payload, |
| 248 | size_t payload_len, WEBSOCKET_OPCODE opcode, bool use_compression); |
| 249 | int websocket_protocol_send_text(WS_CLIENT *wsc, const char *text); |
| 250 | int websocket_protocol_send_binary(WS_CLIENT *wsc, const void *data, size_t length); |
| 251 | int websocket_protocol_send_close(WS_CLIENT *wsc, WEBSOCKET_CLOSE_CODE code, const char *reason); |
| 252 | int websocket_protocol_send_ping(WS_CLIENT *wsc, const char *data, size_t length); |
| 253 | int websocket_protocol_send_pong(WS_CLIENT *wsc, const char *data, size_t length); |
| 254 | |
| 255 | // IO functions from old implementation - will be refactored |
| 256 | ssize_t websocket_receive_data(struct websocket_server_client *wsc); |
| 257 | ssize_t websocket_write_data(struct websocket_server_client *wsc); |
| 258 | |
| 259 | // WebSocket message sending functions |
| 260 | int websocket_send_message(WS_CLIENT *wsc, const char *message, size_t length, WEBSOCKET_OPCODE opcode); |
| 261 | int websocket_broadcast_message(const char *message, WEBSOCKET_OPCODE opcode); |
| 262 | |
| 263 | bool websocket_protocol_parse_header_from_buffer(const char *buffer, size_t length, |
| 264 | WEBSOCKET_FRAME_HEADER *header); |
| 265 | #endif // NETDATA_WEBSOCKET_INTERNAL_H |