Clean up and ensure proper usage of alloca()
[asterisk/asterisk.git] / res / res_http_websocket.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief WebSocket support for the Asterisk internal HTTP server
22  *
23  * \author Joshua Colp <jcolp@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>extended</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "asterisk/module.h"
35 #include "asterisk/http.h"
36 #include "asterisk/astobj2.h"
37 #include "asterisk/strings.h"
38 #include "asterisk/file.h"
39 #include "asterisk/unaligned.h"
40 #include "asterisk/http_websocket.h"
41
42 /*! \brief GUID used to compute the accept key, defined in the specifications */
43 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
44
45 /*! \brief Number of buckets for registered protocols */
46 #define MAX_PROTOCOL_BUCKETS 7
47
48 /*! \brief Size of the pre-determined buffer for WebSocket frames */
49 #define MAXIMUM_FRAME_SIZE 8192
50
51 /*! \brief Default reconstruction size for multi-frame payload reconstruction. If exceeded the next frame will start a
52  *         payload.
53  */
54 #define DEFAULT_RECONSTRUCTION_CEILING 16384
55
56 /*! \brief Maximum reconstruction size for multi-frame payload reconstruction. */
57 #define MAXIMUM_RECONSTRUCTION_CEILING 16384
58
59 /*! \brief Structure definition for session */
60 struct ast_websocket {
61         FILE *f;                          /*!< Pointer to the file instance used for writing and reading */
62         int fd;                           /*!< File descriptor for the session, only used for polling */
63         struct ast_sockaddr address;      /*!< Address of the remote client */
64         enum ast_websocket_opcode opcode; /*!< Cached opcode for multi-frame messages */
65         size_t payload_len;               /*!< Length of the payload */
66         char *payload;                    /*!< Pointer to the payload */
67         size_t reconstruct;               /*!< Number of bytes before a reconstructed payload will be returned and a new one started */
68         unsigned int secure:1;            /*!< Bit to indicate that the transport is secure */
69         unsigned int closing:1;           /*!< Bit to indicate that the session is in the process of being closed */
70 };
71
72 /*! \brief Structure definition for protocols */
73 struct websocket_protocol {
74         char *name;                      /*!< Name of the protocol */
75         ast_websocket_callback callback; /*!< Callback called when a new session is established */
76 };
77
78 /*! \brief Container for registered protocols */
79 static struct ao2_container *protocols;
80
81 /*! \brief Hashing function for protocols */
82 static int protocol_hash_fn(const void *obj, const int flags)
83 {
84         const struct websocket_protocol *protocol = obj;
85         const char *name = obj;
86
87         return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
88 }
89
90 /*! \brief Comparison function for protocols */
91 static int protocol_cmp_fn(void *obj, void *arg, int flags)
92 {
93         const struct websocket_protocol *protocol1 = obj, *protocol2 = arg;
94         const char *protocol = arg;
95
96         return !strcasecmp(protocol1->name, flags & OBJ_KEY ? protocol : protocol2->name) ? CMP_MATCH | CMP_STOP : 0;
97 }
98
99 /*! \brief Destructor function for protocols */
100 static void protocol_destroy_fn(void *obj)
101 {
102         struct websocket_protocol *protocol = obj;
103         ast_free(protocol->name);
104 }
105
106 /*! \brief Destructor function for sessions */
107 static void session_destroy_fn(void *obj)
108 {
109         struct ast_websocket *session = obj;
110
111         if (session->f) {
112                 fclose(session->f);
113                 ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
114         }
115
116         ast_free(session->payload);
117 }
118
119 int ast_websocket_add_protocol(const char *name, ast_websocket_callback callback)
120 {
121         struct websocket_protocol *protocol;
122
123         ao2_lock(protocols);
124
125         /* Ensure a second protocol handler is not registered for the same protocol */
126         if ((protocol = ao2_find(protocols, name, OBJ_KEY | OBJ_NOLOCK))) {
127                 ao2_ref(protocol, -1);
128                 ao2_unlock(protocols);
129                 return -1;
130         }
131
132         if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
133                 ao2_unlock(protocols);
134                 return -1;
135         }
136
137         if (!(protocol->name = ast_strdup(name))) {
138                 ao2_ref(protocol, -1);
139                 ao2_unlock(protocols);
140                 return -1;
141         }
142
143         protocol->callback = callback;
144
145         ao2_link_flags(protocols, protocol, OBJ_NOLOCK);
146         ao2_unlock(protocols);
147         ao2_ref(protocol, -1);
148
149         ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);
150
151         return 0;
152 }
153
154 int ast_websocket_remove_protocol(const char *name, ast_websocket_callback callback)
155 {
156         struct websocket_protocol *protocol;
157
158         if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) {
159                 return -1;
160         }
161
162         if (protocol->callback != callback) {
163                 ao2_ref(protocol, -1);
164                 return -1;
165         }
166
167         ao2_unlink(protocols, protocol);
168         ao2_ref(protocol, -1);
169
170         ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name);
171
172         return 0;
173 }
174
175 /*! \brief Close function for websocket session */
176 int ast_websocket_close(struct ast_websocket *session, uint16_t reason)
177 {
178         char frame[4] = { 0, }; /* The header is 2 bytes and the reason code takes up another 2 bytes */
179
180         frame[0] = AST_WEBSOCKET_OPCODE_CLOSE | 0x80;
181         frame[1] = 2; /* The reason code is always 2 bytes */
182
183         /* If no reason has been specified assume 1000 which is normal closure */
184         put_unaligned_uint16(&frame[2], htons(reason ? reason : 1000));
185
186         session->closing = 1;
187
188         return (fwrite(frame, 1, 4, session->f) == 4) ? 0 : -1;
189 }
190
191
192 /*! \brief Write function for websocket traffic */
193 int ast_websocket_write(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
194 {
195         size_t header_size = 2; /* The minimum size of a websocket frame is 2 bytes */
196         char *frame;
197         uint64_t length = 0;
198
199         if (actual_length < 126) {
200                 length = actual_length;
201         } else if (actual_length < (1 << 16)) {
202                 length = 126;
203                 /* We need an additional 2 bytes to store the extended length */
204                 header_size += 2;
205         } else {
206                 length = 127;
207                 /* We need an additional 8 bytes to store the really really extended length */
208                 header_size += 8;
209         }
210
211         frame = ast_alloca(header_size);
212         memset(frame, 0, sizeof(*frame));
213
214         frame[0] = opcode | 0x80;
215         frame[1] = length;
216
217         /* Use the additional available bytes to store the length */
218         if (length == 126) {
219                 put_unaligned_uint16(&frame[2], htons(actual_length));
220         } else if (length == 127) {
221                 put_unaligned_uint64(&frame[2], htonl(actual_length));
222         }
223
224         if (fwrite(frame, 1, header_size, session->f) != header_size) {
225                 return -1;
226         }
227
228         if (fwrite(payload, 1, actual_length, session->f) != actual_length) {
229                 return -1;
230         }
231
232         return 0;
233 }
234
235 void ast_websocket_reconstruct_enable(struct ast_websocket *session, size_t bytes)
236 {
237         session->reconstruct = MIN(bytes, MAXIMUM_RECONSTRUCTION_CEILING);
238 }
239
240 void ast_websocket_reconstruct_disable(struct ast_websocket *session)
241 {
242         session->reconstruct = 0;
243 }
244
245 void ast_websocket_ref(struct ast_websocket *session)
246 {
247         ao2_ref(session, +1);
248 }
249
250 void ast_websocket_unref(struct ast_websocket *session)
251 {
252         ao2_ref(session, -1);
253 }
254
255 int ast_websocket_fd(struct ast_websocket *session)
256 {
257         return session->closing ? -1 : session->fd;
258 }
259
260 struct ast_sockaddr *ast_websocket_remote_address(struct ast_websocket *session)
261 {
262         return &session->address;
263 }
264
265 int ast_websocket_is_secure(struct ast_websocket *session)
266 {
267         return session->secure;
268 }
269
270 int ast_websocket_set_nonblock(struct ast_websocket *session)
271 {
272         int flags;
273
274         if ((flags = fcntl(session->fd, F_GETFL)) == -1) {
275                 return -1;
276         }
277
278         flags |= O_NONBLOCK;
279
280         if ((flags = fcntl(session->fd, F_SETFL, flags)) == -1) {
281                 return -1;
282         }
283
284         return 0;
285 }
286
287 int ast_websocket_read(struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented)
288 {
289         char buf[MAXIMUM_FRAME_SIZE] = "";
290         size_t frame_size, expected = 2;
291
292         *payload = NULL;
293         *payload_len = 0;
294         *fragmented = 0;
295
296         /* We try to read in 14 bytes, which is the largest possible WebSocket header */
297         if ((frame_size = fread(&buf, 1, 14, session->f)) < 1) {
298                 return -1;
299         }
300
301         /* The minimum size for a WebSocket frame is 2 bytes */
302         if (frame_size < expected) {
303                 return -1;
304         }
305
306         *opcode = buf[0] & 0xf;
307
308         if (*opcode == AST_WEBSOCKET_OPCODE_TEXT || *opcode == AST_WEBSOCKET_OPCODE_BINARY || *opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ||
309             *opcode == AST_WEBSOCKET_OPCODE_PING || *opcode == AST_WEBSOCKET_OPCODE_PONG) {
310                 int fin = (buf[0] >> 7) & 1;
311                 int mask_present = (buf[1] >> 7) & 1;
312                 char *mask = NULL, *new_payload;
313                 size_t remaining;
314
315                 if (mask_present) {
316                         /* The mask should take up 4 bytes */
317                         expected += 4;
318
319                         if (frame_size < expected) {
320                                 /* Per the RFC 1009 means we received a message that was too large for us to process */
321                                 ast_websocket_close(session, 1009);
322                                 return 0;
323                         }
324                 }
325
326                 /* Assume no extended length and no masking at the beginning */
327                 *payload_len = buf[1] & 0x7f;
328                 *payload = &buf[2];
329
330                 /* Determine if extended length is being used */
331                 if (*payload_len == 126) {
332                         /* Use the next 2 bytes to get a uint16_t */
333                         expected += 2;
334                         *payload += 2;
335
336                         if (frame_size < expected) {
337                                 ast_websocket_close(session, 1009);
338                                 return 0;
339                         }
340
341                         *payload_len = ntohs(get_unaligned_uint16(&buf[2]));
342                 } else if (*payload_len == 127) {
343                         /* Use the next 8 bytes to get a uint64_t */
344                         expected += 8;
345                         *payload += 8;
346
347                         if (frame_size < expected) {
348                                 ast_websocket_close(session, 1009);
349                                 return 0;
350                         }
351
352                         *payload_len = ntohl(get_unaligned_uint64(&buf[2]));
353                 }
354
355                 /* If masking is present the payload currently points to the mask, so move it over 4 bytes to the actual payload */
356                 if (mask_present) {
357                         mask = *payload;
358                         *payload += 4;
359                 }
360
361                 /* Determine how much payload we need to read in as we may have already read some in */
362                 remaining = *payload_len - (frame_size - expected);
363
364                 /* If how much payload they want us to read in exceeds what we are capable of close the session, things
365                  * will fail no matter what most likely */
366                 if (remaining > (MAXIMUM_FRAME_SIZE - frame_size)) {
367                         ast_websocket_close(session, 1009);
368                         return 0;
369                 }
370
371                 new_payload = *payload + (frame_size - expected);
372
373                 /* Read in the remaining payload */
374                 while (remaining > 0) {
375                         size_t payload_read;
376
377                         /* Wait for data to come in */
378                         if (ast_wait_for_input(session->fd, -1) <= 0) {
379                                 *opcode = AST_WEBSOCKET_OPCODE_CLOSE;
380                                 *payload = NULL;
381                                 session->closing = 1;
382                                 return 0;
383                         }
384
385                         /* If some sort of failure occurs notify the caller */
386                         if ((payload_read = fread(new_payload, 1, remaining, session->f)) < 1) {
387                                 return -1;
388                         }
389
390                         remaining -= payload_read;
391                         new_payload += payload_read;
392                 }
393
394                 /* If a mask is present unmask the payload */
395                 if (mask_present) {
396                         unsigned int pos;
397                         for (pos = 0; pos < *payload_len; pos++) {
398                                 (*payload)[pos] ^= mask[pos % 4];
399                         }
400                 }
401
402                 if (!(new_payload = ast_realloc(session->payload, session->payload_len + *payload_len))) {
403                         *payload_len = 0;
404                         ast_websocket_close(session, 1009);
405                         return 0;
406                 }
407
408                 /* Per the RFC for PING we need to send back an opcode with the application data as received */
409                 if (*opcode == AST_WEBSOCKET_OPCODE_PING) {
410                         ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len);
411                 }
412
413                 session->payload = new_payload;
414                 memcpy(session->payload + session->payload_len, *payload, *payload_len);
415                 session->payload_len += *payload_len;
416
417                 if (!fin && session->reconstruct && (session->payload_len < session->reconstruct)) {
418                         /* If this is not a final message we need to defer returning it until later */
419                         if (*opcode != AST_WEBSOCKET_OPCODE_CONTINUATION) {
420                                 session->opcode = *opcode;
421                         }
422                         *opcode = AST_WEBSOCKET_OPCODE_CONTINUATION;
423                         *payload_len = 0;
424                         *payload = NULL;
425                 } else {
426                         if (*opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
427                                 if (!fin) {
428                                         /* If this was not actually the final message tell the user it is fragmented so they can deal with it accordingly */
429                                         *fragmented = 1;
430                                 } else {
431                                         /* Final frame in multi-frame so push up the actual opcode */
432                                         *opcode = session->opcode;
433                                 }
434                         }
435                         *payload_len = session->payload_len;
436                         *payload = session->payload;
437                         session->payload_len = 0;
438                 }
439         } else if (*opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
440                 char *new_payload;
441
442                 *payload_len = buf[1] & 0x7f;
443
444                 /* Make the payload available so the user can look at the reason code if they so desire */
445                 if ((*payload_len) && (new_payload = ast_realloc(session->payload, *payload_len))) {
446                         session->payload = new_payload;
447                         memcpy(session->payload, &buf[2], *payload_len);
448                         *payload = session->payload;
449                 }
450
451                 if (!session->closing) {
452                         ast_websocket_close(session, 0);
453                 }
454
455                 fclose(session->f);
456                 session->f = NULL;
457                 ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
458         } else {
459                 /* We received an opcode that we don't understand, the RFC states that 1003 is for a type of data that can't be accepted... opcodes
460                  * fit that, I think. */
461                 ast_websocket_close(session, 1003);
462         }
463
464         return 0;
465 }
466
467 /*! \brief Callback that is executed everytime an HTTP request is received by this module */
468 static int websocket_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
469 {
470         struct ast_variable *v;
471         char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
472         int version = 0, flags = 1;
473         struct websocket_protocol *protocol_handler = NULL;
474         struct ast_websocket *session;
475
476         /* Upgrade requests are only permitted on GET methods */
477         if (method != AST_HTTP_GET) {
478                 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
479                 return -1;
480         }
481
482         /* Get the minimum headers required to satisfy our needs */
483         for (v = headers; v; v = v->next) {
484                 if (!strcasecmp(v->name, "Upgrade")) {
485                         upgrade = ast_strip(ast_strdupa(v->value));
486                 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key")) {
487                         key = ast_strip(ast_strdupa(v->value));
488                 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key1")) {
489                         key1 = ast_strip(ast_strdupa(v->value));
490                 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key2")) {
491                         key2 = ast_strip(ast_strdupa(v->value));
492                 } else if (!strcasecmp(v->name, "Sec-WebSocket-Protocol")) {
493                         requested_protocols = ast_strip(ast_strdupa(v->value));
494                         protos = ast_strdupa(requested_protocols);
495                 } else if (!strcasecmp(v->name, "Sec-WebSocket-Version")) {
496                         if (sscanf(v->value, "%30d", &version) != 1) {
497                                 version = 0;
498                         }
499                 }
500         }
501
502         /* If this is not a websocket upgrade abort */
503         if (!upgrade || strcasecmp(upgrade, "websocket")) {
504                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket",
505                         ast_sockaddr_stringify(&ser->remote_address));
506                 ast_http_error(ser, 426, "Upgrade Required", NULL);
507                 return -1;
508         } else if (ast_strlen_zero(requested_protocols)) {
509                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested",
510                         ast_sockaddr_stringify(&ser->remote_address));
511                 fputs("HTTP/1.1 400 Bad Request\r\n"
512                       "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
513                 return -1;
514         } else if (key1 && key2) {
515                 /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and
516                  * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/
517                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen",
518                         ast_sockaddr_stringify(&ser->remote_address));
519                 fputs("HTTP/1.1 400 Bad Request\r\n"
520                       "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
521                 return 0;
522         }
523
524         /* Iterate through the requested protocols trying to find one that we have a handler for */
525         while ((protocol = strsep(&requested_protocols, ","))) {
526                 if ((protocol_handler = ao2_find(protocols, ast_strip(protocol), OBJ_KEY))) {
527                         break;
528                 }
529         }
530
531         /* If no protocol handler exists bump this back to the requester */
532         if (!protocol_handler) {
533                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported\n",
534                         ast_sockaddr_stringify(&ser->remote_address), protos);
535                 fputs("HTTP/1.1 400 Bad Request\r\n"
536                       "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
537                 return 0;
538         }
539
540         /* Determine how to respond depending on the version */
541         if (version == 7 || version == 8 || version == 13) {
542                 /* Version 7 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 */
543                 /* Version 8 defined in specification http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 */
544                 /* Version 13 defined in specification http://tools.ietf.org/html/rfc6455 */
545                 char combined[strlen(key) + strlen(WEBSOCKET_GUID) + 1], base64[64];
546                 uint8_t sha[20];
547
548                 if (!(session = ao2_alloc(sizeof(*session), session_destroy_fn))) {
549                         ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted",
550                                 ast_sockaddr_stringify(&ser->remote_address));
551                         fputs("HTTP/1.1 400 Bad Request\r\n"
552                               "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
553                         ao2_ref(protocol_handler, -1);
554                         return 0;
555                 }
556
557                 snprintf(combined, sizeof(combined), "%s%s", key, WEBSOCKET_GUID);
558                 ast_sha1_hash_uint(sha, combined);
559                 ast_base64encode(base64, (const unsigned char*)sha, 20, sizeof(base64));
560
561                 fprintf(ser->f, "HTTP/1.1 101 Switching Protocols\r\n"
562                         "Upgrade: %s\r\n"
563                         "Connection: Upgrade\r\n"
564                         "Sec-WebSocket-Accept: %s\r\n"
565                         "Sec-WebSocket-Protocol: %s\r\n\r\n",
566                         upgrade,
567                         base64,
568                         protocol);
569         } else {
570
571                 /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */
572                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen",
573                         ast_sockaddr_stringify(&ser->remote_address), version ? version : 75);
574                 fputs("HTTP/1.1 400 Bad Request\r\n"
575                       "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
576                 ao2_ref(protocol_handler, -1);
577                 return 0;
578         }
579
580         /* Enable keepalive on all sessions so the underlying user does not have to */
581         if (setsockopt(ser->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
582                 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive",
583                         ast_sockaddr_stringify(&ser->remote_address));
584                 fputs("HTTP/1.1 400 Bad Request\r\n"
585                       "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
586                 ao2_ref(session, -1);
587                 ao2_ref(protocol_handler, -1);
588                 return 0;
589         }
590
591         ast_verb(2, "WebSocket connection from '%s' for protocol '%s' accepted using version '%d'\n", ast_sockaddr_stringify(&ser->remote_address), protocol, version);
592
593         /* Populate the session with all the needed details */
594         session->f = ser->f;
595         session->fd = ser->fd;
596         ast_sockaddr_copy(&session->address, &ser->remote_address);
597         session->opcode = -1;
598         session->reconstruct = DEFAULT_RECONSTRUCTION_CEILING;
599         session->secure = ser->ssl ? 1 : 0;
600
601         /* Give up ownership of the socket and pass it to the protocol handler */
602         protocol_handler->callback(session, get_vars, headers);
603         ao2_ref(protocol_handler, -1);
604
605         /* By dropping the FILE* from the session it won't get closed when the HTTP server cleans up */
606         ser->f = NULL;
607
608         return 0;
609 }
610
611 static struct ast_http_uri websocketuri = {
612         .callback = websocket_callback,
613         .description = "Asterisk HTTP WebSocket",
614         .uri = "ws",
615         .has_subtree = 0,
616         .data = NULL,
617         .key = __FILE__,
618 };
619
620 /*! \brief Simple echo implementation which echoes received text and binary frames */
621 static void websocket_echo_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
622 {
623         int flags, res;
624
625         if ((flags = fcntl(ast_websocket_fd(session), F_GETFL)) == -1) {
626                 goto end;
627         }
628
629         flags |= O_NONBLOCK;
630
631         if (fcntl(ast_websocket_fd(session), F_SETFL, flags) == -1) {
632                 goto end;
633         }
634
635         while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {
636                 char *payload;
637                 uint64_t payload_len;
638                 enum ast_websocket_opcode opcode;
639                 int fragmented;
640
641                 if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
642                         /* We err on the side of caution and terminate the session if any error occurs */
643                         break;
644                 }
645
646                 if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
647                         ast_websocket_write(session, opcode, payload, payload_len);
648                 } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
649                         break;
650                 }
651         }
652
653 end:
654         ast_websocket_unref(session);
655 }
656
657 static int load_module(void)
658 {
659         protocols = ao2_container_alloc(MAX_PROTOCOL_BUCKETS, protocol_hash_fn, protocol_cmp_fn);
660         ast_http_uri_link(&websocketuri);
661         ast_websocket_add_protocol("echo", websocket_echo_callback);
662
663         return 0;
664 }
665
666 static int unload_module(void)
667 {
668         ast_websocket_remove_protocol("echo", websocket_echo_callback);
669         ast_http_uri_unlink(&websocketuri);
670         ao2_ref(protocols, -1);
671
672         return 0;
673 }
674
675 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "HTTP WebSocket Support",
676                 .load = load_module,
677                 .unload = unload_module,
678                 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
679         );