sorcery: Create AST_SORCERY dialplan function.
authorGeorge Joseph <george.joseph@fairview5.com>
Thu, 6 Mar 2014 22:39:54 +0000 (22:39 +0000)
committerGeorge Joseph <george.joseph@fairview5.com>
Thu, 6 Mar 2014 22:39:54 +0000 (22:39 +0000)
This patch creates the AST_SORCERY dialplan function which allows someone to
retrieve any value from a sorcery-based config file.  It's similar to
AST_CONFIG.

The creation of the function itself was fairly straightforward but it required
changes to the underlying sorcery infrastructure that rippled into individual
sorcery objects.  The changes stemmed from inconsistencies in how sorcery
created ast_variable objectsets from sorcery objects and the inconsistency
in how individual objects used that feature especially when it came to
parameters that can be specified multiple times like contact in aor and match
in identify.  You can read more here...
http://lists.digium.com/pipermail/asterisk-dev/2014-February/065202.html

So, what this patch does, besides actually creating the AST_SORCERY function,
is the following...

* Creates ast_variable_list_append which is a helper to append one ast_variable
  list to another.
* Modifies the ast_sorcery_object_field_register functions to accept the
  already-defined sorcery_fields_handler callback.
* Modifies ast_sorcery_objectset_create to accept a parameter indicating return
  type preference...a single ast_variable with all values concatenated or an
  ast_variable list with multiple entries.  Also fixed a few bugs.
* Modifies individual sorcery object implementations to use the new function
  definition of the ast_sorcery_object_field_register functions.
* Modifies location.c and res_pjsip_endpoint_identifier_ip.c to implement
  sorcery_fields_handler handlers so they return multiple occurrences as an
  ast_variable_list.
* Added a whole bunch of tests to test_sorcery.

(closes issue ASTERISK-22537)
Review: http://reviewboard.asterisk.org/r/3254/

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

15 files changed:
CHANGES
funcs/func_sorcery.c [new file with mode: 0644]
include/asterisk/config.h
include/asterisk/sorcery.h
main/bucket.c
main/config.c
main/sorcery.c
res/res_pjsip/config_auth.c
res/res_pjsip/config_transport.c
res/res_pjsip/location.c
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_acl.c
res/res_pjsip_endpoint_identifier_ip.c
res/res_pjsip_outbound_registration.c
tests/test_sorcery.c

diff --git a/CHANGES b/CHANGES
index 91acc9e..81ed1bc 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -98,6 +98,12 @@ ARI
  * The live recording object on recording events now contains a target_uri
    field which contains the URI of what is being recorded.
 
+Core
+------------------
+ * Exposed sorcery-based configuration files like pjsip.conf to dialplans via
+   the new AST_SORCERY diaplan function.
+
+
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 12.0.0 to Asterisk 12.1.0 ------------
 ------------------------------------------------------------------------------
diff --git a/funcs/func_sorcery.c b/funcs/func_sorcery.c
new file mode 100644 (file)
index 0000000..1671b3f
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+/*! \file
+ *
+ * \brief Get a field from a sorcery object
+ *
+ * \author \verbatim George Joseph <george.joseph@fairview5.com> \endverbatim
+ *
+ * \ingroup functions
+ *
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "")
+
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/sorcery.h"
+
+/*** DOCUMENTATION
+       <function name="AST_SORCERY" language="en_US">
+               <synopsis>
+                       Get a field from a sorcery object
+               </synopsis>
+               <syntax>
+                       <parameter name="module_name" required="true">
+                               <para>The name of the module owning the sorcery instance.</para>
+                       </parameter>
+                       <parameter name="object_type" required="true">
+                               <para>The type of object to query.</para>
+                       </parameter>
+                       <parameter name="object_id" required="true">
+                               <para>The id of the object to query.</para>
+                       </parameter>
+                       <parameter name="field_name" required="true">
+                               <para>The name of the field.</para>
+                       </parameter>
+                       <parameter name="retrieval_method" required="false">
+                               <para>Fields that have multiple occurrences may be retrieved in two ways.</para>
+                               <enumlist>
+                                       <enum name="concat"><para>Returns all matching fields concatenated
+                                       in a single string separated by <replaceable>separator</replaceable>
+                                       which defaults to <literal>,</literal>.</para></enum>
+
+                                       <enum name="single"><para>Returns the nth occurrence of the field
+                                       as specified by <replaceable>occurrence_number</replaceable> which defaults to <literal>1</literal>.
+                                       </para></enum>
+                               </enumlist>
+                               <para>The default is <literal>concat</literal> with separator <literal>,</literal>.</para>
+                       </parameter>
+                       <parameter name="retrieval_details" required="false">
+                               <para>Specifies either the separator for <literal>concat</literal>
+                               or the occurrence number for <literal>single</literal>.</para>
+                       </parameter>
+               </syntax>
+       </function>
+***/
+
+static int sorcery_function_read(struct ast_channel *chan,
+       const char *cmd, char *data, struct ast_str **buf, ssize_t len)
+{
+       char *parsed_data = ast_strdupa(data);
+       RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+       RAII_VAR(void *, sorcery_obj, NULL, ao2_cleanup);
+       struct ast_variable *change_set;
+       struct ast_variable *it_change_set;
+       int found, field_number = 1, ix, method;
+       char *separator = ",";
+
+       enum methods {
+               CONCAT,
+               SINGLE,
+       };
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(module_name);
+               AST_APP_ARG(object_type);
+               AST_APP_ARG(object_id);
+               AST_APP_ARG(field_name);
+               AST_APP_ARG(method);
+               AST_APP_ARG(method_arg);
+       );
+
+       /* Check for zero arguments */
+       if (ast_strlen_zero(parsed_data)) {
+               ast_log(AST_LOG_ERROR, "Cannot call %s without arguments\n", cmd);
+               return -1;
+       }
+
+       AST_STANDARD_APP_ARGS(args, parsed_data);
+
+       if (ast_strlen_zero(args.module_name)) {
+               ast_log(AST_LOG_ERROR, "Cannot call %s without a module name to query\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.object_type)) {
+               ast_log(AST_LOG_ERROR, "Cannot call %s with an empty object type\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.object_id)) {
+               ast_log(AST_LOG_ERROR, "Cannot call %s with an empty object name\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.field_name)) {
+               ast_log(AST_LOG_ERROR, "Cannot call %s with an empty field name\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.method)) {
+               method = CONCAT;
+       } else {
+               if (strcmp(args.method, "concat") == 0) {
+                       method = CONCAT;
+                       if (ast_strlen_zero(args.method_arg)) {
+                               separator = ",";
+                       } else {
+                               separator = args.method_arg;
+                       }
+
+               } else if (strcmp(args.method, "single") == 0) {
+                       method = SINGLE;
+                       if (!ast_strlen_zero(args.method_arg)) {
+                               if (sscanf(args.method_arg, "%30d", &field_number) <= 0 || field_number <= 0 ) {
+                                       ast_log(AST_LOG_ERROR, "occurrence_number must be a positive integer\n");
+                                       return -1;
+                               }
+                       }
+               } else {
+                       ast_log(AST_LOG_ERROR, "Retrieval method must be 'concat' or 'single'\n");
+                       return -1;
+               }
+       }
+
+       sorcery = ast_sorcery_retrieve_by_module_name(args.module_name);
+       if (!sorcery) {
+               ast_log(AST_LOG_ERROR, "Failed to retrieve sorcery instance for module %s\n", args.module_name);
+               return -1;
+       }
+
+       sorcery_obj = ast_sorcery_retrieve_by_id(sorcery, args.object_type, args.object_id);
+       if (!sorcery_obj) {
+               return -1;
+       }
+
+       change_set = ast_sorcery_objectset_create(sorcery, sorcery_obj);
+       if (!change_set) {
+               return -1;
+       }
+
+       ix=1;
+       found = 0;
+       for (it_change_set = change_set; it_change_set; it_change_set = it_change_set->next) {
+
+               if (method == CONCAT && strcmp(it_change_set->name, args.field_name) == 0) {
+                       ast_str_append(buf, 0, "%s%s", it_change_set->value, separator);
+                       found = 1;
+                       continue;
+               }
+
+               if (method == SINGLE && strcmp(it_change_set->name, args.field_name) == 0  && ix++ == field_number) {
+                       ast_str_set(buf, len, "%s", it_change_set->value);
+                       found = 1;
+                       break;
+               }
+       }
+
+       ast_variables_destroy(change_set);
+
+       if (!found) {
+               return -1;
+       }
+
+       if (method == CONCAT) {
+               ast_str_truncate(*buf, -1);
+       }
+
+       return 0;
+}
+
+static struct ast_custom_function sorcery_function = {
+       .name = "AST_SORCERY",
+       .read2 = sorcery_function_read,
+};
+
+static int unload_module(void)
+{
+       return ast_custom_function_unregister(&sorcery_function);
+}
+
+static int load_module(void)
+{
+       return ast_custom_function_register(&sorcery_function);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Get a field from a sorcery object");
+
index 6d341db..1c10d17 100644 (file)
@@ -691,7 +691,34 @@ 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 Performs an in-place sort on the variable list by ascending name
+ *
+ * \param head The variable list head
+ *
+ * \return The new list head
+ */
+struct ast_variable *ast_variable_list_sort(struct ast_variable *head);
+
+/*!
+ * \brief Appends a variable list to the end of another list
+ *
+ * \param head A pointer to an ast_variable * of the existing variable list head. May NOT be NULL
+ * but the content may be to initialize a new list.  If so, upon return, this parameter will be updated
+ * with a pointer to the new list head.
+ * \param search_hint The place in the current list to start searching for the end of the list.
+ * Might help performance on longer lists.  If NULL, it defaults to head.
+ * \param new_var The head of the new variable list to be appended
+ *
+ * \return The tail of the resulting list.
+ *
+ * \note If the existing *head is NULL, it will be updated to new_var.  This allows you to call
+ * ast_variable_list_append in a loop or callback without initializing the list first.
+ */
+struct ast_variable *ast_variable_list_append_hint(struct ast_variable **head, struct ast_variable *search_hint,
+       struct ast_variable *new_var);
+#define ast_variable_list_append(head, new_var) ast_variable_list_append_hint(head, NULL, new_var)
 
 /*!
  * \brief Update variable value within a config
index cf11528..d4d62bf 100644 (file)
@@ -119,6 +119,24 @@ enum ast_sorcery_retrieve_flags {
        AST_RETRIEVE_FLAG_ALL = (1 << 1),
 };
 
+/*!
+ * \brief Field handler flags
+ */
+enum ast_sorcery_field_handler_flags {
+       /*! \brief Try both handlers, string first */
+       AST_HANDLER_PREFER_STRING,
+
+       /*! \brief Try both handlers, list first */
+       AST_HANDLER_PREFER_LIST,
+
+       /*! \brief Use string handler only */
+       AST_HANDLER_ONLY_STRING,
+
+       /*! \brief Use list handler only */
+       AST_HANDLER_ONLY_LIST,
+};
+
+
 /*! \brief Forward declaration for the sorcery main structure */
 struct ast_sorcery;
 
@@ -465,15 +483,19 @@ int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *
  * \param type Type of object
  * \param name Name of the field
  * \param default_val Default value of the field
+ * \param config_handler A custom handler for translating the string representation of the fields
+ * \param sorcery_handler A custom handler for translating the native representation of the fields
+ * \param multiple_handler A custom handler for translating the native representation of the fields
  * \param opt_type Option type
  * \param flags Option type specific flags
  *
  * \retval 0 success
  * \retval -1 failure
  */
-int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type,
-                                        aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, unsigned int no_doc,
-                                        size_t argc, ...);
+int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type,
+       const char *name, const char *default_val, enum aco_option_type opt_type,
+       aco_option_handler config_handler, sorcery_field_handler sorcery_handler,
+       sorcery_fields_handler multiple_handler, unsigned int flags, unsigned int no_doc, size_t argc, ...);
 
 /*!
  * \brief Register a field within an object
@@ -489,7 +511,7 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
  * \retval -1 failure
  */
 #define ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, flags, ...) \
-    __ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, NULL, NULL, flags, 0, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
+    __ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, NULL, NULL, NULL, flags, 0, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
 
 /*!
  * \brief Register a field within an object without documentation
@@ -505,7 +527,7 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
  * \retval -1 failure
  */
 #define ast_sorcery_object_field_register_nodoc(sorcery, type, name, default_val, opt_type, flags, ...) \
-    __ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, NULL, NULL, flags, 1, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
+    __ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, NULL, NULL, NULL, flags, 1, VA_NARGS(__VA_ARGS__), __VA_ARGS__)
 
 /*!
  * \brief Register a field within an object with custom handlers
@@ -516,13 +538,14 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
  * \param default_val Default value of the field
  * \param config_handler Custom configuration handler
  * \param sorcery_handler Custom sorcery handler
+ * \param multiple_handler Custom multiple handler
  * \param flags Option type specific flags
  *
  * \retval 0 success
  * \retval -1 failure
  */
-#define ast_sorcery_object_field_register_custom(sorcery, type, name, default_val, config_handler, sorcery_handler, flags, ...) \
-    __ast_sorcery_object_field_register(sorcery, type, name, default_val, OPT_CUSTOM_T, config_handler, sorcery_handler, flags, 0, VA_NARGS(__VA_ARGS__), __VA_ARGS__);
+#define ast_sorcery_object_field_register_custom(sorcery, type, name, default_val, config_handler, sorcery_handler, multiple_handler, flags, ...) \
+    __ast_sorcery_object_field_register(sorcery, type, name, default_val, OPT_CUSTOM_T, config_handler, sorcery_handler, multiple_handler, flags, 0, VA_NARGS(__VA_ARGS__), __VA_ARGS__);
 
 /*!
  * \brief Register a field within an object with custom handlers without documentation
@@ -533,13 +556,14 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
  * \param default_val Default value of the field
  * \param config_handler Custom configuration handler
  * \param sorcery_handler Custom sorcery handler
+ * \param multiple_handler Custom multiple handler
  * \param flags Option type specific flags
  *
  * \retval 0 success
  * \retval -1 failure
  */
-#define ast_sorcery_object_field_register_custom_nodoc(sorcery, type, name, default_val, config_handler, sorcery_handler, flags, ...) \
-    __ast_sorcery_object_field_register(sorcery, type, name, default_val, OPT_CUSTOM_T, config_handler, sorcery_handler, flags, 1, VA_NARGS(__VA_ARGS__), __VA_ARGS__);
+#define ast_sorcery_object_field_register_custom_nodoc(sorcery, type, name, default_val, config_handler, sorcery_handler, multiple_handler, flags, ...) \
+    __ast_sorcery_object_field_register(sorcery, type, name, default_val, OPT_CUSTOM_T, config_handler, sorcery_handler, multiple_handler, flags, 1, VA_NARGS(__VA_ARGS__), __VA_ARGS__);
 
 /*!
  * \brief Inform any wizards to load persistent objects
@@ -578,6 +602,22 @@ void ast_sorcery_reload_object(const struct ast_sorcery *sorcery, const char *ty
  */
 void ast_sorcery_ref(struct ast_sorcery *sorcery);
 
+
+/*!
+ * \brief Create an object set (KVP list) for an object
+ *
+ * \param sorcery Pointer to a sorcery structure
+ * \param object Pointer to a sorcery object
+ * \param flags Flags indicating which handler to use and in what order.
+ *
+ * \retval non-NULL success
+ * \retval NULL if error occurred
+ *
+ * \note The returned ast_variable list must be destroyed using ast_variables_destroy
+ */
+struct ast_variable *ast_sorcery_objectset_create2(const struct ast_sorcery *sorcery,
+       const void *object, enum ast_sorcery_field_handler_flags flags);
+
 /*!
  * \brief Create an object set (KVP list) for an object
  *
@@ -588,8 +628,14 @@ void ast_sorcery_ref(struct ast_sorcery *sorcery);
  * \retval NULL if error occurred
  *
  * \note The returned ast_variable list must be destroyed using ast_variables_destroy
+ *
+ * \note This function attempts to use a field's sorcery_fields_handler first and if that
+ * doesn't exist or fails, a field's sorcery_field_handler is used.  The difference is
+ * that the former may return multiple list entries for the same field and the latter will only
+ * return 1.  It's up to the field itself to determine what the appropriate content is.
  */
-struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorcery, const void *object);
+#define ast_sorcery_objectset_create(sorcery, object) \
+       ast_sorcery_objectset_create2(sorcery, object, AST_HANDLER_PREFER_LIST)
 
 /*!
  * \brief Create an object set in JSON format for an object
index b3a0d3c..f698c57 100644 (file)
@@ -944,8 +944,8 @@ int ast_bucket_init(void)
        }
 
        ast_sorcery_object_field_register(bucket_sorcery, "bucket", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket, scheme));
-       ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket, created));
-       ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket, modified));
+       ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, created));
+       ast_sorcery_object_field_register_custom(bucket_sorcery, "bucket", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket, modified));
 
        if (ast_sorcery_apply_default(bucket_sorcery, "file", "bucket_file", NULL)) {
                ast_log(LOG_ERROR, "Failed to apply intermediary for 'file' object type in Bucket sorcery\n");
@@ -958,8 +958,8 @@ int ast_bucket_init(void)
        }
 
        ast_sorcery_object_field_register(bucket_sorcery, "file", "scheme", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_bucket_file, scheme));
-       ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket_file, created));
-       ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, 0, FLDSET(struct ast_bucket_file, modified));
+       ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "created", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, created));
+       ast_sorcery_object_field_register_custom(bucket_sorcery, "file", "modified", "", timeval_str2struct, timeval_struct2str, NULL, 0, FLDSET(struct ast_bucket_file, modified));
 
        return 0;
 }
index 70ef6a8..db9182a 100644 (file)
@@ -620,6 +620,26 @@ struct ast_variable *ast_variable_list_sort(struct ast_variable *start)
        return top.next;
 }
 
+struct ast_variable *ast_variable_list_append_hint(struct ast_variable **head, struct ast_variable *search_hint, struct ast_variable *newvar)
+{
+       struct ast_variable *curr;
+       ast_assert(head != NULL);
+
+       if (!*head) {
+               *head = newvar;
+       } else {
+               if (search_hint == NULL) {
+                       search_hint = *head;
+               }
+               for (curr = search_hint; curr->next; curr = curr->next);
+               curr->next = newvar;
+       }
+
+       for (curr = newvar; curr->next; curr = curr->next);
+
+       return curr;
+}
+
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
 {
        const char *tmp;
index bbdfa8c..99d051a 100644 (file)
@@ -811,7 +811,7 @@ int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *
 }
 
 int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type,
-                                       aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, unsigned int no_doc, size_t argc, ...)
+                                       aco_option_handler config_handler, sorcery_field_handler sorcery_handler, sorcery_fields_handler multiple_handler, unsigned int flags, unsigned int no_doc, size_t argc, ...)
 {
        RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
        RAII_VAR(struct ast_sorcery_object_field *, object_field, NULL, ao2_cleanup);
@@ -832,6 +832,7 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
 
        ast_copy_string(object_field->name, name, sizeof(object_field->name));
        object_field->handler = sorcery_handler;
+       object_field->multiple_handler = multiple_handler;
 
        va_start(args, argc);
        for (pos = 0; pos < argc; pos++) {
@@ -1015,13 +1016,47 @@ void ast_sorcery_ref(struct ast_sorcery *sorcery)
        ao2_ref(sorcery, +1);
 }
 
-struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorcery, const void *object)
+static struct ast_variable *get_single_field_as_var_list(const void *object, struct ast_sorcery_object_field *object_field)
+{
+       struct ast_variable *tmp = NULL;
+       char *buf = NULL;
+
+       if (!object_field->handler) {
+               return NULL;
+       }
+
+       if (!(object_field->handler(object, object_field->args, &buf))) {
+               tmp = ast_variable_new(object_field->name, S_OR(buf, ""), "");
+       }
+       ast_free(buf);
+
+       return tmp;
+}
+
+static struct ast_variable *get_multiple_fields_as_var_list(const void *object, struct ast_sorcery_object_field *object_field)
+{
+       struct ast_variable *tmp = NULL;
+
+       if (!object_field->multiple_handler) {
+               return NULL;
+       }
+
+       if (object_field->multiple_handler(object, &tmp)) {
+               ast_variables_destroy(tmp);
+               tmp = NULL;
+       }
+
+       return tmp;
+}
+
+struct ast_variable *ast_sorcery_objectset_create2(const struct ast_sorcery *sorcery,
+       const void *object,     enum ast_sorcery_field_handler_flags flags)
 {
        const struct ast_sorcery_object_details *details = object;
        RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
        struct ao2_iterator i;
        struct ast_sorcery_object_field *object_field;
-       struct ast_variable *fields = NULL;
+       struct ast_variable *head = NULL, *tail = NULL;
        int res = 0;
 
        if (!object_type) {
@@ -1033,38 +1068,46 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc
        for (; (object_field = ao2_iterator_next(&i)) && !res; ao2_ref(object_field, -1)) {
                struct ast_variable *tmp = NULL;
 
-               if (object_field->multiple_handler) {
-                       if ((res = object_field->multiple_handler(object, &tmp))) {
-                               ast_variables_destroy(tmp);
+               switch (flags) {
+               case AST_HANDLER_PREFER_LIST:
+                       if ((tmp = get_multiple_fields_as_var_list(object, object_field)) ||
+                               (tmp = get_single_field_as_var_list(object, object_field))) {
+                               break;
                        }
-               } else if (object_field->handler) {
-                       char *buf = NULL;
-
-                       if ((res = object_field->handler(object, object_field->args, &buf)) ||
-                               !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) {
-                               res = -1;
+                       continue;
+               case AST_HANDLER_PREFER_STRING:
+                       if ((tmp = get_single_field_as_var_list(object, object_field)) ||
+                               (tmp = get_multiple_fields_as_var_list(object, object_field))) {
+                               break;
                        }
-
-                       ast_free(buf);
-               } else {
+                       continue;
+               case AST_HANDLER_ONLY_LIST:
+                       if ((tmp = get_multiple_fields_as_var_list(object, object_field))) {
+                               break;
+                       }
+                       continue;
+               case AST_HANDLER_ONLY_STRING:
+                       if ((tmp = get_single_field_as_var_list(object, object_field))) {
+                               break;
+                       }
+                       continue;
+               default:
                        continue;
                }
 
-               if (!res && tmp) {
-                       tmp->next = fields;
-                       fields = tmp;
-               }
+               tail = ast_variable_list_append_hint(&head, tail, tmp);
+
        }
 
        ao2_iterator_destroy(&i);
 
        /* If any error occurs we destroy all fields handled before so a partial objectset is not returned */
        if (res) {
-               ast_variables_destroy(fields);
-               fields = NULL;
+               ast_variables_destroy(head);
+               head = NULL;
        }
 
-       return fields;
+       return head;
 }
 
 struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sorcery, const void *object)
index 056449b..bfba262 100644 (file)
@@ -323,7 +323,7 @@ int ast_sip_initialize_sorcery_auth(void)
        ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
                        "32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
        ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
-                       "userpass", auth_type_handler, auth_type_to_str, 0, 0);
+                       "userpass", auth_type_handler, auth_type_to_str, NULL, 0, 0);
 
        ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
        ast_sip_register_cli_formatter(&cli_auth_formatter);
index 58d0f15..797427b 100644 (file)
@@ -440,6 +440,29 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v
        return error;
 }
 
+static int localnet_to_vl(const void *obj, struct ast_variable **fields)
+{
+       const struct ast_sip_transport *transport = obj;
+
+       char str[MAX_OBJECT_FIELD];
+       struct ast_variable *head = NULL;
+       struct ast_ha *ha = transport->localnet;
+
+       for (; ha; ha = ha->next) {
+               const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+               snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
+                       addr, ast_sockaddr_stringify_addr(&ha->netmask));
+
+               ast_variable_list_append(&head, ast_variable_new("local_net", str, ""));
+       }
+
+       if (head) {
+               *fields = head;
+       }
+
+       return 0;
+}
+
 static int localnet_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
@@ -599,8 +622,8 @@ int ast_sip_initialize_sorcery_transport(void)
        }
 
        ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations));
        ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file));
        ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file));
@@ -610,13 +633,13 @@ int ast_sip_initialize_sorcery_transport(void)
        ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535);
        ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address));
        ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain));
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, localnet_to_vl, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
 
        ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);
index 6c77bee..eb12d8e 100644 (file)
@@ -279,6 +279,25 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab
        return 0;
 }
 
+static int contact_to_vl(void *object, void *arg, int flags)
+{
+       struct ast_sip_contact *contact = object;
+       struct ast_variable **var = arg;
+
+       ast_variable_list_append(&*var, ast_variable_new("contact", contact->uri, ""));
+
+       return 0;
+}
+
+static int contacts_to_vl(const void *obj, struct ast_variable **fields)
+{
+       const struct ast_sip_aor *aor = obj;
+
+       ast_sip_for_each_contact(aor, contact_to_vl, fields);
+
+       return 0;
+}
+
 int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
 {
        char *copy, *name;
@@ -343,7 +362,46 @@ int ast_sip_contact_to_str(void *object, void *arg, int flags)
 
 static int sip_aor_to_ami(const struct ast_sip_aor *aor, struct ast_str **buf)
 {
-       return ast_sip_sorcery_object_to_ami(aor, buf);
+       RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create2(
+                        ast_sip_get_sorcery(), aor, AST_HANDLER_ONLY_STRING), ast_variables_destroy);
+       struct ast_variable *i;
+
+       if (!objset) {
+               return -1;
+       }
+
+       ast_str_append(buf, 0, "ObjectType: %s\r\n",
+                      ast_sorcery_object_get_type(aor));
+       ast_str_append(buf, 0, "ObjectName: %s\r\n",
+                      ast_sorcery_object_get_id(aor));
+
+       for (i = objset; i; i = i->next) {
+               char *camel = ast_to_camel_case(i->name);
+               if (strcmp(camel, "Contact") == 0) {
+                       ast_free(camel);
+                       camel = NULL;
+               }
+               ast_str_append(buf, 0, "%s: %s\r\n", S_OR(camel, "Contacts"), i->value);
+               ast_free(camel);
+       }
+
+       return 0;
+}
+
+static int contacts_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct ast_sip_aor *aor = obj;
+       RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
+
+       ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &str);
+       ast_str_truncate(str, -1);
+
+       *buf = ast_strdup(ast_str_buffer(str));
+       if (!*buf) {
+               return -1;
+       }
+
+       return 0;
 }
 
 static int format_ami_aor_handler(void *obj, void *arg, int flags)
@@ -364,11 +422,6 @@ 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");
-
        total_contacts = ao2_container_count(contacts);
        num_permanent = aor->permanent_contacts ?
                ao2_container_count(aor->permanent_contacts) : 0;
@@ -670,7 +723,7 @@ int ast_sip_initialize_sorcery_location(void)
        ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
        ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
        ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
-       ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 0, 0);
        ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
                                          PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
        ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
@@ -684,7 +737,7 @@ int ast_sip_initialize_sorcery_location(void)
        ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
        ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
        ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
-       ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, contacts_to_str, contacts_to_vl, 0, 0);
        ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes));
        ast_sorcery_object_field_register(sorcery, "aor", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, outbound_proxy));
        ast_sorcery_object_field_register(sorcery, "aor", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, support_path));
index 994987b..110be29 100644 (file)
@@ -809,12 +809,12 @@ static int set_var_handler(const struct aco_option *opt,
        }
 
        *val++ = '\0';
+
        if (!(new_var = ast_variable_new(name, val, ""))) {
                return -1;
        }
 
-       new_var->next = endpoint->channel_vars;
-       endpoint->channel_vars = new_var;
+       ast_variable_list_append(&endpoint->channel_vars, new_var);
 
        return 0;
 }
@@ -834,6 +834,16 @@ static int set_var_to_str(const void *obj, const intptr_t *args, char **buf)
        return 0;
 }
 
+static int set_var_to_vl(const void *obj, struct ast_variable **fields)
+{
+       const struct ast_sip_endpoint *endpoint = obj;
+       if (endpoint->channel_vars) {
+               *fields = ast_variables_dup(endpoint->channel_vars);
+       }
+       return 0;
+}
+
+
 static void *sip_nat_hook_alloc(const char *name)
 {
        return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL);
@@ -1021,8 +1031,8 @@ static void sip_sorcery_object_ami_set_type_name(const void *obj, struct ast_str
 
 int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf)
 {
-       RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create(
-                        ast_sip_get_sorcery(), obj), ast_variables_destroy);
+       RAII_VAR(struct ast_variable *, objset, ast_sorcery_objectset_create2(
+                        ast_sip_get_sorcery(), obj, AST_HANDLER_ONLY_STRING), ast_variables_destroy);
        struct ast_variable *i;
 
        if (!objset) {
@@ -1569,7 +1579,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, media.prefs, media.codecs));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmf_mode", "rfc4733", dtmf_handler, dtmf_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmf_mode", "rfc4733", dtmf_handler, dtmf_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ipv6));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.symmetric));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ice_support", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.ice_support));
@@ -1579,23 +1589,23 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, transport));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, outbound_proxy));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "moh_suggest", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mohsuggest));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "100rel", "yes", prack_handler, prack_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "timers", "yes", timers_handler, timers_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "100rel", "yes", prack_handler, prack_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "timers", "yes", timers_handler, timers_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_min_se", "90", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.min_se));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "timers_sess_expires", "1800", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, extensions.timer.sess_expires));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "auth", "", inbound_auth_handler, inbound_auths_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "auth", "", inbound_auth_handler, inbound_auths_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aors", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, aors));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.address));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, ident_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, ident_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.enabled));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, direct_media_method_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, connected_line_method_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, direct_media_glare_mitigation_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, direct_media_method_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, connected_line_method_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, direct_media_glare_mitigation_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disable_direct_media_on_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.disable_on_nat));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, caller_id_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_privacy", "", caller_id_privacy_handler, caller_id_privacy_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_tag", "", caller_id_tag_handler, caller_id_tag_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, caller_id_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_privacy", "", caller_id_privacy_handler, caller_id_privacy_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid_tag", "", caller_id_tag_handler, caller_id_tag_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_inbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_inbound));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.trust_outbound));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_pai));
@@ -1603,17 +1613,17 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, media_encryption_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.use_avpf));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickup_group", "", group_handler, pickupgroup_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_call_group", "", named_groups_handler, named_callgroups_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_pickup_group", "", named_groups_handler, named_pickupgroups_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickup_group", "", group_handler, pickupgroup_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_call_group", "", named_groups_handler, named_callgroups_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "named_pickup_group", "", named_groups_handler, named_pickupgroups_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "device_state_busy_at", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, devicestate_busy_at));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.enabled));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "t38_udptl_ec", "none", t38udptl_ec_handler, t38udptl_ec_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "t38_udptl_ec", "none", t38udptl_ec_handler, t38udptl_ec_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl_maxdatagram", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.t38.maxdatagram));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "fax_detect", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, faxdetect));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "t38_udptl_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.t38.nat));
@@ -1625,8 +1635,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_transfer", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allowtransfer));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_owner", "-", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpowner));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sdp_session", "Asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.sdpsession));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_video", "0", tos_handler, tos_video_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_audio", "0", tos_handler, tos_audio_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "tos_video", "0", tos_handler, tos_video_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_audio", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_audio));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow));
@@ -1635,17 +1645,17 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_verify", "", dtls_handler, dtlsverify_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_rekey", "", dtls_handler, dtlsrekey_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cert_file", "", dtls_handler, dtlscertfile_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_private_key", "", dtls_handler, dtlsprivatekey_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cipher", "", dtls_handler, dtlscipher_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_file", "", dtls_handler, dtlscafile_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_path", "", dtls_handler, dtlscapath_to_str, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_setup", "", dtls_handler, dtlssetup_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_verify", "", dtls_handler, dtlsverify_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_rekey", "", dtls_handler, dtlsrekey_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cert_file", "", dtls_handler, dtlscertfile_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_private_key", "", dtls_handler, dtlsprivatekey_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_cipher", "", dtls_handler, dtlscipher_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_file", "", dtls_handler, dtlscafile_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_ca_path", "", dtls_handler, dtlscapath_to_str, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtls_setup", "", dtls_handler, dtlssetup_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "srtp_tag_32", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.srtp_tag_32));
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "redirect_method", "user", redirect_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "set_var", "", set_var_handler, set_var_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "redirect_method", "user", redirect_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "set_var", "", set_var_handler, set_var_to_str, set_var_to_vl, 0, 0);
 
        if (ast_sip_initialize_sorcery_transport()) {
                ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
index 48c2ea4..acb073d 100644 (file)
@@ -278,12 +278,12 @@ static int load_module(void)
        }
 
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "type", "", OPT_NOOP_T, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "permit", "", acl_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "deny", "", acl_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "acl", "", acl_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_permit", "", acl_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_deny", "", acl_handler, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_acl", "", acl_handler, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "permit", "", acl_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "deny", "", acl_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "acl", "", acl_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_permit", "", acl_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_deny", "", acl_handler, NULL, NULL, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), SIP_SORCERY_ACL_TYPE, "contact_acl", "", acl_handler, NULL, NULL, 0, 0);
 
        ast_sip_register_service(&acl_module);
        return AST_MODULE_LOAD_SUCCESS;
index d78901e..414f430 100644 (file)
@@ -184,7 +184,8 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va
        return error;
 }
 
-static int ip_identify_match_to_str(const void *obj, const intptr_t *args, char **buf)
+
+static int match_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
        const struct ip_identify_match *identify = obj;
@@ -194,6 +195,29 @@ static int ip_identify_match_to_str(const void *obj, const intptr_t *args, char
        return 0;
 }
 
+static int match_to_var_list(const void *obj, struct ast_variable **fields)
+{
+       char str[MAX_OBJECT_FIELD];
+       const struct ip_identify_match *identify = obj;
+       struct ast_variable *head = NULL;
+       struct ast_ha *ha = identify->matches;
+
+       for (; ha; ha = ha->next) {
+               const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
+               snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
+                       addr, ast_sockaddr_stringify_addr(&ha->netmask));
+
+               ast_variable_list_append(&head, ast_variable_new("match", str, ""));
+
+       }
+
+       if (head) {
+               *fields = head;
+       }
+
+       return 0;
+}
+
 static int sip_identify_to_ami(const struct ip_identify_match *identify,
                               struct ast_str **buf)
 {
@@ -374,7 +398,7 @@ static int load_module(void)
 
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, ip_identify_match_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, match_to_str, match_to_var_list, 0, 0);
        ast_sorcery_reload_object(ast_sip_get_sorcery(), "identify");
 
        ast_sip_register_endpoint_identifier(&ip_identifier);
index 0ef9507..019ac51 100644 (file)
@@ -891,9 +891,28 @@ static int outbound_auth_handler(const struct aco_option *opt, struct ast_variab
 static int outbound_auths_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        const struct sip_outbound_registration *registration = obj;
+
        return ast_sip_auths_to_str(&registration->outbound_auths, buf);
 }
 
+static int outbound_auths_to_var_list(const void *obj, struct ast_variable **fields)
+{
+       const struct sip_outbound_registration *registration = obj;
+       int i;
+       struct ast_variable *head = NULL;
+
+       for (i = 0; i < AST_VECTOR_SIZE(&registration->outbound_auths) ; i++) {
+               ast_variable_list_append(&head, ast_variable_new("outbound_auth",
+                       AST_VECTOR_GET(&registration->outbound_auths, i), ""));
+       }
+
+       if (head) {
+               *fields = head;
+       }
+
+       return 0;
+}
+
 static struct sip_outbound_registration *retrieve_registration(const char *registration_name)
 {
        return ast_sorcery_retrieve_by_id(
@@ -1083,7 +1102,7 @@ static int ami_outbound_registration_detail(void *obj, void *arg, int flags)
 static int ami_show_outbound_registrations(struct mansession *s,
                                           const struct message *m)
 {
-       struct ast_sip_ami ami = { s = s, m = m };
+       struct ast_sip_ami ami = { .s = s, .m = m };
        struct sip_ami_outbound ami_outbound = { .ami = &ami };
        RAII_VAR(struct ao2_container *, regs, ast_sorcery_retrieve_by_fields(
                         ast_sip_get_sorcery(), "registration", AST_RETRIEVE_FLAG_MULTIPLE |
@@ -1240,7 +1259,7 @@ static int load_module(void)
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
-       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, 0, 0);
+       ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, support_path));
        ast_sorcery_reload_object(ast_sip_get_sorcery(), "registration");
        sip_outbound_registration_perform_all();
index fe1992b..ed4d604 100644 (file)
@@ -26,6 +26,7 @@
 
 /*** MODULEINFO
        <depend>TEST_FRAMEWORK</depend>
+       <depend>func_sorcery</depend>
        <support_level>core</support_level>
  ***/
 
@@ -36,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "")
 #include "asterisk/test.h"
 #include "asterisk/module.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/logger.h"
 #include "asterisk/json.h"
@@ -45,12 +47,22 @@ struct test_sorcery_object {
        SORCERY_OBJECT(details);
        unsigned int bob;
        unsigned int joe;
+       struct ast_variable *jim;
+       struct ast_variable *jack;
 };
 
+/*! \brief Internal function to destroy a test object */
+static void test_sorcery_object_destroy(void *obj)
+{
+       struct test_sorcery_object *tobj = obj;
+       ast_variables_destroy(tobj->jim);
+       ast_variables_destroy(tobj->jack);
+}
+
 /*! \brief Internal function to allocate a test object */
 static void *test_sorcery_object_alloc(const char *id)
 {
-       return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
+       return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), test_sorcery_object_destroy);
 }
 
 /*! \brief Internal function for object set transformation */
@@ -85,6 +97,8 @@ static int test_sorcery_copy(const void *src, void *dst)
        struct test_sorcery_object *obj = dst;
        obj->bob = 10;
        obj->joe = 20;
+       obj->jim = ast_variable_new("jim", "444", "");
+       obj->jack = ast_variable_new("jack", "999,000", "");
        return 0;
 }
 
@@ -235,6 +249,55 @@ static const struct ast_sorcery_observer test_observer = {
        .loaded = sorcery_observer_loaded,
 };
 
+/*  This handler takes a simple value and creates new list entry for it*/
+static int jim_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct test_sorcery_object *tobj = obj;
+
+       ast_variable_list_append(&tobj->jim, ast_variables_dup(var));
+
+       return 0;
+}
+
+/*  This handler takes a CSV string and creates new a new list entry for each value */
+static int jack_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+       struct test_sorcery_object *tobj = obj;
+
+       char *jacks = ast_strdupa(var->value);
+       char *val;
+
+       while ((val = strsep(&jacks, ","))) {
+               ast_variable_list_append(&tobj->jack, ast_variable_new("jack", val, ""));
+       }
+       return 0;
+}
+
+static int jim_vl(const void *obj, struct ast_variable **fields)
+{
+       const struct test_sorcery_object *tobj = obj;
+       if (tobj->jim) {
+               *fields = ast_variables_dup(tobj->jim);
+       }
+       return 0;
+}
+
+static int jack_str(const void *obj, const intptr_t *args, char **buf)
+{
+       const struct test_sorcery_object *tobj = obj;
+       struct ast_variable *curr = tobj->jack;
+       RAII_VAR(struct ast_str *, str, ast_str_create(128), ast_free);
+
+       while(curr) {
+               ast_str_append(&str, 0, "%s,", curr->value);
+               curr = curr->next;
+       }
+       ast_str_truncate(str, -1);
+       *buf = ast_strdup(ast_str_buffer(str));
+       str = NULL;
+       return 0;
+}
+
 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
 {
        struct ast_sorcery *sorcery;
@@ -251,6 +314,8 @@ static struct ast_sorcery *alloc_and_initialize_sorcery(void)
 
        ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob));
        ast_sorcery_object_field_register_nodoc(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe));
+       ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jim", "444", jim_handler, NULL, jim_vl, 0, 0);
+       ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jack", "888,999", jack_handler, jack_str, NULL, 0, 0);
 
        return sorcery;
 }
@@ -727,6 +792,8 @@ AST_TEST_DEFINE(object_copy)
 
        obj->bob = 50;
        obj->joe = 100;
+       jim_handler(NULL, ast_variable_new("jim", "444", ""), obj);
+       jim_handler(NULL, ast_variable_new("jim", "555", ""), obj);
 
        if (!(copy = ast_sorcery_copy(sorcery, obj))) {
                ast_test_status_update(test, "Failed to create a copy of a known valid object\n");
@@ -740,6 +807,22 @@ AST_TEST_DEFINE(object_copy)
        } else if (copy->joe != obj->joe) {
                ast_test_status_update(test, "Value of 'joe' on newly created copy is not the same as original\n");
                res = AST_TEST_FAIL;
+       } else if (!copy->jim) {
+               ast_test_status_update(test, "A new ast_variable was not created for 'jim'\n");
+               res = AST_TEST_FAIL;
+       } else if (copy->jim == obj->jim) {
+               ast_test_status_update(test, "Created copy of 'jim' is actually the ogirinal 'jim'\n");
+               res = AST_TEST_FAIL;
+       } else if (strcmp(copy->jim->value, obj->jim->value)) {
+               ast_test_status_update(test, "Value of 1st 'jim' on newly created copy is not the same as original\n");
+               res = AST_TEST_FAIL;
+       } else if (!copy->jim->next) {
+               ast_test_status_update(test, "A new ast_variable was not created for 2nd 'jim'\n");
+               res = AST_TEST_FAIL;
+       } else if (strcmp(copy->jim->next->value, obj->jim->next->value)) {
+               ast_test_status_update(test, "Value of 2nd 'jim' (%s %s) on newly created copy is not the same as original (%s %s)\n",
+                       copy->jim->value, copy->jim->next->value, obj->jim->value, obj->jim->next->value);
+               res = AST_TEST_FAIL;
        }
 
        return res;
@@ -791,6 +874,12 @@ AST_TEST_DEFINE(object_copy_native)
        } else if (copy->joe != 20) {
                ast_test_status_update(test, "Value of 'joe' on newly created copy is not the predefined native copy value\n");
                res = AST_TEST_FAIL;
+       } else if (!copy->jim) {
+               ast_test_status_update(test, "A new ast_variable was not created for 'jim'\n");
+               res = AST_TEST_FAIL;
+       } else if (strcmp(copy->jim->value, "444")) {
+               ast_test_status_update(test, "Value of 'jim' on newly created copy is not the predefined native copy value\n");
+               res = AST_TEST_FAIL;
        }
 
        return res;
@@ -804,6 +893,7 @@ AST_TEST_DEFINE(object_diff)
        RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy);
        struct ast_variable *field;
        int res = AST_TEST_PASS;
+       int jims = 0;
 
        switch (cmd) {
        case TEST_INIT:
@@ -829,6 +919,8 @@ AST_TEST_DEFINE(object_diff)
 
        obj1->bob = 99;
        obj1->joe = 55;
+       jim_handler(NULL, ast_variable_new("jim", "444", ""), obj1);
+       jim_handler(NULL, ast_variable_new("jim", "555", ""), obj1);
 
        if (!(obj2 = ast_sorcery_alloc(sorcery, "test", "blah2"))) {
               ast_test_status_update(test, "Failed to allocate a second known object type\n");
@@ -837,6 +929,9 @@ AST_TEST_DEFINE(object_diff)
 
        obj2->bob = 99;
        obj2->joe = 42;
+       jim_handler(NULL, ast_variable_new("jim", "444", ""), obj2);
+       jim_handler(NULL, ast_variable_new("jim", "666", ""), obj2);
+       jim_handler(NULL, ast_variable_new("jim", "777", ""), obj2);
 
        if (ast_sorcery_diff(sorcery, obj1, obj2, &changes)) {
               ast_test_status_update(test, "Failed to diff obj1 and obj2\n");
@@ -845,16 +940,30 @@ AST_TEST_DEFINE(object_diff)
               return AST_TEST_FAIL;
        }
 
-       for (field = changes; field; field = field->next) {
-              if (!strcmp(field->name, "joe")) {
-                      if (strcmp(field->value, "42")) {
-                              ast_test_status_update(test, "Object diff produced unexpected value '%s' for joe\n", field->value);
-                              res = AST_TEST_FAIL;
-                      }
-              } else {
-                      ast_test_status_update(test, "Object diff produced unexpected field '%s'\n", field->name);
-                      res = AST_TEST_FAIL;
-              }
+       for (field = changes; field; field = field->next) {
+               if (!strcmp(field->name, "joe")) {
+                       if (strcmp(field->value, "42")) {
+                               ast_test_status_update(test,
+                                       "Object diff produced unexpected value '%s' for joe\n", field->value);
+                               res = AST_TEST_FAIL;
+                       }
+               } else if (!strcmp(field->name, "jim")) {
+                       jims++;
+                       if (!strcmp(field->value, "555")) {
+                               ast_test_status_update(test,
+                                       "Object diff produced unexpected value '%s' for jim\n", field->value);
+                               res = AST_TEST_FAIL;
+                       }
+               } else {
+                       ast_test_status_update(test, "Object diff produced unexpected field '%s'\n",
+                               field->name);
+                       res = AST_TEST_FAIL;
+               }
+       }
+
+       if (jims != 2) {
+              ast_test_status_update(test, "Object diff didn't produce 2 jims\n");
+              res = AST_TEST_FAIL;
        }
 
        return res;
@@ -972,6 +1081,16 @@ AST_TEST_DEFINE(objectset_create)
                                ast_test_status_update(test, "Object set failed to create proper value for 'joe'\n");
                                res = AST_TEST_FAIL;
                        }
+               } else if (!strcmp(field->name, "jim")) {
+                       if (strcmp(field->value, "444")) {
+                               ast_test_status_update(test, "Object set failed to create proper value for 'jim'\n");
+                               res = AST_TEST_FAIL;
+                       }
+               } else if (!strcmp(field->name, "jack")) {
+                       if (strcmp(field->value, "888,999")) {
+                               ast_test_status_update(test, "Object set failed to create proper value (%s) for 'jack'\n", field->value);
+                               res = AST_TEST_FAIL;
+                       }
                } else {
                        ast_test_status_update(test, "Object set created field '%s' which is unknown\n", field->name);
                        res = AST_TEST_FAIL;
@@ -1029,6 +1148,16 @@ AST_TEST_DEFINE(objectset_json_create)
                                ast_test_status_update(test, "Object set failed to create proper value for 'joe'\n");
                                res = AST_TEST_FAIL;
                        }
+               } else if (!strcmp(ast_json_object_iter_key(field), "jim")) {
+                       if (strcmp(ast_json_string_get(value), "444")) {
+                               ast_test_status_update(test, "Object set failed to create proper value for 'jim'\n");
+                               res = AST_TEST_FAIL;
+                       }
+               } else if (!strcmp(ast_json_object_iter_key(field), "jack")) {
+                       if (strcmp(ast_json_string_get(value), "888,999")) {
+                               ast_test_status_update(test, "Object set failed to create proper value for 'jack'\n");
+                               res = AST_TEST_FAIL;
+                       }
                } else {
                        ast_test_status_update(test, "Object set created field '%s' which is unknown\n", ast_json_object_iter_key(field));
                        res = AST_TEST_FAIL;
@@ -2696,6 +2825,176 @@ AST_TEST_DEFINE(configuration_file_wizard_retrieve_multiple_all)
        return AST_TEST_PASS;
 }
 
+AST_TEST_DEFINE(dialplan_function)
+{
+       RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+       RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+       RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
+       struct ast_str *buf;
+       char expression[256];
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "dialplan_function";
+               info->category = "/main/sorcery/";
+               info->summary = "AST_SORCERY dialplan function";
+               info->description =
+                       "Test the AST_SORCERY dialplan function";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       if (!(sorcery = alloc_and_initialize_sorcery())) {
+               ast_test_status_update(test, "Failed to open sorcery structure\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) {
+               ast_test_status_update(test, "Failed to allocate a known object type\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (ast_sorcery_create(sorcery, obj)) {
+               ast_test_status_update(test, "Failed to create a known object type\n");
+               return AST_TEST_FAIL;
+       }
+
+       if (!(buf = ast_str_create(16))) {
+               ast_test_status_update(test, "Failed to allocate return buffer\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "notest_sorcery", "test", "blah", "bob");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Retrieved a non-existent module\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "test_sorcery", "notest", "blah", "bob");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Retrieved a non-existent type\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "test_sorcery", "test", "noid", "bob");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Retrieved a non-existent id\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "test_sorcery", "test", "blah", "nobob");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Retrieved a non-existent field\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "test_sorcery", "test", "blah", "bob");
+       if (ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve field 'bob'\n");
+               return AST_TEST_FAIL;
+       }
+       if (strcmp(ast_str_buffer(buf), "5")) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve field.  Got '%d', should be '5'\n", obj->bob);
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,single,1)", "test_sorcery", "test", "blah", "bob");
+       if (ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve field 'bob'\n");
+               return AST_TEST_FAIL;
+       }
+       if (strcmp(ast_str_buffer(buf), "5")) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve field.  Got '%d', should be '5'\n", obj->bob);
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,single,2)", "test_sorcery", "test", "blah", "bob");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Got a second 'bob' and shouldn't have\n");
+               return AST_TEST_FAIL;
+       }
+
+       /* 444 is already the first item in the list */
+       jim_handler(NULL, ast_variable_new("jim", "555", ""), obj);
+       jim_handler(NULL, ast_variable_new("jim", "666", ""), obj);
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s)", "test_sorcery", "test", "blah", "jim");
+       if (ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Couldn't retrieve 'jim'\n");
+               return AST_TEST_FAIL;
+       }
+       if (strcmp(ast_str_buffer(buf), "444,555,666")) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve jim.  Got '%s', should be '444,555,666'\n", ast_str_buffer(buf));
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,single,2)", "test_sorcery", "test", "blah", "jim");
+       if (ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Couldn't retrieve 2nd jim\n");
+               return AST_TEST_FAIL;
+       }
+       if (strcmp(ast_str_buffer(buf), "555")) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve 2nd jim.  Got '%s', should be '555'\n", ast_str_buffer(buf));
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,concat,|)", "test_sorcery", "test", "blah", "jim");
+       if (ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Couldn't retrieve any 'jim'\n");
+               return AST_TEST_FAIL;
+       }
+       if (strcmp(ast_str_buffer(buf), "444|555|666")) {
+               ast_free(buf);
+               ast_test_status_update(test, "Failed retrieve 'jim'.  Got '%s', should be '444|555|666'\n", ast_str_buffer(buf));
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,noconcat,3)", "test_sorcery", "test", "blah", "jim");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Should have failed with invalid retrieval_type\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_str_reset(buf);
+       snprintf(expression, sizeof(expression), "AST_SORCERY(%s,%s,%s,%s,single,|)", "test_sorcery", "test", "blah", "jim");
+       if (!ast_func_read2(NULL, expression, &buf, 16)) {
+               ast_free(buf);
+               ast_test_status_update(test, "Should have failed with invalid occurrence_number\n");
+               return AST_TEST_FAIL;
+       }
+
+       ast_free(buf);
+
+       return AST_TEST_PASS;
+}
+
 static int unload_module(void)
 {
        AST_TEST_UNREGISTER(wizard_registration);
@@ -2741,6 +3040,7 @@ static int unload_module(void)
        AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_field);
        AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_multiple);
        AST_TEST_UNREGISTER(configuration_file_wizard_retrieve_multiple_all);
+       AST_TEST_UNREGISTER(dialplan_function);
        return 0;
 }
 
@@ -2789,6 +3089,7 @@ static int load_module(void)
        AST_TEST_REGISTER(configuration_file_wizard_retrieve_field);
        AST_TEST_REGISTER(configuration_file_wizard_retrieve_multiple);
        AST_TEST_REGISTER(configuration_file_wizard_retrieve_multiple_all);
+       AST_TEST_REGISTER(dialplan_function);
        return AST_MODULE_LOAD_SUCCESS;
 }