Asterisk - The Open Source Telephony Project GIT-master-590b490
Loading...
Searching...
No Matches
app_stasis_broadcast.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2026, Aurora Innovation AB
5 *
6 * Daniel Donoghue <daniel.donoghue@aurorainnovation.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 * \brief Stasis broadcast dialplan application
22 *
23 * \author Daniel Donoghue <daniel.donoghue@aurorainnovation.com>
24 */
25
26/*** MODULEINFO
27 <depend type="module">res_stasis</depend>
28 <depend type="module">res_stasis_broadcast</depend>
29 <support_level>extended</support_level>
30 ***/
31
32#include "asterisk.h"
33
34#include "asterisk/app.h"
35#include "asterisk/module.h"
36#include "asterisk/pbx.h"
39
40/*** DOCUMENTATION
41 <application name="StasisBroadcast" language="en_US">
42 <since>
43 <version>20.17.0</version>
44 <version>22.7.0</version>
45 <version>23.1.0</version>
46 </since>
47 <synopsis>Broadcast a channel to multiple ARI applications for claiming,
48 then hand control to the winning application.</synopsis>
49 <syntax>
50 <parameter name="timeout">
51 <para>Timeout in milliseconds to wait for a claim.</para>
52 <para>Valid range: 0 to 60000ms</para>
53 <para>Default: 500ms</para>
54 </parameter>
55 <parameter name="app_filter">
56 <para>Regular expression to filter which ARI applications
57 receive the broadcast. Only applications with names matching
58 the regex will be notified.</para>
59 <para>Because arguments are comma-delimited, commas cannot
60 appear in the regex pattern. Use character classes
61 (e.g. <literal>[,]</literal>) if a literal comma is
62 needed, or omit the filter and handle selection in the
63 ARI application.</para>
64 <para>Default: all connected applications</para>
65 </parameter>
66 <parameter name="args">
67 <para>Optional colon-delimited arguments passed to the winning
68 application via the <literal>StasisStart</literal> event. These
69 are equivalent to the extra arguments in <literal>Stasis()</literal>.</para>
70 <para>Example: <literal>sales:priority-high</literal></para>
71 </parameter>
72 <parameter name="notify_claimed">
73 <para>Whether to send a <literal>CallClaimed</literal> event to
74 ARI applications when a channel is claimed.</para>
75 <para>When enabled, the <literal>CallClaimed</literal> event is
76 sent only to applications that matched the
77 <replaceable>app_filter</replaceable> (or all applications if no
78 filter was set).</para>
79 <para>Disabled by default to minimise WebSocket traffic under
80 high load. Losing claimants already receive a
81 <literal>409</literal> HTTP response.</para>
82 <para>Default: no</para>
83 </parameter>
84 </syntax>
85 <description>
86 <para>Broadcasts the incoming channel to all connected ARI applications
87 (or a filtered subset) via a <literal>CallBroadcast</literal> event.
88 ARI applications can respond with a claim request. The first application
89 to claim the channel wins, and subsequent claims are rejected.</para>
90 <para>If an application claims the channel within the timeout, the channel
91 is automatically placed under Stasis control with the winning application,
92 exactly as if <literal>Stasis(winner_app)</literal> had been called.
93 The winning application receives a <literal>StasisStart</literal> event
94 and has full channel control until it calls <literal>continue</literal>
95 or the channel hangs up.</para>
96 <para>If no application claims the channel within the timeout, control
97 returns to the dialplan immediately, allowing fallback handling.</para>
98 <para>This application will set the following channel variables:</para>
99 <variablelist>
100 <variable name="STASISSTATUS">
101 <value name="SUCCESS">
102 An application claimed the channel and the Stasis
103 session completed without failures.
104 </value>
105 <value name="FAILED">
106 An application claimed the channel but a failure
107 occurred when executing the Stasis application.
108 </value>
109 <value name="TIMEOUT">
110 No application claimed the channel within the
111 timeout period.
112 </value>
113 </variable>
114 </variablelist>
115 <example>
116 ; Broadcast with default timeout (500ms) to all apps
117 ; Channel automatically enters Stasis with the winner
118 exten => _X.,1,StasisBroadcast()
119 same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
120 same => n,Hangup()
121 same => n(no_route),Playback(sorry-no-agent)
122 same => n,Hangup()
123 </example>
124 <example>
125 ; Broadcast with custom timeout, app filter, and args for the winner
126 exten => _X.,1,StasisBroadcast(2000,^ivr-.*,sales:priority-high)
127 same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
128 same => n,Hangup()
129 same => n(no_route),Playback(sorry-no-agent)
130 same => n,Hangup()
131 </example>
132 </description>
133 </application>
134 ***/
135
136/*! \brief Dialplan application name */
137static const char *app = "StasisBroadcast";
138
139/*! \brief Default timeout in milliseconds */
140#define DEFAULT_TIMEOUT_MS 500
141
142/*! \brief Maximum timeout in milliseconds */
143#define MAX_TIMEOUT_MS 60000
144
145/*! \brief Maximum number of Stasis arguments */
146#define MAX_STASIS_ARGS 128
147
148/*! \brief StasisBroadcast dialplan application callback */
149static int stasis_broadcast_exec(struct ast_channel *chan, const char *data)
150{
151 char *parse = NULL;
152 int timeout_ms = DEFAULT_TIMEOUT_MS;
153 const char *app_filter = NULL;
154 const char *stasis_args_raw = NULL;
155 unsigned int flags = STASIS_BROADCAST_FLAG_SUPPRESS_CLAIMED;
156 char *winner = NULL;
157 int result = 0;
158 int stasis_argc = 0;
159 char *stasis_argv[MAX_STASIS_ARGS];
160
162 AST_APP_ARG(timeout);
163 AST_APP_ARG(app_filter);
164 AST_APP_ARG(stasis_args);
165 AST_APP_ARG(notify_claimed);
166 );
167
168 ast_assert(chan != NULL);
169
170 /* Initialize channel variable */
171 pbx_builtin_setvar_helper(chan, "STASISSTATUS", "");
172
173 /* Parse positional arguments if provided */
174 if (!ast_strlen_zero(data)) {
175 parse = ast_strdupa(data);
177
178 if (!ast_strlen_zero(args.timeout)) {
179 if (sscanf(args.timeout, "%d", &timeout_ms) != 1
180 || timeout_ms < 0 || timeout_ms > MAX_TIMEOUT_MS) {
182 "Channel %s: invalid timeout value '%s' (must be 0-%dms), using default %dms\n",
184 timeout_ms = DEFAULT_TIMEOUT_MS;
185 }
186 }
187
188 if (!ast_strlen_zero(args.app_filter)) {
189 app_filter = args.app_filter;
190 }
191
192 if (!ast_strlen_zero(args.stasis_args)) {
193 stasis_args_raw = args.stasis_args;
194 }
195
196 if (!ast_strlen_zero(args.notify_claimed) && ast_true(args.notify_claimed)) {
197 flags &= ~STASIS_BROADCAST_FLAG_SUPPRESS_CLAIMED;
198 }
199 }
200
201 /*
202 * Parse colon-delimited Stasis arguments. stasis_argv[] holds
203 * pointers into the stack-allocated args_copy buffer. This is
204 * safe because stasis_app_exec is called within this same
205 * function scope so the stack frame remains alive.
206 */
207 if (!ast_strlen_zero(stasis_args_raw)) {
208 char *args_copy = ast_strdupa(stasis_args_raw);
209 char *arg;
210
211 while ((arg = strsep(&args_copy, ":")) != NULL && stasis_argc < MAX_STASIS_ARGS) {
212 stasis_argv[stasis_argc++] = arg;
213 }
214 }
215
216 ast_debug(3, "Broadcasting channel %s (timeout=%dms, filter=%s, args=%d)\n",
217 ast_channel_name(chan), timeout_ms, app_filter ? app_filter : "none",
218 stasis_argc);
219
220 /* Start the broadcast */
221 result = stasis_app_broadcast_channel(chan, timeout_ms, app_filter, flags);
222 if (result) {
223 ast_log(LOG_ERROR, "Failed to broadcast channel %s: %s\n",
224 ast_channel_name(chan),
225 result == AST_OPTIONAL_API_UNAVAILABLE ? "res_stasis_broadcast not loaded" : "internal error");
226 pbx_builtin_setvar_helper(chan, "STASISSTATUS", "FAILED");
227 return 0;
228 }
229
230 /* Wait for a claim. A late claim can arrive between the timeout
231 * expiring and our cleanup call, so always check for a winner
232 * regardless of the wait result. */
233 stasis_app_broadcast_wait(chan, timeout_ms);
235
236 if (winner) {
237 int ret;
238
239 ast_debug(3, "Channel %s claimed by %s, entering Stasis\n",
240 ast_channel_name(chan), winner);
241
242 /* Defer cleanup until after Stasis so concurrent claimants can still
243 * find the context (with claimed=1) and receive 409 Conflict instead
244 * of 404 Not Found. */
245 ret = stasis_app_exec(chan, winner, stasis_argc, stasis_argv);
246 ast_free(winner);
247
248 /* Clean up now that the Stasis session has ended */
250
251 if (ret) {
252 pbx_builtin_setvar_helper(chan, "STASISSTATUS", "FAILED");
253 if (ast_check_hangup(chan)) {
254 return -1;
255 }
256 } else {
257 pbx_builtin_setvar_helper(chan, "STASISSTATUS", "SUCCESS");
258 }
259 } else {
260 /* No winner: clean up immediately, nothing to race against */
262 ast_log(LOG_WARNING, "Channel %s: not claimed within %dms timeout\n",
263 ast_channel_name(chan), timeout_ms);
264 pbx_builtin_setvar_helper(chan, "STASISSTATUS", "TIMEOUT");
265 }
266
267 return 0;
268}
269
270static int load_module(void)
271{
273}
274
275static int unload_module(void)
276{
278}
279
281 "Stasis application broadcast",
282 .support_level = AST_MODULE_SUPPORT_EXTENDED,
283 .load = load_module,
284 .unload = unload_module,
285 .requires = "res_stasis,res_stasis_broadcast",
static int stasis_broadcast_exec(struct ast_channel *chan, const char *data)
StasisBroadcast dialplan application callback.
static const char * app
Dialplan application name.
#define MAX_TIMEOUT_MS
Maximum timeout in milliseconds.
static int load_module(void)
static int unload_module(void)
#define MAX_STASIS_ARGS
Maximum number of Stasis arguments.
#define DEFAULT_TIMEOUT_MS
Default timeout in milliseconds.
char * strsep(char **str, const char *delims)
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition astmm.h:298
#define ast_log
Definition astobj2.c:42
static PGresult * result
Definition cel_pgsql.c:84
const char * ast_channel_name(const struct ast_channel *chan)
const char * ast_channel_uniqueid(const struct ast_channel *chan)
int ast_check_hangup(struct ast_channel *chan)
Check to see if a channel is needing hang up.
Definition channel.c:446
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition module.h:329
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODULE_SUPPORT_EXTENDED
Definition module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
int ast_unregister_application(const char *app)
Unregister an application.
Definition pbx_app.c:404
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition module.h:640
#define AST_OPTIONAL_API_UNAVAILABLE
A common value for optional API stub functions to return.
Core PBX routines and definitions.
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name.
static struct @522 args
#define NULL
Definition resample.c:96
Stasis Application Broadcast API.
void AST_OPTIONAL_API_NAME() stasis_app_broadcast_cleanup(const char *channel_id)
Clean up broadcast context for a channel.
int AST_OPTIONAL_API_NAME() stasis_app_broadcast_wait(struct ast_channel *chan, int timeout_ms)
Wait for a broadcast channel to be claimed.
#define STASIS_BROADCAST_FLAG_SUPPRESS_CLAIMED
Suppress CallClaimed event for this broadcast.
int AST_OPTIONAL_API_NAME() stasis_app_broadcast_channel(struct ast_channel *chan, int timeout_ms, const char *app_filter, unsigned int flags)
Start a broadcast for a channel.
char *AST_OPTIONAL_API_NAME() stasis_app_broadcast_winner(const char *channel_id)
Get the winner app name for a broadcast channel.
Backend API for implementing components of res_stasis.
int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, char *argv[])
Control a channel using stasis_app.
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition utils.c:2233
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition strings.h:65
Main Channel structure associated with a channel.
#define ast_assert(a)
Definition utils.h:779