diff --git a/apps/app_queue.c b/apps/app_queue.c index 012f04a710..ef547df60f 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -5551,6 +5551,7 @@ static void log_attended_transfer(struct queue_stasis_data *queue_data, struct a ast_str_set(&transfer_str, 0, "BRIDGE|%s", atxfer_msg->dest.bridge); break; case AST_ATTENDED_TRANSFER_DEST_APP: + case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP: ast_str_set(&transfer_str, 0, "APP|%s", atxfer_msg->dest.app); break; case AST_ATTENDED_TRANSFER_DEST_LINK: @@ -5619,10 +5620,7 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su struct stasis_message *msg) { struct queue_stasis_data *queue_data = userdata; - struct ast_bridge_blob *blind_blob = stasis_message_data(msg); - struct ast_json *result_blob; - struct ast_json *exten_blob; - struct ast_json *context_blob; + struct ast_blind_transfer_message *transfer_msg = stasis_message_data(msg); const char *exten; const char *context; RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup); @@ -5632,19 +5630,14 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su return; } - result_blob = ast_json_object_get(blind_blob->blob, "result"); - if (!result_blob) { - return; - } - - if (ast_json_integer_get(result_blob) != AST_BRIDGE_TRANSFER_SUCCESS) { + if (transfer_msg->result != AST_BRIDGE_TRANSFER_SUCCESS) { return; } ao2_lock(queue_data); if (ast_strlen_zero(queue_data->bridge_uniqueid) || - strcmp(queue_data->bridge_uniqueid, blind_blob->bridge->uniqueid)) { + strcmp(queue_data->bridge_uniqueid, transfer_msg->to_transferee.bridge_snapshot->uniqueid)) { ao2_unlock(queue_data); return; } @@ -5654,10 +5647,8 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su ao2_unlock(queue_data); - exten_blob = ast_json_object_get(blind_blob->blob, "exten"); - exten = exten_blob ? ast_json_string_get(exten_blob) : ""; - context_blob = ast_json_object_get(blind_blob->blob, "context"); - context = context_blob ? ast_json_string_get(context_blob) : ""; + exten = transfer_msg->exten; + context = transfer_msg->context; ast_debug(3, "Detected blind transfer in queue %s\n", queue_data->queue->name); ast_queue_log(queue_data->queue->name, caller_snapshot->uniqueid, queue_data->member->membername, diff --git a/include/asterisk/bridge_features.h b/include/asterisk/bridge_features.h index dddc9b0431..df01a0dca8 100644 --- a/include/asterisk/bridge_features.h +++ b/include/asterisk/bridge_features.h @@ -157,6 +157,25 @@ typedef void (*ast_bridge_hook_pvt_destructor)(void *hook_pvt); */ typedef int (*ast_bridge_talking_indicate_callback)(struct ast_bridge_channel *bridge_channel, void *hook_pvt, int talking); +/*! + * \brief Move indicator callback + * + * \details + * This callback can be registered with the bridge channel in order + * to be notified when the bridge channel is being moved from one + * bridge to another. + * + * \param bridge_channel The channel executing the feature + * \param hook_pvt Private data passed in when the hook was created + * \param src The bridge from which the bridge channel is moving + * \param dst The bridge into which the bridge channel is moving + * + * \retval 0 Keep the callback hook. + * \retval -1 Remove the callback hook. + */ +typedef int (*ast_bridge_move_indicate_callback)(struct ast_bridge_channel *bridge_channel, + void *hook_pvt, struct ast_bridge *src, struct ast_bridge *dst); + enum ast_bridge_hook_remove_flags { /*! The hook is removed when the channel is pulled from the bridge. */ AST_BRIDGE_HOOK_REMOVE_ON_PULL = (1 << 0), @@ -173,6 +192,7 @@ enum ast_bridge_hook_type { AST_BRIDGE_HOOK_TYPE_JOIN, AST_BRIDGE_HOOK_TYPE_LEAVE, AST_BRIDGE_HOOK_TYPE_TALK, + AST_BRIDGE_HOOK_TYPE_MOVE, }; /*! \brief Structure that is the essence of a feature hook. */ @@ -620,6 +640,37 @@ int ast_bridge_talk_detector_hook(struct ast_bridge_features *features, ast_bridge_hook_pvt_destructor destructor, enum ast_bridge_hook_remove_flags remove_flags); +/*! + * \brief Attach a bridge channel move detection hook to a bridge features structure + * + * \param features Bridge features structure + * \param callback Function to execute upon activation + * \param hook_pvt Unique data + * \param destructor Optional destructor callback for hook_pvt data + * \param remove_flags Dictates what situations the hook should be removed. + * + * \retval 0 on success + * \retval -1 on failure (The caller must cleanup any hook_pvt resources.) + * + * Example usage: + * + * \code + * struct ast_bridge_features features; + * ast_bridge_features_init(&features); + * ast_bridge_move_hook(&features, move_callback, NULL, NULL, 0); + * \endcode + * + * This makes the bridging core call \ref callback when a + * channel is moved from one bridge to another. A + * pointer to useful data may be provided to the hook_pvt + * parameter. + */ +int ast_bridge_move_hook(struct ast_bridge_features *features, + ast_bridge_move_indicate_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + enum ast_bridge_hook_remove_flags remove_flags); + /*! * \brief Enable a built in feature on a bridge features structure * diff --git a/include/asterisk/datastore.h b/include/asterisk/datastore.h index 9060a5f82c..8f59fd3cf7 100644 --- a/include/asterisk/datastore.h +++ b/include/asterisk/datastore.h @@ -34,7 +34,7 @@ struct ast_datastore_info { void (*destroy)(void *data); /*!< Destroy function */ /*! - * \brief Fix up channel references + * \brief Fix up channel references on the masquerading channel * * \arg data The datastore data * \arg old_chan The old channel owning the datastore @@ -48,6 +48,20 @@ struct ast_datastore_info { * \return nothing. */ void (*chan_fixup)(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); + + /*! + * \brief Fix up channel references on the channel being masqueraded into + * + * \arg data The datastore data + * \arg old_chan The old channel owning the datastore + * \arg new_chan The new channel owning the datastore + * + * This is the same as the above callback, except it is called for the channel + * being masqueraded into instead of the channel that is masquerading. + * + * \return nothing. + */ + void (*chan_breakdown)(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); }; /*! \brief Structure for a data store object */ diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index a7b2040346..e06e68ed26 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -799,6 +799,28 @@ void stasis_app_unref(void); */ struct stasis_message_sanitizer *stasis_app_get_sanitizer(void); +/*! + * \brief Stasis message type for a StasisEnd event + */ +struct stasis_message_type *ast_stasis_end_message_type(void); + +/*! + * \brief Indicate that this channel has had a StasisEnd published for it + * + * \param The channel that is exiting Stasis. + */ +void stasis_app_channel_set_stasis_end_published(struct ast_channel *chan); + +/*! + * \brief Has this channel had a StasisEnd published on it? + * + * \param chan The channel upon which the query rests. + * + * \retval 0 No + * \retval 1 Yes + */ +int stasis_app_channel_is_stasis_end_published(struct ast_channel *chan); + /*! @} */ #endif /* _ASTERISK_STASIS_APP_H */ diff --git a/include/asterisk/stasis_bridges.h b/include/asterisk/stasis_bridges.h index 49d4db2f76..a4e46cdb0a 100644 --- a/include/asterisk/stasis_bridges.h +++ b/include/asterisk/stasis_bridges.h @@ -284,6 +284,24 @@ struct ast_bridge_channel_pair { */ struct stasis_message_type *ast_blind_transfer_type(void); +/*! + * \brief Message published during a blind transfer + */ +struct ast_blind_transfer_message { + /*! Result of the transfer */ + enum ast_transfer_result result; + /*! True if the transfer was initiated by an external source (i.e. not DTMF-initiated) */ + int is_external; + /*! Transferer and its bridge */ + struct ast_bridge_channel_snapshot_pair to_transferee; + /*! Destination context */ + char context[AST_MAX_CONTEXT]; + /*! Destination extension */ + char exten[AST_MAX_EXTENSION]; + /*! Transferee channel. NULL if there were multiple transferee channels */ + struct ast_channel_snapshot *transferee; +}; + /*! * \brief Publish a blind transfer event * @@ -294,9 +312,11 @@ struct stasis_message_type *ast_blind_transfer_type(void); * \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 + * \param transferee_channel If a single channel is being transferred, this is it. If multiple parties are being transferred, this is NULL. */ 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); + struct ast_bridge_channel_pair *to_transferee, const char *context, const char *exten, + struct ast_channel *transferee_channel); enum ast_attended_transfer_dest_type { /*! The transfer failed, so there is no appropriate final state */ @@ -305,6 +325,8 @@ enum ast_attended_transfer_dest_type { 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 a channel or bridge running an application via a local channel */ + AST_ATTENDED_TRANSFER_DEST_LOCAL_APP, /*! The transfer results in both bridges remaining with a local channel linking them */ AST_ATTENDED_TRANSFER_DEST_LINK, /*! The transfer results in a threeway call between transferer, transferee, and transfer target */ @@ -323,6 +345,12 @@ struct ast_attended_transfer_message { 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; + /*! Local channel connecting transferee bridge to application */ + struct ast_channel_snapshot *replace_channel; + /*! Transferee channel. Will be NULL if there were multiple channels transferred. */ + struct ast_channel_snapshot *transferee; + /*! Transfer target channel. Will be NULL if there were multiple channels targeted. */ + struct ast_channel_snapshot *target; /*! Indicates the final state of the transfer */ enum ast_attended_transfer_dest_type dest_type; union { @@ -358,9 +386,12 @@ struct stasis_message_type *ast_attended_transfer_type(void); * \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 + * \param transferee_channel If a single channel is being transferred, this is it. If multiple parties are being transferred, this is NULL. + * \param target_channel If a single channel is being transferred to, this is it. If multiple parties are being transferred to, this is NULL. */ 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); + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, + struct ast_channel *transferee_channel, struct ast_channel *target_channel); /*! * \since 12 @@ -382,10 +413,13 @@ void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfe * \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. + * \param transferee_channel If a single channel is being transferred, this is it. If multiple parties are being transferred, this is NULL. + * \param target_channel If a single channel is being transferred to, this is it. If multiple parties are being transferred to, this is NULL. */ 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); + struct ast_bridge *final_bridge, struct ast_channel *transferee_channel, + struct ast_channel *target_channel); /*! * \since 12 @@ -403,10 +437,13 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast * \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_pair The bridge that the parties end up in, and the transferer channel that is in this bridge. + * \param transferee_channel If a single channel is being transferred, this is it. If multiple parties are being transferred, this is NULL. + * \param target_channel If a single channel is being transferred to, this is it. If multiple parties are being transferred to, this is NULL. */ void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result, struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, - struct ast_bridge_channel_pair *final_pair); + struct ast_bridge_channel_pair *final_pair, struct ast_channel *transferee_channel, + struct ast_channel *target_channel); /*! * \since 12 @@ -423,13 +460,23 @@ void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_tra * * \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. + * \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 replace_channel The channel that will be replacing the transferee bridge + * transferer channel when a local channel is involved + * \param dest_app The application that the channel or bridge is running upon transfer + * completion. + * \param transferee_channel If a single channel is being transferred, this is it. + * If multiple parties are being transferred, this is NULL. + * \param target_channel If a single channel is being transferred to, this is it. + * If multiple parties are being transferred to, this is NULL. */ 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); + struct ast_channel *replace_channel, const char *dest_app, + struct ast_channel *transferee_channel, struct ast_channel *target_channel); /*! * \since 12 @@ -451,10 +498,13 @@ void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_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. + * \param transferee_channel If a single channel is being transferred, this is it. If multiple parties are being transferred, this is NULL. + * \param target_channel If a single channel is being transferred to, this is it. If multiple parties are being transferred to, this is NULL. */ 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]); + struct ast_channel *locals[2], struct ast_channel *transferee_channel, + struct ast_channel *target_channel); /*! * \brief Returns the most recent snapshot for the bridge. diff --git a/main/bridge.c b/main/bridge.c index 462676ca84..926004bc90 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -1825,6 +1825,32 @@ static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_chann ao2_ref(old_bridge, -1); } +static void bridge_channel_moving(struct ast_bridge_channel *bridge_channel, struct ast_bridge *src, struct ast_bridge *dst) +{ + struct ast_bridge_features *features = bridge_channel->features; + struct ast_bridge_hook *hook; + struct ao2_iterator iter; + + /* Run any moving hooks. */ + iter = ao2_iterator_init(features->other_hooks, 0); + for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) { + int remove_me; + ast_bridge_move_indicate_callback move_cb; + + if (hook->type != AST_BRIDGE_HOOK_TYPE_MOVE) { + continue; + } + move_cb = (ast_bridge_move_indicate_callback) hook->callback; + remove_me = move_cb(bridge_channel, hook->hook_pvt, src, dst); + if (remove_me) { + ast_debug(1, "Move detection hook %p is being removed from %p(%s)\n", + hook, bridge_channel, ast_channel_name(bridge_channel->chan)); + ao2_unlink(features->other_hooks, hook); + } + } + ao2_iterator_destroy(&iter); +} + void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick, unsigned int optimized) { @@ -1873,6 +1899,8 @@ void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridg continue; } + bridge_channel_moving(bridge_channel, bridge_channel->bridge, dst_bridge); + /* Point to new bridge.*/ bridge_channel_change_bridge(bridge_channel, dst_bridge); @@ -2122,6 +2150,8 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */ bridge_channel_change_bridge(bridge_channel, dst_bridge); + bridge_channel_moving(bridge_channel, orig_bridge, dst_bridge); + if (bridge_channel_internal_push(bridge_channel)) { /* Try to put the channel back into the original bridge. */ ast_bridge_features_remove(bridge_channel->features, @@ -3089,6 +3119,18 @@ int ast_bridge_talk_detector_hook(struct ast_bridge_features *features, AST_BRIDGE_HOOK_TYPE_TALK); } +int ast_bridge_move_hook(struct ast_bridge_features *features, + ast_bridge_move_indicate_callback callback, + void *hook_pvt, + ast_bridge_hook_pvt_destructor destructor, + enum ast_bridge_hook_remove_flags remove_flags) +{ + ast_bridge_hook_callback hook_cb = (ast_bridge_hook_callback) callback; + + return bridge_other_hook(features, hook_cb, hook_pvt, destructor, remove_flags, + AST_BRIDGE_HOOK_TYPE_MOVE); +} + int ast_bridge_interval_hook(struct ast_bridge_features *features, enum ast_bridge_hook_timer_option flags, unsigned int interval, @@ -3828,14 +3870,55 @@ struct stasis_attended_transfer_publish_data { 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; + /* The Local;1 that will replace the transferee bridge transferer channel */ + struct ast_channel *replace_channel; + /* The transferee channel. NULL if there is no transferee channel or if multiple parties are transferred */ + struct ast_channel *transferee_channel; + /* The transfer target channel. NULL if there is no transfer target channel or if multiple parties are transferred */ + struct ast_channel *target_channel; }; +/*! + * \internal + * \brief Get the transferee channel + * + * This is only applicable to cases where a transfer is occurring on a + * two-party bridge. The channels container passed in is expected to only + * contain two channels, the transferer and the transferee. The transferer + * channel is passed in as a parameter to ensure we don't return it as + * the transferee channel. + * + * \param channels A two-channel container containing the transferer and transferee + * \param transferer The party that is transfering the call + * \return The party that is being transferred + */ +static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer) +{ + struct ao2_iterator channel_iter; + struct ast_channel *transferee; + + for (channel_iter = ao2_iterator_init(channels, 0); + (transferee = ao2_iterator_next(&channel_iter)); + ao2_cleanup(transferee)) { + if (transferee != transferer) { + break; + } + } + + ao2_iterator_destroy(&channel_iter); + return transferee; +} + + 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); + ast_channel_cleanup(publication->transferee_channel); + ast_channel_cleanup(publication->target_channel); ao2_cleanup(publication->to_transferee.bridge); ao2_cleanup(publication->to_transfer_target.bridge); + ao2_cleanup(publication->replace_channel); } /*! @@ -3865,6 +3948,9 @@ static void stasis_publish_data_init(struct ast_channel *to_transferee, ao2_ref(to_target_bridge, +1); publication->to_transfer_target.bridge = to_target_bridge; } + + publication->transferee_channel = ast_bridge_peer(to_transferee_bridge, to_transferee); + publication->target_channel = ast_bridge_peer(to_target_bridge, to_transfer_target); } /* @@ -3878,7 +3964,8 @@ static void publish_attended_transfer_bridge_merge(struct stasis_attended_transf 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); + &publication->to_transferee, &publication->to_transfer_target, final_bridge, + publication->transferee_channel, publication->target_channel); } /* @@ -3892,7 +3979,9 @@ static void publish_attended_transfer_app(struct stasis_attended_transfer_publis const char *app) { ast_bridge_publish_attended_transfer_app(1, AST_BRIDGE_TRANSFER_SUCCESS, - &publication->to_transferee, &publication->to_transfer_target, app); + &publication->to_transferee, &publication->to_transfer_target, + publication->replace_channel, app, + publication->transferee_channel, publication->target_channel); } /* @@ -3909,7 +3998,8 @@ static void publish_attended_transfer_link(struct stasis_attended_transfer_publi 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); + &publication->to_transferee, &publication->to_transfer_target, locals, + publication->transferee_channel, publication->target_channel); } /* @@ -3923,7 +4013,8 @@ static void publish_attended_transfer_fail(struct stasis_attended_transfer_publi enum ast_transfer_result result) { ast_bridge_publish_attended_transfer_fail(1, result, &publication->to_transferee, - &publication->to_transfer_target); + &publication->to_transfer_target, publication->transferee_channel, + publication->target_channel); } /*! @@ -3987,9 +4078,12 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha return AST_BRIDGE_TRANSFER_FAIL; } + /* Get a ref for use later since this one is being stolen */ + ao2_ref(local_chan, +1); if (ast_bridge_impart(bridge1, local_chan, chan1, NULL, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { ast_hangup(local_chan); + ao2_cleanup(local_chan); return AST_BRIDGE_TRANSFER_FAIL; } @@ -4005,40 +4099,12 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha publish_attended_transfer_link(publication, local_chan, local_chan2); } else { + publication->replace_channel = ao2_bump(local_chan); publish_attended_transfer_app(publication, app); } - return AST_BRIDGE_TRANSFER_SUCCESS; -} -/*! - * \internal - * \brief Get the transferee channel - * - * This is only applicable to cases where a transfer is occurring on a - * two-party bridge. The channels container passed in is expected to only - * contain two channels, the transferer and the transferee. The transferer - * channel is passed in as a parameter to ensure we don't return it as - * the transferee channel. - * - * \param channels A two-channel container containing the transferer and transferee - * \param transferer The party that is transfering the call - * \return The party that is being transferred - */ -static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer) -{ - struct ao2_iterator channel_iter; - struct ast_channel *transferee; - - for (channel_iter = ao2_iterator_init(channels, 0); - (transferee = ao2_iterator_next(&channel_iter)); - ao2_cleanup(transferee)) { - if (transferee != transferer) { - break; - } - } - - ao2_iterator_destroy(&channel_iter); - return transferee; + ao2_cleanup(local_chan); + return AST_BRIDGE_TRANSFER_SUCCESS; } static enum ast_transfer_result try_parking(struct ast_channel *transferer, @@ -4142,7 +4208,7 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan) 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) + const char *context, const char *exten, struct ast_channel *transferee_channel) { struct ast_bridge_channel_pair pair; pair.channel = transferer; @@ -4150,7 +4216,7 @@ static void publish_blind_transfer(int is_external, enum ast_transfer_result res if (bridge) { ast_bridge_lock(bridge); } - ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten); + ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten, transferee_channel); if (bridge) { ast_bridge_unlock(bridge); } @@ -4174,6 +4240,9 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, transfer_result = AST_BRIDGE_TRANSFER_INVALID; goto publish; } + + transferee = ast_bridge_peer(bridge, transferer); + ast_channel_lock(transferer); bridge_channel = ast_channel_get_bridge_channel(transferer); ast_channel_unlock(transferer); @@ -4235,7 +4304,6 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, /* Reaching this portion means that we're dealing with a two-party bridge */ - transferee = get_transferee(channels, transferer); if (!transferee) { transfer_result = AST_BRIDGE_TRANSFER_FAIL; goto publish; @@ -4251,7 +4319,7 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, transfer_result = AST_BRIDGE_TRANSFER_SUCCESS; publish: - publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten); + publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten, transferee); return transfer_result; } diff --git a/main/bridge_basic.c b/main/bridge_basic.c index 0d95d83d27..a69750d152 100644 --- a/main/bridge_basic.c +++ b/main/bridge_basic.c @@ -1525,10 +1525,86 @@ static void stimulate_attended_transfer(struct attended_transfer_properties *pro ao2_unlock(props); } +/*! + * \brief Get a desired transfer party for a bridge the transferer is not in. + * + * \param bridge The bridge to get the party from. May be NULL. + * \param[out] party The lone channel in the bridge. Will be set NULL if bridge is NULL or multiple parties are present. + */ +static void get_transfer_party_non_transferer_bridge(struct ast_bridge *bridge, + struct ast_channel **party) +{ + if (bridge && bridge->num_channels == 1) { + *party = ast_channel_ref(AST_LIST_FIRST(&bridge->channels)->chan); + } else { + *party = NULL; + } +} + +/*! + * \brief Get the transferee and transfer target when the transferer is in a bridge with + * one of the desired parties. + * + * \param transferer_bridge The bridge the transferer is in + * \param other_bridge The bridge the transferer is not in. May be NULL. + * \param transferer The transferer party + * \param[out] transferer_peer The party that is in the bridge with the transferer + * \param[out] other_party The party that is in the other_bridge + */ +static void get_transfer_parties_transferer_bridge(struct ast_bridge *transferer_bridge, + struct ast_bridge *other_bridge, struct ast_channel *transferer, + struct ast_channel **transferer_peer, struct ast_channel **other_party) +{ + *transferer_peer = ast_bridge_peer(transferer_bridge, transferer); + get_transfer_party_non_transferer_bridge(other_bridge, other_party); +} + +/*! + * \brief determine transferee and transfer target for an attended transfer + * + * In builtin attended transfers, there is a single transferer channel that jumps between + * the two bridges involved. At the time the attended transfer occurs, the transferer could + * be in either bridge, so determining the parties is a bit more complex than normal. + * + * The method used here is to determine which of the two bridges the transferer is in, and + * grabbing the peer from that bridge. The other bridge, if it only has a single channel in it, + * has the other desired channel. + * + * \param transferer The channel performing the transfer + * \param transferee_bridge The bridge that the transferee is in + * \param target_bridge The bridge that the transfer target is in + * \param[out] transferee The transferee channel + * \param[out] transfer_target The transfer target channel + */ +static void get_transfer_parties(struct ast_channel *transferer, struct ast_bridge *transferee_bridge, + struct ast_bridge *target_bridge, struct ast_channel **transferee, + struct ast_channel **transfer_target) +{ + struct ast_bridge *transferer_bridge; + + ast_channel_lock(transferer); + transferer_bridge = ast_channel_get_bridge(transferer); + ast_channel_unlock(transferer); + + if (transferer_bridge == transferee_bridge) { + get_transfer_parties_transferer_bridge(transferee_bridge, target_bridge, + transferer, transferee, transfer_target); + } else if (transferer_bridge == target_bridge) { + get_transfer_parties_transferer_bridge(target_bridge, transferee_bridge, + transferer, transfer_target, transferee); + } else { + get_transfer_party_non_transferer_bridge(transferee_bridge, transferee); + get_transfer_party_non_transferer_bridge(target_bridge, transfer_target); + } + + ao2_cleanup(transferer_bridge); +} + /*! * \brief Send a stasis publication for a successful attended transfer */ -static void publish_transfer_success(struct attended_transfer_properties *props) +static void publish_transfer_success(struct attended_transfer_properties *props, + struct ast_channel *transferee_channel, struct ast_channel *target_channel) { struct ast_bridge_channel_pair transferee = { .channel = props->transferer, @@ -1548,7 +1624,8 @@ static void publish_transfer_success(struct attended_transfer_properties *props) } ast_bridge_publish_attended_transfer_bridge_merge(0, AST_BRIDGE_TRANSFER_SUCCESS, - &transferee, &transfer_target, props->transferee_bridge); + &transferee, &transfer_target, props->transferee_bridge, transferee_channel, + target_channel); if (transferee.bridge) { ast_bridge_unlock(transferee.bridge); @@ -1561,7 +1638,8 @@ static void publish_transfer_success(struct attended_transfer_properties *props) /*! * \brief Send a stasis publication for an attended transfer that ends in a threeway call */ -static void publish_transfer_threeway(struct attended_transfer_properties *props) +static void publish_transfer_threeway(struct attended_transfer_properties *props, + struct ast_channel *transferee_channel, struct ast_channel *target_channel) { struct ast_bridge_channel_pair transferee = { .channel = props->transferer, @@ -1585,7 +1663,8 @@ static void publish_transfer_threeway(struct attended_transfer_properties *props } ast_bridge_publish_attended_transfer_threeway(0, AST_BRIDGE_TRANSFER_SUCCESS, - &transferee, &transfer_target, &threeway); + &transferee, &transfer_target, &threeway, transferee_channel, + target_channel); if (transferee.bridge) { ast_bridge_unlock(transferee.bridge); @@ -1608,6 +1687,8 @@ static void publish_transfer_fail(struct attended_transfer_properties *props) .channel = props->transferer, .bridge = props->target_bridge, }; + struct ast_channel *transferee_channel; + struct ast_channel *target_channel; if (transferee.bridge && transfer_target.bridge) { ast_bridge_lock_both(transferee.bridge, transfer_target.bridge); @@ -1617,8 +1698,12 @@ static void publish_transfer_fail(struct attended_transfer_properties *props) ast_bridge_lock(transfer_target.bridge); } + get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge, + &transferee_channel, &target_channel); ast_bridge_publish_attended_transfer_fail(0, AST_BRIDGE_TRANSFER_FAIL, - &transferee, &transfer_target); + &transferee, &transfer_target, transferee_channel, target_channel); + ast_channel_cleanup(transferee_channel); + ast_channel_cleanup(target_channel); if (transferee.bridge) { ast_bridge_unlock(transferee.bridge); @@ -2072,11 +2157,18 @@ static int resume_enter(struct attended_transfer_properties *props) static int threeway_enter(struct attended_transfer_properties *props) { + struct ast_channel *transferee_channel; + struct ast_channel *target_channel; + + get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge, + &transferee_channel, &target_channel); bridge_merge(props->transferee_bridge, props->target_bridge, NULL, 0); play_sound(props->transfer_target, props->xfersound); play_sound(props->transferer, props->xfersound); - publish_transfer_threeway(props); + publish_transfer_threeway(props, transferee_channel, target_channel); + ast_channel_cleanup(transferee_channel); + ast_channel_cleanup(target_channel); return 0; } @@ -2178,17 +2270,33 @@ static enum attended_transfer_state double_checking_exit(struct attended_transfe static int complete_enter(struct attended_transfer_properties *props) { + struct ast_channel *transferee_channel; + struct ast_channel *target_channel; + + get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge, + &transferee_channel, &target_channel); bridge_merge(props->transferee_bridge, props->target_bridge, &props->transferer, 1); play_sound(props->transfer_target, props->xfersound); - publish_transfer_success(props); + publish_transfer_success(props, transferee_channel, target_channel); + + ast_channel_cleanup(transferee_channel); + ast_channel_cleanup(target_channel); return 0; } static int blond_enter(struct attended_transfer_properties *props) { + struct ast_channel *transferee_channel; + struct ast_channel *target_channel; + + get_transfer_parties(props->transferer, props->transferee_bridge, props->target_bridge, + &transferee_channel, &target_channel); bridge_merge(props->transferee_bridge, props->target_bridge, &props->transferer, 1); ringing(props->transfer_target); - publish_transfer_success(props); + publish_transfer_success(props, transferee_channel, target_channel); + + ast_channel_cleanup(transferee_channel); + ast_channel_cleanup(target_channel); return 0; } diff --git a/main/cel.c b/main/cel.c index 4be13511c1..bb0f750519 100644 --- a/main/cel.c +++ b/main/cel.c @@ -1358,44 +1358,20 @@ static void cel_blind_transfer_cb( void *data, struct stasis_subscription *sub, struct stasis_message *message) { - struct ast_bridge_blob *obj = stasis_message_data(message); - struct ast_channel_snapshot *chan_snapshot = obj->channel; - struct ast_bridge_snapshot *bridge_snapshot = obj->bridge; - struct ast_json *blob = obj->blob; - struct ast_json *json_result = ast_json_object_get(blob, "result"); - struct ast_json *json_exten; - struct ast_json *json_context; + struct ast_blind_transfer_message *transfer_msg = stasis_message_data(message); + struct ast_channel_snapshot *chan_snapshot = transfer_msg->to_transferee.channel_snapshot; + struct ast_bridge_snapshot *bridge_snapshot = transfer_msg->to_transferee.bridge_snapshot; struct ast_json *extra; - const char *exten; - const char *context; - enum ast_transfer_result result; - if (!json_result) { - return; - } - - result = ast_json_integer_get(json_result); - if (result != AST_BRIDGE_TRANSFER_SUCCESS) { - return; - } - - json_exten = ast_json_object_get(blob, "exten"); - json_context = ast_json_object_get(blob, "context"); - - if (!json_exten || !json_context) { - return; - } - - exten = ast_json_string_get(json_exten); - context = ast_json_string_get(json_context); - if (!exten || !context) { + if (transfer_msg->result != AST_BRIDGE_TRANSFER_SUCCESS) { return; } extra = ast_json_pack("{s: s, s: s, s: s}", - "extension", exten, - "context", context, - "bridge_id", bridge_snapshot->uniqueid); + "extension", transfer_msg->exten, + "context", transfer_msg->context, + "bridge_id", bridge_snapshot->uniqueid, + "transferee_channel_name", transfer_msg->transferee ? transfer_msg->transferee->name: "N/A"); if (extra) { cel_report_event(chan_snapshot, AST_CEL_BLINDTRANSFER, NULL, extra, NULL); ast_json_unref(extra); @@ -1431,19 +1407,24 @@ static void cel_attended_transfer_cb( case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE: case AST_ATTENDED_TRANSFER_DEST_LINK: case AST_ATTENDED_TRANSFER_DEST_THREEWAY: - extra = ast_json_pack("{s: s, s: s, s: s}", + extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}", "bridge1_id", bridge1->uniqueid, "channel2_name", channel2->name, - "bridge2_id", bridge2->uniqueid); + "bridge2_id", bridge2->uniqueid, + "transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A", + "transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A"); if (!extra) { return; } break; case AST_ATTENDED_TRANSFER_DEST_APP: - extra = ast_json_pack("{s: s, s: s, s: s}", + case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP: + extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}", "bridge1_id", bridge1->uniqueid, "channel2_name", channel2->name, - "app", xfer->dest.app); + "app", xfer->dest.app, + "transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A", + "transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A"); if (!extra) { return; } diff --git a/main/channel.c b/main/channel.c index 23799d9be8..50b9e87261 100644 --- a/main/channel.c +++ b/main/channel.c @@ -6596,17 +6596,36 @@ static void channel_do_masquerade(struct ast_channel *original, struct ast_chann *ast_channel_hangup_handlers(original) = *ast_channel_hangup_handlers(clonechan); *ast_channel_hangup_handlers(clonechan) = exchange.handlers; - /* Move data stores over */ + /* Call fixup handlers for the clone chan */ if (AST_LIST_FIRST(ast_channel_datastores(clonechan))) { struct ast_datastore *ds; /* We use a safe traversal here because some fixup routines actually * remove the datastore from the list and free them. */ AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_datastores(clonechan), ds, entry) { - if (ds->info->chan_fixup) + if (ds->info->chan_fixup) { ds->info->chan_fixup(ds->data, clonechan, original); + } + } + AST_LIST_TRAVERSE_SAFE_END; + } + + /* Call breakdown handlers for the original chan */ + if (AST_LIST_FIRST(ast_channel_datastores(original))) { + struct ast_datastore *ds; + /* We use a safe traversal here because some breakdown routines may + * remove the datastore from the list and free them. + */ + AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_datastores(original), ds, entry) { + if (ds->info->chan_breakdown) { + ds->info->chan_breakdown(ds->data, clonechan, original); + } } AST_LIST_TRAVERSE_SAFE_END; + } + + /* Move data stores over */ + if (AST_LIST_FIRST(ast_channel_datastores(clonechan))) { AST_LIST_APPEND_LIST(ast_channel_datastores(original), ast_channel_datastores(clonechan), entry); } diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c index 56f7605f75..c94d2ea10a 100644 --- a/main/stasis_bridges.c +++ b/main/stasis_bridges.c @@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") contacted. It means that a party was succesfully placed into the dialplan at the expected location. + Indicates if the transfer was performed outside of Asterisk. For instance, @@ -113,6 +114,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") The name of the surviving transferer channel when a transfer results in a threeway call This header is only present when DestType is Threeway + The headers in this event attempt to describe all the major details of the attended transfer. The two transferer channels @@ -633,30 +635,41 @@ static const char *result_strs[] = { static struct ast_json *blind_transfer_to_json(struct stasis_message *msg, const struct stasis_message_sanitizer *sanitize) { - struct ast_bridge_blob *blob = stasis_message_data(msg); - struct ast_json *json_channel, *out; + struct ast_blind_transfer_message *transfer_msg = stasis_message_data(msg); + struct ast_json *json_transferer, *json_transferee, *out; const struct timeval *tv = stasis_message_timestamp(msg); - json_channel = ast_channel_snapshot_to_json(blob->channel, sanitize); - if (!json_channel) { + json_transferer = ast_channel_snapshot_to_json(transfer_msg->to_transferee.channel_snapshot, sanitize); + if (!json_transferer) { return NULL; } - out = ast_json_pack("{s: s, s: o, s: o, s: O, s: O, s: s, s: o}", + if (transfer_msg->transferee) { + json_transferee = ast_channel_snapshot_to_json(transfer_msg->transferee, sanitize); + if (!json_transferee) { + return NULL; + } + } else { + json_transferee = ast_json_null(); + } + + out = ast_json_pack("{s: s, s: o, s: o, s: o, s: s, s: s, s: s, s: o}", "type", "BridgeBlindTransfer", "timestamp", ast_json_timeval(*tv, NULL), - "channel", json_channel, - "exten", ast_json_object_get(blob->blob, "exten"), - "context", ast_json_object_get(blob->blob, "context"), - "result", result_strs[ast_json_integer_get(ast_json_object_get(blob->blob, "result"))], - "is_external", ast_json_boolean(ast_json_integer_get(ast_json_object_get(blob->blob, "is_external")))); + "transferer", json_transferer, + "transferee", json_transferee, + "exten", transfer_msg->exten, + "context", transfer_msg->context, + "result", result_strs[transfer_msg->result], + "is_external", ast_json_boolean(transfer_msg->is_external)); if (!out) { return NULL; } - if (blob->bridge) { - struct ast_json *json_bridge = ast_bridge_snapshot_to_json(blob->bridge, sanitize); + if (transfer_msg->to_transferee.bridge_snapshot) { + struct ast_json *json_bridge = ast_bridge_snapshot_to_json( + transfer_msg->to_transferee.bridge_snapshot, sanitize); if (!json_bridge || ast_json_object_set(out, "bridge", json_bridge)) { ast_json_unref(out); @@ -669,73 +682,96 @@ static struct ast_json *blind_transfer_to_json(struct stasis_message *msg, 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 *, transferer_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; + RAII_VAR(struct ast_str *, transferee_state, NULL, ast_free_ptr); + struct ast_blind_transfer_message *transfer_msg = stasis_message_data(msg); - if (!blob) { + if (!transfer_msg) { return NULL; } - channel_state = ast_manager_build_channel_state_string_prefix(blob->channel, "Transferer"); - if (!channel_state) { + transferer_state = ast_manager_build_channel_state_string_prefix( + transfer_msg->to_transferee.channel_snapshot, "Transferer"); + if (!transferer_state) { return NULL; } - if (blob->bridge) { - bridge_state = ast_manager_build_bridge_state_string(blob->bridge); + if (transfer_msg->to_transferee.bridge_snapshot) { + bridge_state = ast_manager_build_bridge_state_string(transfer_msg->to_transferee.bridge_snapshot); if (!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")); + if (transfer_msg->transferee) { + transferee_state = ast_manager_build_channel_state_string_prefix( + transfer_msg->transferee, "Transferee"); + if (!transferee_state) { + return NULL; + } + } return ast_manager_event_blob_create(EVENT_FLAG_CALL, "BlindTransfer", "Result: %s\r\n" "%s" "%s" + "%s" "IsExternal: %s\r\n" "Context: %s\r\n" "Extension: %s\r\n", - result_strs[result], - ast_str_buffer(channel_state), + result_strs[transfer_msg->result], + ast_str_buffer(transferer_state), + transferee_state ? ast_str_buffer(transferee_state) : "", bridge_state ? ast_str_buffer(bridge_state) : "", - is_external ? "Yes" : "No", - context, - exten); + transfer_msg->is_external ? "Yes" : "No", + transfer_msg->context, + transfer_msg->exten); +} + +static void blind_transfer_dtor(void *obj) +{ + struct ast_blind_transfer_message *msg = obj; + + bridge_channel_snapshot_pair_cleanup(&msg->to_transferee); + ao2_cleanup(msg->transferee); } 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) + struct ast_bridge_channel_pair *transferer, const char *context, const char *exten, + struct ast_channel *transferee_channel) { - RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref); - RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + struct ast_blind_transfer_message *msg; + struct stasis_message *stasis; - json_object = ast_json_pack("{s: s, s: s, s: i, s: i}", - "context", context, "exten", exten, "result", result, "is_external", is_external); + msg = ao2_alloc(sizeof(*msg), blind_transfer_dtor); + if (!msg) { + return; + } - if (!json_object) { - ast_log(LOG_NOTICE, "Failed to create json bridge blob\n"); + if (bridge_channel_snapshot_pair_init(transferer, &msg->to_transferee)) { + ao2_cleanup(msg); return; } - msg = ast_bridge_blob_create(ast_blind_transfer_type(), - transferer->bridge, transferer->channel, json_object); + if (transferee_channel) { + msg->transferee = ast_channel_snapshot_get_latest(ast_channel_uniqueid(transferee_channel)); + } + msg->is_external = is_external; + msg->result = result; + ast_copy_string(msg->context, context, sizeof(msg->context)); + ast_copy_string(msg->exten, exten, sizeof(msg->exten)); - if (!msg) { - ast_log(LOG_NOTICE, "Failed to create blob msg\n"); + stasis = stasis_message_create(ast_blind_transfer_type(), msg); + if (!stasis) { + ao2_cleanup(msg); return; } - stasis_publish(ast_bridge_topic_all(), msg); + stasis_publish(ast_bridge_topic_all(), stasis); + + ao2_cleanup(stasis); + ao2_cleanup(msg); } static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, @@ -743,7 +779,7 @@ static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, { struct ast_attended_transfer_message *transfer_msg = stasis_message_data(msg); RAII_VAR(struct ast_json *, out, NULL, ast_json_unref); - struct ast_json *json_transferer1, *json_transferer2, *json_bridge, *json_channel; + struct ast_json *json_transferer1, *json_transferer2, *json_bridge, *json_channel, *json_transferee, *json_target; const struct timeval *tv = stasis_message_timestamp(msg); int res = 0; @@ -758,11 +794,25 @@ static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, return NULL; } - out = ast_json_pack("{s: s, s: o, s: o, s: o, s: s, s: o}", + if (transfer_msg->transferee) { + json_transferee = ast_channel_snapshot_to_json(transfer_msg->transferee, sanitize); + } else { + json_transferee = ast_json_null(); + } + + if (transfer_msg->target) { + json_target = ast_channel_snapshot_to_json(transfer_msg->target, sanitize); + } else { + json_target = ast_json_null(); + } + + out = ast_json_pack("{s: s, s: o, s: o, s: o, s: o, s: o, s: s, s: o}", "type", "BridgeAttendedTransfer", "timestamp", ast_json_timeval(*tv, NULL), "transferer_first_leg", json_transferer1, "transferer_second_leg", json_transferer2, + "transferee", json_transferee, + "transfer_target", json_target, "result", result_strs[transfer_msg->result], "is_external", ast_json_boolean(transfer_msg->is_external)); if (!out) { @@ -794,6 +844,9 @@ static struct ast_json *attended_transfer_to_json(struct stasis_message *msg, res |= ast_json_object_set(out, "destination_type", ast_json_string_create("bridge")); res |= ast_json_object_set(out, "destination_bridge", ast_json_string_create(transfer_msg->dest.bridge)); break; + case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP: + res |= ast_json_object_set(out, "replace_channel", ast_channel_snapshot_to_json(transfer_msg->replace_channel, sanitize)); + /* fallthrough */ case AST_ATTENDED_TRANSFER_DEST_APP: res |= ast_json_object_set(out, "destination_type", ast_json_string_create("application")); res |= ast_json_object_set(out, "destination_application", ast_json_string_create(transfer_msg->dest.app)); @@ -851,6 +904,8 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes 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); + RAII_VAR(struct ast_str *, transferee_state, NULL, ast_free_ptr); + RAII_VAR(struct ast_str *, target_state, NULL, ast_free_ptr); struct ast_attended_transfer_message *transfer_msg = stasis_message_data(msg); if (!variable_data) { @@ -862,6 +917,19 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes if (!transferer1_state || !transferer2_state) { return NULL; } + if (transfer_msg->transferee) { + transferee_state = ast_manager_build_channel_state_string_prefix(transfer_msg->transferee, "Transferee"); + if (!transferee_state) { + return NULL; + } + } + + if (transfer_msg->target) { + target_state = ast_manager_build_channel_state_string_prefix(transfer_msg->target, "TransferTarget"); + if (!target_state) { + return NULL; + } + } if (transfer_msg->to_transferee.bridge_snapshot) { bridge1_state = ast_manager_build_bridge_state_string_prefix( @@ -885,6 +953,7 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes ast_str_append(&variable_data, 0, "DestBridgeUniqueid: %s\r\n", transfer_msg->dest.bridge); break; case AST_ATTENDED_TRANSFER_DEST_APP: + case AST_ATTENDED_TRANSFER_DEST_LOCAL_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; @@ -914,6 +983,8 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes "%s" "%s" "%s" + "%s" + "%s" "IsExternal: %s\r\n" "%s", result_strs[transfer_msg->result], @@ -921,6 +992,8 @@ static struct ast_manager_event_blob *attended_transfer_to_ami(struct stasis_mes bridge1_state ? ast_str_buffer(bridge1_state) : "", ast_str_buffer(transferer2_state), bridge2_state ? ast_str_buffer(bridge2_state) : "", + transferee_state ? ast_str_buffer(transferee_state) : "", + target_state ? ast_str_buffer(target_state) : "", transfer_msg->is_external ? "Yes" : "No", ast_str_buffer(variable_data)); } @@ -932,6 +1005,9 @@ static void attended_transfer_dtor(void *obj) bridge_channel_snapshot_pair_cleanup(&msg->to_transferee); bridge_channel_snapshot_pair_cleanup(&msg->to_transfer_target); + ao2_cleanup(msg->replace_channel); + ao2_cleanup(msg->transferee); + ao2_cleanup(msg->target); if (msg->dest_type != AST_ATTENDED_TRANSFER_DEST_LINK) { return; @@ -942,8 +1018,10 @@ static void attended_transfer_dtor(void *obj) } } -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) +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, struct ast_channel *replace_channel, + struct ast_channel *transferee_channel, struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, msg, NULL, ao2_cleanup); @@ -957,6 +1035,19 @@ static struct ast_attended_transfer_message *attended_transfer_message_create(in return NULL; } + if (replace_channel) { + msg->replace_channel = ast_channel_snapshot_get_latest(ast_channel_uniqueid(replace_channel)); + if (!msg->replace_channel) { + return NULL; + } + } + + if (transferee_channel) { + msg->transferee = ast_channel_snapshot_get_latest(ast_channel_uniqueid(transferee_channel)); + } + if (target_channel) { + msg->target = ast_channel_snapshot_get_latest(ast_channel_uniqueid(target_channel)); + } msg->is_external = is_external; msg->result = result; @@ -965,7 +1056,8 @@ static struct ast_attended_transfer_message *attended_transfer_message_create(in } 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) + struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, + struct ast_channel *transferee_channel, struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); @@ -974,7 +1066,8 @@ void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfe return; } - transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + transfer_msg = attended_transfer_message_create(is_external, result, + transferee, target, NULL, transferee_channel, target_channel); if (!transfer_msg) { return; } @@ -991,7 +1084,8 @@ void ast_bridge_publish_attended_transfer_fail(int is_external, enum ast_transfe 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) + struct ast_bridge *final_bridge, struct ast_channel *transferee_channel, + struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); @@ -1000,7 +1094,8 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast return; } - transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + transfer_msg = attended_transfer_message_create(is_external, result, + transferee, target, NULL, transferee_channel, target_channel); if (!transfer_msg) { return; } @@ -1019,7 +1114,8 @@ void ast_bridge_publish_attended_transfer_bridge_merge(int is_external, enum ast void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_transfer_result result, struct ast_bridge_channel_pair *transferee, struct ast_bridge_channel_pair *target, - struct ast_bridge_channel_pair *final_pair) + struct ast_bridge_channel_pair *final_pair, struct ast_channel *transferee_channel, + struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); @@ -1028,7 +1124,8 @@ void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_tra return; } - transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + transfer_msg = attended_transfer_message_create(is_external, result, + transferee, target, NULL, transferee_channel, target_channel); if (!transfer_msg) { return; } @@ -1056,7 +1153,8 @@ void ast_bridge_publish_attended_transfer_threeway(int is_external, enum ast_tra 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) + struct ast_channel *replace_channel, const char *dest_app, + struct ast_channel *transferee_channel, struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); @@ -1065,12 +1163,13 @@ void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer return; } - transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + transfer_msg = attended_transfer_message_create(is_external, result, + transferee, target, replace_channel, transferee_channel, target_channel); if (!transfer_msg) { return; } - transfer_msg->dest_type = AST_ATTENDED_TRANSFER_DEST_APP; + transfer_msg->dest_type = replace_channel ? AST_ATTENDED_TRANSFER_DEST_LOCAL_APP : 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); @@ -1083,7 +1182,8 @@ void ast_bridge_publish_attended_transfer_app(int is_external, enum ast_transfer 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]) + struct ast_channel *locals[2], struct ast_channel *transferee_channel, + struct ast_channel *target_channel) { RAII_VAR(struct ast_attended_transfer_message *, transfer_msg, NULL, ao2_cleanup); RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); @@ -1093,7 +1193,8 @@ void ast_bridge_publish_attended_transfer_link(int is_external, enum ast_transfe return; } - transfer_msg = attended_transfer_message_create(is_external, result, transferee, target); + transfer_msg = attended_transfer_message_create(is_external, result, + transferee, target, NULL, transferee_channel, target_channel); if (!transfer_msg) { return; } diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c index 10fd3bd832..be1a244dfb 100644 --- a/res/ari/ari_model_validators.c +++ b/res/ari/ari_model_validators.c @@ -1845,6 +1845,15 @@ int ast_ari_validate_bridge_attended_transfer(struct ast_json *json) res = 0; } } else + if (strcmp("replace_channel", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field replace_channel failed validation\n"); + res = 0; + } + } else if (strcmp("result", ast_json_object_iter_key(iter)) == 0) { int prop_is_valid; has_result = 1; @@ -1855,6 +1864,24 @@ int ast_ari_validate_bridge_attended_transfer(struct ast_json *json) res = 0; } } else + if (strcmp("transfer_target", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transfer_target failed validation\n"); + res = 0; + } + } else + if (strcmp("transferee", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transferee failed validation\n"); + res = 0; + } + } else if (strcmp("transferer_first_leg", ast_json_object_iter_key(iter)) == 0) { int prop_is_valid; has_transferer_first_leg = 1; @@ -2045,6 +2072,15 @@ int ast_ari_validate_bridge_blind_transfer(struct ast_json *json) res = 0; } } else + if (strcmp("transferee", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field transferee failed validation\n"); + res = 0; + } + } else { ast_log(LOG_ERROR, "ARI BridgeBlindTransfer has undocumented field %s\n", @@ -4828,6 +4864,15 @@ int ast_ari_validate_stasis_start(struct ast_json *json) res = 0; } } else + if (strcmp("replace_channel", ast_json_object_iter_key(iter)) == 0) { + int prop_is_valid; + prop_is_valid = ast_ari_validate_channel( + ast_json_object_iter_value(iter)); + if (!prop_is_valid) { + ast_log(LOG_ERROR, "ARI StasisStart field replace_channel failed validation\n"); + res = 0; + } + } else { ast_log(LOG_ERROR, "ARI StasisStart has undocumented field %s\n", diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h index beace67b2c..64dd1b0716 100644 --- a/res/ari/ari_model_validators.h +++ b/res/ari/ari_model_validators.h @@ -1287,7 +1287,10 @@ ari_validator ast_ari_validate_application_fn(void); * - destination_threeway_channel: Channel * - destination_type: string (required) * - is_external: boolean (required) + * - replace_channel: Channel * - result: string (required) + * - transfer_target: Channel + * - transferee: Channel * - transferer_first_leg: Channel (required) * - transferer_first_leg_bridge: Bridge * - transferer_second_leg: Channel (required) @@ -1302,6 +1305,7 @@ ari_validator ast_ari_validate_application_fn(void); * - exten: string (required) * - is_external: boolean (required) * - result: string (required) + * - transferee: Channel * BridgeCreated * - type: string (required) * - application: string (required) @@ -1467,6 +1471,7 @@ ari_validator ast_ari_validate_application_fn(void); * - timestamp: Date * - args: List[string] (required) * - channel: Channel (required) + * - replace_channel: Channel * TextMessageReceived * - type: string (required) * - application: string (required) diff --git a/res/res_stasis.c b/res/res_stasis.c index 7b5d16f1ab..3480c9e237 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -109,6 +109,31 @@ struct ao2_container *app_bridges_moh; struct ao2_container *app_bridges_playback; +static struct ast_json *stasis_end_json_payload(struct ast_channel_snapshot *snapshot, + const struct stasis_message_sanitizer *sanitize) +{ + return ast_json_pack("{s: s, s: o, s: o}", + "type", "StasisEnd", + "timestamp", ast_json_timeval(ast_tvnow(), NULL), + "channel", ast_channel_snapshot_to_json(snapshot, sanitize)); +} + +static struct ast_json *stasis_end_to_json(struct stasis_message *message, + const struct stasis_message_sanitizer *sanitize) +{ + struct ast_channel_blob *payload = stasis_message_data(message); + + if (sanitize && sanitize->channel_snapshot && + sanitize->channel_snapshot(payload->snapshot)) { + return NULL; + } + + return stasis_end_json_payload(payload->snapshot, sanitize); +} + +STASIS_MESSAGE_TYPE_DEFN(ast_stasis_end_message_type, + .to_json = stasis_end_to_json); + const char *stasis_app_name(const struct stasis_app *app) { return app_name(app); @@ -726,26 +751,121 @@ void stasis_app_bridge_destroy(const char *bridge_id) ast_bridge_destroy(bridge, 0); } -static int send_start_msg(struct stasis_app *app, struct ast_channel *chan, - int argc, char *argv[]) +struct replace_channel_store { + struct ast_channel_snapshot *snapshot; + char *app; +}; + +static void replace_channel_destroy(void *obj) { - RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); - RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); + struct replace_channel_store *replace = obj; - struct ast_json *json_args; - int i; - struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer(); + ao2_cleanup(replace->snapshot); + ast_free(replace->app); + ast_free(replace); +} - ast_assert(chan != NULL); +static const struct ast_datastore_info replace_channel_store_info = { + .type = "replace-channel-store", + .destroy = replace_channel_destroy, +}; - /* Set channel info */ - ast_channel_lock(chan); - snapshot = ast_channel_snapshot_create(chan); - ast_channel_unlock(chan); - if (!snapshot) { +static struct replace_channel_store *get_replace_channel_store(struct ast_channel *chan, int no_create) +{ + struct ast_datastore *datastore; + + SCOPED_CHANNELLOCK(lock, chan); + datastore = ast_channel_datastore_find(chan, &replace_channel_store_info, NULL); + if (!datastore) { + if (no_create) { + return NULL; + } + + datastore = ast_datastore_alloc(&replace_channel_store_info, NULL); + if (!datastore) { + return NULL; + } + ast_channel_datastore_add(chan, datastore); + } + + if (!datastore->data) { + datastore->data = ast_calloc(1, sizeof(struct replace_channel_store)); + } + return datastore->data; +} + +int app_set_replace_channel_snapshot(struct ast_channel *chan, struct ast_channel_snapshot *replace_snapshot) +{ + struct replace_channel_store *replace = get_replace_channel_store(chan, 0); + + if (!replace) { return -1; } + ao2_replace(replace->snapshot, replace_snapshot); + return 0; +} + +int app_set_replace_channel_app(struct ast_channel *chan, const char *replace_app) +{ + struct replace_channel_store *replace = get_replace_channel_store(chan, 0); + + if (!replace) { + return -1; + } + + ast_free(replace->app); + replace->app = NULL; + + if (replace_app) { + replace->app = ast_strdup(replace_app); + if (!replace->app) { + return -1; + } + } + + return 0; +} + +static struct ast_channel_snapshot *get_replace_channel_snapshot(struct ast_channel *chan) +{ + struct replace_channel_store *replace = get_replace_channel_store(chan, 1); + struct ast_channel_snapshot *replace_channel_snapshot; + + if (!replace) { + return NULL; + } + + replace_channel_snapshot = replace->snapshot; + replace->snapshot = NULL; + + return replace_channel_snapshot; +} + +char *app_get_replace_channel_app(struct ast_channel *chan) +{ + struct replace_channel_store *replace = get_replace_channel_store(chan, 1); + char *replace_channel_app; + + if (!replace) { + return NULL; + } + + replace_channel_app = replace->app; + replace->app = NULL; + + return replace_channel_app; +} + +static int send_start_msg_snapshots(struct stasis_app *app, + int argc, char *argv[], struct ast_channel_snapshot *snapshot, + struct ast_channel_snapshot *replace_channel_snapshot) +{ + RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); + struct ast_json *json_args; + struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer(); + int i; + if (sanitize && sanitize->channel_snapshot && sanitize->channel_snapshot(snapshot)) { return 0; @@ -760,6 +880,15 @@ static int send_start_msg(struct stasis_app *app, struct ast_channel *chan, return -1; } + if (replace_channel_snapshot) { + int res = ast_json_object_set(msg, "replace_channel", + ast_channel_snapshot_to_json(replace_channel_snapshot, NULL)); + + if (res) { + return -1; + } + } + /* Append arguments to args array */ json_args = ast_json_object_get(msg, "args"); ast_assert(json_args != NULL); @@ -776,39 +905,213 @@ static int send_start_msg(struct stasis_app *app, struct ast_channel *chan, return 0; } -static int send_end_msg(struct stasis_app *app, struct ast_channel *chan) +static int send_start_msg(struct stasis_app *app, struct ast_channel *chan, + int argc, char *argv[]) { - RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); - struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer(); + RAII_VAR(struct ast_channel_snapshot *, replace_channel_snapshot, + NULL, ao2_cleanup); ast_assert(chan != NULL); + replace_channel_snapshot = get_replace_channel_snapshot(chan); + /* Set channel info */ ast_channel_lock(chan); snapshot = ast_channel_snapshot_create(chan); ast_channel_unlock(chan); - if (snapshot == NULL) { + if (!snapshot) { return -1; } + return send_start_msg_snapshots(app, argc, argv, snapshot, replace_channel_snapshot); +} + +static int send_end_msg_snapshot(struct stasis_app *app, struct ast_channel_snapshot *snapshot) +{ + struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer(); + struct ast_json *msg; if (sanitize && sanitize->channel_snapshot && sanitize->channel_snapshot(snapshot)) { return 0; } - msg = ast_json_pack("{s: s, s: o, s: o}", - "type", "StasisEnd", - "timestamp", ast_json_timeval(ast_tvnow(), NULL), - "channel", ast_channel_snapshot_to_json(snapshot, NULL)); + msg = stasis_end_json_payload(snapshot, sanitize); if (!msg) { return -1; } app_send(app, msg); + ast_json_unref(msg); + return 0; +} + +static void remove_masquerade_store(struct ast_channel *chan); + +static int masq_match_cb(void *obj, void *data, int flags) +{ + struct stasis_app_control *control = obj; + struct ast_channel *chan = data; + + if (!strcmp(ast_channel_uniqueid(chan), + stasis_app_control_get_channel_id(control))) { + return CMP_MATCH; + } + + return 0; +} + +static void channel_stolen_cb(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + struct ast_channel_snapshot *snapshot; + struct stasis_app_control *control; + + /* grab a snapshot */ + snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(new_chan)); + if (!snapshot) { + ast_log(LOG_ERROR, "Could not get snapshot for masqueraded channel\n"); + return; + } + + /* find control */ + control = ao2_callback(app_controls, 0, masq_match_cb, old_chan); + if (!control) { + ast_log(LOG_ERROR, "Could not find control for masqueraded channel\n"); + ao2_cleanup(snapshot); + return; + } + + /* send the StasisEnd message to the app */ + send_end_msg_snapshot(control_app(control), snapshot); + + /* remove the datastore */ + remove_masquerade_store(old_chan); + + ao2_cleanup(control); + ao2_cleanup(snapshot); +} + +static void channel_replaced_cb(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + RAII_VAR(struct ast_channel_snapshot *, new_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, old_snapshot, NULL, ao2_cleanup); + struct stasis_app_control *control; + + /* At this point, new_chan is the channel pointer that is in Stasis() and + * has the unknown channel's name in it while old_chan is the channel pointer + * that is not in Stasis(), but has the guts of the channel that Stasis() knows + * about */ + + /* grab a snapshot for the channel that is jumping into Stasis() */ + new_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(new_chan)); + if (!new_snapshot) { + ast_log(LOG_ERROR, "Could not get snapshot for masquerading channel\n"); + return; + } + + /* grab a snapshot for the channel that has been kicked out of Stasis() */ + old_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(old_chan)); + if (!old_snapshot) { + ast_log(LOG_ERROR, "Could not get snapshot for masqueraded channel\n"); + return; + } + + /* find, unlink, and relink control since the channel has a new name and + * its hash has likely changed */ + control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, new_chan); + if (!control) { + ast_log(LOG_ERROR, "Could not find control for masquerading channel\n"); + return; + } + ao2_link(app_controls, control); + + + /* send the StasisStart with replace_channel to the app */ + send_start_msg_snapshots(control_app(control), 0, NULL, new_snapshot, + old_snapshot); + /* send the StasisEnd message to the app */ + send_end_msg_snapshot(control_app(control), old_snapshot); + + /* fixup channel topic forwards */ + if (app_replace_channel_forwards(control_app(control), old_snapshot->uniqueid, new_chan)) { + ast_log(LOG_ERROR, "Failed to fixup channel topic forwards for %s(%s) owned by %s\n", + old_snapshot->name, old_snapshot->uniqueid, app_name(control_app(control))); + } + ao2_cleanup(control); +} + +static const struct ast_datastore_info masquerade_store_info = { + .type = "stasis-masqerade", + .chan_fixup = channel_stolen_cb, + .chan_breakdown = channel_replaced_cb, +}; + +static int has_masquerade_store(struct ast_channel *chan) +{ + SCOPED_CHANNELLOCK(lock, chan); + return !!ast_channel_datastore_find(chan, &masquerade_store_info, NULL); +} + +static int add_masquerade_store(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + SCOPED_CHANNELLOCK(lock, chan); + if (ast_channel_datastore_find(chan, &masquerade_store_info, NULL)) { + return 0; + } + + datastore = ast_datastore_alloc(&masquerade_store_info, NULL); + if (!datastore) { + return -1; + } + + ast_channel_datastore_add(chan, datastore); + return 0; } +static void remove_masquerade_store(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + SCOPED_CHANNELLOCK(lock, chan); + datastore = ast_channel_datastore_find(chan, &masquerade_store_info, NULL); + if (!datastore) { + return; + } + + ast_channel_datastore_remove(chan, datastore); + ast_datastore_free(datastore); +} + +static int send_end_msg(struct stasis_app *app, struct ast_channel *chan) +{ + struct ast_channel_snapshot *snapshot; + int res = 0; + + ast_assert(chan != NULL); + + /* A masquerade has occurred and this message will be wrong so it + * has already been sent elsewhere. */ + if (!has_masquerade_store(chan)) { + return 0; + } + + /* Set channel info */ + snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)); + if (!snapshot) { + return -1; + } + + if (send_end_msg_snapshot(app, snapshot)) { + res = -1; + } + + ao2_cleanup(snapshot); + return res; +} + void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control) { while (!control_is_done(control)) { @@ -837,6 +1140,46 @@ int stasis_app_control_is_done(struct stasis_app_control *control) return control_is_done(control); } +struct ast_datastore_info set_end_published_info = { + .type = "stasis_end_published", +}; + +void stasis_app_channel_set_stasis_end_published(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + datastore = ast_datastore_alloc(&set_end_published_info, NULL); + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); +} + +int stasis_app_channel_is_stasis_end_published(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &set_end_published_info, NULL); + ast_channel_unlock(chan); + + return datastore ? 1 : 0; +} + +static void remove_stasis_end_published(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &set_end_published_info, NULL); + ast_channel_unlock(chan); + + if (datastore) { + ast_channel_datastore_remove(chan, datastore); + ast_datastore_free(datastore); + } +} + /*! /brief Stasis dialplan application callback */ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, char *argv[]) @@ -850,6 +1193,11 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, ast_assert(chan != NULL); + /* Just in case there's a lingering indication that the channel has had a stasis + * end published on it, remove that now. + */ + remove_stasis_end_published(chan); + app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); if (!app) { ast_log(LOG_ERROR, @@ -869,10 +1217,16 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, } ao2_link(app_controls, control); + if (add_masquerade_store(chan)) { + ast_log(LOG_ERROR, "Failed to attach masquerade detector\n"); + return -1; + } + res = send_start_msg(app, chan, argc, argv); if (res != 0) { ast_log(LOG_ERROR, "Error sending start message to '%s'\n", app_name); + remove_masquerade_store(chan); return -1; } @@ -880,9 +1234,13 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, if (res != 0) { ast_log(LOG_ERROR, "Error subscribing app '%s' to channel '%s'\n", app_name, ast_channel_name(chan)); + remove_masquerade_store(chan); return -1; } + /* Pull queued prestart commands and execute */ + control_prestart_dispatch_all(control, chan); + while (!control_is_done(control)) { RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor); int r; @@ -948,14 +1306,20 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, } app_unsubscribe_bridge(app, stasis_app_get_bridge(control)); - app_unsubscribe_channel(app, chan); ao2_cleanup(bridge); - res = send_end_msg(app, chan); - if (res != 0) { - ast_log(LOG_ERROR, - "Error sending end message to %s\n", app_name); - return res; + /* Only publish a stasis_end event if it hasn't already been published */ + if (!stasis_app_channel_is_stasis_end_published(chan)) { + app_unsubscribe_channel(app, chan); + res = send_end_msg(app, chan); + remove_masquerade_store(chan); + if (res != 0) { + ast_log(LOG_ERROR, + "Error sending end message to %s\n", app_name); + return res; + } + } else { + remove_stasis_end_published(chan); } /* There's an off chance that app is ready for cleanup. Go ahead @@ -1434,8 +1798,15 @@ void stasis_app_unref(void) ast_module_unref(ast_module_info->self); } +/*! + * \brief Subscription to StasisEnd events + */ +struct stasis_subscription *stasis_end_sub; + static int unload_module(void) { + stasis_end_sub = stasis_unsubscribe(stasis_end_sub); + stasis_app_unregister_event_sources(); messaging_cleanup(); @@ -1455,6 +1826,8 @@ static int unload_module(void) ao2_cleanup(app_bridges_playback); app_bridges_playback = NULL; + STASIS_MESSAGE_TYPE_CLEANUP(ast_stasis_end_message_type); + return 0; } @@ -1486,8 +1859,53 @@ struct stasis_message_sanitizer *stasis_app_get_sanitizer(void) return &app_sanitizer; } +static void remove_masquerade_store_by_name(const char *channel_name) +{ + struct ast_channel *chan; + + chan = ast_channel_get_by_name(channel_name); + if (!chan) { + return; + } + + remove_masquerade_store(chan); + ast_channel_unref(chan); +} + +static void check_for_stasis_end(void *data, struct stasis_subscription *sub, + struct stasis_message *message) +{ + struct ast_channel_blob *payload; + struct ast_channel_snapshot *snapshot; + const char *app_name; + char *channel_uri; + size_t alloc_size; + const char *channels[1]; + + if (stasis_message_type(message) != ast_stasis_end_message_type()) { + return; + } + + payload = stasis_message_data(message); + snapshot = payload->snapshot; + app_name = ast_json_string_get(ast_json_object_get(payload->blob, "app")); + + /* +8 is for the length of "channel:" */ + alloc_size = AST_MAX_UNIQUEID + 8; + channel_uri = ast_alloca(alloc_size); + snprintf(channel_uri, alloc_size, "channel:%s", snapshot->uniqueid); + + channels[0] = channel_uri; + stasis_app_unsubscribe(app_name, channels, ARRAY_LEN(channels), NULL); + + remove_masquerade_store_by_name(snapshot->name); +} + static int load_module(void) { + if (STASIS_MESSAGE_TYPE_INIT(ast_stasis_end_message_type) != 0) { + return AST_MODULE_LOAD_DECLINE; + } apps_registry = ao2_container_alloc(APPS_NUM_BUCKETS, app_hash, app_compare); app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, control_hash, control_compare); app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash, bridges_compare); @@ -1511,6 +1929,12 @@ static int load_module(void) stasis_app_register_event_sources(); + stasis_end_sub = stasis_subscribe(ast_channel_topic_all(), check_for_stasis_end, NULL); + if (!stasis_end_sub) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + return AST_MODULE_LOAD_SUCCESS; } diff --git a/res/stasis/app.c b/res/stasis/app.c index 7e7911b9ca..7459696152 100644 --- a/res/stasis/app.c +++ b/res/stasis/app.c @@ -28,6 +28,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "app.h" +#include "control.h" #include "messaging.h" #include "asterisk/callerid.h" @@ -699,14 +700,32 @@ static void bridge_blind_transfer_handler(void *data, struct stasis_subscription struct stasis_message *message) { struct stasis_app *app = data; - struct ast_bridge_blob *blob = stasis_message_data(message); + struct ast_blind_transfer_message *transfer_msg = stasis_message_data(message); + struct ast_bridge_snapshot *bridge = transfer_msg->to_transferee.bridge_snapshot; - if (bridge_app_subscribed(app, blob->channel->uniqueid) || - (blob->bridge && bridge_app_subscribed_involved(app, blob->bridge))) { + if (bridge_app_subscribed(app, transfer_msg->to_transferee.channel_snapshot->uniqueid) || + (bridge && bridge_app_subscribed_involved(app, bridge))) { stasis_publish(app->topic, message); } } +static void set_replacement_channel(struct ast_channel_snapshot *to_be_replaced, + struct ast_channel_snapshot *replacing) +{ + struct stasis_app_control *control = stasis_app_control_find_by_channel_id( + to_be_replaced->uniqueid); + struct ast_channel *chan = ast_channel_get_by_name(replacing->uniqueid); + + if (control && chan) { + ast_channel_lock(chan); + app_set_replace_channel_app(chan, app_name(control_app(control))); + app_set_replace_channel_snapshot(chan, to_be_replaced); + ast_channel_unlock(chan); + } + ast_channel_cleanup(chan); + ao2_cleanup(control); +} + static void bridge_attended_transfer_handler(void *data, struct stasis_subscription *sub, struct stasis_message *message) { @@ -751,6 +770,18 @@ static void bridge_attended_transfer_handler(void *data, struct stasis_subscript if (subscribed) { stasis_publish(app->topic, message); } + + if (transfer_msg->replace_channel) { + set_replacement_channel(transfer_msg->to_transferee.channel_snapshot, + transfer_msg->replace_channel); + } + + if (transfer_msg->dest_type == AST_ATTENDED_TRANSFER_DEST_LINK) { + set_replacement_channel(transfer_msg->to_transferee.channel_snapshot, + transfer_msg->dest.links[0]); + set_replacement_channel(transfer_msg->to_transfer_target.channel_snapshot, + transfer_msg->dest.links[1]); + } } static void bridge_default_handler(void *data, struct stasis_subscription *sub, @@ -1091,6 +1122,30 @@ int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id) return forwards != NULL; } +int app_replace_channel_forwards(struct stasis_app *app, const char *old_id, struct ast_channel *new_chan) +{ + RAII_VAR(struct app_forwards *, old_forwards, NULL, ao2_cleanup); + struct app_forwards *new_forwards; + + old_forwards = ao2_find(app->forwards, old_id, OBJ_SEARCH_KEY | OBJ_UNLINK); + if (!old_forwards) { + return -1; + } + + new_forwards = forwards_create_channel(app, new_chan); + if (!new_forwards) { + return -1; + } + + new_forwards->interested = old_forwards->interested; + ao2_link_flags(app->forwards, new_forwards, 0); + ao2_cleanup(new_forwards); + + /* Clean up old forwards */ + forwards_unsubscribe(old_forwards); + return 0; +} + static void *channel_find(const struct stasis_app *app, const char *id) { return ast_channel_get_by_name(id); diff --git a/res/stasis/app.h b/res/stasis/app.h index 419ec54a85..1ab6097a78 100644 --- a/res/stasis/app.h +++ b/res/stasis/app.h @@ -226,4 +226,48 @@ int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id) */ int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id); +/*! + * \brief Set the snapshot of the channel that this channel will replace + * + * \param channel The channel on which this will be set + * \param replace_snapshot The snapshot of the channel that is being replaced + * + * \retval zero success + * \retval non-zero failure + */ +int app_set_replace_channel_snapshot(struct ast_channel *chan, struct ast_channel_snapshot *replace_snapshot); + +/*! + * \brief Set the app that the replacement channel will be controlled by + * + * \param channel The channel on which this will be set + * \param replace_app The app that will be controlling this channel + * + * \retval zero success + * \retval non-zero failure + */ +int app_set_replace_channel_app(struct ast_channel *chan, const char *replace_app); + +/*! + * \brief Get the app that the replacement channel will be controlled by + * + * \param channel The channel on which this will be set + * + * \retval NULL on error + * \return the name of the controlling app (must be ast_free()d) + */ +char *app_get_replace_channel_app(struct ast_channel *chan); + +/*! + * \brief Replace channel topic forwards for the old channel with forwards for the new channel + * + * \param app The app that owns the channel + * \param old_id The unique ID of the channel to be replaced + * \param new_chan The channel that is replacing the old one + * + * \retval zero on success + * \return non-zero on failure + */ +int app_replace_channel_forwards(struct stasis_app *app, const char *old_id, struct ast_channel *new_chan); + #endif /* _ASTERISK_RES_STASIS_APP_H */ diff --git a/res/stasis/command.c b/res/stasis/command.c index a9e53af125..867de180aa 100644 --- a/res/stasis/command.c +++ b/res/stasis/command.c @@ -93,3 +93,61 @@ void command_invoke(struct stasis_app_command *command, command_complete(command, retval); } +static void command_queue_prestart_destroy(void *obj) +{ + /* Clean up the container */ + ao2_cleanup(obj); +} + +static const struct ast_datastore_info command_queue_prestart = { + .type = "stasis-command-prestart-queue", + .destroy = command_queue_prestart_destroy, +}; + +int command_prestart_queue_command(struct ast_channel *chan, + stasis_app_command_cb command_fn, void *data) +{ + struct ast_datastore *datastore; + struct ao2_container *command_queue; + RAII_VAR(struct stasis_app_command *, command, + command_create(command_fn, data), ao2_cleanup); + + if (!command) { + return -1; + } + + datastore = ast_channel_datastore_find(chan, &command_queue_prestart, NULL); + if (datastore) { + command_queue = datastore->data; + ao2_link(command_queue, command); + return 0; + } + + command_queue = ao2_container_alloc_list( + AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL); + if (!command_queue) { + return -1; + } + + datastore = ast_datastore_alloc(&command_queue_prestart, NULL); + if (!datastore) { + ao2_cleanup(command_queue); + return -1; + } + ast_channel_datastore_add(chan, datastore); + + datastore->data = command_queue; + ao2_link(command_queue, command); + + return 0; +} + +struct ao2_container *command_prestart_get_container(struct ast_channel *chan) +{ + struct ast_datastore *datastore = ast_channel_datastore_find(chan, &command_queue_prestart, NULL); + if (!datastore) { + return NULL; + } + + return ao2_bump(datastore->data); +} diff --git a/res/stasis/command.h b/res/stasis/command.h index a99d40d0a6..7f12ab36fa 100644 --- a/res/stasis/command.h +++ b/res/stasis/command.h @@ -41,4 +41,31 @@ void command_invoke(struct stasis_app_command *command, int command_join(struct stasis_app_command *command); +/*! + * \brief Queue a Stasis() prestart command for a channel + * + * \pre chan must be locked + * + * \param chan The channel on which to queue the prestart command + * \param command_fn The callback to call for the command + * \param data The data to pass to the command callback + * + * \retval zero on success + * \retval non-zero on failure + */ +int command_prestart_queue_command(struct ast_channel *chan, + stasis_app_command_cb command_fn, void *data); + +/*! + * \brief Get the Stasis() prestart commands for a channel + * + * \pre chan must be locked + * + * \param chan The channel from which to get prestart commands + * + * \return The command prestart container for chan (must be ao2_cleanup()'d) + */ +struct ao2_container *command_prestart_get_container(struct ast_channel *chan); + + #endif /* _ASTERISK_RES_STASIS_CONTROL_H */ diff --git a/res/stasis/control.c b/res/stasis/control.c index 8802e8128e..0a9669d3bc 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -276,10 +276,6 @@ struct stasis_app_control_dial_data { int timeout; }; -static int app_control_add_channel_to_bridge( - struct stasis_app_control *control, - struct ast_channel *chan, void *data); - static int app_control_dial(struct stasis_app_control *control, struct ast_channel *chan, void *data) { @@ -322,7 +318,7 @@ static int app_control_dial(struct stasis_app_control *control, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { ast_hangup(new_chan); } else { - app_control_add_channel_to_bridge(control, chan, bridge); + control_add_channel_to_bridge(control, chan, bridge); } return 0; @@ -855,7 +851,7 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason, ast_bridge_after_cb_reason_string(reason)); } -static int app_control_add_channel_to_bridge( +int control_add_channel_to_bridge( struct stasis_app_control *control, struct ast_channel *chan, void *data) { @@ -935,7 +931,7 @@ int stasis_app_control_add_channel_to_bridge( stasis_app_control_get_channel_id(control)); return app_send_command_on_condition( - control, app_control_add_channel_to_bridge, bridge, + control, control_add_channel_to_bridge, bridge, app_control_can_add_channel_to_bridge); } @@ -1036,3 +1032,36 @@ void control_wait(struct stasis_app_control *control) } ao2_unlock(control->command_queue); } + +int control_prestart_dispatch_all(struct stasis_app_control *control, + struct ast_channel *chan) +{ + struct ao2_container *command_queue; + int count = 0; + struct ao2_iterator iter; + struct stasis_app_command *command; + + ast_channel_lock(chan); + command_queue = command_prestart_get_container(chan); + ast_channel_unlock(chan); + if (!command_queue) { + return 0; + } + + iter = ao2_iterator_init(command_queue, AO2_ITERATOR_UNLINK); + + while ((command = ao2_iterator_next(&iter))) { + command_invoke(command, control, chan); + ao2_cleanup(command); + ++count; + } + + ao2_iterator_destroy(&iter); + ao2_cleanup(command_queue); + return count; +} + +struct stasis_app *control_app(struct stasis_app_control *control) +{ + return control->app; +} diff --git a/res/stasis/control.h b/res/stasis/control.h index 0febd84386..a139f82e43 100644 --- a/res/stasis/control.h +++ b/res/stasis/control.h @@ -77,5 +77,36 @@ int control_is_done(struct stasis_app_control *control); void control_mark_done(struct stasis_app_control *control); +/*! + * \brief Dispatch all queued prestart commands + * + * \param control The control for chan + * \param channel The channel on which commands should be executed + * + * \return The number of commands executed + */ +int control_prestart_dispatch_all(struct stasis_app_control *control, + struct ast_channel *chan); + +/*! + * \brief Returns the pointer (non-reffed) to the app associated with this control + * + * \param control Control to query. + * + * \returns A pointer to the associated stasis_app + */ +struct stasis_app *control_app(struct stasis_app_control *control); + +/*! + * \brief Command callback for adding a channel to a bridge + * + * \param control The control for chan + * \param channel The channel on which commands should be executed + * \param bridge Data to be passed to the callback + */ +int control_add_channel_to_bridge( + struct stasis_app_control *control, + struct ast_channel *chan, void *obj); + #endif /* _ASTERISK_RES_STASIS_CONTROL_H */ diff --git a/res/stasis/stasis_bridge.c b/res/stasis/stasis_bridge.c index c3a266a115..be7836d35c 100644 --- a/res/stasis/stasis_bridge.c +++ b/res/stasis/stasis_bridge.c @@ -32,11 +32,73 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/bridge.h" +#include "asterisk/bridge_after.h" #include "asterisk/bridge_internal.h" +#include "asterisk/bridge_features.h" +#include "asterisk/stasis_app.h" +#include "asterisk/stasis_channels.h" #include "stasis_bridge.h" +#include "control.h" +#include "command.h" +#include "app.h" +#include "asterisk/stasis_app.h" +#include "asterisk/pbx.h" /* ------------------------------------------------------------------- */ +static struct ast_bridge_methods bridge_stasis_v_table; + +static void bridge_stasis_run_cb(struct ast_channel *chan, void *data) +{ + RAII_VAR(char *, app_name, NULL, ast_free); + struct ast_app *app_stasis; + + /* Take ownership of the swap_app memory from the datastore */ + app_name = app_get_replace_channel_app(chan); + if (!app_name) { + ast_log(LOG_ERROR, "Failed to get app name for %s (%p)\n", ast_channel_name(chan), chan); + return; + } + + /* find Stasis() */ + app_stasis = pbx_findapp("Stasis"); + if (!app_stasis) { + ast_log(LOG_WARNING, "Could not find application (Stasis)\n"); + return; + } + + if (ast_check_hangup_locked(chan)) { + /* channel hungup, don't run Stasis() */ + return; + } + + /* run Stasis() */ + pbx_exec(chan, app_stasis, app_name); +} + +static int add_channel_to_bridge( + struct stasis_app_control *control, + struct ast_channel *chan, void *obj) +{ + struct ast_bridge *bridge = obj; + int res; + + res = control_add_channel_to_bridge(control, + chan, bridge); + ao2_cleanup(bridge); + return res; +} + +static void bridge_stasis_queue_join_action(struct ast_bridge *self, + struct ast_bridge_channel *bridge_channel) +{ + ast_channel_lock(bridge_channel->chan); + if (command_prestart_queue_command(bridge_channel->chan, add_channel_to_bridge, ao2_bump(self))) { + ao2_cleanup(self); + } + ast_channel_unlock(bridge_channel->chan); +} + /*! * \internal * \brief Push this channel into the Stasis bridge. @@ -53,6 +115,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") */ static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap) { + struct stasis_app_control *control = stasis_app_control_find_by_channel(bridge_channel->chan); + + if (!control) { + /* channel not in Stasis(), get it there */ + /* Attach after-bridge callback and pass ownership of swap_app to it */ + if (ast_bridge_set_after_callback(bridge_channel->chan, + bridge_stasis_run_cb, NULL, NULL)) { + ast_log(LOG_ERROR, "Failed to set after bridge callback\n"); + return -1; + } + + bridge_stasis_queue_join_action(self, bridge_channel); + + /* Return -1 so the push fails and the after-bridge callback gets called */ + return -1; + } + + ao2_cleanup(control); if (self->allowed_capabilities & STASIS_BRIDGE_MIXING_CAPABILITIES) { ast_bridge_channel_update_linkedids(bridge_channel, swap); if (ast_test_flag(&self->feature_flags, AST_BRIDGE_FLAG_SMART)) { @@ -63,6 +143,33 @@ static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel return ast_bridge_base_v_table.push(self, bridge_channel, swap); } +static int bridge_stasis_moving(struct ast_bridge_channel *bridge_channel, void *hook_pvt, + struct ast_bridge *src, struct ast_bridge *dst) +{ + if (src->v_table == &bridge_stasis_v_table && + dst->v_table != &bridge_stasis_v_table) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + struct ast_channel *chan; + + chan = bridge_channel->chan; + ast_assert(chan != NULL); + + control = stasis_app_control_find_by_channel(chan); + if (!control) { + return -1; + } + + blob = ast_json_pack("{s: s}", "app", app_name(control_app(control))); + + stasis_app_channel_set_stasis_end_published(chan); + + ast_channel_publish_blob(chan, ast_stasis_end_message_type(), blob); + } + + return -1; +} + /*! * \internal * \brief Pull this channel from the Stasis bridge. @@ -82,11 +189,11 @@ static void bridge_stasis_pull(struct ast_bridge *self, struct ast_bridge_channe ast_bridge_channel_update_accountcodes(NULL, bridge_channel); } + ast_bridge_move_hook(bridge_channel->features, bridge_stasis_moving, NULL, NULL, 0); + ast_bridge_base_v_table.pull(self, bridge_channel); } -static struct ast_bridge_methods bridge_stasis_v_table; - struct ast_bridge *bridge_stasis_new(uint32_t capabilities, unsigned int flags, const char *name, const char *id) { void *bridge; diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json index 5e115fbd24..a0f70fa684 100644 --- a/rest-api/api-docs/events.json +++ b/rest-api/api-docs/events.json @@ -285,6 +285,11 @@ "required": true, "type": "Channel" }, + "transferee": { + "description": "The channel that is being transferred", + "required": false, + "type": "Channel" + }, "exten": { "description": "The extension transferred to", "required": true, @@ -325,6 +330,21 @@ "required": true, "type": "Channel" }, + "replace_channel": { + "description": "The channel that is replacing transferer_first_leg in the swap", + "required": false, + "type": "Channel" + }, + "transferee": { + "description": "The channel that is being transferred", + "required": false, + "type": "Channel" + }, + "transfer_target": { + "description": "The channel that is being transferred to", + "required": false, + "type": "Channel" + }, "result": { "description": "The result of the transfer attempt", "required": true, @@ -670,6 +690,10 @@ "channel": { "required": true, "type": "Channel" + }, + "replace_channel": { + "required": false, + "type": "Channel" } } }, diff --git a/tests/test_cel.c b/tests/test_cel.c index 010f19987f..80594af529 100644 --- a/tests/test_cel.c +++ b/tests/test_cel.c @@ -1250,7 +1250,7 @@ AST_TEST_DEFINE(test_cel_blind_transfer) pair.channel = chan_alice; ast_bridge_lock(bridge); ast_bridge_publish_blind_transfer(1, AST_BRIDGE_TRANSFER_SUCCESS, - &pair, "transfer_context", "transfer_extension"); + &pair, "transfer_context", "transfer_extension", NULL); ast_bridge_unlock(bridge); BLINDTRANSFER_EVENT(chan_alice, bridge, "transfer_extension", "transfer_context");