Replace most uses of ast_register_atexit with ast_register_cleanup.
[asterisk/asterisk.git] / main / ccss.c
index 4dcacd3..7f63690 100644 (file)
  * \author Mark Michelson <mmichelson@digium.com>
  */
 
+/*! \li \ref ccss.c uses the configuration file \ref ccss.conf
+ * \addtogroup configuration_file Configuration Files
+ */
+
+/*!
+ * \page ccss.conf ccss.conf
+ * \verbinclude ccss.conf.sample
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
 #include "asterisk.h"
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
@@ -32,12 +45,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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">
@@ -48,6 +63,21 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <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">
@@ -57,6 +87,20 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <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>
  ***/
@@ -68,9 +112,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  */
 
 /*!
- * The sched_thread ID used for all generic CC timeouts
+ * The ast_sched_context used for all generic CC timeouts
  */
-static struct ast_sched_thread *cc_sched_thread;
+static struct ast_sched_context *cc_sched_context;
 /*!
  * Counter used to create core IDs for CC calls. Each new
  * core ID is created by atomically adding 1 to the core_id_counter
@@ -126,6 +170,7 @@ struct ast_cc_config_params {
        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];
 };
 
@@ -501,6 +546,113 @@ static int count_agents_cb(void *obj, void *arg, void *data, int flags)
        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 */
@@ -519,6 +671,7 @@ static const struct ast_cc_config_params cc_default_params = {
        .cc_max_agents = CC_MAX_AGENTS_DEFAULT,
        .cc_max_monitors = CC_MAX_MONITORS_DEFAULT,
        .cc_callback_macro = "",
+       .cc_callback_sub = "",
        .cc_agent_dialstring = "",
 };
 
@@ -613,8 +766,11 @@ int ast_cc_get_param(struct ast_cc_config_params *params, const char * const nam
                char *buf, size_t buf_len)
 {
        const char *value = 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")) {
@@ -622,8 +778,7 @@ int ast_cc_get_param(struct ast_cc_config_params *params, const char * const nam
        } else if (!strcasecmp(name, "cc_agent_dialstring")) {
                value = ast_get_cc_agent_dialstring(params);
        }
-
-       if (!ast_strlen_zero(value)) {
+       if (value) {
                ast_copy_string(buf, value, buf_len);
                return 0;
        }
@@ -665,6 +820,9 @@ int ast_cc_set_param(struct ast_cc_config_params *params, const char * const nam
        } 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) {
@@ -701,6 +859,7 @@ int ast_cc_is_config_param(const char * const name)
                                !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"));
 }
@@ -843,8 +1002,14 @@ const char *ast_get_cc_callback_macro(struct ast_cc_config_params *config)
        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 {
@@ -852,6 +1017,152 @@ void ast_set_cc_callback_macro(struct ast_cc_config_params *config, const char *
        }
 }
 
+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)
+{
+       RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+       RAII_VAR(struct ast_json_payload *, payload, NULL, ao2_cleanup);
+       RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+       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);
+       }
+
+       if (!(payload = ast_json_payload_create(blob))) {
+               return -1;
+       }
+
+       if (!(message = stasis_message_create(message_type, payload))) {
+               return -1;
+       }
+
+       stasis_publish(ast_system_topic(), message);
+
+       return 0;
+}
+
+static void cc_publish_available(int core_id, const char *callee, const char *service)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s, s: s}",
+               "callee", callee,
+               "service", service);
+
+       cc_publish(ast_cc_available_type(), core_id, extras);
+}
+
+static void cc_publish_offertimerstart(int core_id, const char *caller, unsigned int expires)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s, s: i}",
+               "caller", caller,
+               "expires", expires);
+
+       cc_publish(ast_cc_offertimerstart_type(), core_id, extras);
+}
+
+static void cc_publish_requested(int core_id, const char *caller, const char *callee)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s, s: s}",
+               "caller", caller,
+               "callee", callee);
+
+       cc_publish(ast_cc_requested_type(), core_id, extras);
+}
+
+static void cc_publish_requestacknowledged(int core_id, const char *caller)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "caller", caller);
+
+       cc_publish(ast_cc_requestacknowledged_type(), core_id, extras);
+}
+
+static void cc_publish_callerstopmonitoring(int core_id, const char *caller)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "caller", caller);
+
+       cc_publish(ast_cc_callerstopmonitoring_type(), core_id, extras);
+}
+
+static void cc_publish_callerstartmonitoring(int core_id, const char *caller)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "caller", caller);
+
+       cc_publish(ast_cc_callerstartmonitoring_type(), core_id, extras);
+}
+
+static void cc_publish_callerrecalling(int core_id, const char *caller)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "caller", caller);
+
+       cc_publish(ast_cc_callerrecalling_type(), core_id, extras);
+}
+
+static void cc_publish_recallcomplete(int core_id, const char *caller)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "caller", caller);
+
+       cc_publish(ast_cc_recallcomplete_type(), core_id, extras);
+}
+
+static void cc_publish_failure(int core_id, const char *caller, const char *reason)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s, s: s}",
+               "caller", caller,
+               "reason", reason);
+
+       cc_publish(ast_cc_failure_type(), core_id, extras);
+}
+
+static void cc_publish_monitorfailed(int core_id, const char *callee)
+{
+       RAII_VAR(struct ast_json *, extras, NULL, ast_json_unref);
+
+       extras = ast_json_pack("{s: s}",
+               "callee", callee);
+
+       cc_publish(ast_cc_monitorfailed_type(), core_id, extras);
+}
+
 struct cc_monitor_backend {
        AST_LIST_ENTRY(cc_monitor_backend) next;
        const struct ast_cc_monitor_callbacks *callbacks;
@@ -979,6 +1290,20 @@ static const struct ast_cc_agent_callbacks *find_agent_callbacks(struct ast_chan
        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);
@@ -1022,7 +1347,7 @@ struct generic_monitor_instance_list {
         * recalled
         */
        int fit_for_recall;
-       struct ast_event_sub *sub;
+       struct stasis_subscription *sub;
        AST_LIST_HEAD_NOLOCK(, generic_monitor_instance) list;
 };
 
@@ -1060,7 +1385,10 @@ static int generic_monitor_cmp_fn(void *obj, void *arg, int flags)
 
 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");
 }
@@ -1070,31 +1398,38 @@ static void generic_monitor_instance_list_destructor(void *obj)
        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;
 
-       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))) {
+       device_specific_topic = ast_device_state_topic(device_name);
+       if (!device_specific_topic) {
+               return NULL;
+       }
+
+       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;
        }
@@ -1103,42 +1438,32 @@ static struct generic_monitor_instance_list *create_new_generic_list(struct ast_
        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) {
@@ -1151,33 +1476,31 @@ static int generic_monitor_devstate_tp_cb(void *data)
                }
        }
        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;
        }
 }
 
@@ -1235,7 +1558,7 @@ static int cc_generic_monitor_request_cc(struct ast_cc_monitor *monitor, int *av
        when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
                ast_get_ccnr_available_timer(monitor->interface->config_params);
 
-       *available_timer_id = ast_sched_thread_add(cc_sched_thread, when * 1000,
+       *available_timer_id = ast_sched_add(cc_sched_context, when * 1000,
                        ast_cc_available_timer_expire, cc_ref(monitor, "Give the scheduler a monitor reference"));
        if (*available_timer_id == -1) {
                cc_unref(monitor, "Failed to schedule available timer. (monitor)");
@@ -1275,7 +1598,7 @@ static int cc_generic_monitor_suspend(struct ast_cc_monitor *monitor)
        /* 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;
        }
@@ -1307,7 +1630,7 @@ static int cc_generic_monitor_unsuspend(struct ast_cc_monitor *monitor)
        /* 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");
        }
 
@@ -1333,7 +1656,7 @@ static int cc_generic_monitor_cancel_available_timer(struct ast_cc_monitor *moni
 
        ast_log_dynamic_level(cc_logger_level, "Core %d: Canceling generic monitor available timer for monitor %s\n",
                        monitor->core_id, monitor->interface->device_name);
-       if (!ast_sched_thread_del(cc_sched_thread, *sched_id)) {
+       if (!ast_sched_del(cc_sched_context, *sched_id)) {
                cc_unref(monitor, "Remove scheduler's reference to the monitor");
        }
        *sched_id = -1;
@@ -1393,8 +1716,8 @@ static void cc_generic_monitor_destructor(void *private_data)
                /* 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 "
@@ -1454,7 +1777,7 @@ struct extension_child_dialstring {
         *
         * \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
@@ -1790,7 +2113,7 @@ static struct ast_cc_monitor *cc_extension_monitor_init(const char * const exten
        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;
 }
 
@@ -1830,7 +2153,7 @@ static int cc_interfaces_datastore_init(struct ast_channel *chan) {
                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;
        }
@@ -1969,7 +2292,7 @@ static struct ast_cc_monitor *cc_device_monitor_init(const char * const device_n
        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;
 }
@@ -2077,12 +2400,7 @@ void ast_handle_cc_control_frame(struct ast_channel *inbound, struct ast_channel
 
        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");
@@ -2127,7 +2445,7 @@ int ast_cc_call_init(struct ast_channel *chan, int *ignore_cc)
                 */
                *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;
        }
 
@@ -2147,8 +2465,8 @@ int ast_cc_call_init(struct ast_channel *chan, int *ignore_cc)
        }
 
        /* 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;
@@ -2197,7 +2515,18 @@ static long count_agents(const char * const caller, const int core_id_exception)
 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)
@@ -2205,7 +2534,7 @@ 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);
@@ -2258,7 +2587,7 @@ static struct ast_cc_agent *cc_agent_init(struct ast_channel *caller_chan,
                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;
 }
@@ -2267,7 +2596,7 @@ static struct ast_cc_agent *cc_agent_init(struct ast_channel *caller_chan,
 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);
@@ -2279,7 +2608,7 @@ static struct ast_cc_agent_callbacks generic_agent_callbacks = {
        .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,
@@ -2296,7 +2625,7 @@ struct cc_generic_agent_pvt {
         * 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.
         */
@@ -2346,14 +2675,14 @@ static int cc_generic_agent_init(struct ast_cc_agent *agent, struct ast_channel
        }
 
        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;
@@ -2363,7 +2692,7 @@ static int offer_timer_expire(const void *data)
 {
        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);
@@ -2377,13 +2706,13 @@ static int cc_generic_agent_start_offer_timer(struct ast_cc_agent *agent)
        int sched_id;
        struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
 
-       ast_assert(cc_sched_thread != NULL);
+       ast_assert(cc_sched_context != NULL);
        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_thread_add(cc_sched_thread, when, offer_timer_expire, cc_ref(agent, "Give scheduler an agent ref"))) == -1) {
+       if ((sched_id = ast_sched_add(cc_sched_context, when, offer_timer_expire, cc_ref(agent, "Give scheduler an agent ref"))) == -1) {
                return -1;
        }
        generic_pvt->offer_timer_id = sched_id;
@@ -2395,7 +2724,7 @@ static int cc_generic_agent_stop_offer_timer(struct ast_cc_agent *agent)
        struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
 
        if (generic_pvt->offer_timer_id != -1) {
-               if (!ast_sched_thread_del(cc_sched_thread, generic_pvt->offer_timer_id)) {
+               if (!ast_sched_del(cc_sched_context, generic_pvt->offer_timer_id)) {
                        cc_unref(agent, "Remove scheduler's reference to the agent");
                }
                generic_pvt->offer_timer_id = -1;
@@ -2403,7 +2732,7 @@ static int cc_generic_agent_stop_offer_timer(struct ast_cc_agent *agent)
        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.
@@ -2429,27 +2758,33 @@ static int cc_generic_agent_stop_ringing(struct ast_cc_agent *agent)
        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);
 }
 
@@ -2457,17 +2792,21 @@ static int cc_generic_agent_start_monitoring(struct ast_cc_agent *agent)
 {
        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;
 }
 
@@ -2481,29 +2820,31 @@ static void *generic_recall(void *data)
        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_cap *tmp_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+
+       if (!tmp_cap) {
+               return NULL;
+       }
 
        tech = interface;
        if ((target = strchr(interface, '/'))) {
                *target++ = '\0';
        }
-       if (!(chan = ast_request_and_dial(tech, AST_FORMAT_SLINEAR, 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);
+               ao2_ref(tmp_cap, -1);
                return NULL;
        }
-       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",
-                               agent->core_id, agent->device_name);
-               if (ast_app_run_macro(NULL, chan, callback_macro, NULL)) {
-                       ast_cc_failed(agent->core_id, "Callback macro to %s failed. Maybe a hangup?", agent->device_name);
-                       ast_hangup(chan);
-                       return NULL;
-               }
-       }
+       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
@@ -2512,11 +2853,39 @@ static void *generic_recall(void *data)
        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_cc_agent_recalling(agent->core_id, "Generic agent %s is recalling", agent->device_name);
-       ast_pbx_start(chan);
+       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 %u: There's a callback macro configured for agent %s\n",
+                               agent->core_id, agent->device_name);
+               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;
+               }
+       }
+
+       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;
 }
 
@@ -2525,7 +2894,7 @@ static int cc_generic_agent_recall(struct ast_cc_agent *agent)
        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.
                 */
@@ -2547,7 +2916,7 @@ static void cc_generic_agent_destructor(struct ast_cc_agent *agent)
 
        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);
@@ -2620,6 +2989,7 @@ static struct cc_core_instance *cc_core_init_instance(struct ast_channel *caller
 }
 
 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];
@@ -2630,7 +3000,7 @@ static int is_state_change_valid(enum cc_state current_state, const enum cc_stat
        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:
@@ -2673,7 +3043,7 @@ static int is_state_change_valid(enum cc_state current_state, const enum cc_stat
                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;
        }
@@ -2695,11 +3065,7 @@ static int cc_caller_offered(struct cc_core_instance *core_instance, struct cc_s
                                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;
@@ -2748,11 +3114,7 @@ static void request_cc(struct cc_core_instance *core_instance)
                                                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);
                        }
                }
        }
@@ -2768,6 +3130,8 @@ static int cc_caller_requested(struct cc_core_instance *core_instance, struct cc
 {
        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;
        }
@@ -2806,16 +3170,11 @@ static int cc_active(struct cc_core_instance *core_instance, struct cc_state_cha
         *    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 */
@@ -2857,10 +3216,7 @@ static int cc_caller_busy(struct cc_core_instance *core_instance, struct cc_stat
         */
        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;
 }
 
@@ -2891,10 +3247,7 @@ static int cc_recalling(struct cc_core_instance *core_instance, struct cc_state_
        /* 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;
 }
 
@@ -2902,21 +3255,14 @@ static int cc_complete(struct cc_core_instance *core_instance, struct cc_state_c
 {
        /* 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;
 }
@@ -2940,18 +3286,22 @@ static int cc_do_state_change(void *datap)
        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;
@@ -2962,6 +3312,11 @@ static int cc_do_state_change(void *datap)
        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;
@@ -2973,6 +3328,7 @@ static int cc_request_state_change(enum cc_state state, const int core_id, const
        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
@@ -2988,12 +3344,22 @@ static int cc_request_state_change(enum cc_state state, const int core_id, const
                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;
@@ -3027,7 +3393,7 @@ static void cc_recall_ds_destroy(void *data)
        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,
@@ -3186,15 +3552,19 @@ struct ast_cc_monitor *ast_cc_get_monitor_by_recall_core_id(const int core_id, c
  * \param dialstring A new dialstring to add
  * \retval void
  */
-static void cc_unique_append(struct ast_str *str, const char * const dialstring)
+static void cc_unique_append(struct ast_str **str, const char *dialstring)
 {
        char dialstring_search[AST_CHANNEL_NAME];
 
+       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);
 }
 
 /*!
@@ -3212,12 +3582,16 @@ static void cc_unique_append(struct ast_str *str, const char * const dialstring)
  * \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;
        struct ast_cc_monitor *monitor_iter = starting_point;
        int top_level_id = starting_point->id;
+       size_t length;
+
+       /* Init to an empty string. */
+       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
@@ -3240,7 +3614,15 @@ static void build_cc_interfaces_chanvar(struct ast_cc_monitor *starting_point, s
        /* str will have an extra '&' tacked onto the end of it, so we need
         * to get rid of that.
         */
-       ast_str_truncate(str, ast_str_strlen(str) - 1);
+       length = ast_str_strlen(*str);
+       if (length) {
+               ast_str_truncate(*str, length - 1);
+       }
+       if (length <= 1) {
+               /* Nothing to recall?  This should not happen. */
+               ast_log(LOG_ERROR, "CC_INTERFACES is empty. starting device_name:'%s'\n",
+                       starting_point->interface->device_name);
+       }
 }
 
 int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan)
@@ -3269,7 +3651,7 @@ int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan)
 
        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));
@@ -3321,7 +3703,7 @@ int ast_set_cc_interfaces_chanvar(struct ast_channel *chan, const char * const e
                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));
@@ -3383,7 +3765,7 @@ int ast_cc_offer(struct ast_channel *caller_chan)
        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;
 }
@@ -3534,10 +3916,7 @@ static int cc_monitor_failed(void *data)
                                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.");
                        }
                }
@@ -3820,7 +4199,7 @@ void ast_cc_call_failed(struct ast_channel *incoming, struct ast_channel *outgoi
        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
                 */
@@ -3884,7 +4263,9 @@ static int ccreq_exec(struct ast_channel *chan, const char *data)
        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",
@@ -3894,6 +4275,7 @@ static int ccreq_exec(struct ast_channel *chan, const char *data)
                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;
        }
@@ -3903,14 +4285,19 @@ static int ccreq_exec(struct ast_channel *chan, const char *data)
                                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";
@@ -3926,19 +4313,27 @@ static int cccancel_exec(struct ast_channel *chan, const char *data)
 
        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 {
@@ -4007,6 +4402,59 @@ static void initialize_cc_max_requests(void)
        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;
@@ -4175,6 +4623,34 @@ static struct ast_cli_entry cc_cli[] = {
        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;
@@ -4192,16 +4668,27 @@ int ast_cc_init(void)
        if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS core", TPS_REF_DEFAULT))) {
                return -1;
        }
-       if (!(cc_sched_thread = ast_sched_thread_create())) {
+       if (!(cc_sched_context = ast_sched_context_create())) {
+               return -1;
+       }
+       if (ast_sched_start_thread(cc_sched_context)) {
                return -1;
        }
        res = ast_register_application2(ccreq_app, ccreq_exec, NULL, NULL, NULL);
        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;
 }