summaryrefslogtreecommitdiff
path: root/res/res_pjsip_pubsub.c
diff options
context:
space:
mode:
authorRichard Mudgett <rmudgett@digium.com>2017-01-05 13:21:08 -0600
committerRichard Mudgett <rmudgett@digium.com>2017-01-23 18:07:04 -0600
commit0ea3c371c5286daffd9986417d46af214b0dc663 (patch)
tree2461f584c799534051d749eaebb03afbd36d953d /res/res_pjsip_pubsub.c
parent2481a55fba93b77e72d54b1ddfd07213750206ba (diff)
res_pjsip_pubsub.c: Implement "pjsip show subscriptions" commands.
ASTERISK-23828 #close Change-Id: Ifb8a3b61f447aedc58a8e6b36a810f7566018567
Diffstat (limited to 'res/res_pjsip_pubsub.c')
-rw-r--r--res/res_pjsip_pubsub.c556
1 files changed, 548 insertions, 8 deletions
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 <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;
}