res_pjsip: Add PJSIP CLI commands
authorMatthew Jordan <mjordan@digium.com>
Fri, 20 Dec 2013 21:32:13 +0000 (21:32 +0000)
committerMatthew Jordan <mjordan@digium.com>
Fri, 20 Dec 2013 21:32:13 +0000 (21:32 +0000)
Implements the following cli commands:
pjsip list aors
pjsip list auths
pjsip list channels
pjsip list contacts
pjsip list endpoints
pjsip show aor(s)
pjsip show auth(s)
pjsip show channels
pjsip show endpoint(s)

Also...
Minor modifications made to the AMI command implementations to facilitate
reuse.

New function ast_variable_list_sort added to config.c and config.h to implement
variable list sorting.

(issue ASTERISK-22610)
patches:
  pjsip_cli_v2.patch uploaded by george.joseph (License 6322)
........

Merged revisions 404480 from http://svn.asterisk.org/svn/asterisk/branches/12

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@404507 65c4cc65-6c06-0410-ace0-fbb531ad65f3

15 files changed:
CREDITS
include/asterisk/config.h
include/asterisk/res_pjsip.h
include/asterisk/res_pjsip_cli.h [new file with mode: 0644]
include/asterisk/sorcery.h
main/channel.c
main/config.c
main/sorcery.c
res/res_pjsip/config_auth.c
res/res_pjsip/include/res_pjsip_private.h
res/res_pjsip/location.c
res/res_pjsip/pjsip_cli.c [new file with mode: 0644]
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_endpoint_identifier_ip.c
res/res_pjsip_registrar.c

diff --git a/CREDITS b/CREDITS
index a767396..6560976 100644 (file)
--- a/CREDITS
+++ b/CREDITS
        * Andrew "lathama" Latham <lathama at gmail dot com>
                Doxygen, HTTP-Static, Phoneprov, make update
 
+       * George Joseph - PJSIP CLI commands, PJSIP_HEADER dialplan function
+
 === OTHER CONTRIBUTIONS ===
 
  We'd like to thank the following for their listed contributions.
index d669e7a..6d341db 100644 (file)
@@ -691,6 +691,7 @@ void ast_include_rename(struct ast_config *conf, const char *from_file, const ch
 void ast_variable_append(struct ast_category *category, struct ast_variable *variable);
 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line);
 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line);
+struct ast_variable *ast_variable_list_sort(struct ast_variable *start);
 
 /*!
  * \brief Update variable value within a config
index b3701c0..f4f9ba7 100644 (file)
@@ -40,6 +40,9 @@
 #include "asterisk/rtp_engine.h"
 /* Needed for AST_VECTOR macro */
 #include "asterisk/vector.h"
+/* Needed for ast_sip_for_each_channel_snapshot struct */
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_endpoints.h"
 
 /* Forward declarations of PJSIP stuff */
 struct pjsip_rx_data;
@@ -214,6 +217,17 @@ struct ast_sip_aor {
 };
 
 /*!
+ * \brief Aor/Contact pair used for ast_sip_for_each_contact callback.
+ */
+struct ast_sip_aor_contact_pair {
+       SORCERY_OBJECT(details);
+       /*! Aor */
+       struct ast_sip_aor *aor;
+       /*! Contact */
+       struct ast_sip_contact *contact;
+};
+
+/*!
  * \brief DTMF modes for SIP endpoints
  */
 enum ast_sip_dtmf_mode {
@@ -1547,13 +1561,6 @@ void *ast_sip_dict_set(pj_pool_t* pool, void *ht,
        mod_data[id] = ast_sip_dict_set(pool, mod_data[id], key, val)
 
 /*!
- * \brief Function pointer for contact callbacks.
- */
-typedef int (*on_contact_t)(const struct ast_sip_aor *aor,
-                           const struct ast_sip_contact *contact,
-                           int last, void *arg);
-
-/*!
  * \brief For every contact on an AOR call the given 'on_contact' handler.
  *
  * \param aor the aor containing a list of contacts to iterate
@@ -1561,21 +1568,18 @@ typedef int (*on_contact_t)(const struct ast_sip_aor *aor,
  * \param arg user data passed to handler
  * \retval 0 Success, non-zero on failure
  */
-int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
-                            on_contact_t on_contact, void *arg);
+int ast_sip_for_each_contact(struct ast_sip_aor *aor,
+               ao2_callback_fn on_contact, void *arg);
 
 /*!
  * \brief Handler used to convert a contact to a string.
  *
- * \param aor the aor containing a list of contacts to iterate
- * \param contact the contact to convert
- * \param last is this the last contact
+ * \param object the ast_sip_aor_contact_pair containing a list of contacts to iterate and the contact
  * \param arg user data passed to handler
+ * \param flags
  * \retval 0 Success, non-zero on failure
  */
-int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
-                          const struct ast_sip_contact *contact,
-                          int last, void *arg);
+int ast_sip_contact_to_str(void *object, void *arg, int flags);
 
 /*!
  * \brief For every aor in the comma separated aors string call the
@@ -1696,4 +1700,47 @@ int ast_sip_format_endpoint_ami(struct ast_sip_endpoint *endpoint,
 int ast_sip_format_auths_ami(const struct ast_sip_auth_vector *auths,
                             struct ast_sip_ami *ami);
 
+/*!
+ * \brief Retrieve the endpoint snapshot for an endpoint
+ *
+ * \param endpoint The endpoint whose snapshot is to be retreieved.
+ * \retval The endpoint snapshot
+ */
+struct ast_endpoint_snapshot *ast_sip_get_endpoint_snapshot(
+       const struct ast_sip_endpoint *endpoint);
+
+/*!
+ * \brief Retrieve the device state for an endpoint.
+ *
+ * \param endpoint The endpoint whose state is to be retrieved.
+ * \retval The device state.
+ */
+const char *ast_sip_get_device_state(const struct ast_sip_endpoint *endpoint);
+
+/*!
+ * \brief For every channel snapshot on an endpoint snapshot call the given
+ *        'on_channel_snapshot' handler.
+ *
+ * \param endpoint_snapshot snapshot of an endpoint
+ * \param on_channel_snapshot callback for each channel snapshot
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoint_snapshot,
+               ao2_callback_fn on_channel_snapshot,
+                                     void *arg);
+
+/*!
+ * \brief For every channel snapshot on an endpoint all the given
+ *        'on_channel_snapshot' handler.
+ *
+ * \param endpoint endpoint
+ * \param on_channel_snapshot callback for each channel snapshot
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_channel(const struct ast_sip_endpoint *endpoint,
+               ao2_callback_fn on_channel_snapshot,
+                                     void *arg);
+
 #endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_cli.h b/include/asterisk/res_pjsip_cli.h
new file mode 100644 (file)
index 0000000..cd7d81e
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Fairview 5 Engineering, LLC.
+ *
+ * George Joseph <george.joseph@fairview5.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_PJSIP_CLI_H_
+#define RES_PJSIP_CLI_H_
+
+#define CLI_HEADER_FILLER ".........................................................................................."
+#define CLI_DETAIL_FILLER "                                                                                          "
+#define CLI_MAX_WIDTH 90
+#define CLI_LAST_TABSTOP 62
+#define CLI_MAX_TITLE_NAME 8
+#define CLI_INDENT_TO_SPACES(x) ((x * 2) + 1 + CLI_MAX_TITLE_NAME)
+
+/*
+ * \brief CLI Formatter Context
+ */
+struct ast_sip_cli_context {
+       int peers_mon_online;
+       int peers_mon_offline;
+       int peers_unmon_offline;
+       int peers_unmon_online;
+       struct ast_str *output_buffer;
+       const struct ast_cli_args *a;
+       const struct ast_sip_endpoint *current_endpoint;
+       const struct ast_sip_auth *current_auth;
+       const struct ast_sip_aor *current_aor;
+       char *auth_direction;
+       unsigned int print_flags;
+       int indent_level;
+       unsigned show_details : 1;
+       unsigned recurse : 1;
+       unsigned show_details_only_level_0 : 1;
+};
+
+/*
+ * \brief CLI Formatter Registry Entry
+ */
+struct ast_sip_cli_formatter_entry {
+       const char *name;
+       ao2_callback_fn *print_header;
+       ao2_callback_fn *print_body;
+       struct ao2_container *(* get_container)(struct ast_sorcery *);
+};
+
+/*!
+ * \brief Registers a CLI formatter.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \param formatter An ao2_callback_fn that outputs the formatted data.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter);
+
+/*!
+ * \brief Unregisters a CLI formatter.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_unregister_cli_formatter(struct ast_sip_cli_formatter_entry *formatter);
+
+/*!
+ * \brief Looks up a CLI formatter by type.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \retval Pointer to formatter entry structure
+ */
+struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name);
+
+/*!
+ * \brief Prints a sorcery object's ast_variable list
+ *
+ * \param obj The sorcery object
+ * \param arg The ast_sip_cli_context.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_cli_print_sorcery_objectset(void *obj, void *arg, int flags);
+
+
+#endif /* RES_PJSIP_CLI_H_ */
index 1402c58..47ad5e4 100644 (file)
@@ -823,6 +823,12 @@ const char *ast_sorcery_object_get_extended(const void *object, const char *name
  */
 int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value);
 
+/*!
+ * \brief Sorcery object comparator based on id.
+ */
+int ast_sorcery_object_id_compare(const void *obj_left, const void *obj_right, int flags);
+
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
index 4789943..9ff3d07 100644 (file)
@@ -733,6 +733,8 @@ const char *ast_state2str(enum ast_channel_state state)
                return "Dialing Offhook";
        case AST_STATE_PRERING:
                return "Pre-ring";
+       case AST_STATE_MUTE:
+               return "Mute";
        default:
                if (!(buf = ast_threadstorage_get(&state2str_threadbuf, STATE2STR_BUFSIZE)))
                        return "Unknown";
index b8356a9..c6458ca 100644 (file)
@@ -70,6 +70,7 @@ static char *extconfig_conf = "extconfig.conf";
 
 static struct ao2_container *cfg_hooks;
 static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg);
+inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
 
 /*! \brief Structure to keep comments for rewriting configuration files */
 struct ast_comment {
@@ -581,6 +582,39 @@ struct ast_variable *ast_variable_browse(const struct ast_config *config, const
        return (cat) ? cat->root : NULL;
 }
 
+inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2)
+{
+    l1->next = l2->next;
+    l2->next = l1;
+    return l2;
+}
+
+struct ast_variable *ast_variable_list_sort(struct ast_variable *start)
+{
+       struct ast_variable *p, *q, *top;
+       int changed = 1;
+       top = ast_calloc(1, sizeof(struct ast_variable));
+       top->next = start;
+       if (start != NULL && start->next != NULL) {
+               while (changed) {
+                       changed = 0;
+                       q = top;
+                       p = top->next;
+                       while (p->next != NULL) {
+                               if (p->next != NULL && strcmp(p->name, p->next->name) > 0) {
+                                       q->next = variable_list_switch(p, p->next);
+
+                                       changed = 1;
+                               }
+                               q = p;
+                               if (p->next != NULL)
+                                       p = p->next;
+                       }
+               }
+       }
+       return top->next;
+}
+
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
 {
        const char *tmp;
index c7e1a03..1753ba1 100644 (file)
@@ -1583,3 +1583,11 @@ void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *
        ao2_callback(object_type->observers, OBJ_NODATA | OBJ_UNLINK,
                sorcery_observer_remove, cbs);
 }
+
+int ast_sorcery_object_id_compare(const void *obj_left, const void *obj_right, int flags)
+{
+       if (!obj_left || !obj_right) {
+               return 0;
+       }
+       return strcmp(ast_sorcery_object_get_id(obj_left), ast_sorcery_object_get_id(obj_right));
+}
index 047e933..d7e759f 100644 (file)
@@ -23,7 +23,9 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
+#include "asterisk/cli.h"
 #include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
 
 static void auth_destroy(void *obj)
 {
@@ -197,6 +199,59 @@ static struct ast_sip_endpoint_formatter endpoint_auth_formatter = {
        .format_ami = format_ami_endpoint_auth
 };
 
+static struct ao2_container *cli_get_auth_container(struct ast_sorcery *sip_sorcery)
+{
+       return ast_sorcery_retrieve_by_fields(sip_sorcery, "auth",
+                               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static int cli_print_auth_header(void *obj, void *arg, int flags) {
+       struct ast_sip_cli_context *context = arg;
+       int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       int filler = CLI_MAX_WIDTH - indent - 20;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       ast_str_append(&context->output_buffer, 0,
+               "%*s:  <AuthId/UserName%*.*s>\n", indent, "I/OAuth", filler, filler, CLI_HEADER_FILLER);
+
+       return 0;
+}
+
+static int cli_print_auth_body(void *obj, void *arg, int flags) {
+       struct ast_sip_auth *auth = obj;
+       struct ast_sip_cli_context *context = arg;
+       char title[32];
+
+       context->current_auth = auth;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       snprintf(title, 32, "%sAuth",context->auth_direction ? context->auth_direction : "");
+
+       ast_str_append(&context->output_buffer, 0, "%*s:  %s/%s\n",
+               CLI_INDENT_TO_SPACES(context->indent_level), title,
+               ast_sorcery_object_get_id(auth), auth->auth_user);
+
+       if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+               ast_str_append(&context->output_buffer, 0, "\n");
+               ast_sip_cli_print_sorcery_objectset(auth, context, 0);
+       }
+
+       return 0;
+}
+
+static struct ast_sip_cli_formatter_entry  cli_auth_formatter = {
+       .name = SIP_SORCERY_AUTH_TYPE,
+       .print_header = cli_print_auth_header,
+       .print_body = cli_print_auth_body,
+       .get_container = cli_get_auth_container,
+};
+
 /*! \brief Initialize sorcery with auth support */
 int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
 {
@@ -222,5 +277,7 @@ int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
                        "userpass", auth_type_handler, auth_type_to_str, 0, 0);
 
        ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
+       ast_sip_register_cli_formatter(&cli_auth_formatter);
+
        return 0;
 }
index 368bdd0..a2d1c6e 100644 (file)
@@ -9,8 +9,6 @@
 #define RES_PJSIP_PRIVATE_H_
 
 #include "asterisk/module.h"
-#include "asterisk/stasis_channels.h"
-#include "asterisk/stasis_endpoints.h"
 
 struct ao2_container;
 struct ast_threadpool_options;
@@ -87,25 +85,6 @@ void ast_res_pjsip_cleanup_options_handling(void);
 void sip_get_threadpool_options(struct ast_threadpool_options *threadpool_options);
 
 /*!
- * \brief Function pointer for channel snapshot callbacks.
- */
-typedef int (*on_channel_snapshot_t)(
-       const struct ast_channel_snapshot *snapshot, int last, void *arg);
-
-/*!
- * \brief For every channel snapshot on an endpoint snapshot call the given
- *        'on_channel_snapshot' handler.
- *
- * \param endpoint_snapshot snapshot of an endpoint
- * \param on_channel_snapshot callback for each channel snapshot
- * \param arg user data passed to handler
- * \retval 0 Success, non-zero on failure
- */
-int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoint_snapshot,
-                                     on_channel_snapshot_t on_channel_snapshot,
-                                     void *arg);
-
-/*!
  * \brief Retrieve the name of the default outbound endpoint.
  *
  * \note This returns a memory allocated copy of the name that
@@ -116,4 +95,10 @@ int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoi
  */
 char *ast_sip_global_default_outbound_endpoint(void);
 
+/*!
+ * \brief Functions for initializing and destroying the CLI.
+ */
+int ast_sip_initialize_cli(struct ast_sorcery *sip_sorcery);
+void ast_sip_destroy_cli(void);
+
 #endif /* RES_PJSIP_PRIVATE_H_ */
index 52f69cf..3bd4dea 100644 (file)
@@ -25,6 +25,7 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/sorcery.h"
 #include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
 
 /*! \brief Destructor for AOR */
 static void aor_destroy(void *obj)
@@ -284,12 +285,37 @@ int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
        return 0;
 }
 
-int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
-                            on_contact_t on_contact, void *arg)
+static void destroy_contact_pair(void *obj)
+{
+       struct ast_sip_aor_contact_pair *pair = obj;
+       ao2_cleanup(pair->aor);
+       ao2_cleanup(pair->contact);
+}
+
+static struct ast_sip_aor_contact_pair *create_contact_pair(
+       struct ast_sip_aor *aor, struct ast_sip_contact *contact)
+{
+       struct ast_sip_aor_contact_pair *pair = ao2_alloc(
+               sizeof(*pair), destroy_contact_pair);
+
+       if (!pair) {
+               return NULL;
+       }
+
+       pair->aor = aor;
+       pair->contact = contact;
+
+       ao2_ref(pair->aor, +1);
+       ao2_ref(pair->contact, +1);
+
+       return pair;
+}
+
+int ast_sip_for_each_contact(struct ast_sip_aor *aor,
+               ao2_callback_fn on_contact, void *arg)
 {
        RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
        struct ast_sip_contact *contact;
-       int num;
        struct ao2_iterator i;
 
        if (!on_contact ||
@@ -297,13 +323,13 @@ int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
                return 0;
        }
 
-       num = ao2_container_count(contacts);
        i = ao2_iterator_init(contacts, 0);
        while ((contact = ao2_iterator_next(&i))) {
-               int res = on_contact(aor, contact, --num == 0, arg);
+               int res;
+               RAII_VAR(struct ast_sip_aor_contact_pair *,
+                        acp, create_contact_pair(aor, contact), ao2_cleanup);
 
-               ao2_ref(contact, -1);
-               if (res) {
+               if (!acp || (res = on_contact(acp, arg, 0))) {
                        ao2_iterator_destroy(&i);
                        return -1;
                }
@@ -312,18 +338,13 @@ int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
        return 0;
 }
 
-int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
-                          const struct ast_sip_contact *contact,
-                          int last, void *arg)
+int ast_sip_contact_to_str(void *object, void *arg, int flags)
 {
+       struct ast_sip_aor_contact_pair *acp = object;
        struct ast_str **buf = arg;
 
-       ast_str_append(buf, 0, "%s/%s",
-                      ast_sorcery_object_get_id(aor), contact->uri);
-
-       if (!last) {
-               ast_str_append(buf, 0, ",");
-       }
+       ast_str_append(buf, 0, "%s/%s,",
+                      ast_sorcery_object_get_id(acp->aor), acp->contact->uri);
 
        return 0;
 }
@@ -335,7 +356,7 @@ static int sip_aor_to_ami(const struct ast_sip_aor *aor, struct ast_str **buf)
 
 static int format_ami_aor_handler(void *obj, void *arg, int flags)
 {
-       const struct ast_sip_aor *aor = obj;
+       struct ast_sip_aor *aor = obj;
        struct ast_sip_ami *ami = arg;
        const struct ast_sip_endpoint *endpoint = ami->arg;
        RAII_VAR(struct ast_str *, buf,
@@ -352,6 +373,7 @@ static int format_ami_aor_handler(void *obj, void *arg, int flags)
        sip_aor_to_ami(aor, &buf);
        ast_str_append(&buf, 0, "Contacts: ");
        ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &buf);
+       ast_str_truncate(buf, -1);
        ast_str_append(&buf, 0, "\r\n");
 
        num = ao2_container_count(contacts);
@@ -377,6 +399,193 @@ struct ast_sip_endpoint_formatter endpoint_aor_formatter = {
        .format_ami = format_ami_endpoint_aor
 };
 
+static int populate_contact_container(void *obj, void *arg, int flags)
+{
+       struct ast_sip_aor_contact_pair *acp = obj;
+       struct ao2_container *container = arg;
+       ao2_link_flags(container, acp, OBJ_NOLOCK);
+       return 0;
+}
+
+static int gather_aor_channels(void *obj, void *arg, int flags)
+{
+       struct ast_sip_aor *aor = obj;
+       struct ao2_container *container = arg;
+       ast_sip_for_each_contact(aor, populate_contact_container, container);
+       return 0;
+}
+
+static struct ao2_container *cli_get_contact_container(struct ast_sorcery *sip_sorcery)
+{
+       RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, s_parent_container, NULL, ao2_cleanup);
+       struct ao2_container *child_container;
+
+       parent_container = ast_sorcery_retrieve_by_fields(sip_sorcery, "aor",
+                       AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       if (!parent_container) {
+               return NULL;
+       }
+
+       s_parent_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+       if (!s_parent_container) {
+               return NULL;
+       }
+
+       ao2_container_dup(s_parent_container, parent_container, OBJ_ORDER_ASCENDING);
+
+       child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+       if (!child_container) {
+               return NULL;
+       }
+
+       ao2_callback(s_parent_container, OBJ_NODATA, gather_aor_channels, child_container);
+
+       return child_container;
+}
+
+
+static int cli_print_contact_header(void *obj, void *arg, int flags)
+{
+       struct ast_sip_cli_context *context = arg;
+       int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       int filler = CLI_LAST_TABSTOP - indent - 18;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+       ast_str_append(&context->output_buffer, 0,
+               "%*s:  <Aor/ContactUri%*.*s>  <Status....>  <RTT(ms)..>\n",
+               indent, "Contact", filler, filler, CLI_HEADER_FILLER);
+
+       return 0;
+}
+
+static int cli_print_contact_body(void *obj, void *arg, int flags)
+{
+       struct ast_sip_aor_contact_pair *acp = obj;
+       struct ast_sip_cli_context *context = arg;
+       char *print_name = NULL;
+       int print_name_len;
+       int indent;
+       int flexwidth;
+
+       RAII_VAR(struct ast_sip_contact_status *, status,
+               ast_sorcery_retrieve_by_id( ast_sip_get_sorcery(), CONTACT_STATUS, ast_sorcery_object_get_id(acp->contact)),
+               ao2_cleanup);
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       print_name_len = strlen(ast_sorcery_object_get_id(acp->aor))
+               + strlen(acp->contact->uri) + 2;
+       if (!(print_name = alloca(print_name_len))) {
+               return -1;
+       }
+       snprintf(print_name, print_name_len, "%s/%s",
+               ast_sorcery_object_get_id(acp->aor), acp->contact->uri);
+
+       indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       flexwidth = CLI_LAST_TABSTOP - indent - 2;
+
+       ast_str_append(&context->output_buffer, 0, "%*s:  %-*.*s  %-12.12s  %11.3f\n",
+               indent,
+               "Contact",
+               flexwidth, flexwidth,
+               print_name,
+               (status ? (status->status == AVAILABLE ? "Avail" : "Unavail") : "Unknown"),
+               (status ? ((long long) status->rtt) / 1000.0 : NAN));
+
+       return 0;
+}
+
+static struct ao2_container *cli_get_aor_container(struct ast_sorcery *sip_sorcery)
+{
+       return ast_sorcery_retrieve_by_fields(sip_sorcery, "aor",
+                               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static int cli_print_aor_header(void *obj, void *arg, int flags)
+{
+       struct ast_sip_cli_context *context = arg;
+       struct ast_sip_cli_formatter_entry *formatter_entry;
+
+       int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       int filler = CLI_LAST_TABSTOP - indent - 7;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+       ast_str_append(&context->output_buffer, 0,
+               "%*s:  <Aor%*.*s>  <MaxContact>\n",
+               indent, "Aor", filler, filler, CLI_HEADER_FILLER);
+
+       if (context->recurse) {
+               context->indent_level++;
+               formatter_entry = ast_sip_lookup_cli_formatter("contact");
+               if (formatter_entry) {
+                       formatter_entry->print_header(NULL, context, 0);
+               }
+               context->indent_level--;
+       }
+       return 0;
+}
+
+static int cli_print_aor_body(void *obj, void *arg, int flags)
+{
+       struct ast_sip_aor *aor = obj;
+       struct ast_sip_cli_context *context = arg;
+       struct ast_sip_cli_formatter_entry *formatter_entry;
+       int indent;
+       int flexwidth;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       context->current_aor = aor;
+
+       indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       flexwidth = CLI_LAST_TABSTOP - indent - 12;
+
+       ast_str_append(&context->output_buffer, 0, "%*s:  %-*.*s %12d\n",
+               indent,
+               "Aor",
+               flexwidth, flexwidth,
+               ast_sorcery_object_get_id(aor), aor->max_contacts);
+
+       if (context->recurse) {
+               context->indent_level++;
+               formatter_entry = ast_sip_lookup_cli_formatter("contact");
+               if (formatter_entry) {
+                       ast_sip_for_each_contact(aor, formatter_entry->print_body, context);
+               }
+               context->indent_level--;
+       }
+
+       if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+               ast_str_append(&context->output_buffer, 0, "\n");
+               ast_sip_cli_print_sorcery_objectset(aor, context, 0);
+       }
+
+       return 0;
+}
+
+static struct ast_sip_cli_formatter_entry cli_contact_formatter = {
+       .name = "contact",
+       .print_header = cli_print_contact_header,
+       .print_body = cli_print_contact_body,
+       .get_container = cli_get_contact_container,
+};
+
+static struct ast_sip_cli_formatter_entry cli_aor_formatter = {
+       .name = "aor",
+       .print_header = cli_print_aor_header,
+       .print_body = cli_print_aor_body,
+       .get_container = cli_get_aor_container,
+};
+
 /*! \brief Initialize sorcery with location support */
 int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 {
@@ -408,6 +617,8 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
        ast_sorcery_object_field_register(sorcery, "aor", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, outbound_proxy));
 
        ast_sip_register_endpoint_formatter(&endpoint_aor_formatter);
+       ast_sip_register_cli_formatter(&cli_contact_formatter);
+       ast_sip_register_cli_formatter(&cli_aor_formatter);
        return 0;
 }
 
diff --git a/res/res_pjsip/pjsip_cli.c b/res/res_pjsip/pjsip_cli.c
new file mode 100644 (file)
index 0000000..5f8157d
--- /dev/null
@@ -0,0 +1,319 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph@fairview5.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/acl.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/hashtab.h"
+#include "asterisk/utils.h"
+#include "asterisk/sorcery.h"
+
+static struct ast_hashtab *formatter_registry;
+
+static struct ast_sorcery *sip_sorcery;
+
+struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name)
+{
+       struct ast_sip_cli_formatter_entry fake_entry = {
+               .name = name,
+       };
+       return ast_hashtab_lookup(formatter_registry, &fake_entry);
+}
+
+int ast_sip_cli_print_sorcery_objectset(void *obj, void *arg, int flags)
+{
+       struct ast_sip_cli_context *context = arg;
+       struct ast_variable *i;
+       int max_name_width = 13;
+       int max_value_width = 14;
+       int width;
+       char *separator;
+       struct ast_variable *objset;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       objset = ast_sorcery_objectset_create(ast_sip_get_sorcery(),obj);
+       if (!objset) {
+               return -1;
+       }
+
+       for (i = objset; i; i = i->next) {
+               if (i->name) {
+                       width = strlen(i->name);
+                       max_name_width = width > max_name_width ? width : max_name_width;
+               }
+               if (i->value) {
+                       width = strlen(i->value);
+                       max_value_width = width > max_value_width ? width : max_value_width;
+               }
+       }
+
+       if (!(separator = alloca(max_name_width + max_value_width + 8))) {
+               return -1;
+       }
+       memset(separator, '=', max_name_width + max_value_width + 3);
+       separator[max_name_width + max_value_width + 3] = 0;
+
+       ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, "ParameterName", "ParameterValue");
+       ast_str_append(&context->output_buffer, 0, " %s\n", separator);
+
+       objset = ast_variable_list_sort(objset);
+
+       for (i = objset; i; i = i->next) {
+               ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, i->name, i->value);
+       }
+
+       return 0;
+}
+
+static char *complete_show_sorcery_object(struct ao2_container *container,
+       const char *word, int state)
+{
+       char *result = NULL;
+       int wordlen = strlen(word);
+       int which = 0;
+
+       struct ao2_iterator i = ao2_iterator_init(container, 0);
+       void *object;
+
+       while ((object = ao2_t_iterator_next(&i, "iterate thru endpoints table"))) {
+               if (!strncasecmp(word, ast_sorcery_object_get_id(object), wordlen)
+                       && ++which > state) {
+                       result = ast_strdup(ast_sorcery_object_get_id(object));
+               }
+               ao2_t_ref(object, -1, "toss iterator endpoint ptr before break");
+               if (result) {
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&i);
+       return result;
+}
+
+static void dump_str_and_free(int fd, struct ast_str *buf) {
+       ast_cli(fd, "%s", ast_str_buffer(buf));
+       ast_free(buf);
+}
+
+static char *cli_traverse_objects(struct ast_cli_entry *e, int cmd,
+       struct ast_cli_args *a)
+{
+       RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, s_container, NULL, ao2_cleanup);
+       RAII_VAR(void *, object, NULL, ao2_cleanup);
+       int is_container = 0;
+       const char *cmd1 = NULL;
+       const char *cmd2 = NULL;
+       const char *object_id = NULL;
+       char formatter_type[64];
+       struct ast_sip_cli_formatter_entry *formatter_entry;
+       int is_plural = 0;
+
+       struct ast_sip_cli_context context = {
+               .peers_mon_online = 0,
+               .peers_mon_offline = 0,
+               .peers_unmon_online = 0,
+               .peers_unmon_offline = 0,
+               .a = a,
+               .indent_level = 0,
+       };
+
+       if (cmd == CLI_INIT) {
+               return NULL;
+       }
+
+       cmd1 = e->cmda[1];
+       cmd2 = e->cmda[2];
+       object_id = a->argv[3];
+
+       if (!ast_ends_with(cmd2, "s")) {
+               ast_copy_string(formatter_type, cmd2, strlen(cmd2)+1);
+               is_plural = 0;
+       } else {
+               ast_copy_string(formatter_type, cmd2, strlen(cmd2));
+               is_plural = 1;
+       }
+
+       if (!strcmp(cmd1, "show")) {
+               if (is_plural) {
+                       context.recurse = 1;
+                       context.show_details = 0;
+                       context.show_details_only_level_0 = 0;
+                       is_container = 1;
+               } else {
+                       context.recurse = 1;
+                       context.show_details = 0;
+                       context.show_details_only_level_0 = 1;
+                       is_container = 0;
+               }
+       } else {
+               context.recurse = 0;
+               context.show_details = 0;
+               context.show_details_only_level_0 = 0;
+               is_container = 1;
+       }
+
+       context.output_buffer = ast_str_create(256);
+       formatter_entry = ast_sip_lookup_cli_formatter(formatter_type);
+       if (!formatter_entry) {
+               ast_log(LOG_ERROR, "CLI TRAVERSE failure.  No container found for object type %s\n", formatter_type);
+               return CLI_FAILURE;
+       }
+       ast_str_append(&context.output_buffer, 0, "\n");
+       formatter_entry->print_header(NULL, &context, 0);
+       ast_str_append(&context.output_buffer, 0, " =========================================================================================\n\n");
+
+       if (is_container || cmd == CLI_GENERATE) {
+               container = formatter_entry->get_container(sip_sorcery);
+               if (!container) {
+                       ast_cli(a->fd, "CLI TRAVERSE failure.  No container found for object type %s\n", formatter_type);
+                       return CLI_FAILURE ;
+               }
+       }
+
+       if (!is_container && cmd == CLI_GENERATE) {
+               return complete_show_sorcery_object(container, a->word, a->n);
+       }
+
+       if (is_container) {
+               if (!ao2_container_count(container)) {
+                       dump_str_and_free(a->fd, context.output_buffer);
+                       ast_cli(a->fd, "No objects found.\n\n");
+                       return CLI_SUCCESS ;
+               }
+
+               if (!strcmp(formatter_type, "channel") || !strcmp(formatter_type, "contact")) {
+                       s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+               } else {
+                       s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+               }
+
+               ao2_container_dup(s_container, container, OBJ_ORDER_ASCENDING);
+
+               ao2_callback(s_container, OBJ_NODATA, formatter_entry->print_body, &context);
+       } else {
+               if (!(object = ast_sorcery_retrieve_by_id(
+                       ast_sip_get_sorcery(), formatter_type, object_id))) {
+                       dump_str_and_free(a->fd, context.output_buffer);
+                       ast_cli(a->fd, "Unable to retrieve object %s\n", object_id);
+                       return CLI_FAILURE ;
+               }
+               formatter_entry->print_body(object, &context, 0);
+       }
+
+       ast_str_append(&context.output_buffer, 0, "\n");
+       dump_str_and_free(a->fd, context.output_buffer);
+       return CLI_SUCCESS ;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+       AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Channels", .command = "pjsip list channels",
+                       .usage = "Usage: pjsip list channels\n       List the active PJSIP channels\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Channels", .command = "pjsip show channels",
+                       .usage = "Usage: pjsip show channels\n       List(detailed) the active PJSIP channels\n"),
+
+       AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Aors", .command = "pjsip list aors",
+                       .usage = "Usage: pjsip list aors\n       List the configured PJSIP Aors\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Aors", .command = "pjsip show aors",
+                       .usage = "Usage: pjsip show aors\n       Show the configured PJSIP Aors\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Aor", .command = "pjsip show aor",
+                       .usage = "Usage: pjsip show aor\n       Show the configured PJSIP Aor\n"),
+
+       AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Contacts", .command = "pjsip list contacts",
+                       .usage = "Usage: pjsip list contacts\n       List the configured PJSIP contacts\n"),
+
+       AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Endpoints", .command = "pjsip list endpoints",
+                       .usage = "Usage: pjsip list endpoints\n       List the configured PJSIP endpoints\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Endpoints", .command = "pjsip show endpoints",
+                       .usage = "Usage: pjsip show endpoints\n       List(detailed) the configured PJSIP endpoints\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Endpoint", .command = "pjsip show endpoint",
+                       .usage = "Usage: pjsip show endpoint <id>\n       Show the configured PJSIP endpoint\n"),
+
+       AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Auths", .command = "pjsip list auths",
+                       .usage = "Usage: pjsip list auths\n       List the configured PJSIP Auths\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Auths", .command = "pjsip show auths",
+                       .usage = "Usage: pjsip show auths\n       Show the configured PJSIP Auths\n"),
+       AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Auth", .command = "pjsip show auth",
+                       .usage = "Usage: pjsip show auth\n       Show the configured PJSIP Auth\n"),
+
+};
+
+
+static int compare_formatters(const void *a, const void *b) {
+       const struct ast_sip_cli_formatter_entry *afe = a;
+       const struct ast_sip_cli_formatter_entry *bfe = b;
+       if (!afe || !bfe) {
+               ast_log(LOG_ERROR, "One of the arguments to compare_formatters was NULL\n");
+               return -1;
+       }
+       return strcmp(afe->name, bfe->name);
+}
+
+static unsigned int hash_formatters(const void *a) {
+       const struct ast_sip_cli_formatter_entry *afe = a;
+       return ast_hashtab_hash_string(afe->name);
+}
+
+int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter) {
+       ast_hashtab_insert_safe(formatter_registry, formatter);
+       return 0;
+}
+
+int ast_sip_unregister_cli_formatter(struct ast_sip_cli_formatter_entry *formatter) {
+       struct ast_sip_cli_formatter_entry *entry = ast_hashtab_lookup(formatter_registry, formatter);
+       if (!entry) {
+               return -1;
+       }
+       ast_hashtab_remove_this_object(formatter_registry, entry);
+       return 0;
+}
+
+int ast_sip_initialize_cli(struct ast_sorcery *sorcery)
+{
+       formatter_registry = ast_hashtab_create(17, compare_formatters,
+               ast_hashtab_resize_java, ast_hashtab_newsize_java, hash_formatters, 0);
+       if (!formatter_registry) {
+               ast_log(LOG_ERROR, "Unable to create formatter_registry.\n");
+               return -1;
+       }
+
+       if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
+               ast_log(LOG_ERROR, "Failed to register pjsip cli commands.\n");
+               ast_hashtab_destroy(formatter_registry, ast_free);
+               return -1;
+       }
+       sip_sorcery = sorcery;
+       return 0;
+}
+
+void ast_sip_destroy_cli(void)
+{
+       ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+       if (formatter_registry) {
+               ast_hashtab_destroy(formatter_registry, ast_free);
+       }
+}
index 010eeb6..c8e8e3b 100644 (file)
@@ -12,7 +12,8 @@
 
 #include "asterisk/res_pjsip.h"
 #include "include/res_pjsip_private.h"
-#include "asterisk/cli.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/acl.h"
 #include "asterisk/manager.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/utils.h"
@@ -97,131 +98,6 @@ static const struct ast_sorcery_observer state_contact_observer = {
        .deleted = persistent_endpoint_contact_observer,
 };
 
-static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
-       struct ao2_iterator it_endpoints;
-       struct ast_sip_endpoint *endpoint;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "pjsip show endpoints";
-               e->usage =
-                       "Usage: pjsip show endpoints\n"
-                       "       Show the registered PJSIP endpoints\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       endpoints = ast_sip_get_endpoints();
-       if (!endpoints) {
-               return CLI_FAILURE;
-       }
-
-       if (!ao2_container_count(endpoints)) {
-               ast_cli(a->fd, "No endpoints found\n");
-               return CLI_SUCCESS;
-       }
-
-       ast_cli(a->fd, "Endpoints:\n");
-       it_endpoints = ao2_iterator_init(endpoints, 0);
-       while ((endpoint = ao2_iterator_next(&it_endpoints))) {
-               ast_cli(a->fd, "%s\n", ast_sorcery_object_get_id(endpoint));
-               ao2_ref(endpoint, -1);
-       }
-       ao2_iterator_destroy(&it_endpoints);
-       return CLI_SUCCESS;
-}
-
-static int show_contact(void *obj, void *arg, int flags)
-{
-       struct ast_sip_contact *contact = obj;
-       struct ast_cli_args *a = arg;
-       RAII_VAR(struct ast_sip_contact_status *, status, ast_sorcery_retrieve_by_id(
-                        ast_sip_get_sorcery(), CONTACT_STATUS,
-                        ast_sorcery_object_get_id(contact)), ao2_cleanup);
-
-       ast_cli(a->fd, "\tContact %s:\n", contact->uri);
-
-       if (!status) {
-               ast_cli(a->fd, "\tStatus not found!\n");
-               return 0;
-       }
-
-       ast_cli(a->fd, "\t\tavailable = %s\n", status->status ? "yes" : "no");
-
-       if (status->status) {
-               ast_cli(a->fd, "\t\tRTT = %lld microseconds\n", (long long)status->rtt);
-       }
-
-       return 0;
-}
-
-static void show_endpoint(struct ast_sip_endpoint *endpoint, struct ast_cli_args *a)
-{
-       char *aor_name, *aors;
-
-       if (ast_strlen_zero(endpoint->aors)) {
-               return;
-       }
-
-       aors = ast_strdupa(endpoint->aors);
-
-       while ((aor_name = strsep(&aors, ","))) {
-               RAII_VAR(struct ast_sip_aor *, aor,
-                        ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
-               RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
-
-               if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
-                       continue;
-               }
-
-               ast_cli(a->fd, "AOR %s:\n", ast_sorcery_object_get_id(aor));
-               ao2_callback(contacts, OBJ_NODATA, show_contact, a);
-       }
-
-       return;
-}
-
-static char *cli_show_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-       RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-       const char *endpoint_name;
-
-       switch (cmd) {
-       case CLI_INIT:
-               e->command = "pjsip show endpoint";
-               e->usage =
-                       "Usage: pjsip show endpoint <endpoint>\n"
-                       "       Show the given PJSIP endpoint.\n";
-               return NULL;
-       case CLI_GENERATE:
-               return NULL;
-       }
-
-       if (a->argc != 4) {
-               return CLI_SHOWUSAGE;
-       }
-
-       endpoint_name = a->argv[3];
-
-       if (!(endpoint = ast_sorcery_retrieve_by_id(
-                     ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
-               ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);
-               return CLI_FAILURE;
-       }
-
-       ast_cli(a->fd, "Endpoint %s:\n", endpoint_name);
-       show_endpoint(endpoint, a);
-
-       return CLI_SUCCESS;
-}
-
-static struct ast_cli_entry cli_commands[] = {
-       AST_CLI_DEFINE(handle_cli_show_endpoints, "Show PJSIP Endpoints"),
-       AST_CLI_DEFINE(cli_show_endpoint, "Show PJSIP Endpoint")
-};
 
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -956,7 +832,7 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
        return 0;
 }
 
-static const char *get_device_state(const struct ast_sip_endpoint *endpoint)
+const char *ast_sip_get_device_state(const struct ast_sip_endpoint *endpoint)
 {
        char device[MAX_OBJECT_FIELD];
 
@@ -964,7 +840,7 @@ static const char *get_device_state(const struct ast_sip_endpoint *endpoint)
        return ast_devstate2str(ast_device_state(device));
 }
 
-static struct ast_endpoint_snapshot *sip_get_endpoint_snapshot(
+struct ast_endpoint_snapshot *ast_sip_get_endpoint_snapshot(
        const struct ast_sip_endpoint *endpoint)
 {
        return ast_endpoint_latest_snapshot(
@@ -974,46 +850,44 @@ static struct ast_endpoint_snapshot *sip_get_endpoint_snapshot(
 
 int ast_sip_for_each_channel_snapshot(
        const struct ast_endpoint_snapshot *endpoint_snapshot,
-       on_channel_snapshot_t on_channel_snapshot, void *arg)
+       ao2_callback_fn on_channel_snapshot, void *arg)
 {
        int num, num_channels = endpoint_snapshot->num_channels;
-       RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup);
 
-       if (!on_channel_snapshot || !num_channels ||
-           !(cache = ast_channel_cache())) {
+       if (!on_channel_snapshot || !num_channels) {
                return 0;
        }
 
-       ao2_ref(cache, +1);
-
        for (num = 0; num < num_channels; ++num) {
-               RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
-               struct ast_channel_snapshot *snapshot;
-
-               msg = stasis_cache_get(cache, ast_channel_snapshot_type(),
-                       endpoint_snapshot->channel_ids[num]);
+               RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+               int res;
 
-               if (!(snapshot = stasis_message_data(msg))) {
+               snapshot = ast_channel_snapshot_get_latest(endpoint_snapshot->channel_ids[num]);
+               if (!snapshot) {
                        continue;
                }
 
-               if (on_channel_snapshot(
-                           snapshot, num == (num_channels - 1), arg)) {
+               res = on_channel_snapshot(snapshot, arg, 0);
+               if (res) {
                        return -1;
                }
        }
        return 0;
 }
 
-static int active_channels_to_str_cb(const struct ast_channel_snapshot *snapshot,
-                                    int last, void *arg)
+int ast_sip_for_each_channel(
+       const struct ast_sip_endpoint *endpoint,
+       ao2_callback_fn on_channel_snapshot, void *arg)
+{
+       RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot, ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+       return ast_sip_for_each_channel_snapshot(endpoint_snapshot, on_channel_snapshot, arg);
+}
+
+static int active_channels_to_str_cb(void *object, void *arg, int flags)
 {
+       const struct ast_channel_snapshot *snapshot = object;
        struct ast_str **buf = arg;
-       if (last) {
-               ast_str_append(buf, 0, "%s", snapshot->name);
-       } else {
-               ast_str_append(buf, 0, "%s,", snapshot->name);
-       }
+       ast_str_append(buf, 0, "%s,", snapshot->name);
        return 0;
 }
 
@@ -1022,7 +896,7 @@ static void active_channels_to_str(const struct ast_sip_endpoint *endpoint,
 {
 
        RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot,
-                sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+                ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
 
        if (endpoint_snapshot) {
                return;
@@ -1030,6 +904,7 @@ static void active_channels_to_str(const struct ast_sip_endpoint *endpoint,
 
        ast_sip_for_each_channel_snapshot(endpoint_snapshot,
                                          active_channels_to_str_cb, str);
+       ast_str_truncate(*str, -1);
 }
 
 #define AMI_DEFAULT_STR_SIZE 512
@@ -1078,7 +953,7 @@ int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf)
 
 static int sip_endpoints_aors_ami(void *obj, void *arg, int flags)
 {
-       const struct ast_sip_aor *aor = obj;
+       struct ast_sip_aor *aor = obj;
        struct ast_str **buf = arg;
 
        ast_str_append(buf, 0, "Contacts: ");
@@ -1096,7 +971,7 @@ static int sip_endpoint_to_ami(const struct ast_sip_endpoint *endpoint,
        }
 
        ast_str_append(buf, 0, "DeviceState: %s\r\n",
-                      get_device_state(endpoint));
+                      ast_sip_get_device_state(endpoint));
 
        ast_str_append(buf, 0, "ActiveChannels: ");
        active_channels_to_str(endpoint, buf);
@@ -1206,7 +1081,7 @@ static int format_ami_endpoints(void *obj, void *arg, int flags)
                             sip_endpoints_aors_ami, &buf);
 
        ast_str_append(&buf, 0, "DeviceState: %s\r\n",
-                      get_device_state(endpoint));
+                      ast_sip_get_device_state(endpoint));
 
        ast_str_append(&buf, 0, "ActiveChannels: ");
        active_channels_to_str(endpoint, &buf);
@@ -1244,11 +1119,241 @@ static int ami_show_endpoints(struct mansession *s, const struct message *m)
        return 0;
 }
 
-int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info)
+static int populate_channel_container(void *obj, void *arg, int flags) {
+       struct ast_channel_snapshot *snapshot = obj;
+       struct ao2_container *container = arg;
+       ao2_link_flags(container, snapshot, OBJ_NOLOCK);
+       return 0;
+}
+
+static int gather_endpoint_channels(void *obj, void *arg, int flags) {
+       struct ast_sip_endpoint *endpoint = obj;
+       struct ao2_container *channels = arg;
+       ast_sip_for_each_channel(endpoint, populate_channel_container, channels);
+       return 0;
+}
+
+static struct ao2_container *cli_get_channel_container(struct ast_sorcery *sip_sorcery)
+{
+       RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, s_parent_container, NULL, ao2_cleanup);
+       struct ao2_container *child_container;
+
+       parent_container = ast_sorcery_retrieve_by_fields(sip_sorcery, "endpoint",
+                               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       if (!parent_container) {
+               return NULL;
+       }
+
+       s_parent_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+       if (!s_parent_container) {
+               return NULL;
+       }
+
+       if (ao2_container_dup(s_parent_container, parent_container, OBJ_ORDER_ASCENDING)) {
+               return NULL;
+       }
+
+       child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+       if (!child_container) {
+               return NULL;
+       }
+
+       ao2_callback(s_parent_container, OBJ_NODATA, gather_endpoint_channels, child_container);
+
+       return child_container;
+}
+
+static int cli_print_channel_header(void *obj, void *arg, int flags)
+{
+       struct ast_sip_cli_context *context = arg;
+       int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       int filler = CLI_LAST_TABSTOP - indent - 13;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+       ast_str_append(&context->output_buffer, 0,
+               "%*s:  <ChannelId%*.*s>  <State.....>  <Time(sec)>\n",
+               indent, "Channel", filler, filler, CLI_HEADER_FILLER);
+
+       context->indent_level++;
+       indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       filler = CLI_LAST_TABSTOP - indent - 38;
+       ast_str_append(&context->output_buffer, 0,
+               "%*s:  <Codec>  Exten: <DialedExten%*.*s>  CLCID: <ConnectedLineCID.......>\n",
+               indent, "Codec", filler, filler, CLI_HEADER_FILLER);
+       context->indent_level--;
+       return 0;
+}
+
+static int cli_print_channel_body(void *obj, void *arg, int flags) {
+       struct ast_channel_snapshot *snapshot = obj;
+       struct ast_sip_cli_context *context = arg;
+       struct timeval current_time;
+       char *print_name = NULL;
+       int print_name_len;
+       int indent;
+       int flexwidth;
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       gettimeofday(&current_time, NULL);
+
+       print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
+       if (!(print_name = alloca(print_name_len))) {
+               return -1;
+       }
+
+       snprintf(print_name, print_name_len, "%s/%s", snapshot->name, snapshot->appl);
+
+       indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       flexwidth = CLI_LAST_TABSTOP - indent;
+
+       ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s  %11ld\n",
+               CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
+               flexwidth, flexwidth,
+               print_name,
+               ast_state2str(snapshot->state),
+               current_time.tv_sec - snapshot->creationtime.tv_sec);
+
+       context->indent_level++;
+       indent = CLI_INDENT_TO_SPACES(context->indent_level);
+       flexwidth = CLI_LAST_TABSTOP - indent - 25;
+
+       ast_str_append(&context->output_buffer, 0, "%*s:  %-7s  Exten: %-*.*s  CLCID: \"%s\" <%s>\n",
+               indent, "Codec",
+               snapshot->nativeformats,
+               flexwidth, flexwidth,
+               snapshot->exten,
+               snapshot->connected_name,
+               snapshot->connected_number
+               );
+       context->indent_level--;
+       return 0;
+}
+
+static struct ao2_container *cli_get_endpoint_container(struct ast_sorcery *sip_sorcery)
+{
+       return ast_sip_get_endpoints();
+}
+
+static int cli_print_endpoint_header(void *obj, void *arg, int flags)
 {
-       if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
+       struct ast_sip_cli_context *context = arg;
+       struct ast_sip_cli_formatter_entry *formatter_entry;
+
+       if (!context->output_buffer) {
                return -1;
        }
+       ast_str_append(&context->output_buffer, 0,
+                       " <Endpoint/CID................................................>  <State.....>  <Channels.>\n");
+
+       if (context->recurse) {
+               context->indent_level++;
+               formatter_entry = ast_sip_lookup_cli_formatter("auth");
+               if (formatter_entry) {
+                       formatter_entry->print_header(NULL, context, 0);
+               }
+               formatter_entry = ast_sip_lookup_cli_formatter("aor");
+               if (formatter_entry) {
+                       formatter_entry->print_header(NULL, context, 0);
+               }
+               formatter_entry = ast_sip_lookup_cli_formatter("identify");
+               if (formatter_entry) {
+                       formatter_entry->print_header(NULL, context, 0);
+               }
+               formatter_entry = ast_sip_lookup_cli_formatter("channel");
+               if (formatter_entry) {
+                       formatter_entry->print_header(NULL, context, 0);
+               }
+               context->indent_level--;
+       }
+       return 0;
+}
+
+static int cli_print_endpoint_body(void *obj, void *arg, int flags) {
+       struct ast_sip_endpoint *endpoint = obj;
+       RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot, ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+       struct ast_sip_cli_context *context = arg;
+       const char *id = ast_sorcery_object_get_id(endpoint);
+       struct ast_sip_cli_formatter_entry *formatter_entry;
+       char *print_name = NULL;
+       int print_name_len;
+       char *number = S_COR(endpoint->id.self.number.valid,
+               endpoint->id.self.number.str, NULL);
+
+       if (!context->output_buffer) {
+               return -1;
+       }
+
+       context->current_endpoint = endpoint;
+
+       if (number) {
+               print_name_len = strlen(id) + strlen(number) + 2;
+               if (!(print_name = alloca(print_name_len))) {
+                       return -1;
+               }
+               snprintf(print_name, print_name_len, "%s/%s", id, number);
+       }
+
+       ast_str_append(&context->output_buffer, 0, " %-62s  %-12.12s  %d of %.0f\n",
+               print_name ? print_name : id,
+               ast_sip_get_device_state(endpoint),
+               endpoint_snapshot->num_channels,
+               (double) endpoint->devicestate_busy_at ? endpoint->devicestate_busy_at :
+                                                                                                               INFINITY
+                                                                                                               );
+
+       if (context->recurse) {
+               context->indent_level++;
+
+               formatter_entry = ast_sip_lookup_cli_formatter("auth");
+               if (formatter_entry) {
+                       context->auth_direction = "Out";
+                       ast_sip_for_each_auth(&endpoint->outbound_auths, formatter_entry->print_body, context);
+                       context->auth_direction = "In";
+                       ast_sip_for_each_auth(&endpoint->inbound_auths, formatter_entry->print_body, context);
+               }
+               formatter_entry = ast_sip_lookup_cli_formatter("aor");
+               if (formatter_entry) {
+                       ast_sip_for_each_aor(endpoint->aors, formatter_entry->print_body, context);
+               }
+               formatter_entry = ast_sip_lookup_cli_formatter("channel");
+               if (formatter_entry) {
+                       ast_sip_for_each_channel(endpoint, formatter_entry->print_body, context);
+               }
+
+               context->indent_level--;
+       }
+
+       if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+               ast_str_append(&context->output_buffer, 0, "\n");
+               ast_sip_cli_print_sorcery_objectset(endpoint, context, 0);
+       }
+
+       return 0;
+}
+
+static struct ast_sip_cli_formatter_entry cli_channel_formatter = {
+       .name = "channel",
+       .print_header = cli_print_channel_header,
+       .print_body = cli_print_channel_body,
+       .get_container = cli_get_channel_container,
+};
+
+static struct ast_sip_cli_formatter_entry cli_endpoint_formatter = {
+       .name = "endpoint",
+       .print_header = cli_print_endpoint_header,
+       .print_body = cli_print_endpoint_body,
+       .get_container = cli_get_endpoint_container,
+};
+
+
+int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info)
+{
 
        if (ast_manager_register_xml(AMI_SHOW_ENDPOINTS, EVENT_FLAG_SYSTEM, ami_show_endpoints) ||
            ast_manager_register_xml(AMI_SHOW_ENDPOINT, EVENT_FLAG_SYSTEM, ami_show_endpoint)) {
@@ -1266,6 +1371,11 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
        ast_sorcery_apply_config(sip_sorcery, "res_pjsip");
 
+       ast_sip_initialize_cli(sip_sorcery);
+       ast_sip_register_cli_formatter(&cli_channel_formatter);
+       ast_sip_register_cli_formatter(&cli_endpoint_formatter);
+
+
        if (ast_sip_initialize_sorcery_auth(sip_sorcery)) {
                ast_log(LOG_ERROR, "Failed to register SIP authentication support\n");
                ast_sorcery_unref(sip_sorcery);
@@ -1411,7 +1521,6 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
 void ast_res_pjsip_destroy_configuration(void)
 {
-       ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
        ast_manager_unregister(AMI_SHOW_ENDPOINT);
        ast_manager_unregister(AMI_SHOW_ENDPOINTS);
        ast_sorcery_unref(sip_sorcery);
index fe2948a..34c8f2c 100644 (file)
@@ -255,6 +255,7 @@ static int reload_module(void)
 
 static int unload_module(void)
 {
+       ast_sip_unregister_endpoint_formatter(&endpoint_identify_formatter);
        ast_sip_unregister_endpoint_identifier(&ip_identifier);
        return 0;
 }
index 9771bdc..89e8cd1 100644 (file)
@@ -590,11 +590,9 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 
 /* function pointer to callback needs to be within the module
    in order to avoid problems with an undefined symbol */
-static int sip_contact_to_str(const struct ast_sip_aor *aor,
-                             const struct ast_sip_contact *contact,
-                             int last, void *arg)
+static int sip_contact_to_str(void *acp, void *arg, int flags)
 {
-       return ast_sip_contact_to_str(aor, contact, last, arg);
+       return ast_sip_contact_to_str(acp, arg, flags);
 }
 
 static int ami_registrations_aor(void *obj, void *arg, int flags)