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