Skip to content
Snippets Groups Projects
ccss.c 153 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
    
    	if (!core_instance) {
    		return -1;
    	}
    
    	res = ast_taskprocessor_push(cc_core_taskprocessor, cc_status_request, core_instance);
    	if (res) {
    		cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
    	}
    	return res;
    }
    
    static int cc_stop_ringing(void *data)
    {
    	struct cc_core_instance *core_instance = data;
    	int res = 0;
    
    	if (core_instance->agent->callbacks->stop_ringing) {
    		res = core_instance->agent->callbacks->stop_ringing(core_instance->agent);
    	}
    	/* If an agent is being asked to stop ringing, then he needs to be prepared if for
    	 * whatever reason he needs to be called back again. The proper state to be in to
    	 * detect such a circumstance is the CC_ACTIVE state.
    	 *
    	 * We get to this state using the slightly unintuitive method of calling
    	 * ast_cc_monitor_request_acked because it gets us to the proper state.
    	 */
    	ast_cc_monitor_request_acked(core_instance->core_id, "Agent %s asked to stop ringing. Be prepared to be recalled again.",
    			core_instance->agent->device_name);
    	cc_unref(core_instance, "Stop ringing finished. Unref core_instance");
    	return res;
    }
    
    int ast_cc_monitor_stop_ringing(int core_id)
    {
    	int res;
    	struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
    
    	if (!core_instance) {
    		return -1;
    	}
    
    	res = ast_taskprocessor_push(cc_core_taskprocessor, cc_stop_ringing, core_instance);
    	if (res) {
    		cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
    	}
    	return res;
    }
    
    static int cc_party_b_free(void *data)
    {
    	struct cc_core_instance *core_instance = data;
    	int res = 0;
    
    	if (core_instance->agent->callbacks->party_b_free) {
    		res = core_instance->agent->callbacks->party_b_free(core_instance->agent);
    	}
    	cc_unref(core_instance, "Party B free finished. Unref core_instance");
    	return res;
    }
    
    int ast_cc_monitor_party_b_free(int core_id)
    {
    	int res;
    	struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
    
    	if (!core_instance) {
    		return -1;
    	}
    
    	res = ast_taskprocessor_push(cc_core_taskprocessor, cc_party_b_free, core_instance);
    	if (res) {
    		cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
    	}
    	return res;
    }
    
    struct cc_status_response_args {
    	struct cc_core_instance *core_instance;
    	enum ast_device_state devstate;
    };
    
    static int cc_status_response(void *data)
    {
    	struct cc_status_response_args *args = data;
    	struct cc_core_instance *core_instance = args->core_instance;
    	struct ast_cc_monitor *monitor_iter;
    	enum ast_device_state devstate = args->devstate;
    
    	ast_free(args);
    
    	AST_LIST_LOCK(core_instance->monitors);
    	AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
    		if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR &&
    				monitor_iter->callbacks->status_response) {
    			monitor_iter->callbacks->status_response(monitor_iter, devstate);
    		}
    	}
    	AST_LIST_UNLOCK(core_instance->monitors);
    	cc_unref(core_instance, "Status response finished. Unref core instance");
    	return 0;
    }
    
    int ast_cc_agent_status_response(int core_id, enum ast_device_state devstate)
    {
    	struct cc_status_response_args *args;
    	struct cc_core_instance *core_instance;
    	int res;
    
    	args = ast_calloc(1, sizeof(*args));
    	if (!args) {
    		return -1;
    	}
    
    	core_instance = find_cc_core_instance(core_id);
    	if (!core_instance) {
    		ast_free(args);
    		return -1;
    	}
    
    	args->core_instance = core_instance;
    	args->devstate = devstate;
    
    	res = ast_taskprocessor_push(cc_core_taskprocessor, cc_status_response, args);
    	if (res) {
    		cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
    		ast_free(args);
    	}
    	return res;
    }
    
    static int cc_build_payload(struct ast_channel *chan, struct ast_cc_config_params *cc_params,
    	const char *monitor_type, const char * const device_name, const char * dialstring,
    	enum ast_cc_service_type service, void *private_data, struct cc_control_payload *payload)
    {
    	struct ast_datastore *datastore;
    	struct dialed_cc_interfaces *cc_interfaces;
    	int dial_parent_id;
    
    	ast_channel_lock(chan);
    	datastore = ast_channel_datastore_find(chan, &dialed_cc_interfaces_info, NULL);
    	if (!datastore) {
    		ast_channel_unlock(chan);
    		return -1;
    	}
    	cc_interfaces = datastore->data;
    	dial_parent_id = cc_interfaces->dial_parent_id;
    	ast_channel_unlock(chan);
    
    	payload->monitor_type = monitor_type;
    	payload->private_data = private_data;
    	payload->service = service;
    	ast_cc_copy_config_params(&payload->config_params, cc_params);
    	payload->parent_interface_id = dial_parent_id;
    	ast_copy_string(payload->device_name, device_name, sizeof(payload->device_name));
    	ast_copy_string(payload->dialstring, dialstring, sizeof(payload->dialstring));
    	return 0;
    }
    
    int ast_queue_cc_frame(struct ast_channel *chan, const char *monitor_type,
    		const char * const dialstring, enum ast_cc_service_type service, void *private_data)
    {
    	struct ast_frame frame = {0,};
    	char device_name[AST_CHANNEL_NAME];
    	int retval;
    	struct ast_cc_config_params *cc_params;
    
    	cc_params = ast_channel_get_cc_config_params(chan);
    	if (!cc_params) {
    		return -1;
    	}
    	ast_channel_get_device_name(chan, device_name, sizeof(device_name));
    	if (ast_cc_monitor_count(device_name, monitor_type) >= ast_get_cc_max_monitors(cc_params)) {
    		ast_log(LOG_NOTICE, "Not queuing a CC frame for device %s since it already has its maximum monitors allocated\n", device_name);
    		return -1;
    	}
    
    	if (ast_cc_build_frame(chan, cc_params, monitor_type, device_name, dialstring, service, private_data, &frame)) {
    		/* Frame building failed. We can't use this. */
    		return -1;
    	}
    	retval = ast_queue_frame(chan, &frame);
    	ast_frfree(&frame);
    	return retval;
    }
    
    int ast_cc_build_frame(struct ast_channel *chan, struct ast_cc_config_params *cc_params,
    	const char *monitor_type, const char * const device_name,
    	const char * const dialstring, enum ast_cc_service_type service, void *private_data,
    	struct ast_frame *frame)
    {
    	struct cc_control_payload *payload = ast_calloc(1, sizeof(*payload));
    
    	if (!payload) {
    		return -1;
    	}
    	if (cc_build_payload(chan, cc_params, monitor_type, device_name, dialstring, service, private_data, payload)) {
    		/* Something screwed up, we can't make a frame with this */
    		ast_free(payload);
    		return -1;
    	}
    	frame->frametype = AST_FRAME_CONTROL;
    	frame->subclass.integer = AST_CONTROL_CC;
    	frame->data.ptr = payload;
    	frame->datalen = sizeof(*payload);
    	frame->mallocd = AST_MALLOCD_DATA;
    	return 0;
    }
    
    void ast_cc_call_failed(struct ast_channel *incoming, struct ast_channel *outgoing, const char * const dialstring)
    {
    	char device_name[AST_CHANNEL_NAME];
    	struct cc_control_payload payload;
    	struct ast_cc_config_params *cc_params;
    
    
    	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
    		 */
    		return;
    	}
    
    	cc_params = ast_channel_get_cc_config_params(outgoing);
    	if (!cc_params) {
    		return;
    	}
    	if (ast_get_cc_monitor_policy(cc_params) != AST_CC_MONITOR_GENERIC) {
    		/* This sort of CCBS only works if using generic CC. For native, we would end up sending
    		 * a CC request for a non-existent call. The far end will reject this every time
    		 */
    		return;
    	}
    
    	ast_channel_get_device_name(outgoing, device_name, sizeof(device_name));
    	if (cc_build_payload(outgoing, cc_params, AST_CC_GENERIC_MONITOR_TYPE, device_name,
    		dialstring, AST_CC_CCBS, NULL, &payload)) {
    		/* Something screwed up, we can't make a frame with this */
    		return;
    	}
    	ast_handle_cc_control_frame(incoming, outgoing, &payload);
    }
    
    void ast_cc_busy_interface(struct ast_channel *inbound, struct ast_cc_config_params *cc_params,
    	const char *monitor_type, const char * const device_name, const char * const dialstring, void *private_data)
    {
    	struct cc_control_payload payload;
    	if (cc_build_payload(inbound, cc_params, monitor_type, device_name, dialstring, AST_CC_CCBS, private_data, &payload)) {
    		/* Something screwed up. Don't try to handle this payload */
    		call_destructor_with_no_monitor(monitor_type, private_data);
    		return;
    	}
    	ast_handle_cc_control_frame(inbound, NULL, &payload);
    }
    
    int ast_cc_callback(struct ast_channel *inbound, const char * const tech, const char * const dest, ast_cc_callback_fn callback)
    {
    	const struct ast_channel_tech *chantech = ast_get_channel_tech(tech);
    
    	if (chantech && chantech->cc_callback) {
    		chantech->cc_callback(inbound, dest, callback);
    	}
    
    	return 0;
    }
    
    static const char *ccreq_app = "CallCompletionRequest";
    
    static int ccreq_exec(struct ast_channel *chan, const char *data)
    {
    	struct cc_core_instance *core_instance;
    	char device_name[AST_CHANNEL_NAME];
    	unsigned long match_flags;
    	int res;
    
    	ast_channel_get_device_name(chan, device_name, sizeof(device_name));
    
    	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);
    
    		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",
    			core_instance->core_id, device_name);
    
    	if (strcmp(core_instance->agent->callbacks->type, "generic")) {
    		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;
    	}
    
    	if (!ast_cc_request_is_within_limits()) {
    		ast_log_dynamic_level(cc_logger_level, "Core %d: CallCompletionRequest failed. Too many requests in the system\n",
    				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");
    
    }
    
    static const char *cccancel_app = "CallCompletionCancel";
    
    static int cccancel_exec(struct ast_channel *chan, const char *data)
    {
    	struct cc_core_instance *core_instance;
    	char device_name[AST_CHANNEL_NAME];
    	unsigned long match_flags;
    	int res;
    
    	ast_channel_get_device_name(chan, device_name, sizeof(device_name));
    
    	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_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");
    
    		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");
    
    	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 {
    	const char *device_name;
    	const char *monitor_type;
    	int count;
    };
    
    static int count_monitors_cb(void *obj, void *arg, int flags)
    {
    	struct cc_core_instance *core_instance = obj;
    	struct count_monitors_cb_data *cb_data = arg;
    	const char *device_name = cb_data->device_name;
    	const char *monitor_type = cb_data->monitor_type;
    	struct ast_cc_monitor *monitor_iter;
    
    	AST_LIST_LOCK(core_instance->monitors);
    	AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
    		if (!strcmp(monitor_iter->interface->device_name, device_name) &&
    				!strcmp(monitor_iter->interface->monitor_type, monitor_type)) {
    			cb_data->count++;
    			break;
    		}
    	}
    	AST_LIST_UNLOCK(core_instance->monitors);
    	return 0;
    }
    
    int ast_cc_monitor_count(const char * const name, const char * const type)
    {
    	struct count_monitors_cb_data data = {.device_name = name, .monitor_type = type,};
    
    	ao2_t_callback(cc_core_instances, OBJ_NODATA, count_monitors_cb, &data, "Counting agents");
    	ast_log_dynamic_level(cc_logger_level, "Counted %d monitors\n", data.count);
    	return data.count;
    }
    
    static void initialize_cc_max_requests(void)
    {
    	struct ast_config *cc_config;
    	const char *cc_max_requests_str;
    	struct ast_flags config_flags = {0,};
    	char *endptr;
    
    	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_max_requests default\n");
    		global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
    		return;
    	}
    
    	if (!(cc_max_requests_str = ast_variable_retrieve(cc_config, "general", "cc_max_requests"))) {
    		ast_config_destroy(cc_config);
    		global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
    		return;
    	}
    
    	global_cc_max_requests = strtol(cc_max_requests_str, &endptr, 10);
    
    	if (!ast_strlen_zero(endptr)) {
    		ast_log(LOG_WARNING, "Invalid input given for cc_max_requests. Using default\n");
    		global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
    	}
    
    	ast_config_destroy(cc_config);
    	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;
    	if (!monitor) {
    		return;
    	}
    
    	ast_cli(fd, "\t\t|-->%s", monitor->interface->device_name);
    	if (monitor->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
    		ast_cli(fd, "(%s)", cc_service_to_string(monitor->service_offered));
    	}
    	ast_cli(fd, "\n");
    
    	while ((child_monitor_iter = AST_LIST_NEXT(child_monitor_iter, next))) {
    		if (child_monitor_iter->parent_id == monitor->id) {
    			cc_cli_print_monitor_stats(child_monitor_iter, fd, child_monitor_iter->id);
    		}
    	}
    }
    
    static int print_stats_cb(void *obj, void *arg, int flags)
    {
    	int *cli_fd = arg;
    	struct cc_core_instance *core_instance = obj;
    
    	ast_cli(*cli_fd, "%d\t\t%s\t\t%s\n", core_instance->core_id, core_instance->agent->device_name,
    			cc_state_to_string(core_instance->current_state));
    	AST_LIST_LOCK(core_instance->monitors);
    	cc_cli_print_monitor_stats(AST_LIST_FIRST(core_instance->monitors), *cli_fd, 0);
    	AST_LIST_UNLOCK(core_instance->monitors);
    	return 0;
    }
    
    static int cc_cli_output_status(void *data)
    {
    	int *cli_fd = data;
    	int count = ao2_container_count(cc_core_instances);
    
    	if (!count) {
    		ast_cli(*cli_fd, "There are currently no active call completion transactions\n");
    	} else {
    		ast_cli(*cli_fd, "%d Call completion transactions\n", count);
    		ast_cli(*cli_fd, "Core ID\t\tCaller\t\t\t\tStatus\n");
    		ast_cli(*cli_fd, "----------------------------------------------------------------------------\n");
    		ao2_t_callback(cc_core_instances, OBJ_NODATA, print_stats_cb, cli_fd, "Printing stats to CLI");
    	}
    	ast_free(cli_fd);
    	return 0;
    }
    
    static char *handle_cc_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	int *cli_fd;
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "cc report status";
    		e->usage =
    			"Usage: cc report status\n"
    			"       Report the current status of any ongoing CC transactions\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	cli_fd = ast_malloc(sizeof(*cli_fd));
    	if (!cli_fd) {
    		return CLI_FAILURE;
    	}
    
    	*cli_fd = a->fd;
    
    	if (ast_taskprocessor_push(cc_core_taskprocessor, cc_cli_output_status, cli_fd)) {
    		ast_free(cli_fd);
    		return CLI_FAILURE;
    	}
    	return CLI_SUCCESS;
    }
    
    static int kill_cores(void *obj, void *arg, int flags)
    {
    	int *core_id = arg;
    	struct cc_core_instance *core_instance = obj;
    
    	if (!core_id || (core_instance->core_id == *core_id)) {
    		ast_cc_failed(core_instance->core_id, "CC transaction canceled administratively\n");
    	}
    	return 0;
    }
    
    static char *complete_core_id(const char *line, const char *word, int pos, int state)
    {
    	int which = 0;
    	int wordlen = strlen(word);
    	char *ret = NULL;
    	struct ao2_iterator core_iter = ao2_iterator_init(cc_core_instances, 0);
    	struct cc_core_instance *core_instance;
    
    	for (; (core_instance = ao2_t_iterator_next(&core_iter, "Next core instance"));
    			cc_unref(core_instance, "CLI tab completion iteration")) {
    		char core_id_str[20];
    		snprintf(core_id_str, sizeof(core_id_str), "%d", core_instance->core_id);
    		if (!strncmp(word, core_id_str, wordlen) && ++which > state) {
    			ret = ast_strdup(core_id_str);
    			cc_unref(core_instance, "Found a matching core ID for CLI tab-completion");
    			break;
    		}
    	}
    	ao2_iterator_destroy(&core_iter);
    
    	return ret;
    }
    
    static char *handle_cc_kill(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    
    		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"
    			"          core ID equal to the specified core ID.\n"
    			"       2. 'cc cancel all' will cancel all active CC transactions.\n";
    		return NULL;
    	case CLI_GENERATE:
    
    		if (a->pos == 3 && !strcasecmp(a->argv[2], "core")) {
    
    			return complete_core_id(a->line, a->word, a->pos, a->n);
    		}
    		return NULL;
    	}
    
    	if (a->argc == 4) {
    		int core_id;
    		char *endptr;
    		if (strcasecmp(a->argv[2], "core")) {
    			return CLI_SHOWUSAGE;
    		}
    		core_id = strtol(a->argv[3], &endptr, 10);
    		if ((errno != 0 && core_id == 0) || (endptr == a->argv[3])) {
    			return CLI_SHOWUSAGE;
    		}
    		ao2_t_callback(cc_core_instances, OBJ_NODATA, kill_cores, &core_id, "CLI Killing Core Id");
    	} else if (a->argc == 3) {
    		if (strcasecmp(a->argv[2], "all")) {
    			return CLI_SHOWUSAGE;
    		}
    		ao2_t_callback(cc_core_instances, OBJ_NODATA, kill_cores, NULL, "CLI Killing all CC cores");
    	} else {
    		return CLI_SHOWUSAGE;
    	}
    
    	return CLI_SUCCESS;
    }
    
    static struct ast_cli_entry cc_cli[] = {
    	AST_CLI_DEFINE(handle_cc_status, "Reports CC stats"),
    	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;
    
    	if (!(cc_core_instances = ao2_t_container_alloc(CC_CORE_INSTANCES_BUCKETS,
    					cc_core_instance_hash_fn, cc_core_instance_cmp_fn,
    					"Create core instance container"))) {
    		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"))) {
    		return -1;
    	}
    
    	if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS_core", TPS_REF_DEFAULT))) {
    
    	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);