From 0ea3c371c5286daffd9986417d46af214b0dc663 Mon Sep 17 00:00:00 2001 From: Richard Mudgett Date: Thu, 5 Jan 2017 13:21:08 -0600 Subject: res_pjsip_pubsub.c: Implement "pjsip show subscriptions" commands. ASTERISK-23828 #close Change-Id: Ifb8a3b61f447aedc58a8e6b36a810f7566018567 --- res/res_pjsip_pubsub.c | 556 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 548 insertions(+), 8 deletions(-) (limited to 'res') diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 5799379a6..42f0dc11e 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 @@ 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 \n" + " pjsip show subscription outbound \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: \n" \ + "Resource: \n" \ + " Expiry: \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), + ""); + + /* Call-id */ + if (sub_tree->dlg) { + ast_copy_pj_str(callid, &sub_tree->dlg->call_id->id, sizeof(callid)); + } else { + ast_copy_string(callid, "", 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 ]\n" + " Show active inbound subscriptions\n" + " pjsip show subscriptions outbound [like ]\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, ""))); + + /* 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, "", 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 ]\n" + " List active inbound subscriptions\n" + " pjsip list subscriptions outbound [like ]\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; } -- cgit v1.2.3