res_pjsip_pubsub.c: Implement "pjsip show subscriptions" commands.
authorRichard Mudgett <rmudgett@digium.com>
Thu, 5 Jan 2017 19:21:08 +0000 (13:21 -0600)
committerRichard Mudgett <rmudgett@digium.com>
Tue, 24 Jan 2017 00:07:04 +0000 (18:07 -0600)
ASTERISK-23828 #close

Change-Id: Ifb8a3b61f447aedc58a8e6b36a810f7566018567

res/res_pjsip_pubsub.c

index 5799379..42f0dc1 100644 (file)
@@ -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 @@ static int ami_show_resource_lists(struct mansession *s, const struct message *m
 #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 @@ static int load_module(void)
        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 load_module(void)
 
 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");
@@ -4657,14 +5205,6 @@ static int unload_module(void)
                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;
 }