From 684c83b29b88d219aa406d0aa1673ee338c9159d Mon Sep 17 00:00:00 2001 From: Kinsey Moore Date: Sat, 20 Jul 2013 13:10:22 +0000 Subject: [PATCH] Add transfer support to CEL This adds CEL support for blind and attended transfers and call pickup. During the course of adding this functionality I noticed that CONF_ENTER, CONF_EXIT, and BRIDGE_TO_CONF events are particularly useless without a bridge identifier, so I added that as well. This adds tests for blind transfers, several types of attended transfers, and call pickup. The extra field in CEL records now consists of a JSON blob whose fields are defined on a per-event basis. Review: https://reviewboard.asterisk.org/r/2658/ (closes issue ASTERISK-21565) git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@394858 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 17 + apps/app_celgenuserevent.c | 4 +- apps/app_dial.c | 1 - apps/app_directed_pickup.c | 1 - apps/app_queue.c | 1 - channels/chan_dahdi.c | 1 - channels/chan_sip.c | 1 - channels/sig_analog.c | 1 - include/asterisk/cel.h | 33 +- main/cel.c | 335 ++++++++++------- main/channel.c | 1 - main/features.c | 5 - main/pbx.c | 1 - tests/test_cel.c | 723 ++++++++++++++++++++++++++++++++----- 14 files changed, 853 insertions(+), 272 deletions(-) diff --git a/CHANGES b/CHANGES index 56389dc810..3634eca55a 100644 --- a/CHANGES +++ b/CHANGES @@ -320,6 +320,23 @@ CDR (Call Detail Records) included in the resulting CDR. If both parties have the same variable, only the Party A value is provided. +CEL (Channel Event Logging) +------------------ + * The 'extra' field of all CEL events that use it now consists of a JSON blob + with key/value pairs which are defined in the Asterisk 12 CEL documentation. + + * AST_CEL_BLINDTRANSFER events now report the transferee bridge unique + identifier, extension, and context in a JSON blob as the extra string + instead of the transferee channel name as the peer. + + * AST_CEL_ATTENDEDTRANSFER events now report the peer as NULL and additional + information in the 'extra' string as a JSON blob. For transfers that occur + between two bridged channels, the 'extra' JSON blob contains the primary + bridge unique identifier, the secondary channel name, and the secondary + bridge unique identifier. For transfers that occur between a bridged channel + and a channel running an app, the 'extra' JSON blob contains the primary + bridge unique identifier, the secondary channel name, and the app name. + Features ------------------- * The BRIDGE_FEATURES channel variable would previously only set features for diff --git a/apps/app_celgenuserevent.c b/apps/app_celgenuserevent.c index d0331aebeb..25065dccb2 100644 --- a/apps/app_celgenuserevent.c +++ b/apps/app_celgenuserevent.c @@ -75,9 +75,9 @@ static int celgenuserevent_exec(struct ast_channel *chan, const char *data) parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); - blob = ast_json_pack("{s: s, s: s}", + blob = ast_json_pack("{s: s, s: {s: s}}", "event", args.event, - "extra", args.extra); + "extra", "extra", args.extra); if (!blob) { return res; } diff --git a/apps/app_dial.c b/apps/app_dial.c index 806859011b..0a43197b44 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -60,7 +60,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stringfields.h" #include "asterisk/global_datastores.h" #include "asterisk/dsp.h" -#include "asterisk/cel.h" #include "asterisk/aoc.h" #include "asterisk/ccss.h" #include "asterisk/indications.h" diff --git a/apps/app_directed_pickup.c b/apps/app_directed_pickup.c index 6fcde07483..2a11371201 100644 --- a/apps/app_directed_pickup.c +++ b/apps/app_directed_pickup.c @@ -46,7 +46,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/features.h" #include "asterisk/manager.h" #include "asterisk/callerid.h" -#include "asterisk/cel.h" #define PICKUPMARK "PICKUPMARK" diff --git a/apps/app_queue.c b/apps/app_queue.c index 668fe57eb8..8c5291a0df 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -103,7 +103,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/taskprocessor.h" #include "asterisk/aoc.h" #include "asterisk/callerid.h" -#include "asterisk/cel.h" #include "asterisk/data.h" #include "asterisk/term.h" #include "asterisk/dial.h" diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c index b8a1b88b98..ac8695369b 100644 --- a/channels/chan_dahdi.c +++ b/channels/chan_dahdi.c @@ -107,7 +107,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/callerid.h" #include "asterisk/adsi.h" #include "asterisk/cli.h" -#include "asterisk/cel.h" #include "asterisk/features.h" #include "asterisk/musiconhold.h" #include "asterisk/say.h" diff --git a/channels/chan_sip.c b/channels/chan_sip.c index d9c89d82d6..62afeb0614 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -276,7 +276,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/translate.h" #include "asterisk/ast_version.h" #include "asterisk/event.h" -#include "asterisk/cel.h" #include "asterisk/data.h" #include "asterisk/aoc.h" #include "asterisk/message.h" diff --git a/channels/sig_analog.c b/channels/sig_analog.c index 276e0fd3e2..d80f68535e 100644 --- a/channels/sig_analog.c +++ b/channels/sig_analog.c @@ -41,7 +41,6 @@ #include "asterisk/manager.h" #include "asterisk/astdb.h" #include "asterisk/features.h" -#include "asterisk/cel.h" #include "asterisk/causes.h" #include "asterisk/features_config.h" #include "asterisk/bridging.h" diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h index 505204a4fd..d17568824d 100644 --- a/include/asterisk/cel.h +++ b/include/asterisk/cel.h @@ -67,8 +67,6 @@ enum ast_cel_event_type { AST_CEL_BLINDTRANSFER = 13, /*! \brief a transfer occurs */ AST_CEL_ATTENDEDTRANSFER = 14, - /*! \brief a transfer occurs */ - AST_CEL_TRANSFER = 15, /*! \brief a 3-way conference, usually part of a transfer */ AST_CEL_HOOKFLASH = 16, /*! \brief a 3-way conference, usually part of a transfer */ @@ -167,31 +165,6 @@ enum ast_cel_event_type ast_cel_str_to_event_type(const char *name); */ struct ast_channel *ast_cel_fabricate_channel_from_event(const struct ast_event *event); -/*! - * \brief Report a channel event - * - * \param chan This argument is required. This is the primary channel associated with - * this channel event. - * \param event_type This is the type of call event being reported. - * \param userdefevname This is an optional custom name for the call event. - * \param extra This is an optional opaque field that will go into the "CEL_EXTRA" - * information element of the call event. - * \param peer2 All CEL events contain a "peer name" information element. The first - * place the code will look to get a peer name is from the bridged channel to - * chan. If chan has no bridged channel and peer2 is specified, then the name - * of peer2 will go into the "peer name" field. If neither are available, the - * peer name field will be blank. - * - * \since 1.8 - * - * \pre chan and peer2 are both unlocked - * - * \retval 0 success - * \retval non-zero failure - */ -int ast_cel_report_event(struct ast_channel *chan, enum ast_cel_event_type event_type, - const char *userdefevname, const char *extra, struct ast_channel *peer2); - /*! * \brief Helper struct for getting the fields out of a CEL event */ @@ -312,8 +285,8 @@ struct ast_channel_snapshot; * 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 extra An event-specific opaque JSON blob to be rendered and placed + * in 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 @@ -323,7 +296,7 @@ struct ast_channel_snapshot; */ 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 ast_json *extra, const char *peer_name); #if defined(__cplusplus) || defined(c_plusplus) } diff --git a/main/cel.c b/main/cel.c index 0e9519a1d0..c212dcd7bb 100644 --- a/main/cel.c +++ b/main/cel.c @@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_bridging.h" #include "asterisk/bridging.h" #include "asterisk/parking.h" +#include "asterisk/features.h" /*** DOCUMENTATION @@ -101,7 +102,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - @@ -308,7 +308,6 @@ static const char * const cel_event_types[CEL_MAX_EVENT_IDS] = { [AST_CEL_CONF_END] = "CONF_END", [AST_CEL_PARK_START] = "PARK_START", [AST_CEL_PARK_END] = "PARK_END", - [AST_CEL_TRANSFER] = "TRANSFER", [AST_CEL_USER_DEFINED] = "USER_DEFINED", [AST_CEL_CONF_ENTER] = "CONF_ENTER", [AST_CEL_CONF_EXIT] = "CONF_EXIT", @@ -634,9 +633,13 @@ 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 ast_json *extra, const char *peer_name) { struct timeval eventtime = ast_tvnow(); + RAII_VAR(char *, extra_txt, NULL, ast_free); + if (extra) { + extra_txt = ast_json_dump_string(extra); + } 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, @@ -658,14 +661,14 @@ struct ast_event *ast_cel_create_event(struct ast_channel_snapshot *snapshot, 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_EXTRA, AST_EVENT_IE_PLTYPE_STR, S_OR(extra_txt, ""), 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, +static int cel_report_event(struct ast_channel_snapshot *snapshot, enum ast_cel_event_type event_type, const char *userdefevname, - const char *extra, const char *peer2_name) + struct ast_json *extra, const char *peer2_name) { struct ast_event *ev; char *linkedid = ast_strdupa(snapshot->linkedid); @@ -735,7 +738,7 @@ static void check_retire_linkedid(struct ast_channel_snapshot *snapshot) * before unreffing the channel we have a refcount of 3, we're done. Unlink and report. */ if (ao2_ref(lid, -1) == 3) { ast_str_container_remove(linkedids, lid); - report_event_snapshot(snapshot, AST_CEL_LINKEDID_END, NULL, NULL, NULL); + cel_report_event(snapshot, AST_CEL_LINKEDID_END, NULL, NULL, NULL); } ao2_ref(lid, -1); } @@ -895,109 +898,6 @@ static int cel_linkedid_ref(const char *linkedid) return 0; } -int ast_cel_report_event(struct ast_channel *chan, enum ast_cel_event_type event_type, - const char *userdefevname, const char *extra, struct ast_channel *peer2) -{ - struct timeval eventtime; - struct ast_event *ev; - const char *peername = ""; - struct ast_channel *peer; - char *linkedid = ast_strdupa(ast_channel_linkedid(chan)); - - if (!ast_cel_check_enabled()) { - return 0; - } - - /* Record the linkedid of new channels if we are tracking LINKEDID_END even if we aren't - * reporting on CHANNEL_START so we can track when to send LINKEDID_END */ - if (ast_cel_track_event(AST_CEL_LINKEDID_END) && event_type == AST_CEL_CHANNEL_START && linkedid) { - if (cel_linkedid_ref(linkedid)) { - return -1; - } - } - - if (!ast_cel_track_event(event_type)) { - return 0; - } - - if ((event_type == AST_CEL_APP_START || event_type == AST_CEL_APP_END) - && !cel_track_app(ast_channel_appl(chan))) { - return 0; - } - - ast_channel_lock(chan); - peer = ast_bridged_channel(chan); - if (peer) { - ast_channel_ref(peer); - } - ast_channel_unlock(chan); - - if (peer) { - ast_channel_lock(peer); - peername = ast_strdupa(ast_channel_name(peer)); - ast_channel_unlock(peer); - } else if (peer2) { - ast_channel_lock(peer2); - peername = ast_strdupa(ast_channel_name(peer2)); - ast_channel_unlock(peer2); - } - - if (!userdefevname) { - userdefevname = ""; - } - - if (!extra) { - extra = ""; - } - - eventtime = ast_tvnow(); - - ast_channel_lock(chan); - - 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, userdefevname, - AST_EVENT_IE_CEL_CIDNAME, AST_EVENT_IE_PLTYPE_STR, - S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, ""), - AST_EVENT_IE_CEL_CIDNUM, AST_EVENT_IE_PLTYPE_STR, - S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, ""), - AST_EVENT_IE_CEL_CIDANI, AST_EVENT_IE_PLTYPE_STR, - S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, ""), - AST_EVENT_IE_CEL_CIDRDNIS, AST_EVENT_IE_PLTYPE_STR, - S_COR(ast_channel_redirecting(chan)->from.number.valid, ast_channel_redirecting(chan)->from.number.str, ""), - AST_EVENT_IE_CEL_CIDDNID, AST_EVENT_IE_PLTYPE_STR, - S_OR(ast_channel_dialed(chan)->number.str, ""), - AST_EVENT_IE_CEL_EXTEN, AST_EVENT_IE_PLTYPE_STR, ast_channel_exten(chan), - AST_EVENT_IE_CEL_CONTEXT, AST_EVENT_IE_PLTYPE_STR, ast_channel_context(chan), - AST_EVENT_IE_CEL_CHANNAME, AST_EVENT_IE_PLTYPE_STR, ast_channel_name(chan), - AST_EVENT_IE_CEL_APPNAME, AST_EVENT_IE_PLTYPE_STR, S_OR(ast_channel_appl(chan), ""), - AST_EVENT_IE_CEL_APPDATA, AST_EVENT_IE_PLTYPE_STR, S_OR(ast_channel_data(chan), ""), - AST_EVENT_IE_CEL_AMAFLAGS, AST_EVENT_IE_PLTYPE_UINT, ast_channel_amaflags(chan), - AST_EVENT_IE_CEL_ACCTCODE, AST_EVENT_IE_PLTYPE_STR, ast_channel_accountcode(chan), - AST_EVENT_IE_CEL_PEERACCT, AST_EVENT_IE_PLTYPE_STR, ast_channel_peeraccount(chan), - AST_EVENT_IE_CEL_UNIQUEID, AST_EVENT_IE_PLTYPE_STR, ast_channel_uniqueid(chan), - AST_EVENT_IE_CEL_LINKEDID, AST_EVENT_IE_PLTYPE_STR, ast_channel_linkedid(chan), - AST_EVENT_IE_CEL_USERFIELD, AST_EVENT_IE_PLTYPE_STR, ast_channel_userfield(chan), - AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, extra, - AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, peername, - AST_EVENT_IE_END); - - ast_channel_unlock(chan); - - if (peer) { - peer = ast_channel_unref(peer); - } - - if (ev && ast_event_queue(ev)) { - ast_event_destroy(ev); - return -1; - } - - return 0; -} - int ast_cel_fill_record(const struct ast_event *e, struct ast_cel_event_record *r) { if (r->version != AST_CEL_EVENT_RECORD_VERSION) { @@ -1074,13 +974,13 @@ static void cel_channel_state_change( int is_hungup, was_hungup; if (!new_snapshot) { - report_event_snapshot(old_snapshot, AST_CEL_CHANNEL_END, NULL, NULL, NULL); + cel_report_event(old_snapshot, AST_CEL_CHANNEL_END, NULL, NULL, NULL); check_retire_linkedid(old_snapshot); return; } if (!old_snapshot) { - report_event_snapshot(new_snapshot, AST_CEL_CHANNEL_START, NULL, NULL, NULL); + cel_report_event(new_snapshot, AST_CEL_CHANNEL_START, NULL, NULL, NULL); return; } @@ -1088,22 +988,22 @@ static void cel_channel_state_change( is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_DEAD) ? 1 : 0; if (!was_hungup && is_hungup) { - RAII_VAR(struct ast_str *, extra_str, ast_str_create(128), ast_free); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); RAII_VAR(struct ast_multi_channel_blob *, blob, get_dialstatus_blob(new_snapshot->uniqueid), ao2_cleanup); const char *dialstatus = ""; if (blob && !ast_strlen_zero(get_blob_variable(blob, "dialstatus"))) { dialstatus = get_blob_variable(blob, "dialstatus"); } - ast_str_set(&extra_str, 0, "%d,%s,%s", - new_snapshot->hangupcause, - new_snapshot->hangupsource, - dialstatus); - report_event_snapshot(new_snapshot, AST_CEL_HANGUP, NULL, ast_str_buffer(extra_str), NULL); + extra = ast_json_pack("{s: i, s: s, s: s}", + "hangupcause", new_snapshot->hangupcause, + "hangupsource", new_snapshot->hangupsource, + "dialstatus", dialstatus); + cel_report_event(new_snapshot, AST_CEL_HANGUP, NULL, extra, NULL); return; } if (old_snapshot->state != new_snapshot->state && new_snapshot->state == AST_STATE_UP) { - report_event_snapshot(new_snapshot, AST_CEL_ANSWER, NULL, NULL, NULL); + cel_report_event(new_snapshot, AST_CEL_ANSWER, NULL, NULL, NULL); return; } } @@ -1136,12 +1036,12 @@ static void cel_channel_app_change( /* old snapshot has an application, end it */ if (old_snapshot && !ast_strlen_zero(old_snapshot->appl)) { - report_event_snapshot(old_snapshot, AST_CEL_APP_END, NULL, NULL, NULL); + 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)) { - report_event_snapshot(new_snapshot, AST_CEL_APP_START, NULL, NULL, NULL); + cel_report_event(new_snapshot, AST_CEL_APP_START, NULL, NULL, NULL); } } @@ -1260,7 +1160,11 @@ static void cel_bridge_enter_cb( if (snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE)) { if (assoc && assoc->track_as_conf) { - report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + extra = ast_json_pack("{s: s}", "bridge_id", snapshot->uniqueid); + if (extra) { + cel_report_event(chan_snapshot, AST_CEL_CONF_ENTER, NULL, extra, NULL); + } return; } @@ -1286,7 +1190,7 @@ 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); + cel_report_event(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); @@ -1295,18 +1199,31 @@ static void cel_bridge_enter_cb( /* this bridge will no longer be treated like a bridge, so mark the bridge_assoc as such */ if (!assoc->track_as_conf) { + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); assoc->track_as_conf = 1; - report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL, - chan_snapshot->name, assoc->secondary_name); + + extra = ast_json_pack("{s: s, s: s}", + "channel_name", chan_snapshot->name, + "bridge_id", snapshot->uniqueid); + + if (extra) { + cel_report_event(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL, + extra, assoc->secondary_name); + } + ast_string_field_set(assoc, secondary_name, ""); } } } else if (snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); if (!assoc) { add_bridge_primary(chan_snapshot, snapshot->uniqueid, ""); return; } - report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL); + extra = ast_json_pack("{s: s}", "bridge_id", snapshot->uniqueid); + if (extra) { + cel_report_event(chan_snapshot, AST_CEL_CONF_ENTER, NULL, extra, NULL); + } } } @@ -1333,17 +1250,25 @@ static void cel_bridge_leave_cb( } if (assoc->track_as_conf) { - report_event_snapshot(chan_snapshot, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + extra = ast_json_pack("{s: s}", "bridge_id", snapshot->uniqueid); + if (extra) { + cel_report_event(chan_snapshot, AST_CEL_CONF_EXIT, NULL, extra, NULL); + } return; } if (ao2_container_count(snapshot->channels) == 1) { - report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_END, NULL, NULL, assoc->secondary_name); + cel_report_event(assoc->primary_snapshot, AST_CEL_BRIDGE_END, NULL, NULL, assoc->secondary_name); remove_bridge_primary(assoc->primary_snapshot->uniqueid); return; } } else if (snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) { - report_event_snapshot(chan_snapshot, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + extra = ast_json_pack("{s: s}", "bridge_id", snapshot->uniqueid); + if (extra) { + cel_report_event(chan_snapshot, AST_CEL_CONF_EXIT, NULL, extra, NULL); + } } } @@ -1353,26 +1278,36 @@ static void cel_parking_cb( 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: - report_event_snapshot(parked_payload->parkee, AST_CEL_PARK_START, NULL, - parked_payload->parkinglot, - parked_payload->parker_dial_string); - break; + 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); + } + return; case PARKED_CALL_TIMEOUT: - report_event_snapshot(parked_payload->parkee, AST_CEL_PARK_END, NULL, "ParkedCallTimeOut", NULL); + reason = "ParkedCallTimeOut"; break; case PARKED_CALL_GIVEUP: - report_event_snapshot(parked_payload->parkee, AST_CEL_PARK_END, NULL, "ParkedCallGiveUp", NULL); + reason = "ParkedCallGiveUp"; break; case PARKED_CALL_UNPARKED: - report_event_snapshot(parked_payload->parkee, AST_CEL_PARK_END, NULL, "ParkedCallUnparked", NULL); + reason = "ParkedCallUnparked"; break; case PARKED_CALL_FAILED: - report_event_snapshot(parked_payload->parkee, AST_CEL_PARK_END, NULL, "ParkedCallFailed", NULL); + reason = "ParkedCallFailed"; break; } + + 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) @@ -1396,11 +1331,15 @@ static void cel_dial_cb(void *data, struct stasis_subscription *sub, if (!ast_strlen_zero(get_blob_variable(blob, "forward"))) { struct ast_channel_snapshot *caller = ast_multi_channel_blob_get_channel(blob, "caller"); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); if (!caller) { return; } - report_event_snapshot(caller, AST_CEL_FORWARD, NULL, get_blob_variable(blob, "forward"), NULL); + extra = ast_json_pack("{s: s}", "forward", get_blob_variable(blob, "forward")); + if (extra) { + cel_report_event(caller, AST_CEL_FORWARD, NULL, extra, NULL); + } } if (ast_strlen_zero(get_blob_variable(blob, "dialstatus"))) { @@ -1423,8 +1362,8 @@ static void cel_generic_cb( case AST_CEL_USER_DEFINED: { const char *event = ast_json_string_get(ast_json_object_get(event_details, "event")); - const char *extra = ast_json_string_get(ast_json_object_get(event_details, "extra")); - report_event_snapshot(obj->snapshot, event_type, event, extra, NULL); + struct ast_json *extra = ast_json_object_get(event_details, "extra"); + cel_report_event(obj->snapshot, event_type, event, extra, NULL); break; } default: @@ -1433,6 +1372,107 @@ static void cel_generic_cb( } } +static void cel_blind_transfer_cb( + void *data, struct stasis_subscription *sub, + struct stasis_topic *topic, + struct stasis_message *message) +{ + struct ast_bridge_blob *obj = stasis_message_data(message); + struct ast_channel_snapshot *chan_snapshot = obj->channel; + struct ast_bridge_snapshot *bridge_snapshot = obj->bridge; + struct ast_json *blob = obj->blob; + struct ast_json *json_exten = ast_json_object_get(blob, "exten"); + struct ast_json *json_context = ast_json_object_get(blob, "context"); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + const char *exten, *context; + + if (!json_exten || !json_context) { + return; + } + + exten = ast_json_string_get(json_exten); + context = ast_json_string_get(json_context); + if (!exten || !context) { + return; + } + extra = ast_json_pack("{s: s, s: s, s: s}", + "extension", exten, + "context", context, + "bridge_id", bridge_snapshot->uniqueid); + + 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_topic *topic, + struct stasis_message *message) +{ + struct ast_attended_transfer_message *xfer = stasis_message_data(message); + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); + 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 two the same */ + case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: + case AST_ATTENDED_TRANSFER_DEST_LINK: + extra = ast_json_pack("{s: s, s: s, s: s}", + "bridge1_id", bridge1->uniqueid, + "channel2_name", channel2->name, + "bridge2_id", bridge2->uniqueid); + + if (!extra) { + return; + } + break; + case AST_ATTENDED_TRANSFER_DEST_APP: + extra = ast_json_pack("{s: s, s: s, s: s}", + "bridge1_id", bridge1->uniqueid, + "channel2_name", channel2->name, + "app", xfer->dest.app); + + 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_topic *topic, + 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; + } + + cel_report_event(target, AST_CEL_PICKUP, NULL, NULL, channel->name); +} + static void ast_cel_engine_term(void) { aco_info_destroy(&cel_cfg_info); @@ -1554,6 +1594,21 @@ int ast_cel_engine_init(void) 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); + /* If somehow we failed to add any routes, just shut down the whole * thing and fail it. */ diff --git a/main/channel.c b/main/channel.c index c5983199aa..d972982a63 100644 --- a/main/channel.c +++ b/main/channel.c @@ -50,7 +50,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/translate.h" #include "asterisk/manager.h" -#include "asterisk/cel.h" #include "asterisk/chanvars.h" #include "asterisk/linkedlists.h" #include "asterisk/indications.h" diff --git a/main/features.c b/main/features.c index fd72b9cb25..928a81e94f 100644 --- a/main/features.c +++ b/main/features.c @@ -70,7 +70,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/audiohook.h" #include "asterisk/global_datastores.h" #include "asterisk/astobj2.h" -#include "asterisk/cel.h" #include "asterisk/test.h" #include "asterisk/bridging.h" #include "asterisk/bridging_basic.h" @@ -2553,8 +2552,6 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st } /* Initiate the channel transfer of party A to party C (or recalled party B). */ - ast_cel_report_event(transferee, AST_CEL_ATTENDEDTRANSFER, NULL, NULL, newchan); - xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", ast_channel_linkedid(transferee), 0, "Transfered/%s", ast_channel_name(transferee)); if (!xferchan) { ast_autoservice_chan_hangup_peer(transferee, newchan); @@ -4739,8 +4736,6 @@ int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target) ast_channel_unlock(chan); connected_caller.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; - ast_cel_report_event(target, AST_CEL_PICKUP, NULL, NULL, chan); - if (ast_answer(chan)) { ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan_name); goto pickup_failed; diff --git a/main/pbx.c b/main/pbx.c index 21a1a149b0..0b8024a1f2 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -50,7 +50,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/file.h" #include "asterisk/callerid.h" #include "asterisk/cdr.h" -#include "asterisk/cel.h" #include "asterisk/config.h" #include "asterisk/term.h" #include "asterisk/time.h" diff --git a/tests/test_cel.c b/tests/test_cel.c index c29b2e786a..5b64f21cbf 100644 --- a/tests/test_cel.c +++ b/tests/test_cel.c @@ -47,6 +47,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/bridging_basic.h" #include "asterisk/stasis_channels.h" #include "asterisk/stasis_bridging.h" +#include "asterisk/json.h" +#include "asterisk/features.h" #define TEST_CATEGORY "/main/cel/" @@ -78,6 +80,73 @@ static void do_sleep(void) } \ } while (0) +#define APPEND_EVENT_SNAPSHOT(snapshot, ev_type, userevent, extra, peer) do { \ + if (append_expected_event_snapshot(snapshot, ev_type, userevent, extra, peer)) { \ + return AST_TEST_FAIL; \ + } \ + } while (0) + +#define APPEND_DUMMY_EVENT() do { \ + if (append_dummy_event()) { \ + return AST_TEST_FAIL; \ + } \ + } while (0) + +#define CONF_EXIT(channel, bridge) do { \ + ast_test_validate(test, 0 == ast_bridge_depart(channel)); \ + CONF_EXIT_EVENT(channel, bridge); \ + } while (0) + +#define CONF_EXIT_EVENT(channel, bridge) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s}", "bridge_id", bridge->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(channel, AST_CEL_CONF_EXIT, NULL, extra, NULL); \ + } while (0) + +#define CONF_EXIT_SNAPSHOT(channel, bridge) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s}", "bridge_id", bridge->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT_SNAPSHOT(channel, AST_CEL_CONF_EXIT, NULL, extra, NULL); \ + } while (0) + +#define CONF_ENTER_EVENT(channel, bridge) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s}", "bridge_id", bridge->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(channel, AST_CEL_CONF_ENTER, NULL, extra, NULL); \ + } while (0) + +#define BRIDGE_TO_CONF(first, second, third, bridge) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s, s: s}", \ + "channel_name", ast_channel_name(third), \ + "bridge_id", bridge->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(first, AST_CEL_BRIDGE_TO_CONF, NULL, extra, ast_channel_name(second)); \ + } while (0) + +#define BLINDTRANSFER_EVENT(channel, bridge, extension, context) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s, s: s, s: s}", \ + "extension", extension, \ + "context", context, \ + "bridge_id", bridge->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(channel, AST_CEL_BLINDTRANSFER, NULL, extra, NULL); \ + } while (0) + +#define ATTENDEDTRANSFER_BRIDGE(channel1, bridge1, channel2, bridge2) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: s, s: s, s: s}", \ + "bridge1_id", bridge1->uniqueid, \ + "channel2_name", ast_channel_name(channel2), \ + "bridge2_id", bridge2->uniqueid); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(channel1, AST_CEL_ATTENDEDTRANSFER, NULL, extra, NULL); \ + } 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, } @@ -90,31 +159,45 @@ static void do_sleep(void) /*! \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 Eve's Caller ID */ +#define EVE_CALLERID { .id.name.str = "Eve", .id.name.valid = 1, .id.number.str = "500", .id.number.valid = 1, } + +/*! \brief Fred's Caller ID */ +#define FRED_CALLERID { .id.name.str = "Fred", .id.name.valid = 1, .id.number.str = "600", .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. */ +/*! \brief Create a \ref test_cel_chan_tech for David. */ #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 Create a \ref test_cel_chan_tech for Eve. */ +#define CREATE_EVE_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, "500", "500", "default", NULL, 0, CHANNEL_TECH_NAME "/Eve"); \ + APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ + } while (0) + +/*! \brief Create a \ref test_cel_chan_tech for Eve. */ +#define CREATE_FRED_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, "600", "600", "default", NULL, 0, CHANNEL_TECH_NAME "/Fred"); \ APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \ } while (0) @@ -139,24 +222,41 @@ static void do_sleep(void) } 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); \ - 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; \ +#define HANGUP_CHANNEL(channel, cause, dialstatus) do { \ + ast_channel_hangupcause_set((channel), (cause)); \ + ao2_ref(channel, +1); \ + ast_hangup((channel)); \ + HANGUP_EVENT(channel, cause, dialstatus); \ + 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; \ + } while (0) + +#define HANGUP_EVENT(channel, cause, dialstatus) do { \ + RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \ + extra = ast_json_pack("{s: i, s: s, s: s}", \ + "hangupcause", cause, \ + "hangupsource", "", \ + "dialstatus", dialstatus); \ + ast_test_validate(test, extra != NULL); \ + APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, extra, NULL); \ } 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); + struct ast_json *extra, const char *peer); + +static int append_expected_event_snapshot( + struct ast_channel_snapshot *snapshot, + enum ast_cel_event_type type, + const char *userdefevname, + struct ast_json *extra, const char *peer); + +static int append_dummy_event(void); static void safe_channel_release(struct ast_channel *chan) { @@ -185,7 +285,7 @@ AST_TEST_DEFINE(test_cel_channel_creation) CREATE_ALICE_CHANNEL(chan, (&caller)); - HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -213,7 +313,7 @@ AST_TEST_DEFINE(test_cel_unanswered_inbound_call) EMULATE_APP_DATA(chan, 1, "Wait", "1"); - HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -246,7 +346,7 @@ AST_TEST_DEFINE(test_cel_unanswered_outbound_call) 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,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -273,7 +373,7 @@ AST_TEST_DEFINE(test_cel_single_party) ANSWER_CHANNEL(chan); EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1"); - HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -312,7 +412,7 @@ AST_TEST_DEFINE(test_cel_single_bridge) ast_bridge_depart(chan); - HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -353,7 +453,7 @@ AST_TEST_DEFINE(test_cel_single_bridge_continue) EMULATE_APP_DATA(chan, 3, "Wait", ""); /* And then it hangs up */ - HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -403,8 +503,8 @@ AST_TEST_DEFINE(test_cel_single_twoparty_bridge_a) 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,,"); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -455,8 +555,8 @@ AST_TEST_DEFINE(test_cel_single_twoparty_bridge_b) 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,,"); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -510,18 +610,15 @@ AST_TEST_DEFINE(test_cel_single_multiparty_bridge) 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)); + BRIDGE_TO_CONF(chan_alice, chan_bob, chan_charlie, bridge); - 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); + CONF_EXIT(chan_alice, bridge); + CONF_EXIT(chan_bob, bridge); + CONF_EXIT(chan_charlie, bridge); - HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,"); - HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); - HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -574,8 +671,8 @@ AST_TEST_DEFINE(test_cel_dial_unanswered) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER, "NOANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER, ""); return AST_TEST_PASS; } @@ -609,8 +706,8 @@ AST_TEST_DEFINE(test_cel_dial_busy) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY, "BUSY"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY, ""); return AST_TEST_PASS; } @@ -643,8 +740,8 @@ AST_TEST_DEFINE(test_cel_dial_congestion) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION, "CONGESTION"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION, ""); return AST_TEST_PASS; } @@ -677,8 +774,8 @@ AST_TEST_DEFINE(test_cel_dial_unavailable) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION, "CHANUNAVAIL"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION, ""); return AST_TEST_PASS; } @@ -712,8 +809,8 @@ AST_TEST_DEFINE(test_cel_dial_caller_cancel) 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"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "CANCEL"); return AST_TEST_PASS; } @@ -755,18 +852,18 @@ AST_TEST_DEFINE(test_cel_dial_parallel_failed) /* Charlie is busy */ ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY"); - HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY, "17,,"); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY, ""); /* David is congested */ ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION"); - HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION, "34,,"); + HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION, ""); /* Bob is canceled */ ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL"); - HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); /* Alice hangs up */ - HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,BUSY"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "BUSY"); return AST_TEST_PASS; } @@ -809,8 +906,8 @@ AST_TEST_DEFINE(test_cel_dial_answer_no_bridge) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -860,8 +957,8 @@ AST_TEST_DEFINE(test_cel_dial_answer_twoparty_bridge_a) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -910,8 +1007,8 @@ AST_TEST_DEFINE(test_cel_dial_answer_twoparty_bridge_b) 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,,"); + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -970,28 +1067,431 @@ AST_TEST_DEFINE(test_cel_dial_answer_multiparty) 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)); + BRIDGE_TO_CONF(chan_charlie, chan_david, chan_bob, bridge); 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); + CONF_ENTER_EVENT(chan_alice, bridge); - ast_test_validate(test, 0 == ast_bridge_depart(chan_alice)); - APPEND_EVENT(chan_alice, AST_CEL_CONF_EXIT, NULL, NULL, NULL); + CONF_EXIT(chan_alice, bridge); + CONF_EXIT(chan_bob, bridge); + CONF_EXIT(chan_charlie, bridge); + CONF_EXIT(chan_david, bridge); + + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL, ""); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_blind_transfer) +{ + 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 alice_caller = ALICE_CALLERID; + struct ast_party_caller bob_caller = BOB_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test blind transfers to an extension"; + info->description = + "This test creates two channels, bridges them, and then" + " blind transfers the bridge to an extension.\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); + CREATE_BOB_CHANNEL(chan_bob, &bob_caller); + + ANSWER_NO_APP(chan_alice); + ANSWER_NO_APP(chan_bob); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == 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)); + + BLINDTRANSFER_EVENT(chan_alice, bridge, "transfer_extension", "transfer_context"); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_alice)); + + ast_bridge_transfer_blind(1, chan_alice, "transfer_extension", "transfer_context", 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, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_attended_transfer_bridges_swap) +{ + 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_fred, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge1, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge *, bridge2, NULL, ao2_cleanup); + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_party_caller bob_caller = BOB_CALLERID; + struct ast_party_caller charlie_caller = CHARLIE_CALLERID; + struct ast_party_caller fred_caller = ALICE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test attended transfers between two pairs of bridged parties"; + info->description = + "This test creates four channels, places each pair in" + " a bridge, and then attended transfers the bridges" + " together.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + /* Create first set of bridged parties */ + bridge1 = ast_bridge_basic_new(); + ast_test_validate(test, bridge1 != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller); + CREATE_BOB_CHANNEL(chan_bob, &bob_caller); + ANSWER_NO_APP(chan_alice); + ANSWER_NO_APP(chan_bob); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_bob, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_alice, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_alice)); + + /* Create second set of bridged parties */ + bridge2 = ast_bridge_basic_new(); + ast_test_validate(test, bridge2 != NULL); + + CREATE_FRED_CHANNEL(chan_fred, &fred_caller); + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller); + ANSWER_NO_APP(chan_fred); + ANSWER_NO_APP(chan_charlie); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_charlie, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_fred, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_charlie, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_fred)); + + /* Perform attended transfer */ + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_alice)); + + ast_bridge_transfer_attended(chan_alice, chan_fred); + do_sleep(); + BRIDGE_TO_CONF(chan_charlie, chan_fred, chan_bob, bridge2); + CONF_EXIT_EVENT(chan_fred, bridge2); + + ATTENDEDTRANSFER_BRIDGE(chan_alice, bridge1, chan_fred, bridge2); + + CONF_EXIT(chan_bob, bridge2); + CONF_EXIT(chan_charlie, bridge2); + + do_sleep(); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_fred, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, ""); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_attended_transfer_bridges_merge) +{ + 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_channel *, chan_eve, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_fred, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge1, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge *, bridge2, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, eve_tmp_snapshot, NULL, ao2_cleanup); + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_party_caller bob_caller = BOB_CALLERID; + struct ast_party_caller charlie_caller = CHARLIE_CALLERID; + struct ast_party_caller david_caller = DAVID_CALLERID; + struct ast_party_caller eve_caller = EVE_CALLERID; + struct ast_party_caller fred_caller = EVE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test attended transfers between two pairs of" + " bridged parties that results in a bridge merge"; + info->description = + "This test creates six channels, places each triplet" + " in a bridge, and then attended transfers the bridges" + " together causing a bridge merge.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + /* Create first set of bridged parties */ + bridge1 = ast_bridge_basic_new(); + ast_test_validate(test, bridge1 != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller); + CREATE_BOB_CHANNEL(chan_bob, &bob_caller); + CREATE_DAVID_CHANNEL(chan_david, &david_caller); + ANSWER_NO_APP(chan_alice); + ANSWER_NO_APP(chan_bob); + ANSWER_NO_APP(chan_david); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_bob, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_alice, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_alice)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_david, NULL, NULL, 0)); + do_sleep(); + BRIDGE_TO_CONF(chan_bob, chan_alice, chan_david, bridge1); + + /* Create second set of bridged parties */ + bridge2 = ast_bridge_basic_new(); + ast_test_validate(test, bridge2 != NULL); + + CREATE_FRED_CHANNEL(chan_fred, &fred_caller); + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller); + CREATE_EVE_CHANNEL(chan_eve, &eve_caller); + ANSWER_NO_APP(chan_fred); + ANSWER_NO_APP(chan_charlie); + ANSWER_NO_APP(chan_eve); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_charlie, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_fred, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_charlie, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_fred)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_eve, NULL, NULL, 0)); + do_sleep(); + BRIDGE_TO_CONF(chan_charlie, chan_fred, chan_eve, bridge2); + + /* Perform attended transfer */ + CONF_EXIT_EVENT(chan_charlie, bridge2); + eve_tmp_snapshot = ast_channel_snapshot_create(chan_eve); + ast_bridge_transfer_attended(chan_alice, chan_fred); + do_sleep(); + CONF_ENTER_EVENT(chan_charlie, bridge1); + + /* Fred goes away */ + CONF_EXIT_EVENT(chan_fred, bridge2); + CONF_EXIT_SNAPSHOT(eve_tmp_snapshot, bridge2); + CONF_ENTER_EVENT(chan_eve, bridge1); + + /* Alice goes away */ + CONF_EXIT_EVENT(chan_alice, bridge1); + + ATTENDEDTRANSFER_BRIDGE(chan_alice, bridge1, chan_fred, bridge2); + + CONF_EXIT(chan_bob, bridge1); + CONF_EXIT(chan_charlie, bridge1); + CONF_EXIT(chan_david, bridge1); + CONF_EXIT(chan_eve, bridge1); + + do_sleep(); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_fred, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_eve, AST_CAUSE_NORMAL, ""); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_attended_transfer_bridges_link) +{ + 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_channel *, chan_eve, NULL, safe_channel_release); + RAII_VAR(struct ast_channel *, chan_fred, NULL, safe_channel_release); + RAII_VAR(struct ast_bridge *, bridge1, NULL, ao2_cleanup); + RAII_VAR(struct ast_bridge *, bridge2, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, eve_tmp_snapshot, NULL, ao2_cleanup); + struct ast_party_caller alice_caller = ALICE_CALLERID; + struct ast_party_caller bob_caller = BOB_CALLERID; + struct ast_party_caller charlie_caller = CHARLIE_CALLERID; + struct ast_party_caller david_caller = DAVID_CALLERID; + struct ast_party_caller eve_caller = EVE_CALLERID; + struct ast_party_caller fred_caller = EVE_CALLERID; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = TEST_CATEGORY; + info->summary = "Test attended transfers between two pairs of" + " bridged parties that results in a bridge merge"; + info->description = + "This test creates six channels, places each triplet" + " in a bridge, and then attended transfers the bridges" + " together causing a bridge merge.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + /* Create first set of bridged parties */ + bridge1 = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE | AST_BRIDGE_CAPABILITY_MULTIMIX, + AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM + | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED | AST_BRIDGE_FLAG_SMART); + ast_test_validate(test, bridge1 != NULL); + + CREATE_ALICE_CHANNEL(chan_alice, &alice_caller); + CREATE_BOB_CHANNEL(chan_bob, &bob_caller); + CREATE_DAVID_CHANNEL(chan_david, &david_caller); + ANSWER_NO_APP(chan_alice); + ANSWER_NO_APP(chan_bob); + ANSWER_NO_APP(chan_david); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_bob, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_alice, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_alice)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge1, chan_david, NULL, NULL, 0)); + do_sleep(); + BRIDGE_TO_CONF(chan_bob, chan_alice, chan_david, bridge1); + + /* Create second set of bridged parties */ + bridge2 = ast_bridge_basic_new(); + ast_test_validate(test, bridge2 != NULL); + + CREATE_FRED_CHANNEL(chan_fred, &fred_caller); + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller); + CREATE_EVE_CHANNEL(chan_eve, &eve_caller); + ANSWER_NO_APP(chan_fred); + ANSWER_NO_APP(chan_charlie); + ANSWER_NO_APP(chan_eve); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_charlie, NULL, NULL, 0)); + do_sleep(); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_fred, NULL, NULL, 0)); + do_sleep(); + APPEND_EVENT(chan_charlie, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_fred)); + + ast_test_validate(test, 0 == ast_bridge_impart(bridge2, chan_eve, NULL, NULL, 0)); + do_sleep(); + BRIDGE_TO_CONF(chan_charlie, chan_fred, chan_eve, bridge2); + + /* Perform attended transfer */ + ast_bridge_transfer_attended(chan_alice, chan_fred); + do_sleep(); + + /* Append dummy event for the link channel ;1 start */ + APPEND_DUMMY_EVENT(); + + /* Append dummy event for the link channel ;2 start */ + APPEND_DUMMY_EVENT(); + + /* Append dummy event for the link channel ;2 answer */ + APPEND_DUMMY_EVENT(); + + ATTENDEDTRANSFER_BRIDGE(chan_alice, bridge1, chan_fred, bridge2); + + /* Append dummy event for the link channel ;1 enter */ + APPEND_DUMMY_EVENT(); + + /* Append dummy events for the link channel ;2 enter and Alice's exit, + * must both be dummies since they're racing */ + APPEND_DUMMY_EVENT(); + APPEND_DUMMY_EVENT(); + + /* Append dummy events for the link channel ;1 answer and Fred's exit, + * must both be dummies since they're racing */ + APPEND_DUMMY_EVENT(); + APPEND_DUMMY_EVENT(); + + CONF_EXIT(chan_bob, bridge1); + CONF_EXIT(chan_charlie, bridge2); + CONF_EXIT(chan_david, bridge1); + CONF_EXIT(chan_eve, bridge2); + + do_sleep(); + HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_fred, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_david, AST_CAUSE_NORMAL, ""); + HANGUP_CHANNEL(chan_eve, AST_CAUSE_NORMAL, ""); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(test_cel_dial_pickup) +{ + 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_channel *, chan_charlie, NULL, safe_channel_release); + struct ast_party_caller 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 call pickup"; + info->description = + "Test CEL records for a call that is\n" + "inbound to Asterisk, executes some dialplan, and\n" + "is picked up.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + CREATE_ALICE_CHANNEL(chan_caller, &caller); - 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,,"); + EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob"); + + START_DIALED(chan_caller, chan_callee); + + ast_channel_state_set(chan_caller, AST_STATE_RINGING); + + CREATE_CHARLIE_CHANNEL(chan_charlie, &charlie_caller); + + { + SCOPED_CHANNELLOCK(lock, chan_callee); + APPEND_EVENT(chan_callee, AST_CEL_PICKUP, NULL, NULL, ast_channel_name(chan_charlie)); + ast_test_validate(test, 0 == ast_do_pickup(chan_charlie, chan_callee)); + } + + /* Hang up the masqueraded zombie */ + HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, ""); + + ast_channel_publish_dial(chan_caller, chan_callee, NULL, "ANSWER"); + + HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "ANSWER"); + HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, ""); return AST_TEST_PASS; } @@ -1022,32 +1522,59 @@ static struct ast_event *ao2_dup_event(const struct ast_event *event) 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) +static int append_event(struct ast_event *ev) +{ + RAII_VAR(struct ast_event *, ao2_ev, NULL, ao2_cleanup); + ao2_ev = ao2_dup_event(ev); + if (!ao2_ev) { + return -1; + } + + ao2_link(cel_expected_events, ao2_ev); + return 0; +} + +static int append_dummy_event(void) { - 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) { + + ev = ast_event_new(AST_EVENT_CUSTOM, AST_EVENT_IE_END); + if (!ev) { return -1; } + return append_event(ev); +} + +static int append_expected_event_snapshot( + struct ast_channel_snapshot *snapshot, + enum ast_cel_event_type type, + const char *userdefevname, + struct ast_json *extra, const char *peer) +{ + RAII_VAR(struct ast_event *, ev, NULL, ast_free); ev = ast_cel_create_event(snapshot, type, userdefevname, extra, peer); if (!ev) { return -1; } - ao2_ev = ao2_dup_event(ev); - if (!ao2_ev) { + return append_event(ev); +} + +static int append_expected_event( + struct ast_channel *chan, + enum ast_cel_event_type type, + const char *userdefevname, + struct ast_json *extra, const char *peer) +{ + RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); + snapshot = ast_channel_snapshot_create(chan); + if (!snapshot) { return -1; } - ao2_link(cel_expected_events, ao2_ev); - return 0; + return append_expected_event_snapshot(snapshot, type, userdefevname, extra, peer); } ast_mutex_t sync_lock; @@ -1149,19 +1676,24 @@ static int match_ie_val( return 0; } -static int events_are_equal(struct ast_event *event1, struct ast_event *event2) +static int events_are_equal(struct ast_event *received, struct ast_event *expected) { struct ast_event_iterator iterator; int res; - for (res = ast_event_iterator_init(&iterator, event1); !res; res = ast_event_iterator_next(&iterator)) { + if (ast_event_get_type(expected) == AST_EVENT_CUSTOM) { + /* this event is flagged as a wildcard match */ + return 1; + } + + for (res = ast_event_iterator_init(&iterator, received); !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)) { + && !match_ie_val(received, expected, ie_type)) { ast_log(LOG_ERROR, "Failed matching on field %s\n", ast_event_get_ie_type_name(ie_type)); return 0; } @@ -1244,8 +1776,8 @@ static int check_events(struct ao2_container *local_expected, struct ao2_contain return -1; } if (debug) { - ast_log(LOG_ERROR, "Compared events successfully\n"); - dump_event(ex_event); + ast_log(LOG_ERROR, "Compared events successfully%s\n", ast_event_get_type(ex_event) == AST_EVENT_CUSTOM ? " (wildcard match)" : ""); + dump_event(rx_event); } ao2_cleanup(rx_event); ao2_cleanup(ex_event); @@ -1370,6 +1902,13 @@ static int unload_module(void) AST_TEST_UNREGISTER(test_cel_dial_answer_twoparty_bridge_b); AST_TEST_UNREGISTER(test_cel_dial_answer_multiparty); + AST_TEST_UNREGISTER(test_cel_blind_transfer); + AST_TEST_UNREGISTER(test_cel_attended_transfer_bridges_swap); + AST_TEST_UNREGISTER(test_cel_attended_transfer_bridges_merge); + AST_TEST_UNREGISTER(test_cel_attended_transfer_bridges_link); + + AST_TEST_UNREGISTER(test_cel_dial_pickup); + ast_channel_unregister(&test_cel_chan_tech); ao2_cleanup(cel_test_config); @@ -1405,6 +1944,9 @@ static int load_module(void) cel_test_config->events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<events |= 1<