Asterisk - The Open Source Telephony Project GIT-master-4c84066
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 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)
}
 
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_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_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 }, }
 
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 218 of file chan_websocket.c.

◆ _create_event_MEDIA_XON

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

Definition at line 217 of file chan_websocket.c.

◆ _create_event_QUEUE_DRAINED

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

Definition at line 219 of file chan_websocket.c.

◆ ANSWER_CHANNEL

#define ANSWER_CHANNEL   "ANSWER"

Definition at line 129 of file chan_websocket.c.

◆ CONTINUE_MEDIA

#define CONTINUE_MEDIA   "CONTINUE_MEDIA"

Definition at line 138 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 434 of file chan_websocket.c.

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

689 { \
690 if (instance->media_direction == direction) { \
691 send_event(instance, ERROR, "%s not supported while media direction " \
692 "is '%s'", command, websocket_media_direction_map[direction]); \
693 ast_debug(4, "%s: WebSocket media direction is '%s'. Ignoring %s command.\n", \
694 ast_channel_name(instance->channel), websocket_media_direction_map[direction], command); \
695 return 0; \
696 } \
697})
static const char * websocket_media_direction_map[]

◆ ERROR_ON_PASSTHROUGH_MODE_RTN

#define ERROR_ON_PASSTHROUGH_MODE_RTN (   instance,
  command 
)

Definition at line 678 of file chan_websocket.c.

679 { \
680 if (instance->passthrough) { \
681 send_event(instance, ERROR, "%s not supported in passthrough mode", command); \
682 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n", \
683 ast_channel_name(instance->channel), command); \
684 return 0; \
685 } \
686})

◆ FLUSH_MEDIA

#define FLUSH_MEDIA   "FLUSH_MEDIA"

Definition at line 134 of file chan_websocket.c.

◆ GET_DRIVER_STATUS

#define GET_DRIVER_STATUS   "GET_STATUS"

Definition at line 135 of file chan_websocket.c.

◆ HANGUP_CHANNEL

#define HANGUP_CHANNEL   "HANGUP"

Definition at line 130 of file chan_websocket.c.

◆ INCOMING_CONNECTION_ID

#define INCOMING_CONNECTION_ID   "INCOMING"

Definition at line 127 of file chan_websocket.c.

◆ MARK_MEDIA

#define MARK_MEDIA   "MARK_MEDIA"

Definition at line 133 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 144 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_CONNECTION_ID

#define MEDIA_WEBSOCKET_CONNECTION_ID   "MEDIA_WEBSOCKET_CONNECTION_ID"

Definition at line 126 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE

#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE   "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"

Definition at line 125 of file chan_websocket.c.

◆ PAUSE_MEDIA

#define PAUSE_MEDIA   "PAUSE_MEDIA"

Definition at line 137 of file chan_websocket.c.

◆ QUEUE_LENGTH_MAX

#define QUEUE_LENGTH_MAX   1000

Definition at line 141 of file chan_websocket.c.

◆ QUEUE_LENGTH_XOFF_LEVEL

#define QUEUE_LENGTH_XOFF_LEVEL   900

Definition at line 142 of file chan_websocket.c.

◆ QUEUE_LENGTH_XON_LEVEL

#define QUEUE_LENGTH_XON_LEVEL   800

Definition at line 143 of file chan_websocket.c.

◆ REPORT_QUEUE_DRAINED

#define REPORT_QUEUE_DRAINED   "REPORT_QUEUE_DRAINED"

Definition at line 136 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 441 of file chan_websocket.c.

442 { \
443 int _res = -1; \
444 char *_payload = _create_event_ ## _event(_instance, ##__VA_ARGS__); \
445 if (_payload && _instance->websocket) { \
446 _res = ast_websocket_write_string(_instance->websocket, _payload); \
447 if (_res != 0) { \
448 ast_log(LOG_ERROR, "%s: Unable to send event %s\n", \
449 ast_channel_name(instance->channel), _payload); \
450 } else { \
451 ast_debug(3, "%s: Sent %s\n", \
452 ast_channel_name(instance->channel), _payload); \
453 }\
454 ast_free(_payload); \
455 } \
456 (_res); \
457})

◆ SET_MEDIA_DIRECTION

#define SET_MEDIA_DIRECTION   "SET_MEDIA_DIRECTION"

Definition at line 139 of file chan_websocket.c.

◆ START_MEDIA_BUFFERING

#define START_MEDIA_BUFFERING   "START_MEDIA_BUFFERING"

Definition at line 131 of file chan_websocket.c.

◆ STOP_MEDIA_BUFFERING

#define STOP_MEDIA_BUFFERING   "STOP_MEDIA_BUFFERING"

Definition at line 132 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 158 of file chan_websocket.c.

◆ WS_TIMER_FDNO

#define WS_TIMER_FDNO   (AST_EXTENDED_FDS + 1)

Definition at line 122 of file chan_websocket.c.

◆ WS_WEBSOCKET_FDNO

#define WS_WEBSOCKET_FDNO   (AST_EXTENDED_FDS + 2)

Definition at line 123 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 

Definition at line 1513 of file chan_websocket.c.

1513 {
1514 OPT_WS_CODEC = (1 << 0),
1515 OPT_WS_NO_AUTO_ANSWER = (1 << 1),
1516 OPT_WS_URI_PARAM = (1 << 2),
1517 OPT_WS_PASSTHROUGH = (1 << 3),
1518 OPT_WS_MSG_FORMAT = (1 << 4),
1519 OPT_WS_MEDIA_DIRECTION = (1 << 5),
1520};

◆ 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_ARRAY_SIZE 

Definition at line 1522 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 74 of file chan_websocket.c.

Function Documentation

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 2100 of file chan_websocket.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 2100 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 327 of file chan_websocket.c.

329{
330 char *payload = NULL;
332 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s#}",
333 "event", "DTMF_END",
334 "channel_id", ast_channel_uniqueid(instance->channel),
335 "digit", &digit, 1
336 );
337 if (!msg) {
338 return NULL;
339 }
341 ast_json_unref(msg);
342 } else {
343 ast_asprintf(&payload, "%s digit:%c channel_id:%s",
344 "DTMF_END", digit, ast_channel_uniqueid(instance->channel));
345 }
346
347 return payload;
348}
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 395 of file chan_websocket.c.

397{
398 char *payload = NULL;
399 char *error_text = NULL;
400 va_list ap;
401 int res = 0;
402
403 va_start(ap, format);
404 res = ast_vasprintf(&error_text, format, ap);
405 va_end(ap);
406 if (res < 0 || !error_text) {
407 return NULL;
408 }
409
411 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
412 "event", "ERROR",
413 "channel_id", ast_channel_uniqueid(instance->channel),
414 "error_text", error_text);
415 ast_free(error_text);
416 if (!msg) {
417 return NULL;
418 }
420 ast_json_unref(msg);
421 } else {
422 ast_asprintf(&payload, "%s channel_id:%s error_text:%s",
423 "ERROR", ast_channel_uniqueid(instance->channel), error_text);
424 ast_free(error_text);
425 }
426
427 return payload;
428}
#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 267 of file chan_websocket.c.

269{
270 char *payload = NULL;
272 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
273 "event", "MEDIA_BUFFERING_COMPLETED",
274 "channel_id", ast_channel_uniqueid(instance->channel),
275 "correlation_id", S_OR(id, "")
276 );
277 if (!msg) {
278 return NULL;
279 }
281 ast_json_unref(msg);
282 } else {
283 ast_asprintf(&payload, "%s%s%s",
284 "MEDIA_BUFFERING_COMPLETED",
285 S_COR(id, " ",""), S_OR(id, ""));
286
287 }
288
289 return payload;
290}
#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 297 of file chan_websocket.c.

299{
300 char *payload = NULL;
302 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s}",
303 "event", "MEDIA_MARK_PROCESSED",
304 "channel_id", ast_channel_uniqueid(instance->channel),
305 "correlation_id", S_OR(id, "")
306 );
307 if (!msg) {
308 return NULL;
309 }
311 ast_json_unref(msg);
312 } else {
313 ast_asprintf(&payload, "%s%s%s",
314 "MEDIA_MARK_PROCESSED",
315 S_COR(id, " ",""), S_OR(id, ""));
316
317 }
318
319 return payload;
320}

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 226 of file chan_websocket.c.

227{
228 char *payload = NULL;
229
231 struct ast_json *msg = ast_json_pack("{s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:o }",
232 "event", "MEDIA_START",
233 "connection_id", instance->connection_id,
234 "channel", ast_channel_name(instance->channel),
235 "channel_id", ast_channel_uniqueid(instance->channel),
236 "format", ast_format_get_name(instance->native_format),
237 "optimal_frame_size", instance->optimal_frame_size,
238 "ptime", instance->native_codec->default_ms,
239 "channel_variables", ast_json_channel_vars(ast_channel_varshead(
240 instance->channel))
241 );
242 if (!msg) {
243 return NULL;
244 }
246 ast_json_unref(msg);
247 } else {
248 ast_asprintf(&payload, "%s %s:%s %s:%s %s:%s %s:%s %s:%d %s:%d",
249 "MEDIA_START",
250 "connection_id", instance->connection_id,
251 "channel", ast_channel_name(instance->channel),
252 "channel_id", ast_channel_uniqueid(instance->channel),
253 "format", ast_format_get_name(instance->native_format),
254 "optimal_frame_size", instance->optimal_frame_size,
255 "ptime", instance->native_codec->default_ms
256 );
257 }
258
259 return payload;
260}
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, and WEBCHAN_CONTROL_MSG_FORMAT_JSON.

◆ _create_event_nodata()

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

Definition at line 198 of file chan_websocket.c.

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

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 355 of file chan_websocket.c.

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

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 1181 of file chan_websocket.c.

1183{
1184 if (!instance || !instance->channel) {
1185 return;
1186 }
1187 ast_debug(3, "%s:%s: Hangup requested from %s line %d. cause: %s(%d) tech_cause: %s(%d)",
1188 ast_channel_name(instance->channel), instance->remote_addr,
1189 function, line,
1190 ast_cause2str(ast_cause), ast_cause, ast_websocket_status_to_str(tech_cause), tech_cause);
1191
1192 if (tech_cause) {
1193 ast_channel_tech_hangupcause_set(instance->channel, tech_cause);
1194 }
1195 ast_queue_hangup_with_cause(instance->channel, ast_cause);
1196}

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 2100 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 185 of file chan_websocket.c.

186{
188 return NULL;
189 }
190 return msg_format_map[value];
191}
#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().

◆ dequeue_frame()

static struct ast_frame * dequeue_frame ( struct websocket_pvt instance)
static

Definition at line 465 of file chan_websocket.c.

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

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_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, 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 1965 of file chan_websocket.c.

1966{
1968 sizeof(*cfg), NULL);
1969
1970 if (!cfg) {
1971 return NULL;
1972 }
1973
1974 return cfg;
1975}

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 1977 of file chan_websocket.c.

1978{
1979 struct webchan_conf_global *cfg = obj;
1980
1981 ast_debug(1, "control_msg_format: %s\n",
1983
1984 return 0;
1985}

References ast_debug, webchan_conf_global::control_msg_format, and control_msg_format_to_str().

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 1939 of file chan_websocket.c.

1941{
1942 struct webchan_conf_global *cfg = obj;
1943
1945
1947 ast_log(LOG_ERROR, "chan_websocket.conf: Invalid value '%s' for "
1948 "control_mesage_format. Must be 'plain-text' or 'json'\n",
1949 var->value);
1950 return -1;
1951 }
1952
1953 return 0;
1954}

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 1956 of file chan_websocket.c.

1957{
1958 const struct webchan_conf_global *cfg = obj;
1959
1961
1962 return 0;
1963}

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 708 of file chan_websocket.c.

709{
710 int res = 0;
711 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
712 const char *command = NULL;
713 char *data = NULL;
714
716 struct ast_json_error json_error;
717
718 json = ast_json_load_buf(buffer, strlen(buffer), &json_error);
719 if (!json) {
720 send_event(instance, ERROR, "Unable to parse JSON command");
721 return -1;
722 }
723 command = ast_json_object_string_get(json, "command");
724 } else {
725 command = buffer;
726 data = strchr(buffer, ' ');
727 if (data) {
728 *data = '\0';
729 data++;
730 }
731 }
732
733 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
735
736 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
738
739 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
740 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
742 AST_LIST_LOCK(&instance->frame_queue);
743 instance->bulk_media_in_progress = 1;
744 AST_LIST_UNLOCK(&instance->frame_queue);
745
746 } else if (ast_strings_equal(command, STOP_MEDIA_BUFFERING)) {
747 const char *id;
748 char *option;
749 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
751
753 id = ast_json_object_string_get(json, "correlation_id");
754 } else {
755 id = data;
756 }
757
758 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
760
761 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
763 (int)instance->leftover_len);
764
765 instance->bulk_media_in_progress = 0;
766 if (instance->leftover_len > 0) {
767 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
768 if (res != 0) {
769 return res;
770 }
771 }
772 instance->leftover_len = 0;
773 option = create_event(instance, MEDIA_BUFFERING_COMPLETED, id);
774 if (!option) {
775 return -1;
776 }
777 res = queue_option_frame(instance, option);
778 ast_free(option);
779
780 } else if (ast_strings_equal(command, MARK_MEDIA)) {
781 const char *id;
782 char *option;
783 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
785
786 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
788
790 id = ast_json_object_string_get(json, "correlation_id");
791 } else {
792 id = data;
793 }
794
795 ast_debug(4, "%s: %s %s\n",
796 ast_channel_name(instance->channel), MARK_MEDIA, id);
797
798 option = create_event(instance, MEDIA_MARK_PROCESSED, id);
799 if (!option) {
800 return -1;
801 }
802 res = queue_option_frame(instance, option);
803 ast_free(option);
804
805 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
806 struct ast_frame *frame = NULL;
807
808 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
809
810 AST_LIST_LOCK(&instance->frame_queue);
811 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
812 ast_frfree(frame);
813 }
814 instance->frame_queue_length = 0;
815 instance->bulk_media_in_progress = 0;
816 instance->leftover_len = 0;
817 AST_LIST_UNLOCK(&instance->frame_queue);
818
819 } else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
820 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
821
822 AST_LIST_LOCK(&instance->frame_queue);
823 instance->report_queue_drained = 1;
824 AST_LIST_UNLOCK(&instance->frame_queue);
825
826 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
827 return send_event(instance, STATUS);
828
829 } else if (ast_strings_equal(command, PAUSE_MEDIA)) {
830 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
832 AST_LIST_LOCK(&instance->frame_queue);
833 instance->queue_paused = 1;
834 AST_LIST_UNLOCK(&instance->frame_queue);
835
836 } else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
837 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
839 AST_LIST_LOCK(&instance->frame_queue);
840 instance->queue_paused = 0;
841 AST_LIST_UNLOCK(&instance->frame_queue);
842
843 } else if (ast_strings_equal(command, SET_MEDIA_DIRECTION)) {
844 const char *direction;
845
846 ERROR_ON_PASSTHROUGH_MODE_RTN(instance, command);
847
849 send_event(instance, ERROR, "%s only supports JSON format.\n", command);
850 return 0;
851 }
852
853 direction = ast_json_object_string_get(json, "direction");
854 if (!direction) {
855 send_event(instance, ERROR, "%s requires a 'direction' parameter.\n", command);
856 return 0;
857 }
858
859 if (!strcmp("both", direction)) {
861 return 0;
862 }
863
864 if (!instance->timer) {
865 set_channel_timer(instance);
867 }
868
870
871 } else if (!strcmp("out", direction)) {
873 return 0;
874 }
875
876 if (!instance->timer) {
877 set_channel_timer(instance);
879 }
880
882
883 } else if (!strcmp("in", direction)) {
885 return 0;
886 }
887
888 if (instance->timer) {
890 ast_timer_close(instance->timer);
891 instance->timer = NULL;
893 }
894
896
897 } else {
898 send_event(instance, ERROR, "'%s' is not a valid direction for %s.\n",
899 direction, command);
900 return 0;
901 }
902
903 } else {
904 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
905 ast_channel_name(instance->channel), command);
906 }
907
908 return res;
909}

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, FLUSH_MEDIA, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, GET_DRIVER_STATUS, HANGUP_CHANNEL, id, 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, 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 1788 of file chan_websocket.c.

1790{
1791 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1792 struct ast_variable *v;
1793 const char *connection_id = NULL;
1794 struct websocket_pvt *instance = NULL;
1795
1796 ast_debug(3, "WebSocket established\n");
1797
1798 for (v = upgrade_headers; v; v = v->next) {
1799 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1800 }
1801 for (v = get_params; v; v = v->next) {
1802 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1803 }
1804
1805 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1806 if (!connection_id) {
1807 /*
1808 * This can't really happen because websocket_http_callback won't
1809 * let it get this far if it can't add the connection_id to the
1810 * get_params.
1811 * Just in case though...
1812 */
1813 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1816 return;
1817 }
1818
1820 if (!instance) {
1821 /*
1822 * This also can't really happen because websocket_http_callback won't
1823 * let it get this far if it can't find the instance.
1824 * Just in case though...
1825 */
1826 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1829 return;
1830 }
1831 instance->websocket = ao2_bump(ast_ws_session);
1832
1834 ao2_cleanup(instance);
1835 /*
1836 * The instance is the channel's responsibility now.
1837 * We just return here.
1838 */
1839}

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

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 1851 of file chan_websocket.c.

1855{
1856 struct ast_http_uri fake_urih = {
1858 };
1859 int res = 0;
1860 /*
1861 * Normally the http server will destroy the get_params
1862 * when the session ends but if there weren't any initially
1863 * and we create some and add them to the list, the http server
1864 * won't know about it so we have to destroy it ourselves.
1865 */
1866 int destroy_get_params = (get_params == NULL);
1867 struct ast_variable *v = NULL;
1868 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1869
1870 ast_debug(2, "URI: %s Starting\n", uri);
1871
1872 /*
1873 * The client will have issued the GET request with a URI of
1874 * /media/<connection_id>
1875 *
1876 * Since this callback is registered for the /media URI prefix the
1877 * http server will strip that off the front of the URI passing in
1878 * only the path components after that in the 'uri' parameter.
1879 * This should leave only the connection id without a leading '/'.
1880 */
1881 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1882 if (!instance) {
1883 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
1884 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
1885 return -1;
1886 }
1887
1888 /*
1889 * We don't allow additional connections using the same connection id.
1890 */
1891 if (instance->websocket) {
1892 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
1893 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
1894 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
1895 return -1;
1896 }
1897
1898 v = ast_variable_new("CONNECTION_ID", uri, "");
1899 if (!v) {
1900 ast_http_error(ser, 500, "Server error", "");
1901 return -1;
1902 }
1903 ast_variable_list_append(&get_params, v);
1904
1905 for (v = get_params; v; v = v->next) {
1906 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1907 }
1908
1909 /*
1910 * This will ultimately call internal_ws_established_cb() so
1911 * this function will block until the websocket is closed and
1912 * internal_ws_established_cb() returns;
1913 */
1914 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
1915 get_params, headers);
1916 if (destroy_get_params) {
1917 ast_variables_destroy(get_params);
1918 }
1919
1920 ast_debug(2, "URI: %s DONE\n", uri);
1921
1922 return res;
1923}

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 1337 of file chan_websocket.c.

1338{
1339 struct instance_proxy *proxy = weakproxy;
1340 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
1341 ao2_unlink(instances, weakproxy);
1342}

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 1987 of file chan_websocket.c.

1988{
1989 ast_debug(2, "Initializing Websocket Client Configuration\n");
1991 if (!sorcery) {
1992 ast_log(LOG_ERROR, "Failed to open sorcery\n");
1993 return -1;
1994 }
1995
1996 ast_sorcery_apply_default(sorcery, "global", "config",
1997 "chan_websocket.conf,criteria=type=global,single_object=yes,explicit_name=global");
1998
2000 ast_log(LOG_ERROR, "Failed to register chan_websocket global object with sorcery\n");
2002 sorcery = NULL;
2003 return -1;
2004 }
2005
2006 ast_sorcery_object_field_register_nodoc(sorcery, "global", "type", "", OPT_NOOP_T, 0, 0);
2007 ast_sorcery_register_cust(global, control_message_format, "plain-text");
2008
2010
2011 return 0;
2012}

References ast_debug, 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_unref, global, global_alloc(), global_apply(), LOG_ERROR, NULL, OPT_NOOP_T, and sorcery.

Referenced by load_module().

◆ load_module()

static int load_module ( void  )
static

Function called when our module is loaded.

Definition at line 2043 of file chan_websocket.c.

2044{
2045 int res = 0;
2046 struct ast_websocket_protocol *protocol;
2047
2048 res = load_config();
2049 if (res != 0) {
2051 }
2052
2055 }
2056
2059 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
2060 unload_module();
2062 }
2063
2065 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
2066 instance_proxy_sort_fn, instance_proxy_cmp_fn);
2067 if (!instances) {
2069 "Failed to allocate the chan_websocket instance registry\n");
2070 unload_module();
2072 }
2073
2075 if (!ast_ws_server) {
2076 unload_module();
2078 }
2079
2080 protocol = ast_websocket_sub_protocol_alloc("media");
2081 if (!protocol) {
2082 unload_module();
2084 }
2087
2089
2091}

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 944 of file chan_websocket.c.

946{
947 char *next_frame_ptr = NULL;
948 size_t bytes_read = 0;
949 int res = 0;
950 size_t bytes_left = 0;
951
952 {
953 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
955 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
956 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
957 ast_channel_name(instance->channel));
958 return 0;
959 }
960 }
961
962 next_frame_ptr = payload;
963 instance->bytes_read += payload_len;
964
965 if (instance->passthrough) {
966 res = queue_frame_from_buffer(instance, payload, payload_len);
967 return res;
968 }
969
970 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
971 /*
972 * We have leftover data from a previous websocket message.
973 * Try to make a complete frame by appending data from
974 * the current message to the leftover data.
975 */
976 char *append_ptr = instance->leftover_data + instance->leftover_len;
977 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
978 /*
979 * It's possible that even the current message doesn't have enough
980 * data to make a complete frame.
981 */
982 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
983
984 /*
985 * Append whatever we can to the end of the leftover data
986 * even if it's not enough to make a complete frame.
987 */
988 memcpy(append_ptr, payload, bytes_avail_to_copy);
989
990 /*
991 * If leftover data is still short, just return and wait for the
992 * next websocket message.
993 */
994 if (bytes_avail_to_copy < bytes_needed_for_frame) {
995 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
996 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
997 instance->leftover_len += bytes_avail_to_copy;
998 return 0;
999 }
1000
1001 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
1002 if (res < 0) {
1003 return -1;
1004 }
1005
1006 /*
1007 * We stole data from the current payload so decrement payload_len
1008 * and set the next frame pointer after the data in payload
1009 * we just copied.
1010 */
1011 payload_len -= bytes_avail_to_copy;
1012 next_frame_ptr = payload + bytes_avail_to_copy;
1013
1014 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
1015 ast_channel_name(instance->channel),
1016 instance->frame_queue_length,
1017 (int)instance->bytes_read,
1018 (int)(payload_len + bytes_avail_to_copy),
1019 (int)instance->leftover_len,
1020 payload,
1021 next_frame_ptr,
1022 (int)(next_frame_ptr - payload),
1023 (int)payload_len,
1024 (int)bytes_avail_to_copy
1025 );
1026
1027
1028 instance->leftover_len = 0;
1029 }
1030
1031 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
1032 instance->leftover_len = 0;
1033 }
1034
1035 bytes_left = payload_len;
1036 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
1037 res = queue_frame_from_buffer(instance, next_frame_ptr,
1038 instance->optimal_frame_size);
1039 if (res < 0) {
1040 break;
1041 }
1042 bytes_read += instance->optimal_frame_size;
1043 next_frame_ptr += instance->optimal_frame_size;
1044 bytes_left -= instance->optimal_frame_size;
1045 }
1046
1047 if (instance->bulk_media_in_progress && bytes_left > 0) {
1048 /*
1049 * We have a partial frame. Save the leftover data.
1050 */
1051 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
1052 ast_channel_name(instance->channel),
1053 (int)instance->bytes_read,
1054 instance->frame_queue_length,
1055 (int)payload_len,
1056 (int)instance->leftover_len,
1057 payload,
1058 next_frame_ptr,
1059 (int)(next_frame_ptr - payload),
1060 (int)bytes_left
1061 );
1062 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
1063 instance->leftover_len = bytes_left;
1064 }
1065
1066 return 0;
1067}

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, websocket_pvt::passthrough, queue_frame_from_buffer(), QUEUE_LENGTH_MAX, and SCOPED_LOCK.

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 911 of file chan_websocket.c.

913{
914 char *command;
915
916 if (payload_len == 0) {
917 ast_log(LOG_WARNING, "%s: WebSocket TEXT message has 0 length\n",
918 ast_channel_name(instance->channel));
919 return 0;
920 }
921
922 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
923 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
924 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
925 return 0;
926 }
927
928 /*
929 * Unfortunately, payload is not NULL terminated even when it's
930 * a TEXT frame so we need to allocate a new buffer, copy
931 * the data into it, and NULL terminate it.
932 */
933 command = ast_alloca(payload_len + 1);
934 memcpy(command, payload, payload_len); /* Safe */
935 command[payload_len] = '\0';
936 command = ast_strip(command);
937
938 ast_debug(4, "%s: Received: %s\n",
939 ast_channel_name(instance->channel), command);
940
941 return handle_command(instance, command);
942}

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 616 of file chan_websocket.c.

618{
619 struct ast_frame fr = { 0, };
620 struct ast_frame *duped_frame = NULL;
621
622 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
624 fr.subclass.format = instance->native_format;
625 fr.samples = instance->native_codec->samples_count(&fr);
626
627 duped_frame = ast_frisolate(&fr);
628 if (!duped_frame) {
629 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
630 ast_channel_name(instance->channel));
631 return -1;
632 }
633
634 {
635 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
637 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
638 instance->frame_queue_length++;
639 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
640 instance->queue_full = 1;
641 send_event(instance, MEDIA_XOFF);
642 }
643 }
644
645 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
646 duped_frame->datalen);
647
648 return 0;
649}

References ast_channel_name(), ast_debug, AST_FRAME_SET_BUFFER, AST_FRAME_VOICE, ast_frisolate, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_log, websocket_pvt::channel, ast_frame::datalen, ast_frame_subclass::format, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, ast_frame::frametype, len(), LOG_WARNING, websocket_pvt::native_codec, websocket_pvt::native_format, NULL, websocket_pvt::queue_full, QUEUE_LENGTH_XOFF_LEVEL, ast_frame::samples, ast_codec::samples_count, SCOPED_LOCK, send_event, and ast_frame::subclass.

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 651 of file chan_websocket.c.

653{
654 struct ast_frame fr = { 0, };
655 struct ast_frame *duped_frame = NULL;
656
657 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
660
661 duped_frame = ast_frisolate(&fr);
662 if (!duped_frame) {
663 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
664 ast_channel_name(instance->channel));
665 return -1;
666 }
667
668 AST_LIST_LOCK(&instance->frame_queue);
669 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
670 AST_LIST_UNLOCK(&instance->frame_queue);
671
672 ast_debug(4, "%s: Queued '%s' option frame\n",
673 ast_channel_name(instance->channel), buffer);
674
675 return 0;
676}

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 1069 of file chan_websocket.c.

1070{
1071 uint64_t payload_len = 0;
1072 char *payload = NULL;
1073 enum ast_websocket_opcode opcode;
1074 int fragmented = 0;
1075 int res = 0;
1076
1077 if (!instance->websocket) {
1078 ast_log(LOG_WARNING, "%s: WebSocket session not found\n",
1079 ast_channel_name(instance->channel));
1080 return -1;
1081 }
1082
1083 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
1084 &opcode, &fragmented);
1085
1086 if (res) {
1087 ast_debug(3, "%s: WebSocket read error\n",
1088 ast_channel_name(instance->channel));
1090 return -1;
1091 }
1092 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
1093 (int)payload_len);
1094
1095 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
1096 return process_text_message(instance, payload, payload_len);
1097 }
1098
1099 /*
1100 * PINGs and PONGs will have been handled by res_http_websocket.
1101 * We also need to ignore CONTINUATION frames as they will be accumulated
1102 * by res_http_websocket until the threshold set in websocket_handoff_to_channel()
1103 * is reached, then it will send us a TEXT or BINARY frame.
1104 */
1105 if (opcode == AST_WEBSOCKET_OPCODE_PING || opcode == AST_WEBSOCKET_OPCODE_PONG
1106 || opcode == AST_WEBSOCKET_OPCODE_CONTINUATION) {
1107 return 0;
1108 }
1109
1110 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
1111 ast_debug(3, "%s: WebSocket closed by remote\n",
1112 ast_channel_name(instance->channel));
1114 return -1;
1115 }
1116
1117 if (opcode == AST_WEBSOCKET_OPCODE_BINARY) {
1118 /* If the application's media direction is 'in', drop any media we receive from it */
1120 ast_debug(5, "%s: WebSocket dropped frame (application media direction is 'in')\n",
1121 ast_channel_name(instance->channel));
1122 return 0;
1123 }
1124 } else {
1125 ast_log(LOG_WARNING, "%s: WebSocket frame type %d not supported\n",
1126 ast_channel_name(instance->channel), (int)opcode);
1128 return 0;
1129 }
1130
1131 return process_binary_message(instance, payload, payload_len);
1132}

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 2034 of file chan_websocket.c.

2035{
2036 ast_debug(2, "Reloading chan_websocket configuration\n");
2038
2039 return 0;
2040}

References ast_debug, ast_sorcery_reload(), and sorcery.

◆ set_channel_timer()

static int set_channel_timer ( struct websocket_pvt instance)
static

Definition at line 1448 of file chan_websocket.c.

1449{
1450 int rate = 0;
1451 instance->timer = ast_timer_open();
1452 if (!instance->timer) {
1453 return -1;
1454 }
1455 /* Rate is the number of ticks per second, not the interval. */
1456 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1457 ast_debug(3, "%s: WebSocket timer rate %d\n",
1458 ast_channel_name(instance->channel), rate);
1459 ast_timer_set_rate(instance->timer, rate);
1460 /*
1461 * Calling ast_channel_set_fd will cause the channel thread to call
1462 * webchan_read at 'rate' times per second.
1463 */
1465
1466 return 0;
1467}

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 1469 of file chan_websocket.c.

1470{
1471 char *pkt_size = NULL;
1472 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1473 if (res <= 0) {
1474 return -1;
1475 }
1476
1478 pkt_size);
1479 ast_free(pkt_size);
1481 instance->connection_id);
1482
1483 return 0;
1484}

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 1486 of file chan_websocket.c.

1487{
1488 char *params = ast_strdupa(uri_params);
1489 char *nvp = NULL;
1490 char *nv = NULL;
1491
1492 /*
1493 * uri_params should be a comma-separated list of key=value pairs.
1494 * For example:
1495 * name1=value1,name2=value2
1496 * We're verifying that each name and value either doesn't need
1497 * to be encoded or that it already is.
1498 */
1499
1500 while((nvp = ast_strsep(&params, ',', 0))) {
1501 /* nvp will be name1=value1 */
1502 while((nv = ast_strsep(&nvp, '=', 0))) {
1503 /* nv will be either name1 or value1 */
1504 if (!ast_uri_verify_encoded(nv)) {
1505 return 0;
1506 }
1507 }
1508 }
1509
1510 return 1;
1511}

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 1242 of file chan_websocket.c.

1244{
1245 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1247
1248 if (!instance) {
1249 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
1250 ast_channel_name(ast));
1252 return -1;
1253 }
1254
1255 if (instance->type == AST_WS_TYPE_SERVER) {
1256 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
1257 return 0;
1258 }
1259 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
1260
1261 if (!instance->client) {
1262 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
1263 ast_channel_name(ast));
1265 return -1;
1266 }
1267
1268 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
1269 ast_channel_name(ast), dest, instance->connection_id);
1270
1271 if (!ast_strlen_zero(instance->uri_params)) {
1273 }
1274
1275 instance->websocket = ast_websocket_client_connect(instance->client,
1276 instance, ast_channel_name(ast), &result);
1277 if (!instance->websocket || result != WS_OK) {
1278 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
1281 return -1;
1282 }
1283
1284 return websocket_handoff_to_channel(instance);
1285}

References AST_CAUSE_FAILURE, AST_CAUSE_NO_ROUTE_DESTINATION, ast_channel_hangupcause_set(), ast_channel_name(), ast_channel_tech_pvt(), ast_debug, ast_log, ast_strlen_zero(), ast_websocket_client_add_uri_params(), ast_websocket_client_connect(), ast_websocket_result_to_str(), AST_WS_TYPE_SERVER, websocket_pvt::channel, websocket_pvt::client, websocket_pvt::connection_id, LOG_WARNING, result, websocket_pvt::type, websocket_pvt::uri_params, websocket_pvt::websocket, websocket_handoff_to_channel(), and WS_OK.

◆ webchan_hangup()

static int webchan_hangup ( struct ast_channel ast)
static

Definition at line 1745 of file chan_websocket.c.

1746{
1747 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1748
1749 if (!instance) {
1750 return -1;
1751 }
1752 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1753 ast_channel_name(ast), instance->connection_id);
1754
1755 if (instance->websocket) {
1757 ast_websocket_unref(instance->websocket);
1758 instance->websocket = NULL;
1759 }
1761
1762 /* Clean up the reference from adding the instance to the channel */
1763 ao2_cleanup(instance);
1764
1765 return 0;
1766}

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 579 of file chan_websocket.c.

580{
581 struct websocket_pvt *instance = NULL;
582 struct ast_frame *native_frame = NULL;
583 int fdno = ast_channel_fdno(ast);
584
585 instance = ast_channel_tech_pvt(ast);
586 if (!instance) {
587 return NULL;
588 }
589
590 if (fdno == WS_WEBSOCKET_FDNO) {
591 read_from_ws_and_queue(instance);
592 return &ast_null_frame;
593 }
594 if (fdno != WS_TIMER_FDNO) {
595 return &ast_null_frame;
596 }
597
599 ast_timer_ack(instance->timer, 1);
600 }
601
602 native_frame = dequeue_frame(instance);
603
604 /*
605 * No frame when the timer fires means we have to return a null frame in its place.
606 */
607 if (!native_frame) {
608 ast_debug(4, "%s: WebSocket read timer fired with no frame available. Returning NULL frame.\n",
609 ast_channel_name(ast));
610 return &ast_null_frame;
611 }
612
613 return native_frame;
614}

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, dequeue_frame(), NULL, read_from_ws_and_queue(), websocket_pvt::timer, 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 1541 of file chan_websocket.c.

1544{
1545 char *parse;
1546 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1547 struct ast_channel *chan = NULL;
1548 struct ast_format *fmt = NULL;
1549 struct ast_format_cap *caps = NULL;
1551 AST_APP_ARG(connection_id);
1553 );
1554 struct ast_flags opts = { 0, };
1555 char *opt_args[OPT_ARG_ARRAY_SIZE];
1556 const char *requestor_name = requestor ? ast_channel_name(requestor) :
1557 (assignedids && !ast_strlen_zero(assignedids->uniqueid) ? assignedids->uniqueid : "<unknown>");
1558 RAII_VAR(struct webchan_conf_global *, global_cfg, NULL, ao2_cleanup);
1559
1560 global_cfg = ast_sorcery_retrieve_by_id(sorcery, "global", "global");
1561
1562 ast_debug(3, "%s: WebSocket channel requested\n",
1563 requestor_name);
1564
1565 if (ast_strlen_zero(data)) {
1566 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1567 requestor_name);
1568 goto failure;
1569 }
1570 parse = ast_strdupa(data);
1571 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1572
1573 if (ast_strlen_zero(args.connection_id)) {
1574 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1575 requestor_name);
1576 goto failure;
1577 }
1578
1579 if (!ast_strlen_zero(args.options)
1580 && ast_app_parse_options(websocket_options, &opts, opt_args,
1581 ast_strdupa(args.options))) {
1582 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1583 requestor_name, args.options);
1584 goto failure;
1585 }
1586
1587 if (ast_test_flag(&opts, OPT_WS_CODEC)
1588 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1589 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1590 } else {
1591 /*
1592 * If codec wasn't specified in the dial string,
1593 * use the first format in the capabilities.
1594 */
1595 fmt = ast_format_cap_get_format(cap, 0);
1596 }
1597
1598 if (!fmt) {
1599 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1600 requestor_name, args.connection_id);
1601 goto failure;
1602 }
1603
1604 ast_debug(3, "%s: Using format %s from %s\n",
1605 requestor_name, ast_format_get_name(fmt),
1606 ast_test_flag(&opts, OPT_WS_CODEC) ? "dialstring" : "requester");
1607
1608 instance = websocket_new(requestor_name, args.connection_id, fmt);
1609 if (!instance) {
1610 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1611 requestor_name);
1612 goto failure;
1613 }
1614
1615 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_BOTH;
1617 if (!strcmp("both", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1618 /* The default. Don't need to do anything here other than
1619 * ensure it is an allowed value. */
1620 } else if (!strcmp("out", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1621 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_OUT;
1622 } else if (!strcmp("in", opt_args[OPT_ARG_WS_MEDIA_DIRECTION])) {
1623 instance->media_direction = WEBCHAN_MEDIA_DIRECTION_IN;
1624 } else {
1625 ast_log(LOG_ERROR, "Unrecognized option for media direction: '%s'.\n",
1626 opt_args[OPT_ARG_WS_MEDIA_DIRECTION]);
1627 goto failure;
1628 }
1629 }
1630
1631 instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
1632 if (!instance->passthrough) {
1633 instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
1634 }
1635
1637 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1638 char *comma;
1639
1640 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1642 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1643 requestor_name);
1644 goto failure;
1645 }
1646
1647 ast_debug(3, "%s: Using URI parameters '%s'\n",
1648 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1649
1651 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1652 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1653 args.connection_id);
1654 goto failure;
1655 }
1656
1657 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1658 comma = instance->uri_params;
1659 /*
1660 * The normal separator for query string components is an
1661 * ampersand ('&') but the Dial app interprets them as additional
1662 * channels to dial in parallel so we instruct users to separate
1663 * the parameters with commas (',') instead. We now have to
1664 * convert those commas back to ampersands.
1665 */
1666 while ((comma = strchr(comma,','))) {
1667 *comma = '&';
1668 }
1669 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1670 }
1671
1672 if (ast_test_flag(&opts, OPT_WS_MSG_FORMAT)) {
1673 instance->control_msg_format = control_msg_format_from_str(opt_args[OPT_ARG_WS_MSG_FORMAT]);
1674
1675 if (instance->control_msg_format == WEBCHAN_CONTROL_MSG_FORMAT_INVALID) {
1676 ast_log(LOG_WARNING, "%s: 'f/control message format' dialstring parameter value missing or invalid. "
1677 "Defaulting to 'plain-text'\n",
1678 ast_channel_name(requestor));
1679 instance->control_msg_format = WEBCHAN_CONTROL_MSG_FORMAT_PLAIN;
1680 }
1681 } else if (global_cfg) {
1682 instance->control_msg_format = global_cfg->control_msg_format;
1683 }
1684
1685 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1686 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1687 if (!chan) {
1688 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", ast_channel_name(requestor));
1689 goto failure;
1690 }
1691
1692 /* Prevent device state caching as this channel involves ephemeral destinations or sources */
1694 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1695 ast_channel_name(chan), requestor_name,
1696 instance->connection_id);
1697
1698 instance->channel = ao2_bump(chan);
1699 ast_channel_tech_set(instance->channel, &websocket_tech);
1700
1701 /* If the application's media direction is 'both' or 'out', we need the channel timer. */
1702 if (instance->media_direction != WEBCHAN_MEDIA_DIRECTION_IN
1703 && set_channel_timer(instance) != 0) {
1704 goto failure;
1705 }
1706
1707 if (set_channel_variables(instance) != 0) {
1708 goto failure;
1709 }
1710
1712 if (!caps) {
1713 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1714 goto failure;
1715 }
1716
1717 ast_format_cap_append(caps, instance->native_format, 0);
1718 ast_channel_nativeformats_set(instance->channel, caps);
1719 ast_channel_set_writeformat(instance->channel, instance->native_format);
1720 ast_channel_set_rawwriteformat(instance->channel, instance->native_format);
1721 ast_channel_set_readformat(instance->channel, instance->native_format);
1722 ast_channel_set_rawreadformat(instance->channel, instance->native_format);
1723 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1724 ast_channel_unlock(chan);
1725 ao2_cleanup(caps);
1726
1727 ast_debug(3, "%s: WebSocket channel created to %s\n",
1728 ast_channel_name(chan), args.connection_id);
1729
1730 return chan;
1731
1732failure:
1733 if (chan) {
1734 ast_channel_unlock(chan);
1735 }
1736 *cause = AST_CAUSE_FAILURE;
1737 return NULL;
1738}

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_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 1768 of file chan_websocket.c.

1769{
1770 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1771
1772 if (!instance) {
1773 return -1;
1774 }
1775
1776 return send_event(instance, DTMF_END, digit);
1777}

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 1199 of file chan_websocket.c.

1200{
1201 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1202
1203 if (!instance || !instance->websocket) {
1204 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
1205 ast_channel_name(ast));
1206 return -1;
1207 }
1208
1209 /* The app doesn't want media right now */
1211 return 0;
1212 }
1213
1214 if (f->frametype == AST_FRAME_CNG) {
1215 return 0;
1216 }
1217
1218 if (f->frametype != AST_FRAME_VOICE) {
1219 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
1220 ast_channel_name(ast));
1221 return 0;
1222 }
1223
1225 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format, not '%s'\n",
1228 return -1;
1229 }
1230
1232 (char *)f->data.ptr, (uint64_t)f->datalen);
1233}

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 1287 of file chan_websocket.c.

1288{
1289 struct websocket_pvt *instance = data;
1290 struct ast_frame *frame = NULL;
1291 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
1292
1293 AST_LIST_LOCK(&instance->frame_queue);
1294 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
1295 ast_frfree(frame);
1296 }
1297 AST_LIST_UNLOCK(&instance->frame_queue);
1298
1299 if (instance->timer) {
1300 ast_timer_close(instance->timer);
1301 instance->timer = NULL;
1302 }
1303
1304 if (instance->channel) {
1305 ast_channel_unref(instance->channel);
1306 instance->channel = NULL;
1307 }
1308 if (instance->websocket) {
1309 ast_websocket_unref(instance->websocket);
1310 instance->websocket = NULL;
1311 }
1312
1313 ao2_cleanup(instance->client);
1314 instance->client = NULL;
1315
1316 ao2_cleanup(instance->native_codec);
1317 instance->native_codec = NULL;
1318
1319 ao2_cleanup(instance->native_format);
1320 instance->native_format = NULL;
1321
1322 if (instance->leftover_data) {
1323 ast_free(instance->leftover_data);
1324 instance->leftover_data = NULL;
1325 }
1326
1327 ast_free(instance->uri_params);
1328 ast_free(instance->remote_addr);
1329}

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 1134 of file chan_websocket.c.

1135{
1136 int res = 0;
1137 int nodelay = 1;
1138 struct ast_sockaddr *remote_addr = ast_websocket_remote_address(instance->websocket);
1139
1140 instance->remote_addr = ast_strdup(ast_sockaddr_stringify(remote_addr));
1141 ast_debug(3, "%s: WebSocket connection with %s established\n",
1142 ast_channel_name(instance->channel), instance->remote_addr);
1143
1144 if (setsockopt(ast_websocket_fd(instance->websocket),
1145 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1146 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
1147 }
1148
1149 /*
1150 * Tell res_http_websocket to accumulate incoming WebSocket CONTINUATION frames
1151 * into chunks of 1024 bytes and send us a TEXT or BINARY frame when the threshold
1152 * is reached.
1153 */
1155
1157
1158 res = send_event(instance, MEDIA_START);
1159 if (res != 0 ) {
1160 if (instance->type == AST_WS_TYPE_SERVER) {
1162 } else {
1163 /*
1164 * We were called by webchan_call so just need to set causes.
1165 * The core will hangup the channel.
1166 */
1169 }
1170 return -1;
1171 }
1172
1173 if (!instance->no_auto_answer) {
1174 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
1176 }
1177
1178 return 0;
1179}

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_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 1344 of file chan_websocket.c.

1346{
1347 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
1348 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1349 char uuid[AST_UUID_STR_LEN];
1350 enum ast_websocket_type ws_type;
1351
1352 SCOPED_AO2WRLOCK(locker, instances);
1353
1354 if (ast_strings_equal(connection_id, INCOMING_CONNECTION_ID)) {
1355 connection_id = ast_uuid_generate_str(uuid, sizeof(uuid));
1356 ws_type = AST_WS_TYPE_SERVER;
1357 } else {
1358 ws_type = AST_WS_TYPE_CLIENT;
1359 }
1360
1361 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1362 if (!proxy) {
1363 return NULL;
1364 }
1365 strcpy(proxy->connection_id, connection_id); /* Safe */
1366
1367 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1369 if (!instance) {
1370 return NULL;
1371 }
1372 strcpy(instance->connection_id, connection_id); /* Safe */
1373
1374 instance->type = ws_type;
1375 if (ws_type == AST_WS_TYPE_CLIENT) {
1376 instance->client = ast_websocket_client_retrieve_by_id(instance->connection_id);
1377 if (!instance->client) {
1378 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1379 chan_name, instance->connection_id);
1380 return NULL;
1381 }
1382 }
1383
1384 AST_LIST_HEAD_INIT(&instance->frame_queue);
1385
1386 /*
1387 * We need the codec to calculate the number of samples in a frame
1388 * so we'll get it once and store it in the instance.
1389 *
1390 * References for native_format and native_codec are now held by the
1391 * instance and will be released when the instance is destroyed.
1392 */
1393 instance->native_format = fmt;
1394 instance->native_codec = ast_format_get_codec(instance->native_format);
1395 /*
1396 * References for native_format and native_codec are now held by the
1397 * instance and will be released when the instance is destroyed.
1398 */
1399
1400 /*
1401 * It's not possible for us to re-time or re-frame media if the data
1402 * stream can't be broken up on arbitrary byte boundaries. This is usually
1403 * indicated by the codec's minimum_bytes being small (10 bytes or less).
1404 * We need to force passthrough mode in this case.
1405 */
1406 if (instance->native_codec->minimum_bytes <= 10) {
1407 instance->passthrough = 1;
1408 instance->optimal_frame_size = 0;
1409 } else {
1410 instance->optimal_frame_size =
1411 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1412 / instance->native_codec->minimum_ms;
1413 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1414 if (!instance->leftover_data) {
1415 return NULL;
1416 }
1417 }
1418
1419 ast_debug(3,
1420 "%s: WebSocket channel native format '%s' Sample rate: %d ptime: %dms minms: %u minbytes: %u passthrough: %d optimal_frame_size: %d\n",
1421 chan_name, ast_format_get_name(instance->native_format),
1422 ast_format_get_sample_rate(instance->native_format),
1423 ast_format_get_default_ms(instance->native_format),
1424 ast_format_get_minimum_ms(instance->native_format),
1425 ast_format_get_minimum_bytes(instance->native_format),
1426 instance->passthrough,
1427 instance->optimal_frame_size);
1428
1429 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1430 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1431 return NULL;
1432 }
1433
1435 return NULL;
1436 }
1437
1438 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1439 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1440 proxy->connection_id);
1441 return NULL;
1442 }
1443 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1444
1445 return ao2_bump(instance);
1446}

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 2100 of file chan_websocket.c.

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 2100 of file chan_websocket.c.

◆ ast_ws_server

struct ast_websocket_server* ast_ws_server
static

Definition at line 86 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 1925 of file chan_websocket.c.

1925 {
1926 .callback = incoming_ws_http_callback,
1927 .description = "Media over Websocket",
1928 .uri = "media",
1929 .has_subtree = 1,
1930 .data = NULL,
1931 .key = __FILE__,
1932 .no_decode_uri = 1,
1933};

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(), 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_request(), and wizard_apply_handler().

◆ websocket_media_direction_map

const char* websocket_media_direction_map[]
static
Initial value:

Definition at line 80 of file chan_websocket.c.

80 {
84};

◆ 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 }, }
static

Definition at line 1539 of file chan_websocket.c.

Referenced by webchan_request().

◆ websocket_tech

struct ast_channel_tech websocket_tech
static

Definition at line 161 of file chan_websocket.c.

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

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