[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