1 | #ifndef VWS_WEBSOCKET_DECLARE |
2 | #define VWS_WEBSOCKET_DECLARE |
3 | |
4 | #include <stdint.h> |
5 | #include <stddef.h> |
6 | |
7 | #include <openssl/ssl.h> |
8 | #include <openssl/err.h> |
9 | #include <openssl/crypto.h> |
10 | |
11 | #include <stdbool.h> |
12 | |
13 | #include "vws.h" |
14 | #include "socket.h" |
15 | #include "util/sc_queue.h" |
16 | |
17 | #ifdef __cplusplus |
18 | extern "C" { |
19 | #endif |
20 | |
21 | //------------------------------------------------------------------------------ |
22 | // Frame |
23 | //------------------------------------------------------------------------------ |
24 | |
25 | /** @brief Defines the states of a WebSocket frame. */ |
26 | typedef enum |
27 | { |
28 | /** Not all of the frame data has been received or processed. */ |
29 | FRAME_INCOMPLETE, |
30 | |
31 | /** All of the frame data has been received and processed. */ |
32 | FRAME_COMPLETE, |
33 | |
34 | /** There was an error in processing the frame data. */ |
35 | FRAME_ERROR |
36 | |
37 | } fs_t; |
38 | |
39 | /** @brief Defines the types of WebSocket frames */ |
40 | typedef enum |
41 | { |
42 | /** A continuation frame, data message split across multiple frames. */ |
43 | CONTINUATION_FRAME = 0x0, |
44 | |
45 | /** A text data frame. */ |
46 | TEXT_FRAME = 0x1, |
47 | |
48 | /** A binary data frame. */ |
49 | BINARY_FRAME = 0x2, |
50 | |
51 | /** A close control frame. */ |
52 | CLOSE_FRAME = 0x8, |
53 | |
54 | /** A ping control frame. */ |
55 | PING_FRAME = 0x9, |
56 | |
57 | /** A pong control frame, in response to a ping. */ |
58 | PONG_FRAME = 0xA |
59 | |
60 | } frame_type_t; |
61 | |
62 | /** |
63 | * @brief Represents a Websocket frame. |
64 | */ |
65 | typedef struct vws_frame |
66 | { |
67 | /**< Final frame in the message (1) or not (0). */ |
68 | unsigned char fin; |
69 | |
70 | /**< Defines the interpretation of the payload data. */ |
71 | unsigned char opcode; |
72 | |
73 | /**< Defines whether the payload is masked. */ |
74 | unsigned char mask; |
75 | |
76 | /**< Position of the data in the frame. */ |
77 | unsigned int offset; |
78 | |
79 | /**< The size of the payload data. */ |
80 | unsigned long long size; |
81 | |
82 | /**< The payload data for the frame. */ |
83 | unsigned char* data; |
84 | |
85 | } vws_frame; |
86 | |
87 | /** |
88 | * @brief Instantiates a new websocket frame given some data, size, and opcode. |
89 | * |
90 | * @param data The data to be contained in the frame. |
91 | * @param s The size of the data. |
92 | * @param oc The opcode for the frame. |
93 | * @return Returns a pointer to the new frame. |
94 | */ |
95 | vws_frame* vws_frame_new(ucstr data, size_t s, unsigned char oc); |
96 | |
97 | /** |
98 | * @brief Frees memory associated with a vws_frame object. |
99 | * |
100 | * @param frame The vws_frame object to free. |
101 | * @return void |
102 | * |
103 | * @ingroup FrameFunctions |
104 | */ |
105 | void vws_frame_free(vws_frame* frame); |
106 | |
107 | /** |
108 | * @brief Serializes a vws_frame into a buffer that can be sent over the |
109 | * network. |
110 | * |
111 | * @param f The vws_frame to serialize. |
112 | * @return A pointer to the serialized buffer. |
113 | * |
114 | * @ingroup FrameFunctions |
115 | */ |
116 | vws_buffer* vws_serialize(vws_frame* f); |
117 | |
118 | /** |
119 | * @brief Deserializes raw network data into a vws_frame, updating consumed |
120 | * with the number of bytes processed. |
121 | * |
122 | * @param data The raw network data. |
123 | * @param size The size of the data. |
124 | * @param f The vws_frame to deserialize into. |
125 | * @param consumed Pointer to the number of bytes consumed during |
126 | * deserialization. |
127 | * @return The status of the deserialization process, 0 if successful, an error |
128 | * code otherwise. |
129 | * |
130 | * @ingroup FrameFunctions |
131 | */ |
132 | fs_t vws_deserialize(ucstr data, size_t size, vws_frame* f, size_t* consumed); |
133 | |
134 | /** |
135 | * @brief Generates a close frame for a WebSocket connection. |
136 | * |
137 | * @return A pointer to the generated close frame. |
138 | * |
139 | * @ingroup FrameFunctions |
140 | */ |
141 | vws_buffer* vws_generate_close_frame(); |
142 | |
143 | /** |
144 | * @brief Generates a pong frame in response to a received ping frame. |
145 | * |
146 | * @param ping_data The data from the received ping frame. |
147 | * @param s The size of the ping data. |
148 | * @return A pointer to the generated pong frame. |
149 | * |
150 | * @ingroup FrameFunctions |
151 | */ |
152 | vws_buffer* vws_generate_pong_frame(ucstr ping_data, size_t s); |
153 | |
154 | /** |
155 | * @brief Dumps the contents of a WebSocket frame for debugging purposes. |
156 | * |
157 | * @param data The data of the WebSocket frame. |
158 | * @param size The size of the WebSocket frame. |
159 | * |
160 | * @ingroup TraceFunctions |
161 | */ |
162 | void vws_dump_websocket_frame(const unsigned char* data, size_t size); |
163 | |
164 | //------------------------------------------------------------------------------ |
165 | // Message |
166 | //------------------------------------------------------------------------------ |
167 | |
168 | /** |
169 | * @brief Represents a websocket message, which contains opcode and data. |
170 | */ |
171 | typedef struct vws_msg |
172 | { |
173 | /**< Defines the interpretation of the payload data. */ |
174 | unsigned char opcode; |
175 | |
176 | /**< The payload data for the message. */ |
177 | vws_buffer* data; |
178 | } vws_msg; |
179 | |
180 | /** |
181 | * @brief Instantiates a new websocket message. |
182 | * |
183 | * @return Returns a pointer to the new message. |
184 | */ |
185 | vws_msg* vws_msg_new(); |
186 | |
187 | /** |
188 | * @brief Deallocates a websocket message. |
189 | * |
190 | * @param msg The message to be deallocated. |
191 | */ |
192 | void vws_msg_free(vws_msg* msg); |
193 | |
194 | //------------------------------------------------------------------------------ |
195 | // Connection |
196 | //------------------------------------------------------------------------------ |
197 | |
198 | struct vws_cnx; |
199 | |
200 | /** |
201 | * @brief Callback for frame processing |
202 | * @param cnx The connection instance |
203 | * @param frame The frame to process |
204 | */ |
205 | typedef void (*vws_process_frame)(struct vws_cnx* cnx, vws_frame* frame); |
206 | |
207 | typedef struct vws_url_data |
208 | { |
209 | char* href; |
210 | char* protocol; |
211 | char* host; |
212 | char* auth; |
213 | char* hostname; |
214 | char* pathname; |
215 | char* search; |
216 | char* path; |
217 | char* hash; |
218 | char* query; |
219 | char* port; |
220 | } vws_url_data; |
221 | |
222 | /** |
223 | * @defgroup ConnectionFunctions |
224 | * |
225 | * @brief Functions that manage WebSocket connections |
226 | * |
227 | */ |
228 | |
229 | /** |
230 | * @brief Callback for disconnect. This is called after socket disconnect. This |
231 | * allows additional logic to be hooked into disconnect process. |
232 | * @param cnx The connection instance |
233 | * @return Returns true on successful reconnect, false otherwise. |
234 | */ |
235 | typedef void (*vws_cnx_disconnect)(struct vws_cnx* cnx); |
236 | |
237 | /** |
238 | * @brief A WebSocket connection. |
239 | */ |
240 | typedef struct vws_cnx |
241 | { |
242 | /**< Base class */ |
243 | struct vws_socket base; |
244 | |
245 | /**< Connection state flags. */ |
246 | uint64_t flags; |
247 | |
248 | /**< The URL of the websocket server. */ |
249 | vws_url_data* url; |
250 | |
251 | /**< The websocket key. */ |
252 | char* key; |
253 | |
254 | /**< Queue for incoming frames. */ |
255 | struct sc_queue_ptr queue; |
256 | |
257 | /**< Frame processing callback. */ |
258 | vws_process_frame process; |
259 | |
260 | /**< Disconnect callback. */ |
261 | vws_cnx_disconnect disconnect; |
262 | |
263 | /**< User-defined data associated with the connection */ |
264 | char* data; |
265 | |
266 | } vws_cnx; |
267 | |
268 | /** |
269 | * @brief Generates a WebSocket accept key from input. |
270 | * |
271 | * @param key The input key |
272 | * @return Returns the accept key on the heap. Caller MUST call free() on return |
273 | * value |
274 | * |
275 | * @ingroup ConnectionFunctions |
276 | */ |
277 | cstr vws_accept_key(cstr key); |
278 | |
279 | /** |
280 | * @brief Connects to a specified host URL. |
281 | * |
282 | * @param c The websocket connection. |
283 | * @param uri The URL of the host to connect to. |
284 | * @return Returns true if the connection is successful, false otherwise. |
285 | * |
286 | * @ingroup ConnectionFunctions |
287 | */ |
288 | bool vws_connect(vws_cnx* c, cstr uri); |
289 | |
290 | /** |
291 | * @brief Attempts to reconnects based on previous URL. If no previous |
292 | * connection was made, this function does nothing and returns false. |
293 | * |
294 | * @param c The websocket connection. |
295 | * @return Returns true if the connection is successful, false otherwise. |
296 | * |
297 | * @ingroup ConnectionFunctions |
298 | */ |
299 | bool vws_reconnect(vws_cnx* c); |
300 | |
301 | /** |
302 | * @brief Closes the connection to the host. |
303 | * |
304 | * @param c The websocket connection. |
305 | * |
306 | * @ingroup ConnectionFunctions |
307 | */ |
308 | void vws_disconnect(vws_cnx* c); |
309 | |
310 | /** |
311 | * @brief Allocates a new websocket connection. |
312 | * |
313 | * @return A pointer to the new connection instance. |
314 | * |
315 | * @ingroup ConnectionFunctions |
316 | */ |
317 | vws_cnx* vws_cnx_new(); |
318 | |
319 | /** |
320 | * @brief Deallocates a websocket connection. |
321 | * |
322 | * @param c The websocket connection. |
323 | * |
324 | * @ingroup ConnectionFunctions |
325 | */ |
326 | void vws_cnx_free(vws_cnx* c); |
327 | |
328 | /** |
329 | * @brief Checks connection state |
330 | * |
331 | * @param c The websocket connection. |
332 | * @return Returns true if connection is established, false otherwise. If false, |
333 | * sets vrtql.e to VE_SOCKET. |
334 | * |
335 | * @ingroup ConnectionFunctions |
336 | */ |
337 | bool vws_cnx_is_connected(vws_cnx* c); |
338 | |
339 | /** |
340 | * @brief Sets the connection to server mode |
341 | * |
342 | * @param c The websocket connection. |
343 | * @return Returns void. |
344 | * |
345 | * @ingroup ConnectionFunctions |
346 | */ |
347 | void vws_cnx_set_server_mode(vws_cnx* c); |
348 | |
349 | /** |
350 | * @brief Processes incoming data from a Socket. |
351 | * |
352 | * This function parses the data in the socket buffer and attempts to extract |
353 | * complete WebSocket frames. It processes as many frames as possible and |
354 | * calls the appropriate processing function for each frame. The consumed |
355 | * data is then drained from the buffer. |
356 | * |
357 | * @param c The WebSocket connection. |
358 | * @return The total number of bytes consumed from the socket buffer. |
359 | * If no complete frame is available or an error occurs, it returns 0. |
360 | * |
361 | * @ingroup ConnectionFunctions |
362 | */ |
363 | ssize_t vws_cnx_ingress(vws_cnx* c); |
364 | |
365 | //------------------------------------------------------------------------------ |
366 | // Messaging API |
367 | //------------------------------------------------------------------------------ |
368 | |
369 | /** |
370 | * @brief Sends a TEXT frame |
371 | * |
372 | * @param c The connection. |
373 | * @param string The text to send. |
374 | |
375 | * @return Returns the number of bytes sent or -1 on error. In the case of |
376 | * error, check vws.e for details, especially for VE_SOCKET. |
377 | */ |
378 | ssize_t vws_frame_send_text(vws_cnx* c, cstr string); |
379 | |
380 | /** |
381 | * @brief Sends a BINARY frame |
382 | * |
383 | * @param c The connection. |
384 | * @param string The data to send. |
385 | * @param size The size of the data in bytes. |
386 | * @return Returns the number of bytes sent or -1 on error. In the case of |
387 | * error, check vws.e for details, especially for VE_SOCKET. |
388 | */ |
389 | ssize_t vws_frame_send_binary(vws_cnx* c, ucstr string, size_t size); |
390 | |
391 | /** |
392 | * @brief Sends custom frame containing data |
393 | * |
394 | * @param c The connection. |
395 | * @param dataThe data to send. |
396 | * @param size The size of the data in bytes. |
397 | * @param oc The websocket opcode defining the frame type. |
398 | * @return Returns the number of bytes sent or -1 on error. In the case of |
399 | * error, check vws.e for details, especially for VE_SOCKET. |
400 | */ |
401 | ssize_t vws_frame_send_data(vws_cnx* c, ucstr data, size_t size, int oc); |
402 | |
403 | /** |
404 | * @brief Sends a prebuilt websocket frame. This function will take ownership of |
405 | * the frame and deallocate it for the caller. |
406 | * |
407 | * @param c The connection. |
408 | * @param frame The frame to send. This function will take ownership of the |
409 | * frame and deallocate it for the caller. |
410 | * @return Returns the number of bytes sent or -1 on error. In the case of |
411 | * error, check vws.e for details, especially for VE_SOCKET. |
412 | */ |
413 | ssize_t vws_frame_send(vws_cnx* c, vws_frame* frame); |
414 | |
415 | /** |
416 | * @brief Sends a TEXT message |
417 | * |
418 | * @param c The connection. |
419 | * @param string The text to send. |
420 | * @return Returns the number of bytes sent or -1 on error. In the case of |
421 | * error, check vws.e for details, especially for VE_SOCKET. |
422 | */ |
423 | ssize_t vws_msg_send_text(vws_cnx* c, cstr string); |
424 | |
425 | /** |
426 | * @brief Sends a BINARY message |
427 | * |
428 | * @param c The connection. |
429 | * @param string The data to send. |
430 | * @param size The size of the data in bytes. |
431 | * @return Returns the number of bytes sent or -1 on error. In the case of |
432 | * error, check vws.e for details, especially for VE_SOCKET. |
433 | */ |
434 | ssize_t vws_msg_send_binary(vws_cnx* c, ucstr string, size_t size); |
435 | |
436 | /** |
437 | * @brief Sends custom message containing |
438 | * |
439 | * @param c The connection. |
440 | * @param dataThe data to send. |
441 | * @param size The size of the data in bytes. |
442 | * @param oc The websocket opcode defining the frame type. |
443 | * @return Returns the number of bytes sent or -1 on error. In the case of |
444 | * error, check vws.e for details, especially for VE_SOCKET. |
445 | */ |
446 | ssize_t vws_msg_send_data(vws_cnx* c, ucstr data, size_t size, int oc); |
447 | |
448 | /** |
449 | * @brief Receives a websocket message from the connection. If there are no |
450 | * messages in queue, it will call socket_wait_for_frame(). |
451 | * |
452 | * @param c The connection. |
453 | * @return Returns the most recent websocket message or NULL if the socket |
454 | * timed out without receiving a full message. If NULL, check for |
455 | * socket error (vws.e.code == VE_SOCKET or vws_cnx_is_connected()). |
456 | */ |
457 | vws_msg* vws_msg_recv(vws_cnx* c); |
458 | |
459 | /** |
460 | * @brief Removes and returns the first complete message from the connection's |
461 | * message queue. |
462 | * |
463 | * @param c The vws_cnx representing the WebSocket connection. |
464 | * @return A pointer to the popped vws_msg object, or NULL if the queue is |
465 | * empty. |
466 | * |
467 | * @ingroup MessageFunctions |
468 | */ |
469 | vws_msg* vws_msg_pop(vws_cnx* c); |
470 | |
471 | /** |
472 | * @brief Receives a websocket frame from the connection. If there are no |
473 | * frames in queue, it will call socket_wait_for_frame(). |
474 | * |
475 | * @param c The connection. |
476 | * @return Returns the most recent websocket frame or NULL if the socket timed |
477 | * out without receiving a full frame. If NULL, check for |
478 | * socket error (vws.e.code == VE_SOCKET or vws_cnx_is_connected()). |
479 | */ |
480 | vws_frame* vws_frame_recv(vws_cnx* c); |
481 | |
482 | #ifdef __cplusplus |
483 | } |
484 | #endif |
485 | |
486 | #endif /* VWS_WEBSOCKET_DECLARE */ |
487 | |