From 6d624eb008b82b1197cd6bb744073d5c6ef945d7 Mon Sep 17 00:00:00 2001 From: Mark Michelson Date: Fri, 28 Jun 2013 18:42:24 +0000 Subject: Add stasis publications for blind and attended transfers. This creates stasis messages that are sent during a blind or attended transfer. The stasis messages also are converted to AMI events. Review: https://reviewboard.asterisk.org/r/2619 (closes issue ASTERISK-21337) Reported by Matt Jordan git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@393182 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- main/bridging.c | 264 +++++++++++++++++--- main/manager.c | 2 +- main/manager_bridging.c | 38 ++- main/stasis_bridging.c | 646 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 914 insertions(+), 36 deletions(-) (limited to 'main') diff --git a/main/bridging.c b/main/bridging.c index ee7f511cb..92c66ac5c 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -5923,6 +5923,113 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf return AST_BRIDGE_TRANSFER_SUCCESS; } +/*! + * \internal + * \brief Base data to publish for stasis attended transfer messages + */ +struct stasis_attended_transfer_publish_data { + /* The bridge between the transferer and transferee, and the transferer channel in this bridge */ + struct ast_bridge_channel_pair to_transferee; + /* The bridge between the transferer and transfer target, and the transferer channel in this bridge */ + struct ast_bridge_channel_pair to_transfer_target; +}; + +static void stasis_publish_data_cleanup(struct stasis_attended_transfer_publish_data *publication) +{ + ast_channel_unref(publication->to_transferee.channel); + ast_channel_unref(publication->to_transfer_target.channel); + ao2_cleanup(publication->to_transferee.bridge); + ao2_cleanup(publication->to_transfer_target.bridge); +} + +/*! + * \internal + * \brief Set up base data for an attended transfer stasis publication + * + * \param to_transferee The original transferer channel, which may be bridged to a transferee + * \param to_transferee_bridge The bridge that to_transferee is in. + * \param to_transfer_target The second transferer channel, which may be bridged to a transfer target + * \param to_target_bridge The bridge that to_transfer_target_is in. + * \param[out] publication A structure to hold the other parameters + */ +static void stasis_publish_data_init(struct ast_channel *to_transferee, + struct ast_bridge *to_transferee_bridge, struct ast_channel *to_transfer_target, + struct ast_bridge *to_target_bridge, + struct stasis_attended_transfer_publish_data *publication) +{ + memset(publication, 0, sizeof(*publication)); + publication->to_transferee.channel = ast_channel_ref(to_transferee); + if (to_transferee_bridge) { + ao2_ref(to_transferee_bridge, +1); + publication->to_transferee.bridge = to_transferee_bridge; + } + + publication->to_transfer_target.channel = ast_channel_ref(to_transfer_target); + if (to_target_bridge) { + ao2_ref(to_target_bridge, +1); + publication->to_transfer_target.bridge = to_target_bridge; + } +} + +/* + * \internal + * \brief Publish a stasis attended transfer resulting in a bridge merge + * + * \param publication Base data about the attended transfer + * \param final_bridge The surviving bridge of the attended transfer + */ +static void publish_attended_transfer_bridge_merge(struct stasis_attended_transfer_publish_data *publication, + struct ast_bridge *final_bridge) +{ + ast_bridge_publish_attended_transfer_bridge_merge(1, AST_BRIDGE_TRANSFER_SUCCESS, + &publication->to_transferee, &publication->to_transfer_target, final_bridge); +} + +/* + * \internal + * \brief Publish a stasis attended transfer to an application + * + * \param publication Base data about the attended transfer + * \param app The app that is running at the conclusion of the transfer + */ +static void publish_attended_transfer_app(struct stasis_attended_transfer_publish_data *publication, + const char *app) +{ + ast_bridge_publish_attended_transfer_app(1, AST_BRIDGE_TRANSFER_SUCCESS, + &publication->to_transferee, &publication->to_transfer_target, app); +} + +/* + * \internal + * \brief Publish a stasis attended transfer showing a link between bridges + * + * \param publication Base data about the attended transfer + * \param local_channel1 Local channel in the original bridge + * \param local_channel2 Local channel in the second bridge + */ +static void publish_attended_transfer_link(struct stasis_attended_transfer_publish_data *publication, + struct ast_channel *local_channel1, struct ast_channel *local_channel2) +{ + struct ast_channel *locals[2] = { local_channel1, local_channel2 }; + + ast_bridge_publish_attended_transfer_link(1, AST_BRIDGE_TRANSFER_SUCCESS, + &publication->to_transferee, &publication->to_transfer_target, locals); +} + +/* + * \internal + * \brief Publish a stasis attended transfer failure + * + * \param publication Base data about the attended transfer + * \param result The transfer result + */ +static void publish_attended_transfer_fail(struct stasis_attended_transfer_publish_data *publication, + enum ast_transfer_result result) +{ + ast_bridge_publish_attended_transfer_fail(1, result, &publication->to_transferee, + &publication->to_transfer_target); +} + /*! * \brief Perform an attended transfer of a bridge * @@ -5941,16 +6048,19 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf * \param chan2 Other transferer channel. May or may not be bridged. * \param bridge1 Bridge that chan1 is in. Guaranteed to be non-NULL. * \param bridge2 Bridge that chan2 is in. If NULL, then chan2 is not bridged. + * \param publication Data to publish for a stasis attended transfer message. * \retval AST_BRIDGE_TRANSFER_FAIL Internal error occurred * \retval AST_BRIDGE_TRANSFER_SUCCESS Succesfully transferred the bridge */ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *chan1, - struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2) + struct ast_channel *chan2, struct ast_bridge *bridge1, struct ast_bridge *bridge2, + struct stasis_attended_transfer_publish_data *publication) { static const char *dest = "_attended@transfer/m"; struct ast_channel *local_chan; int cause; int res; + const char *app = NULL; local_chan = ast_request("Local", ast_channel_nativeformats(chan1), chan1, dest, &cause); @@ -5962,6 +6072,7 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha if (bridge2) { res = ast_local_setup_bridge(local_chan, bridge2, chan2, NULL); } else { + app = ast_strdupa(ast_channel_appl(chan2)); res = ast_local_setup_masquerade(local_chan, chan2); } @@ -5980,6 +6091,20 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha return AST_BRIDGE_TRANSFER_FAIL; } + if (bridge2) { + RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup); + + ast_channel_lock(local_chan); + local_chan2 = ast_local_get_peer(local_chan); + ast_channel_unlock(local_chan); + + ast_assert(local_chan2 != NULL); + + publish_attended_transfer_link(publication, + local_chan, local_chan2); + } else { + publish_attended_transfer_app(publication, app); + } return AST_BRIDGE_TRANSFER_SUCCESS; } @@ -6162,8 +6287,18 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan) return bridge; } -enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer, - const char *exten, const char *context, +static void publish_blind_transfer(int is_external, enum ast_transfer_result result, + struct ast_channel *transferer, struct ast_bridge *bridge, + const char *context, const char *exten) +{ + struct ast_bridge_channel_pair pair; + pair.channel = transferer; + pair.bridge = bridge; + ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten); +} + +enum ast_transfer_result ast_bridge_transfer_blind(int is_external, + struct ast_channel *transferer, const char *exten, const char *context, transfer_channel_cb new_channel_cb, void *user_data) { RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); @@ -6173,16 +6308,19 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere int do_bridge_transfer; int transfer_prohibited; enum try_parking_result parking_result; + enum ast_transfer_result transfer_result; bridge = acquire_bridge(transferer); if (!bridge) { - return AST_BRIDGE_TRANSFER_INVALID; + transfer_result = AST_BRIDGE_TRANSFER_INVALID; + goto publish; } ast_channel_lock(transferer); bridge_channel = ast_channel_get_bridge_channel(transferer); ast_channel_unlock(transferer); if (!bridge_channel) { - return AST_BRIDGE_TRANSFER_INVALID; + transfer_result = AST_BRIDGE_TRANSFER_INVALID; + goto publish; } /* Take off hold if they are on hold. */ @@ -6191,9 +6329,11 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere parking_result = try_parking(bridge, transferer, exten, context); switch (parking_result) { case PARKING_SUCCESS: - return AST_BRIDGE_TRANSFER_SUCCESS; + transfer_result = AST_BRIDGE_TRANSFER_SUCCESS; + goto publish; case PARKING_FAILURE: - return AST_BRIDGE_TRANSFER_FAIL; + transfer_result = AST_BRIDGE_TRANSFER_FAIL; + goto publish; case PARKING_NOT_APPLICABLE: default: break; @@ -6204,10 +6344,12 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere channels = ast_bridge_peers_nolock(bridge); if (!channels) { - return AST_BRIDGE_TRANSFER_FAIL; + transfer_result = AST_BRIDGE_TRANSFER_FAIL; + goto publish; } if (ao2_container_count(channels) <= 1) { - return AST_BRIDGE_TRANSFER_INVALID; + transfer_result = AST_BRIDGE_TRANSFER_INVALID; + goto publish; } transfer_prohibited = ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); @@ -6217,30 +6359,38 @@ enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transfere } if (transfer_prohibited) { - return AST_BRIDGE_TRANSFER_NOT_PERMITTED; + transfer_result = AST_BRIDGE_TRANSFER_NOT_PERMITTED; + goto publish; } set_blind_transfer_variables(transferer, channels); if (do_bridge_transfer) { - return blind_transfer_bridge(transferer, bridge, exten, context, + transfer_result = blind_transfer_bridge(transferer, bridge, exten, context, new_channel_cb, user_data); + goto publish; } /* Reaching this portion means that we're dealing with a two-party bridge */ transferee = get_transferee(channels, transferer); if (!transferee) { - return AST_BRIDGE_TRANSFER_FAIL; + transfer_result = AST_BRIDGE_TRANSFER_FAIL; + goto publish; } if (bridge_channel_queue_blind_transfer(transferee, exten, context, new_channel_cb, user_data)) { - return AST_BRIDGE_TRANSFER_FAIL; + transfer_result = AST_BRIDGE_TRANSFER_FAIL; + goto publish; } ast_bridge_remove(bridge, transferer); - return AST_BRIDGE_TRANSFER_SUCCESS; + transfer_result = AST_BRIDGE_TRANSFER_SUCCESS; + +publish: + publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten); + return transfer_result; } /*! @@ -6297,30 +6447,42 @@ static enum ast_transfer_result bridge_swap_attended_transfer(struct ast_bridge * \param to_target_bridge_channel to_transfer_target's bridge_channel * \param to_transferee_bridge The bridge between to_transferee and the transferee * \param to_target_bridge The bridge between to_transfer_target and the transfer_target + * \param publication Data to publish for a stasis attended transfer message * \return The success or failure of the attended transfer */ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel *to_transferee, struct ast_bridge_channel *to_transferee_bridge_channel, struct ast_channel *to_transfer_target, struct ast_bridge_channel *to_target_bridge_channel, - struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge) + struct ast_bridge *to_transferee_bridge, struct ast_bridge *to_target_bridge, + struct stasis_attended_transfer_publish_data *publication) { struct ast_bridge_channel *kick_me[] = { to_transferee_bridge_channel, to_target_bridge_channel, }; + enum ast_transfer_result res; + struct ast_bridge *final_bridge = NULL; switch (ast_bridges_allow_optimization(to_transferee_bridge, to_target_bridge)) { case AST_BRIDGE_OPTIMIZE_SWAP_TO_CHAN_BRIDGE: - return bridge_swap_attended_transfer(to_transferee_bridge, to_target_bridge_channel, to_transferee); + final_bridge = to_transferee_bridge; + res = bridge_swap_attended_transfer(to_transferee_bridge, to_target_bridge_channel, to_transferee); + goto end; case AST_BRIDGE_OPTIMIZE_SWAP_TO_PEER_BRIDGE: - return bridge_swap_attended_transfer(to_target_bridge, to_transferee_bridge_channel, to_transfer_target); + final_bridge = to_target_bridge; + res = bridge_swap_attended_transfer(to_target_bridge, to_transferee_bridge_channel, to_transfer_target); + goto end; case AST_BRIDGE_OPTIMIZE_MERGE_TO_CHAN_BRIDGE: + final_bridge = to_transferee_bridge; bridge_merge_do(to_transferee_bridge, to_target_bridge, kick_me, ARRAY_LEN(kick_me)); - return AST_BRIDGE_TRANSFER_SUCCESS; + res = AST_BRIDGE_TRANSFER_SUCCESS; + goto end; case AST_BRIDGE_OPTIMIZE_MERGE_TO_PEER_BRIDGE: + final_bridge = to_target_bridge; bridge_merge_do(to_target_bridge, to_transferee_bridge, kick_me, ARRAY_LEN(kick_me)); - return AST_BRIDGE_TRANSFER_SUCCESS; + res = AST_BRIDGE_TRANSFER_SUCCESS; + goto end; case AST_BRIDGE_OPTIMIZE_PROHIBITED: default: /* Just because optimization wasn't doable doesn't necessarily mean @@ -6329,11 +6491,23 @@ static enum ast_transfer_result two_bridge_attended_transfer(struct ast_channel */ if (to_transferee_bridge->inhibit_merge || to_transferee_bridge->dissolved || to_target_bridge->inhibit_merge || to_target_bridge->dissolved) { - return AST_BRIDGE_TRANSFER_INVALID; + res = AST_BRIDGE_TRANSFER_INVALID; + goto end; } + + /* Don't goto end here. attended_transfer_bridge will publish its own + * stasis message if it succeeds + */ return attended_transfer_bridge(to_transferee, to_transfer_target, - to_transferee_bridge, to_target_bridge); + to_transferee_bridge, to_target_bridge, publication); } + +end: + if (res == AST_BRIDGE_TRANSFER_SUCCESS) { + publish_attended_transfer_bridge_merge(publication, final_bridge); + } + + return res; } enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee, @@ -6350,13 +6524,20 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra struct ast_channel *chan_unbridged; int transfer_prohibited; int do_bridge_transfer; + enum ast_transfer_result res; + const char *app = NULL; + struct stasis_attended_transfer_publish_data publication; to_transferee_bridge = acquire_bridge(to_transferee); to_target_bridge = acquire_bridge(to_transfer_target); + stasis_publish_data_init(to_transferee, to_transferee_bridge, + to_transfer_target, to_target_bridge, &publication); + /* They can't both be unbridged, you silly goose! */ if (!to_transferee_bridge && !to_target_bridge) { - return AST_BRIDGE_TRANSFER_INVALID; + res = AST_BRIDGE_TRANSFER_INVALID; + goto end; } ast_channel_lock(to_transferee); @@ -6407,20 +6588,20 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra /* Let's get the easy one out of the way first */ if (to_transferee_bridge && to_target_bridge) { - enum ast_transfer_result res; if (!to_transferee_bridge_channel || !to_target_bridge_channel) { - return AST_BRIDGE_TRANSFER_INVALID; + res = AST_BRIDGE_TRANSFER_INVALID; + goto end; } ast_bridge_lock_both(to_transferee_bridge, to_target_bridge); res = two_bridge_attended_transfer(to_transferee, to_transferee_bridge_channel, to_transfer_target, to_target_bridge_channel, - to_transferee_bridge, to_target_bridge); + to_transferee_bridge, to_target_bridge, &publication); ast_bridge_unlock(to_transferee_bridge); ast_bridge_unlock(to_target_bridge); - return res; + goto end; } the_bridge = to_transferee_bridge ?: to_target_bridge; @@ -6433,11 +6614,13 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra channels = ast_bridge_peers_nolock(the_bridge); if (!channels) { - return AST_BRIDGE_TRANSFER_FAIL; + res = AST_BRIDGE_TRANSFER_FAIL; + goto end; } chan_count = ao2_container_count(channels); if (chan_count <= 1) { - return AST_BRIDGE_TRANSFER_INVALID; + res = AST_BRIDGE_TRANSFER_INVALID; + goto end; } transfer_prohibited = ast_test_flag(&the_bridge->feature_flags, AST_BRIDGE_FLAG_TRANSFER_PROHIBITED); @@ -6447,24 +6630,41 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra } if (transfer_prohibited) { - return AST_BRIDGE_TRANSFER_NOT_PERMITTED; + res = AST_BRIDGE_TRANSFER_NOT_PERMITTED; + goto end; } if (do_bridge_transfer) { - return attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL); + res = attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge, NULL, &publication); + goto end; } transferee = get_transferee(channels, chan_bridged); if (!transferee) { - return AST_BRIDGE_TRANSFER_FAIL; + res = AST_BRIDGE_TRANSFER_FAIL; + goto end; } + app = ast_strdupa(ast_channel_appl(chan_unbridged)); if (bridge_channel_queue_attended_transfer(transferee, chan_unbridged)) { - return AST_BRIDGE_TRANSFER_FAIL; + res = AST_BRIDGE_TRANSFER_FAIL; + goto end; } ast_bridge_remove(the_bridge, chan_bridged); + + publish_attended_transfer_app(&publication, app); return AST_BRIDGE_TRANSFER_SUCCESS; + +end: + /* All successful transfer paths have published an appropriate stasis message. + * All failure paths have deferred publishing a stasis message until this point + */ + if (res != AST_BRIDGE_TRANSFER_SUCCESS) { + publish_attended_transfer_fail(&publication, res); + } + stasis_publish_data_cleanup(&publication); + return res; } /*! diff --git a/main/manager.c b/main/manager.c index 8f4fffbf2..7e5b1080c 100644 --- a/main/manager.c +++ b/main/manager.c @@ -4064,7 +4064,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m) context = ast_channel_context(chan); } - switch (ast_bridge_transfer_blind(chan, exten, context, NULL, NULL)) { + switch (ast_bridge_transfer_blind(1, chan, exten, context, NULL, NULL)) { case AST_BRIDGE_TRANSFER_NOT_PERMITTED: astman_send_error(s, m, "Transfer not permitted"); break; diff --git a/main/manager_bridging.c b/main/manager_bridging.c index 01ce68aaf..5e5f409ee 100644 --- a/main/manager_bridging.c +++ b/main/manager_bridging.c @@ -108,6 +108,11 @@ static struct stasis_message_router *bridge_state_router; ***/ +/*! \brief The \ref stasis subscription returned by the forwarding of the channel topic + * to the manager topic + */ +static struct stasis_subscription *topic_forwarder; + struct ast_str *ast_manager_build_bridge_state_string( const struct ast_bridge_snapshot *snapshot, const char *suffix) @@ -409,10 +414,16 @@ static int manager_bridge_info(struct mansession *s, const struct message *m) return 0; } -static void manager_bridging_shutdown(void) +static void manager_bridging_cleanup(void) { stasis_message_router_unsubscribe(bridge_state_router); bridge_state_router = NULL; + stasis_unsubscribe(topic_forwarder); + topic_forwarder = NULL; +} + +static void manager_bridging_shutdown(void) +{ ast_manager_unregister("BridgeList"); ast_manager_unregister("BridgeInfo"); } @@ -420,6 +431,8 @@ static void manager_bridging_shutdown(void) int manager_bridging_init(void) { int ret = 0; + struct stasis_topic *manager_topic; + struct stasis_topic *bridge_topic; if (bridge_state_router) { /* Already initialized */ @@ -427,10 +440,29 @@ int manager_bridging_init(void) } ast_register_atexit(manager_bridging_shutdown); + ast_register_cleanup(manager_bridging_cleanup); + + manager_topic = ast_manager_get_topic(); + if (!manager_topic) { + return -1; + } + + bridge_topic = stasis_caching_get_topic(ast_bridge_topic_all_cached()); + if (!bridge_topic) { + return -1; + } - bridge_state_router = stasis_message_router_create( - stasis_caching_get_topic(ast_bridge_topic_all_cached())); + topic_forwarder = stasis_forward_all(bridge_topic, manager_topic); + if (!topic_forwarder) { + return -1; + } + /* BUGBUG - This should really route off of the manager_router, but + * can't b/c manager_channels is already routing the + * stasis_cache_update_type() messages. Having a separate router can + * cause some message ordering issues with bridge and channel messages. + */ + bridge_state_router = stasis_message_router_create(bridge_topic); if (!bridge_state_router) { return -1; } diff --git a/main/stasis_bridging.c b/main/stasis_bridging.c index 3c4ac1fb1..0b6411671 100644 --- a/main/stasis_bridging.c +++ b/main/stasis_bridging.c @@ -41,6 +41,321 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define SNAPSHOT_CHANNELS_BUCKETS 13 +/*** DOCUMENTATION + + + Raised when a blind transfer is complete. + + + Indicates if the transfer was successful or if it failed. + + An internal error occurred. + Invalid configuration for transfer (e.g. Not bridged) + Bridge does not permit transfers + Transfer completed successfully + + A result of Success does not necessarily mean that a target was succesfully + contacted. It means that a party was succesfully placed into the dialplan at the expected location. + + + The name of the channel that performed the transfer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The ID of the bridge where the Transferer performed the transfer + + + The type of the bridge where the Transferer performed the transfer + + + Indicates if the transfer was performed outside of Asterisk. For instance, + a channel protocol native transfer is external. A DTMF transfer is internal. + + + + + + + Destination context for the blind transfer. + + + Destination extension for the blind transfer. + + + + + + + Raised when an attended transfer is complete. + + + + The original transferer channel that performed the attended transfer. + + + A numeric code for the channel's current state, related to DestChannelStateDesc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The ID of the bridge where the Transferer performed the transfer + This header will not be present if the original transferer was not in a bridge. + + + The type of the bridge where the Transferer performed the transfer + This header will not be present if the original transferer was not in a bridge. + + + The second transferer channel involved in the attended transfer. + + + A numeric code for the channel's current state, related to SecondTransfererChannelStateDesc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The unique ID of the bridge that the second transferer channel was in, or None if the second transferer channel was not bridged + This header will not be present if the second transferer was not in a bridge. + + + The type of the bridge where the Transferer performed the transfer + This header will not be present if the second transferer was not in a bridge. + + + Indicates the method by which the attended transfer completed. + + The transfer was accomplished by merging two bridges into one. + The transfer was accomplished by having a channel or bridge run a dialplan application. + The transfer was accomplished by linking two bridges together using a local channel pair. + The transfer failed. + + + + Indicates the surviving bridge when bridges were merged to complete the transfer + This header is only present when DestType is Bridge + + + Indicates the application that is running when the transfer completes + This header is only present when DestType is App + + + The local channel that is bridged with the original bridge when forming a link between bridges + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + + + + + + + + + + + + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + The local channel that is bridged with the second bridge when forming a link between bridges + This header is only present when DestType is Link + + + A numeric code for the channel's current state, related to LocalTwoChannelStateDesc + This header is only present when DestType is Link + + + + + + + + + + + + + + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + This header is only present when DestType is Link + + + + The headers in this event attempt to describe all the major details of the attended transfer. The two transferer channels + and the two bridges are determined based on their chronological establishment. So consider that Alice calls Bob, and then Alice + transfers the call to Voicemail. The transferer and bridge headers would be arranged as follows: + OrigTransfererChannel: Alice's channel in the bridge with Bob. + BridgeUniqueidOrig: The bridge between Alice and Bob. + SecondTransfererChannel: Alice's channel that called Voicemail. + BridgeUniqueidSecond: Not present, since a call to Voicemail has no bridge. + Now consider if the order were reversed; instead of having Alice call Bob and transfer him to Voicemail, Alice instead + calls her Voicemail and transfers that to Bob. The transferer and bridge headers would be arranged as follows: + OrigTransfererChannel: Alice's channel that called Voicemail. + BridgeUniqueidOrig: Not present, since a call to Voicemail has no bridge. + SecondTransfererChannel: Alice's channel in the bridge with Bob. + BridgeUniqueidSecond: The bridge between Alice and Bob. + + + + ***/ + +static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *message); +static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *message); + /*! * @{ \brief Define bridge message types. */ @@ -48,6 +363,8 @@ STASIS_MESSAGE_TYPE_DEFN(ast_bridge_snapshot_type); STASIS_MESSAGE_TYPE_DEFN(ast_bridge_merge_message_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_entered_bridge_type); STASIS_MESSAGE_TYPE_DEFN(ast_channel_left_bridge_type); +STASIS_MESSAGE_TYPE_DEFN(ast_blind_transfer_type, .to_ami = blind_transfer_to_ami); +STASIS_MESSAGE_TYPE_DEFN(ast_attended_transfer_type, .to_ami = attended_transfer_to_ami); /*! @} */ /*! \brief Aggregate topic for bridge messages */ @@ -352,6 +669,330 @@ struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *s return ast_json_ref(json_bridge); } +/*! + * \internal + * \brief Allocate the fields of an \ref ast_bridge_channel_snapshot_pair. + * + * \param pair A bridge and channel to get snapshots of + * \param[out] snapshot_pair An allocated snapshot pair. + * \retval 0 Success + * \retval non-zero Failure + */ +static int bridge_channel_snapshot_pair_init(struct ast_bridge_channel_pair *pair, struct ast_bridge_channel_snapshot_pair *snapshot_pair) +{ + if (pair->bridge) { + snapshot_pair->bridge_snapshot = ast_bridge_snapshot_create(pair->bridge); + if (!snapshot_pair->bridge_snapshot) { + return -1; + } + } + + snapshot_pair->channel_snapshot = ast_channel_snapshot_create(pair->channel); + if (!snapshot_pair->channel_snapshot) { + return -1; + } + + return 0; +} + +/*! + * \internal + * \brief Free the fields of an \ref ast_bridge_channel_snapshot_pair. + * + * \param pair The snapshot pair whose fields are to be cleaned up + */ +static void bridge_channel_snapshot_pair_cleanup(struct ast_bridge_channel_snapshot_pair *pair) +{ + ao2_cleanup(pair->bridge_snapshot); + ao2_cleanup(pair->channel_snapshot); +} + +static const char *result_strs[] = { + [AST_BRIDGE_TRANSFER_FAIL] = "Fail", + [AST_BRIDGE_TRANSFER_INVALID] = "Invalid", + [AST_BRIDGE_TRANSFER_NOT_PERMITTED] = "Not Permitted", + [AST_BRIDGE_TRANSFER_SUCCESS] = "Success", +}; + +static struct ast_manager_event_blob *blind_transfer_to_ami(struct stasis_message *msg) +{ + RAII_VAR(struct ast_str *, channel_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, bridge_state, NULL, ast_free_ptr); + struct ast_bridge_blob *blob = stasis_message_data(msg); + const char *exten; + const char *context; + enum ast_transfer_result result; + int is_external; + + if (!blob) { + return NULL; + } + + channel_state = ast_manager_build_channel_state_string_prefix(blob->channel, "Transferer"); + bridge_state = ast_manager_build_bridge_state_string(blob->bridge, ""); + + if (!channel_state || !bridge_state) { + return NULL; + } + + exten = ast_json_string_get(ast_json_object_get(blob->blob, "exten")); + context = ast_json_string_get(ast_json_object_get(blob->blob, "context")); + result = ast_json_integer_get(ast_json_object_get(blob->blob, "result")); + is_external = ast_json_integer_get(ast_json_object_get(blob->blob, "is_external")); + + return ast_manager_event_blob_create(EVENT_FLAG_CALL, "BlindTransfer", + "Result: %s\r\n" + "%s" + "%s" + "IsExternal: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n", + result_strs[result], + ast_str_buffer(channel_state), + ast_str_buffer(bridge_state), + is_external ? "Yes" : "No", + context, + exten); +} + +void ast_bridge_publish_blind_transfer(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferer, const char *context, const char *exten) +{ + RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + json_object = ast_json_pack("{s: s, s: s, s: i, s: i}", + "context", context, "exten", exten, "result", result, "is_external", is_external); + + if (!json_object) { + ast_log(LOG_NOTICE, "Failed to create json bridge blob\n"); + return; + } + + msg = ast_bridge_blob_create(ast_blind_transfer_type(), + transferer->bridge, transferer->channel, json_object); + + if (!msg) { + ast_log(LOG_NOTICE, "Failed to create blob msg\n"); + return; + } + + stasis_publish(ast_bridge_topic_all(), msg); +} + +static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_message *msg) +{ + RAII_VAR(struct ast_str *, variable_data, ast_str_create(64), ast_free_ptr); + RAII_VAR(struct ast_str *, transferer1_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, bridge1_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, transferer2_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, bridge2_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, local1_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, local2_state, NULL, ast_free_ptr); + struct ast_attended_transfer_message *transfer_msg = stasis_message_data(msg); + + if (!variable_data) { + return NULL; + } + + transferer1_state = ast_manager_build_channel_state_string_prefix(transfer_msg->to_transferee.channel_snapshot, "OrigTransferer"); + transferer2_state = ast_manager_build_channel_state_string_prefix(transfer_msg->to_transfer_target.channel_snapshot, "SecondTransferer"); + + if (!transferer1_state || !transferer2_state) { + return NULL; + } + + if (transfer_msg->to_transferee.bridge_snapshot) { + bridge1_state = ast_manager_build_bridge_state_string(transfer_msg->to_transferee.bridge_snapshot, "Orig"); + if (!bridge1_state) { + return NULL; + } + } + + if (transfer_msg->to_transfer_target.bridge_snapshot) { + bridge2_state = ast_manager_build_bridge_state_string(transfer_msg->to_transfer_target.bridge_snapshot, "Second"); + if (!bridge2_state) { + return NULL; + } + } + + switch (transfer_msg->dest_type) { + case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: + ast_str_append(&variable_data, 0, "DestType: Bridge\r\n"); + ast_str_append(&variable_data, 0, "DestBridgeUniqueid: %s\r\n", transfer_msg->dest.bridge); + break; + case AST_ATTENDED_TRANSFER_DEST_APP: + ast_str_append(&variable_data, 0, "DestType: App\r\n"); + ast_str_append(&variable_data, 0, "DestApp: %s\r\n", transfer_msg->dest.app); + break; + case AST_ATTENDED_TRANSFER_DEST_LINK: + local1_state = ast_manager_build_channel_state_string_prefix(transfer_msg->dest.links[0], "LocalOne"); + local2_state = ast_manager_build_channel_state_string_prefix(transfer_msg->dest.links[1], "LocalTwo"); + if (!local1_state || !local2_state) { + return NULL; + } + ast_str_append(&variable_data, 0, "DestType: Link\r\n"); + ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local1_state)); + ast_str_append(&variable_data, 0, "%s", ast_str_buffer(local2_state)); + break; + case AST_ATTENDED_TRANSFER_DEST_FAIL: + ast_str_append(&variable_data, 0, "DestType: Fail\r\n"); + break; + } + + return ast_manager_event_blob_create(EVENT_FLAG_CALL, "AttendedTransfer", + "Result: %s\r\n" + "%s" + "%s" + "%s" + "%s" + "IsExternal: %s\r\n" + "%s\r\n", + result_strs[transfer_msg->result], + ast_str_buffer(transferer1_state), + bridge1_state ? ast_str_buffer(bridge1_state) : "", + ast_str_buffer(transferer2_state), + bridge2_state ? ast_str_buffer(bridge2_state) : "", + transfer_msg->is_external ? "Yes" : "No", + ast_str_buffer(variable_data)); +} + +static void attended_transfer_dtor(void *obj) +{ + struct ast_attended_transfer_message *msg = obj; + int i; + + bridge_channel_snapshot_pair_cleanup(&msg->to_transferee); + bridge_channel_snapshot_pair_cleanup(&msg->to_transfer_target); + + if (msg->dest_type != AST_ATTENDED_TRANSFER_DEST_LINK) { + return; + } + + for (i = 0; i < ARRAY_LEN(msg->dest.links); ++i) { + ao2_cleanup(msg->dest.links[i]); + } +} + +static struct ast_attended_transfer_message *attended_transfer_message_create(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target) +{ + RAII_VAR(struct ast_attended_transfer_message *, msg, NULL, ao2_cleanup); + + msg = ao2_alloc(sizeof(*msg), attended_transfer_dtor); + if (!msg) { + return NULL; + } + + if (bridge_channel_snapshot_pair_init(transferee, &msg->to_transferee) || + bridge_channel_snapshot_pair_init(target, &msg->to_transfer_target)) { + return NULL; + } + + msg->is_external = is_external; + msg->result = result; + + ao2_ref(msg, +1); + return msg; +} + +void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target) +{ + RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + if (!transfer_msg) { + return; + } + + transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_FAIL; + + msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg); + if (!msg) { + return; + } + + stasis_publish(ast_bridge_topic_all(), msg); +} + +void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, + struct ast_bridge *final_bridge) +{ + RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + if (!transfer_msg) { + return; + } + + transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE; + ast_copy_string(transfer_msg->dest.bridge, final_bridge->uniqueid, + sizeof(transfer_msg->dest.bridge)); + + msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg); + if (!msg) { + return; + } + + stasis_publish(ast_bridge_topic_all(), msg); +} + +void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, + const char *dest_app) +{ + RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + if (!transfer_msg) { + return; + } + + transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_APP; + ast_copy_string(transfer_msg->dest.app, dest_app, sizeof(transfer_msg->dest.app)); + + msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg); + if (!msg) { + return; + } + + stasis_publish(ast_bridge_topic_all(), msg); +} + +void ast_bridge_publish_attended_transfer_link(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, + struct ast_channel *locals[2]) +{ + RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + int i; + + transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + if (!transfer_msg) { + return; + } + + transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_LINK; + for (i = 0; i < 2; ++i) { + transfer_msg->dest.links[i] = ast_channel_snapshot_create(locals[i]); + if (!transfer_msg->dest.links[i]) { + return; + } + } + + msg = stasis_message_create(ast_attended_transfer_type(), transfer_msg); + if (!msg) { + return; + } + + stasis_publish(ast_bridge_topic_all(), msg); +} + struct ast_bridge_snapshot *ast_bridge_snapshot_get_latest(const char *uniqueid) { RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup); @@ -387,6 +1028,8 @@ static void stasis_bridging_cleanup(void) STASIS_MESSAGE_TYPE_CLEANUP(ast_bridge_merge_message_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_entered_bridge_type); STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_left_bridge_type); + STASIS_MESSAGE_TYPE_CLEANUP(ast_blind_transfer_type); + STASIS_MESSAGE_TYPE_CLEANUP(ast_attended_transfer_type); } /*! \brief snapshot ID getter for caching topic */ @@ -408,9 +1051,12 @@ int ast_stasis_bridging_init(void) STASIS_MESSAGE_TYPE_INIT(ast_bridge_merge_message_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_entered_bridge_type); STASIS_MESSAGE_TYPE_INIT(ast_channel_left_bridge_type); + STASIS_MESSAGE_TYPE_INIT(ast_blind_transfer_type); + STASIS_MESSAGE_TYPE_INIT(ast_attended_transfer_type); bridge_topic_all = stasis_topic_create("ast_bridge_topic_all"); bridge_topic_all_cached = stasis_caching_topic_create(bridge_topic_all, bridge_snapshot_get_id); bridge_topic_pool = stasis_topic_pool_create(bridge_topic_all); + return !bridge_topic_all || !bridge_topic_all_cached || !bridge_topic_pool ? -1 : 0; -- cgit v1.2.3