Asterisk - The Open Source Telephony Project GIT-master-6144b6b
Loading...
Searching...
No Matches
extension_state_legacy.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2026, Sangoma Technologies Corporation
5 *
6 * Joshua Colp <jcolp@sangoma.com>
7 *
8 * See https://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/*** MODULEINFO
20 <support_level>core</support_level>
21 ***/
22
23#include "asterisk.h"
24#include "asterisk/_private.h"
25#include "asterisk/module.h"
27#include "asterisk/pbx.h"
28#include "asterisk/stasis.h"
30#include "asterisk/astobj2.h"
31#include "asterisk/lock.h"
32#include "asterisk/vector.h"
33
35 /*! Watcher ID returned when registered. */
36 int id;
37 /*! Message router for the traffic to this callback */
39 /*! Arbitrary data passed for callbacks. */
40 void *data;
41 /*! Flag if this callback is an extended callback containing detailed device status */
43 /*! Callback when state changes. */
45 /*! Callback when destroyed so any resources given by the registerer can be freed. */
47};
48
49/*! \brief Lock to protect the callbacks vector */
51
52/*! \brief Legacy callbacks, the index of it in the vector is the id given to the API user for per-extension */
54
55int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
56{
57 struct ast_extension_state_device_snapshot *device_snapshot;
58 enum ast_extension_states device_state;
59
60 device_snapshot = ast_extension_state_get_latest_device_snapshot(c, exten, context);
61 if (!device_snapshot) {
62 return -1;
63 }
64
65 device_state = device_snapshot->state;
66 ao2_ref(device_snapshot, -1);
67
68 return device_state;
69}
70
71int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
72{
73 struct ast_extension_state_presence_snapshot *presence_snapshot;
75
76 presence_snapshot = ast_extension_state_get_latest_presence_snapshot(c, exten, context);
77 if (!presence_snapshot) {
78 return -1;
79 }
80
81 presence_state = presence_snapshot->presence_state;
82 if (presence_snapshot->presence_subtype) {
83 *subtype = ast_strdup(presence_snapshot->presence_subtype);
84 }
85 if (presence_snapshot->presence_message) {
86 *message = ast_strdup(presence_snapshot->presence_message);
87 }
88
89 ao2_ref(presence_snapshot, -1);
90
91 return presence_state;
92}
93
94/*!
95 * \internal
96 * \brief Destroy function for device state info objects.
97 *
98 * \param obj The device state info object to destroy.
99 */
100static void device_state_info_destroy(void *obj)
101{
102 struct ast_device_state_info *info = obj;
103
104 ao2_cleanup(info->causing_channel);
105}
106
107/*!
108 * \internal
109 * \brief Create a container of device state info objects from an extension device state message.
110 *
111 * \param device_state_message The extension device state message to create device state info from.
112 * \return A container of device state info objects, or NULL on failure.
113 */
115{
117 int i;
118
119 if (!device_state_info) {
120 return NULL;
121 }
122
123 for (i = 0; i < AST_VECTOR_SIZE(&device_snapshot->additional_devices); i++) {
124 struct ast_extension_state_device_state_info *source_info = AST_VECTOR_GET(&device_snapshot->additional_devices, i);
125 struct ast_device_state_info *obj;
126
127 obj = ao2_alloc_options(sizeof(*obj) + strlen(source_info->device) + 1, device_state_info_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
128 if (!obj) {
129 ao2_ref(device_state_info, -1);
130 return NULL;
131 }
132
133 obj->device_state = source_info->state;
134 strcpy(obj->device_name, source_info->device); /* Safe */
136 ao2_link(device_state_info, obj);
137 ao2_ref(obj, -1);
138 }
139
140 return device_state_info;
141}
142
143int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
144 struct ao2_container **device_state_info)
145{
146 struct ast_extension_state_device_snapshot *device_snapshot;
147 enum ast_extension_states device_state;
148
149 device_snapshot = ast_extension_state_get_latest_device_snapshot(c, exten, context);
150 if (!device_snapshot) {
151 return -1;
152 }
153
154 device_state = device_snapshot->state;
155
156 /* The caller wants device state info, so allocate a container and populate it */
157 if (device_state_info) {
158 *device_state_info = extension_state_legacy_create_device_state_info(device_snapshot);
159 }
160
161 ao2_ref(device_snapshot, -1);
162
163 return device_state;
164}
165
166/*!
167 * \internal
168 * \brief Callback for subscription state change, used for reference handling.
169 *
170 * \param userdata The user data passed to the subscription.
171 * \param sub The subscription that received the message.
172 * \param msg The message received by the subscription.
173 */
175 struct stasis_message *msg)
176{
178 ao2_cleanup(userdata);
179 }
180}
181
182/*!
183 * \internal
184 * \brief Destructor for \ref extension_state_legacy_state_cb.
185 *
186 * \param obj The extension state legacy state callback object to destroy.
187 */
189{
190 struct extension_state_legacy_state_cb *cb = obj;
191
192 if (cb->destroy_cb) {
193 cb->destroy_cb(cb->id, cb->data);
194 }
195}
196
197/*!
198 * \internal
199 * \brief Callback for extension state updates.
200 *
201 * \param userdata The user data passed to the subscription.
202 * \param sub The subscription that received the message.
203 * \param msg The message received by the subscription.
204 */
205static void extension_state_legacy_update_cb(void *userdata, struct stasis_subscription *sub,
206 struct stasis_message *msg)
207{
208 struct extension_state_legacy_state_cb *cb = userdata;
209 struct ast_extension_state_update_message *update_message = stasis_message_data(msg);
210 struct ast_state_cb_info info = {
211 .exten_state = update_message->new_device_snapshot->state,
212 .presence_state = update_message->new_presence_snapshot->presence_state,
213 .presence_subtype = S_OR(update_message->new_presence_snapshot->presence_subtype, ""),
214 .presence_message = S_OR(update_message->new_presence_snapshot->presence_message, ""),
215 };
216
217 /* If the presence has changed, notify the callback */
218 if (update_message->new_presence_snapshot != update_message->old_presence_snapshot) {
219 info.reason = AST_HINT_UPDATE_PRESENCE;
220 cb->change_cb(update_message->context, update_message->extension, &info, cb->data);
221 }
222
223 /* If the device state has changed, notify the callback */
224 if (update_message->new_device_snapshot != update_message->old_device_snapshot) {
225 info.reason = AST_HINT_UPDATE_DEVICE;
226
227 /* If they want extended information we need to provide the channels */
228 if (cb->extended) {
229 info.device_state_info = extension_state_legacy_create_device_state_info(update_message->new_device_snapshot);
230 }
231
232 cb->change_cb(update_message->context, update_message->extension, &info, cb->data);
233
234 ao2_cleanup(info.device_state_info);
235 }
236}
237
238/*!
239 * \internal
240 * \brief Callback for extension state removal updates.
241 *
242 * \param userdata The user data passed to the subscription.
243 * \param sub The subscription that received the message.
244 * \param msg The message received by the subscription.
245 */
246static void extension_state_legacy_remove_cb(void *userdata, struct stasis_subscription *sub,
247 struct stasis_message *msg)
248{
249 struct extension_state_legacy_state_cb *cb = userdata;
250 struct ast_extension_state_remove_message *remove_message = stasis_message_data(msg);
251 struct ast_state_cb_info info = {
252 .reason = AST_HINT_UPDATE_DEVICE,
253 .exten_state = AST_EXTENSION_REMOVED,
254 };
255
256 cb->change_cb(remove_message->context, remove_message->extension, &info, cb->data);
257}
258
259/*!
260 * \internal
261 * \brief Add a legacy extension state callback.
262 *
263 * \param context The context to monitor.
264 * \param exten The extension to monitor.
265 * \param change_cb The callback to call when the extension state changes.
266 * \param destroy_cb The callback to call when the callback is removed.
267 * \param data The data to pass to the callback.
268 * \param extended Whether to include extended information in the callback.
269 * \return The ID of the callback, or -1 on failure.
270 */
271static int extension_state_legacy_add_destroy(const char *context, const char *exten,
272 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
273{
274 struct extension_state_legacy_state_cb *state_cb;
275 int id;
276
278 if (!state_cb) {
279 return -1;
280 }
281
282 state_cb->change_cb = change_cb;
283 state_cb->destroy_cb = destroy_cb;
284 state_cb->data = data;
285 state_cb->extended = extended;
286
288
289 /*
290 * Callbacks for both per-extension and all are stored in a single vector which may have gaps in it.
291 * When adding a new callback, we look for the first gap in the vector and insert the callback there.
292 * If there are no gaps, we append it to the end of the vector.
293 * For per-extension the ID of a callback is its index in the vector + 1, since 0 is reserved for "all" callbacks.
294 */
295 for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) {
297 continue;
298 }
299
300 state_cb->id = id + 1;
301
302 /* This can't fail since the vector would have already resized */
304
305 break;
306 }
307
308 if (!state_cb->id) {
309 /* The vector will resize when we append, which can fail, so handle it */
312 ao2_ref(state_cb, -1);
313 return -1;
314 }
316 }
317
318 /* At this point it is guaranteed that the callback has been inserted so we can setup
319 * the message router to accept messages from the appropriate topic and translate into
320 * the legacy callback.
321 */
322 if (!context && !exten) {
323 /*
324 * The all topic will receive all extension state updates which can end up being quite
325 * a lot, so we use a dedicated thread for each legacy callback to ensure that the
326 * pool of stasis threads does not become overloaded.
327 */
329 } else {
330 struct stasis_topic *topic = ast_extension_state_topic(exten, context);
331
332 /*
333 * Per-extension on the other hand will have comparatively few extension state updates
334 * so we use the pool for it instead. Additionally the creation of the message router will
335 * fail if topic is NULL, so we don't do an explicit check and just let it try.
336 */
337 state_cb->router = stasis_message_router_create_pool(topic);
338 ao2_cleanup(topic);
339 }
340
341 /* If there is no message router allocated this callback is useless, so bail */
342 if (!state_cb->router) {
345 ao2_ref(state_cb, -1);
346 return -1;
347 }
348
349 /*
350 * Each of the message router callbacks translates the extension state messages into
351 * the legacy callback format and then calls the legacy callback with the appropriate data.
352 */
359
361
362 /*
363 * We don't hold a reference directly but the vector does and since we haven't given the ID back
364 * there's no way for the caller to remove it, thus it has to be valid even now.
365 */
366 return (!context && !exten) ? 0 : state_cb->id;
367}
368
369int ast_extension_state_add_destroy(const char *context, const char *exten,
370 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
371{
372 return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
373}
374
375int ast_extension_state_add(const char *context, const char *exten,
376 ast_state_cb_type change_cb, void *data)
377{
378 return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 0);
379}
380
381int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
382 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
383{
384 return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
385}
386
387int ast_extension_state_add_extended(const char *context, const char *exten,
388 ast_state_cb_type change_cb, void *data)
389{
390 return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 1);
391}
392
394{
396
397 /* A negative id is considered invalid */
398 if (id < 0) {
399 return -1;
400 }
401
402 if (!id) { /* id == 0 is a callback without extension */
403 if (!change_cb) {
404 return -1;
405 }
406
407 /*
408 * Global callbacks all have the ID of 0 so we need to find the actual index
409 * for them in the vector for removal based on callback.
410 */
412 for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) {
414 if (cb && cb->change_cb == change_cb) {
418 ao2_ref(cb, -1);
419 return 0;
420 }
421 }
423
424 return -1;
425 }
426
430 if (cb) {
434 ao2_ref(cb, -1);
435 return 0;
436 }
437 }
439
440 return -1;
441}
442
443/*!
444 * \internal
445 * \brief Clean up the legacy extension state system, called at shutdown.
446 *
447 * This function unregisters all legacy extension state callbacks and cleans up
448 * the associated resources.
449 */
469
471{
472 /* Since we're not pre-allocating for any callbacks this can't fail */
475
476 return 0;
477}
Prototypes for public functions only of internal interest,.
enum queue_result id
Definition app_queue.c:1790
Asterisk main include file. File version handling, generic pbx functions.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition clicompat.c:19
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ao2_link(container, obj)
Add an object to a container.
Definition astobj2.h:1532
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition astobj2.h:367
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition astobj2.h:404
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
#define ao2_container_alloc_list(ao2_options, container_options, sort_fn, cmp_fn)
Allocate and initialize a list container.
Definition astobj2.h:1327
struct stasis_message_type * ast_extension_state_update_message_type(void)
Get extension state update message type.
struct ast_extension_state_device_snapshot * ast_extension_state_get_latest_device_snapshot(struct ast_channel *chan, const char *exten, const char *context)
Get the latest device state message for an extension.
struct ast_extension_state_presence_snapshot * ast_extension_state_get_latest_presence_snapshot(struct ast_channel *chan, const char *exten, const char *context)
Get the latest presence state message for an extension.
struct stasis_topic * ast_extension_state_topic(const char *exten, const char *context)
Get the Stasis topic to receive extension state messages for a specific extension.
struct stasis_topic * ast_extension_state_topic_all(void)
Get the Stasis topic to receive all extension state messages.
struct stasis_message_type * ast_extension_state_remove_message_type(void)
Get extension state remove message type.
struct ast_channel * ast_extension_state_get_device_causing_channel(const char *device, enum ast_device_state device_state)
Get the channel that is causing the device to be in the given state, if any.
int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
Uses hint and presence state callback to get the presence state of an extension.
int ast_extension_state_add_destroy_extended(const char *context, const char *exten, ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
Add watcher for extended extension states with destructor.
int ast_extension_state_add_destroy(const char *context, const char *exten, ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
Add watcher for extension states with destructor.
static struct @377 extension_state_legacy_callbacks
Legacy callbacks, the index of it in the vector is the id given to the API user for per-extension.
int ast_extension_state_legacy_init(void)
static void extension_state_legacy_update_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
static struct ao2_container * extension_state_legacy_create_device_state_info(struct ast_extension_state_device_snapshot *device_snapshot)
static int extension_state_legacy_add_destroy(const char *context, const char *exten, ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
int ast_extension_state_add_extended(const char *context, const char *exten, ast_state_cb_type change_cb, void *data)
Add watcher for extended extension states.
int ast_extension_state_del(int id, ast_state_cb_type change_cb)
Deletes a state change watcher by ID.
int ast_extension_state_add(const char *context, const char *exten, ast_state_cb_type change_cb, void *data)
Add watcher for extension states.
static void extension_state_legacy_remove_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
static void extension_state_legacy_subscription_change_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
static ast_mutex_t extension_state_legacy_callbacks_lock
Lock to protect the callbacks vector.
static void extension_state_legacy_cleanup(void)
int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
Uses hint and devicestate callback to get the state of an extension.
int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten, struct ao2_container **device_state_info)
Uses hint and devicestate callback to get the extended state of an extension.
static void device_state_info_destroy(void *obj)
static void extension_state_legacy_state_cb_destroy(void *obj)
struct stasis_message_type * stasis_subscription_change_type(void)
Gets the message type for subscription change notices.
Asterisk locking-related definitions:
#define ast_mutex_unlock(a)
Definition lock.h:197
#define ast_mutex_lock(a)
Definition lock.h:196
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition lock.h:527
Asterisk module definitions.
Core PBX routines and definitions.
int(* ast_state_cb_type)(const char *context, const char *exten, struct ast_state_cb_info *info, void *data)
Typedef for devicestate and hint callbacks.
Definition pbx.h:112
ast_extension_states
Extension states.
Definition pbx.h:61
@ AST_EXTENSION_REMOVED
Definition pbx.h:62
@ AST_HINT_UPDATE_DEVICE
Definition pbx.h:91
@ AST_HINT_UPDATE_PRESENCE
Definition pbx.h:93
void(* ast_state_cb_destroy_type)(int id, void *data)
Typedef for devicestate and hint callback removal indication callback.
Definition pbx.h:115
ast_presence_state
static struct stasis_subscription * sub
Statsd channel stats. Exmaple of how to subscribe to Stasis events.
#define NULL
Definition resample.c:96
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
int stasis_subscription_final_message(struct stasis_subscription *sub, struct stasis_message *msg)
Determine whether a message is the final message to be received on a subscription.
Definition stasis.c:1252
#define stasis_message_router_create(topic)
Create a new message router object.
void stasis_message_router_unsubscribe(struct stasis_message_router *router)
Unsubscribe the router from the upstream topic.
int stasis_message_router_add(struct stasis_message_router *router, struct stasis_message_type *message_type, stasis_subscription_cb callback, void *data)
Add a route to a message router.
void stasis_message_router_unsubscribe_and_join(struct stasis_message_router *router)
Unsubscribe the router from the upstream topic, blocking until the final message has been processed.
#define stasis_message_router_create_pool(topic)
Create a new message router object.
#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
Generic container type.
Main Channel structure associated with a channel.
struct ast_channel * causing_channel
Definition pbx.h:98
char device_name[1]
Definition pbx.h:99
enum ast_device_state device_state
Definition pbx.h:97
Device snapshot for an extension state.
struct ast_extension_state_device_snapshot::@232 additional_devices
Vector of additional device states that contributed to update.
enum ast_extension_states state
The state of the extension.
Individual device states that contributed to snapshot.
enum ast_device_state state
The state of the device.
char device[0]
The name of the device.
Presence snapshot for an extension state.
char * presence_subtype
The subtype of the presence state.
char * presence_message
An optional message for the presence.
enum ast_presence_state presence_state
The presence state of the extension.
Stasis message for extension state removal message.
char * context
The dialplan context.
char extension[0]
The dialplan extension.
Stasis message for extension state update message.
char * context
The dialplan context.
struct ast_extension_state_presence_snapshot * old_presence_snapshot
The old presence snapshot.
char extension[0]
The dialplan extension.
struct ast_extension_state_device_snapshot * old_device_snapshot
The old device snapshot.
struct ast_extension_state_presence_snapshot * new_presence_snapshot
The new presence snapshot - will be pointer equivalent to old if unchanged.
struct ast_extension_state_device_snapshot * new_device_snapshot
The new device snapshot - will be pointer equivalent to old if unchanged.
struct stasis_message_router * router
ast_state_cb_destroy_type destroy_cb
static struct test_val c
Vector container support.
#define AST_VECTOR_REPLACE(vec, idx, elem)
Replace an element at a specific position in a vector, growing the vector if needed.
Definition vector.h:295
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:620
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition vector.h:185
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition vector.h:124
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition vector.h:267
#define AST_VECTOR(name, type)
Define a vector structure.
Definition vector.h:44
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition vector.h:691