summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKinsey Moore <kmoore@digium.com>2014-08-07 15:30:19 +0000
committerKinsey Moore <kmoore@digium.com>2014-08-07 15:30:19 +0000
commit0ac7f96057fb9fc0d012515f47bfea8d63eb5199 (patch)
tree8f807ace812c5d8d1cccb01b021ae050e975e550
parenta8829490b6b9891b352e39f3846de1f274ca632c (diff)
Stasis: Convey transfer information to applications
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
-rw-r--r--apps/app_queue.c21
-rw-r--r--include/asterisk/bridge_features.h51
-rw-r--r--include/asterisk/datastore.h16
-rw-r--r--include/asterisk/stasis_app.h22
-rw-r--r--include/asterisk/stasis_bridges.h68
-rw-r--r--main/bridge.c146
-rw-r--r--main/bridge_basic.c124
-rw-r--r--main/cel.c53
-rw-r--r--main/channel.c23
-rw-r--r--main/stasis_bridges.c219
-rw-r--r--res/ari/ari_model_validators.c45
-rw-r--r--res/ari/ari_model_validators.h5
-rw-r--r--res/res_stasis.c478
-rw-r--r--res/stasis/app.c61
-rw-r--r--res/stasis/app.h44
-rw-r--r--res/stasis/command.c58
-rw-r--r--res/stasis/command.h27
-rw-r--r--res/stasis/control.c43
-rw-r--r--res/stasis/control.h31
-rw-r--r--res/stasis/stasis_bridge.c111
-rw-r--r--rest-api/api-docs/events.json24
-rw-r--r--tests/test_cel.c2
22 files changed, 1463 insertions, 209 deletions
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 012f04a71..ef547df60 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -5551,6 +5551,7 @@ static void log_attended_transfer(struct queue_stasis_data *queue_data, struct a
ast_str_set(&transfer_str, 0, "BRIDGE|%s", atxfer_msg->dest.bridge);
break;
case AST_ATTENDED_TRANSFER_DEST_APP:
+ case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP:
ast_str_set(&transfer_str, 0, "APP|%s", atxfer_msg->dest.app);
break;
case AST_ATTENDED_TRANSFER_DEST_LINK:
@@ -5619,10 +5620,7 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su
struct stasis_message *msg)
{
struct queue_stasis_data *queue_data = userdata;
- struct ast_bridge_blob *blind_blob = stasis_message_data(msg);
- struct ast_json *result_blob;
- struct ast_json *exten_blob;
- struct ast_json *context_blob;
+ struct ast_blind_transfer_message *transfer_msg = stasis_message_data(msg);
const char *exten;
const char *context;
RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
@@ -5632,19 +5630,14 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su
return;
}
- result_blob = ast_json_object_get(blind_blob->blob, "result");
- if (!result_blob) {
- return;
- }
-
- if (ast_json_integer_get(result_blob) != AST_BRIDGE_TRANSFER_SUCCESS) {
+ if (transfer_msg->result != AST_BRIDGE_TRANSFER_SUCCESS) {
return;
}
ao2_lock(queue_data);
if (ast_strlen_zero(queue_data->bridge_uniqueid) ||
- strcmp(queue_data->bridge_uniqueid, blind_blob->bridge->uniqueid)) {
+ strcmp(queue_data->bridge_uniqueid, transfer_msg->to_transferee.bridge_snapshot->uniqueid)) {
ao2_unlock(queue_data);
return;
}
@@ -5654,10 +5647,8 @@ static void handle_blind_transfer(void *userdata, struct stasis_subscription *su
ao2_unlock(queue_data);
- exten_blob = ast_json_object_get(blind_blob->blob, "exten");
- exten = exten_blob ? ast_json_string_get(exten_blob) : "<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,
diff --git a/include/asterisk/bridge_features.h b/include/asterisk/bridge_features.h
index dddc9b043..df01a0dca 100644
--- a/include/asterisk/bridge_features.h
+++ b/include/asterisk/bridge_features.h
@@ -157,6 +157,25 @@ typedef void (*ast_bridge_hook_pvt_destructor)(void *hook_pvt);
*/
typedef int (*ast_bridge_talking_indicate_callback)(struct ast_bridge_channel *bridge_channel, void *hook_pvt, int talking);
+/*!
+ * \brief Move indicator callback
+ *
+ * \details
+ * This callback can be registered with the bridge channel in order
+ * to be notified when the bridge channel is being moved from one
+ * bridge to another.
+ *
+ * \param bridge_channel The channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ * \param src The bridge from which the bridge channel is moving
+ * \param dst The bridge into which the bridge channel is moving
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+typedef int (*ast_bridge_move_indicate_callback)(struct ast_bridge_channel *bridge_channel,
+ void *hook_pvt, struct ast_bridge *src, struct ast_bridge *dst);
+
enum ast_bridge_hook_remove_flags {
/*! The hook is removed when the channel is pulled from the bridge. */
AST_BRIDGE_HOOK_REMOVE_ON_PULL = (1 << 0),
@@ -173,6 +192,7 @@ enum ast_bridge_hook_type {
AST_BRIDGE_HOOK_TYPE_JOIN,
AST_BRIDGE_HOOK_TYPE_LEAVE,
AST_BRIDGE_HOOK_TYPE_TALK,
+ AST_BRIDGE_HOOK_TYPE_MOVE,
};
/*! \brief Structure that is the essence of a feature hook. */
@@ -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
diff --git a/include/asterisk/datastore.h b/include/asterisk/datastore.h
index 9060a5f82..8f59fd3cf 100644
--- a/include/asterisk/datastore.h
+++ b/include/asterisk/datastore.h
@@ -34,7 +34,7 @@ struct ast_datastore_info {
void (*destroy)(void *data); /*!< Destroy function */
/*!
- * \brief Fix up channel references
+ * \brief Fix up channel references on the masquerading channel
*
* \arg data The datastore data
* \arg old_chan The old channel owning the datastore
@@ -48,6 +48,20 @@ struct ast_datastore_info {
* \return nothing.
*/
void (*chan_fixup)(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
+
+ /*!
+ * \brief Fix up channel references on the channel being masqueraded into
+ *
+ * \arg data The datastore data
+ * \arg old_chan The old channel owning the datastore
+ * \arg new_chan The new channel owning the datastore
+ *
+ * This is the same as the above callback, except it is called for the channel
+ * being masqueraded into instead of the channel that is masquerading.
+ *
+ * \return nothing.
+ */
+ void (*chan_breakdown)(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
};
/*! \brief Structure for a data store object */
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index a7b204034..e06e68ed2 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -799,6 +799,28 @@ void stasis_app_unref(void);
*/
struct stasis_message_sanitizer *stasis_app_get_sanitizer(void);
+/*!
+ * \brief Stasis message type for a StasisEnd event
+ */
+struct stasis_message_type *ast_stasis_end_message_type(void);
+
+/*!
+ * \brief Indicate that this channel has had a StasisEnd published for it
+ *
+ * \param The channel that is exiting Stasis.
+ */
+void stasis_app_channel_set_stasis_end_published(struct ast_channel *chan);
+
+/*!
+ * \brief Has this channel had a StasisEnd published on it?
+ *
+ * \param chan The channel upon which the query rests.
+ *
+ * \retval 0 No
+ * \retval 1 Yes
+ */
+int stasis_app_channel_is_stasis_end_published(struct ast_channel *chan);
+
/*! @} */
#endif /* _ASTERISK_STASIS_APP_H */
diff --git a/include/asterisk/stasis_bridges.h b/include/asterisk/stasis_bridges.h
index 49d4db2f7..a4e46cdb0 100644
--- a/include/asterisk/stasis_bridges.h
+++ b/include/asterisk/stasis_bridges.h
@@ -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.
diff --git a/main/bridge.c b/main/bridge.c
index 462676ca8..926004bc9 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -1825,6 +1825,32 @@ static void bridge_channel_change_bridge(struct ast_bridge_channel *bridge_chann
ao2_ref(old_bridge, -1);
}
+static void bridge_channel_moving(struct ast_bridge_channel *bridge_channel, struct ast_bridge *src, struct ast_bridge *dst)
+{
+ struct ast_bridge_features *features = bridge_channel->features;
+ struct ast_bridge_hook *hook;
+ struct ao2_iterator iter;
+
+ /* Run any moving hooks. */
+ iter = ao2_iterator_init(features->other_hooks, 0);
+ for (; (hook = ao2_iterator_next(&iter)); ao2_ref(hook, -1)) {
+ int remove_me;
+ ast_bridge_move_indicate_callback move_cb;
+
+ if (hook->type != AST_BRIDGE_HOOK_TYPE_MOVE) {
+ continue;
+ }
+ move_cb = (ast_bridge_move_indicate_callback) hook->callback;
+ remove_me = move_cb(bridge_channel, hook->hook_pvt, src, dst);
+ if (remove_me) {
+ ast_debug(1, "Move detection hook %p is being removed from %p(%s)\n",
+ hook, bridge_channel, ast_channel_name(bridge_channel->chan));
+ ao2_unlink(features->other_hooks, hook);
+ }
+ }
+ ao2_iterator_destroy(&iter);
+}
+
void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridge, struct ast_bridge_channel **kick_me, unsigned int num_kick,
unsigned int optimized)
{
@@ -1873,6 +1899,8 @@ void bridge_do_merge(struct ast_bridge *dst_bridge, struct ast_bridge *src_bridg
continue;
}
+ bridge_channel_moving(bridge_channel, bridge_channel->bridge, dst_bridge);
+
/* Point to new bridge.*/
bridge_channel_change_bridge(bridge_channel, dst_bridge);
@@ -2122,6 +2150,8 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
ao2_ref(orig_bridge, +1);/* Keep a ref in case the push fails. */
bridge_channel_change_bridge(bridge_channel, dst_bridge);
+ bridge_channel_moving(bridge_channel, orig_bridge, dst_bridge);
+
if (bridge_channel_internal_push(bridge_channel)) {
/* Try to put the channel back into the original bridge. */
ast_bridge_features_remove(bridge_channel->features,
@@ -3089,6 +3119,18 @@ int ast_bridge_talk_detector_hook(struct ast_bridge_features *features,
AST_BRIDGE_HOOK_TYPE_TALK);
}
+int ast_bridge_move_hook(struct ast_bridge_features *features,
+ ast_bridge_move_indicate_callback callback,
+ void *hook_pvt,
+ ast_bridge_hook_pvt_destructor destructor,
+ enum ast_bridge_hook_remove_flags remove_flags)
+{
+ ast_bridge_hook_callback hook_cb = (ast_bridge_hook_callback) callback;
+
+ return bridge_other_hook(features, hook_cb, hook_pvt, destructor, remove_flags,
+ AST_BRIDGE_HOOK_TYPE_MOVE);
+}
+
int ast_bridge_interval_hook(struct ast_bridge_features *features,
enum ast_bridge_hook_timer_option flags,
unsigned int interval,
@@ -3828,14 +3870,55 @@ struct stasis_attended_transfer_publish_data {
struct ast_bridge_channel_pair to_transferee;
/* The bridge between the transferer and transfer target, and the transferer channel in this bridge */
struct ast_bridge_channel_pair to_transfer_target;
+ /* The Local;1 that will replace the transferee bridge transferer channel */
+ struct ast_channel *replace_channel;
+ /* The transferee channel. NULL if there is no transferee channel or if multiple parties are transferred */
+ struct ast_channel *transferee_channel;
+ /* The transfer target channel. NULL if there is no transfer target channel or if multiple parties are transferred */
+ struct ast_channel *target_channel;
};
+/*!
+ * \internal
+ * \brief Get the transferee channel
+ *
+ * This is only applicable to cases where a transfer is occurring on a
+ * two-party bridge. The channels container passed in is expected to only
+ * contain two channels, the transferer and the transferee. The transferer
+ * channel is passed in as a parameter to ensure we don't return it as
+ * the transferee channel.
+ *
+ * \param channels A two-channel container containing the transferer and transferee
+ * \param transferer The party that is transfering the call
+ * \return The party that is being transferred
+ */
+static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
+{
+ struct ao2_iterator channel_iter;
+ struct ast_channel *transferee;
+
+ for (channel_iter = ao2_iterator_init(channels, 0);
+ (transferee = ao2_iterator_next(&channel_iter));
+ ao2_cleanup(transferee)) {
+ if (transferee != transferer) {
+ break;
+ }
+ }
+
+ ao2_iterator_destroy(&channel_iter);
+ return transferee;
+}
+
+
static void stasis_publish_data_cleanup(struct stasis_attended_transfer_publish_data *publication)
{
ast_channel_unref(publication->to_transferee.channel);
ast_channel_unref(publication->to_transfer_target.channel);
+ ast_channel_cleanup(publication->transferee_channel);
+ ast_channel_cleanup(publication->target_channel);
ao2_cleanup(publication->to_transferee.bridge);
ao2_cleanup(publication->to_transfer_target.bridge);
+ ao2_cleanup(publication->replace_channel);
}
/*!
@@ -3865,6 +3948,9 @@ static void stasis_publish_data_init(struct ast_channel *to_transferee,
ao2_ref(to_target_bridge, +1);
publication->to_transfer_target.bridge = to_target_bridge;
}
+
+ publication->transferee_channel = ast_bridge_peer(to_transferee_bridge, to_transferee);
+ publication->target_channel = ast_bridge_peer(to_target_bridge, to_transfer_target);
}
/*
@@ -3878,7 +3964,8 @@ static void publish_attended_transfer_bridge_merge(struct stasis_attended_transf
struct ast_bridge *final_bridge)
{
ast_bridge_publish_attended_transfer_bridge_merge(1, AST_BRIDGE_TRANSFER_SUCCESS,
- &publication->to_transferee, &publication->to_transfer_target, final_bridge);
+ &publication->to_transferee, &publication->to_transfer_target, final_bridge,
+ publication->transferee_channel, publication->target_channel);
}
/*
@@ -3892,7 +3979,9 @@ static void publish_attended_transfer_app(struct stasis_attended_transfer_publis
const char *app)
{
ast_bridge_publish_attended_transfer_app(1, AST_BRIDGE_TRANSFER_SUCCESS,
- &publication->to_transferee, &publication->to_transfer_target, app);
+ &publication->to_transferee, &publication->to_transfer_target,
+ publication->replace_channel, app,
+ publication->transferee_channel, publication->target_channel);
}
/*
@@ -3909,7 +3998,8 @@ static void publish_attended_transfer_link(struct stasis_attended_transfer_publi
struct ast_channel *locals[2] = { local_channel1, local_channel2 };
ast_bridge_publish_attended_transfer_link(1, AST_BRIDGE_TRANSFER_SUCCESS,
- &publication->to_transferee, &publication->to_transfer_target, locals);
+ &publication->to_transferee, &publication->to_transfer_target, locals,
+ publication->transferee_channel, publication->target_channel);
}
/*
@@ -3923,7 +4013,8 @@ static void publish_attended_transfer_fail(struct stasis_attended_transfer_publi
enum ast_transfer_result result)
{
ast_bridge_publish_attended_transfer_fail(1, result, &publication->to_transferee,
- &publication->to_transfer_target);
+ &publication->to_transfer_target, publication->transferee_channel,
+ publication->target_channel);
}
/*!
@@ -3987,9 +4078,12 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
return AST_BRIDGE_TRANSFER_FAIL;
}
+ /* Get a ref for use later since this one is being stolen */
+ ao2_ref(local_chan, +1);
if (ast_bridge_impart(bridge1, local_chan, chan1, NULL,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_hangup(local_chan);
+ ao2_cleanup(local_chan);
return AST_BRIDGE_TRANSFER_FAIL;
}
@@ -4005,40 +4099,12 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
publish_attended_transfer_link(publication,
local_chan, local_chan2);
} else {
+ publication->replace_channel = ao2_bump(local_chan);
publish_attended_transfer_app(publication, app);
}
- return AST_BRIDGE_TRANSFER_SUCCESS;
-}
-/*!
- * \internal
- * \brief Get the transferee channel
- *
- * This is only applicable to cases where a transfer is occurring on a
- * two-party bridge. The channels container passed in is expected to only
- * contain two channels, the transferer and the transferee. The transferer
- * channel is passed in as a parameter to ensure we don't return it as
- * the transferee channel.
- *
- * \param channels A two-channel container containing the transferer and transferee
- * \param transferer The party that is transfering the call
- * \return The party that is being transferred
- */
-static struct ast_channel *get_transferee(struct ao2_container *channels, struct ast_channel *transferer)
-{
- struct ao2_iterator channel_iter;
- struct ast_channel *transferee;
-
- for (channel_iter = ao2_iterator_init(channels, 0);
- (transferee = ao2_iterator_next(&channel_iter));
- ao2_cleanup(transferee)) {
- if (transferee != transferer) {
- break;
- }
- }
-
- ao2_iterator_destroy(&channel_iter);
- return transferee;
+ ao2_cleanup(local_chan);
+ return AST_BRIDGE_TRANSFER_SUCCESS;
}
static enum ast_transfer_result try_parking(struct ast_channel *transferer,
@@ -4142,7 +4208,7 @@ static struct ast_bridge *acquire_bridge(struct ast_channel *chan)
static void publish_blind_transfer(int is_external, enum ast_transfer_result result,
struct ast_channel *transferer, struct ast_bridge *bridge,
- const char *context, const char *exten)
+ const char *context, const char *exten, struct ast_channel *transferee_channel)
{
struct ast_bridge_channel_pair pair;
pair.channel = transferer;
@@ -4150,7 +4216,7 @@ static void publish_blind_transfer(int is_external, enum ast_transfer_result res
if (bridge) {
ast_bridge_lock(bridge);
}
- ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten);
+ ast_bridge_publish_blind_transfer(is_external, result, &pair, context, exten, transferee_channel);
if (bridge) {
ast_bridge_unlock(bridge);
}
@@ -4174,6 +4240,9 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
transfer_result = AST_BRIDGE_TRANSFER_INVALID;
goto publish;
}
+
+ transferee = ast_bridge_peer(bridge, transferer);
+
ast_channel_lock(transferer);
bridge_channel = ast_channel_get_bridge_channel(transferer);
ast_channel_unlock(transferer);
@@ -4235,7 +4304,6 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
/* Reaching this portion means that we're dealing with a two-party bridge */
- transferee = get_transferee(channels, transferer);
if (!transferee) {
transfer_result = AST_BRIDGE_TRANSFER_FAIL;
goto publish;
@@ -4251,7 +4319,7 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external,
transfer_result = AST_BRIDGE_TRANSFER_SUCCESS;
publish:
- publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten);
+ publish_blind_transfer(is_external, transfer_result, transferer, bridge, context, exten, transferee);
return transfer_result;
}
diff --git a/main/bridge_basic.c b/main/bridge_basic.c
index 0d95d83d2..a69750d15 100644
--- a/main/bridge_basic.c
+++ b/main/bridge_basic.c
@@ -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;
}
diff --git a/main/cel.c b/main/cel.c
index 4be13511c..bb0f75051 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -1358,44 +1358,20 @@ static void cel_blind_transfer_cb(
void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
- struct ast_bridge_blob *obj = stasis_message_data(message);
- struct ast_channel_snapshot *chan_snapshot = obj->channel;
- struct ast_bridge_snapshot *bridge_snapshot = obj->bridge;
- struct ast_json *blob = obj->blob;
- struct ast_json *json_result = ast_json_object_get(blob, "result");
- struct ast_json *json_exten;
- struct ast_json *json_context;
+ struct ast_blind_transfer_message *transfer_msg = stasis_message_data(message);
+ struct ast_channel_snapshot *chan_snapshot = transfer_msg->to_transferee.channel_snapshot;
+ struct ast_bridge_snapshot *bridge_snapshot = transfer_msg->to_transferee.bridge_snapshot;
struct ast_json *extra;
- const char *exten;
- const char *context;
- enum ast_transfer_result result;
- if (!json_result) {
- return;
- }
-
- result = ast_json_integer_get(json_result);
- if (result != AST_BRIDGE_TRANSFER_SUCCESS) {
- return;
- }
-
- json_exten = ast_json_object_get(blob, "exten");
- json_context = ast_json_object_get(blob, "context");
-
- if (!json_exten || !json_context) {
- return;
- }
-
- exten = ast_json_string_get(json_exten);
- context = ast_json_string_get(json_context);
- if (!exten || !context) {
+ if (transfer_msg->result != AST_BRIDGE_TRANSFER_SUCCESS) {
return;
}
extra = ast_json_pack("{s: s, s: s, s: s}",
- "extension", exten,
- "context", context,
- "bridge_id", bridge_snapshot->uniqueid);
+ "extension", transfer_msg->exten,
+ "context", transfer_msg->context,
+ "bridge_id", bridge_snapshot->uniqueid,
+ "transferee_channel_name", transfer_msg->transferee ? transfer_msg->transferee->name: "N/A");
if (extra) {
cel_report_event(chan_snapshot, AST_CEL_BLINDTRANSFER, NULL, extra, NULL);
ast_json_unref(extra);
@@ -1431,19 +1407,24 @@ static void cel_attended_transfer_cb(
case AST_ATTENDED_TRANSFER_DEST_BRIDGE_MERGE:
case AST_ATTENDED_TRANSFER_DEST_LINK:
case AST_ATTENDED_TRANSFER_DEST_THREEWAY:
- extra = ast_json_pack("{s: s, s: s, s: s}",
+ extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
"bridge1_id", bridge1->uniqueid,
"channel2_name", channel2->name,
- "bridge2_id", bridge2->uniqueid);
+ "bridge2_id", bridge2->uniqueid,
+ "transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A",
+ "transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A");
if (!extra) {
return;
}
break;
case AST_ATTENDED_TRANSFER_DEST_APP:
- extra = ast_json_pack("{s: s, s: s, s: s}",
+ case AST_ATTENDED_TRANSFER_DEST_LOCAL_APP:
+ extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",
"bridge1_id", bridge1->uniqueid,
"channel2_name", channel2->name,
- "app", xfer->dest.app);
+ "app", xfer->dest.app,
+ "transferee_channel_name", xfer->transferee ? xfer->transferee->name : "N/A",
+ "transfer_target_channel_name", xfer->target ? xfer->target->name : "N/A");
if (!extra) {
return;
}
diff --git a/main/channel.c b/main/channel.c
index 23799d9be..50b9e8726 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -6596,17 +6596,36 @@ static void channel_do_masquerade(struct ast_channel *original, struct ast_chann
*ast_channel_hangup_handlers(original) = *ast_channel_hangup_handlers(clonechan);
*ast_channel_hangup_handlers(clonechan) = exchange.handlers;
- /* Move data stores over */
+ /* Call fixup handlers for the clone chan */
if (AST_LIST_FIRST(ast_channel_datastores(clonechan))) {
struct ast_datastore *ds;
/* We use a safe traversal here because some fixup routines actually
* remove the datastore from the list and free them.
*/
AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_datastores(clonechan), ds, entry) {
- if (ds->info->chan_fixup)
+ if (ds->info->chan_fixup) {
ds->info->chan_fixup(ds->data, clonechan, original);
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ }
+
+ /* Call breakdown handlers for the original chan */
+ if (AST_LIST_FIRST(ast_channel_datastores(original))) {
+ struct ast_datastore *ds;
+ /* We use a safe traversal here because some breakdown routines may
+ * remove the datastore from the list and free them.
+ */
+ AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_datastores(original), ds, entry) {
+ if (ds->info->chan_breakdown) {
+ ds->info->chan_breakdown(ds->data, clonechan, original);
+ }
}
AST_LIST_TRAVERSE_SAFE_END;
+ }
+
+ /* Move data stores over */
+ if (AST_LIST_FIRST(ast_channel_datastores(clonechan))) {
AST_LIST_APPEND_LIST(ast_channel_datastores(original), ast_channel_datastores(clonechan), entry);
}
diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c
index 56f7605f7..c94d2ea10 100644
--- a/main/stasis_bridges.c
+++ b/main/stasis_bridges.c
@@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
contacted. It means that a party was succesfully placed into the dialplan at the expected location.</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;
}
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 10fd3bd83..be1a244df 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1845,6 +1845,15 @@ int ast_ari_validate_bridge_attended_transfer(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("replace_channel", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_channel(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field replace_channel failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("result", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_result = 1;
@@ -1855,6 +1864,24 @@ int ast_ari_validate_bridge_attended_transfer(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("transfer_target", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_channel(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transfer_target failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("transferee", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_channel(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeAttendedTransfer field transferee failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("transferer_first_leg", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_transferer_first_leg = 1;
@@ -2045,6 +2072,15 @@ int ast_ari_validate_bridge_blind_transfer(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("transferee", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_channel(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeBlindTransfer field transferee failed validation\n");
+ res = 0;
+ }
+ } else
{
ast_log(LOG_ERROR,
"ARI BridgeBlindTransfer has undocumented field %s\n",
@@ -4828,6 +4864,15 @@ int ast_ari_validate_stasis_start(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("replace_channel", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_channel(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI StasisStart field replace_channel failed validation\n");
+ res = 0;
+ }
+ } else
{
ast_log(LOG_ERROR,
"ARI StasisStart has undocumented field %s\n",
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index beace67b2..64dd1b071 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1287,7 +1287,10 @@ ari_validator ast_ari_validate_application_fn(void);
* - destination_threeway_channel: Channel
* - destination_type: string (required)
* - is_external: boolean (required)
+ * - replace_channel: Channel
* - result: string (required)
+ * - transfer_target: Channel
+ * - transferee: Channel
* - transferer_first_leg: Channel (required)
* - transferer_first_leg_bridge: Bridge
* - transferer_second_leg: Channel (required)
@@ -1302,6 +1305,7 @@ ari_validator ast_ari_validate_application_fn(void);
* - exten: string (required)
* - is_external: boolean (required)
* - result: string (required)
+ * - transferee: Channel
* BridgeCreated
* - type: string (required)
* - application: string (required)
@@ -1467,6 +1471,7 @@ ari_validator ast_ari_validate_application_fn(void);
* - timestamp: Date
* - args: List[string] (required)
* - channel: Channel (required)
+ * - replace_channel: Channel
* TextMessageReceived
* - type: string (required)
* - application: string (required)
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 7b5d16f1a..3480c9e23 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -109,6 +109,31 @@ struct ao2_container *app_bridges_moh;
struct ao2_container *app_bridges_playback;
+static struct ast_json *stasis_end_json_payload(struct ast_channel_snapshot *snapshot,
+ const struct stasis_message_sanitizer *sanitize)
+{
+ return ast_json_pack("{s: s, s: o, s: o}",
+ "type", "StasisEnd",
+ "timestamp", ast_json_timeval(ast_tvnow(), NULL),
+ "channel", ast_channel_snapshot_to_json(snapshot, sanitize));
+}
+
+static struct ast_json *stasis_end_to_json(struct stasis_message *message,
+ const struct stasis_message_sanitizer *sanitize)
+{
+ struct ast_channel_blob *payload = stasis_message_data(message);
+
+ if (sanitize && sanitize->channel_snapshot &&
+ sanitize->channel_snapshot(payload->snapshot)) {
+ return NULL;
+ }
+
+ return stasis_end_json_payload(payload->snapshot, sanitize);
+}
+
+STASIS_MESSAGE_TYPE_DEFN(ast_stasis_end_message_type,
+ .to_json = stasis_end_to_json);
+
const char *stasis_app_name(const struct stasis_app *app)
{
return app_name(app);
@@ -726,26 +751,121 @@ void stasis_app_bridge_destroy(const char *bridge_id)
ast_bridge_destroy(bridge, 0);
}
-static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
- int argc, char *argv[])
+struct replace_channel_store {
+ struct ast_channel_snapshot *snapshot;
+ char *app;
+};
+
+static void replace_channel_destroy(void *obj)
{
- RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
- RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+ struct replace_channel_store *replace = obj;
- struct ast_json *json_args;
- int i;
- struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer();
+ ao2_cleanup(replace->snapshot);
+ ast_free(replace->app);
+ ast_free(replace);
+}
- ast_assert(chan != NULL);
+static const struct ast_datastore_info replace_channel_store_info = {
+ .type = "replace-channel-store",
+ .destroy = replace_channel_destroy,
+};
- /* Set channel info */
- ast_channel_lock(chan);
- snapshot = ast_channel_snapshot_create(chan);
- ast_channel_unlock(chan);
- if (!snapshot) {
+static struct replace_channel_store *get_replace_channel_store(struct ast_channel *chan, int no_create)
+{
+ struct ast_datastore *datastore;
+
+ SCOPED_CHANNELLOCK(lock, chan);
+ datastore = ast_channel_datastore_find(chan, &replace_channel_store_info, NULL);
+ if (!datastore) {
+ if (no_create) {
+ return NULL;
+ }
+
+ datastore = ast_datastore_alloc(&replace_channel_store_info, NULL);
+ if (!datastore) {
+ return NULL;
+ }
+ ast_channel_datastore_add(chan, datastore);
+ }
+
+ if (!datastore->data) {
+ datastore->data = ast_calloc(1, sizeof(struct replace_channel_store));
+ }
+ return datastore->data;
+}
+
+int app_set_replace_channel_snapshot(struct ast_channel *chan, struct ast_channel_snapshot *replace_snapshot)
+{
+ struct replace_channel_store *replace = get_replace_channel_store(chan, 0);
+
+ if (!replace) {
return -1;
}
+ ao2_replace(replace->snapshot, replace_snapshot);
+ return 0;
+}
+
+int app_set_replace_channel_app(struct ast_channel *chan, const char *replace_app)
+{
+ struct replace_channel_store *replace = get_replace_channel_store(chan, 0);
+
+ if (!replace) {
+ return -1;
+ }
+
+ ast_free(replace->app);
+ replace->app = NULL;
+
+ if (replace_app) {
+ replace->app = ast_strdup(replace_app);
+ if (!replace->app) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static struct ast_channel_snapshot *get_replace_channel_snapshot(struct ast_channel *chan)
+{
+ struct replace_channel_store *replace = get_replace_channel_store(chan, 1);
+ struct ast_channel_snapshot *replace_channel_snapshot;
+
+ if (!replace) {
+ return NULL;
+ }
+
+ replace_channel_snapshot = replace->snapshot;
+ replace->snapshot = NULL;
+
+ return replace_channel_snapshot;
+}
+
+char *app_get_replace_channel_app(struct ast_channel *chan)
+{
+ struct replace_channel_store *replace = get_replace_channel_store(chan, 1);
+ char *replace_channel_app;
+
+ if (!replace) {
+ return NULL;
+ }
+
+ replace_channel_app = replace->app;
+ replace->app = NULL;
+
+ return replace_channel_app;
+}
+
+static int send_start_msg_snapshots(struct stasis_app *app,
+ int argc, char *argv[], struct ast_channel_snapshot *snapshot,
+ struct ast_channel_snapshot *replace_channel_snapshot)
+{
+ RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
+ struct ast_json *json_args;
+ struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer();
+ int i;
+
if (sanitize && sanitize->channel_snapshot
&& sanitize->channel_snapshot(snapshot)) {
return 0;
@@ -760,6 +880,15 @@ static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
return -1;
}
+ if (replace_channel_snapshot) {
+ int res = ast_json_object_set(msg, "replace_channel",
+ ast_channel_snapshot_to_json(replace_channel_snapshot, NULL));
+
+ if (res) {
+ return -1;
+ }
+ }
+
/* Append arguments to args array */
json_args = ast_json_object_get(msg, "args");
ast_assert(json_args != NULL);
@@ -776,39 +905,213 @@ static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
return 0;
}
-static int send_end_msg(struct stasis_app *app, struct ast_channel *chan)
+static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
+ int argc, char *argv[])
{
- RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
- struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer();
+ RAII_VAR(struct ast_channel_snapshot *, replace_channel_snapshot,
+ NULL, ao2_cleanup);
ast_assert(chan != NULL);
+ replace_channel_snapshot = get_replace_channel_snapshot(chan);
+
/* Set channel info */
ast_channel_lock(chan);
snapshot = ast_channel_snapshot_create(chan);
ast_channel_unlock(chan);
- if (snapshot == NULL) {
+ if (!snapshot) {
return -1;
}
+ return send_start_msg_snapshots(app, argc, argv, snapshot, replace_channel_snapshot);
+}
+
+static int send_end_msg_snapshot(struct stasis_app *app, struct ast_channel_snapshot *snapshot)
+{
+ struct stasis_message_sanitizer *sanitize = stasis_app_get_sanitizer();
+ struct ast_json *msg;
if (sanitize && sanitize->channel_snapshot
&& sanitize->channel_snapshot(snapshot)) {
return 0;
}
- msg = ast_json_pack("{s: s, s: o, s: o}",
- "type", "StasisEnd",
- "timestamp", ast_json_timeval(ast_tvnow(), NULL),
- "channel", ast_channel_snapshot_to_json(snapshot, NULL));
+ msg = stasis_end_json_payload(snapshot, sanitize);
if (!msg) {
return -1;
}
app_send(app, msg);
+ ast_json_unref(msg);
+ return 0;
+}
+
+static void remove_masquerade_store(struct ast_channel *chan);
+
+static int masq_match_cb(void *obj, void *data, int flags)
+{
+ struct stasis_app_control *control = obj;
+ struct ast_channel *chan = data;
+
+ if (!strcmp(ast_channel_uniqueid(chan),
+ stasis_app_control_get_channel_id(control))) {
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+static void channel_stolen_cb(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ struct ast_channel_snapshot *snapshot;
+ struct stasis_app_control *control;
+
+ /* grab a snapshot */
+ snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(new_chan));
+ if (!snapshot) {
+ ast_log(LOG_ERROR, "Could not get snapshot for masqueraded channel\n");
+ return;
+ }
+
+ /* find control */
+ control = ao2_callback(app_controls, 0, masq_match_cb, old_chan);
+ if (!control) {
+ ast_log(LOG_ERROR, "Could not find control for masqueraded channel\n");
+ ao2_cleanup(snapshot);
+ return;
+ }
+
+ /* send the StasisEnd message to the app */
+ send_end_msg_snapshot(control_app(control), snapshot);
+
+ /* remove the datastore */
+ remove_masquerade_store(old_chan);
+
+ ao2_cleanup(control);
+ ao2_cleanup(snapshot);
+}
+
+static void channel_replaced_cb(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ RAII_VAR(struct ast_channel_snapshot *, new_snapshot, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel_snapshot *, old_snapshot, NULL, ao2_cleanup);
+ struct stasis_app_control *control;
+
+ /* At this point, new_chan is the channel pointer that is in Stasis() and
+ * has the unknown channel's name in it while old_chan is the channel pointer
+ * that is not in Stasis(), but has the guts of the channel that Stasis() knows
+ * about */
+
+ /* grab a snapshot for the channel that is jumping into Stasis() */
+ new_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(new_chan));
+ if (!new_snapshot) {
+ ast_log(LOG_ERROR, "Could not get snapshot for masquerading channel\n");
+ return;
+ }
+
+ /* grab a snapshot for the channel that has been kicked out of Stasis() */
+ old_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(old_chan));
+ if (!old_snapshot) {
+ ast_log(LOG_ERROR, "Could not get snapshot for masqueraded channel\n");
+ return;
+ }
+
+ /* find, unlink, and relink control since the channel has a new name and
+ * its hash has likely changed */
+ control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, new_chan);
+ if (!control) {
+ ast_log(LOG_ERROR, "Could not find control for masquerading channel\n");
+ return;
+ }
+ ao2_link(app_controls, control);
+
+
+ /* send the StasisStart with replace_channel to the app */
+ send_start_msg_snapshots(control_app(control), 0, NULL, new_snapshot,
+ old_snapshot);
+ /* send the StasisEnd message to the app */
+ send_end_msg_snapshot(control_app(control), old_snapshot);
+
+ /* fixup channel topic forwards */
+ if (app_replace_channel_forwards(control_app(control), old_snapshot->uniqueid, new_chan)) {
+ ast_log(LOG_ERROR, "Failed to fixup channel topic forwards for %s(%s) owned by %s\n",
+ old_snapshot->name, old_snapshot->uniqueid, app_name(control_app(control)));
+ }
+ ao2_cleanup(control);
+}
+
+static const struct ast_datastore_info masquerade_store_info = {
+ .type = "stasis-masqerade",
+ .chan_fixup = channel_stolen_cb,
+ .chan_breakdown = channel_replaced_cb,
+};
+
+static int has_masquerade_store(struct ast_channel *chan)
+{
+ SCOPED_CHANNELLOCK(lock, chan);
+ return !!ast_channel_datastore_find(chan, &masquerade_store_info, NULL);
+}
+
+static int add_masquerade_store(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ SCOPED_CHANNELLOCK(lock, chan);
+ if (ast_channel_datastore_find(chan, &masquerade_store_info, NULL)) {
+ return 0;
+ }
+
+ datastore = ast_datastore_alloc(&masquerade_store_info, NULL);
+ if (!datastore) {
+ return -1;
+ }
+
+ ast_channel_datastore_add(chan, datastore);
+
return 0;
}
+static void remove_masquerade_store(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ SCOPED_CHANNELLOCK(lock, chan);
+ datastore = ast_channel_datastore_find(chan, &masquerade_store_info, NULL);
+ if (!datastore) {
+ return;
+ }
+
+ ast_channel_datastore_remove(chan, datastore);
+ ast_datastore_free(datastore);
+}
+
+static int send_end_msg(struct stasis_app *app, struct ast_channel *chan)
+{
+ struct ast_channel_snapshot *snapshot;
+ int res = 0;
+
+ ast_assert(chan != NULL);
+
+ /* A masquerade has occurred and this message will be wrong so it
+ * has already been sent elsewhere. */
+ if (!has_masquerade_store(chan)) {
+ return 0;
+ }
+
+ /* Set channel info */
+ snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
+ if (!snapshot) {
+ return -1;
+ }
+
+ if (send_end_msg_snapshot(app, snapshot)) {
+ res = -1;
+ }
+
+ ao2_cleanup(snapshot);
+ return res;
+}
+
void stasis_app_control_execute_until_exhausted(struct ast_channel *chan, struct stasis_app_control *control)
{
while (!control_is_done(control)) {
@@ -837,6 +1140,46 @@ int stasis_app_control_is_done(struct stasis_app_control *control)
return control_is_done(control);
}
+struct ast_datastore_info set_end_published_info = {
+ .type = "stasis_end_published",
+};
+
+void stasis_app_channel_set_stasis_end_published(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ datastore = ast_datastore_alloc(&set_end_published_info, NULL);
+
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, datastore);
+ ast_channel_unlock(chan);
+}
+
+int stasis_app_channel_is_stasis_end_published(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &set_end_published_info, NULL);
+ ast_channel_unlock(chan);
+
+ return datastore ? 1 : 0;
+}
+
+static void remove_stasis_end_published(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &set_end_published_info, NULL);
+ ast_channel_unlock(chan);
+
+ if (datastore) {
+ ast_channel_datastore_remove(chan, datastore);
+ ast_datastore_free(datastore);
+ }
+}
+
/*! /brief Stasis dialplan application callback */
int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
char *argv[])
@@ -850,6 +1193,11 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
ast_assert(chan != NULL);
+ /* Just in case there's a lingering indication that the channel has had a stasis
+ * end published on it, remove that now.
+ */
+ remove_stasis_end_published(chan);
+
app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
if (!app) {
ast_log(LOG_ERROR,
@@ -869,10 +1217,16 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
}
ao2_link(app_controls, control);
+ if (add_masquerade_store(chan)) {
+ ast_log(LOG_ERROR, "Failed to attach masquerade detector\n");
+ return -1;
+ }
+
res = send_start_msg(app, chan, argc, argv);
if (res != 0) {
ast_log(LOG_ERROR,
"Error sending start message to '%s'\n", app_name);
+ remove_masquerade_store(chan);
return -1;
}
@@ -880,9 +1234,13 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
if (res != 0) {
ast_log(LOG_ERROR, "Error subscribing app '%s' to channel '%s'\n",
app_name, ast_channel_name(chan));
+ remove_masquerade_store(chan);
return -1;
}
+ /* Pull queued prestart commands and execute */
+ control_prestart_dispatch_all(control, chan);
+
while (!control_is_done(control)) {
RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
int r;
@@ -948,14 +1306,20 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
}
app_unsubscribe_bridge(app, stasis_app_get_bridge(control));
- app_unsubscribe_channel(app, chan);
ao2_cleanup(bridge);
- res = send_end_msg(app, chan);
- if (res != 0) {
- ast_log(LOG_ERROR,
- "Error sending end message to %s\n", app_name);
- return res;
+ /* Only publish a stasis_end event if it hasn't already been published */
+ if (!stasis_app_channel_is_stasis_end_published(chan)) {
+ app_unsubscribe_channel(app, chan);
+ res = send_end_msg(app, chan);
+ remove_masquerade_store(chan);
+ if (res != 0) {
+ ast_log(LOG_ERROR,
+ "Error sending end message to %s\n", app_name);
+ return res;
+ }
+ } else {
+ remove_stasis_end_published(chan);
}
/* There's an off chance that app is ready for cleanup. Go ahead
@@ -1434,8 +1798,15 @@ void stasis_app_unref(void)
ast_module_unref(ast_module_info->self);
}
+/*!
+ * \brief Subscription to StasisEnd events
+ */
+struct stasis_subscription *stasis_end_sub;
+
static int unload_module(void)
{
+ stasis_end_sub = stasis_unsubscribe(stasis_end_sub);
+
stasis_app_unregister_event_sources();
messaging_cleanup();
@@ -1455,6 +1826,8 @@ static int unload_module(void)
ao2_cleanup(app_bridges_playback);
app_bridges_playback = NULL;
+ STASIS_MESSAGE_TYPE_CLEANUP(ast_stasis_end_message_type);
+
return 0;
}
@@ -1486,8 +1859,53 @@ struct stasis_message_sanitizer *stasis_app_get_sanitizer(void)
return &app_sanitizer;
}
+static void remove_masquerade_store_by_name(const char *channel_name)
+{
+ struct ast_channel *chan;
+
+ chan = ast_channel_get_by_name(channel_name);
+ if (!chan) {
+ return;
+ }
+
+ remove_masquerade_store(chan);
+ ast_channel_unref(chan);
+}
+
+static void check_for_stasis_end(void *data, struct stasis_subscription *sub,
+ struct stasis_message *message)
+{
+ struct ast_channel_blob *payload;
+ struct ast_channel_snapshot *snapshot;
+ const char *app_name;
+ char *channel_uri;
+ size_t alloc_size;
+ const char *channels[1];
+
+ if (stasis_message_type(message) != ast_stasis_end_message_type()) {
+ return;
+ }
+
+ payload = stasis_message_data(message);
+ snapshot = payload->snapshot;
+ app_name = ast_json_string_get(ast_json_object_get(payload->blob, "app"));
+
+ /* +8 is for the length of "channel:" */
+ alloc_size = AST_MAX_UNIQUEID + 8;
+ channel_uri = ast_alloca(alloc_size);
+ snprintf(channel_uri, alloc_size, "channel:%s", snapshot->uniqueid);
+
+ channels[0] = channel_uri;
+ stasis_app_unsubscribe(app_name, channels, ARRAY_LEN(channels), NULL);
+
+ remove_masquerade_store_by_name(snapshot->name);
+}
+
static int load_module(void)
{
+ if (STASIS_MESSAGE_TYPE_INIT(ast_stasis_end_message_type) != 0) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
apps_registry = ao2_container_alloc(APPS_NUM_BUCKETS, app_hash, app_compare);
app_controls = ao2_container_alloc(CONTROLS_NUM_BUCKETS, control_hash, control_compare);
app_bridges = ao2_container_alloc(BRIDGES_NUM_BUCKETS, bridges_hash, bridges_compare);
@@ -1511,6 +1929,12 @@ static int load_module(void)
stasis_app_register_event_sources();
+ stasis_end_sub = stasis_subscribe(ast_channel_topic_all(), check_for_stasis_end, NULL);
+ if (!stasis_end_sub) {
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 7e7911b9c..745969615 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -28,6 +28,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "app.h"
+#include "control.h"
#include "messaging.h"
#include "asterisk/callerid.h"
@@ -699,14 +700,32 @@ static void bridge_blind_transfer_handler(void *data, struct stasis_subscription
struct stasis_message *message)
{
struct stasis_app *app = data;
- struct ast_bridge_blob *blob = stasis_message_data(message);
+ struct ast_blind_transfer_message *transfer_msg = stasis_message_data(message);
+ struct ast_bridge_snapshot *bridge = transfer_msg->to_transferee.bridge_snapshot;
- if (bridge_app_subscribed(app, blob->channel->uniqueid) ||
- (blob->bridge && bridge_app_subscribed_involved(app, blob->bridge))) {
+ if (bridge_app_subscribed(app, transfer_msg->to_transferee.channel_snapshot->uniqueid) ||
+ (bridge && bridge_app_subscribed_involved(app, bridge))) {
stasis_publish(app->topic, message);
}
}
+static void set_replacement_channel(struct ast_channel_snapshot *to_be_replaced,
+ struct ast_channel_snapshot *replacing)
+{
+ struct stasis_app_control *control = stasis_app_control_find_by_channel_id(
+ to_be_replaced->uniqueid);
+ struct ast_channel *chan = ast_channel_get_by_name(replacing->uniqueid);
+
+ if (control && chan) {
+ ast_channel_lock(chan);
+ app_set_replace_channel_app(chan, app_name(control_app(control)));
+ app_set_replace_channel_snapshot(chan, to_be_replaced);
+ ast_channel_unlock(chan);
+ }
+ ast_channel_cleanup(chan);
+ ao2_cleanup(control);
+}
+
static void bridge_attended_transfer_handler(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
@@ -751,6 +770,18 @@ static void bridge_attended_transfer_handler(void *data, struct stasis_subscript
if (subscribed) {
stasis_publish(app->topic, message);
}
+
+ if (transfer_msg->replace_channel) {
+ set_replacement_channel(transfer_msg->to_transferee.channel_snapshot,
+ transfer_msg->replace_channel);
+ }
+
+ if (transfer_msg->dest_type == AST_ATTENDED_TRANSFER_DEST_LINK) {
+ set_replacement_channel(transfer_msg->to_transferee.channel_snapshot,
+ transfer_msg->dest.links[0]);
+ set_replacement_channel(transfer_msg->to_transfer_target.channel_snapshot,
+ transfer_msg->dest.links[1]);
+ }
}
static void bridge_default_handler(void *data, struct stasis_subscription *sub,
@@ -1091,6 +1122,30 @@ int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id)
return forwards != NULL;
}
+int app_replace_channel_forwards(struct stasis_app *app, const char *old_id, struct ast_channel *new_chan)
+{
+ RAII_VAR(struct app_forwards *, old_forwards, NULL, ao2_cleanup);
+ struct app_forwards *new_forwards;
+
+ old_forwards = ao2_find(app->forwards, old_id, OBJ_SEARCH_KEY | OBJ_UNLINK);
+ if (!old_forwards) {
+ return -1;
+ }
+
+ new_forwards = forwards_create_channel(app, new_chan);
+ if (!new_forwards) {
+ return -1;
+ }
+
+ new_forwards->interested = old_forwards->interested;
+ ao2_link_flags(app->forwards, new_forwards, 0);
+ ao2_cleanup(new_forwards);
+
+ /* Clean up old forwards */
+ forwards_unsubscribe(old_forwards);
+ return 0;
+}
+
static void *channel_find(const struct stasis_app *app, const char *id)
{
return ast_channel_get_by_name(id);
diff --git a/res/stasis/app.h b/res/stasis/app.h
index 419ec54a8..1ab6097a7 100644
--- a/res/stasis/app.h
+++ b/res/stasis/app.h
@@ -226,4 +226,48 @@ int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id)
*/
int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id);
+/*!
+ * \brief Set the snapshot of the channel that this channel will replace
+ *
+ * \param channel The channel on which this will be set
+ * \param replace_snapshot The snapshot of the channel that is being replaced
+ *
+ * \retval zero success
+ * \retval non-zero failure
+ */
+int app_set_replace_channel_snapshot(struct ast_channel *chan, struct ast_channel_snapshot *replace_snapshot);
+
+/*!
+ * \brief Set the app that the replacement channel will be controlled by
+ *
+ * \param channel The channel on which this will be set
+ * \param replace_app The app that will be controlling this channel
+ *
+ * \retval zero success
+ * \retval non-zero failure
+ */
+int app_set_replace_channel_app(struct ast_channel *chan, const char *replace_app);
+
+/*!
+ * \brief Get the app that the replacement channel will be controlled by
+ *
+ * \param channel The channel on which this will be set
+ *
+ * \retval NULL on error
+ * \return the name of the controlling app (must be ast_free()d)
+ */
+char *app_get_replace_channel_app(struct ast_channel *chan);
+
+/*!
+ * \brief Replace channel topic forwards for the old channel with forwards for the new channel
+ *
+ * \param app The app that owns the channel
+ * \param old_id The unique ID of the channel to be replaced
+ * \param new_chan The channel that is replacing the old one
+ *
+ * \retval zero on success
+ * \return non-zero on failure
+ */
+int app_replace_channel_forwards(struct stasis_app *app, const char *old_id, struct ast_channel *new_chan);
+
#endif /* _ASTERISK_RES_STASIS_APP_H */
diff --git a/res/stasis/command.c b/res/stasis/command.c
index a9e53af12..867de180a 100644
--- a/res/stasis/command.c
+++ b/res/stasis/command.c
@@ -93,3 +93,61 @@ void command_invoke(struct stasis_app_command *command,
command_complete(command, retval);
}
+static void command_queue_prestart_destroy(void *obj)
+{
+ /* Clean up the container */
+ ao2_cleanup(obj);
+}
+
+static const struct ast_datastore_info command_queue_prestart = {
+ .type = "stasis-command-prestart-queue",
+ .destroy = command_queue_prestart_destroy,
+};
+
+int command_prestart_queue_command(struct ast_channel *chan,
+ stasis_app_command_cb command_fn, void *data)
+{
+ struct ast_datastore *datastore;
+ struct ao2_container *command_queue;
+ RAII_VAR(struct stasis_app_command *, command,
+ command_create(command_fn, data), ao2_cleanup);
+
+ if (!command) {
+ return -1;
+ }
+
+ datastore = ast_channel_datastore_find(chan, &command_queue_prestart, NULL);
+ if (datastore) {
+ command_queue = datastore->data;
+ ao2_link(command_queue, command);
+ return 0;
+ }
+
+ command_queue = ao2_container_alloc_list(
+ AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
+ if (!command_queue) {
+ return -1;
+ }
+
+ datastore = ast_datastore_alloc(&command_queue_prestart, NULL);
+ if (!datastore) {
+ ao2_cleanup(command_queue);
+ return -1;
+ }
+ ast_channel_datastore_add(chan, datastore);
+
+ datastore->data = command_queue;
+ ao2_link(command_queue, command);
+
+ return 0;
+}
+
+struct ao2_container *command_prestart_get_container(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore = ast_channel_datastore_find(chan, &command_queue_prestart, NULL);
+ if (!datastore) {
+ return NULL;
+ }
+
+ return ao2_bump(datastore->data);
+}
diff --git a/res/stasis/command.h b/res/stasis/command.h
index a99d40d0a..7f12ab36f 100644
--- a/res/stasis/command.h
+++ b/res/stasis/command.h
@@ -41,4 +41,31 @@ void command_invoke(struct stasis_app_command *command,
int command_join(struct stasis_app_command *command);
+/*!
+ * \brief Queue a Stasis() prestart command for a channel
+ *
+ * \pre chan must be locked
+ *
+ * \param chan The channel on which to queue the prestart command
+ * \param command_fn The callback to call for the command
+ * \param data The data to pass to the command callback
+ *
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int command_prestart_queue_command(struct ast_channel *chan,
+ stasis_app_command_cb command_fn, void *data);
+
+/*!
+ * \brief Get the Stasis() prestart commands for a channel
+ *
+ * \pre chan must be locked
+ *
+ * \param chan The channel from which to get prestart commands
+ *
+ * \return The command prestart container for chan (must be ao2_cleanup()'d)
+ */
+struct ao2_container *command_prestart_get_container(struct ast_channel *chan);
+
+
#endif /* _ASTERISK_RES_STASIS_CONTROL_H */
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 8802e8128..0a9669d3b 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -276,10 +276,6 @@ struct stasis_app_control_dial_data {
int timeout;
};
-static int app_control_add_channel_to_bridge(
- struct stasis_app_control *control,
- struct ast_channel *chan, void *data);
-
static int app_control_dial(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
@@ -322,7 +318,7 @@ static int app_control_dial(struct stasis_app_control *control,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_hangup(new_chan);
} else {
- app_control_add_channel_to_bridge(control, chan, bridge);
+ control_add_channel_to_bridge(control, chan, bridge);
}
return 0;
@@ -855,7 +851,7 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
ast_bridge_after_cb_reason_string(reason));
}
-static int app_control_add_channel_to_bridge(
+int control_add_channel_to_bridge(
struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
@@ -935,7 +931,7 @@ int stasis_app_control_add_channel_to_bridge(
stasis_app_control_get_channel_id(control));
return app_send_command_on_condition(
- control, app_control_add_channel_to_bridge, bridge,
+ control, control_add_channel_to_bridge, bridge,
app_control_can_add_channel_to_bridge);
}
@@ -1036,3 +1032,36 @@ void control_wait(struct stasis_app_control *control)
}
ao2_unlock(control->command_queue);
}
+
+int control_prestart_dispatch_all(struct stasis_app_control *control,
+ struct ast_channel *chan)
+{
+ struct ao2_container *command_queue;
+ int count = 0;
+ struct ao2_iterator iter;
+ struct stasis_app_command *command;
+
+ ast_channel_lock(chan);
+ command_queue = command_prestart_get_container(chan);
+ ast_channel_unlock(chan);
+ if (!command_queue) {
+ return 0;
+ }
+
+ iter = ao2_iterator_init(command_queue, AO2_ITERATOR_UNLINK);
+
+ while ((command = ao2_iterator_next(&iter))) {
+ command_invoke(command, control, chan);
+ ao2_cleanup(command);
+ ++count;
+ }
+
+ ao2_iterator_destroy(&iter);
+ ao2_cleanup(command_queue);
+ return count;
+}
+
+struct stasis_app *control_app(struct stasis_app_control *control)
+{
+ return control->app;
+}
diff --git a/res/stasis/control.h b/res/stasis/control.h
index 0febd8438..a139f82e4 100644
--- a/res/stasis/control.h
+++ b/res/stasis/control.h
@@ -77,5 +77,36 @@ int control_is_done(struct stasis_app_control *control);
void control_mark_done(struct stasis_app_control *control);
+/*!
+ * \brief Dispatch all queued prestart commands
+ *
+ * \param control The control for chan
+ * \param channel The channel on which commands should be executed
+ *
+ * \return The number of commands executed
+ */
+int control_prestart_dispatch_all(struct stasis_app_control *control,
+ struct ast_channel *chan);
+
+/*!
+ * \brief Returns the pointer (non-reffed) to the app associated with this control
+ *
+ * \param control Control to query.
+ *
+ * \returns A pointer to the associated stasis_app
+ */
+struct stasis_app *control_app(struct stasis_app_control *control);
+
+/*!
+ * \brief Command callback for adding a channel to a bridge
+ *
+ * \param control The control for chan
+ * \param channel The channel on which commands should be executed
+ * \param bridge Data to be passed to the callback
+ */
+int control_add_channel_to_bridge(
+ struct stasis_app_control *control,
+ struct ast_channel *chan, void *obj);
+
#endif /* _ASTERISK_RES_STASIS_CONTROL_H */
diff --git a/res/stasis/stasis_bridge.c b/res/stasis/stasis_bridge.c
index c3a266a11..be7836d35 100644
--- a/res/stasis/stasis_bridge.c
+++ b/res/stasis/stasis_bridge.c
@@ -32,11 +32,73 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/bridge.h"
+#include "asterisk/bridge_after.h"
#include "asterisk/bridge_internal.h"
+#include "asterisk/bridge_features.h"
+#include "asterisk/stasis_app.h"
+#include "asterisk/stasis_channels.h"
#include "stasis_bridge.h"
+#include "control.h"
+#include "command.h"
+#include "app.h"
+#include "asterisk/stasis_app.h"
+#include "asterisk/pbx.h"
/* ------------------------------------------------------------------- */
+static struct ast_bridge_methods bridge_stasis_v_table;
+
+static void bridge_stasis_run_cb(struct ast_channel *chan, void *data)
+{
+ RAII_VAR(char *, app_name, NULL, ast_free);
+ struct ast_app *app_stasis;
+
+ /* Take ownership of the swap_app memory from the datastore */
+ app_name = app_get_replace_channel_app(chan);
+ if (!app_name) {
+ ast_log(LOG_ERROR, "Failed to get app name for %s (%p)\n", ast_channel_name(chan), chan);
+ return;
+ }
+
+ /* find Stasis() */
+ app_stasis = pbx_findapp("Stasis");
+ if (!app_stasis) {
+ ast_log(LOG_WARNING, "Could not find application (Stasis)\n");
+ return;
+ }
+
+ if (ast_check_hangup_locked(chan)) {
+ /* channel hungup, don't run Stasis() */
+ return;
+ }
+
+ /* run Stasis() */
+ pbx_exec(chan, app_stasis, app_name);
+}
+
+static int add_channel_to_bridge(
+ struct stasis_app_control *control,
+ struct ast_channel *chan, void *obj)
+{
+ struct ast_bridge *bridge = obj;
+ int res;
+
+ res = control_add_channel_to_bridge(control,
+ chan, bridge);
+ ao2_cleanup(bridge);
+ return res;
+}
+
+static void bridge_stasis_queue_join_action(struct ast_bridge *self,
+ struct ast_bridge_channel *bridge_channel)
+{
+ ast_channel_lock(bridge_channel->chan);
+ if (command_prestart_queue_command(bridge_channel->chan, add_channel_to_bridge, ao2_bump(self))) {
+ ao2_cleanup(self);
+ }
+ ast_channel_unlock(bridge_channel->chan);
+}
+
/*!
* \internal
* \brief Push this channel into the Stasis bridge.
@@ -53,6 +115,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
*/
static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
{
+ struct stasis_app_control *control = stasis_app_control_find_by_channel(bridge_channel->chan);
+
+ if (!control) {
+ /* channel not in Stasis(), get it there */
+ /* Attach after-bridge callback and pass ownership of swap_app to it */
+ if (ast_bridge_set_after_callback(bridge_channel->chan,
+ bridge_stasis_run_cb, NULL, NULL)) {
+ ast_log(LOG_ERROR, "Failed to set after bridge callback\n");
+ return -1;
+ }
+
+ bridge_stasis_queue_join_action(self, bridge_channel);
+
+ /* Return -1 so the push fails and the after-bridge callback gets called */
+ return -1;
+ }
+
+ ao2_cleanup(control);
if (self->allowed_capabilities & STASIS_BRIDGE_MIXING_CAPABILITIES) {
ast_bridge_channel_update_linkedids(bridge_channel, swap);
if (ast_test_flag(&self->feature_flags, AST_BRIDGE_FLAG_SMART)) {
@@ -63,6 +143,33 @@ static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel
return ast_bridge_base_v_table.push(self, bridge_channel, swap);
}
+static int bridge_stasis_moving(struct ast_bridge_channel *bridge_channel, void *hook_pvt,
+ struct ast_bridge *src, struct ast_bridge *dst)
+{
+ if (src->v_table == &bridge_stasis_v_table &&
+ dst->v_table != &bridge_stasis_v_table) {
+ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+ RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
+ struct ast_channel *chan;
+
+ chan = bridge_channel->chan;
+ ast_assert(chan != NULL);
+
+ control = stasis_app_control_find_by_channel(chan);
+ if (!control) {
+ return -1;
+ }
+
+ blob = ast_json_pack("{s: s}", "app", app_name(control_app(control)));
+
+ stasis_app_channel_set_stasis_end_published(chan);
+
+ ast_channel_publish_blob(chan, ast_stasis_end_message_type(), blob);
+ }
+
+ return -1;
+}
+
/*!
* \internal
* \brief Pull this channel from the Stasis bridge.
@@ -82,11 +189,11 @@ static void bridge_stasis_pull(struct ast_bridge *self, struct ast_bridge_channe
ast_bridge_channel_update_accountcodes(NULL, bridge_channel);
}
+ ast_bridge_move_hook(bridge_channel->features, bridge_stasis_moving, NULL, NULL, 0);
+
ast_bridge_base_v_table.pull(self, bridge_channel);
}
-static struct ast_bridge_methods bridge_stasis_v_table;
-
struct ast_bridge *bridge_stasis_new(uint32_t capabilities, unsigned int flags, const char *name, const char *id)
{
void *bridge;
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 5e115fbd2..a0f70fa68 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -285,6 +285,11 @@
"required": true,
"type": "Channel"
},
+ "transferee": {
+ "description": "The channel that is being transferred",
+ "required": false,
+ "type": "Channel"
+ },
"exten": {
"description": "The extension transferred to",
"required": true,
@@ -325,6 +330,21 @@
"required": true,
"type": "Channel"
},
+ "replace_channel": {
+ "description": "The channel that is replacing transferer_first_leg in the swap",
+ "required": false,
+ "type": "Channel"
+ },
+ "transferee": {
+ "description": "The channel that is being transferred",
+ "required": false,
+ "type": "Channel"
+ },
+ "transfer_target": {
+ "description": "The channel that is being transferred to",
+ "required": false,
+ "type": "Channel"
+ },
"result": {
"description": "The result of the transfer attempt",
"required": true,
@@ -670,6 +690,10 @@
"channel": {
"required": true,
"type": "Channel"
+ },
+ "replace_channel": {
+ "required": false,
+ "type": "Channel"
}
}
},
diff --git a/tests/test_cel.c b/tests/test_cel.c
index 010f19987..80594af52 100644
--- a/tests/test_cel.c
+++ b/tests/test_cel.c
@@ -1250,7 +1250,7 @@ AST_TEST_DEFINE(test_cel_blind_transfer)
pair.channel = chan_alice;
ast_bridge_lock(bridge);
ast_bridge_publish_blind_transfer(1, AST_BRIDGE_TRANSFER_SUCCESS,
- &pair, "transfer_context", "transfer_extension");
+ &pair, "transfer_context", "transfer_extension", NULL);
ast_bridge_unlock(bridge);
BLINDTRANSFER_EVENT(chan_alice, bridge, "transfer_extension", "transfer_context");