summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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");