[Asterisk-code-review] res_geolocation: Address user issues, remove complexity, plug leaks (asterisk[18])

George Joseph asteriskteam at digium.com
Thu Aug 11 06:11:55 CDT 2022


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/18897 )

Change subject: res_geolocation: Address user issues, remove complexity, plug leaks
......................................................................

res_geolocation: Address user issues, remove complexity, plug leaks

* Added processing for the 'confidence' element.
* Added documentation to some APIs.
* removed a lot of complex code related to the very-off-nominal
  case of needing to process multiple location info sources.
* Create a new 'ast_geoloc_eprofile_to_pidf' API that just takes
  one eprofile instead of a datastore of multiples.
* Plugged a huge leak in XML processing that arose from
  insufficient documentation by the libxml/libxslt authors.
* Refactored stylesheets to be more efficient.
* Renamed 'profile_action' to 'profile_precedence' to better
  reflect it's purpose.
* Added the config option for 'allow_routing_use' which
  sets the value of the 'Geolocation-Routing' header.
* Removed the GeolocProfileCreate and GeolocProfileDelete
  dialplan apps.
* Changed the GEOLOC_PROFILE dialplan function as follows:
  * Removed the 'profile' argument.
  * Automatically create a profile if it doesn't exist.
  * Delete a profile if 'inheritable' is set to no.
* Fixed various bugs and leaks
* Updated Asterisk WiKi documentation.

ASTERISK-30167

Change-Id: If38c23f26228e96165be161c2f5e849cb8e16fa0
---
M configs/samples/geolocation.conf.sample
A doc/CHANGES-staging/res_geolocation.txt
M include/asterisk/res_geolocation.h
M include/asterisk/xml.h
M main/xml.c
M res/res_geolocation/eprofile_to_pidf.xslt
M res/res_geolocation/geoloc_config.c
M res/res_geolocation/geoloc_dialplan.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_lo_test.xml
M res/res_geolocation/pidf_to_eprofile.xslt
M res/res_geolocation/wiki/AsteriskImplementation.md
M res/res_pjsip_geolocation.c
16 files changed, 1,119 insertions(+), 1,258 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved; Approved for Submit



diff --git a/configs/samples/geolocation.conf.sample b/configs/samples/geolocation.conf.sample
index 0566cbd..8c44edb 100644
--- a/configs/samples/geolocation.conf.sample
+++ b/configs/samples/geolocation.conf.sample
@@ -134,14 +134,24 @@
 Example:
 location_source = sip1.myserver.net
 
+-- confidence (optional) -----------------------------------------
+The confidence in the location specified.
+confidence = pdf=[ unknown | normal | rectangular ], value=<percent_confident>
+
+Please see RFC7459 for the exact description of this parameter.
+
+Example:
+confidence = pdf=normal, value=75
+
+
 -- Location Example ---------------------------------------------------
 
 [mylocation]
 type = location
 format = civicAddress
 location_info = country=US
-location_info = A1="New York", A3="New York", A4=Manhattan  
-location_info = HNO=1633, PRD=W, RD=46th, STS=Street  
+location_info = A1="New York", A3="New York", A4=Manhattan
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street
 location_info = PC=10222
 method = Manual
 location_source = sip1.myserver.net
@@ -160,9 +170,9 @@
 Defines the object type.
 type = profile
 
--- profile_action (optional) ------------------------------------------
+-- profile_precedence (optional) ------------------------------------------
 Sets how to reconcile incoming and configured profiles.
-profile_action = < prefer_incoming | prefer_config | discard_incoming
+profile_precedence = < prefer_incoming | prefer_config | discard_incoming
     | discard_config >
 
 On an incoming call leg, "incoming" is the location description
@@ -188,7 +198,7 @@
 discard_incoming is the default.
 
 Example:
-profile_action = prefer_config
+profile_precedence = prefer_config
 
 -- pidf_element (optional) --------------------------------------------
 PIDF-LO element in which to place the location description.
@@ -207,10 +217,10 @@
 Example:
 pidf_element = tuple
 
--- geolocation_routing (optional) -------------------------------------
+-- allow_routing_use (optional) -------------------------------------
 Sets whether the "Geolocation-Routing" header is added to outgoing
 requests.
-geolocation_routing = < yes | no >
+allow_routing_use = < yes | no >
 
 Set to "yes" to indicate that servers later in the path
 can use the location information for routing purposes.  Set to "no"
@@ -218,7 +228,7 @@
 "Geolocation-Routing" header will be added.
 
 Example:
-geolocation_routing = yes
+allow_routing_use = yes
 
 -- location_reference (optional) --------------------------------------
 The name of an existing Location object.
diff --git a/doc/CHANGES-staging/res_geolocation.txt b/doc/CHANGES-staging/res_geolocation.txt
new file mode 100644
index 0000000..a93d7b6
--- /dev/null
+++ b/doc/CHANGES-staging/res_geolocation.txt
@@ -0,0 +1,23 @@
+Subject: res_geolocation
+
+* Added processing for the 'confidence' element.
+* Added documentation to some APIs.
+* removed a lot of complex code related to the very-off-nominal
+  case of needing to process multiple location info sources.
+* Create a new 'ast_geoloc_eprofile_to_pidf' API that just takes
+  one eprofile instead of a datastore of multiples.
+* Plugged a huge leak in XML processing that arose from
+  insufficient documentation by the libxml/libxslt authors.
+* Refactored stylesheets to be more efficient.
+* Renamed 'profile_action' to 'profile_precedence' to better
+  reflect it's purpose.
+* Added the config option for 'allow_routing_use' which
+  sets the value of the 'Geolocation-Routing' header.
+* Removed the GeolocProfileCreate and GeolocProfileDelete
+  dialplan apps.
+* Changed the GEOLOC_PROFILE dialplan function as follows:
+  * Removed the 'profile' argument.
+  * Automatically create a profile if it doesn't exist.
+  * Delete a profile if 'inheritable' is set to no.
+* Fixed various bugs and leaks
+* Updated Asterisk WiKi documentation.
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
index 403e6c8..31426c0c 100644
--- a/include/asterisk/res_geolocation.h
+++ b/include/asterisk/res_geolocation.h
@@ -29,8 +29,8 @@
 
 enum ast_geoloc_pidf_element {
 	AST_PIDF_ELEMENT_NONE = 0,
-	AST_PIDF_ELEMENT_TUPLE,
 	AST_PIDF_ELEMENT_DEVICE,
+	AST_PIDF_ELEMENT_TUPLE,
 	AST_PIDF_ELEMENT_PERSON,
 	AST_PIDF_ELEMENT_LAST,
 };
@@ -43,13 +43,22 @@
 	AST_GEOLOC_FORMAT_LAST,
 };
 
-enum ast_geoloc_action {
-	AST_GEOLOC_ACT_PREFER_INCOMING = 0,
-	AST_GEOLOC_ACT_PREFER_CONFIG,
-	AST_GEOLOC_ACT_DISCARD_INCOMING,
-	AST_GEOLOC_ACT_DISCARD_CONFIG,
+enum ast_geoloc_precedence {
+	AST_GEOLOC_PRECED_PREFER_INCOMING = 0,
+	AST_GEOLOC_PRECED_PREFER_CONFIG,
+	AST_GEOLOC_PRECED_DISCARD_INCOMING,
+	AST_GEOLOC_PRECED_DISCARD_CONFIG,
 };
 
+#define CONFIG_STR_TO_ENUM_DECL(_stem) int ast_geoloc_ ## _stem ## _str_to_enum(const char *str);
+CONFIG_STR_TO_ENUM_DECL(pidf_element)
+CONFIG_STR_TO_ENUM_DECL(format);
+CONFIG_STR_TO_ENUM_DECL(precedence);
+#define GEOLOC_ENUM_TO_NAME_DECL(_stem) const char * ast_geoloc_ ## _stem ## _to_name(int ix);
+GEOLOC_ENUM_TO_NAME_DECL(pidf_element)
+GEOLOC_ENUM_TO_NAME_DECL(format);
+GEOLOC_ENUM_TO_NAME_DECL(precedence);
+
 struct ast_geoloc_location {
 	SORCERY_OBJECT(details);
 	AST_DECLARE_STRING_FIELDS(
@@ -58,6 +67,7 @@
 	);
 	enum ast_geoloc_format format;
 	struct ast_variable *location_info;
+	struct ast_variable *confidence;
 };
 
 struct ast_geoloc_profile {
@@ -67,8 +77,8 @@
 		AST_STRING_FIELD(notes);
 	);
 	enum ast_geoloc_pidf_element pidf_element;
-	enum ast_geoloc_action action;
-	int geolocation_routing;
+	enum ast_geoloc_precedence precedence;
+	int allow_routing_use;
 	struct ast_variable *location_refinement;
 	struct ast_variable *location_variables;
 	struct ast_variable *usage_rules;
@@ -83,14 +93,15 @@
 		AST_STRING_FIELD(notes);
 	);
 	enum ast_geoloc_pidf_element pidf_element;
-	enum ast_geoloc_action action;
-	int geolocation_routing;
+	enum ast_geoloc_precedence precedence;
+	int allow_routing_use;
 	enum ast_geoloc_format format;
 	struct ast_variable *location_info;
 	struct ast_variable *location_refinement;
 	struct ast_variable *location_variables;
 	struct ast_variable *effective_location;
 	struct ast_variable *usage_rules;
+	struct ast_variable *confidence;
 };
 
 /*!
@@ -331,13 +342,46 @@
 struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
 	const char *reference_string);
 
+/*!
+ * \brief Convert a URI eprofile to a URI string
+ *
+ * \param eprofile   Effective profile to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation of URI allocated from buf or NULL on failure
+ */
 const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
 	struct ast_channel *chan, struct ast_str **buf, const char *ref_string);
 
+/*!
+ * \brief Convert a datastore containing eprofiles to a PIDF-LO document
+ *
+ * \param ds         Datastore containing effective profiles to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation PIDF-LO allocated from buf or NULL on failure.
+ */
 const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
 	struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
 
 /*!
+ * \brief Convert a single eprofile to a PIDF-LO document
+ *
+ * \param eprofile   Effective profile to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation PIDF-LO allocated from buf or NULL on failure.
+ */
+const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
+
+/*!
  * \brief Refresh the effective profile with any changed info.
  *
  * \param eprofile The eprofile to refresh.
diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h
index c1b0797..5c2e9f9 100644
--- a/include/asterisk/xml.h
+++ b/include/asterisk/xml.h
@@ -163,6 +163,7 @@
  * \param attrname Attribute name.
  * \retval NULL on error
  * \return The attribute value on success.
+ * \note The result must be freed with ast_xml_free_attr().
  */
 const char *ast_xml_get_attribute(struct ast_xml_node *node, const char *attrname);
 
diff --git a/main/xml.c b/main/xml.c
index d244e4e..8c49a9e 100644
--- a/main/xml.c
+++ b/main/xml.c
@@ -544,35 +544,45 @@
 {
 	xsltStylesheet *xslt = (xsltStylesheet *)axslt;
 	xmlDoc *xml = (xmlDoc *)axml;
+	xmlDoc *res;
 	xsltTransformContextPtr ctxt;
 	xmlNs *ns;
-	xmlDoc *res;
 	int options = XSLT_PARSE_OPTIONS;
 
 	/*
 	 * Normally we could just call xsltApplyStylesheet() without creating
-	 * our own transform context but we need to pass parameters to it
-	 * that have namespace prefixes and that's not supported.  Instead
-	 * we have to create a transform context, iterate over the namespace
-	 * declarations in the stylesheet (not the incoming xml document),
-	 * and add them to the transform context's xpath context.
+	 * our own transform context but passing parameters to it that have
+	 * namespace prefixes isn't supported. Instead we have to create a
+	 * transform context, iterate over the namespace declarations in the
+	 * stylesheet (not the incoming xml document), add them to the
+	 * transform context's xpath context, and call xsltApplyStylesheetUser.
 	 *
-	 * The alternative would be to pass the parameters with namespaces
-	 * as text strings but that's not intuitive and results in much
-	 * slower performance than adding the namespaces here.
+	 * Since this is a bit involved and libxslt apparently doesn't completely
+	 * clean up after itself in this situation,  we'll only do that dance
+	 * if there are parameters passed in.  Otherwise we just call the simpler
+	 * xsltApplyStylesheet.
+	 *
 	 */
+
+	if (!params) {
+		res = xsltApplyStylesheet(xslt, xml, params);
+		return (struct ast_xml_doc *)res;
+	}
+
 	ctxt = xsltNewTransformContext(xslt, xml);
 	xsltSetCtxtParseOptions(ctxt, options);
 
 	for (ns = xslt->doc->children->nsDef; ns; ns = ns->next) {
 		if (xmlXPathRegisterNs(ctxt->xpathCtxt, ns->prefix, ns->href) != 0) {
-			xmlXPathFreeContext(ctxt->xpathCtxt);
 			xsltFreeTransformContext(ctxt);
 			return NULL;
 		}
 	}
 
 	res = xsltApplyStylesheetUser(xslt, xml, params, NULL, NULL, ctxt);
+	xmlXPathFreeContext(ctxt->xpathCtxt);
+	ctxt->xpathCtxt = NULL;
+	xsltFreeTransformContext(ctxt);
 
 	return (struct ast_xml_doc *)res;
 }
diff --git a/res/res_geolocation/eprofile_to_pidf.xslt b/res/res_geolocation/eprofile_to_pidf.xslt
index dbfe17b..dec73ed 100644
--- a/res/res_geolocation/eprofile_to_pidf.xslt
+++ b/res/res_geolocation/eprofile_to_pidf.xslt
@@ -8,6 +8,7 @@
 	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:con="urn:ietf:params:xml:ns:geopriv:conf"
 	xmlns:date="http://exslt.org/dates-and-times">
 
 	<xsl:output method="xml" indent="yes"/>
@@ -20,12 +21,16 @@
 		<!-- 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"/>
+			<xsl:apply-templates/>
 		</presence>
 	</xsl:template>
 
-	<xsl:template match="device">
-		<dm:device>
+	<xsl:template match="person|device">
+		<xsl:element name="dm:{local-name(.)}">
+			<xsl:if test="@id">
+				<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			</xsl:if>
+
 			<gp:geopriv>
 				<xsl:apply-templates select="./location-info"/>
 				<xsl:apply-templates select="./usage-rules"/>
@@ -42,7 +47,7 @@
 					<xsl:value-of select="./deviceID"/>
 				</dm:deviceID>
 			</xsl:if>
-		</dm:device>
+		</xsl:element>
 	</xsl:template>
 
 	<xsl:template match="tuple">
@@ -63,21 +68,6 @@
 		</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>
@@ -233,5 +223,10 @@
 	<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:template match="confidence">
+		<con:confidence pdf="{@pdf}">
+			<xsl:value-of select="."/>
+		</con:confidence>
+	</xsl:template>
 
 </xsl:stylesheet>
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
index 33cd333..772bdc7 100644
--- a/res/res_geolocation/geoloc_config.c
+++ b/res/res_geolocation/geoloc_config.c
@@ -26,8 +26,8 @@
 
 static const char *pidf_element_names[] = {
 	"<none>",
-	"tuple",
 	"device",
+	"tuple",
 	"person"
 };
 
@@ -38,7 +38,7 @@
 	"URI",
 };
 
-static const char * action_names[] = {
+static const char * precedence_names[] = {
 	"prefer_incoming",
 	"prefer_config",
 	"discard_incoming",
@@ -47,12 +47,14 @@
 
 CONFIG_ENUM(location, format)
 CONFIG_VAR_LIST(location, location_info)
+CONFIG_VAR_LIST(location, confidence)
 
 static void geoloc_location_destructor(void *obj) {
 	struct ast_geoloc_location *location = obj;
 
 	ast_string_field_free_memory(location);
 	ast_variables_destroy(location->location_info);
+	ast_variables_destroy(location->confidence);
 }
 
 static void *geoloc_location_alloc(const char *name)
@@ -67,7 +69,7 @@
 
 
 CONFIG_ENUM(profile, pidf_element)
-CONFIG_ENUM(profile, action)
+CONFIG_ENUM(profile, precedence)
 CONFIG_VAR_LIST(profile, location_refinement)
 CONFIG_VAR_LIST(profile, location_variables)
 CONFIG_VAR_LIST(profile, usage_rules)
@@ -308,7 +310,7 @@
 	int using_regex = 0;
 	char *result = CLI_SUCCESS;
 	int ret = 0;
-	char *action;
+	char *precedence;
 	int count = 0;
 
 	switch (cmd) {
@@ -365,13 +367,13 @@
 	for (; (profile = ao2_iterator_next(&iter)); ao2_ref(profile, -1)) {
 		ao2_lock(profile);
 
-		action_to_str(profile, NULL, &action);
+		precedence_to_str(profile, NULL, &precedence);
 		ast_cli(a->fd, "%-46.46s %-16s %-s\n",
 			ast_sorcery_object_get_id(profile),
-			action,
+			precedence,
 			profile->location_reference);
 		ao2_unlock(profile);
-		ast_free(action);
+		ast_free(precedence);
 		count++;
 	}
 	ao2_iterator_destroy(&iter);
@@ -460,11 +462,11 @@
 		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);
+		precedence_to_str(eprofile, NULL, &action);
 
 		ast_cli(a->fd,
 			"id:                   %-s\n"
-			"profile_action:       %-s\n"
+			"profile_disposition:  %-s\n"
 			"pidf_element:         %-s\n"
 			"location_reference:   %-s\n"
 			"Location_format:      %-s\n"
@@ -472,6 +474,7 @@
 			"location_method:      %-s\n"
 			"location_refinement:  %-s\n"
 			"location_variables:   %-s\n"
+			"allow_routing_use:    %-s\n"
 			"effective_location:   %-s\n"
 			"usage_rules:          %-s\n"
 			"notes:                %-s\n",
@@ -484,6 +487,7 @@
 			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(eprofile->precedence, "yes", "no"),
 			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>")
@@ -597,6 +601,8 @@
 		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_custom(geoloc_sorcery, "location", "confidence", NULL,
+		confidence_handler, confidence_to_str, confidence_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,
@@ -616,8 +622,8 @@
 		pidf_element_names[AST_PIDF_ELEMENT_DEVICE], pidf_element_handler, pidf_element_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_reference", "", OPT_STRINGFIELD_T,
 		0, STRFLDSET(struct ast_geoloc_profile, location_reference));
-	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "profile_action", "discard_incoming",
-		action_handler, action_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "profile_precedence", "discard_incoming",
+		precedence_handler, precedence_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "usage_rules", NULL,
 		usage_rules_handler, usage_rules_to_str, usage_rules_dup, 0, 0);
 	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info_refinement", NULL,
@@ -626,6 +632,9 @@
 		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_object_field_register(geoloc_sorcery, "profile", "allow_routing_use",
+		"no", OPT_BOOL_T, 1, FLDSET(struct ast_geoloc_profile, allow_routing_use));
+
 
 	ast_sorcery_load(geoloc_sorcery);
 
diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c
index efa234a..c75189f 100644
--- a/res/res_geolocation/geoloc_dialplan.c
+++ b/res/res_geolocation/geoloc_dialplan.c
@@ -41,84 +41,63 @@
 	const char *cmd, char *data, struct ast_str **buf, ssize_t len)
 {
 	char *parsed_data = ast_strdupa(data);
-	int index = -1;
 	struct ast_datastore *ds;
 	struct ast_geoloc_eprofile *eprofile = NULL;
-	int profile_count = 0;
 
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(field);
-		AST_APP_ARG(index);
 	);
 
 	/* Check for zero arguments */
 	if (ast_strlen_zero(parsed_data)) {
 		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", cmd);
-		return -1;
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
 	}
 
 	AST_STANDARD_APP_ARGS(args, parsed_data);
 
 	if (ast_strlen_zero(args.field)) {
 		ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", cmd);
-		return -1;
-	}
-
-	if (!ast_strlen_zero(args.index)) {
-		if (sscanf(args.index, "%30d", &index) != 1) {
-			ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", cmd, args.index);
-			return -1;
-		}
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
 	}
 
 	ds = ast_geoloc_datastore_find(chan);
 	if (!ds) {
-		ast_log(LOG_NOTICE, "%s: There are no geoloc profiles on this channel\n", cmd);
-		return -1;
-	}
-
-	profile_count = ast_geoloc_datastore_size(ds);
-
-	if (index < 0) {
-		if (ast_strings_equal(args.field, "count")) {
-			ast_str_append(buf, len, "%d", profile_count);
-		} else if (ast_strings_equal(args.field, "inheritable")) {
-			ast_str_append(buf, len, "%d", ds->inheritance ? 1 : 0);
-		} else {
-			ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
-			return -1;
-		}
-
+		ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", cmd);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
 		return 0;
 	}
 
-	if (index >= profile_count) {
-		ast_log(LOG_ERROR, "%s: index %d is out of range 0 -> %d\n", cmd, index, profile_count);
-		return -1;
-	}
-
-	eprofile = ast_geoloc_datastore_get_eprofile(ds, index);
+	eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
 	if (!eprofile) {
-		ast_log(LOG_ERROR, "%s: Internal Error.  Profile at index %d couldn't be retrieved.\n", cmd, index);
-		return -1;
+		ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", cmd);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+		return 0;
 	}
 
-	if (ast_strings_equal(args.field, "id")) {
+	pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
+	if (ast_strings_equal(args.field, "inheritable")) {
+		ast_str_append(buf, len, "%s", ds->inheritance ? "true" : "false");
+	} else if (ast_strings_equal(args.field, "id")) {
 		ast_str_append(buf, len, "%s", eprofile->id);
 	} else if (ast_strings_equal(args.field, "location_reference")) {
 		ast_str_append(buf, len, "%s", eprofile->location_reference);
 	} else if (ast_strings_equal(args.field, "method")) {
 		ast_str_append(buf, len, "%s", eprofile->method);
-	} else if (ast_strings_equal(args.field, "geolocation_routing")) {
-		ast_str_append(buf, len, "%s", eprofile->geolocation_routing ? "yes" : "no");
-	} else if (ast_strings_equal(args.field, "profile_action")) {
-		ast_str_append(buf, len, "%s", geoloc_action_to_name(eprofile->action));
+	} else if (ast_strings_equal(args.field, "allow_routing_use")) {
+		ast_str_append(buf, len, "%s", eprofile->allow_routing_use ? "yes" : "no");
+	} else if (ast_strings_equal(args.field, "profile_precedence")) {
+		ast_str_append(buf, len, "%s", ast_geoloc_precedence_to_name(eprofile->precedence));
 	} else if (ast_strings_equal(args.field, "format")) {
-		ast_str_append(buf, len, "%s", geoloc_format_to_name(eprofile->format));
+		ast_str_append(buf, len, "%s", ast_geoloc_format_to_name(eprofile->format));
 	} else if (ast_strings_equal(args.field, "pidf_element")) {
-		ast_str_append(buf, len, "%s", geoloc_pidf_element_to_name(eprofile->pidf_element));
+		ast_str_append(buf, len, "%s", ast_geoloc_pidf_element_to_name(eprofile->pidf_element));
 	} else if (ast_strings_equal(args.field, "location_source")) {
 		ast_str_append(buf, len, "%s", eprofile->location_source);
+	} else if (ast_strings_equal(args.field, "notes")) {
+		ast_str_append(buf, len, "%s", eprofile->notes);
 	} else if (ast_strings_equal(args.field, "location_info")) {
 		varlist_to_str(eprofile->location_info, buf, len);
 	} else if (ast_strings_equal(args.field, "location_info_refinement")) {
@@ -129,41 +108,37 @@
 		varlist_to_str(eprofile->effective_location, buf, len);
 	} else if (ast_strings_equal(args.field, "usage_rules")) {
 		varlist_to_str(eprofile->usage_rules, buf, len);
+	} else if (ast_strings_equal(args.field, "confidence")) {
+		varlist_to_str(eprofile->confidence, buf, len);
 	} else {
 		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
-		return -1;
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
 	}
 
 	ao2_ref(eprofile, -1);
 	return 0;
 }
 
-#define TEST_ENUM_VALUE(_cmd, _ep, _field, _value) \
+#define TEST_ENUM_VALUE(_chan_name, _ep, _field, _value) \
 ({ \
 	enum ast_geoloc_ ## _field v; \
-	if (!_ep) { \
-		ast_log(LOG_ERROR, "%s: Field %s requires a valid index\n", _cmd, #_field); \
-		return -1; \
-	} \
-	v = geoloc_ ## _field ## _str_to_enum(_value); \
+	v = ast_geoloc_ ## _field ## _str_to_enum(_value); \
 	if (v == AST_GEOLOC_INVALID_VALUE) { \
-		ast_log(LOG_ERROR, "%s: %s '%s' is invalid\n", _cmd, #_field, value); \
-		return -1; \
+		ast_log(LOG_ERROR, "%s: %s '%s' is invalid\n", _chan_name, #_field, value); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
 	} \
 	_ep->_field = v; \
 })
 
-#define TEST_VARLIST(_cmd, _ep, _field, _value) \
+#define TEST_VARLIST(_chan_name, _ep, _field, _value) \
 ({ \
 	struct ast_variable *_list; \
-	if (!_ep) { \
-		ast_log(LOG_ERROR, "%s: Field %s requires a valid index\n", _cmd, #_field); \
-		return -1; \
-	} \
 	_list = ast_variable_list_from_quoted_string(_value, ",", "=", "\"" ); \
 	if (!_list) { \
-		ast_log(LOG_ERROR, "%s: %s '%s' is malformed or contains invalid values", _cmd, #_field, _value); \
-		return -1; \
+		ast_log(LOG_ERROR, "%s: %s '%s' is malformed or contains invalid values", _chan_name, #_field, _value); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
 	} \
 	ast_variables_destroy(_ep->_field); \
 	_ep->_field = _list; \
@@ -173,105 +148,106 @@
 	 const char *value)
 {
 	char *parsed_data = ast_strdupa(data);
-	struct ast_datastore *ds;
+	const char *chan_name = ast_channel_name(chan);
+	struct ast_datastore *ds; /* Reminder: datastores aren't ao2 objects */
 	RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup);
-	int profile_count = 0;
-	int index = -1;
 
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(field);
-		AST_APP_ARG(index);
 	);
 
 	/* Check for zero arguments */
 	if (ast_strlen_zero(parsed_data)) {
-		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", cmd);
-		return -1;
+		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
 	}
 
 	AST_STANDARD_APP_ARGS(args, parsed_data);
 
 	if (ast_strlen_zero(args.field)) {
-		ast_log(LOG_ERROR, "%s: Cannot call without a field to set\n", cmd);
-		return -1;
-	}
-
-	if (!ast_strlen_zero(args.index)) {
-		if (sscanf(args.index, "%30d", &index) != 1) {
-			ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", cmd, args.index);
-			return -1;
-		}
+		ast_log(LOG_ERROR, "%s: Cannot call without a field to set\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
 	}
 
 	ds = ast_geoloc_datastore_find(chan);
 	if (!ds) {
-		ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", cmd);
-		return -1;
+		ds = ast_geoloc_datastore_create(ast_channel_name(chan));
+		if (!ds) {
+			ast_log(LOG_WARNING, "%s: Unable to create geolocation datastore\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
+		}
+		ast_channel_datastore_add(chan, ds);
 	}
 
-	profile_count = ast_geoloc_datastore_size(ds);
-
-	if (index >= 0 && index < profile_count) {
-		eprofile = ast_geoloc_datastore_get_eprofile(ds, index);
+	eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
+	if (!eprofile) {
+		int rc;
+		eprofile = ast_geoloc_eprofile_alloc(chan_name);
 		if (!eprofile) {
-			ast_log(LOG_ERROR, "%s: Internal Error.  Profile at index %d couldn't be retrieved.\n", cmd, index);
-			return -1;
+			ast_log(LOG_ERROR, "%s: Could not allocate eprofile\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
 		}
-	} else if (index >= profile_count) {
-		ast_log(LOG_ERROR, "%s: index %d is out of range 0 -> %d\n", cmd, index, profile_count);
-		return -1;
-	} else {
-		if (ast_strings_equal(args.field, "inheritable")) {
-			ast_geoloc_datastore_set_inheritance(ds, ast_true(value));
- 		} else {
-			ast_log(LOG_ERROR, "%s: Field '%s' is not valid or requires a profile index\n", cmd, args.field);
-			return -1;
+		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+		if (rc <= 0) {
+			ast_log(LOG_ERROR, "%s: Could not add eprofile to datastore\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
 		}
-
-		return 0;
 	}
 
-	if (ast_strings_equal(args.field, "location_reference")) {
+	if (ast_strings_equal(args.field, "inheritable")) {
+		ast_geoloc_datastore_set_inheritance(ds, ast_true(value));
+	} else if (ast_strings_equal(args.field, "location_reference")) {
 		struct ast_geoloc_location *loc = ast_geoloc_get_location(value);
 		ao2_cleanup(loc);
 		if (!loc) {
-			ast_log(LOG_ERROR, "%s: Location reference '%s' doesn't exist\n", cmd, value);
-			return -1;
+			ast_log(LOG_ERROR, "%s: Location reference '%s' doesn't exist\n", chan_name, value);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
+			return 0;
 		}
 		ast_string_field_set(eprofile, location_reference, value);
 	} else if (ast_strings_equal(args.field, "method")) {
 		ast_string_field_set(eprofile, method, value);
 
-	} else if (ast_strings_equal(args.field, "geolocation_routing")) {
-		eprofile->geolocation_routing = ast_true(value);
+	} else if (ast_strings_equal(args.field, "allow_routing_use")) {
+		eprofile->allow_routing_use = ast_true(value);
 
-	} else if (ast_strings_equal(args.field, "profile_action")) {
-		TEST_ENUM_VALUE(cmd, eprofile, action, value);
+	} else if (ast_strings_equal(args.field, "profile_precedence")) {
+		TEST_ENUM_VALUE(chan_name, eprofile, precedence, value);
 
 	} else if (ast_strings_equal(args.field, "format")) {
-		TEST_ENUM_VALUE(cmd, eprofile, format, value);
+		TEST_ENUM_VALUE(chan_name, eprofile, format, value);
 
 	} else if (ast_strings_equal(args.field, "pidf_element")) {
-		TEST_ENUM_VALUE(cmd, eprofile, pidf_element, value);
+		TEST_ENUM_VALUE(chan_name, eprofile, pidf_element, value);
 
 	} else if (ast_strings_equal(args.field, "location_info")) {
-		TEST_VARLIST(cmd, eprofile, location_info, value);
+		TEST_VARLIST(chan_name, eprofile, location_info, value);
 	} else if (ast_strings_equal(args.field, "location_source")) {
 		ast_string_field_set(eprofile, location_source, value);
 	} else if (ast_strings_equal(args.field, "location_info_refinement")) {
-		TEST_VARLIST(cmd, eprofile, location_refinement, value);
+		TEST_VARLIST(chan_name, eprofile, location_refinement, value);
 	} else if (ast_strings_equal(args.field, "location_variables")) {
-		TEST_VARLIST(cmd, eprofile, location_variables, value);
+		TEST_VARLIST(chan_name, eprofile, location_variables, value);
 	} else if (ast_strings_equal(args.field, "effective_location")) {
-		TEST_VARLIST(cmd, eprofile, effective_location, value);
+		TEST_VARLIST(chan_name, eprofile, effective_location, value);
 	} else if (ast_strings_equal(args.field, "usage_rules")) {
-		TEST_VARLIST(cmd, eprofile, usage_rules, value);
+		TEST_VARLIST(chan_name, eprofile, usage_rules, value);
+	} else if (ast_strings_equal(args.field, "confidence")) {
+		TEST_VARLIST(chan_name, eprofile, confidence, value);
 	} else {
-		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
-		return -1;
+		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
+		return 0;
 	}
 
 	ast_geoloc_eprofile_refresh_location(eprofile);
+	pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
+
 	return 0;
 }
 
@@ -281,155 +257,8 @@
 	.write = geoloc_profile_write,
 };
 
-#define profile_create "GeolocProfileCreate"
-
-static int geoloc_eprofile_create(struct ast_channel *chan, const char *data)
-{
-	char *parsed_data = ast_strdupa(data);
-	struct ast_datastore *ds;
-	struct ast_geoloc_eprofile * eprofile;
-	int profile_count = 0;
-	int index = -1;
-	int rc = 0;
-	struct ast_str *new_size;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(id);
-		AST_APP_ARG(index);
-	);
-
-	/* Check for zero arguments */
-	if (ast_strlen_zero(parsed_data)) {
-		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", profile_create);
-		return -1;
-	}
-
-	AST_STANDARD_APP_ARGS(args, parsed_data);
-
-	if (ast_strlen_zero(args.id)) {
-		ast_log(LOG_ERROR, "%s: Cannot call without an id field\n", profile_create);
-		return -1;
-	}
-
-	if (!ast_strlen_zero(args.index)) {
-		if (sscanf(args.index, "%30d", &index) != 1) {
-			ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", profile_create, args.index);
-			return -1;
-		}
-	} else {
-		index = -1;
-	}
-
-	ds = ast_geoloc_datastore_find(chan);
-	if (!ds) {
-		ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", profile_create);
-		return -1;
-	}
-
-	profile_count = ast_geoloc_datastore_size(ds);
-	if (index < -1 || index >= profile_count) {
-		ast_log(LOG_ERROR, "%s: Invalid insert_before index '%d'.  It must be 0 to insert at the beginning of the list or -1 to append to the end of the list\n", profile_create, index);
-		return -1;
-	}
-
-	eprofile = ast_geoloc_eprofile_alloc(args.id);
-	if (!eprofile) {
-		ast_log(LOG_ERROR, "%s: Could not allocate eprofile '%s'\n", profile_create, args.id);
-		return -1;
-	}
-
-	ds = ast_geoloc_datastore_find(chan);
-	if (!ds) {
-		ds = ast_geoloc_datastore_create_from_eprofile(eprofile);
-		if (!ds) {
-			ao2_ref(eprofile, -1);
-			ast_log(LOG_ERROR, "%s: Could not create datastore for eprofile '%s'\n", profile_create, args.id);
-			return -1;
-		}
-		rc = 1;
-		ast_channel_datastore_add(chan, ds);
-	} else if (index < 0) {
-		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
-		if (rc <= 0) {
-			ao2_ref(eprofile, -1);
-			ast_log(LOG_ERROR, "%s: Could not add eprofile '%s' to datastore\n", profile_create, args.id);
-			return -1;
-		}
-	} else {
-		rc = ast_geoloc_datastore_insert_eprofile(ds, eprofile, index);
-		if (rc <= 0) {
-			ao2_ref(eprofile, -1);
-			ast_log(LOG_ERROR, "%s: Could not insert eprofile '%s' to datastore\n", profile_create, args.id);
-			return -1;
-		}
-	}
-
-	new_size = ast_str_alloca(16);
-	ast_str_append(&new_size, 0, "%d", rc);
-	pbx_builtin_setvar_helper(chan, "GEOLOC_PROFILE_COUNT", ast_str_buffer(new_size));
-
-	return 0;
-}
-
-#define profile_delete "GeolocProfileDelete"
-
-static int geoloc_eprofile_delete(struct ast_channel *chan, const char *data)
-{
-	char *parsed_data = ast_strdupa(data);
-	struct ast_datastore *ds;
-	int profile_count = 0;
-	int index = -1;
-	struct ast_str *new_size;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(index);
-	);
-
-	/* Check for zero arguments */
-	if (ast_strlen_zero(parsed_data)) {
-		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", profile_delete);
-		return -1;
-	}
-
-	AST_STANDARD_APP_ARGS(args, parsed_data);
-
-	if (!ast_strlen_zero(args.index)) {
-		if (sscanf(args.index, "%30d", &index) != 1) {
-			ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", profile_delete, args.index);
-			return -1;
-		}
-	} else {
-		ast_log(LOG_ERROR, "%s: A profile_index is required\n", profile_delete);
-		return -1;
-	}
-
-	ds = ast_geoloc_datastore_find(chan);
-	if (!ds) {
-		ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", profile_delete);
-		return -1;
-	}
-
-	profile_count = ast_geoloc_datastore_size(ds);
-	if (index < -1 || index >= profile_count) {
-		ast_log(LOG_ERROR, "%s: Invalid profile_index '%d'.  It must be between 0 and %d\n",
-			profile_create, index, profile_count - 1);
-		return -1;
-	}
-
-	ast_geoloc_datastore_delete_eprofile(ds, index);
-	profile_count = ast_geoloc_datastore_size(ds);
-
-	new_size = ast_str_alloca(16);
-	ast_str_append(&new_size, 0, "%d", profile_count);
-	pbx_builtin_setvar_helper(chan, "GEOLOC_PROFILE_COUNT", ast_str_buffer(new_size));
-
-	return 0;
-}
-
 int geoloc_dialplan_unload(void)
 {
-	ast_unregister_application(profile_delete);
-	ast_unregister_application(profile_create);
 	ast_custom_function_unregister(&geoloc_function);
 
 	return AST_MODULE_LOAD_SUCCESS;
@@ -440,12 +269,6 @@
 	int res = 0;
 
 	res = ast_custom_function_register(&geoloc_function);
-	if (res == 0) {
-		res = ast_register_application_xml(profile_create, geoloc_eprofile_create);
-	}
-	if (res == 0) {
-		res = ast_register_application_xml(profile_delete, geoloc_eprofile_delete);
-	}
 
 	return res == 0 ? AST_MODULE_LOAD_SUCCESS : AST_MODULE_LOAD_DECLINE;
 }
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
index 5c8bf79..2e28f7e 100644
--- a/res/res_geolocation/geoloc_doc.xml
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -94,6 +94,34 @@
 						</enumlist>
 					</description>
 				</configOption>
+				<configOption name="confidence" default="">
+					<synopsis>Level of confidence</synopsis>
+					<description>
+						<para>This is a rarely used field in the specification that would
+						indicate the confidence in the location specified.  See RFC7459
+						for exact details.
+						</para>
+						<para>
+						Sub-parameters:
+						</para>
+						<enumlist>
+						<enum name="pdf">
+						<para>One of:</para>
+						<enumlist>
+						<enum name="unknown"/>
+						<enum name="normal"/>
+						<enum name="rectangular"/>
+						</enumlist>
+						</enum>
+						<enum name="value">
+						<para>A percentage indicating the confidence.</para>
+						</enum>
+						</enumlist>
+					</description>
+					<see-also>
+						<ref type="link">https://www.rfc-editor.org/rfc/rfc7459</ref>
+					</see-also>
+				</configOption>
 			</configObject>
 			<configObject name="profile">
 				<synopsis>Profile</synopsis>
@@ -144,7 +172,11 @@
 						any recipients.</para>
 					</description>
 				</configOption>
-				<configOption name="profile_action" default="discard_incoming">
+				<configOption name="allow_routing_use">
+					<synopsis>Sets the value of the Geolocation-Routing header.</synopsis>
+				</configOption>
+
+				<configOption name="profile_precedence" default="discard_incoming">
 					<synopsis>Determine which profile on a channel should be used</synopsis>
 					<description>
 						<enumlist>
@@ -180,56 +212,47 @@
 		</synopsis>
 		<syntax>
 			<parameter name="field" required="true">
-				<para>The profile field to operate on.</para>
-			</parameter>
-			<parameter name="profile_index" required="false">
-				<para>The index of the profile to operate on.  Not required for the special fields.</para>
+				<para>The profile field to operate on. The following fields from the
+				Location and Profile objects are supported.</para>
+				<enumlist>
+					<enum name="id"/>
+					<enum name="location_reference"/>
+					<enum name="method"/>
+					<enum name="allow_routing_use"/>
+					<enum name="profile_precedence"/>
+					<enum name="format"/>
+					<enum name="pidf_element"/>
+					<enum name="location_source"/>
+					<enum name="notes"/>
+					<enum name="location_info"/>
+					<enum name="location_info_refinement"/>
+					<enum name="location_variables"/>
+					<enum name="effective_location"/>
+					<enum name="usage_rules"/>
+					<enum name="confidence"/>
+				</enumlist>
+				<para>Additionally, the <literal>inheritable</literal> field may be
+				set to <literal>true</literal> or <literal>false</literal> to control
+				whether the profile will be passed to the outgoing channel.
+				</para>
 			</parameter>
 		</syntax>
+		<description><para>
+		When used to set a field on a profile, if the profile doesn't already exist, a new
+		one will be created automatically.
+		</para>
+		<para>
+		The <literal>${GEOLOCPROFILESTATUS}</literal> channel variable will be set with
+		a return code indicating the result of the operation.  Possible values are:
+		</para>
+		<enumlist>
+			<enum name="0"><para>Success</para></enum>
+			<enum name="-1"><para>No or not enough parameters were supplied</para></enum>
+			<enum name="-2"><para>There was an internal error finding or creating a profile</para></enum>
+			<enum name="-3"><para>There was an issue specific to the field specified
+			(value not valid or field name not found)</para></enum>
+		</enumlist>
+		</description>
 	</function>
-	<application name="GeolocProfileCreate" language="en_US">
-		<synopsis>
-			Create a new, empty Geolocation Profile on a channel
-		</synopsis>
-		<syntax>
-			<parameter name="id" required="true"><para>
-				The id of the new profile.
-			</para></parameter>
-			<parameter name="profile_index" required="false"><para>
-			 The position at which to insert the new eprofile.
-			 Existing profiles will be moved forward to make room.
-			 Leave empty to append to the end of the list.
-			</para></parameter>
-		</syntax>
-		<description>
-			<para>This application adds a new, empty Geolocation Profile to a channel.</para>
-			<para>The following variable is set:</para>
-			<variablelist>
-				<variable name="GEOLOC_PROFILE_COUNT">
-					<para>The number of profiles on the channel after the new one is created</para>
-				</variable>
-			</variablelist>
-		</description>
-	</application>
-	<application name="GeolocProfileDelete" language="en_US">
-		<synopsis>
-			Delete a Geolocation Profile from a channel
-		</synopsis>
-		<syntax>
-			<parameter name="profile_index" required="true"><para>
-			 The position of the profile to be deleted
-			 Existing profiles will be moved back.
-			</para></parameter>
-		</syntax>
-		<description>
-			<para>This application deletes a Geolocation Profile from a channel.</para>
-			<para>The following variable is set:</para>
-			<variablelist>
-				<variable name="GEOLOC_PROFILE_COUNT">
-					<para>The number of profiles left on the channel after the delete.</para>
-				</variable>
-			</variablelist>
-		</description>
-	</application>
 </docs>
 
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
index af0e89c..bfba2d2 100644
--- a/res/res_geolocation/geoloc_eprofile.c
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -63,6 +63,7 @@
 	ast_variables_destroy(eprofile->location_variables);
 	ast_variables_destroy(eprofile->effective_location);
 	ast_variables_destroy(eprofile->usage_rules);
+	ast_variables_destroy(eprofile->confidence);
 }
 
 struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name)
@@ -79,8 +80,12 @@
 int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile)
 {
 	struct ast_geoloc_location *loc = NULL;
-	struct ast_variable *temp_locinfo = NULL;
-	struct ast_variable *temp_effloc = NULL;
+	RAII_VAR(struct ast_variable *, temp_locinfo, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, temp_effloc, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, temp_confidence, NULL, ast_variables_destroy);
+	const char *method = NULL;
+	const char *location_source = NULL;
+	enum ast_geoloc_format format;
 	struct ast_variable *var;
 	int rc = 0;
 
@@ -96,20 +101,32 @@
 			return -1;
 		}
 
-		eprofile->format = loc->format;
+		format = loc->format;
+		method = loc->method;
+		location_source = loc->location_source;
 		rc = DUP_VARS(temp_locinfo, loc->location_info);
-		ast_string_field_set(eprofile, method, loc->method);
+		if (rc == 0) {
+			rc = DUP_VARS(temp_confidence, loc->confidence);
+		}
 		ao2_ref(loc, -1);
 		if (rc != 0) {
 			return -1;
 		}
 	} else {
-		temp_locinfo = eprofile->location_info;
+		format = eprofile->format;
+		method = eprofile->method;
+		location_source = eprofile->location_source;
+		rc = DUP_VARS(temp_locinfo, eprofile->location_info);
+		if (rc == 0) {
+			rc = DUP_VARS(temp_confidence, eprofile->confidence);
+		}
+		if (rc != 0) {
+			return -1;
+		}
 	}
 
 	rc = DUP_VARS(temp_effloc, temp_locinfo);
 	if (rc != 0) {
-		ast_variables_destroy(temp_locinfo);
 		return -1;
 	}
 
@@ -117,8 +134,6 @@
 		for (var = eprofile->location_refinement; var; var = var->next) {
 			struct ast_variable *newvar = ast_variable_new(var->name, var->value, "");
 			if (!newvar) {
-				ast_variables_destroy(temp_locinfo);
-				ast_variables_destroy(temp_effloc);
 				return -1;
 			}
 			if (ast_variable_list_replace(&temp_effloc, newvar)) {
@@ -127,10 +142,16 @@
 		}
 	}
 
+	eprofile->format = format;
+	ast_string_field_set(eprofile, method, method);
+	ast_string_field_set(eprofile, location_source, location_source);
+
 	ast_variables_destroy(eprofile->location_info);
 	eprofile->location_info = temp_locinfo;
+	temp_locinfo = NULL;
 	ast_variables_destroy(eprofile->effective_location);
 	eprofile->effective_location = temp_effloc;
+	temp_effloc = NULL;
 
 	return 0;
 }
@@ -153,7 +174,7 @@
 	}
 
 	ao2_lock(profile);
-	eprofile->geolocation_routing = profile->geolocation_routing;
+	eprofile->allow_routing_use = profile->allow_routing_use;
 	eprofile->pidf_element = profile->pidf_element;
 
 	rc = ast_string_field_set(eprofile, location_reference, profile->location_reference);
@@ -175,7 +196,7 @@
 		return NULL;
 	}
 
-	eprofile->action = profile->action;
+	eprofile->precedence = profile->precedence;
 	ao2_unlock(profile);
 
 	if (ast_geoloc_eprofile_refresh_location(eprofile) != 0) {
@@ -301,20 +322,20 @@
 
 
 const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
-	struct ast_channel *chan, struct ast_str **buf, const char *ref_string)
+	struct ast_channel *chan, struct ast_str **buf, const char *ref_str)
 {
 	const char *uri = NULL;
 	struct ast_variable *resolved = NULL;
 	char *result;
 	int we_created_buf = 0;
 
-	if (!eprofile || !buf) {
+	if (!eprofile || !buf || !chan) {
 		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));
+			ref_str, eprofile->id, ast_geoloc_format_to_name(eprofile->format));
 		return NULL;
 	}
 
@@ -330,7 +351,7 @@
 
 	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);
+			ref_str, eprofile->id);
 		return NULL;
 	}
 
@@ -353,14 +374,14 @@
 	return ast_str_buffer(*buf);
 }
 
-static struct ast_variable *var_list_from_node(struct ast_xml_node *node, const char *reference_string)
+static struct ast_variable *var_list_from_node(struct ast_xml_node *node,
+	const char *ref_str)
 {
 	struct ast_variable *list = NULL;
 	struct ast_xml_node *container;
 	struct ast_xml_node *child;
 	struct ast_variable *var;
-	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
-	SCOPE_ENTER(3, "%s\n", reference_string);
+	SCOPE_ENTER(3, "%s\n", ref_str);
 
 	container = ast_xml_node_get_children(node);
 	for (child = container; child; child = ast_xml_node_get_next(child)) {
@@ -370,168 +391,296 @@
 
 		if (uom) {
 			/* '20 radians\0' */
-			char *newval = ast_malloc(strlen(value) + 1 + strlen(uom) + 1);
+			char newval[strlen(value) + 1 + 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, "");
 		}
 
+		ast_xml_free_text(value);
+		ast_xml_free_attr(uom);
+
 		if (!var) {
 			ast_variables_destroy(list);
-			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
 		}
 		ast_variable_list_append(&list, var);
 	}
 
-	ast_variable_list_join(list, ", ", "=", "\"", &buf);
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
 
-	SCOPE_EXIT_RTN_VALUE(list, "%s: Done. %s\n", reference_string, ast_str_buffer(buf));
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
 }
 
 static struct ast_variable *var_list_from_loc_info(struct ast_xml_node *locinfo,
-	enum ast_geoloc_format format, const char *reference_string)
+	enum ast_geoloc_format format, const char *ref_str)
 {
 	struct ast_variable *list = NULL;
 	struct ast_xml_node *container;
-	struct ast_variable *var;
-	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
-	SCOPE_ENTER(3, "%s\n", reference_string);
+	struct ast_variable *var = NULL;
+	const char *attr;
+	SCOPE_ENTER(3, "%s\n", ref_str);
 
 	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);
+		attr = ast_xml_get_attribute(container, "lang");
+		if (attr) {
+			var = ast_variable_new("lang", attr, "");
+			ast_xml_free_attr(attr);
+			if (!var) {
+				SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+			}
+			ast_variable_list_append(&list, var);
 		}
-		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);
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
 		}
 		ast_variable_list_append(&list, var);
-		var = ast_variable_new("crs", ast_xml_get_attribute(container, "srsName"), "");
+
+		attr = ast_xml_get_attribute(container, "srsName");
+		var = ast_variable_new("crs", attr, "");
+		ast_xml_free_attr(attr);
 		if (!var) {
 			ast_variables_destroy(list);
-			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
 		}
 		ast_variable_list_append(&list, var);
 	}
 
-	ast_variable_list_append(&list, var_list_from_node(container, reference_string));
+	ast_variable_list_append(&list, var_list_from_node(container, ref_str));
 
-	ast_variable_list_join(list, ", ", "=", "\"", &buf);
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
 
-	SCOPE_EXIT_RTN_VALUE(list, "%s: Done. %s\n", reference_string, ast_str_buffer(buf));
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
+}
+
+static struct ast_variable *var_list_from_confidence(struct ast_xml_node *confidence,
+	const char *ref_str)
+{
+	struct ast_variable *list = NULL;
+	struct ast_variable *var;
+	const char *pdf;
+	const char *value;
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	if (!confidence) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: No confidence\n", ref_str);
+	}
+
+	pdf = ast_xml_get_attribute(confidence, "pdf");
+	var = ast_variable_new("pdf", S_OR(pdf, "unknown"), "");
+	ast_xml_free_attr(pdf);
+	if (!var) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+	ast_variable_list_append(&list, var);
+
+	value = ast_xml_get_text(confidence);
+	var = ast_variable_new("value", S_OR(value, "95"), "");
+	ast_xml_free_text(value);
+	if (!var) {
+		ast_variables_destroy(list);
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+	ast_variable_list_append(&list, var);
+
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
 }
 
 static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
-	struct ast_xml_doc *result_doc,
-	const char *reference_string)
+	struct ast_xml_doc *result_doc, const char *ref_str)
 {
 	struct ast_geoloc_eprofile *eprofile;
+	/*
+	 * None of the ast_xml_nodes needs to be freed
+	 * because they're just pointers into result_doc.
+	 */
 	struct ast_xml_node *presence = NULL;
 	struct ast_xml_node *pidf_element = NULL;
 	struct ast_xml_node *location_info = NULL;
+	struct ast_xml_node *confidence = 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;
+	/*
+	 * Like nodes, names of nodes are just
+	 * pointers into result_doc and don't need to be freed.
+	 */
 	const char *pidf_element_str;
-	const char *method_str;
-	const char *note_well_str;
-	SCOPE_ENTER(3, "%s\n", reference_string);
+	/*
+	 * Attributes and element text however are allocated on the fly
+	 * so they DO need to be freed after use.
+	 */
+	const char *id = NULL;
+	const char *format_str = NULL;
+	const char *method_str = NULL;
+	const char *note_well_str = NULL;
 
-	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);
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	if (!result_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: result_doc was NULL", ref_str);
+	}
+
+	if (TRACE_ATLEAST(5)) {
+		char *doc_str = NULL;
+		int doc_len = 0;
+
+		ast_xml_doc_dump_memory(result_doc, &doc_str, &doc_len);
+		ast_trace(5, "xslt result doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		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);
+	if (!presence) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find 'presence' root element\n",
+			ref_str);
+	}
 
-	id = S_OR(ast_xml_get_attribute(pidf_element, "id"), ast_xml_get_attribute(presence, "entity"));
+	pidf_element = ast_xml_node_get_children(presence);
+	if (!pidf_element) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find a device, tuple or person element\n",
+			ref_str);
+	}
+
+	id = ast_xml_get_attribute(pidf_element, "id");
+	if (ast_strlen_zero(id)) {
+		ast_xml_free_attr(id);
+		id = ast_xml_get_attribute(presence, "entity");
+	}
+
+	if (ast_strlen_zero(id)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Unable to find 'id' attribute\n", ref_str);
+	}
+
 	eprofile = ast_geoloc_eprofile_alloc(id);
+	ast_xml_free_attr(id);
 	if (!eprofile) {
-		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", reference_string);
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+
+	location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
+	if (!location_info) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find a location-info element\n",
+			ref_str);
 	}
 
 	format_str = ast_xml_get_attribute(location_info, "format");
+	if (ast_strlen_zero(format_str)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Unable to find 'format' attribute\n", ref_str);
+	}
+
+	eprofile->format = AST_GEOLOC_FORMAT_NONE;
 	if (strcasecmp(format_str, "gml") == 0) {
 		eprofile->format = AST_GEOLOC_FORMAT_GML;
 	} else if (strcasecmp(format_str, "civicAddress") == 0) {
 		eprofile->format = AST_GEOLOC_FORMAT_CIVIC_ADDRESS;
-	} else {
-		ao2_ref(eprofile, -1);
-		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unknown format '%s'\n", reference_string, format_str);
 	}
 
-	pidf_element_str = ast_xml_node_get_name(pidf_element);
-	eprofile->pidf_element = geoloc_pidf_element_str_to_enum(pidf_element_str);
+	if (eprofile->format == AST_GEOLOC_FORMAT_NONE) {
+		char *dup_format_str = ast_strdupa(format_str);
+		ast_xml_free_attr(format_str);
+		ao2_ref(eprofile, -1);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unknown format '%s'\n", ref_str, dup_format_str);
+	}
+	ast_xml_free_attr(format_str);
 
-	eprofile->location_info = var_list_from_loc_info(location_info, eprofile->format, reference_string);
+	pidf_element_str = ast_xml_node_get_name(pidf_element);
+	eprofile->pidf_element = ast_geoloc_pidf_element_str_to_enum(pidf_element_str);
+
+	eprofile->location_info = var_list_from_loc_info(location_info, eprofile->format, ref_str);
 	if (!eprofile->location_info) {
 		ao2_ref(eprofile, -1);
 		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR,
-			"%s: Unable to create location variables\n", reference_string);
+			"%s: Unable to create location variables\n", ref_str);
 	}
 
-	eprofile->usage_rules = var_list_from_node(usage_rules, reference_string);
+	/*
+	 * The function calls that follow are all NULL tolerant
+	 * so no need for explicit error checking.
+	 */
+	usage_rules = ast_xml_find_child_element(pidf_element, "usage-rules", NULL, NULL);
+	eprofile->usage_rules = var_list_from_node(usage_rules, ref_str);
+	confidence = ast_xml_find_child_element(location_info, "confidence", NULL, NULL);
+	eprofile->confidence = var_list_from_confidence(confidence, ref_str);
 
+	method = ast_xml_find_child_element(pidf_element, "method", NULL, NULL);
 	method_str = ast_xml_get_text(method);
 	ast_string_field_set(eprofile, method, method_str);
+	ast_xml_free_text(method_str);
 
+	note_well = ast_xml_find_child_element(pidf_element, "note-well", NULL, NULL);
 	note_well_str = ast_xml_get_text(note_well);
 	ast_string_field_set(eprofile, notes, note_well_str);
+	ast_xml_free_text(note_well_str);
 
-	SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", reference_string);
+	SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", ref_str);
+}
+
+static int is_pidf_lo(struct ast_xml_doc *result_doc)
+{
+	struct ast_xml_node *presence;
+	struct ast_xml_node *pidf_element;
+	struct ast_xml_node *location_info;
+	const char *pidf_element_name;
+
+	if (!result_doc) {
+		return 0;
+	}
+	presence = ast_xml_get_root(result_doc);
+	if (!presence || !ast_strings_equal("presence", ast_xml_node_get_name(presence))) {
+		return 0;
+	}
+
+	pidf_element = ast_xml_node_get_children(presence);
+	if (!pidf_element) {
+		return 0;
+	}
+	pidf_element_name = ast_xml_node_get_name(pidf_element);
+	if (!ast_strings_equal(pidf_element_name, "device") &&
+		!ast_strings_equal(pidf_element_name, "tuple") &&
+		!ast_strings_equal(pidf_element_name, "person")) {
+		return 0;
+	}
+
+	location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
+	if (!location_info) {
+		return 0;
+	}
+
+	return 1;
 }
 
 struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
 	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
-	 * ones in the embedded pidf_to_eprofile stylesheet.
-	 *
-	 * RFC5491 Rule 8 states that...
-	 * Where a PIDF document contains more than one <geopriv>
-     * element, the priority of interpretation is given to the first
-     * <device> element in the document containing a location.  If no
-     * <device> element containing a location is present in the document,
-     * then priority is given to the first <tuple> element containing a
-     * location.  Locations contained in <person> tuples SHOULD only be
-     * used as a last resort.
-     *
-     * Reminder: xpath arrays are 1-based not 0-based.
-	 */
-	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};
+	struct ast_xml_doc *result_doc = NULL;
+	struct ast_geoloc_eprofile *eprofile = NULL;
+
 	SCOPE_ENTER(3, "%s\n", ref_str);
 
-
-	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_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_to_eprofile_xslt, pidf_xmldoc, find_person);
-	}
-	if (!result_doc || !ast_xml_node_get_children((struct ast_xml_node *)result_doc)) {
+	result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, NULL);
+	if (!is_pidf_lo(result_doc)) {
 		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Not a PIDF-LO.  Skipping.\n", ref_str);
 	}
 
@@ -546,17 +695,27 @@
 	 *  </presence>
 	 *
 	 * Regardless of whether the pidf-element was tuple, device or person and whether
-	 * the format is gml or civicAddress, the presence, pidf-element, location-info,
-	 * usage-rules and method elements should be there although usage-rules and method
-	 * may be empty.
+	 * the format is gml or civicAddress, the presence, pidf-element and location-info
+	 * elements should be there.
 	 *
-	 * The contents of the location-info and usage-rules elements can be passed directly to
-	 * ast_variable_list_from_string().
+	 * The confidence, usage-rules and note-well elements are optional.
 	 */
 
-	eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, ref_str);
+	if (TRACE_ATLEAST(5)) {
+		char *doc_str = NULL;
+		int doc_len = 0;
 
-	if (geoloc_uri) {
+		ast_xml_doc_dump_memory(result_doc, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
+	}
+
+	eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, ref_str);
+	ast_xml_close(result_doc);
+
+	if (eprofile && geoloc_uri) {
 		set_loc_src(eprofile, geoloc_uri, ref_str);
 	}
 
@@ -586,6 +745,7 @@
 	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 *confidence_node;
 	struct ast_xml_node *info_node;
 	struct ast_xml_node *rules_node;
 	struct ast_xml_node *method_node;
@@ -613,7 +773,7 @@
 		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));
+	rc = ast_xml_set_attribute(loc_node, "format", ast_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);
 	}
@@ -625,14 +785,33 @@
 	} else {
 		info_node = geoloc_gml_list_to_xml(resolved_location, ref_string);
 	}
+	ast_variables_destroy(resolved_location);
+
 	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));
+			ref_string, ast_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));
+			ref_string, ast_geoloc_format_to_name(eprofile->format));
+	}
+
+	if (eprofile->confidence) {
+		const char *value = S_OR(ast_variable_find_in_list(eprofile->confidence, "value"), "95");
+		const char *pdf = S_OR(ast_variable_find_in_list(eprofile->confidence, "pdf"), "unknown");
+
+		confidence_node = ast_xml_new_child(loc_node, "confidence");
+		if (!confidence_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'confidence' XML node\n",
+				ref_string);
+		}
+		rc = ast_xml_set_attribute(confidence_node, "pdf", pdf);
+		if (rc != 0) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'pdf' attribute on 'confidence' element\n", ref_string);
+		}
+
+		ast_xml_set_text(confidence_node, value);
 	}
 
 	rules_node = ast_xml_new_child(pidf_node, "usage-rules");
@@ -646,6 +825,7 @@
 		struct ast_xml_node *ur = ast_xml_new_child(rules_node, var->name);
 		ast_xml_set_text(ur, var->value);
 	}
+	ast_variables_destroy(resolved_usage);
 
 	if (!ast_strlen_zero(eprofile->method)) {
 		method_node = ast_xml_new_child(pidf_node, "method");
@@ -699,8 +879,8 @@
 	struct ast_geoloc_eprofile *eprofile;
 	int eprofile_count = 0;
 	int i;
-	RAII_VAR(char *, doc_str, NULL, ast_xml_free_text);
-	int doc_len;
+	char *doc_str = NULL;
+	int doc_len = 0;
 	int rc = 0;
 	SCOPE_ENTER(3, "%s\n", ref_string);
 
@@ -726,20 +906,28 @@
 		struct ast_xml_node *new_loc = NULL;
 		struct ast_xml_node *new_loc_child = NULL;
 		struct ast_xml_node *new_loc_child_dup = NULL;
+		const char *entity = NULL;
+		int has_no_entity = 0;
 		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"))) {
+		entity = ast_xml_get_attribute(root_node, "entity");
+		has_no_entity = ast_strlen_zero(entity);
+		ast_xml_free_attr(entity);
+		if (has_no_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),
+		temp_node = geoloc_eprofile_to_intermediate(ast_geoloc_pidf_element_to_name(eprofile->pidf_element),
 			eprofile, chan, ref_string);
+		if (!temp_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create temp_node\n", ref_string);
+		}
 
 		if (!pidfs[eprofile->pidf_element]) {
 			pidfs[eprofile->pidf_element] = temp_node;
@@ -756,18 +944,18 @@
 		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);
+	if (TRACE_ATLEAST(5)) {
+		ast_xml_doc_dump_memory(intermediate, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
 	}
 
-	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);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create final PIDF-LO doc from intermediate docs\n",
+			ref_string);
 	}
 
 	ast_xml_doc_dump_memory(pidf_doc, &doc_str, &doc_len);
@@ -777,6 +965,93 @@
 	}
 
 	rc = ast_str_set(buf, 0, "%s", doc_str);
+	ast_xml_free_text(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);
+}
+
+const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
+	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;
+	char *doc_str = NULL;
+	int doc_len;
+	int rc = 0;
+	struct ast_xml_node *temp_node = NULL;
+	const char *entity = NULL;
+	int has_no_entity = 0;
+
+	SCOPE_ENTER(3, "%s\n", ref_string);
+
+	if (!eprofile || !chan || !buf || !*buf || ast_strlen_zero(ref_string)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: One of eprofile, chan or buf was NULL\n",
+			ref_string);
+	}
+
+	if (eprofile->format == AST_GEOLOC_FORMAT_URI) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: eprofile '%s' was a URI format\n",
+			ref_string, eprofile->id);
+	}
+
+	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);
+
+	entity = ast_xml_get_attribute(root_node, "entity");
+	has_no_entity = ast_strlen_zero(entity);
+	ast_xml_free_attr(entity);
+	if (has_no_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(
+		ast_geoloc_pidf_element_to_name(eprofile->pidf_element), eprofile, chan, ref_string);
+	if (!temp_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create temp_node for eprofile '%s'\n",
+			ref_string, eprofile->id);
+	}
+
+	ast_xml_add_child(root_node, temp_node);
+
+	if (TRACE_ATLEAST(5)) {
+		ast_xml_doc_dump_memory(intermediate, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
+	}
+
+	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",
+			ref_string);
+	}
+
+	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);
+	ast_xml_free_text(doc_str);
 	if (rc <= 0) {
 		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to extend buffer (%d)\n",
 			ref_string, rc);
@@ -899,10 +1174,9 @@
 	RAII_VAR(struct ast_str *, str, NULL, ast_free);
 	RAII_VAR(struct ast_geoloc_eprofile *, eprofile,  NULL, ao2_cleanup);
 	RAII_VAR(struct ast_xml_doc *, result_doc, NULL, ast_xml_close);
-	const char *search[] = { "path", path, NULL };
 
 	if (!ast_strlen_zero(path)) {
-		result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, (const char **)search);
+		result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, NULL);
 		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");
@@ -912,8 +1186,8 @@
 
 	ast_test_validate(test, eprofile != NULL);
 	ast_test_status_update(test, "ID: '%s'  pidf_element: '%s'  format: '%s'  method: '%s'\n", eprofile->id,
-		geoloc_pidf_element_to_name(eprofile->pidf_element),
-		geoloc_format_to_name(eprofile->format),
+		ast_geoloc_pidf_element_to_name(eprofile->pidf_element),
+		ast_geoloc_format_to_name(eprofile->format),
 		eprofile->method);
 
 	ast_test_validate(test, ast_strings_equal(eprofile->id, id));
@@ -959,34 +1233,15 @@
 
 	res = validate_eprofile(test, pidf_xmldoc,
 		NULL,
-		"arcband-2d",
-		AST_PIDF_ELEMENT_DEVICE,
+		"point-2d",
+		AST_PIDF_ELEMENT_TUPLE,
 		AST_GEOLOC_FORMAT_GML,
-		"TA-NMR",
-		"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'"
+		"Manual",
+		"shape=Point,crs=2d,pos=-34.410649 150.87651",
+		"retransmission-allowed='no',retention-expiry='2010-11-14T20:00:00Z'"
 		);
 	ast_test_validate(test, res == AST_TEST_PASS);
 
-
-	res = validate_eprofile(test, pidf_xmldoc,
-		"/def:presence/dm:device[.//ca:civicAddress][1]",
-		"pres:alice at asterisk.org",
-		AST_PIDF_ELEMENT_DEVICE,
-		AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
-		"GPS",
-		"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",
-		"retransmission-allowed='yes',ruleset-preference='https:/www/more.com',"
-			"retention-expires='2007-06-22T20:57:29Z'"
-		);
-	ast_test_validate(test, res == AST_TEST_PASS);
-
-
 	return res;
 }
 
diff --git a/res/res_geolocation/geoloc_gml.c b/res/res_geolocation/geoloc_gml.c
index 8a66162..9a5942c 100644
--- a/res/res_geolocation/geoloc_gml.c
+++ b/res/res_geolocation/geoloc_gml.c
@@ -247,6 +247,11 @@
 
 	SCOPE_ENTER(3, "%s", ref_string);
 
+	if (!resolved_location) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: resolved_location was NULL\n",
+			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",
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
index 860f3b5..6b9a63e 100644
--- a/res/res_geolocation/geoloc_private.h
+++ b/res/res_geolocation/geoloc_private.h
@@ -25,18 +25,8 @@
 #include "asterisk/lock.h"
 #include "asterisk/res_geolocation.h"
 
-#define CONFIG_STR_TO_ENUM_DECL(_stem) int geoloc_ ## _stem ## _str_to_enum(const char *str);
-CONFIG_STR_TO_ENUM_DECL(pidf_element)
-CONFIG_STR_TO_ENUM_DECL(format);
-CONFIG_STR_TO_ENUM_DECL(action);
-#define GEOLOC_ENUM_TO_NAME_DECL(_stem) const char * geoloc_ ## _stem ## _to_name(int ix);
-GEOLOC_ENUM_TO_NAME_DECL(pidf_element)
-GEOLOC_ENUM_TO_NAME_DECL(format);
-GEOLOC_ENUM_TO_NAME_DECL(action);
-
-
 #define CONFIG_STR_TO_ENUM(_stem) \
-int geoloc_ ## _stem ## _str_to_enum(const char *str) \
+int ast_geoloc_ ## _stem ## _str_to_enum(const char *str) \
 { \
 	int i; \
 	for (i = 0; i < ARRAY_LEN(_stem ## _names); i++) { \
@@ -51,7 +41,7 @@
 static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
 { \
 	struct ast_geoloc_ ## _object *_thisobject = obj; \
-	int enumval = geoloc_ ## _stem ## _str_to_enum(var->value); \
+	int enumval = ast_geoloc_ ## _stem ## _str_to_enum(var->value); \
 	if (enumval == -1) { \
 		return -1; \
 	} \
@@ -61,7 +51,7 @@
 
 
 #define GEOLOC_ENUM_TO_NAME(_stem) \
-const char * geoloc_ ## _stem ## _to_name(int ix) \
+const char * ast_geoloc_ ## _stem ## _to_name(int ix) \
 { \
 	if (!ARRAY_IN_BOUNDS(ix, _stem ## _names)) { \
 		return "none"; \
diff --git a/res/res_geolocation/pidf_lo_test.xml b/res/res_geolocation/pidf_lo_test.xml
index 3948063..bea98d6 100644
--- a/res/res_geolocation/pidf_lo_test.xml
+++ b/res/res_geolocation/pidf_lo_test.xml
@@ -6,6 +6,7 @@
 	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:con="urn:ietf:params:xml:ns:geopriv:conf"
 	xmlns:gs="http://www.opengis.net/pidflo/1.0">
 	<tuple id="point-2d">
 		<status>
@@ -14,299 +15,19 @@
 					<gml:Point srsName="urn:ogc:def:crs:EPSG::4326">
 						<gml:pos>-34.410649 150.87651</gml:pos>
 					</gml:Point>
+				<con:confidence pdf="normal">66</con:confidence>
 				</gp:location-info>
 				<gp:usage-rules>
 					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
 					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
 				</gp:usage-rules>
 				<gp:method>Manual</gp:method>
+				<gp:note-well>
+					this is a test
+					of the emergency broadcast system
+				</gp:note-well>
 			</gp:geopriv>
 		</status>
 		<timestamp>2007-06-22T20:57:29Z</timestamp>
 	</tuple>
-	<dm:person id="point-3d">
-		<gp:geopriv>
-			<gp:location-info>
-				<gml:Point srsName="urn:ogc:def:crs:EPSG::4979">
-					<gml:pos>-34.410649 150.87651 1800</gml:pos>
-				</gml:Point>
-			</gp:location-info>
-			<gp:usage-rules>
-				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-			</gp:usage-rules>
-			<gp:method>802.11</gp:method>
-		</gp:geopriv>
-		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
-	</dm:person>
-	<tuple id="circle-2d">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Circle srsName="urn:ogc:def:crs:EPSG::4326">
-						<gml:pos>-34.410649 150.87651</gml:pos>
-						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
-					</gs:Circle>
-				</gp:location-info>
-				<gp:usage-rules>
-					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-				</gp:usage-rules>
-				<gp:method>802.11</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<tuple id="circle-3d">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Circle srsName="urn:ogc:def:crs:EPSG::4979">
-						<gml:pos>-34.410649 150.87651 1800</gml:pos>
-						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
-					</gs:Circle>
-				</gp:location-info>
-				<gp:usage-rules>
-					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-				</gp:usage-rules>
-				<gp:method>802.11</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<dm:person id="polygon-2d">
-		<gp:geopriv>
-			<gp:location-info>
-				<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
-					<gml:exterior>
-						<gml:LinearRing>
-							<gml:pos>43.311 -73.422</gml:pos>
-							<gml:pos>43.111 -73.322</gml:pos>
-							<gml:pos>43.111 -73.222</gml:pos>
-							<gml:pos>43.311 -73.122</gml:pos>
-							<gml:pos>43.411 -73.222</gml:pos>
-							<gml:pos>43.411 -73.322</gml:pos>
-							<gml:pos>43.311 -73.422</gml:pos>
-						</gml:LinearRing>
-					</gml:exterior>
-				</gml:Polygon>
-			</gp:location-info>
-			<gp:usage-rules>
-				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-			</gp:usage-rules>
-			<gp:method>802.11</gp:method>
-		</gp:geopriv>
-		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
-	</dm:person>
-	<dm:person id="polygon-3d">
-		<gp:geopriv>
-			<gp:location-info>
-				<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
-					<gml:exterior>
-						<gml:LinearRing>
-							<gml:pos>43.311 -73.422 1800</gml:pos>
-							<gml:pos>43.111 -73.322 1800</gml:pos>
-							<gml:pos>43.111 -73.222 1800</gml:pos>
-							<gml:pos>43.311 -73.122 1800</gml:pos>
-							<gml:pos>43.411 -73.222 1800</gml:pos>
-							<gml:pos>43.411 -73.322 1800</gml:pos>
-							<gml:pos>43.311 -73.422 1800</gml:pos>
-						</gml:LinearRing>
-					</gml:exterior>
-				</gml:Polygon>
-			</gp:location-info>
-			<gp:usage-rules>
-				<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-				<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-			</gp:usage-rules>
-			<gp:method>802.11</gp:method>
-		</gp:geopriv>
-		<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
-	</dm:person>
-	<tuple id="polygon-poslist-2d">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
-						<gml:exterior>
-							<gml:LinearRing>
-								<gml:posList>
-									43.311 -73.422 43.111 -73.322
-									43.111 -73.222 43.311 -73.122
-									43.411 -73.222 43.411 -73.322
-									43.311 -73.422
-								</gml:posList>
-							</gml:LinearRing>
-						</gml:exterior>
-					</gml:Polygon>
-				</gp:location-info>
-				<gp:usage-rules>
-					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-				</gp:usage-rules>
-				<gp:method>Wiremap</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<tuple id="polygon-poslist-3d">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
-						<gml:exterior>
-							<gml:LinearRing>
-								<gml:posList>
-									43.311 -73.422 1800 43.111 -73.322 1800
-									43.111 -73.222 1800 43.311 -73.122 1800
-									43.411 -73.222 1800 43.411 -73.322 1800
-									43.311 -73.422 1800
-								</gml:posList>
-							</gml:LinearRing>
-						</gml:exterior>
-					</gml:Polygon>
-				</gp:location-info>
-				<gp:usage-rules>
-					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-				</gp:usage-rules>
-				<gp:method>Wiremap</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<tuple id="ellipse-2d">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Ellipse srsName="urn:ogc:def:crs:EPSG::4326">
-						<gml:pos>42.5463 -73.2512</gml:pos>
-						<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">1275</gs:semiMajorAxis>
-						<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">670</gs:semiMinorAxis>
-						<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">43.2</gs:orientation>
-					</gs:Ellipse>
-				</gp:location-info>
-				<gp:usage-rules/>
-				<gp:method>Device-Assisted_A-GPS</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<dm:device id="arcband-2d">
-		<gp:geopriv>
-			<gp:location-info>
-				<gs:ArcBand srsName="urn:ogc:def:crs:EPSG::4326">
-					<gml:pos>-43.5723 153.21760</gml:pos>
-					<gs:innerRadius uom="urn:ogc:def:uom:EPSG::9001">3594</gs:innerRadius>
-					<gs:outerRadius uom="urn:ogc:def:uom:EPSG::9001">4148</gs:outerRadius>
-					<gs:startAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:startAngle>
-					<gs:openingAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:openingAngle>
-				</gs:ArcBand>
-			</gp:location-info>
-			<gp:usage-rules>
-				<gp:retransmission-allowed>yes</gp:retransmission-allowed>
-				<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
-				<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
-			</gp:usage-rules>
-			<gp:method>TA-NMR</gp:method>
-		</gp:geopriv>
-		<dm:deviceID>mac:1234567890ab</dm:deviceID>
-		<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
-	</dm:device>
-	<tuple id="sphere">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Sphere srsName="urn:ogc:def:crs:EPSG::4979">
-						<gml:pos>42.5463 -73.2512 26.3</gml:pos>
-						<gs:radius uom="urn:ogc:def:uom:EPSG::9001">850.24</gs:radius>
-					</gs:Sphere>
-				</gp:location-info>
-				<gp:usage-rules>
-					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
-					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
-				</gp:usage-rules>
-				<gp:method>Device-Based_A-GPS</gp:method>
-			</gp:geopriv>
-		</status>
-	</tuple>
-	<tuple id="ellipsoid">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Ellipsoid srsName="urn:ogc:def:crs:EPSG::4979">
-						<gml:pos>42.5463 -73.2512 26.3</gml:pos>
-						<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">7.7156</gs:semiMajorAxis>
-						<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">3.31</gs:semiMinorAxis>
-						<gs:verticalAxis uom="urn:ogc:def:uom:EPSG::9001">28.7</gs:verticalAxis>
-						<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">90</gs:orientation>
-					</gs:Ellipsoid>
-				</gp:location-info>
-				<gp:usage-rules/>
-				<gp:method>Hybrid_A-GPS</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<tuple id="prism">
-		<status>
-			<gp:geopriv>
-				<gp:location-info>
-					<gs:Prism srsName="urn:ogc:def:crs:EPSG::4979">
-						<gs:base>
-							<gml:Polygon>
-								<gml:exterior>
-									<gml:LinearRing>
-										<gml:posList>
-											42.556844 -73.248157 36.6 <!--A -->
-											42.656844 -73.248157 36.6 <!--B -->
-											42.656844 -73.348157 36.6 <!--C -->
-											42.556844 -73.348157 36.6 <!--D -->
-											42.556844 -73.248157 36.6 <!--A -->
-										</gml:posList>
-									</gml:LinearRing>
-								</gml:exterior>
-							</gml:Polygon>
-						</gs:base>
-						<gs:height uom="urn:ogc:def:uom:EPSG::9001">2.4</gs:height>
-					</gs:Prism>
-				</gp:location-info>
-				<gp:usage-rules/>
-				<gp:method>Wiremap</gp:method>
-			</gp:geopriv>
-		</status>
-		<timestamp>2007-06-22T20:57:29Z</timestamp>
-	</tuple>
-	<dm:device>
-		<gp:geopriv>
-			<gp:location-info>
-				<ca:civicAddress xml:lang="en-AU">
-					<ca:country>AU</ca:country>
-					<ca:A1>NSW</ca:A1>
-					<ca:A3>Wollongong</ca:A3>
-					<ca:A4>North Wollongong</ca:A4>
-					<ca:RD>Flinders</ca:RD>
-					<ca:STS>Street</ca:STS>
-					<ca:RDBR>Campbell Street</ca:RDBR>
-					<ca:LMK>Gilligan's Island</ca:LMK>
-					<ca:LOC>Corner</ca:LOC>
-					<ca:NAM> Video Rental Store </ca:NAM>
-					<ca:PC>2500</ca:PC>
-					<ca:ROOM> Westerns and Classics </ca:ROOM>
-					<ca:PLC>store</ca:PLC>
-					<ca:POBOX>Private Box 15</ca:POBOX>
-				</ca:civicAddress>
-			</gp:location-info>
-			<gp:usage-rules>
-				<gp:retransmission-allowed>yes</gp:retransmission-allowed>
-				<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
-				<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
-			</gp:usage-rules>
-			<gp:method>GPS</gp:method>
-		</gp:geopriv>
-		<dm:deviceID>mac:1234567890ab</dm:deviceID>
-		<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
-	</dm:device>
 </presence>
diff --git a/res/res_geolocation/pidf_to_eprofile.xslt b/res/res_geolocation/pidf_to_eprofile.xslt
index 05f4df8..95f000c 100644
--- a/res/res_geolocation/pidf_to_eprofile.xslt
+++ b/res/res_geolocation/pidf_to_eprofile.xslt
@@ -8,6 +8,7 @@
 	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:con="urn:ietf:params:xml:ns:geopriv:conf"
 	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 
@@ -41,34 +42,47 @@
 
 	<xsl:output method="xml" indent="yes"/>
 	<xsl:strip-space elements="*"/>
-	<xsl:param name="path"/>
 
 	<!--
 		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.
+		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"/>
+			<!--
+				We only want devices, tuples and persons (in that order) that
+				have location-info elements.
+			 -->
+			<xsl:apply-templates select="dm:device[./gp:geopriv/gp:location-info]"/>
+			<xsl:apply-templates select="def:tuple[./def:status/gp:geopriv/gp:location-info]"/>
+			<xsl:apply-templates select="dm:person[.//gp:geopriv/gp:location-info]"/>
 		</xsl:element>
 	</xsl:template>
 
-	<xsl:template match="dm:device">
-		<xsl:element name="device">
+	<xsl:template name="geopriv">
+			<xsl:apply-templates select=".//gp:geopriv/gp:location-info"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:usage-rules"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:method"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:note-well"/>
+	</xsl:template>
+
+	<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="./dm:timestamp">
-				<timestamp>
-					<xsl:value-of select="./dm:timestamp"/>
-				</timestamp>
-			</xsl:if>
+			<xsl:call-template name="geopriv"/>
+			<xsl:apply-templates select="./def:timestamp"/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="dm:device|dm:person">
+		<xsl:element name="{local-name(.)}">
+			<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			<xsl:call-template name="geopriv"/>
+			<xsl:apply-templates select="./dm:timestamp"/>
+			<!-- deviceID should only apply to devices -->
 			<xsl:if test="./dm:deviceID">
 				<deviceID>
 					<xsl:value-of select="./dm:deviceID"/>
@@ -77,63 +91,69 @@
 		</xsl:element>
 	</xsl:template>
 
-	<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:*">
+	<xsl:template match="gp:geopriv/gp:location-info">
 		<xsl:element name="location-info">
-			<xsl:attribute name="format">gml</xsl:attribute>
-			<xsl:call-template name="shape" />
+			<xsl:choose>
+				<xsl:when test="ca:civicAddress">
+					<xsl:attribute name="format">civicAddress</xsl:attribute>
+				</xsl:when>
+				<xsl:when test="gml:*">
+					<xsl:attribute name="format">gml</xsl:attribute>
+				</xsl:when>
+				<xsl:when test="gs:*">
+					<xsl:attribute name="format">gml</xsl:attribute>
+				</xsl:when>
+			</xsl:choose>
+			<xsl:apply-templates/>  <!-- Down we go! -->
 		</xsl:element>
 	</xsl:template>
 
-	<xsl:template match="gp:location-info/gs:*">
-		<xsl:element name="location-info">
-			<xsl:attribute name="format">gml</xsl:attribute>
-			<xsl:call-template name="shape" />
-		</xsl:element>
-	</xsl:template>
-
+	<!-- Civic Address -->
 	<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 name="civicAddress">
+			<xsl:attribute name="lang"><xsl:value-of select="@xml:lang"/></xsl:attribute>
+			<!-- The for-each seems to be slightly faster than applying another template -->
+			<xsl:for-each select="./*">
+				<xsl:call-template name="name-value" />
+			</xsl:for-each>
 		</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...
+	<!-- End of Civic Address.  Back up to location-info. -->
 
-		name1="value1", name2="value2"
-	-->
+	<!-- The GML shapes:  gml:Point, gs:Circle, etc. -->
+	<xsl:template match="gp:location-info/gml:*|gp:location-info/gs:*">
+		<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 />  <!-- Down we go! -->
+		</xsl:element>
+	</xsl:template>
+
+	<!-- The supported GML attributes -->
+	<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>
+
+	<!-- The GML attribute types -->
 	<xsl:template name="name-value">
 		<xsl:element name="{local-name(.)}">
 			<xsl:value-of select="normalize-space(.)"/>
@@ -154,58 +174,39 @@
 		</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>
+	<!-- End of GML.  Back up to location-info -->
 
-	<xsl:template name="shape">
+	<xsl:template match="gp:location-info/con:confidence">
 		<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:attribute name="pdf"><xsl:value-of select="@pdf"/></xsl:attribute>
+			<xsl:value-of select="normalize-space(.)" />
 		</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="./*"/>
+	<!-- End of location-info.  Back up to geopriv -->
+
+	<xsl:template match="gp:geopriv/gp:usage-rules">
+		<xsl:element name="usage-rules">
+			<xsl:for-each select="./*">
+				<xsl:call-template name="name-value" />
+			</xsl:for-each>
 		</xsl:element>
 	</xsl:template>
 
-	<xsl:template match="gp:usage-rules/*">
+	<xsl:template match="gp:geopriv/gp:method">
 		<xsl:call-template name="name-value" />
 	</xsl:template>
 
-	<xsl:template match="gp:usage-rules">
-		<xsl:element name="usage-rules">
-			<xsl:apply-templates />
+	<xsl:template match="gp:geopriv/gp:note-well">
+		<xsl:element name="note-well">
+			<xsl:value-of select="." />
 		</xsl:element>
 	</xsl:template>
 
-	<xsl:template match="gp:method">
-		<xsl:element name="method">
-		<xsl:value-of select="normalize-space(.)" />
-		</xsl:element>
+	<!-- End of geopriv.  Back up to device/tuple/person -->
+
+	<xsl:template match="def:timestamp|dm:timestamp">
+		<xsl:call-template name="name-value" />
 	</xsl:template>
 
 
diff --git a/res/res_geolocation/wiki/AsteriskImplementation.md b/res/res_geolocation/wiki/AsteriskImplementation.md
index e818c72..ec1dad6 100644
--- a/res/res_geolocation/wiki/AsteriskImplementation.md
+++ b/res/res_geolocation/wiki/AsteriskImplementation.md
@@ -69,6 +69,11 @@
 * GML: A set of sub-parameters that describe the location.
 Example: {{location_info = shape=Circle, pos="39.12345 -105.98766", radius=100}}
 See the [GML] page for more info.|
+|confidence|no|no|yes|This is a rarely used field in the specification that would indicate the confidence in the location specified.  See [RFC7459|https://www.rfc-editor.org/rfc/rfc7459] for exact details.
+Sub-parameters:
+* {{pdf}}: One of: "unknown", "normal", "rectangular".
+* {{value}}: A percentage indicating the confidence.
+|
 
 
 h2. Profile
@@ -78,14 +83,15 @@
 |type|yes|no|no|Must be "profile"|
 |location_reference|no|no|no|Specifies the id of a Location object to use.|
 |pidf_element|no|no|no|For Civic Address and GML location formats, this parameter specifies the PIDF element that will carry the location description on outgoing SIP requests.  Must be one of "tuple", "device" or "person".  The default is "device".|
-|allow_use_for_routing|no|no|no|This value controls the value of the {{Geolocation-Routing}} header sent on SIP requests,  Must be "yes" or "no".  The default is "no".
+|allow_routing_use|no|no|no|This value controls the value of the {{Geolocation-Routing}} header sent on SIP requests,  Must be "yes" or "no".  The default is "no".
 See [RFC6442|Geolocation Reference Information#rfc6442] for more information.|
-|action|no|no|no|Specifies what should be done with any incoming location descriptions received by a channel referencing this profile.\\
-* {{discard}}: Discard any incoming location descriptions and use only the location description specified by {{location_reference}} (if any).\\
-* {{append}}: Append any incoming location descriptions to the one specified by {{location_reference}} (if any).\\
-* {{prepend}}: Prepend any incoming location descriptions to the one specified by {{location_reference}} (if any).\\
-* {{replace}}: Replace the location description specified by {{location_reference}} (if any) with the ones received.\\
-*WARNING*: Using the {{append}} or {{prepend}} options can cause _multiple_ location objects to be sent to a recipient. [RFC5491|Geolocation Reference Information#rfc5491] discourages the use of multiple location objects but has rules that should be followed if it's necessary.  Unfortunately, as is typical for RFCs, there are many "SHOULD"s and very few "MUST"s in the rules so you should read that RFC carefully before you allow multiple locations.|
+|profile_precedence|no|no|no|Specifies which of the available profiles (configured or incoming) takes precedence.\\
+NOTE: On an incoming call leg/channel, the "incoming" profile is the one received by the channel driver from the calling party in the SIP INVITE and the "configured" profile is the one attached to the calling party's pjsip endpoint.  On an outgoing call segment/channel, the "incoming" profile is the one received by the channel driver from the Asterisk core/dialplan and the "configured" profile one is the one attached to the called party's pjsip endpoint.
+* {{prefer_incoming}}: Use the incoming profile if it exists and has location information, otherwise use the	configured profile if it has location information. If neither profile has location information, nothing is sent.
+* {{force_incoming}}: Discard any configured profile and use the incoming profile if it exists and it has location information.  If the incoming profile doesn't exist or has no location information, nothing is sent.
+* {{prefer_config}}: Use the configured profile if it exists and has location information, otherwise use the	incoming profile if it exists and has location information. If neither profile has location 							information, nothing is sent.
+* {{force_config}}: Discard any incoming profile and use the configured profile if it exists and it has location information.  If the configured profile doesn't exist or has no location information, nothing is sent.
+|
 |usage_rules|no|yes|yes|For Civic Address and GML location formats, this parameter specifies the contents of the {{usage-rules}} PIDF-LO element.\\
 * {{retransmission-allowed}}: Must be "yes" or "no".  The default is "no".\\
 * {{retention-expires}}: An ISO-format timestamp after which the recipient MUST discard and location information associated with this request.  The default is 24 hours after the request was sent.  You can use dialplan functions to create a timestamp yourself if needed.  For example, to set the timestamp to 1 hour after the request is sent, use:
diff --git a/res/res_pjsip_geolocation.c b/res/res_pjsip_geolocation.c
index c4ba003..0ca1e58 100644
--- a/res/res_pjsip_geolocation.c
+++ b/res/res_pjsip_geolocation.c
@@ -36,6 +36,7 @@
 #include "asterisk/res_pjsip_session.h"
 
 static pj_str_t GEOLOCATION_HDR;
+static pj_str_t GEOLOCATION_ROUTING_HDR;
 
 static int find_pidf(const char *session_name, struct pjsip_rx_data *rdata, char *geoloc_uri,
 	char **pidf_body, unsigned int *pidf_len)
@@ -94,6 +95,42 @@
 	return 0;
 }
 
+static int add_eprofile_to_channel(struct ast_sip_session *session,
+	struct ast_geoloc_eprofile *eprofile, struct ast_str * buf)
+{
+	const char *session_name = (session ? ast_sip_session_get_name(session) : "NULL_SESSION");
+	struct ast_datastore *ds = NULL;
+	int rc = 0;
+	SCOPE_ENTER(4, "%s\n", session_name);
+
+	ds = ast_geoloc_datastore_create(session_name);
+	if (!ds) {
+		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
+			"%s: Couldn't allocate a geoloc datastore\n", session_name);
+	}
+
+	/*
+	 * We want the datastore to pass through the dialplan and the core
+	 * so we need to turn inheritance on.
+	 */
+	ast_geoloc_datastore_set_inheritance(ds, 1);
+
+	rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+	if (rc <= 0) {
+		ast_datastore_free(ds);
+		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
+			"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
+			eprofile->id);
+	}
+
+	ast_channel_lock(session->channel);
+	ast_channel_datastore_add(session->channel, ds);
+	ast_channel_unlock(session->channel);
+
+	SCOPE_EXIT_RTN_VALUE(0, "%s: eprofile: '%s' EffectiveLoc: %s\n",
+		session_name, eprofile->id, ast_str_buffer(
+		ast_variable_list_join(eprofile->effective_location, ",", "=", NULL, &buf)));
+}
 
 static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
@@ -101,14 +138,16 @@
 	struct ast_sip_endpoint *endpoint = (session ? session->endpoint : NULL);
 	struct ast_channel *channel = (session ? session->channel : NULL);
 	RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
-	RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_datastore *, ds, NULL, ast_datastore_free);
-	size_t eprofile_count = 0;
+	RAII_VAR(struct ast_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
 	char *geoloc_hdr_value = NULL;
+	char *geoloc_routing_hdr_value = NULL;
 	char *geoloc_uri = NULL;
 	int rc = 0;
 	RAII_VAR(struct ast_str *, buf, ast_str_create(1024), ast_free);
 	pjsip_generic_string_hdr *geoloc_hdr = NULL;
+	pjsip_generic_string_hdr *geoloc_routing_hdr = NULL;
 	SCOPE_ENTER(3, "%s\n", session_name);
 
 	if (!session) {
@@ -130,7 +169,13 @@
 			session_name);
 	}
 
+	/*
+	 * We don't need geoloc_hdr or geoloc_routing_hdr for a while but we get it now
+	 * for trace purposes.
+	 */
 	geoloc_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &GEOLOCATION_HDR, NULL);
+	geoloc_routing_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+		&GEOLOCATION_ROUTING_HDR, NULL);
 
 	if (!geoloc_hdr) {
 		ast_trace(4, "%s: Message has no Geolocation header\n", session_name);
@@ -143,11 +188,11 @@
 		if (geoloc_hdr) {
 			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
 				PJSTR_PRINTF_SPEC "' but endpoint has no geoloc_incoming_call_profile. "
-				"Geolocation info discarded.\n", session_name,
+				"Done.\n", session_name,
 				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
 		} else {
 			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Endpoint has no geoloc_incoming_call_profile. "
-				"Skipping.\n", session_name);
+				"Done.\n", session_name);
 		}
 	}
 
@@ -156,252 +201,177 @@
 		if (geoloc_hdr) {
 			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
 				PJSTR_PRINTF_SPEC "' but endpoint's geoloc_incoming_call_profile doesn't exist. "
-				"Geolocation info discarded.\n", session_name,
+				"Done.\n", session_name,
 				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
 		} else {
 			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has no Geolocation header and endpoint has "
-				" an invalid geoloc_incoming_call_profile.  Nothing to do..\n", session_name);
+				" an invalid geoloc_incoming_call_profile. Done.\n", session_name);
 		}
 	}
 
-	ds = ast_geoloc_datastore_create(session_name);
-	if (!ds) {
-		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
-			"%s: Couldn't allocate a geoloc datastore\n", session_name);
-	}
-
-	/*
-	 * We want the datastore to pass through the dialplan and the core
-	 * so we need to turn inheritance on.
-	 */
-	ast_geoloc_datastore_set_inheritance(ds, 1);
-
-	switch (config_profile->action) {
-	case AST_GEOLOC_ACT_DISCARD_INCOMING:
-		if (geoloc_hdr) {
-			ast_trace(4, "%s: Profile '%s' location_disposition is 'discard_incoming' so "
-				"discarding Geolocation: " PJSTR_PRINTF_SPEC "\n", session_name,
-				ast_sorcery_object_get_id(config_profile),
-				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
-		} else {
-			ast_trace(4, "%s: Profile '%s' location_disposition is 'discard_incoming' but there was no Geolocation header"
-				"so there's nothing to discard\n",
-				session_name, ast_sorcery_object_get_id(config_profile));
-		}
-
-		eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
-		if (!eprofile) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Unable to create eprofile from "
+	if (config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_CONFIG) {
+		config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
+		if (!config_eprofile) {
+			ast_log(LOG_WARNING, "%s: Unable to create config_eprofile from "
 				"profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
 		}
 
-		if (!eprofile->effective_location) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Configured profile has no effective location.  Skipping."
-				"profile '%s'\n", session_name, ast_sorcery_object_get_id(eprofile));
-		}
+		if (config_eprofile && config_eprofile->effective_location) {
+			ast_trace(4, "%s: config eprofile '%s' has effective location\n",
+				session_name, config_eprofile->id);
 
-		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
-		if (rc <= 0) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
-				"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
-				eprofile->id);
-		}
+			if (!geoloc_hdr || config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING ||
+				config_profile->precedence == AST_GEOLOC_PRECED_PREFER_CONFIG) {
 
-		ast_channel_lock(channel);
-		ast_channel_datastore_add(channel, ds);
-		ast_channel_unlock(channel);
-		/* We gave the datastore to the channel so don't let RAII_VAR clean it up. */
-		ds = NULL;
+				ast_trace(4, "%s: config eprofile '%s' is being used\n",
+					session_name, config_eprofile->id);
 
-		ast_trace(4, "ep: '%s' EffectiveLoc: %s\n", eprofile->id, ast_str_buffer(
-			ast_variable_list_join(eprofile->effective_location, ",", "=", NULL, &buf)));
-		ast_str_reset(buf);
+				/*
+				 * If we have an effective location and there's no geolocation header,
+				 * or the action is either DISCARD_INCOMING or PREFER_CONFIG,
+				 * we don't need to even look for a Geolocation header so just add the
+				 * config eprofile to the channel and exit.
+				 */
 
-		/* We discarded the Geolocation header so there's no need to go on. */
-		SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with 1 eprofile from config\n",
-			session_name);
+				rc = add_eprofile_to_channel(session, config_eprofile, buf);
+				if (rc != 0) {
+					SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
+						"%s: Couldn't add config eprofile '%s' to datastore. Fail.\n", session_name,
+						config_eprofile->id);
+				}
 
-	case AST_GEOLOC_ACT_DISCARD_CONFIG:
-		if (geoloc_hdr) {
-			ast_trace(4, "%s: Profile '%s' location_disposition is 'discard_config' so "
-				"discarding config profile\n", session_name, ast_sorcery_object_get_id(config_profile));
-			/* We process the Geolocation header down below. */
-		} else {
-			/* Discarded the config and there's no Geolocation header so we're done. */
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Profile '%s' location_disposition is 'discard_config' but "
-				"there was no Geolocation header so there's nothing left to process\n",
-				session_name, ast_sorcery_object_get_id(config_profile));
-		}
-		break;
-
-	case AST_GEOLOC_ACT_PREFER_CONFIG:
-		eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
-		if (!eprofile) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Unable to create eprofile from "
-				"profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
-		}
-
-		if (!eprofile->effective_location) {
-			if (geoloc_hdr) {
-				ast_trace(4, "%s: Profile '%s' location_disposition is 'prefer_config' but the configured"
-					"eprofile has no location information.  Falling back to Geolocation: "
-					PJSTR_PRINTF_SPEC "\n", session_name, ast_sorcery_object_get_id(config_profile),
-				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
-				/* We process the Geolocation header down below. */
-			} else {
-				SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Configured profile '%s' has no effective location"
-					" and there was no Geolocation header.  Skipping.\n",
-					session_name, ast_sorcery_object_get_id(eprofile));
+				SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with eprofile from config. Done.\n",
+					session_name);
 			}
-			break;
-		}
-
-		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
-		if (rc <= 0) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
-				"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
-				eprofile->id);
-		}
-
-		ast_channel_lock(channel);
-		ast_channel_datastore_add(channel, ds);
-		ast_channel_unlock(channel);
-		/* We gave the datastore to the channel so don't let RAII_VAR clean it up. */
-		ds = NULL;
-
-		if (geoloc_hdr) {
-			ast_trace(4, "%s: Profile '%s' location_disposition is 'prefer_config' so "
-				"discarding Geolocation: " PJSTR_PRINTF_SPEC "\n",
-				session_name, ast_sorcery_object_get_id(config_profile), PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
-		}
-
-		ast_trace(4, "ep: '%s' EffectiveLoc: %s\n", eprofile->id, ast_str_buffer(
-			ast_variable_list_join(eprofile->effective_location, ",", "=", NULL, &buf)));
-		ast_str_reset(buf);
-
-		/* We discarded the Geolocation header so there's no need to go on. */
-		SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with 1 eprofile from config\n",
-			session_name);
-
-	case AST_GEOLOC_ACT_PREFER_INCOMING:
-		if (geoloc_hdr) {
-			ast_trace(4, "%s: Profile '%s' location_disposition is 'replace' so "
-				"we don't need to do anything with the configured profile", session_name,
-				ast_sorcery_object_get_id(config_profile));
 		} else {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE,
-				"%s: Profile '%s' location_disposition is 'replace' but there's "
-				"no Geolocation header and therefore no location info to replace"
-				"it with\n", session_name, ast_sorcery_object_get_id(config_profile));
+			/*
+			 * If the config eprofile has no effective location, just get rid
+			 * of it.
+			 */
+			ast_trace(4, "%s: Either config_eprofile didn't exist or it had no effective location\n",
+				session_name);
+
+			ao2_cleanup(config_eprofile);
+			config_eprofile = NULL;
+			if (config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING) {
+				SCOPE_EXIT_RTN_VALUE(0, "%s: DISCARD_INCOMING set and no config eprofile. Done.\n",
+					session_name);
+			}
 		}
 	}
 
-	geoloc_hdr_value = ast_alloca(geoloc_hdr->hvalue.slen + 1);
-	ast_copy_pj_str(geoloc_hdr_value, &geoloc_hdr->hvalue, geoloc_hdr->hvalue.slen + 1);
-
 	/*
-	 * From RFC-6442:
-	 * Geolocation-header = "Geolocation" HCOLON locationValue
-	 *                      *( COMMA locationValue )
-	 * locationValue      = LAQUOT locationURI RAQUOT
-	 *                      *(SEMI geoloc-param)
-	 * locationURI        = sip-URI / sips-URI / pres-URI
-	 *                        / http-URI / https-URI
-	 *	                      / cid-url ; (from RFC 2392)
-	 *                        / absoluteURI ; (from RFC 3261)
+	 * At this point, if we have a config_eprofile, then the action was
+	 * PREFER_INCOMING so we're going to keep it as a backup if we can't
+	 * get a profile from the incoming message.
 	 */
-	while((geoloc_uri = ast_strsep(&geoloc_hdr_value, ',', AST_STRSEP_TRIM))) {
-		/* 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_hdr && config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_INCOMING) {
 
-		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;
-		}
 		/*
-		 * If the URI isn't "cid" then we're just going to pass it through.
+		 * From RFC-6442:
+		 * Geolocation-header = "Geolocation" HCOLON locationValue
+		 *                      *( COMMA locationValue )
+		 * locationValue      = LAQUOT locationURI RAQUOT
+		 *                      *(SEMI geoloc-param)
+		 * locationURI        = sip-URI / sips-URI / pres-URI
+		 *                        / http-URI / https-URI
+		 *	                      / cid-url ; (from RFC 2392)
+		 *                        / absoluteURI ; (from RFC 3261)
 		 */
-		if (!ast_begins_with(geoloc_uri, "<cid:")) {
+
+		geoloc_hdr_value = ast_alloca(geoloc_hdr->hvalue.slen + 1);
+		ast_copy_pj_str(geoloc_hdr_value, &geoloc_hdr->hvalue, geoloc_hdr->hvalue.slen + 1);
+
+		/*
+		 * We're going to scan the header value for URIs until we find
+		 * one that processes successfully or we run out of URIs.
+		 * I.E.  The first good one wins.
+		 */
+		while (geoloc_hdr_value && !incoming_eprofile) {
+			char *pidf_body = NULL;
+			unsigned int pidf_len = 0;
+			struct ast_xml_doc *incoming_doc = NULL;
+			int rc = 0;
+
+			/* We're only going to consider the first URI in the header for now */
+			geoloc_uri = ast_strsep(&geoloc_hdr_value, ',', AST_STRSEP_TRIM);
+			if (ast_strlen_zero(geoloc_uri) || geoloc_uri[0] != '<' || strchr(geoloc_uri, '>') == NULL) {
+				ast_log(LOG_WARNING, "%s: Geolocation header has no or bad URI '%s'.  Skipping\n", session_name,
+					S_OR(geoloc_uri, "<empty>"));
+				continue;
+			}
+
 			ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
 
-			eprofile = ast_geoloc_eprofile_create_from_uri(geoloc_uri, session_name);
-			if (!eprofile) {
-				ast_log(LOG_WARNING, "%s: Unable to create effective profile for URI '%s'.  Skipping\n",
-					session_name, geoloc_uri);
-				continue;
+			if (!ast_begins_with(geoloc_uri, "<cid:")) {
+				ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
+
+				incoming_eprofile = ast_geoloc_eprofile_create_from_uri(geoloc_uri, session_name);
+				if (!incoming_eprofile) {
+					ast_log(LOG_WARNING, "%s: Unable to create effective profile for URI '%s'.  Skipping\n",
+						session_name, geoloc_uri);
+					continue;
+				}
+			} else {
+				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) {
+					ast_log(LOG_WARNING, "%s: Unable to parse pidf document for URI '%s'\n",
+						session_name, geoloc_uri);
+					continue;
+				}
+
+				incoming_eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, geoloc_uri, session_name);
+				ast_xml_close(incoming_doc);
+
+				if (!incoming_eprofile) {
+					ast_log(LOG_WARNING,
+						"%s: Couldn't create incoming_eprofile from pidf\n", session_name);
+					continue;
+				}
 			}
-		} else {
-			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) {
-				ast_log(LOG_WARNING, "%s: Unable to parse pidf document for URI '%s'\n",
-					session_name, geoloc_uri);
-				continue;
-			}
-
-			eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, geoloc_uri, session_name);
-		}
-		eprofile->action = config_profile->action;
-
-		ast_trace(4, "Processing URI '%s'.  Adding to datastore\n", geoloc_uri);
-		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
-		ao2_ref(eprofile, -1);
-		if (rc <= 0) {
-			ast_log(LOG_WARNING, "%s: Unable to add effective profile for URI '%s' to datastore.  Skipping\n",
-				session_name, geoloc_uri);
 		}
 	}
 
-	if (config_profile->action == AST_GEOLOC_ACT_PREFER_CONFIG) {
-		ast_trace(4, "%s: Profile '%s' location_disposition is 'prepend' so "
-			"adding to datastore first", session_name, ast_sorcery_object_get_id(config_profile));
-
-		eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
-		if (!eprofile) {
-			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Unable to create eprofile from"
-				" profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
+	if (!incoming_eprofile) {
+		/* Use the config_eprofile as a backup if there was one */
+		incoming_eprofile = config_eprofile;
+	} else {
+		ao2_cleanup(config_eprofile);
+		config_eprofile = NULL;
+		if (geoloc_routing_hdr) {
+			geoloc_routing_hdr_value = ast_alloca(geoloc_routing_hdr->hvalue.slen + 1);
+			ast_copy_pj_str(geoloc_routing_hdr_value, &geoloc_routing_hdr->hvalue,
+				geoloc_routing_hdr->hvalue.slen + 1);
+			incoming_eprofile->allow_routing_use = ast_true(geoloc_routing_hdr_value);
 		}
+	}
 
-		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
-		if (rc <= 0) {
+	if (incoming_eprofile) {
+		rc = add_eprofile_to_channel(session, incoming_eprofile, buf);
+		if (rc != 0) {
 			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
-				"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
-				eprofile->id);
+				"%s: Couldn't add eprofile '%s' to channel. Fail.\n", session_name,
+				incoming_eprofile->id);
 		}
+
+		SCOPE_EXIT_RTN_VALUE(0, "%s: Added eprofile '%s' to channel. Done.\n",
+			session_name, incoming_eprofile->id);
 	}
 
-	eprofile_count = ast_geoloc_datastore_size(ds);
-	if (eprofile_count == 0) {
-		SCOPE_EXIT_RTN_VALUE(0,
-			"%s: Unable to add any effective profiles.  Not adding datastore to channel.\n",
-			session_name);
-	}
-
-	ast_channel_lock(channel);
-	ast_channel_datastore_add(channel, ds);
-	ast_channel_unlock(channel);
-	ds = NULL;
-
-	SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with %" PRIu64 " eprofiles\n",
-		session_name, eprofile_count);
+	SCOPE_EXIT_RTN_VALUE(0, "%s: No eprofiles to add to channel. Done.\n",	session_name);
 }
 
-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 char *add_eprofile_to_tdata(struct ast_geoloc_eprofile *eprofile, struct ast_channel *channel,
+	struct pjsip_tx_data *tdata, struct ast_str **buf, const char *session_name)
 {
 	static const pj_str_t from_name = { "From", 4};
 	static const pj_str_t cid_name = { "Content-ID", 10 };
@@ -417,9 +387,8 @@
 	char id[6];
 	size_t alloc_size;
 	RAII_VAR(char *, base_cid, NULL, ast_free);
-	const char *final;
+	const char *final_doc;
 	int rc = 0;
-	RAII_VAR(struct ast_str *, buf, ast_str_create(1024), ast_free);
 	SCOPE_ENTER(3, "%s\n", session_name);
 
 	/*
@@ -427,8 +396,13 @@
 	 * 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);
+	final_doc = ast_geoloc_eprofile_to_pidf(eprofile, channel, buf, session_name);
+	ast_trace(5, "Final pidf: \n%s\n", final_doc);
+
+	if (!final_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create pidf document from"
+			" eprofile '%s'\n\n", session_name, eprofile->id);
+	}
 
 	/*
 	 * There _should_ be an SDP already attached to the tdata at this point
@@ -450,7 +424,7 @@
 
 		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",
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create sdp multipart body\n",
 				session_name);
 		}
 	} else {
@@ -458,7 +432,7 @@
 	}
 
 	pidf_part = pjsip_multipart_create_part(tdata->pool);
-	pj_cstr(&pidf_body_text, final);
+	pj_cstr(&pidf_body_text, final_doc);
 	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);
 
@@ -471,10 +445,8 @@
 			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)));
+	ast_str_set(buf, 0, "cid:%s", base_cid);
+	ast_trace(4, "cid: '%s' uri: '%s'\n", base_cid, ast_str_buffer(*buf));
 
 	cid_value.ptr = pj_pool_alloc(tdata->pool, alloc_size);
 	cid_value.slen = sprintf(cid_value.ptr, "<%s>", base_cid);
@@ -485,7 +457,7 @@
 
     tdata->msg->body = multipart_body;
 
-	SCOPE_EXIT_RTN_VALUE(0, "%s: PIDF-LO added with cid '%s'\n", session_name, base_cid);
+	SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%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)
@@ -495,18 +467,15 @@
 	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_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
+	struct ast_geoloc_eprofile *final_eprofile = NULL;
 	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;
+	pjsip_msg_body *orig_body = NULL;
+	pjsip_generic_string_hdr *geoloc_hdr = NULL;
 	int eprofile_count = 0;
-	int pidf_index = -1;
-	int geoloc_routing = 0;
 	int rc = 0;
-	const char *final;
+	const char *uri;
 	SCOPE_ENTER(3, "%s\n", session_name);
 
 	if (!buf) {
@@ -541,136 +510,111 @@
 			"profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
 	}
 
-	if (config_profile->action != AST_GEOLOC_ACT_PREFER_INCOMING) {
-		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);
-		}
+	if (!config_eprofile->effective_location) {
+		/*
+		 * If there's no effective location on the eprofile
+		 * we don't need to keep it.
+		 */
+		ast_trace(4, "%s: There was no effective location for config profile '%s'\n",
+			session_name, ast_sorcery_object_get_id(config_profile));
+		ao2_ref(config_eprofile, -1);
+		config_eprofile = NULL;
 	}
 
-	/*
-	 * 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");
+	ds = ast_geoloc_datastore_find(channel);
 	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);
+		ast_trace(4, "%s: There was no geoloc datastore on the channel\n", session_name);
 	} else {
-		if (config_profile->action == AST_GEOLOC_ACT_PREFER_CONFIG) {
-			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_ACT_PREFER_INCOMING) {
-			ast_trace(4, "%s: appending config_eprofile\n", session_name);
-			ast_geoloc_datastore_add_eprofile(tempds, config_eprofile);
-		}
+		eprofile_count = ast_geoloc_datastore_size(ds);
+		ast_trace(4, "%s: There are %d geoloc profiles on this channel\n", session_name,
+			eprofile_count);
+		/*
+		 * There'd better be a max of 1 at this time.  In the future
+		 * we may allow more than 1.
+		 */
+		incoming_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
 	}
 
-	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);
+	ast_trace(4, "%s: Profile precedence: %s\n\n", session_name,
+		ast_geoloc_precedence_to_name(config_profile->precedence));
 
-	/*
-	 * 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);
+	switch (config_profile->precedence) {
+	case AST_GEOLOC_PRECED_DISCARD_INCOMING:
+		final_eprofile = config_eprofile;
+		ao2_cleanup(incoming_eprofile);
+		incoming_eprofile = NULL;
+		break;
+	case AST_GEOLOC_PRECED_PREFER_INCOMING:
+		if (incoming_eprofile) {
+			final_eprofile = incoming_eprofile;
+			ao2_cleanup(config_eprofile);
+			config_eprofile = NULL;
 		} 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;
-			}
+			final_eprofile = config_eprofile;
 		}
-		/* The LAST eprofile determines routing */
-		geoloc_routing = ep->geolocation_routing;
-		ao2_ref(ep, -1);
+		break;
+	case AST_GEOLOC_PRECED_DISCARD_CONFIG:
+		final_eprofile = incoming_eprofile;
+		ao2_cleanup(config_eprofile);
+		config_eprofile = NULL;
+		break;
+	case AST_GEOLOC_PRECED_PREFER_CONFIG:
+		if (config_eprofile) {
+			final_eprofile = config_eprofile;
+			ao2_cleanup(incoming_eprofile);
+			incoming_eprofile = NULL;
+		} else {
+			final_eprofile = incoming_eprofile;
+		}
+		break;
 	}
 
-	/*
-	 * 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);
+	if (!final_eprofile) {
+		SCOPE_EXIT_RTN("%s: No eprofiles to send.  Done.\n",
+			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_geoloc_eprofile_refresh_location(final_eprofile);
+	if (final_eprofile->format == AST_GEOLOC_FORMAT_URI) {
+		uri = ast_geoloc_eprofile_to_uri(final_eprofile, channel, &buf, session_name);
+		if (!uri) {
+			SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to create URI from eprofile '%s'\n",
+				session_name, final_eprofile->id);
+		}
+	} else {
+		orig_body = tdata->msg->body;
+		uri = add_eprofile_to_tdata(final_eprofile, channel, tdata, &buf, session_name);
+		if (!uri) {
+			tdata->msg->body = orig_body;
+			SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add eprofile '%s' to tdata\n",
+				session_name, final_eprofile->id);
+		}
+	}
+
+	uri = ast_strdupa(ast_str_buffer(buf));
 	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_str_set(&buf, 0, "<%s>", uri);
+	uri = ast_strdupa(ast_str_buffer(buf));
 
-	AST_VECTOR_RESET(&uris, ast_free);
-	AST_VECTOR_FREE(&uris);
+	ast_trace(4, "%s: Using URI '%s'\n", session_name, uri);
 
 	/* 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));
+	geoloc_hdr = ast_sip_add_header2(tdata, "Geolocation", uri);
 	if (geoloc_hdr == NULL) {
-		tdata->msg->body = orig_body;
+		if (orig_body) {
+			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");
+	rc = ast_sip_add_header(tdata, "Geolocation-Routing", final_eprofile->allow_routing_use ? "yes" : "no");
 	if (rc != 0) {
-		tdata->msg->body = orig_body;
+		if (orig_body) {
+			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));
+	SCOPE_EXIT_RTN("%s: Geolocation: %s\n", session_name, uri);
 }
 
 static struct ast_sip_session_supplement geolocation_supplement = {
@@ -697,6 +641,7 @@
 {
 	int res = 0;
 	GEOLOCATION_HDR = pj_str("Geolocation");
+	GEOLOCATION_ROUTING_HDR = pj_str("Geolocation-Routing");
 
 	ast_sip_session_register_supplement(&geolocation_supplement);
 

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18897
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 18
Gerrit-Change-Id: If38c23f26228e96165be161c2f5e849cb8e16fa0
Gerrit-Change-Number: 18897
Gerrit-PatchSet: 5
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220811/92fd46de/attachment-0001.html>


More information about the asterisk-code-review mailing list