summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/res_pjsip_presence_xml.h15
-rw-r--r--include/asterisk/res_pjsip_pubsub.h37
-rw-r--r--include/asterisk/strings.h16
-rw-r--r--main/strings.c12
-rw-r--r--res/res_pjsip_dialog_info_body_generator.c11
-rw-r--r--res/res_pjsip_exten_state.c145
-rw-r--r--res/res_pjsip_mwi.c50
-rw-r--r--res/res_pjsip_pidf_body_generator.c7
-rw-r--r--res/res_pjsip_pubsub.c2632
-rw-r--r--res/res_pjsip_xpidf_body_generator.c7
10 files changed, 2401 insertions, 531 deletions
diff --git a/include/asterisk/res_pjsip_presence_xml.h b/include/asterisk/res_pjsip_presence_xml.h
index 8318067ad..add5f8918 100644
--- a/include/asterisk/res_pjsip_presence_xml.h
+++ b/include/asterisk/res_pjsip_presence_xml.h
@@ -17,6 +17,21 @@
*/
/*!
+ * \brief The length of the XML prolog when printing
+ * presence or other XML in PJSIP.
+ *
+ * When calling any variant of pj_xml_print(), the documentation
+ * claims that it will return -1 if the provided buffer is not
+ * large enough. However, if the XML prolog is requested to be
+ * printed, then the length of the XML prolog is returned upon
+ * failure instead of -1.
+ *
+ * This constant is useful to check against when trying to determine
+ * if printing XML succeeded or failed.
+ */
+#define AST_PJSIP_XML_PROLOG_LEN 39
+
+/*!
* PIDF state
*/
enum ast_sip_pidf_state {
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index 8ad133471..73b987479 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -240,23 +240,29 @@ struct ast_sip_notifier {
*/
int (*new_subscribe)(struct ast_sip_endpoint *endpoint, const char *resource);
/*!
- * \brief The subscription is in need of a NOTIFY request.
+ * \brief Called when an inbound subscription has been accepted.
*
- * A reason of AST_SIP_SUBSCRIPTION_NOTIFY_REASON_STARTED is given immediately
- * after a SUBSCRIBE is accepted. This is a good opportunity for the notifier to
- * perform setup duties such as establishing Stasis subscriptions or adding
- * datastores to the subscription.
+ * This is a prime opportunity for notifiers to add any notifier-specific
+ * data to the subscription (such as datastores) that it needs to.
*
- * A reason of AST_SIP_SUBSCRIPTION_NOTIFY_REASON_TERMINATED is given when the
- * subscriber has terminated the subscription. If there are any duties that the
+ * \note There is no need to send a NOTIFY request when this callback
+ * is called
*
- *
- * \param sub The subscription to send the NOTIFY on.
- * \param reason The reason why the NOTIFY is being sent.
+ * \param sub The new subscription
* \retval 0 Success
* \retval -1 Failure
*/
- int (*notify_required)(struct ast_sip_subscription *sub, enum ast_sip_subscription_notify_reason reason);
+ int (*subscription_established)(struct ast_sip_subscription *sub);
+ /*!
+ * \brief Supply data needed to create a NOTIFY body.
+ *
+ * The returned data must be an ao2 object. The caller of this function
+ * will be responsible for decrementing the refcount of the returned object
+ *
+ * \param sub The subscription
+ * \return An ao2 object that can be used to create a NOTIFY body.
+ */
+ void *(*get_notify_data)(struct ast_sip_subscription *sub);
};
struct ast_sip_subscriber {
@@ -343,10 +349,9 @@ struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_sub
/*!
* \brief Notify a SIP subscription of a state change.
*
- * This will create a NOTIFY body to be sent out for the subscribed resource.
- * On real subscriptions, a NOTIFY request will be generated and sent.
- * On virtual subscriptions, the NOTIFY is saved on the virtual subscription and the
- * parent subscription is alerted.
+ * This tells the pubsub core that the state of a subscribed resource has changed.
+ * The pubsub core will generate an appropriate NOTIFY request to send to the
+ * subscriber.
*
* \param sub The subscription on which a state change is occurring.
* \param notify_data Event package-specific data used to create the NOTIFY body.
@@ -359,7 +364,7 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, void *notify_d
/*!
* \brief Retrieve the local URI for this subscription
*
- * This is the local URI as determined by the underlying SIP dialog.
+ * This is the local URI of the subscribed resource.
*
* \param sub The subscription
* \param[out] buf The buffer into which to store the URI.
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index 5dbebba95..0b98a2bb0 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -1196,4 +1196,20 @@ int ast_str_container_add(struct ao2_container *str_container, const char *add);
*/
void ast_str_container_remove(struct ao2_container *str_container, const char *remove);
+/*!
+ * \brief Create a pseudo-random string of a fixed length.
+ *
+ * This function is useful for generating a string whose randomness
+ * does not need to be across all time and space, does not need to
+ * be cryptographically secure, and needs to fit in a limited space.
+ *
+ * This function will write a null byte at the final position
+ * in the buffer (buf[size - 1]). So if you pass in a size of
+ * 10, then this will generate a random 9-character string.
+ *
+ * \param buf Buffer to write random string into.
+ * \param size The size of the buffer.
+ * \return A pointer to buf
+ */
+char *ast_generate_random_string(char *buf, size_t size);
#endif /* _ASTERISK_STRINGS_H */
diff --git a/main/strings.c b/main/strings.c
index a65df39da..73892eb0a 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -195,3 +195,15 @@ void ast_str_container_remove(struct ao2_container *str_container, const char *r
{
ao2_find(str_container, remove, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
}
+
+char *ast_generate_random_string(char *buf, size_t size)
+{
+ int i;
+
+ for (i = 0; i < size - 1; ++i) {
+ buf[i] = 'a' + (ast_random() % 26);
+ }
+ buf[i] = '\0';
+
+ return buf;
+}
diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c
index d4ad2160a..848ec10e4 100644
--- a/res/res_pjsip_dialog_info_body_generator.c
+++ b/res/res_pjsip_dialog_info_body_generator.c
@@ -156,11 +156,6 @@ static int dialog_info_generate_body_content(void *body, void *data)
*/
#define MAX_STRING_GROWTHS 3
-/* When having pj_xml_print add the XML prolog to the output body the function will return 39
- * instead of -1 if the rest of the document can not be printed into the body.
- */
-#define XML_PROLOG 39
-
static void dialog_info_to_string(void *body, struct ast_str **str)
{
pj_xml_node *dialog_info = body;
@@ -169,13 +164,13 @@ static void dialog_info_to_string(void *body, struct ast_str **str)
do {
size = pj_xml_print(dialog_info, ast_str_buffer(*str), ast_str_size(*str), PJ_TRUE);
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_str_make_space(str, ast_str_size(*str) * 2);
++growths;
}
- } while (size == XML_PROLOG && growths < MAX_STRING_GROWTHS);
+ } while (size == AST_PJSIP_XML_PROLOG_LEN && growths < MAX_STRING_GROWTHS);
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_log(LOG_WARNING, "dialog-info+xml body text too large\n");
return;
}
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index fb6f72b27..29b26cc69 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -70,15 +70,23 @@ struct exten_state_subscription {
static void subscription_shutdown(struct ast_sip_subscription *sub);
static int new_subscribe(struct ast_sip_endpoint *endpoint, const char *resource);
-static int notify_required(struct ast_sip_subscription *sub,
- enum ast_sip_subscription_notify_reason reason);
+static int subscription_established(struct ast_sip_subscription *sub);
+static void *get_notify_data(struct ast_sip_subscription *sub);
static void to_ami(struct ast_sip_subscription *sub,
struct ast_str **buf);
struct ast_sip_notifier presence_notifier = {
.default_accept = DEFAULT_PRESENCE_BODY,
.new_subscribe = new_subscribe,
- .notify_required = notify_required,
+ .subscription_established = subscription_established,
+ .get_notify_data = get_notify_data,
+};
+
+struct ast_sip_notifier dialog_notifier = {
+ .default_accept = DEFAULT_DIALOG_BODY,
+ .new_subscribe = new_subscribe,
+ .subscription_established = subscription_established,
+ .get_notify_data = get_notify_data,
};
struct ast_sip_subscription_handler presence_handler = {
@@ -94,7 +102,7 @@ struct ast_sip_subscription_handler dialog_handler = {
.accept = { DEFAULT_DIALOG_BODY, },
.subscription_shutdown = subscription_shutdown,
.to_ami = to_ami,
- .notifier = &presence_notifier,
+ .notifier = &dialog_notifier,
};
static void exten_state_subscription_destructor(void *obj)
@@ -153,45 +161,6 @@ static struct exten_state_subscription *exten_state_subscription_alloc(
return exten_state_sub;
}
-/*!
- * \internal
- * \brief Get device state information and send notification to the subscriber.
- */
-static void send_notify(struct exten_state_subscription *exten_state_sub)
-{
- RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup);
- char *subtype = NULL, *message = NULL;
- struct ast_sip_exten_state_data exten_state_data = {
- .exten = exten_state_sub->exten,
- .presence_state = ast_hint_presence_state(NULL, exten_state_sub->context,
- exten_state_sub->exten, &subtype, &message),
- .presence_subtype = subtype,
- .presence_message = message,
- .sub = exten_state_sub->sip_sub,
- .user_agent = exten_state_sub->user_agent
- };
-
- ast_sip_subscription_get_local_uri(exten_state_sub->sip_sub,
- exten_state_data.local, sizeof(exten_state_data.local));
- ast_sip_subscription_get_remote_uri(exten_state_sub->sip_sub,
- exten_state_data.remote, sizeof(exten_state_data.remote));
-
- if ((exten_state_data.exten_state = ast_extension_state_extended(
- NULL, exten_state_sub->context, exten_state_sub->exten, &info)) < 0) {
-
- ast_log(LOG_WARNING, "Unable to get device hint/info for extension %s\n",
- exten_state_sub->exten);
- return;
- }
-
- exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
- "exten_state", 1024, 1024);
-
- exten_state_data.device_state_info = info;
- ast_sip_subscription_notify(exten_state_sub->sip_sub, &exten_state_data, 0);
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), exten_state_data.pool);
-}
-
struct notify_task_data {
struct ast_sip_exten_state_data exten_state_data;
struct exten_state_subscription *exten_state_sub;
@@ -231,11 +200,8 @@ static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten
task_data->exten_state_data.presence_subtype = ast_strdup(info->presence_subtype);
task_data->exten_state_data.presence_message = ast_strdup(info->presence_message);
task_data->exten_state_data.user_agent = ast_strdup(exten_state_sub->user_agent);
- task_data->exten_state_data.device_state_info = info->device_state_info;
-
- if (task_data->exten_state_data.device_state_info) {
- ao2_ref(task_data->exten_state_data.device_state_info, +1);
- }
+ task_data->exten_state_data.device_state_info = ao2_bump(info->device_state_info);
+ task_data->exten_state_data.sub = exten_state_sub->sip_sub;
ast_sip_subscription_get_local_uri(exten_state_sub->sip_sub,
task_data->exten_state_data.local, sizeof(task_data->exten_state_data.local));
@@ -259,6 +225,9 @@ static int notify_task(void *obj)
/* Pool allocation has to happen here so that we allocate within a PJLIB thread */
task_data->exten_state_data.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
"exten_state", 1024, 1024);
+ if (!task_data->exten_state_data.pool) {
+ return -1;
+ }
task_data->exten_state_data.sub = task_data->exten_state_sub->sip_sub;
@@ -366,7 +335,7 @@ static int new_subscribe(struct ast_sip_endpoint *endpoint,
return 200;
}
-static int initial_subscribe(struct ast_sip_subscription *sip_sub)
+static int subscription_established(struct ast_sip_subscription *sip_sub)
{
struct ast_sip_endpoint *endpoint = ast_sip_subscription_get_endpoint(sip_sub);
const char *resource = ast_sip_subscription_get_resource_name(sip_sub);
@@ -403,33 +372,77 @@ static int initial_subscribe(struct ast_sip_subscription *sip_sub)
return -1;
}
- send_notify(exten_state_sub);
ao2_cleanup(exten_state_sub);
return 0;
}
-static int notify_required(struct ast_sip_subscription *sub,
- enum ast_sip_subscription_notify_reason reason)
+static void exten_state_data_destructor(void *obj)
{
- struct exten_state_subscription *exten_state_sub;
+ struct ast_sip_exten_state_data *exten_state_data = obj;
- switch (reason) {
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_STARTED:
- return initial_subscribe(sub);
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_RENEWED:
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_TERMINATED:
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_OTHER:
- exten_state_sub = get_exten_state_sub(sub);
+ ao2_cleanup(exten_state_data->device_state_info);
+ ast_free(exten_state_data->presence_subtype);
+ ast_free(exten_state_data->presence_message);
+ if (exten_state_data->pool) {
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), exten_state_data->pool);
+ }
+}
- if (!exten_state_sub) {
- return -1;
- }
+static struct ast_sip_exten_state_data *exten_state_data_alloc(struct ast_sip_subscription *sip_sub,
+ struct exten_state_subscription *exten_state_sub)
+{
+ struct ast_sip_exten_state_data *exten_state_data;
+ char *subtype = NULL;
+ char *message = NULL;
- send_notify(exten_state_sub);
- break;
+ exten_state_data = ao2_alloc(sizeof(*exten_state_data), exten_state_data_destructor);
+ if (!exten_state_data) {
+ return NULL;
}
- return 0;
+ exten_state_data->exten = exten_state_sub->exten;
+ if ((exten_state_data->presence_state = ast_hint_presence_state(NULL, exten_state_sub->context,
+ exten_state_sub->exten, &subtype, &message)) == -1) {
+ ao2_cleanup(exten_state_data);
+ return NULL;
+ }
+ exten_state_data->presence_subtype = subtype;
+ exten_state_data->presence_message = message;
+ exten_state_data->user_agent = exten_state_sub->user_agent;
+ ast_sip_subscription_get_local_uri(sip_sub, exten_state_data->local,
+ sizeof(exten_state_data->local));
+ ast_sip_subscription_get_remote_uri(sip_sub, exten_state_data->remote,
+ sizeof(exten_state_data->remote));
+ exten_state_data->sub = sip_sub;
+
+ exten_state_data->exten_state = ast_extension_state_extended(
+ NULL, exten_state_sub->context, exten_state_sub->exten,
+ &exten_state_data->device_state_info);
+ if (exten_state_data->exten_state < 0) {
+ ao2_cleanup(exten_state_data);
+ return NULL;
+ }
+
+ exten_state_data->pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+ "exten_state", 1024, 1024);
+ if (!exten_state_data->pool) {
+ ao2_cleanup(exten_state_data);
+ return NULL;
+ }
+
+ return exten_state_data;
+}
+
+static void *get_notify_data(struct ast_sip_subscription *sub)
+{
+ struct exten_state_subscription *exten_state_sub;
+
+ exten_state_sub = get_exten_state_sub(sub);
+ if (!exten_state_sub) {
+ return NULL;
+ }
+
+ return exten_state_data_alloc(sub, exten_state_sub);
}
static void to_ami(struct ast_sip_subscription *sub,
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index 55ef300e8..37e1da0bb 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -48,17 +48,20 @@ AO2_GLOBAL_OBJ_STATIC(unsolicited_mwi);
#define MWI_TYPE "application"
#define MWI_SUBTYPE "simple-message-summary"
+#define MWI_DATASTORE "MWI datastore"
+
static void mwi_subscription_shutdown(struct ast_sip_subscription *sub);
static void mwi_to_ami(struct ast_sip_subscription *sub, struct ast_str **buf);
static int mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
const char *resource);
-static int mwi_notify_required(struct ast_sip_subscription *sip_sub,
- enum ast_sip_subscription_notify_reason reason);
+static int mwi_subscription_established(struct ast_sip_subscription *sub);
+static void *mwi_get_notify_data(struct ast_sip_subscription *sub);
static struct ast_sip_notifier mwi_notifier = {
.default_accept = MWI_TYPE"/"MWI_SUBTYPE,
.new_subscribe = mwi_new_subscribe,
- .notify_required = mwi_notify_required,
+ .subscription_established = mwi_subscription_established,
+ .get_notify_data = mwi_get_notify_data,
};
static struct ast_sip_subscription_handler mwi_handler = {
@@ -457,7 +460,7 @@ static void mwi_subscription_shutdown(struct ast_sip_subscription *sub)
{
struct mwi_subscription *mwi_sub;
RAII_VAR(struct ast_datastore *, mwi_datastore,
- ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+ ast_sip_subscription_get_datastore(sub, MWI_DATASTORE), ao2_cleanup);
if (!mwi_datastore) {
return;
@@ -473,7 +476,7 @@ static int add_mwi_datastore(struct mwi_subscription *sub)
{
RAII_VAR(struct ast_datastore *, mwi_datastore, NULL, ao2_cleanup);
- mwi_datastore = ast_sip_subscription_alloc_datastore(&mwi_ds_info, "MWI datastore");
+ mwi_datastore = ast_sip_subscription_alloc_datastore(&mwi_ds_info, MWI_DATASTORE);
if (!mwi_datastore) {
return -1;
}
@@ -676,7 +679,7 @@ static int mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
return 200;
}
-static int mwi_initial_subscription(struct ast_sip_subscription *sip_sub)
+static int mwi_subscription_established(struct ast_sip_subscription *sip_sub)
{
const char *resource = ast_sip_subscription_get_resource_name(sip_sub);
struct mwi_subscription *sub;
@@ -694,39 +697,32 @@ static int mwi_initial_subscription(struct ast_sip_subscription *sip_sub)
return -1;
}
- send_mwi_notify(sub);
-
ao2_cleanup(sub);
ao2_cleanup(endpoint);
return 0;
}
-static int mwi_notify_required(struct ast_sip_subscription *sip_sub,
- enum ast_sip_subscription_notify_reason reason)
+static void *mwi_get_notify_data(struct ast_sip_subscription *sub)
{
+ struct ast_sip_message_accumulator *counter;
struct mwi_subscription *mwi_sub;
struct ast_datastore *mwi_datastore;
- switch (reason) {
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_STARTED:
- return mwi_initial_subscription(sip_sub);
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_RENEWED:
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_TERMINATED:
- case AST_SIP_SUBSCRIPTION_NOTIFY_REASON_OTHER:
- mwi_datastore = ast_sip_subscription_get_datastore(sip_sub, "MWI datastore");
-
- if (!mwi_datastore) {
- return -1;
- }
-
- mwi_sub = mwi_datastore->data;
+ mwi_datastore = ast_sip_subscription_get_datastore(sub, MWI_DATASTORE);
+ if (!mwi_datastore) {
+ return NULL;
+ }
+ mwi_sub = mwi_datastore->data;
- send_mwi_notify(mwi_sub);
+ counter = ao2_alloc(sizeof(*counter), NULL);
+ if (!counter) {
ao2_cleanup(mwi_datastore);
- break;
+ return NULL;
}
- return 0;
+ ao2_callback(mwi_sub->stasis_subs, OBJ_NODATA, get_message_count, counter);
+ ao2_cleanup(mwi_datastore);
+ return counter;
}
static void mwi_subscription_mailboxes_str(struct ao2_container *stasis_subs,
@@ -753,7 +749,7 @@ static void mwi_to_ami(struct ast_sip_subscription *sub,
{
struct mwi_subscription *mwi_sub;
RAII_VAR(struct ast_datastore *, mwi_datastore,
- ast_sip_subscription_get_datastore(sub, "MWI datastore"), ao2_cleanup);
+ ast_sip_subscription_get_datastore(sub, MWI_DATASTORE), ao2_cleanup);
if (!mwi_datastore) {
return;
diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c
index b3164a22d..3181b2982 100644
--- a/res/res_pjsip_pidf_body_generator.c
+++ b/res/res_pjsip_pidf_body_generator.c
@@ -81,7 +81,6 @@ static int pidf_generate_body_content(void *body, void *data)
}
#define MAX_STRING_GROWTHS 5
-#define XML_PROLOG 39
static void pidf_to_string(void *body, struct ast_str **str)
{
@@ -91,13 +90,13 @@ static void pidf_to_string(void *body, struct ast_str **str)
do {
size = pjpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str) - 1);
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_str_make_space(str, ast_str_size(*str) * 2);
++growths;
}
- } while (size == XML_PROLOG && growths < MAX_STRING_GROWTHS);
+ } while (size == AST_PJSIP_XML_PROLOG_LEN && growths < MAX_STRING_GROWTHS);
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_log(LOG_WARNING, "PIDF body text too large\n");
return;
}
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 6a8ec12db..09fd6295a 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -44,6 +44,7 @@
#include "asterisk/manager.h"
#include "asterisk/test.h"
#include "res_pjsip/include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_presence_xml.h"
/*** DOCUMENTATION
<manager name="PJSIPShowSubscriptionsInbound" language="en_US">
@@ -72,6 +73,20 @@
</para>
</description>
</manager>
+ <manager name="PJSIPShowResourceLists" language="en_US">
+ <synopsis>
+ Displays settings for configured resource lists.
+ </synopsis>
+ <syntax />
+ <description>
+ <para>
+ Provides a listing of all resource lists. An event <literal>ResourceListDetail</literal>
+ is issued for each resource list object. Once all detail events are completed a
+ <literal>ResourceListDetailComplete</literal> event is issued.
+ </para>
+ </description>
+ </manager>
+
<configInfo name="res_pjsip_pubsub" language="en_US">
<synopsis>Module that implements publish and subscribe support.</synopsis>
<configFile name="pjsip.conf">
@@ -108,6 +123,66 @@
<synopsis>The time at which the subscription expires</synopsis>
</configOption>
</configObject>
+ <configObject name="resource_list">
+ <synopsis>Resource list configuration parameters.</synopsis>
+ <configOption name="type">
+ <synopsis>Must be of type 'resource_list'</synopsis>
+ </configOption>
+ <configOption name="event">
+ <synopsis>The SIP event package that the list resource belong to.</synopsis>
+ <description><para>
+ The SIP event package describes the types of resources that Asterisk reports
+ the state of.
+ </para>
+ <enumlist>
+ <enum name="presence"><para>
+ Device state and presence reporting.
+ </para></enum>
+ <enum name="message-summary"><para>
+ Message-waiting indication (MWI) reporting.
+ </para></enum>
+ </enumlist>
+ </description>
+ </configOption>
+ <configOption name="list_item">
+ <synopsis>The name of a resource to report state on</synopsis>
+ <description>
+ <para>In general Asterisk looks up list items in the following way:</para>
+ <para>1. Check if the list item refers to another configured resource list.</para>
+ <para>2. Pass the name of the resource off to event-package-specific handlers
+ to find the specified resource.</para>
+ <para>The second part means that the way the list item is specified depends
+ on what type of list this is. For instance, if you have the <replaceable>event</replaceable>
+ set to <literal>presence</literal>, then list items should be in the form of
+ dialplan_extension@dialplan_context. For <literal>message-summary</literal> mailbox
+ names should be listed.</para>
+ </description>
+ </configOption>
+ <configOption name="full_state" default="no">
+ <synopsis>Indicates if the entire list's state should be sent out.</synopsis>
+ <description>
+ <para>If this option is enabled, and a resource changes state, then Asterisk will construct
+ a notification that contains the state of all resources in the list. If the option is
+ disabled, Asterisk will construct a notification that only contains the states of
+ resources that have changed.</para>
+ <note>
+ <para>Even with this option disabled, there are certain situations where Asterisk is forced
+ to send a notification with the states of all resources in the list. When a subscriber
+ renews or terminates its subscription to the list, Asterisk MUST send a full state
+ notification.</para>
+ </note>
+ </description>
+ </configOption>
+ <configOption name="notification_batch_interval" default="0">
+ <synopsis>Time Asterisk should wait, in milliseconds, before sending notifications.</synopsis>
+ <description>
+ <para>When a resource's state changes, it may be desired to wait a certain amount before Asterisk
+ sends a notification to subscribers. This allows for other state changes to accumulate, so that
+ Asterisk can communicate multiple state changes in a single notification instead of rapidly sending
+ many notifications.</para>
+ </description>
+ </configOption>
+ </configObject>
<configObject name="inbound-publication">
<synopsis>The configuration for inbound publications</synopsis>
<configOption name="endpoint" default="">
@@ -143,6 +218,12 @@ static struct ast_sched_context *sched;
/*! \brief Default expiration time for PUBLISH if one is not specified */
#define DEFAULT_PUBLISH_EXPIRES 3600
+/*! \brief Number of buckets for subscription datastore */
+#define DATASTORE_BUCKETS 53
+
+/*! \brief Default expiration for subscriptions */
+#define DEFAULT_EXPIRES 3600
+
/*! \brief Defined method for PUBLISH */
const pjsip_method pjsip_publish_method =
{
@@ -206,6 +287,26 @@ enum sip_publish_type {
};
/*!
+ * \brief A vector of strings commonly used throughout this module
+ */
+AST_VECTOR(resources, const char *);
+
+/*!
+ * \brief Resource list configuration item
+ */
+struct resource_list {
+ SORCERY_OBJECT(details);
+ /*! SIP event package the list uses. */
+ char event[32];
+ /*! Strings representing resources in the list. */
+ struct resources items;
+ /*! Indicates if Asterisk sends full or partial state on notifications. */
+ unsigned int full_state;
+ /*! Time, in milliseconds Asterisk waits before sending a batched notification.*/
+ unsigned int notification_batch_interval;
+};
+
+/*!
* Used to create new entity IDs by ESCs.
*/
static int esc_etag_counter;
@@ -264,83 +365,72 @@ struct subscription_persistence {
};
/*!
- * \brief Real subscription details
+ * \brief A tree of SIP subscriptions
*
- * A real subscription is one that has a direct link to a
- * PJSIP subscription and dialog.
+ * Because of the ability to subscribe to resource lists, a SIP
+ * subscription can result in a tree of subscriptions being created.
+ * This structure represents the information relevant to the subscription
+ * as a whole, to include the underlying PJSIP structure for the
+ * subscription.
*/
-struct ast_sip_real_subscription {
+struct sip_subscription_tree {
+ /*! The endpoint with which the subscription is communicating */
+ struct ast_sip_endpoint *endpoint;
+ /*! Serializer on which to place operations for this subscription */
+ struct ast_taskprocessor *serializer;
+ /*! The role for this subscription */
+ enum ast_sip_subscription_role role;
+ /*! Persistence information */
+ struct subscription_persistence *persistence;
/*! The underlying PJSIP event subscription structure */
pjsip_evsub *evsub;
/*! The underlying PJSIP dialog */
pjsip_dialog *dlg;
+ /*! Interval to use for batching notifications */
+ unsigned int notification_batch_interval;
+ /*! Scheduler ID for batched notification */
+ int notify_sched_id;
+ /*! Indicator if scheduled batched notification should be sent */
+ unsigned int send_scheduled_notify;
+ /*! The root of the subscription tree */
+ struct ast_sip_subscription *root;
+ /*! Is this subscription to a list? */
+ int is_list;
+ /*! Next item in the list */
+ AST_LIST_ENTRY(sip_subscription_tree) next;
};
/*!
- * \brief Virtual subscription details
+ * \brief Structure representing a "virtual" SIP subscription.
*
- * A virtual subscription is one that does not have a direct
- * link to a PJSIP subscription. Instead, it is a descendent
- * of an ast_sip_subscription. Following the ancestry will
- * eventually lead to a real subscription.
- */
-struct ast_sip_virtual_subscription {
- struct ast_sip_subscription *parent;
-};
-
-/*!
- * \brief Discriminator between real and virtual subscriptions
- */
-enum sip_subscription_type {
- /*!
- * \brief a "real" subscription.
- *
- * Real subscriptions are at the root of a tree of subscriptions.
- * A real subscription has a corresponding SIP subscription in the
- * PJSIP stack.
- */
- SIP_SUBSCRIPTION_REAL,
- /*!
- * \brief a "virtual" subscription.
- *
- * Virtual subscriptions are the descendents of real subscriptions
- * in a tree of subscriptions. Virtual subscriptions do not have
- * a corresponding SIP subscription in the PJSIP stack. Instead,
- * when a state change happens on a virtual subscription, the
- * state change is indicated to the virtual subscription's parent.
- */
- SIP_SUBSCRIPTION_VIRTUAL,
-};
-
-/*!
- * \brief Structure representing a SIP subscription
+ * This structure serves a dual purpose. Structurally, it is
+ * the constructed tree of subscriptions based on the resources
+ * being subscribed to. API-wise, this serves as the handle that
+ * subscription handlers use in order to interact with the pubsub API.
*/
struct ast_sip_subscription {
/*! Subscription datastores set up by handlers */
struct ao2_container *datastores;
- /*! The endpoint with which the subscription is communicating */
- struct ast_sip_endpoint *endpoint;
- /*! Serializer on which to place operations for this subscription */
- struct ast_taskprocessor *serializer;
/*! The handler for this subscription */
const struct ast_sip_subscription_handler *handler;
- /*! The role for this subscription */
- enum ast_sip_subscription_role role;
- /*! Indicator of real or virtual subscription */
- enum sip_subscription_type type;
- /*! Real and virtual components of the subscription */
- union {
- struct ast_sip_real_subscription real;
- struct ast_sip_virtual_subscription virtual;
- } reality;
+ /*! Pointer to the base of the tree */
+ struct sip_subscription_tree *tree;
/*! Body generaator for NOTIFYs */
struct ast_sip_pubsub_body_generator *body_generator;
- /*! Persistence information */
- struct subscription_persistence *persistence;
- /*! Next item in the list */
- AST_LIST_ENTRY(ast_sip_subscription) next;
- /*! List of child subscriptions */
- AST_LIST_HEAD_NOLOCK(,ast_sip_subscription) children;
+ /*! Vector of child subscriptions */
+ AST_VECTOR(, struct ast_sip_subscription *) children;
+ /*! Saved NOTIFY body text for this subscription */
+ struct ast_str *body_text;
+ /*! Indicator that the body text has changed since the last notification */
+ int body_changed;
+ /*! The current state of the subscription */
+ pjsip_evsub_state subscription_state;
+ /*! For lists, the current version to place in the RLMI body */
+ unsigned int version;
+ /*! For lists, indicates if full state should always be communicated. */
+ unsigned int full_state;
+ /*! URI associated with the subscription */
+ pjsip_sip_uri *uri;
/*! Name of resource being subscribed to */
char resource[0];
};
@@ -362,20 +452,26 @@ static const char *sip_subscription_roles_map[] = {
[AST_SIP_NOTIFIER] = "Notifier"
};
-AST_RWLIST_HEAD_STATIC(subscriptions, ast_sip_subscription);
+AST_RWLIST_HEAD_STATIC(subscriptions, sip_subscription_tree);
AST_RWLIST_HEAD_STATIC(body_generators, ast_sip_pubsub_body_generator);
AST_RWLIST_HEAD_STATIC(body_supplements, ast_sip_pubsub_body_supplement);
-static pjsip_evsub *sip_subscription_get_evsub(const struct ast_sip_subscription *sub)
-{
- return sub->reality.real.evsub;
-}
-
-static pjsip_dialog *sip_subscription_get_dlg(const struct ast_sip_subscription *sub)
-{
- return sub->reality.real.dlg;
-}
+static void pubsub_on_evsub_state(pjsip_evsub *sub, pjsip_event *event);
+static void pubsub_on_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata,
+ int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
+static void pubsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code,
+ pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
+static void pubsub_on_client_refresh(pjsip_evsub *sub);
+static void pubsub_on_server_timeout(pjsip_evsub *sub);
+
+static pjsip_evsub_user pubsub_cb = {
+ .on_evsub_state = pubsub_on_evsub_state,
+ .on_rx_refresh = pubsub_on_rx_refresh,
+ .on_rx_notify = pubsub_on_rx_notify,
+ .on_client_refresh = pubsub_on_client_refresh,
+ .on_server_timeout = pubsub_on_server_timeout,
+};
/*! \brief Destructor for publication resource */
static void publication_resource_destroy(void *obj)
@@ -408,7 +504,7 @@ static void *subscription_persistence_alloc(const char *name)
}
/*! \brief Function which creates initial persistence information of a subscription in sorcery */
-static struct subscription_persistence *subscription_persistence_create(struct ast_sip_subscription *sub)
+static struct subscription_persistence *subscription_persistence_create(struct sip_subscription_tree *sub_tree)
{
char tag[PJ_GUID_STRING_LENGTH + 1];
@@ -418,13 +514,13 @@ static struct subscription_persistence *subscription_persistence_create(struct a
struct subscription_persistence *persistence = ast_sorcery_alloc(ast_sip_get_sorcery(),
"subscription_persistence", NULL);
- pjsip_dialog *dlg = sip_subscription_get_dlg(sub);
+ pjsip_dialog *dlg = sub_tree->dlg;
if (!persistence) {
return NULL;
}
- persistence->endpoint = ast_strdup(ast_sorcery_object_get_id(sub->endpoint));
+ persistence->endpoint = ast_strdup(ast_sorcery_object_get_id(sub_tree->endpoint));
ast_copy_pj_str(tag, &dlg->local.info->tag, sizeof(tag));
persistence->tag = ast_strdup(tag);
@@ -433,47 +529,49 @@ static struct subscription_persistence *subscription_persistence_create(struct a
}
/*! \brief Function which updates persistence information of a subscription in sorcery */
-static void subscription_persistence_update(struct ast_sip_subscription *sub,
+static void subscription_persistence_update(struct sip_subscription_tree *sub_tree,
pjsip_rx_data *rdata)
{
pjsip_dialog *dlg;
- if (!sub->persistence) {
+ if (!sub_tree->persistence) {
return;
}
- dlg = sip_subscription_get_dlg(sub);
- sub->persistence->cseq = dlg->local.cseq;
+ dlg = sub_tree->dlg;
+ sub_tree->persistence->cseq = dlg->local.cseq;
if (rdata) {
int expires;
pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
- sub->persistence->expires = ast_tvadd(ast_tvnow(), ast_samp2tv(expires, 1));
+ sub_tree->persistence->expires = ast_tvadd(ast_tvnow(), ast_samp2tv(expires, 1));
- ast_copy_string(sub->persistence->packet, rdata->pkt_info.packet, sizeof(sub->persistence->packet));
- ast_copy_string(sub->persistence->src_name, rdata->pkt_info.src_name, sizeof(sub->persistence->src_name));
- sub->persistence->src_port = rdata->pkt_info.src_port;
- ast_copy_string(sub->persistence->transport_key, rdata->tp_info.transport->type_name,
- sizeof(sub->persistence->transport_key));
- ast_copy_pj_str(sub->persistence->local_name, &rdata->tp_info.transport->local_name.host,
- sizeof(sub->persistence->local_name));
- sub->persistence->local_port = rdata->tp_info.transport->local_name.port;
+ ast_copy_string(sub_tree->persistence->packet, rdata->pkt_info.packet,
+ sizeof(sub_tree->persistence->packet));
+ ast_copy_string(sub_tree->persistence->src_name, rdata->pkt_info.src_name,
+ sizeof(sub_tree->persistence->src_name));
+ sub_tree->persistence->src_port = rdata->pkt_info.src_port;
+ ast_copy_string(sub_tree->persistence->transport_key, rdata->tp_info.transport->type_name,
+ sizeof(sub_tree->persistence->transport_key));
+ ast_copy_pj_str(sub_tree->persistence->local_name, &rdata->tp_info.transport->local_name.host,
+ sizeof(sub_tree->persistence->local_name));
+ sub_tree->persistence->local_port = rdata->tp_info.transport->local_name.port;
}
- ast_sorcery_update(ast_sip_get_sorcery(), sub->persistence);
+ ast_sorcery_update(ast_sip_get_sorcery(), sub_tree->persistence);
}
/*! \brief Function which removes persistence of a subscription from sorcery */
-static void subscription_persistence_remove(struct ast_sip_subscription *sub)
+static void subscription_persistence_remove(struct sip_subscription_tree *sub_tree)
{
- if (!sub->persistence) {
+ if (!sub_tree->persistence) {
return;
}
- ast_sorcery_delete(ast_sip_get_sorcery(), sub->persistence);
- ao2_ref(sub->persistence, -1);
+ ast_sorcery_delete(ast_sip_get_sorcery(), sub_tree->persistence);
+ ao2_ref(sub_tree->persistence, -1);
}
@@ -503,23 +601,62 @@ static struct ast_sip_subscription_handler *subscription_get_handler_from_rdata(
return handler;
}
+/*!
+ * \brief Accept headers that are exceptions to the rule
+ *
+ * Typically, when a SUBSCRIBE arrives, we attempt to find a
+ * body generator that matches one of the Accept headers in
+ * the request. When subscribing to a single resource, this works
+ * great. However, when subscribing to a list, things work
+ * differently. Most Accept header values are fine, but there
+ * are a couple that are endemic to resource lists that need
+ * to be ignored when searching for a body generator to use
+ * for the individual resources of the subscription.
+ */
+const char *accept_exceptions[] = {
+ "multipart/related",
+ "application/rlmi+xml",
+};
+
+/*!
+ * \brief Is the Accept header from the SUBSCRIBE in the list of exceptions?
+ *
+ * \retval 1 This Accept header value is an exception to the rule.
+ * \retval 0 This Accept header is not an exception to the rule.
+ */
+static int exceptional_accept(const pj_str_t *accept)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_LEN(accept_exceptions); ++i) {
+ if (!pj_strcmp2(accept, accept_exceptions[i])) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
/*! \brief Retrieve a body generator using the Accept header of an rdata message */
static struct ast_sip_pubsub_body_generator *subscription_get_generator_from_rdata(pjsip_rx_data *rdata,
const struct ast_sip_subscription_handler *handler)
{
- pjsip_accept_hdr *accept_header;
+ pjsip_accept_hdr *accept_header = (pjsip_accept_hdr *) &rdata->msg_info.msg->hdr;
char accept[AST_SIP_MAX_ACCEPT][64];
- size_t num_accept_headers;
+ size_t num_accept_headers = 0;
- accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, rdata->msg_info.msg->hdr.next);
- if (accept_header) {
+ while ((accept_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, accept_header->next))) {
int i;
for (i = 0; i < accept_header->count; ++i) {
- ast_copy_pj_str(accept[i], &accept_header->values[i], sizeof(accept[i]));
+ if (!exceptional_accept(&accept_header->values[i])) {
+ ast_copy_pj_str(accept[num_accept_headers], &accept_header->values[i], sizeof(accept[num_accept_headers]));
+ ++num_accept_headers;
+ }
}
- num_accept_headers = accept_header->count;
- } else {
+ }
+
+ if (num_accept_headers == 0) {
/* If a SUBSCRIBE contains no Accept headers, then we must assume that
* the default accept type for the event package is to be used.
*/
@@ -530,9 +667,583 @@ static struct ast_sip_pubsub_body_generator *subscription_get_generator_from_rda
return find_body_generator(accept, num_accept_headers);
}
-static struct ast_sip_subscription *notifier_create_subscription(const struct ast_sip_subscription_handler *handler,
+struct resource_tree;
+
+/*!
+ * \brief A node for a resource tree.
+ */
+struct tree_node {
+ AST_VECTOR(, struct tree_node *) children;
+ unsigned int full_state;
+ char resource[0];
+};
+
+/*!
+ * \brief Helper function for retrieving a resource list for a given event.
+ *
+ * This will retrieve a resource list that corresponds to the resource and event provided.
+ *
+ * \param resource The name of the resource list to retrieve
+ * \param event The expected event name on the resource list
+ */
+static struct resource_list *retrieve_resource_list(const char *resource, const char *event)
+{
+ struct resource_list *list;
+
+ list = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "resource_list", resource);
+ if (!list) {
+ return NULL;
+ }
+
+ if (strcmp(list->event, event)) {
+ ast_log(LOG_WARNING, "Found resource list %s, but its event type (%s) does not match SUBSCRIBE's (%s)\n",
+ resource, list->event, event);
+ ao2_cleanup(list);
+ return NULL;
+ }
+
+ return list;
+}
+
+/*!
+ * \brief Allocate a tree node
+ *
+ * In addition to allocating and initializing the tree node, the node is also added
+ * to the vector of visited resources. See \ref build_resource_tree for more information
+ * on the visited resources.
+ *
+ * \param resource The name of the resource for this tree node.
+ * \param visited The vector of resources that have been visited.
+ * \param if allocating a list, indicate whether full state is requested in notifications.
+ * \retval NULL Allocation failure.
+ * \retval non-NULL The newly-allocated tree_node
+ */
+static struct tree_node *tree_node_alloc(const char *resource, struct resources *visited, unsigned int full_state)
+{
+ struct tree_node *node;
+
+ node = ast_calloc(1, sizeof(*node) + strlen(resource) + 1);
+ if (!node) {
+ return NULL;
+ }
+
+ strcpy(node->resource, resource);
+ if (AST_VECTOR_INIT(&node->children, 4)) {
+ ast_free(node);
+ return NULL;
+ }
+ node->full_state = full_state;
+
+ if (visited) {
+ AST_VECTOR_APPEND(visited, resource);
+ }
+ return node;
+}
+
+/*!
+ * \brief Destructor for a tree node
+ *
+ * This function calls recursively in order to destroy
+ * all nodes lower in the tree from the given node in
+ * addition to the node itself.
+ *
+ * \param node The node to destroy.
+ */
+static void tree_node_destroy(struct tree_node *node)
+{
+ int i;
+ if (!node) {
+ return;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&node->children); ++i) {
+ tree_node_destroy(AST_VECTOR_GET(&node->children, i));
+ }
+ AST_VECTOR_FREE(&node->children);
+ ast_free(node);
+}
+
+/*!
+ * \brief Determine if this resource has been visited already
+ *
+ * See \ref build_resource_tree for more information
+ *
+ * \param resource The resource currently being visited
+ * \param visited The resources that have previously been visited
+ */
+static int have_visited(const char *resource, struct resources *visited)
+{
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(visited); ++i) {
+ if (!strcmp(resource, AST_VECTOR_GET(visited, i))) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Build child nodes for a given parent.
+ *
+ * This iterates through the items on a resource list and creates tree nodes for each one. The
+ * tree nodes created are children of the supplied parent node. If an item in the resource
+ * list is itself a list, then this function is called recursively to provide children for
+ * the the new node.
+ *
+ * If an item in a resource list is not a list, then the supplied subscription handler is
+ * called into as if a new SUBSCRIBE for the list item were presented. The handler's response
+ * is used to determine if the node can be added to the tree or not.
+ *
+ * If a parent node ends up having no child nodes added under it, then the parent node is
+ * pruned from the tree.
+ *
+ * \param endpoint The endpoint that sent the inbound SUBSCRIBE.
+ * \param handler The subscription handler for leaf nodes in the tree.
+ * \param list The configured resource list from which the child node is being built.
+ * \param parent The parent node for these children.
+ * \param visited The resources that have already been visited.
+ */
+static void build_node_children(struct ast_sip_endpoint *endpoint, const struct ast_sip_subscription_handler *handler,
+ struct resource_list *list, struct tree_node *parent, struct resources *visited)
+{
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
+ struct tree_node *current;
+ struct resource_list *child_list;
+ const char *resource = AST_VECTOR_GET(&list->items, i);
+
+ if (have_visited(resource, visited)) {
+ ast_debug(1, "Already visited resource %s. Avoiding duplicate resource or potential loop.\n", resource);
+ continue;
+ }
+
+ child_list = retrieve_resource_list(resource, list->event);
+ if (!child_list) {
+ int resp = handler->notifier->new_subscribe(endpoint, resource);
+ if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
+ current = tree_node_alloc(resource, visited, 0);
+ if (!current) {
+ ast_debug(1, "Subscription to leaf resource %s was successful, but encountered"
+ "allocation error afterwards\n", resource);
+ continue;
+ }
+ ast_debug(1, "Subscription to leaf resource %s resulted in success. Adding to parent %s\n",
+ resource, parent->resource);
+ AST_VECTOR_APPEND(&parent->children, current);
+ } else {
+ ast_debug(1, "Subscription to leaf resource %s resulted in error response %d\n",
+ resource, resp);
+ }
+ } else {
+ ast_debug(1, "Resource %s (child of %s) is a list\n", resource, parent->resource);
+ current = tree_node_alloc(resource, visited, child_list->full_state);
+ if (!current) {
+ ast_debug(1, "Cannot build children of resource %s due to allocation failure\n", resource);
+ continue;
+ }
+ build_node_children(endpoint, handler, child_list, current, visited);
+ if (AST_VECTOR_SIZE(&current->children) > 0) {
+ ast_debug(1, "List %s had no successful children.\n", resource);
+ AST_VECTOR_APPEND(&parent->children, current);
+ } else {
+ ast_debug(1, "List %s had successful children. Adding to parent %s\n",
+ resource, parent->resource);
+ tree_node_destroy(current);
+ }
+ ao2_cleanup(child_list);
+ }
+ }
+}
+
+/*!
+ * \brief A resource tree
+ *
+ * When an inbound SUBSCRIBE arrives, the resource being subscribed to may
+ * be a resource list. If this is the case, the resource list may contain resources
+ * that are themselves lists. The structure needed to hold the resources is
+ * a tree.
+ *
+ * Upon receipt of the SUBSCRIBE, the tree is built by determining if subscriptions
+ * to the individual resources in the tree would be successful or not. Any successful
+ * subscriptions result in a node in the tree being created. Any unsuccessful subscriptions
+ * result in no node being created.
+ *
+ * This tree can be seen as a bare-bones analog of the tree of ast_sip_subscriptions that
+ * will end up being created to actually carry out the duties of a SIP SUBSCRIBE dialog.
+ */
+struct resource_tree {
+ struct tree_node *root;
+ unsigned int notification_batch_interval;
+};
+
+/*!
+ * \brief Destroy a resource tree.
+ *
+ * This function makes no assumptions about how the tree itself was
+ * allocated and does not attempt to free the tree itself. Callers
+ * of this function are responsible for freeing the tree.
+ *
+ * \param tree The tree to destroy.
+ */
+static void resource_tree_destroy(struct resource_tree *tree)
+{
+ if (tree) {
+ tree_node_destroy(tree->root);
+ }
+}
+
+/*!
+ * \brief Build a resource tree
+ *
+ * This function builds a resource tree based on the requested resource in a SUBSCRIBE request.
+ *
+ * This function also creates a container that has all resources that have been visited during
+ * creation of the tree, whether those resources resulted in a tree node being created or not.
+ * Keeping this container of visited resources allows for misconfigurations such as loops in
+ * the tree or duplicated resources to be detected.
+ *
+ * \param endpoint The endpoint that sent the SUBSCRIBE request.
+ * \param handler The subscription handler for leaf nodes in the tree.
+ * \param resource The resource requested in the SUBSCRIBE request.
+ * \param tree The tree that is to be built.
+ *
+ * \retval 200-299 Successfully subscribed to at least one resource.
+ * \retval 300-699 Failure to subscribe to requested resource.
+ */
+static int build_resource_tree(struct ast_sip_endpoint *endpoint, const struct ast_sip_subscription_handler *handler,
+ const char *resource, struct resource_tree *tree)
+{
+ struct resource_list *list;
+ struct resources visited;
+
+ list = retrieve_resource_list(resource, handler->event_name);
+ if (!list) {
+ ast_debug(1, "Subscription to resource %s is not to a list\n", resource);
+ tree->root = tree_node_alloc(resource, NULL, 0);
+ if (!tree->root) {
+ return 500;
+ }
+ return handler->notifier->new_subscribe(endpoint, resource);
+ }
+
+ ast_debug(1, "Subscription to resource %s is a list\n", resource);
+ if (AST_VECTOR_INIT(&visited, AST_VECTOR_SIZE(&list->items))) {
+ return 500;
+ }
+
+ tree->root = tree_node_alloc(resource, &visited, list->full_state);
+ if (!tree->root) {
+ return 500;
+ }
+
+ tree->notification_batch_interval = list->notification_batch_interval;
+
+ build_node_children(endpoint, handler, list, tree->root, &visited);
+ AST_VECTOR_FREE(&visited);
+ ao2_cleanup(list);
+
+ if (AST_VECTOR_SIZE(&tree->root->children) > 0) {
+ return 200;
+ } else {
+ return 500;
+ }
+}
+
+static int datastore_hash(const void *obj, int flags)
+{
+ const struct ast_datastore *datastore = obj;
+ const char *uid = flags & OBJ_KEY ? obj : datastore->uid;
+
+ ast_assert(uid != NULL);
+
+ return ast_str_hash(uid);
+}
+
+static int datastore_cmp(void *obj, void *arg, int flags)
+{
+ const struct ast_datastore *datastore1 = obj;
+ const struct ast_datastore *datastore2 = arg;
+ const char *uid2 = flags & OBJ_KEY ? arg : datastore2->uid;
+
+ ast_assert(datastore1->uid != NULL);
+ ast_assert(uid2 != NULL);
+
+ return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static int subscription_remove_serializer(void *obj)
+{
+ struct sip_subscription_tree *sub_tree = obj;
+
+ /* This is why we keep the dialog on the subscription. When the subscription
+ * is destroyed, there is no guarantee that the underlying dialog is ready
+ * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+ * either. The dialog could be destroyed before our subscription is. We fix
+ * this problem by keeping a reference to the dialog until it is time to
+ * destroy the subscription. We need to have the dialog available when the
+ * subscription is destroyed so that we can guarantee that our attempt to
+ * remove the serializer will be successful.
+ */
+ ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
+ pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
+
+ return 0;
+}
+
+static void add_subscription(struct sip_subscription_tree *obj)
+{
+ SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_INSERT_TAIL(&subscriptions, obj, next);
+}
+
+static void remove_subscription(struct sip_subscription_tree *obj)
+{
+ struct sip_subscription_tree *i;
+ SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&subscriptions, i, next) {
+ if (i == obj) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ ast_debug(1, "Removing subscription to resource %s from list of subscriptions\n",
+ ast_sip_subscription_get_resource_name(i->root));
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+static void subscription_destructor(void *obj)
+{
+ struct ast_sip_subscription *sub = obj;
+
+ ast_debug(3, "Destroying SIP subscription to resource %s\n", sub->resource);
+ ast_free(sub->body_text);
+
+ ao2_cleanup(sub->datastores);
+}
+
+static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_subscription_handler *handler,
+ const char *resource, struct sip_subscription_tree *tree)
+{
+ struct ast_sip_subscription *sub;
+ pjsip_sip_uri *contact_uri;
+
+ sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor);
+ if (!sub) {
+ return NULL;
+ }
+ strcpy(sub->resource, resource); /* Safe */
+
+ sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
+ if (!sub->datastores) {
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+
+ sub->body_text = ast_str_create(128);
+ if (!sub->body_text) {
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+
+ sub->uri = pjsip_sip_uri_create(tree->dlg->pool, PJ_FALSE);
+ contact_uri = pjsip_uri_get_uri(tree->dlg->local.contact->uri);
+ pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, contact_uri);
+ pj_strdup2(tree->dlg->pool, &sub->uri->user, resource);
+
+ sub->handler = handler;
+ sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
+ sub->tree = tree;
+
+ return sub;
+}
+
+/*!
+ * \brief Create a tree of virtual subscriptions based on a resource tree node.
+ *
+ * \param handler The handler to supply to leaf subscriptions.
+ * \param resource The requested resource for this subscription.
+ * \param generator Body generator to use for leaf subscriptions.
+ * \param tree The root of the subscription tree.
+ * \param current The tree node that corresponds to the subscription being created.
+ */
+static struct ast_sip_subscription *create_virtual_subscriptions(const struct ast_sip_subscription_handler *handler,
+ const char *resource, struct ast_sip_pubsub_body_generator *generator,
+ struct sip_subscription_tree *tree, struct tree_node *current)
+{
+ int i;
+ struct ast_sip_subscription *sub;
+
+ sub = allocate_subscription(handler, resource, tree);
+ if (!sub) {
+ return NULL;
+ }
+
+ sub->full_state = current->full_state;
+ sub->body_generator = generator;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&current->children); ++i) {
+ struct ast_sip_subscription *child;
+ struct tree_node *child_node = AST_VECTOR_GET(&current->children, i);
+
+ child = create_virtual_subscriptions(handler, child_node->resource, generator,
+ tree, child_node);
+
+ if (!child) {
+ ast_debug(1, "Child subscription to resource %s could not be created\n",
+ child_node->resource);
+ continue;
+ }
+
+ if (AST_VECTOR_APPEND(&sub->children, child)) {
+ ast_debug(1, "Child subscription to resource %s could not be appended\n",
+ child_node->resource);
+ }
+ }
+
+ return sub;
+}
+
+static void shutdown_subscriptions(struct ast_sip_subscription *sub)
+{
+ int i;
+
+ if (AST_VECTOR_SIZE(&sub->children) > 0) {
+ for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+ shutdown_subscriptions(AST_VECTOR_GET(&sub->children, i));
+ ao2_cleanup(AST_VECTOR_GET(&sub->children, i));
+ }
+ return;
+ }
+
+ if (sub->handler->subscription_shutdown) {
+ sub->handler->subscription_shutdown(sub);
+ }
+}
+
+static void subscription_tree_destructor(void *obj)
+{
+ struct sip_subscription_tree *sub_tree = obj;
+
+ remove_subscription(sub_tree);
+
+ subscription_persistence_remove(sub_tree);
+ ao2_cleanup(sub_tree->endpoint);
+
+ if (sub_tree->dlg) {
+ ast_sip_push_task_synchronous(NULL, subscription_remove_serializer, sub_tree);
+ }
+
+ shutdown_subscriptions(sub_tree->root);
+ ao2_cleanup(sub_tree->root);
+
+ ast_taskprocessor_unreference(sub_tree->serializer);
+ ast_module_unref(ast_module_info->self);
+}
+
+static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
+{
+ /* We keep a reference to the dialog until our subscription is destroyed. See
+ * the subscription_destructor for more details
+ */
+ pjsip_dlg_inc_session(dlg, &pubsub_module);
+ sub_tree->dlg = dlg;
+ ast_sip_dialog_set_serializer(dlg, sub_tree->serializer);
+ pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree);
+}
+
+static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint)
+{
+ struct sip_subscription_tree *sub_tree;
+
+ sub_tree = ao2_alloc(sizeof *sub_tree, subscription_tree_destructor);
+ if (!sub_tree) {
+ return NULL;
+ }
+
+ ast_module_ref(ast_module_info->self);
+
+ sub_tree->serializer = ast_sip_create_serializer();
+ if (!sub_tree->serializer) {
+ ao2_ref(sub_tree, -1);
+ return NULL;
+ }
+
+ sub_tree->endpoint = ao2_bump(endpoint);
+ sub_tree->notify_sched_id = -1;
+
+ add_subscription(sub_tree);
+ return sub_tree;
+}
+
+/*!
+ * \brief Create a subscription tree based on a resource tree.
+ *
+ * Using the previously-determined valid resources in the provided resource tree,
+ * a corresponding tree of ast_sip_subscriptions are created. The root of the
+ * subscription tree is a real subscription, and the rest in the tree are
+ * virtual subscriptions.
+ *
+ * \param handler The handler to use for leaf subscriptions
+ * \param endpoint The endpoint that sent the SUBSCRIBE request
+ * \param rdata The SUBSCRIBE content
+ * \param resource The requested resource in the SUBSCRIBE request
+ * \param generator The body generator to use in leaf subscriptions
+ * \param tree The resource tree on which the subscription tree is based
+ *
+ * \retval NULL Could not create the subscription tree
+ * \retval non-NULL The root of the created subscription tree
+ */
+
+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 ast_sip_pubsub_body_generator *generator, struct resource_tree *tree)
+{
+ struct sip_subscription_tree *sub_tree;
+ pjsip_dialog *dlg;
+ struct subscription_persistence *persistence;
+
+ sub_tree = allocate_subscription_tree(endpoint);
+ if (!sub_tree) {
+ return NULL;
+ }
+
+ dlg = ast_sip_create_dialog_uas(endpoint, rdata);
+ if (!dlg) {
+ ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
+ ao2_ref(sub_tree, -1);
+ return NULL;
+ }
+
+ persistence = ast_sip_mod_data_get(rdata->endpt_info.mod_data,
+ pubsub_module.id, MOD_DATA_PERSISTENCE);
+ if (persistence) {
+ /* Update the created dialog with the persisted information */
+ pjsip_ua_unregister_dlg(pjsip_ua_instance(), dlg);
+ pj_strdup2(dlg->pool, &dlg->local.info->tag, persistence->tag);
+ dlg->local.tag_hval = pj_hash_calc_tolower(0, NULL, &dlg->local.info->tag);
+ pjsip_ua_register_dlg(pjsip_ua_instance(), dlg);
+ dlg->local.cseq = persistence->cseq;
+ dlg->remote.cseq = persistence->cseq;
+ }
+
+ pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub);
+ subscription_setup_dialog(sub_tree, dlg);
+
+ ast_sip_mod_data_set(dlg->pool, dlg->mod_data, pubsub_module.id, MOD_DATA_MSG,
+ pjsip_msg_clone(dlg->pool, rdata->msg_info.msg));
+
+ sub_tree->notification_batch_interval = tree->notification_batch_interval;
+
+ 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;
+ }
+
+ return sub_tree;
+}
/*! \brief Callback function to perform the actual recreation of a subscription */
static int subscription_persistence_recreate(void *obj, void *arg, int flags)
@@ -540,15 +1251,16 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags)
struct subscription_persistence *persistence = obj;
pj_pool_t *pool = arg;
pjsip_rx_data rdata = { { 0, }, };
- pjsip_expires_hdr *expires_header;
- struct ast_sip_subscription_handler *handler;
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
- struct ast_sip_subscription *sub;
+ struct sip_subscription_tree *sub_tree;
struct ast_sip_pubsub_body_generator *generator;
int resp;
char *resource;
size_t resource_size;
pjsip_sip_uri *request_uri;
+ struct resource_tree tree;
+ pjsip_expires_hdr *expires_header;
+ struct ast_sip_subscription_handler *handler;
/* If this subscription has already expired remove it */
if (ast_tvdiff_ms(persistence->expires, ast_tvnow()) <= 0) {
@@ -607,14 +1319,16 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags)
ast_sip_mod_data_set(rdata.tp_info.pool, rdata.endpt_info.mod_data,
pubsub_module.id, MOD_DATA_PERSISTENCE, persistence);
- resp = handler->notifier->new_subscribe(endpoint, resource);
+ memset(&tree, 0, sizeof(tree));
+ resp = build_resource_tree(endpoint, handler, resource, &tree);
if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
- sub = notifier_create_subscription(handler, endpoint, &rdata, resource, generator);
- sub->persistence = ao2_bump(persistence);
- subscription_persistence_update(sub, &rdata);
+ sub_tree = create_subscription_tree(handler, endpoint, &rdata, resource, generator, &tree);
+ sub_tree->persistence = ao2_bump(persistence);
+ subscription_persistence_update(sub_tree, &rdata);
} else {
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
}
+ resource_tree_destroy(&tree);
return 0;
}
@@ -668,33 +1382,12 @@ static void subscription_persistence_event_cb(void *data, struct stasis_subscrip
stasis_unsubscribe(sub);
}
-static void add_subscription(struct ast_sip_subscription *obj)
-{
- SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
- AST_RWLIST_INSERT_TAIL(&subscriptions, obj, next);
- ast_module_ref(ast_module_info->self);
-}
-
-static void remove_subscription(struct ast_sip_subscription *obj)
-{
- struct ast_sip_subscription *i;
- SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
- AST_RWLIST_TRAVERSE_SAFE_BEGIN(&subscriptions, i, next) {
- if (i == obj) {
- AST_RWLIST_REMOVE_CURRENT(next);
- ast_module_unref(ast_module_info->self);
- break;
- }
- }
- AST_RWLIST_TRAVERSE_SAFE_END;
-}
-
-typedef int (*on_subscription_t)(struct ast_sip_subscription *sub, void *arg);
+typedef int (*on_subscription_t)(struct sip_subscription_tree *sub, void *arg);
static int for_each_subscription(on_subscription_t on_subscription, void *arg)
{
int num = 0;
- struct ast_sip_subscription *i;
+ struct sip_subscription_tree *i;
SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
if (!on_subscription) {
@@ -710,22 +1403,21 @@ static int for_each_subscription(on_subscription_t on_subscription, void *arg)
return num;
}
-static void sip_subscription_to_ami(struct ast_sip_subscription *sub,
+static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree,
struct ast_str **buf)
{
char str[256];
- struct ast_sip_endpoint_id_configuration *id = &sub->endpoint->id;
+ struct ast_sip_endpoint_id_configuration *id = &sub_tree->endpoint->id;
ast_str_append(buf, 0, "Role: %s\r\n",
- sip_subscription_roles_map[sub->role]);
+ sip_subscription_roles_map[sub_tree->role]);
ast_str_append(buf, 0, "Endpoint: %s\r\n",
- ast_sorcery_object_get_id(sub->endpoint));
+ ast_sorcery_object_get_id(sub_tree->endpoint));
- ast_copy_pj_str(str, &sip_subscription_get_dlg(sub)->call_id->id, sizeof(str));
+ ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
ast_str_append(buf, 0, "Callid: %s\r\n", str);
- ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(
- sip_subscription_get_evsub(sub)));
+ ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub));
ast_callerid_merge(str, sizeof(str),
S_COR(id->self.name.valid, id->self.name.str, NULL),
@@ -734,333 +1426,624 @@ static void sip_subscription_to_ami(struct ast_sip_subscription *sub,
ast_str_append(buf, 0, "Callerid: %s\r\n", str);
- if (sub->handler->to_ami) {
- sub->handler->to_ami(sub, buf);
+ /* XXX This needs to be done recursively for lists */
+ if (sub_tree->root->handler->to_ami) {
+ sub_tree->root->handler->to_ami(sub_tree->root, buf);
}
}
-#define DATASTORE_BUCKETS 53
-
-#define DEFAULT_EXPIRES 3600
-static int datastore_hash(const void *obj, int flags)
+void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header)
{
- const struct ast_datastore *datastore = obj;
- const char *uid = flags & OBJ_KEY ? obj : datastore->uid;
+ pjsip_dialog *dlg = sub->tree->dlg;
+ pjsip_msg *msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
+ pj_str_t name;
- ast_assert(uid != NULL);
+ pj_cstr(&name, header);
- return ast_str_hash(uid);
+ return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
}
-static int datastore_cmp(void *obj, void *arg, int flags)
+struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
+ struct ast_sip_endpoint *endpoint, const char *resource)
{
- const struct ast_datastore *datastore1 = obj;
- const struct ast_datastore *datastore2 = arg;
- const char *uid2 = flags & OBJ_KEY ? arg : datastore2->uid;
+ struct ast_sip_subscription *sub;
+ pjsip_dialog *dlg;
+ struct ast_sip_contact *contact;
+ pj_str_t event;
+ pjsip_tx_data *tdata;
+ pjsip_evsub *evsub;
+ struct sip_subscription_tree *sub_tree = NULL;
- ast_assert(datastore1->uid != NULL);
- ast_assert(uid2 != NULL);
+ sub_tree = allocate_subscription_tree(endpoint);
+ if (!sub_tree) {
+ return NULL;
+ }
- return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
-}
+ sub = allocate_subscription(handler, resource, sub_tree);
+ if (!sub) {
+ ao2_cleanup(sub_tree);
+ return NULL;
+ }
-static int subscription_remove_serializer(void *obj)
-{
- struct ast_sip_subscription *sub = obj;
+ contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ if (!contact || ast_strlen_zero(contact->uri)) {
+ ast_log(LOG_WARNING, "No contacts configured for endpoint %s. Unable to create SIP subsription\n",
+ ast_sorcery_object_get_id(endpoint));
+ ao2_ref(sub_tree, -1);
+ ao2_cleanup(contact);
+ return NULL;
+ }
- /* This is why we keep the dialog on the subscription. When the subscription
- * is destroyed, there is no guarantee that the underlying dialog is ready
- * to be destroyed. Furthermore, there's no guarantee in the opposite direction
- * either. The dialog could be destroyed before our subscription is. We fix
- * this problem by keeping a reference to the dialog until it is time to
- * destroy the subscription. We need to have the dialog available when the
- * subscription is destroyed so that we can guarantee that our attempt to
- * remove the serializer will be successful.
- */
- ast_sip_dialog_set_serializer(sip_subscription_get_dlg(sub), NULL);
- pjsip_dlg_dec_session(sip_subscription_get_dlg(sub), &pubsub_module);
+ dlg = ast_sip_create_dialog_uac(endpoint, contact->uri, NULL);
+ ao2_cleanup(contact);
+ if (!dlg) {
+ ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
+ ao2_ref(sub_tree, -1);
+ return NULL;
+ }
- return 0;
+ pj_cstr(&event, handler->event_name);
+ pjsip_evsub_create_uac(dlg, &pubsub_cb, &event, 0, &sub_tree->evsub);
+ subscription_setup_dialog(sub_tree, dlg);
+
+ evsub = sub_tree->evsub;
+
+ if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
+ pjsip_evsub_send_request(evsub, tdata);
+ } else {
+ /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
+ * being called and terminating the subscription. Therefore, we don't
+ * need to decrease the reference count of sub here.
+ */
+ pjsip_evsub_terminate(evsub, PJ_TRUE);
+ ao2_ref(sub_tree, -1);
+ return NULL;
+ }
+
+ return sub;
}
-static void subscription_destructor(void *obj)
+struct ast_sip_endpoint *ast_sip_subscription_get_endpoint(struct ast_sip_subscription *sub)
{
- struct ast_sip_subscription *sub = obj;
+ ast_assert(sub->tree->endpoint != NULL);
+ return ao2_bump(sub->tree->endpoint);
+}
- ast_debug(3, "Destroying SIP subscription\n");
+struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_subscription *sub)
+{
+ ast_assert(sub->tree->serializer != NULL);
+ return sub->tree->serializer;
+}
- subscription_persistence_remove(sub);
+static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
+{
+#ifdef TEST_FRAMEWORK
+ struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
+#endif
+ int res;
- remove_subscription(sub);
+ res = pjsip_evsub_send_request(sub_tree->evsub, tdata) == PJ_SUCCESS ? 0 : -1;
+ subscription_persistence_update(sub_tree, NULL);
- ao2_cleanup(sub->datastores);
- ao2_cleanup(sub->endpoint);
+ ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
+ "StateText: %s\r\n"
+ "Endpoint: %s\r\n",
+ pjsip_evsub_get_state_name(sub_tree->evsub),
+ ast_sorcery_object_get_id(endpoint));
- if (sip_subscription_get_dlg(sub)) {
- ast_sip_push_task_synchronous(NULL, subscription_remove_serializer, sub);
- }
- ast_taskprocessor_unreference(sub->serializer);
+ return res;
}
+/*!
+ * \brief Add a resource XML element to an RLMI body
+ *
+ * Each resource element represents a subscribed resource in the list. This function currently
+ * will unconditionally add an instance element to each created resource element. Instance
+ * elements refer to later parts in the multipart body.
+ *
+ * \param pool PJLIB allocation pool
+ * \param cid Content-ID header of the resource
+ * \param resource_name Name of the resource
+ * \param resource_uri URI of the resource
+ * \param state State of the subscribed resource
+ */
+static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_generic_string_hdr *cid,
+ const char *resource_name, const pjsip_sip_uri *resource_uri, pjsip_evsub_state state)
+{
+ static pj_str_t cid_name = { "cid", 3 };
+ pj_xml_node *resource;
+ pj_xml_node *name;
+ pj_xml_node *instance;
+ pj_xml_attr *cid_attr;
+ char id[6];
+ char uri[PJSIP_MAX_URL_SIZE];
+
+ /* This creates a string representing the Content-ID without the enclosing < > */
+ const pj_str_t cid_stripped = {
+ .ptr = cid->hvalue.ptr + 1,
+ .slen = cid->hvalue.slen - 2,
+ };
-static void pubsub_on_evsub_state(pjsip_evsub *sub, pjsip_event *event);
-static void pubsub_on_rx_refresh(pjsip_evsub *sub, pjsip_rx_data *rdata,
- int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
-static void pubsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code,
- pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body);
-static void pubsub_on_client_refresh(pjsip_evsub *sub);
-static void pubsub_on_server_timeout(pjsip_evsub *sub);
+ resource = ast_sip_presence_xml_create_node(pool, rlmi, "resource");
+ name = ast_sip_presence_xml_create_node(pool, resource, "name");
+ instance = ast_sip_presence_xml_create_node(pool, resource, "instance");
+ pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, resource_uri, uri, sizeof(uri));
+ ast_sip_presence_xml_create_attr(pool, resource, "uri", uri);
-static pjsip_evsub_user pubsub_cb = {
- .on_evsub_state = pubsub_on_evsub_state,
- .on_rx_refresh = pubsub_on_rx_refresh,
- .on_rx_notify = pubsub_on_rx_notify,
- .on_client_refresh = pubsub_on_client_refresh,
- .on_server_timeout = pubsub_on_server_timeout,
+ pj_strdup2(pool, &name->content, resource_name);
+
+ ast_generate_random_string(id, sizeof(id));
+
+ ast_sip_presence_xml_create_attr(pool, instance, "id", id);
+ ast_sip_presence_xml_create_attr(pool, instance, "state",
+ state == PJSIP_EVSUB_STATE_TERMINATED ? "terminated" : "active");
+
+ /* Use the PJLIB-util XML library directly here since we are using a
+ * pj_str_t
+ */
+
+ cid_attr = pj_xml_attr_new(pool, &cid_name, &cid_stripped);
+ pj_xml_add_attr(instance, cid_attr);
+}
+
+/*!
+ * \brief A multipart body part and meta-information
+ *
+ * When creating a multipart body part, the end result (the
+ * pjsip_multipart_part) is hard to inspect without undoing
+ * a lot of what was done to create it. Therefore, we use this
+ * structure to store meta-information about the body part.
+ *
+ * The main consumer of this is the creator of the RLMI body
+ * part of a multipart resource list body.
+ */
+struct body_part {
+ /*! Content-ID header for the body part */
+ pjsip_generic_string_hdr *cid;
+ /*! Subscribed resource represented in the body part */
+ const char *resource;
+ /*! URI for the subscribed body part */
+ pjsip_sip_uri *uri;
+ /*! Subscription state of the resource represented in the body part */
+ pjsip_evsub_state state;
+ /*! The actual body part that will be present in the multipart body */
+ pjsip_multipart_part *part;
};
-static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_subscription_handler *handler,
- struct ast_sip_endpoint *endpoint, const char *resource, enum ast_sip_subscription_role role)
+/*!
+ * \brief Type declaration for container of body part structures
+ */
+AST_VECTOR(body_part_list, struct body_part *);
+
+/*!
+ * \brief Create a Content-ID header
+ *
+ * Content-ID headers are required by RFC2387 for multipart/related
+ * bodies. They serve as identifiers for each part of the multipart body.
+ *
+ * \param pool PJLIB allocation pool
+ * \param sub Subscription to a resource
+ */
+static pjsip_generic_string_hdr *generate_content_id_hdr(pj_pool_t *pool,
+ const struct ast_sip_subscription *sub)
{
- struct ast_sip_subscription *sub;
+ static const pj_str_t cid_name = { "Content-ID", 10 };
+ pjsip_generic_string_hdr *cid;
+ char id[6];
+ size_t alloc_size;
+ pj_str_t cid_value;
- sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor);
- if (!sub) {
- return NULL;
- }
- strcpy(sub->resource, resource); /* Safe */
+ /* '<' + '@' + '>' = 3. pj_str_t does not require a null-terminator */
+ alloc_size = sizeof(id) + pj_strlen(&sub->uri->host) + 3;
+ cid_value.ptr = pj_pool_alloc(pool, alloc_size);
+ cid_value.slen = sprintf(cid_value.ptr, "<%s@%.*s>",
+ ast_generate_random_string(id, sizeof(id)),
+ (int) pj_strlen(&sub->uri->host), pj_strbuf(&sub->uri->host));
+ cid = pjsip_generic_string_hdr_create(pool, &cid_name, &cid_value);
- sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
- if (!sub->datastores) {
- ao2_ref(sub, -1);
- return NULL;
- }
- sub->serializer = ast_sip_create_serializer();
- if (!sub->serializer) {
- ao2_ref(sub, -1);
- return NULL;
+ return cid;
+}
+
+static int rlmi_print_body(struct pjsip_msg_body *msg_body, char *buf, pj_size_t size)
+{
+ int num_printed;
+ pj_xml_node *rlmi = msg_body->data;
+
+ num_printed = pj_xml_print(rlmi, buf, size, PJ_TRUE);
+ if (num_printed == AST_PJSIP_XML_PROLOG_LEN) {
+ return -1;
}
- sub->role = role;
- sub->type = SIP_SUBSCRIPTION_REAL;
- sub->endpoint = ao2_bump(endpoint);
- sub->handler = handler;
- return sub;
+ return num_printed;
}
-static void subscription_setup_dialog(struct ast_sip_subscription *sub, pjsip_dialog *dlg)
+static void *rlmi_clone_data(pj_pool_t *pool, const void *data, unsigned len)
{
- /* We keep a reference to the dialog until our subscription is destroyed. See
- * the subscription_destructor for more details
- */
- pjsip_dlg_inc_session(dlg, &pubsub_module);
- sub->reality.real.dlg = dlg;
- ast_sip_dialog_set_serializer(dlg, sub->serializer);
- pjsip_evsub_set_mod_data(sip_subscription_get_evsub(sub), pubsub_module.id, sub);
+ const pj_xml_node *rlmi = data;
+
+ return pj_xml_clone(pool, rlmi);
}
-static struct ast_sip_subscription *notifier_create_subscription(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)
+/*!
+ * \brief Create an RLMI body part for a multipart resource list body
+ *
+ * RLMI (Resource list meta information) is a special body type that lists
+ * the subscribed resources and tells subscribers the number of subscribed
+ * resources and what other body parts are in the multipart body. The
+ * RLMI body also has a version number that a subscriber can use to ensure
+ * that the locally-stored state corresponds to server state.
+ *
+ * \param pool The allocation pool
+ * \param sub The subscription representing the subscribed resource list
+ * \param body_parts A container of body parts that RLMI will refer to
+ * \param full_state Indicates whether this is a full or partial state notification
+ * \return The multipart part representing the RLMI body
+ */
+static pjsip_multipart_part *build_rlmi_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
+ struct body_part_list *body_parts, unsigned int full_state)
+{
+ static const pj_str_t rlmi_type = { "application", 11 };
+ static const pj_str_t rlmi_subtype = { "rlmi+xml", 8 };
+ pj_xml_node *rlmi;
+ pj_xml_node *name;
+ pjsip_multipart_part *rlmi_part;
+ char version_str[32];
+ char uri[PJSIP_MAX_URL_SIZE];
+ pjsip_generic_string_hdr *cid;
+ int i;
+
+ rlmi = ast_sip_presence_xml_create_node(pool, NULL, "list");
+ ast_sip_presence_xml_create_attr(pool, rlmi, "xmlns", "urn:ietf:params:xml:ns:rlmi");
+
+ ast_sip_subscription_get_local_uri(sub, uri, sizeof(uri));
+ ast_sip_presence_xml_create_attr(pool, rlmi, "uri", uri);
+
+ snprintf(version_str, sizeof(version_str), "%u", sub->version++);
+ ast_sip_presence_xml_create_attr(pool, rlmi, "version", version_str);
+ ast_sip_presence_xml_create_attr(pool, rlmi, "fullState", full_state ? "true" : "false");
+
+ name = ast_sip_presence_xml_create_node(pool, rlmi, "name");
+ pj_strdup2(pool, &name->content, ast_sip_subscription_get_resource_name(sub));
+
+ for (i = 0; i < AST_VECTOR_SIZE(body_parts); ++i) {
+ const struct body_part *part = AST_VECTOR_GET(body_parts, i);
+
+ add_rlmi_resource(pool, rlmi, part->cid, part->resource, part->uri, part->state);
+ }
+
+ rlmi_part = pjsip_multipart_create_part(pool);
+
+ rlmi_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ pj_strdup(pool, &rlmi_part->body->content_type.type, &rlmi_type);
+ pj_strdup(pool, &rlmi_part->body->content_type.subtype, &rlmi_subtype);
+ pj_list_init(&rlmi_part->body->content_type.param);
+
+ rlmi_part->body->data = pj_xml_clone(pool, rlmi);
+ rlmi_part->body->clone_data = rlmi_clone_data;
+ rlmi_part->body->print_body = rlmi_print_body;
+
+ cid = generate_content_id_hdr(pool, sub);
+ pj_list_insert_before(&rlmi_part->hdr, cid);
+
+ return rlmi_part;
+}
+
+static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
+ unsigned int force_full_state);
+
+/*!
+ * \brief Destroy a list of body parts
+ *
+ * \param parts The container of parts to destroy
+ */
+static void free_body_parts(struct body_part_list *parts)
{
- struct ast_sip_subscription *sub;
- pjsip_dialog *dlg;
- struct subscription_persistence *persistence;
+ int i;
- sub = allocate_subscription(handler, endpoint, resource, AST_SIP_NOTIFIER);
- if (!sub) {
- return NULL;
+ for (i = 0; i < AST_VECTOR_SIZE(parts); ++i) {
+ struct body_part *part = AST_VECTOR_GET(parts, i);
+ ast_free(part);
}
- sub->body_generator = generator;
- dlg = ast_sip_create_dialog_uas(endpoint, rdata);
- if (!dlg) {
- ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
- ao2_ref(sub, -1);
+ AST_VECTOR_FREE(parts);
+}
+
+/*!
+ * \brief Allocate and initialize a body part structure
+ *
+ * \param pool PJLIB allocation pool
+ * \param sub Subscription representing a subscribed resource
+ */
+static struct body_part *allocate_body_part(pj_pool_t *pool, const struct ast_sip_subscription *sub)
+{
+ struct body_part *bp;
+
+ bp = ast_calloc(1, sizeof(*bp));
+ if (!bp) {
return NULL;
}
- persistence = ast_sip_mod_data_get(rdata->endpt_info.mod_data,
- pubsub_module.id, MOD_DATA_PERSISTENCE);
- if (persistence) {
- /* Update the created dialog with the persisted information */
- pjsip_ua_unregister_dlg(pjsip_ua_instance(), dlg);
- pj_strdup2(dlg->pool, &dlg->local.info->tag, persistence->tag);
- dlg->local.tag_hval = pj_hash_calc_tolower(0, NULL, &dlg->local.info->tag);
- pjsip_ua_register_dlg(pjsip_ua_instance(), dlg);
- dlg->local.cseq = persistence->cseq;
- dlg->remote.cseq = persistence->cseq;
+ bp->cid = generate_content_id_hdr(pool, sub);
+ bp->resource = sub->resource;
+ bp->state = sub->subscription_state;
+ bp->uri = sub->uri;
+
+ return bp;
+}
+
+/*!
+ * \brief Create a multipart body part for a subscribed resource
+ *
+ * \param pool PJLIB allocation pool
+ * \param sub The subscription representing a subscribed resource
+ * \param parts A vector of parts to append the created part to.
+ * \param use_full_state Unused locally, but may be passed to other functions
+ */
+static void build_body_part(pj_pool_t *pool, struct ast_sip_subscription *sub,
+ struct body_part_list *parts, unsigned int use_full_state)
+{
+ struct body_part *bp;
+ pjsip_msg_body *body;
+
+ bp = allocate_body_part(pool, sub);
+ if (!bp) {
+ return;
}
- pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub->reality.real.evsub);
- subscription_setup_dialog(sub, dlg);
+ body = generate_notify_body(pool, sub, use_full_state);
+ if (!body) {
+ /* Partial state was requested and the resource has not changed state */
+ ast_free(bp);
+ return;
+ }
- ast_sip_mod_data_set(dlg->pool, dlg->mod_data, pubsub_module.id, MOD_DATA_MSG,
- pjsip_msg_clone(dlg->pool, rdata->msg_info.msg));
+ bp->part = pjsip_multipart_create_part(pool);
+ bp->part->body = body;
+ pj_list_insert_before(&bp->part->hdr, bp->cid);
- add_subscription(sub);
- return sub;
+ AST_VECTOR_APPEND(parts, bp);
}
-void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header)
+/*!
+ * \brief Create and initialize the PJSIP multipart body structure for a resource list subscription
+ *
+ * \param pool
+ * \return The multipart message body
+ */
+static pjsip_msg_body *create_multipart_body(pj_pool_t *pool)
{
- pjsip_dialog *dlg = sip_subscription_get_dlg(sub);
- pjsip_msg *msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
- pj_str_t name;
+ pjsip_media_type media_type;
+ pjsip_param *media_type_param;
+ char boundary[6];
+ pj_str_t pj_boundary;
- pj_cstr(&name, header);
+ pjsip_media_type_init2(&media_type, "multipart", "related");
- return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
+ media_type_param = pj_pool_alloc(pool, sizeof(*media_type_param));
+ pj_list_init(media_type_param);
+
+ pj_strdup2(pool, &media_type_param->name, "type");
+ pj_strdup2(pool, &media_type_param->value, "\"application/rlmi+xml\"");
+
+ pj_list_insert_before(&media_type.param, media_type_param);
+
+ pj_cstr(&pj_boundary, ast_generate_random_string(boundary, sizeof(boundary)));
+ return pjsip_multipart_create(pool, &media_type, &pj_boundary);
}
-struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
- struct ast_sip_endpoint *endpoint, const char *resource)
+/*!
+ * \brief Create a resource list body for NOTIFY requests
+ *
+ * Resource list bodies are multipart/related bodies. The first part of the multipart body
+ * is an RLMI body that describes the rest of the parts to come. The other parts of the body
+ * convey state of individual subscribed resources.
+ *
+ * \param pool PJLIB allocation pool
+ * \param sub Subscription details from which to generate body
+ * \param force_full_state If true, ignore resource list settings and send a full state notification
+ * \return The generated multipart/related body
+ */
+static pjsip_msg_body *generate_list_body(pj_pool_t *pool, struct ast_sip_subscription *sub,
+ unsigned int force_full_state)
{
- struct ast_sip_subscription *sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor);
- pjsip_dialog *dlg;
- struct ast_sip_contact *contact;
- pj_str_t event;
- pjsip_tx_data *tdata;
- pjsip_evsub *evsub;
+ int i;
+ pjsip_multipart_part *rlmi_part;
+ pjsip_msg_body *multipart;
+ struct body_part_list body_parts;
+ unsigned int use_full_state = force_full_state ? 1 : sub->full_state;
- sub = allocate_subscription(handler, endpoint, resource, AST_SIP_SUBSCRIBER);
- if (!sub) {
+ if (AST_VECTOR_INIT(&body_parts, AST_VECTOR_SIZE(&sub->children))) {
return NULL;
}
- contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
- if (!contact || ast_strlen_zero(contact->uri)) {
- ast_log(LOG_WARNING, "No contacts configured for endpoint %s. Unable to create SIP subsription\n",
- ast_sorcery_object_get_id(endpoint));
- ao2_ref(sub, -1);
- ao2_cleanup(contact);
- return NULL;
+ for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+ build_body_part(pool, AST_VECTOR_GET(&sub->children, i), &body_parts, use_full_state);
}
- dlg = ast_sip_create_dialog_uac(endpoint, contact->uri, NULL);
- ao2_cleanup(contact);
- if (!dlg) {
- ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n");
- ao2_ref(sub, -1);
+ /* This can happen if issuing partial state and no children of the list have changed state */
+ if (AST_VECTOR_SIZE(&body_parts) == 0) {
return NULL;
}
- pj_cstr(&event, handler->event_name);
- pjsip_evsub_create_uac(dlg, &pubsub_cb, &event, 0, &sub->reality.real.evsub);
- subscription_setup_dialog(sub, dlg);
+ multipart = create_multipart_body(pool);
- add_subscription(sub);
+ rlmi_part = build_rlmi_body(pool, sub, &body_parts, use_full_state);
+ if (!rlmi_part) {
+ return NULL;
+ }
+ pjsip_multipart_add_part(pool, multipart, rlmi_part);
- evsub = sip_subscription_get_evsub(sub);
+ for (i = 0; i < AST_VECTOR_SIZE(&body_parts); ++i) {
+ pjsip_multipart_add_part(pool, multipart, AST_VECTOR_GET(&body_parts, i)->part);
+ }
- if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
- pjsip_evsub_send_request(evsub, tdata);
+ free_body_parts(&body_parts);
+ return multipart;
+}
+
+/*!
+ * \brief Create the body for a NOTIFY request.
+ *
+ * \param pool The pool used for allocations
+ * \param root The root of the subscription tree
+ * \param force_full_state If true, ignore resource list settings and send a full state notification
+ */
+static pjsip_msg_body *generate_notify_body(pj_pool_t *pool, struct ast_sip_subscription *root,
+ unsigned int force_full_state)
+{
+ pjsip_msg_body *body;
+
+ if (AST_VECTOR_SIZE(&root->children) == 0) {
+ if (force_full_state || root->body_changed) {
+ /* Not a list. We've already generated the body and saved it on the subscription.
+ * Use that directly.
+ */
+ pj_str_t type;
+ pj_str_t subtype;
+ pj_str_t text;
+
+ pj_cstr(&type, ast_sip_subscription_get_body_type(root));
+ pj_cstr(&subtype, ast_sip_subscription_get_body_subtype(root));
+ pj_cstr(&text, ast_str_buffer(root->body_text));
+
+ body = pjsip_msg_body_create(pool, &type, &subtype, &text);
+ root->body_changed = 0;
+ } else {
+ body = NULL;
+ }
} else {
- /* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
- * being called and terminating the subscription. Therefore, we don't
- * need to decrease the reference count of sub here.
- */
- pjsip_evsub_terminate(evsub, PJ_TRUE);
- return NULL;
+ body = generate_list_body(pool, root, force_full_state);
}
- return sub;
+ return body;
}
-struct ast_sip_endpoint *ast_sip_subscription_get_endpoint(struct ast_sip_subscription *sub)
+/*!
+ * \brief Shortcut method to create a Require: eventlist header
+ */
+static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool)
{
- ast_assert(sub->endpoint != NULL);
- ao2_ref(sub->endpoint, +1);
- return sub->endpoint;
-}
+ pjsip_require_hdr *require;
-struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_subscription *sub)
-{
- ast_assert(sub->serializer != NULL);
- return sub->serializer;
+ require = pjsip_require_hdr_create(pool);
+ pj_strdup2(pool, &require->values[0], "eventlist");
+ require->count = 1;
+
+ return require;
}
-static int sip_subscription_send_request(struct ast_sip_subscription *sub, pjsip_tx_data *tdata)
+/*!
+ * \brief Send a NOTIFY request to a subscriber
+ *
+ * \param sub_tree The subscription tree representing the subscription
+ * \param force_full_state If true, ignore resource list settings and send full resource list state.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int force_full_state)
{
- struct ast_sip_endpoint *endpoint = ast_sip_subscription_get_endpoint(sub);
- int res;
+ pjsip_evsub *evsub = sub_tree->evsub;
+ pjsip_tx_data *tdata;
- ao2_ref(sub, +1);
- res = pjsip_evsub_send_request(sip_subscription_get_evsub(sub),
- tdata) == PJ_SUCCESS ? 0 : -1;
+ if (pjsip_evsub_notify(evsub, sub_tree->root->subscription_state,
+ NULL, NULL, &tdata) != PJ_SUCCESS) {
+ return -1;
+ }
- subscription_persistence_update(sub, NULL);
+ tdata->msg->body = generate_notify_body(tdata->pool, sub_tree->root, force_full_state);
+ if (!tdata->msg->body) {
+ pjsip_tx_data_dec_ref(tdata);
+ return -1;
+ }
- ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
- "StateText: %s\r\n"
- "Endpoint: %s\r\n",
- pjsip_evsub_get_state_name(sip_subscription_get_evsub(sub)),
- ast_sorcery_object_get_id(endpoint));
- ao2_cleanup(sub);
- ao2_cleanup(endpoint);
+ if (sub_tree->is_list) {
+ pjsip_require_hdr *require = create_require_eventlist(tdata->pool);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require);
+ }
- return res;
+ if (sip_subscription_send_request(sub_tree, tdata)) {
+ return -1;
+ }
+
+ sub_tree->send_scheduled_notify = 0;
+
+ return 0;
}
-int ast_sip_subscription_notify(struct ast_sip_subscription *sub, void *notify_data,
- int terminate)
+static int serialized_send_notify(void *userdata)
{
- struct ast_sip_body body = {
- .type = ast_sip_subscription_get_body_type(sub),
- .subtype = ast_sip_subscription_get_body_subtype(sub),
- };
- struct ast_str *body_text = ast_str_create(64);
- pjsip_evsub *evsub = sip_subscription_get_evsub(sub);
- pjsip_tx_data *tdata;
- pjsip_evsub_state state;
+ struct sip_subscription_tree *sub_tree = userdata;
- if (!body_text) {
- return -1;
+ /* It's possible that between when the notification was scheduled
+ * and now, that a new SUBSCRIBE arrived, requiring full state to be
+ * sent out in an immediate NOTIFY. If that has happened, we need to
+ * bail out here instead of sending the batched NOTIFY.
+ */
+ if (!sub_tree->send_scheduled_notify) {
+ ao2_cleanup(sub_tree);
+ return 0;
}
- if (ast_sip_pubsub_generate_body_content(body.type, body.subtype, notify_data, &body_text)) {
- ast_free(body_text);
- return -1;
- }
+ send_notify(sub_tree, 0);
+ ao2_cleanup(sub_tree);
+ return 0;
+}
- body.body_text = ast_str_buffer(body_text);
+static int sched_cb(const void *data)
+{
+ struct sip_subscription_tree *sub_tree = (struct sip_subscription_tree *) data;
- if (terminate) {
- state = PJSIP_EVSUB_STATE_TERMINATED;
- } else {
- state = pjsip_evsub_get_state(evsub) <= PJSIP_EVSUB_STATE_ACTIVE ?
- PJSIP_EVSUB_STATE_ACTIVE : PJSIP_EVSUB_STATE_TERMINATED;
+ /* We don't need to bump the refcount of sub_tree since we bumped it when scheduling this task */
+ ast_sip_push_task(sub_tree->serializer, serialized_send_notify, sub_tree);
+ return 0;
+}
+
+static int schedule_notification(struct sip_subscription_tree *sub_tree)
+{
+ /* There's already a notification scheduled */
+ if (sub_tree->notify_sched_id > -1) {
+ return 0;
}
- if (pjsip_evsub_notify(evsub, state, NULL, NULL, &tdata) != PJ_SUCCESS) {
- ast_free(body_text);
+ sub_tree->notify_sched_id = ast_sched_add(sched, sub_tree->notification_batch_interval, sched_cb, ao2_bump(sub_tree));
+ if (sub_tree->notify_sched_id < 0) {
return -1;
}
- if (ast_sip_add_body(tdata, &body)) {
- ast_free(body_text);
- pjsip_tx_data_dec_ref(tdata);
+
+ sub_tree->send_scheduled_notify = 1;
+ return 0;
+}
+
+int ast_sip_subscription_notify(struct ast_sip_subscription *sub, void *notify_data,
+ int terminate)
+{
+ if (ast_sip_pubsub_generate_body_content(ast_sip_subscription_get_body_type(sub),
+ ast_sip_subscription_get_body_subtype(sub), notify_data, &sub->body_text)) {
return -1;
}
- if (sip_subscription_send_request(sub, tdata)) {
- ast_free(body_text);
- pjsip_tx_data_dec_ref(tdata);
- return -1;
+
+ sub->body_changed = 1;
+ if (terminate) {
+ sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
}
- return 0;
+ if (sub->tree->notification_batch_interval) {
+ return schedule_notification(sub->tree);
+ } else {
+ return send_notify(sub->tree, 0);
+ }
}
void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
{
- pjsip_dialog *dlg = sip_subscription_get_dlg(sub);
- ast_copy_pj_str(buf, &dlg->local.info_str, size);
+ pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, sub->uri, buf, size);
}
void ast_sip_subscription_get_remote_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
{
- pjsip_dialog *dlg = sip_subscription_get_dlg(sub);
+ pjsip_dialog *dlg = sub->tree->dlg;
ast_copy_pj_str(buf, &dlg->remote.info_str, size);
}
@@ -1069,14 +2052,22 @@ const char *ast_sip_subscription_get_resource_name(struct ast_sip_subscription *
return sub->resource;
}
-static int sip_subscription_accept(struct ast_sip_subscription *sub, pjsip_rx_data *rdata, int response)
+static int sip_subscription_accept(struct sip_subscription_tree *sub_tree, pjsip_rx_data *rdata, int response)
{
+ pjsip_hdr res_hdr;
+
/* If this is a persistence recreation the subscription has already been accepted */
if (ast_sip_mod_data_get(rdata->endpt_info.mod_data, pubsub_module.id, MOD_DATA_PERSISTENCE)) {
return 0;
}
- return pjsip_evsub_accept(sip_subscription_get_evsub(sub), rdata, response, NULL) == PJ_SUCCESS ? 0 : -1;
+ pj_list_init(&res_hdr);
+ if (sub_tree->is_list) {
+ /* If subscribing to a list, our response has to have a Require: eventlist header in it */
+ pj_list_insert_before(&res_hdr, create_require_eventlist(rdata->tp_info.pool));
+ }
+
+ return pjsip_evsub_accept(sub_tree->evsub, rdata, response, &res_hdr) == PJ_SUCCESS ? 0 : -1;
}
static void subscription_datastore_destroy(void *obj)
@@ -1350,18 +2341,53 @@ static struct ast_sip_pubsub_body_generator *find_body_generator(char accept[AST
return generator;
}
+static int generate_initial_notify(struct ast_sip_subscription *sub)
+{
+ void *notify_data;
+ int res;
+
+ if (AST_VECTOR_SIZE(&sub->children) > 0) {
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+ if (generate_initial_notify(AST_VECTOR_GET(&sub->children, i))) {
+ return -1;
+ }
+ }
+
+ return 0;
+ }
+
+ if (sub->handler->notifier->subscription_established(sub)) {
+ return -1;
+ }
+
+ notify_data = sub->handler->notifier->get_notify_data(sub);
+ if (!notify_data) {
+ return -1;
+ }
+
+ res = ast_sip_pubsub_generate_body_content(ast_sip_subscription_get_body_type(sub),
+ ast_sip_subscription_get_body_subtype(sub), notify_data, &sub->body_text);
+
+ ao2_cleanup(notify_data);
+
+ return res;
+}
+
static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
{
pjsip_expires_hdr *expires_header;
struct ast_sip_subscription_handler *handler;
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
- struct ast_sip_subscription *sub;
+ struct sip_subscription_tree *sub_tree;
struct ast_sip_pubsub_body_generator *generator;
char *resource;
pjsip_uri *request_uri;
pjsip_sip_uri *request_uri_sip;
size_t resource_size;
int resp;
+ struct resource_tree tree;
endpoint = ast_pjsip_rdata_get_endpoint(rdata);
ast_assert(endpoint != NULL);
@@ -1417,24 +2443,28 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
return PJ_TRUE;
}
- resp = handler->notifier->new_subscribe(endpoint, resource);
+ memset(&tree, 0, sizeof(tree));
+ resp = build_resource_tree(endpoint, handler, resource, &tree);
if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
+ resource_tree_destroy(&tree);
return PJ_TRUE;
}
- sub = notifier_create_subscription(handler, endpoint, rdata, resource, generator);
- if (!sub) {
+ sub_tree = create_subscription_tree(handler, endpoint, rdata, resource, generator, &tree);
+ if (!sub_tree) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
} else {
- sub->persistence = subscription_persistence_create(sub);
- subscription_persistence_update(sub, rdata);
- sip_subscription_accept(sub, rdata, resp);
- if (handler->notifier->notify_required(sub, AST_SIP_SUBSCRIPTION_NOTIFY_REASON_STARTED)) {
- pjsip_evsub_terminate(sip_subscription_get_evsub(sub), PJ_TRUE);
+ sub_tree->persistence = subscription_persistence_create(sub_tree);
+ subscription_persistence_update(sub_tree, rdata);
+ sip_subscription_accept(sub_tree, rdata, resp);
+ if (generate_initial_notify(sub_tree->root)) {
+ pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
}
+ send_notify(sub_tree, 1);
}
+ resource_tree_destroy(&tree);
return PJ_TRUE;
}
@@ -1918,40 +2948,63 @@ static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
{
- struct ast_sip_subscription *sub;
+ struct sip_subscription_tree *sub_tree;
+
if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
return;
}
- sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- if (!sub) {
+ sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ if (!sub_tree) {
return;
}
- if (sub->handler->subscription_shutdown) {
- sub->handler->subscription_shutdown(sub);
- }
+ ao2_cleanup(sub_tree);
+
pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
}
+static void set_state_terminated(struct ast_sip_subscription *sub)
+{
+ int i;
+
+ sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
+ for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+ set_state_terminated(AST_VECTOR_GET(&sub->children, i));
+ }
+}
+
static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
{
- struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- enum ast_sip_subscription_notify_reason reason;
+ struct sip_subscription_tree *sub_tree;
- if (!sub) {
+ sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ if (!sub_tree) {
return;
}
- if (pjsip_evsub_get_state(sip_subscription_get_evsub(sub)) == PJSIP_EVSUB_STATE_TERMINATED) {
- reason = AST_SIP_SUBSCRIPTION_NOTIFY_REASON_TERMINATED;
- } else {
- reason = AST_SIP_SUBSCRIPTION_NOTIFY_REASON_RENEWED;
+ /* If sending a NOTIFY to terminate a subscription, then pubsub_on_evsub_state()
+ * will be called when we send the NOTIFY, and that will result in dropping the
+ * refcount of sub_tree by one, and possibly destroying the sub_tree. We need to
+ * hold a reference to the sub_tree until this function returns so that we don't
+ * try to read from or write to freed memory by accident
+ */
+ ao2_ref(sub_tree, +1);
+
+ if (pjsip_evsub_get_state(evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ set_state_terminated(sub_tree->root);
}
- if (sub->handler->notifier->notify_required(sub, reason)) {
+
+ if (send_notify(sub_tree, 1)) {
*p_st_code = 500;
}
+
+ if (sub_tree->is_list) {
+ pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
+ }
+
+ ao2_ref(sub_tree, -1);
}
static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
@@ -1969,46 +3022,43 @@ static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p
static int serialized_pubsub_on_client_refresh(void *userdata)
{
- struct ast_sip_subscription *sub = userdata;
- pjsip_evsub *evsub;
+ struct sip_subscription_tree *sub_tree = userdata;
pjsip_tx_data *tdata;
- evsub = sip_subscription_get_evsub(sub);
-
- if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
- pjsip_evsub_send_request(evsub, tdata);
+ if (pjsip_evsub_initiate(sub_tree->evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
+ pjsip_evsub_send_request(sub_tree->evsub, tdata);
} else {
- pjsip_evsub_terminate(evsub, PJ_TRUE);
+ pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
return 0;
}
- ao2_cleanup(sub);
+ ao2_cleanup(sub_tree);
return 0;
}
static void pubsub_on_client_refresh(pjsip_evsub *evsub)
{
- struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- ao2_ref(sub, +1);
- ast_sip_push_task(sub->serializer, serialized_pubsub_on_client_refresh, sub);
+ ao2_ref(sub_tree, +1);
+ ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, sub_tree);
}
static int serialized_pubsub_on_server_timeout(void *userdata)
{
- struct ast_sip_subscription *sub = userdata;
+ struct sip_subscription_tree *sub_tree = userdata;
- sub->handler->notifier->notify_required(sub,
- AST_SIP_SUBSCRIPTION_NOTIFY_REASON_TERMINATED);
+ set_state_terminated(sub_tree->root);
+ send_notify(sub_tree, 1);
- ao2_cleanup(sub);
+ ao2_cleanup(sub_tree);
return 0;
}
static void pubsub_on_server_timeout(pjsip_evsub *evsub)
{
- struct ast_sip_subscription *sub = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+ struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
- if (!sub) {
+ if (!sub_tree) {
/* if a subscription has been terminated and the subscription
timeout/expires is less than the time it takes for all pending
transactions to end then the subscription timer will not have
@@ -2017,11 +3067,11 @@ static void pubsub_on_server_timeout(pjsip_evsub *evsub)
return;
}
- ao2_ref(sub, +1);
- ast_sip_push_task(sub->serializer, serialized_pubsub_on_server_timeout, sub);
+ ao2_ref(sub_tree, +1);
+ ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_server_timeout, sub_tree);
}
-static int ami_subscription_detail(struct ast_sip_subscription *sub,
+static int ami_subscription_detail(struct sip_subscription_tree *sub_tree,
struct ast_sip_ami *ami,
const char *event)
{
@@ -2032,21 +3082,21 @@ static int ami_subscription_detail(struct ast_sip_subscription *sub,
return -1;
}
- sip_subscription_to_ami(sub, &buf);
+ sip_subscription_to_ami(sub_tree, &buf);
astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
return 0;
}
-static int ami_subscription_detail_inbound(struct ast_sip_subscription *sub, void *arg)
+static int ami_subscription_detail_inbound(struct sip_subscription_tree *sub_tree, void *arg)
{
- return sub->role == AST_SIP_NOTIFIER ? ami_subscription_detail(
- sub, arg, "InboundSubscriptionDetail") : 0;
+ return sub_tree->role == AST_SIP_NOTIFIER ? ami_subscription_detail(
+ sub_tree, arg, "InboundSubscriptionDetail") : 0;
}
-static int ami_subscription_detail_outbound(struct ast_sip_subscription *sub, void *arg)
+static int ami_subscription_detail_outbound(struct sip_subscription_tree *sub_tree, void *arg)
{
- return sub->role == AST_SIP_SUBSCRIBER ? ami_subscription_detail(
- sub, arg, "OutboundSubscriptionDetail") : 0;
+ return sub_tree->role == AST_SIP_SUBSCRIBER ? ami_subscription_detail(
+ sub_tree, arg, "OutboundSubscriptionDetail") : 0;
}
static int ami_show_subscriptions_inbound(struct mansession *s, const struct message *m)
@@ -2087,6 +3137,53 @@ static int ami_show_subscriptions_outbound(struct mansession *s, const struct me
return 0;
}
+static int format_ami_resource_lists(void *obj, void *arg, int flags)
+{
+ struct resource_list *list = obj;
+ struct ast_sip_ami *ami = arg;
+ struct ast_str *buf;
+
+ buf = ast_sip_create_ami_event("ResourceListDetail", ami);
+ if (!buf) {
+ return CMP_STOP;
+ }
+
+ if (ast_sip_sorcery_object_to_ami(list, &buf)) {
+ ast_free(buf);
+ return CMP_STOP;
+ }
+ astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
+
+ ast_free(buf);
+ return 0;
+}
+
+static int ami_show_resource_lists(struct mansession *s, const struct message *m)
+{
+ struct ast_sip_ami ami = { .s = s, .m = m };
+ int num;
+ struct ao2_container *lists;
+
+ lists = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "resource_list",
+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+
+ if (!lists || !(num = ao2_container_count(lists))) {
+ astman_send_error(s, m, "No resource lists found\n");
+ return 0;
+ }
+
+ astman_send_listack(s, m, "A listing of resource lists follows, "
+ "presented as ResourceListDetail events", "start");
+
+ ao2_callback(lists, OBJ_NODATA, format_ami_resource_lists, &ami);
+
+ astman_append(s,
+ "Event: ResourceListDetailComplete\r\n"
+ "EventList: Complete\r\n"
+ "ListItems: %d\r\n\r\n", num);
+ return 0;
+}
+
#define AMI_SHOW_SUBSCRIPTIONS_INBOUND "PJSIPShowSubscriptionsInbound"
#define AMI_SHOW_SUBSCRIPTIONS_OUTBOUND "PJSIPShowSubscriptionsOutbound"
@@ -2134,6 +3231,705 @@ static int persistence_expires_struct2str(const void *obj, const intptr_t *args,
return (ast_asprintf(buf, "%ld", persistence->expires.tv_sec) < 0) ? -1 : 0;
}
+#define RESOURCE_LIST_INIT_SIZE 4
+
+static void resource_list_destructor(void *obj)
+{
+ struct resource_list *list = obj;
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
+ ast_free((char *) AST_VECTOR_GET(&list->items, i));
+ }
+
+ AST_VECTOR_FREE(&list->items);
+}
+
+static void *resource_list_alloc(const char *name)
+{
+ struct resource_list *list;
+
+ list = ast_sorcery_generic_alloc(sizeof(*list), resource_list_destructor);
+ if (!list) {
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&list->items, RESOURCE_LIST_INIT_SIZE)) {
+ ao2_cleanup(list);
+ return NULL;
+ }
+
+ return list;
+}
+
+static int item_in_vector(const struct resource_list *list, const char *item)
+{
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
+ if (!strcmp(item, AST_VECTOR_GET(&list->items, i))) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int list_item_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct resource_list *list = obj;
+ char *items = ast_strdupa(var->value);
+ char *item;
+
+ while ((item = strsep(&items, ","))) {
+ if (item_in_vector(list, item)) {
+ ast_log(LOG_WARNING, "Ignoring duplicated list item '%s'\n", item);
+ continue;
+ }
+ if (AST_VECTOR_APPEND(&list->items, ast_strdup(item))) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int list_item_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct resource_list *list = obj;
+ int i;
+ struct ast_str *str = ast_str_create(32);
+
+ for (i = 0; i < AST_VECTOR_SIZE(&list->items); ++i) {
+ ast_str_append(&str, 0, "%s,", AST_VECTOR_GET(&list->items, i));
+ }
+
+ /* Chop off trailing comma */
+ ast_str_truncate(str, -1);
+ *buf = ast_strdup(ast_str_buffer(str));
+ ast_free(str);
+ return 0;
+}
+
+static int resource_list_apply_handler(const struct ast_sorcery *sorcery, void *obj)
+{
+ struct resource_list *list = obj;
+
+ if (ast_strlen_zero(list->event)) {
+ ast_log(LOG_WARNING, "Resource list '%s' has no event set\n",
+ ast_sorcery_object_get_id(list));
+ return -1;
+ }
+
+ if (AST_VECTOR_SIZE(&list->items) == 0) {
+ ast_log(LOG_WARNING, "Resource list '%s' has no list items\n",
+ ast_sorcery_object_get_id(list));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int apply_list_configuration(struct ast_sorcery *sorcery)
+{
+ ast_sorcery_apply_default(sorcery, "resource_list", "config",
+ "pjsip.conf,criteria=type=resource_list");
+ if (ast_sorcery_object_register(sorcery, "resource_list", resource_list_alloc,
+ NULL, resource_list_apply_handler)) {
+ return -1;
+ }
+
+ ast_sorcery_object_field_register(sorcery, "resource_list", "type", "",
+ OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register(sorcery, "resource_list", "event", "",
+ OPT_CHAR_ARRAY_T, 1, CHARFLDSET(struct resource_list, event));
+ ast_sorcery_object_field_register(sorcery, "resource_list", "full_state", "no",
+ OPT_BOOL_T, 1, FLDSET(struct resource_list, full_state));
+ ast_sorcery_object_field_register(sorcery, "resource_list", "notification_batch_interval",
+ "0", OPT_UINT_T, 0, FLDSET(struct resource_list, notification_batch_interval));
+ ast_sorcery_object_field_register_custom(sorcery, "resource_list", "list_item",
+ "", list_item_handler, list_item_to_str, NULL, 0, 0);
+
+ ast_sorcery_reload_object(sorcery, "resource_list");
+
+ return 0;
+}
+
+#ifdef TEST_FRAMEWORK
+
+/*!
+ * \brief "bad" resources
+ *
+ * These are resources that the test handler will reject subscriptions to.
+ */
+const char *bad_resources[] = {
+ "coconut",
+ "cilantro",
+ "olive",
+ "cheese",
+};
+
+/*!
+ * \brief new_subscribe callback for unit tests
+ *
+ * Will give a 200 OK response to any resource except the "bad" ones.
+ */
+static int test_new_subscribe(struct ast_sip_endpoint *endpoint, const char *resource)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_LEN(bad_resources); ++i) {
+ if (!strcmp(resource, bad_resources[i])) {
+ return 400;
+ }
+ }
+
+ return 200;
+}
+
+/*!
+ * \brief Subscription notifier for unit tests.
+ *
+ * Since unit tests are only concerned with building a resource tree,
+ * only the new_subscribe callback needs to be defined.
+ */
+struct ast_sip_notifier test_notifier = {
+ .new_subscribe = test_new_subscribe,
+};
+
+/*!
+ * \brief Subscription handler for unit tests.
+ */
+struct ast_sip_subscription_handler test_handler = {
+ .event_name = "test",
+ .notifier = &test_notifier,
+};
+
+/*!
+ * \brief Set properties on an allocated resource list
+ *
+ * \param list The list to set details on.
+ * \param event The list's event.
+ * \param resources Array of resources to add to the list.
+ * \param num_resources Number of resources in the array.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+static int populate_list(struct resource_list *list, const char *event, const char **resources, size_t num_resources)
+{
+ int i;
+
+ ast_copy_string(list->event, event, sizeof(list->event));
+
+ for (i = 0; i < num_resources; ++i) {
+ if (AST_VECTOR_APPEND(&list->items, ast_strdup(resources[i]))) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/*!
+ * \brief RAII callback to destroy a resource list
+ */
+static void cleanup_resource_list(struct resource_list *list)
+{
+ if (!list) {
+ return;
+ }
+
+ ast_sorcery_delete(ast_sip_get_sorcery(), list);
+ ao2_cleanup(list);
+}
+
+/*!
+ * \brief allocate a resource list, store it in sorcery, and set its details
+ *
+ * \param test The unit test. Used for logging status messages.
+ * \param list_name The name of the list to create.
+ * \param event The event the list services
+ * \param resources Array of resources to apply to the list
+ * \param num_resources The number of resources in the array
+ * \retval NULL Failed to allocate or populate list
+ * \retval non-NULL The created list
+ */
+static struct resource_list *create_resource_list(struct ast_test *test,
+ const char *list_name, const char *event, const char **resources, size_t num_resources)
+{
+ struct resource_list *list;
+
+ list = ast_sorcery_alloc(ast_sip_get_sorcery(), "resource_list", list_name);
+ if (!list) {
+ ast_test_status_update(test, "Could not allocate resource list in sorcery\n");
+ return NULL;
+ }
+
+ if (ast_sorcery_create(ast_sip_get_sorcery(), list)) {
+ ast_test_status_update(test, "Could not store the resource list in sorcery\n");
+ ao2_cleanup(list);
+ return NULL;
+ }
+
+ if (populate_list(list, event, resources, num_resources)) {
+ ast_test_status_update(test, "Could not add resources to the resource list\n");
+ cleanup_resource_list(list);
+ return NULL;
+ }
+
+ return list;
+}
+
+/*!
+ * \brief Check the integrity of a tree node against a set of resources.
+ *
+ * The tree node's resources must be in the same order as the resources in
+ * the supplied resources array. Because of this constraint, tests can misrepresent
+ * the size of the resources array as being smaller than it really is if resources
+ * at the end of the array should not be present in the tree node.
+ *
+ * \param test The unit test. Used for printing status messages.
+ * \param node The constructed tree node whose integrity is under question.
+ * \param resources Array of expected resource values
+ * \param num_resources The number of resources to check in the array.
+ */
+static int check_node(struct ast_test *test, struct tree_node *node,
+ const char **resources, size_t num_resources)
+{
+ int i;
+
+ if (AST_VECTOR_SIZE(&node->children) != num_resources) {
+ ast_test_status_update(test, "Unexpected number of resources in tree. Expected %d, got %d\n",
+ num_resources, AST_VECTOR_SIZE(&node->children));
+ return -1;
+ }
+
+ for (i = 0; i < num_resources; ++i) {
+ if (strcmp(resources[i], AST_VECTOR_GET(&node->children, i)->resource)) {
+ ast_test_status_update(test, "Mismatched resources. Expected '%s' but got '%s'\n",
+ resources[i], AST_VECTOR_GET(&node->children, i)->resource);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief RAII_VAR callback to destroy an allocated resource tree
+ */
+static void test_resource_tree_destroy(struct resource_tree *tree)
+{
+ resource_tree_destroy(tree);
+ ast_free(tree);
+}
+
+AST_TEST_DEFINE(resource_tree)
+{
+ RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources[] = {
+ "huey",
+ "dewey",
+ "louie",
+ };
+ int resp;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "resource_tree";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Basic resource tree integrity check";
+ info->description =
+ "Create a resource list and ensure that our attempt to build a tree works as expected.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
+ if (!list) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (check_node(test, tree->root, resources, ARRAY_LEN(resources))) {
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(complex_resource_tree)
+{
+ RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources_1[] = {
+ "huey",
+ "dewey",
+ "louie",
+ "dwarves",
+ };
+ const char *resources_2[] = {
+ "happy",
+ "grumpy",
+ "doc",
+ "bashful",
+ "dopey",
+ "sneezy",
+ "sleepy",
+ };
+ int resp;
+ struct tree_node *node;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "complex_resource_tree";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Complex resource tree integrity check";
+ info->description =
+ "Create a complex resource list and ensure that our attempt to build a tree works as expected.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
+ if (!list_1) {
+ return AST_TEST_FAIL;
+ }
+
+ list_2 = create_resource_list(test, "dwarves", "test", resources_2, ARRAY_LEN(resources_2));
+ if (!list_2) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ node = tree->root;
+ if (check_node(test, node, resources_1, ARRAY_LEN(resources_1))) {
+ return AST_TEST_FAIL;
+ }
+
+ /* The embedded list is at index 3 in the root node's children */
+ node = AST_VECTOR_GET(&node->children, 3);
+ if (check_node(test, node, resources_2, ARRAY_LEN(resources_2))) {
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(bad_resource)
+{
+ RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources[] = {
+ "huey",
+ "dewey",
+ "louie",
+ "coconut", /* A "bad" resource */
+ };
+ int resp;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bad_resource";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Ensure bad resources do not end up in the tree";
+ info->description =
+ "Create a resource list with a single bad resource. Ensure the bad resource does not end up in the tree.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list = create_resource_list(test, "foo", "test", resources, ARRAY_LEN(resources));
+ if (!list) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ /* We check against all but the final resource since we expect it not to be in the tree */
+ if (check_node(test, tree->root, resources, ARRAY_LEN(resources) - 1)) {
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+
+}
+
+AST_TEST_DEFINE(bad_branch)
+{
+ RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources_1[] = {
+ "huey",
+ "dewey",
+ "louie",
+ "gross",
+ };
+ /* This list has nothing but bad resources */
+ const char *resources_2[] = {
+ "coconut",
+ "cilantro",
+ "olive",
+ "cheese",
+ };
+ int resp;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bad_branch";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Ensure bad branches are pruned from the tree";
+ info->description =
+ "Create a resource list that makes a tree with an entire branch of bad resources.\n"
+ "Ensure the bad branch is pruned from the tree.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
+ if (!list_1) {
+ return AST_TEST_FAIL;
+ }
+ list_2 = create_resource_list(test, "gross", "test", resources_2, ARRAY_LEN(resources_2));
+ if (!list_2) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ /* We check against all but the final resource of the list since the entire branch should
+ * be pruned from the tree
+ */
+ if (check_node(test, tree->root, resources_1, ARRAY_LEN(resources_1) - 1)) {
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+
+}
+
+AST_TEST_DEFINE(duplicate_resource)
+{
+ RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources_1[] = {
+ "huey",
+ "ducks",
+ "dewey",
+ "louie",
+ };
+ const char *resources_2[] = {
+ "donald",
+ "daisy",
+ "scrooge",
+ "dewey",
+ "louie",
+ "huey",
+ };
+ int resp;
+ struct tree_node *node;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "duplicate_resource";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Ensure duplicated resources do not end up in the tree";
+ info->description =
+ "Create a resource list with a single duplicated resource. Ensure the duplicated resource does not end up in the tree.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list_1 = create_resource_list(test, "foo", "test", resources_1, ARRAY_LEN(resources_1));
+ if (!list_1) {
+ return AST_TEST_FAIL;
+ }
+
+ list_2 = create_resource_list(test, "ducks", "test", resources_2, ARRAY_LEN(resources_2));
+ if (!list_2) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ node = tree->root;
+ /* This node should have "huey" and "ducks". "dewey" and "louie" should not
+ * be present since they were found in the "ducks" list.
+ */
+ if (check_node(test, node, resources_1, ARRAY_LEN(resources_1) - 2)) {
+ return AST_TEST_FAIL;
+ }
+
+ /* This node should have "donald", "daisy", "scrooge", "dewey", and "louie".
+ * "huey" is not here since that was already encountered in the parent list
+ */
+ node = AST_VECTOR_GET(&node->children, 1);
+ if (check_node(test, node, resources_2, ARRAY_LEN(resources_2) - 1)) {
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(loop)
+{
+ RAII_VAR(struct resource_list *, list_1, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_list *, list_2, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources_1[] = {
+ "derp",
+ };
+ const char *resources_2[] = {
+ "herp",
+ };
+ int resp;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "loop";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Test that loops are properly detected.";
+ info->description =
+ "Create two resource lists that refer to each other. Ensure that attempting to build a tree\n"
+ "results in an empty tree.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list_1 = create_resource_list(test, "herp", "test", resources_1, ARRAY_LEN(resources_1));
+ if (!list_1) {
+ return AST_TEST_FAIL;
+ }
+ list_2 = create_resource_list(test, "derp", "test", resources_2, ARRAY_LEN(resources_2));
+ if (!list_2) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ resp = build_resource_tree(NULL, &test_handler, "herp", tree);
+ if (resp == 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(bad_event)
+{
+ RAII_VAR(struct resource_list *, list, NULL, cleanup_resource_list);
+ RAII_VAR(struct resource_tree *, tree, NULL, test_resource_tree_destroy);
+ const char *resources[] = {
+ "huey",
+ "dewey",
+ "louie",
+ };
+ int resp;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "bad_event";
+ info->category = "/res/res_pjsip_pubsub/";
+ info->summary = "Ensure that list with wrong event specified is not retrieved";
+ info->description =
+ "Create a simple resource list for event 'tsetse'. Ensure that trying to retrieve the list for event 'test' fails.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ list = create_resource_list(test, "foo", "tsetse", resources, ARRAY_LEN(resources));
+ if (!list) {
+ return AST_TEST_FAIL;
+ }
+
+ tree = ast_calloc(1, sizeof(*tree));
+ /* Since the test_handler is for event "test", this should not build a list, but
+ * instead result in a single resource being created, called "foo"
+ */
+ resp = build_resource_tree(NULL, &test_handler, "foo", tree);
+ if (resp != 200) {
+ ast_test_status_update(test, "Unexpected response %d when building resource tree\n", resp);
+ return AST_TEST_FAIL;
+ }
+
+ if (!tree->root) {
+ ast_test_status_update(test, "Resource tree has no root\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (strcmp(tree->root->resource, "foo")) {
+ ast_test_status_update(test, "Unexpected resource %s found in tree\n", tree->root->resource);
+ return AST_TEST_FAIL;
+ }
+
+ return AST_TEST_PASS;
+}
+
+#endif
+
static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_publication_resource *resource = obj;
@@ -2224,6 +4020,11 @@ static int load_module(void)
ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
persistence_expires_str2struct, persistence_expires_struct2str, NULL, 0, 0);
+ if (apply_list_configuration(sorcery)) {
+ ast_sip_unregister_service(&pubsub_module);
+ ast_sched_context_destroy(sched);
+ }
+
ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
if (ast_sorcery_object_register(sorcery, "inbound-publication", publication_resource_alloc,
NULL, NULL)) {
@@ -2248,6 +4049,16 @@ static int load_module(void)
ami_show_subscriptions_inbound);
ast_manager_register_xml(AMI_SHOW_SUBSCRIPTIONS_OUTBOUND, EVENT_FLAG_SYSTEM,
ami_show_subscriptions_outbound);
+ ast_manager_register_xml("PJSIPShowResourceLists", EVENT_FLAG_SYSTEM,
+ ami_show_resource_lists);
+
+ AST_TEST_REGISTER(resource_tree);
+ AST_TEST_REGISTER(complex_resource_tree);
+ AST_TEST_REGISTER(bad_resource);
+ AST_TEST_REGISTER(bad_branch);
+ AST_TEST_REGISTER(duplicate_resource);
+ AST_TEST_REGISTER(loop);
+ AST_TEST_REGISTER(bad_event);
return AST_MODULE_LOAD_SUCCESS;
}
@@ -2256,11 +4067,20 @@ static int unload_module(void)
{
ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_OUTBOUND);
ast_manager_unregister(AMI_SHOW_SUBSCRIPTIONS_INBOUND);
+ ast_manager_unregister("PJSIPShowResourceLists");
if (sched) {
ast_sched_context_destroy(sched);
}
+ AST_TEST_UNREGISTER(resource_tree);
+ AST_TEST_UNREGISTER(complex_resource_tree);
+ AST_TEST_UNREGISTER(bad_resource);
+ AST_TEST_UNREGISTER(bad_branch);
+ AST_TEST_UNREGISTER(duplicate_resource);
+ AST_TEST_UNREGISTER(loop);
+ AST_TEST_UNREGISTER(bad_event);
+
return 0;
}
diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c
index 4e0587d35..38e036a7b 100644
--- a/res/res_pjsip_xpidf_body_generator.c
+++ b/res/res_pjsip_xpidf_body_generator.c
@@ -98,7 +98,6 @@ static int xpidf_generate_body_content(void *body, void *data)
}
#define MAX_STRING_GROWTHS 5
-#define XML_PROLOG 39
static void xpidf_to_string(void *body, struct ast_str **str)
{
@@ -108,13 +107,13 @@ static void xpidf_to_string(void *body, struct ast_str **str)
do {
size = pjxpidf_print(pres, ast_str_buffer(*str), ast_str_size(*str));
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_str_make_space(str, ast_str_size(*str) * 2);
++growths;
}
- } while (size == XML_PROLOG && growths < MAX_STRING_GROWTHS);
+ } while (size == AST_PJSIP_XML_PROLOG_LEN && growths < MAX_STRING_GROWTHS);
- if (size == XML_PROLOG) {
+ if (size == AST_PJSIP_XML_PROLOG_LEN) {
ast_log(LOG_WARNING, "XPIDF body text too large\n");
return;
}