00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include "asterisk.h"
00031
00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 409703 $")
00033
00034 #include "asterisk/module.h"
00035 #include "asterisk/http.h"
00036 #include "asterisk/astobj2.h"
00037 #include "asterisk/strings.h"
00038 #include "asterisk/file.h"
00039 #include "asterisk/unaligned.h"
00040
00041 #define AST_API_MODULE
00042 #include "asterisk/http_websocket.h"
00043
00044
00045 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
00046
00047
00048 #define MAX_PROTOCOL_BUCKETS 7
00049
00050
00051 #define MAXIMUM_FRAME_SIZE 8192
00052
00053
00054
00055
00056 #define DEFAULT_RECONSTRUCTION_CEILING 16384
00057
00058
00059 #define MAXIMUM_RECONSTRUCTION_CEILING 16384
00060
00061
00062
00063
00064
00065
00066
00067
00068 #define MAX_WS_HDR_SZ 14
00069 #define MIN_WS_HDR_SZ 2
00070
00071
00072 struct ast_websocket {
00073 FILE *f;
00074 int fd;
00075 struct ast_sockaddr address;
00076 enum ast_websocket_opcode opcode;
00077 size_t payload_len;
00078 char *payload;
00079 size_t reconstruct;
00080 unsigned int secure:1;
00081 unsigned int closing:1;
00082 };
00083
00084
00085 struct websocket_protocol {
00086 char *name;
00087 ast_websocket_callback callback;
00088 };
00089
00090
00091 static struct ao2_container *protocols;
00092
00093
00094 static int protocol_hash_fn(const void *obj, const int flags)
00095 {
00096 const struct websocket_protocol *protocol = obj;
00097 const char *name = obj;
00098
00099 return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
00100 }
00101
00102
00103 static int protocol_cmp_fn(void *obj, void *arg, int flags)
00104 {
00105 const struct websocket_protocol *protocol1 = obj, *protocol2 = arg;
00106 const char *protocol = arg;
00107
00108 return !strcasecmp(protocol1->name, flags & OBJ_KEY ? protocol : protocol2->name) ? CMP_MATCH | CMP_STOP : 0;
00109 }
00110
00111
00112 static void protocol_destroy_fn(void *obj)
00113 {
00114 struct websocket_protocol *protocol = obj;
00115 ast_free(protocol->name);
00116 }
00117
00118
00119 static void session_destroy_fn(void *obj)
00120 {
00121 struct ast_websocket *session = obj;
00122
00123 if (session->f) {
00124 fclose(session->f);
00125 ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
00126 }
00127
00128 ast_free(session->payload);
00129 }
00130
00131 int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback)
00132 {
00133 struct websocket_protocol *protocol;
00134
00135 ao2_lock(protocols);
00136
00137
00138 if ((protocol = ao2_find(protocols, name, OBJ_KEY | OBJ_NOLOCK))) {
00139 ao2_ref(protocol, -1);
00140 ao2_unlock(protocols);
00141 return -1;
00142 }
00143
00144 if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
00145 ao2_unlock(protocols);
00146 return -1;
00147 }
00148
00149 if (!(protocol->name = ast_strdup(name))) {
00150 ao2_ref(protocol, -1);
00151 ao2_unlock(protocols);
00152 return -1;
00153 }
00154
00155 protocol->callback = callback;
00156
00157 ao2_link_flags(protocols, protocol, OBJ_NOLOCK);
00158 ao2_unlock(protocols);
00159 ao2_ref(protocol, -1);
00160
00161 ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);
00162
00163 return 0;
00164 }
00165
00166 int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback)
00167 {
00168 struct websocket_protocol *protocol;
00169
00170 if (!(protocol = ao2_find(protocols, name, OBJ_KEY))) {
00171 return -1;
00172 }
00173
00174 if (protocol->callback != callback) {
00175 ao2_ref(protocol, -1);
00176 return -1;
00177 }
00178
00179 ao2_unlink(protocols, protocol);
00180 ao2_ref(protocol, -1);
00181
00182 ast_verb(2, "WebSocket unregistered sub-protocol '%s'\n", name);
00183
00184 return 0;
00185 }
00186
00187
00188 int AST_OPTIONAL_API_NAME(ast_websocket_close)(struct ast_websocket *session, uint16_t reason)
00189 {
00190 char frame[4] = { 0, };
00191
00192 frame[0] = AST_WEBSOCKET_OPCODE_CLOSE | 0x80;
00193 frame[1] = 2;
00194
00195
00196 put_unaligned_uint16(&frame[2], htons(reason ? reason : 1000));
00197
00198 session->closing = 1;
00199
00200 return (fwrite(frame, 1, 4, session->f) == 4) ? 0 : -1;
00201 }
00202
00203
00204
00205 int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
00206 {
00207 size_t header_size = 2;
00208 char *frame;
00209 uint64_t length = 0;
00210
00211 if (actual_length < 126) {
00212 length = actual_length;
00213 } else if (actual_length < (1 << 16)) {
00214 length = 126;
00215
00216 header_size += 2;
00217 } else {
00218 length = 127;
00219
00220 header_size += 8;
00221 }
00222
00223 frame = ast_alloca(header_size);
00224 memset(frame, 0, sizeof(*frame));
00225
00226 frame[0] = opcode | 0x80;
00227 frame[1] = length;
00228
00229
00230 if (length == 126) {
00231 put_unaligned_uint16(&frame[2], htons(actual_length));
00232 } else if (length == 127) {
00233 put_unaligned_uint64(&frame[2], htonl(actual_length));
00234 }
00235
00236 if (fwrite(frame, 1, header_size, session->f) != header_size) {
00237 return -1;
00238 }
00239
00240 if (fwrite(payload, 1, actual_length, session->f) != actual_length) {
00241 return -1;
00242 }
00243 fflush(session->f);
00244
00245 return 0;
00246 }
00247
00248 void AST_OPTIONAL_API_NAME(ast_websocket_reconstruct_enable)(struct ast_websocket *session, size_t bytes)
00249 {
00250 session->reconstruct = MIN(bytes, MAXIMUM_RECONSTRUCTION_CEILING);
00251 }
00252
00253 void AST_OPTIONAL_API_NAME(ast_websocket_reconstruct_disable)(struct ast_websocket *session)
00254 {
00255 session->reconstruct = 0;
00256 }
00257
00258 void AST_OPTIONAL_API_NAME(ast_websocket_ref)(struct ast_websocket *session)
00259 {
00260 ao2_ref(session, +1);
00261 }
00262
00263 void AST_OPTIONAL_API_NAME(ast_websocket_unref)(struct ast_websocket *session)
00264 {
00265 ao2_ref(session, -1);
00266 }
00267
00268 int AST_OPTIONAL_API_NAME(ast_websocket_fd)(struct ast_websocket *session)
00269 {
00270 return session->closing ? -1 : session->fd;
00271 }
00272
00273 struct ast_sockaddr * AST_OPTIONAL_API_NAME(ast_websocket_remote_address)(struct ast_websocket *session)
00274 {
00275 return &session->address;
00276 }
00277
00278 int AST_OPTIONAL_API_NAME(ast_websocket_is_secure)(struct ast_websocket *session)
00279 {
00280 return session->secure;
00281 }
00282
00283 int AST_OPTIONAL_API_NAME(ast_websocket_set_nonblock)(struct ast_websocket *session)
00284 {
00285 int flags;
00286
00287 if ((flags = fcntl(session->fd, F_GETFL)) == -1) {
00288 return -1;
00289 }
00290
00291 flags |= O_NONBLOCK;
00292
00293 if ((flags = fcntl(session->fd, F_SETFL, flags)) == -1) {
00294 return -1;
00295 }
00296
00297 return 0;
00298 }
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328 static inline int ws_safe_read(struct ast_websocket *session, char *buf, int len, enum ast_websocket_opcode *opcode)
00329 {
00330 int sanity;
00331 size_t rlen;
00332 int xlen = len;
00333 char *rbuf = buf;
00334 for (sanity = 10; sanity; sanity--) {
00335 clearerr(session->f);
00336 rlen = fread(rbuf, 1, xlen, session->f);
00337 if (0 == rlen && ferror(session->f) && errno != EAGAIN) {
00338 ast_log(LOG_ERROR, "Error reading from web socket: %s\n", strerror(errno));
00339 (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00340 session->closing = 1;
00341 return -1;
00342 }
00343 xlen = (xlen - rlen);
00344 rbuf = rbuf + rlen;
00345 if (0 == xlen) {
00346 break;
00347 }
00348 if (ast_wait_for_input(session->fd, 1000) < 0) {
00349 ast_log(LOG_ERROR, "ast_wait_for_input returned err: %s\n", strerror(errno));
00350 (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00351 session->closing = 1;
00352 return -1;
00353 }
00354 }
00355 if (!sanity) {
00356 ast_log(LOG_WARNING, "Websocket seems unresponsive, disconnecting ...\n");
00357 (*opcode) = AST_WEBSOCKET_OPCODE_CLOSE;
00358 session->closing = 1;
00359 return -1;
00360 }
00361 return 0;
00362 }
00363
00364 int AST_OPTIONAL_API_NAME(ast_websocket_read)(struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented)
00365 {
00366 char buf[MAXIMUM_FRAME_SIZE] = "";
00367 int fin = 0;
00368 int mask_present = 0;
00369 char *mask = NULL, *new_payload = NULL;
00370 size_t options_len = 0, frame_size = 0;
00371
00372 *payload = NULL;
00373 *payload_len = 0;
00374 *fragmented = 0;
00375
00376 if (ws_safe_read(session, &buf[0], MIN_WS_HDR_SZ, opcode)) {
00377 return 0;
00378 }
00379 frame_size += MIN_WS_HDR_SZ;
00380
00381
00382 *opcode = buf[0] & 0xf;
00383 *payload_len = buf[1] & 0x7f;
00384 if (*opcode == AST_WEBSOCKET_OPCODE_TEXT || *opcode == AST_WEBSOCKET_OPCODE_BINARY || *opcode == AST_WEBSOCKET_OPCODE_CONTINUATION ||
00385 *opcode == AST_WEBSOCKET_OPCODE_PING || *opcode == AST_WEBSOCKET_OPCODE_PONG) {
00386 fin = (buf[0] >> 7) & 1;
00387 mask_present = (buf[1] >> 7) & 1;
00388
00389
00390 options_len += mask_present ? 4 : 0;
00391 options_len += (*payload_len == 126) ? 2 : (*payload_len == 127) ? 8 : 0;
00392 if (options_len) {
00393
00394 if (ws_safe_read(session, &buf[frame_size], options_len, opcode)) {
00395 return 0;
00396 }
00397 frame_size += options_len;
00398 }
00399
00400 if (*payload_len == 126) {
00401
00402 *payload_len = ntohs(get_unaligned_uint16(&buf[2]));
00403 mask = &buf[4];
00404 } else if (*payload_len == 127) {
00405
00406 *payload_len = ntohl(get_unaligned_uint64(&buf[2]));
00407 mask = &buf[10];
00408 } else {
00409
00410 mask = &buf[2];
00411 }
00412
00413
00414 *payload = &buf[frame_size];
00415 frame_size = frame_size + (*payload_len);
00416 if (frame_size > MAXIMUM_FRAME_SIZE) {
00417 ast_log(LOG_WARNING, "Cannot fit huge websocket frame of %zd bytes\n", frame_size);
00418
00419 ast_websocket_close(session, 1009);
00420 return -1;
00421 }
00422
00423 if (ws_safe_read(session, (*payload), (*payload_len), opcode)) {
00424 return 0;
00425 }
00426
00427
00428 if (mask_present) {
00429 unsigned int pos;
00430 for (pos = 0; pos < *payload_len; pos++) {
00431 (*payload)[pos] ^= mask[pos % 4];
00432 }
00433 }
00434
00435 if (!(new_payload = ast_realloc(session->payload, (session->payload_len + *payload_len)))) {
00436 ast_log(LOG_WARNING, "Failed allocation: %p, %zd, %"PRIu64"\n",
00437 session->payload, session->payload_len, *payload_len);
00438 *payload_len = 0;
00439 ast_websocket_close(session, 1009);
00440 return 0;
00441 }
00442
00443
00444 if (*opcode == AST_WEBSOCKET_OPCODE_PING) {
00445 ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len);
00446 }
00447
00448 session->payload = new_payload;
00449 memcpy((session->payload + session->payload_len), (*payload), (*payload_len));
00450 session->payload_len += *payload_len;
00451
00452 if (!fin && session->reconstruct && (session->payload_len < session->reconstruct)) {
00453
00454 if (*opcode != AST_WEBSOCKET_OPCODE_CONTINUATION) {
00455 session->opcode = *opcode;
00456 }
00457 *opcode = AST_WEBSOCKET_OPCODE_CONTINUATION;
00458 *payload_len = 0;
00459 *payload = NULL;
00460 } else {
00461 if (*opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
00462 if (!fin) {
00463
00464 *fragmented = 1;
00465 } else {
00466
00467 *opcode = session->opcode;
00468 }
00469 }
00470 *payload_len = session->payload_len;
00471 *payload = session->payload;
00472 session->payload_len = 0;
00473 }
00474 } else if (*opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
00475
00476 if ((*payload_len) && (new_payload = ast_realloc(session->payload, *payload_len))) {
00477 if (ws_safe_read(session, &buf[frame_size], (*payload_len), opcode)) {
00478 return 0;
00479 }
00480 session->payload = new_payload;
00481 memcpy(session->payload, &buf[frame_size], *payload_len);
00482 *payload = session->payload;
00483 frame_size += (*payload_len);
00484 }
00485
00486 if (!session->closing) {
00487 ast_websocket_close(session, 0);
00488 }
00489
00490 fclose(session->f);
00491 session->f = NULL;
00492 ast_verb(2, "WebSocket connection from '%s' closed\n", ast_sockaddr_stringify(&session->address));
00493 } else {
00494 ast_log(LOG_WARNING, "WebSocket unknown opcode %d\n", *opcode);
00495
00496
00497 ast_websocket_close(session, 1003);
00498 }
00499
00500 return 0;
00501 }
00502
00503
00504 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)
00505 {
00506 struct ast_variable *v;
00507 char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
00508 int version = 0, flags = 1;
00509 struct websocket_protocol *protocol_handler = NULL;
00510 struct ast_websocket *session;
00511
00512
00513 if (method != AST_HTTP_GET) {
00514 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
00515 return -1;
00516 }
00517
00518
00519 for (v = headers; v; v = v->next) {
00520 if (!strcasecmp(v->name, "Upgrade")) {
00521 upgrade = ast_strip(ast_strdupa(v->value));
00522 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key")) {
00523 key = ast_strip(ast_strdupa(v->value));
00524 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key1")) {
00525 key1 = ast_strip(ast_strdupa(v->value));
00526 } else if (!strcasecmp(v->name, "Sec-WebSocket-Key2")) {
00527 key2 = ast_strip(ast_strdupa(v->value));
00528 } else if (!strcasecmp(v->name, "Sec-WebSocket-Protocol")) {
00529 requested_protocols = ast_strip(ast_strdupa(v->value));
00530 protos = ast_strdupa(requested_protocols);
00531 } else if (!strcasecmp(v->name, "Sec-WebSocket-Version")) {
00532 if (sscanf(v->value, "%30d", &version) != 1) {
00533 version = 0;
00534 }
00535 }
00536 }
00537
00538
00539 if (!upgrade || strcasecmp(upgrade, "websocket")) {
00540 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket\n",
00541 ast_sockaddr_stringify(&ser->remote_address));
00542 ast_http_error(ser, 426, "Upgrade Required", NULL);
00543 return -1;
00544 } else if (ast_strlen_zero(requested_protocols)) {
00545 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n",
00546 ast_sockaddr_stringify(&ser->remote_address));
00547 fputs("HTTP/1.1 400 Bad Request\r\n"
00548 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00549 return -1;
00550 } else if (key1 && key2) {
00551
00552
00553 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen\n",
00554 ast_sockaddr_stringify(&ser->remote_address));
00555 fputs("HTTP/1.1 400 Bad Request\r\n"
00556 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00557 return 0;
00558 }
00559
00560
00561 while ((protocol = strsep(&requested_protocols, ","))) {
00562 if ((protocol_handler = ao2_find(protocols, ast_strip(protocol), OBJ_KEY))) {
00563 break;
00564 }
00565 }
00566
00567
00568 if (!protocol_handler) {
00569 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported\n",
00570 ast_sockaddr_stringify(&ser->remote_address), protos);
00571 fputs("HTTP/1.1 400 Bad Request\r\n"
00572 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00573 return 0;
00574 }
00575
00576
00577 if (version == 7 || version == 8 || version == 13) {
00578
00579
00580
00581 char *combined, base64[64];
00582 unsigned combined_length;
00583 uint8_t sha[20];
00584
00585 combined_length = (key ? strlen(key) : 0) + strlen(WEBSOCKET_GUID) + 1;
00586 if (!key || combined_length > 8192) {
00587 fputs("HTTP/1.1 400 Bad Request\r\n"
00588 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00589 ao2_ref(protocol_handler, -1);
00590 return 0;
00591 }
00592
00593 if (!(session = ao2_alloc(sizeof(*session), session_destroy_fn))) {
00594 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted\n",
00595 ast_sockaddr_stringify(&ser->remote_address));
00596 fputs("HTTP/1.1 400 Bad Request\r\n"
00597 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00598 ao2_ref(protocol_handler, -1);
00599 return 0;
00600 }
00601
00602 combined = ast_alloca(combined_length);
00603 snprintf(combined, combined_length, "%s%s", key, WEBSOCKET_GUID);
00604 ast_sha1_hash_uint(sha, combined);
00605 ast_base64encode(base64, (const unsigned char*)sha, 20, sizeof(base64));
00606
00607 fprintf(ser->f, "HTTP/1.1 101 Switching Protocols\r\n"
00608 "Upgrade: %s\r\n"
00609 "Connection: Upgrade\r\n"
00610 "Sec-WebSocket-Accept: %s\r\n"
00611 "Sec-WebSocket-Protocol: %s\r\n\r\n",
00612 upgrade,
00613 base64,
00614 protocol);
00615 fflush(ser->f);
00616 } else {
00617
00618
00619 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen\n",
00620 ast_sockaddr_stringify(&ser->remote_address), version ? version : 75);
00621 fputs("HTTP/1.1 400 Bad Request\r\n"
00622 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00623 ao2_ref(protocol_handler, -1);
00624 return 0;
00625 }
00626
00627
00628 if (setsockopt(ser->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
00629 ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive\n",
00630 ast_sockaddr_stringify(&ser->remote_address));
00631 fputs("HTTP/1.1 400 Bad Request\r\n"
00632 "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f);
00633 ao2_ref(session, -1);
00634 ao2_ref(protocol_handler, -1);
00635 return 0;
00636 }
00637
00638 ast_verb(2, "WebSocket connection from '%s' for protocol '%s' accepted using version '%d'\n", ast_sockaddr_stringify(&ser->remote_address), protocol, version);
00639
00640
00641 session->f = ser->f;
00642 session->fd = ser->fd;
00643 ast_sockaddr_copy(&session->address, &ser->remote_address);
00644 session->opcode = -1;
00645 session->reconstruct = DEFAULT_RECONSTRUCTION_CEILING;
00646 session->secure = ser->ssl ? 1 : 0;
00647
00648
00649 protocol_handler->callback(session, get_vars, headers);
00650 ao2_ref(protocol_handler, -1);
00651
00652
00653 ser->f = NULL;
00654
00655 return 0;
00656 }
00657
00658 static struct ast_http_uri websocketuri = {
00659 .callback = websocket_callback,
00660 .description = "Asterisk HTTP WebSocket",
00661 .uri = "ws",
00662 .has_subtree = 0,
00663 .data = NULL,
00664 .key = __FILE__,
00665 };
00666
00667
00668 static void websocket_echo_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
00669 {
00670 int flags, res;
00671
00672 ast_debug(1, "Entering WebSocket echo loop\n");
00673
00674 if ((flags = fcntl(ast_websocket_fd(session), F_GETFL)) == -1) {
00675 goto end;
00676 }
00677
00678 flags |= O_NONBLOCK;
00679
00680 if (fcntl(ast_websocket_fd(session), F_SETFL, flags) == -1) {
00681 goto end;
00682 }
00683
00684 while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {
00685 char *payload;
00686 uint64_t payload_len;
00687 enum ast_websocket_opcode opcode;
00688 int fragmented;
00689
00690 if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
00691
00692 ast_log(LOG_WARNING, "Read failure during WebSocket echo loop\n");
00693 break;
00694 }
00695
00696 if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
00697 ast_websocket_write(session, opcode, payload, payload_len);
00698 } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
00699 break;
00700 } else {
00701 ast_debug(1, "Ignored WebSocket opcode %d\n", opcode);
00702 }
00703 }
00704
00705 end:
00706 ast_debug(1, "Exitting WebSocket echo loop\n");
00707 ast_websocket_unref(session);
00708 }
00709
00710 static int load_module(void)
00711 {
00712 protocols = ao2_container_alloc(MAX_PROTOCOL_BUCKETS, protocol_hash_fn, protocol_cmp_fn);
00713 ast_http_uri_link(&websocketuri);
00714 ast_websocket_add_protocol("echo", websocket_echo_callback);
00715
00716 return 0;
00717 }
00718
00719 static int unload_module(void)
00720 {
00721 ast_websocket_remove_protocol("echo", websocket_echo_callback);
00722 ast_http_uri_unlink(&websocketuri);
00723 ao2_ref(protocols, -1);
00724
00725 return 0;
00726 }
00727
00728 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "HTTP WebSocket Support",
00729 .load = load_module,
00730 .unload = unload_module,
00731 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
00732 );