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);