commit 837eb1885106cebff6d6c7ca6c28b41224ce381a
Author: Mark Michelson <mmichelson@digium.com>
Date:   Wed Oct 12 15:21:05 2016 -0500

    Add support for ARI channel variables.
    
    Change-Id: I2ca680690223e53bfa6f5e98c3d5025f48aa34b3

diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 3293dd7..957c81b 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -4330,6 +4330,36 @@ void ast_channel_set_manager_vars(size_t varc, char **vars);
 struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan);
 
 /*!
+ * \since 14.1.0
+ * \brief Return whether or not any ARI variables have been set
+ *
+ * \retval 0 if no ARI variables are expected
+ * \retval 1 if ARI variables are expected
+ */
+int ast_channel_has_ari_vars(void);
+
+/*!
+ * \since 14.1.0
+ * \brief Sets the variables to be stored in the \a ari_vars field of all
+ * snapshots.
+ * \param varc Number of variable names.
+ * \param vars Array of variable names.
+ */
+void ast_channel_set_ari_vars(size_t varc, char **vars);
+
+/*!
+ * \since 14.1.0
+ * \brief Gets the variables for a given channel, as specified by ast_channel_set_ari_vars().
+ *
+ * The returned variable list is an AO2 object, so ao2_cleanup() to free it.
+ *
+ * \param chan Channel to get variables for.
+ * \return List of channel variables.
+ * \return \c NULL on error
+ */
+struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan);
+
+/*!
  * \since 12
  * \brief Gets the variables for a given channel, as set using pbx_builtin_setvar_helper().
  *
diff --git a/include/asterisk/json.h b/include/asterisk/json.h
index 28ebfbd..886a588 100644
--- a/include/asterisk/json.h
+++ b/include/asterisk/json.h
@@ -1041,6 +1041,9 @@ enum ast_json_to_ast_vars_code {
  */
 enum ast_json_to_ast_vars_code ast_json_to_ast_variables(struct ast_json *json_variables, struct ast_variable **variables);
 
+struct varshead;
+struct ast_json *ast_json_channel_vars(struct varshead *channelvars);
+
 /*!@}*/
 
 #endif /* _ASTERISK_JSON_H */
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 6c6cd51..deb79b0 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -73,6 +73,7 @@ struct ast_channel_snapshot {
 	struct ast_flags softhangup_flags;      /*!< softhangup channel flags */
 	struct varshead *manager_vars;          /*!< Variables to be appended to manager events */
 	int tech_properties;                    /*!< Properties of the channel's technology */
+	struct varshead *ari_vars;              /*!< Variables to be appended to ARI events */
 };
 
 /*!
diff --git a/main/channel.c b/main/channel.c
index 94505ad..a1bbfc8 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -7661,35 +7661,48 @@ struct manager_channel_variable {
 	char name[];
 };
 
-static AST_RWLIST_HEAD_STATIC(channelvars, manager_channel_variable);
+AST_RWLIST_HEAD(external_vars, manager_channel_variable);
 
-static void free_channelvars(void)
+static struct external_vars ami_vars;
+static struct external_vars ari_vars;
+
+static void free_external_channelvars(struct external_vars *channelvars)
 {
 	struct manager_channel_variable *var;
-	AST_RWLIST_WRLOCK(&channelvars);
-	while ((var = AST_RWLIST_REMOVE_HEAD(&channelvars, entry))) {
+	AST_RWLIST_WRLOCK(channelvars);
+	while ((var = AST_RWLIST_REMOVE_HEAD(channelvars, entry))) {
 		ast_free(var);
 	}
-	AST_RWLIST_UNLOCK(&channelvars);
+	AST_RWLIST_UNLOCK(channelvars);
 }
 
-int ast_channel_has_manager_vars(void)
+static int channel_has_external_vars(struct external_vars *channelvars)
 {
 	int vars_present;
 
-	AST_RWLIST_RDLOCK(&channelvars);
-	vars_present = !AST_LIST_EMPTY(&channelvars);
-	AST_RWLIST_UNLOCK(&channelvars);
+	AST_RWLIST_RDLOCK(channelvars);
+	vars_present = !AST_LIST_EMPTY(channelvars);
+	AST_RWLIST_UNLOCK(channelvars);
 
 	return vars_present;
 }
 
-void ast_channel_set_manager_vars(size_t varc, char **vars)
+int ast_channel_has_manager_vars(void)
+{
+	return channel_has_external_vars(&ami_vars);
+}
+
+int ast_channel_has_ari_vars(void)
+{
+	return channel_has_external_vars(&ari_vars);
+}
+
+static void channel_set_external_vars(struct external_vars *channelvars, size_t varc, char **vars)
 {
 	size_t i;
 
-	free_channelvars();
-	AST_RWLIST_WRLOCK(&channelvars);
+	free_external_channelvars(channelvars);
+	AST_RWLIST_WRLOCK(channelvars);
 	for (i = 0; i < varc; ++i) {
 		const char *var = vars[i];
 		struct manager_channel_variable *mcv;
@@ -7700,9 +7713,20 @@ void ast_channel_set_manager_vars(size_t varc, char **vars)
 		if (strchr(var, '(')) {
 			mcv->isfunc = 1;
 		}
-		AST_RWLIST_INSERT_TAIL(&channelvars, mcv, entry);
+		AST_RWLIST_INSERT_TAIL(channelvars, mcv, entry);
 	}
-	AST_RWLIST_UNLOCK(&channelvars);
+	AST_RWLIST_UNLOCK(channelvars);
+
+}
+
+void ast_channel_set_manager_vars(size_t varc, char **vars)
+{
+	channel_set_external_vars(&ami_vars, varc, vars);
+}
+
+void ast_channel_set_ari_vars(size_t varc, char **vars)
+{
+	channel_set_external_vars(&ari_vars, varc, vars);
 }
 
 /*!
@@ -7744,14 +7768,15 @@ struct varshead *ast_channel_get_vars(struct ast_channel *chan)
 	return ret;
 }
 
-struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan)
+static struct varshead *channel_get_external_vars(struct external_vars *channelvars,
+	struct ast_channel *chan)
 {
 	RAII_VAR(struct varshead *, ret, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_str *, tmp, NULL, ast_free);
 	struct manager_channel_variable *mcv;
-	SCOPED_LOCK(lock, &channelvars, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+	SCOPED_LOCK(lock, channelvars, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
 
-	if (AST_LIST_EMPTY(&channelvars)) {
+	if (AST_LIST_EMPTY(channelvars)) {
 		return NULL;
 	}
 
@@ -7762,7 +7787,7 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan)
 		return NULL;
 	}
 
-	AST_LIST_TRAVERSE(&channelvars, mcv, entry) {
+	AST_LIST_TRAVERSE(channelvars, mcv, entry) {
 		const char *val = NULL;
 		struct ast_var_t *var;
 
@@ -7787,11 +7812,23 @@ struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan)
 
 	ao2_ref(ret, +1);
 	return ret;
+
+}
+
+struct varshead *ast_channel_get_manager_vars(struct ast_channel *chan)
+{
+	return channel_get_external_vars(&ami_vars, chan);
+}
+
+struct varshead *ast_channel_get_ari_vars(struct ast_channel *chan)
+{
+	return channel_get_external_vars(&ari_vars, chan);
 }
 
 static void channels_shutdown(void)
 {
-	free_channelvars();
+	free_external_channelvars(&ami_vars);
+	free_external_channelvars(&ari_vars);
 
 	ast_data_unregister(NULL);
 	ast_cli_unregister_multiple(cli_channel, ARRAY_LEN(cli_channel));
@@ -7824,6 +7861,9 @@ int ast_channels_init(void)
 
 	ast_register_cleanup(channels_shutdown);
 
+	AST_RWLIST_HEAD_INIT(&ami_vars);
+	AST_RWLIST_HEAD_INIT(&ari_vars);
+
 	return 0;
 }
 
diff --git a/main/json.c b/main/json.c
index 12d5fc8..efc861c 100644
--- a/main/json.c
+++ b/main/json.c
@@ -926,3 +926,16 @@ enum ast_json_to_ast_vars_code ast_json_to_ast_variables(struct ast_json *json_v
 
 	return AST_JSON_TO_AST_VARS_CODE_SUCCESS;
 }
+
+struct ast_json *ast_json_channel_vars(struct varshead *channelvars)
+{
+	struct ast_json *ret;
+	struct ast_var_t *var;
+
+	ret = ast_json_object_create();
+	AST_LIST_TRAVERSE(channelvars, var, entries) {
+		ast_json_object_set(ret, var->name, ast_json_string_create(var->value));
+	}
+
+	return ret;
+}
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index e1c50c6..496def8 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -272,6 +272,7 @@ struct ast_channel_snapshot *ast_channel_snapshot_create(struct ast_channel *cha
 	ast_set_flag(&snapshot->softhangup_flags, ast_channel_softhangup_internal_flag(chan));
 
 	snapshot->manager_vars = ast_channel_get_manager_vars(chan);
+	snapshot->ari_vars = ast_channel_get_ari_vars(chan);
 	snapshot->tech_properties = ast_channel_tech(chan)->properties;
 
 	return snapshot;
@@ -920,6 +921,10 @@ struct ast_json *ast_channel_snapshot_to_json(
 		"creationtime", ast_json_timeval(snapshot->creationtime, NULL),
 		"language", snapshot->language);
 
+	if (snapshot->ari_vars && !AST_LIST_EMPTY(snapshot->ari_vars)) {
+		ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(snapshot->ari_vars));
+	}
+
 	return ast_json_ref(json_chan);
 }
 
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 8f05db0..4ce220a 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1053,6 +1053,15 @@ int ast_ari_validate_channel(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("channelvars", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_object(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field channelvars failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("connected", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_connected = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 2634528..3a560d7 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1432,6 +1432,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * Channel
  * - accountcode: string (required)
  * - caller: CallerID (required)
+ * - channelvars: object
  * - connected: CallerID (required)
  * - creationtime: Date (required)
  * - dialplan: DialplanCEP (required)
diff --git a/res/ari/config.c b/res/ari/config.c
index f8fdc27..5326a62 100644
--- a/res/ari/config.c
+++ b/res/ari/config.c
@@ -28,6 +28,8 @@ ASTERISK_REGISTER_FILE()
 
 #include "asterisk/config_options.h"
 #include "asterisk/http_websocket.h"
+#include "asterisk/app.h"
+#include "asterisk/channel.h"
 #include "internal.h"
 
 /*! \brief Locking container for safe configuration access. */
@@ -318,6 +320,22 @@ static int process_config(int reload)
 	return 0;
 }
 
+#define MAX_VARS 128
+
+static int channelvars_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	char *parse = NULL;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(vars)[MAX_VARS];
+	);
+
+	parse = ast_strdupa(var->value);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	ast_channel_set_ari_vars(args.argc, args.vars);
+	return 0;
+}
+
 int ast_ari_config_init(void)
 {
 	if (aco_info_init(&cfg_info)) {
@@ -341,6 +359,8 @@ int ast_ari_config_init(void)
 	aco_option_register(&cfg_info, "websocket_write_timeout", ACO_EXACT, general_options,
 		AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR, OPT_INT_T, PARSE_IN_RANGE,
 		FLDSET(struct ast_ari_conf_general, write_timeout), 1, INT_MAX);
+	aco_option_register_custom(&cfg_info, "channelvars", ACO_EXACT, general_options,
+		"", channelvars_handler, 0);
 
 	/* ARI type=user category options */
 	aco_option_register(&cfg_info, "type", ACO_EXACT, user, NULL,
diff --git a/res/res_ari.c b/res/res_ari.c
index eb15a88..f976723 100644
--- a/res/res_ari.c
+++ b/res/res_ari.c
@@ -112,6 +112,9 @@
 				<configOption name="allowed_origins">
 					<synopsis>Comma separated list of allowed origins, for Cross-Origin Resource Sharing. May be set to * to allow all origins.</synopsis>
 				</configOption>
+				<configOption name="channelvars">
+					<synopsis>Comma separated list of channel variables to display in channel json.</synopsis>
+				</configOption>
 			</configObject>
 
 			<configObject name="user">
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 75feeb1..6e5a82b 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -1746,6 +1746,11 @@
 					"required": true,
 					"type": "string",
 					"description": "The default spoken language"
+				},
+				"channelvars": {
+					"required": false,
+					"type": "object",
+					"description": "Channel variables"
 				}
 			}
 		}
