Asterisk - The Open Source Telephony Project GIT-master-545c459
Loading...
Searching...
No Matches
Data Structures | Macros | Enumerations | Functions | Variables
chan_websocket.c File Reference

Websocket Media Channel. More...

#include "asterisk.h"
#include "asterisk/app.h"
#include "asterisk/causes.h"
#include "asterisk/channel.h"
#include "asterisk/codec.h"
#include "asterisk/http_websocket.h"
#include "asterisk/format_cache.h"
#include "asterisk/frame.h"
#include "asterisk/json.h"
#include "asterisk/lock.h"
#include "asterisk/mod_format.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/uuid.h"
#include "asterisk/timing.h"
#include "asterisk/translate.h"
#include "asterisk/websocket_client.h"
#include "asterisk/sorcery.h"
Include dependency graph for chan_websocket.c:

Go to the source code of this file.

Data Structures

struct  instance_proxy
 
struct  webchan_conf_global
 
struct  websocket_pvt
 

Macros

#define _create_event_MEDIA_XOFF(_instance)   _create_event_nodata(_instance, "MEDIA_XOFF");
 
#define _create_event_MEDIA_XON(_instance)   _create_event_nodata(_instance, "MEDIA_XON");
 
#define _create_event_QUEUE_DRAINED(_instance)   _create_event_nodata(_instance, "QUEUE_DRAINED");
 
#define ANSWER_CHANNEL   "ANSWER"
 
#define CONTINUE_MEDIA   "CONTINUE_MEDIA"
 
#define create_event(_instance, _event, ...)    _create_event_ ## _event(_instance, ##__VA_ARGS__)
 Use this macro to create events passing in any event-specific parameters.
 
#define ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, direction)
 
#define ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command)
 
#define ERROR_ON_UNBUFFERED_MODE_RTN(instance, command)
 
#define FLUSH_MEDIA   "FLUSH_MEDIA"
 
#define GET_DRIVER_STATUS   "GET_STATUS"
 
#define HANGUP_CHANNEL   "HANGUP"
 
#define INCOMING_CONNECTION_ID   "INCOMING"
 
#define MARK_MEDIA   "MARK_MEDIA"
 
#define MAX_TEXT_MESSAGE_LEN   MIN(128, (AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE - 1))
 
#define MEDIA_WEBSOCKET_CONNECTION_ID   "MEDIA_WEBSOCKET_CONNECTION_ID"
 
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE   "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"
 
#define PAUSE_MEDIA   "PAUSE_MEDIA"
 
#define QUEUE_LENGTH_MAX   1000
 
#define QUEUE_LENGTH_XOFF_LEVEL   900
 
#define QUEUE_LENGTH_XON_LEVEL   800
 
#define REPORT_QUEUE_DRAINED   "REPORT_QUEUE_DRAINED"
 
#define send_event(_instance, _event, ...)
 Use this macro to create and send events passing in any event-specific parameters.
 
#define SET_MEDIA_DIRECTION   "SET_MEDIA_DIRECTION"
 
#define START_MEDIA_BUFFERING   "START_MEDIA_BUFFERING"
 
#define STOP_MEDIA_BUFFERING   "STOP_MEDIA_BUFFERING"
 
#define websocket_request_hangup(_instance, _cause, _tech)    _websocket_request_hangup(_instance, _cause, _tech, __LINE__, __FUNCTION__)
 
#define WS_TIMER_FDNO   (AST_EXTENDED_FDS + 1)
 
#define WS_WEBSOCKET_FDNO   (AST_EXTENDED_FDS + 2)
 

Enumerations

enum  {
  OPT_WS_CODEC = (1 << 0) , OPT_WS_NO_AUTO_ANSWER = (1 << 1) , OPT_WS_URI_PARAM = (1 << 2) , OPT_WS_PASSTHROUGH = (1 << 3) ,
  OPT_WS_MSG_FORMAT = (1 << 4) , OPT_WS_MEDIA_DIRECTION = (1 << 5) , OPT_WS_UNBUFFERED = (1 << 6)
}
 
enum  {
  OPT_ARG_WS_CODEC , OPT_ARG_WS_NO_AUTO_ANSWER , OPT_ARG_WS_URI_PARAM , OPT_ARG_WS_PASSTHROUGH ,
  OPT_ARG_WS_MSG_FORMAT , OPT_ARG_WS_MEDIA_DIRECTION , OPT_ARG_WS_UNBUFFERED , OPT_ARG_ARRAY_SIZE
}
 
enum  webchan_control_msg_format { WEBCHAN_CONTROL_MSG_FORMAT_PLAIN = 0 , WEBCHAN_CONTROL_MSG_FORMAT_JSON , WEBCHAN_CONTROL_MSG_FORMAT_INVALID }
 
enum  webchan_media_direction { WEBCHAN_MEDIA_DIRECTION_BOTH , WEBCHAN_MEDIA_DIRECTION_OUT , WEBCHAN_MEDIA_DIRECTION_IN }
 

Functions

static void __reg_module (void)
 
static void __unreg_module (void)
 
static char * _create_event_DTMF_END (struct websocket_pvt *instance, const char digit)
 
static char * _create_event_ERROR (struct websocket_pvt *instance, const char *format,...)
 
static char * _create_event_MEDIA_BUFFERING_COMPLETED (struct websocket_pvt *instance, const char *id)
 
static char * _create_event_MEDIA_MARK_PROCESSED (struct websocket_pvt *instance, const char *id)
 
static char * _create_event_MEDIA_START (struct websocket_pvt *instance)
 
static char * _create_event_nodata (struct websocket_pvt *instance, char *event)
 
static char * _create_event_STATUS (struct websocket_pvt *instance)
 
static void _websocket_request_hangup (struct websocket_pvt *instance, int ast_cause, enum ast_websocket_status_code tech_cause, int line, const char *function)
 
struct ast_moduleAST_MODULE_SELF_SYM (void)
 
static enum webchan_control_msg_format control_msg_format_from_str (const char *value)
 
static const char * control_msg_format_to_str (enum webchan_control_msg_format value)
 
static struct ast_framecreate_frame_from_buffer (struct websocket_pvt *instance, char *buffer, size_t len)
 
static struct ast_framedequeue_frame (struct websocket_pvt *instance)
 
static void * global_alloc (const char *name)
 
static int global_apply (const struct ast_sorcery *sorcery, void *obj)
 
static int global_control_message_format_from_str (const struct aco_option *opt, struct ast_variable *var, void *obj)
 
static int global_control_message_format_to_str (const void *obj, const intptr_t *args, char **buf)
 
static int handle_command (struct websocket_pvt *instance, char *buffer)
 
static void incoming_ws_established_cb (struct ast_websocket *ast_ws_session, struct ast_variable *get_params, struct ast_variable *upgrade_headers)
 
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)
 
static void instance_proxy_cb (void *weakproxy, void *data)
 
static int load_config (void)
 
static int load_module (void)
 Function called when our module is loaded.
 
static int process_binary_message (struct websocket_pvt *instance, char *payload, uint64_t payload_len)
 
static int process_text_message (struct websocket_pvt *instance, char *payload, uint64_t payload_len)
 
static int queue_frame_from_buffer (struct websocket_pvt *instance, char *buffer, size_t len)
 
static int queue_option_frame (struct websocket_pvt *instance, char *buffer)
 
static int read_from_ws_and_queue (struct websocket_pvt *instance)
 
static int reload_module (void)
 
static int set_channel_timer (struct websocket_pvt *instance)
 
static int set_channel_variables (struct websocket_pvt *instance)
 
static int unload_module (void)
 Function called when our module is unloaded.
 
static int validate_uri_parameters (const char *uri_params)
 
static int webchan_call (struct ast_channel *ast, const char *dest, int timeout)
 
static int webchan_hangup (struct ast_channel *ast)
 
static struct ast_framewebchan_read (struct ast_channel *ast)
 
static struct ast_channelwebchan_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 int webchan_send_dtmf_text (struct ast_channel *ast, char digit, unsigned int duration)
 
static int webchan_write (struct ast_channel *ast, struct ast_frame *f)
 Function called when we should write a frame to the channel.
 
static void websocket_destructor (void *data)
 
static int websocket_handoff_to_channel (struct websocket_pvt *instance)
 
static struct websocket_pvtwebsocket_new (const char *chan_name, const char *connection_id, struct ast_format *fmt)
 

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Websocket Media Channel" , .key = ASTERISK_GPL_KEY , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .requires = "res_http_websocket,res_websocket_client", }
 
static const struct ast_module_infoast_module_info = &__mod_info
 
static struct ast_websocket_serverast_ws_server
 
static struct ast_http_uri http_uri
 
static struct ao2_containerinstances = NULL
 
static const char * msg_format_map []
 
static struct ast_sorcerysorcery = NULL
 
static const char * websocket_media_direction_map []
 
static const struct ast_app_option websocket_options [128] = { [ 'c' ] = { .flag = OPT_WS_CODEC , .arg_index = OPT_ARG_WS_CODEC + 1 }, [ 'n' ] = { .flag = OPT_WS_NO_AUTO_ANSWER }, [ 'v' ] = { .flag = OPT_WS_URI_PARAM , .arg_index = OPT_ARG_WS_URI_PARAM + 1 }, [ 'p' ] = { .flag = OPT_WS_PASSTHROUGH }, [ 'f' ] = { .flag = OPT_WS_MSG_FORMAT , .arg_index = OPT_ARG_WS_MSG_FORMAT + 1 }, [ 'd' ] = { .flag = OPT_WS_MEDIA_DIRECTION , .arg_index = OPT_ARG_WS_MEDIA_DIRECTION + 1 }, [ 'u' ] = { .flag = OPT_WS_UNBUFFERED }, }
 
static struct ast_channel_tech websocket_tech
 

Detailed Description

Websocket Media Channel.

Author
George Joseph gjose.nosp@m.ph@s.nosp@m.angom.nosp@m.a.co.nosp@m.m

Definition in file chan_websocket.c.

Macro Definition Documentation

◆ _create_event_MEDIA_XOFF

#define _create_event_MEDIA_XOFF (   _instance)    _create_event_nodata(_instance, "MEDIA_XOFF");

Definition at line 221 of file chan_websocket.c.

◆ _create_event_MEDIA_XON

#define _create_event_MEDIA_XON (   _instance)    _create_event_nodata(_instance, "MEDIA_XON");

Definition at line 220 of file chan_websocket.c.

◆ _create_event_QUEUE_DRAINED

#define _create_event_QUEUE_DRAINED (   _instance)    _create_event_nodata(_instance, "QUEUE_DRAINED");

Definition at line 222 of file chan_websocket.c.

◆ ANSWER_CHANNEL

#define ANSWER_CHANNEL   "ANSWER"

Definition at line 132 of file chan_websocket.c.

◆ CONTINUE_MEDIA

#define CONTINUE_MEDIA   "CONTINUE_MEDIA"

Definition at line 141 of file chan_websocket.c.

◆ create_event

#define create_event (   _instance,
  _event,
  ... 
)     _create_event_ ## _event(_instance, ##__VA_ARGS__)

Use this macro to create events passing in any event-specific parameters.

Definition at line 441 of file chan_websocket.c.

448 { \
449 int _res = -1; \
450 char *_payload = _create_event_ ## _event(_instance, ##__VA_ARGS__); \
451 if (_payload && _instance->websocket) { \
452 _res = ast_websocket_write_string(_instance->websocket, _payload); \
453 if (_res != 0) { \
454 ast_log(LOG_ERROR, "%s: Unable to send event %s\n", \
455 ast_channel_name(instance->channel), _payload); \
456 } else { \
457 ast_debug(3, "%s: Sent %s\n", \
458 ast_channel_name(instance->channel), _payload); \
459 }\
460 ast_free(_payload); \
461 } \
462 (_res); \
463})
464
465/*!
466 * \internal
467 *
468 * This function gets called by webchan_read which is triggered by the channel
469 * timer firing or by a websocket frame being received while in unbuffered mode.
470 * It always gets called at least every 20ms (or whatever the timer is set to)
471 * even if there are no frames in the queue.
472 */
473static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
474{
475 struct ast_frame *queued_frame = NULL;
476 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
478
479 /*
480 * If the queue is paused, don't read a frame.
481 */
482 if (instance->queue_paused) {
483 return NULL;
484 }
485
486 /*
487 * We need to check if we need to send an XON before anything
488 * else because there are multiple escape paths in this function
489 * and we don't want to accidentally keep the queue in a "full"
490 * state.
491 */
492 if (instance->queue_full && instance->frame_queue_length < QUEUE_LENGTH_XON_LEVEL) {
493 instance->queue_full = 0;
494 ast_debug(4, "%s: WebSocket sending MEDIA_XON\n",
495 ast_channel_name(instance->channel));
496 send_event(instance, MEDIA_XON);
497 }
498
499 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
500
501 /*
502 * If there are no frames in the queue, we need to return NULL.
503 * We also need to send the QUEUE_DRAINED notification if we
504 * were requested to do so.
505 */
506 if (!queued_frame) {
507 if (instance->report_queue_drained) {
508 instance->report_queue_drained = 0;
509 ast_debug(4, "%s: WebSocket sending QUEUE_DRAINED\n",
510 ast_channel_name(instance->channel));
511 send_event(instance, QUEUE_DRAINED);
512 }
513 return NULL;
514 }
515
516 /*
517 * The only way a control frame could be present here is as
518 * a result of us calling queue_option_frame() in response
519 * to an incoming TEXT command from the websocket.
520 * We'll be safe and make sure it's a AST_CONTROL_OPTION
521 * frame anyway.
522 *
523 * It's quite possible that there are multiple control frames
524 * in a row in the queue so we need to process consecutive ones
525 * immediately.
526 *
527 * In any case, processing a control frame MUST not use up
528 * a media timeslot so after all control frames have been
529 * processed, we need to read an audio frame and process it.
530 */
531 while (queued_frame && queued_frame->frametype == AST_FRAME_CONTROL) {
532 if (queued_frame->subclass.integer == AST_CONTROL_OPTION) {
533 /*
534 * We just need to send the data to the websocket.
535 * The data should already be NULL terminated.
536 */
537 int res = ast_websocket_write_string(instance->websocket,
538 queued_frame->data.ptr);
539 if (res != 0) {
540 ast_log(LOG_ERROR, "%s: Unable to send event %s\n",
541 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
542 } else {
543 ast_debug(4, "%s: Sent %s\n",
544 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
545 }
546 }
547 /*
548 * We do NOT send these to the core so we need to free
549 * the frame and grab the next one. If it's also a
550 * control frame, we need to process it otherwise
551 * continue down in the function.
552 */
553 ast_frame_free(queued_frame, 0);
554 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
555 /*
556 * Jut FYI... We didn't bump the queue length when we added the control
557 * frames so we don't need to decrement it here.
558 */
559 }
560
561 /*
562 * If, after reading all control frames, there are no frames
563 * left in the queue, we need to return NULL.
564 */
565 if (!queued_frame) {
566 return NULL;
567 }
568
569 instance->frame_queue_length--;
570
571 return queued_frame;
572}
573
574static struct ast_frame *create_frame_from_buffer(struct websocket_pvt *instance,
575 char *buffer, size_t len)
576{
577 struct ast_frame fr = { 0, };
578 struct ast_frame *duped_frame = NULL;
579
580 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
582 fr.subclass.format = instance->native_format;
583 if (instance->native_codec->samples_count) {
584 fr.samples = instance->native_codec->samples_count(&fr);
585 }
586
587 duped_frame = ast_frisolate(&fr);
588 if (!duped_frame) {
589 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
590 ast_channel_name(instance->channel));
591 return NULL;
592 }
593
594 return duped_frame;
595}
596
597/*!
598 * \internal
599 *
600 * There are two file descriptors on this channel that can trigger
601 * this function...
602 *
603 * The timer fd (WS_TIMER_FDNO) which gets triggered at a constant
604 * rate determined by the format. In this case, we need to pull a
605 * frame OFF the queue and return it to the core.
606 *
607 * The websocket fd (WS_WEBSOCKET_FDNO) which gets triggered when
608 * there's incoming data to read from the websocket. In this case,
609 * we read the data and put it ON the queue. If in unbuffered mode,
610 * we'll read it off the queue immediately and return it. If not in
611 * unbuffered mode, we'll return a null frame now and the frame read
612 * will be processed by the timer tick when it comes to the head
613 * of the queue.
614 *
615 * The reason for queueing and dequeueing immediately in unbuffered
616 * mode is that read_from_ws_and_queue does a lot of work before adding
617 * a frame to the frame queue, if it even needs to. It's much simpler
618 * to just queue and dequeue, both from a code organization standpoint
619 * as well as an instruction-path-length standpoint, than it would be
620 * refactor that code so it can return a frame directly.
621 */
622static struct ast_frame *webchan_read(struct ast_channel *ast)
623{
624 struct websocket_pvt *instance = NULL;
625 struct ast_frame *native_frame = NULL;
626 int fdno = ast_channel_fdno(ast);
627
628 instance = ast_channel_tech_pvt(ast);
629 if (!instance) {
630 return NULL;
631 }
632
633 if (fdno != WS_TIMER_FDNO && fdno != WS_WEBSOCKET_FDNO) {
634 return &ast_null_frame;
635 }
636
637 if (fdno == WS_WEBSOCKET_FDNO) {
638 read_from_ws_and_queue(instance);
639 if (!instance->unbuffered) {
640 return &ast_null_frame;
641 }
642 } else {
644 ast_timer_ack(instance->timer, 1);
645 }
646 }
647
648 native_frame = dequeue_frame(instance);
649 if (!native_frame) {
650 if (instance->leftover_len > 0) {
651 native_frame = create_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
652 if (native_frame) {
653 ast_debug(4, "%s: Triggered with no frame available but with %d bytes in leftover_data. Returning partial frame.\n",
654 ast_channel_name(ast), (int)instance->leftover_len);
655 instance->leftover_len = 0;
656 return native_frame;
657 }
658 }
659 ast_debug(4, "%s: Triggered with no frame available and no data in leftover_data. Returning NULL frame.\n",
660 ast_channel_name(ast));
661 return &ast_null_frame;
662 }
663
664 ast_debug(5, "%s: Dequeued %d byte frame. Left in buffer: %d\n",
665 ast_channel_name(ast), native_frame->datalen, (int)instance->leftover_len);
666
667 return native_frame;
668}
669
670static int queue_frame_from_buffer(struct websocket_pvt *instance,
671 char *buffer, size_t len)
672{
673 struct ast_frame *duped_frame = NULL;
674
675 duped_frame = create_frame_from_buffer(instance, buffer, len);
676 if (!duped_frame) {
677 return -1;
678 }
679
680 {
681 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
683 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
684 instance->frame_queue_length++;
685 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
686 instance->queue_full = 1;
687 send_event(instance, MEDIA_XOFF);
688 }
689 }
690
691 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
692 duped_frame->datalen);
693
694 return 0;
695}
696
697static int queue_option_frame(struct websocket_pvt *instance,
698 char *buffer)
699{
700 struct ast_frame fr = { 0, };
701 struct ast_frame *duped_frame = NULL;
702
703 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
706
707 duped_frame = ast_frisolate(&fr);
708 if (!duped_frame) {
709 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
710 ast_channel_name(instance->channel));
711 return -1;
712 }
713
714 AST_LIST_LOCK(&instance->frame_queue);
715 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
716 AST_LIST_UNLOCK(&instance->frame_queue);
717
718 ast_debug(4, "%s: Queued '%s' option frame\n",
719 ast_channel_name(instance->channel), buffer);
720
721 return 0;
722}
723
724#define ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command) \
725({ \
726 if (instance->passthrough) { \
727 send_event(instance, ERROR, "%s not supported in passthrough mode", command); \
728 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n", \
729 ast_channel_name(instance->channel), command); \
730 return 0; \
731 } \
732})
733
734#define ERROR_ON_UNBUFFERED_MODE_RTN(instance, command) \
735({ \
736 if (instance->unbuffered) { \
737 send_event(instance, ERROR, "%s not supported in unbuffered mode", command); \
738 ast_debug(4, "%s: WebSocket in unbuffered mode. Ignoring %s command.\n", \
739 ast_channel_name(instance->channel), command); \
740 return 0; \
741 } \
742})
743
744#define ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, direction) \
745({ \
746 if (instance->media_direction == direction) { \
747 send_event(instance, ERROR, "%s not supported while media direction " \
748 "is '%s'", command, websocket_media_direction_map[direction]); \
749 ast_debug(4, "%s: WebSocket media direction is '%s'. Ignoring %s command.\n", \
750 ast_channel_name(instance->channel), websocket_media_direction_map[direction], command); \
751 return 0; \
752 } \
753})
754
755/*!
756 * \internal
757 * \brief Handle commands from the websocket
758 *
759 * \param instance
760 * \param buffer Allocated by caller so don't free.
761 * \retval 0 Success
762 * \retval -1 Failure
763 */
764static int handle_command(struct websocket_pvt *instance, char *buffer)
765{
766 int res = 0;
767 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
768 const char *command = NULL;
769 char *data = NULL;
770
772 struct ast_json_error json_error;
773
774 json = ast_json_load_buf(buffer, strlen(buffer), &json_error);
775 if (!json) {
776 send_event(instance, ERROR, "Unable to parse JSON command");
777 return -1;
778 }
779 command = ast_json_object_string_get(json, "command");
780 } else {
781 command = buffer;
782 data = strchr(buffer, ' ');
783 if (data) {
784 *data = '\0';
785 data++;
786 }
787 }
788
789 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
791
792 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
794
795 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
796 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
798 if (instance->bulk_media_in_progress) {
799 send_event(instance, ERROR, "START_MEDIA_BUFFERING can't be called when media buffering is already active.\n");
800 return 0;
801 }
802
803 AST_LIST_LOCK(&instance->frame_queue);
804 instance->bulk_media_in_progress = 1;
805 instance->last_unbuffered = instance->unbuffered;
806 instance->unbuffered = 0;
807 AST_LIST_UNLOCK(&instance->frame_queue);
808
809 } else if (ast_strings_equal(command, STOP_MEDIA_BUFFERING)) {
810 const char *id;
811 char *option;
812 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
814
816 id = ast_json_object_string_get(json, "correlation_id");
817 } else {
818 id = data;
819 }
820
821 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
823
824 if (!instance->bulk_media_in_progress) {
825 send_event(instance, ERROR, "STOP_MEDIA_BUFFERING can't be called when media buffering isn't active.\n");
826 return 0;
827 }
828
829 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
831 (int)instance->leftover_len);
832
833 instance->bulk_media_in_progress = 0;
834 instance->unbuffered = instance->last_unbuffered;
835 if (instance->leftover_len > 0) {
836 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
837 if (res != 0) {
838 return res;
839 }
840 }
841 instance->leftover_len = 0;
842 option = create_event(instance, MEDIA_BUFFERING_COMPLETED, id);
843 if (!option) {
844 return -1;
845 }
846 res = queue_option_frame(instance, option);
847 ast_free(option);
848
849 } else if (ast_strings_equal(command, MARK_MEDIA)) {
850 const char *id;
851 char *option;
852 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
854
856
858 id = ast_json_object_string_get(json, "correlation_id");
859 } else {
860 id = data;
861 }
862
863 ast_debug(4, "%s: %s %s\n",
864 ast_channel_name(instance->channel), MARK_MEDIA, id);
865
866 option = create_event(instance, MEDIA_MARK_PROCESSED, id);
867 if (!option) {
868 return -1;
869 }
870 res = queue_option_frame(instance, option);
871 ast_free(option);
872
873 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
874 struct ast_frame *frame = NULL;
875
876 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
877 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
878
879 AST_LIST_LOCK(&instance->frame_queue);
880 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
881 ast_frfree(frame);
882 }
883 instance->frame_queue_length = 0;
884 instance->bulk_media_in_progress = 0;
885 instance->leftover_len = 0;
886 instance->unbuffered = instance->last_unbuffered;
887 AST_LIST_UNLOCK(&instance->frame_queue);
888
889 } else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
890 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
891
892 AST_LIST_LOCK(&instance->frame_queue);
893 instance->report_queue_drained = 1;
894 AST_LIST_UNLOCK(&instance->frame_queue);
895
896 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
897 return send_event(instance, STATUS);
898
899 } else if (ast_strings_equal(command, PAUSE_MEDIA)) {
900 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
901 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
903 AST_LIST_LOCK(&instance->frame_queue);
904 instance->queue_paused = 1;
905 AST_LIST_UNLOCK(&instance->frame_queue);
906
907 } else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
908 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
909 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
911 AST_LIST_LOCK(&instance->frame_queue);
912 instance->queue_paused = 0;
913 AST_LIST_UNLOCK(&instance->frame_queue);
914
915 } else if (ast_strings_equal(command, SET_MEDIA_DIRECTION)) {
916 const char *direction;
917
918 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
919
921 send_event(instance, ERROR, "%s only supports JSON format.\n", command);
922 return 0;
923 }
924
925 direction = ast_json_object_string_get(json, "direction");
926 if (!direction) {
927 send_event(instance, ERROR, "%s requires a 'direction' parameter.\n", command);
928 return 0;
929 }
930
931 if (!strcmp("both", direction)) {
933 return 0;
934 }
935
936 if (!instance->timer) {
937 set_channel_timer(instance);
939 }
940
942
943 } else if (!strcmp("out", direction)) {
945 return 0;
946 }
947
948 if (!instance->timer) {
949 set_channel_timer(instance);
951 }
952
954
955 } else if (!strcmp("in", direction)) {
957 return 0;
958 }
959
960 if (instance->timer) {
962 ast_timer_close(instance->timer);
963 instance->timer = NULL;
965 }
966
968
969 } else {
970 send_event(instance, ERROR, "'%s' is not a valid direction for %s.\n",
971 direction, command);
972 return 0;
973 }
974
975 } else {
976 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
977 ast_channel_name(instance->channel), command);
978 }
979
980 return res;
981}
982
983static int process_text_message(struct websocket_pvt *instance,
984 char *payload, uint64_t payload_len)
985{
986 char *command;
987
988 if (payload_len == 0) {
989 ast_log(LOG_WARNING, "%s: WebSocket TEXT message has 0 length\n",
990 ast_channel_name(instance->channel));
991 return 0;
992 }
993
994 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
995 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
996 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
997 return 0;
998 }
999
1000 /*
1001 * Unfortunately, payload is not NULL terminated even when it's
1002 * a TEXT frame so we need to allocate a new buffer, copy
1003 * the data into it, and NULL terminate it.
1004 */
1005 command = ast_alloca(payload_len + 1);
1006 memcpy(command, payload, payload_len); /* Safe */
1007 command[payload_len] = '\0';
1008 command = ast_strip(command);
1009
1010 ast_debug(4, "%s: Received: %s\n",
1011 ast_channel_name(instance->channel), command);
1012
1013 return handle_command(instance, command);
1014}
1015
1016static int process_binary_message(struct websocket_pvt *instance,
1017 char *payload, uint64_t payload_len)
1018{
1019 char *next_frame_ptr = NULL;
1020 size_t bytes_read = 0;
1021 int res = 0;
1022 size_t bytes_left = 0;
1023
1024 {
1025 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
1027 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
1028 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
1029 ast_channel_name(instance->channel));
1030 return 0;
1031 }
1032 }
1033
1034 next_frame_ptr = payload;
1035 instance->bytes_read += payload_len;
1036
1037 if (instance->unbuffered) {
1038 res = queue_frame_from_buffer(instance, payload, payload_len);
1039 return res;
1040 }
1041
1042 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
1043 /*
1044 * We have leftover data from a previous websocket message.
1045 * Try to make a complete frame by appending data from
1046 * the current message to the leftover data.
1047 */
1048 char *append_ptr = instance->leftover_data + instance->leftover_len;
1049 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
1050 /*
1051 * It's possible that even the current message doesn't have enough
1052 * data to make a complete frame.
1053 */
1054 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
1055
1056 /*
1057 * Append whatever we can to the end of the leftover data
1058 * even if it's not enough to make a complete frame.
1059 */
1060 memcpy(append_ptr, payload, bytes_avail_to_copy);
1061
1062 /*
1063 * If leftover data is still short, just return and wait for the
1064 * next websocket message.
1065 */
1066 if (bytes_avail_to_copy < bytes_needed_for_frame) {
1067 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
1068 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
1069 instance->leftover_len += bytes_avail_to_copy;
1070 return 0;
1071 }
1072
1073 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
1074 if (res < 0) {
1075 return -1;
1076 }
1077
1078 /*
1079 * We stole data from the current payload so decrement payload_len
1080 * and set the next frame pointer after the data in payload
1081 * we just copied.
1082 */
1083 payload_len -= bytes_avail_to_copy;
1084 next_frame_ptr = payload + bytes_avail_to_copy;
1085
1086 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
1087 ast_channel_name(instance->channel),
1088 instance->frame_queue_length,
1089 (int)instance->bytes_read,
1090 (int)(payload_len + bytes_avail_to_copy),
1091 (int)instance->leftover_len,
1092 payload,
1093 next_frame_ptr,
1094 (int)(next_frame_ptr - payload),
1095 (int)payload_len,
1096 (int)bytes_avail_to_copy
1097 );
1098
1099
1100 instance->leftover_len = 0;
1101 }
1102
1103 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
1104 instance->leftover_len = 0;
1105 }
1106
1107 bytes_left = payload_len;
1108 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
1109 res = queue_frame_from_buffer(instance, next_frame_ptr,
1110 instance->optimal_frame_size);
1111 if (res < 0) {
1112 break;
1113 }
1114 bytes_read += instance->optimal_frame_size;
1115 next_frame_ptr += instance->optimal_frame_size;
1116 bytes_left -= instance->optimal_frame_size;
1117 }
1118
1119 if (instance->bulk_media_in_progress && bytes_left > 0) {
1120 /*
1121 * We have a partial frame. Save the leftover data.
1122 */
1123 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
1124 ast_channel_name(instance->channel),
1125 (int)instance->bytes_read,
1126 instance->frame_queue_length,
1127 (int)payload_len,
1128 (int)instance->leftover_len,
1129 payload,
1130 next_frame_ptr,
1131 (int)(next_frame_ptr - payload),
1132 (int)bytes_left
1133 );
1134 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
1135 instance->leftover_len = bytes_left;
1136 }
1137
1138 return 0;
1139}
1140
1141static int read_from_ws_and_queue(struct websocket_pvt *instance)
1142{
1143 uint64_t payload_len = 0;
1144 char *payload = NULL;
1145 enum ast_websocket_opcode opcode;
1146 int fragmented = 0;
1147 int res = 0;
1148
1149 if (!instance->websocket) {
1150 ast_log(LOG_WARNING, "%s: WebSocket session not found\n",
1151 ast_channel_name(instance->channel));
1152 return -1;
1153 }
1154
1155 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
1156 &opcode, &fragmented);
1157
1158 if (res) {
1159 ast_debug(3, "%s: WebSocket read error\n",
1160 ast_channel_name(instance->channel));
1162 return -1;
1163 }
1164 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
1165 (int)payload_len);
1166
1167 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
1168 return process_text_message(instance, payload, payload_len);
1169 }
1170
1171 /*
1172 * PINGs and PONGs will have been handled by res_http_websocket.
1173 * We also need to ignore CONTINUATION frames as they will be accumulated
1174 * by res_http_websocket until the threshold set in websocket_handoff_to_channel()
1175 * is reached, then it will send us a TEXT or BINARY frame.
1176 */
1177 if (opcode == AST_WEBSOCKET_OPCODE_PING || opcode == AST_WEBSOCKET_OPCODE_PONG
1178 || opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
1179 return 0;
1180 }
1181
1182 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
1183 ast_debug(3, "%s: WebSocket closed by remote\n",
1184 ast_channel_name(instance->channel));
1186 return -1;
1187 }
1188
1189 if (opcode == AST_WEBSOCKET_OPCODE_BINARY) {
1190 /* If the application's media direction is 'in', drop any media we receive from it */
1192 ast_debug(5, "%s: WebSocket dropped frame (application media direction is 'in')\n",
1193 ast_channel_name(instance->channel));
1194 return 0;
1195 }
1196 } else {
1197 ast_log(LOG_WARNING, "%s: WebSocket frame type %d not supported\n",
1198 ast_channel_name(instance->channel), (int)opcode);
1200 return 0;
1201 }
1202
1203 return process_binary_message(instance, payload, payload_len);
1204}
1205
1206static int websocket_handoff_to_channel(struct websocket_pvt *instance)
1207{
1208 int res = 0;
1209 int nodelay = 1;
1210 struct ast_sockaddr *remote_addr = ast_websocket_remote_address(instance->websocket);
1211
1212 instance->remote_addr = ast_strdup(ast_sockaddr_stringify(remote_addr));
1213 ast_debug(3, "%s: WebSocket connection with %s established\n",
1214 ast_channel_name(instance->channel), instance->remote_addr);
1215
1216 if (setsockopt(ast_websocket_fd(instance->websocket),
1217 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1218 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
1219 }
1220
1221 /*
1222 * The way write timeouts are handled in iostream requires the socket to be
1223 * in non-blocking mode. This is fine for reads as well because we already
1224 * set the websocket file descriptor on the channel and let it call
1225 * webchan_read() when data is available.
1226 */
1228
1229 /*
1230 * Tell res_http_websocket to accumulate incoming WebSocket CONTINUATION frames
1231 * into chunks of 1024 bytes and send us a TEXT or BINARY frame when the threshold
1232 * is reached.
1233 */
1235
1237
1238 res = send_event(instance, MEDIA_START);
1239 if (res != 0 ) {
1240 if (instance->type == AST_WS_TYPE_SERVER) {
1242 } else {
1243 /*
1244 * We were called by webchan_call so just need to set causes.
1245 * The core will hangup the channel.
1246 */
1249 }
1250 return -1;
1251 }
1252
1253 if (!instance->no_auto_answer) {
1254 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
1256 }
1257
1258 return 0;
1259}
1260
1261static void _websocket_request_hangup(struct websocket_pvt *instance, int ast_cause,
1262 enum ast_websocket_status_code tech_cause, int line, const char *function)
1263{
1264 if (!instance || !instance->channel) {
1265 return;
1266 }
1267 ast_debug(3, "%s:%s: Hangup requested from %s line %d. cause: %s(%d) tech_cause: %s(%d)",
1268 ast_channel_name(instance->channel), instance->remote_addr,
1269 function, line,
1270 ast_cause2str(ast_cause), ast_cause, ast_websocket_status_to_str(tech_cause), tech_cause);
1271
1272 if (tech_cause) {
1273 ast_channel_tech_hangupcause_set(instance->channel, tech_cause);
1274 }
1275 ast_queue_hangup_with_cause(instance->channel, ast_cause);
1276}
1277
1278/*! \brief Function called when we should write a frame to the channel */
1279static int webchan_write(struct ast_channel *ast, struct ast_frame *f)
1280{
1281 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1282 int res = 0;
1283
1284 if (!instance || !instance->websocket) {
1285 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
1286 ast_channel_name(ast));
1287 return -1;
1288 }
1289
1290 /* The app doesn't want media right now */
1292 return 0;
1293 }
1294
1295 if (f->frametype == AST_FRAME_CNG) {
1296 return 0;
1297 }
1298
1299 if (f->frametype != AST_FRAME_VOICE) {
1300 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
1301 ast_channel_name(ast));
1302 return 0;
1303 }
1304
1306 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format, not '%s'\n",
1309 return -1;
1310 }
1311
1313 (char *)f->data.ptr, (uint64_t)f->datalen);
1314 if (res != 0) {
1315 ast_log(LOG_WARNING, "%s: WebSocket write failure\n", ast_channel_name(ast));
1316 }
1317
1318 return res;
1319}
1320
1321/*!
1322 * \internal
1323 *
1324 * Called by the core to actually call the remote.
1325 * The core will hang up the channel if a non-zero is returned.
1326 * We just need to set hangup causes if appropriate.
1327 */
1328static int webchan_call(struct ast_channel *ast, const char *dest,
1329 int timeout)
1330{
1331 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1333 struct webchan_conf_global *global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1334 int global_write_timeout = global_cfg ? global_cfg->write_timeout : AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
1335
1336 ao2_cleanup(global_cfg);
1337
1338 if (!instance) {
1339 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
1340 ast_channel_name(ast));
1342 return -1;
1343 }
1344
1345 if (instance->type == AST_WS_TYPE_SERVER) {
1346 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
1347 return 0;
1348 }
1349 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
1350
1351 if (!instance->client) {
1352 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
1353 ast_channel_name(ast));
1355 return -1;
1356 }
1357
1358 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
1359 ast_channel_name(ast), dest, instance->connection_id);
1360
1361 if (!ast_strlen_zero(instance->uri_params)) {
1363 }
1364
1365 instance->websocket = ast_websocket_client_connect(instance->client,
1366 instance, ast_channel_name(ast), &result);
1367 if (!instance->websocket || result != WS_OK) {
1368 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
1371 return -1;
1372 }
1373
1374 /*
1375 * If websocket_client->write_timeout was set in websocket_client.conf, it will
1376 * have been applied to the websocket by ast_websocket_client_connect() above.
1377 * If it wasn't set in websocket_client.conf, the value will be INT_MAX and
1378 * and ast_websocket_client_connect() will have set AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT
1379 * on the websocket. However, the user may have set write_timeout in the global section
1380 * of chan_websocket.conf so if it wasn't set in websocket_client.conf, we'll now set
1381 * the websocket timeout to that. If they haven't set it in chan_websocket.conf either,
1382 * it'll default to AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT as well so the call below will
1383 * basically become a no-op.
1384 */
1385 if (instance->client->write_timeout == INT_MAX) {
1386 ast_websocket_set_timeout(instance->websocket, global_write_timeout);
1387 }
1388
1389 return websocket_handoff_to_channel(instance);
1390}
1391
1392static void websocket_destructor(void *data)
1393{
1394 struct websocket_pvt *instance = data;
1395 struct ast_frame *frame = NULL;
1396 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
1397
1398 AST_LIST_LOCK(&instance->frame_queue);
1399 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
1400 ast_frfree(frame);
1401 }
1402 AST_LIST_UNLOCK(&instance->frame_queue);
1403
1404 if (instance->timer) {
1405 ast_timer_close(instance->timer);
1406 instance->timer = NULL;
1407 }
1408
1409 if (instance->channel) {
1410 ast_channel_unref(instance->channel);
1411 instance->channel = NULL;
1412 }
1413 if (instance->websocket) {
1414 ast_websocket_unref(instance->websocket);
1415 instance->websocket = NULL;
1416 }
1417
1418 ao2_cleanup(instance->client);
1419 instance->client = NULL;
1420
1421 ao2_cleanup(instance->native_codec);
1422 instance->native_codec = NULL;
1423
1424 ao2_cleanup(instance->native_format);
1425 instance->native_format = NULL;
1426
1427 if (instance->leftover_data) {
1428 ast_free(instance->leftover_data);
1429 instance->leftover_data = NULL;
1430 }
1431
1432 ast_free(instance->uri_params);
1433 ast_free(instance->remote_addr);
1434}
1435
1436struct instance_proxy {
1437 AO2_WEAKPROXY();
1438 /*! \brief The name of the module owning this sorcery instance */
1439 char connection_id[0];
1440};
1441
1442static void instance_proxy_cb(void *weakproxy, void *data)
1443{
1444 struct instance_proxy *proxy = weakproxy;
1445 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
1446 ao2_unlink(instances, weakproxy);
1447}
1448
1449static struct websocket_pvt* websocket_new(const char *chan_name,
1450 const char *connection_id, struct ast_format *fmt)
1451{
1452 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
1453 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1454 char uuid[AST_UUID_STR_LEN];
1455 enum ast_websocket_type ws_type;
1456
1457 SCOPED_AO2WRLOCK(locker, instances);
1458
1461 ws_type = AST_WS_TYPE_SERVER;
1462 } else {
1463 ws_type = AST_WS_TYPE_CLIENT;
1464 }
1465
1466 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1467 if (!proxy) {
1468 return NULL;
1469 }
1470 strcpy(proxy->connection_id, connection_id); /* Safe */
1471
1472 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1474 if (!instance) {
1475 return NULL;
1476 }
1477 strcpy(instance->connection_id, connection_id); /* Safe */
1478
1479 instance->type = ws_type;
1480 if (ws_type == AST_WS_TYPE_CLIENT) {
1482 if (!instance->client) {
1483 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1484 chan_name, instance->connection_id);
1485 return NULL;
1486 }
1487 }
1488
1489 AST_LIST_HEAD_INIT(&instance->frame_queue);
1490
1491 /*
1492 * We need the codec to calculate the number of samples in a frame
1493 * so we'll get it once and store it in the instance.
1494 *
1495 * References for native_format and native_codec are now held by the
1496 * instance and will be released when the instance is destroyed.
1497 */
1498 instance->native_format = fmt;
1499 instance->native_codec = ast_format_get_codec(instance->native_format);
1500 /*
1501 * References for native_format and native_codec are now held by the
1502 * instance and will be released when the instance is destroyed.
1503 */
1504
1505 /*
1506 * It's not possible for us to re-time or re-frame media if the data
1507 * stream can't be broken up on arbitrary byte boundaries. This is usually
1508 * indicated by the codec's minimum_bytes being small (10 bytes or less).
1509 * We need to force the passthrough and unbuffered modes in this case.
1510 */
1511 if (instance->native_codec->minimum_bytes <= 10) {
1512 instance->passthrough = 1;
1513 instance->unbuffered = 1;
1514 instance->optimal_frame_size = 0;
1515 } else {
1516 instance->optimal_frame_size =
1517 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1518 / instance->native_codec->minimum_ms;
1519 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1520 if (!instance->leftover_data) {
1521 return NULL;
1522 }
1523 }
1524
1525 ast_debug(3,
1526 "%s: WebSocket channel native format '%s' Sample rate: %d ptime: %dms minms: %u minbytes: %u passthrough: %d optimal_frame_size: %d\n",
1527 chan_name, ast_format_get_name(instance->native_format),
1532 instance->passthrough,
1533 instance->optimal_frame_size);
1534
1535 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1536 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1537 return NULL;
1538 }
1539
1541 return NULL;
1542 }
1543
1544 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1545 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1546 proxy->connection_id);
1547 return NULL;
1548 }
1549 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1550
1551 return ao2_bump(instance);
1552}
1553
1554static int set_channel_timer(struct websocket_pvt *instance)
1555{
1556 int rate = 0;
1557 instance->timer = ast_timer_open();
1558 if (!instance->timer) {
1559 return -1;
1560 }
1561 /* Rate is the number of ticks per second, not the interval. */
1562 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1563 ast_debug(3, "%s: WebSocket timer rate %d\n",
1564 ast_channel_name(instance->channel), rate);
1565 ast_timer_set_rate(instance->timer, rate);
1566 /*
1567 * Calling ast_channel_set_fd will cause the channel thread to call
1568 * webchan_read at 'rate' times per second.
1569 */
1571
1572 return 0;
1573}
1574
1575static int set_channel_variables(struct websocket_pvt *instance)
1576{
1577 char *pkt_size = NULL;
1578 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1579 if (res <= 0) {
1580 return -1;
1581 }
1582
1584 pkt_size);
1585 ast_free(pkt_size);
1587 instance->connection_id);
1588
1589 return 0;
1590}
1591
1592static int validate_uri_parameters(const char *uri_params)
1593{
1594 char *params = ast_strdupa(uri_params);
1595 char *nvp = NULL;
1596 char *nv = NULL;
1597
1598 /*
1599 * uri_params should be a comma-separated list of key=value pairs.
1600 * For example:
1601 * name1=value1,name2=value2
1602 * We're verifying that each name and value either doesn't need
1603 * to be encoded or that it already is.
1604 */
1605
1606 while((nvp = ast_strsep(&params, ',', 0))) {
1607 /* nvp will be name1=value1 */
1608 while((nv = ast_strsep(&nvp, '=', 0))) {
1609 /* nv will be either name1 or value1 */
1610 if (!ast_uri_verify_encoded(nv)) {
1611 return 0;
1612 }
1613 }
1614 }
1615
1616 return 1;
1617}
1618
1619enum {
1620 OPT_WS_CODEC = (1 << 0),
1621 OPT_WS_NO_AUTO_ANSWER = (1 << 1),
1622 OPT_WS_URI_PARAM = (1 << 2),
1623 OPT_WS_PASSTHROUGH = (1 << 3),
1624 OPT_WS_MSG_FORMAT = (1 << 4),
1625 OPT_WS_MEDIA_DIRECTION = (1 << 5),
1626 OPT_WS_UNBUFFERED = (1 << 6),
1627};
1628
1629enum {
1638};
1639
1648 END_OPTIONS );
1649
1650static struct ast_channel *webchan_request(const char *type,
1651 struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids,
1652 const struct ast_channel *requestor, const char *data, int *cause)
1653{
1654 char *parse;
1655 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1656 struct ast_channel *chan = NULL;
1657 struct ast_format *fmt = NULL;
1658 struct ast_format_cap *caps = NULL;
1660 AST_APP_ARG(connection_id);
1662 );
1663 struct ast_flags opts = { 0, };
1664 char *opt_args[OPT_ARG_ARRAY_SIZE];
1665 const char *requestor_name = requestor ? ast_channel_name(requestor) :
1666 (assignedids && !ast_strlen_zero(assignedids->uniqueid) ? assignedids->uniqueid : "<unknown>");
1667 RAII_VAR(struct webchan_conf_global *, global_cfg, NULL, ao2_cleanup);
1668
1669 global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1670
1671 ast_debug(3, "%s: WebSocket channel requested\n",
1672 requestor_name);
1673
1674 if (ast_strlen_zero(data)) {
1675 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1676 requestor_name);
1677 goto failure;
1678 }
1679 parse = ast_strdupa(data);
1680 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1681
1682 if (ast_strlen_zero(args.connection_id)) {
1683 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1684 requestor_name);
1685 goto failure;
1686 }
1687
1688 if (!ast_strlen_zero(args.options)
1689 && ast_app_parse_options(websocket_options, &opts, opt_args,
1690 ast_strdupa(args.options))) {
1691 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1692 requestor_name, args.options);
1693 goto failure;
1694 }
1695
1696 if (ast_test_flag(&opts, OPT_WS_CODEC)
1697 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1698 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1699 } else {
1700 /*
1701 * If codec wasn't specified in the dial string,
1702 * use the first format in the capabilities.
1703 */
1704 fmt = ast_format_cap_get_format(cap, 0);
1705 }
1706
1707 if (!fmt) {
1708 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1709 requestor_name, args.connection_id);
1710 goto failure;
1711 }
1712
1713 ast_debug(3, "%s: Using format %s from %s\n",
1714 requestor_name, ast_format_get_name(fmt),
1715 ast_test_flag(&opts, OPT_WS_CODEC) ? "dialstring" : "requester");
1716
1717 instance = websocket_new(requestor_name, args.connection_id, fmt);
1718 if (!instance) {
1719 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1720 requestor_name);
1721 goto failure;
1722 }
1723
1726 if (!strcmp("both", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1727 /* The default. Don't need to do anything here other than
1728 * ensure it is an allowed value. */
1729 } else if (!strcmp("out", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1731 } else if (!strcmp("in", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1733 } else {
1734 ast_log(LOG_ERROR, "Unrecognized option for media direction: '%s'.\n",
1735 opt_args[OPT_ARG_WS_MEDIA_DIRECTION]);
1736 goto failure;
1737 }
1738 }
1739
1741
1742 /*
1743 * Passthrough requires unbuffered. If passthrough was forced by choice of
1744 * codec, unbuffered will already have been set. If passthrough was set by
1745 * the dialstring option, we need to force unbuffered.
1746 */
1747 if (!instance->passthrough) {
1748 instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
1749 instance->unbuffered = instance->passthrough;
1750 }
1751 /*
1752 * If unbuffered hasn't been forced by passthrough, set it according to the
1753 * dialstring "u" option.
1754 */
1755 if (!instance->unbuffered) {
1756 instance->unbuffered = ast_test_flag(&opts, OPT_WS_UNBUFFERED);
1757 }
1758
1760 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1761 char *comma;
1762
1763 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1765 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1766 requestor_name);
1767 goto failure;
1768 }
1769
1770 ast_debug(3, "%s: Using URI parameters '%s'\n",
1771 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1772
1774 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1775 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1776 args.connection_id);
1777 goto failure;
1778 }
1779
1780 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1781 comma = instance->uri_params;
1782 /*
1783 * The normal separator for query string components is an
1784 * ampersand ('&') but the Dial app interprets them as additional
1785 * channels to dial in parallel so we instruct users to separate
1786 * the parameters with commas (',') instead. We now have to
1787 * convert those commas back to ampersands.
1788 */
1789 while ((comma = strchr(comma,','))) {
1790 *comma = '&';
1791 }
1792 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1793 }
1794
1795 if (ast_test_flag(&opts, OPT_WS_MSG_FORMAT)) {
1797
1799 ast_log(LOG_WARNING, "%s: 'f/control message format' dialstring parameter value missing or invalid. "
1800 "Defaulting to 'plain-text'\n",
1801 requestor_name);
1803 }
1804 } else if (global_cfg) {
1805 instance->control_msg_format = global_cfg->control_msg_format;
1806 }
1807
1808 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1809 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1810 if (!chan) {
1811 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", requestor_name);
1812 goto failure;
1813 }
1814
1815 /* Prevent device state caching as this channel involves ephemeral destinations or sources */
1817 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1818 ast_channel_name(chan), requestor_name,
1819 instance->connection_id);
1820
1821 instance->channel = ao2_bump(chan);
1823
1824 /* If the application's media direction is 'both' or 'out', we need the channel timer. */
1826 && set_channel_timer(instance) != 0) {
1827 goto failure;
1828 }
1829
1830 if (set_channel_variables(instance) != 0) {
1831 goto failure;
1832 }
1833
1835 if (!caps) {
1836 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1837 goto failure;
1838 }
1839
1840 ast_format_cap_append(caps, instance->native_format, 0);
1841 ast_channel_nativeformats_set(instance->channel, caps);
1844 ast_channel_set_readformat(instance->channel, instance->native_format);
1846 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1847 ast_channel_unlock(chan);
1848 ao2_cleanup(caps);
1849
1850 ast_debug(3, "%s: WebSocket channel created to %s\n",
1851 ast_channel_name(chan), args.connection_id);
1852
1853 return chan;
1854
1855failure:
1856 if (chan) {
1857 ast_channel_unlock(chan);
1858 }
1859 *cause = AST_CAUSE_FAILURE;
1860 return NULL;
1861}
1862
1863/*!
1864 * \internal
1865 *
1866 * Called by the core to hang up the channel.
1867 */
1868static int webchan_hangup(struct ast_channel *ast)
1869{
1870 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1871
1872 if (!instance) {
1873 return -1;
1874 }
1875 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1876 ast_channel_name(ast), instance->connection_id);
1877
1878 if (instance->websocket) {
1880 ast_websocket_unref(instance->websocket);
1881 instance->websocket = NULL;
1882 }
1884
1885 /* Clean up the reference from adding the instance to the channel */
1886 ao2_cleanup(instance);
1887
1888 return 0;
1889}
1890
1891static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration)
1892{
1893 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1894
1895 if (!instance) {
1896 return -1;
1897 }
1898
1899 return send_event(instance, DTMF_END, digit);
1900}
1901
1902/*!
1903 * \internal
1904 *
1905 * Called by res_http_websocket after a client has connected and
1906 * successfully upgraded from HTTP to WebSocket.
1907 *
1908 * Depends on incoming_ws_http_callback parsing the connection_id from
1909 * the HTTP request and storing it in get_params.
1910 */
1911static void incoming_ws_established_cb(struct ast_websocket *ast_ws_session,
1912 struct ast_variable *get_params, struct ast_variable *upgrade_headers)
1913{
1914 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1915 struct ast_variable *v;
1916 const char *connection_id = NULL;
1917 struct websocket_pvt *instance = NULL;
1918 struct webchan_conf_global *global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1919 int global_write_timeout = global_cfg ? global_cfg->write_timeout : AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
1920
1921 ao2_cleanup(global_cfg);
1922
1923 ast_debug(3, "WebSocket established\n");
1924
1925 for (v = upgrade_headers; v; v = v->next) {
1926 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1927 }
1928 for (v = get_params; v; v = v->next) {
1929 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1930 }
1931
1932 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1933 if (!connection_id) {
1934 /*
1935 * This can't really happen because websocket_http_callback won't
1936 * let it get this far if it can't add the connection_id to the
1937 * get_params.
1938 * Just in case though...
1939 */
1940 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1943 return;
1944 }
1945
1946 instance = ao2_weakproxy_find(instances, connection_id, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1947 if (!instance) {
1948 /*
1949 * This also can't really happen because websocket_http_callback won't
1950 * let it get this far if it can't find the instance.
1951 * Just in case though...
1952 */
1953 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1956 return;
1957 }
1958 instance->websocket = ao2_bump(ast_ws_session);
1959
1960 ast_websocket_set_timeout(instance->websocket, global_write_timeout);
1961
1963 ao2_cleanup(instance);
1964 /*
1965 * The instance is the channel's responsibility now.
1966 * We just return here.
1967 */
1968}
1969
1970/*!
1971 * \internal
1972 *
1973 * Called by the core http server after a client connects but before
1974 * the upgrade from HTTP to Websocket. We need to save the URI in
1975 * the CONNECTION_ID in a get_param because it contains the connection UUID
1976 * we gave to the client when they used externalMedia to create the channel.
1977 * incoming_ws_established_cb() will use this to retrieve the chan_websocket
1978 * instance.
1979 */
1981 const struct ast_http_uri *urih, const char *uri,
1982 enum ast_http_method method, struct ast_variable *get_params,
1983 struct ast_variable *headers)
1984{
1985 struct ast_http_uri fake_urih = {
1987 };
1988 int res = 0;
1989 /*
1990 * Normally the http server will destroy the get_params
1991 * when the session ends but if there weren't any initially
1992 * and we create some and add them to the list, the http server
1993 * won't know about it so we have to destroy it ourselves.
1994 */
1995 int destroy_get_params = (get_params == NULL);
1996 struct ast_variable *v = NULL;
1997 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1998
1999 ast_debug(2, "URI: %s Starting\n", uri);
2000
2001 /*
2002 * The client will have issued the GET request with a URI of
2003 * /media/<connection_id>
2004 *
2005 * Since this callback is registered for the /media URI prefix the
2006 * http server will strip that off the front of the URI passing in
2007 * only the path components after that in the 'uri' parameter.
2008 * This should leave only the connection id without a leading '/'.
2009 */
2010 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
2011 if (!instance) {
2012 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
2013 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
2014 return -1;
2015 }
2016
2017 /*
2018 * We don't allow additional connections using the same connection id.
2019 */
2020 if (instance->websocket) {
2021 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
2022 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
2023 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
2024 return -1;
2025 }
2026
2027 v = ast_variable_new("CONNECTION_ID", uri, "");
2028 if (!v) {
2029 ast_http_error(ser, 500, "Server error", "");
2030 return -1;
2031 }
2032 ast_variable_list_append(&get_params, v);
2033
2034 for (v = get_params; v; v = v->next) {
2035 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
2036 }
2037
2038 /*
2039 * This will ultimately call internal_ws_established_cb() so
2040 * this function will block until the websocket is closed and
2041 * internal_ws_established_cb() returns;
2042 */
2043 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
2044 get_params, headers);
2045 if (destroy_get_params) {
2046 ast_variables_destroy(get_params);
2047 }
2048
2049 ast_debug(2, "URI: %s DONE\n", uri);
2050
2051 return res;
2052}
2053
2054static struct ast_http_uri http_uri = {
2056 .description = "Media over Websocket",
2057 .uri = "media",
2058 .has_subtree = 1,
2059 .data = NULL,
2060 .key = __FILE__,
2061 .no_decode_uri = 1,
2062};
2063
2067
2068static int global_control_message_format_from_str(const struct aco_option *opt,
2069 struct ast_variable *var, void *obj)
2070{
2071 struct webchan_conf_global *cfg = obj;
2072
2074
2076 ast_log(LOG_ERROR, "chan_websocket.conf: Invalid value '%s' for "
2077 "control_mesage_format. Must be 'plain-text' or 'json'\n",
2078 var->value);
2079 return -1;
2080 }
2081
2082 return 0;
2083}
2084
2085static int global_control_message_format_to_str(const void *obj, const intptr_t *args, char **buf)
2086{
2087 const struct webchan_conf_global *cfg = obj;
2088
2090
2091 return 0;
2092}
2093
2094static void *global_alloc(const char *name)
2095{
2097 sizeof(*cfg), NULL);
2098
2099 if (!cfg) {
2100 return NULL;
2101 }
2102
2103 return cfg;
2104}
2105
2106static int global_apply(const struct ast_sorcery *sorcery, void *obj)
2107{
2108 struct webchan_conf_global *cfg = obj;
2109
2110 ast_debug(1, "control_msg_format: %s\n",
2112
2113 if (cfg->write_timeout <= 0) {
2114 ast_log(LOG_WARNING, "The write_timeout parameter must be > 0\n");
2115 return -1;
2116 }
2117
2118 return 0;
2119}
2120
2121static int load_config(void)
2122{
2123 ast_debug(2, "Initializing Websocket Client Configuration\n");
2125 if (!sorcery) {
2126 ast_log(LOG_ERROR, "Failed to open sorcery\n");
2127 return -1;
2128 }
2129
2130 ast_sorcery_apply_default(sorcery, "global", "config",
2131 "chan_websocket.conf,criteria=type=global,single_object=yes,explicit_name=global");
2132
2134 ast_log(LOG_ERROR, "Failed to register chan_websocket global object with sorcery\n");
2136 sorcery = NULL;
2137 return -1;
2138 }
2139
2140 ast_sorcery_object_field_register_nodoc(sorcery, "global", "type", "", OPT_NOOP_T, 0, 0);
2141 ast_sorcery_register_cust(global, control_message_format, "plain-text");
2144
2146
2147 return 0;
2148}
2149
2150/*! \brief Function called when our module is unloaded */
2151static int unload_module(void)
2152{
2156
2160
2162 instances = NULL;
2163
2165 sorcery = NULL;
2166
2167 return 0;
2168}
2169
2170static int reload_module(void)
2171{
2172 ast_debug(2, "Reloading chan_websocket configuration\n");
2174
2175 return 0;
2176}
2177
2178/*! \brief Function called when our module is loaded */
2179static int load_module(void)
2180{
2181 int res = 0;
2182 struct ast_websocket_protocol *protocol;
2183
2184 res = load_config();
2185 if (res != 0) {
2187 }
2188
2191 }
2192
2195 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
2196 unload_module();
2198 }
2199
2201 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
2202 instance_proxy_sort_fn, instance_proxy_cmp_fn);
2203 if (!instances) {
2205 "Failed to allocate the chan_websocket instance registry\n");
2206 unload_module();
2208 }
2209
2211 if (!ast_ws_server) {
2212 unload_module();
2214 }
2215
2216 protocol = ast_websocket_sub_protocol_alloc("media");
2217 if (!protocol) {
2218 unload_module();
2220 }
2223
2225
2227}
2228
2229AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Websocket Media Channel",
2230 .support_level = AST_MODULE_SUPPORT_CORE,
2231 .load = load_module,
2232 .unload = unload_module,
2234 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
2235 .requires = "res_http_websocket,res_websocket_client",
2236);
char digit
enum queue_result id
Definition app_queue.c:1790
#define var
Definition ast_expr2f.c:605
#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_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_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_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
#define AST_CAUSE_FAILURE
Definition causes.h:150
#define AST_CAUSE_NORMAL
Definition causes.h:151
#define AST_CAUSE_NETWORK_OUT_OF_ORDER
Definition causes.h:121
#define AST_CAUSE_NO_ROUTE_DESTINATION
Definition causes.h:100
static PGresult * result
Definition cel_pgsql.c:84
static const char type[]
#define ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command)
#define FLUSH_MEDIA
static void _websocket_request_hangup(struct websocket_pvt *instance, int ast_cause, enum ast_websocket_status_code tech_cause, int line, const char *function)
#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)
@ OPT_WS_MEDIA_DIRECTION
@ OPT_WS_CODEC
@ OPT_WS_PASSTHROUGH
@ OPT_WS_URI_PARAM
@ OPT_WS_MSG_FORMAT
@ OPT_WS_NO_AUTO_ANSWER
@ OPT_WS_UNBUFFERED
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 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)
#define websocket_request_hangup(_instance, _cause, _tech)
#define MEDIA_WEBSOCKET_CONNECTION_ID
#define WS_WEBSOCKET_FDNO
static int read_from_ws_and_queue(struct websocket_pvt *instance)
@ 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)
@ WEBCHAN_MEDIA_DIRECTION_BOTH
@ WEBCHAN_MEDIA_DIRECTION_OUT
@ WEBCHAN_MEDIA_DIRECTION_IN
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)
#define WS_TIMER_FDNO
static struct ast_sorcery * sorcery
#define ERROR_ON_INVALID_MEDIA_DIRECTION_RTN(instance, command, direction)
#define START_MEDIA_BUFFERING
static int set_channel_timer(struct websocket_pvt *instance)
static struct ast_frame * create_frame_from_buffer(struct websocket_pvt *instance, char *buffer, size_t len)
#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
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE
#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
#define ERROR_ON_UNBUFFERED_MODE_RTN(instance, command)
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 struct ast_websocket_server * ast_ws_server
#define SET_MEDIA_DIRECTION
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 int websocket_handoff_to_channel(struct websocket_pvt *instance)
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 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)
@ OPT_ARG_WS_PASSTHROUGH
@ OPT_ARG_WS_URI_PARAM
@ OPT_ARG_WS_MEDIA_DIRECTION
@ OPT_ARG_WS_MSG_FORMAT
@ OPT_ARG_WS_CODEC
@ OPT_ARG_WS_NO_AUTO_ANSWER
@ OPT_ARG_WS_UNBUFFERED
@ OPT_ARG_ARRAY_SIZE
const char * ast_channel_name(const struct ast_channel *chan)
int ast_channel_tech_hangupcause(const struct ast_channel *chan)
void * ast_channel_tech_pvt(const struct ast_channel *chan)
void ast_channel_tech_hangupcause_set(struct ast_channel *chan, int value)
#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)
int ast_channel_fdno(const struct ast_channel *chan)
@ AST_FLAG_DISABLE_DEVSTATE_CACHE
Definition channel.h:1049
void ast_channel_unregister(const struct ast_channel_tech *tech)
Unregister a channel technology.
Definition channel.c:571
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
Queue a control frame without payload.
Definition channel.c:1289
struct ast_flags * ast_channel_flags(struct ast_channel *chan)
int ast_queue_frame(struct ast_channel *chan, struct ast_frame *f)
Queue one or more frames to a channel's frame queue.
Definition channel.c:1171
int ast_queue_hangup_with_cause(struct ast_channel *chan, int cause)
Queue a hangup frame with hangupcause set.
Definition channel.c:1213
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:540
#define ast_channel_unref(c)
Decrease channel reference count.
Definition channel.h:3019
const char * ast_cause2str(int cause) attribute_pure
Gives the string form of a given cause code.
Definition channel.c:613
void ast_channel_set_fd(struct ast_channel *chan, int which, int fd)
Definition channel.c:2417
void ast_channel_internal_fd_clear(struct ast_channel *chan, int which)
void ast_channel_hangupcause_set(struct ast_channel *chan, int value)
void ast_channel_tech_set(struct ast_channel *chan, const struct ast_channel_tech *value)
#define ast_channel_unlock(chan)
Definition channel.h:2984
void ast_channel_set_writeformat(struct ast_channel *chan, struct ast_format *format)
@ AST_STATE_DOWN
@ 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
#define ast_format_cache_get(name)
Retrieve a named format from the cache.
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:408
@ 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
direction
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:779
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:722
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition http.c:747
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.
ast_websocket_status_code
Websocket Status Codes from RFC-6455.
@ AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA
@ AST_WEBSOCKET_STATUS_NORMAL
@ AST_WEBSOCKET_STATUS_GOING_AWAY
@ AST_WEBSOCKET_STATUS_INTERNAL_ERROR
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.
void AST_OPTIONAL_API_NAME() ast_websocket_reconstruct_enable(struct ast_websocket *session, size_t bytes)
Enable multi-frame reconstruction up to a certain number of bytes.
struct ast_sockaddr *AST_OPTIONAL_API_NAME() ast_websocket_remote_address(struct ast_websocket *session)
Get the remote address for a WebSocket connected session.
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.
#define AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT
Default websocket write timeout, in ms.
ast_websocket_opcode
WebSocket operation codes.
@ AST_WEBSOCKET_OPCODE_PING
@ AST_WEBSOCKET_OPCODE_PONG
@ AST_WEBSOCKET_OPCODE_CONTINUATION
@ AST_WEBSOCKET_OPCODE_BINARY
@ AST_WEBSOCKET_OPCODE_CLOSE
@ AST_WEBSOCKET_OPCODE_TEXT
int AST_OPTIONAL_API_NAME() ast_websocket_set_nonblock(struct ast_websocket *session)
Set the socket of a WebSocket session to be non-blocking.
const char *AST_OPTIONAL_API_NAME() ast_websocket_status_to_str(enum ast_websocket_status_code code)
Convert a websocket status code to a string.
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_set_timeout(struct ast_websocket *session, int timeout)
Set the timeout on a non-blocking WebSocket session.
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.
#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:3067
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
#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_frfree(fr)
#define AST_FRAME_SET_BUFFER(fr, _base, _ofs, _datalen)
@ AST_FRAME_CONTROL
@ AST_CONTROL_ANSWER
@ AST_CONTROL_OPTION
struct ast_frame ast_null_frame
Definition main/frame.c:79
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
#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_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
#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 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
@ 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
static char * ast_sockaddr_stringify(const struct ast_sockaddr *addr)
Wrapper around ast_sockaddr_stringify_fmt() with default format.
Definition netsock2.h:256
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:1277
static struct @523 args
#define NULL
Definition resample.c:96
#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_register_int(object, structure, option, field, def_value)
Register an int field as type OPT_INT_T within an object.
Definition sorcery.h:1710
#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
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:1869
Structure to pass both assignedid values to channel drivers.
Definition channel.h:606
struct ast_format_cap * capabilities
Definition channel.h:652
Main Channel structure associated with a channel.
const char * data
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::@237 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, ...).
Socket address structure.
Definition netsock2.h:97
Full structure for sorcery.
Definition sorcery.c:231
describes a server instance
Definition tcptls.h:151
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 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 ast_channel * channel
enum webchan_control_msg_format control_msg_format
struct websocket_pvt::@141 frame_queue
enum ast_websocket_type type
struct ast_timer * timer
struct ast_codec * native_codec
struct ast_websocket_client * client
struct ast_websocket * websocket
static struct aco_type global
static struct test_options options
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
#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
#define ast_set_flag(p, flag)
Definition utils.h:71
int ast_uri_verify_encoded(const char *string)
Verify if a string is valid as a URI component.
Definition utils.c:779
#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.

◆ ERROR_ON_INVALID_MEDIA_DIRECTION_RTN

#define ERROR_ON_INVALID_MEDIA_DIRECTION_RTN (   instance,
  command,
  direction 
)

Definition at line 745 of file chan_websocket.c.

746 { \
747 if (instance->media_direction == direction) { \
748 send_event(instance, ERROR, "%s not supported while media direction " \
749 "is '%s'", command, websocket_media_direction_map[direction]); \
750 ast_debug(4, "%s: WebSocket media direction is '%s'. Ignoring %s command.\n", \
751 ast_channel_name(instance->channel), websocket_media_direction_map[direction], command); \
752 return 0; \
753 } \
754})
static const char * websocket_media_direction_map[]

◆ ERROR_ON_PASSTHROUGH_MODE_RTN

#define ERROR_ON_PASSTHROUGH_MODE_RTN (   instance,
  command 
)

Definition at line 725 of file chan_websocket.c.

726 { \
727 if (instance->passthrough) { \
728 send_event(instance, ERROR, "%s not supported in passthrough mode", command); \
729 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n", \
730 ast_channel_name(instance->channel), command); \
731 return 0; \
732 } \
733})

◆ ERROR_ON_UNBUFFERED_MODE_RTN

#define ERROR_ON_UNBUFFERED_MODE_RTN (   instance,
  command 
)

Definition at line 735 of file chan_websocket.c.

736 { \
737 if (instance->unbuffered) { \
738 send_event(instance, ERROR, "%s not supported in unbuffered mode", command); \
739 ast_debug(4, "%s: WebSocket in unbuffered mode. Ignoring %s command.\n", \
740 ast_channel_name(instance->channel), command); \
741 return 0; \
742 } \
743})

◆ FLUSH_MEDIA

#define FLUSH_MEDIA   "FLUSH_MEDIA"

Definition at line 137 of file chan_websocket.c.

◆ GET_DRIVER_STATUS

#define GET_DRIVER_STATUS   "GET_STATUS"

Definition at line 138 of file chan_websocket.c.

◆ HANGUP_CHANNEL

#define HANGUP_CHANNEL   "HANGUP"

Definition at line 133 of file chan_websocket.c.

◆ INCOMING_CONNECTION_ID

#define INCOMING_CONNECTION_ID   "INCOMING"

Definition at line 130 of file chan_websocket.c.

◆ MARK_MEDIA

#define MARK_MEDIA   "MARK_MEDIA"

Definition at line 136 of file chan_websocket.c.

◆ MAX_TEXT_MESSAGE_LEN

#define MAX_TEXT_MESSAGE_LEN   MIN(128, (AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE - 1))

Definition at line 147 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_CONNECTION_ID

#define MEDIA_WEBSOCKET_CONNECTION_ID   "MEDIA_WEBSOCKET_CONNECTION_ID"

Definition at line 129 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE

#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE   "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"

Definition at line 128 of file chan_websocket.c.

◆ PAUSE_MEDIA

#define PAUSE_MEDIA   "PAUSE_MEDIA"

Definition at line 140 of file chan_websocket.c.

◆ QUEUE_LENGTH_MAX

#define QUEUE_LENGTH_MAX   1000

Definition at line 144 of file chan_websocket.c.

◆ QUEUE_LENGTH_XOFF_LEVEL

#define QUEUE_LENGTH_XOFF_LEVEL   900

Definition at line 145 of file chan_websocket.c.

◆ QUEUE_LENGTH_XON_LEVEL

#define QUEUE_LENGTH_XON_LEVEL   800

Definition at line 146 of file chan_websocket.c.

◆ REPORT_QUEUE_DRAINED

#define REPORT_QUEUE_DRAINED   "REPORT_QUEUE_DRAINED"

Definition at line 139 of file chan_websocket.c.

◆ send_event

#define send_event (   _instance,
  _event,
  ... 
)

Use this macro to create and send events passing in any event-specific parameters.

Definition at line 448 of file chan_websocket.c.

449 { \
450 int _res = -1; \
451 char *_payload = _create_event_ ## _event(_instance, ##__VA_ARGS__); \
452 if (_payload && _instance->websocket) { \
453 _res = ast_websocket_write_string(_instance->websocket, _payload); \
454 if (_res != 0) { \
455 ast_log(LOG_ERROR, "%s: Unable to send event %s\n", \
456 ast_channel_name(instance->channel), _payload); \
457 } else { \
458 ast_debug(3, "%s: Sent %s\n", \
459 ast_channel_name(instance->channel), _payload); \
460 }\
461 ast_free(_payload); \
462 } \
463 (_res); \
464})

◆ SET_MEDIA_DIRECTION

#define SET_MEDIA_DIRECTION   "SET_MEDIA_DIRECTION"

Definition at line 142 of file chan_websocket.c.

◆ START_MEDIA_BUFFERING

#define START_MEDIA_BUFFERING   "START_MEDIA_BUFFERING"

Definition at line 134 of file chan_websocket.c.

◆ STOP_MEDIA_BUFFERING

#define STOP_MEDIA_BUFFERING   "STOP_MEDIA_BUFFERING"

Definition at line 135 of file chan_websocket.c.

◆ websocket_request_hangup

#define websocket_request_hangup (   _instance,
  _cause,
  _tech 
)     _websocket_request_hangup(_instance, _cause, _tech, __LINE__, __FUNCTION__)

Definition at line 161 of file chan_websocket.c.

◆ WS_TIMER_FDNO

#define WS_TIMER_FDNO   (AST_EXTENDED_FDS + 1)

Definition at line 125 of file chan_websocket.c.

◆ WS_WEBSOCKET_FDNO

#define WS_WEBSOCKET_FDNO   (AST_EXTENDED_FDS + 2)

Definition at line 126 of file chan_websocket.c.

Enumeration Type Documentation

◆ anonymous enum

anonymous enum
Enumerator
OPT_WS_CODEC 
OPT_WS_NO_AUTO_ANSWER 
OPT_WS_URI_PARAM 
OPT_WS_PASSTHROUGH 
OPT_WS_MSG_FORMAT 
OPT_WS_MEDIA_DIRECTION 
OPT_WS_UNBUFFERED 

Definition at line 1620 of file chan_websocket.c.

1620 {
1621 OPT_WS_CODEC = (1 << 0),
1622 OPT_WS_NO_AUTO_ANSWER = (1 << 1),
1623 OPT_WS_URI_PARAM = (1 << 2),
1624 OPT_WS_PASSTHROUGH = (1 << 3),
1625 OPT_WS_MSG_FORMAT = (1 << 4),
1626 OPT_WS_MEDIA_DIRECTION = (1 << 5),
1627 OPT_WS_UNBUFFERED = (1 << 6),
1628};

◆ anonymous enum

anonymous enum
Enumerator
OPT_ARG_WS_CODEC 
OPT_ARG_WS_NO_AUTO_ANSWER 
OPT_ARG_WS_URI_PARAM 
OPT_ARG_WS_PASSTHROUGH 
OPT_ARG_WS_MSG_FORMAT 
OPT_ARG_WS_MEDIA_DIRECTION 
OPT_ARG_WS_UNBUFFERED 
OPT_ARG_ARRAY_SIZE 

Definition at line 1630 of file chan_websocket.c.

◆ webchan_control_msg_format

Enumerator
WEBCHAN_CONTROL_MSG_FORMAT_PLAIN 
WEBCHAN_CONTROL_MSG_FORMAT_JSON 
WEBCHAN_CONTROL_MSG_FORMAT_INVALID 

Definition at line 56 of file chan_websocket.c.

◆ webchan_media_direction

Enumerator
WEBCHAN_MEDIA_DIRECTION_BOTH 
WEBCHAN_MEDIA_DIRECTION_OUT 
WEBCHAN_MEDIA_DIRECTION_IN 

Definition at line 75 of file chan_websocket.c.

Function Documentation

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 2237 of file chan_websocket.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 2237 of file chan_websocket.c.

◆ _create_event_DTMF_END()

static char * _create_event_DTMF_END ( struct websocket_pvt instance,
const char  digit 
)
static

Definition at line 334 of file chan_websocket.c.

336{
337 char *payload = NULL;
339 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s#}",
340 "event", "DTMF_END",
341 "channel_id", ast_channel_uniqueid(instance->channel),
342 "digit", &digit, 1
343 );
344 if (!msg) {
345 return NULL;
346 }
348 ast_json_unref(msg);
349 } else {
350 ast_asprintf(&payload, "%s digit:%c channel_id:%s",
351 "DTMF_END", digit, ast_channel_uniqueid(instance->channel));
352 }
353
354 return payload;
355}
const char * ast_channel_uniqueid(const struct ast_channel *chan)
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
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

References ast_asprintf, ast_channel_uniqueid(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), websocket_pvt::channel, websocket_pvt::control_msg_format, digit, NULL, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_ERROR()

static char * _create_event_ERROR ( struct websocket_pvt instance,
const char *  format,
  ... 
)
static

Definition at line 402 of file chan_websocket.c.

404{
405 char *payload = NULL;
406 char *error_text = NULL;
407 va_list ap;
408 int res = 0;
409
410 va_start(ap, format);
411 res = ast_vasprintf(&error_text, format, ap);
412 va_end(ap);
413 if (res < 0 || !error_text) {
414 return NULL;
415 }
416
418 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
419 "event", "ERROR",
420 "channel_id", ast_channel_uniqueid(instance->channel),
421 "error_text", error_text);
422 ast_free(error_text);
423 if (!msg) {
424 return NULL;
425 }
427 ast_json_unref(msg);
428 } else {
429 ast_asprintf(&payload, "%s channel_id:%s error_text:%s",
430 "ERROR", ast_channel_uniqueid(instance->channel), error_text);
431 ast_free(error_text);
432 }
433
434 return payload;
435}
#define ast_vasprintf(ret, fmt, ap)
A wrapper for vasprintf()
Definition astmm.h:278

References ast_asprintf, ast_channel_uniqueid(), ast_free, AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), ast_vasprintf, NULL, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_MEDIA_BUFFERING_COMPLETED()

static char * _create_event_MEDIA_BUFFERING_COMPLETED ( struct websocket_pvt instance,
const char *  id 
)
static

Definition at line 274 of file chan_websocket.c.

276{
277 char *payload = NULL;
279 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
280 "event", "MEDIA_BUFFERING_COMPLETED",
281 "channel_id", ast_channel_uniqueid(instance->channel),
282 "correlation_id", S_OR(id, "")
283 );
284 if (!msg) {
285 return NULL;
286 }
288 ast_json_unref(msg);
289 } else {
290 ast_asprintf(&payload, "%s%s%s",
291 "MEDIA_BUFFERING_COMPLETED",
292 S_COR(id, " ",""), S_OR(id, ""));
293
294 }
295
296 return payload;
297}
#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

References ast_asprintf, ast_channel_uniqueid(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), websocket_pvt::channel, websocket_pvt::control_msg_format, NULL, S_COR, S_OR, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_MEDIA_MARK_PROCESSED()

static char * _create_event_MEDIA_MARK_PROCESSED ( struct websocket_pvt instance,
const char *  id 
)
static

Definition at line 304 of file chan_websocket.c.

306{
307 char *payload = NULL;
309 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
310 "event", "MEDIA_MARK_PROCESSED",
311 "channel_id", ast_channel_uniqueid(instance->channel),
312 "correlation_id", S_OR(id, "")
313 );
314 if (!msg) {
315 return NULL;
316 }
318 ast_json_unref(msg);
319 } else {
320 ast_asprintf(&payload, "%s%s%s",
321 "MEDIA_MARK_PROCESSED",
322 S_COR(id, " ",""), S_OR(id, ""));
323
324 }
325
326 return payload;
327}

References ast_asprintf, ast_channel_uniqueid(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), websocket_pvt::channel, websocket_pvt::control_msg_format, NULL, S_COR, S_OR, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_MEDIA_START()

static char * _create_event_MEDIA_START ( struct websocket_pvt instance)
static

Definition at line 229 of file chan_websocket.c.

230{
231 char *payload = NULL;
232
234 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:b, s:b, s:o }",
235 "event", "MEDIA_START",
236 "connection_id", instance->connection_id,
237 "channel", ast_channel_name(instance->channel),
238 "channel_id", ast_channel_uniqueid(instance->channel),
239 "format", ast_format_get_name(instance->native_format),
240 "optimal_frame_size", instance->optimal_frame_size,
241 "ptime", instance->native_codec->default_ms,
242 "passthrough", instance->passthrough,
243 "unbuffered", instance->unbuffered,
244 "channel_variables", ast_json_channel_vars(ast_channel_varshead(
245 instance->channel))
246 );
247 if (!msg) {
248 return NULL;
249 }
251 ast_json_unref(msg);
252 } else {
253 ast_asprintf(&payload, "%s %s:%s %s:%s %s:%s %s:%s %s:%d %s:%d %s:%s %s:%s",
254 "MEDIA_START",
255 "connection_id", instance->connection_id,
256 "channel", ast_channel_name(instance->channel),
257 "channel_id", ast_channel_uniqueid(instance->channel),
258 "format", ast_format_get_name(instance->native_format),
259 "optimal_frame_size", instance->optimal_frame_size,
260 "ptime", instance->native_codec->default_ms,
261 "passthrough", instance->passthrough ? "true" : "false",
262 "unbuffered", instance->unbuffered ? "true" : "false"
263 );
264 }
265
266 return payload;
267}
struct varshead * ast_channel_varshead(struct ast_channel *chan)
struct ast_json * ast_json_channel_vars(struct varshead *channelvars)
Construct a JSON object from a ast_var_t list.
Definition json.c:941

References ast_asprintf, ast_channel_name(), ast_channel_uniqueid(), ast_channel_varshead(), ast_format_get_name(), ast_json_channel_vars(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), websocket_pvt::channel, websocket_pvt::connection_id, websocket_pvt::control_msg_format, ast_codec::default_ms, websocket_pvt::native_codec, websocket_pvt::native_format, NULL, websocket_pvt::optimal_frame_size, websocket_pvt::passthrough, websocket_pvt::unbuffered, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_nodata()

static char * _create_event_nodata ( struct websocket_pvt instance,
char *  event 
)
static

Definition at line 201 of file chan_websocket.c.

202{
203 char *payload = NULL;
205 struct ast_json * msg = ast_json_pack("{ s:s s:s }",
206 "event", event,
207 "channel_id", ast_channel_uniqueid(instance->channel));
208 if (!msg) {
209 return NULL;
210 }
212 ast_json_unref(msg);
213 } else {
214 payload = ast_strdup(event);
215 }
216
217 return payload;
218}

References ast_channel_uniqueid(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), ast_strdup, websocket_pvt::channel, websocket_pvt::control_msg_format, NULL, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_STATUS()

static char * _create_event_STATUS ( struct websocket_pvt instance)
static

Definition at line 362 of file chan_websocket.c.

363{
364 char *payload = NULL;
365
367 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:i, s:i, s:i, s:b, s:b, s:b }",
368 "event", "STATUS",
369 "channel_id", ast_channel_uniqueid(instance->channel),
370 "queue_length", instance->frame_queue_length,
371 "xon_level", QUEUE_LENGTH_XON_LEVEL,
372 "xoff_level", QUEUE_LENGTH_XOFF_LEVEL,
373 "queue_full", instance->queue_full,
374 "bulk_media", instance->bulk_media_in_progress,
375 "media_paused", instance->queue_paused
376 );
377 if (!msg) {
378 return NULL;
379 }
381 ast_json_unref(msg);
382 } else {
383 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",
384 "STATUS",
385 ast_channel_uniqueid(instance->channel),
388 S_COR(instance->queue_full, "true", "false"),
389 S_COR(instance->bulk_media_in_progress, "true", "false"),
390 S_COR(instance->queue_paused, "true", "false")
391 );
392 }
393
394 return payload;
395}

References ast_asprintf, ast_channel_uniqueid(), AST_JSON_COMPACT, ast_json_dump_string_format(), ast_json_pack(), ast_json_unref(), websocket_pvt::bulk_media_in_progress, websocket_pvt::channel, websocket_pvt::control_msg_format, websocket_pvt::frame_queue_length, NULL, websocket_pvt::queue_full, QUEUE_LENGTH_XOFF_LEVEL, QUEUE_LENGTH_XON_LEVEL, websocket_pvt::queue_paused, S_COR, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _websocket_request_hangup()

static void _websocket_request_hangup ( struct websocket_pvt instance,
int  ast_cause,
enum ast_websocket_status_code  tech_cause,
int  line,
const char *  function 
)
static

Definition at line 1262 of file chan_websocket.c.

1264{
1265 if (!instance || !instance->channel) {
1266 return;
1267 }
1268 ast_debug(3, "%s:%s: Hangup requested from %s line %d. cause: %s(%d) tech_cause: %s(%d)",
1269 ast_channel_name(instance->channel), instance->remote_addr,
1270 function, line,
1271 ast_cause2str(ast_cause), ast_cause, ast_websocket_status_to_str(tech_cause), tech_cause);
1272
1273 if (tech_cause) {
1274 ast_channel_tech_hangupcause_set(instance->channel, tech_cause);
1275 }
1276 ast_queue_hangup_with_cause(instance->channel, ast_cause);
1277}

References ast_cause2str(), ast_channel_name(), ast_channel_tech_hangupcause_set(), ast_debug, ast_queue_hangup_with_cause(), ast_websocket_status_to_str(), websocket_pvt::channel, and websocket_pvt::remote_addr.

◆ AST_MODULE_SELF_SYM()

struct ast_module * AST_MODULE_SELF_SYM ( void  )

Definition at line 2237 of file chan_websocket.c.

◆ control_msg_format_from_str()

static enum webchan_control_msg_format control_msg_format_from_str ( const char *  value)
static

◆ control_msg_format_to_str()

static const char * control_msg_format_to_str ( enum webchan_control_msg_format  value)
static

Definition at line 188 of file chan_websocket.c.

189{
191 return NULL;
192 }
193 return msg_format_map[value];
194}
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition utils.h:727

References ARRAY_IN_BOUNDS, msg_format_map, NULL, and value.

Referenced by global_apply(), and global_control_message_format_to_str().

◆ create_frame_from_buffer()

static struct ast_frame * create_frame_from_buffer ( struct websocket_pvt instance,
char *  buffer,
size_t  len 
)
static

Definition at line 575 of file chan_websocket.c.

577{
578 struct ast_frame fr = { 0, };
579 struct ast_frame *duped_frame = NULL;
580
581 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
583 fr.subclass.format = instance->native_format;
584 if (instance->native_codec->samples_count) {
585 fr.samples = instance->native_codec->samples_count(&fr);
586 }
587
588 duped_frame = ast_frisolate(&fr);
589 if (!duped_frame) {
590 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
591 ast_channel_name(instance->channel));
592 return NULL;
593 }
594
595 return duped_frame;
596}

References ast_channel_name(), AST_FRAME_SET_BUFFER, AST_FRAME_VOICE, ast_frisolate, ast_log, websocket_pvt::channel, ast_frame_subclass::format, ast_frame::frametype, len(), LOG_WARNING, websocket_pvt::native_codec, websocket_pvt::native_format, NULL, ast_frame::samples, ast_codec::samples_count, and ast_frame::subclass.

Referenced by queue_frame_from_buffer(), and webchan_read().

◆ dequeue_frame()

static struct ast_frame * dequeue_frame ( struct websocket_pvt instance)
static

Definition at line 474 of file chan_websocket.c.

475{
476 struct ast_frame *queued_frame = NULL;
477 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
479
480 /*
481 * If the queue is paused, don't read a frame.
482 */
483 if (instance->queue_paused) {
484 return NULL;
485 }
486
487 /*
488 * We need to check if we need to send an XON before anything
489 * else because there are multiple escape paths in this function
490 * and we don't want to accidentally keep the queue in a "full"
491 * state.
492 */
493 if (instance->queue_full && instance->frame_queue_length < QUEUE_LENGTH_XON_LEVEL) {
494 instance->queue_full = 0;
495 ast_debug(4, "%s: WebSocket sending MEDIA_XON\n",
496 ast_channel_name(instance->channel));
497 send_event(instance, MEDIA_XON);
498 }
499
500 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
501
502 /*
503 * If there are no frames in the queue, we need to return NULL.
504 * We also need to send the QUEUE_DRAINED notification if we
505 * were requested to do so.
506 */
507 if (!queued_frame) {
508 if (instance->report_queue_drained) {
509 instance->report_queue_drained = 0;
510 ast_debug(4, "%s: WebSocket sending QUEUE_DRAINED\n",
511 ast_channel_name(instance->channel));
512 send_event(instance, QUEUE_DRAINED);
513 }
514 return NULL;
515 }
516
517 /*
518 * The only way a control frame could be present here is as
519 * a result of us calling queue_option_frame() in response
520 * to an incoming TEXT command from the websocket.
521 * We'll be safe and make sure it's a AST_CONTROL_OPTION
522 * frame anyway.
523 *
524 * It's quite possible that there are multiple control frames
525 * in a row in the queue so we need to process consecutive ones
526 * immediately.
527 *
528 * In any case, processing a control frame MUST not use up
529 * a media timeslot so after all control frames have been
530 * processed, we need to read an audio frame and process it.
531 */
532 while (queued_frame && queued_frame->frametype == AST_FRAME_CONTROL) {
533 if (queued_frame->subclass.integer == AST_CONTROL_OPTION) {
534 /*
535 * We just need to send the data to the websocket.
536 * The data should already be NULL terminated.
537 */
538 int res = ast_websocket_write_string(instance->websocket,
539 queued_frame->data.ptr);
540 if (res != 0) {
541 ast_log(LOG_ERROR, "%s: Unable to send event %s\n",
542 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
543 } else {
544 ast_debug(4, "%s: Sent %s\n",
545 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
546 }
547 }
548 /*
549 * We do NOT send these to the core so we need to free
550 * the frame and grab the next one. If it's also a
551 * control frame, we need to process it otherwise
552 * continue down in the function.
553 */
554 ast_frame_free(queued_frame, 0);
555 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
556 /*
557 * Jut FYI... We didn't bump the queue length when we added the control
558 * frames so we don't need to decrement it here.
559 */
560 }
561
562 /*
563 * If, after reading all control frames, there are no frames
564 * left in the queue, we need to return NULL.
565 */
566 if (!queued_frame) {
567 return NULL;
568 }
569
570 instance->frame_queue_length--;
571
572 return queued_frame;
573}

References ast_channel_name(), AST_CONTROL_OPTION, ast_debug, AST_FRAME_CONTROL, ast_frame_free(), AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log, ast_websocket_write_string(), websocket_pvt::channel, ast_frame::data, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, ast_frame::frametype, ast_frame_subclass::integer, LOG_ERROR, NULL, ast_frame::ptr, websocket_pvt::queue_full, QUEUE_LENGTH_XON_LEVEL, websocket_pvt::queue_paused, websocket_pvt::report_queue_drained, SCOPED_LOCK, send_event, ast_frame::subclass, and websocket_pvt::websocket.

Referenced by webchan_read().

◆ global_alloc()

static void * global_alloc ( const char *  name)
static

Definition at line 2095 of file chan_websocket.c.

2096{
2098 sizeof(*cfg), NULL);
2099
2100 if (!cfg) {
2101 return NULL;
2102 }
2103
2104 return cfg;
2105}

References ast_sorcery_generic_alloc(), and NULL.

Referenced by load_config().

◆ global_apply()

static int global_apply ( const struct ast_sorcery sorcery,
void *  obj 
)
static

Definition at line 2107 of file chan_websocket.c.

2108{
2109 struct webchan_conf_global *cfg = obj;
2110
2111 ast_debug(1, "control_msg_format: %s\n",
2113
2114 if (cfg->write_timeout <= 0) {
2115 ast_log(LOG_WARNING, "The write_timeout parameter must be > 0\n");
2116 return -1;
2117 }
2118
2119 return 0;
2120}

References ast_debug, ast_log, webchan_conf_global::control_msg_format, control_msg_format_to_str(), LOG_WARNING, and webchan_conf_global::write_timeout.

Referenced by load_config().

◆ global_control_message_format_from_str()

static int global_control_message_format_from_str ( const struct aco_option opt,
struct ast_variable var,
void *  obj 
)
static

Definition at line 2069 of file chan_websocket.c.

2071{
2072 struct webchan_conf_global *cfg = obj;
2073
2075
2077 ast_log(LOG_ERROR, "chan_websocket.conf: Invalid value '%s' for "
2078 "control_mesage_format. Must be 'plain-text' or 'json'\n",
2079 var->value);
2080 return -1;
2081 }
2082
2083 return 0;
2084}

References ast_log, webchan_conf_global::control_msg_format, control_msg_format_from_str(), LOG_ERROR, var, and WEBCHAN_CONTROL_MSG_FORMAT_INVALID.

◆ global_control_message_format_to_str()

static int global_control_message_format_to_str ( const void *  obj,
const intptr_t *  args,
char **  buf 
)
static

Definition at line 2086 of file chan_websocket.c.

2087{
2088 const struct webchan_conf_global *cfg = obj;
2089
2091
2092 return 0;
2093}

References ast_strdup, buf, webchan_conf_global::control_msg_format, and control_msg_format_to_str().

◆ handle_command()

static int handle_command ( struct websocket_pvt instance,
char *  buffer 
)
static

Definition at line 765 of file chan_websocket.c.

766{
767 int res = 0;
768 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
769 const char *command = NULL;
770 char *data = NULL;
771
773 struct ast_json_error json_error;
774
775 json = ast_json_load_buf(buffer, strlen(buffer), &json_error);
776 if (!json) {
777 send_event(instance, ERROR, "Unable to parse JSON command");
778 return -1;
779 }
780 command = ast_json_object_string_get(json, "command");
781 } else {
782 command = buffer;
783 data = strchr(buffer, ' ');
784 if (data) {
785 *data = '\0';
786 data++;
787 }
788 }
789
790 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
792
793 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
795
796 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
797 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
799 if (instance->bulk_media_in_progress) {
800 send_event(instance, ERROR, "START_MEDIA_BUFFERING can't be called when media buffering is already active.\n");
801 return 0;
802 }
803
804 AST_LIST_LOCK(&instance->frame_queue);
805 instance->bulk_media_in_progress = 1;
806 instance->last_unbuffered = instance->unbuffered;
807 instance->unbuffered = 0;
808 AST_LIST_UNLOCK(&instance->frame_queue);
809
810 } else if (ast_strings_equal(command, STOP_MEDIA_BUFFERING)) {
811 const char *id;
812 char *option;
813 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
815
817 id = ast_json_object_string_get(json, "correlation_id");
818 } else {
819 id = data;
820 }
821
822 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
824
825 if (!instance->bulk_media_in_progress) {
826 send_event(instance, ERROR, "STOP_MEDIA_BUFFERING can't be called when media buffering isn't active.\n");
827 return 0;
828 }
829
830 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
832 (int)instance->leftover_len);
833
834 instance->bulk_media_in_progress = 0;
835 instance->unbuffered = instance->last_unbuffered;
836 if (instance->leftover_len > 0) {
837 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
838 if (res != 0) {
839 return res;
840 }
841 }
842 instance->leftover_len = 0;
843 option = create_event(instance, MEDIA_BUFFERING_COMPLETED, id);
844 if (!option) {
845 return -1;
846 }
847 res = queue_option_frame(instance, option);
848 ast_free(option);
849
850 } else if (ast_strings_equal(command, MARK_MEDIA)) {
851 const char *id;
852 char *option;
853 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
855
857
859 id = ast_json_object_string_get(json, "correlation_id");
860 } else {
861 id = data;
862 }
863
864 ast_debug(4, "%s: %s %s\n",
865 ast_channel_name(instance->channel), MARK_MEDIA, id);
866
867 option = create_event(instance, MEDIA_MARK_PROCESSED, id);
868 if (!option) {
869 return -1;
870 }
871 res = queue_option_frame(instance, option);
872 ast_free(option);
873
874 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
875 struct ast_frame *frame = NULL;
876
877 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
878 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
879
880 AST_LIST_LOCK(&instance->frame_queue);
881 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
882 ast_frfree(frame);
883 }
884 instance->frame_queue_length = 0;
885 instance->bulk_media_in_progress = 0;
886 instance->leftover_len = 0;
887 instance->unbuffered = instance->last_unbuffered;
888 AST_LIST_UNLOCK(&instance->frame_queue);
889
890 } else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
891 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
892
893 AST_LIST_LOCK(&instance->frame_queue);
894 instance->report_queue_drained = 1;
895 AST_LIST_UNLOCK(&instance->frame_queue);
896
897 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
898 return send_event(instance, STATUS);
899
900 } else if (ast_strings_equal(command, PAUSE_MEDIA)) {
901 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
902 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
904 AST_LIST_LOCK(&instance->frame_queue);
905 instance->queue_paused = 1;
906 AST_LIST_UNLOCK(&instance->frame_queue);
907
908 } else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
909 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
910 ERROR_ON_UNBUFFERED_MODE_RTN(instance, command);
912 AST_LIST_LOCK(&instance->frame_queue);
913 instance->queue_paused = 0;
914 AST_LIST_UNLOCK(&instance->frame_queue);
915
916 } else if (ast_strings_equal(command, SET_MEDIA_DIRECTION)) {
917 const char *direction;
918
919 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
920
922 send_event(instance, ERROR, "%s only supports JSON format.\n", command);
923 return 0;
924 }
925
926 direction = ast_json_object_string_get(json, "direction");
927 if (!direction) {
928 send_event(instance, ERROR, "%s requires a 'direction' parameter.\n", command);
929 return 0;
930 }
931
932 if (!strcmp("both", direction)) {
934 return 0;
935 }
936
937 if (!instance->timer) {
938 set_channel_timer(instance);
940 }
941
943
944 } else if (!strcmp("out", direction)) {
946 return 0;
947 }
948
949 if (!instance->timer) {
950 set_channel_timer(instance);
952 }
953
955
956 } else if (!strcmp("in", direction)) {
958 return 0;
959 }
960
961 if (instance->timer) {
963 ast_timer_close(instance->timer);
964 instance->timer = NULL;
966 }
967
969
970 } else {
971 send_event(instance, ERROR, "'%s' is not a valid direction for %s.\n",
972 direction, command);
973 return 0;
974 }
975
976 } else {
977 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
978 ast_channel_name(instance->channel), command);
979 }
980
981 return res;
982}

References ANSWER_CHANNEL, AST_CAUSE_NORMAL, ast_channel_internal_fd_clear(), ast_channel_name(), AST_CONTROL_ANSWER, ast_debug, ast_free, ast_frfree, ast_json_load_buf(), ast_json_object_string_get, ast_json_unref(), AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log, ast_null_frame, ast_queue_control(), ast_queue_frame(), ast_strings_equal(), ast_timer_close(), AST_WEBSOCKET_STATUS_NORMAL, websocket_pvt::bulk_media_in_progress, websocket_pvt::channel, CONTINUE_MEDIA, websocket_pvt::control_msg_format, create_event, ast_frame::data, ERROR_ON_INVALID_MEDIA_DIRECTION_RTN, ERROR_ON_PASSTHROUGH_MODE_RTN, ERROR_ON_UNBUFFERED_MODE_RTN, FLUSH_MEDIA, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, GET_DRIVER_STATUS, HANGUP_CHANNEL, id, websocket_pvt::last_unbuffered, websocket_pvt::leftover_data, websocket_pvt::leftover_len, LOG_WARNING, MARK_MEDIA, websocket_pvt::media_direction, NULL, PAUSE_MEDIA, queue_frame_from_buffer(), queue_option_frame(), websocket_pvt::queue_paused, RAII_VAR, websocket_pvt::report_queue_drained, REPORT_QUEUE_DRAINED, SCOPED_LOCK, send_event, set_channel_timer(), SET_MEDIA_DIRECTION, START_MEDIA_BUFFERING, STOP_MEDIA_BUFFERING, websocket_pvt::timer, websocket_pvt::unbuffered, WEBCHAN_CONTROL_MSG_FORMAT_JSON, WEBCHAN_MEDIA_DIRECTION_BOTH, WEBCHAN_MEDIA_DIRECTION_IN, WEBCHAN_MEDIA_DIRECTION_OUT, websocket_request_hangup, and WS_TIMER_FDNO.

Referenced by process_text_message().

◆ incoming_ws_established_cb()

static void incoming_ws_established_cb ( struct ast_websocket ast_ws_session,
struct ast_variable get_params,
struct ast_variable upgrade_headers 
)
static

Definition at line 1912 of file chan_websocket.c.

1914{
1915 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1916 struct ast_variable *v;
1917 const char *connection_id = NULL;
1918 struct websocket_pvt *instance = NULL;
1919 struct webchan_conf_global *global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1920 int global_write_timeout = global_cfg ? global_cfg->write_timeout : AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
1921
1922 ao2_cleanup(global_cfg);
1923
1924 ast_debug(3, "WebSocket established\n");
1925
1926 for (v = upgrade_headers; v; v = v->next) {
1927 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1928 }
1929 for (v = get_params; v; v = v->next) {
1930 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1931 }
1932
1933 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1934 if (!connection_id) {
1935 /*
1936 * This can't really happen because websocket_http_callback won't
1937 * let it get this far if it can't add the connection_id to the
1938 * get_params.
1939 * Just in case though...
1940 */
1941 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1944 return;
1945 }
1946
1947 instance = ao2_weakproxy_find(instances, connection_id, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1948 if (!instance) {
1949 /*
1950 * This also can't really happen because websocket_http_callback won't
1951 * let it get this far if it can't find the instance.
1952 * Just in case though...
1953 */
1954 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1957 return;
1958 }
1959 instance->websocket = ao2_bump(ast_ws_session);
1960
1961 ast_websocket_set_timeout(instance->websocket, global_write_timeout);
1962
1964 ao2_cleanup(instance);
1965 /*
1966 * The instance is the channel's responsibility now.
1967 * We just return here.
1968 */
1969}

References ao2_bump, ao2_cleanup, ao2_weakproxy_find, AST_CAUSE_FAILURE, ast_debug, AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT, ast_log, ast_sorcery_retrieve_by_id(), ast_variable_find_in_list(), ast_websocket_close(), ast_websocket_set_timeout(), AST_WEBSOCKET_STATUS_INTERNAL_ERROR, ast_websocket_unref(), instances, LOG_WARNING, ast_variable::name, ast_variable::next, NULL, OBJ_NOLOCK, OBJ_SEARCH_KEY, RAII_VAR, sorcery, ast_variable::value, websocket_pvt::websocket, websocket_handoff_to_channel(), websocket_request_hangup, and webchan_conf_global::write_timeout.

Referenced by load_module().

◆ incoming_ws_http_callback()

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 
)
static

Definition at line 1981 of file chan_websocket.c.

1985{
1986 struct ast_http_uri fake_urih = {
1988 };
1989 int res = 0;
1990 /*
1991 * Normally the http server will destroy the get_params
1992 * when the session ends but if there weren't any initially
1993 * and we create some and add them to the list, the http server
1994 * won't know about it so we have to destroy it ourselves.
1995 */
1996 int destroy_get_params = (get_params == NULL);
1997 struct ast_variable *v = NULL;
1998 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1999
2000 ast_debug(2, "URI: %s Starting\n", uri);
2001
2002 /*
2003 * The client will have issued the GET request with a URI of
2004 * /media/<connection_id>
2005 *
2006 * Since this callback is registered for the /media URI prefix the
2007 * http server will strip that off the front of the URI passing in
2008 * only the path components after that in the 'uri' parameter.
2009 * This should leave only the connection id without a leading '/'.
2010 */
2011 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
2012 if (!instance) {
2013 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
2014 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
2015 return -1;
2016 }
2017
2018 /*
2019 * We don't allow additional connections using the same connection id.
2020 */
2021 if (instance->websocket) {
2022 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
2023 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
2024 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
2025 return -1;
2026 }
2027
2028 v = ast_variable_new("CONNECTION_ID", uri, "");
2029 if (!v) {
2030 ast_http_error(ser, 500, "Server error", "");
2031 return -1;
2032 }
2033 ast_variable_list_append(&get_params, v);
2034
2035 for (v = get_params; v; v = v->next) {
2036 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
2037 }
2038
2039 /*
2040 * This will ultimately call internal_ws_established_cb() so
2041 * this function will block until the websocket is closed and
2042 * internal_ws_established_cb() returns;
2043 */
2044 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
2045 get_params, headers);
2046 if (destroy_get_params) {
2047 ast_variables_destroy(get_params);
2048 }
2049
2050 ast_debug(2, "URI: %s DONE\n", uri);
2051
2052 return res;
2053}

References ao2_cleanup, ao2_weakproxy_find, ast_channel_name(), ast_debug, ast_http_error(), ast_log, ast_variable_list_append, ast_variable_new, ast_variables_destroy(), ast_websocket_uri_cb(), ast_ws_server, ast_http_uri::data, instances, LOG_WARNING, method, ast_variable::name, ast_variable::next, NULL, OBJ_NOLOCK, OBJ_SEARCH_KEY, RAII_VAR, and ast_variable::value.

◆ instance_proxy_cb()

static void instance_proxy_cb ( void *  weakproxy,
void *  data 
)
static

Definition at line 1443 of file chan_websocket.c.

1444{
1445 struct instance_proxy *proxy = weakproxy;
1446 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
1447 ao2_unlink(instances, weakproxy);
1448}

References ao2_unlink, ast_debug, instance_proxy::connection_id, and instances.

Referenced by websocket_new().

◆ load_config()

static int load_config ( void  )
static

Definition at line 2122 of file chan_websocket.c.

2123{
2124 ast_debug(2, "Initializing Websocket Client Configuration\n");
2126 if (!sorcery) {
2127 ast_log(LOG_ERROR, "Failed to open sorcery\n");
2128 return -1;
2129 }
2130
2131 ast_sorcery_apply_default(sorcery, "global", "config",
2132 "chan_websocket.conf,criteria=type=global,single_object=yes,explicit_name=global");
2133
2135 ast_log(LOG_ERROR, "Failed to register chan_websocket global object with sorcery\n");
2137 sorcery = NULL;
2138 return -1;
2139 }
2140
2141 ast_sorcery_object_field_register_nodoc(sorcery, "global", "type", "", OPT_NOOP_T, 0, 0);
2142 ast_sorcery_register_cust(global, control_message_format, "plain-text");
2143 ast_sorcery_register_int(global, webchan_conf_global, write_timeout, write_timeout,
2145
2147
2148 return 0;
2149}

References ast_debug, AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT, ast_log, ast_sorcery_apply_default, ast_sorcery_load(), ast_sorcery_object_field_register_nodoc, ast_sorcery_object_register, ast_sorcery_open, ast_sorcery_register_cust, ast_sorcery_register_int, ast_sorcery_unref, global, global_alloc(), global_apply(), LOG_ERROR, NULL, OPT_NOOP_T, sorcery, and webchan_conf_global::write_timeout.

Referenced by load_module().

◆ load_module()

static int load_module ( void  )
static

Function called when our module is loaded.

Definition at line 2180 of file chan_websocket.c.

2181{
2182 int res = 0;
2183 struct ast_websocket_protocol *protocol;
2184
2185 res = load_config();
2186 if (res != 0) {
2188 }
2189
2192 }
2193
2196 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
2197 unload_module();
2199 }
2200
2202 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
2203 instance_proxy_sort_fn, instance_proxy_cmp_fn);
2204 if (!instances) {
2206 "Failed to allocate the chan_websocket instance registry\n");
2207 unload_module();
2209 }
2210
2212 if (!ast_ws_server) {
2213 unload_module();
2215 }
2216
2217 protocol = ast_websocket_sub_protocol_alloc("media");
2218 if (!protocol) {
2219 unload_module();
2221 }
2224
2226
2228}

References AO2_ALLOC_OPT_LOCK_RWLOCK, ao2_container_alloc_hash, AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, ast_channel_register(), ast_format_cap_alloc, ast_format_cap_append_by_type(), AST_FORMAT_CAP_FLAG_DEFAULT, ast_http_uri_link(), ast_log, AST_MEDIA_TYPE_UNKNOWN, AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, ast_websocket_server_add_protocol2(), ast_websocket_server_create(), ast_websocket_sub_protocol_alloc(), ast_ws_server, ast_channel_tech::capabilities, http_uri, incoming_ws_established_cb(), instances, load_config(), LOG_ERROR, LOG_WARNING, ast_websocket_protocol::session_established, unload_module(), and websocket_tech.

◆ process_binary_message()

static int process_binary_message ( struct websocket_pvt instance,
char *  payload,
uint64_t  payload_len 
)
static

Definition at line 1017 of file chan_websocket.c.

1019{
1020 char *next_frame_ptr = NULL;
1021 size_t bytes_read = 0;
1022 int res = 0;
1023 size_t bytes_left = 0;
1024
1025 {
1026 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
1028 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
1029 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
1030 ast_channel_name(instance->channel));
1031 return 0;
1032 }
1033 }
1034
1035 next_frame_ptr = payload;
1036 instance->bytes_read += payload_len;
1037
1038 if (instance->unbuffered) {
1039 res = queue_frame_from_buffer(instance, payload, payload_len);
1040 return res;
1041 }
1042
1043 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
1044 /*
1045 * We have leftover data from a previous websocket message.
1046 * Try to make a complete frame by appending data from
1047 * the current message to the leftover data.
1048 */
1049 char *append_ptr = instance->leftover_data + instance->leftover_len;
1050 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
1051 /*
1052 * It's possible that even the current message doesn't have enough
1053 * data to make a complete frame.
1054 */
1055 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
1056
1057 /*
1058 * Append whatever we can to the end of the leftover data
1059 * even if it's not enough to make a complete frame.
1060 */
1061 memcpy(append_ptr, payload, bytes_avail_to_copy);
1062
1063 /*
1064 * If leftover data is still short, just return and wait for the
1065 * next websocket message.
1066 */
1067 if (bytes_avail_to_copy < bytes_needed_for_frame) {
1068 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
1069 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
1070 instance->leftover_len += bytes_avail_to_copy;
1071 return 0;
1072 }
1073
1074 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
1075 if (res < 0) {
1076 return -1;
1077 }
1078
1079 /*
1080 * We stole data from the current payload so decrement payload_len
1081 * and set the next frame pointer after the data in payload
1082 * we just copied.
1083 */
1084 payload_len -= bytes_avail_to_copy;
1085 next_frame_ptr = payload + bytes_avail_to_copy;
1086
1087 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
1088 ast_channel_name(instance->channel),
1089 instance->frame_queue_length,
1090 (int)instance->bytes_read,
1091 (int)(payload_len + bytes_avail_to_copy),
1092 (int)instance->leftover_len,
1093 payload,
1094 next_frame_ptr,
1095 (int)(next_frame_ptr - payload),
1096 (int)payload_len,
1097 (int)bytes_avail_to_copy
1098 );
1099
1100
1101 instance->leftover_len = 0;
1102 }
1103
1104 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
1105 instance->leftover_len = 0;
1106 }
1107
1108 bytes_left = payload_len;
1109 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
1110 res = queue_frame_from_buffer(instance, next_frame_ptr,
1111 instance->optimal_frame_size);
1112 if (res < 0) {
1113 break;
1114 }
1115 bytes_read += instance->optimal_frame_size;
1116 next_frame_ptr += instance->optimal_frame_size;
1117 bytes_left -= instance->optimal_frame_size;
1118 }
1119
1120 if (instance->bulk_media_in_progress && bytes_left > 0) {
1121 /*
1122 * We have a partial frame. Save the leftover data.
1123 */
1124 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
1125 ast_channel_name(instance->channel),
1126 (int)instance->bytes_read,
1127 instance->frame_queue_length,
1128 (int)payload_len,
1129 (int)instance->leftover_len,
1130 payload,
1131 next_frame_ptr,
1132 (int)(next_frame_ptr - payload),
1133 (int)bytes_left
1134 );
1135 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
1136 instance->leftover_len = bytes_left;
1137 }
1138
1139 return 0;
1140}

References ast_channel_name(), ast_debug, AST_LIST_LOCK, AST_LIST_UNLOCK, websocket_pvt::bulk_media_in_progress, websocket_pvt::bytes_read, websocket_pvt::channel, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, websocket_pvt::leftover_data, websocket_pvt::leftover_len, MIN, NULL, websocket_pvt::optimal_frame_size, queue_frame_from_buffer(), QUEUE_LENGTH_MAX, SCOPED_LOCK, and websocket_pvt::unbuffered.

Referenced by read_from_ws_and_queue().

◆ process_text_message()

static int process_text_message ( struct websocket_pvt instance,
char *  payload,
uint64_t  payload_len 
)
static

Definition at line 984 of file chan_websocket.c.

986{
987 char *command;
988
989 if (payload_len == 0) {
990 ast_log(LOG_WARNING, "%s: WebSocket TEXT message has 0 length\n",
991 ast_channel_name(instance->channel));
992 return 0;
993 }
994
995 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
996 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
997 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
998 return 0;
999 }
1000
1001 /*
1002 * Unfortunately, payload is not NULL terminated even when it's
1003 * a TEXT frame so we need to allocate a new buffer, copy
1004 * the data into it, and NULL terminate it.
1005 */
1006 command = ast_alloca(payload_len + 1);
1007 memcpy(command, payload, payload_len); /* Safe */
1008 command[payload_len] = '\0';
1009 command = ast_strip(command);
1010
1011 ast_debug(4, "%s: Received: %s\n",
1012 ast_channel_name(instance->channel), command);
1013
1014 return handle_command(instance, command);
1015}

References ast_alloca, ast_channel_name(), ast_debug, ast_log, ast_strip(), websocket_pvt::channel, handle_command(), LOG_WARNING, and MAX_TEXT_MESSAGE_LEN.

Referenced by read_from_ws_and_queue().

◆ queue_frame_from_buffer()

static int queue_frame_from_buffer ( struct websocket_pvt instance,
char *  buffer,
size_t  len 
)
static

Definition at line 671 of file chan_websocket.c.

673{
674 struct ast_frame *duped_frame = NULL;
675
676 duped_frame = create_frame_from_buffer(instance, buffer, len);
677 if (!duped_frame) {
678 return -1;
679 }
680
681 {
682 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
684 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
685 instance->frame_queue_length++;
686 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
687 instance->queue_full = 1;
688 send_event(instance, MEDIA_XOFF);
689 }
690 }
691
692 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
693 duped_frame->datalen);
694
695 return 0;
696}

References ast_channel_name(), ast_debug, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, websocket_pvt::channel, create_frame_from_buffer(), ast_frame::datalen, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, len(), NULL, websocket_pvt::queue_full, QUEUE_LENGTH_XOFF_LEVEL, SCOPED_LOCK, and send_event.

Referenced by handle_command(), and process_binary_message().

◆ queue_option_frame()

static int queue_option_frame ( struct websocket_pvt instance,
char *  buffer 
)
static

Definition at line 698 of file chan_websocket.c.

700{
701 struct ast_frame fr = { 0, };
702 struct ast_frame *duped_frame = NULL;
703
704 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
707
708 duped_frame = ast_frisolate(&fr);
709 if (!duped_frame) {
710 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
711 ast_channel_name(instance->channel));
712 return -1;
713 }
714
715 AST_LIST_LOCK(&instance->frame_queue);
716 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
717 AST_LIST_UNLOCK(&instance->frame_queue);
718
719 ast_debug(4, "%s: Queued '%s' option frame\n",
720 ast_channel_name(instance->channel), buffer);
721
722 return 0;
723}

References ast_channel_name(), AST_CONTROL_OPTION, ast_debug, AST_FRAME_CONTROL, AST_FRAME_SET_BUFFER, ast_frisolate, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_log, websocket_pvt::channel, websocket_pvt::frame_queue, ast_frame::frametype, ast_frame_subclass::integer, LOG_WARNING, NULL, and ast_frame::subclass.

Referenced by handle_command().

◆ read_from_ws_and_queue()

static int read_from_ws_and_queue ( struct websocket_pvt instance)
static

Definition at line 1142 of file chan_websocket.c.

1143{
1144 uint64_t payload_len = 0;
1145 char *payload = NULL;
1146 enum ast_websocket_opcode opcode;
1147 int fragmented = 0;
1148 int res = 0;
1149
1150 if (!instance->websocket) {
1151 ast_log(LOG_WARNING, "%s: WebSocket session not found\n",
1152 ast_channel_name(instance->channel));
1153 return -1;
1154 }
1155
1156 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
1157 &opcode, &fragmented);
1158
1159 if (res) {
1160 ast_debug(3, "%s: WebSocket read error\n",
1161 ast_channel_name(instance->channel));
1163 return -1;
1164 }
1165 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
1166 (int)payload_len);
1167
1168 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
1169 return process_text_message(instance, payload, payload_len);
1170 }
1171
1172 /*
1173 * PINGs and PONGs will have been handled by res_http_websocket.
1174 * We also need to ignore CONTINUATION frames as they will be accumulated
1175 * by res_http_websocket until the threshold set in websocket_handoff_to_channel()
1176 * is reached, then it will send us a TEXT or BINARY frame.
1177 */
1178 if (opcode == AST_WEBSOCKET_OPCODE_PING || opcode == AST_WEBSOCKET_OPCODE_PONG
1179 || opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
1180 return 0;
1181 }
1182
1183 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
1184 ast_debug(3, "%s: WebSocket closed by remote\n",
1185 ast_channel_name(instance->channel));
1187 return -1;
1188 }
1189
1190 if (opcode == AST_WEBSOCKET_OPCODE_BINARY) {
1191 /* If the application's media direction is 'in', drop any media we receive from it */
1193 ast_debug(5, "%s: WebSocket dropped frame (application media direction is 'in')\n",
1194 ast_channel_name(instance->channel));
1195 return 0;
1196 }
1197 } else {
1198 ast_log(LOG_WARNING, "%s: WebSocket frame type %d not supported\n",
1199 ast_channel_name(instance->channel), (int)opcode);
1201 return 0;
1202 }
1203
1204 return process_binary_message(instance, payload, payload_len);
1205}

References AST_CAUSE_FAILURE, AST_CAUSE_NETWORK_OUT_OF_ORDER, AST_CAUSE_NORMAL, ast_channel_name(), ast_debug, ast_log, AST_WEBSOCKET_OPCODE_BINARY, AST_WEBSOCKET_OPCODE_CLOSE, AST_WEBSOCKET_OPCODE_CONTINUATION, AST_WEBSOCKET_OPCODE_PING, AST_WEBSOCKET_OPCODE_PONG, AST_WEBSOCKET_OPCODE_TEXT, ast_websocket_read(), AST_WEBSOCKET_STATUS_GOING_AWAY, AST_WEBSOCKET_STATUS_UNSUPPORTED_DATA, websocket_pvt::channel, LOG_WARNING, websocket_pvt::media_direction, NULL, process_binary_message(), process_text_message(), WEBCHAN_MEDIA_DIRECTION_IN, websocket_pvt::websocket, and websocket_request_hangup.

Referenced by webchan_read().

◆ reload_module()

static int reload_module ( void  )
static

Definition at line 2171 of file chan_websocket.c.

2172{
2173 ast_debug(2, "Reloading chan_websocket configuration\n");
2175
2176 return 0;
2177}

References ast_debug, ast_sorcery_reload(), and sorcery.

◆ set_channel_timer()

static int set_channel_timer ( struct websocket_pvt instance)
static

Definition at line 1555 of file chan_websocket.c.

1556{
1557 int rate = 0;
1558 instance->timer = ast_timer_open();
1559 if (!instance->timer) {
1560 return -1;
1561 }
1562 /* Rate is the number of ticks per second, not the interval. */
1563 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1564 ast_debug(3, "%s: WebSocket timer rate %d\n",
1565 ast_channel_name(instance->channel), rate);
1566 ast_timer_set_rate(instance->timer, rate);
1567 /*
1568 * Calling ast_channel_set_fd will cause the channel thread to call
1569 * webchan_read at 'rate' times per second.
1570 */
1572
1573 return 0;
1574}

References ast_channel_name(), ast_channel_set_fd(), ast_debug, ast_format_get_default_ms(), ast_timer_fd(), ast_timer_open(), ast_timer_set_rate(), websocket_pvt::channel, websocket_pvt::native_format, websocket_pvt::timer, and WS_TIMER_FDNO.

Referenced by handle_command(), and webchan_request().

◆ set_channel_variables()

static int set_channel_variables ( struct websocket_pvt instance)
static

Definition at line 1576 of file chan_websocket.c.

1577{
1578 char *pkt_size = NULL;
1579 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1580 if (res <= 0) {
1581 return -1;
1582 }
1583
1585 pkt_size);
1586 ast_free(pkt_size);
1588 instance->connection_id);
1589
1590 return 0;
1591}

References ast_asprintf, ast_free, websocket_pvt::channel, websocket_pvt::connection_id, MEDIA_WEBSOCKET_CONNECTION_ID, MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE, NULL, websocket_pvt::optimal_frame_size, and pbx_builtin_setvar_helper().

Referenced by webchan_request().

◆ unload_module()

static int unload_module ( void  )
static

◆ validate_uri_parameters()

static int validate_uri_parameters ( const char *  uri_params)
static

Definition at line 1593 of file chan_websocket.c.

1594{
1595 char *params = ast_strdupa(uri_params);
1596 char *nvp = NULL;
1597 char *nv = NULL;
1598
1599 /*
1600 * uri_params should be a comma-separated list of key=value pairs.
1601 * For example:
1602 * name1=value1,name2=value2
1603 * We're verifying that each name and value either doesn't need
1604 * to be encoded or that it already is.
1605 */
1606
1607 while((nvp = ast_strsep(&params, ',', 0))) {
1608 /* nvp will be name1=value1 */
1609 while((nv = ast_strsep(&nvp, '=', 0))) {
1610 /* nv will be either name1 or value1 */
1611 if (!ast_uri_verify_encoded(nv)) {
1612 return 0;
1613 }
1614 }
1615 }
1616
1617 return 1;
1618}

References ast_strdupa, ast_strsep(), ast_uri_verify_encoded(), NULL, and websocket_pvt::uri_params.

Referenced by webchan_request().

◆ webchan_call()

static int webchan_call ( struct ast_channel ast,
const char *  dest,
int  timeout 
)
static

Definition at line 1329 of file chan_websocket.c.

1331{
1332 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1334 struct webchan_conf_global *global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1335 int global_write_timeout = global_cfg ? global_cfg->write_timeout : AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
1336
1337 ao2_cleanup(global_cfg);
1338
1339 if (!instance) {
1340 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
1341 ast_channel_name(ast));
1343 return -1;
1344 }
1345
1346 if (instance->type == AST_WS_TYPE_SERVER) {
1347 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
1348 return 0;
1349 }
1350 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
1351
1352 if (!instance->client) {
1353 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
1354 ast_channel_name(ast));
1356 return -1;
1357 }
1358
1359 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
1360 ast_channel_name(ast), dest, instance->connection_id);
1361
1362 if (!ast_strlen_zero(instance->uri_params)) {
1364 }
1365
1366 instance->websocket = ast_websocket_client_connect(instance->client,
1367 instance, ast_channel_name(ast), &result);
1368 if (!instance->websocket || result != WS_OK) {
1369 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
1372 return -1;
1373 }
1374
1375 /*
1376 * If websocket_client->write_timeout was set in websocket_client.conf, it will
1377 * have been applied to the websocket by ast_websocket_client_connect() above.
1378 * If it wasn't set in websocket_client.conf, the value will be INT_MAX and
1379 * and ast_websocket_client_connect() will have set AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT
1380 * on the websocket. However, the user may have set write_timeout in the global section
1381 * of chan_websocket.conf so if it wasn't set in websocket_client.conf, we'll now set
1382 * the websocket timeout to that. If they haven't set it in chan_websocket.conf either,
1383 * it'll default to AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT as well so the call below will
1384 * basically become a no-op.
1385 */
1386 if (instance->client->write_timeout == INT_MAX) {
1387 ast_websocket_set_timeout(instance->websocket, global_write_timeout);
1388 }
1389
1390 return websocket_handoff_to_channel(instance);
1391}

References ao2_cleanup, AST_CAUSE_FAILURE, AST_CAUSE_NO_ROUTE_DESTINATION, ast_channel_hangupcause_set(), ast_channel_name(), ast_channel_tech_pvt(), ast_debug, AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT, ast_log, ast_sorcery_retrieve_by_id(), ast_strlen_zero(), ast_websocket_client_add_uri_params(), ast_websocket_client_connect(), ast_websocket_result_to_str(), ast_websocket_set_timeout(), AST_WS_TYPE_SERVER, websocket_pvt::channel, websocket_pvt::client, websocket_pvt::connection_id, LOG_WARNING, result, sorcery, websocket_pvt::type, websocket_pvt::uri_params, websocket_pvt::websocket, websocket_handoff_to_channel(), webchan_conf_global::write_timeout, ast_websocket_client::write_timeout, and WS_OK.

◆ webchan_hangup()

static int webchan_hangup ( struct ast_channel ast)
static

Definition at line 1869 of file chan_websocket.c.

1870{
1871 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1872
1873 if (!instance) {
1874 return -1;
1875 }
1876 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1877 ast_channel_name(ast), instance->connection_id);
1878
1879 if (instance->websocket) {
1881 ast_websocket_unref(instance->websocket);
1882 instance->websocket = NULL;
1883 }
1885
1886 /* Clean up the reference from adding the instance to the channel */
1887 ao2_cleanup(instance);
1888
1889 return 0;
1890}

References ao2_cleanup, ast_channel_name(), ast_channel_tech_hangupcause(), ast_channel_tech_pvt(), ast_channel_tech_pvt_set(), ast_debug, ast_websocket_close(), ast_websocket_unref(), websocket_pvt::connection_id, NULL, and websocket_pvt::websocket.

◆ webchan_read()

static struct ast_frame * webchan_read ( struct ast_channel ast)
static

Definition at line 623 of file chan_websocket.c.

624{
625 struct websocket_pvt *instance = NULL;
626 struct ast_frame *native_frame = NULL;
627 int fdno = ast_channel_fdno(ast);
628
629 instance = ast_channel_tech_pvt(ast);
630 if (!instance) {
631 return NULL;
632 }
633
634 if (fdno != WS_TIMER_FDNO && fdno != WS_WEBSOCKET_FDNO) {
635 return &ast_null_frame;
636 }
637
638 if (fdno == WS_WEBSOCKET_FDNO) {
639 read_from_ws_and_queue(instance);
640 if (!instance->unbuffered) {
641 return &ast_null_frame;
642 }
643 } else {
645 ast_timer_ack(instance->timer, 1);
646 }
647 }
648
649 native_frame = dequeue_frame(instance);
650 if (!native_frame) {
651 if (instance->leftover_len > 0) {
652 native_frame = create_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
653 if (native_frame) {
654 ast_debug(4, "%s: Triggered with no frame available but with %d bytes in leftover_data. Returning partial frame.\n",
655 ast_channel_name(ast), (int)instance->leftover_len);
656 instance->leftover_len = 0;
657 return native_frame;
658 }
659 }
660 ast_debug(4, "%s: Triggered with no frame available and no data in leftover_data. Returning NULL frame.\n",
661 ast_channel_name(ast));
662 return &ast_null_frame;
663 }
664
665 ast_debug(5, "%s: Dequeued %d byte frame. Left in buffer: %d\n",
666 ast_channel_name(ast), native_frame->datalen, (int)instance->leftover_len);
667
668 return native_frame;
669}

References ast_channel_fdno(), ast_channel_name(), ast_channel_tech_pvt(), ast_debug, ast_null_frame, ast_timer_ack(), ast_timer_get_event(), AST_TIMING_EVENT_EXPIRED, create_frame_from_buffer(), ast_frame::datalen, dequeue_frame(), websocket_pvt::leftover_data, websocket_pvt::leftover_len, NULL, read_from_ws_and_queue(), websocket_pvt::timer, websocket_pvt::unbuffered, WS_TIMER_FDNO, and WS_WEBSOCKET_FDNO.

◆ webchan_request()

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

Definition at line 1651 of file chan_websocket.c.

1654{
1655 char *parse;
1656 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1657 struct ast_channel *chan = NULL;
1658 struct ast_format *fmt = NULL;
1659 struct ast_format_cap *caps = NULL;
1661 AST_APP_ARG(connection_id);
1663 );
1664 struct ast_flags opts = { 0, };
1665 char *opt_args[OPT_ARG_ARRAY_SIZE];
1666 const char *requestor_name = requestor ? ast_channel_name(requestor) :
1667 (assignedids && !ast_strlen_zero(assignedids->uniqueid) ? assignedids->uniqueid : "<unknown>");
1668 RAII_VAR(struct webchan_conf_global *, global_cfg, NULL, ao2_cleanup);
1669
1670 global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1671
1672 ast_debug(3, "%s: WebSocket channel requested\n",
1673 requestor_name);
1674
1675 if (ast_strlen_zero(data)) {
1676 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1677 requestor_name);
1678 goto failure;
1679 }
1680 parse = ast_strdupa(data);
1681 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1682
1683 if (ast_strlen_zero(args.connection_id)) {
1684 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1685 requestor_name);
1686 goto failure;
1687 }
1688
1689 if (!ast_strlen_zero(args.options)
1690 && ast_app_parse_options(websocket_options, &opts, opt_args,
1691 ast_strdupa(args.options))) {
1692 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1693 requestor_name, args.options);
1694 goto failure;
1695 }
1696
1697 if (ast_test_flag(&opts, OPT_WS_CODEC)
1698 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1699 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1700 } else {
1701 /*
1702 * If codec wasn't specified in the dial string,
1703 * use the first format in the capabilities.
1704 */
1705 fmt = ast_format_cap_get_format(cap, 0);
1706 }
1707
1708 if (!fmt) {
1709 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1710 requestor_name, args.connection_id);
1711 goto failure;
1712 }
1713
1714 ast_debug(3, "%s: Using format %s from %s\n",
1715 requestor_name, ast_format_get_name(fmt),
1716 ast_test_flag(&opts, OPT_WS_CODEC) ? "dialstring" : "requester");
1717
1718 instance = websocket_new(requestor_name, args.connection_id, fmt);
1719 if (!instance) {
1720 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1721 requestor_name);
1722 goto failure;
1723 }
1724
1725 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_BOTH;
1727 if (!strcmp("both", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1728 /* The default. Don't need to do anything here other than
1729 * ensure it is an allowed value. */
1730 } else if (!strcmp("out", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1731 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_OUT;
1732 } else if (!strcmp("in", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1733 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_IN;
1734 } else {
1735 ast_log(LOG_ERROR, "Unrecognized option for media direction: '%s'.\n",
1736 opt_args[OPT_ARG_WS_MEDIA_DIRECTION]);
1737 goto failure;
1738 }
1739 }
1740
1741 instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
1742
1743 /*
1744 * Passthrough requires unbuffered. If passthrough was forced by choice of
1745 * codec, unbuffered will already have been set. If passthrough was set by
1746 * the dialstring option, we need to force unbuffered.
1747 */
1748 if (!instance->passthrough) {
1749 instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
1750 instance->unbuffered = instance->passthrough;
1751 }
1752 /*
1753 * If unbuffered hasn't been forced by passthrough, set it according to the
1754 * dialstring "u" option.
1755 */
1756 if (!instance->unbuffered) {
1757 instance->unbuffered = ast_test_flag(&opts, OPT_WS_UNBUFFERED);
1758 }
1759
1761 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1762 char *comma;
1763
1764 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1766 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1767 requestor_name);
1768 goto failure;
1769 }
1770
1771 ast_debug(3, "%s: Using URI parameters '%s'\n",
1772 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1773
1775 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1776 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1777 args.connection_id);
1778 goto failure;
1779 }
1780
1781 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1782 comma = instance->uri_params;
1783 /*
1784 * The normal separator for query string components is an
1785 * ampersand ('&') but the Dial app interprets them as additional
1786 * channels to dial in parallel so we instruct users to separate
1787 * the parameters with commas (',') instead. We now have to
1788 * convert those commas back to ampersands.
1789 */
1790 while ((comma = strchr(comma,','))) {
1791 *comma = '&';
1792 }
1793 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1794 }
1795
1796 if (ast_test_flag(&opts, OPT_WS_MSG_FORMAT)) {
1797 instance->control_msg_format = control_msg_format_from_str(opt_args[OPT_ARG_WS_MSG_FORMAT]);
1798
1799 if (instance->control_msg_format == WEBCHAN_CONTROL_MSG_FORMAT_INVALID) {
1800 ast_log(LOG_WARNING, "%s: 'f/control message format' dialstring parameter value missing or invalid. "
1801 "Defaulting to 'plain-text'\n",
1802 requestor_name);
1803 instance->control_msg_format = WEBCHAN_CONTROL_MSG_FORMAT_PLAIN;
1804 }
1805 } else if (global_cfg) {
1806 instance->control_msg_format = global_cfg->control_msg_format;
1807 }
1808
1809 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1810 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1811 if (!chan) {
1812 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", requestor_name);
1813 goto failure;
1814 }
1815
1816 /* Prevent device state caching as this channel involves ephemeral destinations or sources */
1818 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1819 ast_channel_name(chan), requestor_name,
1820 instance->connection_id);
1821
1822 instance->channel = ao2_bump(chan);
1823 ast_channel_tech_set(instance->channel, &websocket_tech);
1824
1825 /* If the application's media direction is 'both' or 'out', we need the channel timer. */
1826 if (instance->media_direction != WEBCHAN_MEDIA_DIRECTION_IN
1827 && set_channel_timer(instance) != 0) {
1828 goto failure;
1829 }
1830
1831 if (set_channel_variables(instance) != 0) {
1832 goto failure;
1833 }
1834
1836 if (!caps) {
1837 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1838 goto failure;
1839 }
1840
1841 ast_format_cap_append(caps, instance->native_format, 0);
1842 ast_channel_nativeformats_set(instance->channel, caps);
1843 ast_channel_set_writeformat(instance->channel, instance->native_format);
1844 ast_channel_set_rawwriteformat(instance->channel, instance->native_format);
1845 ast_channel_set_readformat(instance->channel, instance->native_format);
1846 ast_channel_set_rawreadformat(instance->channel, instance->native_format);
1847 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1848 ast_channel_unlock(chan);
1849 ao2_cleanup(caps);
1850
1851 ast_debug(3, "%s: WebSocket channel created to %s\n",
1852 ast_channel_name(chan), args.connection_id);
1853
1854 return chan;
1855
1856failure:
1857 if (chan) {
1858 ast_channel_unlock(chan);
1859 }
1860 *cause = AST_CAUSE_FAILURE;
1861 return NULL;
1862}

References ao2_bump, ao2_cleanup, args, AST_APP_ARG, ast_app_parse_options(), AST_CAUSE_FAILURE, ast_channel_alloc, ast_channel_flags(), ast_channel_name(), ast_channel_nativeformats_set(), ast_channel_set_rawreadformat(), ast_channel_set_rawwriteformat(), ast_channel_set_readformat(), ast_channel_set_writeformat(), ast_channel_tech_pvt_set(), ast_channel_tech_set(), ast_channel_unlock, ast_debug, AST_DECLARE_APP_ARGS, AST_FLAG_DISABLE_DEVSTATE_CACHE, ast_format_cache_get, ast_format_cap_alloc, ast_format_cap_append, AST_FORMAT_CAP_FLAG_DEFAULT, ast_format_cap_get_format(), ast_format_get_name(), ast_log, AST_NONSTANDARD_APP_ARGS, ast_set_flag, ast_sorcery_retrieve_by_id(), AST_STATE_DOWN, ast_strdup, ast_strdupa, ast_strings_equal(), ast_strlen_zero(), ast_test_flag, control_msg_format_from_str(), INCOMING_CONNECTION_ID, LOG_ERROR, LOG_WARNING, NULL, OPT_ARG_ARRAY_SIZE, OPT_ARG_WS_CODEC, OPT_ARG_WS_MEDIA_DIRECTION, OPT_ARG_WS_MSG_FORMAT, OPT_ARG_WS_URI_PARAM, OPT_WS_CODEC, OPT_WS_MEDIA_DIRECTION, OPT_WS_MSG_FORMAT, OPT_WS_NO_AUTO_ANSWER, OPT_WS_PASSTHROUGH, OPT_WS_UNBUFFERED, OPT_WS_URI_PARAM, options, RAII_VAR, set_channel_timer(), set_channel_variables(), sorcery, ast_assigned_ids::uniqueid, validate_uri_parameters(), WEBCHAN_CONTROL_MSG_FORMAT_INVALID, WEBCHAN_CONTROL_MSG_FORMAT_PLAIN, WEBCHAN_MEDIA_DIRECTION_BOTH, WEBCHAN_MEDIA_DIRECTION_IN, WEBCHAN_MEDIA_DIRECTION_OUT, websocket_new(), websocket_options, and websocket_tech.

◆ webchan_send_dtmf_text()

static int webchan_send_dtmf_text ( struct ast_channel ast,
char  digit,
unsigned int  duration 
)
static

Definition at line 1892 of file chan_websocket.c.

1893{
1894 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1895
1896 if (!instance) {
1897 return -1;
1898 }
1899
1900 return send_event(instance, DTMF_END, digit);
1901}

References ast_channel_tech_pvt(), digit, and send_event.

◆ webchan_write()

static int webchan_write ( struct ast_channel ast,
struct ast_frame f 
)
static

Function called when we should write a frame to the channel.

Definition at line 1280 of file chan_websocket.c.

1281{
1282 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1283 int res = 0;
1284
1285 if (!instance || !instance->websocket) {
1286 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
1287 ast_channel_name(ast));
1288 return -1;
1289 }
1290
1291 /* The app doesn't want media right now */
1293 return 0;
1294 }
1295
1296 if (f->frametype == AST_FRAME_CNG) {
1297 return 0;
1298 }
1299
1300 if (f->frametype != AST_FRAME_VOICE) {
1301 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
1302 ast_channel_name(ast));
1303 return 0;
1304 }
1305
1307 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format, not '%s'\n",
1310 return -1;
1311 }
1312
1314 (char *)f->data.ptr, (uint64_t)f->datalen);
1315 if (res != 0) {
1316 ast_log(LOG_WARNING, "%s: WebSocket write failure\n", ast_channel_name(ast));
1317 }
1318
1319 return res;
1320}

References ast_channel_name(), ast_channel_tech_pvt(), ast_format_cmp(), AST_FORMAT_CMP_NOT_EQUAL, ast_format_get_name(), AST_FRAME_CNG, AST_FRAME_VOICE, ast_log, AST_WEBSOCKET_OPCODE_BINARY, ast_websocket_write(), ast_frame::data, ast_frame::datalen, ast_frame_subclass::format, ast_frame::frametype, LOG_WARNING, websocket_pvt::media_direction, websocket_pvt::native_format, ast_frame::ptr, ast_frame::subclass, WEBCHAN_MEDIA_DIRECTION_OUT, and websocket_pvt::websocket.

◆ websocket_destructor()

static void websocket_destructor ( void *  data)
static

Definition at line 1393 of file chan_websocket.c.

1394{
1395 struct websocket_pvt *instance = data;
1396 struct ast_frame *frame = NULL;
1397 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
1398
1399 AST_LIST_LOCK(&instance->frame_queue);
1400 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
1401 ast_frfree(frame);
1402 }
1403 AST_LIST_UNLOCK(&instance->frame_queue);
1404
1405 if (instance->timer) {
1406 ast_timer_close(instance->timer);
1407 instance->timer = NULL;
1408 }
1409
1410 if (instance->channel) {
1411 ast_channel_unref(instance->channel);
1412 instance->channel = NULL;
1413 }
1414 if (instance->websocket) {
1415 ast_websocket_unref(instance->websocket);
1416 instance->websocket = NULL;
1417 }
1418
1419 ao2_cleanup(instance->client);
1420 instance->client = NULL;
1421
1422 ao2_cleanup(instance->native_codec);
1423 instance->native_codec = NULL;
1424
1425 ao2_cleanup(instance->native_format);
1426 instance->native_format = NULL;
1427
1428 if (instance->leftover_data) {
1429 ast_free(instance->leftover_data);
1430 instance->leftover_data = NULL;
1431 }
1432
1433 ast_free(instance->uri_params);
1434 ast_free(instance->remote_addr);
1435}

References ao2_cleanup, ast_channel_unref, ast_debug, ast_free, ast_frfree, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_timer_close(), ast_websocket_unref(), websocket_pvt::channel, websocket_pvt::client, websocket_pvt::connection_id, websocket_pvt::frame_queue, websocket_pvt::leftover_data, websocket_pvt::native_codec, websocket_pvt::native_format, NULL, websocket_pvt::remote_addr, websocket_pvt::timer, websocket_pvt::uri_params, and websocket_pvt::websocket.

Referenced by websocket_new().

◆ websocket_handoff_to_channel()

static int websocket_handoff_to_channel ( struct websocket_pvt instance)
static

Definition at line 1207 of file chan_websocket.c.

1208{
1209 int res = 0;
1210 int nodelay = 1;
1211 struct ast_sockaddr *remote_addr = ast_websocket_remote_address(instance->websocket);
1212
1213 instance->remote_addr = ast_strdup(ast_sockaddr_stringify(remote_addr));
1214 ast_debug(3, "%s: WebSocket connection with %s established\n",
1215 ast_channel_name(instance->channel), instance->remote_addr);
1216
1217 if (setsockopt(ast_websocket_fd(instance->websocket),
1218 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1219 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
1220 }
1221
1222 /*
1223 * The way write timeouts are handled in iostream requires the socket to be
1224 * in non-blocking mode. This is fine for reads as well because we already
1225 * set the websocket file descriptor on the channel and let it call
1226 * webchan_read() when data is available.
1227 */
1229
1230 /*
1231 * Tell res_http_websocket to accumulate incoming WebSocket CONTINUATION frames
1232 * into chunks of 1024 bytes and send us a TEXT or BINARY frame when the threshold
1233 * is reached.
1234 */
1236
1238
1239 res = send_event(instance, MEDIA_START);
1240 if (res != 0 ) {
1241 if (instance->type == AST_WS_TYPE_SERVER) {
1243 } else {
1244 /*
1245 * We were called by webchan_call so just need to set causes.
1246 * The core will hangup the channel.
1247 */
1250 }
1251 return -1;
1252 }
1253
1254 if (!instance->no_auto_answer) {
1255 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
1257 }
1258
1259 return 0;
1260}

References AST_CAUSE_NETWORK_OUT_OF_ORDER, ast_channel_hangupcause_set(), ast_channel_name(), ast_channel_set_fd(), ast_channel_tech_hangupcause_set(), AST_CONTROL_ANSWER, ast_debug, ast_log, ast_queue_control(), ast_sockaddr_stringify(), ast_strdup, ast_websocket_fd(), ast_websocket_reconstruct_enable(), ast_websocket_remote_address(), ast_websocket_set_nonblock(), AST_WEBSOCKET_STATUS_GOING_AWAY, AST_WS_TYPE_SERVER, websocket_pvt::channel, errno, LOG_WARNING, websocket_pvt::no_auto_answer, websocket_pvt::remote_addr, send_event, websocket_pvt::type, websocket_pvt::websocket, websocket_request_hangup, and WS_WEBSOCKET_FDNO.

Referenced by incoming_ws_established_cb(), and webchan_call().

◆ websocket_new()

static struct websocket_pvt * websocket_new ( const char *  chan_name,
const char *  connection_id,
struct ast_format fmt 
)
static

Definition at line 1450 of file chan_websocket.c.

1452{
1453 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
1454 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1455 char uuid[AST_UUID_STR_LEN];
1456 enum ast_websocket_type ws_type;
1457
1458 SCOPED_AO2WRLOCK(locker, instances);
1459
1460 if (ast_strings_equal(connection_id, INCOMING_CONNECTION_ID)) {
1461 connection_id = ast_uuid_generate_str(uuid, sizeof(uuid));
1462 ws_type = AST_WS_TYPE_SERVER;
1463 } else {
1464 ws_type = AST_WS_TYPE_CLIENT;
1465 }
1466
1467 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1468 if (!proxy) {
1469 return NULL;
1470 }
1471 strcpy(proxy->connection_id, connection_id); /* Safe */
1472
1473 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1475 if (!instance) {
1476 return NULL;
1477 }
1478 strcpy(instance->connection_id, connection_id); /* Safe */
1479
1480 instance->type = ws_type;
1481 if (ws_type == AST_WS_TYPE_CLIENT) {
1482 instance->client = ast_websocket_client_retrieve_by_id(instance->connection_id);
1483 if (!instance->client) {
1484 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1485 chan_name, instance->connection_id);
1486 return NULL;
1487 }
1488 }
1489
1490 AST_LIST_HEAD_INIT(&instance->frame_queue);
1491
1492 /*
1493 * We need the codec to calculate the number of samples in a frame
1494 * so we'll get it once and store it in the instance.
1495 *
1496 * References for native_format and native_codec are now held by the
1497 * instance and will be released when the instance is destroyed.
1498 */
1499 instance->native_format = fmt;
1500 instance->native_codec = ast_format_get_codec(instance->native_format);
1501 /*
1502 * References for native_format and native_codec are now held by the
1503 * instance and will be released when the instance is destroyed.
1504 */
1505
1506 /*
1507 * It's not possible for us to re-time or re-frame media if the data
1508 * stream can't be broken up on arbitrary byte boundaries. This is usually
1509 * indicated by the codec's minimum_bytes being small (10 bytes or less).
1510 * We need to force the passthrough and unbuffered modes in this case.
1511 */
1512 if (instance->native_codec->minimum_bytes <= 10) {
1513 instance->passthrough = 1;
1514 instance->unbuffered = 1;
1515 instance->optimal_frame_size = 0;
1516 } else {
1517 instance->optimal_frame_size =
1518 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1519 / instance->native_codec->minimum_ms;
1520 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1521 if (!instance->leftover_data) {
1522 return NULL;
1523 }
1524 }
1525
1526 ast_debug(3,
1527 "%s: WebSocket channel native format '%s' Sample rate: %d ptime: %dms minms: %u minbytes: %u passthrough: %d optimal_frame_size: %d\n",
1528 chan_name, ast_format_get_name(instance->native_format),
1529 ast_format_get_sample_rate(instance->native_format),
1530 ast_format_get_default_ms(instance->native_format),
1531 ast_format_get_minimum_ms(instance->native_format),
1532 ast_format_get_minimum_bytes(instance->native_format),
1533 instance->passthrough,
1534 instance->optimal_frame_size);
1535
1536 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1537 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1538 return NULL;
1539 }
1540
1542 return NULL;
1543 }
1544
1545 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1546 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1547 proxy->connection_id);
1548 return NULL;
1549 }
1550 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1551
1552 return ao2_bump(instance);
1553}

References ao2_alloc, ao2_bump, ao2_cleanup, ao2_link_flags, ao2_weakproxy_alloc, ao2_weakproxy_set_object, ao2_weakproxy_subscribe(), ast_calloc, ast_debug, ast_format_get_codec(), ast_format_get_default_ms(), ast_format_get_minimum_bytes(), ast_format_get_minimum_ms(), ast_format_get_name(), ast_format_get_sample_rate(), AST_LIST_HEAD_INIT, ast_log, ast_strings_equal(), ast_uuid_generate_str(), AST_UUID_STR_LEN, ast_websocket_client_retrieve_by_id(), AST_WS_TYPE_CLIENT, AST_WS_TYPE_SERVER, websocket_pvt::connection_id, INCOMING_CONNECTION_ID, instance_proxy_cb(), instances, LOG_ERROR, NULL, OBJ_NOLOCK, RAII_VAR, SCOPED_AO2WRLOCK, uuid(), and websocket_destructor().

Referenced by webchan_request().

Variable Documentation

◆ __mod_info

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Websocket Media Channel" , .key = ASTERISK_GPL_KEY , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .reload = reload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .requires = "res_http_websocket,res_websocket_client", }
static

Definition at line 2237 of file chan_websocket.c.

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 2237 of file chan_websocket.c.

◆ ast_ws_server

struct ast_websocket_server* ast_ws_server
static

Definition at line 87 of file chan_websocket.c.

Referenced by incoming_ws_http_callback(), load_module(), and unload_module().

◆ http_uri

struct ast_http_uri http_uri
static

Definition at line 2055 of file chan_websocket.c.

2055 {
2056 .callback = incoming_ws_http_callback,
2057 .description = "Media over Websocket",
2058 .uri = "media",
2059 .has_subtree = 1,
2060 .data = NULL,
2061 .key = __FILE__,
2062 .no_decode_uri = 1,
2063};

Referenced by load_module(), and unload_module().

◆ instances

struct ao2_container* instances = NULL
static

◆ msg_format_map

const char* msg_format_map[]
static
Initial value:

Definition at line 62 of file chan_websocket.c.

62 {
63 [WEBCHAN_CONTROL_MSG_FORMAT_PLAIN] = "plain-text",
66};

Referenced by control_msg_format_from_str(), and control_msg_format_to_str().

◆ sorcery

struct ast_sorcery* sorcery = NULL
static

Definition at line 54 of file chan_websocket.c.

Referenced by __ast_sorcery_apply_config(), __ast_sorcery_apply_default(), __ast_sorcery_apply_wizard_mapping(), __ast_sorcery_insert_wizard_mapping(), __ast_sorcery_object_field_register(), __ast_sorcery_object_register(), __ast_sorcery_object_type_insert_wizard(), __ast_sorcery_object_type_remove_wizard(), __ast_sorcery_open(), __ast_sorcery_remove_wizard_mapping(), alloc_and_initialize_sorcery(), alloc_and_initialize_sorcery(), alloc_and_initialize_sorcery(), apply_list_configuration(), as_config_load(), as_config_reload(), ast_ari_asterisk_delete_object(), ast_ari_asterisk_get_object(), ast_ari_asterisk_update_object(), ast_sip_destroy_sorcery_global(), ast_sip_initialize_sorcery_auth(), ast_sip_initialize_sorcery_domain_alias(), ast_sip_initialize_sorcery_global(), ast_sip_initialize_sorcery_location(), ast_sip_initialize_sorcery_transport(), ast_sorcery_alloc(), ast_sorcery_copy(), ast_sorcery_create(), ast_sorcery_delete(), ast_sorcery_diff(), ast_sorcery_force_reload(), ast_sorcery_force_reload_object(), ast_sorcery_get_module(), ast_sorcery_get_object_type(), ast_sorcery_get_wizard_mapping(), ast_sorcery_get_wizard_mapping_count(), ast_sorcery_instance_observer_add(), ast_sorcery_instance_observer_remove(), ast_sorcery_is_stale(), ast_sorcery_load(), ast_sorcery_load_object(), ast_sorcery_object_fields_register(), ast_sorcery_object_set_congestion_levels(), ast_sorcery_object_set_copy_handler(), ast_sorcery_object_set_diff_handler(), ast_sorcery_object_unregister(), ast_sorcery_objectset_apply(), ast_sorcery_objectset_create2(), ast_sorcery_objectset_json_create(), ast_sorcery_observer_add(), ast_sorcery_observer_remove(), ast_sorcery_ref(), ast_sorcery_reload(), ast_sorcery_reload_object(), ast_sorcery_retrieve_by_fields(), ast_sorcery_retrieve_by_id(), ast_sorcery_retrieve_by_prefix(), ast_sorcery_retrieve_by_regex(), ast_sorcery_update(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), AST_TEST_DEFINE(), bucket_file_wizard_create(), bucket_file_wizard_delete(), bucket_file_wizard_is_stale(), bucket_file_wizard_retrieve(), bucket_file_wizard_update(), bucket_http_wizard_retrieve_id(), bucket_wizard_create(), bucket_wizard_delete(), bucket_wizard_is_stale(), bucket_wizard_retrieve(), can_reuse_registration(), create_object(), deinitialize_sorcery(), deinitialize_sorcery(), global_loaded_observer(), handle_aor(), handle_auth(), handle_auths(), handle_endpoint(), handle_export_primitives(), handle_identify(), handle_phoneprov(), handle_registrations(), incoming_ws_established_cb(), instance_created_observer(), instance_destroying_observer(), load_config(), load_module(), memory_cache_full_update(), memory_cache_populate_external(), memory_cache_populate_internal(), memory_cache_stale_check(), memory_cache_stale_check_object(), memory_cache_stale_update_full(), memory_cache_stale_update_object(), mock_retrieve_id(), object_type_loaded_observer(), object_type_registered_observer(), profile_load(), profile_reload(), reload_module(), return_sorcery_object(), sorcery_astdb_create(), sorcery_astdb_filter_objectset(), sorcery_astdb_retrieve_fields(), sorcery_astdb_retrieve_fields_common(), sorcery_astdb_retrieve_id(), sorcery_astdb_retrieve_multiple(), sorcery_astdb_retrieve_prefix(), sorcery_astdb_retrieve_regex(), sorcery_astdb_update(), sorcery_config_internal_load(), sorcery_config_load(), sorcery_config_reload(), sorcery_config_retrieve_fields(), sorcery_config_retrieve_multiple(), sorcery_config_retrieve_prefix(), sorcery_config_retrieve_regex(), sorcery_destructor(), sorcery_function_read(), sorcery_is_configuration_met(), sorcery_is_explicit_name_met(), sorcery_memory_cache_ami_populate(), sorcery_memory_cache_create(), sorcery_memory_cache_load(), sorcery_memory_cache_populate(), sorcery_memory_cache_retrieve_fields(), sorcery_memory_cache_retrieve_id(), sorcery_memory_cache_retrieve_multiple(), sorcery_memory_cache_retrieve_prefix(), sorcery_memory_cache_retrieve_regex(), sorcery_memory_cached_object_alloc(), sorcery_memory_retrieve_fields(), sorcery_memory_retrieve_multiple(), sorcery_memory_retrieve_prefix(), sorcery_memory_retrieve_regex(), sorcery_realtime_create(), sorcery_realtime_filter_objectset(), sorcery_realtime_retrieve_fields(), sorcery_realtime_retrieve_id(), sorcery_realtime_retrieve_multiple(), sorcery_realtime_retrieve_prefix(), sorcery_realtime_retrieve_regex(), sorcery_realtime_update(), sorcery_reloadable(), sorcery_test_retrieve_id(), sorcery_wizard_load(), stale_cache_update_task_data_alloc(), stale_update_task_data_alloc(), tn_config_load(), tn_config_reload(), transport_apply(), unload_module(), vs_config_load(), vs_config_reload(), webchan_call(), webchan_request(), and wizard_apply_handler().

◆ websocket_media_direction_map

const char* websocket_media_direction_map[]
static
Initial value:

Definition at line 81 of file chan_websocket.c.

81 {
85};

◆ websocket_options

const struct ast_app_option websocket_options[128] = { [ 'c' ] = { .flag = OPT_WS_CODEC , .arg_index = OPT_ARG_WS_CODEC + 1 }, [ 'n' ] = { .flag = OPT_WS_NO_AUTO_ANSWER }, [ 'v' ] = { .flag = OPT_WS_URI_PARAM , .arg_index = OPT_ARG_WS_URI_PARAM + 1 }, [ 'p' ] = { .flag = OPT_WS_PASSTHROUGH }, [ 'f' ] = { .flag = OPT_WS_MSG_FORMAT , .arg_index = OPT_ARG_WS_MSG_FORMAT + 1 }, [ 'd' ] = { .flag = OPT_WS_MEDIA_DIRECTION , .arg_index = OPT_ARG_WS_MEDIA_DIRECTION + 1 }, [ 'u' ] = { .flag = OPT_WS_UNBUFFERED }, }
static

Definition at line 1649 of file chan_websocket.c.

Referenced by webchan_request().

◆ websocket_tech

struct ast_channel_tech websocket_tech
static

Definition at line 164 of file chan_websocket.c.

164 {
165 .type = "WebSocket",
166 .description = "Media over WebSocket Channel Driver",
167 .requester = webchan_request,
168 .call = webchan_call,
169 .read = webchan_read,
170 .write = webchan_write,
171 .hangup = webchan_hangup,
172 .send_digit_end = webchan_send_dtmf_text,
173};

Referenced by load_module(), unload_module(), and webchan_request().