Asterisk - The Open Source Telephony Project GIT-master-57e2743
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 Add a device state info object to a container.
110 *
111 * \param device_state_info The container to add the device state info to.
112 * \param device_state The device state info to add.
113 * \retval 0 on success
114 * \retval -1 on failure
115 */
118{
119 struct ast_device_state_info *obj;
120
121 obj = ao2_alloc_options(sizeof(*obj) + strlen(device_state->device) + 1, device_state_info_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
122 if (!obj) {
123 return -1;
124 }
125
126 obj->device_state = device_state->state;
127 strcpy(obj->device_name, device_state->device); /* Safe */
129 ao2_link(device_state_info, obj);
130 ao2_ref(obj, -1);
131
132 return 0;
133}
134
135/*!
136 * \internal
137 * \brief Create a container of device state info objects from an extension device state message.
138 *
139 * \param device_state_message The extension device state message to create device state info from.
140 * \return A container of device state info objects, or NULL on failure.
141 */
143{
145 int i;
146
147 if (!device_state_info) {
148 return NULL;
149 }
150
151 /*
152 * The causing device is not part of additional_devices, but the legacy API
153 * expects the device state info to contain every contributing device.
154 */
155 if (device_snapshot->causing_device &&
156 extension_state_legacy_add_device_state_info(device_state_info, device_snapshot->causing_device)) {
157 ao2_ref(device_state_info, -1);
158 return NULL;
159 }
160
161 for (i = 0; i < AST_VECTOR_SIZE(&device_snapshot->additional_devices); i++) {
162 struct ast_extension_state_device_state_info *source_info = AST_VECTOR_GET(&device_snapshot->additional_devices, i);
163
164 if (extension_state_legacy_add_device_state_info(device_state_info, source_info)) {
165 ao2_ref(device_state_info, -1);
166 return NULL;
167 }
168 }
169
170 return device_state_info;
171}
172
173int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
174 struct ao2_container **device_state_info)
175{
176 struct ast_extension_state_device_snapshot *device_snapshot;
177 enum ast_extension_states device_state;
178
179 device_snapshot = ast_extension_state_get_latest_device_snapshot(c, exten, context);
180 if (!device_snapshot) {
181 return -1;
182 }
183
184 device_state = device_snapshot->state;
185
186 /* The caller wants device state info, so allocate a container and populate it */
187 if (device_state_info) {
188 *device_state_info = extension_state_legacy_create_device_state_info(device_snapshot);
189 }
190
191 ao2_ref(device_snapshot, -1);
192
193 return device_state;
194}
195
196/*!
197 * \internal
198 * \brief Callback for subscription state change, used for reference handling.
199 *
200 * \param userdata The user data passed to the subscription.
201 * \param sub The subscription that received the message.
202 * \param msg The message received by the subscription.
203 */
205 struct stasis_message *msg)
206{
208 ao2_cleanup(userdata);
209 }
210}
211
212/*!
213 * \internal
214 * \brief Destructor for \ref extension_state_legacy_state_cb.
215 *
216 * \param obj The extension state legacy state callback object to destroy.
217 */
219{
220 struct extension_state_legacy_state_cb *cb = obj;
221
222 if (cb->destroy_cb) {
223 cb->destroy_cb(cb->id, cb->data);
224 }
225}
226
227/*!
228 * \internal
229 * \brief Callback for extension state updates.
230 *
231 * \param userdata The user data passed to the subscription.
232 * \param sub The subscription that received the message.
233 * \param msg The message received by the subscription.
234 */
235static void extension_state_legacy_update_cb(void *userdata, struct stasis_subscription *sub,
236 struct stasis_message *msg)
237{
238 struct extension_state_legacy_state_cb *cb = userdata;
239 struct ast_extension_state_update_message *update_message = stasis_message_data(msg);
240 struct ast_state_cb_info info = {
241 .exten_state = update_message->new_device_snapshot->state,
242 .presence_state = update_message->new_presence_snapshot->presence_state,
243 .presence_subtype = S_OR(update_message->new_presence_snapshot->presence_subtype, ""),
244 .presence_message = S_OR(update_message->new_presence_snapshot->presence_message, ""),
245 };
246
247 /* If the presence has changed, notify the callback */
248 if (update_message->new_presence_snapshot != update_message->old_presence_snapshot) {
249 info.reason = AST_HINT_UPDATE_PRESENCE;
250 cb->change_cb(update_message->context, update_message->extension, &info, cb->data);
251 }
252
253 /* If the device state has changed, notify the callback */
254 if (update_message->new_device_snapshot != update_message->old_device_snapshot) {
255 info.reason = AST_HINT_UPDATE_DEVICE;
256
257 /* If they want extended information we need to provide the channels */
258 if (cb->extended) {
259 info.device_state_info = extension_state_legacy_create_device_state_info(update_message->new_device_snapshot);
260 }
261
262 cb->change_cb(update_message->context, update_message->extension, &info, cb->data);
263
264 ao2_cleanup(info.device_state_info);
265 }
266}
267
268/*!
269 * \internal
270 * \brief Callback for extension state removal updates.
271 *
272 * \param userdata The user data passed to the subscription.
273 * \param sub The subscription that received the message.
274 * \param msg The message received by the subscription.
275 */
276static void extension_state_legacy_remove_cb(void *userdata, struct stasis_subscription *sub,
277 struct stasis_message *msg)
278{
279 struct extension_state_legacy_state_cb *cb = userdata;
280 struct ast_extension_state_remove_message *remove_message = stasis_message_data(msg);
281 struct ast_state_cb_info info = {
282 .reason = AST_HINT_UPDATE_DEVICE,
283 .exten_state = AST_EXTENSION_REMOVED,
284 };
285
286 cb->change_cb(remove_message->context, remove_message->extension, &info, cb->data);
287}
288
289/*!
290 * \internal
291 * \brief Add a legacy extension state callback.
292 *
293 * \param context The context to monitor.
294 * \param exten The extension to monitor.
295 * \param change_cb The callback to call when the extension state changes.
296 * \param destroy_cb The callback to call when the callback is removed.
297 * \param data The data to pass to the callback.
298 * \param extended Whether to include extended information in the callback.
299 * \return The ID of the callback, or -1 on failure.
300 */
301static int extension_state_legacy_add_destroy(const char *context, const char *exten,
302 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
303{
304 struct extension_state_legacy_state_cb *state_cb;
305 int id;
306
308 if (!state_cb) {
309 return -1;
310 }
311
312 state_cb->change_cb = change_cb;
313 state_cb->destroy_cb = destroy_cb;
314 state_cb->data = data;
315 state_cb->extended = extended;
316
318
319 /*
320 * Callbacks for both per-extension and all are stored in a single vector which may have gaps in it.
321 * When adding a new callback, we look for the first gap in the vector and insert the callback there.
322 * If there are no gaps, we append it to the end of the vector.
323 * For per-extension the ID of a callback is its index in the vector + 1, since 0 is reserved for "all" callbacks.
324 */
325 for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) {
327 continue;
328 }
329
330 state_cb->id = id + 1;
331
332 /* This can't fail since the vector would have already resized */
334
335 break;
336 }
337
338 if (!state_cb->id) {
339 /* The vector will resize when we append, which can fail, so handle it */
342 ao2_ref(state_cb, -1);
343 return -1;
344 }
346 }
347
348 /* At this point it is guaranteed that the callback has been inserted so we can setup
349 * the message router to accept messages from the appropriate topic and translate into
350 * the legacy callback.
351 */
352 if (!context && !exten) {
353 /*
354 * The all topic will receive all extension state updates which can end up being quite
355 * a lot, so we use a dedicated thread for each legacy callback to ensure that the
356 * pool of stasis threads does not become overloaded.
357 */
359 } else {
360 struct stasis_topic *topic = ast_extension_state_topic(exten, context);
361
362 /*
363 * Per-extension on the other hand will have comparatively few extension state updates
364 * so we use the pool for it instead. Additionally the creation of the message router will
365 * fail if topic is NULL, so we don't do an explicit check and just let it try.
366 */
367 state_cb->router = stasis_message_router_create_pool(topic);
368 ao2_cleanup(topic);
369 }
370
371 /* If there is no message router allocated this callback is useless, so bail */
372 if (!state_cb->router) {
375 ao2_ref(state_cb, -1);
376 return -1;
377 }
378
379 /*
380 * Each of the message router callbacks translates the extension state messages into
381 * the legacy callback format and then calls the legacy callback with the appropriate data.
382 */
389
391
392 /*
393 * We don't hold a reference directly but the vector does and since we haven't given the ID back
394 * there's no way for the caller to remove it, thus it has to be valid even now.
395 */
396 return (!context && !exten) ? 0 : state_cb->id;
397}
398
399int ast_extension_state_add_destroy(const char *context, const char *exten,
400 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
401{
402 return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
403}
404
405int ast_extension_state_add(const char *context, const char *exten,
406 ast_state_cb_type change_cb, void *data)
407{
408 return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 0);
409}
410
411int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
412 ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
413{
414 return extension_state_legacy_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
415}
416
417int ast_extension_state_add_extended(const char *context, const char *exten,
418 ast_state_cb_type change_cb, void *data)
419{
420 return extension_state_legacy_add_destroy(context, exten, change_cb, NULL, data, 1);
421}
422
424{
426
427 /* A negative id is considered invalid */
428 if (id < 0) {
429 return -1;
430 }
431
432 if (!id) { /* id == 0 is a callback without extension */
433 if (!change_cb) {
434 return -1;
435 }
436
437 /*
438 * Global callbacks all have the ID of 0 so we need to find the actual index
439 * for them in the vector for removal based on callback.
440 */
442 for (id = 0; id < AST_VECTOR_SIZE(&extension_state_legacy_callbacks); id++) {
444 if (cb && cb->change_cb == change_cb) {
448 ao2_ref(cb, -1);
449 return 0;
450 }
451 }
453
454 return -1;
455 }
456
460 if (cb) {
464 ao2_ref(cb, -1);
465 return 0;
466 }
467 }
469
470 return -1;
471}
472
473/*!
474 * \internal
475 * \brief Clean up the legacy extension state system, called at shutdown.
476 *
477 * This function unregisters all legacy extension state callbacks and cleans up
478 * the associated resources.
479 */
499
501{
502 /* Since we're not pre-allocating for any callbacks this can't fail */
505
506 return 0;
507}
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.
static int extension_state_legacy_add_device_state_info(struct ao2_container *device_state_info, const struct ast_extension_state_device_state_info *device_state)
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.
struct ast_extension_state_device_state_info * causing_device
The device that caused this update.
enum ast_extension_states state
The state of the extension.
Individual device states that contributed to snapshot.
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:637
#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:708