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

This commit is contained in:
George Joseph 2017-01-24 15:47:34 -06:00 committed by Gerrit Code Review
commit f114b7b8c9
1 changed files with 548 additions and 8 deletions

View 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;
}