Files
asterisk/main/extension_state.c
Joshua C. Colp f4f6ec0313 extension_state: Add new extension state API.
Extension state to this point has been an API implemented
inside the PBX core resulting in its state being intermingled
with that of the dialplan. This increased the complexity of
the PBX core and made it difficult to enact improvements.

This change adds a new separate extension state API
which receives updates from the PBX core as configuration
changes but maintains its own separate state. The API is
also written to fully take advantage of modern APIs in a
more selective manner by subscribing each extension state to
only the devices it is interested in, ultimately reducing
resource consumption during updates. Presence state updates
being infrequently done use a single shared subscription that
goes through the extension states to find and update ones
that the update is applicable to.

Legacy API support is provided by reimplementing the API
as wrappers over the new extension state API. This improves
the legacy API by making it multithreaded, with each callback
being individually subscribed.

Autohints support is maintained but has been separated out
into a self contained new implementation.

Synchronous subscription support has also been added to
Stasis to remove the overhead of asynchronous publishing when
the handling of published messages is small and fast.
2026-06-11 18:31:03 +00:00

1598 lines
53 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2026, Sangoma Technologies Corporation
*
* Joshua Colp <jcolp@sangoma.com>
*
* See https://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.
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/_private.h"
#include "asterisk/module.h"
#include "asterisk/extension_state.h"
#include "asterisk/pbx.h"
#include "asterisk/stasis.h"
#include "asterisk/stasis_message_router.h"
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "pbx_private.h"
/*** DOCUMENTATION
<manager name="ExtensionStateList" language="en_US">
<since>
<version>13.0.0</version>
</since>
<synopsis>
List the current known extension states.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
</syntax>
<description>
<para>This will list out all known extension states in a
sequence of <replaceable>ExtensionStatus</replaceable> events.
When finished, a <replaceable>ExtensionStateListComplete</replaceable> event
will be emitted.</para>
</description>
<see-also>
<ref type="manager">ExtensionState</ref>
<ref type="function">HINT</ref>
<ref type="function">EXTENSION_STATE</ref>
</see-also>
<responses>
<list-elements>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='ExtensionStatus'])" />
</list-elements>
<managerEvent name="ExtensionStateListComplete" language="en_US">
<managerEventInstance class="EVENT_FLAG_COMMAND">
<since>
<version>13.0.0</version>
</since>
<synopsis>
Indicates the end of the list the current known extension states.
</synopsis>
<syntax>
<parameter name="EventList">
<para>Conveys the status of the event list.</para>
</parameter>
<parameter name="ListItems">
<para>Conveys the number of statuses reported.</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
</responses>
</manager>
***/
#define HINTDEVICE_DATA_LENGTH 16
AST_THREADSTORAGE(hintdevice_data);
/*! \brief Device state source feeding an extension state */
struct extension_state_device_source {
/*! \brief The current state of the device - this is immutable */
struct ast_extension_state_device_state_info *info;
/*! \brief Synchronous subscription to the device state topic */
struct stasis_subscription *device_state_subscription;
/*! \brief The current version for this source */
unsigned int version;
};
AST_VECTOR(device_state_sources_vector, struct extension_state_device_source *);
/*! \brief Extension state information */
struct extension_state {
/*! \brief The current device snapshot for the extension */
struct ast_extension_state_device_snapshot *device_snapshot;
/*! \brief The current presence snapshot for the extension */
struct ast_extension_state_presence_snapshot *presence_snapshot;
/*! \brief The extension state topic for this extension */
struct stasis_topic *extension_state_topic;
/*! \brief Forwarder from per-extension topic to all topic */
struct stasis_forward *extension_state_forwarder;
/*! \brief Device state sources feeding the hint topic, and their forwarding */
struct device_state_sources_vector device_state_sources;
/*! \brief The string representation of all presence state sources feeding this extension state */
char *presence_sources_string;
/*! \brief The dialplan hint that last configured this extension state */
struct ast_exten *hint_extension;
/*! \brief The dialplan context */
char dialplan_context[AST_MAX_CONTEXT];
/*! \brief The dialplan extension */
char dialplan_extension[AST_MAX_EXTENSION];
/*! \brief The combined extension this state is for (extension@context) */
char extension[0];
};
/*! \brief Number of buckets for extension states */
#ifdef LOW_MEMORY
#define EXTENSION_STATE_BUCKETS 17
#else
#define EXTENSION_STATE_BUCKETS 563
#endif
static const struct cfextension_states {
int extension_state;
const char * const text;
} extension_state_mappings[] = {
{ AST_EXTENSION_NOT_INUSE, "Idle" },
{ AST_EXTENSION_INUSE, "InUse" },
{ AST_EXTENSION_BUSY, "Busy" },
{ AST_EXTENSION_UNAVAILABLE, "Unavailable" },
{ AST_EXTENSION_RINGING, "Ringing" },
{ AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" },
{ AST_EXTENSION_ONHOLD, "Hold" },
{ AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" }
};
/*! \brief The global container of extension states */
static struct ao2_container *extension_states;
/*! \brief Topic which receives all extension state updates */
static struct stasis_topic *extension_state_topic_all;
/*! \brief Single presence state subscription, for all extension states */
static struct stasis_subscription *presence_state_sub;
/*! \brief Sort function for extension states */
AO2_STRING_FIELD_SORT_FN(extension_state, extension)
/*! \brief Compare function for extension states */
AO2_STRING_FIELD_CMP_FN(extension_state, extension)
/*! \brief Message type for extension state updates */
STASIS_MESSAGE_TYPE_DEFN(ast_extension_state_update_message_type);
/*!
* \internal
* \brief Destroy an extension state update message
* \param obj The extension state update message to destroy
*/
static void extension_state_update_message_destroy(void *obj)
{
struct ast_extension_state_update_message *update_message = obj;
ao2_cleanup(update_message->old_device_snapshot);
ao2_cleanup(update_message->new_device_snapshot);
ao2_cleanup(update_message->old_presence_snapshot);
ao2_cleanup(update_message->new_presence_snapshot);
}
/*!
* \internal
* \brief Create an extension state update message
*
* \param context The context of the extension
* \param extension The extension
* \param old_device_snapshot The old device state snapshot
* \param new_device_snapshot The new device state snapshot
* \param old_presence_snapshot The old presence state snapshot
* \param new_presence_snapshot The new presence state snapshot
* \retval An allocated extension state update message, or NULL on failure
*
* This function creates an extension state update message for the specified context, extension,
* old device state snapshot, new device state snapshot, old presence state snapshot, and new presence state snapshot.
*/
static struct ast_extension_state_update_message *extension_state_update_message_create(const char *context,
const char *extension, struct ast_extension_state_device_snapshot *old_device_snapshot,
struct ast_extension_state_device_snapshot *new_device_snapshot, struct ast_extension_state_presence_snapshot *old_presence_snapshot,
struct ast_extension_state_presence_snapshot *new_presence_snapshot)
{
size_t context_len = strlen(context) + 1;
size_t extension_len = strlen(extension) + 1;
struct ast_extension_state_update_message *update_message;
update_message = ao2_alloc_options(sizeof(*update_message) + context_len + extension_len,
extension_state_update_message_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!update_message) {
return NULL;
}
ast_copy_string(update_message->extension, extension, extension_len); /* Safe */
update_message->context = update_message->extension + extension_len;
ast_copy_string(update_message->context, context, context_len); /* Safe */
update_message->old_device_snapshot = ao2_bump(old_device_snapshot);
update_message->new_device_snapshot = ao2_bump(new_device_snapshot);
update_message->old_presence_snapshot = ao2_bump(old_presence_snapshot);
update_message->new_presence_snapshot = ao2_bump(new_presence_snapshot);
return update_message;
}
/*!
* \internal
* \brief Destroy an extension state device source
* \param source The extension state device source to destroy
*
* This function destroys an extension state device source by unsubscribing from the device state
* topic and cleaning up the associated resources.
*/
static void extension_state_device_source_destroy(struct extension_state_device_source *source)
{
if (source->device_state_subscription) {
stasis_unsubscribe(source->device_state_subscription);
}
ao2_cleanup(source->info);
ast_free(source);
}
/*!
* \internal
* \brief Allocate an extension device state info object
*
* \param device The device name
* \param state The device state
* \retval An allocated extension device state info object, or NULL on failure
*
* This function allocates an extension device state info object with the specified device name and state.
*/
static struct ast_extension_state_device_state_info *extension_state_device_state_info_alloc(const char *device,
enum ast_device_state state)
{
struct ast_extension_state_device_state_info *info;
info = ao2_alloc_options(sizeof(*info) + strlen(device) + 1, NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!info) {
return NULL;
}
info->state = state;
strcpy(info->device, device); /* Safe */
return info;
}
/*!
* \internal
* \brief Destroy an extension state device snapshot
*
* \param obj The extension state device snapshot to destroy
*
* This function destroys an extension state device snapshot by cleaning up
* the causing device and additional devices.
*/
static void extension_state_device_snapshot_destroy(void *obj)
{
struct ast_extension_state_device_snapshot *device_snapshot = obj;
ao2_cleanup(device_snapshot->causing_device);
AST_VECTOR_CALLBACK_VOID(&device_snapshot->additional_devices, ao2_cleanup);
AST_VECTOR_FREE(&device_snapshot->additional_devices);
}
/*!
* \internal
* \brief Create an extension state device snapshot
*
* \param device_state The device state
* \param device_state_sources The device state sources
* \param causing_device The causing device
* \retval An allocated extension state device snapshot, or NULL on failure
*
* This function creates an extension state device snapshot with the device state,
* device state sources, and causing device.
*/
static struct ast_extension_state_device_snapshot *extension_state_device_snapshot_create(
enum ast_extension_states device_state, struct device_state_sources_vector *device_state_sources,
struct ast_extension_state_device_state_info *causing_device)
{
struct ast_extension_state_device_snapshot *device_snapshot;
int i;
device_snapshot = ao2_alloc_options(sizeof(*device_snapshot),
extension_state_device_snapshot_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!device_snapshot) {
return NULL;
}
device_snapshot->state = device_state;
device_snapshot->causing_device = ao2_bump(causing_device);
if (AST_VECTOR_INIT(&device_snapshot->additional_devices, AST_VECTOR_SIZE(device_state_sources))) {
ao2_ref(device_snapshot, -1);
return NULL;
}
for (i = 0; i < AST_VECTOR_SIZE(device_state_sources); i++) {
struct extension_state_device_source *source = AST_VECTOR_GET(device_state_sources, i);
if (causing_device && source->info == causing_device) {
continue;
}
AST_VECTOR_APPEND(&device_snapshot->additional_devices, ao2_bump(source->info));
}
return device_snapshot;
}
/*!
* \internal
* \brief Callback for device state changes
*
* \param userdata The extension state to update
* \param sub The subscription
* \param msg The device state message
*
* This function is called when a device state changes and updates the extension state
* accordingly by aggregating the device states and publishing the new state.
*/
static void extension_state_device_state_cb(void *userdata, struct stasis_subscription *sub,
struct stasis_message *msg)
{
struct extension_state *state = userdata;
struct ast_device_state_message *device_state;
struct ast_devstate_aggregate agg;
struct ast_extension_state_device_state_info *extension_device_state_info;
int i;
unsigned int updated = 0;
enum ast_extension_states new_device_state;
if (stasis_message_type(msg) != ast_device_state_message_type()) {
return;
}
device_state = stasis_message_data(msg);
/* We only care about the aggregate state */
if (device_state->eid) {
return;
}
ast_devstate_aggregate_init(&agg);
/*
* Alrighty, the reason that we store an extension_device_state_info is to reduce the memory allocation that
* has to occur every time we get a device state update and have to construct a new message. If the extension
* state contains only a single device source we have to do this anyway, but if there's multiple then if we
* didn't store the result we'd be creating new ones every message to put in the additional_devices vector.
*/
extension_device_state_info = extension_state_device_state_info_alloc(device_state->device, device_state->state);
if (!extension_device_state_info) {
return;
}
ao2_lock(state);
for (i = 0; i < AST_VECTOR_SIZE(&state->device_state_sources); i++) {
struct extension_state_device_source *source = AST_VECTOR_GET(&state->device_state_sources, i);
if (!strcmp(source->info->device, device_state->device)) {
ao2_replace(source->info, extension_device_state_info);
updated = 1;
}
ast_devstate_aggregate_add(&agg, source->info->state);
}
/* We don't really care about the device state info contents now, so we can drop the reference */
ao2_ref(extension_device_state_info, -1);
/*
* It's possible for a device state update to come in for a device which is no longer feeding this
* extension state if it has been updated, so only actually care about the new device state if a
* source has actually been updated.
*/
if (!updated) {
ao2_unlock(state);
return;
}
/*
* We actually update things and raise a message if the state is different, or if the state is ringing
* as that can actually just be an update that someone else is ringing the same extension.
*/
new_device_state = ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
if ((state->device_snapshot->state != new_device_state) || (new_device_state & AST_EXTENSION_RINGING)) {
struct ast_extension_state_device_snapshot *device_snapshot;
struct ast_extension_state_update_message *update_message;
struct stasis_message *message;
/* Now above you probably noticed I dropped the reference for extension_device_state_info but now I'm
* passing it in here. Don't panic - a reference exists on the device state source still and since we
* have the state locked it can't go away.
*/
device_snapshot = extension_state_device_snapshot_create(new_device_state, &state->device_state_sources,
extension_device_state_info);
if (!device_snapshot) {
ao2_unlock(state);
return;
}
update_message = extension_state_update_message_create(state->dialplan_context, state->dialplan_extension,
state->device_snapshot, device_snapshot, state->presence_snapshot, state->presence_snapshot);
/* Even if we can't publish an update message we still ensure the local cached snapshot is up to date */
ao2_replace(state->device_snapshot, device_snapshot);
ao2_ref(device_snapshot, -1);
if (!update_message) {
ao2_unlock(state);
return;
}
/* Inform any subscribers of the change to the device snapshot */
message = stasis_message_create(ast_extension_state_update_message_type(), update_message);
if (message) {
stasis_publish(state->extension_state_topic, message);
ao2_ref(message, -1);
}
ao2_ref(update_message, -1);
}
ao2_unlock(state);
}
/*!
* \internal
* \brief Allocate a device source for an extension state
*
* \param state The extension state to allocate the device source for
* \param device The device to allocate the source for
* \retval An allocated device source, or NULL on failure
*
* This function allocates a device source for an extension state by creating a device state source
* and setting up the necessary subscriptions and references.
*/
static struct extension_state_device_source *extension_state_device_source_alloc(struct extension_state *state, const char *device)
{
struct extension_state_device_source *source;
struct stasis_topic *topic;
/*
* Ensure that we have a direct device state topic for the device, note this is returned without a reference but
* is guaranteed to exist regardless.
*/
topic = ast_device_state_topic(device);
if (!topic) {
return NULL;
}
/*
* The device state source is only used within the extension state and is never
* passed around so the overhead of an ao2 object with reference counting is unnecessary.
*/
source = ast_calloc(1, sizeof(*source));
if (!source) {
return NULL;
}
source->info = extension_state_device_state_info_alloc(device, ast_device_state(device));
if (!source->info) {
extension_state_device_source_destroy(source);
return NULL;
}
/*
* We do a synchronous subscription to the device state topic, as our callback is extremely
* short lived and the added overhead of queueing to a taskprocessor for another thread to handle
* it is just not worth it.
*/
source->device_state_subscription = stasis_subscribe_synchronous(topic, extension_state_device_state_cb, state);
if (!source->device_state_subscription) {
extension_state_device_source_destroy(source);
return NULL;
}
stasis_subscription_accept_message_type(source->device_state_subscription, ast_device_state_message_type());
stasis_subscription_set_filter(source->device_state_subscription, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
return source;
}
/*!
* \internal
* \brief Destroy an extension state presence snapshot
*
* \param obj The extension state presence snapshot to destroy
*
* This function destroys an extension state presence snapshot by cleaning up
* the presence snapshot.
*/
static void extension_state_presence_snapshot_destroy(void *obj)
{
struct ast_extension_state_presence_snapshot *presence_snapshot = obj;
ast_free(presence_snapshot->presence_subtype);
ast_free(presence_snapshot->presence_message);
}
/*!
* \internal
* \brief Create an extension state presence snapshot
*
* \param presence_state The presence state
* \param presence_subtype The presence subtype (can be NULL)
* \param presence_message The presence message (can be NULL)
* \retval An allocated extension state presence snapshot, or NULL on failure
*
* This function creates an extension state presence snapshot for the specified presence state,
* presence subtype, and presence message.
*/
static struct ast_extension_state_presence_snapshot *extension_state_presence_snapshot_create(enum ast_presence_state presence_state,
const char *presence_subtype, const char *presence_message)
{
struct ast_extension_state_presence_snapshot *presence_snapshot;
presence_snapshot = ao2_alloc_options(sizeof(*presence_snapshot), extension_state_presence_snapshot_destroy,
AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!presence_snapshot) {
return NULL;
}
/* To ensure that we don't give a partial snapshot we fail creation if any allocation fails */
presence_snapshot->presence_state = presence_state;
if (presence_subtype) {
presence_snapshot->presence_subtype = ast_strdup(presence_subtype);
if (!presence_snapshot->presence_subtype) {
ao2_ref(presence_snapshot, -1);
return NULL;
}
}
if (presence_message) {
presence_snapshot->presence_message = ast_strdup(presence_message);
if (!presence_snapshot->presence_message) {
ao2_ref(presence_snapshot, -1);
return NULL;
}
}
return presence_snapshot;
}
/*!
* \brief device source non-matching version comparator for AST_VECTOR_REMOVE_CMP_UNORDERED()
*
* \param elem Element to compare against
* \param value Value to compare with the vector element.
*
* \return 0 if element does not match.
* \return Non-zero if element matches.
*/
#define DEVICE_SOURCE_ELEM_VERSION_CMP(elem, value) ((elem)->version != (value))
/*!
* \internal
* \brief Update the sources of an extension state
*
* \param state The extension state to update
* \param exten The extension to update
* \retval 0 on success, -1 on failure
*
* This function updates the sources of an extension state by parsing the app part
* of the extension and updating the device and presence state sources.
*/
static int extension_state_update_sources(struct extension_state *state, struct ast_exten *exten)
{
struct ast_str *str = ast_str_thread_get(&hintdevice_data, HINTDEVICE_DATA_LENGTH);
char *devices, *device, *presence_state_sources;
struct ast_devstate_aggregate agg;
enum ast_extension_states new_device_state;
unsigned int version;
struct ast_extension_state_device_snapshot *device_snapshot = NULL;
struct ast_extension_state_presence_snapshot *presence_snapshot = NULL;
ast_str_set(&str, 0, "%s", ast_get_extension_app(exten));
devices = ast_str_buffer(str);
ao2_lock(state);
/*
* The format of the app part of a hint is "[device[&device]],[presence[&presence]]" so
* we can just find the first occurrence of ',' in order to get to the presence sources.
*/
presence_state_sources = strchr(devices, ',');
if (presence_state_sources) {
*presence_state_sources++ = '\0';
}
ast_devstate_aggregate_init(&agg);
version = ast_random();
/* Devices are separated by '&' */
while ((device = strsep(&devices, "&"))) {
struct extension_state_device_source *source = NULL;
int i;
/* Skip any device names that are empty, as we can do nothing */
if (ast_strlen_zero(device)) {
continue;
}
for (i = 0; i < AST_VECTOR_SIZE(&state->device_state_sources); i++) {
struct extension_state_device_source *existing_source = AST_VECTOR_GET(&state->device_state_sources, i);
if (!strcmp(existing_source->info->device, device)) {
source = existing_source;
break;
}
}
if (!source) {
source = extension_state_device_source_alloc(state, device);
if (!source) {
ao2_unlock(state);
return -1;
}
AST_VECTOR_APPEND(&state->device_state_sources, source);
}
ast_devstate_aggregate_add(&agg, source->info->state);
source->version = version;
}
/* Do a pass and remove all old device sources */
AST_VECTOR_REMOVE_ALL_CMP_UNORDERED(&state->device_state_sources, version,
DEVICE_SOURCE_ELEM_VERSION_CMP, extension_state_device_source_destroy);
/* If device state sources exist it is what produces the device state, otherwise we use the default */
if (AST_VECTOR_SIZE(&state->device_state_sources)) {
new_device_state = ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
} else {
new_device_state = AST_EXTENSION_UNAVAILABLE;
}
/* If the device state has changed, create a new snapshot */
if (state->device_snapshot->state != new_device_state) {
device_snapshot = extension_state_device_snapshot_create(new_device_state, &state->device_state_sources, NULL);
}
if (!device_snapshot) {
/* If there's no new device snapshot we use the old one */
device_snapshot = state->device_snapshot;
}
/* If the presence state has changed, create a new snapshot */
if ((!state->presence_sources_string && presence_state_sources) ||
(state->presence_sources_string && strcmp(state->presence_sources_string, presence_state_sources))) {
enum ast_presence_state presence_state = AST_PRESENCE_NOT_SET;
char *presence_subtype = NULL, *presence_message = NULL;
ast_free(state->presence_sources_string);
if (!ast_strlen_zero(presence_state_sources)) {
state->presence_sources_string = ast_strdup(presence_state_sources);
/* Presence state is also separated by & but only the presence state API can handle it and aggregate */
presence_state = ast_presence_state(presence_state_sources, &presence_subtype,
&presence_message);
} else {
state->presence_sources_string = NULL;
}
presence_snapshot = extension_state_presence_snapshot_create(presence_state, presence_subtype, presence_message);
ast_free(presence_subtype);
ast_free(presence_message);
}
if (!presence_snapshot) {
/* If there's no new presence snapshot we use the old one */
presence_snapshot = state->presence_snapshot;
}
/* If any snapshots have changed create an update message containing them */
if (state->device_snapshot != device_snapshot || state->presence_snapshot != presence_snapshot) {
struct ast_extension_state_update_message *update_message;
update_message = extension_state_update_message_create(state->dialplan_context, state->dialplan_extension, state->device_snapshot,
device_snapshot, state->presence_snapshot, presence_snapshot);
if (update_message) {
struct stasis_message *message = stasis_message_create(ast_extension_state_update_message_type(), update_message);
if (message) {
stasis_publish(state->extension_state_topic, message);
ao2_ref(message, -1);
}
ao2_ref(update_message, -1);
}
/* If applicable, update the snapshots on the state to their new version */
if (state->device_snapshot != device_snapshot) {
ao2_replace(state->device_snapshot, device_snapshot);
ao2_ref(device_snapshot, -1);
}
if (state->presence_snapshot != presence_snapshot) {
ao2_replace(state->presence_snapshot, presence_snapshot);
ao2_ref(presence_snapshot, -1);
}
}
ao2_unlock(state);
return 0;
}
/*!
* \internal
* \brief Destroy an extension state
*
* \param obj The extension state to destroy
*
* This function destroys an extension state by cleaning up its resources.
*/
static void extension_state_destroy(void *obj)
{
struct extension_state *state = obj;
ao2_cleanup(state->device_snapshot);
ao2_cleanup(state->presence_snapshot);
ao2_cleanup(state->extension_state_topic);
ast_free(state->presence_sources_string);
}
/*! \brief Stasis message type for extension state remove messages */
STASIS_MESSAGE_TYPE_DEFN(ast_extension_state_remove_message_type);
/*!
* \internal
* \brief Create an extension state remove message
*
* \param context The context of the extension
* \param extension The extension to remove
* \retval A stasis message for the remove event, or NULL on failure
*
* This function creates an extension state remove message for the specified context and extension.
*/
static struct stasis_message *extension_state_remove_message_create(const char *context, const char *extension)
{
size_t context_len = strlen(context) + 1;
size_t extension_len = strlen(extension) + 1;
struct ast_extension_state_remove_message *remove_message;
struct stasis_message *message;
remove_message = ao2_alloc_options(sizeof(*remove_message) + context_len + extension_len, NULL,
AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!remove_message) {
return NULL;
}
ast_copy_string(remove_message->extension, extension, extension_len); /* Safe */
remove_message->context = remove_message->extension + extension_len;
ast_copy_string(remove_message->context, context, context_len); /* Safe */
message = stasis_message_create(ast_extension_state_remove_message_type(), remove_message);
ao2_ref(remove_message, -1);
return message;
}
/*!
* \internal
* \brief Shut down an extension state
*
* \param state The extension state to shut down
*
* This function shuts down an extension state by publishing a remove message to its topic
* and cleaning up its resources.
*/
static void extension_state_shutdown(struct extension_state *state)
{
struct stasis_message *remove_message;
/*
* Shutting down an extension state requires us to publish to its topic so all subscribers
* know that it is going away. However, if the topic failed to be created then we have nothing
* to publish to and can just return.
*/
if (!state->extension_state_topic) {
return;
}
/* Inform all subscribers that this extension state is being removed */
remove_message = extension_state_remove_message_create(state->dialplan_context,
state->dialplan_extension);
if (remove_message) {
stasis_publish(state->extension_state_topic, remove_message);
ao2_ref(remove_message, -1);
}
AST_VECTOR_CALLBACK_VOID(&state->device_state_sources, extension_state_device_source_destroy);
AST_VECTOR_FREE(&state->device_state_sources);
stasis_forward_cancel(state->extension_state_forwarder);
}
/*!
* \internal
* \brief Callback for presence state messages
*
* \param unused Unused parameter
* \param sub The stasis subscription
* \param msg The stasis message
*
* This callback is invoked when a presence state message is received. It updates
* the presence state of all extension states that are interested in the presence
* state provider and publishes an extension state update message if it has changed.
*/
static void extension_state_presence_state_cb(void *unused, struct stasis_subscription *sub,
struct stasis_message *msg)
{
struct ast_presence_state_message *presence_state;
struct ao2_iterator iter;
struct extension_state *state;
if (stasis_message_type(msg) != ast_presence_state_message_type()) {
return;
}
presence_state = stasis_message_data(msg);
ao2_lock(extension_states);
iter = ao2_iterator_init(extension_states, 0);
for (; (state = ao2_iterator_next(&iter)); ao2_ref(state, -1)) {
enum ast_presence_state presence_state_new;
char *presence_subtype, *presence_message;
struct ast_extension_state_presence_snapshot *presence_snapshot;
struct ast_extension_state_update_message *update_message;
ao2_lock(state);
/*
* We determine if this update is relevant to this extension state by seeing if the presence sources string
* even remotely contains the provider for this update. Worst case it's a substring and the calculated presence
* state is the same as before in which case we ignore it.
*/
if (!state->presence_sources_string || !strcasestr(state->presence_sources_string, presence_state->provider)) {
ao2_unlock(state);
continue;
}
/*
* Aggregation of presence state is done by requesting the current presence state with passing in a complete
* list of providers. This means that a presence state change message is just a notification to us to go and
* retrieve the new presence state. We don't just take it from the message itself. Since presence state is not
* as common as device state this is not a problem despite being inefficient in comparison to the device state
* implementation.
*/
presence_state_new = ast_presence_state(state->presence_sources_string, &presence_subtype, &presence_message);
if (presence_state_new == AST_PRESENCE_INVALID) {
/* For the invalid case we just ignore this update */
ao2_unlock(state);
continue;
}
if ((state->presence_snapshot->presence_state == presence_state_new) &&
((!presence_subtype && !state->presence_snapshot->presence_subtype) ||
(presence_subtype && state->presence_snapshot->presence_subtype &&
!strcmp(presence_subtype, state->presence_snapshot->presence_subtype))) &&
((!presence_message && !state->presence_snapshot->presence_message) ||
(presence_message && state->presence_snapshot->presence_message &&
!strcmp(presence_message, state->presence_snapshot->presence_message)))) {
/* No change in presence state, so ignore this update */
ao2_unlock(state);
ast_free(presence_subtype);
ast_free(presence_message);
continue;
}
presence_snapshot = extension_state_presence_snapshot_create(presence_state_new, presence_subtype, presence_message);
if (!presence_snapshot) {
ao2_unlock(state);
ast_free(presence_subtype);
ast_free(presence_message);
continue;
}
update_message = extension_state_update_message_create(state->dialplan_context,
state->dialplan_extension, state->device_snapshot, state->device_snapshot, state->presence_snapshot, presence_snapshot);
ao2_replace(state->presence_snapshot, presence_snapshot);
ao2_ref(presence_snapshot, -1);
if (update_message) {
struct stasis_message *message = stasis_message_create(ast_extension_state_update_message_type(), update_message);
if (message) {
stasis_publish(state->extension_state_topic, message);
ao2_ref(message, -1);
}
ao2_ref(update_message, -1);
}
ao2_unlock(state);
ast_free(presence_subtype);
ast_free(presence_message);
}
ao2_iterator_destroy(&iter);
ao2_unlock(extension_states);
}
/*!
* \internal
*
* \brief Allocate an extension state object
* \param exten The extension
* \param context The context
* \retval A pointer to the allocated extension state, or NULL on failure
*
* This function allocates an extension state object with the specified extension and context.
*/
static struct extension_state *extension_state_alloc(struct ast_exten *exten, struct ast_context *context)
{
char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
struct extension_state *state;
char *extension_state_topic_name;
snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context));
/*
* Each individual extension state has its own lock to ensure that when updating it
* we do not cause problems for either the existing topic ingesting updates
* or any access to the extension state cached message.
*/
state = ao2_alloc(sizeof(*state) + strlen(extension) + 1, extension_state_destroy);
if (!state) {
return NULL;
}
ast_copy_string(state->dialplan_context, ast_get_context_name(context), sizeof(state->dialplan_context));
ast_copy_string(state->dialplan_extension, ast_get_extension_name(exten), sizeof(state->dialplan_extension));
strcpy(state->extension, extension); /* Safe */
AST_VECTOR_INIT(&state->device_state_sources, 0);
/* These are the default if no sources are present */
state->device_snapshot = extension_state_device_snapshot_create(AST_EXTENSION_UNAVAILABLE,
&state->device_state_sources, NULL);
state->presence_snapshot = extension_state_presence_snapshot_create(AST_PRESENCE_NOT_SET, NULL, NULL);
if (!state->device_snapshot || !state->presence_snapshot) {
ao2_ref(state, -1);
return NULL;
}
/*
* We don't actually access the contents of exten past guarantee of it being valid so we can safely
* store a pointer to just do pointer comparison.
*/
state->hint_extension = exten;
/* Pattern match extensions don't have sources or a topic, so return early */
if (extension[0] == '_') {
return state;
}
/* We most likely have at least one device state source */
if (AST_VECTOR_INIT(&state->device_state_sources, 1)) {
ao2_ref(state, -1);
return NULL;
}
if (ast_asprintf(&extension_state_topic_name, "extension_state:extension/%s", extension) < 0) {
ao2_ref(state, -1);
return NULL;
}
state->extension_state_topic = stasis_topic_create(extension_state_topic_name);
ast_free(extension_state_topic_name);
if (!state->extension_state_topic) {
ao2_ref(state, -1);
return NULL;
}
state->extension_state_forwarder = stasis_forward_all(state->extension_state_topic, extension_state_topic_all);
if (!state->extension_state_forwarder) {
ao2_ref(state, -1);
return NULL;
}
return state;
}
/*!
* \internal
*
* \brief Get an extension state object
* \param chan The channel
* \param context The context
* \param extension The extension
* \retval A pointer to the extension state, or NULL on failure
*
* This function gets an extension state object for the specified channel, context, and extension.
* If the extension state does not exist due to being from a pattern match, it will be created.
*/
static struct extension_state *extension_state_get(struct ast_channel *chan, const char *context, const char *extension)
{
struct extension_state *state;
struct ast_exten *hint_exten;
struct pbx_find_info q = { .stacklen = 0 };
char location[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
/* We optimistically search for the extension state using the provided context and extension */
snprintf(location, sizeof(location), "%s@%s", extension, context);
state = ao2_find(extension_states, location, OBJ_SEARCH_KEY);
if (state) {
return state;
}
/*
* Pattern match extensions do exist within extension state for the purposes of listing them out,
* but they can't resolve down to anything else
*/
if (extension[0] == '_') {
return NULL;
}
ast_wrlock_contexts();
/*
* We can't use the provided context and extension as-is because an include
* could have resulted in the context being different than what was provided.
* To handle this we query the dialplan to find where the hint actually is.
* We also need to do this to determine if this is a pattern match or an explicit
* extension.
*/
hint_exten = pbx_find_extension(chan, NULL, &q, context, extension,
PRIORITY_HINT, NULL, "", E_MATCH);
if (!hint_exten) {
/*
* The extension must ALWAYS exist in the dialplan in some capacity. It is
* either in the dialplan as an explicit extension or a pattern match.
*/
ast_unlock_contexts();
return NULL;
}
if (ast_get_extension_name(hint_exten)[0] == '_') {
/*
* If this resolved down to a pattern match that means this is the first request
* for this explicit extension so we need to add it to the dialplan which will create
* an extension state for it. It's possible for us to conflict with another thread but
* in that case the ast_add_extension call will fail and be a no-op and we will return
* the extension state the other thread created.
*/
ast_add_extension(q.foundcontext, 0, extension, PRIORITY_HINT, ast_get_extension_label(hint_exten),
ast_get_extension_matchcid(hint_exten) ? ast_get_extension_cidmatch(hint_exten) : NULL,
ast_get_extension_app(hint_exten), ast_strdup(ast_get_extension_app_data(hint_exten)), ast_free_ptr,
ast_get_extension_registrar(hint_exten));
}
/* The extension state should already exist at this point */
snprintf(location, sizeof(location), "%s@%s", extension, q.foundcontext);
ast_unlock_contexts();
return ao2_find(extension_states, location, OBJ_SEARCH_KEY);
}
struct ast_channel *ast_extension_state_get_device_causing_channel(const char *device, enum ast_device_state device_state)
{
enum ast_channel_state search_state = 0; /* prevent false uninit warning */
char match[AST_CHANNEL_NAME];
struct ast_channel_iterator *chan_iter;
struct ast_channel *chan, *channel = NULL;
struct timeval chantime = {0, }; /* prevent false uninit warning */
switch (device_state) {
case AST_DEVICE_RINGING:
case AST_DEVICE_RINGINUSE:
/* find ringing channel */
search_state = AST_STATE_RINGING;
break;
case AST_DEVICE_BUSY:
/* find busy channel */
search_state = AST_STATE_BUSY;
break;
case AST_DEVICE_ONHOLD:
case AST_DEVICE_INUSE:
/* find up channel */
search_state = AST_STATE_UP;
break;
case AST_DEVICE_UNKNOWN:
case AST_DEVICE_NOT_INUSE:
case AST_DEVICE_INVALID:
case AST_DEVICE_UNAVAILABLE:
case AST_DEVICE_TOTAL /* not a state */:
/* no channels are of interest */
return NULL;
}
/* iterate over all channels of the device */
snprintf(match, sizeof(match), "%s-", device);
chan_iter = ast_channel_iterator_by_name_new(match, strlen(match));
for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
ast_channel_lock(chan);
/* this channel's state doesn't match */
if (search_state != ast_channel_state(chan)) {
ast_channel_unlock(chan);
continue;
}
/* any non-ringing channel will fit */
if (search_state != AST_STATE_RINGING) {
ast_channel_unlock(chan);
channel = chan;
break;
}
/* but we need the oldest ringing channel of the device to match with undirected pickup */
if (!channel) {
chantime = ast_channel_creationtime(chan);
ast_channel_ref(chan); /* must ref it! */
channel = chan;
} else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) {
chantime = ast_channel_creationtime(chan);
ast_channel_unref(channel);
ast_channel_ref(chan); /* must ref it! */
channel = chan;
}
ast_channel_unlock(chan);
}
ast_channel_iterator_destroy(chan_iter);
return channel;
}
enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate)
{
switch (devstate) {
case AST_DEVICE_ONHOLD:
return AST_EXTENSION_ONHOLD;
case AST_DEVICE_BUSY:
return AST_EXTENSION_BUSY;
case AST_DEVICE_UNKNOWN:
return AST_EXTENSION_NOT_INUSE;
case AST_DEVICE_UNAVAILABLE:
case AST_DEVICE_INVALID:
return AST_EXTENSION_UNAVAILABLE;
case AST_DEVICE_RINGINUSE:
return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING);
case AST_DEVICE_RINGING:
return AST_EXTENSION_RINGING;
case AST_DEVICE_INUSE:
return AST_EXTENSION_INUSE;
case AST_DEVICE_NOT_INUSE:
return AST_EXTENSION_NOT_INUSE;
case AST_DEVICE_TOTAL: /* not a device state, included for completeness */
break;
}
return AST_EXTENSION_NOT_INUSE;
}
const char *ast_extension_state2str(int extension_state)
{
int i;
for (i = 0; (i < ARRAY_LEN(extension_state_mappings)); i++) {
if (extension_state_mappings[i].extension_state == extension_state)
return extension_state_mappings[i].text;
}
return "Unknown";
}
/*!
* \internal
* \brief Handle the ExtensionStateList Manager action
*
* \param s The manager session
* \param m The manager message
* \retval 0 on success, -1 on failure
*
* This function handles the ExtensionStateList Manager action by returning a list of all extension states.
*/
static int action_extensionstatelist(struct mansession *s, const struct message *m)
{
const char *action_id = astman_get_header(m, "ActionID");
if (!ao2_container_count(extension_states)) {
astman_send_error(s, m, "No dialplan hints are available");
return 0;
}
astman_send_listack(s, m, "Extension Statuses will follow", "start");
ao2_lock(extension_states);
if (ao2_container_count(extension_states)) {
struct ao2_iterator it_states;
struct extension_state *state;
int count = 0;
it_states = ao2_iterator_init(extension_states, 0);
for (; (state = ao2_iterator_next(&it_states)); ao2_ref(state, -1)) {
if (state->extension[0] == '_') {
continue;
}
++count;
astman_append(s, "Event: ExtensionStatus\r\n");
if (!ast_strlen_zero(action_id)) {
astman_append(s, "ActionID: %s\r\n", action_id);
}
ao2_lock(state);
astman_append(s,
"Exten: %s\r\n"
"Context: %s\r\n"
"Hint: %s\r\n"
"Status: %d\r\n"
"StatusText: %s\r\n\r\n",
state->dialplan_extension,
state->dialplan_context,
state->hint_extension ? ast_get_extension_app(state->hint_extension) : "None",
state->device_snapshot->state,
ast_extension_state2str(state->device_snapshot->state));
ao2_unlock(state);
}
ao2_iterator_destroy(&it_states);
astman_send_list_complete_start(s, m, "ExtensionStateListComplete", count);
astman_send_list_complete_end(s);
} else {
astman_send_error(s, m, "No dialplan hints are available");
}
ao2_unlock(extension_states);
return 0;
}
void pbx_extension_state_hint_set(struct ast_exten *exten, struct ast_context *context)
{
char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
struct extension_state *state;
snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context));
ao2_lock(extension_states);
state = ao2_find(extension_states, extension, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!state) {
state = extension_state_alloc(exten, context);
if (!state) {
ast_log(LOG_WARNING, "Could not create extension state for hint '%s', it will be unavailable\n",
extension);
ao2_unlock(extension_states);
return;
}
ao2_link_flags(extension_states, state, OBJ_NOLOCK);
}
state->hint_extension = exten;
if (extension[0] != '_') {
extension_state_update_sources(state, exten);
}
ao2_ref(state, -1);
ao2_unlock(extension_states);
}
void pbx_extension_state_hint_remove(struct ast_exten *exten, struct ast_context *context)
{
char extension[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 2];
struct extension_state *state;
snprintf(extension, sizeof(extension), "%s@%s", ast_get_extension_name(exten), ast_get_context_name(context));
ao2_lock(extension_states);
state = ao2_find(extension_states, extension, OBJ_SEARCH_KEY | OBJ_NOLOCK);
/* If this is not the latest hint extension that configured this extension state it can't remove it */
if (!state || state->hint_extension != exten) {
ao2_unlock(extension_states);
ao2_cleanup(state);
return;
}
extension_state_shutdown(state);
ao2_unlink_flags(extension_states, state, OBJ_NOLOCK);
ao2_ref(state, -1);
ao2_unlock(extension_states);
}
struct stasis_topic *ast_extension_state_topic_all(void)
{
return extension_state_topic_all;
}
struct stasis_topic *ast_extension_state_topic(const char *exten, const char *context)
{
struct extension_state *state;
struct stasis_topic *topic;
state = extension_state_get(NULL, context, exten);
if (!state) {
return NULL;
}
ao2_lock(state);
topic = ao2_bump(state->extension_state_topic);
ao2_unlock(state);
ao2_ref(state, -1);
return topic;
}
struct ast_extension_state_device_snapshot *ast_extension_state_get_latest_device_snapshot(struct ast_channel *chan,
const char *exten, const char *context)
{
struct extension_state *state;
struct ast_extension_state_device_snapshot *device_snapshot;
state = extension_state_get(chan, context, exten);
if (!state) {
return NULL;
}
ao2_lock(state);
device_snapshot = ao2_bump(state->device_snapshot);
ao2_unlock(state);
ao2_ref(state, -1);
return device_snapshot;
}
struct ast_extension_state_presence_snapshot *ast_extension_state_get_latest_presence_snapshot(struct ast_channel *chan,
const char *exten, const char *context)
{
struct extension_state *state;
struct ast_extension_state_presence_snapshot *presence_snapshot;
state = extension_state_get(chan, context, exten);
if (!state) {
return NULL;
}
ao2_lock(state);
presence_snapshot = ao2_bump(state->presence_snapshot);
ao2_unlock(state);
ao2_ref(state, -1);
return presence_snapshot;
}
/*!
* \internal
* \brief CLI command to show hints
*
* \param e The CLI entry
* \param cmd The command
* \param a The CLI arguments
*
* This function shows all registered hints in the CLI.
*/
static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct extension_state *state;
int num = 0;
struct ao2_iterator i;
switch (cmd) {
case CLI_INIT:
e->command = "core show hints";
e->usage =
"Usage: core show hints\n"
" List registered hints.\n"
" Hint details are shown in five columns. In order from left to right, they are:\n"
" 1. Hint extension URI.\n"
" 2. List of mapped device or presence state identifiers.\n"
" 3. Current extension state. The aggregate of mapped device states.\n"
" 4. Current presence state for the mapped presence state provider.\n"
" 5. Watchers - number of subscriptions and other entities watching this hint.\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (ao2_container_count(extension_states) == 0) {
ast_cli(a->fd, "There are no registered dialplan hints\n");
return CLI_SUCCESS;
}
/* ... we have hints ... */
ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n");
i = ao2_iterator_init(extension_states, 0);
for (; (state = ao2_iterator_next(&i)); ao2_ref(state, -1)) {
ao2_lock(state);
ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2zd\n",
state->extension,
state->hint_extension ? ast_get_extension_app(state->hint_extension) : "None",
ast_extension_state2str(state->device_snapshot->state),
ast_presence_state2str(state->presence_snapshot->presence_state),
state->extension_state_topic ? stasis_topic_subscribers(state->extension_state_topic) : 0);
ao2_unlock(state);
num++;
}
ao2_iterator_destroy(&i);
ast_cli(a->fd, "----------------\n");
ast_cli(a->fd, "- %d hints registered\n", num);
return CLI_SUCCESS;
}
/*!
* \internal
* \brief Complete the core show hint CLI command
*
* \param line The command line
* \param word The word being completed
* \param pos The position in the command
* \param state The completion state
*
* This function completes the core show hint CLI command.
*/
static char *complete_core_show_hint(const char *line, const char *word, int pos, int state)
{
struct extension_state *extstate;
char *ret = NULL;
int which = 0;
int wordlen;
struct ao2_iterator i;
if (pos != 3)
return NULL;
wordlen = strlen(word);
/* walk through all hints */
i = ao2_iterator_init(extension_states, 0);
for (; (extstate = ao2_iterator_next(&i)); ao2_ref(extstate, -1)) {
ao2_lock(extstate);
if (!strncasecmp(word, extstate->dialplan_extension, wordlen) && ++which > state) {
ret = ast_strdup(extstate->dialplan_extension);
ao2_unlock(extstate);
ao2_ref(extstate, -1);
break;
}
ao2_unlock(extstate);
}
ao2_iterator_destroy(&i);
return ret;
}
/*!
* \internal
* \brief CLI support for listing registered dial plan hint
*
* \param e The CLI entry
* \param cmd The command
* \param a The CLI arguments
*
* This function handles the core show hint CLI command.
*/
static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct extension_state *extstate;
int num = 0, extenlen;
struct ao2_iterator i;
switch (cmd) {
case CLI_INIT:
e->command = "core show hint";
e->usage =
"Usage: core show hint <exten>\n"
" List registered hint.\n"
" Hint details are shown in five columns. In order from left to right, they are:\n"
" 1. Hint extension URI.\n"
" 2. List of mapped device or presence state identifiers.\n"
" 3. Current extension state. The aggregate of mapped device states.\n"
" 4. Current presence state for the mapped presence state provider.\n"
" 5. Watchers - number of subscriptions and other entities watching this hint.\n";
return NULL;
case CLI_GENERATE:
return complete_core_show_hint(a->line, a->word, a->pos, a->n);
}
if (a->argc < 4)
return CLI_SHOWUSAGE;
if (ao2_container_count(extension_states) == 0) {
ast_cli(a->fd, "There are no registered dialplan hints\n");
return CLI_SUCCESS;
}
extenlen = strlen(a->argv[3]);
i = ao2_iterator_init(extension_states, 0);
for (; (extstate = ao2_iterator_next(&i)); ao2_ref(extstate, -1)) {
ao2_lock(extstate);
if (!strncasecmp(extstate->extension, a->argv[3], extenlen)) {
ast_cli(a->fd, "%-30.30s: %-60.60s State:%-15.15s Presence:%-15.15s Watchers %2zd\n",
extstate->extension,
extstate->hint_extension ? ast_get_extension_app(extstate->hint_extension) : "None",
ast_extension_state2str(extstate->device_snapshot->state),
ast_presence_state2str(extstate->presence_snapshot->presence_state),
extstate->extension_state_topic ? stasis_topic_subscribers(extstate->extension_state_topic) : 0);
num++;
}
ao2_unlock(extstate);
}
ao2_iterator_destroy(&i);
if (!num)
ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]);
else
ast_cli(a->fd, "%d hint%s matching extension %s\n", num, (num!=1 ? "s":""), a->argv[3]);
return CLI_SUCCESS;
}
static struct ast_cli_entry extension_state_cli[] = {
AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"),
AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"),
};
/*!
* \internal
* \brief Callback function to clean up an individual extension state.
*
* \param obj The extension state object
* \param arg Additional argument (not used)
* \param flags Flags for the callback
* \return CMP_MATCH if the object was processed, 0 otherwise
*/
static int extension_state_cleanup_individual(void *obj, void *arg, int flags)
{
struct extension_state *state = obj;
extension_state_shutdown(state);
return CMP_MATCH;
}
/*!
* \internal
* \brief Clean up the extension state subsystem, called at shutdown.
*/
static void extension_state_cleanup(void)
{
ast_cli_unregister_multiple(extension_state_cli, ARRAY_LEN(extension_state_cli));
ast_manager_unregister("ExtensionStateList");
if (extension_states) {
ao2_callback(extension_states, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, extension_state_cleanup_individual, NULL);
ao2_ref(extension_states, -1);
}
presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub);
STASIS_MESSAGE_TYPE_CLEANUP(ast_extension_state_update_message_type);
STASIS_MESSAGE_TYPE_CLEANUP(ast_extension_state_remove_message_type);
}
int ast_extension_state_init(void)
{
if (STASIS_MESSAGE_TYPE_INIT(ast_extension_state_update_message_type) != 0) {
return -1;
}
if (STASIS_MESSAGE_TYPE_INIT(ast_extension_state_remove_message_type) != 0) {
return -1;
}
/* Initialize extension state subsystem */
extension_states = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
extension_state_sort_fn, extension_state_cmp_fn);
if (!extension_states) {
ast_log(LOG_ERROR, "Failed to allocate new states container\n");
return -1;
}
extension_state_topic_all = stasis_topic_create("extension_state:all");
if (!extension_state_topic_all) {
ast_log(LOG_ERROR, "Failed to create extension state topic\n");
return -1;
}
presence_state_sub = stasis_subscribe(ast_presence_state_topic_all(), extension_state_presence_state_cb, NULL);
if (!presence_state_sub) {
ast_log(LOG_ERROR, "Failed to create subscription to receive presence state updates\n");
return -1;
}
stasis_subscription_accept_message_type(presence_state_sub, ast_presence_state_message_type());
stasis_subscription_set_filter(presence_state_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
ast_cli_register_multiple(extension_state_cli, ARRAY_LEN(extension_state_cli));
ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist);
ast_register_cleanup(extension_state_cleanup);
return 0;
}