aboutsummaryrefslogtreecommitdiffstats
path: root/channels/chan_misdn.c
diff options
context:
space:
mode:
Diffstat (limited to 'channels/chan_misdn.c')
-rw-r--r--channels/chan_misdn.c152
1 files changed, 141 insertions, 11 deletions
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
index a6d623e7a..35ac69d06 100644
--- a/channels/chan_misdn.c
+++ b/channels/chan_misdn.c
@@ -8497,6 +8497,40 @@ static void release_chan_early(struct chan_list *ch)
/*!
* \internal
+ * \brief Copy the source connected line information to the destination for a transfer.
+ * \since 1.8
+ *
+ * \param dest Destination connected line
+ * \param src Source connected line
+ *
+ * \return Nothing
+ */
+static void misdn_connected_line_copy_transfer(struct ast_party_connected_line *dest, struct ast_party_connected_line *src)
+{
+ struct ast_party_connected_line connected;
+
+ connected = *src;
+ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER;
+
+ /* Make sure empty strings will be erased. */
+ if (!connected.id.name.str) {
+ connected.id.name.str = "";
+ }
+ if (!connected.id.number.str) {
+ connected.id.number.str = "";
+ }
+ if (!connected.id.subaddress.str) {
+ connected.id.subaddress.str = "";
+ }
+ if (!connected.id.tag) {
+ connected.id.tag = "";
+ }
+
+ ast_party_connected_line_copy(dest, &connected);
+}
+
+/*!
+ * \internal
* \brief Attempt to transfer the active channel party to the held channel party.
*
* \param active_ch Channel currently connected.
@@ -8508,7 +8542,10 @@ static void release_chan_early(struct chan_list *ch)
static int misdn_attempt_transfer(struct chan_list *active_ch, struct chan_list *held_ch)
{
int retval;
- struct ast_channel *bridged;
+ struct ast_channel *target;
+ struct ast_channel *transferee;
+ struct ast_party_connected_line target_colp;
+ struct ast_party_connected_line transferee_colp;
switch (active_ch->state) {
case MISDN_PROCEEDING:
@@ -8520,23 +8557,116 @@ static int misdn_attempt_transfer(struct chan_list *active_ch, struct chan_list
return -1;
}
- bridged = ast_bridged_channel(held_ch->ast);
- if (bridged) {
- ast_queue_control(held_ch->ast, AST_CONTROL_UNHOLD);
- held_ch->hold.state = MISDN_HOLD_TRANSFER;
+ ast_channel_lock(held_ch->ast);
+ while (ast_channel_trylock(active_ch->ast)) {
+ CHANNEL_DEADLOCK_AVOIDANCE(held_ch->ast);
+ }
- chan_misdn_log(1, held_ch->hold.port, "TRANSFERRING %s to %s\n",
- held_ch->ast->name, active_ch->ast->name);
- retval = ast_channel_masquerade(active_ch->ast, bridged);
- } else {
+ transferee = ast_bridged_channel(held_ch->ast);
+ if (!transferee) {
/*
* Could not transfer. Held channel is not bridged anymore.
* Held party probably got tired of waiting and hung up.
*/
- retval = -1;
+ ast_channel_unlock(held_ch->ast);
+ ast_channel_unlock(active_ch->ast);
+ return -1;
}
- return retval;
+ target = active_ch->ast;
+ chan_misdn_log(1, held_ch->hold.port, "TRANSFERRING %s to %s\n",
+ held_ch->ast->name, target->name);
+
+ ast_party_connected_line_init(&target_colp);
+ misdn_connected_line_copy_transfer(&target_colp, &target->connected);
+ ast_party_connected_line_init(&transferee_colp);
+ misdn_connected_line_copy_transfer(&transferee_colp, &held_ch->ast->connected);
+ held_ch->hold.state = MISDN_HOLD_TRANSFER;
+
+ /*
+ * Before starting a masquerade, all channel and pvt locks must
+ * be unlocked. Any recursive channel locks held before
+ * ast_channel_masquerade() invalidates deadlock avoidance. Any
+ * recursive channel locks held before ast_do_masquerade()
+ * invalidates channel container locking order. Since we are
+ * unlocking both the pvt and its owner channel it is possible
+ * for "target" and "transferee" to be destroyed by their pbx
+ * threads. To prevent this we must give "target" and
+ * "transferee" a reference before any unlocking takes place.
+ */
+ ao2_ref(target, +1);
+ ao2_ref(transferee, +1);
+ ast_channel_unlock(held_ch->ast);
+ ast_channel_unlock(active_ch->ast);
+
+ /* Release hold on the transferee channel. */
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+ /* Setup transfer masquerade. */
+ retval = ast_channel_masquerade(target, transferee);
+ if (retval) {
+ /* Masquerade setup failed. */
+ ast_party_connected_line_free(&target_colp);
+ ast_party_connected_line_free(&transferee_colp);
+ ao2_ref(target, -1);
+ ao2_ref(transferee, -1);
+ return -1;
+ }
+ ao2_ref(transferee, -1);
+
+ /*
+ * Make sure masquerade is complete.
+ *
+ * After the masquerade, the "target" channel pointer actually
+ * points to the new transferee channel and the bridged channel
+ * is still the intended target of the transfer.
+ *
+ * By manually completing the masquerade, we can send connected
+ * line updates where they need to go.
+ */
+ ast_do_masquerade(target);
+
+ /* Transfer COLP between target and transferee channels. */
+ {
+ /*
+ * Since "target" may not actually be bridged to another
+ * channel, there is no way for us to queue a frame so that its
+ * connected line status will be updated. Instead, we use the
+ * somewhat hackish approach of using a special control frame
+ * type that instructs ast_read() to perform a specific action.
+ * In this case, the frame we queue tells ast_read() to call the
+ * connected line interception macro configured for "target".
+ */
+ struct ast_control_read_action_payload *frame_payload;
+ int payload_size;
+ int frame_size;
+ unsigned char connected_line_data[1024];
+
+ payload_size = ast_connected_line_build_data(connected_line_data,
+ sizeof(connected_line_data), &target_colp, NULL);
+ if (payload_size != -1) {
+ frame_size = payload_size + sizeof(*frame_payload);
+ frame_payload = alloca(frame_size);
+ frame_payload->action = AST_FRAME_READ_ACTION_CONNECTED_LINE_MACRO;
+ frame_payload->payload_size = payload_size;
+ memcpy(frame_payload->payload, connected_line_data, payload_size);
+ ast_queue_control_data(target, AST_CONTROL_READ_ACTION, frame_payload,
+ frame_size);
+ }
+ /*
+ * In addition to queueing the read action frame so that the
+ * connected line info on "target" will be updated, we also
+ * are going to queue a plain old connected line update on
+ * "target" to update the target channel.
+ */
+ ast_channel_queue_connected_line_update(target, &transferee_colp, NULL);
+ }
+
+ ast_party_connected_line_free(&target_colp);
+ ast_party_connected_line_free(&transferee_colp);
+
+ ao2_ref(target, -1);
+ return 0;
}