summaryrefslogtreecommitdiff
path: root/res/res_pjsip_exten_state.c
diff options
context:
space:
mode:
authorJoshua Colp <jcolp@digium.com>2016-04-14 09:03:24 -0300
committerRichard Mudgett <rmudgett@digium.com>2016-04-26 18:47:51 -0500
commit81ea80b74ce1ae35eb5b377a825948c27ef65b45 (patch)
tree726bf046802dd82d86dc64d7d511218660b8ae17 /res/res_pjsip_exten_state.c
parent8ae69cffef03dcd4ba8a2ef56299ea3d19546a34 (diff)
res_pjsip_exten_state: Add config support for exten state publishers.
This change adds the ability to configure outbound publishing of extension state. Right now stuff is merely set up to store the configuration and to register a global extension state callback. The act of constructing the body and sending is not yet complete. Configurable elements right now are a regex for filtering the context, a regex for filtering the extension, and the body type to publish. ASTERISK-25922 #close Change-Id: Ia7e630136dfc355073c1cadff8ad394a08523d78
Diffstat (limited to 'res/res_pjsip_exten_state.c')
-rw-r--r--res/res_pjsip_exten_state.c303
1 files changed, 294 insertions, 9 deletions
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index a4ad1cd78..1f8b121e8 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -20,16 +20,20 @@
<depend>pjproject</depend>
<depend>res_pjsip</depend>
<depend>res_pjsip_pubsub</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 <pjlib.h>
#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_outbound_publish.h"
#include "asterisk/res_pjsip_pubsub.h"
#include "asterisk/res_pjsip_body_generator_types.h"
#include "asterisk/module.h"
@@ -43,6 +47,16 @@
#define EVENT_TYPE_SIZE 50
/*!
+ * \brief The number of buckets to use for storing publishers
+ */
+#define PUBLISHER_BUCKETS 31
+
+/*!
+ * \brief Container of active outbound extension state publishers
+ */
+static struct ao2_container *publishers;
+
+/*!
* \brief A subscription for extension state
*
* This structure acts as the owner for the underlying SIP subscription. It
@@ -68,6 +82,29 @@ struct exten_state_subscription {
enum ast_presence_state last_presence_state;
};
+/*!
+ * \brief An extension state publisher
+ *
+ */
+struct exten_state_publisher {
+ /*! Regular expression for context filtering */
+ regex_t context_regex;
+ /*! Regular expression for extension filtering */
+ regex_t exten_regex;
+ /*! Publish client to use for sending publish messages */
+ struct ast_sip_outbound_publish_client *client;
+ /*! Whether context filtering is active */
+ unsigned int context_filter;
+ /*! Whether extension filtering is active */
+ unsigned int exten_filter;
+ /*! The body type to use for this publisher - stored after the name */
+ char *body_type;
+ /*! The body subtype to use for this publisher - stored after the body type */
+ char *body_subtype;
+ /*! The name of this publisher */
+ char name[0];
+};
+
#define DEFAULT_PRESENCE_BODY "application/pidf+xml"
#define DEFAULT_DIALOG_BODY "application/dialog-info+xml"
@@ -77,6 +114,9 @@ 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);
+static int publisher_start(struct ast_sip_outbound_publish *configuration,
+ struct ast_sip_outbound_publish_client *client);
+static int publisher_stop(struct ast_sip_outbound_publish_client *client);
struct ast_sip_notifier presence_notifier = {
.default_accept = DEFAULT_PRESENCE_BODY,
@@ -101,6 +141,12 @@ struct ast_sip_subscription_handler presence_handler = {
.notifier = &presence_notifier,
};
+struct ast_sip_event_publisher_handler presence_publisher = {
+ .event_name = "presence",
+ .start_publishing = publisher_start,
+ .stop_publishing = publisher_stop,
+};
+
struct ast_sip_subscription_handler dialog_handler = {
.event_name = "dialog",
.body_type = AST_SIP_EXTEN_STATE_DATA,
@@ -110,6 +156,12 @@ struct ast_sip_subscription_handler dialog_handler = {
.notifier = &dialog_notifier,
};
+struct ast_sip_event_publisher_handler dialog_publisher = {
+ .event_name = "dialog",
+ .start_publishing = publisher_start,
+ .stop_publishing = publisher_stop,
+};
+
static void exten_state_subscription_destructor(void *obj)
{
struct exten_state_subscription *sub = obj;
@@ -490,31 +542,264 @@ static void to_ami(struct ast_sip_subscription *sub,
exten_state_sub->last_exten_state));
}
+/*!
+ * \brief Global extension state callback function
+ */
+static int exten_state_publisher_state_cb(const char *context, const char *exten, struct ast_state_cb_info *info, void *data)
+{
+ struct ao2_iterator publisher_iter;
+ struct exten_state_publisher *publisher;
+
+ publisher_iter = ao2_iterator_init(publishers, 0);
+ for (; (publisher = ao2_iterator_next(&publisher_iter)); ao2_ref(publisher, -1)) {
+ if ((publisher->context_filter && regexec(&publisher->context_regex, context, 0, NULL, 0)) ||
+ (publisher->exten_filter && regexec(&publisher->exten_regex, exten, 0, NULL, 0))) {
+ continue;
+ }
+ /* This is a placeholder for additional code to come */
+ }
+ ao2_iterator_destroy(&publisher_iter);
+
+ return 0;
+}
+
+/*!
+ * \brief Hashing function for extension state publisher
+ */
+static int exten_state_publisher_hash(const void *obj, const int flags)
+{
+ const struct exten_state_publisher *object;
+ const char *key;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key = object->name;
+ break;
+ default:
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_hash(key);
+}
+
+/*!
+ * \brief Comparator function for extension state publisher
+ */
+static int exten_state_publisher_cmp(void *obj, void *arg, int flags)
+{
+ const struct exten_state_publisher *object_left = obj;
+ const struct exten_state_publisher *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(object_left->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ /* Not supported by container. */
+ ast_assert(0);
+ return 0;
+ default:
+ cmp = 0;
+ break;
+ }
+ if (cmp) {
+ return 0;
+ }
+ return CMP_MATCH;
+}
+
+/*!
+ * \brief Destructor for extension state publisher
+ */
+static void exten_state_publisher_destroy(void *obj)
+{
+ struct exten_state_publisher *publisher = obj;
+
+ if (publisher->context_filter) {
+ regfree(&publisher->context_regex);
+ }
+
+ if (publisher->exten_filter) {
+ regfree(&publisher->exten_regex);
+ }
+
+ ao2_cleanup(publisher->client);
+}
+
+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 publisher_start(struct ast_sip_outbound_publish *configuration, struct ast_sip_outbound_publish_client *client)
+{
+ struct exten_state_publisher *publisher;
+ size_t name_size;
+ size_t body_type_size;
+ size_t body_subtype_size;
+ char *body_subtype;
+ const char *body_full;
+ const char *body_type;
+ const char *name;
+ const char *context;
+ const char *exten;
+
+ name = ast_sorcery_object_get_id(configuration);
+
+ body_full = ast_sorcery_object_get_extended(configuration, "body");
+ if (ast_strlen_zero(body_full)) {
+ ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Body not set\n",
+ name);
+ return -1;
+ }
+
+ body_subtype = ast_strdupa(body_full);
+ body_type = strsep(&body_subtype, "/");
+ if (ast_strlen_zero(body_type) || ast_strlen_zero(body_subtype)) {
+ ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Body '%s' missing type or subtype\n",
+ name, body_full);
+ return -1;
+ }
+
+ name_size = strlen(name) + 1;
+ body_type_size = strlen(body_type) + 1;
+ body_subtype_size = strlen(body_subtype) + 1;
+
+ publisher = ao2_alloc_options(
+ sizeof(*publisher) + name_size + body_type_size + body_subtype_size,
+ exten_state_publisher_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!publisher) {
+ return -1;
+ }
+
+ ast_copy_string(publisher->name, name, name_size);
+ publisher->body_type = publisher->name + name_size;
+ ast_copy_string(publisher->body_type, body_type, body_type_size);
+ publisher->body_subtype = publisher->body_type + body_type_size;
+ ast_copy_string(publisher->body_subtype, body_subtype, body_subtype_size);
+
+ context = ast_sorcery_object_get_extended(configuration, "context");
+ if (!ast_strlen_zero(context)) {
+ if (build_regex(&publisher->context_regex, context)) {
+ ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Could not build context filter '%s'\n",
+ name, context);
+ ao2_ref(publisher, -1);
+ return -1;
+ }
+
+ publisher->context_filter = 1;
+ }
+
+ exten = ast_sorcery_object_get_extended(configuration, "exten");
+ if (!ast_strlen_zero(exten)) {
+ if (build_regex(&publisher->exten_regex, exten)) {
+ ast_log(LOG_ERROR, "Outbound extension state publisher '%s': Could not build exten filter '%s'\n",
+ name, exten);
+ ao2_ref(publisher, -1);
+ return -1;
+ }
+
+ publisher->exten_filter = 1;
+ }
+
+ publisher->client = ao2_bump(client);
+
+ ao2_lock(publishers);
+ if (!ao2_container_count(publishers)) {
+ ast_extension_state_add(NULL, NULL, exten_state_publisher_state_cb, NULL);
+ }
+ ao2_link_flags(publishers, publisher, OBJ_NOLOCK);
+ ao2_unlock(publishers);
+
+ ao2_ref(publisher, -1);
+
+ return 0;
+}
+
+static int publisher_stop(struct ast_sip_outbound_publish_client *client)
+{
+ ao2_find(publishers, ast_sorcery_object_get_id(client), OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
+ return 0;
+}
+
+static int unload_module(void)
+{
+ ast_sip_unregister_event_publisher_handler(&dialog_publisher);
+ ast_sip_unregister_subscription_handler(&dialog_handler);
+ ast_sip_unregister_event_publisher_handler(&presence_publisher);
+ ast_sip_unregister_subscription_handler(&presence_handler);
+
+ ast_extension_state_del(0, exten_state_publisher_state_cb);
+ ao2_cleanup(publishers);
+ publishers = NULL;
+
+ return 0;
+}
+
static int load_module(void)
{
- CHECK_PJSIP_MODULE_LOADED();
+ CHECK_PJSIP_PUBSUB_MODULE_LOADED();
+
+ if (!ast_module_check("res_pjsip_outbound_publish.so")) {
+ ast_log(LOG_WARNING, "This module requires the 'res_pjsip_outbound_publish.so' module to be loaded\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ publishers = ao2_container_alloc(PUBLISHER_BUCKETS, exten_state_publisher_hash,
+ exten_state_publisher_cmp);
+ if (!publishers) {
+ ast_log(LOG_WARNING, "Unable to create container to store extension state publishers\n");
+ return AST_MODULE_LOAD_DECLINE;
+ }
if (ast_sip_register_subscription_handler(&presence_handler)) {
ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
presence_handler.event_name);
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ if (ast_sip_register_event_publisher_handler(&presence_publisher)) {
+ ast_log(LOG_WARNING, "Unable to register presence publisher %s\n",
+ presence_publisher.event_name);
+ unload_module();
return AST_MODULE_LOAD_DECLINE;
}
if (ast_sip_register_subscription_handler(&dialog_handler)) {
ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
dialog_handler.event_name);
- ast_sip_unregister_subscription_handler(&presence_handler);
+ unload_module();
return AST_MODULE_LOAD_DECLINE;
}
- return AST_MODULE_LOAD_SUCCESS;
-}
+ if (ast_sip_register_event_publisher_handler(&dialog_publisher)) {
+ ast_log(LOG_WARNING, "Unable to register presence publisher %s\n",
+ dialog_publisher.event_name);
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
-static int unload_module(void)
-{
- ast_sip_unregister_subscription_handler(&dialog_handler);
- ast_sip_unregister_subscription_handler(&presence_handler);
- return 0;
+ return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Notifications",