* \author Mark Michelson <mmichelson@digium.com>
*/
-#include "asterisk.h"
+/*! \li \ref ccss.c uses the configuration file \ref ccss.conf
+ * \addtogroup configuration_file Configuration Files
+ */
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+/*!
+ * \page ccss.conf ccss.conf
+ * \verbinclude ccss.conf.sample
+ */
+
+/*** MODULEINFO
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
#include "asterisk/pbx.h"
#include "asterisk/utils.h"
#include "asterisk/taskprocessor.h"
-#include "asterisk/event.h"
+#include "asterisk/devicestate.h"
#include "asterisk/module.h"
#include "asterisk/app.h"
#include "asterisk/cli.h"
#include "asterisk/manager.h"
#include "asterisk/causes.h"
+#include "asterisk/stasis_system.h"
+#include "asterisk/format_cache.h"
/*** DOCUMENTATION
<application name="CallCompletionRequest" language="en_US">
<description>
<para>Request call completion service for a previously failed
call attempt.</para>
+ <para>This application sets the following channel variables:</para>
+ <variablelist>
+ <variable name="CC_REQUEST_RESULT">
+ <para>This is the returned status of the request.</para>
+ <value name="SUCCESS" />
+ <value name="FAIL" />
+ </variable>
+ <variable name="CC_REQUEST_REASON">
+ <para>This is the reason the request failed.</para>
+ <value name="NO_CORE_INSTANCE" />
+ <value name="NOT_GENERIC" />
+ <value name="TOO_MANY_REQUESTS" />
+ <value name="UNSPECIFIED" />
+ </variable>
+ </variablelist>
</description>
</application>
<application name="CallCompletionCancel" language="en_US">
<syntax />
<description>
<para>Cancel a Call Completion Request.</para>
+ <para>This application sets the following channel variables:</para>
+ <variablelist>
+ <variable name="CC_CANCEL_RESULT">
+ <para>This is the returned status of the cancel.</para>
+ <value name="SUCCESS" />
+ <value name="FAIL" />
+ </variable>
+ <variable name="CC_CANCEL_REASON">
+ <para>This is the reason the cancel failed.</para>
+ <value name="NO_CORE_INSTANCE" />
+ <value name="NOT_GENERIC" />
+ <value name="UNSPECIFIED" />
+ </variable>
+ </variablelist>
</description>
</application>
***/
unsigned int cc_max_agents;
unsigned int cc_max_monitors;
char cc_callback_macro[AST_MAX_EXTENSION];
+ char cc_callback_sub[AST_MAX_EXTENSION];
char cc_agent_dialstring[AST_MAX_EXTENSION];
};
return 0;
}
+/* default values mapping from cc_state to ast_dev_state */
+
+#define CC_AVAILABLE_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE
+#define CC_CALLER_OFFERED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE
+#define CC_CALLER_REQUESTED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE
+#define CC_ACTIVE_DEVSTATE_DEFAULT AST_DEVICE_INUSE
+#define CC_CALLEE_READY_DEVSTATE_DEFAULT AST_DEVICE_RINGING
+#define CC_CALLER_BUSY_DEVSTATE_DEFAULT AST_DEVICE_ONHOLD
+#define CC_RECALLING_DEVSTATE_DEFAULT AST_DEVICE_RINGING
+#define CC_COMPLETE_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE
+#define CC_FAILED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE
+
+/*!
+ * \internal
+ * \brief initialization of defaults for CC_STATE to DEVICE_STATE map
+ */
+static enum ast_device_state cc_state_to_devstate_map[] = {
+ [CC_AVAILABLE] = CC_AVAILABLE_DEVSTATE_DEFAULT,
+ [CC_CALLER_OFFERED] = CC_CALLER_OFFERED_DEVSTATE_DEFAULT,
+ [CC_CALLER_REQUESTED] = CC_CALLER_REQUESTED_DEVSTATE_DEFAULT,
+ [CC_ACTIVE] = CC_ACTIVE_DEVSTATE_DEFAULT,
+ [CC_CALLEE_READY] = CC_CALLEE_READY_DEVSTATE_DEFAULT,
+ [CC_CALLER_BUSY] = CC_CALLER_BUSY_DEVSTATE_DEFAULT,
+ [CC_RECALLING] = CC_RECALLING_DEVSTATE_DEFAULT,
+ [CC_COMPLETE] = CC_COMPLETE_DEVSTATE_DEFAULT,
+ [CC_FAILED] = CC_FAILED_DEVSTATE_DEFAULT,
+};
+
+/*!
+ * \internal
+ * \brief lookup the ast_device_state mapped to cc_state
+ *
+ * \param state
+ *
+ * \return the correponding DEVICE STATE from the cc_state_to_devstate_map
+ * when passed an internal state.
+ */
+static enum ast_device_state cc_state_to_devstate(enum cc_state state)
+{
+ return cc_state_to_devstate_map[state];
+}
+
+/*!
+ * \internal
+ * \brief Callback for devicestate providers
+ *
+ * \details
+ * Initialize with ast_devstate_prov_add() and returns the corresponding
+ * DEVICE STATE based on the current CC_STATE state machine if the requested
+ * device is found and is a generic device. Returns the equivalent of
+ * CC_FAILED, which defaults to NOT_INUSE, if no device is found. NOT_INUSE would
+ * indicate that there is no presence of any pending call back.
+ */
+static enum ast_device_state ccss_device_state(const char *device_name)
+{
+ struct cc_core_instance *core_instance;
+ unsigned long match_flags;
+ enum ast_device_state cc_current_state;
+
+ match_flags = MATCH_NO_REQUEST;
+ core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent,
+ (char *) device_name, &match_flags,
+ "Find Core Instance for ccss_device_state reqeust.");
+ if (!core_instance) {
+ ast_log_dynamic_level(cc_logger_level,
+ "Couldn't find a core instance for caller %s\n", device_name);
+ return cc_state_to_devstate(CC_FAILED);
+ }
+
+ ast_log_dynamic_level(cc_logger_level,
+ "Core %d: Found core_instance for caller %s in state %s\n",
+ core_instance->core_id, device_name, cc_state_to_string(core_instance->current_state));
+
+ if (strcmp(core_instance->agent->callbacks->type, "generic")) {
+ ast_log_dynamic_level(cc_logger_level,
+ "Core %d: Device State is only for generic agent types.\n",
+ core_instance->core_id);
+ cc_unref(core_instance, "Unref core_instance since ccss_device_state was called with native agent");
+ return cc_state_to_devstate(CC_FAILED);
+ }
+ cc_current_state = cc_state_to_devstate(core_instance->current_state);
+ cc_unref(core_instance, "Unref core_instance done with ccss_device_state");
+ return cc_current_state;
+}
+
+/*!
+ * \internal
+ * \brief Notify Device State Changes from CC STATE MACHINE
+ *
+ * \details
+ * Any time a state is changed, we call this function to notify the DEVICE STATE
+ * subsystem of the change so that subscribed phones to any corresponding hints that
+ * are using that state are updated.
+ */
+static void ccss_notify_device_state_change(const char *device, enum cc_state state)
+{
+ enum ast_device_state devstate;
+
+ devstate = cc_state_to_devstate(state);
+
+ ast_log_dynamic_level(cc_logger_level,
+ "Notification of CCSS state change to '%s', device state '%s' for device '%s'\n",
+ cc_state_to_string(state), ast_devstate2str(devstate), device);
+
+ ast_devstate_changed(devstate, AST_DEVSTATE_CACHABLE, "ccss:%s", device);
+}
+
#define CC_OFFER_TIMER_DEFAULT 20 /* Seconds */
#define CCNR_AVAILABLE_TIMER_DEFAULT 7200 /* Seconds */
#define CCBS_AVAILABLE_TIMER_DEFAULT 4800 /* Seconds */
.cc_max_agents = CC_MAX_AGENTS_DEFAULT,
.cc_max_monitors = CC_MAX_MONITORS_DEFAULT,
.cc_callback_macro = "",
+ .cc_callback_sub = "",
.cc_agent_dialstring = "",
};
struct ast_cc_config_params *__ast_cc_config_params_init(const char *file, int line, const char *function)
{
-#if defined(__AST_DEBUG_MALLOC)
struct ast_cc_config_params *params = __ast_malloc(sizeof(*params), file, line, function);
-#else
- struct ast_cc_config_params *params = ast_malloc(sizeof(*params));
-#endif
if (!params) {
return NULL;
if (!strcasecmp(name, "cc_callback_macro")) {
value = ast_get_cc_callback_macro(params);
+ } else if (!strcasecmp(name, "cc_callback_sub")) {
+ value = ast_get_cc_callback_sub(params);
} else if (!strcasecmp(name, "cc_agent_policy")) {
value = agent_policy_to_str(ast_get_cc_agent_policy(params));
} else if (!strcasecmp(name, "cc_monitor_policy")) {
} else if (!strcasecmp(name, "cc_callback_macro")) {
ast_set_cc_callback_macro(params, value);
return 0;
+ } else if (!strcasecmp(name, "cc_callback_sub")) {
+ ast_set_cc_callback_sub(params, value);
+ return 0;
}
- if (!sscanf(value, "%30u", &value_as_uint) == 1) {
+ if (sscanf(value, "%30u", &value_as_uint) != 1) {
return -1;
}
!strcasecmp(name, "cc_max_agents") ||
!strcasecmp(name, "cc_max_monitors") ||
!strcasecmp(name, "cc_callback_macro") ||
+ !strcasecmp(name, "cc_callback_sub") ||
!strcasecmp(name, "cc_agent_dialstring") ||
!strcasecmp(name, "cc_recall_timer"));
}
return config->cc_callback_macro;
}
+const char *ast_get_cc_callback_sub(struct ast_cc_config_params *config)
+{
+ return config->cc_callback_sub;
+}
+
void ast_set_cc_callback_macro(struct ast_cc_config_params *config, const char * const value)
{
+ ast_log(LOG_WARNING, "Usage of cc_callback_macro is deprecated. Please use cc_callback_sub instead.\n");
if (ast_strlen_zero(value)) {
config->cc_callback_macro[0] = '\0';
} else {
}
}
+void ast_set_cc_callback_sub(struct ast_cc_config_params *config, const char * const value)
+{
+ if (ast_strlen_zero(value)) {
+ config->cc_callback_sub[0] = '\0';
+ } else {
+ ast_copy_string(config->cc_callback_sub, value, sizeof(config->cc_callback_sub));
+ }
+}
+
+static int cc_publish(struct stasis_message_type *message_type, int core_id, struct ast_json *extras)
+{
+ struct ast_json *blob;
+ struct ast_json_payload *payload;
+ struct stasis_message *message;
+
+ if (!message_type) {
+ return -1;
+ }
+
+ blob = ast_json_pack("{s: i}",
+ "core_id", core_id);
+ if (!blob) {
+ return -1;
+ }
+
+ if (extras) {
+ ast_json_object_update(blob, extras);
+ }
+
+ payload = ast_json_payload_create(blob);
+ ast_json_unref(blob);
+
+ if (!payload) {
+ return -1;
+ }
+
+ message = stasis_message_create(message_type, payload);
+ ao2_ref(payload, -1);
+
+ if (!message) {
+ return -1;
+ }
+
+ stasis_publish(ast_system_topic(), message);
+ ao2_ref(message, -1);
+
+ return 0;
+}
+
+static void cc_publish_available(int core_id, const char *callee, const char *service)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s, s: s}",
+ "callee", callee,
+ "service", service);
+
+ cc_publish(ast_cc_available_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_offertimerstart(int core_id, const char *caller, unsigned int expires)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s, s: i}",
+ "caller", caller,
+ "expires", expires);
+
+ cc_publish(ast_cc_offertimerstart_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_requested(int core_id, const char *caller, const char *callee)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s, s: s}",
+ "caller", caller,
+ "callee", callee);
+
+ cc_publish(ast_cc_requested_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_requestacknowledged(int core_id, const char *caller)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "caller", caller);
+
+ cc_publish(ast_cc_requestacknowledged_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_callerstopmonitoring(int core_id, const char *caller)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "caller", caller);
+
+ cc_publish(ast_cc_callerstopmonitoring_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_callerstartmonitoring(int core_id, const char *caller)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "caller", caller);
+
+ cc_publish(ast_cc_callerstartmonitoring_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_callerrecalling(int core_id, const char *caller)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "caller", caller);
+
+ cc_publish(ast_cc_callerrecalling_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_recallcomplete(int core_id, const char *caller)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "caller", caller);
+
+ cc_publish(ast_cc_recallcomplete_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_failure(int core_id, const char *caller, const char *reason)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s, s: s}",
+ "caller", caller,
+ "reason", reason);
+
+ cc_publish(ast_cc_failure_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
+static void cc_publish_monitorfailed(int core_id, const char *callee)
+{
+ struct ast_json *extras;
+
+ extras = ast_json_pack("{s: s}",
+ "callee", callee);
+
+ cc_publish(ast_cc_monitorfailed_type(), core_id, extras);
+ ast_json_unref(extras);
+}
+
struct cc_monitor_backend {
AST_LIST_ENTRY(cc_monitor_backend) next;
const struct ast_cc_monitor_callbacks *callbacks;
return callbacks;
}
+/*!
+ * \internal
+ * \brief Determine if the given device state is considered available by generic CCSS.
+ * \since 1.8
+ *
+ * \param state Device state to test.
+ *
+ * \return TRUE if the given device state is considered available by generic CCSS.
+ */
+static int cc_generic_is_device_available(enum ast_device_state state)
+{
+ return state == AST_DEVICE_NOT_INUSE || state == AST_DEVICE_UNKNOWN;
+}
+
static int cc_generic_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
static int cc_generic_monitor_suspend(struct ast_cc_monitor *monitor);
static int cc_generic_monitor_unsuspend(struct ast_cc_monitor *monitor);
* recalled
*/
int fit_for_recall;
- struct ast_event_sub *sub;
+ struct stasis_subscription *sub;
AST_LIST_HEAD_NOLOCK(, generic_monitor_instance) list;
};
int core_id;
};
-static int generic_monitor_hash_fn(const void *obj, const int flags)
-{
- const struct generic_monitor_instance_list *generic_list = obj;
- return ast_str_hash(generic_list->device_name);
-}
-
-static int generic_monitor_cmp_fn(void *obj, void *arg, int flags)
-{
- const struct generic_monitor_instance_list *generic_list1 = obj;
- const struct generic_monitor_instance_list *generic_list2 = arg;
-
- return !strcmp(generic_list1->device_name, generic_list2->device_name) ? CMP_MATCH | CMP_STOP : 0;
-}
+AO2_STRING_FIELD_HASH_FN(generic_monitor_instance_list, device_name)
+AO2_STRING_FIELD_CMP_FN(generic_monitor_instance_list, device_name)
static struct generic_monitor_instance_list *find_generic_monitor_instance_list(const char * const device_name)
{
- struct generic_monitor_instance_list finder = {.device_name = device_name};
+ struct generic_monitor_instance_list finder = {0};
+ char *uppertech = ast_strdupa(device_name);
+ ast_tech_to_upper(uppertech);
+ finder.device_name = uppertech;
return ao2_t_find(generic_monitors, &finder, OBJ_POINTER, "Finding generic monitor instance list");
}
struct generic_monitor_instance_list *generic_list = obj;
struct generic_monitor_instance *generic_instance;
- generic_list->sub = ast_event_unsubscribe(generic_list->sub);
+ generic_list->sub = stasis_unsubscribe(generic_list->sub);
while ((generic_instance = AST_LIST_REMOVE_HEAD(&generic_list->list, next))) {
ast_free(generic_instance);
}
ast_free((char *)generic_list->device_name);
}
-static void generic_monitor_devstate_cb(const struct ast_event *event, void *userdata);
+static void generic_monitor_devstate_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg);
static struct generic_monitor_instance_list *create_new_generic_list(struct ast_cc_monitor *monitor)
{
struct generic_monitor_instance_list *generic_list = ao2_t_alloc(sizeof(*generic_list),
generic_monitor_instance_list_destructor, "allocate generic monitor instance list");
+ char * device_name;
+ struct stasis_topic *device_specific_topic;
if (!generic_list) {
return NULL;
}
- if (!(generic_list->device_name = ast_strdup(monitor->interface->device_name))) {
+ if (!(device_name = ast_strdup(monitor->interface->device_name))) {
cc_unref(generic_list, "Failed to strdup the monitor's device name");
return NULL;
}
+ ast_tech_to_upper(device_name);
+ generic_list->device_name = device_name;
+
+ device_specific_topic = ast_device_state_topic(device_name);
+ if (!device_specific_topic) {
+ return NULL;
+ }
- if (!(generic_list->sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, generic_monitor_devstate_cb,
- "Requesting CC", NULL, AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR,
- monitor->interface->device_name, AST_EVENT_IE_END))) {
+ if (!(generic_list->sub = stasis_subscribe(device_specific_topic, generic_monitor_devstate_cb, NULL))) {
cc_unref(generic_list, "Failed to subscribe to device state");
return NULL;
}
return generic_list;
}
-struct generic_tp_cb_data {
- const char *device_name;
- enum ast_device_state new_state;
-};
-
static int generic_monitor_devstate_tp_cb(void *data)
{
- struct generic_tp_cb_data *gtcd = data;
- enum ast_device_state new_state = gtcd->new_state;
- enum ast_device_state previous_state = gtcd->new_state;
- const char *monitor_name = gtcd->device_name;
+ RAII_VAR(struct ast_device_state_message *, dev_state, data, ao2_cleanup);
+ enum ast_device_state new_state = dev_state->state;
+ enum ast_device_state previous_state;
struct generic_monitor_instance_list *generic_list;
struct generic_monitor_instance *generic_instance;
- if (!(generic_list = find_generic_monitor_instance_list(monitor_name))) {
+ if (!(generic_list = find_generic_monitor_instance_list(dev_state->device))) {
/* The most likely cause for this is that we destroyed the monitor in the
* time between subscribing to its device state and the time this executes.
* Not really a big deal.
*/
- ast_free((char *) gtcd->device_name);
- ast_free(gtcd);
return 0;
}
if (generic_list->current_state == new_state) {
/* The device state hasn't actually changed, so we don't really care */
cc_unref(generic_list, "Kill reference of generic list in devstate taskprocessor callback");
- ast_free((char *) gtcd->device_name);
- ast_free(gtcd);
return 0;
}
previous_state = generic_list->current_state;
generic_list->current_state = new_state;
- if ((new_state == AST_DEVICE_NOT_INUSE || new_state == AST_DEVICE_UNKNOWN) &&
+ if (cc_generic_is_device_available(new_state) &&
(previous_state == AST_DEVICE_INUSE || previous_state == AST_DEVICE_UNAVAILABLE ||
previous_state == AST_DEVICE_BUSY)) {
AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
}
}
cc_unref(generic_list, "Kill reference of generic list in devstate taskprocessor callback");
- ast_free((char *) gtcd->device_name);
- ast_free(gtcd);
return 0;
}
-static void generic_monitor_devstate_cb(const struct ast_event *event, void *userdata)
+static void generic_monitor_devstate_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
{
/* Wow, it's cool that we've picked up on a state change, but we really want
* the actual work to be done in the core's taskprocessor execution thread
* so that all monitor operations can be serialized. Locks?! We don't need
* no steenkin' locks!
*/
- struct generic_tp_cb_data *gtcd = ast_calloc(1, sizeof(*gtcd));
-
- if (!gtcd) {
+ struct ast_device_state_message *dev_state;
+ if (ast_device_state_message_type() != stasis_message_type(msg)) {
return;
}
- if (!(gtcd->device_name = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE)))) {
- ast_free(gtcd);
+ dev_state = stasis_message_data(msg);
+ if (dev_state->eid) {
+ /* ignore non-aggregate states */
return;
}
- gtcd->new_state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
- if (ast_taskprocessor_push(cc_core_taskprocessor, generic_monitor_devstate_tp_cb, gtcd)) {
- ast_free((char *)gtcd->device_name);
- ast_free(gtcd);
+ ao2_t_ref(dev_state, +1, "Bumping dev_state ref for cc_core_taskprocessor");
+ if (ast_taskprocessor_push(cc_core_taskprocessor, generic_monitor_devstate_tp_cb, dev_state)) {
+ ao2_cleanup(dev_state);
+ return;
}
}
/* If the device being suspended is currently in use, then we don't need to
* take any further actions
*/
- if (state != AST_DEVICE_NOT_INUSE && state != AST_DEVICE_UNKNOWN) {
+ if (!cc_generic_is_device_available(state)) {
cc_unref(generic_list, "Device is in use. Nothing to do. Unref generic list.");
return 0;
}
/* If the device is currently available, we can immediately announce
* its availability
*/
- if (state == AST_DEVICE_NOT_INUSE || state == AST_DEVICE_UNKNOWN) {
+ if (cc_generic_is_device_available(state)) {
ast_cc_monitor_callee_available(monitor->core_id, "Generic monitored party has become available");
}
/* First things first. We don't even want to consider this action if
* the device in question isn't available right now.
*/
- if (generic_list->fit_for_recall && (generic_list->current_state == AST_DEVICE_NOT_INUSE ||
- generic_list->current_state == AST_DEVICE_UNKNOWN)) {
+ if (generic_list->fit_for_recall
+ && cc_generic_is_device_available(generic_list->current_state)) {
AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
if (!generic_instance->is_suspended && generic_instance->monitoring) {
ast_cc_monitor_callee_available(generic_instance->core_id, "Signaling generic monitor "
*
* \details
* This serves mainly as a key when searching for a particular dialstring.
- * For instance, let's say that we have called device SIP/400@somepeer. This
+ * For instance, let's say that we have called device SIP/400\@somepeer. This
* device offers call completion, but then due to some unforeseen circumstance,
* this device backs out and makes CC unavailable. When that happens, we need
* to find the dialstring that corresponds to that device, and we use the
cc_interface->monitor_class = AST_CC_EXTENSION_MONITOR;
strcpy(cc_interface->device_name, ast_str_buffer(str));
monitor->interface = cc_interface;
- ast_log_dynamic_level(cc_logger_level, "Created an extension cc interface for '%s' with id %d and parent %d\n", cc_interface->device_name, monitor->id, monitor->parent_id);
+ ast_log_dynamic_level(cc_logger_level, "Created an extension cc interface for '%s' with id %u and parent %u\n", cc_interface->device_name, monitor->id, monitor->parent_id);
return monitor;
}
return -1;
}
- if (!(monitor = cc_extension_monitor_init(S_OR(chan->macroexten, chan->exten), S_OR(chan->macrocontext, chan->context), 0))) {
+ if (!(monitor = cc_extension_monitor_init(S_OR(ast_channel_macroexten(chan), ast_channel_exten(chan)), S_OR(ast_channel_macrocontext(chan), ast_channel_context(chan)), 0))) {
ast_free(interfaces);
return -1;
}
* Note that it is not necessarily erroneous to add the same
* device to the tree twice. If the same device is called by
* two different extension during the same call, then
- * that is a legitimate situation. Of course, I'm pretty sure
- * the dialed_interfaces global datastore will not allow that
- * to happen anyway.
+ * that is a legitimate situation.
*
* \param device_name The name of the device being added to the tree
* \param dialstring The dialstring used to dial the device being added
monitor->interface = cc_interface;
monitor->available_timer_id = -1;
ast_cc_copy_config_params(cc_interface->config_params, &cc_data->config_params);
- ast_log_dynamic_level(cc_logger_level, "Core %d: Created a device cc interface for '%s' with id %d and parent %d\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Created a device cc interface for '%s' with id %u and parent %u\n",
monitor->core_id, cc_interface->device_name, monitor->id, monitor->parent_id);
return monitor;
}
cc_extension_monitor_change_is_valid(core_instance, monitor->parent_id, monitor->interface->device_name, 0);
- manager_event(EVENT_FLAG_CC, "CCAvailable",
- "CoreID: %d\r\n"
- "Callee: %s\r\n"
- "Service: %s\r\n",
- cc_interfaces->core_id, device_name, cc_service_to_string(cc_data->service)
- );
+ cc_publish_available(cc_interfaces->core_id, device_name, cc_service_to_string(cc_data->service));
cc_unref(core_instance, "Done with core_instance after handling CC control frame");
cc_unref(monitor, "Unref reference from allocating monitor");
*/
*ignore_cc = 1;
ast_channel_unlock(chan);
- ast_log_dynamic_level(cc_logger_level, "Agent policy for %s is 'never'. CC not possible\n", chan->name);
+ ast_log_dynamic_level(cc_logger_level, "Agent policy for %s is 'never'. CC not possible\n", ast_channel_name(chan));
return 0;
}
}
/* Situation 2 has occurred */
- if (!(monitor = cc_extension_monitor_init(S_OR(chan->macroexten, chan->exten),
- S_OR(chan->macrocontext, chan->context), interfaces->dial_parent_id))) {
+ if (!(monitor = cc_extension_monitor_init(S_OR(ast_channel_macroexten(chan), ast_channel_exten(chan)),
+ S_OR(ast_channel_macrocontext(chan), ast_channel_context(chan)), interfaces->dial_parent_id))) {
return -1;
}
monitor->core_id = interfaces->core_id;
static void kill_duplicate_offers(char *caller)
{
unsigned long match_flags = MATCH_NO_REQUEST;
- ao2_t_callback_data(cc_core_instances, OBJ_UNLINK | OBJ_NODATA, match_agent, caller, &match_flags, "Killing duplicate offers");
+ struct ao2_iterator *dups_iter;
+
+ /*
+ * Must remove the ref that was in cc_core_instances outside of
+ * the container lock to prevent deadlock.
+ */
+ dups_iter = ao2_t_callback_data(cc_core_instances, OBJ_MULTIPLE | OBJ_UNLINK,
+ match_agent, caller, &match_flags, "Killing duplicate offers");
+ if (dups_iter) {
+ /* Now actually unref any duplicate offers by simply destroying the iterator. */
+ ao2_iterator_destroy(dups_iter);
+ }
}
static void check_callback_sanity(const struct ast_cc_agent_callbacks *callbacks)
ast_assert(callbacks->init != NULL);
ast_assert(callbacks->start_offer_timer != NULL);
ast_assert(callbacks->stop_offer_timer != NULL);
- ast_assert(callbacks->ack != NULL);
+ ast_assert(callbacks->respond != NULL);
ast_assert(callbacks->status_request != NULL);
ast_assert(callbacks->start_monitoring != NULL);
ast_assert(callbacks->callee_available != NULL);
cc_unref(agent, "Agent init callback failed.");
return NULL;
}
- ast_log_dynamic_level(cc_logger_level, "Core %d: Created an agent for caller %s\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: Created an agent for caller %s\n",
agent->core_id, agent->device_name);
return agent;
}
static int cc_generic_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan);
static int cc_generic_agent_start_offer_timer(struct ast_cc_agent *agent);
static int cc_generic_agent_stop_offer_timer(struct ast_cc_agent *agent);
-static void cc_generic_agent_ack(struct ast_cc_agent *agent);
+static void cc_generic_agent_respond(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason);
static int cc_generic_agent_status_request(struct ast_cc_agent *agent);
static int cc_generic_agent_stop_ringing(struct ast_cc_agent *agent);
static int cc_generic_agent_start_monitoring(struct ast_cc_agent *agent);
.init = cc_generic_agent_init,
.start_offer_timer = cc_generic_agent_start_offer_timer,
.stop_offer_timer = cc_generic_agent_stop_offer_timer,
- .ack = cc_generic_agent_ack,
+ .respond = cc_generic_agent_respond,
.status_request = cc_generic_agent_status_request,
.stop_ringing = cc_generic_agent_stop_ringing,
.start_monitoring = cc_generic_agent_start_monitoring,
* device state of the caller in order to
* determine when we may move on
*/
- struct ast_event_sub *sub;
+ struct stasis_subscription *sub;
/*!
* Scheduler id of offer timer.
*/
}
generic_pvt->offer_timer_id = -1;
- if (chan->caller.id.number.valid && chan->caller.id.number.str) {
- ast_copy_string(generic_pvt->cid_num, chan->caller.id.number.str, sizeof(generic_pvt->cid_num));
+ if (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str) {
+ ast_copy_string(generic_pvt->cid_num, ast_channel_caller(chan)->id.number.str, sizeof(generic_pvt->cid_num));
}
- if (chan->caller.id.name.valid && chan->caller.id.name.str) {
- ast_copy_string(generic_pvt->cid_name, chan->caller.id.name.str, sizeof(generic_pvt->cid_name));
+ if (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str) {
+ ast_copy_string(generic_pvt->cid_name, ast_channel_caller(chan)->id.name.str, sizeof(generic_pvt->cid_name));
}
- ast_copy_string(generic_pvt->exten, S_OR(chan->macroexten, chan->exten), sizeof(generic_pvt->exten));
- ast_copy_string(generic_pvt->context, S_OR(chan->macrocontext, chan->context), sizeof(generic_pvt->context));
+ ast_copy_string(generic_pvt->exten, S_OR(ast_channel_macroexten(chan), ast_channel_exten(chan)), sizeof(generic_pvt->exten));
+ ast_copy_string(generic_pvt->context, S_OR(ast_channel_macrocontext(chan), ast_channel_context(chan)), sizeof(generic_pvt->context));
agent->private_data = generic_pvt;
ast_set_flag(agent, AST_CC_AGENT_SKIP_OFFER);
return 0;
{
struct ast_cc_agent *agent = (struct ast_cc_agent *) data;
struct cc_generic_agent_pvt *agent_pvt = agent->private_data;
- ast_log_dynamic_level(cc_logger_level, "Core %d: Queuing change request because offer timer has expired.\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: Queuing change request because offer timer has expired.\n",
agent->core_id);
agent_pvt->offer_timer_id = -1;
ast_cc_failed(agent->core_id, "Generic agent %s offer timer expired", agent->device_name);
ast_assert(agent->cc_params != NULL);
when = ast_get_cc_offer_timer(agent->cc_params) * 1000;
- ast_log_dynamic_level(cc_logger_level, "Core %d: About to schedule offer timer expiration for %d ms\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: About to schedule offer timer expiration for %d ms\n",
agent->core_id, when);
if ((sched_id = ast_sched_add(cc_sched_context, when, offer_timer_expire, cc_ref(agent, "Give scheduler an agent ref"))) == -1) {
return -1;
return 0;
}
-static void cc_generic_agent_ack(struct ast_cc_agent *agent)
+static void cc_generic_agent_respond(struct ast_cc_agent *agent, enum ast_cc_agent_response_reason reason)
{
/* The generic agent doesn't have to do anything special to
* acknowledge a CC request. Just return.
return 0;
}
-static int generic_agent_devstate_unsubscribe(void *data)
+static void generic_agent_devstate_cb(void *userdata, struct stasis_subscription *sub, struct stasis_message *msg)
{
- struct ast_cc_agent *agent = data;
+ struct ast_cc_agent *agent = userdata;
+ enum ast_device_state new_state;
+ struct ast_device_state_message *dev_state;
struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
- if (generic_pvt->sub != NULL) {
- generic_pvt->sub = ast_event_unsubscribe(generic_pvt->sub);
+ if (stasis_subscription_final_message(sub, msg)) {
+ cc_unref(agent, "Done holding ref for subscription");
+ return;
+ } else if (ast_device_state_message_type() != stasis_message_type(msg)) {
+ return;
}
- cc_unref(agent, "Done unsubscribing from devstate");
- return 0;
-}
-static void generic_agent_devstate_cb(const struct ast_event *event, void *userdata)
-{
- struct ast_cc_agent *agent = userdata;
+ dev_state = stasis_message_data(msg);
+ if (dev_state->eid) {
+ /* ignore non-aggregate states */
+ return;
+ }
- /* We can't unsubscribe from device state events here because it causes a deadlock */
- if (ast_taskprocessor_push(cc_core_taskprocessor, generic_agent_devstate_unsubscribe,
- cc_ref(agent, "ref agent for device state unsubscription"))) {
- cc_unref(agent, "Unref agent unsubscribing from devstate failed");
+ new_state = dev_state->state;
+ if (!cc_generic_is_device_available(new_state)) {
+ /* Not interested in this new state of the device. It is still busy. */
+ return;
}
+
+ generic_pvt->sub = stasis_unsubscribe(sub);
ast_cc_agent_caller_available(agent->core_id, "%s is no longer busy", agent->device_name);
}
{
struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
struct ast_str *str = ast_str_alloca(128);
+ struct stasis_topic *device_specific_topic;
ast_assert(generic_pvt->sub == NULL);
- ast_str_set(&str, 0, "Starting to monitor %s device state since it is busy\n", agent->device_name);
+ ast_str_set(&str, 0, "Agent monitoring %s device state since it is busy\n",
+ agent->device_name);
- if (!(generic_pvt->sub = ast_event_subscribe(
- AST_EVENT_DEVICE_STATE, generic_agent_devstate_cb, ast_str_buffer(str), agent,
- AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, agent->device_name,
- AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, AST_DEVICE_NOT_INUSE,
- AST_EVENT_IE_END))) {
+ device_specific_topic = ast_device_state_topic(agent->device_name);
+ if (!device_specific_topic) {
return -1;
}
+
+ if (!(generic_pvt->sub = stasis_subscribe(device_specific_topic, generic_agent_devstate_cb, agent))) {
+ return -1;
+ }
+ cc_ref(agent, "Ref agent for subscription");
return 0;
}
int reason;
struct ast_channel *chan;
const char *callback_macro = ast_get_cc_callback_macro(agent->cc_params);
+ const char *callback_sub = ast_get_cc_callback_sub(agent->cc_params);
unsigned int recall_timer = ast_get_cc_recall_timer(agent->cc_params) * 1000;
- struct ast_format tmp_fmt;
- struct ast_format_cap *tmp_cap = ast_format_cap_alloc_nolock();
+ struct ast_format_cap *tmp_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (!tmp_cap) {
return NULL;
*target++ = '\0';
}
- ast_format_cap_add(tmp_cap, ast_format_set(&tmp_fmt, AST_FORMAT_SLINEAR, 0));
- if (!(chan = ast_request_and_dial(tech, tmp_cap, NULL, target, recall_timer, &reason, generic_pvt->cid_num, generic_pvt->cid_name))) {
+ ast_format_cap_append(tmp_cap, ast_format_slin, 0);
+ if (!(chan = ast_request_and_dial(tech, tmp_cap, NULL, NULL, target, recall_timer, &reason, generic_pvt->cid_num, generic_pvt->cid_name))) {
/* Hmm, no channel. Sucks for you, bud.
*/
- ast_log_dynamic_level(cc_logger_level, "Core %d: Failed to call back %s for reason %d\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: Failed to call back %s for reason %d\n",
agent->core_id, agent->device_name, reason);
ast_cc_failed(agent->core_id, "Failed to call back device %s/%s", tech, target);
- ast_format_cap_destroy(tmp_cap);
+ ao2_ref(tmp_cap, -1);
return NULL;
}
- ast_format_cap_destroy(tmp_cap);
-
+ ao2_ref(tmp_cap, -1);
+
/* We have a channel. It's time now to set up the datastore of recalled CC interfaces.
* This will be a common task for all recall functions. If it were possible, I'd have
* the core do it automatically, but alas I cannot. Instead, I will provide a public
ast_setup_cc_recall_datastore(chan, agent->core_id);
ast_cc_agent_set_interfaces_chanvar(chan);
- ast_copy_string(chan->exten, generic_pvt->exten, sizeof(chan->exten));
- ast_copy_string(chan->context, generic_pvt->context, sizeof(chan->context));
- chan->priority = 1;
+ ast_channel_exten_set(chan, generic_pvt->exten);
+ ast_channel_context_set(chan, generic_pvt->context);
+ ast_channel_priority_set(chan, 1);
+
+ pbx_builtin_setvar_helper(chan, "CC_EXTEN", generic_pvt->exten);
+ pbx_builtin_setvar_helper(chan, "CC_CONTEXT", generic_pvt->context);
if (!ast_strlen_zero(callback_macro)) {
- ast_log_dynamic_level(cc_logger_level, "Core %d: There's a callback macro configured for agent %s\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: There's a callback macro configured for agent %s\n",
agent->core_id, agent->device_name);
- if (ast_app_run_macro(NULL, chan, callback_macro, NULL)) {
+ if (ast_app_exec_macro(NULL, chan, callback_macro)) {
ast_cc_failed(agent->core_id, "Callback macro to %s failed. Maybe a hangup?", agent->device_name);
ast_hangup(chan);
return NULL;
}
}
- ast_cc_agent_recalling(agent->core_id, "Generic agent %s is recalling", agent->device_name);
- ast_pbx_start(chan);
+
+ if (!ast_strlen_zero(callback_sub)) {
+ ast_log_dynamic_level(cc_logger_level, "Core %u: There's a callback subroutine configured for agent %s\n",
+ agent->core_id, agent->device_name);
+ if (ast_app_exec_sub(NULL, chan, callback_sub, 0)) {
+ ast_cc_failed(agent->core_id, "Callback subroutine to %s failed. Maybe a hangup?", agent->device_name);
+ ast_hangup(chan);
+ return NULL;
+ }
+ }
+ if (ast_pbx_start(chan)) {
+ ast_cc_failed(agent->core_id, "PBX failed to start for %s.", agent->device_name);
+ ast_hangup(chan);
+ return NULL;
+ }
+ ast_cc_agent_recalling(agent->core_id, "Generic agent %s is recalling",
+ agent->device_name);
return NULL;
}
pthread_t clotho;
enum ast_device_state current_state = ast_device_state(agent->device_name);
- if (current_state != AST_DEVICE_NOT_INUSE && current_state != AST_DEVICE_UNKNOWN) {
+ if (!cc_generic_is_device_available(current_state)) {
/* We can't try to contact the device right now because he's not available
* Let the core know he's busy.
*/
cc_generic_agent_stop_offer_timer(agent);
if (agent_pvt->sub) {
- agent_pvt->sub = ast_event_unsubscribe(agent_pvt->sub);
+ agent_pvt->sub = stasis_unsubscribe(agent_pvt->sub);
}
ast_free(agent_pvt);
}
struct cc_state_change_args {
+ struct cc_core_instance *core_instance;/*!< Holds reference to core instance. */
enum cc_state state;
int core_id;
char debug[1];
int is_valid = 0;
switch (new_state) {
case CC_AVAILABLE:
- ast_log_dynamic_level(cc_logger_level, "Core %d: Asked to change to state %d? That should never happen.\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: Asked to change to state %u? That should never happen.\n",
agent->core_id, new_state);
break;
case CC_CALLER_OFFERED:
is_valid = 1;
break;
default:
- ast_log_dynamic_level(cc_logger_level, "Core %d: Asked to change to unknown state %d\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %u: Asked to change to unknown state %u\n",
agent->core_id, new_state);
break;
}
core_instance->agent->device_name);
return -1;
}
- manager_event(EVENT_FLAG_CC, "CCOfferTimerStart",
- "CoreID: %d\r\n"
- "Caller: %s\r\n"
- "Expires: %u\r\n",
- core_instance->core_id, core_instance->agent->device_name, core_instance->agent->cc_params->cc_offer_timer);
+ cc_publish_offertimerstart(core_instance->core_id, core_instance->agent->device_name, core_instance->agent->cc_params->cc_offer_timer);
ast_log_dynamic_level(cc_logger_level, "Core %d: Started the offer timer for the agent %s!\n",
core_instance->core_id, core_instance->agent->device_name);
return 0;
monitor_iter->interface->device_name, 1);
cc_unref(monitor_iter, "request_cc failed. Unref list's reference to monitor");
} else {
- manager_event(EVENT_FLAG_CC, "CCRequested",
- "CoreID: %d\r\n"
- "Caller: %s\r\n"
- "Callee: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name, monitor_iter->interface->device_name);
+ cc_publish_requested(core_instance->core_id, core_instance->agent->device_name, monitor_iter->interface->device_name);
}
}
}
{
if (!ast_cc_request_is_within_limits()) {
ast_log(LOG_WARNING, "Cannot request CC since there is no more room for requests\n");
+ core_instance->agent->callbacks->respond(core_instance->agent,
+ AST_CC_AGENT_RESPONSE_FAILURE_TOO_MANY);
ast_cc_failed(core_instance->core_id, "Too many requests in the system");
return -1;
}
* call monitor's unsuspend callback.
*/
if (previous_state == CC_CALLER_REQUESTED) {
- core_instance->agent->callbacks->ack(core_instance->agent);
- manager_event(EVENT_FLAG_CC, "CCRequestAcknowledged",
- "CoreID: %d\r\n"
- "Caller: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name);
+ core_instance->agent->callbacks->respond(core_instance->agent,
+ AST_CC_AGENT_RESPONSE_SUCCESS);
+ cc_publish_requestacknowledged(core_instance->core_id, core_instance->agent->device_name);
} else if (previous_state == CC_CALLER_BUSY) {
- manager_event(EVENT_FLAG_CC, "CCCallerStopMonitoring",
- "CoreID: %d\r\n"
- "Caller: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name);
+ cc_publish_callerstopmonitoring(core_instance->core_id, core_instance->agent->device_name);
unsuspend(core_instance);
}
/* Not possible for previous_state to be anything else due to the is_state_change_valid check at the beginning */
*/
suspend(core_instance);
core_instance->agent->callbacks->start_monitoring(core_instance->agent);
- manager_event(EVENT_FLAG_CC, "CCCallerStartMonitoring",
- "CoreID: %d\r\n"
- "Caller: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name);
+ cc_publish_callerstartmonitoring(core_instance->core_id, core_instance->agent->device_name);
return 0;
}
/* Both caller and callee are available, call agent's recall callback
*/
cancel_available_timer(core_instance);
- manager_event(EVENT_FLAG_CC, "CCCallerRecalling",
- "CoreID: %d\r\n"
- "Caller: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name);
+ cc_publish_callerrecalling(core_instance->core_id, core_instance->agent->device_name);
return 0;
}
{
/* Recall has made progress, call agent and monitor destructor functions
*/
- manager_event(EVENT_FLAG_CC, "CCRecallComplete",
- "CoreID: %d\r\n"
- "Caller: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name);
+ cc_publish_recallcomplete(core_instance->core_id, core_instance->agent->device_name);
ao2_t_unlink(cc_core_instances, core_instance, "Unlink core instance since CC recall has completed");
return 0;
}
static int cc_failed(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
{
- manager_event(EVENT_FLAG_CC, "CCFailure",
- "CoreID: %d\r\n"
- "Caller: %s\r\n"
- "Reason: %s\r\n",
- core_instance->core_id, core_instance->agent->device_name, args->debug);
+ cc_publish_failure(core_instance->core_id, core_instance->agent->device_name, args->debug);
ao2_t_unlink(cc_core_instances, core_instance, "Unlink core instance since CC failed");
return 0;
}
enum cc_state previous_state;
int res;
- ast_log_dynamic_level(cc_logger_level, "Core %d: State change to %d requested. Reason: %s\n",
+ ast_log_dynamic_level(cc_logger_level, "Core %d: State change to %u requested. Reason: %s\n",
args->core_id, args->state, args->debug);
- if (!(core_instance = find_cc_core_instance(args->core_id))) {
- ast_log_dynamic_level(cc_logger_level, "Core %d: Unable to find core instance.\n", args->core_id);
- ast_free(args);
- return -1;
- }
+ core_instance = args->core_instance;
if (!is_state_change_valid(core_instance->current_state, args->state, core_instance->agent)) {
ast_log_dynamic_level(cc_logger_level, "Core %d: Invalid state change requested. Cannot go from %s to %s\n",
args->core_id, cc_state_to_string(core_instance->current_state), cc_state_to_string(args->state));
+ if (args->state == CC_CALLER_REQUESTED) {
+ /*
+ * For out-of-order requests, we need to let the requester know that
+ * we can't handle the request now.
+ */
+ core_instance->agent->callbacks->respond(core_instance->agent,
+ AST_CC_AGENT_RESPONSE_FAILURE_INVALID);
+ }
ast_free(args);
cc_unref(core_instance, "Unref core instance from when it was found earlier");
return -1;
core_instance->current_state = args->state;
res = state_change_funcs[core_instance->current_state](core_instance, args, previous_state);
+ /* If state change successful then notify any device state watchers of the change */
+ if (!res && !strcmp(core_instance->agent->callbacks->type, "generic")) {
+ ccss_notify_device_state_change(core_instance->agent->device_name, core_instance->current_state);
+ }
+
ast_free(args);
cc_unref(core_instance, "Unref since state change has completed"); /* From ao2_find */
return res;
int debuglen;
char dummy[1];
va_list aq;
+ struct cc_core_instance *core_instance;
struct cc_state_change_args *args;
/* This initial call to vsnprintf is simply to find what the
* size of the string needs to be
return -1;
}
+ core_instance = find_cc_core_instance(core_id);
+ if (!core_instance) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Unable to find core instance.\n",
+ core_id);
+ ast_free(args);
+ return -1;
+ }
+
+ args->core_instance = core_instance;
args->state = state;
args->core_id = core_id;
vsnprintf(args->debug, debuglen, debug, ap);
res = ast_taskprocessor_push(cc_core_taskprocessor, cc_do_state_change, args);
if (res) {
+ cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
ast_free(args);
}
return res;
ast_free(recall_data);
}
-static struct ast_datastore_info recall_ds_info = {
+static const struct ast_datastore_info recall_ds_info = {
.type = "cc_recall",
.duplicate = cc_recall_ds_duplicate,
.destroy = cc_recall_ds_destroy,
* \param dialstring A new dialstring to add
* \retval void
*/
-static void cc_unique_append(struct ast_str *str, const char *dialstring)
+static void cc_unique_append(struct ast_str **str, const char *dialstring)
{
- char dialstring_search[AST_CHANNEL_NAME];
+ char dialstring_search[AST_CHANNEL_NAME + 1];
if (ast_strlen_zero(dialstring)) {
/* No dialstring to append. */
return;
}
snprintf(dialstring_search, sizeof(dialstring_search), "%s%c", dialstring, '&');
- if (strstr(ast_str_buffer(str), dialstring_search)) {
+ if (strstr(ast_str_buffer(*str), dialstring_search)) {
return;
}
- ast_str_append(&str, 0, "%s", dialstring_search);
+ ast_str_append(str, 0, "%s", dialstring_search);
}
/*!
* \param str Where we will store CC_INTERFACES
* \retval void
*/
-static void build_cc_interfaces_chanvar(struct ast_cc_monitor *starting_point, struct ast_str *str)
+static void build_cc_interfaces_chanvar(struct ast_cc_monitor *starting_point, struct ast_str **str)
{
struct extension_monitor_pvt *extension_pvt;
struct extension_child_dialstring *child_dialstring;
size_t length;
/* Init to an empty string. */
- ast_str_truncate(str, 0);
+ ast_str_truncate(*str, 0);
/* First we need to take all of the is_valid child_dialstrings from
* the extension monitor we found and add them to the CC_INTERFACES
/* str will have an extra '&' tacked onto the end of it, so we need
* to get rid of that.
*/
- length = ast_str_strlen(str);
+ length = ast_str_strlen(*str);
if (length) {
- ast_str_truncate(str, length - 1);
+ ast_str_truncate(*str, length - 1);
}
if (length <= 1) {
/* Nothing to recall? This should not happen. */
AST_LIST_LOCK(interface_tree);
monitor = AST_LIST_FIRST(interface_tree);
- build_cc_interfaces_chanvar(monitor, str);
+ build_cc_interfaces_chanvar(monitor, &str);
AST_LIST_UNLOCK(interface_tree);
pbx_builtin_setvar_helper(chan, "CC_INTERFACES", ast_str_buffer(str));
return -1;
}
- build_cc_interfaces_chanvar(monitor_iter, str);
+ build_cc_interfaces_chanvar(monitor_iter, &str);
AST_LIST_UNLOCK(interface_tree);
pbx_builtin_setvar_helper(chan, "CC_INTERFACES", ast_str_buffer(str));
ast_channel_unlock(caller_chan);
if (cc_is_offerable) {
- res = cc_offer(core_id, "CC offered to caller %s", caller_chan->name);
+ res = cc_offer(core_id, "CC offered to caller %s", ast_channel_name(caller_chan));
}
return res;
}
cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
monitor_iter->interface->device_name, 1);
monitor_iter->callbacks->cancel_available_timer(monitor_iter, &monitor_iter->available_timer_id);
- manager_event(EVENT_FLAG_CC, "CCMonitorFailed",
- "CoreID: %d\r\n"
- "Callee: %s\r\n",
- monitor_iter->core_id, monitor_iter->interface->device_name);
+ cc_publish_monitorfailed(monitor_iter->core_id, monitor_iter->interface->device_name);
cc_unref(monitor_iter, "Monitor reported failure. Unref list's reference.");
}
}
struct cc_control_payload payload;
struct ast_cc_config_params *cc_params;
- if (outgoing->hangupcause != AST_CAUSE_BUSY && outgoing->hangupcause != AST_CAUSE_CONGESTION) {
+ if (ast_channel_hangupcause(outgoing) != AST_CAUSE_BUSY && ast_channel_hangupcause(outgoing) != AST_CAUSE_CONGESTION) {
/* It doesn't make sense to try to offer CCBS to the caller if the reason for ast_call
* failing is something other than busy or congestion
*/
match_flags = MATCH_NO_REQUEST;
if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionRequest"))) {
ast_log_dynamic_level(cc_logger_level, "Couldn't find a core instance for caller %s\n", device_name);
- return -1;
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL");
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "NO_CORE_INSTANCE");
+ return 0;
}
ast_log_dynamic_level(cc_logger_level, "Core %d: Found core_instance for caller %s\n",
ast_log_dynamic_level(cc_logger_level, "Core %d: CallCompletionRequest is only for generic agent types.\n",
core_instance->core_id);
pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL");
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "NOT_GENERIC");
cc_unref(core_instance, "Unref core_instance since CallCompletionRequest was called with native agent");
return 0;
}
core_instance->core_id);
ast_cc_failed(core_instance->core_id, "Too many CC requests\n");
pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL");
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "TOO_MANY_REQUESTS");
cc_unref(core_instance, "Unref core_instance since too many CC requests");
return 0;
}
res = ast_cc_agent_accept_request(core_instance->core_id, "CallCompletionRequest called by caller %s for core_id %d", device_name, core_instance->core_id);
pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", res ? "FAIL" : "SUCCESS");
+ if (res) {
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "UNSPECIFIED");
+ }
+
cc_unref(core_instance, "Done with CallCompletionRequest");
- return res;
+ return 0;
}
static const char *cccancel_app = "CallCompletionCancel";
match_flags = MATCH_REQUEST;
if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionCancel"))) {
- ast_log(LOG_WARNING, "Cannot find CC transaction to cancel for caller %s\n", device_name);
- return -1;
+ ast_log_dynamic_level(cc_logger_level, "Cannot find CC transaction to cancel for caller %s\n", device_name);
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", "FAIL");
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "NO_CORE_INSTANCE");
+ return 0;
}
if (strcmp(core_instance->agent->callbacks->type, "generic")) {
ast_log(LOG_WARNING, "CallCompletionCancel may only be used for calles with a generic agent\n");
cc_unref(core_instance, "Unref core instance found during CallCompletionCancel");
- return -1;
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", "FAIL");
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "NOT_GENERIC");
+ return 0;
}
res = ast_cc_failed(core_instance->core_id, "Call completion request Cancelled for core ID %d by caller %s",
core_instance->core_id, device_name);
cc_unref(core_instance, "Unref core instance found during CallCompletionCancel");
- return res;
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", res ? "FAIL" : "SUCCESS");
+ if (res) {
+ pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "UNSPECIFIED");
+ }
+ return 0;
}
struct count_monitors_cb_data {
return;
}
+/*!
+ * \internal
+ * \brief helper function to parse and configure each devstate map
+ */
+static void initialize_cc_devstate_map_helper(struct ast_config *cc_config, enum cc_state state, const char *cc_setting)
+{
+ const char *cc_devstate_str;
+ enum ast_device_state this_devstate;
+
+ if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", cc_setting))) {
+ this_devstate = ast_devstate_val(cc_devstate_str);
+ if (this_devstate != AST_DEVICE_UNKNOWN) {
+ cc_state_to_devstate_map[state] = this_devstate;
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief initializes cc_state_to_devstate_map from ccss.conf
+ *
+ * \details
+ * The cc_state_to_devstate_map[] is already initialized with all the
+ * default values. This will update that structure with any changes
+ * from the ccss.conf file. The configuration parameters in ccss.conf
+ * should use any valid device state form that is recognized by
+ * ast_devstate_val() function.
+ */
+static void initialize_cc_devstate_map(void)
+{
+ struct ast_config *cc_config;
+ struct ast_flags config_flags = { 0, };
+
+ cc_config = ast_config_load2("ccss.conf", "ccss", config_flags);
+ if (!cc_config || cc_config == CONFIG_STATUS_FILEINVALID) {
+ ast_log(LOG_WARNING,
+ "Could not find valid ccss.conf file. Using cc_[state]_devstate defaults\n");
+ return;
+ }
+
+ initialize_cc_devstate_map_helper(cc_config, CC_AVAILABLE, "cc_available_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_CALLER_OFFERED, "cc_caller_offered_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_CALLER_REQUESTED, "cc_caller_requested_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_ACTIVE, "cc_active_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_CALLEE_READY, "cc_callee_ready_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_CALLER_BUSY, "cc_caller_busy_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_RECALLING, "cc_recalling_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_COMPLETE, "cc_complete_devstate");
+ initialize_cc_devstate_map_helper(cc_config, CC_FAILED, "cc_failed_devstate");
+
+ ast_config_destroy(cc_config);
+}
+
static void cc_cli_print_monitor_stats(struct ast_cc_monitor *monitor, int fd, int parent_id)
{
struct ast_cc_monitor *child_monitor_iter = monitor;
static char *handle_cc_kill(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- static const char * const option[] = { "core", "all", NULL };
-
switch (cmd) {
case CLI_INIT:
- e->command = "cc cancel";
+ e->command = "cc cancel [core|all]";
e->usage =
"Usage: cc cancel can be used in two ways.\n"
" 1. 'cc cancel core [core ID]' will cancel the CC transaction with\n"
" 2. 'cc cancel all' will cancel all active CC transactions.\n";
return NULL;
case CLI_GENERATE:
- if (a->pos == 2) {
- return ast_cli_complete(a->word, option, a->n);
- }
- if (a->pos == 3) {
+ if (a->pos == 3 && !strcasecmp(a->argv[2], "core")) {
return complete_core_id(a->line, a->word, a->pos, a->n);
}
return NULL;
AST_CLI_DEFINE(handle_cc_kill, "Kill a CC transaction"),
};
+static void cc_shutdown(void)
+{
+ ast_devstate_prov_del("ccss");
+ ast_cc_agent_unregister(&generic_agent_callbacks);
+ ast_cc_monitor_unregister(&generic_monitor_cbs);
+ ast_unregister_application(cccancel_app);
+ ast_unregister_application(ccreq_app);
+ ast_logger_unregister_level(CC_LOGGER_LEVEL_NAME);
+ ast_cli_unregister_multiple(cc_cli, ARRAY_LEN(cc_cli));
+
+ if (cc_sched_context) {
+ ast_sched_context_destroy(cc_sched_context);
+ cc_sched_context = NULL;
+ }
+ if (cc_core_taskprocessor) {
+ cc_core_taskprocessor = ast_taskprocessor_unreference(cc_core_taskprocessor);
+ }
+ /* Note that core instances must be destroyed prior to the generic_monitors */
+ if (cc_core_instances) {
+ ao2_t_ref(cc_core_instances, -1, "Unref cc_core_instances container in cc_shutdown");
+ cc_core_instances = NULL;
+ }
+ if (generic_monitors) {
+ ao2_t_ref(generic_monitors, -1, "Unref generic_monitor container in cc_shutdown");
+ generic_monitors = NULL;
+ }
+}
+
int ast_cc_init(void)
{
int res;
return -1;
}
if (!(generic_monitors = ao2_t_container_alloc(CC_CORE_INSTANCES_BUCKETS,
- generic_monitor_hash_fn, generic_monitor_cmp_fn,
- "Create generic monitor container"))) {
+ generic_monitor_instance_list_hash_fn, generic_monitor_instance_list_cmp_fn,
+ "Create generic monitor container"))) {
return -1;
}
- if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS core", TPS_REF_DEFAULT))) {
+ if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS_core", TPS_REF_DEFAULT))) {
return -1;
}
if (!(cc_sched_context = ast_sched_context_create())) {
res |= ast_register_application2(cccancel_app, cccancel_exec, NULL, NULL, NULL);
res |= ast_cc_monitor_register(&generic_monitor_cbs);
res |= ast_cc_agent_register(&generic_agent_callbacks);
+
ast_cli_register_multiple(cc_cli, ARRAY_LEN(cc_cli));
cc_logger_level = ast_logger_register_level(CC_LOGGER_LEVEL_NAME);
dialed_cc_interface_counter = 1;
initialize_cc_max_requests();
+
+ /* Read the map and register the device state callback for generic agents */
+ initialize_cc_devstate_map();
+ res |= ast_devstate_prov_add("ccss", ccss_device_state);
+
+ ast_register_cleanup(cc_shutdown);
+
return res;
}