Commit 5c16412dc4 for asterisk.org

commit 5c16412dc4ed0d1945dd8402418ed3ad87a2d965
Author: George Joseph <gjoseph@sangoma.com>
Date:   Wed Dec 17 15:49:06 2025 -0700

    res_geolocation:  Fix multiple issues with XML generation.

    * 3d positions were being rendered without an enclosing `<gml:pos>`
      element resulting in invalid XML.
    * There was no way to set the `id` attribute on the enclosing `tuple`, `device`
      and `person` elements.
    * There was no way to set the value of the `deviceID` element.
    * Parsing of degree and radian UOMs was broken resulting in them appearing
      outside an XML element.
    * The UOM schemas for degrees and radians were reversed.
    * The Ellipsoid shape was missing and the Ellipse shape was defined multiple
      times.
    * The `crs` location_info parameter, although documented, didn't work.
    * The `pos3d` location_info parameter appears in some documentation but
      wasn't being parsed correctly.
    * The retransmission-allowed and retention-expiry sub-elements of usage-rules
      were using the `gp` namespace instead of the `gbp` namespace.

    In addition to fixing the above, several other code refactorings were
    performed and the unit test enhanced to include a round trip
    XML -> eprofile -> XML validation.

    Resolves: #1667

    UserNote: Geolocation: Two new optional profile parameters have been added.
    * `pidf_element_id` which sets the value of the `id` attribute on the top-level
      PIDF-LO `device`, `person` or `tuple` elements.
    * `device_id` which sets the content of the `<deviceID>` element.
    Both parameters can include channel variables.

    UpgradeNote: Geolocation: In order to correct bugs in both code and
    documentation, the following changes to the parameters for GML geolocation
    locations are now in effect:
    * The documented but unimplemented `crs` (coordinate reference system) element
      has been added to the location_info parameter that indicates whether the `2d`
      or `3d` reference system is to be used. If the crs isn't valid for the shape
      specified, an error will be generated. The default depends on the shape
      specified.
    * The Circle, Ellipse and ArcBand shapes MUST use a `2d` crs.  If crs isn't
      specified, it will default to `2d` for these shapes.
      The Sphere, Ellipsoid and Prism shapes MUST use a `3d` crs. If crs isn't
      specified, it will default to `3d` for these shapes.
      The Point and Polygon shapes may use either crs.  The default crs is `2d`
      however so if `3d` positions are used, the crs must be explicitly set to `3d`.
    * The `geoloc show gml_shape_defs` CLI command has been updated to show which
      coordinate reference systems are valid for each shape.
    * The `pos3d` element has been removed in favor of allowing the `pos` element
      to include altitude if the crs is `3d`.  The number of values in the `pos`
      element MUST be 2 if the crs is `2d` and 3 if the crs is `3d`.  An error
      will be generated for any other combination.
    * The angle unit-of-measure for shapes that use angles should now be included
      in the respective parameter.  The default is `degrees`. There were some
      inconsistent references to `orientation_uom` in some documentation but that
      parameter never worked and is now removed.  See examples below.
    Examples...
    ```
      location_info = shape="Sphere", pos="39.0 -105.0 1620", radius="20"
      location_info = shape="Point", crs="3d", pos="39.0 -105.0 1620"
      location_info = shape="Point", pos="39.0 -105.0"
      location_info = shape=Ellipsoid, pos="39.0 -105.0 1620", semiMajorAxis="20"
                    semiMinorAxis="10", verticalAxis="0", orientation="25 degrees"
      pidf_element_id = ${CHANNEL(name)}-${EXTEN}
      device_id = mac:001122334455
      Set(GEOLOC_PROFILE(pidf_element_id)=${CHANNEL(name)}/${EXTEN})
    ```

diff --git a/configs/samples/geolocation.conf.sample b/configs/samples/geolocation.conf.sample
index 0f2921b42c..25c033e927 100644
--- a/configs/samples/geolocation.conf.sample
+++ b/configs/samples/geolocation.conf.sample
@@ -69,7 +69,7 @@ civicAddress: [RFC4119] [RFC5139] [RFC5491]
               For chan_pjsip, this will be placed in the body of
               outgoing INVITE messages in addition to any SDP.

-GML:          [RFC4119] [RFC5491] [GeoShape]
+GML:          [RFC4119] [RFC4479] [RFC5491] [GeoShape]
               The location information will be placed in an XML document
               conforming to the PIDF-LO standard.
               For chan_pjsip, this will be placed in the body of
@@ -222,6 +222,28 @@ Per [RFC5491], "device" is preferred and therefore the default.
 Example:
 pidf_element = tuple

+-- pidf_element id(optional) ------------------------------------------
+Sets the value of the 'id' attribute for the top-level PIDF-LO element.
+You can reference channel variables in this parameter.
+
+pidf_element_id = <any valid attribute string>
+
+Example:
+pidf_element_id = ${CHANNEL(name)}
+
+-- device_id (optional) -----------------------------------------------
+Sets the contents of the <dm:deviceID> element in the top-level
+PIDF-LO element.  RFC4479 defined this only as a 'URN' with the only
+examples being a mac address in the format 'mac:XXXXXXXXXXXX'.
+You can reference channel variables in this parameter.
+
+device_id = <urn>
+
+Example:
+device_id = mac:40000b0c0d12
+device_id = mac:${MAC_ADDRESS}
+
+
 -- allow_routing_use (optional) ---------------------------------------
 Sets whether the "Geolocation-Routing" header is added to outgoing
 requests.
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
index 2f88c80adc..e900b3b7c7 100644
--- a/include/asterisk/res_geolocation.h
+++ b/include/asterisk/res_geolocation.h
@@ -77,6 +77,8 @@ struct ast_geoloc_profile {
 		AST_STRING_FIELD(notes);
 		AST_STRING_FIELD(method);
 		AST_STRING_FIELD(location_source);
+		AST_STRING_FIELD(pidf_element_id);
+		AST_STRING_FIELD(device_id);
 	);
 	enum ast_geoloc_pidf_element pidf_element;
 	enum ast_geoloc_precedence precedence;
@@ -97,6 +99,8 @@ struct ast_geoloc_eprofile {
 		AST_STRING_FIELD(location_source);
 		AST_STRING_FIELD(method);
 		AST_STRING_FIELD(notes);
+		AST_STRING_FIELD(pidf_element_id);
+		AST_STRING_FIELD(device_id);
 	);
 	enum ast_geoloc_pidf_element pidf_element;
 	enum ast_geoloc_precedence precedence;
@@ -150,13 +154,15 @@ AST_OPTIONAL_API(struct ast_geoloc_profile *, ast_geoloc_get_profile,
 int ast_geoloc_civicaddr_is_code_valid(const char *code);

 enum ast_geoloc_validate_result {
-	AST_GEOLOC_VALIDATE_INVALID_VALUE = -1,
 	AST_GEOLOC_VALIDATE_SUCCESS = 0,
 	AST_GEOLOC_VALIDATE_MISSING_SHAPE,
 	AST_GEOLOC_VALIDATE_INVALID_SHAPE,
 	AST_GEOLOC_VALIDATE_INVALID_VARNAME,
 	AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES,
 	AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES,
+	AST_GEOLOC_VALIDATE_INVALID_CRS,
+	AST_GEOLOC_VALIDATE_INVALID_CRS_FOR_SHAPE,
+	AST_GEOLOC_VALIDATE_INVALID_VALUE,
 };

 const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result);
@@ -170,7 +176,7 @@ const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result re
  * \return result code.
  */
 enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
-	const struct ast_variable *varlist, const char **result);
+	const struct ast_variable *varlist, char **result);

 /*!
  * \brief Validate that the variables in the list represent a valid GML shape
@@ -180,8 +186,8 @@ enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
  *
  * \return result code.
  */
-enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
-	const char **result);
+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(struct ast_variable *varlist,
+	char **result);


 /*!
diff --git a/res/res_geolocation/eprofile_to_pidf.xslt b/res/res_geolocation/eprofile_to_pidf.xslt
index 797fa66cdf..26b975a9a9 100644
--- a/res/res_geolocation/eprofile_to_pidf.xslt
+++ b/res/res_geolocation/eprofile_to_pidf.xslt
@@ -53,6 +53,9 @@

 	<xsl:template match="tuple">
 		<xsl:element name="tuple" namespace="urn:ietf:params:xml:ns:pidf">
+			<xsl:if test="@id">
+				<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			</xsl:if>
 			<xsl:element name="status" namespace="urn:ietf:params:xml:ns:pidf">
 				<gp:geopriv>
 					<xsl:apply-templates select="./location-info"/>
@@ -66,6 +69,11 @@
 					<xsl:value-of select="./timestamp"/>
 				</xsl:element>
 			</xsl:if>
+			<xsl:if test="./deviceID">
+				<dm:deviceID>
+					<xsl:value-of select="./deviceID"/>
+				</dm:deviceID>
+			</xsl:if>
 		</xsl:element>
 	</xsl:template>

@@ -171,11 +179,11 @@
 	</xsl:template>

 	<!-- usage-rules does have children so we add the "gp" namespace and copy in
-		the children, also adding the "gp" namespace -->
+		the children, also adding the "gbp" namespace -->
 	<xsl:template match="usage-rules">
 		<gp:usage-rules>
 			 <xsl:for-each select="*">
-				 <xsl:element name="gp:{local-name()}">
+				 <xsl:element name="gbp:{local-name()}">
 					 <xsl:value-of select="."/>
 				 </xsl:element>
 			 </xsl:for-each>
@@ -201,10 +209,10 @@
 		<xsl:element name="gs:{name()}">
 			<xsl:choose>
 				<xsl:when test="@uom = 'radians'">
-					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
+					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
 				</xsl:when>
 				<xsl:otherwise>
-					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
+					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
 				</xsl:otherwise>
 			</xsl:choose>
 			<xsl:value-of select="."/>
diff --git a/res/res_geolocation/geoloc_civicaddr.c b/res/res_geolocation/geoloc_civicaddr.c
index f5a7c22c5d..e0951b101a 100644
--- a/res/res_geolocation/geoloc_civicaddr.c
+++ b/res/res_geolocation/geoloc_civicaddr.c
@@ -73,13 +73,13 @@ int ast_geoloc_civicaddr_is_code_valid(const char *code)
 }

 enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
-	const struct ast_variable *varlist,	const char **result)
+	const struct ast_variable *varlist,	char **result)
 {
 	const struct ast_variable *var = varlist;
 	for (; var; var = var->next) {
 		int valid = ast_geoloc_civicaddr_is_code_valid(var->name);
 		if (!valid) {
-			*result = var->name;
+			*result = ast_strdup(var->name);
 			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
 		}
 	}
diff --git a/res/res_geolocation/geoloc_common.c b/res/res_geolocation/geoloc_common.c
index bb24a3100a..5903163476 100644
--- a/res/res_geolocation/geoloc_common.c
+++ b/res/res_geolocation/geoloc_common.c
@@ -21,12 +21,14 @@

 static const char *result_names[] = {
 	"Success",
-	"Missing type",
+	"Missing shape type",
 	"Invalid shape type",
 	"Invalid variable name",
 	"Not enough variables",
 	"Too many variables",
-	"Invalid variable value"
+	"Invalid CRS",
+	"Invalid CRS for shape",
+	"Invalid variable value",
 };

 const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result)
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
index 88857c4957..cae4f52157 100644
--- a/res/res_geolocation/geoloc_config.c
+++ b/res/res_geolocation/geoloc_config.c
@@ -104,7 +104,7 @@ static enum ast_geoloc_validate_result validate_location_info(const char *id,
 	enum ast_geoloc_format format, struct ast_variable *location_info)
 {
 	enum ast_geoloc_validate_result result;
-	const char *failed;
+	char *failed;
 	const char *uri;

 	switch (format) {
@@ -117,17 +117,17 @@ static enum ast_geoloc_validate_result validate_location_info(const char *id,
 		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
 			ast_log(LOG_ERROR, "Location '%s' has invalid item '%s' in the location\n",
 				id, failed);
+			ast_free(failed);
 			return result;
 		}
 		break;
 	case AST_GEOLOC_FORMAT_GML:
 		result = ast_geoloc_gml_validate_varlist(location_info, &failed);
 		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
-			ast_log(LOG_ERROR, "%s for item '%s' in location '%s'\n",
-				ast_geoloc_validate_result_to_str(result),	failed, id);
+			ast_log(LOG_ERROR, "Location '%s' failed: %s\n", id, failed);
+			ast_free(failed);
 			return result;
 		}
-
 		break;
 	case AST_GEOLOC_FORMAT_URI:
 		uri = ast_variable_find_in_list(location_info, "URI");
@@ -499,6 +499,7 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
 			"id:                      %-s\n"
 			"profile_precedence:      %-s\n"
 			"pidf_element:            %-s\n"
+			"pidf_element_id:         %-s\n"
 			"location_reference:      %-s\n"
 			"location_format:         %-s\n"
 			"location_info:           %-s\n"
@@ -511,10 +512,12 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
 			"suppress_empty_elements: %-s\n"
 			"effective_location:      %-s\n"
 			"usage_rules:             %-s\n"
-			"notes:                   %-s\n",
+			"notes:                   %-s\n"
+			"device_id:               %-s\n",
 			eprofile->id,
 			precedence_names[eprofile->precedence],
 			pidf_element_names[eprofile->pidf_element],
+			S_OR(eprofile->pidf_element_id, "<none>"),
 			S_OR(eprofile->location_reference, "<none>"),
 			format_names[eprofile->format],
 			S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
@@ -527,7 +530,8 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
 			S_COR(eprofile->suppress_empty_ca_elements, "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>")
+			S_OR(eprofile->notes, "<none>"),
+			S_OR(eprofile->device_id, "<none>")
 			);
 		ao2_ref(eprofile, -1);

@@ -717,6 +721,8 @@ int geoloc_config_load(void)
 	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_element",
 		pidf_element_names[AST_PIDF_ELEMENT_DEVICE], profile_pidf_element_handler, profile_pidf_element_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "pidf_element_id", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, pidf_element_id));
 	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_precedence", "discard_incoming",
@@ -744,7 +750,8 @@ int geoloc_config_load(void)
 		0, STRFLDSET(struct ast_geoloc_profile, location_source));
 	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "method", "", OPT_STRINGFIELD_T,
 		0, STRFLDSET(struct ast_geoloc_profile, method));
-
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "device_id", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, device_id));

 	ast_sorcery_load(geoloc_sorcery);

diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c
index 1d1346a30d..56b0e79d2f 100644
--- a/res/res_geolocation/geoloc_dialplan.c
+++ b/res/res_geolocation/geoloc_dialplan.c
@@ -53,6 +53,23 @@ static void varlist_to_str(struct ast_variable *list, struct ast_str** buf, size
 	} \
 })

+#define RESOLVE_STRINGFIELD_FOR_READ(_param) \
+({ \
+	if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
+		char *resolved = geoloc_eprofile_resolve_string( \
+			eprofile->_param, eprofile->location_variables, chan); \
+		if (!resolved) { \
+			ast_log(LOG_ERROR, "%s: Unable to resolve " #_param "\n", chan_name); \
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+			return 0; \
+		} \
+		ast_str_append(buf, len, "%s", resolved); \
+		ast_free(resolved); \
+	} else { \
+		ast_str_append(buf, len, "%s", eprofile->_param); \
+	} \
+})
+
 enum my_app_option_flags {
 	OPT_GEOLOC_RESOLVE = (1 << 0),
 	OPT_GEOLOC_APPEND = (1 << 1),
@@ -147,6 +164,8 @@ static int geoloc_profile_read(struct ast_channel *chan,
 		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", ast_geoloc_pidf_element_to_name(eprofile->pidf_element));
+	} else if (ast_strings_equal(args.field, "pidf_element_id")) {
+		RESOLVE_STRINGFIELD_FOR_READ(pidf_element_id);
 	} 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")) {
@@ -163,6 +182,8 @@ static int geoloc_profile_read(struct ast_channel *chan,
 		RESOLVE_FOR_READ(usage_rules);
 	} else if (ast_strings_equal(args.field, "confidence")) {
 		varlist_to_str(eprofile->confidence, buf, len);
+	} else if (ast_strings_equal(args.field, "device_id")) {
+		RESOLVE_STRINGFIELD_FOR_READ(device_id);
 	} else {
 		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
 		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
@@ -219,6 +240,23 @@ if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
 } \
 })

+#define RESOLVE_STRINGFIELD_FOR_WRITE(_param, _value) \
+({ \
+if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
+	char *resolved = geoloc_eprofile_resolve_string( \
+		_value, eprofile->location_variables, chan); \
+	if (!resolved) { \
+		ast_log(LOG_ERROR, "%s: Unable to resolve " #_param " %p %p\n", chan_name, eprofile->_param, eprofile->location_variables); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
+	} \
+	ast_string_field_set(eprofile, _param, resolved); \
+	ast_free(resolved); \
+} else { \
+	ast_string_field_set(eprofile, _param, _value); \
+} \
+})
+
 static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char *data,
 	 const char *value)
 {
@@ -313,6 +351,8 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char
 		TEST_ENUM_VALUE(chan_name, eprofile, format, value);
 	} else if (ast_strings_equal(args.field, "pidf_element")) {
 		TEST_ENUM_VALUE(chan_name, eprofile, pidf_element, value);
+	} else if (ast_strings_equal(args.field, "pidf_element_id")) {
+		RESOLVE_STRINGFIELD_FOR_WRITE(pidf_element_id, 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, "notes")) {
@@ -334,6 +374,8 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char
 		RESOLVE_FOR_WRITE(usage_rules);
 	} else if (ast_strings_equal(args.field, "confidence")) {
 		TEST_VARLIST(chan_name, eprofile, confidence, value);
+	} else if (ast_strings_equal(args.field, "device_id")) {
+		RESOLVE_STRINGFIELD_FOR_WRITE(pidf_element_id, value);
 	} else {
 		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
 		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
index 5907b98f87..a93b31e130 100644
--- a/res/res_geolocation/geoloc_doc.xml
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -207,6 +207,24 @@
 					</see-also>
 				</configOption>

+				<configOption name="pidf_element_id" default="">
+					<since>
+						<version>20.18.0</version>
+						<version>22.8.0</version>
+						<version>23.2.0</version>
+					</since>
+					<synopsis>The id attribute value for the PIDF-LO element</synopsis>
+				</configOption>
+
+				<configOption name="device_id" default="">
+					<since>
+						<version>20.18.0</version>
+						<version>22.8.0</version>
+						<version>23.2.0</version>
+					</since>
+					<synopsis>The content of the deviceID element</synopsis>
+				</configOption>
+
 				<configOption name="location_reference" default="none">
 					<since>
 						<version>16.28.0</version>
@@ -341,6 +359,7 @@
 					<enum name="profile_precedence"/>
 					<enum name="format"/>
 					<enum name="pidf_element"/>
+					<enum name="pidf_element_id"/>
 					<enum name="location_source"/>
 					<enum name="notes"/>
 					<enum name="location_info"/>
@@ -349,6 +368,7 @@
 					<enum name="effective_location"/>
 					<enum name="usage_rules"/>
 					<enum name="confidence"/>
+					<enum name="device_id"/>
 				</enumlist>
 				<para>Additionally, the <literal>inheritable</literal> field may be
 				set to <literal>true</literal> or <literal>false</literal> to control
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
index 3d10d12ab4..501560bdcd 100644
--- a/res/res_geolocation/geoloc_eprofile.c
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -181,6 +181,9 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *


 	rc = ast_string_field_set(eprofile, location_reference, src->location_reference);
+	if (rc == 0) {
+		rc = ast_string_field_set(eprofile, pidf_element_id, src->pidf_element_id);
+	}
 	if (rc == 0) {
 		ast_string_field_set(eprofile, notes, src->notes);
 	}
@@ -208,6 +211,9 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *
 	if (rc == 0) {
 		rc = DUP_VARS(eprofile->confidence, src->confidence);
 	}
+	if (rc == 0) {
+		rc = ast_string_field_set(eprofile, device_id, src->device_id);
+	}
 	if (rc != 0) {
 		ao2_ref(eprofile, -1);
 		return NULL;
@@ -240,8 +246,10 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_g
 	eprofile->suppress_empty_ca_elements = profile->suppress_empty_ca_elements;
 	eprofile->format = profile->format;

-
 	rc = ast_string_field_set(eprofile, location_reference, profile->location_reference);
+	if (rc == 0) {
+		rc = ast_string_field_set(eprofile, pidf_element_id, profile->pidf_element_id);
+	}
 	if (rc == 0) {
 		ast_string_field_set(eprofile, notes, profile->notes);
 	}
@@ -266,6 +274,9 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_g
 	if (rc == 0) {
 		rc = DUP_VARS(eprofile->confidence, profile->confidence);
 	}
+	if (rc == 0) {
+		rc = ast_string_field_set(eprofile, device_id, profile->device_id);
+	}
 	if (rc != 0) {
 		ao2_unlock(profile);
 		ao2_ref(eprofile, -1);
@@ -396,6 +407,39 @@ struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source
 	return dest;
 }

+char *geoloc_eprofile_resolve_string(const char *source,
+	struct ast_variable *variables, struct ast_channel *chan)
+{
+	struct varshead *vh = NULL;
+	struct ast_str *buf = ast_str_alloca(256);
+
+	if (!source || !chan) {
+		return NULL;
+	}
+
+	/*
+	 * ast_str_substitute_variables does only minimal recursive resolution so we need to
+	 * pre-resolve each variable in the "variables" list, then use that result to
+	 * do the final pass on the "source" variable list.
+	 */
+	if (variables) {
+		struct ast_variable *var = variables;
+		vh = ast_var_list_create();
+		if (!vh) {
+			return NULL;
+		}
+		for ( ; var; var = var->next) {
+			ast_str_substitute_variables_full2(&buf, 0, chan, vh, var->value, NULL, 1);
+			AST_VAR_LIST_INSERT_TAIL(vh, ast_var_assign(var->name, ast_str_buffer(buf)));
+			ast_str_reset(buf);
+		}
+	}
+
+	ast_str_substitute_variables_full2(&buf, 0, chan, vh, source, NULL, 1);
+	ast_var_list_destroy(vh);
+
+	return ast_strdup(ast_str_buffer(buf));
+}

 const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
 	struct ast_channel *chan, struct ast_str **buf, const char *ref_str)
@@ -604,6 +648,7 @@ static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
 	struct ast_xml_node *usage_rules = NULL;
 	struct ast_xml_node *method = NULL;
 	struct ast_xml_node *note_well = NULL;
+	struct ast_xml_node *device_id = NULL;
 	/*
 	 * Like nodes, names of nodes are just
 	 * pointers into result_doc and don't need to be freed.
@@ -614,9 +659,11 @@ static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
 	 * so they DO need to be freed after use.
 	 */
 	const char *id = NULL;
+	const char *pidf_element_id = NULL;
 	const char *format_str = NULL;
 	const char *method_str = NULL;
 	const char *note_well_str = NULL;
+	const char *device_id_str = NULL;

 	SCOPE_ENTER(3, "%s\n", ref_str);

@@ -638,6 +685,7 @@ static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
 		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find 'presence' root element\n",
 			ref_str);
 	}
+	id = ast_xml_get_attribute(presence, "entity");

 	pidf_element = ast_xml_node_get_children(presence);
 	if (!pidf_element) {
@@ -645,22 +693,18 @@ static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
 			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);
+	eprofile = ast_geoloc_eprofile_alloc(S_OR(id, "unknown"));
 	ast_xml_free_attr(id);
 	if (!eprofile) {
 		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
 	}

+	pidf_element_id = ast_xml_get_attribute(pidf_element, "id");
+	if (!ast_strlen_zero(pidf_element_id)) {
+		ast_string_field_set(eprofile, pidf_element_id, pidf_element_id);
+	}
+	ast_xml_free_attr(pidf_element_id);
+
 	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",
@@ -716,6 +760,11 @@ static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
 	ast_string_field_set(eprofile, notes, note_well_str);
 	ast_xml_free_text(note_well_str);

+	device_id = ast_xml_find_child_element(pidf_element, "deviceID", NULL, NULL);
+	device_id_str = ast_xml_get_text(device_id);
+	ast_string_field_set(eprofile, device_id, device_id_str);
+	ast_xml_free_text(device_id_str);
+
 	SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", ref_str);
 }

@@ -833,6 +882,7 @@ static struct ast_xml_node *geoloc_eprofile_to_intermediate(const char *element_
 	struct ast_xml_node *method_node;
 	struct ast_xml_node *notes_node;
 	struct ast_xml_node *timestamp_node;
+	struct ast_xml_node *device_id_node;
 	struct timeval tv = ast_tvnow();
 	struct tm tm = { 0, };
 	char timestr[32] = { 0, };
@@ -849,6 +899,18 @@ static struct ast_xml_node *geoloc_eprofile_to_intermediate(const char *element_
 		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n",
 			ref_string, element_name);
 	}
+	if (!ast_strlen_zero(eprofile->pidf_element_id)) {
+		char *resolved_pidf_element_id = geoloc_eprofile_resolve_string(eprofile->pidf_element_id,
+			eprofile->location_variables, chan);
+		if (!resolved_pidf_element_id) {
+			SCOPE_EXIT_RTN_VALUE(NULL);
+		}
+		rc = ast_xml_set_attribute(pidf_node, "id", resolved_pidf_element_id);
+		ast_free(resolved_pidf_element_id);
+		if (rc != 0) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'id' XML attribute\n", ref_string);
+		}
+	}

 	loc_node = ast_xml_new_child(pidf_node, "location-info");
 	if (!loc_node) {
@@ -936,6 +998,23 @@ static struct ast_xml_node *geoloc_eprofile_to_intermediate(const char *element_
 	}
 	ast_xml_set_text(timestamp_node, timestr);

+	if (!ast_strlen_zero(eprofile->device_id)) {
+		char *resolved_device_id = geoloc_eprofile_resolve_string(eprofile->device_id,
+			eprofile->location_variables, chan);
+		if (!resolved_device_id) {
+			SCOPE_EXIT_RTN_VALUE(NULL);
+		}
+		device_id_node = ast_xml_new_child(pidf_node, "deviceID");
+		if (!device_id_node) {
+			ast_free(resolved_device_id);
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'deviceID' XML node\n",
+				ref_string);
+		}
+		ast_xml_set_text(device_id_node, resolved_device_id);
+		ast_free(resolved_device_id);
+	}
+
+
 	rtn_pidf_node = pidf_node;
 	pidf_node = NULL;
 	SCOPE_EXIT_RTN_VALUE(rtn_pidf_node, "%s: Done\n", ref_string);
@@ -1058,11 +1137,11 @@ const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
 	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,
+static struct ast_xml_doc *geoloc_eprofile_to_xmldoc(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_doc *pidf_doc = NULL;
 	struct ast_xml_node *root_node;
 	char *doc_str = NULL;
 	int doc_len;
@@ -1130,6 +1209,25 @@ const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
 			ref_string);
 	}

+	SCOPE_EXIT_RTN_VALUE(pidf_doc, "%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 *, pidf_doc, NULL, ast_xml_close);
+	char *doc_str = NULL;
+	int doc_len = 0;
+	int rc = 0;
+	SCOPE_ENTER(3, "%s\n", ref_string);
+
+	pidf_doc = geoloc_eprofile_to_xmldoc(eprofile, chan, buf, ref_string);
+	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",
@@ -1146,6 +1244,7 @@ const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
 	ast_trace(5, "Final doc:\n%s\n", ast_str_buffer(*buf));

 	SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: Done\n", ref_string);
+
 }

 #ifdef TEST_FRAMEWORK
@@ -1247,39 +1346,40 @@ AST_TEST_DEFINE(test_create_from_uri)
 }

 static enum ast_test_result_state validate_eprofile(struct ast_test *test,
+	struct ast_geoloc_eprofile *eprofile,
 	struct ast_xml_doc * pidf_xmldoc,
-	const char *path,
 	const char *id,
+	const char *pidf_element_id,
 	enum ast_geoloc_pidf_element pidf_element,
 	enum ast_geoloc_format format,
 	const char *method,
 	const char *location,
-	const char *usage
+	const char *usage,
+	const char *device_id
 	)
 {
 	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);

-	if (!ast_strlen_zero(path)) {
-		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");
-	} else {
-		eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, NULL, "test_create_from_pidf");
-	}
-
-	ast_test_validate(test, eprofile != NULL);
-	ast_test_status_update(test, "ID: '%s'  pidf_element: '%s'  format: '%s'  method: '%s'\n", eprofile->id,
+	ast_test_status_update(test, "eprofile: ID: '%s'  pidf_element: '%s' peid: %s format: '%s'  method: '%s'  device_id: '%s'\n",
+		eprofile->id,
 		ast_geoloc_pidf_element_to_name(eprofile->pidf_element),
+		eprofile->pidf_element_id,
 		ast_geoloc_format_to_name(eprofile->format),
-		eprofile->method);
+		eprofile->method, eprofile->device_id);
+	ast_test_status_update(test, "xml: ID: '%s'  pidf_element: '%s' peid: %s format: '%s'  method: '%s'  device_id: '%s'\n",
+		id,
+		ast_geoloc_pidf_element_to_name(pidf_element),
+		pidf_element_id,
+		ast_geoloc_format_to_name(format),
+		method, device_id);

 	ast_test_validate(test, ast_strings_equal(eprofile->id, id));
 	ast_test_validate(test, eprofile->pidf_element == pidf_element);
+	ast_test_validate(test, ast_strings_equal(eprofile->pidf_element_id, pidf_element_id));
 	ast_test_validate(test, eprofile->format == format);
 	ast_test_validate(test, ast_strings_equal(eprofile->method, method));
+	ast_test_validate(test, ast_strings_equal(eprofile->device_id, device_id));

 	str = ast_variable_list_join(eprofile->location_info, ",", "=", NULL, NULL);
 	ast_test_validate(test, str != NULL);
@@ -1297,10 +1397,125 @@ static enum ast_test_result_state validate_eprofile(struct ast_test *test,
 	return AST_TEST_PASS;
 }

+static char *normalize_string(char *in)
+{
+	char *out = ast_strip(in);
+	char *ptr = out;
+
+	while (*ptr != '\0') {
+		if (*ptr == '\n') {
+			char *next = ast_skip_blanks(ptr);
+			*ptr = ' ';
+			ptr++;
+			next = ast_strdup(next);
+			strcpy(ptr, next); /* Safe */
+			ast_free(next);
+		}
+		ptr++;
+	}
+	return out;
+}
+
+struct test_xpath_element {
+	const char *path;
+	int validate_content;
+};
+
+static enum ast_test_result_state validate_xml(struct ast_test *test,
+	struct ast_geoloc_eprofile *eprofile,
+	struct ast_xml_doc * pidf_xmldoc,
+	struct ast_xml_doc * eprofile_xmldoc
+	)
+{
+	enum ast_test_result_state res = AST_TEST_PASS;
+	struct ast_xml_namespace_def_vector ns;
+	struct ast_xml_namespace_def def = {"def", "urn:ietf:params:xml:ns:pidf"};
+	struct ast_xml_namespace_def dm = {"dm", "urn:ietf:params:xml:ns:pidf:data-model"};
+	struct ast_xml_namespace_def ca = {"ca", "urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"};
+	struct ast_xml_namespace_def gbp = {"gbp", "urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"};
+	struct ast_xml_namespace_def gml = {"gml", "http://www.opengis.net/gml"};
+	struct ast_xml_namespace_def gp = {"gp", "urn:ietf:params:xml:ns:pidf:geopriv10"};
+	struct ast_xml_namespace_def con = {"con", "urn:ietf:params:xml:ns:geopriv:conf"};
+	struct ast_xml_namespace_def gs = {"gs", "http://www.opengis.net/pidflo/1.0"};
+
+	struct test_xpath_element elements[] = {
+		{"//def:tuple/@id", 1},
+		{"//gml:Point/@srsName", 1},
+		{"//gml:pos/text()", 1},
+		{"//con:confidence/text()", 1},
+		{"//con:confidence/@pdf", 1},
+		{"//gp:usage-rules", 0},
+		{"//gbp:retransmission-allowed/text()", 1},
+		{"//gbp:retention-expiry/text()", 1},
+		{"//gp:method/text()", 1},
+		{"//gp:note-well/text()", 1},
+		{"//dm:deviceID/text()", 1},
+		{"//def:timestamp", 0},
+	};
+	int i;
+
+	AST_VECTOR_INIT(&ns, 12);
+	AST_VECTOR_APPEND(&ns, def);
+	AST_VECTOR_APPEND(&ns, dm);
+	AST_VECTOR_APPEND(&ns, ca);
+	AST_VECTOR_APPEND(&ns, gbp);
+	AST_VECTOR_APPEND(&ns, gml);
+	AST_VECTOR_APPEND(&ns, gp);
+	AST_VECTOR_APPEND(&ns, con);
+	AST_VECTOR_APPEND(&ns, gs);
+
+
+	for (i = 0; i < ARRAY_LEN(elements); i++) {
+		struct ast_xml_xpath_results *aresults = ast_xml_query_with_namespaces(eprofile_xmldoc, elements[i].path, &ns);
+		struct ast_xml_xpath_results *bresults = ast_xml_query_with_namespaces(pidf_xmldoc, elements[i].path, &ns);
+		if (aresults && bresults) {
+			struct ast_xml_node *anode = ast_xml_xpath_get_first_result(aresults);
+			struct ast_xml_node *bnode = ast_xml_xpath_get_first_result(bresults);
+			if (elements[i].validate_content) {
+				char *atext = normalize_string(ast_strdupa(S_OR(ast_xml_get_text(anode), "")));
+				char *btext = normalize_string(ast_strdupa(S_OR(ast_xml_get_text(bnode), "")));
+				int pass = ast_strings_equal(atext, btext);
+				ast_test_status_update(test, "Element: %s  eprofile: %s pidf: %s Result: %s\n",
+					elements[i].path, atext, btext, pass ? "pass" : "FAIL");
+				if (!pass) {
+					ast_log(LOG_ERROR, "Element: %s  eprofile: %s pidf: %s Result: FAIL\n",
+						elements[i].path, atext, btext);
+					res = AST_TEST_FAIL;
+				}
+			} else {
+				int pass = !!anode && !!bnode;
+				ast_test_status_update(test, "Element: %s  eprofile: %s pidf: %s  Result: %s\n",
+					elements[i].path, anode ? "exists" : "doesn't exist", bnode ? "exists" : "doesn't exist",
+					pass ? "pass" : "FAIL");
+				if (!pass) {
+					ast_log(LOG_ERROR, "Element: %s  eprofile: %s pidf: %s\n",
+						elements[i].path, anode ? "exists" : "doesn't exist", bnode ? "exists" : "doesn't exist");
+				}
+			}
+		} else {
+			if (!aresults) {
+				ast_log(LOG_ERROR, "No xpath eprofile result for %s\n", elements[i].path);
+				res = AST_TEST_FAIL;
+			}
+			if (!bresults) {
+				ast_log(LOG_ERROR, "No xpath pidf result for %s\n", elements[i].path);
+				res = AST_TEST_FAIL;
+			}
+		}
+	}
+
+	return res;
+}
+
 AST_TEST_DEFINE(test_create_from_pidf)
 {

 	RAII_VAR(struct ast_xml_doc *, pidf_xmldoc, NULL, ast_xml_close);
+	RAII_VAR(struct ast_xml_doc *, eprofile_xmldoc, NULL, ast_xml_close);
+	RAII_VAR(struct ast_geoloc_eprofile *, eprofile,  NULL, ao2_cleanup);
+	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
+	RAII_VAR(struct ast_channel *, mock_channel, NULL, ast_hangup);
+
 	enum ast_test_result_state res = AST_TEST_PASS;

 	switch (cmd) {
@@ -1317,18 +1532,35 @@ AST_TEST_DEFINE(test_create_from_pidf)
 	pidf_xmldoc = ast_xml_read_memory((char *)_binary_res_geolocation_pidf_lo_test_xml_start, pidf_lo_test_xml_size);
 	ast_test_validate(test, pidf_xmldoc != NULL);

-	res = validate_eprofile(test, pidf_xmldoc,
-		NULL,
+	eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, NULL, "test_create_from_pidf");
+	ast_test_validate(test, eprofile != NULL);
+
+
+	res = validate_eprofile(test, eprofile, pidf_xmldoc,
+		"pres:alice@asterisk.org",
 		"point-2d",
 		AST_PIDF_ELEMENT_TUPLE,
 		AST_GEOLOC_FORMAT_GML,
 		"Manual",
 		"shape=Point,crs=2d,pos=-34.410649 150.87651",
-		"retransmission-allowed='no',retention-expiry='2010-11-14T20:00:00Z'"
+		"retransmission-allowed='no',retention-expiry='2010-11-14T20:00:00Z'",
+		"mac:112233445566"
 		);
-	ast_test_validate(test, res == AST_TEST_PASS);
+	if (res != AST_TEST_PASS) {
+		return res;
+	}

-	return res;
+	buf = ast_str_create(1024);
+	if (!buf) {
+		ast_log(LOG_ERROR, "Unable to allocate buf\n");
+		return AST_TEST_FAIL;
+	}
+
+	mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
+	eprofile->effective_location = ast_variables_dup(eprofile->location_info);
+	eprofile_xmldoc = geoloc_eprofile_to_xmldoc(eprofile, mock_channel, &buf, "session_name");
+
+	return validate_xml(test, eprofile, pidf_xmldoc, eprofile_xmldoc);
 }

 static void load_tests(void) {
diff --git a/res/res_geolocation/geoloc_gml.c b/res/res_geolocation/geoloc_gml.c
index 9a5942cb25..468ac22906 100644
--- a/res/res_geolocation/geoloc_gml.c
+++ b/res/res_geolocation/geoloc_gml.c
@@ -20,176 +20,287 @@
 #include "asterisk/res_geolocation.h"
 #include "geoloc_private.h"

-
-#if 1 //not used yet.
-enum geoloc_shape_attrs {
-	GEOLOC_SHAPE_ATTR_POS = 0,
-	GEOLOC_SHAPE_ATTR_POS3D,
-	GEOLOC_SHAPE_ATTR_RADIUS,
-	GEOLOC_SHAPE_ATTR_SEMI_MAJOR_AXIS,
-	GEOLOC_SHAPE_ATTR_SEMI_MINOR_AXIS,
-	GEOLOC_SHAPE_ATTR_VERTICAL_AXIS,
-	GEOLOC_SHAPE_ATTR_HEIGHT,
-	GEOLOC_SHAPE_ATTR_ORIENTATION,
-	GEOLOC_SHAPE_ATTR_ORIENTATION_UOM,
-	GEOLOC_SHAPE_ATTR_INNER_RADIUS,
-	GEOLOC_SHAPE_ATTR_OUTER_RADIUS,
-	GEOLOC_SHAPE_ATTR_STARTING_ANGLE,
-	GEOLOC_SHAPE_ATTR_OPENING_ANGLE,
-	GEOLOC_SHAPE_ATTR_ANGLE_UOM,
-};
-
-struct geoloc_gml_attr_def {
-	enum geoloc_shape_attrs attr;
-	const char *name;
-	int (*validator)(const char *value);
-	int (*transformer)(struct ast_variable *value);
-};
-
-struct geoloc_gml_attr_def gml_attr_defs[] = {
-	{ GEOLOC_SHAPE_ATTR_POS, "pos", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_POS3D,"pos3d", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_RADIUS,"radius", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_SEMI_MAJOR_AXIS,"semiMajorAxis", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_SEMI_MINOR_AXIS,"semiMinorAxis", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_VERTICAL_AXIS,"verticalAxis", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_HEIGHT,"height", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_ORIENTATION,"orientation", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_ORIENTATION_UOM,"orientation_uom", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_INNER_RADIUS,"innerRadius", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_OUTER_RADIUS,"outerRadius", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_STARTING_ANGLE,"startingAngle", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_OPENING_ANGLE,"openingAngle", NULL, NULL},
-	{ GEOLOC_SHAPE_ATTR_ANGLE_UOM,"angle_uom", NULL, NULL},
-};
-#endif  //not used yet.
-
 struct geoloc_gml_attr {
-	const char *attribute;
+	const char *name;
 	int min_required;
 	int max_allowed;
-	int (*validator)(const char *value);
+	int (*validator)(const char *name, const char *value, const struct ast_variable *varlist,
+		char **result);
 };

+#define MAX_SHAPE_ATTRIBUTES 9
 struct geoloc_gml_shape_def {
 	const char *shape_type;
-	struct geoloc_gml_attr required_attributes[8];
+	const char *crs;
+	struct geoloc_gml_attr required_attributes[MAX_SHAPE_ATTRIBUTES];
 };

-static int pos_validator(const char *value)
+#define SET_RESULT(__result, ...) \
+({ \
+	if (__result) { \
+		__ast_asprintf(__FILE__, __LINE__, __PRETTY_FUNCTION__, result,  __VA_ARGS__); \
+	} \
+})
+
+static int crs_validator(const char *name, const char *value, const struct ast_variable *varlist,
+	char **result)
 {
-	float lat;
-	float lon;
-	return (sscanf(value, "%f %f", &lat, &lon) == 2);
+	if (!ast_strings_equal(value, "2d") && !ast_strings_equal(value, "3d")) {
+		SET_RESULT(result, "Invalid crs '%s'.  Must be either '2d' or '3d'", value);
+		return 0;
+	}
+	return 1;
 }

-static int pos3d_validator(const char *value)
+static int pos_validator(const char *name, const char *value, const struct ast_variable *varlist,
+	char **result)
 {
+	const char *crs = S_OR(ast_variable_find_in_list(varlist, "crs"), "2d");
 	float lat;
 	float lon;
 	float alt;
-	return (sscanf(value, "%f %f %f", &lat, &lon, &alt) == 3);
+	int count;
+
+	count = sscanf(value, "%f %f %f", &lat, &lon, &alt);
+	if (ast_strings_equal(crs, "3d") && count != 3) {
+		SET_RESULT(result, "Invalid 3d position '%s'.  Must be 3 floating point values.", value);
+		return 0;
+	}
+	if (ast_strings_equal(crs, "2d") && count != 2) {
+		SET_RESULT(result, "Invalid 2d position '%s'.  Must be 2 floating point values.", value);
+		return 0;
+	}
+	return 1;
 }

-static int float_validator(const char *value)
+static int float_validator(const char *name, const char *value, const struct ast_variable *varlist,
+	char **result)
 {
 	float val;
-	return (sscanf(value, "%f", &val) == 1);
+	if (sscanf(value, "%f", &val) != 1) {
+		SET_RESULT(result, "Invalid floating point value '%s' in '%s'.", value, name);
+		return 0;
+	}
+	return 1;
 }

-static int uom_validator(const char *value)
+enum angle_parse_result {
+	ANGLE_PARSE_RESULT_SUCCESS = 0,
+	ANGLE_PARSE_ERROR_NO_ANGLE,
+	ANGLE_PARSE_ERROR_INVALID_ANGLE,
+	ANGLE_PARSE_ERROR_ANGLE_OUT_OF_RANGE,
+	ANGLE_PARSE_ERROR_INVALID_UOM,
+};
+
+static enum angle_parse_result angle_parser(const char *name, const char *value,
+	char **angle, char **uom, char **result)
 {
-	return (ast_strings_equal(value, "degrees") || ast_strings_equal(value, "radians"));
+	char *tmp_angle = NULL;
+	char *tmp_uom = NULL;
+	float f_angle;
+	char *junk;
+	char *work = ast_strdupa(value);
+
+	tmp_angle = ast_strsep(&work, ' ', AST_STRSEP_ALL);
+	if (ast_strlen_zero(tmp_angle)) {
+		SET_RESULT(result, "Empty angle in '%s'", name);
+		return ANGLE_PARSE_ERROR_NO_ANGLE;
+	}
+	f_angle = strtof(tmp_angle, &junk);
+	if (!ast_strlen_zero(junk)) {
+		SET_RESULT(result, "Invalid angle '%s' in '%s'", value, name);
+		return ANGLE_PARSE_ERROR_INVALID_ANGLE;
+	}
+
+	tmp_uom = ast_strsep(&work, ' ', AST_STRSEP_ALL);
+	if (ast_strlen_zero(tmp_uom)) {
+		tmp_uom = "degrees";
+	}
+
+	if (ast_begins_with(tmp_uom, "deg")) {
+		tmp_uom = "degrees";
+	} else if (ast_begins_with(tmp_uom, "rad")) {
+		tmp_uom = "radians";
+	} else {
+		SET_RESULT(result, "Invalid UOM '%s' in '%s'.  Must be 'degrees' or 'radians'.", value, name);
+		return ANGLE_PARSE_ERROR_INVALID_UOM;
+	}
+
+	if (ast_strings_equal(tmp_uom, "degrees") && f_angle > 360.0) {
+		SET_RESULT(result, "Angle '%s' must be <= 360.0 for UOM '%s' in '%s'", tmp_angle, tmp_uom, name);
+		return ANGLE_PARSE_ERROR_ANGLE_OUT_OF_RANGE;
+	}
+
+	if (ast_strings_equal(tmp_uom, "radians") && f_angle > 100.0) {
+		SET_RESULT(result, "Angle '%s' must be <= 100.0 for UOM '%s' in '%s'", tmp_angle, tmp_uom, name);
+		return ANGLE_PARSE_ERROR_ANGLE_OUT_OF_RANGE;
+	}
+
+	if (angle) {
+		*angle = ast_strdup(tmp_angle);
+	}
+	if (uom) {
+		*uom = ast_strdup(tmp_uom);
+	}
+	return ANGLE_PARSE_RESULT_SUCCESS;
 }

+static int angle_validator(const char *name, const char *value, const struct ast_variable *varlist,
+	char **result)
+{
+	enum angle_parse_result rc = angle_parser(name, value, NULL, NULL, result);

-static struct geoloc_gml_shape_def gml_shape_defs[8] = {
-	{ "Point", { {"pos", 1, 1, pos_validator}, {NULL, -1, -1} }},
-	{ "Polygon", { {"pos", 3, -1, pos_validator}, {NULL, -1, -1} }},
-	{ "Circle", { {"pos", 1, 1, pos_validator}, {"radius", 1, 1, float_validator},{NULL, -1, -1}}},
-	{ "Ellipse", { {"pos", 1, 1, pos_validator}, {"semiMajorAxis", 1, 1, float_validator},
-		{"semiMinorAxis", 1, 1, float_validator}, {"orientation", 1, 1, float_validator},
-		{"orientation_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
-	{ "ArcBand", { {"pos", 1, 1, pos_validator}, {"innerRadius", 1, 1, float_validator},
-		{"outerRadius", 1, 1, float_validator}, {"startAngle", 1, 1, float_validator},
-		{"startAngle_uom", 1, 1, uom_validator}, {"openingAngle", 1, 1, float_validator},
-		{"openingAngle_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
-	{ "Sphere", { {"pos3d", 1, 1, pos3d_validator}, {"radius", 1, 1, float_validator}, {NULL, -1, -1} }},
-	{ "Ellipse", { {"pos3d", 1, 1, pos3d_validator}, {"semiMajorAxis", 1, 1, float_validator},
+	return rc == ANGLE_PARSE_RESULT_SUCCESS;
+}
+
+#define _SENTRY {NULL, -1, -1, NULL}
+
+#define CRS_OPT {"crs", 0, 1, crs_validator}
+#define CRS_REQ {"crs", 1, 1, crs_validator}
+
+static struct geoloc_gml_shape_def gml_shape_defs[] = {
+	{ "Point", "any", { CRS_OPT, {"pos", 1, 1, pos_validator}, _SENTRY }},
+	{ "Polygon", "any", { CRS_OPT, {"pos", 3, -1, pos_validator}, _SENTRY }},
+	{ "Circle", "2d", { CRS_OPT, {"pos", 1, 1, pos_validator}, {"radius", 1, 1, float_validator}, _SENTRY }},
+	{ "Ellipse", "2d", { CRS_OPT, {"pos", 1, 1, pos_validator}, {"semiMajorAxis", 1, 1, float_validator},
+		{"semiMinorAxis", 1, 1, float_validator}, {"orientation", 1, 1, angle_validator}, _SENTRY }},
+	{ "ArcBand", "2d", { CRS_OPT, {"pos", 1, 1, pos_validator}, {"innerRadius", 1, 1, float_validator},
+		{"outerRadius", 1, 1, float_validator}, {"startAngle", 1, 1, angle_validator},
+		{"openingAngle", 1, 1, angle_validator},
+		_SENTRY }},
+	{ "Sphere", "3d", { CRS_REQ, {"pos", 1, 1, pos_validator}, {"radius", 1, 1, float_validator}, _SENTRY }},
+	{ "Ellipsoid", "3d", { CRS_REQ, {"pos", 1, 1, pos_validator}, {"semiMajorAxis", 1, 1, float_validator},
 		{"semiMinorAxis", 1, 1, float_validator}, {"verticalAxis", 1, 1, float_validator},
-		{"orientation", 1, 1, float_validator}, {"orientation_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
-	{ "Prism", { {"pos3d", 3, -1, pos_validator}, {"height", 1, 1, float_validator}, {NULL, -1, -1} }},
+		{"orientation", 1, 1, angle_validator}, _SENTRY }},
+	{ "Prism", "3d", { CRS_REQ, {"pos", 3, -1, pos_validator}, {"height", 1, 1, float_validator}, _SENTRY }},
 };

-enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
-	const char **result)
+static int find_shape_index(const char *shape)
 {
-	int def_index = -1;
-	const struct ast_variable *var;
-	int i;
-	const char *shape_type = ast_variable_find_in_list(varlist, "shape");
+	int i = 0;
+	int shape_count = ARRAY_LEN(gml_shape_defs);

-	if (!shape_type) {
-		return AST_GEOLOC_VALIDATE_MISSING_SHAPE;
+	for (i = 0; i < shape_count; i++) {
+		if (ast_strings_equal(shape, gml_shape_defs[i].shape_type)) {
+			return i;
+		}
 	}
+	return -1;
+}

-	for (i = 0; i < ARRAY_LEN(gml_shape_defs); i++) {
-		if (ast_strings_equal(gml_shape_defs[i].shape_type, shape_type)) {
-			def_index = i;
+static int find_attribute_index(int shape_index, const char *name)
+{
+	int i = 0;
+
+	for (i = 0; i <  MAX_SHAPE_ATTRIBUTES; i++) {
+		if (gml_shape_defs[shape_index].required_attributes[i].name == NULL) {
+			return -1;
+		}
+		if (ast_strings_equal(name, gml_shape_defs[shape_index].required_attributes[i].name)) {
+			return i;
 		}
 	}
-	if (def_index < 0) {
-		return AST_GEOLOC_VALIDATE_INVALID_SHAPE;
-	}
+	return -1;
+}
+
+static enum ast_geoloc_validate_result validate_def_varlist(int shape_index, const struct ast_variable *varlist,
+	char **result)
+{
+	const struct ast_variable *var;
+	int i;

 	for (var = varlist; var; var = var->next) {
 		int vname_index = -1;
 		if (ast_strings_equal("shape", var->name)) {
 			continue;
 		}
-		for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
-			if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
-				break;
-			}
-			if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, var->name)) {
-				vname_index = i;
-				break;
-			}
-		}
+
+		vname_index = find_attribute_index(shape_index, var->name);
 		if (vname_index < 0) {
-			*result = var->name;
+			SET_RESULT(result, "Invalid variable name '%s'\n", var->name);
 			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
 		}
-		if (!gml_shape_defs[def_index].required_attributes[vname_index].validator(var->value)) {
-			*result = var->name;
+		if (!gml_shape_defs[shape_index].required_attributes[vname_index].validator(var->name, var->value,
+			varlist, result)) {
 			return AST_GEOLOC_VALIDATE_INVALID_VALUE;
 		}
 	}

-	for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
+	for (i = 0; i < ARRAY_LEN(gml_shape_defs[shape_index].required_attributes); i++) {
 		int count = 0;
-		if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
+		if (gml_shape_defs[shape_index].required_attributes[i].name == NULL) {
 			break;
 		}

 		for (var = varlist; var; var = var->next) {
-			if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, var->name)) {
+			if (ast_strings_equal(gml_shape_defs[shape_index].required_attributes[i].name, var->name)) {
 				count++;
 			}
 		}
-		if (count < gml_shape_defs[def_index].required_attributes[i].min_required) {
-			*result = gml_shape_defs[def_index].required_attributes[i].attribute;
+		if (count < gml_shape_defs[shape_index].required_attributes[i].min_required) {
+			SET_RESULT(result, "Number of '%s' variables %d is < %d",
+				gml_shape_defs[shape_index].required_attributes[i].name,
+				count,
+				gml_shape_defs[shape_index].required_attributes[i].min_required);
 			return AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES;
 		}
-		if (gml_shape_defs[def_index].required_attributes[i].max_allowed > 0 &&
-			count > gml_shape_defs[def_index].required_attributes[i].max_allowed) {
-			*result = gml_shape_defs[def_index].required_attributes[i].attribute;
+		if (gml_shape_defs[shape_index].required_attributes[i].max_allowed > 0 &&
+			count > gml_shape_defs[shape_index].required_attributes[i].max_allowed) {
+			SET_RESULT(result, "Number of '%s' variables %d is > %d",
+				gml_shape_defs[shape_index].required_attributes[i].name,
+				count,
+				gml_shape_defs[shape_index].required_attributes[i].max_allowed);
 			return AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES;
 		}
 	}
+
 	return AST_GEOLOC_VALIDATE_SUCCESS;
 }

+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(struct ast_variable *varlist,
+	char **result)
+{
+	const char *shape_type = ast_variable_find_in_list(varlist, "shape");
+	int shape_index = -1;
+	const char *crs = ast_variable_find_in_list(varlist, "crs");
+
+	if (!shape_type) {
+		SET_RESULT(result, "Missing 'shape'");
+		return AST_GEOLOC_VALIDATE_MISSING_SHAPE;
+	}
+
+	shape_index = find_shape_index(shape_type);
+	if (shape_index < 0) {
+		SET_RESULT(result, "Invalid shape '%s'", shape_type);
+		return AST_GEOLOC_VALIDATE_INVALID_SHAPE;
+	}
+
+	if (ast_strlen_zero(crs)) {
+		struct ast_variable *vcrs = NULL;
+		if (ast_strings_equal("any", gml_shape_defs[shape_index].crs)) {
+			crs = "2d";
+		} else {
+			crs = gml_shape_defs[shape_index].crs;
+		}
+
+		vcrs = ast_variable_new("crs", "2d", "");
+		if (vcrs) {
+			ast_variable_list_append(&varlist, vcrs);
+		}
+	}
+	if (!crs_validator("crs", crs, varlist, result)) {
+		return AST_GEOLOC_VALIDATE_INVALID_CRS;
+	}
+
+	if (!ast_strings_equal("any", gml_shape_defs[shape_index].crs)
+		&& !ast_strings_equal(crs, gml_shape_defs[shape_index].crs)) {
+		SET_RESULT(result, "Invalid crs '%s' for shape '%s'", crs, shape_type);
+		return AST_GEOLOC_VALIDATE_INVALID_CRS_FOR_SHAPE;
+	}
+
+	return validate_def_varlist(shape_index, varlist, result);
+}
+
 static char *handle_gml_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	int i;
@@ -205,22 +316,22 @@ static char *handle_gml_show(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
 		return NULL;
 	}

-	ast_cli(a->fd, "%-16s %-32s\n", "Shape", "Attributes name(min,max)");
-	ast_cli(a->fd, "================ ===============================\n");
+	ast_cli(a->fd, "%-16s %-3s %-32s\n", "Shape", "CRS", "Attributes name(min,max)");
+	ast_cli(a->fd, "================ === ===============================\n");

 	for (i = 0; i < ARRAY_LEN(gml_shape_defs); i++) {
 		int j;
-		ast_cli(a->fd, "%-16s", gml_shape_defs[i].shape_type);
+		ast_cli(a->fd, "%-16s %-3s", gml_shape_defs[i].shape_type, gml_shape_defs[i].crs);
 		for (j = 0; j < ARRAY_LEN(gml_shape_defs[i].required_attributes); j++) {
-			if (gml_shape_defs[i].required_attributes[j].attribute == NULL) {
+			if (gml_shape_defs[i].required_attributes[j].name == NULL) {
 				break;
 			}
 			if (gml_shape_defs[i].required_attributes[j].max_allowed >= 0) {
-				ast_cli(a->fd, " %s(%d,%d)", gml_shape_defs[i].required_attributes[j].attribute,
+				ast_cli(a->fd, " %s(%d,%d)", gml_shape_defs[i].required_attributes[j].name,
 					gml_shape_defs[i].required_attributes[j].min_required,
 					gml_shape_defs[i].required_attributes[j].max_allowed);
 			} else {
-				ast_cli(a->fd, " %s(%d,unl)", gml_shape_defs[i].required_attributes[j].attribute,
+				ast_cli(a->fd, " %s(%d,unl)", gml_shape_defs[i].required_attributes[j].name,
 					gml_shape_defs[i].required_attributes[j].min_required);
 			}
 		}
@@ -235,14 +346,16 @@ static struct ast_cli_entry geoloc_gml_cli[] = {
 	AST_CLI_DEFINE(handle_gml_show, "Show the GML Shape definitions"),
 };

-struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+struct ast_xml_node *geoloc_gml_list_to_xml(struct ast_variable *resolved_location,
 	const char *ref_string)
 {
 	const char *shape;
-	char *crs;
+	const char *crs;
 	struct ast_variable *var;
 	struct ast_xml_node *gml_node;
 	struct ast_xml_node *child_node;
+	enum ast_geoloc_validate_result res;
+	RAII_VAR(char *, result, NULL, ast_free);
 	int rc = 0;

 	SCOPE_ENTER(3, "%s", ref_string);
@@ -252,13 +365,24 @@ struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_
 			ref_string);
 	}

+	res = ast_geoloc_gml_validate_varlist(resolved_location, &result);
+	if (res != AST_GEOLOC_VALIDATE_SUCCESS) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: %s\n",
+			ref_string, result);
+	}
+
 	shape = ast_variable_find_in_list(resolved_location, "shape");
 	if (ast_strlen_zero(shape)) {
 		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: There's no 'shape' parameter\n",
 			ref_string);
 	}
-	crs = (char *)ast_variable_find_in_list(resolved_location, "crs");
+
+	crs = ast_variable_find_in_list(resolved_location, "crs");
 	if (ast_strlen_zero(crs)) {
+		struct ast_variable *vcrs = ast_variable_new("crs", "2d", "");
+		if (vcrs) {
+			ast_variable_list_append(&resolved_location, vcrs);
+		}
 		crs = "2d";
 	}

@@ -273,75 +397,36 @@ struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_
 	}

 	for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
-		RAII_VAR(char *, value, NULL, ast_free);
-		char *uom = NULL;

 		if (ast_strings_equal(var->name, "shape") || ast_strings_equal(var->name, "crs")) {
 			continue;
 		}
-		value = ast_strdup(var->value);
-
-		if (ast_strings_equal(var->name, "orientation") || ast_strings_equal(var->name, "startAngle")
-			|| ast_strings_equal(var->name, "openingAngle")) {
-			char *a = NULL;
-			char *junk = NULL;
-			float angle;
-			uom = value;
-
-			/* 'a' should now be the angle and 'uom' should be the uom */
-			a = strsep(&uom, " ");
-			angle = strtof(a, &junk);
-			/*
-			 * strtof sets junk to the first non-valid character so if it's
-			 * not empty after the conversion, there were unrecognized
-			 * characters in the angle.  It'll point to the NULL terminator
-			 * if angle was completely converted.
-			 */
-			if (!ast_strlen_zero(junk)) {
-				ast_xml_free_node(gml_node);
-				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: The angle portion of parameter '%s' ('%s') is malformed\n",
-					ref_string, var->name, var->value);
-			}
-
-			if (ast_strlen_zero(uom)) {
-				uom = "degrees";
-			}
-
-			if (ast_begins_with(uom, "deg")) {
-				if (angle > 360.0) {
-					ast_xml_free_node(gml_node);
-					SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
-						"Degrees can't be > 360.0\n",
-						ref_string, var->name, var->value);
-				}
-			} else if (ast_begins_with(uom, "rad")) {
-				if(angle > 100.0) {
-					ast_xml_free_node(gml_node);
-					SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
-						"Radians can't be  > 100.0\n",
-						ref_string, var->name, var->value);
-				}
-			} else {
-				ast_xml_free_node(gml_node);
-				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
-					"The unit of measure must be 'deg[rees]' or 'rad[ians]'\n",
-					ref_string, var->name, var->value);
-			}
-		}

 		child_node = ast_xml_new_child(gml_node, var->name);
 		if (!child_node) {
 			ast_xml_free_node(gml_node);
 			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
 		}
-		if (!ast_strlen_zero(uom)) {
+
+		if (ast_strings_equal(var->name, "orientation") || ast_strings_equal(var->name, "startAngle")
+			|| ast_strings_equal(var->name, "openingAngle")) {
+			RAII_VAR(char *, angle, NULL, ast_free);
+			RAII_VAR(char *, uom, NULL, ast_free);
+
+			enum angle_parse_result rc = angle_parser(var->name, var->value, &angle, &uom, &result);
+			if (rc != ANGLE_PARSE_RESULT_SUCCESS) {
+				ast_xml_free_node(gml_node);
+				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: %s\n", ref_string, result);
+			}
 			rc = ast_xml_set_attribute(child_node, "uom", uom);
 			if (rc != 0) {
 				ast_xml_free_node(gml_node);
 				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'uom' XML attribute\n", ref_string);
 			}
+			ast_xml_set_text(child_node, angle);
+		} else {
+			ast_xml_set_text(child_node, var->value);
 		}
-		ast_xml_set_text(child_node, value);
 	}

 	SCOPE_EXIT_RTN_VALUE(gml_node, "%s: Done\n", ref_string);
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
index 0bd0797cb7..9327186ccc 100644
--- a/res/res_geolocation/geoloc_private.h
+++ b/res/res_geolocation/geoloc_private.h
@@ -135,7 +135,7 @@ int geoloc_civicaddr_load(void);
 int geoloc_civicaddr_unload(void);
 int geoloc_civicaddr_reload(void);

-struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+struct ast_xml_node *geoloc_gml_list_to_xml(struct ast_variable *resolved_location,
 	const char *ref_string);
 int geoloc_gml_unload(void);
 int geoloc_gml_load(void);
@@ -158,5 +158,7 @@ struct ast_sorcery *geoloc_get_sorcery(void);
 struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source,
 	struct ast_variable *variables, struct ast_channel *chan);

+char *geoloc_eprofile_resolve_string(const char *source,
+	struct ast_variable *variables, struct ast_channel *chan);

 #endif /* GEOLOC_PRIVATE_H_ */
diff --git a/res/res_geolocation/pidf_lo_test.xml b/res/res_geolocation/pidf_lo_test.xml
index bea98d6139..28cb3249f1 100644
--- a/res/res_geolocation/pidf_lo_test.xml
+++ b/res/res_geolocation/pidf_lo_test.xml
@@ -1,33 +1,34 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <presence entity="pres:alice@asterisk.org"
-	xmlns="urn:ietf:params:xml:ns:pidf"
-	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
-	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
-	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>
-			<gp:geopriv>
-				<gp:location-info>
-					<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>
+    xmlns="urn:ietf:params:xml:ns:pidf"
+    xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+    xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+    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>
+            <gp:geopriv>
+                <gp:location-info>
+                    <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>
+        <dm:deviceID>mac:112233445566</dm:deviceID>
+    </tuple>
 </presence>
diff --git a/res/res_geolocation/pidf_to_eprofile.xslt b/res/res_geolocation/pidf_to_eprofile.xslt
index 95f000c825..f63dbe6ad5 100644
--- a/res/res_geolocation/pidf_to_eprofile.xslt
+++ b/res/res_geolocation/pidf_to_eprofile.xslt
@@ -74,6 +74,11 @@
 			<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
 			<xsl:call-template name="geopriv"/>
 			<xsl:apply-templates select="./def:timestamp"/>
+			<xsl:if test="./dm:deviceID">
+				<deviceID>
+					<xsl:value-of select="./dm:deviceID"/>
+				</deviceID>
+			</xsl:if>
 		</xsl:element>
 	</xsl:template>

@@ -82,7 +87,6 @@
 			<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"/>
@@ -165,7 +169,7 @@
 	<xsl:template name="angle">
 		<xsl:element name="{local-name(.)}">
 			<xsl:choose>
-				<xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'">
+				<xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9101'">
 					<xsl:attribute name="uom">radians</xsl:attribute></xsl:when>
 				<xsl:otherwise>
 					<xsl:attribute name="uom">degrees</xsl:attribute></xsl:otherwise>