summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Colp <jcolp@digium.com>2014-08-07 14:37:26 +0000
committerJoshua Colp <jcolp@digium.com>2014-08-07 14:37:26 +0000
commita8829490b6b9891b352e39f3846de1f274ca632c (patch)
tree840553d2ce1725774ed7fdce0e9a07f9c8c4288d
parentece78c699193b5d9665452c8bd6f594370cccba8 (diff)
res_pjsip_publish_asterisk: Add support for exchanging device and mailbox state using SIP.
This module uses the inbound and outbound PUBLISH support to exchange device and mailbox state between Asterisk instances. Each instance is configured to publish to the other and requires no intermediary server. The functionality provided is similar to the XMPP and Corosync support. Review: https://reviewboard.asterisk.org/r/3780/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@420315 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-rw-r--r--include/asterisk/res_pjsip_pubsub.h19
-rw-r--r--res/res_pjsip_publish_asterisk.c927
-rw-r--r--res/res_pjsip_pubsub.c163
-rw-r--r--res/res_pjsip_pubsub.exports.in2
4 files changed, 1096 insertions, 15 deletions
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index bd55a448f..8ad133471 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -58,9 +58,10 @@ struct ast_sip_publish_handler {
*
* \param endpoint The endpoint from whom the PUBLISH arrived.
* \param resource The resource whose state is being published.
+ * \param event_configuration The name of the event type configuration to use for this resource.
* \return Response code for the incoming PUBLISH
*/
- int (*new_publication)(struct ast_sip_endpoint *endpoint, const char *resource);
+ int (*new_publication)(struct ast_sip_endpoint *endpoint, const char *resource, const char *event_configuration);
/*!
* \brief Called when a publication has reached its expiration.
*/
@@ -99,6 +100,22 @@ struct ast_sip_publish_handler {
struct ast_sip_endpoint *ast_sip_publication_get_endpoint(struct ast_sip_publication *pub);
/*!
+ * \brief Given a publication, get the resource the publication is to
+ *
+ * \param pub The publication
+ * \return The resource
+ */
+const char *ast_sip_publication_get_resource(const struct ast_sip_publication *pub);
+
+/*!
+ * \brief Given a publication, get the configuration name for the event type in use
+ *
+ * \param pub The publication
+ * \return The configuration name
+ */
+const char *ast_sip_publication_get_event_configuration(const struct ast_sip_publication *pub);
+
+/*!
* \brief Register a publish handler
*
* \retval 0 Handler was registered successfully
diff --git a/res/res_pjsip_publish_asterisk.c b/res/res_pjsip_publish_asterisk.c
new file mode 100644
index 000000000..8d4d8510d
--- /dev/null
+++ b/res/res_pjsip_publish_asterisk.c
@@ -0,0 +1,927 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+ <depend>pjproject</depend>
+ <depend>res_pjsip</depend>
+ <depend>res_pjsip_outbound_publish</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <regex.h>
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_outbound_publish.h"
+#include "asterisk/res_pjsip_pubsub.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+ <configInfo name="res_pjsip_publish_asterisk" language="en_US">
+ <synopsis>SIP resource for inbound and outbound Asterisk event publications</synopsis>
+ <description><para>
+ <emphasis>Inbound and outbound Asterisk event publication</emphasis>
+ </para>
+ <para>This module allows <literal>res_pjsip</literal> to send and receive Asterisk event publications.</para>
+ </description>
+ <configFile name="pjsip.conf">
+ <configObject name="asterisk-publication">
+ <synopsis>The configuration for inbound Asterisk event publication</synopsis>
+ <description><para>
+ Publish is <emphasis>COMPLETELY</emphasis> separate from the rest of
+ <literal>pjsip.conf</literal>.
+ </para></description>
+ <configOption name="devicestate_publish">
+ <synopsis>Optional name of a publish item that can be used to publish a request for full device state information.</synopsis>
+ </configOption>
+ <configOption name="mailboxstate_publish">
+ <synopsis>Optional name of a publish item that can be used to publish a request for full mailbox state information.</synopsis>
+ </configOption>
+ <configOption name="device_state" default="no">
+ <synopsis>Whether we should permit incoming device state events.</synopsis>
+ </configOption>
+ <configOption name="device_state_filter">
+ <synopsis>Optional regular expression used to filter what devices we accept events for.</synopsis>
+ </configOption>
+ <configOption name="mailbox_state" default="no">
+ <synopsis>Whether we should permit incoming mailbox state events.</synopsis>
+ </configOption>
+ <configOption name="mailbox_state_filter">
+ <synopsis>Optional regular expression used to filter what mailboxes we accept events for.</synopsis>
+ </configOption>
+ <configOption name="type">
+ <synopsis>Must be of type 'asterisk-publication'.</synopsis>
+ </configOption>
+ </configObject>
+ </configFile>
+ </configInfo>
+ ***/
+
+/*! \brief Structure which contains Asterisk device state publisher state information */
+struct asterisk_devicestate_publisher_state {
+ /*! \brief The publish client to send PUBLISH messages on */
+ struct ast_sip_outbound_publish_client *client;
+ /*! \brief Device state subscription */
+ struct stasis_subscription *device_state_subscription;
+ /*! \brief Regex used for filtering outbound device state */
+ regex_t device_state_regex;
+ /*! \brief Device state should be filtered */
+ unsigned int device_state_filter;
+};
+
+/*! \brief Structure which contains Asterisk mailbox publisher state information */
+struct asterisk_mwi_publisher_state {
+ /*! \brief The publish client to send PUBLISH messages on */
+ struct ast_sip_outbound_publish_client *client;
+ /*! \brief Mailbox state subscription */
+ struct stasis_subscription *mailbox_state_subscription;
+ /*! \brief Regex used for filtering outbound mailbox state */
+ regex_t mailbox_state_regex;
+ /*! \brief Mailbox state should be filtered */
+ unsigned int mailbox_state_filter;
+};
+
+/*! \brief Structure which contains Asterisk publication information */
+struct asterisk_publication_config {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Stringfields */
+ AST_DECLARE_STRING_FIELDS(
+ /*! \brief Optional name of a device state publish item, used to request the remote side update us */
+ AST_STRING_FIELD(devicestate_publish);
+ /*! \brief Optional name of a mailbox state publish item, used to request the remote side update us */
+ AST_STRING_FIELD(mailboxstate_publish);
+ );
+ /*! \brief Accept inbound device state events */
+ unsigned int device_state;
+ /*! \brief Regex used for filtering inbound device state */
+ regex_t device_state_regex;
+ /*! \brief Device state should be filtered */
+ unsigned int device_state_filter;
+ /*! \brief Accept inbound mailbox state events */
+ unsigned int mailbox_state;
+ /*! \brief Regex used for filtering inbound mailbox state */
+ regex_t mailbox_state_regex;
+ /*! \brief Mailbox state should be filtered */
+ unsigned int mailbox_state_filter;
+};
+
+/*! \brief Destroy callback for Asterisk devicestate publisher state information from datastore */
+static void asterisk_devicestate_publisher_state_destroy(void *obj)
+{
+ struct asterisk_devicestate_publisher_state *publisher_state = obj;
+
+ ao2_cleanup(publisher_state->client);
+
+ if (publisher_state->device_state_filter) {
+ regfree(&publisher_state->device_state_regex);
+ }
+}
+
+/*! \brief Datastore for attaching devicestate publisher state information */
+static const struct ast_datastore_info asterisk_devicestate_publisher_state_datastore = {
+ .type = "asterisk-devicestate-publisher",
+ .destroy = asterisk_devicestate_publisher_state_destroy,
+};
+
+/*! \brief Destroy callback for Asterisk mwi publisher state information from datastore */
+static void asterisk_mwi_publisher_state_destroy(void *obj)
+{
+ struct asterisk_mwi_publisher_state *publisher_state = obj;
+
+ ao2_cleanup(publisher_state->client);
+
+ if (publisher_state->mailbox_state_filter) {
+ regfree(&publisher_state->mailbox_state_regex);
+ }
+}
+
+/*! \brief Datastore for attaching devicestate publisher state information */
+static const struct ast_datastore_info asterisk_mwi_publisher_state_datastore = {
+ .type = "asterisk-mwi-publisher",
+ .destroy = asterisk_mwi_publisher_state_destroy,
+};
+
+/*!
+ * \brief Callback function for device state events
+ * \param ast_event
+ * \param data void pointer to ast_client structure
+ * \return void
+ */
+static void asterisk_publisher_devstate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
+{
+ struct ast_datastore *datastore = data;
+ struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
+ struct ast_device_state_message *dev_state;
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!stasis_subscription_is_subscribed(sub) || ast_device_state_message_type() != stasis_message_type(msg)) {
+ return;
+ }
+
+ dev_state = stasis_message_data(msg);
+ if (!dev_state->eid || ast_eid_cmp(&ast_eid_default, dev_state->eid)) {
+ /* If the event is aggregate or didn't originate from this server, don't send it out. */
+ return;
+ }
+
+ if (publisher_state->device_state_filter && regexec(&publisher_state->device_state_regex, dev_state->device, 0, NULL, 0)) {
+ /* Outgoing device state has been filtered and the device name does not match */
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s, s: s, s: i, s:s }",
+ "type", "devicestate",
+ "device", dev_state->device,
+ "state", ast_devstate_str(dev_state->state),
+ "cachable", dev_state->cachable,
+ "eid", eid_str);
+ if (!json) {
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ return;
+ }
+ body.body_text = text;
+
+ ast_sip_publish_client_send(publisher_state->client, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+}
+
+/*!
+ * \brief Callback function for mailbox state events
+ * \param ast_event
+ * \param data void pointer to ast_client structure
+ * \return void
+ */
+static void asterisk_publisher_mwistate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
+{
+ struct ast_datastore *datastore = data;
+ struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
+ struct ast_mwi_state *mwi_state;
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!stasis_subscription_is_subscribed(sub) || ast_mwi_state_type() != stasis_message_type(msg)) {
+ return;
+ }
+
+ mwi_state = stasis_message_data(msg);
+ if (ast_eid_cmp(&ast_eid_default, &mwi_state->eid)) {
+ /* If the event is aggregate or didn't originate from this server, don't send it out. */
+ return;
+ }
+
+ if (publisher_state->mailbox_state_filter && regexec(&publisher_state->mailbox_state_regex, mwi_state->uniqueid, 0, NULL, 0)) {
+ /* Outgoing mailbox state has been filtered and the uniqueid does not match */
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s, s: i, s: i, s:s }",
+ "type", "mailboxstate",
+ "uniqueid", mwi_state->uniqueid,
+ "old", mwi_state->old_msgs,
+ "new", mwi_state->new_msgs,
+ "eid", eid_str);
+ if (!json) {
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ return;
+ }
+ body.body_text = text;
+
+ ast_sip_publish_client_send(publisher_state->client, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+}
+
+static int cached_devstate_cb(void *obj, void *arg, int flags)
+{
+ struct stasis_message *msg = obj;
+ struct ast_datastore *datastore = arg;
+ struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
+
+ asterisk_publisher_devstate_cb(arg, publisher_state->device_state_subscription, msg);
+
+ return 0;
+}
+
+static int cached_mwistate_cb(void *obj, void *arg, int flags)
+{
+ struct stasis_message *msg = obj;
+ struct ast_datastore *datastore = arg;
+ struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
+
+ asterisk_publisher_mwistate_cb(arg, publisher_state->mailbox_state_subscription, msg);
+
+ return 0;
+}
+
+static int build_regex(regex_t *regex, const char *text)
+{
+ int res;
+
+ if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
+ size_t len = regerror(res, regex, NULL, 0);
+ char buf[len];
+ regerror(res, regex, buf, len);
+ ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int asterisk_start_devicestate_publishing(struct ast_sip_outbound_publish *configuration,
+ struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ struct asterisk_devicestate_publisher_state *publisher_state;
+ const char *value;
+ struct ao2_container *cached;
+
+ datastore = ast_sip_publish_client_alloc_datastore(&asterisk_devicestate_publisher_state_datastore,
+ "asterisk-devicestate-publisher");
+ if (!datastore) {
+ return -1;
+ }
+
+ publisher_state = ast_calloc(1, sizeof(struct asterisk_devicestate_publisher_state));
+ if (!publisher_state) {
+ return -1;
+ }
+ datastore->data = publisher_state;
+
+ value = ast_sorcery_object_get_extended(configuration, "device_state_filter");
+ if (!ast_strlen_zero(value)) {
+ if (build_regex(&publisher_state->device_state_regex, value)) {
+ return -1;
+ }
+ publisher_state->device_state_filter = 1;
+ }
+
+ publisher_state->client = ao2_bump(client);
+
+ if (ast_sip_publish_client_add_datastore(client, datastore)) {
+ return -1;
+ }
+
+ publisher_state->device_state_subscription = stasis_subscribe(ast_device_state_topic_all(),
+ asterisk_publisher_devstate_cb, ao2_bump(datastore));
+ if (!publisher_state->device_state_subscription) {
+ ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
+ ao2_ref(datastore, -1);
+ return -1;
+ }
+
+ cached = stasis_cache_dump(ast_device_state_cache(), NULL);
+ ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
+ ao2_ref(cached, -1);
+
+ return 0;
+}
+
+static int asterisk_stop_devicestate_publishing(struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher"),
+ ao2_cleanup);
+ struct asterisk_devicestate_publisher_state *publisher_state;
+
+ if (!datastore) {
+ return 0;
+ }
+
+ publisher_state = datastore->data;
+ if (publisher_state->device_state_subscription) {
+ stasis_unsubscribe_and_join(publisher_state->device_state_subscription);
+ ao2_ref(datastore, -1);
+ }
+
+ ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
+
+ return 0;
+}
+
+struct ast_sip_event_publisher_handler asterisk_devicestate_publisher_handler = {
+ .event_name = "asterisk-devicestate",
+ .start_publishing = asterisk_start_devicestate_publishing,
+ .stop_publishing = asterisk_stop_devicestate_publishing,
+};
+
+static int asterisk_start_mwi_publishing(struct ast_sip_outbound_publish *configuration,
+ struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
+ struct asterisk_mwi_publisher_state *publisher_state;
+ const char *value;
+ struct ao2_container *cached;
+
+ datastore = ast_sip_publish_client_alloc_datastore(&asterisk_mwi_publisher_state_datastore, "asterisk-mwi-publisher");
+ if (!datastore) {
+ return -1;
+ }
+
+ publisher_state = ast_calloc(1, sizeof(struct asterisk_mwi_publisher_state));
+ if (!publisher_state) {
+ return -1;
+ }
+ datastore->data = publisher_state;
+
+ value = ast_sorcery_object_get_extended(configuration, "mailbox_state_filter");
+ if (!ast_strlen_zero(value)) {
+ if (build_regex(&publisher_state->mailbox_state_regex, value)) {
+ return -1;
+ }
+ publisher_state->mailbox_state_filter = 1;
+ }
+
+ publisher_state->client = ao2_bump(client);
+
+ if (ast_sip_publish_client_add_datastore(client, datastore)) {
+ return -1;
+ }
+
+ publisher_state->mailbox_state_subscription = stasis_subscribe(ast_mwi_topic_all(),
+ asterisk_publisher_mwistate_cb, ao2_bump(datastore));
+ if (!publisher_state->mailbox_state_subscription) {
+ ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
+ ao2_ref(datastore, -1);
+ return -1;
+ }
+
+ cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
+ ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
+ ao2_ref(cached, -1);
+
+ return 0;
+}
+
+static int asterisk_stop_mwi_publishing(struct ast_sip_outbound_publish_client *client)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher"),
+ ao2_cleanup);
+ struct asterisk_mwi_publisher_state *publisher_state;
+
+ if (!datastore) {
+ return 0;
+ }
+
+ publisher_state = datastore->data;
+ if (publisher_state->mailbox_state_subscription) {
+ stasis_unsubscribe_and_join(publisher_state->mailbox_state_subscription);
+ ao2_ref(datastore, -1);
+ }
+
+ ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
+
+ return 0;
+}
+
+struct ast_sip_event_publisher_handler asterisk_mwi_publisher_handler = {
+ .event_name = "asterisk-mwi",
+ .start_publishing = asterisk_start_mwi_publishing,
+ .stop_publishing = asterisk_stop_mwi_publishing,
+};
+
+static int asterisk_publication_new(struct ast_sip_endpoint *endpoint, const char *resource, const char *event_configuration)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ event_configuration), ao2_cleanup);
+
+ /* If no inbound Asterisk publication configuration exists reject the PUBLISH */
+ if (!config) {
+ return 404;
+ }
+
+ return 200;
+}
+
+static int asterisk_publication_devicestate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
+ struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ const char *device = ast_json_string_get(ast_json_object_get(json, "device"));
+ const char *state = ast_json_string_get(ast_json_object_get(json, "state"));
+ int cachable = ast_json_integer_get(ast_json_object_get(json, "cachable"));
+
+ if (!config->device_state) {
+ ast_debug(2, "Received device state event for resource '%s' but it is not configured to accept them\n",
+ ast_sorcery_object_get_id(config));
+ return 0;
+ }
+
+ if (ast_strlen_zero(device) || ast_strlen_zero(state)) {
+ ast_debug(1, "Received incomplete device state event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ if (config->device_state_filter && regexec(&config->device_state_regex, device, 0, NULL, 0)) {
+ ast_debug(2, "Received device state on resource '%s' for device '%s' but it has been filtered out\n",
+ ast_sorcery_object_get_id(config), device);
+ return 0;
+ }
+
+ ast_publish_device_state_full(device, ast_devstate_val(state),
+ cachable == AST_DEVSTATE_CACHABLE ? AST_DEVSTATE_CACHABLE : AST_DEVSTATE_NOT_CACHABLE,
+ pubsub_eid);
+
+ return 0;
+}
+
+static int asterisk_publication_mailboxstate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
+ struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ const char *uniqueid = ast_json_string_get(ast_json_object_get(json, "uniqueid"));
+ int old_msgs = ast_json_integer_get(ast_json_object_get(json, "old"));
+ int new_msgs = ast_json_integer_get(ast_json_object_get(json, "new"));
+ char *item_id;
+ const char *mailbox;
+
+ if (!config->mailbox_state) {
+ ast_debug(2, "Received mailbox state event for resource '%s' but it is not configured to accept them\n",
+ ast_sorcery_object_get_id(config));
+ return 0;
+ }
+
+ if (ast_strlen_zero(uniqueid)) {
+ ast_debug(1, "Received incomplete mailbox state event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ if (config->mailbox_state_filter && regexec(&config->mailbox_state_regex, uniqueid, 0, NULL, 0)) {
+ ast_debug(2, "Received mailbox state on resource '%s' for uniqueid '%s' but it has been filtered out\n",
+ ast_sorcery_object_get_id(config), uniqueid);
+ return 0;
+ }
+
+ item_id = ast_strdupa(uniqueid);
+ mailbox = strsep(&item_id, "@");
+
+ ast_publish_mwi_state_full(mailbox, item_id, new_msgs, old_msgs, NULL, pubsub_eid);
+
+ return 0;
+}
+
+static int asterisk_publication_devicestate_refresh(struct ast_sip_publication *pub,
+ struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ struct ast_sip_outbound_publish_client *client;
+ struct ast_datastore *datastore;
+ struct ao2_container *cached;
+
+ if (ast_strlen_zero(config->devicestate_publish)) {
+ return 0;
+ }
+
+ client = ast_sip_publish_client_get(config->devicestate_publish);
+ if (!client) {
+ ast_log(LOG_ERROR, "Received refresh request for devicestate on publication '%s' but publish '%s' is not available\n",
+ ast_sorcery_object_get_id(config), config->devicestate_publish);
+ return 0;
+ }
+
+ datastore = ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher");
+ if (!datastore) {
+ ao2_ref(client, -1);
+ return 0;
+ }
+
+ cached = stasis_cache_dump(ast_device_state_cache(), NULL);
+ if (cached) {
+ ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
+ ao2_ref(cached, -1);
+ }
+ ao2_ref(client, -1);
+ ao2_ref(datastore, -1);
+
+ return 0;
+}
+
+static int asterisk_publication_devicestate_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
+ enum ast_sip_publish_state state)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+ const char *eid, *type;
+ struct ast_eid pubsub_eid;
+ int res = -1;
+
+ /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
+ if (!config) {
+ return -1;
+ }
+
+ /* If no body exists this is a refresh and can be ignored */
+ if (!body) {
+ return 0;
+ }
+
+ /* We only accept JSON for content */
+ if (pj_strcmp2(&body->content_type.type, "application") ||
+ pj_strcmp2(&body->content_type.subtype, "json")) {
+ ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ json = ast_json_load_buf(body->data, body->len, NULL);
+ if (!json) {
+ ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ eid = ast_json_string_get(ast_json_object_get(json, "eid"));
+ if (!eid) {
+ ast_debug(1, "Received event without eid for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+ ast_str_to_eid(&pubsub_eid, eid);
+
+ type = ast_json_string_get(ast_json_object_get(json, "type"));
+ if (!type) {
+ ast_debug(1, "Received event without type for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ } else if (!strcmp(type, "devicestate")) {
+ res = asterisk_publication_devicestate(pub, config, &pubsub_eid, json);
+ } else if (!strcmp(type, "refresh")) {
+ res = asterisk_publication_devicestate_refresh(pub, config, &pubsub_eid, json);
+ }
+
+ return res;
+}
+
+static int asterisk_publication_mwi_refresh(struct ast_sip_publication *pub,
+ struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
+{
+ struct ast_sip_outbound_publish_client *client;
+ struct ast_datastore *datastore;
+ struct ao2_container *cached;
+
+ if (ast_strlen_zero(config->mailboxstate_publish)) {
+ return 0;
+ }
+
+ client = ast_sip_publish_client_get(config->mailboxstate_publish);
+ if (!client) {
+ ast_log(LOG_ERROR, "Received refresh request for mwi state on publication '%s' but publish '%s' is not available\n",
+ ast_sorcery_object_get_id(config), config->mailboxstate_publish);
+ return 0;
+ }
+
+ datastore = ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher");
+ if (!datastore) {
+ ao2_ref(client, -1);
+ return 0;
+ }
+
+ cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
+ if (cached) {
+ ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
+ ao2_ref(cached, -1);
+ }
+ ao2_ref(client, -1);
+ ao2_ref(datastore, -1);
+
+ return 0;
+}
+
+static int asterisk_publication_mwi_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
+ enum ast_sip_publish_state state)
+{
+ RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
+ ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+ const char *eid, *type;
+ struct ast_eid pubsub_eid;
+ int res = -1;
+
+ /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
+ if (!config) {
+ return -1;
+ }
+
+ /* If no body exists this is a refresh and can be ignored */
+ if (!body) {
+ return 0;
+ }
+
+ /* We only accept JSON for content */
+ if (pj_strcmp2(&body->content_type.type, "application") ||
+ pj_strcmp2(&body->content_type.subtype, "json")) {
+ ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ json = ast_json_load_buf(body->data, body->len, NULL);
+ if (!json) {
+ ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+
+ eid = ast_json_string_get(ast_json_object_get(json, "eid"));
+ if (!eid) {
+ ast_debug(1, "Received event without eid for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ }
+ ast_str_to_eid(&pubsub_eid, eid);
+
+ type = ast_json_string_get(ast_json_object_get(json, "type"));
+ if (!type) {
+ ast_debug(1, "Received event without type for resource '%s'\n",
+ ast_sorcery_object_get_id(config));
+ return -1;
+ } else if (!strcmp(type, "mailboxstate")) {
+ res = asterisk_publication_mailboxstate(pub, config, &pubsub_eid, json);
+ } else if (!strcmp(type, "refresh")) {
+ res = asterisk_publication_mwi_refresh(pub, config, &pubsub_eid, json);
+ }
+
+ return res;
+}
+
+static int send_refresh_cb(void *obj, void *arg, int flags)
+{
+ struct asterisk_publication_config *config = obj;
+ struct ast_sip_outbound_publish_client *client;
+
+ if (!ast_strlen_zero(config->devicestate_publish)) {
+ client = ast_sip_publish_client_get(config->devicestate_publish);
+ if (client) {
+ ast_sip_publish_client_send(client, arg);
+ ao2_ref(client, -1);
+ }
+ }
+
+ if (!ast_strlen_zero(config->mailboxstate_publish)) {
+ client = ast_sip_publish_client_get(config->mailboxstate_publish);
+ if (client) {
+ ast_sip_publish_client_send(client, arg);
+ ao2_ref(client, -1);
+ }
+ }
+
+ return 0;
+}
+
+/*! \brief Internal function to send refresh requests to all publications */
+static void asterisk_publication_send_refresh(void)
+{
+ struct ao2_container *publications = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "asterisk-publication", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+ char eid_str[20];
+ struct ast_json *json;
+ char *text;
+ struct ast_sip_body body = {
+ .type = "application",
+ .subtype = "json",
+ };
+
+ if (!publications) {
+ return;
+ }
+
+ ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
+ json = ast_json_pack(
+ "{ s: s, s: s }",
+ "type", "refresh",
+ "eid", eid_str);
+ if (!json) {
+ ao2_ref(publications, -1);
+ return;
+ }
+
+ text = ast_json_dump_string(json);
+ if (!text) {
+ ast_json_unref(json);
+ ao2_ref(publications, -1);
+ return;
+ }
+ body.body_text = text;
+
+ ao2_callback(publications, OBJ_NODATA, send_refresh_cb, &body);
+
+ ast_json_free(text);
+ ast_json_unref(json);
+ ao2_ref(publications, -1);
+}
+
+struct ast_sip_publish_handler asterisk_devicestate_publication_handler = {
+ .event_name = "asterisk-devicestate",
+ .new_publication = asterisk_publication_new,
+ .publication_state_change = asterisk_publication_devicestate_state_change,
+};
+
+struct ast_sip_publish_handler asterisk_mwi_publication_handler = {
+ .event_name = "asterisk-mwi",
+ .new_publication = asterisk_publication_new,
+ .publication_state_change = asterisk_publication_mwi_state_change,
+};
+
+/*! \brief Destructor function for Asterisk publication configuration */
+static void asterisk_publication_config_destroy(void *obj)
+{
+ struct asterisk_publication_config *config = obj;
+
+ ast_string_field_free_memory(config);
+}
+
+/*! \brief Allocator function for Asterisk publication configuration */
+static void *asterisk_publication_config_alloc(const char *name)
+{
+ struct asterisk_publication_config *config = ast_sorcery_generic_alloc(sizeof(*config),
+ asterisk_publication_config_destroy);
+
+ if (!config || ast_string_field_init(config, 256)) {
+ ao2_cleanup(config);
+ return NULL;
+ }
+
+ return config;
+}
+
+static int regex_filter_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct asterisk_publication_config *config = obj;
+ int res = -1;
+
+ if (ast_strlen_zero(var->value)) {
+ return 0;
+ }
+
+ if (!strcmp(var->name, "device_state_filter")) {
+ if (!(res = build_regex(&config->device_state_regex, var->value))) {
+ config->device_state_filter = 1;
+ }
+ } else if (!strcmp(var->name, "mailbox_state_filter")) {
+ if (!(res = build_regex(&config->mailbox_state_regex, var->value))) {
+ config->mailbox_state_filter = 1;
+ }
+ }
+
+ return res;
+}
+
+static int load_module(void)
+{
+ ast_sorcery_apply_default(ast_sip_get_sorcery(), "asterisk-publication", "config", "pjsip.conf,criteria=type=asterisk-publication");
+
+ if (ast_sorcery_object_register(ast_sip_get_sorcery(), "asterisk-publication", asterisk_publication_config_alloc, NULL, NULL)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "devicestate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, devicestate_publish));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailboxstate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, mailboxstate_publish));
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "device_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, device_state));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "device_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, mailbox_state));
+ ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
+
+ if (ast_sip_register_publish_handler(&asterisk_devicestate_publication_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
+ asterisk_devicestate_publication_handler.event_name);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_publish_handler(&asterisk_mwi_publication_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
+ asterisk_mwi_publication_handler.event_name);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_event_publisher_handler(&asterisk_devicestate_publisher_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
+ asterisk_devicestate_publisher_handler.event_name);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (ast_sip_register_event_publisher_handler(&asterisk_mwi_publisher_handler)) {
+ ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
+ asterisk_mwi_publisher_handler.event_name);
+ ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ asterisk_publication_send_refresh();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int reload_module(void)
+{
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
+ asterisk_publication_send_refresh();
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
+ ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
+ ast_sip_unregister_event_publisher_handler(&asterisk_devicestate_publisher_handler);
+ ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Asterisk Event PUBLISH Support",
+ .load = load_module,
+ .reload = reload_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 89dd14086..6a8ec12db 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -108,6 +108,15 @@
<synopsis>The time at which the subscription expires</synopsis>
</configOption>
</configObject>
+ <configObject name="inbound-publication">
+ <synopsis>The configuration for inbound publications</synopsis>
+ <configOption name="endpoint" default="">
+ <synopsis>Optional name of an endpoint that is only allowed to publish to this resource</synopsis>
+ </configOption>
+ <configOption name="type">
+ <synopsis>Must be of type 'inbound-publication'.</synopsis>
+ </configOption>
+ </configObject>
</configFile>
</configInfo>
***/
@@ -217,6 +226,12 @@ struct ast_sip_publication {
int expires;
/*! \brief Scheduled item for expiration of publication */
int sched_id;
+ /*! \brief The resource the publication is to */
+ char *resource;
+ /*! \brief The name of the event type configuration */
+ char *event_configuration_name;
+ /*! \brief Data containing the above */
+ char data[0];
};
@@ -330,6 +345,18 @@ struct ast_sip_subscription {
char resource[0];
};
+/*!
+ * \brief Structure representing a publication resource
+ */
+struct ast_sip_publication_resource {
+ /*! \brief Sorcery object details */
+ SORCERY_OBJECT(details);
+ /*! \brief Optional name of an endpoint that is only allowed to publish to this resource */
+ char *endpoint;
+ /*! \brief Mapping for event types to configuration */
+ struct ast_variable *events;
+};
+
static const char *sip_subscription_roles_map[] = {
[AST_SIP_SUBSCRIBER] = "Subscriber",
[AST_SIP_NOTIFIER] = "Notifier"
@@ -350,6 +377,21 @@ static pjsip_dialog *sip_subscription_get_dlg(const struct ast_sip_subscription
return sub->reality.real.dlg;
}
+/*! \brief Destructor for publication resource */
+static void publication_resource_destroy(void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+
+ ast_free(resource->endpoint);
+ ast_variables_destroy(resource->events);
+}
+
+/*! \brief Allocator for publication resource */
+static void *publication_resource_alloc(const char *name)
+{
+ return ast_sorcery_generic_alloc(sizeof(struct ast_sip_publication_resource), publication_resource_destroy);
+}
+
/*! \brief Destructor for subscription persistence */
static void subscription_persistence_destroy(void *obj)
{
@@ -1105,7 +1147,7 @@ struct ast_datastore *ast_sip_subscription_get_datastore(struct ast_sip_subscrip
void ast_sip_subscription_remove_datastore(struct ast_sip_subscription *subscription, const char *name)
{
- ao2_callback(subscription->datastores, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, NULL, (void *) name);
+ ao2_find(subscription->datastores, name, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
}
int ast_sip_publication_add_datastore(struct ast_sip_publication *publication, struct ast_datastore *datastore)
@@ -1454,14 +1496,17 @@ static void publication_destroy_fn(void *obj)
ao2_cleanup(publication->endpoint);
}
-static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata,
+ const char *resource, const char *event_configuration_name)
{
struct ast_sip_publication *publication;
pjsip_expires_hdr *expires_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+ size_t resource_len = strlen(resource) + 1, event_configuration_name_len = strlen(event_configuration_name) + 1;
+ char *dst;
ast_assert(endpoint != NULL);
- if (!(publication = ao2_alloc(sizeof(*publication), publication_destroy_fn))) {
+ if (!(publication = ao2_alloc(sizeof(*publication) + resource_len + event_configuration_name_len, publication_destroy_fn))) {
return NULL;
}
@@ -1475,6 +1520,10 @@ static struct ast_sip_publication *sip_create_publication(struct ast_sip_endpoin
publication->endpoint = endpoint;
publication->expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
publication->sched_id = -1;
+ dst = publication->data;
+ publication->resource = strcpy(dst, resource);
+ dst += resource_len;
+ publication->event_configuration_name = strcpy(dst, event_configuration_name);
return publication;
}
@@ -1521,8 +1570,10 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
struct ast_sip_publish_handler *handler)
{
struct ast_sip_publication *publication;
- char *resource;
+ char *resource_name;
size_t resource_size;
+ RAII_VAR(struct ast_sip_publication_resource *, resource, NULL, ao2_cleanup);
+ struct ast_variable *event_configuration_name = NULL;
pjsip_uri *request_uri;
pjsip_sip_uri *request_uri_sip;
int resp;
@@ -1540,17 +1591,39 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource = alloca(resource_size);
- ast_copy_pj_str(resource, &request_uri_sip->user, resource_size);
+ resource_name = alloca(resource_size);
+ ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size);
- resp = handler->new_publication(endpoint, resource);
+ resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
+ if (!resource) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+ return NULL;
+ }
- if (PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
+ if (!ast_strlen_zero(resource->endpoint) && strcmp(resource->endpoint, ast_sorcery_object_get_id(endpoint))) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+ return NULL;
+ }
+
+ for (event_configuration_name = resource->events; event_configuration_name; event_configuration_name = event_configuration_name->next) {
+ if (!strcmp(event_configuration_name->name, handler->event_name)) {
+ break;
+ }
+ }
+
+ if (!event_configuration_name) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+ return NULL;
+ }
+
+ resp = handler->new_publication(endpoint, resource_name, event_configuration_name->value);
+
+ if (!PJSIP_IS_STATUS_IN_CLASS(resp, 200)) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, resp, NULL, NULL, NULL);
return NULL;
}
- publication = sip_create_publication(endpoint, rdata);
+ publication = sip_create_publication(endpoint, rdata, S_OR(resource_name, ""), event_configuration_name->value);
if (!publication) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 503, NULL, NULL, NULL);
@@ -1574,7 +1647,9 @@ static int publish_expire_callback(void *data)
{
RAII_VAR(struct ast_sip_publication *, publication, data, ao2_cleanup);
- publication->handler->publish_expire(publication);
+ if (publication->handler->publish_expire) {
+ publication->handler->publish_expire(publication);
+ }
return 0;
}
@@ -1603,7 +1678,7 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
pjsip_generic_string_hdr *etag_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_sip_if_match, NULL);
enum sip_publish_type publish_type;
RAII_VAR(struct ast_sip_publication *, publication, NULL, ao2_cleanup);
- int expires = 0, entity_id;
+ int expires = 0, entity_id, response = 0;
endpoint = ast_pjsip_rdata_get_endpoint(rdata);
ast_assert(endpoint != NULL);
@@ -1647,19 +1722,18 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
publication = publish_request_initial(endpoint, rdata, handler);
break;
case SIP_PUBLISH_REFRESH:
- sip_publication_respond(publication, 200, rdata);
case SIP_PUBLISH_MODIFY:
if (handler->publication_state_change(publication, rdata->msg_info.msg->body,
AST_SIP_PUBLISH_STATE_ACTIVE)) {
/* If an error occurs we want to terminate the publication */
expires = 0;
}
- sip_publication_respond(publication, 200, rdata);
+ response = 200;
break;
case SIP_PUBLISH_REMOVE:
handler->publication_state_change(publication, rdata->msg_info.msg->body,
AST_SIP_PUBLISH_STATE_TERMINATED);
- sip_publication_respond(publication, 200, rdata);
+ response = 200;
break;
case SIP_PUBLISH_UNKNOWN:
default:
@@ -1678,6 +1752,10 @@ static pj_bool_t pubsub_on_rx_publish_request(pjsip_rx_data *rdata)
}
}
+ if (response) {
+ sip_publication_respond(publication, response, rdata);
+ }
+
return PJ_TRUE;
}
@@ -1686,6 +1764,15 @@ struct ast_sip_endpoint *ast_sip_publication_get_endpoint(struct ast_sip_publica
return pub->endpoint;
}
+const char *ast_sip_publication_get_resource(const struct ast_sip_publication *pub)
+{
+ return pub->resource;
+}
+
+const char *ast_sip_publication_get_event_configuration(const struct ast_sip_publication *pub)
+{
+ return pub->event_configuration_name;
+}
int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator *generator)
{
@@ -2047,6 +2134,40 @@ static int persistence_expires_struct2str(const void *obj, const intptr_t *args,
return (ast_asprintf(buf, "%ld", persistence->expires.tv_sec) < 0) ? -1 : 0;
}
+static int resource_endpoint_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+
+ ast_free(resource->endpoint);
+ resource->endpoint = ast_strdup(var->value);
+
+ return 0;
+}
+
+static int resource_event_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ struct ast_sip_publication_resource *resource = obj;
+ /* The event configuration name starts with 'event_' so skip past it to get the real name */
+ const char *event = var->name + 6;
+ struct ast_variable *item;
+
+ if (ast_strlen_zero(event) || ast_strlen_zero(var->value)) {
+ return -1;
+ }
+
+ item = ast_variable_new(event, var->value, "");
+ if (!item) {
+ return -1;
+ }
+
+ if (resource->events) {
+ item->next = resource->events;
+ }
+ resource->events = item;
+
+ return 0;
+}
+
static int load_module(void)
{
static const pj_str_t str_PUBLISH = { "PUBLISH", 7 };
@@ -2103,6 +2224,20 @@ static int load_module(void)
ast_sorcery_object_field_register_custom(sorcery, "subscription_persistence", "expires", "",
persistence_expires_str2struct, persistence_expires_struct2str, NULL, 0, 0);
+ 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)) {
+ ast_log(LOG_ERROR, "Could not register subscription persistence object support\n");
+ ast_sip_unregister_service(&pubsub_module);
+ ast_sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_sorcery_object_field_register(sorcery, "inbound-publication", "type", "", OPT_NOOP_T, 0, 0);
+ ast_sorcery_object_field_register_custom(sorcery, "inbound-publication", "endpoint", "",
+ resource_endpoint_handler, NULL, NULL, 0, 0);
+ ast_sorcery_object_fields_register(sorcery, "inbound-publication", "^event_", resource_event_handler, NULL);
+ ast_sorcery_reload_object(sorcery, "inbound-publication");
+
if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
ast_sip_push_task(NULL, subscription_persistence_load, NULL);
} else {
diff --git a/res/res_pjsip_pubsub.exports.in b/res/res_pjsip_pubsub.exports.in
index ca165af92..2a6b75f00 100644
--- a/res/res_pjsip_pubsub.exports.in
+++ b/res/res_pjsip_pubsub.exports.in
@@ -15,6 +15,8 @@
LINKER_SYMBOL_PREFIXast_sip_unregister_subscription_handler;
LINKER_SYMBOL_PREFIXast_sip_create_publication;
LINKER_SYMBOL_PREFIXast_sip_publication_get_endpoint;
+ LINKER_SYMBOL_PREFIXast_sip_publication_get_resource;
+ LINKER_SYMBOL_PREFIXast_sip_publication_get_event_configuration;
LINKER_SYMBOL_PREFIXast_sip_publication_create_response;
LINKER_SYMBOL_PREFIXast_sip_publication_send_response;
LINKER_SYMBOL_PREFIXast_sip_register_publish_handler;