@ -31,76 +31,6 @@
< support_level > core < / support_level >
* * */
/*** DOCUMENTATION
< info name = " Dial_Resource " language = " en_US " tech = " WebSocket " >
< para > WebSocket Dial Strings : < / para >
< para > < literal > Dial ( WebSocket / connectionid [ / websocket_options ] ) < / literal > < / para >
< para > WebSocket Parameters : < / para >
< enumlist >
< enum name = " connectionid " >
< para > For outgoing WebSockets , this is the ID of the connection
in websocket_client . conf to use for the call . To accept incoming
WebSocket connections use the literal < literal > INCOMING < / literal > < / para >
< / enum >
< enum name = " websocket_options " >
< para > Options to control how the WebSocket channel behaves . < / para >
< enumlist >
< enum name = " c(codec) - Specify the codec to use in the channel " >
< para > < / para >
< para > If not specified , the first codec from the caller ' s channel will be used .
< / para >
< / enum >
< enum name = " n - Don't auto answer " >
< para > Normally , the WebSocket channel will be answered when
connection is established with the remote app . If this
option is specified however , the channel will not be
answered until the < literal > ANSWER < / literal > command is
received from the remote app or the remote app calls the
/ channels / answer ARI endpoint .
< / para >
< / enum >
< enum name = " p - Passthrough mode " >
< para > In passthrough mode , the channel driver won ' t attempt
to re - frame or re - time media coming in over the websocket from
the remote app . This can be used for any codec but MUST be used
for codecs that use packet headers or whose data stream can ' t be
broken up on arbitrary byte boundaries . In this case , the remote
app is fully responsible for correctly framing and timing media
sent to Asterisk and the MEDIA text commands that could be sent
over the websocket are disabled . Currently , passthrough mode is
automatically set for the opus , speex and g729 codecs .
< / para >
< / enum >
< enum name = " v(uri_parameters) - Add parameters to the outbound URI " >
< para > This option allows you to add additional parameters to the
outbound URI . The format is :
< literal > v ( param1 = value1 , param2 = value2 . . . ) < / literal >
< / para >
< para > You must ensure that no parameter name or value contains
characters not valid in a URL . The easiest way to do this is to
use the URIENCODE ( ) dialplan function to encode them . Be aware
though that each name and value must be encoded separately . You
can ' t simply encode the whole string . < / para >
< / enum >
< / enumlist >
< / enum >
< / enumlist >
< para > Examples :
< / para >
< example title = " Make an outbound WebSocket connection using connection 'connection1' and the 'sln16' codec. " >
same = > n , Dial ( WebSocket / connection1 / c ( sln16 ) )
< / example >
< example title = " Make an outbound WebSocket connection using connection 'connection1' and the 'opus' codec. Passthrough mode will automatically be set. " >
same = > n , Dial ( WebSocket / connection1 / c ( opus ) )
< / example >
< example title = " Listen for an incoming WebSocket connection and don't auto-answer it. " >
same = > n , Dial ( WebSocket / INCOMING / n )
< / example >
< example title = " Add URI parameters. " >
same = > n , Dial ( WebSocket / connection1 / v ( $ { URIENCODE ( vari able ) } = $ { URIENCODE ( $ { CHANNEL } ) } , variable2 = $ ( URIENCODE ( $ { EXTEN } ) } ) )
< / example >
< / info >
* * */
# include "asterisk.h"
# include "asterisk/app.h"
@ -110,6 +40,7 @@
# 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"
@ -118,6 +49,26 @@
# include "asterisk/timing.h"
# include "asterisk/translate.h"
# include "asterisk/websocket_client.h"
# include "asterisk/sorcery.h"
static struct ast_sorcery * sorcery = NULL ;
enum webchan_control_msg_format {
WEBCHAN_CONTROL_MSG_FORMAT_PLAIN = 0 ,
WEBCHAN_CONTROL_MSG_FORMAT_JSON ,
WEBCHAN_CONTROL_MSG_FORMAT_INVALID ,
} ;
static const char * msg_format_map [ ] = {
[ WEBCHAN_CONTROL_MSG_FORMAT_PLAIN ] = " plain-text " ,
[ WEBCHAN_CONTROL_MSG_FORMAT_JSON ] = " json " ,
[ WEBCHAN_CONTROL_MSG_FORMAT_INVALID ] = " invalid " ,
} ;
struct webchan_conf_global {
SORCERY_OBJECT ( details ) ;
enum webchan_control_msg_format control_msg_format ;
} ;
static struct ast_websocket_server * ast_ws_server ;
@ -141,6 +92,7 @@ struct websocket_pvt {
size_t leftover_len ;
char * uri_params ;
char * leftover_data ;
enum webchan_control_msg_format control_msg_format ;
int no_auto_answer ;
int passthrough ;
int optimal_frame_size ;
@ -166,6 +118,7 @@ struct websocket_pvt {
# define PAUSE_MEDIA "PAUSE_MEDIA"
# define CONTINUE_MEDIA "CONTINUE_MEDIA"
#if 0
# define MEDIA_START "MEDIA_START"
# define MEDIA_XON "MEDIA_XON"
# define MEDIA_XOFF "MEDIA_XOFF"
@ -173,6 +126,7 @@ struct websocket_pvt {
# define DRIVER_STATUS "STATUS"
# define MEDIA_BUFFERING_COMPLETED "MEDIA_BUFFERING_COMPLETED"
# define DTMF_END "DTMF_END"
# endif
# define QUEUE_LENGTH_MAX 1000
# define QUEUE_LENGTH_XOFF_LEVEL 900
@ -198,6 +152,250 @@ static struct ast_channel_tech websocket_tech = {
. send_digit_end = webchan_send_dtmf_text ,
} ;
static enum webchan_control_msg_format control_msg_format_from_str ( const char * value )
{
if ( ast_strlen_zero ( value ) ) {
return WEBCHAN_CONTROL_MSG_FORMAT_INVALID ;
} else if ( strcasecmp ( value , msg_format_map [ WEBCHAN_CONTROL_MSG_FORMAT_PLAIN ] ) = = 0 ) {
return WEBCHAN_CONTROL_MSG_FORMAT_PLAIN ;
} else if ( strcasecmp ( value , msg_format_map [ WEBCHAN_CONTROL_MSG_FORMAT_JSON ] ) = = 0 ) {
return WEBCHAN_CONTROL_MSG_FORMAT_JSON ;
} else {
return WEBCHAN_CONTROL_MSG_FORMAT_INVALID ;
}
}
static const char * control_msg_format_to_str ( enum webchan_control_msg_format value )
{
if ( ! ARRAY_IN_BOUNDS ( value , msg_format_map ) ) {
return NULL ;
}
return msg_format_map [ value ] ;
}
/*!
* \ internal
* \ brief Catch - all to print events that don ' t have any data .
* \ warning Do not call directly .
*/
static char * _create_event_nodata ( struct websocket_pvt * instance , char * event )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " { s:s s:s } " ,
" event " , event ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
payload = ast_strdup ( event ) ;
}
return payload ;
}
# define _create_event_MEDIA_XON(_instance) _create_event_nodata(_instance, "MEDIA_XON");
# define _create_event_MEDIA_XOFF(_instance) _create_event_nodata(_instance, "MEDIA_XOFF");
# define _create_event_QUEUE_DRAINED(_instance) _create_event_nodata(_instance, "QUEUE_DRAINED");
/*!
* \ internal
* \ brief Print the MEDIA_START event .
* \ warning Do not call directly .
*/
static char * _create_event_MEDIA_START ( struct websocket_pvt * instance )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " {s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:o } " ,
" event " , " MEDIA_START " ,
" connection_id " , instance - > connection_id ,
" channel " , ast_channel_name ( instance - > channel ) ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" format " , ast_format_get_name ( instance - > native_format ) ,
" optimal_frame_size " , instance - > optimal_frame_size ,
" ptime " , instance - > native_codec - > default_ms ,
" channel_variables " , ast_json_channel_vars ( ast_channel_varshead (
instance - > channel ) )
) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
ast_asprintf ( & payload , " %s %s:%s %s:%s %s:%s %s:%s %s:%d %s:%d " ,
" MEDIA_START " ,
" connection_id " , instance - > connection_id ,
" channel " , ast_channel_name ( instance - > channel ) ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" format " , ast_format_get_name ( instance - > native_format ) ,
" optimal_frame_size " , instance - > optimal_frame_size ,
" ptime " , instance - > native_codec - > default_ms
) ;
}
return payload ;
}
/*!
* \ internal
* \ brief Print the MEDIA_BUFFERING_COMPLETED event .
* \ warning Do not call directly .
*/
static char * _create_event_MEDIA_BUFFERING_COMPLETED ( struct websocket_pvt * instance ,
const char * id )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " {s:s, s:s, s:s} " ,
" event " , " MEDIA_BUFFERING_COMPLETED " ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" correlation_id " , S_OR ( id , " " )
) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
ast_asprintf ( & payload , " %s%s%s " ,
" MEDIA_BUFFERING_COMPLETED " ,
S_COR ( id , " " , " " ) , S_OR ( id , " " ) ) ;
}
return payload ;
}
/*!
* \ internal
* \ brief Print the DTMF_END event .
* \ warning Do not call directly .
*/
static char * _create_event_DTMF_END ( struct websocket_pvt * instance ,
const char digit )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " {s:s, s:s, s:s#} " ,
" event " , " DTMF_END " ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" digit " , digit , 1
) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
ast_asprintf ( & payload , " %s digit:%c channel_id:%s " ,
" DTMF_END " , digit , ast_channel_uniqueid ( instance - > channel ) ) ;
}
return payload ;
}
/*!
* \ internal
* \ brief Print the STATUS event .
* \ warning Do not call directly .
*/
static char * _create_event_STATUS ( struct websocket_pvt * instance )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " {s:s, s:s, s:i, s:i, s:i, s:b, s:b, s:b } " ,
" event " , " STATUS " ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" queue_length " , instance - > frame_queue_length ,
" xon_level " , QUEUE_LENGTH_XON_LEVEL ,
" xoff_level " , QUEUE_LENGTH_XOFF_LEVEL ,
" queue_full " , instance - > queue_full ,
" bulk_media " , instance - > bulk_media_in_progress ,
" media_paused " , instance - > queue_paused
) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
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 " ,
" STATUS " ,
ast_channel_uniqueid ( instance - > channel ) ,
instance - > frame_queue_length , QUEUE_LENGTH_XON_LEVEL ,
QUEUE_LENGTH_XOFF_LEVEL ,
S_COR ( instance - > queue_full , " true " , " false " ) ,
S_COR ( instance - > bulk_media_in_progress , " true " , " false " ) ,
S_COR ( instance - > queue_paused , " true " , " false " )
) ;
}
return payload ;
}
/*!
* \ internal
* \ brief Print the ERROR event .
* \ warning Do not call directly .
*/
static char * _create_event_ERROR ( struct websocket_pvt * instance ,
const char * error_text )
{
char * payload = NULL ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json * msg = ast_json_pack ( " {s:s, s:s, s:s} " ,
" event " , " ERROR " ,
" channel_id " , ast_channel_uniqueid ( instance - > channel ) ,
" error_text " , error_text ) ;
if ( ! msg ) {
return NULL ;
}
payload = ast_json_dump_string_format ( msg , AST_JSON_COMPACT ) ;
ast_json_unref ( msg ) ;
} else {
ast_asprintf ( & payload , " %s channel_id:%s error_text:%s " ,
" ERROR " , ast_channel_uniqueid ( instance - > channel ) , error_text ) ;
}
return payload ;
}
/*!
* \ def create_event
* \ brief Use this macro to create events passing in any event - specific parameters .
*/
# define create_event(_instance, _event, ...) \
_create_event_ # # _event ( _instance , # # __VA_ARGS__ )
/*!
* \ def send_event
* \ brief Use this macro to create and send events passing in any event - specific parameters .
*/
# define send_event(_instance, _event, ...) \
( { \
int _res = - 1 ; \
char * _payload = _create_event_ # # _event ( _instance , # # __VA_ARGS__ ) ; \
if ( _payload ) { \
_res = ast_websocket_write_string ( _instance - > websocket , _payload ) ; \
if ( _res ! = 0 ) { \
ast_log ( LOG_ERROR , " %s: Unable to send event %s \n " , \
ast_channel_name ( instance - > channel ) , _payload ) ; \
} else { \
ast_debug ( 4 , " %s: Sent %s \n " , \
ast_channel_name ( instance - > channel ) , _payload ) ; \
} \
ast_free ( _payload ) ; \
} \
( _res ) ; \
} )
static void set_channel_format ( struct websocket_pvt * instance ,
struct ast_format * fmt )
{
@ -240,7 +438,7 @@ static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
instance - > queue_full = 0 ;
ast_debug ( 4 , " %s: WebSocket sending MEDIA_XON \n " ,
ast_channel_name ( instance - > channel ) ) ;
ast_websocket_write_string( instance - > websocket , MEDIA_XON ) ;
send_event( instance , MEDIA_XON ) ;
}
queued_frame = AST_LIST_REMOVE_HEAD ( & instance - > frame_queue , frame_list ) ;
@ -256,7 +454,7 @@ static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
instance - > report_queue_drained = 0 ;
ast_debug ( 4 , " %s: WebSocket sending QUEUE_DRAINED \n " ,
ast_channel_name ( instance - > channel ) ) ;
ast_websocket_write_string( instance - > websocket , QUEUE_DRAINED ) ;
send_event( instance , QUEUE_DRAINED ) ;
}
return NULL ;
}
@ -284,7 +482,7 @@ static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
*/
ast_websocket_write_string ( instance - > websocket ,
queued_frame - > data . ptr ) ;
ast_debug ( 4 , " %s: Web Sock et sending %s\n " ,
ast_debug ( 4 , " %s: Sen t %s\n " ,
ast_channel_name ( instance - > channel ) , ( char * ) queued_frame - > data . ptr ) ;
}
/*
@ -445,9 +643,7 @@ static int queue_frame_from_buffer(struct websocket_pvt *instance,
instance - > frame_queue_length + + ;
if ( ! instance - > queue_full & & instance - > frame_queue_length > = QUEUE_LENGTH_XOFF_LEVEL ) {
instance - > queue_full = 1 ;
ast_debug ( 4 , " %s: WebSocket sending %s \n " ,
ast_channel_name ( instance - > channel ) , MEDIA_XOFF ) ;
ast_websocket_write_string ( instance - > websocket , MEDIA_XOFF ) ;
send_event ( instance , MEDIA_XOFF ) ;
}
}
@ -484,30 +680,39 @@ static int queue_option_frame(struct websocket_pvt *instance,
return 0 ;
}
static int process_text_message ( struct websocket_pvt * instance ,
char * payload , uint64_t payload_len )
/*!
* \ internal
* \ brief Handle commands from the websocket
*
* \ param instance
* \ param buffer Allocated by caller so don ' t free .
* \ retval 0 Success
* \ retval - 1 Failure
*/
static int handle_command ( struct websocket_pvt * instance , char * buffer )
{
int res = 0 ;
char * command ;
if ( payload_len > MAX_TEXT_MESSAGE_LEN ) {
ast_log ( LOG_WARNING , " %s: WebSocket TEXT message of length %d exceeds maximum length of %d \n " ,
ast_channel_name ( instance - > channel ) , ( int ) payload_len , MAX_TEXT_MESSAGE_LEN ) ;
return 0 ;
}
RAII_VAR ( struct ast_json * , json , NULL , ast_json_unref ) ;
const char * command = NULL ;
char * data = NULL ;
/*
* Unfortunately , payload is not NULL terminated even when it ' s
* a TEXT frame so we need to allocate a new buffer , copy
* the data into it , and NULL terminate it .
*/
command = ast_alloca ( payload_len + 1 ) ;
memcpy ( command , payload , payload_len ) ; /* Safe */
command [ payload_len ] = ' \0 ' ;
command = ast_strip ( command ) ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
struct ast_json_error json_error ;
ast_debug ( 4 , " %s: WebSocket %s command received \n " ,
ast_channel_name ( instance - > channel ) , command ) ;
json = ast_json_load_buf ( buffer , strlen ( buffer ) , & json_error ) ;
if ( ! json ) {
send_event ( instance , ERROR , " Unable to parse JSON command " ) ;
return - 1 ;
}
command = ast_json_object_string_get ( json , " command " ) ;
} else {
command = buffer ;
data = strchr ( buffer , ' ' ) ;
if ( data ) {
* data = ' \0 ' ;
data + + ;
}
}
if ( ast_strings_equal ( command , ANSWER_CHANNEL ) ) {
ast_queue_control ( instance - > channel , AST_CONTROL_ANSWER ) ;
@ -525,13 +730,17 @@ static int process_text_message(struct websocket_pvt *instance,
instance - > bulk_media_in_progress = 1 ;
AST_LIST_UNLOCK ( & instance - > frame_queue ) ;
} else if ( ast_ begins_with ( command , STOP_MEDIA_BUFFERING ) ) {
char * id ;
} else if ( ast_ strings_equal ( command , STOP_MEDIA_BUFFERING ) ) {
const char * id ;
char * option ;
SCOPED_LOCK ( frame_queue_lock , & instance - > frame_queue , AST_LIST_LOCK ,
AST_LIST_UNLOCK ) ;
id = ast_strip ( command + strlen ( STOP_MEDIA_BUFFERING ) ) ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_JSON ) {
id = ast_json_object_string_get ( json , " correlation_id " ) ;
} else {
id = data ;
}
if ( instance - > passthrough ) {
ast_debug ( 4 , " %s: WebSocket in passthrough mode. Ignoring %s command. \n " ,
@ -551,10 +760,9 @@ static int process_text_message(struct websocket_pvt *instance,
}
}
instance - > leftover_len = 0 ;
res = ast_asprintf ( & option , " %s%s%s " , MEDIA_BUFFERING_COMPLETED ,
S_COR ( ! ast_strlen_zero ( id ) , " " , " " ) , S_OR ( id , " " ) ) ;
if ( res < = 0 | | ! option ) {
return res ;
option = create_event ( instance , MEDIA_BUFFERING_COMPLETED , id ) ;
if ( ! option ) {
return - 1 ;
}
res = queue_option_frame ( instance , option ) ;
ast_free ( option ) ;
@ -589,26 +797,7 @@ static int process_text_message(struct websocket_pvt *instance,
AST_LIST_UNLOCK ( & instance - > frame_queue ) ;
} else if ( ast_strings_equal ( command , GET_DRIVER_STATUS ) ) {
char * status = NULL ;
res = ast_asprintf ( & status , " %s channel_id:%s queue_length:%d xon_level:%d xoff_level:%d queue_full:%s bulk_media:%s media_paused:%s " ,
DRIVER_STATUS ,
ast_channel_uniqueid ( instance - > channel ) ,
instance - > frame_queue_length , QUEUE_LENGTH_XON_LEVEL ,
QUEUE_LENGTH_XOFF_LEVEL ,
S_COR ( instance - > queue_full , " true " , " false " ) ,
S_COR ( instance - > bulk_media_in_progress , " true " , " false " ) ,
S_COR ( instance - > queue_paused , " true " , " false " )
) ;
if ( res < = 0 | | ! status ) {
ast_free ( status ) ;
res = - 1 ;
} else {
ast_debug ( 4 , " %s: WebSocket status: %s \n " ,
ast_channel_name ( instance - > channel ) , status ) ;
res = ast_websocket_write_string ( instance - > websocket , status ) ;
ast_free ( status ) ;
}
return send_event ( instance , STATUS ) ;
} else if ( ast_strings_equal ( command , PAUSE_MEDIA ) ) {
if ( instance - > passthrough ) {
@ -638,6 +827,39 @@ static int process_text_message(struct websocket_pvt *instance,
return res ;
}
static int process_text_message ( struct websocket_pvt * instance ,
char * payload , uint64_t payload_len )
{
char * command ;
if ( payload_len = = 0 ) {
ast_log ( LOG_WARNING , " %s: WebSocket TEXT message has 0 length \n " ,
ast_channel_name ( instance - > channel ) ) ;
return 0 ;
}
if ( payload_len > MAX_TEXT_MESSAGE_LEN ) {
ast_log ( LOG_WARNING , " %s: WebSocket TEXT message of length %d exceeds maximum length of %d \n " ,
ast_channel_name ( instance - > channel ) , ( int ) payload_len , MAX_TEXT_MESSAGE_LEN ) ;
return 0 ;
}
/*
* Unfortunately , payload is not NULL terminated even when it ' s
* a TEXT frame so we need to allocate a new buffer , copy
* the data into it , and NULL terminate it .
*/
command = ast_alloca ( payload_len + 1 ) ;
memcpy ( command , payload , payload_len ) ; /* Safe */
command [ payload_len ] = ' \0 ' ;
command = ast_strip ( command ) ;
ast_debug ( 4 , " %s: Received: %s \n " ,
ast_channel_name ( instance - > channel ) , command ) ;
return handle_command ( instance , command ) ;
}
static int process_binary_message ( struct websocket_pvt * instance ,
char * payload , uint64_t payload_len )
{
@ -838,34 +1060,15 @@ static int read_from_ws_and_queue(struct websocket_pvt *instance)
static void * read_thread_handler ( void * obj )
{
RAII_VAR ( struct websocket_pvt * , instance , obj , ao2_cleanup ) ;
RAII_VAR ( char * , command , NULL , ast_free ) ;
int res = 0 ;
ast_debug ( 3 , " %s: Read thread started \n " , ast_channel_name ( instance - > channel ) ) ;
/*
* We need to tell the remote app what channel this media is for .
* This is especially important for outbound connections otherwise
* the app won ' t know who the media is for .
*/
res = ast_asprintf ( & command , " %s connection_id:%s channel:%s channel_id:%s format:%s optimal_frame_size:%d ptime:%d " , MEDIA_START ,
instance - > connection_id , ast_channel_name ( instance - > channel ) ,
ast_channel_uniqueid ( instance - > channel ) ,
ast_format_get_name ( instance - > native_format ) ,
instance - > optimal_frame_size , instance - > native_codec - > default_ms ) ;
if ( res < = 0 | | ! command ) {
ast_queue_control ( instance - > channel , AST_CONTROL_HANGUP ) ;
ast_log ( LOG_ERROR , " %s: Failed to create MEDIA_START \n " , ast_channel_name ( instance - > channel ) ) ;
return NULL ;
}
res = ast_websocket_write_string ( instance - > websocket , command ) ;
if ( res ! = 0 ) {
ast_log ( LOG_ERROR , " %s: Failed to send MEDIA_START \n " , ast_channel_name ( instance - > channel ) ) ;
res = send_event ( instance , MEDIA_START ) ;
if ( res ! = 0 ) {
ast_queue_control ( instance - > channel , AST_CONTROL_HANGUP ) ;
return NULL ;
}
ast_debug ( 3 , " %s: Sent %s \n " , ast_channel_name ( instance - > channel ) ,
command ) ;
if ( ! instance - > no_auto_answer ) {
ast_debug ( 3 , " %s: ANSWER by auto_answer \n " , ast_channel_name ( instance - > channel ) ) ;
@ -1277,6 +1480,7 @@ enum {
OPT_WS_NO_AUTO_ANSWER = ( 1 < < 1 ) ,
OPT_WS_URI_PARAM = ( 1 < < 2 ) ,
OPT_WS_PASSTHROUGH = ( 1 < < 3 ) ,
OPT_WS_MSG_FORMAT = ( 1 < < 4 ) ,
} ;
enum {
@ -1284,6 +1488,7 @@ enum {
OPT_ARG_WS_NO_AUTO_ANSWER ,
OPT_ARG_WS_URI_PARAM ,
OPT_ARG_WS_PASSTHROUGH ,
OPT_ARG_WS_MSG_FORMAT ,
OPT_ARG_ARRAY_SIZE
} ;
@ -1292,6 +1497,7 @@ AST_APP_OPTIONS(websocket_options, BEGIN_OPTIONS
AST_APP_OPTION ( ' n ' , OPT_WS_NO_AUTO_ANSWER ) ,
AST_APP_OPTION_ARG ( ' v ' , OPT_WS_URI_PARAM , OPT_ARG_WS_URI_PARAM ) ,
AST_APP_OPTION ( ' p ' , OPT_WS_PASSTHROUGH ) ,
AST_APP_OPTION_ARG ( ' f ' , OPT_WS_MSG_FORMAT , OPT_ARG_WS_MSG_FORMAT ) ,
END_OPTIONS ) ;
static struct ast_channel * webchan_request ( const char * type ,
@ -1310,6 +1516,9 @@ static struct ast_channel *webchan_request(const char *type,
struct ast_flags opts = { 0 , } ;
char * opt_args [ OPT_ARG_ARRAY_SIZE ] ;
const char * requestor_name = requestor ? ast_channel_name ( requestor ) : " no channel " ;
RAII_VAR ( struct webchan_conf_global * , global_cfg , NULL , ao2_cleanup ) ;
global_cfg = ast_sorcery_retrieve_by_id ( sorcery , " global " , " global " ) ;
ast_debug ( 3 , " %s: WebSocket channel requested \n " ,
requestor_name ) ;
@ -1405,6 +1614,19 @@ static struct ast_channel *webchan_request(const char *type,
ast_debug ( 3 , " %s: Using final URI '%s' \n " , requestor_name , instance - > uri_params ) ;
}
if ( ast_test_flag ( & opts , OPT_WS_MSG_FORMAT ) ) {
instance - > control_msg_format = control_msg_format_from_str ( opt_args [ OPT_ARG_WS_MSG_FORMAT ] ) ;
if ( instance - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_INVALID ) {
ast_log ( LOG_WARNING , " %s: 'f/control message format' dialstring parameter value missing or invalid. "
" Defaulting to 'plain-text' \n " ,
ast_channel_name ( requestor ) ) ;
instance - > control_msg_format = WEBCHAN_CONTROL_MSG_FORMAT_PLAIN ;
}
} else if ( global_cfg ) {
instance - > control_msg_format = global_cfg - > control_msg_format ;
}
chan = ast_channel_alloc ( 1 , AST_STATE_DOWN , " " , " " , " " , " " , " " , assignedids ,
requestor , 0 , " WebSocket/%s/%p " , args . connection_id , instance ) ;
if ( ! chan ) {
@ -1502,28 +1724,13 @@ static int webchan_hangup(struct ast_channel *ast)
static int webchan_send_dtmf_text ( struct ast_channel * ast , char digit , unsigned int duration )
{
struct websocket_pvt * instance = ast_channel_tech_pvt ( ast ) ;
char * command ;
int res = 0 ;
struct websocket_pvt * instance = ast_channel_tech_pvt ( ast ) ;
if ( ! instance ) {
return - 1 ;
}
if ( ! instance ) {
return - 1 ;
}
res = ast_asprintf ( & command , " %s digit:%c channel_id:%s " , DTMF_END , digit , ast_channel_uniqueid ( instance - > channel ) ) ;
if ( res < = 0 | | ! command ) {
ast_log ( LOG_ERROR , " %s: Failed to create DTMF_END \n " , ast_channel_name ( instance - > channel ) ) ;
return 0 ;
}
res = ast_websocket_write_string ( instance - > websocket , command ) ;
if ( res ! = 0 ) {
ast_log ( LOG_ERROR , " %s: Failed to send DTMF_END \n " , ast_channel_name ( instance - > channel ) ) ;
ast_free ( command ) ;
return 0 ;
}
ast_debug ( 3 , " %s: Sent %s \n " , ast_channel_name ( instance - > channel ) , command ) ;
ast_free ( command ) ;
return 0 ;
return send_event ( instance , DTMF_END , digit ) ;
}
/*!
@ -1687,6 +1894,85 @@ static struct ast_http_uri http_uri = {
. no_decode_uri = 1 ,
} ;
AO2_STRING_FIELD_HASH_FN ( instance_proxy , connection_id )
AO2_STRING_FIELD_CMP_FN ( instance_proxy , connection_id )
AO2_STRING_FIELD_SORT_FN ( instance_proxy , connection_id )
static int global_control_message_format_from_str ( const struct aco_option * opt ,
struct ast_variable * var , void * obj )
{
struct webchan_conf_global * cfg = obj ;
cfg - > control_msg_format = control_msg_format_from_str ( var - > value ) ;
if ( cfg - > control_msg_format = = WEBCHAN_CONTROL_MSG_FORMAT_INVALID ) {
ast_log ( LOG_ERROR , " chan_websocket.conf: Invalid value '%s' for "
" control_mesage_format. Must be 'plain-text' or 'json' \n " ,
var - > value ) ;
return - 1 ;
}
return 0 ;
}
static int global_control_message_format_to_str ( const void * obj , const intptr_t * args , char * * buf )
{
const struct webchan_conf_global * cfg = obj ;
* buf = ast_strdup ( control_msg_format_to_str ( cfg - > control_msg_format ) ) ;
return 0 ;
}
static void * global_alloc ( const char * name )
{
struct webchan_conf_global * cfg = ast_sorcery_generic_alloc (
sizeof ( * cfg ) , NULL ) ;
if ( ! cfg ) {
return NULL ;
}
return cfg ;
}
static int global_apply ( const struct ast_sorcery * sorcery , void * obj )
{
struct webchan_conf_global * cfg = obj ;
ast_debug ( 1 , " control_msg_format: %s \n " ,
control_msg_format_to_str ( cfg - > control_msg_format ) ) ;
return 0 ;
}
static int load_config ( void )
{
ast_debug ( 2 , " Initializing Websocket Client Configuration \n " ) ;
sorcery = ast_sorcery_open ( ) ;
if ( ! sorcery ) {
ast_log ( LOG_ERROR , " Failed to open sorcery \n " ) ;
return - 1 ;
}
ast_sorcery_apply_default ( sorcery , " global " , " config " ,
" chan_websocket.conf,criteria=type=global,single_object=yes,explicit_name=global " ) ;
if ( ast_sorcery_object_register ( sorcery , " global " , global_alloc , NULL , global_apply ) ) {
ast_log ( LOG_ERROR , " Failed to register chan_websocket global object with sorcery \n " ) ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return - 1 ;
}
ast_sorcery_object_field_register_nodoc ( sorcery , " global " , " type " , " " , OPT_NOOP_T , 0 , 0 ) ;
ast_sorcery_register_cust ( global , control_message_format , " plain-text " ) ;
ast_sorcery_load ( sorcery ) ;
return 0 ;
}
/*! \brief Function called when our module is unloaded */
static int unload_module ( void )
{
@ -1701,12 +1987,19 @@ static int unload_module(void)
ao2_cleanup ( instances ) ;
instances = NULL ;
ast_sorcery_unref ( sorcery ) ;
sorcery = NULL ;
return 0 ;
}
AO2_STRING_FIELD_HASH_FN ( instance_proxy , connection_id )
AO2_STRING_FIELD_CMP_FN ( instance_proxy , connection_id )
AO2_STRING_FIELD_SORT_FN ( instance_proxy , connection_id )
static int reload_module ( void )
{
ast_debug ( 2 , " Reloading chan_websocket configuration \n " ) ;
ast_sorcery_reload ( sorcery ) ;
return 0 ;
}
/*! \brief Function called when our module is loaded */
static int load_module ( void )
@ -1714,6 +2007,11 @@ static int load_module(void)
int res = 0 ;
struct ast_websocket_protocol * protocol ;
res = load_config ( ) ;
if ( res ! = 0 ) {
return AST_MODULE_LOAD_DECLINE ;
}
if ( ! ( websocket_tech . capabilities = ast_format_cap_alloc ( AST_FORMAT_CAP_FLAG_DEFAULT ) ) ) {
return AST_MODULE_LOAD_DECLINE ;
}
@ -1758,6 +2056,7 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Websocket Media Chann
. 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 " ,
) ;