From 3a5cc054ed3efb0733a70a6945880b71506b5757 Mon Sep 17 00:00:00 2001
From: Joshua Colp <jcolp@digium.com>
Date: Sat, 14 Dec 2013 17:19:41 +0000
Subject: [PATCH] res_stasis: Expose event for call forwarding and follow
 forwarded channel.

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                    |   2 +-
 apps/app_queue.c                   |   2 +-
 include/asterisk/stasis_channels.h |   2 +
 main/dial.c                        |   7 +-
 main/stasis_channels.c             |  73 ++++++++++++++-
 res/ari/ari_model_validators.c     | 137 +++++++++++++++++++++++++++++
 res/ari/ari_model_validators.h     |  28 ++++++
 res/stasis/app.c                   |  23 +++++
 rest-api/api-docs/events.json      |  37 ++++++++
 9 files changed, 303 insertions(+), 8 deletions(-)

diff --git a/apps/app_dial.c b/apps/app_dial.c
index c9bee19b0b..fdbe05692d 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -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);
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 906dff15fc..d9f0f85d3f 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -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);
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 519a4b676e..8c27803dfd 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -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);
diff --git a/main/dial.c b/main/dial.c
index 134386735a..ca0b9c8d17 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -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;
 }
 
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 38aac982e3..8a39bdfcb2 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -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,
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 7ddef278df..d99240bd86 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -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
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 1f9420cb0a..22ab43be89 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -790,6 +790,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.
  *
@@ -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)
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 8ad41e565b..8e9872aec3 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -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) {
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 5195a5bbbd..a0c5408fc9 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -98,6 +98,7 @@
 				"ChannelHangupRequest",
 				"ChannelVarset",
 				"EndpointStateChange",
+				"Dial",
 				"StasisEnd",
 				"StasisStart"
 			]
@@ -411,6 +412,42 @@
 				}
 			}
 		},
+		"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.",
-- 
GitLab