Stasis: Convey transfer information to applications
authorKinsey Moore <kmoore@digium.com>
Thu, 7 Aug 2014 15:30:19 +0000 (15:30 +0000)
committerKinsey Moore <kmoore@digium.com>
Thu, 7 Aug 2014 15:30:19 +0000 (15:30 +0000)
This fixes a class of issues where Stasis applications were not made
aware that their channels were being manipulated or replaced by
external entitiessuch as transfers, AMI commands, or dialplan
applications such as Bridge(). Inconsistent information such as
StasisEnd events with unknown channels as a result of masquerades has
also been corrected. To accomplish these fixes, several new fields
were added to blind and attended transfer messages as well as
StasisStart and BridgeAttendedTransfer Stasis events.

ASTERISK-23941 #close
Review: https://reviewboard.asterisk.org/r/3865/
Review: https://reviewboard.asterisk.org/r/3857/
Review: https://reviewboard.asterisk.org/r/3852/
Review: https://reviewboard.asterisk.org/r/3816/
Review: https://reviewboard.asterisk.org/r/3731/
Review: https://reviewboard.asterisk.org/r/3729/
Review: https://reviewboard.asterisk.org/r/3728/
........

Merged revisions 420325 from http://svn.asterisk.org/svn/asterisk/branches/12

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@420338 65c4cc65-6c06-0410-ace0-fbb531ad65f3

22 files changed:
apps/app_queue.c
include/asterisk/bridge_features.h
include/asterisk/datastore.h
include/asterisk/stasis_app.h
include/asterisk/stasis_bridges.h
main/bridge.c
main/bridge_basic.c
main/cel.c
main/channel.c
main/stasis_bridges.c
res/ari/ari_model_validators.c
res/ari/ari_model_validators.h
res/res_stasis.c
res/stasis/app.c
res/stasis/app.h
res/stasis/command.c
res/stasis/command.h
res/stasis/control.c
res/stasis/control.h
res/stasis/stasis_bridge.c
rest-api/api-docs/events.json
tests/test_cel.c

index 012f04a..ef547df 100644 (file)
@@ -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) : "<unknown>";
-       context_blob = ast_json_object_get(blind_blob->blob, "context");
-       context = context_blob ? ast_json_string_get(context_blob) : "<unknown>";
+       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,
index dddc9b0..df01a0d 100644 (file)
@@ -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. */
@@ -621,6 +641,37 @@ int ast_bridge_talk_detector_hook(struct ast_bridge_features *features,
        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
  *
  * \param features Bridge features structure
index 9060a5f..8f59fd3 100644 (file)
@@ -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 */
index a7b2040..e06e68e 100644 (file)
@@ -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 */
index 49d4db2..a4e46cd 100644 (file)
@@ -285,6 +285,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
  *
  * \pre Bridges involved are locked. Channels involved are not locked.
@@ -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.
index 462676c..926004b 100644 (file)
@@ -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;
 }
 
index 0d95d83..a69750d 100644 (file)
@@ -1526,9 +1526,85 @@ static void stimulate_attended_transfer(struct attended_transfer_properties *pro
 }
 
 /*!
+ * \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;
 }
 
index 4be1351..bb0f750 100644 (file)
@@ -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;
                }
index 23799d9..50b9e87 100644 (file)
@@ -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);
        }
 
index 56f7605..c94d2ea 100644 (file)
@@ -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.</para></note>
                                </parameter>
                                <channel_snapshot prefix="Transferer"/>
+                               <channel_snapshot prefix="Transferee"/>
                                <bridge_snapshot/>
                                <parameter name="IsExternal">
                                        <para>Indicates if the transfer was performed outside of Asterisk. For instance,
@@ -113,6 +114,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <para>The name of the surviving transferer channel when a transfer results in a threeway call</para>
                                        <note><para>This header is only present when <replaceable>DestType</replaceable> is <literal>Threeway</literal></para></note>
                                </parameter>
+                               <channel_snapshot prefix="Transferee" />
                        </syntax>
                        <description>
                                <para>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;
        }
index 10fd3bd..be1a244 100644 (file)
@@ -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",
index beace67..64dd1b0 100644 (file)
@@ -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)
index 7b5d16f..3480c9e 100644 (file)
@@ -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;
 }
 
index 7e7911b..7459696 100644 (file)
@@ -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);
index 419ec54..1ab6097 100644 (file)
@@ -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 */
index a9e53af..867de18 100644 (file)
@@ -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);
+}
index a99d40d..7f12ab3 100644 (file)
@@ -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 */
index 8802e81..0a9669d 100644 (file)
@@ -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;
+}
index 0febd84..a139f82 100644 (file)
@@ -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 */
index c3a266a..be7836d 100644 (file)
 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;
index 5e115fb..a0f70fa 100644 (file)
                                        "required": true,
                                        "type": "Channel"
                                },
+                               "transferee": {
+                                       "description": "The channel that is being transferred",
+                                       "required": false,
+                                       "type": "Channel"
+                               },
                                "exten": {
                                        "description": "The extension transferred to",
                                        "required": true,
                                        "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,
                                "channel": {
                                        "required": true,
                                        "type": "Channel"
+                               },
+                               "replace_channel": {
+                                       "required": false,
+                                       "type": "Channel"
                                }
                        }
                },
index 010f199..80594af 100644 (file)
@@ -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");