Asterisk - The Open Source Telephony Project GIT-master-27fb039
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/*** DOCUMENTATION
35 <info name="Dial_Resource" language="en_US" tech="WebSocket">
36 <para>WebSocket Dial Strings:</para>
37 <para><literal>Dial(WebSocket/connectionid[/websocket_options])</literal></para>
38 <para>WebSocket Parameters:</para>
39 <enumlist>
40 <enum name="connectionid">
41 <para>For outgoing WebSockets, this is the ID of the connection
42 in websocket_client.conf to use for the call. To accept incoming
43 WebSocket connections use the literal <literal>INCOMING</literal></para>
44 </enum>
45 <enum name="websocket_options">
46 <para>Options to control how the WebSocket channel behaves.</para>
47 <enumlist>
48 <enum name="c(codec) - Specify the codec to use in the channel">
49 <para></para>
50 <para> If not specified, the first codec from the caller's channel will be used.
51 </para>
52 </enum>
53 <enum name="n - Don't auto answer">
54 <para>Normally, the WebSocket channel will be answered when
55 connection is established with the remote app. If this
56 option is specified however, the channel will not be
57 answered until the <literal>ANSWER</literal> command is
58 received from the remote app or the remote app calls the
59 /channels/answer ARI endpoint.
60 </para>
61 </enum>
62 <enum name="p - Passthrough mode">
63 <para>In passthrough mode, the channel driver won't attempt
64 to re-frame or re-time media coming in over the websocket from
65 the remote app. This can be used for any codec but MUST be used
66 for codecs that use packet headers or whose data stream can't be
67 broken up on arbitrary byte boundaries. In this case, the remote
68 app is fully responsible for correctly framing and timing media
69 sent to Asterisk and the MEDIA text commands that could be sent
70 over the websocket are disabled. Currently, passthrough mode is
71 automatically set for the opus, speex and g729 codecs.
72 </para>
73 </enum>
74 <enum name="v(uri_parameters) - Add parameters to the outbound URI">
75 <para>This option allows you to add additional parameters to the
76 outbound URI. The format is:
77 <literal>v(param1=value1,param2=value2...)</literal>
78 </para>
79 <para>You must ensure that no parameter name or value contains
80 characters not valid in a URL. The easiest way to do this is to
81 use the URIENCODE() dialplan function to encode them. Be aware
82 though that each name and value must be encoded separately. You
83 can't simply encode the whole string.</para>
84 </enum>
85 </enumlist>
86 </enum>
87 </enumlist>
88 <para>Examples:
89 </para>
90 <example title="Make an outbound WebSocket connection using connection 'connection1' and the 'sln16' codec.">
91 same => n,Dial(WebSocket/connection1/c(sln16))
92 </example>
93 <example title="Make an outbound WebSocket connection using connection 'connection1' and the 'opus' codec. Passthrough mode will automatically be set.">
94 same => n,Dial(WebSocket/connection1/c(opus))
95 </example>
96 <example title="Listen for an incoming WebSocket connection and don't auto-answer it.">
97 same => n,Dial(WebSocket/INCOMING/n)
98 </example>
99 <example title="Add URI parameters.">
100 same => n,Dial(WebSocket/connection1/v(${URIENCODE(vari able)}=${URIENCODE(${CHANNEL})},variable2=$(URIENCODE(${EXTEN})}))
101 </example>
102 </info>
103***/
104#include "asterisk.h"
105
106#include "asterisk/app.h"
107#include "asterisk/causes.h"
108#include "asterisk/channel.h"
109#include "asterisk/codec.h"
112#include "asterisk/frame.h"
113#include "asterisk/lock.h"
114#include "asterisk/mod_format.h"
115#include "asterisk/module.h"
116#include "asterisk/pbx.h"
117#include "asterisk/uuid.h"
118#include "asterisk/timing.h"
119#include "asterisk/translate.h"
121
123
124static struct ao2_container *instances = NULL;
125
154
155#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE "MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE"
156#define MEDIA_WEBSOCKET_CONNECTION_ID "MEDIA_WEBSOCKET_CONNECTION_ID"
157#define INCOMING_CONNECTION_ID "INCOMING"
158
159#define ANSWER_CHANNEL "ANSWER"
160#define HANGUP_CHANNEL "HANGUP"
161#define START_MEDIA_BUFFERING "START_MEDIA_BUFFERING"
162#define STOP_MEDIA_BUFFERING "STOP_MEDIA_BUFFERING"
163#define FLUSH_MEDIA "FLUSH_MEDIA"
164#define GET_DRIVER_STATUS "GET_STATUS"
165#define REPORT_QUEUE_DRAINED "REPORT_QUEUE_DRAINED"
166#define PAUSE_MEDIA "PAUSE_MEDIA"
167#define CONTINUE_MEDIA "CONTINUE_MEDIA"
168
169#define MEDIA_START "MEDIA_START"
170#define MEDIA_XON "MEDIA_XON"
171#define MEDIA_XOFF "MEDIA_XOFF"
172#define QUEUE_DRAINED "QUEUE_DRAINED"
173#define DRIVER_STATUS "STATUS"
174#define MEDIA_BUFFERING_COMPLETED "MEDIA_BUFFERING_COMPLETED"
175#define DTMF_END "DTMF_END"
176
177#define QUEUE_LENGTH_MAX 1000
178#define QUEUE_LENGTH_XOFF_LEVEL 900
179#define QUEUE_LENGTH_XON_LEVEL 800
180#define MAX_TEXT_MESSAGE_LEN MIN(128, (AST_WEBSOCKET_MAX_RX_PAYLOAD_SIZE - 1))
181
182/* Forward declarations */
183static 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);
184static int webchan_call(struct ast_channel *ast, const char *dest, int timeout);
185static struct ast_frame *webchan_read(struct ast_channel *ast);
186static int webchan_write(struct ast_channel *ast, struct ast_frame *f);
187static int webchan_hangup(struct ast_channel *ast);
188static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration);
189
191 .type = "WebSocket",
192 .description = "Media over WebSocket Channel Driver",
193 .requester = webchan_request,
194 .call = webchan_call,
195 .read = webchan_read,
196 .write = webchan_write,
197 .hangup = webchan_hangup,
198 .send_digit_end = webchan_send_dtmf_text,
199};
200
201static void set_channel_format(struct websocket_pvt * instance,
202 struct ast_format *fmt)
203{
208 ast_debug(4, "Switching readformat to %s\n", ast_format_get_name(fmt));
209 }
210}
211
212/*
213 * Reminder... This function gets called by webchan_read which is
214 * triggered by the channel timer firing. It always gets called
215 * every 20ms (or whatever the timer is set to) even if there are
216 * no frames in the queue.
217 */
218static struct ast_frame *dequeue_frame(struct websocket_pvt *instance)
219{
220 struct ast_frame *queued_frame = NULL;
221 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
223
224 /*
225 * If the queue is paused, don't read a frame. Processing
226 * will continue down the function and a silence frame will
227 * be sent in its place.
228 */
229 if (instance->queue_paused) {
230 return NULL;
231 }
232
233 /*
234 * We need to check if we need to send an XON before anything
235 * else because there are multiple escape paths in this function
236 * and we don't want to accidentally keep the queue in a "full"
237 * state.
238 */
239 if (instance->queue_full && instance->frame_queue_length < QUEUE_LENGTH_XON_LEVEL) {
240 instance->queue_full = 0;
241 ast_debug(4, "%s: WebSocket sending MEDIA_XON\n",
242 ast_channel_name(instance->channel));
244 }
245
246 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
247
248 /*
249 * If there are no frames in the queue, we need to
250 * return NULL so we can send a silence frame. We also need
251 * to send the QUEUE_DRAINED notification if we were requested
252 * to do so.
253 */
254 if (!queued_frame) {
255 if (instance->report_queue_drained) {
256 instance->report_queue_drained = 0;
257 ast_debug(4, "%s: WebSocket sending QUEUE_DRAINED\n",
258 ast_channel_name(instance->channel));
260 }
261 return NULL;
262 }
263
264 /*
265 * The only way a control frame could be present here is as
266 * a result of us calling queue_option_frame() in response
267 * to an incoming TEXT command from the websocket.
268 * We'll be safe and make sure it's a AST_CONTROL_OPTION
269 * frame anyway.
270 *
271 * It's quite possible that there are multiple control frames
272 * in a row in the queue so we need to process consecutive ones
273 * immediately.
274 *
275 * In any case, processing a control frame MUST not use up
276 * a media timeslot so after all control frames have been
277 * processed, we need to read an audio frame and process it.
278 */
279 while (queued_frame && queued_frame->frametype == AST_FRAME_CONTROL) {
280 if (queued_frame->subclass.integer == AST_CONTROL_OPTION) {
281 /*
282 * We just need to send the data to the websocket.
283 * The data should already be NULL terminated.
284 */
286 queued_frame->data.ptr);
287 ast_debug(4, "%s: WebSocket sending %s\n",
288 ast_channel_name(instance->channel), (char *)queued_frame->data.ptr);
289 }
290 /*
291 * We do NOT send these to the core so we need to free
292 * the frame and grab the next one. If it's also a
293 * control frame, we need to process it otherwise
294 * continue down in the function.
295 */
296 ast_frame_free(queued_frame, 0);
297 queued_frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list);
298 /*
299 * Jut FYI... We didn't bump the queue length when we added the control
300 * frames so we don't need to decrement it here.
301 */
302 }
303
304 /*
305 * If, after reading all control frames, there are no frames
306 * left in the queue, we need to return NULL so we can send
307 * a silence frame.
308 */
309 if (!queued_frame) {
310 return NULL;
311 }
312
313 instance->frame_queue_length--;
314
315 return queued_frame;
316}
317/*!
318 * \internal
319 *
320 * Called by the core channel thread each time the instance timer fires.
321 *
322 */
323static struct ast_frame *webchan_read(struct ast_channel *ast)
324{
325 struct websocket_pvt *instance = NULL;
326 struct ast_frame *native_frame = NULL;
327 struct ast_frame *slin_frame = NULL;
328
329 instance = ast_channel_tech_pvt(ast);
330 if (!instance) {
331 return NULL;
332 }
333
335 ast_timer_ack(instance->timer, 1);
336 }
337
338 native_frame = dequeue_frame(instance);
339
340 /*
341 * No frame when the timer fires means we have to create and
342 * return a silence frame in its place.
343 */
344 if (!native_frame) {
345 ast_debug(5, "%s: WebSocket read timer fired with no frame available. Returning silence.\n", ast_channel_name(ast));
346 set_channel_format(instance, instance->slin_format);
347 slin_frame = ast_frdup(&instance->silence);
348 return slin_frame;
349 }
350
351 /*
352 * If we're in passthrough mode or the frame length is already optimal_frame_size,
353 * we can just return it.
354 */
355 if (instance->passthrough || native_frame->datalen == instance->optimal_frame_size) {
356 set_channel_format(instance, instance->native_format);
357 return native_frame;
358 }
359
360 /*
361 * If we're here, we have a short frame that we need to pad
362 * with silence.
363 */
364
365 if (instance->translator) {
366 slin_frame = ast_translate(instance->translator, native_frame, 0);
367 if (!slin_frame) {
368 ast_log(LOG_WARNING, "%s: Failed to translate %d byte frame\n",
369 ast_channel_name(ast), native_frame->datalen);
370 return NULL;
371 }
372 ast_frame_free(native_frame, 0);
373 } else {
374 /*
375 * If there was no translator then the native format
376 * was already slin.
377 */
378 slin_frame = native_frame;
379 }
380
381 set_channel_format(instance, instance->slin_format);
382
383 /*
384 * So now we have an slin frame but it's probably still short
385 * so we create a new data buffer with the correct length
386 * which is filled with zeros courtesy of ast_calloc.
387 * We then copy the short frame data into the new buffer
388 * and set the offset to AST_FRIENDLY_OFFSET so that
389 * the core can read the data without any issues.
390 * If the original frame data was mallocd, we need to free the old
391 * data buffer so we don't leak memory and we need to set
392 * mallocd to AST_MALLOCD_DATA so that the core knows
393 * it needs to free the new data buffer when it's done.
394 */
395
396 if (slin_frame->datalen != instance->silence.datalen) {
397 char *old_data = slin_frame->data.ptr;
398 int old_len = slin_frame->datalen;
399 int old_offset = slin_frame->offset;
400 ast_debug(4, "%s: WebSocket read short frame. Expected %d got %d. Filling with silence\n",
401 ast_channel_name(ast), instance->silence.datalen,
402 slin_frame->datalen);
403
404 slin_frame->data.ptr = ast_calloc(1, instance->silence.datalen + AST_FRIENDLY_OFFSET);
405 if (!slin_frame->data.ptr) {
406 ast_frame_free(slin_frame, 0);
407 return NULL;
408 }
409 slin_frame->data.ptr += AST_FRIENDLY_OFFSET;
410 slin_frame->offset = AST_FRIENDLY_OFFSET;
411 memcpy(slin_frame->data.ptr, old_data, old_len);
412 if (slin_frame->mallocd & AST_MALLOCD_DATA) {
413 ast_free(old_data - old_offset);
414 }
415 slin_frame->mallocd |= AST_MALLOCD_DATA;
416 slin_frame->datalen = instance->silence.datalen;
417 slin_frame->samples = instance->silence.samples;
418 }
419
420 return slin_frame;
421}
422
423static int queue_frame_from_buffer(struct websocket_pvt *instance,
424 char *buffer, size_t len)
425{
426 struct ast_frame fr = { 0, };
427 struct ast_frame *duped_frame = NULL;
428
429 AST_FRAME_SET_BUFFER(&fr, buffer, 0, len);
431 fr.subclass.format = instance->native_format;
432 fr.samples = instance->native_codec->samples_count(&fr);
433
434 duped_frame = ast_frisolate(&fr);
435 if (!duped_frame) {
436 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
437 ast_channel_name(instance->channel));
438 return -1;
439 }
440
441 {
442 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
444 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
445 instance->frame_queue_length++;
446 if (!instance->queue_full && instance->frame_queue_length >= QUEUE_LENGTH_XOFF_LEVEL) {
447 instance->queue_full = 1;
448 ast_debug(4, "%s: WebSocket sending %s\n",
451 }
452 }
453
454 ast_debug(5, "%s: Queued %d byte frame\n", ast_channel_name(instance->channel),
455 duped_frame->datalen);
456
457 return 0;
458}
459
460static int queue_option_frame(struct websocket_pvt *instance,
461 char *buffer)
462{
463 struct ast_frame fr = { 0, };
464 struct ast_frame *duped_frame = NULL;
465
466 AST_FRAME_SET_BUFFER(&fr, buffer, 0, strlen(buffer) + 1);
469
470 duped_frame = ast_frisolate(&fr);
471 if (!duped_frame) {
472 ast_log(LOG_WARNING, "%s: Failed to isolate frame\n",
473 ast_channel_name(instance->channel));
474 return -1;
475 }
476
477 AST_LIST_LOCK(&instance->frame_queue);
478 AST_LIST_INSERT_TAIL(&instance->frame_queue, duped_frame, frame_list);
479 AST_LIST_UNLOCK(&instance->frame_queue);
480
481 ast_debug(4, "%s: Queued '%s' option frame\n",
482 ast_channel_name(instance->channel), buffer);
483
484 return 0;
485}
486
487static int process_text_message(struct websocket_pvt *instance,
488 char *payload, uint64_t payload_len)
489{
490 int res = 0;
491 char *command;
492
493 if (payload_len > MAX_TEXT_MESSAGE_LEN) {
494 ast_log(LOG_WARNING, "%s: WebSocket TEXT message of length %d exceeds maximum length of %d\n",
495 ast_channel_name(instance->channel), (int)payload_len, MAX_TEXT_MESSAGE_LEN);
496 return 0;
497 }
498
499 /*
500 * Unfortunately, payload is not NULL terminated even when it's
501 * a TEXT frame so we need to allocate a new buffer, copy
502 * the data into it, and NULL terminate it.
503 */
504 command = ast_alloca(payload_len + 1);
505 memcpy(command, payload, payload_len); /* Safe */
506 command[payload_len] = '\0';
507 command = ast_strip(command);
508
509 ast_debug(4, "%s: WebSocket %s command received\n",
510 ast_channel_name(instance->channel), command);
511
512 if (ast_strings_equal(command, ANSWER_CHANNEL)) {
514
515 } else if (ast_strings_equal(command, HANGUP_CHANNEL)) {
517
518 } else if (ast_strings_equal(command, START_MEDIA_BUFFERING)) {
519 if (instance->passthrough) {
520 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
521 ast_channel_name(instance->channel), command);
522 return 0;
523 }
524 AST_LIST_LOCK(&instance->frame_queue);
525 instance->bulk_media_in_progress = 1;
526 AST_LIST_UNLOCK(&instance->frame_queue);
527
528 } else if (ast_begins_with(command, STOP_MEDIA_BUFFERING)) {
529 char *id;
530 char *option;
531 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
533
534 id = ast_strip(command + strlen(STOP_MEDIA_BUFFERING));
535
536 if (instance->passthrough) {
537 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
538 ast_channel_name(instance->channel), command);
539 return 0;
540 }
541
542 ast_debug(4, "%s: WebSocket %s '%s' with %d bytes in leftover_data.\n",
544 (int)instance->leftover_len);
545
546 instance->bulk_media_in_progress = 0;
547 if (instance->leftover_len > 0) {
548 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->leftover_len);
549 if (res != 0) {
550 return res;
551 }
552 }
553 instance->leftover_len = 0;
554 res = ast_asprintf(&option, "%s%s%s", MEDIA_BUFFERING_COMPLETED,
555 S_COR(!ast_strlen_zero(id), " ", ""), S_OR(id, ""));
556 if (res <= 0 || !option) {
557 return res;
558 }
559 res = queue_option_frame(instance, option);
560 ast_free(option);
561
562 } else if (ast_strings_equal(command, FLUSH_MEDIA)) {
563 struct ast_frame *frame = NULL;
564
565 if (instance->passthrough) {
566 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
567 ast_channel_name(instance->channel), command);
568 return 0;
569 }
570
571 AST_LIST_LOCK(&instance->frame_queue);
572 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
573 ast_frfree(frame);
574 }
575 instance->frame_queue_length = 0;
576 instance->bulk_media_in_progress = 0;
577 instance->leftover_len = 0;
578 AST_LIST_UNLOCK(&instance->frame_queue);
579
580 } else if (ast_strings_equal(command, REPORT_QUEUE_DRAINED)) {
581 if (instance->passthrough) {
582 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
583 ast_channel_name(instance->channel), command);
584 return 0;
585 }
586
587 AST_LIST_LOCK(&instance->frame_queue);
588 instance->report_queue_drained = 1;
589 AST_LIST_UNLOCK(&instance->frame_queue);
590
591 } else if (ast_strings_equal(command, GET_DRIVER_STATUS)) {
592 char *status = NULL;
593
594 res = ast_asprintf(&status, "%s channel_id:%s queue_length:%d xon_level:%d xoff_level:%d queue_full:%s bulk_media:%s media_paused:%s",
596 ast_channel_uniqueid(instance->channel),
599 S_COR(instance->queue_full, "true", "false"),
600 S_COR(instance->bulk_media_in_progress, "true", "false"),
601 S_COR(instance->queue_paused, "true", "false")
602 );
603 if (res <= 0 || !status) {
605 res = -1;
606 } else {
607 ast_debug(4, "%s: WebSocket status: %s\n",
608 ast_channel_name(instance->channel), status);
611 }
612
613 } else if (ast_strings_equal(command, PAUSE_MEDIA)) {
614 if (instance->passthrough) {
615 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
616 ast_channel_name(instance->channel), command);
617 return 0;
618 }
619 AST_LIST_LOCK(&instance->frame_queue);
620 instance->queue_paused = 1;
621 AST_LIST_UNLOCK(&instance->frame_queue);
622
623 } else if (ast_strings_equal(command, CONTINUE_MEDIA)) {
624 if (instance->passthrough) {
625 ast_debug(4, "%s: WebSocket in passthrough mode. Ignoring %s command.\n",
626 ast_channel_name(instance->channel), command);
627 return 0;
628 }
629 AST_LIST_LOCK(&instance->frame_queue);
630 instance->queue_paused = 0;
631 AST_LIST_UNLOCK(&instance->frame_queue);
632
633 } else {
634 ast_log(LOG_WARNING, "%s: WebSocket %s command unknown\n",
635 ast_channel_name(instance->channel), command);
636 }
637
638 return res;
639}
640
641static int process_binary_message(struct websocket_pvt *instance,
642 char *payload, uint64_t payload_len)
643{
644 char *next_frame_ptr = NULL;
645 size_t bytes_read = 0;
646 int res = 0;
647 size_t bytes_left = 0;
648
649 {
650 SCOPED_LOCK(frame_queue_lock, &instance->frame_queue, AST_LIST_LOCK,
652 if (instance->frame_queue_length >= QUEUE_LENGTH_MAX) {
653 ast_debug(4, "%s: WebSocket queue is full. Ignoring incoming binary message.\n",
654 ast_channel_name(instance->channel));
655 return 0;
656 }
657 }
658
659 next_frame_ptr = payload;
660 instance->bytes_read += payload_len;
661
662 if (instance->passthrough) {
663 res = queue_frame_from_buffer(instance, payload, payload_len);
664 return res;
665 }
666
667 if (instance->bulk_media_in_progress && instance->leftover_len > 0) {
668 /*
669 * We have leftover data from a previous websocket message.
670 * Try to make a complete frame by appending data from
671 * the current message to the leftover data.
672 */
673 char *append_ptr = instance->leftover_data + instance->leftover_len;
674 size_t bytes_needed_for_frame = instance->optimal_frame_size - instance->leftover_len;
675 /*
676 * It's possible that even the current message doesn't have enough
677 * data to make a complete frame.
678 */
679 size_t bytes_avail_to_copy = MIN(bytes_needed_for_frame, payload_len);
680
681 /*
682 * Append whatever we can to the end of the leftover data
683 * even if it's not enough to make a complete frame.
684 */
685 memcpy(append_ptr, payload, bytes_avail_to_copy);
686
687 /*
688 * If leftover data is still short, just return and wait for the
689 * next websocket message.
690 */
691 if (bytes_avail_to_copy < bytes_needed_for_frame) {
692 ast_debug(4, "%s: Leftover data %d bytes but only %d new bytes available of %d needed. Appending and waiting for next message.\n",
693 ast_channel_name(instance->channel), (int)instance->leftover_len, (int)bytes_avail_to_copy, (int)bytes_needed_for_frame);
694 instance->leftover_len += bytes_avail_to_copy;
695 return 0;
696 }
697
698 res = queue_frame_from_buffer(instance, instance->leftover_data, instance->optimal_frame_size);
699 if (res < 0) {
700 return -1;
701 }
702
703 /*
704 * We stole data from the current payload so decrement payload_len
705 * and set the next frame pointer after the data in payload
706 * we just copied.
707 */
708 payload_len -= bytes_avail_to_copy;
709 next_frame_ptr = payload + bytes_avail_to_copy;
710
711 ast_debug(5, "%s: --- BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d NPL: %4d BAC: %3d\n",
712 ast_channel_name(instance->channel),
713 instance->frame_queue_length,
714 (int)instance->bytes_read,
715 (int)(payload_len + bytes_avail_to_copy),
716 (int)instance->leftover_len,
717 payload,
718 next_frame_ptr,
719 (int)(next_frame_ptr - payload),
720 (int)payload_len,
721 (int)bytes_avail_to_copy
722 );
723
724
725 instance->leftover_len = 0;
726 }
727
728 if (!instance->bulk_media_in_progress && instance->leftover_len > 0) {
729 instance->leftover_len = 0;
730 }
731
732 bytes_left = payload_len;
733 while (bytes_read < payload_len && bytes_left >= instance->optimal_frame_size) {
734 res = queue_frame_from_buffer(instance, next_frame_ptr,
735 instance->optimal_frame_size);
736 if (res < 0) {
737 break;
738 }
739 bytes_read += instance->optimal_frame_size;
740 next_frame_ptr += instance->optimal_frame_size;
741 bytes_left -= instance->optimal_frame_size;
742 }
743
744 if (instance->bulk_media_in_progress && bytes_left > 0) {
745 /*
746 * We have a partial frame. Save the leftover data.
747 */
748 ast_debug(5, "%s: +++ BR: %4d FQ: %4d PL: %4d LOL: %3d P: %p NFP: %p OFF: %4d BL: %4d\n",
749 ast_channel_name(instance->channel),
750 (int)instance->bytes_read,
751 instance->frame_queue_length,
752 (int)payload_len,
753 (int)instance->leftover_len,
754 payload,
755 next_frame_ptr,
756 (int)(next_frame_ptr - payload),
757 (int)bytes_left
758 );
759 memcpy(instance->leftover_data, next_frame_ptr, bytes_left);
760 instance->leftover_len = bytes_left;
761 }
762
763 return 0;
764}
765
766static int read_from_ws_and_queue(struct websocket_pvt *instance)
767{
768 uint64_t payload_len = 0;
769 char *payload = NULL;
770 enum ast_websocket_opcode opcode;
771 int fragmented = 0;
772 int res = 0;
773
774 if (!instance || !instance->websocket) {
775 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
776 ast_channel_name(instance->channel));
777 return -1;
778 }
779
780 ast_debug(9, "%s: Waiting for websocket to have data\n", ast_channel_name(instance->channel));
781 res = ast_wait_for_input(
782 ast_websocket_fd(instance->websocket), -1);
783 if (res <= 0) {
784 ast_log(LOG_WARNING, "%s: WebSocket read failed: %s\n",
785 ast_channel_name(instance->channel), strerror(errno));
786 return -1;
787 }
788
789 /*
790 * We need to lock here to prevent the websocket handle from
791 * being pulled out from under us if the core sends us a
792 * hangup request.
793 */
794 ao2_lock(instance);
795 if (!instance->websocket) {
796 ao2_unlock(instance);
797 return -1;
798 }
799
800 res = ast_websocket_read(instance->websocket, &payload, &payload_len,
801 &opcode, &fragmented);
802 ao2_unlock(instance);
803 if (res) {
804 return -1;
805 }
806 ast_debug(5, "%s: WebSocket read %d bytes\n", ast_channel_name(instance->channel),
807 (int)payload_len);
808
809 if (opcode == AST_WEBSOCKET_OPCODE_TEXT) {
810 return process_text_message(instance, payload, payload_len);
811 }
812
813 if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
814 ast_debug(5, "%s: WebSocket closed by remote\n",
815 ast_channel_name(instance->channel));
816 return -1;
817 }
818
819 if (opcode != AST_WEBSOCKET_OPCODE_BINARY) {
820 ast_debug(5, "%s: WebSocket frame type %d not supported. Ignoring.\n",
821 ast_channel_name(instance->channel), (int)opcode);
822 return 0;
823 }
824
825 return process_binary_message(instance, payload, payload_len);
826}
827
828/*!
829 * \internal
830 *
831 * For incoming websocket connections, this function gets called by
832 * incoming_ws_established_cb() and is run in the http server thread
833 * handling the websocket connection.
834 *
835 * For outgoing websocket connections, this function gets started as
836 * a background thread by webchan_call().
837 */
838static void *read_thread_handler(void *obj)
839{
840 RAII_VAR(struct websocket_pvt *, instance, obj, ao2_cleanup);
841 RAII_VAR(char *, command, NULL, ast_free);
842 int res = 0;
843
844 ast_debug(3, "%s: Read thread started\n", ast_channel_name(instance->channel));
845
846 /*
847 * We need to tell the remote app what channel this media is for.
848 * This is especially important for outbound connections otherwise
849 * the app won't know who the media is for.
850 */
851 res = ast_asprintf(&command, "%s connection_id:%s channel:%s channel_id:%s format:%s optimal_frame_size:%d ptime:%d", MEDIA_START,
852 instance->connection_id, ast_channel_name(instance->channel),
853 ast_channel_uniqueid(instance->channel),
854 ast_format_get_name(instance->native_format),
855 instance->optimal_frame_size, instance->native_codec->default_ms);
856 if (res <= 0 || !command) {
857 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
858 ast_log(LOG_ERROR, "%s: Failed to create MEDIA_START\n", ast_channel_name(instance->channel));
859 return NULL;
860 }
861 res = ast_websocket_write_string(instance->websocket, command);
862 if (res != 0) {
863 ast_log(LOG_ERROR, "%s: Failed to send MEDIA_START\n", ast_channel_name(instance->channel));
864 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
865 return NULL;
866 }
867 ast_debug(3, "%s: Sent %s\n", ast_channel_name(instance->channel),
868 command);
869
870 if (!instance->no_auto_answer) {
871 ast_debug(3, "%s: ANSWER by auto_answer\n", ast_channel_name(instance->channel));
872 ast_queue_control(instance->channel, AST_CONTROL_ANSWER);
873 }
874
875 while (read_from_ws_and_queue(instance) == 0)
876 {
877 }
878
879 /*
880 * websocket_hangup will take care of closing the websocket if needed.
881 */
882 ast_debug(3, "%s: HANGUP by websocket close/error\n", ast_channel_name(instance->channel));
883 ast_queue_control(instance->channel, AST_CONTROL_HANGUP);
884
885 return NULL;
886}
887
888/*! \brief Function called when we should write a frame to the channel */
889static int webchan_write(struct ast_channel *ast, struct ast_frame *f)
890{
891 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
892
893 if (!instance || !instance->websocket) {
894 ast_log(LOG_WARNING, "%s: WebSocket instance or client not found\n",
895 ast_channel_name(ast));
896 return -1;
897 }
898
899 if (f->frametype != AST_FRAME_VOICE) {
900 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports AST_FRAME_VOICE frames\n",
901 ast_channel_name(ast));
902 return -1;
903 }
904
906 ast_log(LOG_WARNING, "%s: This WebSocket channel only supports the '%s' format, not '%s'\n",
909 return -1;
910 }
911
913 (char *)f->data.ptr, (uint64_t)f->datalen);
914}
915
916/*!
917 * \internal
918 *
919 * Called by the core to actually call the remote.
920 */
921static int webchan_call(struct ast_channel *ast, const char *dest,
922 int timeout)
923{
924 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
925 int nodelay = 1;
927
928 if (!instance) {
929 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n",
930 ast_channel_name(ast));
931 return -1;
932 }
933
934 if (instance->type == AST_WS_TYPE_SERVER) {
935 ast_debug(3, "%s: Websocket call incoming\n", ast_channel_name(instance->channel));
936 return 0;
937 }
938 ast_debug(3, "%s: Websocket call outgoing\n", ast_channel_name(instance->channel));
939
940 if (!instance->client) {
941 ast_log(LOG_WARNING, "%s: WebSocket client not found\n",
942 ast_channel_name(ast));
943 return -1;
944 }
945
946 ast_debug(3, "%s: WebSocket call requested to %s. cid: %s\n",
947 ast_channel_name(ast), dest, instance->connection_id);
948
949 if (!ast_strlen_zero(instance->uri_params)) {
951 }
952
953 instance->websocket = ast_websocket_client_connect(instance->client,
954 instance, ast_channel_name(ast), &result);
955 if (!instance->websocket || result != WS_OK) {
956 ast_log(LOG_WARNING, "%s: WebSocket connection failed to %s: %s\n",
958 return -1;
959 }
960
961 if (setsockopt(ast_websocket_fd(instance->websocket),
962 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
963 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on websocket connection: %s\n", strerror(errno));
964 }
965
966 ast_debug(3, "%s: WebSocket connection to %s established\n",
967 ast_channel_name(ast), dest);
968
969 /* read_thread_handler() will clean up the bump */
971 read_thread_handler, ao2_bump(instance))) {
972 ast_log(LOG_WARNING, "%s: Failed to create thread.\n", ast_channel_name(ast));
973 ao2_cleanup(instance);
974 return -1;
975 }
976
977 return 0;
978}
979
980static void websocket_destructor(void *data)
981{
982 struct websocket_pvt *instance = data;
983 struct ast_frame *frame = NULL;
984 ast_debug(3, "%s: WebSocket instance freed\n", instance->connection_id);
985
986 AST_LIST_LOCK(&instance->frame_queue);
987 while ((frame = AST_LIST_REMOVE_HEAD(&instance->frame_queue, frame_list))) {
988 ast_frfree(frame);
989 }
990 AST_LIST_UNLOCK(&instance->frame_queue);
991
992 if (instance->timer) {
993 ast_timer_close(instance->timer);
994 instance->timer = NULL;
995 }
996
997 if (instance->channel) {
998 ast_channel_unref(instance->channel);
999 instance->channel = NULL;
1000 }
1001 if (instance->websocket) {
1002 ast_websocket_unref(instance->websocket);
1003 instance->websocket = NULL;
1004 }
1005
1006 ao2_cleanup(instance->client);
1007 instance->client = NULL;
1008
1009 ao2_cleanup(instance->native_codec);
1010 instance->native_codec = NULL;
1011
1012 ao2_cleanup(instance->native_format);
1013 instance->native_format = NULL;
1014
1015 ao2_cleanup(instance->slin_codec);
1016 instance->slin_codec = NULL;
1017
1018 ao2_cleanup(instance->slin_format);
1019 instance->slin_format = NULL;
1020
1021 if (instance->silence.data.ptr) {
1022 ast_free(instance->silence.data.ptr);
1023 instance->silence.data.ptr = NULL;
1024 }
1025
1026 if (instance->translator) {
1028 instance->translator = NULL;
1029 }
1030
1031 if (instance->leftover_data) {
1032 ast_free(instance->leftover_data);
1033 instance->leftover_data = NULL;
1034 }
1035
1036 ast_free(instance->uri_params);
1037}
1038
1041 /*! \brief The name of the module owning this sorcery instance */
1043};
1044
1045static void instance_proxy_cb(void *weakproxy, void *data)
1046{
1047 struct instance_proxy *proxy = weakproxy;
1048 ast_debug(3, "%s: WebSocket instance removed from instances\n", proxy->connection_id);
1049 ao2_unlink(instances, weakproxy);
1050}
1051
1052static struct websocket_pvt* websocket_new(const char *chan_name,
1053 const char *connection_id, struct ast_format *fmt)
1054{
1055 RAII_VAR(struct instance_proxy *, proxy, NULL, ao2_cleanup);
1056 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1057 char uuid[AST_UUID_STR_LEN];
1058 enum ast_websocket_type ws_type;
1059
1060 SCOPED_AO2WRLOCK(locker, instances);
1061
1064 ws_type = AST_WS_TYPE_SERVER;
1065 } else {
1066 ws_type = AST_WS_TYPE_CLIENT;
1067 }
1068
1069 proxy = ao2_weakproxy_alloc(sizeof(*proxy) + strlen(connection_id) + 1, NULL);
1070 if (!proxy) {
1071 return NULL;
1072 }
1073 strcpy(proxy->connection_id, connection_id); /* Safe */
1074
1075 instance = ao2_alloc(sizeof(*instance) + strlen(connection_id) + 1,
1077 if (!instance) {
1078 return NULL;
1079 }
1080 strcpy(instance->connection_id, connection_id); /* Safe */
1081
1082 instance->type = ws_type;
1083 if (ws_type == AST_WS_TYPE_CLIENT) {
1084 instance->client = ast_websocket_client_retrieve_by_id(instance->connection_id);
1085 if (!instance->client) {
1086 ast_log(LOG_ERROR, "%s: WebSocket client connection '%s' not found\n",
1087 chan_name, instance->connection_id);
1088 return NULL;
1089 }
1090 }
1091
1092 AST_LIST_HEAD_INIT(&instance->frame_queue);
1093
1094 /*
1095 * We need the codec to calculate the number of samples in a frame
1096 * so we'll get it once and store it in the instance.
1097 *
1098 * References for native_format and native_codec are now held by the
1099 * instance and will be released when the instance is destroyed.
1100 */
1101 instance->native_format = fmt;
1102 instance->native_codec = ast_format_get_codec(instance->native_format);
1103 /*
1104 * References for native_format and native_codec are now held by the
1105 * instance and will be released when the instance is destroyed.
1106 */
1107
1108 /*
1109 * It's not possible for us to re-time or re-frame media if the data
1110 * stream can't be broken up on arbitrary byte boundaries. This is usually
1111 * indicated by the codec's minimum_bytes being small (10 bytes or less).
1112 * We need to force passthrough mode in this case.
1113 */
1114 if (instance->native_codec->minimum_bytes <= 10) {
1115 instance->passthrough = 1;
1116 instance->optimal_frame_size = 0;
1117 } else {
1118 instance->optimal_frame_size =
1119 (instance->native_codec->default_ms * instance->native_codec->minimum_bytes)
1120 / instance->native_codec->minimum_ms;
1121 instance->leftover_data = ast_calloc(1, instance->optimal_frame_size);
1122 if (!instance->leftover_data) {
1123 return NULL;
1124 }
1125 }
1126
1127 ast_debug(3,
1128 "%s: WebSocket channel native format '%s' Sample rate: %d ptime: %dms minms: %u minbytes: %u passthrough: %d optimal_frame_size: %d\n",
1129 chan_name, ast_format_get_name(instance->native_format),
1130 ast_format_get_sample_rate(instance->native_format),
1131 ast_format_get_default_ms(instance->native_format),
1132 ast_format_get_minimum_ms(instance->native_format),
1133 ast_format_get_minimum_bytes(instance->native_format),
1134 instance->passthrough,
1135 instance->optimal_frame_size);
1136
1137 /* We have exclusive access to proxy and sorcery, no need for locking here. */
1138 if (ao2_weakproxy_set_object(proxy, instance, OBJ_NOLOCK)) {
1139 return NULL;
1140 }
1141
1143 return NULL;
1144 }
1145
1146 if (!ao2_link_flags(instances, proxy, OBJ_NOLOCK)) {
1147 ast_log(LOG_ERROR, "%s: Unable to link WebSocket instance to instances\n",
1148 proxy->connection_id);
1149 return NULL;
1150 }
1151 ast_debug(3, "%s: WebSocket instance created and linked\n", proxy->connection_id);
1152
1153 return ao2_bump(instance);
1154}
1155
1156static int set_instance_translator(struct websocket_pvt *instance)
1157{
1159 instance->slin_format = ao2_bump(instance->native_format);
1160 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1161 return 0;
1162 }
1163
1165 if (!instance->slin_format) {
1166 ast_log(LOG_ERROR, "%s: Unable to get slin format for rate %d\n",
1167 ast_channel_name(instance->channel), instance->native_codec->sample_rate);
1168 return -1;
1169 }
1170 ast_debug(3, "%s: WebSocket channel slin format '%s' Sample rate: %d ptime: %dms\n",
1174
1175 instance->translator = ast_translator_build_path(instance->slin_format, instance->native_format);
1176 if (!instance->translator) {
1177 ast_log(LOG_ERROR, "%s: Unable to build translator path from '%s' to '%s'\n",
1179 ast_format_get_name(instance->slin_format));
1180 return -1;
1181 }
1182
1183 instance->slin_codec = ast_format_get_codec(instance->slin_format);
1184 return 0;
1185}
1186
1187static int set_instance_silence_frame(struct websocket_pvt *instance)
1188{
1189 instance->silence.frametype = AST_FRAME_VOICE;
1190 instance->silence.datalen =
1191 (instance->slin_codec->default_ms * instance->slin_codec->minimum_bytes) / instance->slin_codec->minimum_ms;
1192 instance->silence.samples = instance->silence.datalen / sizeof(uint16_t);
1193 /*
1194 * Even though we'll calloc the data pointer, we don't mark it as
1195 * mallocd because this frame will be around for a while and we don't
1196 * want it accidentally freed before we're done with it.
1197 */
1198 instance->silence.mallocd = 0;
1199 instance->silence.offset = 0;
1200 instance->silence.src = __PRETTY_FUNCTION__;
1201 instance->silence.subclass.format = instance->slin_format;
1202 instance->silence.data.ptr = ast_calloc(1, instance->silence.datalen);
1203 if (!instance->silence.data.ptr) {
1204 return -1;
1205 }
1206
1207 return 0;
1208}
1209
1210static int set_channel_timer(struct websocket_pvt *instance)
1211{
1212 int rate = 0;
1213 instance->timer = ast_timer_open();
1214 if (!instance->timer) {
1215 return -1;
1216 }
1217 /* Rate is the number of ticks per second, not the interval. */
1218 rate = 1000 / ast_format_get_default_ms(instance->native_format);
1219 ast_debug(3, "%s: WebSocket timer rate %d\n",
1220 ast_channel_name(instance->channel), rate);
1221 ast_timer_set_rate(instance->timer, rate);
1222 /*
1223 * Calling ast_channel_set_fd will cause the channel thread to call
1224 * webchan_read at 'rate' times per second.
1225 */
1226 ast_channel_set_fd(instance->channel, 0, ast_timer_fd(instance->timer));
1227
1228 return 0;
1229}
1230
1231static int set_channel_variables(struct websocket_pvt *instance)
1232{
1233 char *pkt_size = NULL;
1234 int res = ast_asprintf(&pkt_size, "%d", instance->optimal_frame_size);
1235 if (res <= 0) {
1236 return -1;
1237 }
1238
1240 pkt_size);
1241 ast_free(pkt_size);
1243 instance->connection_id);
1244
1245 return 0;
1246}
1247
1249{
1250 char *params = ast_strdupa(uri_params);
1251 char *nvp = NULL;
1252 char *nv = NULL;
1253
1254 /*
1255 * uri_params should be a comma-separated list of key=value pairs.
1256 * For example:
1257 * name1=value1,name2=value2
1258 * We're verifying that each name and value either doesn't need
1259 * to be encoded or that it already is.
1260 */
1261
1262 while((nvp = ast_strsep(&params, ',', 0))) {
1263 /* nvp will be name1=value1 */
1264 while((nv = ast_strsep(&nvp, '=', 0))) {
1265 /* nv will be either name1 or value1 */
1266 if (!ast_uri_verify_encoded(nv)) {
1267 return 0;
1268 }
1269 }
1270 }
1271
1272 return 1;
1273}
1274
1275enum {
1276 OPT_WS_CODEC = (1 << 0),
1280};
1281
1282enum {
1289
1296
1297static struct ast_channel *webchan_request(const char *type,
1298 struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids,
1299 const struct ast_channel *requestor, const char *data, int *cause)
1300{
1301 char *parse;
1302 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1303 struct ast_channel *chan = NULL;
1304 struct ast_format *fmt = NULL;
1305 struct ast_format_cap *caps = NULL;
1307 AST_APP_ARG(connection_id);
1309 );
1310 struct ast_flags opts = { 0, };
1311 char *opt_args[OPT_ARG_ARRAY_SIZE];
1312 const char *requestor_name = requestor ? ast_channel_name(requestor) : "no channel";
1313
1314 ast_debug(3, "%s: WebSocket channel requested\n",
1315 requestor_name);
1316
1317 if (ast_strlen_zero(data)) {
1318 ast_log(LOG_ERROR, "%s: A connection id is required for the 'WebSocket' channel\n",
1319 requestor_name);
1320 goto failure;
1321 }
1322 parse = ast_strdupa(data);
1323 AST_NONSTANDARD_APP_ARGS(args, parse, '/');
1324
1325 if (ast_strlen_zero(args.connection_id)) {
1326 ast_log(LOG_ERROR, "%s: connection_id is required for the 'WebSocket' channel\n",
1327 requestor_name);
1328 goto failure;
1329 }
1330
1331 if (!ast_strlen_zero(args.options)
1332 && ast_app_parse_options(websocket_options, &opts, opt_args,
1333 ast_strdupa(args.options))) {
1334 ast_log(LOG_ERROR, "%s: 'WebSocket' channel options '%s' parse error\n",
1335 requestor_name, args.options);
1336 goto failure;
1337 }
1338
1339 if (ast_test_flag(&opts, OPT_WS_CODEC)
1340 && !ast_strlen_zero(opt_args[OPT_ARG_WS_CODEC])) {
1341 ast_debug(3, "%s: Using specified format %s\n",
1342 requestor_name, opt_args[OPT_ARG_WS_CODEC]);
1343 fmt = ast_format_cache_get(opt_args[OPT_ARG_WS_CODEC]);
1344 } else {
1345 /*
1346 * If codec wasn't specified in the dial string,
1347 * use the first format in the capabilities.
1348 */
1349 ast_debug(3, "%s: Using format %s from requesting channel\n",
1350 requestor_name, opt_args[OPT_ARG_WS_CODEC]);
1351 fmt = ast_format_cap_get_format(cap, 0);
1352 }
1353
1354 if (!fmt) {
1355 ast_log(LOG_WARNING, "%s: No codec found for sending media to connection '%s'\n",
1356 requestor_name, args.connection_id);
1357 goto failure;
1358 }
1359
1360 instance = websocket_new(requestor_name, args.connection_id, fmt);
1361 if (!instance) {
1362 ast_log(LOG_ERROR, "%s: Failed to allocate WebSocket channel pvt\n",
1363 requestor_name);
1364 goto failure;
1365 }
1366
1367 instance->no_auto_answer = ast_test_flag(&opts, OPT_WS_NO_AUTO_ANSWER);
1368 if (!instance->passthrough) {
1369 instance->passthrough = ast_test_flag(&opts, OPT_WS_PASSTHROUGH);
1370 }
1371
1373 && !ast_strlen_zero(opt_args[OPT_ARG_WS_URI_PARAM])) {
1374 char *comma;
1375
1376 if (ast_strings_equal(args.connection_id, INCOMING_CONNECTION_ID)) {
1378 "%s: URI parameters are not allowed for 'WebSocket/INCOMING' channels\n",
1379 requestor_name);
1380 goto failure;
1381 }
1382
1383 ast_debug(3, "%s: Using URI parameters '%s'\n",
1384 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM]);
1385
1387 ast_log(LOG_ERROR, "%s: Invalid URI parameters '%s' in WebSocket/%s dial string\n",
1388 requestor_name, opt_args[OPT_ARG_WS_URI_PARAM],
1389 args.connection_id);
1390 goto failure;
1391 }
1392
1393 instance->uri_params = ast_strdup(opt_args[OPT_ARG_WS_URI_PARAM]);
1394 comma = instance->uri_params;
1395 /*
1396 * The normal separator for query string components is an
1397 * ampersand ('&') but the Dial app interprets them as additional
1398 * channels to dial in parallel so we instruct users to separate
1399 * the parameters with commas (',') instead. We now have to
1400 * convert those commas back to ampersands.
1401 */
1402 while ((comma = strchr(comma,','))) {
1403 *comma = '&';
1404 }
1405 ast_debug(3, "%s: Using final URI '%s'\n", requestor_name, instance->uri_params);
1406 }
1407
1408 chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
1409 requestor, 0, "WebSocket/%s/%p", args.connection_id, instance);
1410 if (!chan) {
1411 ast_log(LOG_ERROR, "%s: Unable to alloc channel\n", ast_channel_name(requestor));
1412 goto failure;
1413 }
1414
1415 ast_debug(3, "%s: WebSocket channel %s allocated for connection %s\n",
1416 ast_channel_name(chan), requestor_name,
1417 instance->connection_id);
1418
1419 instance->channel = ao2_bump(chan);
1420 ast_channel_tech_set(instance->channel, &websocket_tech);
1421
1422 if (set_instance_translator(instance) != 0) {
1423 goto failure;
1424 }
1425
1426 if (set_instance_silence_frame(instance) != 0) {
1427 goto failure;
1428 }
1429
1430 if (set_channel_timer(instance) != 0) {
1431 goto failure;
1432 }
1433
1434 if (set_channel_variables(instance) != 0) {
1435 goto failure;
1436 }
1437
1439 if (!caps) {
1440 ast_log(LOG_ERROR, "%s: Unable to alloc caps\n", requestor_name);
1441 goto failure;
1442 }
1443
1444 ast_format_cap_append(caps, instance->native_format, 0);
1445 ast_channel_nativeformats_set(instance->channel, caps);
1446 ast_channel_set_writeformat(instance->channel, instance->native_format);
1447 ast_channel_set_rawwriteformat(instance->channel, instance->native_format);
1448 ast_channel_set_readformat(instance->channel, instance->native_format);
1449 ast_channel_set_rawreadformat(instance->channel, instance->native_format);
1450 ast_channel_tech_pvt_set(chan, ao2_bump(instance));
1451 ast_channel_unlock(chan);
1452 ao2_cleanup(caps);
1453
1454 ast_debug(3, "%s: WebSocket channel created to %s\n",
1455 ast_channel_name(chan), args.connection_id);
1456
1457 return chan;
1458
1459failure:
1460 if (chan) {
1461 ast_channel_unlock(chan);
1462 }
1463 *cause = AST_CAUSE_FAILURE;
1464 return NULL;
1465}
1466
1467/*!
1468 * \internal
1469 *
1470 * Called by the core to hang up the channel.
1471 */
1472static int webchan_hangup(struct ast_channel *ast)
1473{
1474 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1475
1476 if (!instance) {
1477 return -1;
1478 }
1479 ast_debug(3, "%s: WebSocket call hangup. cid: %s\n",
1480 ast_channel_name(ast), instance->connection_id);
1481
1482 /*
1483 * We need to lock because read_from_ws_and_queue() is probably waiting
1484 * on the websocket file descriptor and will unblock and immediately try to
1485 * check the websocket and read from it. We don't want to pull the
1486 * websocket out from under it between the check and read.
1487 */
1488 ao2_lock(instance);
1489 if (instance->websocket) {
1490 ast_websocket_close(instance->websocket, 1000);
1491 ast_websocket_unref(instance->websocket);
1492 instance->websocket = NULL;
1493 }
1495 ao2_unlock(instance);
1496
1497 /* Clean up the reference from adding the instance to the channel */
1498 ao2_cleanup(instance);
1499
1500 return 0;
1501}
1502
1503static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration)
1504{
1505 struct websocket_pvt *instance = ast_channel_tech_pvt(ast);
1506 char *command;
1507 int res = 0;
1508
1509 if (!instance) {
1510 return -1;
1511 }
1512
1513 res = ast_asprintf(&command, "%s digit:%c channel_id:%s", DTMF_END, digit, ast_channel_uniqueid(instance->channel));
1514 if (res <= 0 || !command) {
1515 ast_log(LOG_ERROR, "%s: Failed to create DTMF_END\n", ast_channel_name(instance->channel));
1516 return 0;
1517 }
1518 res = ast_websocket_write_string(instance->websocket, command);
1519 if (res != 0) {
1520 ast_log(LOG_ERROR, "%s: Failed to send DTMF_END\n", ast_channel_name(instance->channel));
1521 ast_free(command);
1522 return 0;
1523 }
1524 ast_debug(3, "%s: Sent %s\n", ast_channel_name(instance->channel), command);
1525 ast_free(command);
1526 return 0;
1527}
1528
1529/*!
1530 * \internal
1531 *
1532 * Called by res_http_websocket after a client has connected and
1533 * successfully upgraded from HTTP to WebSocket.
1534 *
1535 * Depends on incoming_ws_http_callback parsing the connection_id from
1536 * the HTTP request and storing it in get_params.
1537 */
1538static void incoming_ws_established_cb(struct ast_websocket *ast_ws_session,
1539 struct ast_variable *get_params, struct ast_variable *upgrade_headers)
1540{
1541 RAII_VAR(struct ast_websocket *, s, ast_ws_session, ast_websocket_unref);
1542 struct ast_variable *v;
1543 const char *connection_id = NULL;
1544 struct websocket_pvt *instance = NULL;
1545 int nodelay = 1;
1546
1547 ast_debug(3, "WebSocket established\n");
1548
1549 for (v = upgrade_headers; v; v = v->next) {
1550 ast_debug(4, "Header-> %s: %s\n", v->name, v->value);
1551 }
1552 for (v = get_params; v; v = v->next) {
1553 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1554 }
1555
1556 connection_id = ast_variable_find_in_list(get_params, "CONNECTION_ID");
1557 if (!connection_id) {
1558 /*
1559 * This can't really happen because websocket_http_callback won't
1560 * let it get this far if it can't add the connection_id to the
1561 * get_params.
1562 * Just in case though...
1563 */
1564 ast_log(LOG_WARNING, "WebSocket connection id not found\n");
1566 ast_websocket_close(ast_ws_session, 1000);
1567 return;
1568 }
1569
1571 if (!instance) {
1572 /*
1573 * This also can't really happen because websocket_http_callback won't
1574 * let it get this far if it can't find the instance.
1575 * Just in case though...
1576 */
1577 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", connection_id);
1579 ast_websocket_close(ast_ws_session, 1000);
1580 return;
1581 }
1582 instance->websocket = ao2_bump(ast_ws_session);
1583
1584 if (setsockopt(ast_websocket_fd(instance->websocket),
1585 IPPROTO_TCP, TCP_NODELAY, (char *) &nodelay, sizeof(nodelay)) < 0) {
1586 ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on manager connection: %s\n", strerror(errno));
1587 }
1588
1589 /* read_thread_handler cleans up the bump */
1590 read_thread_handler(ao2_bump(instance));
1591
1592 ao2_cleanup(instance);
1593 ast_debug(3, "WebSocket closed\n");
1594}
1595
1596/*!
1597 * \internal
1598 *
1599 * Called by the core http server after a client connects but before
1600 * the upgrade from HTTP to Websocket. We need to save the URI in
1601 * the CONNECTION_ID in a get_param because it contains the connection UUID
1602 * we gave to the client when they used externalMedia to create the channel.
1603 * incoming_ws_established_cb() will use this to retrieve the chan_websocket
1604 * instance.
1605 */
1607 const struct ast_http_uri *urih, const char *uri,
1608 enum ast_http_method method, struct ast_variable *get_params,
1609 struct ast_variable *headers)
1610{
1611 struct ast_http_uri fake_urih = {
1613 };
1614 int res = 0;
1615 /*
1616 * Normally the http server will destroy the get_params
1617 * when the session ends but if there weren't any initially
1618 * and we create some and add them to the list, the http server
1619 * won't know about it so we have to destroy it ourselves.
1620 */
1621 int destroy_get_params = (get_params == NULL);
1622 struct ast_variable *v = NULL;
1623 RAII_VAR(struct websocket_pvt *, instance, NULL, ao2_cleanup);
1624
1625 ast_debug(2, "URI: %s Starting\n", uri);
1626
1627 /*
1628 * The client will have issued the GET request with a URI of
1629 * /media/<connection_id>
1630 *
1631 * Since this callback is registered for the /media URI prefix the
1632 * http server will strip that off the front of the URI passing in
1633 * only the path components after that in the 'uri' parameter.
1634 * This should leave only the connection id without a leading '/'.
1635 */
1636 instance = ao2_weakproxy_find(instances, uri, OBJ_SEARCH_KEY | OBJ_NOLOCK, "");
1637 if (!instance) {
1638 ast_log(LOG_WARNING, "%s: WebSocket instance not found\n", uri);
1639 ast_http_error(ser, 404, "Not found", "WebSocket instance not found");
1640 return -1;
1641 }
1642
1643 /*
1644 * We don't allow additional connections using the same connection id.
1645 */
1646 if (instance->websocket) {
1647 ast_log(LOG_WARNING, "%s: Websocket already connected for channel '%s'\n",
1648 uri, instance->channel ? ast_channel_name(instance->channel) : "unknown");
1649 ast_http_error(ser, 409, "Conflict", "Another websocket connection exists for this connection id");
1650 return -1;
1651 }
1652
1653 v = ast_variable_new("CONNECTION_ID", uri, "");
1654 if (!v) {
1655 ast_http_error(ser, 500, "Server error", "");
1656 return -1;
1657 }
1658 ast_variable_list_append(&get_params, v);
1659
1660 for (v = get_params; v; v = v->next) {
1661 ast_debug(4, " Param-> %s: %s\n", v->name, v->value);
1662 }
1663
1664 /*
1665 * This will ultimately call internal_ws_established_cb() so
1666 * this function will block until the websocket is closed and
1667 * internal_ws_established_cb() returns;
1668 */
1669 res = ast_websocket_uri_cb(ser, &fake_urih, uri, method,
1670 get_params, headers);
1671 if (destroy_get_params) {
1672 ast_variables_destroy(get_params);
1673 }
1674
1675 ast_debug(2, "URI: %s DONE\n", uri);
1676
1677 return res;
1678}
1679
1680static struct ast_http_uri http_uri = {
1682 .description = "Media over Websocket",
1683 .uri = "media",
1684 .has_subtree = 1,
1685 .data = NULL,
1686 .key = __FILE__,
1687 .no_decode_uri = 1,
1688};
1689
1690/*! \brief Function called when our module is unloaded */
1706
1710
1711/*! \brief Function called when our module is loaded */
1712static int load_module(void)
1713{
1714 int res = 0;
1715 struct ast_websocket_protocol *protocol;
1716
1719 }
1720
1723 ast_log(LOG_ERROR, "Unable to register channel class 'WebSocket'\n");
1724 unload_module();
1726 }
1727
1729 AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, 17, instance_proxy_hash_fn,
1730 instance_proxy_sort_fn, instance_proxy_cmp_fn);
1731 if (!instances) {
1733 "Failed to allocate the chan_websocket instance registry\n");
1734 unload_module();
1736 }
1737
1739 if (!ast_ws_server) {
1740 unload_module();
1742 }
1743
1744 protocol = ast_websocket_sub_protocol_alloc("media");
1745 if (!protocol) {
1746 unload_module();
1748 }
1751
1753
1755}
1756
1757AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Websocket Media Channel",
1758 .support_level = AST_MODULE_SUPPORT_CORE,
1759 .load = load_module,
1760 .unload = unload_module,
1761 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
1762 .requires = "res_http_websocket,res_websocket_client",
char digit
jack_status_t status
Definition app_jack.c:149
enum queue_result id
Definition app_queue.c:1771
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_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_unlock(a)
Definition astobj2.h:729
#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_lock(a)
Definition astobj2.h:717
#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
static PGresult * result
Definition cel_pgsql.c:84
static const char type[]
static int set_instance_translator(struct websocket_pvt *instance)
#define FLUSH_MEDIA
#define MEDIA_BUFFERING_COMPLETED
#define QUEUE_LENGTH_XON_LEVEL
#define QUEUE_DRAINED
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 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.
#define MEDIA_XOFF
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)
@ OPT_ARG_WS_PASSTHROUGH
@ OPT_ARG_WS_URI_PARAM
@ OPT_ARG_WS_CODEC
@ OPT_ARG_WS_NO_AUTO_ANSWER
@ OPT_ARG_ARRAY_SIZE
#define MEDIA_START
#define MEDIA_WEBSOCKET_CONNECTION_ID
#define DTMF_END
#define DRIVER_STATUS
static int read_from_ws_and_queue(struct websocket_pvt *instance)
static int webchan_send_dtmf_text(struct ast_channel *ast, char digit, unsigned int duration)
static struct ast_http_uri http_uri
#define GET_DRIVER_STATUS
static int set_channel_variables(struct websocket_pvt *instance)
@ OPT_WS_CODEC
@ OPT_WS_PASSTHROUGH
@ OPT_WS_URI_PARAM
@ OPT_WS_NO_AUTO_ANSWER
static void * read_thread_handler(void *obj)
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 START_MEDIA_BUFFERING
static int set_channel_timer(struct websocket_pvt *instance)
#define MEDIA_XON
#define QUEUE_LENGTH_XOFF_LEVEL
static const struct ast_app_option websocket_options[128]
#define PAUSE_MEDIA
static struct ao2_container * instances
#define MEDIA_WEBSOCKET_OPTIMAL_FRAME_SIZE
static void set_channel_format(struct websocket_pvt *instance, struct ast_format *fmt)
#define INCOMING_CONNECTION_ID
static struct websocket_pvt * websocket_new(const char *chan_name, const char *connection_id, struct ast_format *fmt)
#define REPORT_QUEUE_DRAINED
#define CONTINUE_MEDIA
#define STOP_MEDIA_BUFFERING
static int load_module(void)
Function called when our module is loaded.
static int webchan_call(struct ast_channel *ast, const char *dest, int timeout)
static int process_binary_message(struct websocket_pvt *instance, char *payload, uint64_t payload_len)
static int set_instance_silence_frame(struct websocket_pvt *instance)
static struct ast_websocket_server * ast_ws_server
static int unload_module(void)
Function called when our module is unloaded.
#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 struct ast_channel * webchan_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
static int queue_option_frame(struct websocket_pvt *instance, char *buffer)
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
void * ast_channel_tech_pvt(const struct ast_channel *chan)
struct ast_format * ast_channel_rawreadformat(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)
void ast_channel_unregister(const struct ast_channel_tech *tech)
Unregister a channel technology.
Definition channel.c:570
int ast_queue_control(struct ast_channel *chan, enum ast_control_frame_type control)
Queue a control frame without payload.
Definition channel.c:1288
const char * ast_channel_uniqueid(const struct ast_channel *chan)
int ast_set_read_format(struct ast_channel *chan, struct ast_format *format)
Sets read format on channel chan.
Definition channel.c:5757
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:539
#define ast_channel_unref(c)
Decrease channel reference count.
Definition channel.h:3018
void ast_channel_set_fd(struct ast_channel *chan, int which, int fd)
Definition channel.c:2416
void ast_channel_tech_set(struct ast_channel *chan, const struct ast_channel_tech *value)
#define ast_channel_unlock(chan)
Definition channel.h:2983
void ast_channel_set_writeformat(struct ast_channel *chan, struct ast_format *format)
struct ast_format * ast_channel_readformat(struct ast_channel *chan)
@ AST_STATE_DOWN
Codec API.
@ AST_MEDIA_TYPE_UNKNOWN
Definition codec.h:31
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.
int ast_format_cache_is_slinear(struct ast_format *format)
Determines if a format is one of the cached slin formats.
#define ast_format_cache_get(name)
Retrieve a named format from the cache.
struct ast_format * ast_format_cache_get_slin_by_rate(unsigned int rate)
Retrieve the best signed linear format given a sample rate.
int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_type type)
Add all codecs Asterisk knows about for a specific type to the capabilities structure.
Definition format_cap.c:216
struct ast_format * ast_format_cap_get_format(const struct ast_format_cap *cap, int position)
Get the format at a specific index.
Definition format_cap.c:400
@ AST_FORMAT_CAP_FLAG_DEFAULT
Definition format_cap.h:38
#define ast_format_cap_append(cap, format, framing)
Add format capability to capabilities structure.
Definition format_cap.h:99
#define ast_format_cap_alloc(flags)
Allocate a new ast_format_cap structure.
Definition format_cap.h:49
static 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:721
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_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition http.c:689
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.
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.
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_BINARY
@ AST_WEBSOCKET_OPCODE_CLOSE
@ AST_WEBSOCKET_OPCODE_TEXT
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:3066
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_frdup(fr)
Copies a frame.
#define ast_frfree(fr)
#define AST_MALLOCD_DATA
#define AST_FRAME_SET_BUFFER(fr, _base, _ofs, _datalen)
#define AST_FRIENDLY_OFFSET
Offset into a frame's data buffer.
@ AST_FRAME_CONTROL
@ AST_CONTROL_ANSWER
@ AST_CONTROL_HANGUP
@ AST_CONTROL_OPTION
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
#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
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.
const char * method
Definition res_pjsip.c:1279
static struct @519 args
#define NULL
Definition resample.c:96
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
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition utils.c:1871
Generic container type.
Structure to pass both assignedid values to channel drivers.
Definition channel.h:606
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 sample_rate
Sample rate (number of samples carried in a second)
Definition codec.h:52
unsigned int minimum_bytes
Length in bytes of the data payload of a minimum_ms frame.
Definition codec.h:60
unsigned int default_ms
Default length of media carried (in milliseconds) in a frame.
Definition codec.h:58
int(* samples_count)(struct ast_frame *frame)
Retrieve the number of samples in a frame.
Definition codec.h:68
unsigned int minimum_ms
Minimum length of media that can be carried (in milliseconds) in a frame.
Definition codec.h:54
Structure used to handle boolean flags.
Definition utils.h:220
Format capabilities structure, holds formats + preference order + etc.
Definition format_cap.c:54
Definition of a media format.
Definition format.c:43
struct ast_format * format
Data structure associated with a single frame of data.
struct ast_frame_subclass subclass
enum ast_frame_type frametype
union ast_frame::@239 data
Definition of a URI handler.
Definition http.h:102
ast_http_callback callback
Definition http.h:107
void * data
Definition http.h:116
describes a server instance
Definition tcptls.h:151
Default structure for translators, with the basic fields and buffers, all allocated as part of the sa...
Definition translate.h:213
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.
char connection_id[0]
struct ast_format * native_format
struct websocket_pvt::@145 frame_queue
struct ast_channel * channel
struct ast_codec * slin_codec
enum ast_websocket_type type
struct ast_timer * timer
struct ast_format * slin_format
pthread_t outbound_read_thread
struct ast_frame silence
struct ast_codec * native_codec
struct ast_websocket_client * client
struct ast_websocket * websocket
struct ast_trans_pvt * translator
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.
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
void ast_translator_free_path(struct ast_trans_pvt *tr)
Frees a translator path Frees the given translator path structure.
Definition translate.c:476
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
#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
int ast_wait_for_input(int fd, int ms)
Definition utils.c:1734
#define ast_pthread_create_detached_background(a, b, c, d)
Definition utils.h:637
int ast_uri_verify_encoded(const char *string)
Verify if a string is valid as a URI component.
Definition utils.c:781
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.