res_stasis: Expose event for call forwarding and follow forwarded channel.
authorJoshua Colp <jcolp@digium.com>
Sat, 14 Dec 2013 17:19:41 +0000 (17:19 +0000)
committerJoshua Colp <jcolp@digium.com>
Sat, 14 Dec 2013 17:19:41 +0000 (17:19 +0000)
This change adds an event for when an originated call is redirected to
another target. This event contains the original channel and the newly
created channel. If a stasis subscription exists on the original originated
channel for a stasis application then a new subscription will also be
created on the stasis application to the redirected channel. This allows
the application to follow the call path completely.

(closes issue ASTERISK-22719)
Reported by: Joshua Colp

Review: https://reviewboard.asterisk.org/r/3054/
........

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

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

apps/app_dial.c
apps/app_queue.c
include/asterisk/stasis_channels.h
main/dial.c
main/stasis_channels.c
res/ari/ari_model_validators.c
res/ari/ari_model_validators.h
res/stasis/app.c
rest-api/api-docs/events.json

index c9bee19..fdbe056 100644 (file)
@@ -1007,7 +1007,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
                        ast_channel_unlock(c);
 
                        ast_channel_lock_both(original, in);
-                       ast_channel_publish_dial_forward(in, original, NULL, "CANCEL",
+                       ast_channel_publish_dial_forward(in, original, c, NULL, "CANCEL",
                                ast_channel_call_forward(c));
                        ast_channel_unlock(in);
                        ast_channel_unlock(original);
index 906dff1..d9f0f85 100644 (file)
@@ -4666,7 +4666,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
                                        ast_channel_unlock(qe->chan);
 
                                        ast_channel_lock_both(qe->chan, original);
-                                       ast_channel_publish_dial_forward(qe->chan, original, NULL, "CANCEL",
+                                       ast_channel_publish_dial_forward(qe->chan, original, o->chan, NULL, "CANCEL",
                                                ast_channel_call_forward(original));
                                        ast_channel_unlock(original);
                                        ast_channel_unlock(qe->chan);
index 519a4b6..8c27803 100644 (file)
@@ -518,12 +518,14 @@ void ast_channel_publish_dial(struct ast_channel *caller,
  *
  * \param caller The channel performing the dial operation
  * \param peer The channel being dialed
+ * \param forwarded The channel created as a result of the call forwarding
  * \param dialstring The information passed to the dialing application when beginning a dial
  * \param dialstatus The current status of the dial operation
  * \param forward The call forward string provided by the dialed channel
  */
 void ast_channel_publish_dial_forward(struct ast_channel *caller,
                struct ast_channel *peer,
+               struct ast_channel *forwarded,
                const char *dialstring,
                const char *dialstatus,
                const char *forward);
index 1343867..ca0b9c8 100644 (file)
@@ -465,14 +465,17 @@ static int handle_call_forward(struct ast_dial *dial, struct ast_dial_channel *c
        channel->device = ast_strdup(device);
        AST_LIST_UNLOCK(&dial->channels);
 
-
        /* Drop the original channel */
-       ast_hangup(original);
        channel->owner = NULL;
 
        /* Finally give it a go... send it out into the world */
        begin_dial_channel(channel, chan, chan ? 0 : 1, predial_string);
 
+       ast_channel_publish_dial_forward(chan, original, channel->owner, NULL, "CANCEL",
+               ast_channel_call_forward(original));
+
+       ast_hangup(original);
+
        return 0;
 }
 
index 38aac98..8a39bdf 100644 (file)
@@ -287,14 +287,21 @@ static void channel_blob_dtor(void *obj)
        ast_json_unref(event->blob);
 }
 
+/*! \brief Dummy callback for receiving events */
+static void dummy_event_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
+{
+}
+
 void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_channel *peer,
-       const char *dialstring, const char *dialstatus, const char *forward)
+       struct ast_channel *forwarded, const char *dialstring, const char *dialstatus,
+       const char *forward)
 {
        RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
        RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
        RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
        RAII_VAR(struct ast_channel_snapshot *, caller_snapshot, NULL, ao2_cleanup);
        RAII_VAR(struct ast_channel_snapshot *, peer_snapshot, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_channel_snapshot *, forwarded_snapshot, NULL, ao2_cleanup);
 
        ast_assert(peer != NULL);
        blob = ast_json_pack("{s: s, s: s, s: s}",
@@ -323,18 +330,33 @@ void ast_channel_publish_dial_forward(struct ast_channel *caller, struct ast_cha
        }
        ast_multi_channel_blob_add_channel(payload, "peer", peer_snapshot);
 
+       if (forwarded) {
+               forwarded_snapshot = ast_channel_snapshot_create(forwarded);
+               if (!forwarded_snapshot) {
+                       return;
+               }
+               ast_multi_channel_blob_add_channel(payload, "forwarded", forwarded_snapshot);
+       }
+
        msg = stasis_message_create(ast_channel_dial_type(), payload);
        if (!msg) {
                return;
        }
 
-       publish_message_for_channel_topics(msg, caller);
+       if (forwarded) {
+               struct stasis_subscription *subscription = stasis_subscribe(ast_channel_topic(peer), dummy_event_cb, NULL);
+
+               stasis_publish(ast_channel_topic(peer), msg);
+               stasis_unsubscribe_and_join(subscription);
+       } else {
+               publish_message_for_channel_topics(msg, caller);
+       }
 }
 
 void ast_channel_publish_dial(struct ast_channel *caller, struct ast_channel *peer,
        const char *dialstring, const char *dialstatus)
 {
-       ast_channel_publish_dial_forward(caller, peer, dialstring, dialstatus, NULL);
+       ast_channel_publish_dial_forward(caller, peer, NULL, dialstring, dialstatus, NULL);
 }
 
 static struct stasis_message *create_channel_blob_message(struct ast_channel_snapshot *snapshot,
@@ -931,11 +953,54 @@ static struct ast_json *hangup_request_to_json(
        return channel_blob_to_json(message, "ChannelHangupRequest", sanitize);
 }
 
+static struct ast_json *dial_to_json(
+       struct stasis_message *message,
+       const struct stasis_message_sanitizer *sanitize)
+{
+       struct ast_multi_channel_blob *payload = stasis_message_data(message);
+       struct ast_json *blob = ast_multi_channel_blob_get_json(payload);
+       struct ast_json *caller_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "caller"), sanitize);
+       struct ast_json *peer_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "peer"), sanitize);
+       struct ast_json *forwarded_json = ast_channel_snapshot_to_json(ast_multi_channel_blob_get_channel(payload, "forwarded"), sanitize);
+       struct ast_json *json;
+       const struct timeval *tv = stasis_message_timestamp(message);
+       int res = 0;
+
+       json = ast_json_pack("{s: s, s: o, s: O, s: O, s: O}",
+               "type", "Dial",
+               "timestamp", ast_json_timeval(*tv, NULL),
+               "dialstatus", ast_json_object_get(blob, "dialstatus"),
+               "forward", ast_json_object_get(blob, "forward"),
+               "dialstring", ast_json_object_get(blob, "dialstring"));
+       if (!json) {
+               return NULL;
+       }
+
+       if (caller_json) {
+               res |= ast_json_object_set(json, "caller", caller_json);
+       }
+       if (peer_json) {
+               res |= ast_json_object_set(json, "peer", peer_json);
+       }
+       if (forwarded_json) {
+               res |= ast_json_object_set(json, "forwarded", forwarded_json);
+       }
+
+       if (res) {
+               ast_json_unref(json);
+               return NULL;
+       }
+
+       return json;
+}
+
 /*!
  * @{ \brief Define channel message types.
  */
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_snapshot_type);
-STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type);
+STASIS_MESSAGE_TYPE_DEFN(ast_channel_dial_type,
+       .to_json = dial_to_json,
+       );
 STASIS_MESSAGE_TYPE_DEFN(ast_channel_varset_type,
        .to_ami = varset_to_ami,
        .to_json = varset_to_json,
index 7ddef27..d99240b 100644 (file)
@@ -2879,6 +2879,137 @@ ari_validator ast_ari_validate_device_state_changed_fn(void)
        return ast_ari_validate_device_state_changed;
 }
 
+int ast_ari_validate_dial(struct ast_json *json)
+{
+       int res = 1;
+       struct ast_json_iter *iter;
+       int has_type = 0;
+       int has_application = 0;
+       int has_dialstatus = 0;
+       int has_peer = 0;
+
+       for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+               if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_type = 1;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field type failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_application = 1;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field application failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_date(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field timestamp failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("caller", 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 Dial field caller failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("dialstatus", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_dialstatus = 1;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field dialstatus failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("dialstring", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field dialstring failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("forward", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       prop_is_valid = ast_ari_validate_string(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field forward failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("forwarded", 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 Dial field forwarded failed validation\n");
+                               res = 0;
+                       }
+               } else
+               if (strcmp("peer", ast_json_object_iter_key(iter)) == 0) {
+                       int prop_is_valid;
+                       has_peer = 1;
+                       prop_is_valid = ast_ari_validate_channel(
+                               ast_json_object_iter_value(iter));
+                       if (!prop_is_valid) {
+                               ast_log(LOG_ERROR, "ARI Dial field peer failed validation\n");
+                               res = 0;
+                       }
+               } else
+               {
+                       ast_log(LOG_ERROR,
+                               "ARI Dial has undocumented field %s\n",
+                               ast_json_object_iter_key(iter));
+                       res = 0;
+               }
+       }
+
+       if (!has_type) {
+               ast_log(LOG_ERROR, "ARI Dial missing required field type\n");
+               res = 0;
+       }
+
+       if (!has_application) {
+               ast_log(LOG_ERROR, "ARI Dial missing required field application\n");
+               res = 0;
+       }
+
+       if (!has_dialstatus) {
+               ast_log(LOG_ERROR, "ARI Dial missing required field dialstatus\n");
+               res = 0;
+       }
+
+       if (!has_peer) {
+               ast_log(LOG_ERROR, "ARI Dial missing required field peer\n");
+               res = 0;
+       }
+
+       return res;
+}
+
+ari_validator ast_ari_validate_dial_fn(void)
+{
+       return ast_ari_validate_dial;
+}
+
 int ast_ari_validate_endpoint_state_change(struct ast_json *json)
 {
        int res = 1;
@@ -3023,6 +3154,9 @@ int ast_ari_validate_event(struct ast_json *json)
        if (strcmp("DeviceStateChanged", discriminator) == 0) {
                return ast_ari_validate_device_state_changed(json);
        } else
+       if (strcmp("Dial", discriminator) == 0) {
+               return ast_ari_validate_dial(json);
+       } else
        if (strcmp("EndpointStateChange", discriminator) == 0) {
                return ast_ari_validate_endpoint_state_change(json);
        } else
@@ -3173,6 +3307,9 @@ int ast_ari_validate_message(struct ast_json *json)
        if (strcmp("DeviceStateChanged", discriminator) == 0) {
                return ast_ari_validate_device_state_changed(json);
        } else
+       if (strcmp("Dial", discriminator) == 0) {
+               return ast_ari_validate_dial(json);
+       } else
        if (strcmp("EndpointStateChange", discriminator) == 0) {
                return ast_ari_validate_endpoint_state_change(json);
        } else
index 1f9420c..22ab43b 100644 (file)
@@ -791,6 +791,24 @@ int ast_ari_validate_device_state_changed(struct ast_json *json);
 ari_validator ast_ari_validate_device_state_changed_fn(void);
 
 /*!
+ * \brief Validator for Dial.
+ *
+ * Dialing state has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_dial(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_dial().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_dial_fn(void);
+
+/*!
  * \brief Validator for EndpointStateChange.
  *
  * Endpoint state changed.
@@ -1187,6 +1205,16 @@ ari_validator ast_ari_validate_application_fn(void);
  * - application: string (required)
  * - timestamp: Date
  * - device_state: DeviceState (required)
+ * Dial
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - caller: Channel
+ * - dialstatus: string (required)
+ * - dialstring: string
+ * - forward: string
+ * - forwarded: Channel
+ * - peer: Channel (required)
  * EndpointStateChange
  * - type: string (required)
  * - application: string (required)
index 8ad41e5..8e9872a 100644 (file)
@@ -265,6 +265,25 @@ static void app_dtor(void *obj)
        app->data = NULL;
 }
 
+static void call_forwarded_handler(struct stasis_app *app, struct stasis_message *message)
+{
+       struct ast_multi_channel_blob *payload = stasis_message_data(message);
+       struct ast_channel_snapshot *snapshot = ast_multi_channel_blob_get_channel(payload, "forwarded");
+       struct ast_channel *chan;
+
+       if (!snapshot) {
+               return;
+       }
+
+       chan = ast_channel_get_by_name(snapshot->uniqueid);
+       if (!chan) {
+               return;
+       }
+
+       app_subscribe_channel(app, chan);
+       ast_channel_unref(chan);
+}
+
 static void sub_default_handler(void *data, struct stasis_subscription *sub,
        struct stasis_message *message)
 {
@@ -275,6 +294,10 @@ static void sub_default_handler(void *data, struct stasis_subscription *sub,
                ao2_cleanup(app);
        }
 
+       if (stasis_message_type(message) == ast_channel_dial_type()) {
+               call_forwarded_handler(app, message);
+       }
+
        /* By default, send any message that has a JSON representation */
        json = stasis_message_to_json(message, stasis_app_get_sanitizer());
        if (!json) {
index 5195a5b..a0c5408 100644 (file)
@@ -98,6 +98,7 @@
                                "ChannelHangupRequest",
                                "ChannelVarset",
                                "EndpointStateChange",
+                               "Dial",
                                "StasisEnd",
                                "StasisStart"
                        ]
                                }
                        }
                },
+               "Dial": {
+                       "id": "Dial",
+                       "description": "Dialing state has changed.",
+                       "properties": {
+                               "caller": {
+                                       "required": false,
+                                       "type": "Channel",
+                                       "description": "The calling channel."
+                               },
+                               "peer": {
+                                       "required": true,
+                                       "type": "Channel",
+                                       "description": "The dialed channel."
+                               },
+                               "forward": {
+                                       "required": false,
+                                       "type": "string",
+                                       "description": "Forwarding target requested by the original dialed channel."
+                               },
+                               "forwarded": {
+                                       "required": false,
+                                       "type": "Channel",
+                                       "description": "Channel that the caller has been forwarded to."
+                               },
+                               "dialstring": {
+                                       "required": false,
+                                       "type": "string",
+                                       "description": "The dial string for calling the peer channel."
+                               },
+                               "dialstatus": {
+                                       "required": true,
+                                       "type": "string",
+                                       "description": "Current status of the dialing attempt to the peer."
+                               }
+                       }
+               },
                "StasisEnd": {
                        "id": "StasisEnd",
                        "description": "Notification that a channel has left a Stasis application.",