Asterisk - The Open Source Telephony Project GIT-master-0a46be9
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/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 dependency graph for chan_websocket.c:

Go to the source code of this file.

Data Structures

struct  instance_proxy
 
struct  websocket_pvt
 

Macros

#define ANSWER_CHANNEL   "ANSWER"
 
#define CONTINUE_MEDIA   "CONTINUE_MEDIA"
 
#define DRIVER_STATUS   "STATUS"
 
#define FLUSH_MEDIA   "FLUSH_MEDIA"
 
#define GET_DRIVER_STATUS   "GET_STATUS"
 
#define HANGUP_CHANNEL   "HANGUP"
 
#define INCOMING_CONNECTION_ID   "INCOMING"
 
#define MAX_TEXT_MESSAGE_LEN   MIN(128, (AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE - 1))
 
#define MEDIA_BUFFERING_COMPLETED   "MEDIA_BUFFERING_COMPLETED"
 
#define MEDIA_START   "MEDIA_START"
 
#define MEDIA_WEBSOCKET_CONNECTION_ID   "MEDIA_WEBSOCKET_CONNECTION_ID"
 
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE   "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"
 
#define MEDIA_XOFF   "MEDIA_XOFF"
 
#define MEDIA_XON   "MEDIA_XON"
 
#define PAUSE_MEDIA   "PAUSE_MEDIA"
 
#define QUEUE_DRAINED   "QUEUE_DRAINED"
 
#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 START_MEDIA_BUFFERING   "START_MEDIA_BUFFERING"
 
#define STOP_MEDIA_BUFFERING   "STOP_MEDIA_BUFFERING"
 

Enumerations

enum  { OPT_WS_CODEC = (1 << 0) , OPT_WS_NO_AUTO_ANSWER = (1 << 1) , OPT_WS_URI_PARAM = (1 << 2) }
 
enum  { OPT_ARG_WS_CODEC , OPT_ARG_WS_NO_AUTO_ANSWER , OPT_ARG_WS_URI_PARAM , OPT_ARG_ARRAY_SIZE }
 

Functions

static void __reg_module (void)
 
static void __unreg_module (void)
 
struct ast_moduleAST_MODULE_SELF_SYM (void)
 
static struct ast_framedequeue_frame (struct websocket_pvt *instance)
 
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_module (void)
 Function called when our module is loaded. More...
 
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 void * read_thread_handler (void *obj)
 
static void set_channel_format (struct websocket_pvt *instance, struct ast_format *fmt)
 
static int set_channel_timer (struct websocket_pvt *instance)
 
static int set_channel_variables (struct websocket_pvt *instance)
 
static int set_instance_silence_frame (struct websocket_pvt *instance)
 
static int set_instance_translator (struct websocket_pvt *instance)
 
static int unload_module (void)
 Function called when our module is unloaded. More...
 
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_write (struct ast_channel *ast, struct ast_frame *f)
 Function called when we should write a frame to the channel. More...
 
static void websocket_destructor (void *data)
 
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 = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_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 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 }, }
 
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

◆ ANSWER_CHANNEL

#define ANSWER_CHANNEL   "ANSWER"

Definition at line 143 of file chan_websocket.c.

◆ CONTINUE_MEDIA

#define CONTINUE_MEDIA   "CONTINUE_MEDIA"

Definition at line 151 of file chan_websocket.c.

◆ DRIVER_STATUS

#define DRIVER_STATUS   "STATUS"

Definition at line 157 of file chan_websocket.c.

◆ FLUSH_MEDIA

#define FLUSH_MEDIA   "FLUSH_MEDIA"

Definition at line 147 of file chan_websocket.c.

◆ GET_DRIVER_STATUS

#define GET_DRIVER_STATUS   "GET_STATUS"

Definition at line 148 of file chan_websocket.c.

◆ HANGUP_CHANNEL

#define HANGUP_CHANNEL   "HANGUP"

Definition at line 144 of file chan_websocket.c.

◆ INCOMING_CONNECTION_ID

#define INCOMING_CONNECTION_ID   "INCOMING"

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

◆ MEDIA_BUFFERING_COMPLETED

#define MEDIA_BUFFERING_COMPLETED   "MEDIA_BUFFERING_COMPLETED"

Definition at line 158 of file chan_websocket.c.

◆ MEDIA_START

#define MEDIA_START   "MEDIA_START"

Definition at line 153 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_CONNECTION_ID

#define MEDIA_WEBSOCKET_CONNECTION_ID   "MEDIA_WEBSOCKET_CONNECTION_ID"

Definition at line 140 of file chan_websocket.c.

◆ MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE

#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE   "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"

Definition at line 139 of file chan_websocket.c.

◆ MEDIA_XOFF

#define MEDIA_XOFF   "MEDIA_XOFF"

Definition at line 155 of file chan_websocket.c.

◆ MEDIA_XON

#define MEDIA_XON   "MEDIA_XON"

Definition at line 154 of file chan_websocket.c.

◆ PAUSE_MEDIA

#define PAUSE_MEDIA   "PAUSE_MEDIA"

Definition at line 150 of file chan_websocket.c.

◆ QUEUE_DRAINED

#define QUEUE_DRAINED   "QUEUE_DRAINED"

Definition at line 156 of file chan_websocket.c.

◆ QUEUE_LENGTH_MAX

#define QUEUE_LENGTH_MAX   1000

Definition at line 160 of file chan_websocket.c.

◆ QUEUE_LENGTH_XOFF_LEVEL

#define QUEUE_LENGTH_XOFF_LEVEL   900

Definition at line 161 of file chan_websocket.c.

◆ QUEUE_LENGTH_XON_LEVEL

#define QUEUE_LENGTH_XON_LEVEL   800

Definition at line 162 of file chan_websocket.c.

◆ REPORT_QUEUE_DRAINED

#define REPORT_QUEUE_DRAINED   "REPORT_QUEUE_DRAINED"

Definition at line 149 of file chan_websocket.c.

◆ START_MEDIA_BUFFERING

#define START_MEDIA_BUFFERING   "START_MEDIA_BUFFERING"

Definition at line 145 of file chan_websocket.c.

◆ STOP_MEDIA_BUFFERING

#define STOP_MEDIA_BUFFERING   "STOP_MEDIA_BUFFERING"

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

Definition at line 1191 of file chan_websocket.c.

1191 {
1192 OPT_WS_CODEC = (1 << 0),
1193 OPT_WS_NO_AUTO_ANSWER = (1 << 1),
1194 OPT_WS_URI_PARAM = (1 << 2),
1195};
@ OPT_WS_CODEC
@ OPT_WS_URI_PARAM
@ OPT_WS_NO_AUTO_ANSWER

◆ anonymous enum

anonymous enum
Enumerator
OPT_ARG_WS_CODEC 
OPT_ARG_WS_NO_AUTO_ANSWER 
OPT_ARG_WS_URI_PARAM 
OPT_ARG_ARRAY_SIZE 

Definition at line 1197 of file chan_websocket.c.

1197 {
1202};
@ OPT_ARG_WS_URI_PARAM
@ OPT_ARG_WS_CODEC
@ OPT_ARG_WS_NO_AUTO_ANSWER
@ OPT_ARG_ARRAY_SIZE

Function Documentation

◆ __reg_module()

static void __reg_module ( void  )
static

Definition at line 1647 of file chan_websocket.c.

◆ __unreg_module()

static void __unreg_module ( void  )
static

Definition at line 1647 of file chan_websocket.c.

◆ AST_MODULE_SELF_SYM()

struct ast_module * AST_MODULE_SELF_SYM ( void  )

Definition at line 1647 of file chan_websocket.c.

◆ dequeue_frame()

static struct ast_frame * dequeue_frame ( struct websocket_pvt instance)
static

Definition at line 198 of file chan_websocket.c.

199{
200 struct ast_frame *queued_frame = NULL;
201 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
203
204 /*
205 * If the queue is paused, don't read a frame. Processing
206 * will continue down the function and a silence frame will
207 * be sent in its place.
208 */
209 if (instance->queue_paused) {
210 return NULL;
211 }
212
213 /*
214 * We need to check if we need to send an XON before anything
215 * else because there are multiple escape paths in this function
216 * and we don't want to accidentally keep the queue in a "full"
217 * state.
218 */
219 if (instance->queue_full && instance->frame_queue_length < QUEUE_LENGTH_XON_LEVEL) {
220 instance->queue_full = 0;
221 ast_debug(4, "%s: WebSocket sending MEDIA_XON\n",
222 ast_channel_name(instance->channel));
224 }
225
226 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
227
228 /*
229 * If there are no frames in the queue, we need to
230 * return NULL so we can send a silence frame. We also need
231 * to send the QUEUE_DRAINED notification if we were requested
232 * to do so.
233 */
234 if (!queued_frame) {
235 if (instance->report_queue_drained) {
236 instance->report_queue_drained = 0;
237 ast_debug(4, "%s: WebSocket sending QUEUE_DRAINED\n",
238 ast_channel_name(instance->channel));
240 }
241 return NULL;
242 }
243
244 /*
245 * The only way a control frame could be present here is as
246 * a result of us calling queue_option_frame() in response
247 * to an incoming TEXT command from the websocket.
248 * We'll be safe and make sure it's a AST_CONTROL_OPTION
249 * frame anyway.
250 *
251 * It's quite possible that there are multiple control frames
252 * in a row in the queue so we need to process consecutive ones
253 * immediately.
254 *
255 * In any case, processing a control frame MUST not use up
256 * a media timeslot so after all control frames have been
257 * processed, we need to read an audio frame and process it.
258 */
259 while (queued_frame && queued_frame->frametype == AST_FRAME_CONTROL) {
260 if (queued_frame->subclass.integer == AST_CONTROL_OPTION) {
261 /*
262 * We just need to send the data to the websocket.
263 * The data should already be NULL terminated.
264 */
266 queued_frame->data.ptr);
267 ast_debug(4, "%s: WebSocket sending %s\n",
268 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
269 }
270 /*
271 * We do NOT send these to the core so we need to free
272 * the frame and grab the next one. If it's also a
273 * control frame, we need to process it otherwise
274 * continue down in the function.
275 */
276 ast_frame_free(queued_frame, 0);
277 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
278 /*
279 * Jut FYI... We didn't bump the queue length when we added the control
280 * frames so we don't need to decrement it here.
281 */
282 }
283
284 /*
285 * If, after reading all control frames, there are no frames
286 * left in the queue, we need to return NULL so we can send
287 * a silence frame.
288 */
289 if (!queued_frame) {
290 return NULL;
291 }
292
293 instance->frame_queue_length--;
294
295 return queued_frame;
296}
#define QUEUE_LENGTH_XON_LEVEL
#define QUEUE_DRAINED
#define MEDIA_XON
const char * ast_channel_name(const struct ast_channel *chan)
int ast_websocket_write_string(struct ast_websocket *ws, const char *buf)
Construct and transmit a WebSocket frame containing string data.
void ast_frame_free(struct ast_frame *frame, int cache)
Frees a frame or list of frames.
Definition: main/frame.c:176
@ AST_FRAME_CONTROL
@ AST_CONTROL_OPTION
#define ast_debug(level,...)
Log a DEBUG message.
#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.
Definition: linkedlists.h:833
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define SCOPED_LOCK(varname, lock, lockfunc, unlockfunc)
Scoped Locks.
Definition: lock.h:590
#define NULL
Definition: resample.c:96
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@231 data
struct ast_channel * channel
struct websocket_pvt::@139 frame_queue
struct ast_websocket * websocket

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, MEDIA_XON, NULL, ast_frame::ptr, QUEUE_DRAINED, websocket_pvt::queue_full, QUEUE_LENGTH_XON_LEVEL, websocket_pvt::queue_paused, websocket_pvt::report_queue_drained, SCOPED_LOCK, ast_frame::subclass, and websocket_pvt::websocket.

Referenced by webchan_read().

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

1424{
1425 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1426 struct ast_variable *v;
1427 const char *connection_id = NULL;
1428 struct websocket_pvt *instance = NULL;
1429 int nodelay = 1;
1430
1431 ast_debug(3, "WebSocket established\n");
1432
1433 for (v = upgrade_headers; v; v = v->next) {
1434 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1435 }
1436 for (v = get_params; v; v = v->next) {
1437 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1438 }
1439
1440 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1441 if (!connection_id) {
1442 /*
1443 * This can't really happen because websocket_http_callback won't
1444 * let it get this far if it can't add the connection_id to the
1445 * get_params.
1446 * Just in case though...
1447 */
1448 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1450 ast_websocket_close(ast_ws_session, 1000);
1451 return;
1452 }
1453
1455 if (!instance) {
1456 /*
1457 * This also can't really happen because websocket_http_callback won't
1458 * let it get this far if it can't find the instance.
1459 * Just in case though...
1460 */
1461 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1463 ast_websocket_close(ast_ws_session, 1000);
1464 return;
1465 }
1466 instance->websocket = ao2_bump(ast_ws_session);
1467
1468 if (setsockopt(ast_websocket_fd(instance->websocket),
1469 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1470 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on manager connection: %s\n", strerror(errno));
1471 }
1472
1473 /* read_thread_handler cleans up the bump */
1474 read_thread_handler(ao2_bump(instance));
1475
1476 ao2_cleanup(instance);
1477 ast_debug(3, "WebSocket closed\n");
1478}
#define ast_log
Definition: astobj2.c:42
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#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
@ 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
static void * read_thread_handler(void *obj)
static struct ao2_container * instances
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
Queue a control frame without payload.
Definition: channel.c:1270
int 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.
void ast_websocket_unref(struct ast_websocket *session)
Decrease the reference count for a WebSocket session.
int ast_websocket_fd(struct ast_websocket *session)
Get the file descriptor for a WebSocket session.
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.
Definition: main/config.c:1013
@ AST_CONTROL_HANGUP
#define LOG_WARNING
int errno
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
Structure definition for session.
char connection_id[0]
#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:978

References ao2_bump, ao2_cleanup, ao2_weakproxy_find, AST_CONTROL_HANGUP, ast_debug, ast_log, ast_queue_control(), ast_variable_find_in_list(), ast_websocket_close(), ast_websocket_fd(), ast_websocket_unref(), websocket_pvt::channel, websocket_pvt::connection_id, errno, instances, LOG_WARNING, ast_variable::name, ast_variable::next, NULL, OBJ_NOLOCK, OBJ_SEARCH_KEY, RAII_VAR, read_thread_handler(), ast_variable::value, and websocket_pvt::websocket.

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

1494{
1495 struct ast_http_uri fake_urih = {
1497 };
1498 int res = 0;
1499 /*
1500 * Normally the http server will destroy the get_params
1501 * when the session ends but if there weren't any initially
1502 * and we create some and add them to the list, the http server
1503 * won't know about it so we have to destroy it ourselves.
1504 */
1505 int destroy_get_params = (get_params == NULL);
1506 struct ast_variable *v = NULL;
1507 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1508
1509 ast_debug(2, "URI: %s Starting\n", uri);
1510
1511 /*
1512 * The client will have issued the GET request with a URI of
1513 * /media/<connection_id>
1514 *
1515 * Since this callback is registered for the /media URI prefix the
1516 * http server will strip that off the front of the URI passing in
1517 * only the path components after that in the 'uri' parameter.
1518 * This should leave only the connection id without a leading '/'.
1519 */
1520 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1521 if (!instance) {
1522 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
1523 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
1524 return -1;
1525 }
1526
1527 /*
1528 * We don't allow additional connections using the same connection id.
1529 */
1530 if (instance->websocket) {
1531 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
1532 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
1533 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
1534 return -1;
1535 }
1536
1537 v = ast_variable_new("CONNECTION_ID", uri, "");
1538 if (!v) {
1539 ast_http_error(ser, 500, "Server error", "");
1540 return -1;
1541 }
1542 ast_variable_list_append(&get_params, v);
1543
1544 for (v = get_params; v; v = v->next) {
1545 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1546 }
1547
1548 /*
1549 * This will ultimately call internal_ws_established_cb() so
1550 * this function will block until the websocket is closed and
1551 * internal_ws_established_cb() returns;
1552 */
1553 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
1554 get_params, headers);
1555 if (destroy_get_params) {
1556 ast_variables_destroy(get_params);
1557 }
1558
1559 ast_debug(2, "URI: %s DONE\n", uri);
1560
1561 return res;
1562}
static struct ast_websocket_server * ast_ws_server
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition: http.c:664
int ast_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.
#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:1262
const char * method
Definition: res_pjsip.c:1279
Definition of a URI handler.
Definition: http.h:102
void * data
Definition: http.h:116

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

983{
984 struct instance_proxy *proxy = weakproxy;
985 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
986 ao2_unlink(instances, weakproxy);
987}
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition: astobj2.h:1578
char connection_id[0]
The name of the module owning this sorcery instance.

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

Referenced by websocket_new().

◆ load_module()

static int load_module ( void  )
static

Function called when our module is loaded.

Definition at line 1596 of file chan_websocket.c.

1597{
1598 int res = 0;
1599 struct ast_websocket_protocol *protocol;
1600
1603 }
1604
1607 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
1608 unload_module();
1610 }
1611
1613 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
1614 instance_proxy_sort_fn, instance_proxy_cmp_fn);
1615 if (!instances) {
1617 "Failed to allocate the chan_websocket instance registry\n");
1618 unload_module();
1620 }
1621
1623 if (!ast_ws_server) {
1624 unload_module();
1626 }
1627
1628 protocol = ast_websocket_sub_protocol_alloc("media");
1629 if (!protocol) {
1630 unload_module();
1632 }
1635
1637
1639}
@ AO2_ALLOC_OPT_LOCK_RWLOCK
Definition: astobj2.h:365
#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
static struct ast_channel_tech websocket_tech
static struct ast_http_uri http_uri
static int unload_module(void)
Function called when our module is unloaded.
static void incoming_ws_established_cb(struct ast_websocket *ast_ws_session, struct ast_variable *get_params, struct ast_variable *upgrade_headers)
int ast_channel_register(const struct ast_channel_tech *tech)
Register a channel technology (a new channel driver) Called by a channel module to register the kind ...
Definition: channel.c:539
@ AST_MEDIA_TYPE_UNKNOWN
Definition: codec.h:31
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
@ AST_FORMAT_CAP_FLAG_DEFAULT
Definition: format_cap.h:38
#define ast_format_cap_alloc(flags)
Allocate a new ast_format_cap structure.
Definition: format_cap.h:49
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:689
struct ast_websocket_protocol * ast_websocket_sub_protocol_alloc(const char *name)
Allocate a websocket sub-protocol instance.
int ast_websocket_server_add_protocol2(struct ast_websocket_server *server, struct ast_websocket_protocol *protocol)
Add a sub-protocol handler to the given server.
struct ast_websocket_server * ast_websocket_server_create(void)
Creates a ast_websocket_server.
#define LOG_ERROR
@ 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
struct ast_format_cap * capabilities
Definition: channel.h:652
A websocket protocol implementation.
ast_websocket_callback session_established
Callback called when a new session is established. Mandatory.

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

588{
589 char *next_frame_ptr = NULL;
590 size_t bytes_read = 0;
591 int res = 0;
592 size_t bytes_left = 0;
593
594 {
595 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
597 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
598 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
599 ast_channel_name(instance->channel));
600 return 0;
601 }
602 }
603
604 next_frame_ptr = payload;
605 instance->bytes_read += payload_len;
606
607 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
608 /*
609 * We have leftover data from a previous websocket message.
610 * Try to make a complete frame by appending data from
611 * the current message to the leftover data.
612 */
613 char *append_ptr = instance->leftover_data + instance->leftover_len;
614 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
615 /*
616 * It's possible that even the current message doesn't have enough
617 * data to make a complete frame.
618 */
619 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
620
621 /*
622 * Append whatever we can to the end of the leftover data
623 * even if it's not enough to make a complete frame.
624 */
625 memcpy(append_ptr, payload, bytes_avail_to_copy);
626
627 /*
628 * If leftover data is still short, just return and wait for the
629 * next websocket message.
630 */
631 if (bytes_avail_to_copy < bytes_needed_for_frame) {
632 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
633 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
634 instance->leftover_len += bytes_avail_to_copy;
635 return 0;
636 }
637
638 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
639 if (res < 0) {
640 return -1;
641 }
642
643 /*
644 * We stole data from the current payload so decrement payload_len
645 * and set the next frame pointer after the data in payload
646 * we just copied.
647 */
648 payload_len -= bytes_avail_to_copy;
649 next_frame_ptr = payload + bytes_avail_to_copy;
650
651 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
652 ast_channel_name(instance->channel),
653 instance->frame_queue_length,
654 (int)instance->bytes_read,
655 (int)(payload_len + bytes_avail_to_copy),
656 (int)instance->leftover_len,
657 payload,
658 next_frame_ptr,
659 (int)(next_frame_ptr - payload),
660 (int)payload_len,
661 (int)bytes_avail_to_copy
662 );
663
664
665 instance->leftover_len = 0;
666 }
667
668 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
669 instance->leftover_len = 0;
670 }
671
672 bytes_left = payload_len;
673 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
674 res = queue_frame_from_buffer(instance, next_frame_ptr,
675 instance->optimal_frame_size);
676 if (res < 0) {
677 break;
678 }
679 bytes_read += instance->optimal_frame_size;
680 next_frame_ptr += instance->optimal_frame_size;
681 bytes_left -= instance->optimal_frame_size;
682 }
683
684 if (instance->bulk_media_in_progress && bytes_left > 0) {
685 /*
686 * We have a partial frame. Save the leftover data.
687 */
688 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
689 ast_channel_name(instance->channel),
690 (int)instance->bytes_read,
691 instance->frame_queue_length,
692 (int)payload_len,
693 (int)instance->leftover_len,
694 payload,
695 next_frame_ptr,
696 (int)(next_frame_ptr - payload),
697 (int)bytes_left
698 );
699 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
700 instance->leftover_len = bytes_left;
701 }
702
703 return 0;
704}
static int queue_frame_from_buffer(struct websocket_pvt *instance, char *buffer, size_t len)
#define QUEUE_LENGTH_MAX
char * leftover_data
int bulk_media_in_progress
#define MIN(a, b)
Definition: utils.h:249

References ast_channel_name(), ast_debug, AST_LIST_LOCK, AST_LIST_UNLOCK, websocket_pvt::bulk_media_in_progress, websocket_pvt::bytes_read, websocket_pvt::channel, websocket_pvt::frame_queue, websocket_pvt::frame_queue_length, websocket_pvt::leftover_data, websocket_pvt::leftover_len, MIN, NULL, websocket_pvt::optimal_frame_size, queue_frame_from_buffer(), QUEUE_LENGTH_MAX, 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 467 of file chan_websocket.c.

469{
470 int res = 0;
471 char *command;
472
473 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
474 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
475 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
476 return 0;
477 }
478
479 /*
480 * Unfortunately, payload is not NULL terminated even when it's
481 * a TEXT frame so we need to allocate a new buffer, copy
482 * the data into it, and NULL terminate it.
483 */
484 command = ast_alloca(payload_len + 1);
485 memcpy(command, payload, payload_len); /* Safe */
486 command[payload_len] = '\0';
487 command = ast_strip(command);
488
489 ast_debug(4, "%s: WebSocket %s command received\n",
490 ast_channel_name(instance->channel), command);
491
492 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
494
495 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
497
498 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
499 AST_LIST_LOCK(&instance->frame_queue);
500 instance->bulk_media_in_progress = 1;
501 AST_LIST_UNLOCK(&instance->frame_queue);
502
503 } else if (ast_begins_with(command, STOP_MEDIA_BUFFERING)) {
504 char *id;
505 char *option;
506 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
508
509 id = ast_strip(command + strlen(STOP_MEDIA_BUFFERING));
510
511 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
513 (int)instance->leftover_len);
514
515 instance->bulk_media_in_progress = 0;
516 if (instance->leftover_len > 0) {
517 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
518 if (res != 0) {
519 return res;
520 }
521 }
522 instance->leftover_len = 0;
523 res = ast_asprintf(&option, "%s%s%s", MEDIA_BUFFERING_COMPLETED,
524 S_COR(!ast_strlen_zero(id), " ", ""), S_OR(id, ""));
525 if (res <= 0 || !option) {
526 return res;
527 }
528 res = queue_option_frame(instance, option);
529 ast_free(option);
530
531 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
532 struct ast_frame *frame = NULL;
533 AST_LIST_LOCK(&instance->frame_queue);
534 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
535 ast_frfree(frame);
536 }
537 instance->frame_queue_length = 0;
538 instance->bulk_media_in_progress = 0;
539 instance->leftover_len = 0;
540 AST_LIST_UNLOCK(&instance->frame_queue);
541
542 } else if (ast_strings_equal(payload, REPORT_QUEUE_DRAINED)) {
543 AST_LIST_LOCK(&instance->frame_queue);
544 instance->report_queue_drained = 1;
545 AST_LIST_UNLOCK(&instance->frame_queue);
546
547 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
548 char *status = NULL;
549
550 res = ast_asprintf(&status, "%s queue_length:%d xon_level:%d xoff_level:%d queue_full:%s bulk_media:%s media_paused:%s",
554 S_COR(instance->queue_full, "true", "false"),
555 S_COR(instance->bulk_media_in_progress, "true", "false"),
556 S_COR(instance->queue_paused, "true", "false")
557 );
558 if (res <= 0 || !status) {
560 res = -1;
561 } else {
562 ast_debug(4, "%s: WebSocket status: %s\n",
563 ast_channel_name(instance->channel), status);
566 }
567
568 } else if (ast_strings_equal(payload, PAUSE_MEDIA)) {
569 AST_LIST_LOCK(&instance->frame_queue);
570 instance->queue_paused = 1;
571 AST_LIST_UNLOCK(&instance->frame_queue);
572
573 } else if (ast_strings_equal(payload, CONTINUE_MEDIA)) {
574 AST_LIST_LOCK(&instance->frame_queue);
575 instance->queue_paused = 0;
576 AST_LIST_UNLOCK(&instance->frame_queue);
577
578 } else {
579 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
580 ast_channel_name(instance->channel), command);
581 }
582
583 return res;
584}
jack_status_t status
Definition: app_jack.c:149
enum queue_result id
Definition: app_queue.c:1764
#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_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define FLUSH_MEDIA
#define MEDIA_BUFFERING_COMPLETED
#define ANSWER_CHANNEL
#define HANGUP_CHANNEL
#define DRIVER_STATUS
#define GET_DRIVER_STATUS
#define START_MEDIA_BUFFERING
#define QUEUE_LENGTH_XOFF_LEVEL
#define PAUSE_MEDIA
#define REPORT_QUEUE_DRAINED
#define CONTINUE_MEDIA
#define STOP_MEDIA_BUFFERING
#define MAX_TEXT_MESSAGE_LEN
static int queue_option_frame(struct websocket_pvt *instance, char *buffer)
#define ast_frfree(fr)
@ AST_CONTROL_ANSWER
int ast_strings_equal(const char *str1, const char *str2)
Compare strings for equality checking for NULL.
Definition: strings.c:238
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition: strings.h:80
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition: strings.h:87
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
static int force_inline attribute_pure ast_begins_with(const char *str, const char *prefix)
Checks whether a string begins with another.
Definition: strings.h:97
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223

References ANSWER_CHANNEL, ast_alloca, ast_asprintf, ast_begins_with(), ast_channel_name(), AST_CONTROL_ANSWER, AST_CONTROL_HANGUP, ast_debug, ast_free, ast_frfree, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_log, ast_queue_control(), ast_strings_equal(), ast_strip(), ast_strlen_zero(), ast_websocket_write_string(), websocket_pvt::bulk_media_in_progress, websocket_pvt::channel, CONTINUE_MEDIA, DRIVER_STATUS, 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, MAX_TEXT_MESSAGE_LEN, MEDIA_BUFFERING_COMPLETED, NULL, PAUSE_MEDIA, queue_frame_from_buffer(), websocket_pvt::queue_full, QUEUE_LENGTH_XOFF_LEVEL, QUEUE_LENGTH_XON_LEVEL, queue_option_frame(), websocket_pvt::queue_paused, websocket_pvt::report_queue_drained, REPORT_QUEUE_DRAINED, S_COR, S_OR, SCOPED_LOCK, START_MEDIA_BUFFERING, status, STOP_MEDIA_BUFFERING, and websocket_pvt::websocket.

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

405{
406 struct ast_frame fr = { 0, };
407 struct ast_frame *duped_frame = NULL;
408
409 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
411 fr.subclass.format = instance->native_format;
412 fr.samples = instance->native_codec->samples_count(&fr);
413
414 duped_frame = ast_frisolate(&fr);
415 if (!duped_frame) {
416 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
417 ast_channel_name(instance->channel));
418 return -1;
419 }
420
421 {
422 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
424 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
425 instance->frame_queue_length++;
426 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
427 instance->queue_full = 1;
428 ast_debug(4, "%s: WebSocket sending %s\n",
431 }
432 }
433
434 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
435 duped_frame->datalen);
436
437 return 0;
438}
#define MEDIA_XOFF
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
#define ast_frisolate(fr)
Makes a frame independent of any static storage.
#define AST_FRAME_SET_BUFFER(fr, _base, _ofs, _datalen)
@ AST_FRAME_VOICE
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
int(* samples_count)(struct ast_frame *frame)
Retrieve the number of samples in a frame.
Definition: codec.h:68
struct ast_format * format
struct ast_format * native_format
struct ast_codec * native_codec

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, ast_websocket_write_string(), 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, MEDIA_XOFF, 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, ast_frame::subclass, and websocket_pvt::websocket.

Referenced by process_binary_message(), and process_text_message().

◆ queue_option_frame()

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

Definition at line 440 of file chan_websocket.c.

442{
443 struct ast_frame fr = { 0, };
444 struct ast_frame *duped_frame = NULL;
445
446 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
449
450 duped_frame = ast_frisolate(&fr);
451 if (!duped_frame) {
452 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
453 ast_channel_name(instance->channel));
454 return -1;
455 }
456
457 AST_LIST_LOCK(&instance->frame_queue);
458 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
459 AST_LIST_UNLOCK(&instance->frame_queue);
460
461 ast_debug(4, "%s: Queued '%s' option frame\n",
462 ast_channel_name(instance->channel), buffer);
463
464 return 0;
465}

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 process_text_message().

◆ read_from_ws_and_queue()

static int read_from_ws_and_queue ( struct websocket_pvt instance)
static

Definition at line 706 of file chan_websocket.c.

707{
708 uint64_t payload_len = 0;
709 char *payload = NULL;
710 enum ast_websocket_opcode opcode;
711 int fragmented = 0;
712 int res = 0;
713
714 if (!instance || !instance->websocket) {
715 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
716 ast_channel_name(instance->channel));
717 return -1;
718 }
719
720 ast_debug(9, "%s: Waiting for websocket to have data\n", ast_channel_name(instance->channel));
721 res = ast_wait_for_input(
722 ast_websocket_fd(instance->websocket), -1);
723 if (res <= 0) {
724 ast_log(LOG_WARNING, "%s: WebSocket read failed: %s\n",
725 ast_channel_name(instance->channel), strerror(errno));
726 return -1;
727 }
728
729 /*
730 * We need to lock here to prevent the websocket handle from
731 * being pulled out from under us if the core sends us a
732 * hangup request.
733 */
734 ao2_lock(instance);
735 if (!instance->websocket) {
736 ao2_unlock(instance);
737 return -1;
738 }
739
740 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
741 &opcode, &fragmented);
742 ao2_unlock(instance);
743 if (res) {
744 return -1;
745 }
746 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
747 (int)payload_len);
748
749 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
750 return process_text_message(instance, payload, payload_len);
751 }
752
753 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
754 ast_debug(5, "%s: WebSocket closed by remote\n",
755 ast_channel_name(instance->channel));
756 return -1;
757 }
758
759 if (opcode != AST_WEBSOCKET_OPCODE_BINARY) {
760 ast_debug(5, "%s: WebSocket frame type %d not supported. Ignoring.\n",
761 ast_channel_name(instance->channel), (int)opcode);
762 return 0;
763 }
764
765 return process_binary_message(instance, payload, payload_len);
766}
#define ao2_unlock(a)
Definition: astobj2.h:729
#define ao2_lock(a)
Definition: astobj2.h:717
static int process_text_message(struct websocket_pvt *instance, char *payload, uint64_t payload_len)
static int process_binary_message(struct websocket_pvt *instance, char *payload, uint64_t payload_len)
int 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.
ast_websocket_opcode
WebSocket operation codes.
@ AST_WEBSOCKET_OPCODE_BINARY
@ AST_WEBSOCKET_OPCODE_CLOSE
@ AST_WEBSOCKET_OPCODE_TEXT
int ast_wait_for_input(int fd, int ms)
Definition: utils.c:1734

References ao2_lock, ao2_unlock, ast_channel_name(), ast_debug, ast_log, ast_wait_for_input(), ast_websocket_fd(), AST_WEBSOCKET_OPCODE_BINARY, AST_WEBSOCKET_OPCODE_CLOSE, AST_WEBSOCKET_OPCODE_TEXT, ast_websocket_read(), websocket_pvt::channel, errno, LOG_WARNING, NULL, process_binary_message(), process_text_message(), and websocket_pvt::websocket.

Referenced by read_thread_handler().

◆ read_thread_handler()

static void * read_thread_handler ( void *  obj)
static

Definition at line 778 of file chan_websocket.c.

779{
780 RAII_VAR(struct websocket_pvt *, instance, obj, ao2_cleanup);
781 RAII_VAR(char *, command, NULL, ast_free);
782 int res = 0;
783
784 ast_debug(3, "%s: Read thread started\n", ast_channel_name(instance->channel));
785
786 /*
787 * We need to tell the remote app what channel this media is for.
788 * This is especially important for outbound connections otherwise
789 * the app won't know who the media is for.
790 */
791 res = ast_asprintf(&command, "%s connection_id:%s channel:%s format:%s optimal_frame_size:%d", MEDIA_START,
792 instance->connection_id, ast_channel_name(instance->channel),
793 ast_format_get_name(instance->native_format),
794 instance->optimal_frame_size);
795 if (res <= 0 || !command) {
796 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
797 ast_log(LOG_ERROR, "%s: Failed to create MEDIA_START\n", ast_channel_name(instance->channel));
798 return NULL;
799 }
800 res = ast_websocket_write_string(instance->websocket, command);
801 if (res != 0) {
802 ast_log(LOG_ERROR, "%s: Failed to send MEDIA_START\n", ast_channel_name(instance->channel));
803 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
804 return NULL;
805 }
806 ast_debug(3, "%s: Sent %s\n", ast_channel_name(instance->channel),
807 command);
808
809 if (!instance->no_auto_answer) {
810 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
811 ast_queue_control(instance->channel, AST_CONTROL_ANSWER);
812 }
813
814 while (read_from_ws_and_queue(instance) == 0)
815 {
816 }
817
818 /*
819 * websocket_hangup will take care of closing the websocket if needed.
820 */
821 ast_debug(3, "%s: HANGUP by websocket close/error\n", ast_channel_name(instance->channel));
822 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
823
824 return NULL;
825}
#define MEDIA_START
static int read_from_ws_and_queue(struct websocket_pvt *instance)
const char * ast_format_get_name(const struct ast_format *format)
Get the name associated with a format.
Definition: format.c:334

References ao2_cleanup, ast_asprintf, ast_channel_name(), AST_CONTROL_ANSWER, AST_CONTROL_HANGUP, ast_debug, ast_format_get_name(), ast_free, ast_log, ast_queue_control(), ast_websocket_write_string(), LOG_ERROR, MEDIA_START, NULL, RAII_VAR, and read_from_ws_and_queue().

Referenced by incoming_ws_established_cb(), and webchan_call().

◆ set_channel_format()

static void set_channel_format ( struct websocket_pvt instance,
struct ast_format fmt 
)
static

Definition at line 182 of file chan_websocket.c.

184{
188 ast_debug(4, "Switching readformat to %s\n", ast_format_get_name(fmt));
189 }
190}
struct ast_format * ast_channel_rawreadformat(struct ast_channel *chan)
void ast_channel_set_rawreadformat(struct ast_channel *chan, struct ast_format *format)
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

References ast_channel_rawreadformat(), ast_channel_set_rawreadformat(), ast_debug, ast_format_cmp(), AST_FORMAT_CMP_NOT_EQUAL, ast_format_get_name(), and websocket_pvt::channel.

Referenced by webchan_read().

◆ set_channel_timer()

static int set_channel_timer ( struct websocket_pvt instance)
static

Definition at line 1126 of file chan_websocket.c.

1127{
1128 int rate = 0;
1129 instance->timer = ast_timer_open();
1130 if (!instance->timer) {
1131 return -1;
1132 }
1133 /* Rate is the number of ticks per second, not the interval. */
1134 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1135 ast_debug(3, "%s: WebSocket timer rate %d\n",
1136 ast_channel_name(instance->channel), rate);
1137 ast_timer_set_rate(instance->timer, rate);
1138 /*
1139 * Calling ast_channel_set_fd will cause the channel thread to call
1140 * webchan_read at 'rate' times per second.
1141 */
1142 ast_channel_set_fd(instance->channel, 0, ast_timer_fd(instance->timer));
1143
1144 return 0;
1145}
void ast_channel_set_fd(struct ast_channel *chan, int which, int fd)
Definition: channel.c:2396
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
struct ast_timer * timer
int ast_timer_set_rate(const struct ast_timer *handle, unsigned int rate)
Set the timing tick rate.
Definition: timing.c:166
struct ast_timer * ast_timer_open(void)
Open a timer.
Definition: timing.c:122
int ast_timer_fd(const struct ast_timer *handle)
Get a poll()-able file descriptor for a timer.
Definition: timing.c:161

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, and websocket_pvt::timer.

Referenced by webchan_request().

◆ set_channel_variables()

static int set_channel_variables ( struct websocket_pvt instance)
static

Definition at line 1147 of file chan_websocket.c.

1148{
1149 char *pkt_size = NULL;
1150 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1151 if (res <= 0) {
1152 return -1;
1153 }
1154
1156 pkt_size);
1157 ast_free(pkt_size);
1159 instance->connection_id);
1160
1161 return 0;
1162}
#define MEDIA_WEBSOCKET_CONNECTION_ID
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE
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.

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().

◆ set_instance_silence_frame()

static int set_instance_silence_frame ( struct websocket_pvt instance)
static

Definition at line 1103 of file chan_websocket.c.

1104{
1105 instance->silence.frametype = AST_FRAME_VOICE;
1106 instance->silence.datalen =
1107 (instance->slin_codec->default_ms * instance->slin_codec->minimum_bytes) / instance->slin_codec->minimum_ms;
1108 instance->silence.samples = instance->silence.datalen / sizeof(uint16_t);
1109 /*
1110 * Even though we'll calloc the data pointer, we don't mark it as
1111 * mallocd because this frame will be around for a while and we don't
1112 * want it accidentally freed before we're done with it.
1113 */
1114 instance->silence.mallocd = 0;
1115 instance->silence.offset = 0;
1116 instance->silence.src = __PRETTY_FUNCTION__;
1117 instance->silence.subclass.format = instance->slin_format;
1118 instance->silence.data.ptr = ast_calloc(1, instance->silence.datalen);
1119 if (!instance->silence.data.ptr) {
1120 return -1;
1121 }
1122
1123 return 0;
1124}
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
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
unsigned int minimum_ms
Minimum length of media that can be carried (in milliseconds) in a frame.
Definition: codec.h:54
const char * src
struct ast_codec * slin_codec
struct ast_format * slin_format
struct ast_frame silence

References ast_calloc, AST_FRAME_VOICE, ast_frame::data, ast_frame::datalen, ast_codec::default_ms, ast_frame_subclass::format, ast_frame::frametype, ast_frame::mallocd, ast_codec::minimum_bytes, ast_codec::minimum_ms, ast_frame::offset, ast_frame::ptr, ast_frame::samples, websocket_pvt::silence, websocket_pvt::slin_codec, websocket_pvt::slin_format, ast_frame::src, and ast_frame::subclass.

Referenced by webchan_request().

◆ set_instance_translator()

static int set_instance_translator ( struct websocket_pvt instance)
static

Definition at line 1072 of file chan_websocket.c.

1073{
1075 instance->slin_format = ao2_bump(instance->native_format);
1076 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1077 return 0;
1078 }
1079
1081 if (!instance->slin_format) {
1082 ast_log(LOG_ERROR, "%s: Unable to get slin format for rate %d\n",
1083 ast_channel_name(instance->channel), instance->native_codec->sample_rate);
1084 return -1;
1085 }
1086 ast_debug(3, "%s: WebSocket channel slin format '%s' Sample rate: %d ptime: %dms\n",
1090
1091 instance->translator = ast_translator_build_path(instance->slin_format, instance->native_format);
1092 if (!instance->translator) {
1093 ast_log(LOG_ERROR, "%s: Unable to build translator path from '%s' to '%s'\n",
1095 ast_format_get_name(instance->slin_format));
1096 return -1;
1097 }
1098
1099 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1100 return 0;
1101}
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_sample_rate(const struct ast_format *format)
Get the sample rate of a media format.
Definition: format.c:379
int ast_format_cache_is_slinear(struct ast_format *format)
Determines if a format is one of the cached slin formats.
Definition: format_cache.c:534
struct ast_format * ast_format_cache_get_slin_by_rate(unsigned int rate)
Retrieve the best signed linear format given a sample rate.
Definition: format_cache.c:512
unsigned int sample_rate
Sample rate (number of samples carried in a second)
Definition: codec.h:52
struct ast_trans_pvt * translator
struct ast_trans_pvt * ast_translator_build_path(struct ast_format *dest, struct ast_format *source)
Builds a translator path Build a path (possibly NULL) from source to dest.
Definition: translate.c:486

References ao2_bump, ast_channel_name(), ast_debug, ast_format_cache_get_slin_by_rate(), ast_format_cache_is_slinear(), ast_format_get_codec(), ast_format_get_default_ms(), ast_format_get_name(), ast_format_get_sample_rate(), ast_log, ast_translator_build_path(), websocket_pvt::channel, LOG_ERROR, websocket_pvt::native_codec, websocket_pvt::native_format, ast_codec::sample_rate, websocket_pvt::slin_codec, websocket_pvt::slin_format, and websocket_pvt::translator.

Referenced by webchan_request().

◆ unload_module()

static int unload_module ( void  )
static

Function called when our module is unloaded.

Definition at line 1575 of file chan_websocket.c.

1576{
1580
1584
1586 instances = NULL;
1587
1588 return 0;
1589}
void ast_channel_unregister(const struct ast_channel_tech *tech)
Unregister a channel technology.
Definition: channel.c:570
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition: http.c:721

References ao2_cleanup, ast_channel_unregister(), ast_http_uri_unlink(), ast_ws_server, ast_channel_tech::capabilities, http_uri, instances, NULL, and websocket_tech.

Referenced by load_module().

◆ validate_uri_parameters()

static int validate_uri_parameters ( const char *  uri_params)
static

Definition at line 1164 of file chan_websocket.c.

1165{
1166 char *params = ast_strdupa(uri_params);
1167 char *nvp = NULL;
1168 char *nv = NULL;
1169
1170 /*
1171 * uri_params should be a comma-separated list of key=value pairs.
1172 * For example:
1173 * name1=value1,name2=value2
1174 * We're verifying that each name and value either doesn't need
1175 * to be encoded or that it already is.
1176 */
1177
1178 while((nvp = ast_strsep(&params, ',', 0))) {
1179 /* nvp will be name1=value1 */
1180 while((nv = ast_strsep(&nvp, '=', 0))) {
1181 /* nv will be either name1 or value1 */
1182 if (!ast_uri_verify_encoded(nv)) {
1183 return 0;
1184 }
1185 }
1186 }
1187
1188 return 1;
1189}
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition: utils.c:1871
int ast_uri_verify_encoded(const char *string)
Verify if a string is valid as a URI component.
Definition: utils.c:781

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

860{
861 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
862 int nodelay = 1;
864
865 if (!instance) {
866 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
867 ast_channel_name(ast));
868 return -1;
869 }
870
871 if (instance->type == AST_WS_TYPE_SERVER) {
872 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
873 return 0;
874 }
875 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
876
877 if (!instance->client) {
878 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
879 ast_channel_name(ast));
880 return -1;
881 }
882
883 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
884 ast_channel_name(ast), dest, instance->connection_id);
885
886 if (!ast_strlen_zero(instance->uri_params)) {
888 }
889
890 instance->websocket = ast_websocket_client_connect(instance->client,
891 instance, ast_channel_name(ast), &result);
892 if (!instance->websocket || result != WS_OK) {
893 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
895 return -1;
896 }
897
898 if (setsockopt(ast_websocket_fd(instance->websocket),
899 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
900 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
901 }
902
903 ast_debug(3, "%s: WebSocket connection to %s established\n",
904 ast_channel_name(ast), dest);
905
906 /* read_thread_handler() will clean up the bump */
908 read_thread_handler, ao2_bump(instance))) {
909 ast_log(LOG_WARNING, "%s: Failed to create thread.\n", ast_channel_name(ast));
910 ao2_cleanup(instance);
911 return -1;
912 }
913
914 return 0;
915}
static PGresult * result
Definition: cel_pgsql.c:84
void * ast_channel_tech_pvt(const struct ast_channel *chan)
const char * ast_websocket_result_to_str(enum ast_websocket_result result)
Convert a websocket result code to a string.
ast_websocket_result
Result code for a websocket client.
@ WS_OK
@ AST_WS_TYPE_SERVER
enum ast_websocket_type type
pthread_t outbound_read_thread
struct ast_websocket_client * client
#define ast_pthread_create_detached_background(a, b, c, d)
Definition: utils.h:634
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 * 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.

References ao2_bump, ao2_cleanup, ast_channel_name(), ast_channel_tech_pvt(), ast_debug, ast_log, ast_pthread_create_detached_background, ast_strlen_zero(), ast_websocket_client_add_uri_params(), ast_websocket_client_connect(), ast_websocket_fd(), ast_websocket_result_to_str(), AST_WS_TYPE_SERVER, websocket_pvt::channel, websocket_pvt::client, websocket_pvt::connection_id, errno, LOG_WARNING, NULL, websocket_pvt::outbound_read_thread, read_thread_handler(), result, websocket_pvt::type, websocket_pvt::uri_params, websocket_pvt::websocket, and WS_OK.

◆ webchan_hangup()

static int webchan_hangup ( struct ast_channel ast)
static

Definition at line 1382 of file chan_websocket.c.

1383{
1384 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1385
1386 if (!instance) {
1387 return -1;
1388 }
1389 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1390 ast_channel_name(ast), instance->connection_id);
1391
1392 /*
1393 * We need to lock because read_from_ws_and_queue() is probably waiting
1394 * on the websocket file descriptor and will unblock and immediately try to
1395 * check the websocket and read from it. We don't want to pull the
1396 * websocket out from under it between the check and read.
1397 */
1398 ao2_lock(instance);
1399 if (instance->websocket) {
1400 ast_websocket_close(instance->websocket, 1000);
1401 ast_websocket_unref(instance->websocket);
1402 instance->websocket = NULL;
1403 }
1405 ao2_unlock(instance);
1406
1407 /* Clean up the reference from adding the instance to the channel */
1408 ao2_cleanup(instance);
1409
1410 return 0;
1411}
void ast_channel_tech_pvt_set(struct ast_channel *chan, void *value)

References ao2_cleanup, ao2_lock, ao2_unlock, ast_channel_name(), 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 303 of file chan_websocket.c.

304{
305 struct websocket_pvt *instance = NULL;
306 struct ast_frame *native_frame = NULL;
307 struct ast_frame *slin_frame = NULL;
308
309 instance = ast_channel_tech_pvt(ast);
310 if (!instance) {
311 return NULL;
312 }
313
315 ast_timer_ack(instance->timer, 1);
316 }
317
318 native_frame = dequeue_frame(instance);
319
320 /*
321 * No frame when the timer fires means we have to create and
322 * return a silence frame in its place.
323 */
324 if (!native_frame) {
325 ast_debug(5, "%s: WebSocket read timer fired with no frame available. Returning silence.\n", ast_channel_name(ast));
326 set_channel_format(instance, instance->slin_format);
327 slin_frame = ast_frdup(&instance->silence);
328 return slin_frame;
329 }
330
331 /*
332 * If the frame length is already optimal_frame_size, we can just
333 * return it.
334 */
335 if (native_frame->datalen == instance->optimal_frame_size) {
336 set_channel_format(instance, instance->native_format);
337 return native_frame;
338 }
339
340 /*
341 * If we're here, we have a short frame that we need to pad
342 * with silence.
343 */
344
345 if (instance->translator) {
346 slin_frame = ast_translate(instance->translator, native_frame, 0);
347 if (!slin_frame) {
348 ast_log(LOG_WARNING, "%s: Failed to translate %d byte frame\n",
349 ast_channel_name(ast), native_frame->datalen);
350 return NULL;
351 }
352 ast_frame_free(native_frame, 0);
353 } else {
354 /*
355 * If there was no translator then the native format
356 * was already slin.
357 */
358 slin_frame = native_frame;
359 }
360
361 set_channel_format(instance, instance->slin_format);
362
363 /*
364 * So now we have an slin frame but it's probably still short
365 * so we create a new data buffer with the correct length
366 * which is filled with zeros courtesy of ast_calloc.
367 * We then copy the short frame data into the new buffer
368 * and set the offset to AST_FRIENDLY_OFFSET so that
369 * the core can read the data without any issues.
370 * If the original frame data was mallocd, we need to free the old
371 * data buffer so we don't leak memory and we need to set
372 * mallocd to AST_MALLOCD_DATA so that the core knows
373 * it needs to free the new data buffer when it's done.
374 */
375
376 if (slin_frame->datalen != instance->silence.datalen) {
377 char *old_data = slin_frame->data.ptr;
378 int old_len = slin_frame->datalen;
379 int old_offset = slin_frame->offset;
380 ast_debug(4, "%s: WebSocket read short frame. Expected %d got %d. Filling with silence\n",
381 ast_channel_name(ast), instance->silence.datalen,
382 slin_frame->datalen);
383
384 slin_frame->data.ptr = ast_calloc(1, instance->silence.datalen + AST_FRIENDLY_OFFSET);
385 if (!slin_frame->data.ptr) {
386 ast_frame_free(slin_frame, 0);
387 return NULL;
388 }
389 slin_frame->data.ptr += AST_FRIENDLY_OFFSET;
390 slin_frame->offset = AST_FRIENDLY_OFFSET;
391 memcpy(slin_frame->data.ptr, old_data, old_len);
392 if (slin_frame->mallocd & AST_MALLOCD_DATA) {
393 ast_free(old_data - old_offset);
394 }
395 slin_frame->mallocd |= AST_MALLOCD_DATA;
396 slin_frame->datalen = instance->silence.datalen;
397 slin_frame->samples = instance->silence.samples;
398 }
399
400 return slin_frame;
401}
static struct ast_frame * dequeue_frame(struct websocket_pvt *instance)
static void set_channel_format(struct websocket_pvt *instance, struct ast_format *fmt)
#define ast_frdup(fr)
Copies a frame.
#define AST_MALLOCD_DATA
#define AST_FRIENDLY_OFFSET
Offset into a frame's data buffer.
int ast_timer_ack(const struct ast_timer *handle, unsigned int quantity)
Acknowledge a timer event.
Definition: timing.c:171
enum ast_timer_event ast_timer_get_event(const struct ast_timer *handle)
Retrieve timing event.
Definition: timing.c:186
@ AST_TIMING_EVENT_EXPIRED
Definition: timing.h:58
struct ast_frame * ast_translate(struct ast_trans_pvt *tr, struct ast_frame *f, int consume)
translates one or more frames Apply an input frame into the translator and receive zero or one output...
Definition: translate.c:566

References ast_calloc, ast_channel_name(), ast_channel_tech_pvt(), ast_debug, ast_frame_free(), ast_frdup, ast_free, AST_FRIENDLY_OFFSET, ast_log, AST_MALLOCD_DATA, ast_timer_ack(), ast_timer_get_event(), AST_TIMING_EVENT_EXPIRED, ast_translate(), ast_frame::data, ast_frame::datalen, dequeue_frame(), LOG_WARNING, ast_frame::mallocd, websocket_pvt::native_format, NULL, ast_frame::offset, websocket_pvt::optimal_frame_size, ast_frame::ptr, ast_frame::samples, set_channel_format(), websocket_pvt::silence, websocket_pvt::slin_format, websocket_pvt::timer, and websocket_pvt::translator.

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

1213{
1214 char *parse;
1215 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1216 struct ast_channel *chan = NULL;
1217 struct ast_format *fmt = NULL;
1218 struct ast_format_cap *caps = NULL;
1220 AST_APP_ARG(connection_id);
1222 );
1223 struct ast_flags opts = { 0, };
1224 char *opt_args[OPT_ARG_ARRAY_SIZE];
1225 const char *requestor_name = requestor ? ast_channel_name(requestor) : "no channel";
1226
1227 ast_debug(3, "%s: WebSocket channel requested\n",
1228 requestor_name);
1229
1230 if (ast_strlen_zero(data)) {
1231 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1232 requestor_name);
1233 goto failure;
1234 }
1235 parse = ast_strdupa(data);
1236 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1237
1238 if (ast_strlen_zero(args.connection_id)) {
1239 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1240 requestor_name);
1241 goto failure;
1242 }
1243
1244 if (!ast_strlen_zero(args.options)
1245 && ast_app_parse_options(websocket_options, &opts, opt_args,
1246 ast_strdupa(args.options))) {
1247 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1248 requestor_name, args.options);
1249 goto failure;
1250 }
1251
1252 if (ast_test_flag(&opts, OPT_WS_CODEC)
1253 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1254 ast_debug(3, "%s: Using specified format %s\n",
1255 requestor_name, opt_args[OPT_ARG_WS_CODEC]);
1256 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1257 } else {
1258 /*
1259 * If codec wasn't specified in the dial string,
1260 * use the first format in the capabilities.
1261 */
1262 ast_debug(3, "%s: Using format %s from requesting channel\n",
1263 requestor_name, opt_args[OPT_ARG_WS_CODEC]);
1264 fmt = ast_format_cap_get_format(cap, 0);
1265 }
1266
1267 if (!fmt) {
1268 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1269 requestor_name, args.connection_id);
1270 goto failure;
1271 }
1272
1273 instance = websocket_new(requestor_name, args.connection_id, fmt);
1274 if (!instance) {
1275 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1276 requestor_name);
1277 goto failure;
1278 }
1279
1280 instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
1281
1283 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1284 char *comma;
1285
1286 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1288 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1289 requestor_name);
1290 goto failure;
1291 }
1292
1293 ast_debug(3, "%s: Using URI parameters '%s'\n",
1294 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1295
1297 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1298 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1299 args.connection_id);
1300 goto failure;
1301 }
1302
1303 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1304 comma = instance->uri_params;
1305 /*
1306 * The normal separator for query string components is an
1307 * ampersand ('&') but the Dial app interprets them as additional
1308 * channels to dial in parallel so we instruct users to separate
1309 * the parameters with commas (',') instead. We now have to
1310 * convert those commas back to ampersands.
1311 */
1312 while ((comma = strchr(comma,','))) {
1313 *comma = '&';
1314 }
1315 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1316 }
1317
1318 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1319 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1320 if (!chan) {
1321 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", ast_channel_name(requestor));
1322 goto failure;
1323 }
1324
1325 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1326 ast_channel_name(chan), requestor_name,
1327 instance->connection_id);
1328
1329 instance->channel = ao2_bump(chan);
1330 ast_channel_tech_set(instance->channel, &websocket_tech);
1331
1332 if (set_instance_translator(instance) != 0) {
1333 goto failure;
1334 }
1335
1336 if (set_instance_silence_frame(instance) != 0) {
1337 goto failure;
1338 }
1339
1340 if (set_channel_timer(instance) != 0) {
1341 goto failure;
1342 }
1343
1344 if (set_channel_variables(instance) != 0) {
1345 goto failure;
1346 }
1347
1349 if (!caps) {
1350 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1351 goto failure;
1352 }
1353
1354 ast_format_cap_append(caps, instance->native_format, 0);
1355 ast_channel_nativeformats_set(instance->channel, caps);
1356 ast_channel_set_writeformat(instance->channel, instance->native_format);
1357 ast_channel_set_rawwriteformat(instance->channel, instance->native_format);
1358 ast_channel_set_readformat(instance->channel, instance->native_format);
1359 ast_channel_set_rawreadformat(instance->channel, instance->native_format);
1360 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1361 ast_channel_unlock(chan);
1362 ao2_cleanup(caps);
1363
1364 ast_debug(3, "%s: WebSocket channel created to %s\n",
1365 ast_channel_name(chan), args.connection_id);
1366
1367 return chan;
1368
1369failure:
1370 if (chan) {
1371 ast_channel_unlock(chan);
1372 }
1373 *cause = AST_CAUSE_FAILURE;
1374 return NULL;
1375}
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define AST_CAUSE_FAILURE
Definition: causes.h:150
static int set_instance_translator(struct websocket_pvt *instance)
static int validate_uri_parameters(const char *uri_params)
static int set_channel_variables(struct websocket_pvt *instance)
static int set_channel_timer(struct websocket_pvt *instance)
static const struct ast_app_option websocket_options[128]
#define INCOMING_CONNECTION_ID
static struct websocket_pvt * websocket_new(const char *chan_name, const char *connection_id, struct ast_format *fmt)
static int set_instance_silence_frame(struct websocket_pvt *instance)
#define ast_channel_alloc(needqueue, state, cid_num, cid_name, acctcode, exten, context, assignedids, requestor, amaflag,...)
Create a channel structure.
Definition: channel.h:1299
void ast_channel_nativeformats_set(struct ast_channel *chan, struct ast_format_cap *value)
void ast_channel_set_rawwriteformat(struct ast_channel *chan, struct ast_format *format)
void ast_channel_set_readformat(struct ast_channel *chan, struct ast_format *format)
void ast_channel_tech_set(struct ast_channel *chan, const struct ast_channel_tech *value)
#define ast_channel_unlock(chan)
Definition: channel.h:2973
void ast_channel_set_writeformat(struct ast_channel *chan, struct ast_format *format)
@ AST_STATE_DOWN
Definition: channelstate.h:36
#define ast_format_cache_get(name)
Retrieve a named format from the cache.
Definition: format_cache.h:278
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
#define ast_format_cap_append(cap, format, framing)
Add format capability to capabilities structure.
Definition: format_cap.h:99
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_NONSTANDARD_APP_ARGS(args, parse, sep)
Performs the 'nonstandard' argument separation process for an application.
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
Main Channel structure associated with a channel.
Structure used to handle boolean flags.
Definition: utils.h:217
Format capabilities structure, holds formats + preference order + etc.
Definition: format_cap.c:54
Definition of a media format.
Definition: format.c:43
const char * args
static struct test_options options
#define ast_test_flag(p, flag)
Definition: utils.h:63

References ao2_bump, ao2_cleanup, args, AST_APP_ARG, ast_app_parse_options(), AST_CAUSE_FAILURE, ast_channel_alloc, 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_format_cache_get, ast_format_cap_alloc, ast_format_cap_append, AST_FORMAT_CAP_FLAG_DEFAULT, ast_format_cap_get_format(), ast_log, AST_NONSTANDARD_APP_ARGS, AST_STATE_DOWN, ast_strdup, ast_strdupa, ast_strings_equal(), ast_strlen_zero(), ast_test_flag, INCOMING_CONNECTION_ID, LOG_ERROR, LOG_WARNING, NULL, OPT_ARG_ARRAY_SIZE, OPT_ARG_WS_CODEC, OPT_ARG_WS_URI_PARAM, OPT_WS_CODEC, OPT_WS_NO_AUTO_ANSWER, OPT_WS_URI_PARAM, options, RAII_VAR, set_channel_timer(), set_channel_variables(), set_instance_silence_frame(), set_instance_translator(), validate_uri_parameters(), websocket_new(), websocket_options, and websocket_tech.

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

829{
830 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
831
832 if (!instance || !instance->websocket) {
833 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
834 ast_channel_name(ast));
835 return -1;
836 }
837
838 if (f->frametype != AST_FRAME_VOICE) {
839 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
840 ast_channel_name(ast));
841 return -1;
842 }
843 if (f->subclass.format != instance->native_format) {
844 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format\n",
846 return -1;
847 }
848
850 (char *)f->data.ptr, (uint64_t)f->datalen);
851}
int ast_websocket_write(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)
Construct and transmit a WebSocket frame.

References ast_channel_name(), ast_channel_tech_pvt(), ast_format_get_name(), 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::native_format, ast_frame::ptr, ast_frame::subclass, and websocket_pvt::websocket.

◆ websocket_destructor()

static void websocket_destructor ( void *  data)
static

Definition at line 917 of file chan_websocket.c.

918{
919 struct websocket_pvt *instance = data;
920 struct ast_frame *frame = NULL;
921 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
922
923 AST_LIST_LOCK(&instance->frame_queue);
924 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
925 ast_frfree(frame);
926 }
927 AST_LIST_UNLOCK(&instance->frame_queue);
928
929 if (instance->timer) {
930 ast_timer_close(instance->timer);
931 instance->timer = NULL;
932 }
933
934 if (instance->channel) {
935 ast_channel_unref(instance->channel);
936 instance->channel = NULL;
937 }
938 if (instance->websocket) {
940 instance->websocket = NULL;
941 }
942
943 ao2_cleanup(instance->client);
944 instance->client = NULL;
945
946 ao2_cleanup(instance->native_codec);
947 instance->native_codec = NULL;
948
949 ao2_cleanup(instance->native_format);
950 instance->native_format = NULL;
951
952 ao2_cleanup(instance->slin_codec);
953 instance->slin_codec = NULL;
954
955 ao2_cleanup(instance->slin_format);
956 instance->slin_format = NULL;
957
958 if (instance->silence.data.ptr) {
959 ast_free(instance->silence.data.ptr);
960 instance->silence.data.ptr = NULL;
961 }
962
963 if (instance->translator) {
965 instance->translator = NULL;
966 }
967
968 if (instance->leftover_data) {
969 ast_free(instance->leftover_data);
970 instance->leftover_data = NULL;
971 }
972
973 ast_free(instance->uri_params);
974}
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:3008
void ast_timer_close(struct ast_timer *handle)
Close an opened timing handle.
Definition: timing.c:154
void ast_translator_free_path(struct ast_trans_pvt *tr)
Frees a translator path Frees the given translator path structure.
Definition: translate.c:476

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_translator_free_path(), ast_websocket_unref(), websocket_pvt::channel, websocket_pvt::client, websocket_pvt::connection_id, ast_frame::data, websocket_pvt::frame_queue, websocket_pvt::leftover_data, websocket_pvt::native_codec, websocket_pvt::native_format, NULL, ast_frame::ptr, websocket_pvt::silence, websocket_pvt::slin_codec, websocket_pvt::slin_format, websocket_pvt::timer, websocket_pvt::translator, websocket_pvt::uri_params, and websocket_pvt::websocket.

Referenced by websocket_new().

◆ websocket_new()

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

Definition at line 989 of file chan_websocket.c.

991{
992 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
993 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
995 enum ast_websocket_type ws_type;
996
998
999 if (ast_strings_equal(connection_id, INCOMING_CONNECTION_ID)) {
1000 connection_id = ast_uuid_generate_str(uuid, sizeof(uuid));
1001 ws_type = AST_WS_TYPE_SERVER;
1002 } else {
1003 ws_type = AST_WS_TYPE_CLIENT;
1004 }
1005
1006 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1007 if (!proxy) {
1008 return NULL;
1009 }
1010 strcpy(proxy->connection_id, connection_id); /* Safe */
1011
1012 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1014 if (!instance) {
1015 return NULL;
1016 }
1017 strcpy(instance->connection_id, connection_id); /* Safe */
1018
1019 instance->type = ws_type;
1020 if (ws_type == AST_WS_TYPE_CLIENT) {
1021 instance->client = ast_websocket_client_retrieve_by_id(instance->connection_id);
1022 if (!instance->client) {
1023 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1024 chan_name, instance->connection_id);
1025 return NULL;
1026 }
1027 }
1028
1029 AST_LIST_HEAD_INIT(&instance->frame_queue);
1030
1031 /*
1032 * We need the codec to calculate the number of samples in a frame
1033 * so we'll get it once and store it in the instance.
1034 *
1035 * References for native_format and native_codec are now held by the
1036 * instance and will be released when the instance is destroyed.
1037 */
1038 instance->native_format = fmt;
1039 instance->native_codec = ast_format_get_codec(instance->native_format);
1040 /*
1041 * References for native_format and native_codec are now held by the
1042 * instance and will be released when the instance is destroyed.
1043 */
1044 instance->optimal_frame_size =
1045 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1046 / instance->native_codec->minimum_ms;
1047
1048 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1049 if (!instance->leftover_data) {
1050 return NULL;
1051 }
1052
1053 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1054 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1055 return NULL;
1056 }
1057
1059 return NULL;
1060 }
1061
1062 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1063 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1064 proxy->connection_id);
1065 return NULL;
1066 }
1067 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1068
1069 return ao2_bump(instance);
1070}
#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
#define ao2_link_flags(container, obj, flags)
Add an object to a container.
Definition: astobj2.h:1554
#define ao2_weakproxy_alloc(data_size, destructor_fn)
Allocate an ao2_weakproxy object.
Definition: astobj2.h:550
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
static void instance_proxy_cb(void *weakproxy, void *data)
static void websocket_destructor(void *data)
static int uuid(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_uuid.c:52
ast_websocket_type
WebSocket connection/configuration types.
@ AST_WS_TYPE_CLIENT
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:626
#define SCOPED_AO2WRLOCK(varname, obj)
scoped lock specialization for ao2 write locks.
Definition: lock.h:621
#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
struct ast_websocket_client * ast_websocket_client_retrieve_by_id(const char *id)
Retrieve a websocket client object by ID.

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_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 = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_CHANNEL_DRIVER, .requires = "res_http_websocket,res_websocket_client", }
static

Definition at line 1647 of file chan_websocket.c.

◆ ast_module_info

const struct ast_module_info* ast_module_info = &__mod_info
static

Definition at line 1647 of file chan_websocket.c.

◆ ast_ws_server

struct ast_websocket_server* ast_ws_server
static

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

Referenced by load_module(), and unload_module().

◆ instances

struct ao2_container* instances = NULL
static

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

Definition at line 1208 of file chan_websocket.c.

Referenced by webchan_request().

◆ websocket_tech

struct ast_channel_tech websocket_tech
static

Definition at line 172 of file chan_websocket.c.

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