From 4bf2473ac4ee2f37523745c643f8b09163aa8937 Mon Sep 17 00:00:00 2001
From: Moritz Fain <moritz@fain.io>
Date: Tue, 26 Apr 2022 00:40:49 +0200
Subject: [PATCH] ari: expose channel driver's unique id to ARI channel
 resource

This change exposes the channel driver's unique id (i.e. the Call-ID
for chan_sip/chan_pjsip based channels) to ARI channel resources
as `protocol_id`.

ASTERISK-30027
Reported by: Moritz Fain
Tested by: Moritz Fain

Change-Id: I7cc6e7a9d29efe74bc27811d788dac20fe559b87
---
 channels/chan_pjsip.c                            |  2 +-
 .../ari_add_pvt_id_to_channel_resource.txt       |  7 +++++++
 include/asterisk/stasis_channels.h               |  1 +
 main/channel_internal_api.c                      |  3 +++
 main/stasis_channels.c                           | 11 ++++++++---
 res/ari/ari_model_validators.c                   | 16 ++++++++++++++++
 res/ari/ari_model_validators.h                   |  1 +
 rest-api/api-docs/channels.json                  |  5 +++++
 tests/test_stasis_channels.c                     |  3 ++-
 9 files changed, 44 insertions(+), 5 deletions(-)
 create mode 100644 doc/CHANGES-staging/ari_add_pvt_id_to_channel_resource.txt

diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index b70b210cec..7c6b301080 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -1273,7 +1273,7 @@ static const char *chan_pjsip_get_uniqueid(struct ast_channel *ast)
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
 	char *uniqueid = ast_threadstorage_get(&uniqueid_threadbuf, UNIQUEID_BUFSIZE);
 
-	if (!uniqueid) {
+	if (!channel || !uniqueid) {
 		return "";
 	}
 
diff --git a/doc/CHANGES-staging/ari_add_pvt_id_to_channel_resource.txt b/doc/CHANGES-staging/ari_add_pvt_id_to_channel_resource.txt
new file mode 100644
index 0000000000..a4f008f967
--- /dev/null
+++ b/doc/CHANGES-staging/ari_add_pvt_id_to_channel_resource.txt
@@ -0,0 +1,7 @@
+Subject: ari
+Subject: stasis_channels
+
+Expose channel driver's unique id (which is the Call-ID for SIP/PJSIP)
+to ARI channel resources as 'protocol_id'.
+
+ASTERISK-30027
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 80c79ebd2e..73771864a6 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -112,6 +112,7 @@ struct ast_channel_snapshot_base {
 	);
 	struct timeval creationtime; /*!< The time of channel creation */
 	int tech_properties;         /*!< Properties of the channel's technology */
+	AST_STRING_FIELD_EXTENDED(protocol_id); /*!< Channel driver protocol id (i.e. Call-ID for chan_sip/chan_pjsip) */
 };
 
 /*!
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 131faae452..123f61aebd 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -587,6 +587,9 @@ void *ast_channel_tech_pvt(const struct ast_channel *chan)
 void ast_channel_tech_pvt_set(struct ast_channel *chan, void *value)
 {
 	chan->tech_pvt = value;
+	if (value != NULL) {
+		ast_channel_snapshot_invalidate_segment(chan, AST_CHANNEL_SNAPSHOT_INVALIDATE_BASE);
+	}
 }
 void *ast_channel_timingdata(const struct ast_channel *chan)
 {
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 8becf96f89..d373f6a5ef 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -273,7 +273,7 @@ static struct ast_channel_snapshot_base *channel_snapshot_base_create(struct ast
 		return NULL;
 	}
 
-	if (ast_string_field_init(snapshot, 256)) {
+	if (ast_string_field_init(snapshot, 256) || ast_string_field_init_extended(snapshot, protocol_id)) {
 		ao2_ref(snapshot, -1);
 		return NULL;
 	}
@@ -288,6 +288,10 @@ static struct ast_channel_snapshot_base *channel_snapshot_base_create(struct ast
 	snapshot->creationtime = ast_channel_creationtime(chan);
 	snapshot->tech_properties = ast_channel_tech(chan)->properties;
 
+	if (ast_channel_tech(chan)->get_pvt_uniqueid) {
+		ast_string_field_set(snapshot, protocol_id, ast_channel_tech(chan)->get_pvt_uniqueid(chan));
+	}
+
 	return snapshot;
 }
 
@@ -1266,14 +1270,15 @@ struct ast_json *ast_channel_snapshot_to_json(
 	}
 
 	json_chan = ast_json_pack(
-		/* Broken up into groups of three for readability */
-		"{ s: s, s: s, s: s,"
+		/* Broken up into groups for readability */
+		"{ s: s, s: s, s: s, s: s,"
 		"  s: o, s: o, s: s,"
 		"  s: o, s: o, s: s }",
 		/* First line */
 		"id", snapshot->base->uniqueid,
 		"name", snapshot->base->name,
 		"state", ast_state2str(snapshot->state),
+		"protocol_id", snapshot->base->protocol_id,
 		/* Second line */
 		"caller", ast_json_name_number(
 			snapshot->caller->name, snapshot->caller->number),
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 0bbbb195e2..fffb87a162 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1043,6 +1043,7 @@ int ast_ari_validate_channel(struct ast_json *json)
 	int has_id = 0;
 	int has_language = 0;
 	int has_name = 0;
+	int has_protocol_id = 0;
 	int has_state = 0;
 
 	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
@@ -1135,6 +1136,16 @@ int ast_ari_validate_channel(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("protocol_id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_protocol_id = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field protocol_id failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_state = 1;
@@ -1193,6 +1204,11 @@ int ast_ari_validate_channel(struct ast_json *json)
 		res = 0;
 	}
 
+	if (!has_protocol_id) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field protocol_id\n");
+		res = 0;
+	}
+
 	if (!has_state) {
 		ast_log(LOG_ERROR, "ARI Channel missing required field state\n");
 		res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 583cba33a4..64f167c07c 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1353,6 +1353,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * - id: string (required)
  * - language: string (required)
  * - name: string (required)
+ * - protocol_id: string (required)
  * - state: string (required)
  * Dialed
  * DialplanCEP
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 04d1577337..269976dfa4 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -2126,6 +2126,11 @@
 					"type": "string",
 					"description": "Unique identifier of the channel.\n\nThis is the same as the Uniqueid field in AMI."
 				},
+				"protocol_id": {
+					"required": true,
+					"type": "string",
+					"description": "Protocol id from underlying channel driver (i.e. Call-ID for chan_sip/chan_pjsip; will be empty if not applicable or not implemented by driver)."
+				},
 				"name": {
 					"required": true,
 					"type": "string",
diff --git a/tests/test_stasis_channels.c b/tests/test_stasis_channels.c
index d4a05b206f..5ea9dd807c 100644
--- a/tests/test_stasis_channels.c
+++ b/tests/test_stasis_channels.c
@@ -273,7 +273,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 	ast_test_validate(test, NULL != snapshot);
 
 	actual = ast_channel_snapshot_to_json(snapshot, NULL);
-	expected = ast_json_pack("{ s: s, s: s, s: s, s: s,"
+	expected = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s,"
 				 "  s: { s: s, s: s, s: i, s: s, s: s },"
 				 "  s: { s: s, s: s },"
 				 "  s: { s: s, s: s },"
@@ -284,6 +284,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 				 "state", "Down",
 				 "accountcode", "acctcode",
 				 "id", ast_channel_uniqueid(chan),
+				 "protocol_id", "",
 				 "dialplan",
 				 "context", "context",
 				 "exten", "exten",
-- 
GitLab