Skip to content
Snippets Groups Projects
cel.c 54 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	struct ast_channel_snapshot *old_snapshot,
    	struct ast_channel_snapshot *new_snapshot)
    {
    	int is_hungup, was_hungup;
    
    	if (!new_snapshot) {
    
    		cel_report_event(old_snapshot, AST_CEL_CHANNEL_END, NULL, NULL, NULL);
    
    		if (ast_cel_track_event(AST_CEL_LINKEDID_END)) {
    			check_retire_linkedid(old_snapshot);
    		}
    
    		cel_report_event(new_snapshot, AST_CEL_CHANNEL_START, NULL, NULL, NULL);
    
    	was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
    	is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0;
    
    
    	if (!was_hungup && is_hungup) {
    
    		struct cel_dialstatus *dialstatus = get_dialstatus(new_snapshot->uniqueid);
    
    		extra = ast_json_pack("{s: i, s: s, s: s}",
    			"hangupcause", new_snapshot->hangupcause,
    			"hangupsource", new_snapshot->hangupsource,
    
    			"dialstatus", dialstatus ? dialstatus->dialstatus : "");
    
    		cel_report_event(new_snapshot, AST_CEL_HANGUP, NULL, extra, NULL);
    
    		ao2_cleanup(dialstatus);
    
    		return;
    	}
    
    	if (old_snapshot->state != new_snapshot->state && new_snapshot->state == AST_STATE_UP) {
    
    		cel_report_event(new_snapshot, AST_CEL_ANSWER, NULL, NULL, NULL);
    
    		return;
    	}
    }
    
    static void cel_channel_linkedid_change(
    	struct ast_channel_snapshot *old_snapshot,
    	struct ast_channel_snapshot *new_snapshot)
    {
    	if (!old_snapshot || !new_snapshot) {
    		return;
    	}
    
    
    	ast_assert(!ast_strlen_zero(new_snapshot->linkedid));
    	ast_assert(!ast_strlen_zero(old_snapshot->linkedid));
    
    
    	if (ast_cel_track_event(AST_CEL_LINKEDID_END)
    		&& strcmp(old_snapshot->linkedid, new_snapshot->linkedid)) {
    
    		cel_linkedid_ref(new_snapshot->linkedid);
    
    		check_retire_linkedid(old_snapshot);
    	}
    }
    
    static void cel_channel_app_change(
    	struct ast_channel_snapshot *old_snapshot,
    	struct ast_channel_snapshot *new_snapshot)
    {
    	if (new_snapshot && old_snapshot
    		&& !strcmp(old_snapshot->appl, new_snapshot->appl)) {
    		return;
    	}
    
    	/* old snapshot has an application, end it */
    	if (old_snapshot && !ast_strlen_zero(old_snapshot->appl)) {
    
    		cel_report_event(old_snapshot, AST_CEL_APP_END, NULL, NULL, NULL);
    
    	}
    
    	/* new snapshot has an application, start it */
    	if (new_snapshot && !ast_strlen_zero(new_snapshot->appl)) {
    
    		cel_report_event(new_snapshot, AST_CEL_APP_START, NULL, NULL, NULL);
    
    /* \brief Handlers for channel snapshot changes.
     * \note Order of the handlers matters. Application changes must come before state
     * changes to ensure that hangup notifications occur after application changes.
     * Linkedid checking should always come last.
     */
    
    cel_channel_snapshot_monitor cel_channel_monitors[] = {
    	cel_channel_app_change,
    
    	cel_channel_linkedid_change,
    };
    
    
    static int cel_filter_channel_snapshot(struct ast_channel_snapshot *snapshot)
    {
    	if (!snapshot) {
    		return 0;
    	}
    
    	return snapshot->tech_properties & AST_CHAN_TP_INTERNAL;
    
    static void cel_snapshot_update_cb(void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct stasis_cache_update *update = stasis_message_data(message);
    	if (ast_channel_snapshot_type() == update->type) {
    		struct ast_channel_snapshot *old_snapshot;
    		struct ast_channel_snapshot *new_snapshot;
    		size_t i;
    
    		old_snapshot = stasis_message_data(update->old_snapshot);
    		new_snapshot = stasis_message_data(update->new_snapshot);
    
    
    		if (cel_filter_channel_snapshot(old_snapshot) || cel_filter_channel_snapshot(new_snapshot)) {
    			return;
    		}
    
    
    		for (i = 0; i < ARRAY_LEN(cel_channel_monitors); ++i) {
    			cel_channel_monitors[i](old_snapshot, new_snapshot);
    		}
    	}
    }
    
    
    static struct ast_str *cel_generate_peer_str(
    	struct ast_bridge_snapshot *bridge,
    	struct ast_channel_snapshot *chan)
    {
    	struct ast_str *peer_str = ast_str_create(32);
    	struct ao2_iterator i;
    	char *current_chan = NULL;
    
    	if (!peer_str) {
    		return NULL;
    	}
    
    	for (i = ao2_iterator_init(bridge->channels, 0);
    		(current_chan = ao2_iterator_next(&i));
    		ao2_cleanup(current_chan)) {
    
    		struct ast_channel_snapshot *current_snapshot;
    
    
    		/* Don't add the channel for which this message is being generated */
    		if (!strcmp(current_chan, chan->uniqueid)) {
    			continue;
    		}
    
    		current_snapshot = ast_channel_snapshot_get_latest(current_chan);
    		if (!current_snapshot) {
    			continue;
    		}
    
    		ast_str_append(&peer_str, 0, "%s,", current_snapshot->name);
    
    		ao2_cleanup(current_snapshot);
    
    	}
    	ao2_iterator_destroy(&i);
    
    	/* Rip off the trailing comma */
    	ast_str_truncate(peer_str, -1);
    
    	return peer_str;
    }
    
    
    static void cel_bridge_enter_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_bridge_blob *blob = stasis_message_data(message);
    	struct ast_bridge_snapshot *snapshot = blob->bridge;
    	struct ast_channel_snapshot *chan_snapshot = blob->channel;
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
    
    	RAII_VAR(struct ast_str *, peer_str, NULL, ast_free);
    
    	if (cel_filter_channel_snapshot(chan_snapshot)) {
    		return;
    	}
    
    
    	extra = ast_json_pack("{s: s, s: s}",
    		"bridge_id", snapshot->uniqueid,
    		"bridge_technology", snapshot->technology);
    
    	peer_str = cel_generate_peer_str(snapshot, chan_snapshot);
    	if (!peer_str) {
    		return;
    	}
    
    	cel_report_event(chan_snapshot, AST_CEL_BRIDGE_ENTER, NULL, extra, ast_str_buffer(peer_str));
    
    }
    
    static void cel_bridge_leave_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_bridge_blob *blob = stasis_message_data(message);
    	struct ast_bridge_snapshot *snapshot = blob->bridge;
    	struct ast_channel_snapshot *chan_snapshot = blob->channel;
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
    
    	RAII_VAR(struct ast_str *, peer_str, NULL, ast_free);
    
    	if (cel_filter_channel_snapshot(chan_snapshot)) {
    		return;
    	}
    
    
    	extra = ast_json_pack("{s: s, s: s}",
    		"bridge_id", snapshot->uniqueid,
    		"bridge_technology", snapshot->technology);
    
    	peer_str = cel_generate_peer_str(snapshot, chan_snapshot);
    	if (!peer_str) {
    		return;
    	}
    
    	cel_report_event(chan_snapshot, AST_CEL_BRIDGE_EXIT, NULL, extra, ast_str_buffer(peer_str));
    
    }
    
    static void cel_parking_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_parked_call_payload *parked_payload = stasis_message_data(message);
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
    	const char *reason = NULL;
    
    
    	switch (parked_payload->event_type) {
    	case PARKED_CALL:
    
    		extra = ast_json_pack("{s: s, s: s}",
    			"parker_dial_string", parked_payload->parker_dial_string,
    			"parking_lot", parked_payload->parkinglot);
    		if (extra) {
    
    			cel_report_event(parked_payload->parkee, AST_CEL_PARK_START, NULL, extra, NULL);
    
    	case PARKED_CALL_TIMEOUT:
    
    		reason = "ParkedCallTimeOut";
    
    		break;
    	case PARKED_CALL_GIVEUP:
    
    		reason = "ParkedCallGiveUp";
    
    		break;
    	case PARKED_CALL_UNPARKED:
    
    		reason = "ParkedCallUnparked";
    
    		break;
    	case PARKED_CALL_FAILED:
    
    		reason = "ParkedCallFailed";
    
    	case PARKED_CALL_SWAP:
    		reason = "ParkedCallSwap";
    		break;
    
    	if (parked_payload->retriever) {
    		extra = ast_json_pack("{s: s, s: s}",
    
    			"retriever", parked_payload->retriever->name);
    	} else {
    
    		extra = ast_json_pack("{s: s}", "reason", reason ?: "");
    
    	if (extra) {
    
    		cel_report_event(parked_payload->parkee, AST_CEL_PARK_END, NULL, extra, NULL);
    
    static void save_dialstatus(struct ast_multi_channel_blob *blob, struct ast_channel_snapshot *snapshot)
    
    	struct ao2_container *dial_statuses = ao2_global_obj_ref(cel_dialstatus_store);
    
    	const char *dialstatus_string = get_blob_variable(blob, "dialstatus");
    	struct cel_dialstatus *dialstatus;
    	size_t dialstatus_string_len;
    
    	if (!dial_statuses || ast_strlen_zero(dialstatus_string)) {
    		ao2_cleanup(dial_statuses);
    		return;
    	}
    
    	dialstatus = ao2_find(dial_statuses, snapshot->uniqueid, OBJ_SEARCH_KEY);
    	if (dialstatus) {
    		if (!strcasecmp(dialstatus_string, "ANSWER") && strcasecmp(dialstatus->dialstatus, "ANSWER")) {
    			/* In the case of an answer after we already have a dial status we give
    			 * priority to the answer since the call was, well, answered. In the case of
    			 * failure dial status results we simply let the first failure be the status.
    			 */
    			ao2_unlink(dial_statuses, dialstatus);
    			ao2_ref(dialstatus, -1);
    		} else {
    			ao2_ref(dialstatus, -1);
    			ao2_ref(dial_statuses, -1);
    			return;
    		}
    	}
    
    	dialstatus_string_len = strlen(dialstatus_string) + 1;
    	dialstatus = ao2_alloc_options(sizeof(*dialstatus) + dialstatus_string_len, NULL,
    		AO2_ALLOC_OPT_LOCK_NOLOCK);
    	if (!dialstatus) {
    
    		ao2_ref(dial_statuses, -1);
    
    
    	ast_copy_string(dialstatus->uniqueid, snapshot->uniqueid, sizeof(dialstatus->uniqueid));
    	ast_copy_string(dialstatus->dialstatus, dialstatus_string, dialstatus_string_len);
    
    	ao2_link(dial_statuses, dialstatus);
    	ao2_ref(dialstatus, -1);
    	ao2_ref(dial_statuses, -1);
    
    static int is_valid_dialstatus(struct ast_multi_channel_blob *blob)
    {
    	const char *dialstatus = get_blob_variable(blob, "dialstatus");
    	int res = 0;
    
    	if (ast_strlen_zero(dialstatus)) {
    		res = 0;
    	} else if (!strcasecmp(dialstatus, "CHANUNAVAIL")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "CONGESTION")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "NOANSWER")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "BUSY")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "ANSWER")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "CANCEL")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "DONTCALL")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "TORTURE")) {
    		res = 1;
    	} else if (!strcasecmp(dialstatus, "INVALIDARGS")) {
    		res = 1;
    	}
    	return res;
    }
    
    
    static void cel_dial_cb(void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    
    	struct ast_multi_channel_blob *blob = stasis_message_data(message);
    
    	struct ast_channel_snapshot *snapshot;
    
    	snapshot = ast_multi_channel_blob_get_channel(blob, "caller");
    	if (!snapshot || cel_filter_channel_snapshot(snapshot)) {
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	if (!ast_strlen_zero(get_blob_variable(blob, "forward"))) {
    
    		extra = ast_json_pack("{s: s}", "forward", get_blob_variable(blob, "forward"));
    		if (extra) {
    
    			cel_report_event(snapshot, AST_CEL_FORWARD, NULL, extra, NULL);
    
    	if (is_valid_dialstatus(blob)) {
    
    		save_dialstatus(blob, snapshot);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    static void cel_generic_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_channel_blob *obj = stasis_message_data(message);
    	int event_type = ast_json_integer_get(ast_json_object_get(obj->blob, "event_type"));
    	struct ast_json *event_details = ast_json_object_get(obj->blob, "event_details");
    
    	switch (event_type) {
    	case AST_CEL_USER_DEFINED:
    		{
    			const char *event = ast_json_string_get(ast_json_object_get(event_details, "event"));
    
    			struct ast_json *extra = ast_json_object_get(event_details, "extra");
    
    			cel_report_event(obj->snapshot, event_type, event, extra, NULL);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    			break;
    		}
    	default:
    		ast_log(LOG_ERROR, "Unhandled %s event blob\n", ast_cel_get_type_name(event_type));
    		break;
    	}
    }
    
    
    static void cel_blind_transfer_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    
    	struct ast_blind_transfer_message *transfer_msg = stasis_message_data(message);
    
    	struct ast_channel_snapshot *chan_snapshot = transfer_msg->transferer;
    	struct ast_bridge_snapshot *bridge_snapshot = transfer_msg->bridge;
    
    	if (transfer_msg->result != AST_BRIDGE_TRANSFER_SUCCESS) {
    
    	extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
    
    		"extension", transfer_msg->exten,
    		"context", transfer_msg->context,
    		"bridge_id", bridge_snapshot->uniqueid,
    
    		"transferee_channel_name", transfer_msg->transferee ? transfer_msg->transferee->name : "N/A",
    		"transferee_channel_uniqueid", transfer_msg->transferee ? transfer_msg->transferee->uniqueid  : "N/A");
    
    	if (extra) {
    
    		cel_report_event(chan_snapshot, AST_CEL_BLINDTRANSFER, NULL, extra, NULL);
    
    	}
    }
    
    static void cel_attended_transfer_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_attended_transfer_message *xfer = stasis_message_data(message);
    
    	struct ast_json *extra = NULL;
    
    	struct ast_bridge_snapshot *bridge1, *bridge2;
    	struct ast_channel_snapshot *channel1, *channel2;
    
    	/* Make sure bridge1 is always non-NULL */
    	if (!xfer->to_transferee.bridge_snapshot) {
    		bridge1 = xfer->to_transfer_target.bridge_snapshot;
    		bridge2 = xfer->to_transferee.bridge_snapshot;
    		channel1 = xfer->to_transfer_target.channel_snapshot;
    		channel2 = xfer->to_transferee.channel_snapshot;
    	} else {
    		bridge1 = xfer->to_transferee.bridge_snapshot;
    		bridge2 = xfer->to_transfer_target.bridge_snapshot;
    		channel1 = xfer->to_transferee.channel_snapshot;
    		channel2 = xfer->to_transfer_target.channel_snapshot;
    	}
    
    	switch (xfer->dest_type) {
    	case AST_ATTENDED_TRANSFER_DEST_FAIL:
    		return;
    
    		/* handle these three the same */
    
    	case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
    	case AST_ATTENDED_TRANSFER_DEST_LINK:
    
    	case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
    
    		extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: s, s: s}",
    
    			"bridge1_id", bridge1->uniqueid,
    			"channel2_name", channel2->name,
    
    			"channel2_uniqueid", channel2->uniqueid,
    
    			"bridge2_id", bridge2->uniqueid,
    			"transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A",
    
    			"transferee_channel_uniqueid", xfer->transferee ? xfer->transferee->uniqueid : "N/A",
    			"transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A",
    			"transfer_target_channel_uniqueid", xfer->target ? xfer->target->uniqueid : "N/A");
    
    		if (!extra) {
    			return;
    		}
    		break;
    	case AST_ATTENDED_TRANSFER_DEST_APP:
    
    	case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP:
    
    		extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: s, s: s}",
    
    			"bridge1_id", bridge1->uniqueid,
    			"channel2_name", channel2->name,
    
    			"channel2_uniqueid", channel2->uniqueid,
    
    			"app", xfer->dest.app,
    			"transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A",
    
    			"transferee_channel_uniqueid", xfer->transferee ? xfer->transferee->uniqueid : "N/A",
    			"transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A",
    			"transfer_target_channel_uniqueid", xfer->target ? xfer->target->uniqueid : "N/A");
    
    		if (!extra) {
    			return;
    		}
    		break;
    	}
    
    	cel_report_event(channel1, AST_CEL_ATTENDEDTRANSFER, NULL, extra, NULL);
    
    }
    
    static void cel_pickup_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_multi_channel_blob *obj = stasis_message_data(message);
    	struct ast_channel_snapshot *channel = ast_multi_channel_blob_get_channel(obj, "channel");
    	struct ast_channel_snapshot *target = ast_multi_channel_blob_get_channel(obj, "target");
    
    
    	if (!channel || !target) {
    		return;
    	}
    
    
    	extra = ast_json_pack("{s: s, s: s}",
    		"pickup_channel", channel->name,
    		"pickup_channel_uniqueid", channel->uniqueid);
    
    	cel_report_event(target, AST_CEL_PICKUP, NULL, extra, NULL);
    
    static void cel_local_cb(
    	void *data, struct stasis_subscription *sub,
    	struct stasis_message *message)
    {
    	struct ast_multi_channel_blob *obj = stasis_message_data(message);
    	struct ast_channel_snapshot *localone = ast_multi_channel_blob_get_channel(obj, "1");
    	struct ast_channel_snapshot *localtwo = ast_multi_channel_blob_get_channel(obj, "2");
    
    
    	if (!localone || !localtwo) {
    		return;
    	}
    
    
    	extra = ast_json_pack("{s: s, s: s}",
    		"local_two", localtwo->name,
    		"local_two_uniqueid", localtwo->uniqueid);
    
    	cel_report_event(localone, AST_CEL_LOCAL_OPTIMIZE, NULL, extra, NULL);
    
    static void destroy_routes(void)
    
    	stasis_message_router_unsubscribe_and_join(cel_state_router);
    	cel_state_router = NULL;
    
    static void destroy_subscriptions(void)
    {
    
    	ao2_cleanup(cel_aggregation_topic);
    	cel_aggregation_topic = NULL;
    	ao2_cleanup(cel_topic);
    	cel_topic = NULL;
    
    	cel_channel_forwarder = stasis_forward_cancel(cel_channel_forwarder);
    	cel_bridge_forwarder = stasis_forward_cancel(cel_bridge_forwarder);
    	cel_parking_forwarder = stasis_forward_cancel(cel_parking_forwarder);
    	cel_cel_forwarder = stasis_forward_cancel(cel_cel_forwarder);
    
    static void cel_engine_cleanup(void)
    
    	destroy_routes();
    
    	destroy_subscriptions();
    
    	STASIS_MESSAGE_TYPE_CLEANUP(cel_generic_type);
    
    	ast_cli_unregister(&cli_status);
    
    	aco_info_destroy(&cel_cfg_info);
    	ao2_global_obj_release(cel_configs);
    
    	ao2_global_obj_release(cel_dialstatus_store);
    	ao2_global_obj_release(cel_linkedids);
    	ao2_global_obj_release(cel_backends);
    }
    
    
    /*!
     * \brief Create the Stasis subscriptions for CEL
     */
    static int create_subscriptions(void)
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	cel_aggregation_topic = stasis_topic_create("cel_aggregation_topic");
    	if (!cel_aggregation_topic) {
    		return -1;
    	}
    
    	cel_topic = stasis_topic_create("cel_topic");
    	if (!cel_topic) {
    
    		return -1;
    	}
    
    	cel_channel_forwarder = stasis_forward_all(
    
    		ast_channel_topic_all_cached(),
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    		cel_aggregation_topic);
    
    	if (!cel_channel_forwarder) {
    		return -1;
    	}
    
    	cel_bridge_forwarder = stasis_forward_all(
    
    		ast_bridge_topic_all_cached(),
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    		cel_aggregation_topic);
    
    	if (!cel_bridge_forwarder) {
    		return -1;
    	}
    
    
    	cel_parking_forwarder = stasis_forward_all(
    		ast_parking_topic(),
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    		cel_aggregation_topic);
    
    	if (!cel_parking_forwarder) {
    		return -1;
    	}
    
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	cel_cel_forwarder = stasis_forward_all(
    		ast_cel_topic(),
    		cel_aggregation_topic);
    	if (!cel_cel_forwarder) {
    		return -1;
    	}
    
    
    	return 0;
    }
    
    /*!
     * \brief Create the Stasis message router and routes for CEL
     */
    static int create_routes(void)
    {
    	int ret = 0;
    
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	cel_state_router = stasis_message_router_create(cel_aggregation_topic);
    
    	if (!cel_state_router) {
    		return -1;
    	}
    
    	stasis_message_router_set_congestion_limits(cel_state_router, -1,
    		6 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);
    
    
    	ret |= stasis_message_router_add(cel_state_router,
    		stasis_cache_update_type(),
    		cel_snapshot_update_cb,
    		NULL);
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_channel_dial_type(),
    		cel_dial_cb,
    		NULL);
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_channel_entered_bridge_type(),
    		cel_bridge_enter_cb,
    		NULL);
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_channel_left_bridge_type(),
    		cel_bridge_leave_cb,
    		NULL);
    
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_parked_call_type(),
    		cel_parking_cb,
    		NULL);
    
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	ret |= stasis_message_router_add(cel_state_router,
    		cel_generic_type(),
    		cel_generic_cb,
    		NULL);
    
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_blind_transfer_type(),
    		cel_blind_transfer_cb,
    		NULL);
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_attended_transfer_type(),
    		cel_attended_transfer_cb,
    		NULL);
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_call_pickup_type(),
    		cel_pickup_cb,
    		NULL);
    
    
    	ret |= stasis_message_router_add(cel_state_router,
    		ast_local_optimization_end_type(),
    		cel_local_cb,
    		NULL);
    
    
    		ast_log(AST_LOG_ERROR, "Failed to register for Stasis messages\n");
    	}
    
    	return ret;
    }
    
    
    static int lid_hash(const void *obj, const int flags)
    {
    	const struct cel_linkedid *lid;
    	const char *key;
    
    	switch (flags & OBJ_SEARCH_MASK) {
    	case OBJ_SEARCH_KEY:
    		key = obj;
    		break;
    	case OBJ_SEARCH_OBJECT:
    		lid = obj;
    		key = lid->id;
    		break;
    	default:
    		/* Hash can only work on something with a full key. */
    		ast_assert(0);
    		return 0;
    	}
    	return ast_str_hash(key);
    }
    
    static int lid_cmp(void *obj, void *arg, int flags)
    {
    	const struct cel_linkedid *object_left = obj;
    	const struct cel_linkedid *object_right = arg;
    	const char *right_key = arg;
    	int cmp;
    
    	switch (flags & OBJ_SEARCH_MASK) {
    	case OBJ_SEARCH_OBJECT:
    		right_key = object_right->id;
    		/* Fall through */
    	case OBJ_SEARCH_KEY:
    		cmp = strcmp(object_left->id, right_key);
    		break;
    	case OBJ_SEARCH_PARTIAL_KEY:
    		/*
    		 * We could also use a partial key struct containing a length
    		 * so strlen() does not get called for every comparison instead.
    		 */
    		cmp = strncmp(object_left->id, right_key, strlen(right_key));
    		break;
    	default:
    		/*
    		 * What arg points to is specific to this traversal callback
    		 * and has no special meaning to astobj2.
    		 */
    		cmp = 0;
    		break;
    	}
    	if (cmp) {
    		return 0;
    	}
    	/*
    	 * At this point the traversal callback is identical to a sorted
    	 * container.
    	 */
    	return CMP_MATCH;
    }
    
    
    int ast_cel_engine_init(void)
    {
    
    	struct ao2_container *container;
    
    	container = ao2_container_alloc(NUM_APP_BUCKETS, lid_hash, lid_cmp);
    	ao2_global_obj_replace_unref(cel_linkedids, container);
    	ao2_cleanup(container);
    	if (!container) {
    
    	container = ao2_container_alloc(NUM_DIALSTATUS_BUCKETS,
    		dialstatus_hash, dialstatus_cmp);
    	ao2_global_obj_replace_unref(cel_dialstatus_store, container);
    	ao2_cleanup(container);
    	if (!container) {
    
    		return -1;
    	}
    
    	if (STASIS_MESSAGE_TYPE_INIT(cel_generic_type)) {
    
    		return -1;
    	}
    
    	if (ast_cli_register(&cli_status)) {
    
    	container = ao2_container_alloc(BACKEND_BUCKETS, cel_backend_hash, cel_backend_cmp);
    	ao2_global_obj_replace_unref(cel_backends, container);
    	ao2_cleanup(container);
    	if (!container) {
    
    	if (aco_info_init(&cel_cfg_info)) {
    
    	aco_option_register(&cel_cfg_info, "enable", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct ast_cel_general_config, enable));
    	aco_option_register(&cel_cfg_info, "dateformat", ACO_EXACT, general_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_cel_general_config, date_format));
    
    	aco_option_register_custom(&cel_cfg_info, "apps", ACO_EXACT, general_options, "", apps_handler, 0);
    	aco_option_register_custom(&cel_cfg_info, "events", ACO_EXACT, general_options, "", events_handler, 0);
    
    
    	if (aco_process_config(&cel_cfg_info, 0)) {
    
    		struct cel_config *cel_cfg = cel_config_alloc();
    
    		/* We couldn't process the configuration so create a default config. */
    
    		if (!aco_set_defaults(&general_option, "general", cel_cfg->general)) {
    			ast_log(LOG_NOTICE, "Failed to process CEL configuration; using defaults\n");
    
    			ao2_global_obj_replace_unref(cel_configs, cel_cfg);
    
    	if (create_subscriptions()) {
    
    		return -1;
    	}
    
    	if (ast_cel_check_enabled() && create_routes()) {
    
    	ast_register_cleanup(cel_engine_cleanup);
    
    int ast_cel_engine_reload(void)
    
    {
    	unsigned int was_enabled = ast_cel_check_enabled();
    	unsigned int is_enabled;
    
    	if (aco_process_config(&cel_cfg_info, 1) == ACO_PROCESS_ERROR) {
    		return -1;
    	}
    
    	is_enabled = ast_cel_check_enabled();
    
    	if (!was_enabled && is_enabled) {
    
    		if (create_routes()) {
    
    			return -1;
    		}
    	} else if (was_enabled && !is_enabled) {
    
    		destroy_routes();
    
    	}
    
    	ast_verb(3, "CEL logging %sabled.\n", is_enabled ? "en" : "dis");
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    void ast_cel_publish_event(struct ast_channel *chan,
    	enum ast_cel_event_type event_type,
    	struct ast_json *blob)
    {
    
    	struct ast_json *cel_blob;
    	struct stasis_message *message;
    
    
    	cel_blob = ast_json_pack("{s: i, s: o}",
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    		"event_type", event_type,
    
    		"event_details", ast_json_ref(blob));
    
    	message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(chan), cel_generic_type(), cel_blob);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    	if (message) {
    		stasis_publish(ast_cel_topic(), message);
    	}
    
    	ao2_cleanup(message);
    	ast_json_unref(cel_blob);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    }
    
    struct stasis_topic *ast_cel_topic(void)
    {
    	return cel_topic;
    }
    
    
    struct ast_cel_general_config *ast_cel_get_config(void)
    {
    	RAII_VAR(struct cel_config *, mod_cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
    
    	if (!mod_cfg || !mod_cfg->general) {
    
    	ao2_ref(mod_cfg->general, +1);
    	return mod_cfg->general;
    }
    
    void ast_cel_set_config(struct ast_cel_general_config *config)
    {
    
    	struct ast_cel_general_config *cleanup_config;
    	struct cel_config *mod_cfg = ao2_global_obj_ref(cel_configs);
    
    	if (mod_cfg) {
    
    		was_enabled = ast_cel_check_enabled();
    
    		cleanup_config = mod_cfg->general;
    		ao2_bump(config);
    
    		mod_cfg->general = config;
    
    		ao2_cleanup(cleanup_config);
    
    
    		is_enabled = ast_cel_check_enabled();
    		if (!was_enabled && is_enabled) {
    
    			create_routes();
    
    		} else if (was_enabled && !is_enabled) {
    
    			destroy_routes();
    
    int ast_cel_backend_unregister(const char *name)
    {
    
    	struct ao2_container *backends = ao2_global_obj_ref(cel_backends);
    
    	if (backends) {
    		ao2_find(backends, name, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
    		ao2_ref(backends, -1);
    
    	}
    
    	return 0;
    }
    
    int ast_cel_backend_register(const char *name, ast_cel_backend_cb backend_callback)
    {
    
    	RAII_VAR(struct ao2_container *, backends, ao2_global_obj_ref(cel_backends), ao2_cleanup);
    	struct cel_backend *backend;
    
    	if (!backends || ast_strlen_zero(name) || !backend_callback) {
    
    	/* The backend object is immutable so it doesn't need a lock of its own. */
    	backend = ao2_alloc_options(sizeof(*backend) + 1 + strlen(name), NULL,
    		AO2_ALLOC_OPT_LOCK_NOLOCK);
    
    	strcpy(backend->name, name);/* Safe */
    
    	backend->callback = backend_callback;
    
    
    	ao2_link(backends, backend);
    	ao2_ref(backend, -1);