[Asterisk-code-review] res pjsip pubsub.c: Implement "pjsip show subscriptions" com... (asterisk[14])

George Joseph asteriskteam at digium.com
Tue Jan 24 15:47:26 CST 2017


George Joseph has submitted this change and it was merged. ( https://gerrit.asterisk.org/4746 )

Change subject: res_pjsip_pubsub.c: Implement "pjsip show subscriptions" commands.
......................................................................


res_pjsip_pubsub.c: Implement "pjsip show subscriptions" commands.

ASTERISK-23828 #close

Change-Id: Ifb8a3b61f447aedc58a8e6b36a810f7566018567
---
M res/res_pjsip_pubsub.c
1 file changed, 548 insertions(+), 8 deletions(-)

Approvals:
  George Joseph: Looks good to me, but someone else must approve; Verified
  Joshua Colp: Looks good to me, approved



diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 5799379..42f0dc1 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -42,6 +42,7 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/callerid.h"
 #include "asterisk/manager.h"
+#include "asterisk/cli.h"
 #include "asterisk/test.h"
 #include "res_pjsip/include/res_pjsip_private.h"
 #include "asterisk/res_pjsip_presence_xml.h"
@@ -3694,6 +3695,541 @@
 #define AMI_SHOW_SUBSCRIPTIONS_INBOUND "PJSIPShowSubscriptionsInbound"
 #define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND "PJSIPShowSubscriptionsOutbound"
 
+#define MAX_REGEX_ERROR_LEN 128
+
+struct cli_sub_parms {
+	/*! CLI handler entry e parameter */
+	struct ast_cli_entry *e;
+	/*! CLI handler entry a parameter */
+	struct ast_cli_args *a;
+	/*! CLI subscription entry output line(s) */
+	struct ast_str *buf;
+	/*! Compiled regular expression to select if buf is written to CLI when not NULL. */
+	regex_t *like;
+	int count;
+};
+
+struct cli_sub_complete_parms {
+	struct ast_cli_args *a;
+	/*! Found callid for search position */
+	char *callid;
+	int wordlen;
+	int which;
+};
+
+static int cli_complete_subscription_common(struct sip_subscription_tree *sub_tree, struct cli_sub_complete_parms *cli)
+{
+	pj_str_t *callid;
+
+	if (!sub_tree->dlg) {
+		return 0;
+	}
+
+	callid = &sub_tree->dlg->call_id->id;
+	if (cli->wordlen <= pj_strlen(callid)
+		&& !strncasecmp(cli->a->word, pj_strbuf(callid), cli->wordlen)
+		&& (++cli->which > cli->a->n)) {
+		cli->callid = ast_malloc(pj_strlen(callid) + 1);
+		if (cli->callid) {
+			ast_copy_pj_str(cli->callid, callid, pj_strlen(callid) + 1);
+		}
+		return -1;
+	}
+	return 0;
+}
+
+static int cli_complete_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_NOTIFIER
+		? cli_complete_subscription_common(sub_tree, arg) : 0;
+}
+
+static int cli_complete_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_SUBSCRIBER
+		? cli_complete_subscription_common(sub_tree, arg) : 0;
+}
+
+static char *cli_complete_subscription_callid(struct ast_cli_args *a)
+{
+	struct cli_sub_complete_parms cli;
+	on_subscription_t on_subscription;
+
+	if (a->pos != 4) {
+		return NULL;
+	}
+
+	if (!strcasecmp(a->argv[3], "inbound")) {
+		on_subscription = cli_complete_subscription_inbound;
+	} else if (!strcasecmp(a->argv[3], "outbound")) {
+		on_subscription = cli_complete_subscription_outbound;
+	} else {
+		/* Should never get here */
+		ast_assert(0);
+		return NULL;
+	}
+
+	cli.a = a;
+	cli.callid = NULL;
+	cli.wordlen = strlen(a->word);
+	cli.which = 0;
+	for_each_subscription(on_subscription, &cli);
+
+	return cli.callid;
+}
+
+static int cli_subscription_expiry(struct sip_subscription_tree *sub_tree)
+{
+	int expiry;
+
+	expiry = sub_tree->persistence
+		? ast_tvdiff_ms(sub_tree->persistence->expires, ast_tvnow()) / 1000
+		: 0;
+	if (expiry < 0) {
+		/* Subscription expired */
+		expiry = 0;
+	}
+	return expiry;
+}
+
+static int cli_show_subscription_common(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
+{
+	const char *callid = (const char *) cli->buf;/* Member repurposed to pass in callid */
+	pj_str_t *sub_callid;
+	struct ast_str *buf;
+	char *src;
+	char *dest;
+	char *key;
+	char *value;
+	char *value_end;
+	int key_len;
+	int key_filler_width;
+	int value_len;
+
+	if (!sub_tree->dlg) {
+		return 0;
+	}
+	sub_callid = &sub_tree->dlg->call_id->id;
+	if (pj_strcmp2(sub_callid, callid)) {
+		return 0;
+	}
+
+	buf = ast_str_create(512);
+	if (!buf) {
+		return -1;
+	}
+
+	ast_cli(cli->a->fd,
+		"%-20s: %s\n"
+		"===========================================================================\n",
+		"ParameterName", "ParameterValue");
+
+	ast_str_append(&buf, 0, "Resource: %s\n", sub_tree->root->resource);
+	ast_str_append(&buf, 0, "Event: %s\n", sub_tree->root->handler->event_name);
+	ast_str_append(&buf, 0, "Expiry: %d\n", cli_subscription_expiry(sub_tree));
+
+	sip_subscription_to_ami(sub_tree, &buf);
+
+	/* Convert AMI \r\n to \n line terminators. */
+	src = strchr(ast_str_buffer(buf), '\r');
+	if (src) {
+		dest = src;
+		++src;
+		while (*src) {
+			if (*src == '\r') {
+				++src;
+				continue;
+			}
+			*dest++ = *src++;
+		}
+		*dest = '\0';
+		ast_str_update(buf);
+	}
+
+	/* Reformat AMI key value pairs to pretty columns */
+	key = ast_str_buffer(buf);
+	do {
+		value = strchr(key, ':');
+		if (!value) {
+			break;
+		}
+		value_end = strchr(value, '\n');
+		if (!value_end) {
+			break;
+		}
+
+		/* Calculate field lengths */
+		key_len = value - key;
+		key_filler_width = 20 - key_len;
+		if (key_filler_width < 0) {
+			key_filler_width = 0;
+		}
+		value_len = value_end - value;
+
+		ast_cli(cli->a->fd, "%.*s%*s%.*s\n",
+			key_len, key, key_filler_width, "",
+			value_len, value);
+
+		key = value_end + 1;
+	} while (*key);
+	ast_cli(cli->a->fd, "\n");
+
+	ast_free(buf);
+
+	return -1;
+}
+
+static int cli_show_subscription_inbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_NOTIFIER
+		? cli_show_subscription_common(sub_tree, arg) : 0;
+}
+
+static int cli_show_subscription_outbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_SUBSCRIBER
+		? cli_show_subscription_common(sub_tree, arg) : 0;
+}
+
+static char *cli_show_subscription_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	on_subscription_t on_subscription;
+	struct cli_sub_parms cli;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjsip show subscription {inbound|outbound}";
+		e->usage = "Usage:\n"
+				   "   pjsip show subscription inbound <call-id>\n"
+				   "   pjsip show subscription outbound <call-id>\n"
+				   "      Show active subscription with the dialog call-id\n";
+		return NULL;
+	case CLI_GENERATE:
+		return cli_complete_subscription_callid(a);
+	}
+
+	if (a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!strcasecmp(a->argv[3], "inbound")) {
+		on_subscription = cli_show_subscription_inbound;
+	} else if (!strcasecmp(a->argv[3], "outbound")) {
+		on_subscription = cli_show_subscription_outbound;
+	} else {
+		/* Should never get here */
+		ast_assert(0);
+		return NULL;
+	}
+
+	/* Find the subscription with the specified call-id */
+	cli.a = a;
+	cli.e = e;
+	cli.buf = (void *) a->argv[4];/* Repurpose the buf member to pass in callid */
+	for_each_subscription(on_subscription, &cli);
+
+	return CLI_SUCCESS;
+}
+
+#define CLI_SHOW_SUB_FORMAT_HEADER \
+	"Endpoint: <Endpoint/Caller-ID.............................................>\n" \
+	"Resource: <Resource/Event.................................................>\n" \
+	"  Expiry: <Expiry>  <Call-id..............................................>\n" \
+	"===========================================================================\n\n"
+#define CLI_SHOW_SUB_FORMAT_ENTRY  \
+	"Endpoint: %s/%s\n" \
+	"Resource: %s/%s\n" \
+	"  Expiry: %8d  %s\n\n"
+
+static int cli_show_subscriptions_detail(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
+{
+	char caller_id[256];
+	char callid[256];
+
+	ast_callerid_merge(caller_id, sizeof(caller_id),
+		S_COR(sub_tree->endpoint->id.self.name.valid,
+			sub_tree->endpoint->id.self.name.str, NULL),
+		S_COR(sub_tree->endpoint->id.self.number.valid,
+			sub_tree->endpoint->id.self.number.str, NULL),
+		"<none>");
+
+	/* Call-id */
+	if (sub_tree->dlg) {
+		ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
+	} else {
+		ast_copy_string(callid, "<unknown>", sizeof(callid));
+	}
+
+	ast_str_set(&cli->buf, 0, CLI_SHOW_SUB_FORMAT_ENTRY,
+		ast_sorcery_object_get_id(sub_tree->endpoint), caller_id,
+		sub_tree->root->resource, sub_tree->root->handler->event_name,
+		cli_subscription_expiry(sub_tree), callid);
+
+	if (cli->like) {
+		if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
+			/* Output line did not match the regex */
+			return 0;
+		}
+	}
+
+	ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
+	++cli->count;
+
+	return 0;
+}
+
+static int cli_show_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_NOTIFIER
+		? cli_show_subscriptions_detail(sub_tree, arg) : 0;
+}
+
+static int cli_show_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_SUBSCRIBER
+		? cli_show_subscriptions_detail(sub_tree, arg) : 0;
+}
+
+static char *cli_show_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	on_subscription_t on_subscription;
+	struct cli_sub_parms cli;
+	regex_t like;
+	const char *regex;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjsip show subscriptions {inbound|outbound} [like]";
+		e->usage = "Usage:\n"
+				   "   pjsip show subscriptions inbound [like <regex>]\n"
+				   "      Show active inbound subscriptions\n"
+				   "   pjsip show subscriptions outbound [like <regex>]\n"
+				   "      Show active outbound subscriptions\n"
+				   "\n"
+				   "   The regex selects a subscriptions output that matches.\n"
+				   "   i.e.,  All output lines for a subscription are checked\n"
+				   "   as a block by the regex.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4 && a->argc != 6) {
+		return CLI_SHOWUSAGE;
+	}
+	if (!strcasecmp(a->argv[3], "inbound")) {
+		on_subscription = cli_show_subscriptions_inbound;
+	} else if (!strcasecmp(a->argv[3], "outbound")) {
+		on_subscription = cli_show_subscriptions_outbound;
+	} else {
+		/* Should never get here */
+		ast_assert(0);
+		return CLI_SHOWUSAGE;
+	}
+	if (a->argc == 6) {
+		int rc;
+
+		if (strcasecmp(a->argv[4], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+
+		/* Setup regular expression */
+		memset(&like, 0, sizeof(like));
+		cli.like = &like;
+		regex = a->argv[5];
+		rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
+		if (rc) {
+			char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
+
+			regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
+			ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
+				regex, regerr);
+			return CLI_FAILURE;
+		}
+	} else {
+		cli.like = NULL;
+		regex = NULL;
+	}
+
+	cli.a = a;
+	cli.e = e;
+	cli.count = 0;
+	cli.buf = ast_str_create(256);
+	if (!cli.buf) {
+		if (cli.like) {
+			regfree(cli.like);
+		}
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, CLI_SHOW_SUB_FORMAT_HEADER);
+	for_each_subscription(on_subscription, &cli);
+	ast_cli(a->fd, "%d active subscriptions%s%s%s\n",
+		cli.count,
+		regex ? " matched \"" : "",
+		regex ?: "",
+		regex ? "\"" : "");
+
+	ast_free(cli.buf);
+	if (cli.like) {
+		regfree(cli.like);
+	}
+
+	return CLI_SUCCESS;
+}
+
+#define CLI_LIST_SUB_FORMAT_HEADER "%-30.30s %-30.30s %6.6s %s\n"
+#define CLI_LIST_SUB_FORMAT_ENTRY  "%-30.30s %-30.30s %6d %s\n"
+
+static int cli_list_subscriptions_detail(struct sip_subscription_tree *sub_tree, struct cli_sub_parms *cli)
+{
+	char ep_cid_buf[50];
+	char res_evt_buf[50];
+	char callid[256];
+
+	/* Endpoint/CID column */
+	snprintf(ep_cid_buf, sizeof(ep_cid_buf), "%s/%s",
+		ast_sorcery_object_get_id(sub_tree->endpoint),
+		S_COR(sub_tree->endpoint->id.self.name.valid, sub_tree->endpoint->id.self.name.str,
+			S_COR(sub_tree->endpoint->id.self.number.valid,
+				sub_tree->endpoint->id.self.number.str, "<none>")));
+
+	/* Resource/Event column */
+	snprintf(res_evt_buf, sizeof(res_evt_buf), "%s/%s",
+		sub_tree->root->resource,
+		sub_tree->root->handler->event_name);
+
+	/* Call-id column */
+	if (sub_tree->dlg) {
+		ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid));
+	} else {
+		ast_copy_string(callid, "<unknown>", sizeof(callid));
+	}
+
+	ast_str_set(&cli->buf, 0, CLI_LIST_SUB_FORMAT_ENTRY,
+		ep_cid_buf,
+		res_evt_buf,
+		cli_subscription_expiry(sub_tree),
+		callid);
+
+	if (cli->like) {
+		if (regexec(cli->like, ast_str_buffer(cli->buf), 0, NULL, 0)) {
+			/* Output line did not match the regex */
+			return 0;
+		}
+	}
+
+	ast_cli(cli->a->fd, "%s", ast_str_buffer(cli->buf));
+	++cli->count;
+
+	return 0;
+}
+
+static int cli_list_subscriptions_inbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_NOTIFIER
+		? cli_list_subscriptions_detail(sub_tree, arg) : 0;
+}
+
+static int cli_list_subscriptions_outbound(struct sip_subscription_tree *sub_tree, void *arg)
+{
+	return sub_tree->role == AST_SIP_SUBSCRIBER
+		? cli_list_subscriptions_detail(sub_tree, arg) : 0;
+}
+
+static char *cli_list_subscriptions_inout(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	on_subscription_t on_subscription;
+	struct cli_sub_parms cli;
+	regex_t like;
+	const char *regex;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjsip list subscriptions {inbound|outbound} [like]";
+		e->usage = "Usage:\n"
+				   "   pjsip list subscriptions inbound [like <regex>]\n"
+				   "      List active inbound subscriptions\n"
+				   "   pjsip list subscriptions outbound [like <regex>]\n"
+				   "      List active outbound subscriptions\n"
+				   "\n"
+				   "   The regex selects output lines that match.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4 && a->argc != 6) {
+		return CLI_SHOWUSAGE;
+	}
+	if (!strcasecmp(a->argv[3], "inbound")) {
+		on_subscription = cli_list_subscriptions_inbound;
+	} else if (!strcasecmp(a->argv[3], "outbound")) {
+		on_subscription = cli_list_subscriptions_outbound;
+	} else {
+		/* Should never get here */
+		ast_assert(0);
+		return CLI_SHOWUSAGE;
+	}
+	if (a->argc == 6) {
+		int rc;
+
+		if (strcasecmp(a->argv[4], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+
+		/* Setup regular expression */
+		memset(&like, 0, sizeof(like));
+		cli.like = &like;
+		regex = a->argv[5];
+		rc = regcomp(cli.like, regex, REG_EXTENDED | REG_NOSUB);
+		if (rc) {
+			char *regerr = ast_alloca(MAX_REGEX_ERROR_LEN);
+
+			regerror(rc, cli.like, regerr, MAX_REGEX_ERROR_LEN);
+			ast_cli(a->fd, "Regular expression '%s' failed to compile: %s\n",
+				regex, regerr);
+			return CLI_FAILURE;
+		}
+	} else {
+		cli.like = NULL;
+		regex = NULL;
+	}
+
+	cli.a = a;
+	cli.e = e;
+	cli.count = 0;
+	cli.buf = ast_str_create(256);
+	if (!cli.buf) {
+		if (cli.like) {
+			regfree(cli.like);
+		}
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, CLI_LIST_SUB_FORMAT_HEADER,
+		"Endpoint/CLI", "Resource/Event", "Expiry", "Call-id");
+	for_each_subscription(on_subscription, &cli);
+	ast_cli(a->fd, "\n%d active subscriptions%s%s%s\n",
+		cli.count,
+		regex ? " matched \"" : "",
+		regex ?: "",
+		regex ? "\"" : "");
+
+	ast_free(cli.buf);
+	if (cli.like) {
+		regfree(cli.like);
+	}
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+	AST_CLI_DEFINE(cli_list_subscriptions_inout, "List active inbound/outbound subscriptions"),
+	AST_CLI_DEFINE(cli_show_subscription_inout, "Show active subscription details"),
+	AST_CLI_DEFINE(cli_show_subscriptions_inout, "Show active inbound/outbound subscriptions"),
+};
+
 static int persistence_endpoint_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct subscription_persistence *persistence = obj;
@@ -4635,6 +5171,8 @@
 	ast_manager_register_xml("PJSIPShowResourceLists", EVENT_FLAG_SYSTEM,
 			ami_show_resource_lists);
 
+	ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
 	AST_TEST_REGISTER(resource_tree);
 	AST_TEST_REGISTER(complex_resource_tree);
 	AST_TEST_REGISTER(bad_resource);
@@ -4648,6 +5186,16 @@
 
 static int unload_module(void)
 {
+	AST_TEST_UNREGISTER(resource_tree);
+	AST_TEST_UNREGISTER(complex_resource_tree);
+	AST_TEST_UNREGISTER(bad_resource);
+	AST_TEST_UNREGISTER(bad_branch);
+	AST_TEST_UNREGISTER(duplicate_resource);
+	AST_TEST_UNREGISTER(loop);
+	AST_TEST_UNREGISTER(bad_event);
+
+	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
 	ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_OUTBOUND);
 	ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_INBOUND);
 	ast_manager_unregister("PJSIPShowResourceLists");
@@ -4656,14 +5204,6 @@
 	if (sched) {
 		ast_sched_context_destroy(sched);
 	}
-
-	AST_TEST_UNREGISTER(resource_tree);
-	AST_TEST_UNREGISTER(complex_resource_tree);
-	AST_TEST_UNREGISTER(bad_resource);
-	AST_TEST_UNREGISTER(bad_branch);
-	AST_TEST_UNREGISTER(duplicate_resource);
-	AST_TEST_UNREGISTER(loop);
-	AST_TEST_UNREGISTER(bad_event);
 
 	return 0;
 }

-- 
To view, visit https://gerrit.asterisk.org/4746
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ifb8a3b61f447aedc58a8e6b36a810f7566018567
Gerrit-PatchSet: 2
Gerrit-Project: asterisk
Gerrit-Branch: 14
Gerrit-Owner: Richard Mudgett <rmudgett at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Mark Michelson <mmichelson at digium.com>



More information about the asterisk-code-review mailing list