diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h index ae97da5cca..464a83c831 100644 --- a/include/asterisk/sorcery.h +++ b/include/asterisk/sorcery.h @@ -247,13 +247,13 @@ struct ast_sorcery_observer { void (*loaded)(const char *object_type); }; +/*! \brief Opaque structure for internal sorcery object */ +struct ast_sorcery_object; + /*! \brief Structure which contains details about a sorcery object */ struct ast_sorcery_object_details { - /*! \brief Unique identifier of this object */ - char id[AST_UUID_STR_LEN]; - - /*! \brief Type of object */ - char type[MAX_OBJECT_TYPE]; + /*! \brief Pointer to internal sorcery object information */ + struct ast_sorcery_object *object; }; /*! \brief Macro which must be used at the beginning of each sorcery capable object */ @@ -528,6 +528,17 @@ int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, */ int ast_sorcery_changeset_create(const struct ast_variable *original, const struct ast_variable *modified, struct ast_variable **changes); +/*! + * \brief Allocate a generic sorcery capable object + * + * \param size Size of the object + * \param destructor Optional destructor function + * + * \retval non-NULL success + * \retval NULL failure + */ +void *ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor); + /*! * \brief Allocate an object * @@ -695,6 +706,34 @@ const char *ast_sorcery_object_get_id(const void *object); */ const char *ast_sorcery_object_get_type(const void *object); +/*! + * \brief Get an extended field value from a sorcery object + * + * \param object Pointer to a sorcery object + * \param name Name of the extended field value + * + * \retval non-NULL if found + * \retval NULL if not found + * + * \note The returned string does NOT need to be freed and is guaranteed to remain valid for the lifetime of the object + */ +const char *ast_sorcery_object_get_extended(const void *object, const char *name); + +/*! + * \brief Set an extended field value on a sorcery object + * + * \param object Pointer to a sorcery object + * \param name Name of the extended field + * \param value Value of the extended field + * + * \retval 0 success + * \retval -1 failure + * + * \note The field name MUST begin with '@' to indicate it is an extended field. + * \note If the extended field already exists it will be overwritten with the new value. + */ +int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/sorcery.c b/main/sorcery.c index 99e4c55869..cf4cd02137 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -58,6 +58,21 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") /*! \brief Thread pool for observers */ static struct ast_threadpool *threadpool; +/*! \brief Structure for internal sorcery object information */ +struct ast_sorcery_object { + /*! \brief Unique identifier of this object */ + char id[AST_UUID_STR_LEN]; + + /*! \brief Type of object */ + char type[MAX_OBJECT_TYPE]; + + /*! \brief Optional object destructor */ + ao2_destructor_fn destructor; + + /*! \brief Extended object fields */ + struct ast_variable *extended; +}; + /*! \brief Structure for registered object type */ struct ast_sorcery_object_type { /*! \brief Unique name of the object type */ @@ -525,6 +540,24 @@ int __ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, c return sorcery_apply_wizard_mapping(sorcery, type, module, name, data, 0); } +static int sorcery_extended_config_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + return ast_sorcery_object_set_extended(obj, var->name, var->value); +} + +static int sorcery_extended_fields_handler(const void *obj, struct ast_variable **fields) +{ + const struct ast_sorcery_object_details *details = obj; + + if (details->object->extended) { + *fields = ast_variables_dup(details->object->extended); + } else { + *fields = NULL; + } + + return 0; +} + int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, aco_type_item_alloc alloc, sorcery_transform_handler transform, sorcery_apply_handler apply) { RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); @@ -547,6 +580,10 @@ int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, a return -1; } + if (ast_sorcery_object_fields_register(sorcery, type, "^@", sorcery_extended_config_handler, sorcery_extended_fields_handler)) { + return -1; + } + return 0; } @@ -784,7 +821,7 @@ void ast_sorcery_ref(struct ast_sorcery *sorcery) struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorcery, const void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + 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; @@ -816,7 +853,7 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc continue; } - if (!res) { + if (!res && tmp) { tmp->next = fields; fields = tmp; } @@ -836,7 +873,7 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sorcery, const void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + 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_json *json = ast_json_object_create(); @@ -898,7 +935,7 @@ struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sor int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, struct ast_variable *objectset) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); RAII_VAR(struct ast_variable *, transformed, NULL, ast_variables_destroy); struct ast_variable *field; int res = 0; @@ -914,7 +951,7 @@ int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, } for (; field; field = field->next) { - if ((res = aco_process_var(&object_type->type, details->id, field, object))) { + if ((res = aco_process_var(&object_type->type, details->object->id, field, object))) { break; } } @@ -977,6 +1014,32 @@ int ast_sorcery_changeset_create(const struct ast_variable *original, const stru return res; } +static void sorcery_object_destructor(void *object) +{ + struct ast_sorcery_object_details *details = object; + + if (details->object->destructor) { + details->object->destructor(object); + } + + ast_variables_destroy(details->object->extended); +} + +void *ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor) +{ + void *object = ao2_alloc_options(size + sizeof(struct ast_sorcery_object), sorcery_object_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK); + struct ast_sorcery_object_details *details = object; + + if (!object) { + return NULL; + } + + details->object = object + size; + details->object->destructor = destructor; + + return object; +} + void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id) { RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup); @@ -988,12 +1051,12 @@ void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, con } if (ast_strlen_zero(id)) { - ast_uuid_generate_str(details->id, sizeof(details->id)); + ast_uuid_generate_str(details->object->id, sizeof(details->object->id)); } else { - ast_copy_string(details->id, id, sizeof(details->id)); + ast_copy_string(details->object->id, id, sizeof(details->object->id)); } - ast_copy_string(details->type, type, sizeof(details->type)); + ast_copy_string(details->object->type, type, sizeof(details->object->type)); if (aco_set_defaults(&object_type->type, id, details)) { ao2_ref(details, -1); @@ -1006,8 +1069,8 @@ void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, con void *ast_sorcery_copy(const struct ast_sorcery *sorcery, const void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); - struct ast_sorcery_object_details *copy = ast_sorcery_alloc(sorcery, details->type, details->id); + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); + struct ast_sorcery_object_details *copy = ast_sorcery_alloc(sorcery, details->object->type, details->object->id); RAII_VAR(struct ast_variable *, objectset, NULL, ast_variables_destroy); int res = 0; @@ -1120,7 +1183,6 @@ void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const ch unsigned int cached = 0; if (!object_type) { - ast_log(LOG_NOTICE, "Can't find object type '%s'\n", type); return NULL; } @@ -1222,7 +1284,7 @@ static int sorcery_observers_notify_create(void *data) int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); struct sorcery_details sdetails = { .sorcery = sorcery, @@ -1281,7 +1343,7 @@ static int sorcery_wizard_update(void *obj, void *arg, int flags) int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); struct sorcery_details sdetails = { .sorcery = sorcery, @@ -1340,7 +1402,7 @@ static int sorcery_wizard_delete(void *obj, void *arg, int flags) int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object) { const struct ast_sorcery_object_details *details = object; - RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup); + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup); struct sorcery_details sdetails = { .sorcery = sorcery, @@ -1371,13 +1433,55 @@ void ast_sorcery_unref(struct ast_sorcery *sorcery) const char *ast_sorcery_object_get_id(const void *object) { const struct ast_sorcery_object_details *details = object; - return details->id; + return details->object->id; } const char *ast_sorcery_object_get_type(const void *object) { const struct ast_sorcery_object_details *details = object; - return details->type; + return details->object->type; +} + +const char *ast_sorcery_object_get_extended(const void *object, const char *name) +{ + const struct ast_sorcery_object_details *details = object; + struct ast_variable *field; + + for (field = details->object->extended; field; field = field->next) { + if (!strcmp(field->name + 1, name)) { + return field->value; + } + } + + return NULL; +} + +int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value) +{ + RAII_VAR(struct ast_variable *, field, NULL, ast_variables_destroy); + struct ast_variable *extended = ast_variable_new(name, value, ""), *previous = NULL; + const struct ast_sorcery_object_details *details = object; + + if (!extended) { + return -1; + } + + for (field = details->object->extended; field; previous = field, field = field->next) { + if (!strcmp(field->name, name)) { + if (previous) { + previous->next = field->next; + } else { + details->object->extended = field->next; + } + field->next = NULL; + break; + } + } + + extended->next = details->object->extended; + details->object->extended = extended; + + return 0; } int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks) diff --git a/res/res_sip/config_auth.c b/res/res_sip/config_auth.c index 9881dd8c64..47ff42deb7 100644 --- a/res/res_sip/config_auth.c +++ b/res/res_sip/config_auth.c @@ -32,7 +32,7 @@ static void auth_destroy(void *obj) static void *auth_alloc(const char *name) { - struct ast_sip_auth *auth = ao2_alloc(sizeof(*auth), auth_destroy); + struct ast_sip_auth *auth = ast_sorcery_generic_alloc(sizeof(*auth), auth_destroy); if (!auth) { return NULL; diff --git a/res/res_sip/config_domain_aliases.c b/res/res_sip/config_domain_aliases.c index 86b4636ea7..90cd82f942 100644 --- a/res/res_sip/config_domain_aliases.c +++ b/res/res_sip/config_domain_aliases.c @@ -33,7 +33,7 @@ static void domain_alias_destroy(void *obj) static void *domain_alias_alloc(const char *name) { - struct ast_sip_domain_alias *alias = ao2_alloc(sizeof(*alias), domain_alias_destroy); + struct ast_sip_domain_alias *alias = ast_sorcery_generic_alloc(sizeof(*alias), domain_alias_destroy); if (!alias) { return NULL; diff --git a/res/res_sip/config_transport.c b/res/res_sip/config_transport.c index 1d60274b79..30c1362a52 100644 --- a/res/res_sip/config_transport.c +++ b/res/res_sip/config_transport.c @@ -62,7 +62,7 @@ static void transport_destroy(void *obj) /*! \brief Allocator for transport */ static void *transport_alloc(const char *name) { - struct ast_sip_transport *transport = ao2_alloc(sizeof(*transport), transport_destroy); + struct ast_sip_transport *transport = ast_sorcery_generic_alloc(sizeof(*transport), transport_destroy); if (!transport) { return NULL; diff --git a/res/res_sip/location.c b/res/res_sip/location.c index d0b0a28c95..bc1b5211ed 100644 --- a/res/res_sip/location.c +++ b/res/res_sip/location.c @@ -41,7 +41,7 @@ static void aor_destroy(void *obj) /*! \brief Allocator for AOR */ static void *aor_alloc(const char *name) { - struct ast_sip_aor *aor = ao2_alloc_options(sizeof(struct ast_sip_aor), aor_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + struct ast_sip_aor *aor = ast_sorcery_generic_alloc(sizeof(struct ast_sip_aor), aor_destroy); if (!aor) { return NULL; } @@ -60,7 +60,7 @@ static void contact_destroy(void *obj) /*! \brief Allocator for contact */ static void *contact_alloc(const char *name) { - struct ast_sip_contact *contact = ao2_alloc_options(sizeof(*contact), contact_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK); + struct ast_sip_contact *contact = ast_sorcery_generic_alloc(sizeof(*contact), contact_destroy); if (!contact) { return NULL; diff --git a/res/res_sip/sip_configuration.c b/res/res_sip/sip_configuration.c index 5864bdeecc..49c2da28cf 100644 --- a/res/res_sip/sip_configuration.c +++ b/res/res_sip/sip_configuration.c @@ -515,7 +515,7 @@ static int named_groups_handler(const struct aco_option *opt, static void *sip_nat_hook_alloc(const char *name) { - return ao2_alloc(sizeof(struct ast_sip_nat_hook), NULL); + return ast_sorcery_generic_alloc(sizeof(struct ast_sip_nat_hook), NULL); } /*! \brief Destructor function for persistent endpoint information */ @@ -722,7 +722,7 @@ static void endpoint_destructor(void* obj) void *ast_sip_endpoint_alloc(const char *name) { - struct ast_sip_endpoint *endpoint = ao2_alloc(sizeof(*endpoint), endpoint_destructor); + struct ast_sip_endpoint *endpoint = ast_sorcery_generic_alloc(sizeof(*endpoint), endpoint_destructor); if (!endpoint) { return NULL; } diff --git a/res/res_sip/sip_options.c b/res/res_sip/sip_options.c index 4c8a9f6a79..4f21238b7a 100644 --- a/res/res_sip/sip_options.c +++ b/res/res_sip/sip_options.c @@ -42,8 +42,7 @@ static int qualify_contact(struct ast_sip_contact *contact); */ static void *contact_status_alloc(const char *name) { - struct ast_sip_contact_status *status = ao2_alloc_options( - sizeof(*status), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + struct ast_sip_contact_status *status = ast_sorcery_generic_alloc(sizeof(*status), NULL); if (!status) { ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n"); diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c index e1d4b7fce1..868c582688 100644 --- a/tests/test_sorcery.c +++ b/tests/test_sorcery.c @@ -49,7 +49,7 @@ struct test_sorcery_object { /*! \brief Internal function to allocate a test object */ static void *test_sorcery_object_alloc(const char *id) { - return ao2_alloc(sizeof(struct test_sorcery_object), NULL); + return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL); } /*! \brief Internal function for object set transformation */ @@ -1300,6 +1300,71 @@ AST_TEST_DEFINE(objectset_apply_fields) return res; } +AST_TEST_DEFINE(extended_fields) +{ + int res = AST_TEST_PASS; + 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); + const char *value; + + switch (cmd) { + case TEST_INIT: + info->name = "extended_fields"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object extended fields unit test"; + info->description = + "Test extended fields support in sorcery"; + 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 (!(objset = ast_variable_new("@testing", "toast", ""))) { + ast_test_status_update(test, "Failed to create an object set, test could not occur\n"); + res = AST_TEST_FAIL; + } else if (ast_sorcery_objectset_apply(sorcery, obj, objset)) { + ast_test_status_update(test, "Failed to apply valid object set to object\n"); + res = AST_TEST_FAIL; + } else if (!(value = ast_sorcery_object_get_extended(obj, "testing"))) { + ast_test_status_update(test, "Extended field, which was set using object set, could not be found\n"); + res = AST_TEST_FAIL; + } else if (strcmp(value, "toast")) { + ast_test_status_update(test, "Extended field does not contain expected value\n"); + res = AST_TEST_FAIL; + } else if (ast_sorcery_object_set_extended(obj, "@tacos", "supreme")) { + ast_test_status_update(test, "Extended field could not be set\n"); + res = AST_TEST_FAIL; + } else if (!(value = ast_sorcery_object_get_extended(obj, "tacos"))) { + ast_test_status_update(test, "Extended field, which was set using the API, could not be found\n"); + res = AST_TEST_FAIL; + } else if (strcmp(value, "supreme")) { + ast_test_status_update(test, "Extended field does not contain expected value\n"); + res = AST_TEST_FAIL; + } else if (ast_sorcery_object_set_extended(obj, "@tacos", "canadian")) { + ast_test_status_update(test, "Extended field could not be set a second time\n"); + res = AST_TEST_FAIL; + } else if (!(value = ast_sorcery_object_get_extended(obj, "tacos"))) { + ast_test_status_update(test, "Extended field, which was set using the API, could not be found\n"); + res = AST_TEST_FAIL; + } else if (strcmp(value, "canadian")) { + ast_test_status_update(test, "Extended field does not contain expected value\n"); + res = AST_TEST_FAIL; + } + + return res; +} + AST_TEST_DEFINE(changeset_create) { int res = AST_TEST_PASS; @@ -2604,6 +2669,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(objectset_apply_invalid); AST_TEST_UNREGISTER(objectset_transform); AST_TEST_UNREGISTER(objectset_apply_fields); + AST_TEST_UNREGISTER(extended_fields); AST_TEST_UNREGISTER(changeset_create); AST_TEST_UNREGISTER(changeset_create_unchanged); AST_TEST_UNREGISTER(object_create); @@ -2651,6 +2717,7 @@ static int load_module(void) AST_TEST_REGISTER(objectset_apply_invalid); AST_TEST_REGISTER(objectset_transform); AST_TEST_REGISTER(objectset_apply_fields); + AST_TEST_REGISTER(extended_fields); AST_TEST_REGISTER(changeset_create); AST_TEST_REGISTER(changeset_create_unchanged); AST_TEST_REGISTER(object_create);