diff --git a/src/modules/presence_reginfo/Makefile b/src/modules/presence_reginfo/Makefile index 8e51d2afd4f..b3bad627bda 100644 --- a/src/modules/presence_reginfo/Makefile +++ b/src/modules/presence_reginfo/Makefile @@ -8,6 +8,25 @@ include ../../Makefile.defs auto_gen= NAME=presence_reginfo.so +ifeq ($(CROSS_COMPILE),) +XML2CFG=$(shell which xml2-config) +ifeq ($(XML2CFG),) +XML2CFG=$(shell \ + if pkg-config --exists libxml-2.0; then \ + echo 'pkg-config libxml-2.0'; \ + fi) +endif +endif + +ifneq ($(XML2CFG),) + DEFS += $(shell $(XML2CFG) --cflags ) + LIBS += $(shell $(XML2CFG) --libs) +else + DEFS+=-I$(LOCALBASE)/include/libxml2 \ + -I$(LOCALBASE)/include + LIBS+=-L$(LOCALBASE)/lib -lxml2 +endif + include ../../Makefile.modules diff --git a/src/modules/presence_reginfo/add_events.c b/src/modules/presence_reginfo/add_events.c index 468a14dd3a6..48f4cf3fc4b 100644 --- a/src/modules/presence_reginfo/add_events.c +++ b/src/modules/presence_reginfo/add_events.c @@ -27,6 +27,9 @@ #include "../../core/parser/parse_content.h" #include "../presence/event_list.h" #include "presence_reginfo.h" +#include "notify_body.h" + +extern int pres_reginfo_aggregate_presentities; int reginfo_add_events(void) { @@ -39,12 +42,22 @@ int reginfo_add_events(void) event.content_type.s = "application/reginfo+xml"; event.content_type.len = 23; - event.default_expires= pres_reginfo_default_expires; + event.default_expires = pres_reginfo_default_expires; event.type = PUBL_TYPE; event.req_auth = 0; event.evs_publ_handl = 0; - if (pres_add_event(&event) < 0) { + + if(pres_reginfo_aggregate_presentities) { + /* aggregate XML body and free() fuction */ + event.agg_nbody = reginfo_agg_nbody; + event.free_body = free_xml_body; + /* modify XML body for each watcher to set the correct "version" */ + event.aux_body_processing = reginfo_body_setversion; + event.aux_free_body = free_xml_body; + } + + if(pres_add_event(&event) < 0) { LM_ERR("failed to add event \"reginfo\"\n"); return -1; } diff --git a/src/modules/presence_reginfo/doc/presence_reginfo_admin.xml b/src/modules/presence_reginfo/doc/presence_reginfo_admin.xml index 9c7450a2e20..9175097d4a4 100644 --- a/src/modules/presence_reginfo/doc/presence_reginfo_admin.xml +++ b/src/modules/presence_reginfo/doc/presence_reginfo_admin.xml @@ -86,6 +86,27 @@ +
+ <varname>aggregate_presentities</varname> (int) + + Whether to aggregate in a single notify body all registration + presentities. Useful to have all registrations on first NOTIFY + following initial SUBSCRIBE. + + + Default value is 0 (disabled). + + + + Set <varname>aggregate_presentities</varname> parameter + + ... + modparam("presence_reginfo", "aggregate_presentities", 1) + ... + + +
+ diff --git a/src/modules/presence_reginfo/notify_body.c b/src/modules/presence_reginfo/notify_body.c new file mode 100644 index 00000000000..a4622743b8c --- /dev/null +++ b/src/modules/presence_reginfo/notify_body.c @@ -0,0 +1,314 @@ +/* + * presence_dialoginfo module + * + * Copyright (C) 2006 Voice Sistem S.R.L. + * Copyright (C) 2008 Klaus Darilion, IPCom + * Copyright (C) 2022 Matteo Brancaleoni, VoiSmart + * + * This file is part of Kamailio, a free SIP server. + * + * Kamailio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * Kamailio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/*! \file + * \brief Kamailio Presence_reginfo :: Notify BODY handling + * \ingroup presence_reginfo + */ + +#define MAX_INT_LEN 11 /* 2^32: 10 chars + 1 char sign */ + +#include +#include +#include + +#include "../../core/mem/mem.h" +#include "../presence/hash.h" +#include "../presence/presence.h" + + +str *agregate_xmls(str *pres_user, str *pres_domain, str **body_array, int n); + +void free_xml_body(char *body) +{ + if(body == NULL) + return; + + xmlFree(body); + body = NULL; +} + +str *reginfo_agg_nbody(str *pres_user, str *pres_domain, str **body_array, + int n, int off_index) +{ + str *n_body = NULL; + + LM_DBG("[pres_user]=%.*s [pres_domain]= %.*s, [n]=%d\n", pres_user->len, + pres_user->s, pres_domain->len, pres_domain->s, n); + + if(body_array == NULL) { + return NULL; + } + + n_body = agregate_xmls(pres_user, pres_domain, body_array, n); + LM_DBG("[n_body]=%p\n", n_body); + if(n_body) { + LM_DBG("[*n_body]=%.*s\n", n_body->len, n_body->s); + } + if(n_body == NULL && n != 0) { + LM_ERR("while aggregating body\n"); + } + + xmlCleanupParser(); + xmlMemoryDump(); + + return n_body; +} + +str *agregate_xmls(str *pres_user, str *pres_domain, str **body_array, int n) +{ + int i, j = 0; + + str *body = NULL; + + xmlDocPtr doc = NULL; + xmlNodePtr root_node = NULL; + xmlNsPtr namespace = NULL; + xmlNodePtr p_root = NULL; + xmlDocPtr *xml_array; + xmlNodePtr node = NULL; + xmlNodePtr next_node = NULL; + + LM_DBG("[pres_user]=%.*s [pres_domain]= %.*s, [n]=%d\n", pres_user->len, + pres_user->s, pres_domain->len, pres_domain->s, n); + + xml_array = (xmlDocPtr *)pkg_malloc(n * sizeof(xmlDocPtr)); + if(xml_array == NULL) { + PKG_MEM_ERROR; + return NULL; + } + memset(xml_array, 0, n * sizeof(xmlDocPtr)); + + /* parse all the XML documents into xml_array[] */ + for(i = 0; i < n; i++) { + if(body_array[i] == NULL) + continue; + + xml_array[j] = NULL; + xml_array[j] = xmlParseMemory(body_array[i]->s, body_array[i]->len); + if(xml_array[j] == NULL) { + LM_ERR("while parsing xml body message\n"); + goto error; + } + j++; + } + + if(j == 0) /* no body */ + { + LM_DBG("no body to be built\n"); + goto error; + } + + /* n: number of bodies in total */ + /* j: number of useful bodies; created XML structures */ + /* i: loop counter */ + + /* create the new NOTIFY body */ + doc = xmlNewDoc(BAD_CAST "1.0"); + if(doc == 0) { + LM_ERR("unable to create xml document\n"); + goto error; + } + + root_node = xmlNewNode(NULL, BAD_CAST "reginfo"); + if(root_node == 0) { + goto error; + } + + xmlDocSetRootElement(doc, root_node); + namespace = xmlNewNs( + root_node, BAD_CAST "urn:ietf:params:xml:ns:reginfo", NULL); + if(!namespace) { + LM_ERR("creating namespace failed\n"); + } + xmlSetNs(root_node, namespace); + + /* The version must be increased for each new document and is a 32bit int. + * As the version is different for each watcher, we can not set here the + * correct value. Thus, we just put here a placeholder which will be + * replaced by the correct value in the aux_body_processing callback. + * Thus we have CPU intensive XML aggregation only once and can use + * quick search&replace in the per-watcher aux_body_processing callback. + * We use 11 chracters as an signed int (although RFC says unsigned int we + * use signed int as presence module stores "version" in DB as + * signed int) has max. 10 characters + 1 character for the sign + */ + xmlNewProp(root_node, BAD_CAST "version", BAD_CAST "00000000000"); + xmlNewProp(root_node, BAD_CAST "state", BAD_CAST "full"); + + /* loop over all bodies and create the aggregated body */ + for(i = 0; i < j; i++) { + // get the root reginfo element + p_root = xmlDocGetRootElement(xml_array[i]); + if(p_root == NULL) { + LM_ERR("the xml_tree root element is null\n"); + goto error; + } + + // get the children registration elements + if(p_root->children) { + // loop over registration elements + for(node = p_root->children; node; node = next_node) { + next_node = node->next; + if(node->type != XML_ELEMENT_NODE) { + continue; + } + LM_DBG("node type: Element, name: %s\n", node->name); + + /* we do not copy the node, but unlink it and then add it ot the new node + * this destroys the original document but we do not need it anyway. + */ + xmlUnlinkNode(node); + if(xmlAddChild(root_node, node) == NULL) { + xmlFreeNode(node); + LM_ERR("while adding child\n"); + goto error; + } + } // end of loop over registration elements + } + } // end of loop over all bodies + + // convert to string & cleanup + body = (str *)pkg_malloc(sizeof(str)); + if(body == NULL) { + ERR_MEM(PKG_MEM_STR); + } + + xmlDocDumpFormatMemory(doc, (xmlChar **)(void *)&body->s, &body->len, 1); + + for(i = 0; i < j; i++) { + if(xml_array[i] != NULL) + xmlFreeDoc(xml_array[i]); + } + if(doc) { + xmlFreeDoc(doc); + } + if(xml_array) + pkg_free(xml_array); + + xmlCleanupParser(); + xmlMemoryDump(); + + return body; + + // error handling, cleanup and return NULL +error: + if(xml_array != NULL) { + for(i = 0; i < j; i++) { + if(xml_array[i] != NULL) + xmlFreeDoc(xml_array[i]); + } + pkg_free(xml_array); + } + if(body) { + pkg_free(body); + } + if(doc) { + xmlFreeDoc(doc); + } + + return NULL; +} + +str *reginfo_body_setversion(subs_t *subs, str *body) +{ + char *version_start = 0; + char version[MAX_INT_LEN + 2]; /* +2 becasue of trailing " and \0 */ + int version_len; + str *aux_body = NULL; + + if(!body) { + return NULL; + } + + /* xmlDocDumpFormatMemory creates \0 terminated string */ + /* version parameters starts at minimum at character 30 */ + if(body->len < 37) { + LM_ERR("body string too short!\n"); + return NULL; + } + version_start = strstr(body->s + 30, "version="); + if(!version_start) { + LM_ERR("version string not found!\n"); + return NULL; + } + version_start += 9; + + /* safety check for placeholder - if it is body not set by the module, + * don't update the version */ + if(strncmp(version_start, "00000000000\"", 12) != 0) + return NULL; + + version_len = snprintf(version, MAX_INT_LEN + 2, "%d\"", subs->version); + if(version_len >= MAX_INT_LEN + 2) { + LM_ERR("failed to convert 'version' to string (truncated output)\n"); + return NULL; + } else if(version_len < 0) { + LM_ERR("failed to convert 'version' to string (encoding error)\n"); + return NULL; + } + + aux_body = (str *)pkg_malloc(sizeof(str)); + if(aux_body == NULL) { + PKG_MEM_ERROR_FMT("error allocating memory for aux body str\n"); + return NULL; + } + memset(aux_body, 0, sizeof(str)); + aux_body->s = (char *)pkg_malloc(body->len * sizeof(char)); + if(aux_body->s == NULL) { + pkg_free(aux_body); + PKG_MEM_ERROR_FMT("error allocating memory for aux body buffer\n"); + return NULL; + } + memcpy(aux_body->s, body->s, body->len); + aux_body->len = body->len; + + /* again but on the copied str, no checks needed */ + version_start = strstr(aux_body->s + 30, "version="); + version_start += 9; + /* Replace the placeholder 00000000000 with the version. + * Put the padding behind the "" + */ + LM_DBG("replace version with \"%s\n", version); + memcpy(version_start, version, version_len); + memset(version_start + version_len, ' ', 12 - version_len); + + xmlDocPtr doc = + xmlReadMemory(aux_body->s, aux_body->len, "noname.xml", NULL, 0); + if(doc == NULL) { + LM_ERR("error allocation xmldoc\n"); + pkg_free(aux_body->s); + pkg_free(aux_body); + return NULL; + } + pkg_free(aux_body->s); + xmlDocDumpFormatMemory( + doc, (xmlChar **)(void *)&aux_body->s, &aux_body->len, 1); + xmlFreeDoc(doc); + xmlCleanupParser(); + xmlMemoryDump(); + + return aux_body; +} diff --git a/src/modules/presence_reginfo/notify_body.h b/src/modules/presence_reginfo/notify_body.h new file mode 100644 index 00000000000..95cd0dc94c3 --- /dev/null +++ b/src/modules/presence_reginfo/notify_body.h @@ -0,0 +1,43 @@ +/* + * presence_dialoginfo module + * + * Copyright (C) 2006 Voice Sistem S.R.L. + * Copyright (C) 2008 Klaus Darilion, IPCom + * Copyright (C) 2022 Matteo Brancaleoni, VoiSmart + * + * This file is part of Kamailio, a free SIP server. + * + * Kamailio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * Kamailio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/*! \file + * \brief Kamailio presence reginfo :: + * \ref notify_body.c + * \ingroup presence_reginfo + */ + + +#ifndef _NBODY_H_ +#define _NBODY_H_ + +str *reginfo_agg_nbody(str *pres_user, str *pres_domain, str **body_array, + int n, int off_index); + +str *reginfo_body_setversion(subs_t *subs, str *body); + +void free_xml_body(char *body); + +#endif diff --git a/src/modules/presence_reginfo/presence_reginfo.c b/src/modules/presence_reginfo/presence_reginfo.c index abb8755ca0c..74f37bbb637 100644 --- a/src/modules/presence_reginfo/presence_reginfo.c +++ b/src/modules/presence_reginfo/presence_reginfo.c @@ -47,11 +47,13 @@ static int mod_init(void); add_event_t pres_add_event; /* module parameters */ +int pres_reginfo_aggregate_presentities = 0; unsigned int pres_reginfo_default_expires = 3600; /* module exported paramaters */ static param_export_t params[] = { { "default_expires", INT_PARAM, &pres_reginfo_default_expires }, + { "aggregate_presentities", INT_PARAM, &pres_reginfo_aggregate_presentities }, {0, 0, 0} }; @@ -89,6 +91,12 @@ static int mod_init(void) return -1; } + if (pres_reginfo_aggregate_presentities != 0 + && pres_reginfo_aggregate_presentities != 1) { + LM_ERR("invalid aggregate_presentities param value, should be 0 or 1\n"); + return -1; + } + pres_add_event = pres.add_event; if (pres_add_event == NULL) { LM_ERR("could not import add_event\n");