diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h index d40920aa58..505204a4fd 100644 --- a/include/asterisk/cel.h +++ b/include/asterisk/cel.h @@ -263,6 +263,68 @@ void ast_cel_publish_event(struct ast_channel *chan, */ struct stasis_topic *ast_cel_topic(void); +/*! \brief A structure to hold CEL global configuration options */ +struct ast_cel_general_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(date_format); /*!< The desired date format for logging */ + ); + int enable; /*!< Whether CEL is enabled */ + int64_t events; /*!< The events to be logged */ + /*! The apps for which to log app start and end events. This is + * ast_str_container_alloc()ed and filled with ao2-allocated + * char* which are all-lowercase application names. */ + struct ao2_container *apps; +}; + +/*! + * \brief Allocate a CEL configuration object + * + * \retval NULL on error + * \retval The new CEL configuration object + */ +void *ast_cel_general_config_alloc(void); + +/*! + * \since 12 + * \brief Obtain the current CEL configuration + * + * The configuration is a ref counted object. The caller of this function must + * decrement the ref count when finished with the configuration. + * + * \retval NULL on error + * \retval The current CEL configuration + */ +struct ast_cel_general_config *ast_cel_get_config(void); + +/*! + * \since 12 + * \brief Set the current CEL configuration + * + * \param config The new CEL configuration + */ +void ast_cel_set_config(struct ast_cel_general_config *config); + +struct ast_channel_snapshot; +/*! + * \brief Allocate and populate a CEL event structure + * + * \param snapshot An ast_channel_snapshot of the primary channel associated + * with this channel event. + * \param event_type The type of call event being reported. + * \param userdefevname Custom name for the call event. (optional) + * \param extra An opaque field that will go into the "CEL_EXTRA" information + * element of the call event. (optional) + * \param peer_name The peer name to be placed into the event. (optional) + * + * \since 12 + * + * \retval The created ast_event structure + * \retval NULL on failure + */ +struct ast_event *ast_cel_create_event(struct ast_channel_snapshot *snapshot, + enum ast_cel_event_type event_type, const char *userdefevname, + const char *extra, const char *peer_name); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/cel.c b/main/cel.c index a1f03dd5ed..30838f369e 100644 --- a/main/cel.c +++ b/main/cel.c @@ -180,28 +180,18 @@ static struct ao2_container *cel_dialstatus_store; */ static struct ao2_container *linkedids; -/*! \brief A structure to hold global configuration-related options */ -struct cel_general_config { - AST_DECLARE_STRING_FIELDS( - AST_STRING_FIELD(date_format); /*!< The desired date format for logging */ - ); - int enable; /*!< Whether CEL is enabled */ - int64_t events; /*!< The events to be logged */ - struct ao2_container *apps; /*!< The apps for which to log app start and end events */ -}; - /*! \brief Destructor for cel_config */ static void cel_general_config_dtor(void *obj) { - struct cel_general_config *cfg = obj; + struct ast_cel_general_config *cfg = obj; ast_string_field_free_memory(cfg); ao2_cleanup(cfg->apps); cfg->apps = NULL; } -static void *cel_general_config_alloc(void) +void *ast_cel_general_config_alloc(void) { - RAII_VAR(struct cel_general_config *, cfg, NULL, ao2_cleanup); + RAII_VAR(struct ast_cel_general_config *, cfg, NULL, ao2_cleanup); if (!(cfg = ao2_alloc(sizeof(*cfg), cel_general_config_dtor))) { return NULL; @@ -221,7 +211,7 @@ static void *cel_general_config_alloc(void) /*! \brief A container that holds all config-related information */ struct cel_config { - struct cel_general_config *general; + struct ast_cel_general_config *general; }; @@ -243,7 +233,7 @@ static void *cel_config_alloc(void) return NULL; } - if (!(cfg->general = cel_general_config_alloc())) { + if (!(cfg->general = ast_cel_general_config_alloc())) { return NULL; } @@ -251,7 +241,7 @@ static void *cel_config_alloc(void) return cfg; } -/*! \brief An aco_type structure to link the "general" category to the cel_general_config type */ +/*! \brief An aco_type structure to link the "general" category to the ast_cel_general_config type */ static struct aco_type general_option = { .type = ACO_GLOBAL, .name = "general", @@ -559,7 +549,7 @@ static int ast_cel_track_event(enum ast_cel_event_type et) static int events_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { - struct cel_general_config *cfg = obj; + struct ast_cel_general_config *cfg = obj; char *events = ast_strdupa(var->value); char *cur_event; @@ -589,7 +579,7 @@ static int events_handler(const struct aco_option *opt, struct ast_variable *var static int apps_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { - struct cel_general_config *cfg = obj; + struct ast_cel_general_config *cfg = obj; char *apps = ast_strdupa(var->value); char *cur_app; @@ -642,11 +632,41 @@ static int cel_track_app(const char *const_app) } static int cel_linkedid_ref(const char *linkedid); +struct ast_event *ast_cel_create_event(struct ast_channel_snapshot *snapshot, + enum ast_cel_event_type event_type, const char *userdefevname, + const char *extra, const char *peer_name) +{ + struct timeval eventtime = ast_tvnow(); + return ast_event_new(AST_EVENT_CEL, + AST_EVENT_IE_CEL_EVENT_TYPE, AST_EVENT_IE_PLTYPE_UINT, event_type, + AST_EVENT_IE_CEL_EVENT_TIME, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_sec, + AST_EVENT_IE_CEL_EVENT_TIME_USEC, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_usec, + AST_EVENT_IE_CEL_USEREVENT_NAME, AST_EVENT_IE_PLTYPE_STR, S_OR(userdefevname, ""), + AST_EVENT_IE_CEL_CIDNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_name, + AST_EVENT_IE_CEL_CIDNUM, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_number, + AST_EVENT_IE_CEL_CIDANI, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_ani, + AST_EVENT_IE_CEL_CIDRDNIS, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_rdnis, + AST_EVENT_IE_CEL_CIDDNID, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_dnid, + AST_EVENT_IE_CEL_EXTEN, AST_EVENT_IE_PLTYPE_STR, snapshot->exten, + AST_EVENT_IE_CEL_CONTEXT, AST_EVENT_IE_PLTYPE_STR, snapshot->context, + AST_EVENT_IE_CEL_CHANNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->name, + AST_EVENT_IE_CEL_APPNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->appl, + AST_EVENT_IE_CEL_APPDATA, AST_EVENT_IE_PLTYPE_STR, snapshot->data, + AST_EVENT_IE_CEL_AMAFLAGS, AST_EVENT_IE_PLTYPE_UINT, snapshot->amaflags, + AST_EVENT_IE_CEL_ACCTCODE, AST_EVENT_IE_PLTYPE_STR, snapshot->accountcode, + AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, snapshot->peeraccount, + AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, snapshot->uniqueid, + AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, snapshot->linkedid, + AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, snapshot->userfield, + AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, S_OR(extra, ""), + AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, S_OR(peer_name, ""), + AST_EVENT_IE_END); +} + static int report_event_snapshot(struct ast_channel_snapshot *snapshot, enum ast_cel_event_type event_type, const char *userdefevname, const char *extra, const char *peer2_name) { - struct timeval eventtime; struct ast_event *ev; char *linkedid = ast_strdupa(snapshot->linkedid); const char *peer_name = peer2_name; @@ -685,33 +705,7 @@ static int report_event_snapshot(struct ast_channel_snapshot *snapshot, return 0; } - eventtime = ast_tvnow(); - - ev = ast_event_new(AST_EVENT_CEL, - AST_EVENT_IE_CEL_EVENT_TYPE, AST_EVENT_IE_PLTYPE_UINT, event_type, - AST_EVENT_IE_CEL_EVENT_TIME, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_sec, - AST_EVENT_IE_CEL_EVENT_TIME_USEC, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_usec, - AST_EVENT_IE_CEL_USEREVENT_NAME, AST_EVENT_IE_PLTYPE_STR, S_OR(userdefevname, ""), - AST_EVENT_IE_CEL_CIDNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_name, - AST_EVENT_IE_CEL_CIDNUM, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_number, - AST_EVENT_IE_CEL_CIDANI, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_ani, - AST_EVENT_IE_CEL_CIDRDNIS, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_rdnis, - AST_EVENT_IE_CEL_CIDDNID, AST_EVENT_IE_PLTYPE_STR, snapshot->caller_dnid, - AST_EVENT_IE_CEL_EXTEN, AST_EVENT_IE_PLTYPE_STR, snapshot->exten, - AST_EVENT_IE_CEL_CONTEXT, AST_EVENT_IE_PLTYPE_STR, snapshot->context, - AST_EVENT_IE_CEL_CHANNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->name, - AST_EVENT_IE_CEL_APPNAME, AST_EVENT_IE_PLTYPE_STR, snapshot->appl, - AST_EVENT_IE_CEL_APPDATA, AST_EVENT_IE_PLTYPE_STR, snapshot->data, - AST_EVENT_IE_CEL_AMAFLAGS, AST_EVENT_IE_PLTYPE_UINT, snapshot->amaflags, - AST_EVENT_IE_CEL_ACCTCODE, AST_EVENT_IE_PLTYPE_STR, snapshot->accountcode, - AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, snapshot->peeraccount, - AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, snapshot->uniqueid, - AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, snapshot->linkedid, - AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, snapshot->userfield, - AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, S_OR(extra, ""), - AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, S_OR(peer_name, ""), - AST_EVENT_IE_END); - + ev = ast_cel_create_event(snapshot, event_type, userdefevname, extra, peer_name); if (ev && ast_event_queue(ev)) { ast_event_destroy(ev); return -1; @@ -1216,12 +1210,9 @@ static void cel_snapshot_update_cb(void *data, struct stasis_subscription *sub, cel_channel_monitors[i](old_snapshot, new_snapshot); } } else if (ast_bridge_snapshot_type() == update->type) { - RAII_VAR(struct bridge_assoc *, assoc, NULL, ao2_cleanup); struct ast_bridge_snapshot *old_snapshot; struct ast_bridge_snapshot *new_snapshot; - update = stasis_message_data(message); - old_snapshot = stasis_message_data(update->old_snapshot); new_snapshot = stasis_message_data(update->new_snapshot); @@ -1233,62 +1224,6 @@ static void cel_snapshot_update_cb(void *data, struct stasis_subscription *sub, clear_bridge_primary(old_snapshot->uniqueid); return; } - - if (old_snapshot->capabilities == new_snapshot->capabilities) { - return; - } - - /* handle 1:1/native -> multimix */ - if ((old_snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE)) - && (new_snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) { - assoc = find_bridge_primary_by_bridge_id(new_snapshot->uniqueid); - if (!assoc) { - ast_log(LOG_ERROR, "No association found for bridge %s\n", new_snapshot->uniqueid); - return; - } - - /* this bridge will no longer be treated like a bridge, so mark the bridge_assoc as such */ - assoc->track_as_conf = 1; - report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL, NULL, assoc->secondary_name); - return; - } - - /* handle multimix -> 1:1/native */ - if ((old_snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) - && (new_snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE))) { - struct ao2_iterator i; - RAII_VAR(char *, channel_id, NULL, ao2_cleanup); - RAII_VAR(struct ast_channel_snapshot *, chan_snapshot, NULL, ao2_cleanup); - - assoc = find_bridge_primary_by_bridge_id(new_snapshot->uniqueid); - if (assoc) { - assoc->track_as_conf = 1; - return; - } - - /* get the first item in the container */ - i = ao2_iterator_init(new_snapshot->channels, 0); - while ((channel_id = ao2_iterator_next(&i))) { - break; - } - ao2_iterator_destroy(&i); - - /* create a bridge_assoc for this bridge and mark it as being tracked appropriately */ - chan_snapshot = ast_channel_snapshot_get_latest(channel_id); - if (!chan_snapshot) { - return; - } - - ast_assert(chan_snapshot != NULL); - assoc = bridge_assoc_alloc(chan_snapshot, new_snapshot->uniqueid, chan_snapshot->name); - if (!assoc) { - return; - } - assoc->track_as_conf = 1; - - ao2_link(bridge_primaries, assoc); - return; - } } } @@ -1300,9 +1235,9 @@ static void cel_bridge_enter_cb( 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 bridge_assoc *, assoc, find_bridge_primary_by_bridge_id(snapshot->uniqueid), ao2_cleanup); if (snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE)) { - RAII_VAR(struct bridge_assoc *, assoc, find_bridge_primary_by_bridge_id(snapshot->uniqueid), ao2_cleanup); if (assoc && assoc->track_as_conf) { report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL); return; @@ -1331,8 +1266,25 @@ static void cel_bridge_enter_cb( add_bridge_primary(latest_primary, snapshot->uniqueid, chan_snapshot->name); report_event_snapshot(latest_primary, AST_CEL_BRIDGE_START, NULL, NULL, chan_snapshot->name); + } else if (ao2_container_count(snapshot->channels) > 2) { + if (!assoc) { + ast_log(LOG_ERROR, "No association found for bridge %s\n", snapshot->uniqueid); + return; + } + + /* this bridge will no longer be treated like a bridge, so mark the bridge_assoc as such */ + if (!assoc->track_as_conf) { + assoc->track_as_conf = 1; + report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL, + chan_snapshot->name, assoc->secondary_name); + ast_string_field_set(assoc, secondary_name, ""); + } } } else if (snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { + if (!assoc) { + add_bridge_primary(chan_snapshot, snapshot->uniqueid, ""); + return; + } report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL); } } @@ -1585,8 +1537,8 @@ int ast_cel_engine_init(void) return -1; } - aco_option_register(&cel_cfg_info, "enable", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct cel_general_config, enable)); - aco_option_register(&cel_cfg_info, "dateformat", ACO_EXACT, general_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct cel_general_config, date_format)); + 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); @@ -1623,3 +1575,19 @@ 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); + ao2_ref(mod_cfg->general, +1); + return mod_cfg->general; +} + +void ast_cel_set_config(struct ast_cel_general_config *config) +{ + RAII_VAR(struct cel_config *, mod_cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup); + ao2_cleanup(mod_cfg->general); + mod_cfg->general = config; + ao2_ref(mod_cfg->general, +1); +} + diff --git a/tests/test_cel.c b/tests/test_cel.c new file mode 100644 index 0000000000..fa1b307afc --- /dev/null +++ b/tests/test_cel.c @@ -0,0 +1,1450 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Kinsey Moore + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief CEL unit tests + * + * \author Kinsey Moore + * + */ + +/*** MODULEINFO + TEST_FRAMEWORK + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include "asterisk/module.h" +#include "asterisk/test.h" +#include "asterisk/cel.h" +#include "asterisk/channel.h" +#include "asterisk/linkedlists.h" +#include "asterisk/chanvars.h" +#include "asterisk/utils.h" +#include "asterisk/causes.h" +#include "asterisk/time.h" +#include "asterisk/bridging.h" +#include "asterisk/bridging_basic.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/stasis_bridging.h" + +#define TEST_CATEGORY "/main/cel/" + +#define CHANNEL_TECH_NAME "CELTestChannel" + +/*! \brief A placeholder for Asterisk's 'real' CEL configuration */ +static struct ast_cel_general_config *saved_config; + +/*! \brief The CEL config used for CEL unit tests */ +static struct ast_cel_general_config *cel_test_config; + +/*! \brief A channel technology used for the unit tests */ +static struct ast_channel_tech test_cel_chan_tech = { + .type = CHANNEL_TECH_NAME, + .description = "Mock channel technology for CEL tests", +}; + +/*! \brief A 1 second sleep */ +static struct timespec to_sleep = {1, 0}; + +static void do_sleep(void) +{ + while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)); +} + +#define APPEND_EVENT(chan, ev_type, userevent, extra, peer) do { \ + if (append_expected_event(chan, ev_type, userevent, extra, peer)) { \ + return AST_TEST_FAIL; \ + } \ + } while (0) + +/*! \brief Alice's Caller ID */ +#define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, } + +/*! \brief Bob's Caller ID */ +#define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, } + +/*! \brief Charlie's Caller ID */ +#define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, } + +/*! \brief David's Caller ID */ +#define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, } + +/*! \brief Create a \ref test_cel_chan_tech for Alice. */ +#define CREATE_ALICE_CHANNEL(channel_var, caller_id) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice"); \ + /*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \ + APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Create a \ref test_cel_chan_tech for Bob. */ +#define CREATE_BOB_CHANNEL(channel_var, caller_id) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "200", "200", "default", NULL, 0, CHANNEL_TECH_NAME "/Bob"); \ + /*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \ + APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Create a \ref test_cel_chan_tech for Charlie. */ +#define CREATE_CHARLIE_CHANNEL(channel_var, caller_id) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "300", "300", "default", NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \ + /*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \ + APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Create a \ref test_cel_chan_tech for Charlie. */ +#define CREATE_DAVID_CHANNEL(channel_var, caller_id) do { \ + (channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "400", "400", "default", NULL, 0, CHANNEL_TECH_NAME "/David"); \ + /*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \ + APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Emulate a channel entering into an application */ +#define EMULATE_APP_DATA(channel, priority, application, data) do { \ + if ((priority) > 0) { \ + ast_channel_priority_set((channel), (priority)); \ + } \ + ast_channel_appl_set((channel), (application)); \ + ast_channel_data_set((channel), (data)); \ + ast_channel_publish_snapshot((channel)); \ + } while (0) + +#define ANSWER_CHANNEL(chan) do { \ + EMULATE_APP_DATA(chan, 1, "Answer", ""); \ + ANSWER_NO_APP(chan); \ + } while (0) + +#define ANSWER_NO_APP(chan) do { \ + ast_setstate(chan, AST_STATE_UP); \ + APPEND_EVENT(chan, AST_CEL_ANSWER, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Hang up a test channel safely */ +#define HANGUP_CHANNEL(channel, cause, hangup_extra) do { \ + ast_channel_hangupcause_set((channel), (cause)); \ + ao2_ref(channel, +1); \ + if (!ast_hangup((channel))) { \ + APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, hangup_extra, NULL); \ + APPEND_EVENT(channel, AST_CEL_CHANNEL_END, NULL, NULL, NULL); \ + ao2_cleanup(stasis_cache_get_extended(ast_channel_topic_all_cached(), \ + ast_channel_snapshot_type(), ast_channel_uniqueid(channel), 1)); \ + ao2_cleanup(channel); \ + channel = NULL; \ + } else { \ + APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, hangup_extra, NULL); \ + APPEND_EVENT(channel, AST_CEL_CHANNEL_END, NULL, NULL, NULL); \ + ao2_cleanup(stasis_cache_get_extended(ast_channel_topic_all_cached(), \ + ast_channel_snapshot_type(), ast_channel_uniqueid(channel), 1)); \ + ao2_cleanup(channel); \ + } \ + } while (0) + +static int append_expected_event( + struct ast_channel *chan, + enum ast_cel_event_type type, + const char *userdefevname, + const char *extra, const char *peer); + +static void safe_channel_release(struct ast_channel *chan) +{ + if (!chan) { + return; + } + ast_channel_release(chan); +} + +AST_TEST_DEFINE(test_cel_channel_creation) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test the CEL records created when a channel is created"; + info->description = + "Test the CEL records created when a channel is created"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan, (&caller)); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_unanswered_inbound_call) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test inbound unanswered calls"; + info->description = + "Test CEL records for a call that is\n" + "inbound to Asterisk, executes some dialplan, but\n" + "is never answered.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan, &caller); + + EMULATE_APP_DATA(chan, 1, "Wait", "1"); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_unanswered_outbound_call) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + struct ast_party_caller caller = { + .id.name.str = "", + .id.name.valid = 1, + .id.number.str = "", + .id.number.valid = 1, }; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test outbound unanswered calls"; + info->description = + "Test CEL records for a call that is\n" + "outbound to Asterisk but is never answered.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan, &caller); + + ast_channel_exten_set(chan, "s"); + ast_channel_context_set(chan, "default"); + ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED); + EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_party) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party"; + info->description = + "Test CEL records for a call that is\n" + "answered, but only involves a single channel\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + CREATE_ALICE_CHANNEL(chan, &caller); + + ANSWER_CHANNEL(chan); + EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1"); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_bridge) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party entering/leaving a bridge"; + info->description = + "Test CEL records for a call that is\n" + "answered, enters a bridge, and leaves it.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan, &caller); + + ANSWER_CHANNEL(chan); + EMULATE_APP_DATA(chan, 2, "Bridge", ""); + + do_sleep(); + ast_bridge_impart(bridge, chan, NULL, NULL, 0); + + do_sleep(); + + ast_bridge_depart(chan); + + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_bridge_continue) +{ + RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party entering/leaving a bridge"; + info->description = + "Test CEL records for a call that is\n" + "answered, enters a bridge, and leaves it.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan, &caller); + + ANSWER_CHANNEL(chan); + EMULATE_APP_DATA(chan, 2, "Bridge", ""); + + do_sleep(); + ast_bridge_impart(bridge, chan, NULL, NULL, 0); + + do_sleep(); + + ast_bridge_depart(chan); + + EMULATE_APP_DATA(chan, 3, "Wait", ""); + + /* And then it hangs up */ + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_twoparty_bridge_a) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party entering/leaving a bridge"; + info->description = + "Test CEL records for a call that is\n" + "answered, enters a bridge, and leaves it. In this scenario, the\n" + "Party A should answer the bridge first.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice); + + CREATE_BOB_CHANNEL(chan_bob, &caller_bob); + + ANSWER_CHANNEL(chan_alice); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + do_sleep(); + + ANSWER_CHANNEL(chan_bob); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_bob)); + + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_bob)); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_twoparty_bridge_b) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party entering/leaving a bridge"; + info->description = + "Test CEL records for a call that is\n" + "answered, enters a bridge, and leaves it. In this scenario, the\n" + "Party B should answer the bridge first.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice); + + CREATE_BOB_CHANNEL(chan_bob, &caller_bob); + + ANSWER_CHANNEL(chan_alice); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + ANSWER_CHANNEL(chan_bob); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + do_sleep(); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + do_sleep(); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_alice)); + + ast_bridge_depart(chan_alice); + ast_bridge_depart(chan_bob); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_alice)); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_single_multiparty_bridge) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller_alice = ALICE_CALLERID; + struct ast_party_caller caller_bob = BOB_CALLERID; + struct ast_party_caller caller_charlie = CHARLIE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a single party entering/leaving a multi-party bridge"; + info->description = + "Test CEL records for a call that is\n" + "answered, enters a bridge, and leaves it. A total of three\n" + "parties perform this action.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &caller_alice); + CREATE_BOB_CHANNEL(chan_bob, &caller_bob); + CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie); + + ANSWER_CHANNEL(chan_alice); + EMULATE_APP_DATA(chan_alice, 2, "Bridge", ""); + + do_sleep(); + + ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0); + + ANSWER_CHANNEL(chan_bob); + EMULATE_APP_DATA(chan_bob, 2, "Bridge", ""); + do_sleep(); + + ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_bob)); + + ANSWER_CHANNEL(chan_charlie); + EMULATE_APP_DATA(chan_charlie, 2, "Bridge", ""); + ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_TO_CONF, NULL, ast_channel_name(chan_charlie), ast_channel_name(chan_bob)); + + ast_bridge_depart(chan_alice); + APPEND_EVENT(chan_alice, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + ast_bridge_depart(chan_bob); + APPEND_EVENT(chan_bob, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + ast_bridge_depart(chan_charlie); + APPEND_EVENT(chan_charlie, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +#define EMULATE_DIAL(channel, dialstring) do { \ + EMULATE_APP_DATA(channel, 1, "Dial", dialstring); \ + if (append_expected_event(channel, AST_CEL_APP_START, NULL, NULL, NULL)) { \ + return AST_TEST_FAIL; \ + } \ + } while (0) + +#define START_DIALED(caller, callee) \ + START_DIALED_FULL(caller, callee, "200", "Bob") + +#define START_DIALED_FULL(caller, callee, number, name) do { \ + callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, number, NULL, NULL, ast_channel_linkedid(caller), 0, CHANNEL_TECH_NAME "/" name); \ + if (append_expected_event(callee, AST_CEL_CHANNEL_START, NULL, NULL, NULL)) { \ + return AST_TEST_FAIL; \ + } \ + ast_set_flag(ast_channel_flags(callee), AST_FLAG_OUTGOING); \ + EMULATE_APP_DATA(callee, 0, "AppDial", "(Outgoing Line)"); \ + ast_channel_publish_dial(caller, callee, name, NULL); \ + } while (0) + +AST_TEST_DEFINE(test_cel_dial_unanswered) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a dial that isn't answered"; + info->description = + "Test CEL records for a channel that\n" + "performs a dial operation that isn't answered\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER, "19,,NOANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER, "19,,"); + + return AST_TEST_PASS; +} + + +AST_TEST_DEFINE(test_cel_dial_busy) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a dial that results in a busy"; + info->description = + "Test CEL records for a channel that\n" + "performs a dial operation to an endpoint that's busy\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY, "17,,BUSY"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY, "17,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_congestion) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a dial that results in congestion"; + info->description = + "Test CEL records for a channel that\n" + "performs a dial operation to an endpoint that's congested\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION, "34,,CONGESTION"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION, "34,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_unavailable) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a dial that results in unavailable"; + info->description = + "Test CEL records for a channel that\n" + "performs a dial operation to an endpoint that's unavailable\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION, "3,,CHANUNAVAIL"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION, "3,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_caller_cancel) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test CEL for a dial where the caller cancels"; + info->description = + "Test CEL records for a channel that\n" + "performs a dial operation to an endpoint but then decides\n" + "to hang up, cancelling the dial\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL"); + + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,CANCEL"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_parallel_failed) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test a parallel dial where all channels fail to answer"; + info->description = + "This tests dialing three parties: Bob, Charlie, David. Charlie\n" + "returns BUSY; David returns CONGESTION; Bob fails to answer and\n" + "Alice hangs up. Three records are created for Alice as a result.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + /* Channel enters Dial app */ + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David"); + + /* Outbound channels are created */ + START_DIALED_FULL(chan_caller, chan_bob, "200", "Bob"); + START_DIALED_FULL(chan_caller, chan_charlie, "300", "Charlie"); + START_DIALED_FULL(chan_caller, chan_david, "400", "David"); + + /* Dial starts */ + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + + /* Charlie is busy */ + ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY"); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY, "17,,"); + + /* David is congested */ + ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION"); + HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION, "34,,"); + + /* Bob is canceled */ + ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + + /* Alice hangs up */ + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,BUSY"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_answer_no_bridge) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and not going into a bridge."; + info->description = + "This is a weird one, but theoretically possible. You can perform\n" + "a dial, then bounce both channels to different priorities and\n" + "never have them enter a bridge together. Ew. This makes sure that\n" + "when we answer, we get a CEL, it gets ended at that point, and\n" + "that it gets finalized appropriately.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ANSWER_NO_APP(chan_caller); + ast_clear_flag(ast_channel_flags(chan_callee), AST_FLAG_OUTGOING); + ANSWER_NO_APP(chan_callee); + + EMULATE_APP_DATA(chan_caller, 2, "Wait", "1"); + EMULATE_APP_DATA(chan_callee, 1, "Wait", "1"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_answer_twoparty_bridge_a) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a 2-party bridge"; + info->description = + "The most 'basic' of scenarios\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ANSWER_NO_APP(chan_caller); + ANSWER_NO_APP(chan_callee); + + do_sleep(); + + ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0); + do_sleep(); + ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_caller, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_callee)); + + ast_bridge_depart(chan_caller); + ast_bridge_depart(chan_callee); + APPEND_EVENT(chan_caller, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_callee)); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_answer_twoparty_bridge_b) +{ + RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a 2-party bridge"; + info->description = + "The most 'basic' of scenarios\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_caller, &caller); + + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + ANSWER_NO_APP(chan_caller); + ANSWER_NO_APP(chan_callee); + + do_sleep(); + ast_bridge_impart(bridge, chan_callee, NULL, NULL, 0); + do_sleep(); + ast_bridge_impart(bridge, chan_caller, NULL, NULL, 0); + do_sleep(); + APPEND_EVENT(chan_callee, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_caller)); + + ast_bridge_depart(chan_caller); + ast_bridge_depart(chan_callee); + APPEND_EVENT(chan_callee, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_caller)); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_answer_multiparty) +{ + RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_party_caller charlie_caller = CHARLIE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test dialing, answering, and going into a multi-party bridge"; + info->description = + "A little tricky to get to do, but possible with some redirects.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + bridge = ast_bridge_basic_new(); + ast_test_validate(test, bridge != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller); + + EMULATE_DIAL(chan_alice, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_alice, chan_bob); + + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller); + EMULATE_DIAL(chan_charlie, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED_FULL(chan_charlie, chan_david, "400", "David"); + + ast_channel_state_set(chan_alice, AST_STATE_RINGING); + ast_channel_state_set(chan_charlie, AST_STATE_RINGING); + ast_channel_publish_dial(chan_alice, chan_bob, NULL, "ANSWER"); + ast_channel_publish_dial(chan_charlie, chan_david, NULL, "ANSWER"); + + ANSWER_NO_APP(chan_alice); + ANSWER_NO_APP(chan_bob); + ANSWER_NO_APP(chan_charlie); + ANSWER_NO_APP(chan_david); + + do_sleep(); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0)); + do_sleep(); + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_david, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_charlie, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_david)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_charlie, AST_CEL_BRIDGE_TO_CONF, NULL, ast_channel_name(chan_bob), ast_channel_name(chan_david)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_alice, AST_CEL_CONF_ENTER, NULL, NULL, NULL); + + ast_test_validate(test, 0 == ast_bridge_depart(chan_alice)); + APPEND_EVENT(chan_alice, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + + ast_test_validate(test, 0 == ast_bridge_depart(chan_bob)); + APPEND_EVENT(chan_bob, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + + ast_test_validate(test, 0 == ast_bridge_depart(chan_charlie)); + APPEND_EVENT(chan_charlie, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + + ast_test_validate(test, 0 == ast_bridge_depart(chan_david)); + APPEND_EVENT(chan_david, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,ANSWER"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "16,,ANSWER"); + HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL, "16,,"); + + return AST_TEST_PASS; +} + +/*! Subscription for CEL events */ +static struct ast_event_sub *event_sub = NULL; + +/*! Container for astobj2 duplicated ast_events */ +static struct ao2_container *cel_received_events = NULL; + +/*! Container for expected CEL events */ +static struct ao2_container *cel_expected_events = NULL; + +static struct ast_event *ao2_dup_event(const struct ast_event *event) +{ + struct ast_event *event_dup; + uint16_t event_len; + + event_len = ast_event_get_size(event); + + event_dup = ao2_alloc(event_len, NULL); + if (!event_dup) { + return NULL; + } + + memcpy(event_dup, event, event_len); + + return event_dup; +} + +static int append_expected_event( + struct ast_channel *chan, + enum ast_cel_event_type type, + const char *userdefevname, + const char *extra, const char *peer) +{ + RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_event *, ev, NULL, ast_free); + RAII_VAR(struct ast_event *, ao2_ev, NULL, ao2_cleanup); + snapshot = ast_channel_snapshot_create(chan); + if (!snapshot) { + return -1; + } + + ev = ast_cel_create_event(snapshot, type, userdefevname, extra, peer); + if (!ev) { + return -1; + } + + ao2_ev = ao2_dup_event(ev); + if (!ao2_ev) { + return -1; + } + + ao2_link(cel_expected_events, ao2_ev); + return 0; +} + +ast_mutex_t sync_lock; +ast_cond_t sync_out; + +static void test_sub(const struct ast_event *event, void *data) +{ + struct ast_event *event_dup = ao2_dup_event(event); + const char *sync_tag; + if (!event_dup) { + return; + } + + sync_tag = ast_event_get_ie_str(event, AST_EVENT_IE_SERVICE); + if (sync_tag) { + if (!strcmp(sync_tag, "SYNC")) { + /* trigger things */ + SCOPED_MUTEX(lock, &sync_lock); + ast_cond_signal(&sync_out); + return; + } + } + /* save the event for later processing */ + ao2_link(cel_received_events, event_dup); +} + +/*! + * \internal \brief Callback function called before each test executes + */ +static int test_cel_init_cb(struct ast_test_info *info, struct ast_test *test) +{ + ast_assert(event_sub == NULL); + ast_assert(cel_received_events == NULL); + ast_assert(cel_expected_events == NULL); + + ast_mutex_init(&sync_lock); + ast_cond_init(&sync_out, NULL); + + /* Back up the real CEL config and insert the test's config */ + saved_config = ast_cel_get_config(); + ast_cel_set_config(cel_test_config); + + /* init CEL event storage (degenerate hash table becomes a linked list) */ + cel_received_events = ao2_container_alloc(1, NULL, NULL); + cel_expected_events = ao2_container_alloc(1, NULL, NULL); + + /* start the CEL event callback */ + event_sub = ast_event_subscribe(AST_EVENT_CEL, test_sub, "CEL Test Logging", + NULL, AST_EVENT_IE_END); + return 0; +} + +/*! \brief Check an IE value from two events, */ +static int match_ie_val( + const struct ast_event *event1, + const struct ast_event *event2, + enum ast_event_ie_type type) +{ + enum ast_event_ie_pltype pltype = ast_event_get_ie_pltype(type); + + switch (pltype) { + case AST_EVENT_IE_PLTYPE_UINT: + { + uint32_t val = ast_event_get_ie_uint(event2, type); + + return (val == ast_event_get_ie_uint(event1, type)) ? 1 : 0; + } + case AST_EVENT_IE_PLTYPE_STR: + { + const char *str; + uint32_t hash; + + hash = ast_event_get_ie_str_hash(event2, type); + if (hash != ast_event_get_ie_str_hash(event1, type)) { + return 0; + } + + str = ast_event_get_ie_str(event2, type); + if (str) { + const char *e1str, *e2str; + e1str = ast_event_get_ie_str(event1, type); + e2str = str; + + if (type == AST_EVENT_IE_DEVICE) { + e1str = ast_tech_to_upper(ast_strdupa(e1str)); + e2str = ast_tech_to_upper(ast_strdupa(e2str)); + } + + if (!strcmp(e1str, e2str)) { + return 1; + } + } + + return 0; + } + default: + break; + } + return 0; +} + +static int events_are_equal(struct ast_event *event1, struct ast_event *event2) +{ + struct ast_event_iterator iterator; + int res; + + for (res = ast_event_iterator_init(&iterator, event1); !res; res = ast_event_iterator_next(&iterator)) { + /* XXX ignore sec/usec for now */ + /* ignore EID */ + int ie_type = ast_event_iterator_get_ie_type(&iterator); + if (ie_type != AST_EVENT_IE_CEL_EVENT_TIME_USEC + && ie_type != AST_EVENT_IE_EID + && ie_type != AST_EVENT_IE_CEL_EVENT_TIME + && !match_ie_val(event1, event2, ie_type)) { + ast_log(LOG_ERROR, "Failed matching on field %s\n", ast_event_get_ie_type_name(ie_type)); + return 0; + } + } + + return 1; +} + +static int dump_event(struct ast_event *event) +{ + struct ast_event_iterator i; + + if (ast_event_iterator_init(&i, event)) { + ast_log(LOG_ERROR, "Failed to initialize event iterator. :-(\n"); + return 0; + } + + ast_log(LOG_ERROR, "Event: %s %s\n", ast_event_get_type_name(event), + ast_cel_get_type_name(ast_event_get_ie_uint(event, AST_EVENT_IE_CEL_EVENT_TYPE))); + + do { + enum ast_event_ie_type ie_type; + enum ast_event_ie_pltype ie_pltype; + const char *ie_type_name; + + ie_type = ast_event_iterator_get_ie_type(&i); + ie_type_name = ast_event_get_ie_type_name(ie_type); + ie_pltype = ast_event_get_ie_pltype(ie_type); + + switch (ie_pltype) { + case AST_EVENT_IE_PLTYPE_UNKNOWN: + case AST_EVENT_IE_PLTYPE_EXISTS: + ast_log(LOG_ERROR, "%s\n", ie_type_name); + break; + case AST_EVENT_IE_PLTYPE_STR: + ast_log(LOG_ERROR, "%.30s: %s\n", ie_type_name, + ast_event_iterator_get_ie_str(&i)); + break; + case AST_EVENT_IE_PLTYPE_UINT: + ast_log(LOG_ERROR, "%.30s: %u\n", ie_type_name, + ast_event_iterator_get_ie_uint(&i)); + break; + case AST_EVENT_IE_PLTYPE_BITFLAGS: + ast_log(LOG_ERROR, "%.30s: %u\n", ie_type_name, + ast_event_iterator_get_ie_bitflags(&i)); + default: + break; + } + } while (!ast_event_iterator_next(&i)); + + ast_log(LOG_ERROR, "\n"); + + return 0; +} + +static int check_events(struct ao2_container *local_expected, struct ao2_container *local_received) +{ + struct ao2_iterator expected_it, received_it; + struct ast_event *rx_event, *ex_event; + int debug = 0; + + if (ao2_container_count(local_expected) != ao2_container_count(local_received)) { + ast_log(LOG_ERROR, "Increasing verbosity since the number of expected events (%d)" + " did not match number of received events (%d).\n", + ao2_container_count(local_expected), + ao2_container_count(local_received)); + debug = 1; + } + + expected_it = ao2_iterator_init(local_expected, 0); + received_it = ao2_iterator_init(local_received, 0); + rx_event = ao2_iterator_next(&received_it); + ex_event = ao2_iterator_next(&expected_it); + while (rx_event && ex_event) { + if (!events_are_equal(rx_event, ex_event)) { + ast_log(LOG_ERROR, "Received event:\n"); + dump_event(rx_event); + ast_log(LOG_ERROR, "Expected event:\n"); + dump_event(ex_event); + return -1; + } + if (debug) { + ast_log(LOG_ERROR, "Compared events successfully\n"); + dump_event(ex_event); + } + ao2_cleanup(rx_event); + ao2_cleanup(ex_event); + rx_event = ao2_iterator_next(&received_it); + ex_event = ao2_iterator_next(&expected_it); + } + + if (rx_event) { + ast_log(LOG_ERROR, "Received event:\n"); + dump_event(rx_event); + ao2_cleanup(rx_event); + return -1; + } + if (ex_event) { + ast_log(LOG_ERROR, "Expected event:\n"); + dump_event(ex_event); + ao2_cleanup(ex_event); + return -1; + } + return 0; +} + +static struct ast_event *create_sync_event(void) +{ + struct ast_event *event_dup; + RAII_VAR(struct ast_event *, event, ao2_callback(cel_expected_events, 0, NULL, NULL), ao2_cleanup); + uint16_t event_len; + + if (!event) { + return NULL; + } + + event_len = ast_event_get_size(event); + + event_dup = ast_calloc(1, event_len); + if (!event_dup) { + return NULL; + } + + memcpy(event_dup, event, event_len); + ast_event_append_ie_str(&event_dup, AST_EVENT_IE_SERVICE, "SYNC"); + + return event_dup; +} + +/*! + * \internal \brief Callback function called after each test executes. + * In addition to cleanup, this function also performs verification + * that the events received during a test match the events that were + * expected to have been generated during the test. + */ +static int cel_verify_and_cleanup_cb(struct ast_test_info *info, struct ast_test *test) +{ + struct ast_event *sync; + RAII_VAR(struct ao2_container *, local_expected, cel_expected_events, ao2_cleanup); + RAII_VAR(struct ao2_container *, local_received, cel_received_events, ao2_cleanup); + ast_assert(event_sub != NULL); + ast_assert(cel_received_events != NULL); + ast_assert(cel_expected_events != NULL); + + do_sleep(); + + /* sync with the event system */ + sync = create_sync_event(); + ast_test_validate(test, sync != NULL); + if (ast_event_queue(sync)) { + ast_event_destroy(sync); + ast_test_validate(test, NULL); + } else { + struct timeval start = ast_tvnow(); + struct timespec end = { + .tv_sec = start.tv_sec + 30, + .tv_nsec = start.tv_usec * 1000 + }; + + SCOPED_MUTEX(lock, &sync_lock); + ast_cond_timedwait(&sync_out, &sync_lock, &end); + } + + /* stop the CEL event callback and clean up storage structures*/ + ast_event_unsubscribe(event_sub); + event_sub = NULL; + + cel_expected_events = NULL; + cel_received_events = NULL; + + /* check events */ + ast_test_validate(test, !check_events(local_expected, local_received)); + + /* Restore the real CEL config */ + ast_cel_set_config(saved_config); + ao2_cleanup(saved_config); + saved_config = NULL; + + /* get rid of events */ + ao2_cleanup(cel_received_events); + cel_received_events = NULL; + ao2_cleanup(cel_expected_events); + cel_expected_events = NULL; + ast_mutex_destroy(&sync_lock); + ast_cond_destroy(&sync_out); + return 0; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(test_cel_channel_creation); + AST_TEST_UNREGISTER(test_cel_unanswered_inbound_call); + AST_TEST_UNREGISTER(test_cel_unanswered_outbound_call); + AST_TEST_UNREGISTER(test_cel_single_party); + AST_TEST_UNREGISTER(test_cel_single_bridge); + AST_TEST_UNREGISTER(test_cel_single_bridge_continue); + AST_TEST_UNREGISTER(test_cel_single_twoparty_bridge_a); + AST_TEST_UNREGISTER(test_cel_single_twoparty_bridge_b); + AST_TEST_UNREGISTER(test_cel_single_multiparty_bridge); + + AST_TEST_UNREGISTER(test_cel_dial_unanswered); + AST_TEST_UNREGISTER(test_cel_dial_congestion); + AST_TEST_UNREGISTER(test_cel_dial_busy); + AST_TEST_UNREGISTER(test_cel_dial_unavailable); + AST_TEST_UNREGISTER(test_cel_dial_caller_cancel); + AST_TEST_UNREGISTER(test_cel_dial_parallel_failed); + AST_TEST_UNREGISTER(test_cel_dial_answer_no_bridge); + AST_TEST_UNREGISTER(test_cel_dial_answer_twoparty_bridge_a); + AST_TEST_UNREGISTER(test_cel_dial_answer_twoparty_bridge_b); + AST_TEST_UNREGISTER(test_cel_dial_answer_multiparty); + + ast_channel_unregister(&test_cel_chan_tech); + + ao2_cleanup(cel_test_config); + cel_test_config = NULL; + + return 0; +} + +static int load_module(void) +{ + /* build the test config */ + cel_test_config = ast_cel_general_config_alloc(); + if (!cel_test_config) { + return -1; + } + cel_test_config->enable = 1; + if (ast_str_container_add(cel_test_config->apps, "dial")) { + return -1; + } + if (ast_str_container_add(cel_test_config->apps, "park")) { + return -1; + } + if (ast_str_container_add(cel_test_config->apps, "queue")) { + return -1; + } + cel_test_config->events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<