From: George Joseph Date: Wed, 17 Dec 2025 22:49:06 +0000 (-0700) Subject: res_geolocation: Fix multiple issues with XML generation. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7b8f0c14d10c5b4dcdca1e5198a40ae121ddd310;p=thirdparty%2Fasterisk.git res_geolocation: Fix multiple issues with XML generation. * 3d positions were being rendered without an enclosing `` 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 `` 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 = + +Example: +pidf_element_id = ${CHANNEL(name)} + +-- device_id (optional) ----------------------------------------------- +Sets the contents of the 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 = + +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 @@ + + + @@ -66,6 +69,11 @@ + + + + + @@ -171,11 +179,11 @@ + the children, also adding the "gbp" namespace --> - + @@ -201,10 +209,10 @@ - urn:ogc:def:uom:EPSG::9102 + urn:ogc:def:uom:EPSG::9101 - urn:ogc:def:uom:EPSG::9101 + urn:ogc:def:uom:EPSG::9102 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, ""), S_OR(eprofile->location_reference, ""), format_names[eprofile->format], S_COR(loc_str, ast_str_buffer(loc_str), ""), @@ -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), ""), S_COR(usage_rules_str, ast_str_buffer(usage_rules_str), ""), - S_OR(eprofile->notes, "") + S_OR(eprofile->notes, ""), + S_OR(eprofile->device_id, "") ); 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 @@ + + + 20.18.0 + 22.8.0 + 23.2.0 + + The id attribute value for the PIDF-LO element + + + + + 20.18.0 + 22.8.0 + 23.2.0 + + The content of the deviceID element + + 16.28.0 @@ -341,6 +359,7 @@ + @@ -349,6 +368,7 @@ + Additionally, the inheritable field may be set to true or false 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 @@ - - - - - - -34.410649 150.87651 - - 66 - - - no - 2010-11-14T20:00:00Z - - Manual - - this is a test - of the emergency broadcast system - - - - 2007-06-22T20:57:29Z - + 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"> + + + + + + -34.410649 150.87651 + + 66 + + + no + 2010-11-14T20:00:00Z + + Manual + + this is a test + of the emergency broadcast system + + + + 2007-06-22T20:57:29Z + mac:112233445566 + 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 @@ + + + + + @@ -82,7 +87,6 @@ - @@ -165,7 +169,7 @@ - + radians degrees