summaryrefslogtreecommitdiff
path: root/res/res_pjsip_exten_state.c
diff options
context:
space:
mode:
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",