diff options
author | Mark Michelson <mmichelson@digium.com> | 2013-06-28 18:42:24 +0000 |
---|---|---|
committer | Mark Michelson <mmichelson@digium.com> | 2013-06-28 18:42:24 +0000 |
commit | 6d624eb008b82b1197cd6bb744073d5c6ef945d7 (patch) | |
tree | b3910d3d98c61c20dd198d8bca7f066ac620ad20 | |
parent | ca61a05506c778e155f557c2ffefbf5707874473 (diff) |
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
-rw-r--r-- | bridges/bridge_builtin_features.c | 2 | ||||
-rw-r--r-- | channels/chan_iax2.c | 2 | ||||
-rw-r--r-- | channels/chan_sip.c | 2 | ||||
-rw-r--r-- | channels/chan_skinny.c | 2 | ||||
-rw-r--r-- | include/asterisk/bridging.h | 5 | ||||
-rw-r--r-- | include/asterisk/stasis_bridging.h | 180 | ||||
-rw-r--r-- | main/bridging.c | 264 | ||||
-rw-r--r-- | main/manager.c | 2 | ||||
-rw-r--r-- | main/manager_bridging.c | 38 | ||||
-rw-r--r-- | main/stasis_bridging.c | 646 | ||||
-rw-r--r-- | res/res_sip_refer.c | 4 |
11 files changed, 1103 insertions, 44 deletions
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c index 3a081c83d..2d8a68a1a 100644 --- a/bridges/bridge_builtin_features.c +++ b/bridges/bridge_builtin_features.c @@ -218,7 +218,7 @@ static int feature_blind_transfer(struct ast_bridge *bridge, struct ast_bridge_c ast_after_bridge_set_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr); } - if (ast_bridge_transfer_blind(bridge_channel->chan, exten, context, blind_transfer_cb, + if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb, bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS && !ast_strlen_zero(goto_on_blindxfr)) { ast_after_bridge_goto_discard(bridge_channel->chan); diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c index 0f5c8fc2b..46d1f7d06 100644 --- a/channels/chan_iax2.c +++ b/channels/chan_iax2.c @@ -10684,7 +10684,7 @@ static int socket_process_helper(struct iax2_thread *thread) ast_channel_unlock(owner); ast_mutex_unlock(&iaxsl[fr->callno]); - if (ast_bridge_transfer_blind(owner, ies.called_number, + if (ast_bridge_transfer_blind(1, owner, ies.called_number, context, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) { ast_log(LOG_WARNING, "Blind transfer of '%s' to '%s@%s' failed\n", ast_channel_name(owner), ies.called_number, diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 916fd5ebe..4b795521e 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -26191,7 +26191,7 @@ static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint } sip_pvt_unlock(p); - transfer_res = ast_bridge_transfer_blind(transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data); + transfer_res = ast_bridge_transfer_blind(1, transferer, refer_to, refer_to_context, blind_transfer_cb, &cb_data); sip_pvt_lock(p); switch (transfer_res) { diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index 9bdc0f2d3..647213228 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -5288,7 +5288,7 @@ static void skinny_transfer_blind(struct skinny_subchannel *sub) xferee->related = NULL; ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); - res = ast_bridge_transfer_blind(xferee->owner, sub->exten, sub->line->context, NULL, NULL); + res = ast_bridge_transfer_blind(1, xferee->owner, sub->exten, sub->line->context, NULL, NULL); if (res != AST_BRIDGE_TRANSFER_SUCCESS) { SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d failed to blind transfer %d to '%s'@'%s' - %d\n", diff --git a/include/asterisk/bridging.h b/include/asterisk/bridging.h index eac4b499c..2fde0488b 100644 --- a/include/asterisk/bridging.h +++ b/include/asterisk/bridging.h @@ -1499,6 +1499,7 @@ typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data, * \note Absolutely _NO_ channel locks should be held before * calling this function. * + * \param is_external Indicates that transfer was initiated externally * \param transferer The channel performing the blind transfer * \param exten The dialplan extension to send the call to * \param context The dialplan context to send the call to @@ -1507,8 +1508,8 @@ typedef void (*transfer_channel_cb)(struct ast_channel *chan, void *user_data, * \param user_data Argument for new_channel_cb * \return The success or failure result of the blind transfer */ -enum ast_transfer_result ast_bridge_transfer_blind(struct ast_channel *transferer, - const char *exten, const char *context, +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); /*! diff --git a/include/asterisk/stasis_bridging.h b/include/asterisk/stasis_bridging.h index 76f4a9fed..f2e5acfd6 100644 --- a/include/asterisk/stasis_bridging.h +++ b/include/asterisk/stasis_bridging.h @@ -29,6 +29,7 @@ extern "C" { #include "asterisk/linkedlists.h" #include "asterisk/channel.h" #include "asterisk/bridging.h" +#include "asterisk/pbx.h" /*! * \brief Structure that contains a snapshot of information about a bridge @@ -211,6 +212,185 @@ void ast_bridge_publish_leave(struct ast_bridge *bridge, struct ast_channel *cha struct ast_json *ast_bridge_snapshot_to_json(const struct ast_bridge_snapshot *snapshot); /*! + * \brief Pair showing a bridge snapshot and a specific channel snapshot belonging to the bridge + */ +struct ast_bridge_channel_snapshot_pair { + struct ast_bridge_snapshot *bridge_snapshot; + struct ast_channel_snapshot *channel_snapshot; +}; + +/*! + * \brief Pair showing a bridge and a specific channel belonging to the bridge + */ +struct ast_bridge_channel_pair { + struct ast_bridge *bridge; + struct ast_channel *channel; +}; + +/*! + * \brief Message representing blind transfer + */ +struct ast_blind_transfer_message { + AST_DECLARE_STRING_FIELDS( + /*! The destination context for the blind transfer */ + AST_STRING_FIELD(context); + /*! The destination extension for the blind transfer */ + AST_STRING_FIELD(exten); + ); + /*! Result of the blind transfer */ + enum ast_transfer_result result; + /*! If 0, was core DTMF transfer, otherwise occurred externally*/ + int is_external; + /*! The transferer and its bridge before starting the transfer*/ + struct ast_bridge_channel_snapshot_pair transferer; +}; + +/*! + * \since 12 + * \brief Message type for \ref ast_blind_transfer_message. + * + * \retval Message type for \ref ast_blind_transfer_message. + */ +struct stasis_message_type *ast_blind_transfer_type(void); + +/*! + * \brief Publish a blind transfer event + * + * \param is_external Whether the blind transfer was initiated externally (e.g. via AMI or native protocol) + * \param result The success or failure of the transfer + * \param to_transferee The bridge between the transferer and transferee plus the transferer channel + * \param context The destination context for the blind transfer + * \param exten The destination extension for the blind transfer + */ +void ast_bridge_publish_blind_transfer(int is_external, enum ast_transfer_result result, + struct ast_bridge_channel_pair *to_transferee, const char *context, const char *exten); + +enum ast_attended_transfer_dest_type { + /*! The transfer failed, so there is no appropriate final state */ + AST_ATTENDED_TRANSFER_DEST_FAIL, + /*! The transfer results in a single bridge remaining due to a merge or swap */ + AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE, + /*! The transfer results in a channel or bridge running an application */ + AST_ATTENDED_TRANSFER_DEST_APP, + /*! The transfer results in both bridges remaining with a local channel linking them */ + AST_ATTENDED_TRANSFER_DEST_LINK, +}; + +/*! + * \brief Message representing attended transfer + */ +struct ast_attended_transfer_message { + /*! Result of the attended transfer */ + enum ast_transfer_result result; + /*! Indicates if the transfer was initiated externally*/ + int is_external; + /*! Bridge between transferer <-> transferee and the transferer channel in that bridge. May be NULL */ + struct ast_bridge_channel_snapshot_pair to_transferee; + /*! Bridge between transferer <-> transfer target and the transferer channel in that bridge. May be NULL */ + struct ast_bridge_channel_snapshot_pair to_transfer_target; + /*! Indicates the final state of the transfer */ + enum ast_attended_transfer_dest_type dest_type; + union { + /*! ID of the surviving bridge. Applicable for AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE */ + char bridge[AST_UUID_STR_LEN]; + /*! Destination application of transfer. Applicable for AST_ATTENDED_TRANSFER_DEST_APP */ + char app[AST_MAX_APP]; + /*! Pair of local channels linking the bridges. Applicable for AST_ATTENDED_TRANSFER_DEST_LINK */ + struct ast_channel_snapshot *links[2]; + } dest; +}; + +/*! + * \since 12 + * \brief Message type for \ref ast_attended_transfer_message. + * + * \retval Message type for \ref ast_attended_transfer_message. + */ +struct stasis_message_type *ast_attended_transfer_type(void); + +/*! + * \since 12 + * \brief Publish an attended transfer failure + * + * Publish an \ref ast_attended_transfer_message with the dest_type set to + * \c AST_ATTENDED_TRANSFER_DEST_FAIL. + * + * \param is_external Indicates if the transfer was initiated externally + * \param result The result of the transfer. Will always be a type of failure. + * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge + * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge + */ +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); + +/*! + * \since 12 + * \brief Publish an attended transfer that results in two bridges becoming one. + * + * Publish an \ref ast_attended_transfer_message with the dest_type set to + * \c AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE. This type of attended transfer results from + * having two bridges involved and either + * + * \li Merging the two bridges together + * \li Moving a channel from one bridge to the other, thus emptying a bridge + * + * In either case, two bridges enter, one leaves. + * + * \param is_external Indicates if the transfer was initiated externally + * \param result The result of the transfer. + * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge + * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge + * \param final_bridge The bridge that the parties end up in. Will be a bridge from the transferee or target pair. + */ +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); + +/*! + * \since 12 + * \brief Publish an attended transfer that results in an application being run + * + * Publish an \ref ast_attended_transfer_message with the dest_type set to + * \c AST_ATTENDED_TRANSFER_DEST_APP. This occurs when an attended transfer + * results in either: + * + * \li A transferee channel leaving a bridge to run an app + * \li A bridge of transferees running an app (via a local channel) + * + * \param is_external Indicates if the transfer was initiated externally + * \param result The result of the transfer. + * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge + * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge + * \param dest_app The application that the channel or bridge is running upon transfer completion. + */ +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); + +/*! + * \since 12 + * \brief Publish an attended transfer that results in two bridges linked by a local channel + * + * Publish an \ref ast_attended_transfer_message with the dest_type set to + * \c AST_ATTENDED_TRANSFER_DEST_LINK. This occurs when two bridges are involved + * in an attended transfer, but their properties do not allow for the bridges to + * merge or to have channels moved off of the bridge. An example of this occurs when + * attempting to transfer a ConfBridge to another bridge. + * + * When this type of transfer occurs, the two bridges continue to exist after the + * transfer and a local channel is used to link the two bridges together. + * + * \param is_external Indicates if the transfer was initiated externally + * \param result The result of the transfer. + * \param transferee The bridge between the transferer and transferees as well as the transferer channel from that bridge + * \param target The bridge between the transferer and transfer targets as well as the transferer channel from that bridge + * \param locals The local channels linking the bridges together. + */ +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]); + +/*! * \brief Returns the most recent snapshot for the bridge. * * The returned pointer is AO2 managed, so ao2_cleanup() when you're done. diff --git a/main/bridging.c b/main/bridging.c index ee7f511cb..92c66ac5c 100644 --- a/main/bridging.c +++ b/main/bridging.c @@ -5924,6 +5924,113 @@ static enum ast_transfer_result blind_transfer_bridge(struct ast_channel *transf } /*! + * \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 * * This performs an attended transfer of an entire bridge to a target. @@ -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; </manager> ***/ +/*! \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 + <managerEvent language="en_US" name="BlindTransfer"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a blind transfer is complete.</synopsis> + <syntax> + <parameter name="Result"> + <para>Indicates if the transfer was successful or if it failed.</para> + <enumlist> + <enum name="Fail"><para>An internal error occurred.</para></enum> + <enum name="Invalid"><para>Invalid configuration for transfer (e.g. Not bridged)</para></enum> + <enum name="Not Permitted"><para>Bridge does not permit transfers</para></enum> + <enum name="Success"><para>Transfer completed successfully</para></enum> + </enumlist> + <note><para>A result of <literal>Success</literal> 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.</para></note> + </parameter> + <parameter name="TransfererChannel"> + <para>The name of the channel that performed the transfer</para> + </parameter> + <parameter name="TransfererChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + </parameter> + <parameter name="TransfererCallerIDNum"> + </parameter> + <parameter name="TransfererCallerIDName"> + </parameter> + <parameter name="TransfererConnectedLineNum"> + </parameter> + <parameter name="TransfererConnectedLineName"> + </parameter> + <parameter name="TransfererAccountCode"> + </parameter> + <parameter name="TransfererContext"> + </parameter> + <parameter name="TransfererExten"> + </parameter> + <parameter name="TransfererPriority"> + </parameter> + <parameter name="TransfererUniqueid"> + </parameter> + <parameter name="BridgeUniqueid"> + <para>The ID of the bridge where the Transferer performed the transfer</para> + </parameter> + <parameter name="BridgeType"> + <para>The type of the bridge where the Transferer performed the transfer</para> + </parameter> + <parameter name="IsExternal"> + <para>Indicates if the transfer was performed outside of Asterisk. For instance, + a channel protocol native transfer is external. A DTMF transfer is internal.</para> + <enumlist> + <enum name="Yes" /> + <enum name="No" /> + </enumlist> + </parameter> + <parameter name="Context"> + <para>Destination context for the blind transfer.</para> + </parameter> + <parameter name="Extension"> + <para>Destination extension for the blind transfer.</para> + </parameter> + </syntax> + </managerEventInstance> + </managerEvent> + <managerEvent language="en_US" name="AttendedTransfer"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when an attended transfer is complete.</synopsis> + <syntax> + <xi:include xpointer="xpointer(docs/managerEvent[@name='BlindTransfer']/managerEventInstance/syntax/parameter[@name='Result'])" /> + <parameter name="OrigTransfererChannel"> + <para>The original transferer channel that performed the attended transfer.</para> + </parameter> + <parameter name="OrigTransfererChannelState"> + <para>A numeric code for the channel's current state, related to DestChannelStateDesc</para> + </parameter> + <parameter name="OrigTransfererChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + </parameter> + <parameter name="OrigTransfererCallerIDNum"> + </parameter> + <parameter name="OrigTransfererCallerIDName"> + </parameter> + <parameter name="OrigTransfererConnectedLineNum"> + </parameter> + <parameter name="OrigTransfererConnectedLineName"> + </parameter> + <parameter name="OrigTransfererAccountCode"> + </parameter> + <parameter name="OrigTransfererContext"> + </parameter> + <parameter name="OrigTransfererExten"> + </parameter> + <parameter name="OrigTransfererPriority"> + </parameter> + <parameter name="OrigTransfererUniqueid"> + </parameter> + <parameter name="BridgeUniqueidOrig"> + <para>The ID of the bridge where the Transferer performed the transfer</para> + <note><para>This header will not be present if the original transferer was not in a bridge.</para></note> + </parameter> + <parameter name="BridgeTypeOrig"> + <para>The type of the bridge where the Transferer performed the transfer</para> + <note><para>This header will not be present if the original transferer was not in a bridge.</para></note> + </parameter> + <parameter name="SecondTransfererChannel"> + <para>The second transferer channel involved in the attended transfer.</para> + </parameter> + <parameter name="SecondTransfererChannelState"> + <para>A numeric code for the channel's current state, related to SecondTransfererChannelStateDesc</para> + </parameter> + <parameter name="SecondTransfererChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + </parameter> + <parameter name="SecondTransfererCallerIDNum"> + </parameter> + <parameter name="SecondTransfererCallerIDName"> + </parameter> + <parameter name="SecondTransfererConnectedLineNum"> + </parameter> + <parameter name="SecondTransfererConnectedLineName"> + </parameter> + <parameter name="SecondTransfererAccountCode"> + </parameter> + <parameter name="SecondTransfererContext"> + </parameter> + <parameter name="SecondTransfererExten"> + </parameter> + <parameter name="SecondTransfererPriority"> + </parameter> + <parameter name="SecondTransfererUniqueid"> + </parameter> + <parameter name="BridgeUniqueidSecond"> + <para>The unique ID of the bridge that the second transferer channel was in, or <literal>None</literal> if the second transferer channel was not bridged</para> + <note><para>This header will not be present if the second transferer was not in a bridge.</para></note> + </parameter> + <parameter name="BridgeTypeSecond"> + <para>The type of the bridge where the Transferer performed the transfer</para> + <note><para>This header will not be present if the second transferer was not in a bridge.</para></note> + </parameter> + <parameter name="DestType"> + <para>Indicates the method by which the attended transfer completed.</para> + <enumlist> + <enum name="Bridge"><para>The transfer was accomplished by merging two bridges into one.</para></enum> + <enum name="App"><para>The transfer was accomplished by having a channel or bridge run a dialplan application.</para></enum> + <enum name="Link"><para>The transfer was accomplished by linking two bridges together using a local channel pair.</para></enum> + <enum name="Fail"><para>The transfer failed.</para></enum> + </enumlist> + </parameter> + <parameter name="DestBridgeUniqueid"> + <para>Indicates the surviving bridge when bridges were merged to complete the transfer</para> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Bridge</literal></para></note> + </parameter> + <parameter name="DestApp"> + <para>Indicates the application that is running when the transfer completes</para> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>App</literal></para></note> + </parameter> + <parameter name="LocalOneChannel"> + <para>The local channel that is bridged with the original bridge when forming a link between bridges</para> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneChannelState"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneCallerIDNum"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneCallerIDName"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneConnectedLineNum"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneConnectedLineName"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneAccountCode"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneContext"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneExten"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOnePriority"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalOneUniqueid"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoChannel"> + <para>The local channel that is bridged with the second bridge when forming a link between bridges</para> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoChannelState"> + <para>A numeric code for the channel's current state, related to LocalTwoChannelStateDesc</para> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoCallerIDNum"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoCallerIDName"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoConnectedLineNum"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoConnectedLineName"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoAccountCode"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoContext"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoExten"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoPriority"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + <parameter name="LocalTwoUniqueid"> + <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Link</literal></para></note> + </parameter> + </syntax> + <description> + <para>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:</para> + <para> <replaceable>OrigTransfererChannel</replaceable>: Alice's channel in the bridge with Bob.</para> + <para> <replaceable>BridgeUniqueidOrig</replaceable>: The bridge between Alice and Bob.</para> + <para> <replaceable>SecondTransfererChannel</replaceable>: Alice's channel that called Voicemail.</para> + <para> <replaceable>BridgeUniqueidSecond</replaceable>: Not present, since a call to Voicemail has no bridge.</para> + <para>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:</para> + <para> <replaceable>OrigTransfererChannel</replaceable>: Alice's channel that called Voicemail.</para> + <para> <replaceable>BridgeUniqueidOrig</replaceable>: Not present, since a call to Voicemail has no bridge.</para> + <para> <replaceable>SecondTransfererChannel</replaceable>: Alice's channel in the bridge with Bob.</para> + <para> <replaceable>BridgeUniqueidSecond</replaceable>: The bridge between Alice and Bob.</para> + </description> + </managerEventInstance> + </managerEvent> + ***/ + +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; diff --git a/res/res_sip_refer.c b/res/res_sip_refer.c index dfc35a3f4..9cc46442d 100644 --- a/res/res_sip_refer.c +++ b/res/res_sip_refer.c @@ -552,7 +552,7 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi refer.replaces = replaces; refer.refer_to = target_uri; - switch (ast_bridge_transfer_blind(session->channel, "external_replaces", context, refer_blind_callback, &refer)) { + switch (ast_bridge_transfer_blind(1, session->channel, "external_replaces", context, refer_blind_callback, &refer)) { case AST_BRIDGE_TRANSFER_INVALID: return 400; case AST_BRIDGE_TRANSFER_NOT_PERMITTED: @@ -594,7 +594,7 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r refer.progress = progress; refer.rdata = rdata; - switch (ast_bridge_transfer_blind(session->channel, exten, context, refer_blind_callback, &refer)) { + switch (ast_bridge_transfer_blind(1, session->channel, exten, context, refer_blind_callback, &refer)) { case AST_BRIDGE_TRANSFER_INVALID: return 400; case AST_BRIDGE_TRANSFER_NOT_PERMITTED: |