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