Stasis_channels: Resolve unfinished Dials when doing masquerades
authorJonathan Rose <jrose@digium.com>
Fri, 19 Sep 2014 15:54:20 +0000 (15:54 +0000)
committerJonathan Rose <jrose@digium.com>
Fri, 19 Sep 2014 15:54:20 +0000 (15:54 +0000)
Masquerades into channels that are in the dialing state don't end their dial
and this goes against the model for things like CDRs and generating Dial end
manager actions and such.

ASTERISK-24237 #close
Reported by: Richard Mudgett
Review: https://reviewboard.asterisk.org/r/3990/
........

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

Merged revisions 423530 from http://svn.asterisk.org/svn/asterisk/branches/13

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

main/stasis_channels.c

index d40b570..79c453c 100644 (file)
@@ -40,6 +40,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis.h"
 #include "asterisk/stasis_cache_pattern.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/dial.h"
+#include "asterisk/linkedlists.h"
 
 /*** DOCUMENTATION
        <managerEvent language="en_US" name="VarSet">
@@ -360,10 +362,29 @@ void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_cha
        publish_message_for_channel_topics(msg, caller);
 }
 
+static void remove_dial_masquerade(struct ast_channel *caller,
+       struct ast_channel *peer);
+static int set_dial_masquerade(struct ast_channel *caller,
+       struct ast_channel *peer, const char *dialstring);
+
 void ast_channel_publish_dial(struct ast_channel *caller, struct ast_channel *peer,
        const char *dialstring, const char *dialstatus)
 {
+       if (caller) {
+               ast_channel_lock_both(caller, peer);
+               if (!ast_strlen_zero(dialstatus)) {
+                       remove_dial_masquerade(caller, peer);
+               } else {
+                       set_dial_masquerade(caller, peer, dialstring);
+               }
+       }
+
        ast_channel_publish_dial_forward(caller, peer, NULL, dialstring, dialstatus, NULL);
+
+       if (caller) {
+               ast_channel_unlock(peer);
+               ast_channel_unlock(caller);
+       }
 }
 
 static struct stasis_message *create_channel_blob_message(struct ast_channel_snapshot *snapshot,
@@ -1198,3 +1219,201 @@ int ast_stasis_channels_init(void)
        return res;
 }
 
+/*!
+ * \internal
+ * \brief A list element for the dial_masquerade_datastore -- stores data about a dialed peer
+ */
+struct dial_target {
+       struct ast_channel *peer;
+       char *dialstring;
+       AST_LIST_ENTRY(dial_target) list;
+};
+
+/*!
+ * \internal
+ * \brief Datastore used for advancing dial state in the case of a masquerade
+ *        against a channel in the process of dialing.
+ */
+struct dial_masquerade_datastore {
+       AST_LIST_HEAD(,dial_target) dialed_peers;
+};
+
+/*!
+ * \internal
+ * \brief Destructor for dial_masquerade_datastore
+ */
+static void dial_masquerade_datastore_destroy(void *data)
+{
+       struct dial_masquerade_datastore *ds = data;
+       struct dial_target *cur;
+
+       while ((cur = AST_LIST_REMOVE_HEAD(&ds->dialed_peers, list))) {
+               ast_channel_cleanup(cur->peer);
+               ast_free(cur->dialstring);
+               ast_free(cur);
+       }
+}
+
+static void disable_dial_masquerade(struct ast_channel *caller);
+
+/*!
+ * \internal
+ * \brief Primary purpose for dial_masquerade_datastore, publishes
+ *        the channel dial event needed to set the incoming channel into the
+ *        dial state during a masquerade.
+ * \param data pointer to the dial_masquerade_datastore
+ * \param old_chan Channel being replaced (not used)
+ * \param new_chan Channel being pushed to dial mode
+ */
+static void dial_masquerade(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+       struct dial_masquerade_datastore *masq_data = data;
+       struct dial_target *cur;
+
+       AST_LIST_TRAVERSE(&masq_data->dialed_peers, cur, list) {
+               ast_channel_publish_dial_forward(old_chan, cur->peer, NULL, cur->dialstring, "NOANSWER", NULL);
+               ast_channel_publish_dial_forward(new_chan, cur->peer, NULL, cur->dialstring, NULL, NULL);
+       }
+
+       disable_dial_masquerade(old_chan);
+}
+
+static const struct ast_datastore_info dial_masquerade_info = {
+       .type = "stasis-chan-dial-masq",
+       .destroy = dial_masquerade_datastore_destroy,
+       .chan_breakdown = dial_masquerade,
+};
+
+/*!
+ * \internal
+ * \brief Retrieves the dial_masquerade_datastore from a channel if
+ *        it has one.
+ * \param chan Channel a datastore data is wanted from
+ *
+ * \returns the datastore data if it exists
+ * \returns NULL if no dial_masquerade_datastore has been set for
+ *          chan
+ */
+static struct dial_masquerade_datastore *fetch_dial_masquerade_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+
+       datastore = ast_channel_datastore_find(chan, &dial_masquerade_info, NULL);
+       if (!datastore) {
+               return NULL;
+       }
+
+       return datastore->data;
+}
+
+/*!
+ * \internal
+ * \brief Create a fresh dial_masquerade_datastore on a channel
+ * \param chan Channel we want to create a dial_masquerade_datastore
+ *        for.
+ * \returns pointer to the datastore data if successful
+ * \returns NULL on allocation error
+ */
+static struct dial_masquerade_datastore *setup_dial_masquerade_datastore(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+       struct dial_masquerade_datastore *masq_datastore;
+
+       datastore = ast_datastore_alloc(&dial_masquerade_info, NULL);
+       if (!datastore) {
+               return NULL;
+       }
+
+       masq_datastore = ast_calloc(1, sizeof(*masq_datastore));
+       if (!masq_datastore) {
+               ast_datastore_free(datastore);
+               return NULL;
+       }
+
+       datastore->data = masq_datastore;
+       ast_channel_datastore_add(chan, datastore);
+       return masq_datastore;
+}
+
+/*!
+ * \internal
+ * \brief Get existing dial_masquerade_datastore if it exists. If
+ *        not, create a new one and return a pointer to that data.
+ * \param chan Which channel the datastore is wanted for.
+ * \returns pointer to the datastore if successful
+ * \returns NULL on allocation failure.
+ */
+static struct dial_masquerade_datastore *fetch_or_create_dial_masquerade_datastore(struct ast_channel *chan)
+{
+       struct dial_masquerade_datastore *ds;
+
+       ds = fetch_dial_masquerade_datastore(chan);
+       if (!ds) {
+               ds = setup_dial_masquerade_datastore(chan);
+       }
+
+       return ds;
+}
+
+static int set_dial_masquerade(struct ast_channel *chan, struct ast_channel *peer_chan, const char *dialstring)
+{
+       struct dial_masquerade_datastore *ds = fetch_or_create_dial_masquerade_datastore(chan);
+       struct dial_target *target = ast_calloc(1, sizeof(*target));
+
+       if (!target) {
+               return -1;
+       }
+
+       if (!ds) {
+               ast_free(target);
+               return -1;
+       }
+
+       if (dialstring) {
+               target->dialstring = ast_strdup(dialstring);
+               if (!target->dialstring) {
+                       ast_free(target);
+                       return -1;
+               }
+       }
+
+       ast_channel_ref(peer_chan);
+       target->peer = peer_chan;
+
+       AST_LIST_INSERT_HEAD(&ds->dialed_peers, target, list);
+
+       return 0;
+}
+
+static void remove_dial_masquerade(struct ast_channel *chan, struct ast_channel *peer_chan)
+{
+       struct dial_masquerade_datastore *ds = fetch_dial_masquerade_datastore(chan);
+       struct dial_target *cur;
+
+       if (!ds) {
+               return;
+       }
+
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&ds->dialed_peers, cur, list) {
+               if (cur->peer == peer_chan) {
+                       AST_LIST_REMOVE_CURRENT(list);
+                       ast_channel_cleanup(cur->peer);
+                       ast_free(cur->dialstring);
+                       ast_free(cur);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+}
+
+static void disable_dial_masquerade(struct ast_channel *chan)
+{
+       struct ast_datastore *ds = ast_channel_datastore_find(chan,
+               &dial_masquerade_info, NULL);
+
+       if (!ds) {
+               return;
+       }
+
+       ast_channel_datastore_remove(chan, ds);
+}