diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h index e9d59a0851..4309a099e7 100644 --- a/include/asterisk/res_pjsip_pubsub.h +++ b/include/asterisk/res_pjsip_pubsub.h @@ -545,6 +545,29 @@ void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscrip */ struct ao2_container *ast_sip_subscription_get_datastores(const struct ast_sip_subscription *subscription); +/*! + * \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 a13995faca..3934a8a0fb 100644 --- a/res/res_pjsip_dialog_info_body_generator.c +++ b/res/res_pjsip_dialog_info_body_generator.c @@ -60,51 +60,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 ao2_container *datastores) -{ - struct ast_datastore *datastore = ast_datastores_find(datastores, "dialog-info+xml"); - - if (datastore) { - return datastore; - } - - datastore = ast_datastores_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_datastores_add(datastores, datastore)) { - ao2_ref(datastore, -1); - return NULL; - } - - return datastore; -} - -static unsigned int dialog_info_xml_get_version(struct ao2_container *datastores, unsigned int *version) -{ - struct ast_datastore *datastore = dialog_info_xml_state_find_or_create(datastores); - 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; @@ -113,9 +77,35 @@ static int dialog_info_generate_body_content(void *body, void *data) return -1; } - if (dialog_info_xml_get_version(state_data->datastores, &version)) { - ast_log(LOG_WARNING, "dialog-info+xml version could not be retrieved from datastore\n"); - return -1; + datastore = ast_datastores_find(state_data->datastores, "dialog-info+xml"); + if (!datastore) { + const struct ast_json *version_json = NULL; + + datastore = ast_datastores_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_datastores_add(state_data->datastores, datastore)) { + ao2_ref(datastore, -1); + return -1; + } + datastore_state = datastore->data; + + if (state_data->sub) { + 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, "<", ">"); @@ -130,9 +120,13 @@ 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); + if (state_data->sub) { + 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); @@ -156,6 +150,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 e26f25021a..32e0adf540 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 */ @@ -1198,6 +1208,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); } @@ -1248,6 +1259,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); @@ -1446,11 +1465,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) { @@ -1491,6 +1509,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; @@ -1612,7 +1633,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", @@ -1630,7 +1651,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 */ @@ -2644,6 +2664,28 @@ struct ao2_container *ast_sip_publication_get_datastores(const struct ast_sip_pu return publication->datastores; } +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) @@ -3005,7 +3047,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); @@ -4657,6 +4699,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; @@ -5529,6 +5604,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);