Asterisk - The Open Source Telephony Project GIT-master-9647a4f
Loading...
Searching...
No Matches
chan_websocket.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2025, Sangoma Technologies Corporation
5 *
6 * George Joseph <gjoseph@sangoma.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 * \author George Joseph <gjoseph@sangoma.com>
22 *
23 * \brief Websocket Media Channel
24 *
25 * \ingroup channel_drivers
26 */
27
28/*** MODULEINFO
29 <depend>res_http_websocket</depend>
30 <depend>res_websocket_client</depend>
31 <support_level>core</support_level>
32 ***/
33
34#include "asterisk.h"
35
36#include "asterisk/app.h"
37#include "asterisk/causes.h"
38#include "asterisk/channel.h"
39#include "asterisk/codec.h"
42#include "asterisk/frame.h"
43#include "asterisk/json.h"
44#include "asterisk/lock.h"
45#include "asterisk/mod_format.h"
46#include "asterisk/module.h"
47#include "asterisk/pbx.h"
48#include "asterisk/uuid.h"
49#include "asterisk/timing.h"
50#include "asterisk/translate.h"
52#include "asterisk/sorcery.h"
53
54static struct ast_sorcery *sorcery = NULL;
55
61
62static const char *msg_format_map[] = {
63 [WEBCHAN_CONTROL_MSG_FORMAT_PLAIN] = "plain-text",
66};
67
72
74
75static struct ao2_container *instances = NULL;
76
106
107#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"
108#define MEDIA_WEBSOCKET_CONNECTION_ID "MEDIA_WEBSOCKET_CONNECTION_ID"
109#define INCOMING_CONNECTION_ID "INCOMING"
110
111#define ANSWER_CHANNEL "ANSWER"
112#define HANGUP_CHANNEL "HANGUP"
113#define START_MEDIA_BUFFERING "START_MEDIA_BUFFERING"
114#define STOP_MEDIA_BUFFERING "STOP_MEDIA_BUFFERING"
115#define MARK_MEDIA "MARK_MEDIA"
116#define FLUSH_MEDIA "FLUSH_MEDIA"
117#define GET_DRIVER_STATUS "GET_STATUS"
118#define REPORT_QUEUE_DRAINED "REPORT_QUEUE_DRAINED"
119#define PAUSE_MEDIA "PAUSE_MEDIA"
120#define CONTINUE_MEDIA "CONTINUE_MEDIA"
121
122#define QUEUE_LENGTH_MAX 1000
123#define QUEUE_LENGTH_XOFF_LEVEL 900
124#define QUEUE_LENGTH_XON_LEVEL 800
125#define MAX_TEXT_MESSAGE_LEN MIN(128, (AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE - 1))
126
127/* Forward declarations */
128static struct ast_channel *webchan_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
129static int webchan_call(struct ast_channel *ast, const char *dest, int timeout);
130static struct ast_frame *webchan_read(struct ast_channel *ast);
131static int webchan_write(struct ast_channel *ast, struct ast_frame *f);
132static int webchan_hangup(struct ast_channel *ast);
133static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration);
134
136 .type = "WebSocket",
137 .description = "Media over WebSocket Channel Driver",
138 .requester = webchan_request,
139 .call = webchan_call,
140 .read = webchan_read,
141 .write = webchan_write,
142 .hangup = webchan_hangup,
143 .send_digit_end = webchan_send_dtmf_text,
144};
145
158
160{
162 return NULL;
163 }
164 return msg_format_map[value];
165}
166
167/*!
168 * \internal
169 * \brief Catch-all to print events that don't have any data.
170 * \warning Do not call directly.
171 */
172static char *_create_event_nodata(struct websocket_pvt *instance, char *event)
173{
174 char *payload = NULL;
176 struct ast_json * msg = ast_json_pack("{ s:s s:s }",
177 "event", event,
178 "channel_id", ast_channel_uniqueid(instance->channel));
179 if (!msg) {
180 return NULL;
181 }
183 ast_json_unref(msg);
184 } else {
185 payload = ast_strdup(event);
186 }
187
188 return payload;
189}
190
191#define _create_event_MEDIA_XON(_instance) _create_event_nodata(_instance, "MEDIA_XON");
192#define _create_event_MEDIA_XOFF(_instance) _create_event_nodata(_instance, "MEDIA_XOFF");
193#define _create_event_QUEUE_DRAINED(_instance) _create_event_nodata(_instance, "QUEUE_DRAINED");
194
195/*!
196 * \internal
197 * \brief Print the MEDIA_START event.
198 * \warning Do not call directly.
199 */
200static char *_create_event_MEDIA_START(struct websocket_pvt *instance)
201{
202 char *payload = NULL;
203
205 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:o }",
206 "event", "MEDIA_START",
207 "connection_id", instance->connection_id,
208 "channel", ast_channel_name(instance->channel),
209 "channel_id", ast_channel_uniqueid(instance->channel),
210 "format", ast_format_get_name(instance->native_format),
211 "optimal_frame_size", instance->optimal_frame_size,
212 "ptime", instance->native_codec->default_ms,
213 "channel_variables", ast_json_channel_vars(ast_channel_varshead(
214 instance->channel))
215 );
216 if (!msg) {
217 return NULL;
218 }
220 ast_json_unref(msg);
221 } else {
222 ast_asprintf(&payload, "%s %s:%s %s:%s %s:%s %s:%s %s:%d %s:%d",
223 "MEDIA_START",
224 "connection_id", instance->connection_id,
225 "channel", ast_channel_name(instance->channel),
226 "channel_id", ast_channel_uniqueid(instance->channel),
227 "format", ast_format_get_name(instance->native_format),
228 "optimal_frame_size", instance->optimal_frame_size,
229 "ptime", instance->native_codec->default_ms
230 );
231 }
232
233 return payload;
234}
235
236/*!
237 * \internal
238 * \brief Print the MEDIA_BUFFERING_COMPLETED event.
239 * \warning Do not call directly.
240 */
242 const char *id)
243{
244 char *payload = NULL;
246 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
247 "event", "MEDIA_BUFFERING_COMPLETED",
248 "channel_id", ast_channel_uniqueid(instance->channel),
249 "correlation_id", S_OR(id, "")
250 );
251 if (!msg) {
252 return NULL;
253 }
255 ast_json_unref(msg);
256 } else {
257 ast_asprintf(&payload, "%s%s%s",
258 "MEDIA_BUFFERING_COMPLETED",
259 S_COR(id, " ",""), S_OR(id, ""));
260
261 }
262
263 return payload;
264}
265
266/*!
267 * \internal
268 * \brief Print the MEDIA_MARK_PROCESSED event.
269 * \warning Do not call directly.
270 */
272 const char *id)
273{
274 char *payload = NULL;
276 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
277 "event", "MEDIA_MARK_PROCESSED",
278 "channel_id", ast_channel_uniqueid(instance->channel),
279 "correlation_id", S_OR(id, "")
280 );
281 if (!msg) {
282 return NULL;
283 }
285 ast_json_unref(msg);
286 } else {
287 ast_asprintf(&payload, "%s%s%s",
288 "MEDIA_MARK_PROCESSED",
289 S_COR(id, " ",""), S_OR(id, ""));
290
291 }
292
293 return payload;
294}
295
296/*!
297 * \internal
298 * \brief Print the DTMF_END event.
299 * \warning Do not call directly.
300 */
301static char *_create_event_DTMF_END(struct websocket_pvt *instance,
302 const char digit)
303{
304 char *payload = NULL;
306 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s#}",
307 "event", "DTMF_END",
308 "channel_id", ast_channel_uniqueid(instance->channel),
309 "digit", &digit, 1
310 );
311 if (!msg) {
312 return NULL;
313 }
315 ast_json_unref(msg);
316 } else {
317 ast_asprintf(&payload, "%s digit:%c channel_id:%s",
318 "DTMF_END", digit, ast_channel_uniqueid(instance->channel));
319 }
320
321 return payload;
322}
323
324/*!
325 * \internal
326 * \brief Print the STATUS event.
327 * \warning Do not call directly.
328 */
329static char *_create_event_STATUS(struct websocket_pvt *instance)
330{
331 char *payload = NULL;
332
334 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:i, s:i, s:i, s:b, s:b, s:b }",
335 "event", "STATUS",
336 "channel_id", ast_channel_uniqueid(instance->channel),
337 "queue_length", instance->frame_queue_length,
338 "xon_level", QUEUE_LENGTH_XON_LEVEL,
339 "xoff_level", QUEUE_LENGTH_XOFF_LEVEL,
340 "queue_full", instance->queue_full,
341 "bulk_media", instance->bulk_media_in_progress,
342 "media_paused", instance->queue_paused
343 );
344 if (!msg) {
345 return NULL;
346 }
348 ast_json_unref(msg);
349 } else {
350 ast_asprintf(&payload, "%s channel_id:%s queue_length:%d xon_level:%d xoff_level:%d queue_full:%s bulk_media:%s media_paused:%s",
351 "STATUS",
352 ast_channel_uniqueid(instance->channel),
355 S_COR(instance->queue_full, "true", "false"),
356 S_COR(instance->bulk_media_in_progress, "true", "false"),
357 S_COR(instance->queue_paused, "true", "false")
358 );
359 }
360
361 return payload;
362}
363
364/*!
365 * \internal
366 * \brief Print the ERROR event.
367 * \warning Do not call directly.
368 */
369static __attribute__ ((format (gnu_printf, 2, 3))) char *_create_event_ERROR(
370 struct websocket_pvt *instance, const char *format, ...)
371{
372 char *payload = NULL;
373 char *error_text = NULL;
374 va_list ap;
375 int res = 0;
376
377 va_start(ap, format);
378 res = ast_vasprintf(&error_text, format, ap);
379 va_end(ap);
380 if (res < 0 || !error_text) {
381 return NULL;
382 }
383
384 if (instance->control_msg_format == WEBCHAN_CONTROL_MSG_FORMAT_JSON) {
385 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
386 "event", "ERROR",
387 "channel_id", ast_channel_uniqueid(instance->channel),
388 "error_text", error_text);
389 ast_free(error_text);
390 if (!msg) {
391 return NULL;
392 }
394 ast_json_unref(msg);
395 } else {
396 ast_asprintf(&payload, "%s channel_id:%s error_text:%s",
397 "ERROR", ast_channel_uniqueid(instance->channel), error_text);
398 ast_free(error_text);
399 }
400
401 return payload;
402}
403
404/*!
405 * \def create_event
406 * \brief Use this macro to create events passing in any event-specific parameters.
407 */
408#define create_event(_instance, _event, ...) \
409 _create_event_ ## _event(_instance, ##__VA_ARGS__)
410
411/*!
412 * \def send_event
413 * \brief Use this macro to create and send events passing in any event-specific parameters.
414 */
415#define send_event(_instance, _event, ...) \
416({ \
417 int _res = -1; \
418 char *_payload = _create_event_ ## _event(_instance, ##__VA_ARGS__); \
419 if (_payload) { \
420 _res = ast_websocket_write_string(_instance->websocket, _payload); \
421 if (_res != 0) { \
422 ast_log(LOG_ERROR, "%s: Unable to send event %s\n", \
423 ast_channel_name(instance->channel), _payload); \
424 } else { \
425 ast_debug(4, "%s: Sent %s\n", \
426 ast_channel_name(instance->channel), _payload); \
427 }\
428 ast_free(_payload); \
429 } \
430 (_res); \
431})
432
433static void set_channel_format(struct websocket_pvt * instance,
434 struct ast_format *fmt)
435{
440 ast_debug(4, "Switching readformat to %s\n", ast_format_get_name(fmt));
441 }
442}
443
444/*
445 * Reminder... This function gets called by webchan_read which is
446 * triggered by the channel timer firing. It always gets called
447 * every 20ms (or whatever the timer is set to) even if there are
448 * no frames in the queue.
449 */
450static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
451{
452 struct ast_frame *queued_frame = NULL;
453 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
455
456 /*
457 * If the queue is paused, don't read a frame. Processing
458 * will continue down the function and a silence frame will
459 * be sent in its place.
460 */
461 if (instance->queue_paused) {
462 return NULL;
463 }
464
465 /*
466 * We need to check if we need to send an XON before anything
467 * else because there are multiple escape paths in this function
468 * and we don't want to accidentally keep the queue in a "full"
469 * state.
470 */
471 if (instance->queue_full && instance->frame_queue_length < QUEUE_LENGTH_XON_LEVEL) {
472 instance->queue_full = 0;
473 ast_debug(4, "%s: WebSocket sending MEDIA_XON\n",
474 ast_channel_name(instance->channel));
475 send_event(instance, MEDIA_XON);
476 }
477
478 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
479
480 /*
481 * If there are no frames in the queue, we need to
482 * return NULL so we can send a silence frame. We also need
483 * to send the QUEUE_DRAINED notification if we were requested
484 * to do so.
485 */
486 if (!queued_frame) {
487 if (instance->report_queue_drained) {
488 instance->report_queue_drained = 0;
489 ast_debug(4, "%s: WebSocket sending QUEUE_DRAINED\n",
490 ast_channel_name(instance->channel));
491 send_event(instance, QUEUE_DRAINED);
492 }
493 return NULL;
494 }
495
496 /*
497 * The only way a control frame could be present here is as
498 * a result of us calling queue_option_frame() in response
499 * to an incoming TEXT command from the websocket.
500 * We'll be safe and make sure it's a AST_CONTROL_OPTION
501 * frame anyway.
502 *
503 * It's quite possible that there are multiple control frames
504 * in a row in the queue so we need to process consecutive ones
505 * immediately.
506 *
507 * In any case, processing a control frame MUST not use up
508 * a media timeslot so after all control frames have been
509 * processed, we need to read an audio frame and process it.
510 */
511 while (queued_frame && queued_frame->frametype == AST_FRAME_CONTROL) {
512 if (queued_frame->subclass.integer == AST_CONTROL_OPTION) {
513 /*
514 * We just need to send the data to the websocket.
515 * The data should already be NULL terminated.
516 */
518 queued_frame->data.ptr);
519 ast_debug(4, "%s: Sent %s\n",
520 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
521 }
522 /*
523 * We do NOT send these to the core so we need to free
524 * the frame and grab the next one. If it's also a
525 * control frame, we need to process it otherwise
526 * continue down in the function.
527 */
528 ast_frame_free(queued_frame, 0);
529 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
530 /*
531 * Jut FYI... We didn't bump the queue length when we added the control
532 * frames so we don't need to decrement it here.
533 */
534 }
535
536 /*
537 * If, after reading all control frames, there are no frames
538 * left in the queue, we need to return NULL so we can send
539 * a silence frame.
540 */
541 if (!queued_frame) {
542 return NULL;
543 }
544
545 instance->frame_queue_length--;
546
547 return queued_frame;
548}
549/*!
550 * \internal
551 *
552 * Called by the core channel thread each time the instance timer fires.
553 *
554 */
555static struct ast_frame *webchan_read(struct ast_channel *ast)
556{
557 struct websocket_pvt *instance = NULL;
558 struct ast_frame *native_frame = NULL;
559 struct ast_frame *slin_frame = NULL;
560
561 instance = ast_channel_tech_pvt(ast);
562 if (!instance) {
563 return NULL;
564 }
565
567 ast_timer_ack(instance->timer, 1);
568 }
569
570 native_frame = dequeue_frame(instance);
571
572 /*
573 * No frame when the timer fires means we have to create and
574 * return a silence frame in its place.
575 */
576 if (!native_frame) {
577 ast_debug(5, "%s: WebSocket read timer fired with no frame available. Returning silence.\n", ast_channel_name(ast));
578 set_channel_format(instance, instance->slin_format);
579 slin_frame = ast_frdup(&instance->silence);
580 return slin_frame;
581 }
582
583 /*
584 * If we're in passthrough mode or the frame length is already optimal_frame_size,
585 * we can just return it.
586 */
587 if (instance->passthrough || native_frame->datalen == instance->optimal_frame_size) {
588 set_channel_format(instance, instance->native_format);
589 return native_frame;
590 }
591
592 /*
593 * If we're here, we have a short frame that we need to pad
594 * with silence.
595 */
596
597 if (instance->translator) {
598 slin_frame = ast_translate(instance->translator, native_frame, 0);
599 if (!slin_frame) {
600 ast_log(LOG_WARNING, "%s: Failed to translate %d byte frame\n",
601 ast_channel_name(ast), native_frame->datalen);
602 return NULL;
603 }
604 ast_frame_free(native_frame, 0);
605 } else {
606 /*
607 * If there was no translator then the native format
608 * was already slin.
609 */
610 slin_frame = native_frame;
611 }
612
613 set_channel_format(instance, instance->slin_format);
614
615 /*
616 * So now we have an slin frame but it's probably still short
617 * so we create a new data buffer with the correct length
618 * which is filled with zeros courtesy of ast_calloc.
619 * We then copy the short frame data into the new buffer
620 * and set the offset to AST_FRIENDLY_OFFSET so that
621 * the core can read the data without any issues.
622 * If the original frame data was mallocd, we need to free the old
623 * data buffer so we don't leak memory and we need to set
624 * mallocd to AST_MALLOCD_DATA so that the core knows
625 * it needs to free the new data buffer when it's done.
626 */
627
628 if (slin_frame->datalen != instance->silence.datalen) {
629 char *old_data = slin_frame->data.ptr;
630 int old_len = slin_frame->datalen;
631 int old_offset = slin_frame->offset;
632 ast_debug(4, "%s: WebSocket read short frame. Expected %d got %d. Filling with silence\n",
633 ast_channel_name(ast), instance->silence.datalen,
634 slin_frame->datalen);
635
636 slin_frame->data.ptr = ast_calloc(1, instance->silence.datalen + AST_FRIENDLY_OFFSET);
637 if (!slin_frame->data.ptr) {
638 ast_frame_free(slin_frame, 0);
639 return NULL;
640 }
641 slin_frame->data.ptr += AST_FRIENDLY_OFFSET;
642 slin_frame->offset = AST_FRIENDLY_OFFSET;
643 memcpy(slin_frame->data.ptr, old_data, old_len);
644 if (slin_frame->mallocd & AST_MALLOCD_DATA) {
645 ast_free(old_data - old_offset);
646 }
647 slin_frame->mallocd |= AST_MALLOCD_DATA;
648 slin_frame->datalen = instance->silence.datalen;
649 slin_frame->samples = instance->silence.samples;
650 }
651
652 return slin_frame;
653}
654
655static int queue_frame_from_buffer(struct websocket_pvt *instance,
656 char *buffer, size_t len)
657{
658 struct ast_frame fr = { 0, };
659 struct ast_frame *duped_frame = NULL;
660
661 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
663 fr.subclass.format = instance->native_format;
664 fr.samples = instance->native_codec->samples_count(&fr);
665
666 duped_frame = ast_frisolate(&fr);
667 if (!duped_frame) {
668 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
669 ast_channel_name(instance->channel));
670 return -1;
671 }
672
673 {
674 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
676 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
677 instance->frame_queue_length++;
678 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
679 instance->queue_full = 1;
680 send_event(instance, MEDIA_XOFF);
681 }
682 }
683
684 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
685 duped_frame->datalen);
686
687 return 0;
688}
689
690static int queue_option_frame(struct websocket_pvt *instance,
691 char *buffer)
692{
693 struct ast_frame fr = { 0, };
694 struct ast_frame *duped_frame = NULL;
695
696 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
699
700 duped_frame = ast_frisolate(&fr);
701 if (!duped_frame) {
702 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
703 ast_channel_name(instance->channel));
704 return -1;
705 }
706
707 AST_LIST_LOCK(&instance->frame_queue);
708 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
709 AST_LIST_UNLOCK(&instance->frame_queue);
710
711 ast_debug(4, "%s: Queued '%s' option frame\n",
712 ast_channel_name(instance->channel), buffer);
713
714 return 0;
715}
716
717/*!
718 * \internal
719 * \brief Handle commands from the websocket
720 *
721 * \param instance
722 * \param buffer Allocated by caller so don't free.
723 * \retval 0 Success
724 * \retval -1 Failure
725 */
726static int handle_command(struct websocket_pvt *instance, char *buffer)
727{
728 int res = 0;
729 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
730 const char *command = NULL;
731 char *data = NULL;
732
734 struct ast_json_error json_error;
735
736 json = ast_json_load_buf(buffer, strlen(buffer), &json_error);
737 if (!json) {
738 send_event(instance, ERROR, "Unable to parse JSON command");
739 return -1;
740 }
741 command = ast_json_object_string_get(json, "command");
742 } else {
743 command = buffer;
744 data = strchr(buffer, ' ');
745 if (data) {
746 *data = '\0';
747 data++;
748 }
749 }
750
751 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
753
754 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
756
757 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
758 if (instance->passthrough) {
759 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
760 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
761 ast_channel_name(instance->channel), command);
762 return 0;
763 }
764 AST_LIST_LOCK(&instance->frame_queue);
765 instance->bulk_media_in_progress = 1;
766 AST_LIST_UNLOCK(&instance->frame_queue);
767
768 } else if (ast_strings_equal(command, STOP_MEDIA_BUFFERING)) {
769 const char *id;
770 char *option;
771 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
773
775 id = ast_json_object_string_get(json, "correlation_id");
776 } else {
777 id = data;
778 }
779
780 if (instance->passthrough) {
781 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
782 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
783 ast_channel_name(instance->channel), command);
784 return 0;
785 }
786
787 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
789 (int)instance->leftover_len);
790
791 instance->bulk_media_in_progress = 0;
792 if (instance->leftover_len > 0) {
793 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
794 if (res != 0) {
795 return res;
796 }
797 }
798 instance->leftover_len = 0;
799 option = create_event(instance, MEDIA_BUFFERING_COMPLETED, id);
800 if (!option) {
801 return -1;
802 }
803 res = queue_option_frame(instance, option);
804 ast_free(option);
805
806 } else if (ast_strings_equal(command, MARK_MEDIA)) {
807 const char *id;
808 char *option;
809 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
811
812 if (instance->passthrough) {
813 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
814 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
815 ast_channel_name(instance->channel), command);
816 return 0;
817 }
818
820 id = ast_json_object_string_get(json, "correlation_id");
821 } else {
822 id = data;
823 }
824
825 ast_debug(4, "%s: %s %s\n",
826 ast_channel_name(instance->channel), MARK_MEDIA, id);
827
828 option = create_event(instance, MEDIA_MARK_PROCESSED, id);
829 if (!option) {
830 return -1;
831 }
832 res = queue_option_frame(instance, option);
833 ast_free(option);
834
835 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
836 struct ast_frame *frame = NULL;
837
838 if (instance->passthrough) {
839 send_event(instance, ERROR, "FLUSH_MEDIA not supported in passthrough mode");
840 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
841 ast_channel_name(instance->channel), command);
842 return 0;
843 }
844
845 AST_LIST_LOCK(&instance->frame_queue);
846 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
847 ast_frfree(frame);
848 }
849 instance->frame_queue_length = 0;
850 instance->bulk_media_in_progress = 0;
851 instance->leftover_len = 0;
852 AST_LIST_UNLOCK(&instance->frame_queue);
853
854 } else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
855 if (instance->passthrough) {
856 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
857 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
858 ast_channel_name(instance->channel), command);
859 return 0;
860 }
861
862 AST_LIST_LOCK(&instance->frame_queue);
863 instance->report_queue_drained = 1;
864 AST_LIST_UNLOCK(&instance->frame_queue);
865
866 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
867 return send_event(instance, STATUS);
868
869 } else if (ast_strings_equal(command, PAUSE_MEDIA)) {
870 if (instance->passthrough) {
871 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
872 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
873 ast_channel_name(instance->channel), command);
874 return 0;
875 }
876 AST_LIST_LOCK(&instance->frame_queue);
877 instance->queue_paused = 1;
878 AST_LIST_UNLOCK(&instance->frame_queue);
879
880 } else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
881 if (instance->passthrough) {
882 send_event(instance, ERROR, "%s not supported in passthrough mode", command);
883 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
884 ast_channel_name(instance->channel), command);
885 return 0;
886 }
887 AST_LIST_LOCK(&instance->frame_queue);
888 instance->queue_paused = 0;
889 AST_LIST_UNLOCK(&instance->frame_queue);
890
891 } else {
892 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
893 ast_channel_name(instance->channel), command);
894 }
895
896 return res;
897}
898
899static int process_text_message(struct websocket_pvt *instance,
900 char *payload, uint64_t payload_len)
901{
902 char *command;
903
904 if (payload_len == 0) {
905 ast_log(LOG_WARNING, "%s: WebSocket TEXT message has 0 length\n",
906 ast_channel_name(instance->channel));
907 return 0;
908 }
909
910 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
911 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
912 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
913 return 0;
914 }
915
916 /*
917 * Unfortunately, payload is not NULL terminated even when it's
918 * a TEXT frame so we need to allocate a new buffer, copy
919 * the data into it, and NULL terminate it.
920 */
921 command = ast_alloca(payload_len + 1);
922 memcpy(command, payload, payload_len); /* Safe */
923 command[payload_len] = '\0';
924 command = ast_strip(command);
925
926 ast_debug(4, "%s: Received: %s\n",
927 ast_channel_name(instance->channel), command);
928
929 return handle_command(instance, command);
930}
931
932static int process_binary_message(struct websocket_pvt *instance,
933 char *payload, uint64_t payload_len)
934{
935 char *next_frame_ptr = NULL;
936 size_t bytes_read = 0;
937 int res = 0;
938 size_t bytes_left = 0;
939
940 {
941 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
943 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
944 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
945 ast_channel_name(instance->channel));
946 return 0;
947 }
948 }
949
950 next_frame_ptr = payload;
951 instance->bytes_read += payload_len;
952
953 if (instance->passthrough) {
954 res = queue_frame_from_buffer(instance, payload, payload_len);
955 return res;
956 }
957
958 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
959 /*
960 * We have leftover data from a previous websocket message.
961 * Try to make a complete frame by appending data from
962 * the current message to the leftover data.
963 */
964 char *append_ptr = instance->leftover_data + instance->leftover_len;
965 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
966 /*
967 * It's possible that even the current message doesn't have enough
968 * data to make a complete frame.
969 */
970 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
971
972 /*
973 * Append whatever we can to the end of the leftover data
974 * even if it's not enough to make a complete frame.
975 */
976 memcpy(append_ptr, payload, bytes_avail_to_copy);
977
978 /*
979 * If leftover data is still short, just return and wait for the
980 * next websocket message.
981 */
982 if (bytes_avail_to_copy < bytes_needed_for_frame) {
983 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
984 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
985 instance->leftover_len += bytes_avail_to_copy;
986 return 0;
987 }
988
989 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
990 if (res < 0) {
991 return -1;
992 }
993
994 /*
995 * We stole data from the current payload so decrement payload_len
996 * and set the next frame pointer after the data in payload
997 * we just copied.
998 */
999 payload_len -= bytes_avail_to_copy;
1000 next_frame_ptr = payload + bytes_avail_to_copy;
1001
1002 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
1003 ast_channel_name(instance->channel),
1004 instance->frame_queue_length,
1005 (int)instance->bytes_read,
1006 (int)(payload_len + bytes_avail_to_copy),
1007 (int)instance->leftover_len,
1008 payload,
1009 next_frame_ptr,
1010 (int)(next_frame_ptr - payload),
1011 (int)payload_len,
1012 (int)bytes_avail_to_copy
1013 );
1014
1015
1016 instance->leftover_len = 0;
1017 }
1018
1019 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
1020 instance->leftover_len = 0;
1021 }
1022
1023 bytes_left = payload_len;
1024 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
1025 res = queue_frame_from_buffer(instance, next_frame_ptr,
1026 instance->optimal_frame_size);
1027 if (res < 0) {
1028 break;
1029 }
1030 bytes_read += instance->optimal_frame_size;
1031 next_frame_ptr += instance->optimal_frame_size;
1032 bytes_left -= instance->optimal_frame_size;
1033 }
1034
1035 if (instance->bulk_media_in_progress && bytes_left > 0) {
1036 /*
1037 * We have a partial frame. Save the leftover data.
1038 */
1039 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
1040 ast_channel_name(instance->channel),
1041 (int)instance->bytes_read,
1042 instance->frame_queue_length,
1043 (int)payload_len,
1044 (int)instance->leftover_len,
1045 payload,
1046 next_frame_ptr,
1047 (int)(next_frame_ptr - payload),
1048 (int)bytes_left
1049 );
1050 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
1051 instance->leftover_len = bytes_left;
1052 }
1053
1054 return 0;
1055}
1056
1057static int read_from_ws_and_queue(struct websocket_pvt *instance)
1058{
1059 uint64_t payload_len = 0;
1060 char *payload = NULL;
1061 enum ast_websocket_opcode opcode;
1062 int fragmented = 0;
1063 int res = 0;
1064
1065 if (!instance || !instance->websocket) {
1066 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
1067 ast_channel_name(instance->channel));
1068 return -1;
1069 }
1070
1071 ast_debug(9, "%s: Waiting for websocket to have data\n", ast_channel_name(instance->channel));
1072 res = ast_wait_for_input(
1073 ast_websocket_fd(instance->websocket), -1);
1074 if (res <= 0) {
1075 ast_log(LOG_WARNING, "%s: WebSocket read failed: %s\n",
1076 ast_channel_name(instance->channel), strerror(errno));
1077 return -1;
1078 }
1079
1080 /*
1081 * We need to lock here to prevent the websocket handle from
1082 * being pulled out from under us if the core sends us a
1083 * hangup request.
1084 */
1085 ao2_lock(instance);
1086 if (!instance->websocket) {
1087 ao2_unlock(instance);
1088 return -1;
1089 }
1090
1091 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
1092 &opcode, &fragmented);
1093 ao2_unlock(instance);
1094 if (res) {
1095 return -1;
1096 }
1097 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
1098 (int)payload_len);
1099
1100 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
1101 return process_text_message(instance, payload, payload_len);
1102 }
1103
1104 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
1105 ast_debug(5, "%s: WebSocket closed by remote\n",
1106 ast_channel_name(instance->channel));
1107 return -1;
1108 }
1109
1110 if (opcode != AST_WEBSOCKET_OPCODE_BINARY) {
1111 ast_debug(5, "%s: WebSocket frame type %d not supported. Ignoring.\n",
1112 ast_channel_name(instance->channel), (int)opcode);
1113 return 0;
1114 }
1115
1116 return process_binary_message(instance, payload, payload_len);
1117}
1118
1119/*!
1120 * \internal
1121 *
1122 * For incoming websocket connections, this function gets called by
1123 * incoming_ws_established_cb() and is run in the http server thread
1124 * handling the websocket connection.
1125 *
1126 * For outgoing websocket connections, this function gets started as
1127 * a background thread by webchan_call().
1128 */
1129static void *read_thread_handler(void *obj)
1130{
1131 RAII_VAR(struct websocket_pvt *, instance, obj, ao2_cleanup);
1132 int res = 0;
1133
1134 ast_debug(3, "%s: Read thread started\n", ast_channel_name(instance->channel));
1135
1136 res = send_event(instance, MEDIA_START);
1137 if (res != 0 ) {
1138 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
1139 return NULL;
1140 }
1141
1142 if (!instance->no_auto_answer) {
1143 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
1144 ast_queue_control(instance->channel, AST_CONTROL_ANSWER);
1145 }
1146
1147 while (read_from_ws_and_queue(instance) == 0)
1148 {
1149 }
1150
1151 /*
1152 * websocket_hangup will take care of closing the websocket if needed.
1153 */
1154 ast_debug(3, "%s: HANGUP by websocket close/error\n", ast_channel_name(instance->channel));
1155 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
1156
1157 return NULL;
1158}
1159
1160/*! \brief Function called when we should write a frame to the channel */
1161static int webchan_write(struct ast_channel *ast, struct ast_frame *f)
1162{
1163 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1164
1165 if (!instance || !instance->websocket) {
1166 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
1167 ast_channel_name(ast));
1168 return -1;
1169 }
1170
1171 if (f->frametype == AST_FRAME_CNG) {
1172 return 0;
1173 }
1174
1175 if (f->frametype != AST_FRAME_VOICE) {
1176 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
1177 ast_channel_name(ast));
1178 return 0;
1179 }
1180
1182 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format, not '%s'\n",
1185 return -1;
1186 }
1187
1189 (char *)f->data.ptr, (uint64_t)f->datalen);
1190}
1191
1192/*!
1193 * \internal
1194 *
1195 * Called by the core to actually call the remote.
1196 */
1197static int webchan_call(struct ast_channel *ast, const char *dest,
1198 int timeout)
1199{
1200 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1201 int nodelay = 1;
1203
1204 if (!instance) {
1205 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
1206 ast_channel_name(ast));
1207 return -1;
1208 }
1209
1210 if (instance->type == AST_WS_TYPE_SERVER) {
1211 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
1212 return 0;
1213 }
1214 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
1215
1216 if (!instance->client) {
1217 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
1218 ast_channel_name(ast));
1219 return -1;
1220 }
1221
1222 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
1223 ast_channel_name(ast), dest, instance->connection_id);
1224
1225 if (!ast_strlen_zero(instance->uri_params)) {
1227 }
1228
1229 instance->websocket = ast_websocket_client_connect(instance->client,
1230 instance, ast_channel_name(ast), &result);
1231 if (!instance->websocket || result != WS_OK) {
1232 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
1234 return -1;
1235 }
1236
1237 if (setsockopt(ast_websocket_fd(instance->websocket),
1238 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1239 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
1240 }
1241
1242 ast_debug(3, "%s: WebSocket connection to %s established\n",
1243 ast_channel_name(ast), dest);
1244
1245 /* read_thread_handler() will clean up the bump */
1247 read_thread_handler, ao2_bump(instance))) {
1248 ast_log(LOG_WARNING, "%s: Failed to create thread.\n", ast_channel_name(ast));
1249 ao2_cleanup(instance);
1250 return -1;
1251 }
1252
1253 return 0;
1254}
1255
1256static void websocket_destructor(void *data)
1257{
1258 struct websocket_pvt *instance = data;
1259 struct ast_frame *frame = NULL;
1260 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
1261
1262 AST_LIST_LOCK(&instance->frame_queue);
1263 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
1264 ast_frfree(frame);
1265 }
1266 AST_LIST_UNLOCK(&instance->frame_queue);
1267
1268 if (instance->timer) {
1269 ast_timer_close(instance->timer);
1270 instance->timer = NULL;
1271 }
1272
1273 if (instance->channel) {
1274 ast_channel_unref(instance->channel);
1275 instance->channel = NULL;
1276 }
1277 if (instance->websocket) {
1278 ast_websocket_unref(instance->websocket);
1279 instance->websocket = NULL;
1280 }
1281
1282 ao2_cleanup(instance->client);
1283 instance->client = NULL;
1284
1285 ao2_cleanup(instance->native_codec);
1286 instance->native_codec = NULL;
1287
1288 ao2_cleanup(instance->native_format);
1289 instance->native_format = NULL;
1290
1291 ao2_cleanup(instance->slin_codec);
1292 instance->slin_codec = NULL;
1293
1294 ao2_cleanup(instance->slin_format);
1295 instance->slin_format = NULL;
1296
1297 if (instance->silence.data.ptr) {
1298 ast_free(instance->silence.data.ptr);
1299 instance->silence.data.ptr = NULL;
1300 }
1301
1302 if (instance->translator) {
1304 instance->translator = NULL;
1305 }
1306
1307 if (instance->leftover_data) {
1308 ast_free(instance->leftover_data);
1309 instance->leftover_data = NULL;
1310 }
1311
1312 ast_free(instance->uri_params);
1313}
1314
1317 /*! \brief The name of the module owning this sorcery instance */
1319};
1320
1321static void instance_proxy_cb(void *weakproxy, void *data)
1322{
1323 struct instance_proxy *proxy = weakproxy;
1324 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
1325 ao2_unlink(instances, weakproxy);
1326}
1327
1328static struct websocket_pvt* websocket_new(const char *chan_name,
1329 const char *connection_id, struct ast_format *fmt)
1330{
1331 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
1332 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1333 char uuid[AST_UUID_STR_LEN];
1334 enum ast_websocket_type ws_type;
1335
1336 SCOPED_AO2WRLOCK(locker, instances);
1337
1340 ws_type = AST_WS_TYPE_SERVER;
1341 } else {
1342 ws_type = AST_WS_TYPE_CLIENT;
1343 }
1344
1345 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1346 if (!proxy) {
1347 return NULL;
1348 }
1349 strcpy(proxy->connection_id, connection_id); /* Safe */
1350
1351 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1353 if (!instance) {
1354 return NULL;
1355 }
1356 strcpy(instance->connection_id, connection_id); /* Safe */
1357
1358 instance->type = ws_type;
1359 if (ws_type == AST_WS_TYPE_CLIENT) {
1360 instance->client = ast_websocket_client_retrieve_by_id(instance->connection_id);
1361 if (!instance->client) {
1362 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1363 chan_name, instance->connection_id);
1364 return NULL;
1365 }
1366 }
1367
1368 AST_LIST_HEAD_INIT(&instance->frame_queue);
1369
1370 /*
1371 * We need the codec to calculate the number of samples in a frame
1372 * so we'll get it once and store it in the instance.
1373 *
1374 * References for native_format and native_codec are now held by the
1375 * instance and will be released when the instance is destroyed.
1376 */
1377 instance->native_format = fmt;
1378 instance->native_codec = ast_format_get_codec(instance->native_format);
1379 /*
1380 * References for native_format and native_codec are now held by the
1381 * instance and will be released when the instance is destroyed.
1382 */
1383
1384 /*
1385 * It's not possible for us to re-time or re-frame media if the data
1386 * stream can't be broken up on arbitrary byte boundaries. This is usually
1387 * indicated by the codec's minimum_bytes being small (10 bytes or less).
1388 * We need to force passthrough mode in this case.
1389 */
1390 if (instance->native_codec->minimum_bytes <= 10) {
1391 instance->passthrough = 1;
1392 instance->optimal_frame_size = 0;
1393 } else {
1394 instance->optimal_frame_size =
1395 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1396 / instance->native_codec->minimum_ms;
1397 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1398 if (!instance->leftover_data) {
1399 return NULL;
1400 }
1401 }
1402
1403 ast_debug(3,
1404 "%s: WebSocket channel native format '%s' Sample rate: %d ptime: %dms minms: %u minbytes: %u passthrough: %d optimal_frame_size: %d\n",
1405 chan_name, ast_format_get_name(instance->native_format),
1406 ast_format_get_sample_rate(instance->native_format),
1407 ast_format_get_default_ms(instance->native_format),
1408 ast_format_get_minimum_ms(instance->native_format),
1409 ast_format_get_minimum_bytes(instance->native_format),
1410 instance->passthrough,
1411 instance->optimal_frame_size);
1412
1413 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1414 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1415 return NULL;
1416 }
1417
1419 return NULL;
1420 }
1421
1422 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1423 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1424 proxy->connection_id);
1425 return NULL;
1426 }
1427 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1428
1429 return ao2_bump(instance);
1430}
1431
1432static int set_instance_translator(struct websocket_pvt *instance)
1433{
1435 instance->slin_format = ao2_bump(instance->native_format);
1436 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1437 return 0;
1438 }
1439
1441 if (!instance->slin_format) {
1442 ast_log(LOG_ERROR, "%s: Unable to get slin format for rate %d\n",
1443 ast_channel_name(instance->channel), instance->native_codec->sample_rate);
1444 return -1;
1445 }
1446 ast_debug(3, "%s: WebSocket channel slin format '%s' Sample rate: %d ptime: %dms\n",
1450
1451 instance->translator = ast_translator_build_path(instance->slin_format, instance->native_format);
1452 if (!instance->translator) {
1453 ast_log(LOG_ERROR, "%s: Unable to build translator path from '%s' to '%s'\n",
1455 ast_format_get_name(instance->slin_format));
1456 return -1;
1457 }
1458
1459 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1460 return 0;
1461}
1462
1463static int set_instance_silence_frame(struct websocket_pvt *instance)
1464{
1465 instance->silence.frametype = AST_FRAME_VOICE;
1466 instance->silence.datalen =
1467 (instance->slin_codec->default_ms * instance->slin_codec->minimum_bytes) / instance->slin_codec->minimum_ms;
1468 instance->silence.samples = instance->silence.datalen / sizeof(uint16_t);
1469 /*
1470 * Even though we'll calloc the data pointer, we don't mark it as
1471 * mallocd because this frame will be around for a while and we don't
1472 * want it accidentally freed before we're done with it.
1473 */
1474 instance->silence.mallocd = 0;
1475 instance->silence.offset = 0;
1476 instance->silence.src = __PRETTY_FUNCTION__;
1477 instance->silence.subclass.format = instance->slin_format;
1478 instance->silence.data.ptr = ast_calloc(1, instance->silence.datalen);
1479 if (!instance->silence.data.ptr) {
1480 return -1;
1481 }
1482
1483 return 0;
1484}
1485
1486static int set_channel_timer(struct websocket_pvt *instance)
1487{
1488 int rate = 0;
1489 instance->timer = ast_timer_open();
1490 if (!instance->timer) {
1491 return -1;
1492 }
1493 /* Rate is the number of ticks per second, not the interval. */
1494 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1495 ast_debug(3, "%s: WebSocket timer rate %d\n",
1496 ast_channel_name(instance->channel), rate);
1497 ast_timer_set_rate(instance->timer, rate);
1498 /*
1499 * Calling ast_channel_set_fd will cause the channel thread to call
1500 * webchan_read at 'rate' times per second.
1501 */
1502 ast_channel_set_fd(instance->channel, 0, ast_timer_fd(instance->timer));
1503
1504 return 0;
1505}
1506
1507static int set_channel_variables(struct websocket_pvt *instance)
1508{
1509 char *pkt_size = NULL;
1510 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1511 if (res <= 0) {
1512 return -1;
1513 }
1514
1516 pkt_size);
1517 ast_free(pkt_size);
1519 instance->connection_id);
1520
1521 return 0;
1522}
1523
1525{
1526 char *params = ast_strdupa(uri_params);
1527 char *nvp = NULL;
1528 char *nv = NULL;
1529
1530 /*
1531 * uri_params should be a comma-separated list of key=value pairs.
1532 * For example:
1533 * name1=value1,name2=value2
1534 * We're verifying that each name and value either doesn't need
1535 * to be encoded or that it already is.
1536 */
1537
1538 while((nvp = ast_strsep(&params, ',', 0))) {
1539 /* nvp will be name1=value1 */
1540 while((nv = ast_strsep(&nvp, '=', 0))) {
1541 /* nv will be either name1 or value1 */
1542 if (!ast_uri_verify_encoded(nv)) {
1543 return 0;
1544 }
1545 }
1546 }
1547
1548 return 1;
1549}
1550
1551enum {
1552 OPT_WS_CODEC = (1 << 0),
1557};
1558
1559enum {
1567
1575
1576static struct ast_channel *webchan_request(const char *type,
1577 struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids,
1578 const struct ast_channel *requestor, const char *data, int *cause)
1579{
1580 char *parse;
1581 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1582 struct ast_channel *chan = NULL;
1583 struct ast_format *fmt = NULL;
1584 struct ast_format_cap *caps = NULL;
1586 AST_APP_ARG(connection_id);
1588 );
1589 struct ast_flags opts = { 0, };
1590 char *opt_args[OPT_ARG_ARRAY_SIZE];
1591 const char *requestor_name = requestor ? ast_channel_name(requestor) :
1592 (assignedids && !ast_strlen_zero(assignedids->uniqueid) ? assignedids->uniqueid : "<unknown>");
1593 RAII_VAR(struct webchan_conf_global *, global_cfg, NULL, ao2_cleanup);
1594
1595 global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1596
1597 ast_debug(3, "%s: WebSocket channel requested\n",
1598 requestor_name);
1599
1600 if (ast_strlen_zero(data)) {
1601 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1602 requestor_name);
1603 goto failure;
1604 }
1605 parse = ast_strdupa(data);
1606 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1607
1608 if (ast_strlen_zero(args.connection_id)) {
1609 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1610 requestor_name);
1611 goto failure;
1612 }
1613
1614 if (!ast_strlen_zero(args.options)
1615 && ast_app_parse_options(websocket_options, &opts, opt_args,
1616 ast_strdupa(args.options))) {
1617 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1618 requestor_name, args.options);
1619 goto failure;
1620 }
1621
1622 if (ast_test_flag(&opts, OPT_WS_CODEC)
1623 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1624 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1625 } else {
1626 /*
1627 * If codec wasn't specified in the dial string,
1628 * use the first format in the capabilities.
1629 */
1630 fmt = ast_format_cap_get_format(cap, 0);
1631 }
1632
1633 if (!fmt) {
1634 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1635 requestor_name, args.connection_id);
1636 goto failure;
1637 }
1638
1639 ast_debug(3, "%s: Using format %s from %s\n",
1640 requestor_name, ast_format_get_name(fmt),
1641 ast_test_flag(&opts, OPT_WS_CODEC) ? "dialstring" : "requester");
1642
1643 instance = websocket_new(requestor_name, args.connection_id, fmt);
1644 if (!instance) {
1645 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1646 requestor_name);
1647 goto failure;
1648 }
1649
1650 instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
1651 if (!instance->passthrough) {
1652 instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
1653 }
1654
1656 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1657 char *comma;
1658
1659 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1661 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1662 requestor_name);
1663 goto failure;
1664 }
1665
1666 ast_debug(3, "%s: Using URI parameters '%s'\n",
1667 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1668
1670 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1671 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1672 args.connection_id);
1673 goto failure;
1674 }
1675
1676 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1677 comma = instance->uri_params;
1678 /*
1679 * The normal separator for query string components is an
1680 * ampersand ('&') but the Dial app interprets them as additional
1681 * channels to dial in parallel so we instruct users to separate
1682 * the parameters with commas (',') instead. We now have to
1683 * convert those commas back to ampersands.
1684 */
1685 while ((comma = strchr(comma,','))) {
1686 *comma = '&';
1687 }
1688 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1689 }
1690
1691 if (ast_test_flag(&opts, OPT_WS_MSG_FORMAT)) {
1692 instance->control_msg_format = control_msg_format_from_str(opt_args[OPT_ARG_WS_MSG_FORMAT]);
1693
1694 if (instance->control_msg_format == WEBCHAN_CONTROL_MSG_FORMAT_INVALID) {
1695 ast_log(LOG_WARNING, "%s: 'f/control message format' dialstring parameter value missing or invalid. "
1696 "Defaulting to 'plain-text'\n",
1697 ast_channel_name(requestor));
1698 instance->control_msg_format = WEBCHAN_CONTROL_MSG_FORMAT_PLAIN;
1699 }
1700 } else if (global_cfg) {
1701 instance->control_msg_format = global_cfg->control_msg_format;
1702 }
1703
1704 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1705 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1706 if (!chan) {
1707 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", ast_channel_name(requestor));
1708 goto failure;
1709 }
1710
1711 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1712 ast_channel_name(chan), requestor_name,
1713 instance->connection_id);
1714
1715 instance->channel = ao2_bump(chan);
1716 ast_channel_tech_set(instance->channel, &websocket_tech);
1717
1718 if (set_instance_translator(instance) != 0) {
1719 goto failure;
1720 }
1721
1722 if (set_instance_silence_frame(instance) != 0) {
1723 goto failure;
1724 }
1725
1726 if (set_channel_timer(instance) != 0) {
1727 goto failure;
1728 }
1729
1730 if (set_channel_variables(instance) != 0) {
1731 goto failure;
1732 }
1733
1735 if (!caps) {
1736 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1737 goto failure;
1738 }
1739
1740 ast_format_cap_append(caps, instance->native_format, 0);
1741 ast_channel_nativeformats_set(instance->channel, caps);
1742 ast_channel_set_writeformat(instance->channel, instance->native_format);
1743 ast_channel_set_rawwriteformat(instance->channel, instance->native_format);
1744 ast_channel_set_readformat(instance->channel, instance->native_format);
1745 ast_channel_set_rawreadformat(instance->channel, instance->native_format);
1746 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1747 ast_channel_unlock(chan);
1748 ao2_cleanup(caps);
1749
1750 ast_debug(3, "%s: WebSocket channel created to %s\n",
1751 ast_channel_name(chan), args.connection_id);
1752
1753 return chan;
1754
1755failure:
1756 if (chan) {
1757 ast_channel_unlock(chan);
1758 }
1759 *cause = AST_CAUSE_FAILURE;
1760 return NULL;
1761}
1762
1763/*!
1764 * \internal
1765 *
1766 * Called by the core to hang up the channel.
1767 */
1768static int webchan_hangup(struct ast_channel *ast)
1769{
1770 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1771
1772 if (!instance) {
1773 return -1;
1774 }
1775 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1776 ast_channel_name(ast), instance->connection_id);
1777
1778 /*
1779 * We need to lock because read_from_ws_and_queue() is probably waiting
1780 * on the websocket file descriptor and will unblock and immediately try to
1781 * check the websocket and read from it. We don't want to pull the
1782 * websocket out from under it between the check and read.
1783 */
1784 ao2_lock(instance);
1785 if (instance->websocket) {
1786 ast_websocket_close(instance->websocket, 1000);
1787 ast_websocket_unref(instance->websocket);
1788 instance->websocket = NULL;
1789 }
1791 ao2_unlock(instance);
1792
1793 /* Clean up the reference from adding the instance to the channel */
1794 ao2_cleanup(instance);
1795
1796 return 0;
1797}
1798
1799static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration)
1800{
1801 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1802
1803 if (!instance) {
1804 return -1;
1805 }
1806
1807 return send_event(instance, DTMF_END, digit);
1808}
1809
1810/*!
1811 * \internal
1812 *
1813 * Called by res_http_websocket after a client has connected and
1814 * successfully upgraded from HTTP to WebSocket.
1815 *
1816 * Depends on incoming_ws_http_callback parsing the connection_id from
1817 * the HTTP request and storing it in get_params.
1818 */
1819static void incoming_ws_established_cb(struct ast_websocket *ast_ws_session,
1820 struct ast_variable *get_params, struct ast_variable *upgrade_headers)
1821{
1822 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1823 struct ast_variable *v;
1824 const char *connection_id = NULL;
1825 struct websocket_pvt *instance = NULL;
1826 int nodelay = 1;
1827
1828 ast_debug(3, "WebSocket established\n");
1829
1830 for (v = upgrade_headers; v; v = v->next) {
1831 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1832 }
1833 for (v = get_params; v; v = v->next) {
1834 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1835 }
1836
1837 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1838 if (!connection_id) {
1839 /*
1840 * This can't really happen because websocket_http_callback won't
1841 * let it get this far if it can't add the connection_id to the
1842 * get_params.
1843 * Just in case though...
1844 */
1845 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1847 ast_websocket_close(ast_ws_session, 1000);
1848 return;
1849 }
1850
1852 if (!instance) {
1853 /*
1854 * This also can't really happen because websocket_http_callback won't
1855 * let it get this far if it can't find the instance.
1856 * Just in case though...
1857 */
1858 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1860 ast_websocket_close(ast_ws_session, 1000);
1861 return;
1862 }
1863 instance->websocket = ao2_bump(ast_ws_session);
1864
1865 if (setsockopt(ast_websocket_fd(instance->websocket),
1866 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1867 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on manager connection: %s\n", strerror(errno));
1868 }
1869
1870 /* read_thread_handler cleans up the bump */
1871 read_thread_handler(ao2_bump(instance));
1872
1873 ao2_cleanup(instance);
1874 ast_debug(3, "WebSocket closed\n");
1875}
1876
1877/*!
1878 * \internal
1879 *
1880 * Called by the core http server after a client connects but before
1881 * the upgrade from HTTP to Websocket. We need to save the URI in
1882 * the CONNECTION_ID in a get_param because it contains the connection UUID
1883 * we gave to the client when they used externalMedia to create the channel.
1884 * incoming_ws_established_cb() will use this to retrieve the chan_websocket
1885 * instance.
1886 */
1888 const struct ast_http_uri *urih, const char *uri,
1889 enum ast_http_method method, struct ast_variable *get_params,
1890 struct ast_variable *headers)
1891{
1892 struct ast_http_uri fake_urih = {
1894 };
1895 int res = 0;
1896 /*
1897 * Normally the http server will destroy the get_params
1898 * when the session ends but if there weren't any initially
1899 * and we create some and add them to the list, the http server
1900 * won't know about it so we have to destroy it ourselves.
1901 */
1902 int destroy_get_params = (get_params == NULL);
1903 struct ast_variable *v = NULL;
1904 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1905
1906 ast_debug(2, "URI: %s Starting\n", uri);
1907
1908 /*
1909 * The client will have issued the GET request with a URI of
1910 * /media/<connection_id>
1911 *
1912 * Since this callback is registered for the /media URI prefix the
1913 * http server will strip that off the front of the URI passing in
1914 * only the path components after that in the 'uri' parameter.
1915 * This should leave only the connection id without a leading '/'.
1916 */
1917 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1918 if (!instance) {
1919 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
1920 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
1921 return -1;
1922 }
1923
1924 /*
1925 * We don't allow additional connections using the same connection id.
1926 */
1927 if (instance->websocket) {
1928 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
1929 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
1930 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
1931 return -1;
1932 }
1933
1934 v = ast_variable_new("CONNECTION_ID", uri, "");
1935 if (!v) {
1936 ast_http_error(ser, 500, "Server error", "");
1937 return -1;
1938 }
1939 ast_variable_list_append(&get_params, v);
1940
1941 for (v = get_params; v; v = v->next) {
1942 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1943 }
1944
1945 /*
1946 * This will ultimately call internal_ws_established_cb() so
1947 * this function will block until the websocket is closed and
1948 * internal_ws_established_cb() returns;
1949 */
1950 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
1951 get_params, headers);
1952 if (destroy_get_params) {
1953 ast_variables_destroy(get_params);
1954 }
1955
1956 ast_debug(2, "URI: %s DONE\n", uri);
1957
1958 return res;
1959}
1960
1961static struct ast_http_uri http_uri = {
1963 .description = "Media over Websocket",
1964 .uri = "media",
1965 .has_subtree = 1,
1966 .data = NULL,
1967 .key = __FILE__,
1968 .no_decode_uri = 1,
1969};
1970
1974
1976 struct ast_variable *var, void *obj)
1977{
1978 struct webchan_conf_global *cfg = obj;
1979
1981
1983 ast_log(LOG_ERROR, "chan_websocket.conf: Invalid value '%s' for "
1984 "control_mesage_format. Must be 'plain-text' or 'json'\n",
1985 var->value);
1986 return -1;
1987 }
1988
1989 return 0;
1990}
1991
1992static int global_control_message_format_to_str(const void *obj, const intptr_t *args, char **buf)
1993{
1994 const struct webchan_conf_global *cfg = obj;
1995
1997
1998 return 0;
1999}
2000
2001static void *global_alloc(const char *name)
2002{
2004 sizeof(*cfg), NULL);
2005
2006 if (!cfg) {
2007 return NULL;
2008 }
2009
2010 return cfg;
2011}
2012
2013static int global_apply(const struct ast_sorcery *sorcery, void *obj)
2014{
2015 struct webchan_conf_global *cfg = obj;
2016
2017 ast_debug(1, "control_msg_format: %s\n",
2019
2020 return 0;
2021}
2022
2023static int load_config(void)
2024{
2025 ast_debug(2, "Initializing Websocket Client Configuration\n");
2027 if (!sorcery) {
2028 ast_log(LOG_ERROR, "Failed to open sorcery\n");
2029 return -1;
2030 }
2031
2032 ast_sorcery_apply_default(sorcery, "global", "config",
2033 "chan_websocket.conf,criteria=type=global,single_object=yes,explicit_name=global");
2034
2036 ast_log(LOG_ERROR, "Failed to register chan_websocket global object with sorcery\n");
2038 sorcery = NULL;
2039 return -1;
2040 }
2041
2042 ast_sorcery_object_field_register_nodoc(sorcery, "global", "type", "", OPT_NOOP_T, 0, 0);
2043 ast_sorcery_register_cust(global, control_message_format, "plain-text");
2044
2046
2047 return 0;
2048}
2049
2050/*! \brief Function called when our module is unloaded */
2069
2070static int reload_module(void)
2071{
2072 ast_debug(2, "Reloading chan_websocket configuration\n");
2074
2075 return 0;
2076}
2077
2078/*! \brief Function called when our module is loaded */
2079static int load_module(void)
2080{
2081 int res = 0;
2082 struct ast_websocket_protocol *protocol;
2083
2084 res = load_config();
2085 if (res != 0) {
2087 }
2088
2091 }
2092
2095 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
2096 unload_module();
2098 }
2099
2101 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
2102 instance_proxy_sort_fn, instance_proxy_cmp_fn);
2103 if (!instances) {
2105 "Failed to allocate the chan_websocket instance registry\n");
2106 unload_module();
2108 }
2109
2111 if (!ast_ws_server) {
2112 unload_module();
2114 }
2115
2116 protocol = ast_websocket_sub_protocol_alloc("media");
2117 if (!protocol) {
2118 unload_module();
2120 }
2123
2125
2127}
2128
2129AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Websocket Media Channel",
2130 .support_level = AST_MODULE_SUPPORT_CORE,
2131 .load = load_module,
2132 .unload = unload_module,
2134 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
2135 .requires = "res_http_websocket,res_websocket_client",
char digit
enum queue_result id
Definition app_queue.c:1771
#define var
Definition ast_expr2f.c:605
Asterisk main include file. File version handling, generic pbx functions.
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition astmm.h:288
#define ast_free(a)
Definition astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition astmm.h:298
#define ast_vasprintf(ret, fmt, ap)
A wrapper for vasprintf()
Definition astmm.h:278
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition astmm.h:267
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_log
Definition astobj2.c:42
#define ao2_weakproxy_set_object(weakproxy, obj, flags)
Associate weakproxy with obj.
Definition astobj2.h:579
int ao2_weakproxy_subscribe(void *weakproxy, ao2_weakproxy_notification_cb cb, void *data, int flags)
Request notification when weakproxy points to NULL.
Definition astobj2.c:934
@ AO2_ALLOC_OPT_LOCK_RWLOCK
Definition astobj2.h:365
#define AO2_STRING_FIELD_CMP_FN(stype, field)
Creates a compare function for a structure string field.
Definition astobj2.h:2048
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition astobj2.h:1578
#define ao2_link_flags(container, obj, flags)
Add an object to a container.
Definition astobj2.h:1554
#define AO2_STRING_FIELD_SORT_FN(stype, field)
Creates a sort function for a structure string field.
Definition astobj2.h:2064
#define ao2_unlock(a)
Definition astobj2.h:729
#define ao2_weakproxy_find(c, arg, flags, tag)
Perform an ao2_find on a container with ao2_weakproxy objects, returning the real object.
Definition astobj2.h:1748
#define ao2_lock(a)
Definition astobj2.h:717
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
#define ao2_weakproxy_alloc(data_size, destructor_fn)
Allocate an ao2_weakproxy object.
Definition astobj2.h:550
#define AO2_STRING_FIELD_HASH_FN(stype, field)
Creates a hash function for a structure string field.
Definition astobj2.h:2032
@ OBJ_NOLOCK
Assume that the ao2_container is already locked.
Definition astobj2.h:1063
@ OBJ_SEARCH_KEY
The arg parameter is a search key, but is not an object.
Definition astobj2.h:1101
#define ao2_alloc(data_size, destructor_fn)
Definition astobj2.h:409
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition astobj2.h:1303
@ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE
Replace objects with duplicate keys in container.
Definition astobj2.h:1211
Internal Asterisk hangup causes.
#define AST_CAUSE_FAILURE
Definition causes.h:150
static PGresult * result
Definition cel_pgsql.c:84
static const char type[]
static int set_instance_translator(struct websocket_pvt *instance)
#define FLUSH_MEDIA
#define QUEUE_LENGTH_XON_LEVEL
#define MARK_MEDIA
#define send_event(_instance, _event,...)
Use this macro to create and send events passing in any event-specific parameters.
static int incoming_ws_http_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_params, struct ast_variable *headers)
#define ANSWER_CHANNEL
static void instance_proxy_cb(void *weakproxy, void *data)
static struct ast_frame * dequeue_frame(struct websocket_pvt *instance)
static void * global_alloc(const char *name)
static const char * control_msg_format_to_str(enum webchan_control_msg_format value)
static int validate_uri_parameters(const char *uri_params)
static int webchan_write(struct ast_channel *ast, struct ast_frame *f)
Function called when we should write a frame to the channel.
static char * _create_event_MEDIA_BUFFERING_COMPLETED(struct websocket_pvt *instance, const char *id)
static const char * msg_format_map[]
static int webchan_hangup(struct ast_channel *ast)
static struct ast_channel_tech websocket_tech
#define HANGUP_CHANNEL
static struct ast_frame * webchan_read(struct ast_channel *ast)
@ OPT_ARG_WS_PASSTHROUGH
@ OPT_ARG_WS_URI_PARAM
@ OPT_ARG_WS_MSG_FORMAT
@ OPT_ARG_WS_CODEC
@ OPT_ARG_WS_NO_AUTO_ANSWER
@ OPT_ARG_ARRAY_SIZE
#define MEDIA_WEBSOCKET_CONNECTION_ID
static char * _create_event_MEDIA_MARK_PROCESSED(struct websocket_pvt *instance, const char *id)
static int read_from_ws_and_queue(struct websocket_pvt *instance)
webchan_control_msg_format
@ WEBCHAN_CONTROL_MSG_FORMAT_JSON
@ WEBCHAN_CONTROL_MSG_FORMAT_PLAIN
@ WEBCHAN_CONTROL_MSG_FORMAT_INVALID
static int handle_command(struct websocket_pvt *instance, char *buffer)
static int global_control_message_format_from_str(const struct aco_option *opt, struct ast_variable *var, void *obj)
static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration)
static struct ast_http_uri http_uri
static int reload_module(void)
#define GET_DRIVER_STATUS
static int set_channel_variables(struct websocket_pvt *instance)
@ OPT_WS_CODEC
@ OPT_WS_PASSTHROUGH
@ OPT_WS_URI_PARAM
@ OPT_WS_MSG_FORMAT
@ OPT_WS_NO_AUTO_ANSWER
static void * read_thread_handler(void *obj)
static int process_text_message(struct websocket_pvt *instance, char *payload, uint64_t payload_len)
static void websocket_destructor(void *data)
static int queue_frame_from_buffer(struct websocket_pvt *instance, char *buffer, size_t len)
static struct ast_sorcery * sorcery
#define START_MEDIA_BUFFERING
static int set_channel_timer(struct websocket_pvt *instance)
static char * _create_event_STATUS(struct websocket_pvt *instance)
static char * _create_event_DTMF_END(struct websocket_pvt *instance, const char digit)
#define QUEUE_LENGTH_XOFF_LEVEL
static const struct ast_app_option websocket_options[128]
#define PAUSE_MEDIA
#define create_event(_instance, _event,...)
Use this macro to create events passing in any event-specific parameters.
static struct ao2_container * instances
static char * _create_event_ERROR(struct websocket_pvt *instance, const char *format,...)
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE
static void set_channel_format(struct websocket_pvt *instance, struct ast_format *fmt)
#define INCOMING_CONNECTION_ID
static struct websocket_pvt * websocket_new(const char *chan_name, const char *connection_id, struct ast_format *fmt)
#define REPORT_QUEUE_DRAINED
#define CONTINUE_MEDIA
#define STOP_MEDIA_BUFFERING
static int load_module(void)
Function called when our module is loaded.
static int webchan_call(struct ast_channel *ast, const char *dest, int timeout)
static enum webchan_control_msg_format control_msg_format_from_str(const char *value)
static int process_binary_message(struct websocket_pvt *instance, char *payload, uint64_t payload_len)
static int set_instance_silence_frame(struct websocket_pvt *instance)
static struct ast_websocket_server * ast_ws_server
static char * _create_event_MEDIA_START(struct websocket_pvt *instance)
static int unload_module(void)
Function called when our module is unloaded.
static int global_control_message_format_to_str(const void *obj, const intptr_t *args, char **buf)
#define MAX_TEXT_MESSAGE_LEN
static void incoming_ws_established_cb(struct ast_websocket *ast_ws_session, struct ast_variable *get_params, struct ast_variable *upgrade_headers)
#define QUEUE_LENGTH_MAX
static struct ast_channel * webchan_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
static char * _create_event_nodata(struct websocket_pvt *instance, char *event)
static int load_config(void)
static int global_apply(const struct ast_sorcery *sorcery, void *obj)
static int queue_option_frame(struct websocket_pvt *instance, char *buffer)
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
void * ast_channel_tech_pvt(const struct ast_channel *chan)
struct ast_format * ast_channel_rawreadformat(struct ast_channel *chan)
struct varshead * ast_channel_varshead(struct ast_channel *chan)
#define ast_channel_alloc(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag,...)
Create a channel structure.
Definition channel.h:1299
void ast_channel_nativeformats_set(struct ast_channel *chan, struct ast_format_cap *value)
void ast_channel_unregister(const struct ast_channel_tech *tech)
Unregister a channel technology.
Definition channel.c:570
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
Queue a control frame without payload.
Definition channel.c:1288
const char * ast_channel_uniqueid(const struct ast_channel *chan)
int ast_set_read_format(struct ast_channel *chan, struct ast_format *format)
Sets read format on channel chan.
Definition channel.c:5757
void ast_channel_set_rawreadformat(struct ast_channel *chan, struct ast_format *format)
void ast_channel_tech_pvt_set(struct ast_channel *chan, void *value)
void ast_channel_set_rawwriteformat(struct ast_channel *chan, struct ast_format *format)
void ast_channel_set_readformat(struct ast_channel *chan, struct ast_format *format)
int ast_channel_register(const struct ast_channel_tech *tech)
Register a channel technology (a new channel driver) Called by a channel module to register the kind ...
Definition channel.c:539
#define ast_channel_unref(c)
Decrease channel reference count.
Definition channel.h:3018
void ast_channel_set_fd(struct ast_channel *chan, int which, int fd)
Definition channel.c:2416
void ast_channel_tech_set(struct ast_channel *chan, const struct ast_channel_tech *value)
#define ast_channel_unlock(chan)
Definition channel.h:2983
void ast_channel_set_writeformat(struct ast_channel *chan, struct ast_format *format)
struct ast_format * ast_channel_readformat(struct ast_channel *chan)
@ AST_STATE_DOWN
Codec API.
@ AST_MEDIA_TYPE_UNKNOWN
Definition codec.h:31
@ OPT_NOOP_T
Type for a default handler that should do nothing.
char buf[BUFSIZE]
Definition eagi_proxy.c:66
struct ast_codec * ast_format_get_codec(const struct ast_format *format)
Get the codec associated with a format.
Definition format.c:324
unsigned int ast_format_get_minimum_bytes(const struct ast_format *format)
Get the minimum number of bytes expected in a frame for this format.
Definition format.c:374
unsigned int ast_format_get_sample_rate(const struct ast_format *format)
Get the sample rate of a media format.
Definition format.c:379
unsigned int ast_format_get_minimum_ms(const struct ast_format *format)
Get the minimum amount of media carried in this format.
Definition format.c:364
enum ast_format_cmp_res ast_format_cmp(const struct ast_format *format1, const struct ast_format *format2)
Compare two formats.
Definition format.c:201
@ AST_FORMAT_CMP_NOT_EQUAL
Definition format.h:38
const char * ast_format_get_name(const struct ast_format *format)
Get the name associated with a format.
Definition format.c:334
unsigned int ast_format_get_default_ms(const struct ast_format *format)
Get the default framing size (in milliseconds) for a format.
Definition format.c:359
Media Format Cache API.
int ast_format_cache_is_slinear(struct ast_format *format)
Determines if a format is one of the cached slin formats.
#define ast_format_cache_get(name)
Retrieve a named format from the cache.
struct ast_format * ast_format_cache_get_slin_by_rate(unsigned int rate)
Retrieve the best signed linear format given a sample rate.
int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_type type)
Add all codecs Asterisk knows about for a specific type to the capabilities structure.
Definition format_cap.c:216
struct ast_format * ast_format_cap_get_format(const struct ast_format_cap *cap, int position)
Get the format at a specific index.
Definition format_cap.c:400
@ AST_FORMAT_CAP_FLAG_DEFAULT
Definition format_cap.h:38
#define ast_format_cap_append(cap, format, framing)
Add format capability to capabilities structure.
Definition format_cap.h:99
#define ast_format_cap_alloc(flags)
Allocate a new ast_format_cap structure.
Definition format_cap.h:49
static const char name[]
Definition format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
static int uuid(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition func_uuid.c:52
ast_http_method
HTTP Request methods known by Asterisk.
Definition http.h:58
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition http.c:721
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition http.c:664
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition http.c:689
Support for WebSocket connections within the Asterisk HTTP server and client WebSocket connections to...
int AST_OPTIONAL_API_NAME() ast_websocket_write(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)
Construct and transmit a WebSocket frame.
int AST_OPTIONAL_API_NAME() ast_websocket_server_add_protocol2(struct ast_websocket_server *server, struct ast_websocket_protocol *protocol)
Add a sub-protocol handler to the given server.
int AST_OPTIONAL_API_NAME() ast_websocket_write_string(struct ast_websocket *ws, const char *buf)
Construct and transmit a WebSocket frame containing string data.
int AST_OPTIONAL_API_NAME() ast_websocket_uri_cb(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)
Callback suitable for use with a ast_http_uri.
ast_websocket_result
Result code for a websocket client.
@ WS_OK
int AST_OPTIONAL_API_NAME() ast_websocket_fd(struct ast_websocket *session)
Get the file descriptor for a WebSocket session.
ast_websocket_opcode
WebSocket operation codes.
@ AST_WEBSOCKET_OPCODE_BINARY
@ AST_WEBSOCKET_OPCODE_CLOSE
@ AST_WEBSOCKET_OPCODE_TEXT
ast_websocket_type
WebSocket connection/configuration types.
@ AST_WS_TYPE_CLIENT
@ AST_WS_TYPE_SERVER
struct ast_websocket_protocol *AST_OPTIONAL_API_NAME() ast_websocket_sub_protocol_alloc(const char *name)
Allocate a websocket sub-protocol instance.
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)
Read a WebSocket frame and handle it.
int AST_OPTIONAL_API_NAME() ast_websocket_close(struct ast_websocket *session, uint16_t reason)
Close a WebSocket session by sending a message with the CLOSE opcode and an optional code.
struct ast_websocket_server *AST_OPTIONAL_API_NAME() ast_websocket_server_create(void)
Creates a ast_websocket_server.
void AST_OPTIONAL_API_NAME() ast_websocket_unref(struct ast_websocket *session)
Decrease the reference count for a WebSocket session.
const char *AST_OPTIONAL_API_NAME() ast_websocket_result_to_str(enum ast_websocket_result result)
Convert a websocket result code to a string.
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define END_OPTIONS
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define AST_APP_OPTION_ARG(option, flagno, argno)
Declares an application option that accepts an argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define BEGIN_OPTIONS
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
#define AST_NONSTANDARD_APP_ARGS(args, parse, sep)
Performs the 'nonstandard' argument separation process for an application.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition main/app.c:3066
const char * ast_variable_find_in_list(const struct ast_variable *list, const char *variable)
Gets the value of a variable from a variable list by name.
#define ast_variable_new(name, value, filename)
#define ast_variable_list_append(head, new_var)
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition extconf.c:1260
Asterisk internal frame definitions.
#define ast_frisolate(fr)
Makes a frame independent of any static storage.
void ast_frame_free(struct ast_frame *frame, int cache)
Frees a frame or list of frames.
Definition main/frame.c:176
#define ast_frdup(fr)
Copies a frame.
#define ast_frfree(fr)
#define AST_MALLOCD_DATA
#define AST_FRAME_SET_BUFFER(fr, _base, _ofs, _datalen)
#define AST_FRIENDLY_OFFSET
Offset into a frame's data buffer.
@ AST_FRAME_CONTROL
@ AST_CONTROL_ANSWER
@ AST_CONTROL_HANGUP
@ AST_CONTROL_OPTION
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
Asterisk JSON abstraction layer.
#define ast_json_object_string_get(object, key)
Get a string field from a JSON object.
Definition json.h:600
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition json.c:73
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition json.c:612
@ AST_JSON_COMPACT
Definition json.h:793
struct ast_json * ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error)
Parse buffer with known length into a JSON object or array.
Definition json.c:585
struct ast_json * ast_json_channel_vars(struct varshead *channelvars)
Construct a JSON object from a ast_var_t list.
Definition json.c:941
char * ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format)
Encode a JSON value to a string.
Definition json.c:484
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
#define AST_LIST_LOCK(head)
Locks a list.
Definition linkedlists.h:40
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Asterisk locking-related definitions:
#define SCOPED_AO2WRLOCK(varname, obj)
scoped lock specialization for ao2 write locks.
Definition lock.h:621
#define SCOPED_LOCK(varname, lock, lockfunc, unlockfunc)
Scoped Locks.
Definition lock.h:590
int errno
Header for providers of file and format handling routines. Clients of these routines should include "...
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition module.h:331
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODPRI_CHANNEL_DRIVER
Definition module.h:341
@ AST_MODULE_SUPPORT_CORE
Definition module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition module.h:78
Core PBX routines and definitions.
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name.
static int reload(void)
const char * method
Definition res_pjsip.c:1279
static struct @519 args
#define NULL
Definition resample.c:96
Sorcery Data Access Layer API.
#define ast_sorcery_unref(sorcery)
Decrease the reference count of a sorcery structure.
Definition sorcery.h:1500
#define ast_sorcery_object_field_register_nodoc(sorcery, type, name, default_val, opt_type, flags,...)
Register a field within an object without documentation.
Definition sorcery.h:987
#define ast_sorcery_register_cust(object, option, def_value)
Register a custom field within an object.
Definition sorcery.h:1767
void ast_sorcery_load(const struct ast_sorcery *sorcery)
Inform any wizards to load persistent objects.
Definition sorcery.c:1441
void * ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id)
Retrieve an object using its unique identifier.
Definition sorcery.c:1917
#define ast_sorcery_object_register(sorcery, type, alloc, transform, apply)
Register an object type.
Definition sorcery.h:837
void ast_sorcery_reload(const struct ast_sorcery *sorcery)
Inform any wizards to reload persistent objects.
Definition sorcery.c:1472
void * ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
Allocate a generic sorcery capable object.
Definition sorcery.c:1792
#define ast_sorcery_apply_default(sorcery, type, name, data)
Definition sorcery.h:476
#define ast_sorcery_open()
Open a new sorcery structure.
Definition sorcery.h:406
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition strings.c:238
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition strings.h:80
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition strings.h:87
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition strings.h:65
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition strings.h:223
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition utils.c:1871
Generic container type.
Structure to pass both assignedid values to channel drivers.
Definition channel.h:606
const char * uniqueid
Definition channel.h:607
Structure to describe a channel "technology", ie a channel driver See for examples:
Definition channel.h:648
struct ast_format_cap * capabilities
Definition channel.h:652
const char *const type
Definition channel.h:649
Main Channel structure associated with a channel.
const char * data
Represents a media codec within Asterisk.
Definition codec.h:42
unsigned int sample_rate
Sample rate (number of samples carried in a second)
Definition codec.h:52
unsigned int minimum_bytes
Length in bytes of the data payload of a minimum_ms frame.
Definition codec.h:60
unsigned int default_ms
Default length of media carried (in milliseconds) in a frame.
Definition codec.h:58
int(* samples_count)(struct ast_frame *frame)
Retrieve the number of samples in a frame.
Definition codec.h:68
unsigned int minimum_ms
Minimum length of media that can be carried (in milliseconds) in a frame.
Definition codec.h:54
Structure used to handle boolean flags.
Definition utils.h:220
Format capabilities structure, holds formats + preference order + etc.
Definition format_cap.c:54
Definition of a media format.
Definition format.c:43
struct ast_format * format
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@239 data
Definition of a URI handler.
Definition http.h:102
ast_http_callback callback
Definition http.h:107
void * data
Definition http.h:116
JSON parsing error information.
Definition json.h:887
Abstract JSON element (object, array, string, int, ...).
Full structure for sorcery.
Definition sorcery.c:231
describes a server instance
Definition tcptls.h:151
Default structure for translators, with the basic fields and buffers, all allocated as part of the sa...
Definition translate.h:213
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
A websocket protocol implementation.
ast_websocket_callback session_established
Callback called when a new session is established. Mandatory.
Structure for a WebSocket server.
Structure definition for session.
char connection_id[0]
The name of the module owning this sorcery instance.
enum webchan_control_msg_format control_msg_format
char connection_id[0]
struct ast_format * native_format
struct websocket_pvt::@145 frame_queue
struct ast_channel * channel
struct ast_codec * slin_codec
enum webchan_control_msg_format control_msg_format
enum ast_websocket_type type
struct ast_timer * timer
struct ast_format * slin_format
pthread_t outbound_read_thread
struct ast_frame silence
struct ast_codec * native_codec
struct ast_websocket_client * client
struct ast_websocket * websocket
struct ast_trans_pvt * translator
int value
Definition syslog.c:37
static struct aco_type global
static struct test_options options
Timing source management.
void ast_timer_close(struct ast_timer *handle)
Close an opened timing handle.
Definition timing.c:154
int ast_timer_ack(const struct ast_timer *handle, unsigned int quantity)
Acknowledge a timer event.
Definition timing.c:171
int ast_timer_set_rate(const struct ast_timer *handle, unsigned int rate)
Set the timing tick rate.
Definition timing.c:166
enum ast_timer_event ast_timer_get_event(const struct ast_timer *handle)
Retrieve timing event.
Definition timing.c:186
struct ast_timer * ast_timer_open(void)
Open a timer.
Definition timing.c:122
@ AST_TIMING_EVENT_EXPIRED
Definition timing.h:58
int ast_timer_fd(const struct ast_timer *handle)
Get a poll()-able file descriptor for a timer.
Definition timing.c:161
Support for translation of data formats. translate.c.
struct ast_frame * ast_translate(struct ast_trans_pvt *tr, struct ast_frame *f, int consume)
translates one or more frames Apply an input frame into the translator and receive zero or one output...
Definition translate.c:566
void ast_translator_free_path(struct ast_trans_pvt *tr)
Frees a translator path Frees the given translator path structure.
Definition translate.c:476
struct ast_trans_pvt * ast_translator_build_path(struct ast_format *dest, struct ast_format *source)
Builds a translator path Build a path (possibly NULL) from source to dest.
Definition translate.c:486
#define ast_test_flag(p, flag)
Definition utils.h:64
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition utils.h:981
#define MIN(a, b)
Definition utils.h:252
int ast_wait_for_input(int fd, int ms)
Definition utils.c:1734
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition utils.h:727
#define ast_pthread_create_detached_background(a, b, c, d)
Definition utils.h:637
int ast_uri_verify_encoded(const char *string)
Verify if a string is valid as a URI component.
Definition utils.c:781
Universally unique identifier support.
#define AST_UUID_STR_LEN
Definition uuid.h:27
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition uuid.c:141
void ast_websocket_client_add_uri_params(struct ast_websocket_client *wc, const char *uri_params)
Add additional parameters to the URI.
struct ast_websocket_client * ast_websocket_client_retrieve_by_id(const char *id)
Retrieve a websocket client object by ID.
struct ast_websocket * ast_websocket_client_connect(struct ast_websocket_client *wc, void *lock_obj, const char *display_name, enum ast_websocket_result *result)
Connect to a websocket server using the configured authentication, retry and TLS options.