From 9720b29d8e64c76bcea8b09e753a057daec746a7 Mon Sep 17 00:00:00 2001 From: "Joshua C. Colp" Date: Mon, 6 Jan 2020 15:02:54 +0000 Subject: [PATCH] res_pjsip_pubsub: Add ability to persist generator state information. Some body generators, such as dialog-info+xml, require storing state information which is then conveyed in the NOTIFY request itself. Up until now there was no way for such body generators to persist this information. Two new API calls have been added to allow body generators to set and get persisted data. This data is persisted out alongside the normal persistence information and allows the body generator to restore state information or to simply use this for normal storage of state. State is stored in the form of JSON and it is up to the body generator to interpret this as needed. The dialog-info+xml body generator has been updated to take advantage of this to persist the version number. ASTERISK-27759 Change-Id: I5fda56c624fd13c17b3c48e0319b77079e9e27de --- include/asterisk/res_pjsip_pubsub.h | 23 ++++++ res/res_pjsip_dialog_info_body_generator.c | 75 ++++++++----------- res/res_pjsip_pubsub.c | 87 ++++++++++++++++++++-- 3 files changed, 138 insertions(+), 47 deletions(-) diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h index 94576d38b0..c1b1ec8061 100644 --- a/include/asterisk/res_pjsip_pubsub.h +++ b/include/asterisk/res_pjsip_pubsub.h @@ -517,6 +517,29 @@ struct ast_datastore *ast_sip_subscription_get_datastore(struct ast_sip_subscrip */ void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscription, const char *name); +/*! + * \since 13.31.0 + * \since 16.8.0 + * \since 17.2.0 + * \brief Set persistence data for a subscription + * + * \param subscription The subscription to set persistence data on + * \param persistence_data The persistence data to set + * + * \note This steals the reference to persistence_data + */ +void ast_sip_subscription_set_persistence_data(struct ast_sip_subscription *subscription, struct ast_json *persistence_data); + +/*! + * \since 13.31.0 + * \since 16.8.0 + * \since 17.2.0 + * \brief Retrieve persistence data for a subscription + * + * \param subscription The subscription to retrieve persistence data from + */ +const struct ast_json *ast_sip_subscription_get_persistence_data(const struct ast_sip_subscription *subscription); + /*! * \brief Register a subscription handler * diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c index fa3d710e5e..1d94ef543d 100644 --- a/res/res_pjsip_dialog_info_body_generator.c +++ b/res/res_pjsip_dialog_info_body_generator.c @@ -61,51 +61,15 @@ static void *dialog_info_allocate_body(void *data) return ast_sip_presence_xml_create_node(state_data->pool, NULL, "dialog-info"); } -static struct ast_datastore *dialog_info_xml_state_find_or_create(struct ast_sip_subscription *sub) -{ - struct ast_datastore *datastore = ast_sip_subscription_get_datastore(sub, "dialog-info+xml"); - - if (datastore) { - return datastore; - } - - datastore = ast_sip_subscription_alloc_datastore(&dialog_info_xml_datastore, "dialog-info+xml"); - if (!datastore) { - return NULL; - } - datastore->data = ast_calloc(1, sizeof(struct dialog_info_xml_state)); - if (!datastore->data || ast_sip_subscription_add_datastore(sub, datastore)) { - ao2_ref(datastore, -1); - return NULL; - } - - return datastore; -} - -static unsigned int dialog_info_xml_get_version(struct ast_sip_subscription *sub, unsigned int *version) -{ - struct ast_datastore *datastore = dialog_info_xml_state_find_or_create(sub); - struct dialog_info_xml_state *state; - - if (!datastore) { - return -1; - } - - state = datastore->data; - *version = state->version++; - ao2_ref(datastore, -1); - - return 0; -} - static int dialog_info_generate_body_content(void *body, void *data) { pj_xml_node *dialog_info = body, *dialog, *state; + struct ast_datastore *datastore; + struct dialog_info_xml_state *datastore_state; struct ast_sip_exten_state_data *state_data = data; char *local = ast_strdupa(state_data->local), *stripped, *statestring = NULL; char *pidfstate = NULL, *pidfnote = NULL; enum ast_sip_pidf_state local_state; - unsigned int version; char version_str[32], sanitized[PJSIP_MAX_URL_SIZE]; struct ast_sip_endpoint *endpoint = NULL; unsigned int notify_early_inuse_ringing = 0; @@ -114,9 +78,32 @@ static int dialog_info_generate_body_content(void *body, void *data) return -1; } - if (dialog_info_xml_get_version(state_data->sub, &version)) { - ast_log(LOG_WARNING, "dialog-info+xml version could not be retrieved from datastore\n"); - return -1; + datastore = ast_sip_subscription_get_datastore(state_data->sub, "state_data->dialog-info+xml"); + if (!datastore) { + const struct ast_json *version_json; + + datastore = ast_sip_subscription_alloc_datastore(&dialog_info_xml_datastore, "dialog-info+xml"); + if (!datastore) { + return -1; + } + + datastore->data = ast_calloc(1, sizeof(struct dialog_info_xml_state)); + if (!datastore->data || ast_sip_subscription_add_datastore(state_data->sub, datastore)) { + ao2_ref(datastore, -1); + return -1; + } + datastore_state = datastore->data; + + version_json = ast_sip_subscription_get_persistence_data(state_data->sub); + if (version_json) { + datastore_state->version = ast_json_integer_get(version_json); + datastore_state->version++; + } else { + datastore_state->version = 0; + } + } else { + datastore_state = datastore->data; + datastore_state->version++; } stripped = ast_strip_quoted(local, "<", ">"); @@ -131,9 +118,11 @@ static int dialog_info_generate_body_content(void *body, void *data) ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "xmlns", "urn:ietf:params:xml:ns:dialog-info"); - snprintf(version_str, sizeof(version_str), "%u", version); + snprintf(version_str, sizeof(version_str), "%u", datastore_state->version); ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "version", version_str); + ast_sip_subscription_set_persistence_data(state_data->sub, ast_json_integer_create(datastore_state->version)); + ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "state", "full"); ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "entity", sanitized); @@ -157,6 +146,8 @@ static int dialog_info_generate_body_content(void *body, void *data) ast_sip_presence_xml_create_attr(state_data->pool, param, "pvalue", "no"); } + ao2_ref(datastore, -1); + return 0; } diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 99fdd5fe9f..5712023313 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -132,6 +132,11 @@ and therefore the subscription must be deleted after an asterisk restart. + + If set, contains persistence data for all generators of content + for the subscription. + + Resource list configuration parameters. @@ -389,6 +394,8 @@ struct subscription_persistence { char contact_uri[PJSIP_MAX_URL_SIZE]; /*! Prune subscription on restart */ int prune_on_boot; + /*! Body generator specific persistence data */ + struct ast_json *generator_data; }; /*! @@ -490,6 +497,8 @@ struct ast_sip_subscription { unsigned int full_state; /*! URI associated with the subscription */ pjsip_sip_uri *uri; + /*! Data to be persisted with the subscription */ + struct ast_json *persistence_data; /*! Name of resource being subscribed to */ char resource[0]; }; @@ -615,6 +624,7 @@ static void subscription_persistence_destroy(void *obj) ast_free(persistence->endpoint); ast_free(persistence->tag); + ast_json_unref(persistence->generator_data); } /*! \brief Allocator for subscription persistence */ @@ -1220,6 +1230,7 @@ static void destroy_subscription(struct ast_sip_subscription *sub) AST_VECTOR_FREE(&sub->children); ao2_cleanup(sub->datastores); + ast_json_unref(sub->persistence_data); ast_free(sub); } @@ -1271,6 +1282,14 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, contact_uri); pj_strdup2(tree->dlg->pool, &sub->uri->user, resource); + /* If there is any persistence information available for this subscription that was persisted + * then make it available so that the NOTIFY has the correct state. + */ + + if (tree->persistence && tree->persistence->generator_data) { + sub->persistence_data = ast_json_object_get(tree->persistence->generator_data, resource); + } + sub->handler = handler; sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE; sub->tree = ao2_bump(tree); @@ -1469,11 +1488,10 @@ static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_e static struct sip_subscription_tree *create_subscription_tree(const struct ast_sip_subscription_handler *handler, struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource, struct ast_sip_pubsub_body_generator *generator, struct resource_tree *tree, - pj_status_t *dlg_status) + pj_status_t *dlg_status, struct subscription_persistence *persistence) { struct sip_subscription_tree *sub_tree; pjsip_dialog *dlg; - struct subscription_persistence *persistence; sub_tree = allocate_subscription_tree(endpoint, rdata); if (!sub_tree) { @@ -1514,6 +1532,9 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s sub_tree->notification_batch_interval = tree->notification_batch_interval; + /* Persistence information needs to be available for all the subscriptions */ + sub_tree->persistence = ao2_bump(persistence); + sub_tree->root = create_virtual_subscriptions(handler, resource, generator, sub_tree, tree->root); if (AST_VECTOR_SIZE(&sub_tree->root->children) > 0) { sub_tree->is_list = 1; @@ -1635,7 +1656,7 @@ static int sub_persistence_recreate(void *obj) pj_status_t dlg_status; sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, - &tree, &dlg_status); + &tree, &dlg_status, persistence); if (!sub_tree) { if (dlg_status != PJ_EEXISTS) { ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not create subscription tree.\n", @@ -1653,7 +1674,6 @@ static int sub_persistence_recreate(void *obj) ind->sub_tree = ao2_bump(sub_tree); ind->expires = expires_header->ivalue; - sub_tree->persistence = ao2_bump(persistence); subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_RECREATED); if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ind)) { /* Could not send initial subscribe NOTIFY */ @@ -2710,6 +2730,28 @@ void ast_sip_publication_remove_datastore(struct ast_sip_publication *publicatio ao2_callback(publication->datastores, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, NULL, (void *) name); } +void ast_sip_subscription_set_persistence_data(struct ast_sip_subscription *subscription, struct ast_json *persistence_data) +{ + ast_json_unref(subscription->persistence_data); + subscription->persistence_data = persistence_data; + + if (subscription->tree->persistence) { + if (!subscription->tree->persistence->generator_data) { + subscription->tree->persistence->generator_data = ast_json_object_create(); + if (!subscription->tree->persistence->generator_data) { + return; + } + } + ast_json_object_set(subscription->tree->persistence->generator_data, subscription->resource, + ast_json_ref(persistence_data)); + } +} + +const struct ast_json *ast_sip_subscription_get_persistence_data(const struct ast_sip_subscription *subscription) +{ + return subscription->persistence_data; +} + AST_RWLIST_HEAD_STATIC(publish_handlers, ast_sip_publish_handler); static int publication_hash_fn(const void *obj, const int flags) @@ -3076,7 +3118,7 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata) return PJ_TRUE; } - sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, &tree, &dlg_status); + sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, &tree, &dlg_status, NULL); if (!sub_tree) { if (dlg_status != PJ_EEXISTS) { pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); @@ -4725,6 +4767,39 @@ static int persistence_tag_struct2str(const void *obj, const intptr_t *args, cha return 0; } +static int persistence_generator_data_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct subscription_persistence *persistence = obj; + struct ast_json_error error; + + /* We tolerate a failure of the JSON to load and instead start fresh, since this field + * originates from the persistence code and not a user. + */ + persistence->generator_data = ast_json_load_string(var->value, &error); + + return 0; +} + +static int persistence_generator_data_struct2str(const void *obj, const intptr_t *args, char **buf) +{ + const struct subscription_persistence *persistence = obj; + char *value; + + if (!persistence->generator_data) { + return 0; + } + + value = ast_json_dump_string(persistence->generator_data); + if (!value) { + return -1; + } + + *buf = ast_strdup(value); + ast_json_free(value); + + return 0; +} + static int persistence_expires_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct subscription_persistence *persistence = obj; @@ -5599,6 +5674,8 @@ static int load_module(void) CHARFLDSET(struct subscription_persistence, contact_uri)); ast_sorcery_object_field_register(sorcery, "subscription_persistence", "prune_on_boot", "no", OPT_YESNO_T, 1, FLDSET(struct subscription_persistence, prune_on_boot)); + ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "generator_data", "", + persistence_generator_data_str2struct, persistence_generator_data_struct2str, NULL, 0, 0); if (apply_list_configuration(sorcery)) { ast_sched_context_destroy(sched);