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
13.31
Joshua C. Colp 5 years ago
parent 20c411065b
commit 9720b29d8e

@ -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); 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 * \brief Register a subscription handler
* *

@ -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"); 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) static int dialog_info_generate_body_content(void *body, void *data)
{ {
pj_xml_node *dialog_info = body, *dialog, *state; 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; struct ast_sip_exten_state_data *state_data = data;
char *local = ast_strdupa(state_data->local), *stripped, *statestring = NULL; char *local = ast_strdupa(state_data->local), *stripped, *statestring = NULL;
char *pidfstate = NULL, *pidfnote = NULL; char *pidfstate = NULL, *pidfnote = NULL;
enum ast_sip_pidf_state local_state; enum ast_sip_pidf_state local_state;
unsigned int version;
char version_str[32], sanitized[PJSIP_MAX_URL_SIZE]; char version_str[32], sanitized[PJSIP_MAX_URL_SIZE];
struct ast_sip_endpoint *endpoint = NULL; struct ast_sip_endpoint *endpoint = NULL;
unsigned int notify_early_inuse_ringing = 0; unsigned int notify_early_inuse_ringing = 0;
@ -114,9 +78,32 @@ static int dialog_info_generate_body_content(void *body, void *data)
return -1; return -1;
} }
if (dialog_info_xml_get_version(state_data->sub, &version)) { datastore = ast_sip_subscription_get_datastore(state_data->sub, "state_data->dialog-info+xml");
ast_log(LOG_WARNING, "dialog-info+xml version could not be retrieved from datastore\n"); if (!datastore) {
return -1; 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, "<", ">"); 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"); 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_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, "state", "full");
ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "entity", sanitized); 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"); ast_sip_presence_xml_create_attr(state_data->pool, param, "pvalue", "no");
} }
ao2_ref(datastore, -1);
return 0; return 0;
} }

@ -132,6 +132,11 @@
and therefore the subscription must be deleted after an asterisk restart. and therefore the subscription must be deleted after an asterisk restart.
</synopsis> </synopsis>
</configOption> </configOption>
<configOption name="generator_data">
<synopsis>If set, contains persistence data for all generators of content
for the subscription.
</synopsis>
</configOption>
</configObject> </configObject>
<configObject name="resource_list"> <configObject name="resource_list">
<synopsis>Resource list configuration parameters.</synopsis> <synopsis>Resource list configuration parameters.</synopsis>
@ -389,6 +394,8 @@ struct subscription_persistence {
char contact_uri[PJSIP_MAX_URL_SIZE]; char contact_uri[PJSIP_MAX_URL_SIZE];
/*! Prune subscription on restart */ /*! Prune subscription on restart */
int prune_on_boot; 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; unsigned int full_state;
/*! URI associated with the subscription */ /*! URI associated with the subscription */
pjsip_sip_uri *uri; pjsip_sip_uri *uri;
/*! Data to be persisted with the subscription */
struct ast_json *persistence_data;
/*! Name of resource being subscribed to */ /*! Name of resource being subscribed to */
char resource[0]; char resource[0];
}; };
@ -615,6 +624,7 @@ static void subscription_persistence_destroy(void *obj)
ast_free(persistence->endpoint); ast_free(persistence->endpoint);
ast_free(persistence->tag); ast_free(persistence->tag);
ast_json_unref(persistence->generator_data);
} }
/*! \brief Allocator for subscription persistence */ /*! \brief Allocator for subscription persistence */
@ -1220,6 +1230,7 @@ static void destroy_subscription(struct ast_sip_subscription *sub)
AST_VECTOR_FREE(&sub->children); AST_VECTOR_FREE(&sub->children);
ao2_cleanup(sub->datastores); ao2_cleanup(sub->datastores);
ast_json_unref(sub->persistence_data);
ast_free(sub); 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); pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, contact_uri);
pj_strdup2(tree->dlg->pool, &sub->uri->user, resource); 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->handler = handler;
sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE; sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
sub->tree = ao2_bump(tree); 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, 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_endpoint *endpoint, pjsip_rx_data *rdata, const char *resource,
struct ast_sip_pubsub_body_generator *generator, struct resource_tree *tree, 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; struct sip_subscription_tree *sub_tree;
pjsip_dialog *dlg; pjsip_dialog *dlg;
struct subscription_persistence *persistence;
sub_tree = allocate_subscription_tree(endpoint, rdata); sub_tree = allocate_subscription_tree(endpoint, rdata);
if (!sub_tree) { 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; 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); sub_tree->root = create_virtual_subscriptions(handler, resource, generator, sub_tree, tree->root);
if (AST_VECTOR_SIZE(&sub_tree->root->children) > 0) { if (AST_VECTOR_SIZE(&sub_tree->root->children) > 0) {
sub_tree->is_list = 1; sub_tree->is_list = 1;
@ -1635,7 +1656,7 @@ static int sub_persistence_recreate(void *obj)
pj_status_t dlg_status; pj_status_t dlg_status;
sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator,
&tree, &dlg_status); &tree, &dlg_status, persistence);
if (!sub_tree) { if (!sub_tree) {
if (dlg_status != PJ_EEXISTS) { if (dlg_status != PJ_EEXISTS) {
ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not create subscription tree.\n", 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->sub_tree = ao2_bump(sub_tree);
ind->expires = expires_header->ivalue; ind->expires = expires_header->ivalue;
sub_tree->persistence = ao2_bump(persistence);
subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_RECREATED); subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_RECREATED);
if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ind)) { if (ast_sip_push_task(sub_tree->serializer, initial_notify_task, ind)) {
/* Could not send initial subscribe NOTIFY */ /* 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); 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); AST_RWLIST_HEAD_STATIC(publish_handlers, ast_sip_publish_handler);
static int publication_hash_fn(const void *obj, const int flags) 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; 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 (!sub_tree) {
if (dlg_status != PJ_EEXISTS) { if (dlg_status != PJ_EEXISTS) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); 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; 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) static int persistence_expires_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
{ {
struct subscription_persistence *persistence = obj; struct subscription_persistence *persistence = obj;
@ -5599,6 +5674,8 @@ static int load_module(void)
CHARFLDSET(struct subscription_persistence, contact_uri)); CHARFLDSET(struct subscription_persistence, contact_uri));
ast_sorcery_object_field_register(sorcery, "subscription_persistence", "prune_on_boot", "no", OPT_YESNO_T, 1, ast_sorcery_object_field_register(sorcery, "subscription_persistence", "prune_on_boot", "no", OPT_YESNO_T, 1,
FLDSET(struct subscription_persistence, prune_on_boot)); 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)) { if (apply_list_configuration(sorcery)) {
ast_sched_context_destroy(sched); ast_sched_context_destroy(sched);

Loading…
Cancel
Save