mirror of
https://github.com/asterisk/asterisk.git
synced 2026-04-28 17:53:35 +00:00
stasis_broadcast: Add optional ARI broadcast with first-claim-wins
Adds two optional modules: res_stasis_broadcast.so: Infrastructure for broadcasting a single incoming channel to multiple ARI applications with atomic first-claim-wins semantics. app_stasis_broadcast.so: Provides the StasisBroadcast() dialplan application which invokes the broadcast infrastructure. Both modules are self-contained; if neither is loaded there is zero runtime impact. Loading them does not alter existing Stasis or ARI behavior unless explicitly used. Key Features (only active when modules are loaded): Fisher-Yates shuffled broadcast dispatch for fair claim races Atomic claim operations using mutex + condition variable signaling Configurable broadcast timeouts Safe regex application filtering with validation to mitigate ReDoS risk Thread-safe channel variable snapshotting (channel locked during reads) Late-claim safety: broadcast context kept alive until after the Stasis session ends so concurrent claimants always receive 409 Conflict rather than 404 Not Found Memory safety via RAII_VAR, ast_json_ref/unref, and ao2 reference counting Components Added: res/res_stasis_broadcast.c: Core broadcast + claim logic apps/app_stasis_broadcast.c: StasisBroadcast() dialplan application include/asterisk/stasis_app_broadcast.h: Public API header res/ari/resource_events.c: Integrates POST /ari/events/claim endpoint rest-api/api-docs/events.json: New CallBroadcast and CallClaimed events Implementation Notes: Broadcast contexts reside in an ao2 hash container keyed by channel id. Each context holds atomic claim state, winner application name, timeout metadata, and a condition variable for waiters. Broadcast contexts are kept alive until after stasis_app_exec() returns so that concurrent claimants racing against the timeout always receive 409 Conflict. Broadcast dispatch calls stasis_app_send() directly for each matching application in shuffled order. Regex filters are validated with bounded length, group depth, quantified group count, and alternation limits to reduce pathological backtracking. Timeout calculation uses timespec arithmetic with overflow-safe millisecond remainder handling. Event JSON follows existing Stasis/ARI conventions; references are managed correctly to avoid leaks or double frees. Optional Nature / Impact: No changes to existing APIs, events, or applications when absent. Clean fallback: systems ignoring the modules behave identically to prior versions. Development was assisted by Claude (Anthropic). All generated code has been reviewed, tested, and is understood by the author. UserNote: New optional modules res_stasis_broadcast.so and app_stasis_broadcast.so enable broadcasting an incoming channel to multiple ARI applications. The first application to successfully claim (via POST /ari/events/claim) wins channel control. StasisBroadcast() dialplan application initiates broadcasts. CallBroadcast and CallClaimed events notify applications. When modules are not loaded, behavior is unchanged. DeveloperNote: New public APIs in stasis_app_broadcast.h: stasis_app_broadcast_channel(), stasis_app_claim_channel(), stasis_app_broadcast_winner(), and stasis_app_broadcast_wait(). New ARI event types (CallBroadcast, CallClaimed) added to events.json. All code is isolated; no existing ABI modified.
This commit is contained in:
131
include/asterisk/stasis_app_broadcast.h
Normal file
131
include/asterisk/stasis_app_broadcast.h
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2026, Aurora Innovation AB
|
||||
*
|
||||
* Daniel Donoghue <daniel.donoghue@aurorainnovation.com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef _ASTERISK_STASIS_APP_BROADCAST_H
|
||||
#define _ASTERISK_STASIS_APP_BROADCAST_H
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Stasis Application Broadcast API
|
||||
*
|
||||
* \author Daniel Donoghue <daniel.donoghue@aurorainnovation.com>
|
||||
*
|
||||
* This module provides the infrastructure for broadcasting incoming channels
|
||||
* to multiple ARI applications and handling first-claim winner logic.
|
||||
*/
|
||||
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/optional_api.h"
|
||||
|
||||
/*! \brief Suppress CallClaimed event for this broadcast */
|
||||
#define STASIS_BROADCAST_FLAG_SUPPRESS_CLAIMED (1 << 0)
|
||||
|
||||
/*!
|
||||
* \brief Start a broadcast for a channel
|
||||
*
|
||||
* \since 20
|
||||
*
|
||||
* Broadcasts a channel to all ARI applications (or filtered applications)
|
||||
* allowing them to claim the channel. Only the first claim will succeed.
|
||||
*
|
||||
* When a channel is claimed, a CallClaimed event is sent only to applications
|
||||
* that matched the \a app_filter (or all apps if no filter was set). This can
|
||||
* be suppressed entirely with #STASIS_BROADCAST_FLAG_SUPPRESS_CLAIMED.
|
||||
*
|
||||
* \param chan The channel to broadcast
|
||||
* \param timeout_ms Timeout in milliseconds to wait for a claim
|
||||
* \param app_filter Optional regex filter for application names (NULL for all)
|
||||
* \param flags Combination of STASIS_BROADCAST_FLAG_* values
|
||||
*
|
||||
* \retval 0 on success
|
||||
* \retval -1 on error
|
||||
* \retval AST_OPTIONAL_API_UNAVAILABLE if res_stasis_broadcast is not loaded
|
||||
*/
|
||||
AST_OPTIONAL_API(int, stasis_app_broadcast_channel,
|
||||
(struct ast_channel *chan, int timeout_ms, const char *app_filter,
|
||||
unsigned int flags),
|
||||
{ return AST_OPTIONAL_API_UNAVAILABLE; });
|
||||
|
||||
/*!
|
||||
* \brief Attempt to claim a broadcast channel
|
||||
*
|
||||
* \since 20
|
||||
*
|
||||
* Atomically attempts to claim a channel that is in broadcast state.
|
||||
* Only the first claim for a given channel will succeed.
|
||||
*
|
||||
* \param channel_id The unique ID of the channel
|
||||
* \param app_name The name of the application claiming the channel
|
||||
*
|
||||
* \retval 0 if claim successful
|
||||
* \retval -1 if channel not found
|
||||
* \retval -2 if already claimed by another application
|
||||
* \retval AST_OPTIONAL_API_UNAVAILABLE if res_stasis_broadcast is not loaded
|
||||
*/
|
||||
AST_OPTIONAL_API(int, stasis_app_claim_channel,
|
||||
(const char *channel_id, const char *app_name),
|
||||
{ return AST_OPTIONAL_API_UNAVAILABLE; });
|
||||
|
||||
/*!
|
||||
* \brief Get the winner app name for a broadcast channel
|
||||
*
|
||||
* \since 20
|
||||
*
|
||||
* \param channel_id The unique ID of the channel
|
||||
*
|
||||
* \return A copy of the winner app name (caller must free with ast_free),
|
||||
* or NULL if not claimed or not found
|
||||
* \retval NULL if res_stasis_broadcast is not loaded
|
||||
*/
|
||||
AST_OPTIONAL_API(char *, stasis_app_broadcast_winner,
|
||||
(const char *channel_id),
|
||||
{ return NULL; });
|
||||
|
||||
/*!
|
||||
* \brief Wait for a broadcast channel to be claimed
|
||||
*
|
||||
* \since 20
|
||||
*
|
||||
* Blocks until the channel is claimed or the timeout expires.
|
||||
*
|
||||
* \param chan The channel
|
||||
* \param timeout_ms Maximum time to wait in milliseconds
|
||||
*
|
||||
* \retval 0 if claimed within timeout
|
||||
* \retval -1 if timeout expired or error
|
||||
* \retval AST_OPTIONAL_API_UNAVAILABLE if res_stasis_broadcast is not loaded
|
||||
*/
|
||||
AST_OPTIONAL_API(int, stasis_app_broadcast_wait,
|
||||
(struct ast_channel *chan, int timeout_ms),
|
||||
{ return AST_OPTIONAL_API_UNAVAILABLE; });
|
||||
|
||||
/*!
|
||||
* \brief Clean up broadcast context for a channel
|
||||
*
|
||||
* \since 20
|
||||
*
|
||||
* Removes the broadcast context when the channel is done or leaving the
|
||||
* broadcast state.
|
||||
*
|
||||
* \param channel_id The unique ID of the channel
|
||||
*/
|
||||
AST_OPTIONAL_API(void, stasis_app_broadcast_cleanup,
|
||||
(const char *channel_id),
|
||||
{ return; });
|
||||
|
||||
#endif /* _ASTERISK_STASIS_APP_BROADCAST_H */
|
||||
Reference in New Issue
Block a user