[Asterisk-code-review] res_pjsip_geolocation: Outbound Support (asterisk[development/16/geolocation])
George Joseph
asteriskteam at digium.com
Wed Mar 23 14:04:32 CDT 2022
George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/18251 )
Change subject: res_pjsip_geolocation: Outbound Support
......................................................................
res_pjsip_geolocation: Outbound Support
Primarily added the processing to convert eprofiles received
from the core to actual PIDF-LO XML documents and attach
them to outgoing SIP messages.
Along the way, additional tweaks needed to be made to core
XML and config capabilities as well as core res_geolocation
stuff.
Change-Id: Iccb956dd1800204472a21714d06d3b28486e9490
---
M include/asterisk/pbx.h
M include/asterisk/res_geolocation.h
M include/asterisk/res_pjsip.h
M include/asterisk/xml.h
M main/pbx_variables.c
M main/xml.c
M res/Makefile
A res/res_geolocation/eprofile_to_pidf.xslt
M res/res_geolocation/geoloc_civicaddr.c
M res/res_geolocation/geoloc_config.c
M res/res_geolocation/geoloc_datastore.c
M res/res_geolocation/geoloc_doc.xml
M res/res_geolocation/geoloc_eprofile.c
M res/res_geolocation/geoloc_gml.c
M res/res_geolocation/geoloc_private.h
M res/res_geolocation/pidf_to_eprofile.xslt
M res/res_pjsip.c
M res/res_pjsip_geolocation.c
18 files changed, 1,575 insertions(+), 177 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/51/18251/1
diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h
index f5f0691..c905a4c 100644
--- a/include/asterisk/pbx.h
+++ b/include/asterisk/pbx.h
@@ -1455,6 +1455,28 @@
* \param used Number of bytes read from the template. (May be NULL)
*/
void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used);
+
+/*!
+ * \brief Perform variable/function/expression substitution on an ast_str
+ *
+ * \param buf Result will be placed in this buffer.
+ * \param maxlen -1 if the buffer should not grow, 0 if the buffer
+ * may grow to any size, and >0 if the buffer should
+ * grow only to that number of bytes.
+ * \param c A channel from which to extract values, and to pass
+ * to any dialplan functions.
+ * \param headp A channel variables list to also search for variables.
+ * \param templ Variable template to expand.
+ * \param used Number of bytes read from the template. (May be NULL)
+ * \param use_both Normally, if a channel is specified, headp is ignored.
+ * If this parameter is set to 1 and both a channel and headp
+ * are specified, the channel will be searched for variables
+ * first and any not found will be searched for in headp.
+ */
+void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen,
+ struct ast_channel *c, struct varshead *headp, const char *templ,
+ size_t *used, int use_both);
+
/*! @} */
int ast_extension_patmatch(const char *pattern, const char *data);
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
index 9b8b188..cfe81fb 100644
--- a/include/asterisk/res_geolocation.h
+++ b/include/asterisk/res_geolocation.h
@@ -19,8 +19,9 @@
#ifndef INCLUDE_ASTERISK_RES_GEOLOCATION_H_
#define INCLUDE_ASTERISK_RES_GEOLOCATION_H_
-#include "asterisk/sorcery.h"
+#include "asterisk/channel.h"
#include "asterisk/config.h"
+#include "asterisk/sorcery.h"
#include "asterisk/xml.h"
#include "asterisk/optional_api.h"
@@ -30,7 +31,8 @@
AST_PIDF_ELEMENT_NONE = 0,
AST_PIDF_ELEMENT_TUPLE,
AST_PIDF_ELEMENT_DEVICE,
- AST_PIDF_ELEMENT_PERSON
+ AST_PIDF_ELEMENT_PERSON,
+ AST_PIDF_ELEMENT_LAST,
};
enum ast_geoloc_format {
@@ -38,6 +40,7 @@
AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
AST_GEOLOC_FORMAT_GML,
AST_GEOLOC_FORMAT_URI,
+ AST_GEOLOC_FORMAT_LAST,
};
enum ast_geoloc_action {
@@ -45,12 +48,14 @@
AST_GEOLOC_ACTION_APPEND,
AST_GEOLOC_ACTION_PREPEND,
AST_GEOLOC_ACTION_REPLACE,
+ AST_GEOLOC_ACTION_LAST,
};
struct ast_geoloc_location {
SORCERY_OBJECT(details);
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(method);
+ AST_STRING_FIELD(location_source);
);
enum ast_geoloc_format format;
struct ast_variable *location_info;
@@ -60,6 +65,7 @@
SORCERY_OBJECT(details);
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(location_reference);
+ AST_STRING_FIELD(notes);
);
enum ast_geoloc_pidf_element pidf_element;
enum ast_geoloc_action action;
@@ -74,7 +80,9 @@
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(id);
AST_STRING_FIELD(location_reference);
+ AST_STRING_FIELD(location_source);
AST_STRING_FIELD(method);
+ AST_STRING_FIELD(notes);
);
enum ast_geoloc_pidf_element pidf_element;
enum ast_geoloc_action action;
@@ -147,8 +155,8 @@
enum ast_geoloc_validate_result {
AST_GEOLOC_VALIDATE_INVALID_VALUE = -1,
AST_GEOLOC_VALIDATE_SUCCESS = 0,
- AST_GEOLOC_VALIDATE_MISSING_TYPE,
- AST_GEOLOC_VALIDATE_INVALID_TYPE,
+ AST_GEOLOC_VALIDATE_MISSING_SHAPE,
+ AST_GEOLOC_VALIDATE_INVALID_SHAPE,
AST_GEOLOC_VALIDATE_INVALID_VARNAME,
AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES,
AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES,
@@ -325,12 +333,13 @@
* \brief Allocate a new effective profile from an XML PIDF-LO document
*
* \param pidf_xmldoc The ast_xml_doc to use.
+ * \param geoloc_uri The URI that referenced this document.
* \param reference_string An identifying string to use in error messages.
*
* \return The effective profile ao2 object.
*/
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
- struct ast_xml_doc *pidf_xmldoc, const char *reference_string);
+ struct ast_xml_doc *pidf_xmldoc, const char *geoloc_uri, const char *reference_string);
/*!
* \brief Allocate a new effective profile from a URI.
@@ -343,6 +352,12 @@
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
const char *reference_string);
+const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
+ struct ast_channel *chan, struct ast_str **buf, const char *ref_string);
+
+const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
+ struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
+
/*!
* \brief Refresh the effective profile with any changed info.
*
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 40e13e2..6a705d7 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -2379,6 +2379,17 @@
int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value);
/*!
+ * \brief Add a header to an outbound SIP message, returning a pointer to the header
+ *
+ * \param tdata The message to add the header to
+ * \param name The header name
+ * \param value The header value
+ * \return The pjsip_generic_string_hdr * added.
+ */
+pjsip_generic_string_hdr *ast_sip_add_header2(pjsip_tx_data *tdata,
+ const char *name, const char *value);
+
+/*!
* \brief Add a body to an outbound SIP message
*
* If this is called multiple times, the latest body will replace the current
diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h
index 5564051..c1b0797 100644
--- a/include/asterisk/xml.h
+++ b/include/asterisk/xml.h
@@ -261,6 +261,17 @@
int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc);
/*!
+ * \brief Dump the specified document to a buffer
+ *
+ * \param doc The XML doc to dump
+ * \param buffer A pointer to a char * to receive the address of the results
+ * \param length A pointer to an int to receive the length of the results
+ *
+ * \note The result buffer must be freed with ast_xml_free_text().
+ */
+void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length);
+
+/*!
* \brief Free the XPath results
* \param results The XPath results object to dispose of
*
diff --git a/main/pbx_variables.c b/main/pbx_variables.c
index 6f7439f..48e499c 100644
--- a/main/pbx_variables.c
+++ b/main/pbx_variables.c
@@ -394,7 +394,9 @@
return ret;
}
-void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used)
+void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen,
+ struct ast_channel *c, struct varshead *headp, const char *templ,
+ size_t *used, int use_both)
{
/* Substitutes variables into buf, based on string templ */
const char *whereweare;
@@ -501,7 +503,8 @@
/* Store variable name expression to lookup. */
ast_str_set_substr(&substr1, 0, vars, len);
- ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len);
+ ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n",
+ ast_str_buffer(substr1), vars, len);
/* Substitute if necessary */
if (needsub) {
@@ -511,7 +514,8 @@
continue;
}
}
- ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL);
+ ast_str_substitute_variables_full2(&substr2, 0, c, headp,
+ ast_str_buffer(substr1), NULL, use_both);
finalvars = ast_str_buffer(substr2);
} else {
finalvars = ast_str_buffer(substr1);
@@ -520,9 +524,13 @@
parse_variable_name(finalvars, &offset, &offset2, &isfunction);
if (isfunction) {
/* Evaluate function */
- if (c || !headp) {
+ res = -1;
+ if (c) {
res = ast_func_read2(c, finalvars, &substr3, 0);
- } else {
+ ast_debug(2, "Function %s result is '%s' from channel\n",
+ finalvars, res ? "" : ast_str_buffer(substr3));
+ }
+ if (headp && (!c || (c && res == -1 && use_both))) {
struct varshead old;
struct ast_channel *bogus;
@@ -538,13 +546,22 @@
ast_log(LOG_ERROR, "Unable to allocate bogus channel for function value substitution.\n");
res = -1;
}
+ ast_debug(2, "Function %s result is '%s' from headp\n",
+ finalvars, res ? "" : ast_str_buffer(substr3));
}
- ast_debug(2, "Function %s result is '%s'\n",
- finalvars, res ? "" : ast_str_buffer(substr3));
} else {
- /* Retrieve variable value */
- ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
- res = 0;
+ const char *result;
+ if (c) {
+ result = ast_str_retrieve_variable(&substr3, 0, c, NULL, finalvars);
+ ast_debug(2, "Variable %s result is '%s' from channel\n",
+ finalvars, S_OR(result, ""));
+ }
+ if (headp && (!c || (c && !result && use_both))) {
+ result = ast_str_retrieve_variable(&substr3, 0, NULL, headp, finalvars);
+ ast_debug(2, "Variable %s result is '%s' from headp\n",
+ finalvars, S_OR(result, ""));
+ }
+ res = (result ? 0 : -1);
}
if (!res) {
ast_str_substring(substr3, offset, offset2);
@@ -596,7 +613,8 @@
continue;
}
}
- ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL);
+ ast_str_substitute_variables_full2(&substr2, 0, c, headp,
+ ast_str_buffer(substr1), NULL, use_both);
finalvars = ast_str_buffer(substr2);
} else {
finalvars = ast_str_buffer(substr1);
@@ -616,6 +634,12 @@
ast_free(substr3);
}
+void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen,
+ struct ast_channel *chan, struct varshead *headp, const char *templ, size_t *used)
+{
+ ast_str_substitute_variables_full2(buf, maxlen, chan, headp, templ, used, 0);
+}
+
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
{
ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, NULL);
diff --git a/main/xml.c b/main/xml.c
index 987f125..d244e4e 100644
--- a/main/xml.c
+++ b/main/xml.c
@@ -361,6 +361,11 @@
return xmlDocDump(output, (xmlDocPtr)doc);
}
+void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length)
+{
+ xmlDocDumpFormatMemory((xmlDocPtr)doc, (xmlChar **)buffer, length, 1);
+}
+
const char *ast_xml_node_get_name(struct ast_xml_node *node)
{
return (const char *) ((xmlNode *) node)->name;
diff --git a/res/Makefile b/res/Makefile
index 6abdd2d..b9d1b98 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -70,7 +70,7 @@
$(call MOD_ADD_C,res_geolocation,$(wildcard res_geolocation/*.c))
# These are the xml and xslt files to be embedded
-res_geolocation.so: res_geolocation/pidf_lo_test.o res_geolocation/pidf_to_eprofile.o
+res_geolocation.so: res_geolocation/pidf_lo_test.o res_geolocation/pidf_to_eprofile.o res_geolocation/eprofile_to_pidf.o
res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
snmp/agent.o: _ASTCFLAGS+=-fPIC
diff --git a/res/res_geolocation/eprofile_to_pidf.xslt b/res/res_geolocation/eprofile_to_pidf.xslt
new file mode 100644
index 0000000..c7a8888
--- /dev/null
+++ b/res/res_geolocation/eprofile_to_pidf.xslt
@@ -0,0 +1,238 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.1"
+ xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+ xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+ xmlns:fn="http://www.w3.org/2005/xpath-functions"
+ xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+ xmlns:gs="http://www.opengis.net/pidflo/1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:date="http://exslt.org/dates-and-times">
+
+ <xsl:output method="xml" indent="yes"/>
+ <xsl:strip-space elements="*"/>
+
+ <!-- REMINDER: The "match" and "select" xpaths refer to the input document,
+ not the output document -->
+
+ <xsl:template match="presence">
+ <!-- xslt will take care of adding all of the namespace declarations
+ from the list above -->
+ <presence xmlns="urn:ietf:params:xml:ns:pidf" entity="{@entity}">
+ <xsl:apply-templates select="./device|tuple|person"/>
+ </presence>
+ </xsl:template>
+
+ <xsl:template match="device">
+ <dm:device>
+ <gp:geopriv>
+ <xsl:apply-templates select="./location-info"/>
+ <xsl:apply-templates select="./usage-rules"/>
+ <xsl:apply-templates select="./method"/>
+ <xsl:apply-templates select="./note-well"/>
+ </gp:geopriv>
+ <xsl:if test="./timestamp">
+ <dm:timestamp>
+ <xsl:value-of select="./timestamp"/>
+ </dm:timestamp>
+ </xsl:if>
+ <xsl:if test="./deviceID">
+ <dm:deviceID>
+ <xsl:value-of select="./deviceID"/>
+ </dm:deviceID>
+ </xsl:if>
+ </dm:device>
+ </xsl:template>
+
+ <xsl:template match="tuple">
+ <xsl:element name="tuple" namespace="urn:ietf:params:xml:ns:pidf">
+ <xsl:element name="status" namespace="urn:ietf:params:xml:ns:pidf">
+ <gp:geopriv>
+ <xsl:apply-templates select="./location-info"/>
+ <xsl:apply-templates select="./usage-rules"/>
+ <xsl:apply-templates select="./method"/>
+ <xsl:apply-templates select="./note-well"/>
+ </gp:geopriv>
+ </xsl:element>
+ <xsl:if test="./timestamp">
+ <xsl:element name="timestamp" namespace="urn:ietf:params:xml:ns:pidf">
+ <xsl:value-of select="./timestamp"/>
+ </xsl:element>
+ </xsl:if>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="person">
+ <dm:person>
+ <gp:geopriv>
+ <xsl:apply-templates select="./location-info"/>
+ <xsl:apply-templates select="./usage-rules"/>
+ <xsl:apply-templates select="./method"/>
+ <xsl:apply-templates select="./note-well"/>
+ </gp:geopriv>
+ <xsl:if test="./timestamp">
+ <dm:timestamp>
+ <xsl:value-of select="./timestamp"/>
+ </dm:timestamp>
+ </xsl:if>
+ </dm:person>
+ </xsl:template>
+
+ <xsl:template match="location-info">
+ <gp:location-info>
+ <xsl:apply-templates/>
+ </gp:location-info>
+ </xsl:template>
+
+ <!-- When we're using the civicAddress format, the translation is simple.
+ We add gp:location-info and ca:civicAddress, then we just copy in
+ each element, adding the "ca" namespace -->
+
+ <xsl:template match="civicAddress/*">
+ <xsl:element name="ca:{name()}">
+ <xsl:value-of select="."/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="location-info/civicAddress">
+ <ca:civicAddress xml:lang="{@lang}">
+ <xsl:apply-templates/>
+ </ca:civicAddress>
+ </xsl:template>
+
+ <!-- When we're using the GML format, things get more complex -->
+ <xsl:template match="location-info/Point|Polygon|Circle|Ellipse|ArcBand|Sphere|Ellipsoid|Prism">
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <!-- All GML shapes share common processing for the "srsName" attribute -->
+ <xsl:template name="shape">
+ <xsl:choose>
+ <xsl:when test="@crs = '3d'">
+ <xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4979</xsl:attribute>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4326</xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- The GML shapes themselves. They don't all have the same namespace unfortunately... -->
+
+ <xsl:template match="Point">
+ <gml:Point>
+ <xsl:call-template name="shape"/>
+ <xsl:apply-templates select="./*"/>
+ </gml:Point>
+ </xsl:template>
+
+ <xsl:template match="Circle|Ellipse|ArcBand|Sphere|Ellipsoid">
+ <xsl:element name="gs:{name()}">
+ <xsl:call-template name="shape"/>
+ <xsl:apply-templates select="./*"/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- ... and some are more complex than others. -->
+
+ <xsl:template match="Polygon">
+ <gml:Polygon>
+ <xsl:call-template name="shape"/>
+ <gml:exterior>
+ <gml:LinearRing>
+ <xsl:apply-templates select="./pos|posList"/>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </xsl:template>
+
+ <!-- Prism with a Polygon and height -->
+ <xsl:template match="Prism">
+ <gs:Prism>
+ <xsl:call-template name="shape"/>
+ <gs:base>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <xsl:apply-templates select="./pos|posList"/>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gs:base>
+ <xsl:apply-templates select="./height"/>
+ </gs:Prism>
+ </xsl:template>
+
+ <!-- method has no children so we add the "gp" namespace and copy in the value -->
+ <xsl:template match="method">
+ <gp:method>
+ <xsl:value-of select="."/>
+ </gp:method>
+ </xsl:template>
+
+ <!-- note-well has no children so we add the "gp" namespace and copy in the value -->
+ <xsl:template match="note-well">
+ <gp:note-well>
+ <xsl:value-of select="."/>
+ </gp:note-well>
+ </xsl:template>
+
+ <!-- usage-rules does have children so we add the "gp" namespace and copy in
+ the children, also adding the "gp" namespace -->
+ <xsl:template match="usage-rules">
+ <gp:usage-rules>
+ <xsl:for-each select="*">
+ <xsl:element name="gp:{local-name()}">
+ <xsl:value-of select="."/>
+ </xsl:element>
+ </xsl:for-each>
+ </gp:usage-rules>
+ </xsl:template>
+
+ <!-- These are the GML format primitives -->
+
+ <xsl:template name="name-value">
+ <xsl:element name="gml:{name()}">
+ <xsl:value-of select="."/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template name="length">
+ <xsl:element name="gs:{name()}">
+ <xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9001</xsl:attribute>
+ <xsl:value-of select="."/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template name="angle">
+ <xsl:element name="gs:{name()}">
+ <xsl:choose>
+ <xsl:when test="@uom = 'radians'">
+ <xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="."/>
+ </xsl:element>
+ </xsl:template>
+
+ <!-- These are the GML shape parameters -->
+
+ <xsl:template match="orientation"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="radius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="height"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="verticalAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="innerRadius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="outerRadius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="startAngle"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="openingAngle"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="pos"><xsl:call-template name="name-value" /></xsl:template>
+ <xsl:template match="posList"><xsl:call-template name="name-value" /></xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/res/res_geolocation/geoloc_civicaddr.c b/res/res_geolocation/geoloc_civicaddr.c
index 6816ebd..9a114cd 100644
--- a/res/res_geolocation/geoloc_civicaddr.c
+++ b/res/res_geolocation/geoloc_civicaddr.c
@@ -20,6 +20,7 @@
#include "asterisk/config.h"
#include "asterisk/cli.h"
#include "asterisk/res_geolocation.h"
+#include "asterisk/xml.h"
#include "geoloc_private.h"
struct addr_field_entry {
@@ -93,16 +94,16 @@
struct addr_field_entry key = { .name = name };
struct addr_field_entry *entry = bsearch(&key, addr_name_code_entries, ARRAY_LEN(addr_name_code_entries),
sizeof(struct addr_field_entry), compare_civicaddr_names);
- return entry ? entry->code : NULL;
+ return entry ? entry->code : name;
}
const char *ast_geoloc_civicaddr_resolve_variable(const char *variable)
{
- const char *result = ast_geoloc_civicaddr_get_name_from_code(variable);
+ const char *result = ast_geoloc_civicaddr_get_code_from_name(variable);
if (result) {
return result;
}
- return ast_geoloc_civicaddr_get_code_from_name(variable);
+ return ast_geoloc_civicaddr_get_name_from_code(variable);
}
enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
@@ -149,6 +150,54 @@
AST_CLI_DEFINE(handle_civicaddr_show, "Show the mappings between civicAddress official codes and synonyms"),
};
+struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
+ const char *ref_string)
+{
+ char *lang = NULL;
+ char *s = NULL;
+ struct ast_variable *var;
+ struct ast_xml_node *ca_node;
+ struct ast_xml_node *child_node;
+ int rc = 0;
+ SCOPE_ENTER(3, "%s", ref_string);
+
+ lang = (char *)ast_variable_find_in_list(resolved_location, "lang");
+ if (ast_strlen_zero(lang)) {
+ lang = ast_strdupa(ast_defaultlanguage);
+ for (s = lang; *s; s++) {
+ if (*s == '_') {
+ *s = '-';
+ }
+ }
+ }
+
+ ca_node = ast_xml_new_node("civicAddress");
+ if (!ca_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'civicAddress' XML node\n", ref_string);
+ }
+ rc = ast_xml_set_attribute(ca_node, "lang", lang);
+ if (rc != 0) {
+ ast_xml_free_node(ca_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'lang' XML attribute\n", ref_string);
+ }
+
+ for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
+ const char *n;
+ if (ast_strings_equal(var->name, "lang")) {
+ continue;
+ }
+ n = ast_geoloc_civicaddr_get_code_from_name(var->name);
+ child_node = ast_xml_new_child(ca_node, n);
+ if (!child_node) {
+ ast_xml_free_node(ca_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", n, ref_string);
+ }
+ ast_xml_set_text(child_node, var->value);
+ }
+
+ SCOPE_EXIT_RTN_VALUE(ca_node, "%s: Done\n", ref_string);
+}
+
int geoloc_civicaddr_unload(void)
{
ast_cli_unregister_multiple(geoloc_civicaddr_cli, ARRAY_LEN(geoloc_civicaddr_cli));
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
index 2350463..daefcdb 100644
--- a/res/res_geolocation/geoloc_config.c
+++ b/res/res_geolocation/geoloc_config.c
@@ -102,6 +102,7 @@
switch (location->format) {
case AST_GEOLOC_FORMAT_NONE:
+ case AST_GEOLOC_FORMAT_LAST:
ast_log(LOG_ERROR, "Location '%s' must have a format\n", location_id);
return -1;
case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
@@ -134,6 +135,18 @@
break;
}
+ if (!ast_strlen_zero(location->location_source)) {
+ struct ast_sockaddr loc_source_addr;
+ int rc = ast_sockaddr_parse(&loc_source_addr, location->location_source, PARSE_PORT_FORBID);
+ if (rc == 1) {
+ ast_log(LOG_ERROR, "Geolocation location '%s' location_source '%s' must be a FQDN."
+ " RFC8787 expressly forbids IP addresses.\n",
+ location_id, location->location_source);
+ return -1;
+ }
+ }
+
+
return 0;
}
@@ -165,6 +178,7 @@
if (profile->location_refinement) {
switch (location->format) {
case AST_GEOLOC_FORMAT_NONE:
+ case AST_GEOLOC_FORMAT_LAST:
break;
case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
result = ast_geoloc_civicaddr_validate_varlist(profile->location_refinement, &failed);
@@ -433,6 +447,7 @@
struct ast_str *refinement_str = NULL;
struct ast_str *variables_str = NULL;
struct ast_str *resolved_str = NULL;
+ struct ast_str *usage_rules_str = NULL;
struct ast_geoloc_eprofile *eprofile = ast_geoloc_eprofile_create_from_profile(profile);
ao2_ref(profile, -1);
@@ -443,20 +458,24 @@
refinement_str = ast_variable_list_join(eprofile->location_refinement, ",", "=", "\"", NULL);
variables_str = ast_variable_list_join(eprofile->location_variables, ",", "=", "\"", NULL);
+ usage_rules_str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "\"", NULL);
action_to_str(eprofile, NULL, &action);
ast_cli(a->fd,
- "id: %-s\n"
- "received_location_disposition: %-s\n"
- "send_location: %-s\n"
- "pidf_element: %-s\n"
- "location_reference: %-s\n"
- "Location_format: %-s\n"
- "location_reference_details: %-s\n"
- "location_refinement: %-s\n"
- "location_variables: %-s\n"
- "effective_location: %-s\n\n",
+ "id: %-s\n"
+ "action: %-s\n"
+ "send_location: %-s\n"
+ "pidf_element: %-s\n"
+ "location_reference: %-s\n"
+ "Location_format: %-s\n"
+ "location_details: %-s\n"
+ "location_method: %-s\n"
+ "location_refinement: %-s\n"
+ "location_variables: %-s\n"
+ "effective_location: %-s\n"
+ "usage_rules: %-s\n"
+ "notes: %-s\n",
eprofile->id,
action,
eprofile->send_location ? "yes" : "no",
@@ -464,9 +483,13 @@
S_OR(eprofile->location_reference, "<none>"),
format_names[eprofile->format],
S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
+ S_OR(eprofile->method, "<none>"),
S_COR(refinement_str, ast_str_buffer(refinement_str), "<none>"),
S_COR(variables_str, ast_str_buffer(variables_str), "<none>"),
- S_COR(resolved_str, ast_str_buffer(resolved_str), "<none>"));
+ S_COR(resolved_str, ast_str_buffer(resolved_str), "<none>"),
+ S_COR(usage_rules_str, ast_str_buffer(usage_rules_str), "<none>"),
+ S_OR(eprofile->notes, "<none>")
+ );
ao2_ref(eprofile, -1);
ast_free(action);
@@ -474,6 +497,7 @@
ast_free(refinement_str);
ast_free(variables_str);
ast_free(resolved_str);
+ ast_free(usage_rules_str);
count++;
}
ao2_iterator_destroy(&iter);
@@ -575,6 +599,11 @@
format_handler, format_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "location_info", NULL,
location_info_handler, location_info_to_str, location_info_dup, 0, 0);
+ ast_sorcery_object_field_register(geoloc_sorcery, "location", "location_source", "", OPT_STRINGFIELD_T,
+ 0, STRFLDSET(struct ast_geoloc_location, location_source));
+ ast_sorcery_object_field_register(geoloc_sorcery, "location", "method", "", OPT_STRINGFIELD_T,
+ 0, STRFLDSET(struct ast_geoloc_location, method));
+
ast_sorcery_apply_default(geoloc_sorcery, "profile", "config", "geolocation.conf,criteria=type=profile");
if (ast_sorcery_object_register(geoloc_sorcery, "profile", geoloc_profile_alloc, NULL, geoloc_profile_apply_handler)) {
@@ -599,6 +628,8 @@
location_refinement_handler, location_refinement_to_str, location_refinement_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_variables", NULL,
location_variables_handler, location_variables_to_str, location_variables_dup, 0, 0);
+ ast_sorcery_object_field_register(geoloc_sorcery, "profile", "notes", "", OPT_STRINGFIELD_T,
+ 0, STRFLDSET(struct ast_geoloc_profile, notes));
ast_sorcery_load(geoloc_sorcery);
diff --git a/res/res_geolocation/geoloc_datastore.c b/res/res_geolocation/geoloc_datastore.c
index 1c188f5..2ba2204 100644
--- a/res/res_geolocation/geoloc_datastore.c
+++ b/res/res_geolocation/geoloc_datastore.c
@@ -235,7 +235,7 @@
return -1;
}
- AST_VECTOR_REMOVE(&eds->eprofiles, ix, 1);
+ ao2_ref(AST_VECTOR_REMOVE(&eds->eprofiles, ix, 1), -1);
return 0;
}
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
index 70de7bb..149baf1 100644
--- a/res/res_geolocation/geoloc_doc.xml
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -19,22 +19,22 @@
<enum name="civicAddress">
<para>
The
- <literal>location</literal>
+ <literal>location_info</literal>
parameter must contain a comma separated list of IANA codes
- describing this location.
+ describing the civicAddress of this location.
</para>
</enum>
<enum name="GML">
<para>
The
- <literal>location</literal> parameter must contain a comma
+ <literal>location_info</literal> parameter must contain a comma
separated list valid GML elements describing this location.
</para>
</enum>
<enum name="URI">
<para>
The
- <literal>location</literal> parameter must contain a single
+ <literal>location_info</literal> parameter must contain a single
URI parameter which contains an external URI describing this location.
</para>
</enum>
@@ -45,10 +45,26 @@
<synopsis>Location information</synopsis>
<description>
<para>The contents of this parameter are specific to the
- specification type.</para>
+ location <literal>format</literal>.</para>
</description>
</configOption>
- </configObject>
+ <configOption name="location_source" default="">
+ <synopsis>Fully qualified host name</synopsis>
+ <description>
+ <para>This parameter isn't required but if provides, RFC8787 says it MUST be a fully
+ qualified host name. IP addresses are specifically NOT allowed.</para>
+ </description>
+ </configOption>
+ <configOption name="method" default="">
+ <synopsis>Location determination method</synopsis>
+ <description>
+ <para>This is a rarely used field in the specification that would
+ indicate teh method used to determine the location. It could be
+ something like "GPS" or "802.11". Its usage and values should be
+ pre-negotiated with any recipients.</para>
+ </description>
+ </configOption>
+ </configObject>
<configObject name="profile">
<synopsis>Profile</synopsis>
<description>
@@ -89,6 +105,15 @@
<para>xxxx</para>
</description>
</configOption>
+ <configOption name="notes" default="">
+ <synopsis>Notes to be added to the outgoing PIDF-LO document</synopsis>
+ <description>
+ <para>The specification of this parameter will cause a
+ <literal><note-well></literal> element to be added to the
+ outgoing PIDF-LO document. Its usage should be pre-negotiated with
+ any recipients.</para>
+ </description>
+ </configOption>
<configOption name="action" default="no">
<synopsis>Determine whether the location information supplied to a
channel should be used</synopsis>
@@ -182,7 +207,7 @@
</para></parameter>
</syntax>
<description>
- <para>This application deleted a Geolocation Profile from a channel.</para>
+ <para>This application deletes a Geolocation Profile from a channel.</para>
<para>The following variable is set:</para>
<variablelist>
<variable name="GEOLOC_PROFILE_COUNT">
@@ -191,6 +216,5 @@
</variablelist>
</description>
</application>
-
</docs>
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
index 26c9476..57f8ab4 100644
--- a/res/res_geolocation/geoloc_eprofile.c
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -17,6 +17,8 @@
*/
#include "asterisk.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
#include "asterisk/xml.h"
#include "geoloc_private.h"
@@ -28,7 +30,12 @@
extern const uint8_t _binary_res_geolocation_pidf_lo_test_xml_end[];
static size_t pidf_lo_test_xml_size;
-static struct ast_xslt_doc *pidf_lo_xslt;
+extern const uint8_t _binary_res_geolocation_eprofile_to_pidf_xslt_start[];
+extern const uint8_t _binary_res_geolocation_eprofile_to_pidf_xslt_end[];
+static size_t eprofile_to_pidf_xslt_size;
+
+static struct ast_xslt_doc *eprofile_to_pidf_xslt;
+static struct ast_xslt_doc *pidf_to_eprofile_xslt;
static struct ast_sorcery *geoloc_sorcery;
@@ -91,6 +98,7 @@
eprofile->format = loc->format;
rc = DUP_VARS(temp_locinfo, loc->location_info);
+ ast_string_field_set(eprofile, method, loc->method);
ao2_ref(loc, -1);
if (rc != 0) {
return -1;
@@ -145,11 +153,16 @@
}
ao2_lock(profile);
- ast_string_field_set(eprofile, location_reference, profile->location_reference);
eprofile->geolocation_routing = profile->geolocation_routing;
eprofile->pidf_element = profile->pidf_element;
- rc = DUP_VARS(eprofile->location_refinement, profile->location_refinement);
+ rc = ast_string_field_set(eprofile, location_reference, profile->location_reference);
+ if (rc == 0) {
+ ast_string_field_set(eprofile, notes, profile->notes);
+ }
+ if (rc == 0) {
+ rc = DUP_VARS(eprofile->location_refinement, profile->location_refinement);
+ }
if (rc == 0) {
rc = DUP_VARS(eprofile->location_variables, profile->location_variables);
}
@@ -174,10 +187,41 @@
return eprofile;
}
+static int set_loc_src(struct ast_geoloc_eprofile *eprofile, const char *uri, const char *ref_str)
+{
+ char *local_uri = ast_strdupa(uri);
+ char *loc_src = NULL;
+
+ loc_src = strchr(local_uri, ';');
+ if (loc_src) {
+ loc_src = '\0';
+ loc_src++;
+ }
+
+ if (!ast_strlen_zero(loc_src)) {
+ if (ast_begins_with(loc_src, "loc-src=")) {
+ struct ast_sockaddr loc_source_addr;
+ int rc = 0;
+ loc_src += 8;
+ rc = ast_sockaddr_parse(&loc_source_addr, loc_src, PARSE_PORT_FORBID);
+ if (rc == 1) {
+ ast_log(LOG_WARNING, "%s: URI '%s' has an invalid 'loc-src' parameter."
+ " RFC8787 states that IP addresses MUST be dropped.\n",
+ ref_str, uri);
+ return -1;
+ } else {
+ ast_string_field_set(eprofile, location_source, loc_src);
+ }
+ }
+ }
+ return 0;
+}
+
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
- const char *reference_string)
+ const char *ref_str)
{
struct ast_geoloc_eprofile *eprofile = NULL;
+ char *ra = NULL;
char *local_uri;
if (ast_strlen_zero(uri)) {
@@ -188,9 +232,11 @@
if (local_uri[0] == '<') {
local_uri++;
}
- if (ast_ends_with(local_uri, ">")) {
- local_uri[strlen(local_uri)-1] = '\0';
+ ra = strchr(local_uri, '>');
+ if (ra) {
+ *ra = '\0';
}
+
ast_strip(local_uri);
eprofile = ast_geoloc_eprofile_alloc(local_uri);
@@ -198,12 +244,195 @@
return NULL;
}
+ set_loc_src(eprofile, uri, ref_str);
+
eprofile->format = AST_GEOLOC_FORMAT_URI;
eprofile->location_info = ast_variable_new("URI", local_uri, "");
return eprofile;
}
+static struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source,
+ struct ast_variable *variables, struct ast_channel *chan)
+{
+ struct ast_variable *dest = NULL;
+ struct ast_variable *var = NULL;
+ struct varshead *vh = NULL;
+ struct ast_str *buf = ast_str_alloca(256);
+
+ if (!source || !chan) {
+ return NULL;
+ }
+
+ /*
+ * ast_str_substitute_variables does only minimal recursive resolution so we need to
+ * pre-resolve each variable in the "variables" list, then use that result to
+ * do the final pass on the "source" variable list.
+ */
+ if (variables) {
+ var = variables;
+ vh = ast_var_list_create();
+ if (!vh) {
+ return NULL;
+ }
+ for ( ; var; var = var->next) {
+ ast_str_substitute_variables_full2(&buf, 0, chan, vh, var->value, NULL, 1);
+ AST_VAR_LIST_INSERT_TAIL(vh, ast_var_assign(var->name, ast_str_buffer(buf)));
+ ast_str_reset(buf);
+ }
+ }
+
+ var = source;
+ for ( ; var; var = var->next) {
+ struct ast_variable *newvar = NULL;
+ ast_str_substitute_variables_full2(&buf, 0, chan, vh, var->value, NULL, 1);
+ newvar = ast_variable_new(var->name, ast_str_buffer(buf), "");
+ if (!newvar) {
+ ast_variables_destroy(dest);
+ ast_var_list_destroy(vh);
+ return NULL;
+ }
+ ast_variable_list_append(&dest, newvar);
+ ast_str_reset(buf);
+ }
+ ast_var_list_destroy(vh);
+
+ return dest;
+}
+
+
+const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
+ struct ast_channel *chan, struct ast_str **buf, const char *ref_string)
+{
+ const char *uri = NULL;
+ struct ast_variable *resolved = NULL;
+ char *result;
+ int we_created_buf = 0;
+
+ if (!eprofile || !buf) {
+ return NULL;
+ }
+
+ if (eprofile->format != AST_GEOLOC_FORMAT_URI) {
+ ast_log(LOG_ERROR, "%s: '%s' is not a URI profile. It's '%s'\n",
+ ref_string, eprofile->id, geoloc_format_to_name(eprofile->format));
+ return NULL;
+ }
+
+ resolved = geoloc_eprofile_resolve_varlist(eprofile->effective_location,
+ eprofile->location_variables, chan);
+ if (!resolved) {
+ return NULL;
+ }
+
+ uri = ast_variable_find_in_list(resolved, "URI");
+ result = uri ? ast_strdupa(uri) : NULL;
+ ast_variables_destroy(resolved);
+
+ if (ast_strlen_zero(result)) {
+ ast_log(LOG_ERROR, "%s: '%s' is a URI profile but had no, or an empty, 'URI' entry in location_info\n",
+ ref_string, eprofile->id);
+ return NULL;
+ }
+
+ if (!*buf) {
+ *buf = ast_str_create(256);
+ if (!*buf) {
+ return NULL;
+ }
+ we_created_buf = 1;
+ }
+
+ if (ast_str_append(buf, 0, "%s", result) <= 0) {
+ if (we_created_buf) {
+ ast_free(*buf);
+ *buf = NULL;
+ return NULL;
+ }
+ }
+
+ return ast_str_buffer(*buf);
+}
+
+static struct ast_variable *var_list_from_node(struct ast_xml_node *node, const char *reference_string)
+{
+ struct ast_variable *list = NULL;
+ struct ast_xml_node *container;
+ struct ast_xml_node *child;
+ struct ast_variable *var;
+ struct ast_str *buf = NULL;
+ char *dup;
+ SCOPE_ENTER(3, "%s\n", reference_string);
+
+ container = ast_xml_node_get_children(node);
+ for (child = container; child; child = ast_xml_node_get_next(child)) {
+ const char *name = ast_xml_node_get_name(child);
+ const char *value = ast_xml_get_text(child);
+ const char *uom = ast_xml_get_attribute(child, "uom");
+
+ if (uom) {
+ char *newval = ast_malloc(strlen(value) + strlen(uom) + 1);
+ sprintf(newval, "%s %s", value, uom);
+ var = ast_variable_new(name, newval, "");
+ ast_free(newval);
+ } else {
+ var = ast_variable_new(name, value, "");
+ }
+
+ if (!var) {
+ ast_variables_destroy(list);
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+ }
+ ast_variable_list_append(&list, var);
+ }
+
+ ast_variable_list_join(list, ", ", "=", "\"", &buf);
+ dup = ast_strdupa(ast_str_buffer(buf));
+ ast_free(buf);
+
+ SCOPE_EXIT_RTN_VALUE(list, "%s: Done. %s\n", reference_string, dup);
+}
+
+static struct ast_variable *var_list_from_loc_info(struct ast_xml_node *locinfo,
+ enum ast_geoloc_format format, const char *reference_string)
+{
+ struct ast_variable *list = NULL;
+ struct ast_xml_node *container;
+ struct ast_variable *var;
+ struct ast_str *buf = NULL;
+ char *dup;
+ SCOPE_ENTER(3, "%s\n", reference_string);
+
+ container = ast_xml_node_get_children(locinfo);
+ if (format == AST_GEOLOC_FORMAT_CIVIC_ADDRESS) {
+ var = ast_variable_new("lang", ast_xml_get_attribute(container, "lang"), "");
+ if (!var) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+ }
+ ast_variable_list_append(&list, var);
+ } else {
+ var = ast_variable_new("shape", ast_xml_node_get_name(container), "");
+ if (!var) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+ }
+ ast_variable_list_append(&list, var);
+ var = ast_variable_new("crs", ast_xml_get_attribute(container, "srsName"), "");
+ if (!var) {
+ ast_variables_destroy(list);
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+ }
+ ast_variable_list_append(&list, var);
+ }
+
+ ast_variable_list_append(&list, var_list_from_node(container, reference_string));
+
+ ast_variable_list_join(list, ", ", "=", "\"", &buf);
+ dup = ast_strdupa(ast_str_buffer(buf));
+ ast_free(buf);
+
+ SCOPE_EXIT_RTN_VALUE(list, "%s: Done. %s\n", reference_string, dup);
+}
+
static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
struct ast_xml_doc *result_doc,
const char *reference_string)
@@ -214,65 +443,69 @@
struct ast_xml_node *location_info = NULL;
struct ast_xml_node *usage_rules = NULL;
struct ast_xml_node *method = NULL;
+ struct ast_xml_node *note_well = NULL;
+ char *doc_str;
+ int doc_len;
const char *id;
const char *format_str;
const char *pidf_element_str;
- const char *location_str;
- const char *usage_str;
const char *method_str;
- char *duped;
+ const char *note_well_str;
+ SCOPE_ENTER(3, "%s\n", reference_string);
+
+ ast_xml_doc_dump_memory(result_doc, &doc_str, &doc_len);
+ ast_trace(5, "xslt result doc:\n%s\n", doc_str);
+ ast_xml_free_text(doc_str);
presence = ast_xml_get_root(result_doc);
pidf_element = ast_xml_node_get_children(presence);
location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
usage_rules = ast_xml_find_child_element(pidf_element, "usage-rules", NULL, NULL);
method = ast_xml_find_child_element(pidf_element, "method", NULL, NULL);
+ note_well = ast_xml_find_child_element(pidf_element, "note-well", NULL, NULL);
id = S_OR(ast_xml_get_attribute(pidf_element, "id"), ast_xml_get_attribute(presence, "entity"));
eprofile = ast_geoloc_eprofile_alloc(id);
if (!eprofile) {
- return NULL;
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
}
format_str = ast_xml_get_attribute(location_info, "format");
- if (ast_strings_equal(format_str, "gml")) {
+ if (strcasecmp(format_str, "gml") == 0) {
eprofile->format = AST_GEOLOC_FORMAT_GML;
- } else if (ast_strings_equal(format_str, "civicAddress")) {
+ } else if (strcasecmp(format_str, "civicAddress") == 0) {
eprofile->format = AST_GEOLOC_FORMAT_CIVIC_ADDRESS;
} else {
ao2_ref(eprofile, -1);
- ast_log(LOG_ERROR, "%s: Unknown format '%s'\n", reference_string, format_str);
- return NULL;
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unknown format '%s'\n", reference_string, format_str);
}
- pidf_element_str = ast_xml_get_attribute(pidf_element, "name");
+ pidf_element_str = ast_xml_node_get_name(pidf_element);
eprofile->pidf_element = geoloc_pidf_element_str_to_enum(pidf_element_str);
- location_str = ast_xml_get_text(location_info);
- duped = ast_strdupa(location_str);
- eprofile->location_info = ast_variable_list_from_string(duped, ",", "=", "\"");
+ eprofile->location_info = var_list_from_loc_info(location_info, eprofile->format, reference_string);
if (!eprofile->location_info) {
ao2_ref(eprofile, -1);
- ast_log(LOG_ERROR, "%s: Unable to create location variables from '%s'\n", reference_string, location_str);
- return NULL;
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR,
+ "%s: Unable to create location variables\n", reference_string);
}
- usage_str = ast_xml_get_text(usage_rules);
- duped = ast_strdupa(usage_str);
- eprofile->usage_rules = ast_variable_list_from_string(duped, ",", "=", "\"");
+ eprofile->usage_rules = var_list_from_node(usage_rules, reference_string);
method_str = ast_xml_get_text(method);
ast_string_field_set(eprofile, method, method_str);
- return eprofile;
+ note_well_str = ast_xml_get_text(note_well);
+ ast_string_field_set(eprofile, notes, note_well_str);
+
+ SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", reference_string);
}
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
- struct ast_xml_doc *pidf_xmldoc, const char *reference_string)
+ struct ast_xml_doc *pidf_xmldoc, const char *geoloc_uri, const char *ref_str)
{
RAII_VAR(struct ast_xml_doc *, result_doc, NULL, ast_xml_close);
struct ast_geoloc_eprofile *eprofile;
-
/*
* The namespace prefixes used here (dm, def, gp, etc) don't have to match
* the ones used in the received PIDF-LO document but they MUST match the
@@ -292,28 +525,30 @@
const char *find_device[] = { "path", "/def:presence/dm:device[.//gp:location-info][1]", NULL};
const char *find_tuple[] = { "path", "/def:presence/def:tuple[.//gp:location-info][1]", NULL};
const char *find_person[] = { "path", "/def:presence/dm:person[.//gp:location-info][1]", NULL};
+ SCOPE_ENTER(3, "%s\n", ref_str);
- result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_device);
+
+ result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, find_device);
if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
ast_xml_close(result_doc);
- result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_tuple);
+ result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, find_tuple);
}
if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
ast_xml_close(result_doc);
- result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, find_person);
+ result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, find_person);
}
if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
- return NULL;
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Not a PIDF-LO. Skipping.\n", ref_str);
}
/*
* The document returned from the stylesheet application looks like this...
* <presence id="presence-entity">
- * <pidf-element name="tuple" id="element-id">
- * <location-info format="gml">format="gml", type="Ellipsoid", crs="3d", ...</location-info>
+ * <tuple id="element-id">
+ * <location-info format="gml">shape="Ellipsoid", crs="3d", ...</location-info>
* <usage-rules>retransmission-allowed="no", retention-expiry="2010-11-14T20:00:00Z"</usage-rules>
* <method>Hybrid_A-GPS</method>
- * </pidf-element>
+ * </tuple>
* </presence>
*
* Regardless of whether the pidf-element was tuple, device or person and whether
@@ -325,9 +560,237 @@
* ast_variable_list_from_string().
*/
- eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, reference_string);
+ eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, ref_str);
- return eprofile;
+ if (geoloc_uri) {
+ set_loc_src(eprofile, geoloc_uri, ref_str);
+ }
+
+ SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", ref_str);
+}
+
+/*!
+ * \internal
+ * \brief Create an common, intermediate XML document to pass to the outgoing XSLT process
+ *
+ * \param eprofile The eprofile
+ * \param chan The channel to resolve variables against
+ * \param ref_string A reference string for error messages
+ * \return An XML doc
+ *
+ * \note Given that the document is simple and static, it was easier to just
+ * create the elements in a string buffer and call ast_xml_read_memory()
+ * at the end instead of creating
+ *
+ */
+static struct ast_xml_node *geoloc_eprofile_to_intermediate(const char *element_name, struct ast_geoloc_eprofile *eprofile,
+ struct ast_channel *chan, const char *ref_string)
+{
+ struct ast_variable *resolved_location = NULL;
+ struct ast_variable *resolved_usage = NULL;
+ struct ast_variable *var = NULL;
+ RAII_VAR(struct ast_xml_node *, pidf_node, NULL, ast_xml_free_node);
+ struct ast_xml_node *rtn_pidf_node;
+ struct ast_xml_node *loc_node;
+ struct ast_xml_node *info_node;
+ struct ast_xml_node *rules_node;
+ struct ast_xml_node *method_node;
+ struct ast_xml_node *notes_node;
+ struct ast_xml_node *timestamp_node;
+ struct timeval tv = ast_tvnow();
+ struct tm tm = { 0, };
+ char timestr[32] = { 0, };
+ int rc = 0;
+
+ SCOPE_ENTER(3, "%s\n", ref_string);
+
+ if (!eprofile || !chan) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Either or both eprofile or chan were NULL\n", ref_string);
+ }
+
+ pidf_node = ast_xml_new_node(element_name);
+ if (!pidf_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'pidf' XML node\n",
+ ref_string);
+ }
+
+ loc_node = ast_xml_new_child(pidf_node, "location-info");
+ if (!loc_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'location-info' XML node\n",
+ ref_string);
+ }
+ rc = ast_xml_set_attribute(loc_node, "format", geoloc_format_to_name(eprofile->format));
+ if (rc != 0) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'format' XML attribute\n", ref_string);
+ }
+
+ resolved_location = geoloc_eprofile_resolve_varlist(eprofile->effective_location,
+ eprofile->location_variables, chan);
+ if (eprofile->format == AST_GEOLOC_FORMAT_CIVIC_ADDRESS) {
+ info_node = geoloc_civicaddr_list_to_xml(resolved_location, ref_string);
+ } else {
+ info_node = geoloc_gml_list_to_xml(resolved_location, ref_string);
+ }
+ if (!info_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create XML from '%s' list\n",
+ ref_string, geoloc_format_to_name(eprofile->format));
+ }
+ if (!ast_xml_add_child(loc_node, info_node)) {
+ ast_xml_free_node(info_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable add '%s' node to XML document\n",
+ ref_string, geoloc_format_to_name(eprofile->format));
+ }
+
+ rules_node = ast_xml_new_child(pidf_node, "usage-rules");
+ if (!rules_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'usage-rules' XML node\n",
+ ref_string);
+ }
+ resolved_usage = geoloc_eprofile_resolve_varlist(eprofile->usage_rules,
+ eprofile->location_variables, chan);
+ for (var = resolved_usage; var; var = var->next) {
+ struct ast_xml_node *ur = ast_xml_new_child(rules_node, var->name);
+ ast_xml_set_text(ur, var->value);
+ }
+
+ if (!ast_strlen_zero(eprofile->method)) {
+ method_node = ast_xml_new_child(pidf_node, "method");
+ if (!method_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'method' XML node\n",
+ ref_string);
+ }
+ ast_xml_set_text(method_node, eprofile->method);
+ };
+
+ if (!ast_strlen_zero(eprofile->notes)) {
+ notes_node = ast_xml_new_child(pidf_node, "note-well");
+ if (!notes_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'note-well' XML node\n",
+ ref_string);
+ }
+ ast_xml_set_text(notes_node, eprofile->notes);
+ };
+
+ gmtime_r(&tv.tv_sec, &tm);
+ strftime(timestr, sizeof(timestr), "%FT%TZ", &tm);
+ timestamp_node = ast_xml_new_child(pidf_node, "timestamp");
+ if (!timestamp_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'timestamp' XML node\n",
+ ref_string);
+ }
+ ast_xml_set_text(timestamp_node, timestr);
+
+ rtn_pidf_node = pidf_node;
+ pidf_node = NULL;
+ SCOPE_EXIT_RTN_VALUE(rtn_pidf_node, "%s: Done\n", ref_string);
+}
+
+#define CREATE_NODE_LIST(node) \
+ if (!node) { \
+ node = ast_xml_new_child(root_node, \
+ geoloc_pidf_element_to_name(eprofile->pidf_element)); \
+ if (!pidfs[eprofile->pidf_element]) { \
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create pidf '%s' XML node\n", \
+ ref_string, geoloc_pidf_element_to_name(eprofile->pidf_element)); \
+ } \
+ }
+
+const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
+ struct ast_channel *chan, struct ast_str **buf, const char * ref_string)
+{
+ RAII_VAR(struct ast_xml_doc *, intermediate, NULL, ast_xml_close);
+ RAII_VAR(struct ast_xml_doc *, pidf_doc, NULL, ast_xml_close);
+ struct ast_xml_node *root_node;
+ struct ast_xml_node *pidfs[AST_PIDF_ELEMENT_LAST] = {NULL, };
+ struct ast_geoloc_eprofile *eprofile;
+ int eprofile_count = 0;
+ int i;
+ RAII_VAR(char *, doc_str, NULL, ast_xml_free_text);
+ int doc_len;
+ int rc = 0;
+ SCOPE_ENTER(3, "%s\n", ref_string);
+
+ if (!ds || !chan || !buf || !*buf || ast_strlen_zero(ref_string)) {
+ SCOPE_EXIT_RTN_VALUE(NULL, "%s: Either or both datastore or chan were NULL\n",
+ ref_string);
+ }
+
+ intermediate = ast_xml_new();
+ if (!intermediate) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create XML document\n", ref_string);
+ }
+ root_node = ast_xml_new_node("presence");
+ if (!root_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create root XML node\n", ref_string);
+ }
+ ast_xml_set_root(intermediate, root_node);
+
+ eprofile_count = ast_geoloc_datastore_size(ds);
+ for (i = 0; i < eprofile_count; i++) {
+ struct ast_xml_node *temp_node = NULL;
+ struct ast_xml_node *curr_loc = NULL;
+ struct ast_xml_node *new_loc = NULL;
+ struct ast_xml_node *new_loc_child = NULL;
+ struct ast_xml_node *new_loc_child_dup = NULL;
+ eprofile = ast_geoloc_datastore_get_eprofile(ds, i);
+ if (eprofile->format == AST_GEOLOC_FORMAT_URI) {
+ continue;
+ }
+
+ if (ast_strlen_zero(ast_xml_get_attribute(root_node, "entity"))) {
+ rc = ast_xml_set_attribute(root_node, "entity", eprofile->id);
+ if (rc != 0) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'entity' XML attribute\n", ref_string);
+ }
+ }
+
+ temp_node = geoloc_eprofile_to_intermediate(geoloc_pidf_element_to_name(eprofile->pidf_element),
+ eprofile, chan, ref_string);
+
+ if (!pidfs[eprofile->pidf_element]) {
+ pidfs[eprofile->pidf_element] = temp_node;
+ ast_xml_add_child(root_node, temp_node);
+ continue;
+ }
+
+ curr_loc = ast_xml_find_child_element(pidfs[eprofile->pidf_element], "location-info", NULL, NULL);
+ new_loc = ast_xml_find_child_element(temp_node, "location-info", NULL, NULL);
+ new_loc_child = ast_xml_node_get_children(new_loc);
+ new_loc_child_dup = ast_xml_copy_node_list(new_loc_child);
+ ast_xml_add_child_list(curr_loc, new_loc_child_dup);
+
+ ast_xml_free_node(temp_node);
+ }
+
+ ast_xml_doc_dump_memory(intermediate, &doc_str, &doc_len);
+ if (doc_len == 0 || !doc_str) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to dump intermediate doc to string\n",
+ ref_string);
+ }
+
+ ast_trace(5, "Intermediate doc:\n%s\n", doc_str);
+
+ pidf_doc = ast_xslt_apply(eprofile_to_pidf_xslt, intermediate, NULL);
+ if (!pidf_doc) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create final PIDF-LO doc from intermediate doc:\n%s\n",
+ ref_string, doc_str);
+ }
+
+ ast_xml_doc_dump_memory(pidf_doc, &doc_str, &doc_len);
+ if (doc_len == 0 || !doc_str) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to dump final PIDF-LO doc to string\n",
+ ref_string);
+ }
+
+ rc = ast_str_set(buf, 0, "%s", doc_str);
+ if (rc <= 0) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to extend buffer (%d)\n",
+ ref_string, rc);
+ }
+
+ ast_trace(5, "Final doc:\n%s\n", ast_str_buffer(*buf));
+
+ SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: Done\n", ref_string);
}
#ifdef TEST_FRAMEWORK
@@ -342,8 +805,12 @@
int geoloc_eprofile_unload(void)
{
unload_tests();
- if (pidf_lo_xslt) {
- ast_xslt_close(pidf_lo_xslt);
+ if (pidf_to_eprofile_xslt) {
+ ast_xslt_close(pidf_to_eprofile_xslt);
+ }
+
+ if (eprofile_to_pidf_xslt) {
+ ast_xslt_close(eprofile_to_pidf_xslt);
}
if (geoloc_sorcery) {
@@ -355,21 +822,32 @@
int geoloc_eprofile_load(void)
{
- geoloc_sorcery = geoloc_get_sorcery();
-
pidf_to_eprofile_xslt_size =
(_binary_res_geolocation_pidf_to_eprofile_xslt_end - _binary_res_geolocation_pidf_to_eprofile_xslt_start);
pidf_lo_test_xml_size =
(_binary_res_geolocation_pidf_lo_test_xml_end - _binary_res_geolocation_pidf_lo_test_xml_start);
- pidf_lo_xslt = ast_xslt_read_memory(
+ pidf_to_eprofile_xslt = ast_xslt_read_memory(
(char *)_binary_res_geolocation_pidf_to_eprofile_xslt_start, pidf_to_eprofile_xslt_size);
- if (!pidf_lo_xslt) {
- ast_log(LOG_ERROR, "Unable to read pidf_lo_xslt from memory\n");
+ if (!pidf_to_eprofile_xslt) {
+ ast_log(LOG_ERROR, "Unable to read pidf_to_eprofile_xslt from memory\n");
return AST_MODULE_LOAD_DECLINE;
}
+ eprofile_to_pidf_xslt_size =
+ (_binary_res_geolocation_eprofile_to_pidf_xslt_end - _binary_res_geolocation_eprofile_to_pidf_xslt_start);
+
+ eprofile_to_pidf_xslt = ast_xslt_read_memory(
+ (char *)_binary_res_geolocation_eprofile_to_pidf_xslt_start, eprofile_to_pidf_xslt_size);
+ if (!eprofile_to_pidf_xslt) {
+ ast_log(LOG_ERROR, "Unable to read eprofile_to_pidf_xslt from memory\n");
+ geoloc_eprofile_unload();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ geoloc_sorcery = geoloc_get_sorcery();
+
load_tests();
return AST_MODULE_LOAD_SUCCESS;
@@ -430,12 +908,12 @@
const char *search[] = { "path", path, NULL };
if (!ast_strlen_zero(path)) {
- result_doc = ast_xslt_apply(pidf_lo_xslt, pidf_xmldoc, (const char **)search);
+ result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, (const char **)search);
ast_test_validate(test, (result_doc && ast_xml_node_get_children((struct ast_xml_node *)result_doc)));
eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, "test_create_from_xslt");
} else {
- eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, "test_create_from_pidf");
+ eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, NULL, "test_create_from_pidf");
}
ast_test_validate(test, eprofile != NULL);
@@ -451,13 +929,15 @@
str = ast_variable_list_join(eprofile->location_info, ",", "=", NULL, NULL);
ast_test_validate(test, str != NULL);
- ast_test_status_update(test, "location_vars: %s\n", ast_str_buffer(str));
+ ast_test_status_update(test, "location_vars expected: %s\n", location);
+ ast_test_status_update(test, "location_vars received: %s\n", ast_str_buffer(str));
ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), location));
ast_free(str);
str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "'", NULL);
ast_test_validate(test, str != NULL);
- ast_test_status_update(test, "usage_rules: %s\n", ast_str_buffer(str));
+ ast_test_status_update(test, "usage_rules expected: %s\n", usage);
+ ast_test_status_update(test, "usage_rules received: %s\n", ast_str_buffer(str));
ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), usage));
return AST_TEST_PASS;
@@ -489,9 +969,8 @@
AST_PIDF_ELEMENT_DEVICE,
AST_GEOLOC_FORMAT_GML,
"TA-NMR",
- "format=gml,type=ArcBand,crs=2d,pos=-43.5723 153.21760,innerRadius=3594,"
- "outerRadius=4148,startAngle=20,startAngle_uom=radians,openingAngle=20,"
- "openingAngle_uom=radians",
+ "shape=ArcBand,crs=2d,pos=-43.5723 153.21760,innerRadius=3594,"
+ "outerRadius=4148,startAngle=20 radians,openingAngle=20 radians",
"retransmission-allowed='yes',ruleset-preference='https:/www/more.com',"
"retention-expires='2007-06-22T20:57:29Z'"
);
@@ -504,7 +983,7 @@
AST_PIDF_ELEMENT_DEVICE,
AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
"GPS",
- "format=civicAddress,country=AU,A1=NSW,A3=Wollongong,A4=North Wollongong,"
+ "lang=en-AU,country=AU,A1=NSW,A3=Wollongong,A4=North Wollongong,"
"RD=Flinders,STS=Street,RDBR=Campbell Street,LMK=Gilligan's Island,"
"LOC=Corner,NAM=Video Rental Store,PC=2500,ROOM=Westerns and Classics,"
"PLC=store,POBOX=Private Box 15",
diff --git a/res/res_geolocation/geoloc_gml.c b/res/res_geolocation/geoloc_gml.c
index 04e363d..8a66162 100644
--- a/res/res_geolocation/geoloc_gml.c
+++ b/res/res_geolocation/geoloc_gml.c
@@ -127,10 +127,10 @@
int def_index = -1;
const struct ast_variable *var;
int i;
- const char *shape_type = ast_variable_find_in_list(varlist, "type");
+ const char *shape_type = ast_variable_find_in_list(varlist, "shape");
if (!shape_type) {
- return AST_GEOLOC_VALIDATE_MISSING_TYPE;
+ return AST_GEOLOC_VALIDATE_MISSING_SHAPE;
}
for (i = 0; i < ARRAY_LEN(gml_shape_defs); i++) {
@@ -139,12 +139,12 @@
}
}
if (def_index < 0) {
- return AST_GEOLOC_VALIDATE_INVALID_TYPE;
+ return AST_GEOLOC_VALIDATE_INVALID_SHAPE;
}
for (var = varlist; var; var = var->next) {
int vname_index = -1;
- if (ast_strings_equal("type", var->name)) {
+ if (ast_strings_equal("shape", var->name)) {
continue;
}
for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
@@ -235,6 +235,113 @@
AST_CLI_DEFINE(handle_gml_show, "Show the GML Shape definitions"),
};
+struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+ const char *ref_string)
+{
+ const char *shape;
+ char *crs;
+ struct ast_variable *var;
+ struct ast_xml_node *gml_node;
+ struct ast_xml_node *child_node;
+ int rc = 0;
+
+ SCOPE_ENTER(3, "%s", ref_string);
+
+ shape = ast_variable_find_in_list(resolved_location, "shape");
+ if (ast_strlen_zero(shape)) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: There's no 'shape' parameter\n",
+ ref_string);
+ }
+ crs = (char *)ast_variable_find_in_list(resolved_location, "crs");
+ if (ast_strlen_zero(crs)) {
+ crs = "2d";
+ }
+
+ gml_node = ast_xml_new_node(shape);
+ if (!gml_node) {
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", shape, ref_string);
+ }
+ rc = ast_xml_set_attribute(gml_node, "crs", crs);
+ if (rc != 0) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'crs' XML attribute\n", ref_string);
+ }
+
+ for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
+ RAII_VAR(char *, value, NULL, ast_free);
+ char *uom = NULL;
+
+ if (ast_strings_equal(var->name, "shape") || ast_strings_equal(var->name, "crs")) {
+ continue;
+ }
+ value = ast_strdup(var->value);
+
+ if (ast_strings_equal(var->name, "orientation") || ast_strings_equal(var->name, "startAngle")
+ || ast_strings_equal(var->name, "openingAngle")) {
+ char *a = NULL;
+ char *junk = NULL;
+ float angle;
+ uom = value;
+
+ /* 'a' should now be the angle and 'uom' should be the uom */
+ a = strsep(&uom, " ");
+ angle = strtof(a, &junk);
+ /*
+ * strtof sets junk to the first non-valid character so if it's
+ * not empty after the conversion, there were unrecognized
+ * characters in the angle. It'll point to the NULL terminator
+ * if angle was completely converted.
+ */
+ if (!ast_strlen_zero(junk)) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: The angle portion of parameter '%s' ('%s') is malformed\n",
+ ref_string, var->name, var->value);
+ }
+
+ if (ast_strlen_zero(uom)) {
+ uom = "degrees";
+ }
+
+ if (ast_begins_with(uom, "deg")) {
+ if (angle > 360.0) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+ "Degrees can't be > 360.0\n",
+ ref_string, var->name, var->value);
+ }
+ } else if (ast_begins_with(uom, "rad")) {
+ if(angle > 100.0) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+ "Radians can't be > 100.0\n",
+ ref_string, var->name, var->value);
+ }
+ } else {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+ "The unit of measure must be 'deg[rees]' or 'rad[ians]'\n",
+ ref_string, var->name, var->value);
+ }
+ }
+
+ child_node = ast_xml_new_child(gml_node, var->name);
+ if (!child_node) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
+ }
+ if (!ast_strlen_zero(uom)) {
+ rc = ast_xml_set_attribute(child_node, "uom", uom);
+ if (rc != 0) {
+ ast_xml_free_node(gml_node);
+ SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'uom' XML attribute\n", ref_string);
+ }
+ }
+ ast_xml_set_text(child_node, value);
+ }
+
+ SCOPE_EXIT_RTN_VALUE(gml_node, "%s: Done\n", ref_string);
+}
+
int geoloc_gml_unload(void)
{
ast_cli_unregister_multiple(geoloc_gml_cli, ARRAY_LEN(geoloc_gml_cli));
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
index 18b51a7..860f3b5 100644
--- a/res/res_geolocation/geoloc_private.h
+++ b/res/res_geolocation/geoloc_private.h
@@ -139,10 +139,14 @@
int geoloc_config_reload(void);
int geoloc_config_unload(void);
+struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
+ const char *ref_string);
int geoloc_civicaddr_load(void);
int geoloc_civicaddr_unload(void);
int geoloc_civicaddr_reload(void);
+struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+ const char *ref_string);
int geoloc_gml_unload(void);
int geoloc_gml_load(void);
int geoloc_gml_reload(void);
diff --git a/res/res_geolocation/pidf_to_eprofile.xslt b/res/res_geolocation/pidf_to_eprofile.xslt
index 409ba9e..4e717ad 100644
--- a/res/res_geolocation/pidf_to_eprofile.xslt
+++ b/res/res_geolocation/pidf_to_eprofile.xslt
@@ -9,57 +9,102 @@
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gs="http://www.opengis.net/pidflo/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
- <!--xsl:output method="text" /-->
- <xsl:strip-space elements="*" />
+
+
+<!--
+ The whole purpose of this stylesheet is to convert a PIDF-LO document into a simple,
+ common XML document that is easily parsable by geoloc_eprofile into an eprofile.
+
+ For example:
+
+ <presence>
+ <device>
+ <location-info format="GML">shape="Point", crs="2d", pos="38.456 -105.678"</location-info>
+ <usage-rules>retransmission-allowed=no</usage-rules>
+ <method>GPS</method>
+ </device>
+ </presence>
+
+ WARNING: Don't mess with this stylesheet before brushing up your
+ XPath and XSLT expertise.
+-->
+
+
+<!--
+ All of the namespaces that cound be in the incoming PIDF-LO document
+ have to be declared above. All matching is done based on the URI, not
+ the prefix so we can use whatever prefixes we want. For instance,
+ even if "urn:ietf:params:xml:ns:pidf:data-model" were declared with
+ the "pdm" prefix in the incoming document and with "dm" here,
+ "dm:device" would match "pdm:device" in the document.
+-->
+
+ <xsl:output method="xml" indent="yes"/>
+ <xsl:strip-space elements="*"/>
<xsl:param name="path"/>
-
- <xsl:template name="name-value">
- <xsl:value-of select="local-name(.)" /><xsl:text>="</xsl:text>
- <xsl:value-of select="normalize-space(.)" /><xsl:text>"</xsl:text>
- <xsl:if test="following-sibling::*"><xsl:text>, </xsl:text></xsl:if>
+
+ <!--
+ Even though the "presence", "tuple", and "status" elements won't have namespaces in the
+ incoming PIDF document, we have to use the pseudo-namespace "def" here because of namespace
+ processing quirks in libxml2 and libxslt.
+
+ We don't use namespace prefixes in the output document at all.
+ -->
+ <xsl:template match="/def:presence">
+ <xsl:element name="presence">
+ <xsl:attribute name="entity"><xsl:value-of select="@entity"/></xsl:attribute>
+ <xsl:apply-templates select="$path"/>
+ </xsl:element>
</xsl:template>
- <xsl:template name="length"><xsl:call-template name="name-value" /></xsl:template>
-
- <xsl:template name="angle">
- <xsl:value-of select="local-name(.)" /><xsl:text>="</xsl:text>
- <xsl:value-of select="normalize-space(.)" /><xsl:text>"</xsl:text><xsl:text>, </xsl:text>
- <xsl:value-of select="local-name(.)" /><xsl:text>_uom="</xsl:text>
- <xsl:choose>
- <xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'"><xsl:text>radians</xsl:text></xsl:when>
- <xsl:otherwise><xsl:text>degrees</xsl:text></xsl:otherwise>
- </xsl:choose>
- <xsl:text>"</xsl:text>
- <xsl:if test="following-sibling::*"><xsl:text>, </xsl:text></xsl:if>
- </xsl:template>
- <xsl:template match="gs:orientation"><xsl:call-template name="angle" /></xsl:template>
- <xsl:template match="gs:radius"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:height"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:verticalAxis"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:innerRadius"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:outerRadius"><xsl:call-template name="length" /></xsl:template>
- <xsl:template match="gs:startAngle"><xsl:call-template name="angle" /></xsl:template>
- <xsl:template match="gs:openingAngle"><xsl:call-template name="angle" /></xsl:template>
- <xsl:template match="gml:pos"><xsl:call-template name="name-value" /></xsl:template>
- <xsl:template match="gml:posList"><xsl:call-template name="name-value" /></xsl:template>
-
- <xsl:template name="shape">
- <xsl:text>format="gml", type="</xsl:text><xsl:value-of select="local-name(.)" /><xsl:text>", </xsl:text>
- <xsl:text>crs="</xsl:text>
- <xsl:choose>
- <xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4326'"><xsl:text>2d</xsl:text></xsl:when>
- <xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4979'"><xsl:text>3d</xsl:text></xsl:when>
- <xsl:otherwise><xsl:text>unknown</xsl:text></xsl:otherwise>
- </xsl:choose>
- <xsl:text>", </xsl:text>
- <xsl:apply-templates />
+ <xsl:template match="dm:device">
+ <xsl:element name="device">
+ <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+ <xsl:apply-templates select=".//gp:location-info"/>
+ <xsl:apply-templates select=".//gp:usage-rules"/>
+ <xsl:apply-templates select=".//gp:method"/>
+ <xsl:apply-templates select=".//gp:note-well"/>
+ <xsl:if test="./dm:timestamp">
+ <timestamp>
+ <xsl:value-of select="./dm:timestamp"/>
+ </timestamp>
+ </xsl:if>
+ <xsl:if test="./dm:deviceID">
+ <deviceID>
+ <xsl:value-of select="./dm:deviceID"/>
+ </deviceID>
+ </xsl:if>
+ </xsl:element>
</xsl:template>
- <xsl:template match="ca:civicAddress/*"><xsl:call-template name="name-value" /> </xsl:template>
- <xsl:template name="civicAddress"><xsl:text>format="civicAddress", </xsl:text>
- <xsl:apply-templates />
+ <xsl:template match="def:tuple">
+ <xsl:element name="tuple">
+ <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+ <xsl:apply-templates select=".//gp:location-info"/>
+ <xsl:apply-templates select=".//gp:usage-rules"/>
+ <xsl:apply-templates select=".//gp:method"/>
+ <xsl:apply-templates select=".//gp:note-well"/>
+ <xsl:if test="./timestamp">
+ <timestamp>
+ <xsl:value-of select="./timestamp"/>
+ </timestamp>
+ </xsl:if>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="dm:person">
+ <xsl:element name="person">
+ <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+ <xsl:apply-templates select=".//gp:location-info"/>
+ <xsl:apply-templates select=".//gp:usage-rules"/>
+ <xsl:apply-templates select=".//gp:method"/>
+ <xsl:apply-templates select=".//gp:note-well"/>
+ <xsl:if test="./dm:timestamp">
+ <timestamp>
+ <xsl:value-of select="./dm:timestamp"/>
+ </timestamp>
+ </xsl:if>
+ </xsl:element>
</xsl:template>
<xsl:template match="gp:location-info/gml:*">
@@ -76,13 +121,77 @@
</xsl:element>
</xsl:template>
- <xsl:template match="gp:location-info/ca:*">
+ <xsl:template match="gp:location-info/ca:civicAddress">
<xsl:element name="location-info">
<xsl:attribute name="format">civicAddress</xsl:attribute>
<xsl:call-template name="civicAddress" />
</xsl:element>
</xsl:template>
+ <!--
+ All of the "following-sibling" things just stick a comma after the value if there's another
+ element after it. The result should be...
+
+ name1="value1", name2="value2"
+ -->
+ <xsl:template name="name-value">
+ <xsl:element name="{local-name(.)}">
+ <xsl:value-of select="normalize-space(.)"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template name="length"><xsl:call-template name="name-value" /></xsl:template>
+
+ <xsl:template name="angle">
+ <xsl:element name="{local-name(.)}">
+ <xsl:choose>
+ <xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'">
+ <xsl:attribute name="uom">radians</xsl:attribute></xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="uom">degrees</xsl:attribute></xsl:otherwise>
+ </xsl:choose>
+ <xsl:value-of select="normalize-space(.)"/>
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="gs:orientation"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="gs:radius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:height"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:verticalAxis"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:innerRadius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:outerRadius"><xsl:call-template name="length" /></xsl:template>
+ <xsl:template match="gs:startAngle"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="gs:openingAngle"><xsl:call-template name="angle" /></xsl:template>
+ <xsl:template match="gml:pos"><xsl:call-template name="name-value" /></xsl:template>
+ <xsl:template match="gml:posList"><xsl:call-template name="name-value" /></xsl:template>
+
+ <xsl:template name="shape">
+ <xsl:element name="{local-name(.)}">
+ <xsl:choose>
+ <xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4326'">
+ <xsl:attribute name="srsName">2d</xsl:attribute>
+ </xsl:when>
+ <xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4979'">
+ <xsl:attribute name="srsName">3d</xsl:attribute>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:attribute name="srsName">unknown</xsl:attribute>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:apply-templates />
+ </xsl:element>
+ </xsl:template>
+
+ <xsl:template match="ca:civicAddress/*"><xsl:call-template name="name-value" /></xsl:template>
+ <xsl:template name="civicAddress">
+ <xsl:element name="{local-name(.)}">
+ <xsl:attribute name="lang"><xsl:value-of select="@xml:lang"/></xsl:attribute>
+ <xsl:apply-templates select="./*"/>
+ </xsl:element>
+ </xsl:template>
+
<xsl:template match="gp:usage-rules/*">
<xsl:call-template name="name-value" />
</xsl:template>
@@ -94,31 +203,10 @@
</xsl:template>
<xsl:template match="gp:method">
+ <xsl:element name="method">
<xsl:value-of select="normalize-space(.)" />
- </xsl:template>
-
- <xsl:template name="topnode">
- <xsl:element name="pidf-element">
- <xsl:attribute name="name"><xsl:value-of select="local-name(.)"/></xsl:attribute>
- <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
- <xsl:apply-templates select=".//gp:location-info"/>
- <xsl:apply-templates select=".//gp:usage-rules"/>
- <xsl:element name="method">
- <xsl:apply-templates select=".//gp:method"/>
- </xsl:element>
- </xsl:element>
- <xsl:text>
-</xsl:text>
- </xsl:template>
-
- <xsl:template match="dm:device"><xsl:call-template name="topnode" /></xsl:template>
- <xsl:template match="def:tuple"><xsl:call-template name="topnode" /></xsl:template>
- <xsl:template match="dm:person"><xsl:call-template name="topnode" /></xsl:template>
-
- <xsl:template match="/">
- <xsl:element name="presence">
- <xsl:attribute name="entity"><xsl:value-of select="/*/@entity"/></xsl:attribute>
- <xsl:apply-templates select="$path"/>
</xsl:element>
</xsl:template>
+
+
</xsl:stylesheet>
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 8900964..c910551 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1825,6 +1825,22 @@
return 0;
}
+pjsip_generic_string_hdr *ast_sip_add_header2(pjsip_tx_data *tdata,
+ const char *name, const char *value)
+{
+ pj_str_t hdr_name;
+ pj_str_t hdr_value;
+ pjsip_generic_string_hdr *hdr;
+
+ pj_cstr(&hdr_name, name);
+ pj_cstr(&hdr_value, value);
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &hdr_name, &hdr_value);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) hdr);
+ return hdr;
+}
+
static pjsip_msg_body *ast_body_to_pjsip_body(pj_pool_t *pool, const struct ast_sip_body *body)
{
pj_str_t type;
diff --git a/res/res_pjsip_geolocation.c b/res/res_pjsip_geolocation.c
index 7555134..0e9eb37 100644
--- a/res/res_pjsip_geolocation.c
+++ b/res/res_pjsip_geolocation.c
@@ -40,6 +40,8 @@
static int find_pidf(const char *session_name, struct pjsip_rx_data *rdata, char *geoloc_uri,
char **pidf_body, unsigned int *pidf_len)
{
+ char *local_uri = ast_strdupa(geoloc_uri);
+ char *ra = NULL;
/*
* If the URI is "cid" then we're going to search for a pidf document
* in the body of the message. If there's no body, there's no point.
@@ -50,6 +52,14 @@
return -1;
}
+ if (local_uri[0] == '<') {
+ local_uri++;
+ }
+ ra = strchr(local_uri, '>');
+ if (ra) {
+ *ra = '\0';
+ }
+
/*
* If the message content type is 'application/pidf+xml', then the pidf is
* the only document in the message and we'll just parse the entire body
@@ -62,7 +72,7 @@
*pidf_len = rdata->msg_info.msg->body->len;
} else if (ast_sip_are_media_types_equal(&rdata->msg_info.ctype->media,
&pjsip_media_type_multipart_mixed)) {
- pj_str_t cid = pj_str(geoloc_uri);
+ pj_str_t cid = pj_str(local_uri);
pjsip_multipart_part *mp = pjsip_multipart_find_part_by_cid_str(
rdata->tp_info.pool, rdata->msg_info.msg->body, &cid);
@@ -229,30 +239,25 @@
* / absoluteURI ; (from RFC 3261)
*/
while((geoloc_uri = ast_strsep(&duped_geoloc_hdr_value, ',', AST_STRSEP_TRIM))) {
- /* geoloc_uri should now be <scheme:location> */
- int uri_len = strlen(geoloc_uri);
+ /* geoloc_uri should now be <scheme:location>[;loc-src=fqdn] */
char *pidf_body = NULL;
unsigned int pidf_len = 0;
struct ast_xml_doc *incoming_doc = NULL;
-
struct ast_geoloc_eprofile *eprofile = NULL;
int rc = 0;
ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
- if (geoloc_uri[0] != '<' || geoloc_uri[uri_len - 1] != '>') {
+ if (geoloc_uri[0] != '<' || strchr(geoloc_uri, '>') == NULL) {
ast_log(LOG_WARNING, "%s: Geolocation header has bad URI '%s'. Skipping\n", session_name,
geoloc_uri);
continue;
}
- /* Trim the trailing '>' and skip the leading '<' */
- geoloc_uri[uri_len - 1] = '\0';
- geoloc_uri++;
/*
* If the URI isn't "cid" then we're just going to pass it through.
*/
- if (!ast_begins_with(geoloc_uri, "cid:")) {
- ast_trace(4, "Processing URI '%s'. Reference\n", geoloc_uri);
+ if (!ast_begins_with(geoloc_uri, "<cid:")) {
+ ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
eprofile = ast_geoloc_eprofile_create_from_uri(geoloc_uri, session_name);
if (!eprofile) {
@@ -261,11 +266,13 @@
continue;
}
} else {
- ast_trace(4, "Processing URI '%s'. PIDF\n", geoloc_uri);
+ ast_trace(4, "Processing PIDF-LO '%s'\n", geoloc_uri);
+
rc = find_pidf(session_name, rdata, geoloc_uri, &pidf_body, &pidf_len);
if (rc != 0 || !pidf_body || pidf_len == 0) {
continue;
}
+ ast_trace(5, "Processing PIDF-LO "PJSTR_PRINTF_SPEC"\n", (int)pidf_len, pidf_body);
incoming_doc = ast_xml_read_memory(pidf_body, pidf_len);
if (!incoming_doc) {
@@ -274,7 +281,7 @@
continue;
}
- eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, session_name);
+ eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, geoloc_uri, session_name);
}
eprofile->action = config_profile->action;
eprofile->send_location = config_profile->send_location;
@@ -323,9 +330,276 @@
session_name, eprofile_count);
}
+static int add_pidf_to_tdata(struct ast_datastore *tempds, struct ast_channel *channel,
+ struct ast_vector_string *uris, int pidf_index, struct pjsip_tx_data *tdata, const char *session_name)
+{
+ static const pj_str_t from_name = { "From", 4};
+ static const pj_str_t cid_name = { "Content-ID", 10 };
+
+ pjsip_sip_uri *sip_uri;
+ pjsip_generic_string_hdr *cid;
+ pj_str_t cid_value;
+ pjsip_from_hdr *from = pjsip_msg_find_hdr_by_name(tdata->msg, &from_name, NULL);
+ pjsip_sdp_info *tdata_sdp_info;
+ pjsip_msg_body *multipart_body = NULL;
+ pjsip_multipart_part *pidf_part;
+ pj_str_t pidf_body_text;
+ char id[6];
+ size_t alloc_size;
+ char *base_cid;
+ const char *final;
+ int rc = 0;
+ RAII_VAR(struct ast_str *, buf, ast_str_create(1024), ast_free);
+ SCOPE_ENTER(3, "%s\n", session_name);
+
+ /*
+ * ast_geoloc_eprofiles_to_pidf() takes the datastore with all of the eprofiles
+ * in it, skips over the ones not needing PIDF processing and combines the
+ * rest into one document.
+ */
+ final = ast_geoloc_eprofiles_to_pidf(tempds, channel, &buf, session_name);
+ ast_trace(5, "Final pidf: \n%s\n", final);
+
+ /*
+ * There _should_ be an SDP already attached to the tdata at this point
+ * but maybe not. If we can find an existing one, we'll convert the tdata
+ * body into a multipart body and add the SDP as the first part. Then we'll
+ * create another part to hold the PIDF.
+ *
+ * If we don't find one, we're going to create an empty multipart body
+ * and add the PIDF part to it.
+ *
+ * Technically, if we only have the PIDF, we don't need a multipart
+ * body to hold it but that means we'd have to add the Content-ID header
+ * to the main SIP message. Since it's unlikely, it's just better to
+ * add the multipary body and leave the rest of the processing unchanged.
+ */
+ tdata_sdp_info = pjsip_tdata_get_sdp_info(tdata);
+ if (tdata_sdp_info->sdp) {
+ ast_trace(4, "body: %p %u\n", tdata_sdp_info->sdp, (unsigned)tdata_sdp_info->sdp_err);
+
+ rc = pjsip_create_multipart_sdp_body(tdata->pool, tdata_sdp_info->sdp, &multipart_body);
+ if (rc != PJ_SUCCESS) {
+ SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_ERROR, "%s: Unable to create sdp multipart body\n",
+ session_name);
+ }
+ } else {
+ multipart_body = pjsip_multipart_create(tdata->pool, &pjsip_media_type_multipart_mixed, NULL);
+ }
+
+ pidf_part = pjsip_multipart_create_part(tdata->pool);
+ pj_cstr(&pidf_body_text, final);
+ pidf_part->body = pjsip_msg_body_create(tdata->pool, &pjsip_media_type_application_pidf_xml.type,
+ &pjsip_media_type_application_pidf_xml.subtype, &pidf_body_text);
+
+ pjsip_multipart_add_part(tdata->pool, multipart_body, pidf_part);
+
+ sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(from->uri);
+ alloc_size = sizeof(id) + pj_strlen(&sip_uri->host) + 2;
+ base_cid = ast_malloc(alloc_size);
+ sprintf(base_cid, "%s@%.*s",
+ ast_generate_random_string(id, sizeof(id)),
+ (int) pj_strlen(&sip_uri->host), pj_strbuf(&sip_uri->host));
+
+ ast_str_set(&buf, 0, "cid:%s", base_cid);
+ ast_trace(4, "cid: '%s' uri: '%s' pidf_index: %d\n", base_cid, ast_str_buffer(buf), pidf_index);
+
+ AST_VECTOR_INSERT_AT(uris, pidf_index, ast_strdup(ast_str_buffer(buf)));
+
+ cid_value.ptr = pj_pool_alloc(tdata->pool, alloc_size);
+ cid_value.slen = sprintf(cid_value.ptr, "<%s>", base_cid);
+
+ cid = pjsip_generic_string_hdr_create(tdata->pool, &cid_name, &cid_value);
+
+ pj_list_insert_after(&pidf_part->hdr, cid);
+
+ tdata->msg->body = multipart_body;
+
+ SCOPE_EXIT_RTN_VALUE(0, "%s: PIDF-LO added with cid '%s'\n", session_name, base_cid);
+}
+
static void handle_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
{
+ const char *session_name = ast_sip_session_get_name(session);
+ struct ast_sip_endpoint *endpoint = session->endpoint;
+ struct ast_channel *channel = session->channel;
+ RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_str *, buf, ast_str_create(1024), ast_free);
+ RAII_VAR(struct ast_datastore *, tempds, NULL, ast_datastore_free);
+ struct ast_datastore *ds = NULL; /* The channel cleans up ds */
+ struct ast_vector_string uris;
+ pjsip_msg_body *orig_body;
+ pjsip_generic_string_hdr *geoloc_hdr;
+ int i;
+ int eprofile_count = 0;
+ int pidf_index = -1;
+ int geoloc_routing = 0;
+ int rc = 0;
+ const char *final;
+ SCOPE_ENTER(3, "%s\n", session_name);
+ if (!buf) {
+ SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to allocate buf\n",
+ session_name);
+ }
+
+ if (!endpoint) {
+ SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no endpoint. Skipping.\n",
+ session_name);
+ }
+
+ if (!channel) {
+ SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no channel. Skipping.\n",
+ session_name);
+ }
+
+ if (ast_strlen_zero(endpoint->geoloc_outgoing_call_profile)) {
+ SCOPE_EXIT_LOG_RTN(LOG_NOTICE, "%s: Endpoint has no geoloc_outgoing_call_profile. "
+ "Skipping.\n", session_name);
+ }
+
+ config_profile = ast_geoloc_get_profile(endpoint->geoloc_outgoing_call_profile);
+ if (!config_profile) {
+ SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Endpoint's geoloc_outgoing_call_profile doesn't exist. "
+ "Geolocation info discarded.\n", session_name);
+ }
+
+ config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
+ if (!config_eprofile) {
+ SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to create eprofile from "
+ "profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
+ }
+
+ if (config_profile->action != AST_GEOLOC_ACTION_DISCARD) {
+ ds = ast_geoloc_datastore_find(channel);
+ if (!ds) {
+ ast_trace(4, "%s: There was no geoloc datastore\n", session_name);
+ } else {
+ eprofile_count = ast_geoloc_datastore_size(ds);
+ ast_trace(4, "%s: There are %d geoloc profiles on this channel\n", session_name,
+ eprofile_count);
+ }
+ }
+
+ /*
+ * We don't want to alter the datastore that may (or may not) be on
+ * the channel so we're going to create a temporary one to hold the
+ * config eprofile plus any in the channel datastore. Technically
+ * we could just use a vector but the datastore already has the logic
+ * to release all the eprofile references and the datastore itself.
+ */
+ tempds = ast_geoloc_datastore_create("temp");
+ if (!ds) {
+ ast_trace(4, "%s: There are no geoloc profiles on this channel\n", session_name);
+ ast_geoloc_datastore_add_eprofile(tempds, config_eprofile);
+ } else {
+ if (config_profile->action == AST_GEOLOC_ACTION_APPEND) {
+ ast_trace(4, "%s: prepending config_eprofile\n", session_name);
+ ast_geoloc_datastore_add_eprofile(tempds, config_eprofile);
+ }
+ for (i = 0; i < eprofile_count; i++) {
+ struct ast_geoloc_eprofile *ep = ast_geoloc_datastore_get_eprofile(ds, i);
+ ast_trace(4, "%s: adding eprofile '%s' from channel\n", session_name, ep->id);
+ ast_geoloc_datastore_add_eprofile(tempds, ep);
+ }
+ if (config_profile->action == AST_GEOLOC_ACTION_PREPEND) {
+ ast_trace(4, "%s: appending config_eprofile\n", session_name);
+ ast_geoloc_datastore_add_eprofile(tempds, config_eprofile);
+ }
+ }
+
+ eprofile_count = ast_geoloc_datastore_size(tempds);
+ if (eprofile_count == 0) {
+ SCOPE_EXIT_RTN("%s: There are no profiles left to send\n", session_name);
+ }
+ ast_trace(4, "%s: There are now %d geoloc profiles to be sent\n", session_name,
+ eprofile_count);
+
+ /*
+ * This vector is going to accumulate all of the URIs that
+ * will need to go on the Geolocation header.
+ */
+ rc = AST_VECTOR_INIT(&uris, 2);
+ if (rc != 0) {
+ SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to allocate memory for vector\n", session_name);
+ }
+
+ /*
+ * It's possible that we have a list of eprofiles that have both "pass-by-reference (external URI)"
+ * and "pass by value (to go in PIDF)" eprofiles. The ones that just need a URI added to the
+ * Geolocation header get added to the "uris" vector in this loop. The ones that result in a
+ * PIDF though, need to be combined into a single PIDF-LO document so we're just going to
+ * save the first one's index so we can insert the "cid" header in the right place, then
+ * we'll send the whole list off to add_pidf_to_tdata() so they can be combined into a
+ * single document.
+ */
+
+ for (i = 0; i < eprofile_count; i++) {
+ struct ast_geoloc_eprofile *ep = ast_geoloc_datastore_get_eprofile(tempds, i);
+ ast_geoloc_eprofile_refresh_location(ep);
+
+ ast_trace(4, "ep: '%s' EffectiveLoc: %s\n", ep->id, ast_str_buffer(
+ ast_variable_list_join(ep->effective_location, ",", "=", NULL, &buf)));
+ ast_str_reset(buf);
+
+ if (ep->format == AST_GEOLOC_FORMAT_URI) {
+ final = ast_geoloc_eprofile_to_uri(ep, channel, &buf, session_name);
+ ast_trace(4, "URI: %s\n", final);
+ AST_VECTOR_APPEND(&uris, ast_strdup(final));
+ ast_str_reset(buf);
+ } else {
+ /*
+ * If there are GML or civicAddress eprofiles, we need to save the position
+ * of the first one in relation to any URI ones so we can insert the "cid"
+ * uri for it in the original position.
+ */
+ if (pidf_index < 0) {
+ pidf_index = i;
+ }
+ }
+ /* The LAST eprofile determines routing */
+ geoloc_routing = ep->geolocation_routing;
+ }
+
+ /*
+ * If we found at least one eprofile needing PIDF processing, we'll
+ * send the entire list off to add_pidf_to_tdata(). We're going to save
+ * the pointer to the original tdata body in case we need to revert
+ * if we can't add the headers.
+ */
+ orig_body = tdata->msg->body;
+ if (pidf_index >= 0) {
+ rc = add_pidf_to_tdata(tempds, channel, &uris, pidf_index, tdata, session_name);
+ }
+
+ /*
+ * Now that we have all the URIs in the vector, we'll string them together
+ * to create the data for the Geolocation header.
+ */
+ ast_str_reset(buf);
+ for (i = 0; i < AST_VECTOR_SIZE(&uris); i++) {
+ char *uri = AST_VECTOR_GET(&uris, i);
+ ast_trace(4, "ix: %d of %d LocRef: %s\n", i, (int)AST_VECTOR_SIZE(&uris), uri);
+ ast_str_append(&buf, 0, "%s<%s>", (i > 0 ? "," : ""), uri);
+ }
+
+ AST_VECTOR_RESET(&uris, ast_free);
+ AST_VECTOR_FREE(&uris);
+
+ /* It's almost impossible for add header to fail but you never know */
+ geoloc_hdr = ast_sip_add_header2(tdata, "Geolocation", ast_str_buffer(buf));
+ if (geoloc_hdr == NULL) {
+ tdata->msg->body = orig_body;
+ SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation header\n", session_name);
+ }
+ rc = ast_sip_add_header(tdata, "Geolocation-Routing", geoloc_routing ? "yes" : "no");
+ if (rc != 0) {
+ tdata->msg->body = orig_body;
+ pj_list_erase(geoloc_hdr);
+ SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation-Routing header\n", session_name);
+ }
+ SCOPE_EXIT_RTN("%s: Geolocation: %s\n", session_name, ast_str_buffer(buf));
}
static struct ast_sip_session_supplement geolocation_supplement = {
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18251
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: development/16/geolocation
Gerrit-Change-Id: Iccb956dd1800204472a21714d06d3b28486e9490
Gerrit-Change-Number: 18251
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220323/a7efb88f/attachment-0001.html>
More information about the asterisk-code-review
mailing list