mirror of
https://github.com/asterisk/asterisk.git
synced 2025-11-20 00:30:20 +00:00
Merge "res_pjsip_exten_state: Add config support for exten state publishers."
This commit is contained in:
@@ -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"
|
||||
@@ -42,6 +46,16 @@
|
||||
#define BODY_SIZE 1024
|
||||
#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
|
||||
*
|
||||
@@ -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,33 +542,266 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ast_sip_unregister_subscription_handler(&dialog_handler);
|
||||
ast_sip_unregister_subscription_handler(&presence_handler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Notifications",
|
||||
.support_level = AST_MODULE_SUPPORT_CORE,
|
||||
.load = load_module,
|
||||
|
||||
Reference in New Issue
Block a user