diff --git a/doc/CHANGES-staging/res_geolocation.txt b/doc/CHANGES-staging/res_geolocation.txt index b543c6e29cdd1afa776dcac4a4e9d082beb99da9..4d290ba94fe74f5dbff7228ab83f8d3e20e6bc55 100644 --- a/doc/CHANGES-staging/res_geolocation.txt +++ b/doc/CHANGES-staging/res_geolocation.txt @@ -40,3 +40,10 @@ a profile object for simple scenarios where the location information isn't common with any other profiles. This is mutually exclusive with setting location_reference on the profile. + +Added an 'a' option to the GEOLOC_PROFILE function to allow +variable lists like location_info_refinement to be appended +to instead of replacing the entire list. + +Added an 'r' option to the GEOLOC_PROFILE function to resolve all +variables before a read operation and after a Set operation. diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h index 378a6c736a59f6d148d30cd32a677b5604c9bde1..0a5a61d11d730ab13dab79a55f187e8a58ce723c 100644 --- a/include/asterisk/res_geolocation.h +++ b/include/asterisk/res_geolocation.h @@ -317,6 +317,15 @@ struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan); */ struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name); +/*! + * \brief Duplicate an effective profile. + * + * \param src The eprofile to duplicate. + * + * \return The duplicated effective profile ao2 object. + */ +struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *src); + /*! * \brief Allocate a new effective profile from an existing profile. * diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c index 710daa65a28503c31bda002b452c4f801674cc9a..1d1346a30d28d9574f7e4dab58d49ca18ea54b8c 100644 --- a/res/res_geolocation/geoloc_dialplan.c +++ b/res/res_geolocation/geoloc_dialplan.c @@ -25,7 +25,6 @@ #include "asterisk/strings.h" #include "asterisk/utils.h" #include "asterisk/app.h" -#include "asterisk/res_geolocation.h" #include "geoloc_private.h" static void varlist_to_str(struct ast_variable *list, struct ast_str** buf, size_t len) @@ -37,20 +36,52 @@ static void varlist_to_str(struct ast_variable *list, struct ast_str** buf, size } } +#define RESOLVE_FOR_READ(_param) \ +({ \ + if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \ + struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \ + 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; \ + } \ + varlist_to_str(resolved, buf, len); \ + ast_variables_destroy(resolved); \ + } else { \ + varlist_to_str(eprofile->_param, buf, len); \ + } \ +}) + +enum my_app_option_flags { + OPT_GEOLOC_RESOLVE = (1 << 0), + OPT_GEOLOC_APPEND = (1 << 1), +}; + +AST_APP_OPTIONS(action_options, { + AST_APP_OPTION('r', OPT_GEOLOC_RESOLVE), + AST_APP_OPTION('a', OPT_GEOLOC_APPEND), +}); + + static int geoloc_profile_read(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len) { char *parsed_data = ast_strdupa(data); + const char *chan_name = ast_channel_name(chan); struct ast_datastore *ds; + struct ast_geoloc_eprofile *orig_eprofile = NULL; struct ast_geoloc_eprofile *eprofile = NULL; + struct ast_flags opts = { 0, }; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field); + AST_APP_ARG(options); ); /* Check for zero arguments */ if (ast_strlen_zero(parsed_data)) { - ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", cmd); + ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name); pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1"); return 0; } @@ -58,25 +89,45 @@ static int geoloc_profile_read(struct ast_channel *chan, AST_STANDARD_APP_ARGS(args, parsed_data); if (ast_strlen_zero(args.field)) { - ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", cmd); + ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", chan_name); pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1"); return 0; } + if (!ast_strlen_zero(args.options)) { + if (ast_app_parse_options(action_options, &opts, NULL, args.options)) { + ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options); + pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1"); + return 0; + } + } + ds = ast_geoloc_datastore_find(chan); if (!ds) { - ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", cmd); + ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name); pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2"); return 0; } - eprofile = ast_geoloc_datastore_get_eprofile(ds, 0); + orig_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0); + if (!orig_eprofile) { + ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name); + pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2"); + return 0; + } + + eprofile = ast_geoloc_eprofile_dup(orig_eprofile); + ao2_ref(orig_eprofile, -1); if (!eprofile) { - ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", cmd); + ast_log(LOG_ERROR, "%s: Unable to duplicate eprofile\n", chan_name); pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2"); return 0; } + if (!eprofile->effective_location) { + ast_geoloc_eprofile_refresh_location(eprofile); + } + pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0"); if (ast_strings_equal(args.field, "inheritable")) { ast_str_append(buf, len, "%s", ds->inheritance ? "true" : "false"); @@ -101,19 +152,19 @@ static int geoloc_profile_read(struct ast_channel *chan, } else if (ast_strings_equal(args.field, "notes")) { ast_str_append(buf, len, "%s", eprofile->notes); } else if (ast_strings_equal(args.field, "location_info")) { - varlist_to_str(eprofile->location_info, buf, len); + RESOLVE_FOR_READ(location_info); } else if (ast_strings_equal(args.field, "location_info_refinement")) { - varlist_to_str(eprofile->location_refinement, buf, len); + RESOLVE_FOR_READ(location_refinement); } else if (ast_strings_equal(args.field, "location_variables")) { - varlist_to_str(eprofile->location_variables, buf, len); + RESOLVE_FOR_READ(location_variables); } else if (ast_strings_equal(args.field, "effective_location")) { - varlist_to_str(eprofile->effective_location, buf, len); + RESOLVE_FOR_READ(effective_location); } else if (ast_strings_equal(args.field, "usage_rules")) { - varlist_to_str(eprofile->usage_rules, buf, len); + RESOLVE_FOR_READ(usage_rules); } else if (ast_strings_equal(args.field, "confidence")) { varlist_to_str(eprofile->confidence, buf, len); } else { - ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field); + ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field); pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); } @@ -121,6 +172,10 @@ static int geoloc_profile_read(struct ast_channel *chan, return 0; } +#define VAR_LIST_REPLACE(_old, _new) \ + ast_variables_destroy(_old); \ + _old = _new; + #define TEST_ENUM_VALUE(_chan_name, _ep, _field, _value) \ ({ \ enum ast_geoloc_ ## _field v; \ @@ -142,8 +197,26 @@ static int geoloc_profile_read(struct ast_channel *chan, pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \ return 0; \ } \ - ast_variables_destroy(_ep->_field); \ - _ep->_field = _list; \ + if (ast_test_flag(&opts, OPT_GEOLOC_APPEND)) { \ + ast_variable_list_append(&_ep->_field, _list); \ + } else {\ + VAR_LIST_REPLACE(_ep->_field, _list); \ + } \ +}) + + +#define RESOLVE_FOR_WRITE(_param) \ +({ \ +if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \ + struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \ + eprofile->_param, 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; \ + } \ + VAR_LIST_REPLACE(eprofile->_param, resolved); \ +} \ }) static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char *data, @@ -153,9 +226,11 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char const char *chan_name = ast_channel_name(chan); struct ast_datastore *ds; /* Reminder: datastores aren't ao2 objects */ RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup); + struct ast_flags opts = { 0, }; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field); + AST_APP_ARG(options); ); /* Check for zero arguments */ @@ -173,6 +248,18 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char return 0; } + if (!ast_strlen_zero(args.options)) { + if (ast_app_parse_options(action_options, &opts, NULL, args.options)) { + ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options); + pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1"); + return 0; + } + } + + ast_debug(1, "%s: name: %s value: %s options: %s append: %s resolve: %s\n", chan_name, + args.field, value, args.options, ast_test_flag(&opts, OPT_GEOLOC_APPEND) ? "yes" : "no", + ast_test_flag(&opts, OPT_GEOLOC_RESOLVE) ? "yes" : "no"); + ds = ast_geoloc_datastore_find(chan); if (!ds) { ds = ast_geoloc_datastore_create(ast_channel_name(chan)); @@ -203,6 +290,8 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char if (ast_strings_equal(args.field, "inheritable")) { ast_geoloc_datastore_set_inheritance(ds, ast_true(value)); + } else if (ast_strings_equal(args.field, "id")) { + ast_string_field_set(eprofile, id, value); } else if (ast_strings_equal(args.field, "location_reference")) { struct ast_geoloc_location *loc = ast_geoloc_get_location(value); ao2_cleanup(loc); @@ -224,18 +313,25 @@ 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, "location_info")) { - TEST_VARLIST(chan_name, eprofile, location_info, value); } else if (ast_strings_equal(args.field, "location_source")) { ast_string_field_set(eprofile, location_source, value); + } else if (ast_strings_equal(args.field, "notes")) { + ast_string_field_set(eprofile, notes, value); + } else if (ast_strings_equal(args.field, "location_info")) { + TEST_VARLIST(chan_name, eprofile, location_info, value); + RESOLVE_FOR_WRITE(location_info); } else if (ast_strings_equal(args.field, "location_info_refinement")) { TEST_VARLIST(chan_name, eprofile, location_refinement, value); + RESOLVE_FOR_WRITE(location_refinement); } else if (ast_strings_equal(args.field, "location_variables")) { TEST_VARLIST(chan_name, eprofile, location_variables, value); + RESOLVE_FOR_WRITE(location_variables); } else if (ast_strings_equal(args.field, "effective_location")) { TEST_VARLIST(chan_name, eprofile, effective_location, value); + RESOLVE_FOR_WRITE(effective_location); } else if (ast_strings_equal(args.field, "usage_rules")) { TEST_VARLIST(chan_name, eprofile, usage_rules, value); + RESOLVE_FOR_WRITE(usage_rules); } else if (ast_strings_equal(args.field, "confidence")) { TEST_VARLIST(chan_name, eprofile, confidence, value); } else { @@ -245,6 +341,7 @@ static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char } ast_geoloc_eprofile_refresh_location(eprofile); + pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0"); return 0; diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml index 4f7cdc2339ec26c697e407255c99c2dd4b9b2772..b5713362f2405294487c26a15ea1fd436b486a45 100644 --- a/res/res_geolocation/geoloc_doc.xml +++ b/res/res_geolocation/geoloc_doc.xml @@ -7,12 +7,14 @@ <configObject name="location"> <synopsis>Location</synopsis> <description> - <para>cffdffff</para> + <para>Parameters for defining a Location object</para> </description> + <configOption name="type"> <synopsis>Must be of type 'location'.</synopsis> </configOption> - <configOption name="format" default=""> + + <configOption name="format" default="none"> <synopsis>Location specification type</synopsis> <description> <enumlist> @@ -42,7 +44,8 @@ </enumlist> </description> </configOption> - <configOption name="location_info" default=""> + + <configOption name="location_info" default="none"> <synopsis>Location information</synopsis> <description> <para>The contents of this parameter are specific to the @@ -68,7 +71,8 @@ </enumlist> </description> </configOption> - <configOption name="location_source" default=""> + + <configOption name="location_source" default="none"> <synopsis>Fully qualified host name</synopsis> <description> <para>This parameter isn't required but if provided, RFC8787 says it MUST be a fully @@ -77,7 +81,8 @@ Geolocation</literal> header.</para> </description> </configOption> - <configOption name="method" default=""> + + <configOption name="method" default="none"> <synopsis>Location determination method</synopsis> <description> <para>This is a rarely used field in the specification that would @@ -94,7 +99,8 @@ </enumlist> </description> </configOption> - <configOption name="confidence" default=""> + + <configOption name="confidence" default="none"> <synopsis>Level of confidence</synopsis> <description> <para>This is a rarely used field in the specification that would @@ -123,14 +129,16 @@ </see-also> </configOption> </configObject> + <configObject name="profile"> <synopsis>Profile</synopsis> <description> - <para>cffdffff</para> + <para>Parameters for defining a Profile object</para> </description> <configOption name="type"> <synopsis>Must be of type 'profile'.</synopsis> </configOption> + <configOption name="pidf_element" default="device"> <synopsis>PIDF-LO element to place this profile in</synopsis> <description> @@ -148,21 +156,25 @@ <ref type="link">https://www.rfc-editor.org/rfc/rfc5491.html#section-3.4</ref> </see-also> </configOption> - <configOption name="location_reference" default=""> + + <configOption name="location_reference" default="none"> <synopsis>Reference to a location object</synopsis> </configOption> - <configOption name="location_info_refinement" default=""> + + <configOption name="location_info_refinement" default="none"> <synopsis>Reference to a location object</synopsis> </configOption> - <configOption name="location_variables" default=""> + <configOption name="location_variables" default="none"> <synopsis>Reference to a location object</synopsis> </configOption> - <configOption name="usage_rules" default="yes"> + + <configOption name="usage_rules" default="empty <usage_rules> element"> <synopsis>location specification type</synopsis> <description> <para>xxxx</para> </description> </configOption> + <configOption name="notes" default=""> <synopsis>Notes to be added to the outgoing PIDF-LO document</synopsis> <description> @@ -171,11 +183,13 @@ outgoing PIDF-LO document. Its usage should be pre-negotiated with any recipients.</para> </description> + </configOption> - <configOption name="allow_routing_use"> + <configOption name="allow_routing_use" default="no"> <synopsis>Sets the value of the Geolocation-Routing header.</synopsis> </configOption> - <configOption name="suppress_empty_ca_elements"> + + <configOption name="suppress_empty_ca_elements" default="no"> <synopsis>Sets if empty Civic Address elements should be suppressed from the PIDF-LO document.</synopsis> </configOption> @@ -207,6 +221,7 @@ </enumlist> </description> </configOption> + <xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='format'])"/> <xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='location_info'])"/> <xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='confidence'])"/> @@ -220,8 +235,8 @@ Get or Set a field in a geolocation profile </synopsis> <syntax> - <parameter name="field" required="true"> - <para>The profile field to operate on. The following fields from the + <parameter name="parameter" required="true"> + <para>The profile parameter to operate on. The following fields from the Location and Profile objects are supported.</para> <enumlist> <enum name="id"/> @@ -244,10 +259,38 @@ set to <literal>true</literal> or <literal>false</literal> to control whether the profile will be passed to the outgoing channel. </para> + <para> + </para> + </parameter> + + <parameter name="options" required="false"> + <optionlist> + <option name="a"> + <para>Append provided value to the specified parameter + instead of replacing the existing value. This only applies + to variable list parameters like + <literal>location_info_refinement</literal>. + </para> + </option> + <option name="r"> + <para>Before reading or after writing the specified parameter, + re-resolve the <literal>effective_location</literal> and + <literal>usage_rules</literal> parameters using the + <literal>location_variables</literal> parameter and the variables + set on the channel in effect at the time this function is called. + </para> + <note><para>On a read operation, this does not alter the actual profile + in any way. On a write operation however, the + <literal>effective_location</literal> and/or <literal>usage_rules</literal> + parameters may indeed change and those changes will be passed on + to any outgoing channel. + </para></note> + </option> + </optionlist> </parameter> </syntax> <description><para> - When used to set a field on a profile, if the profile doesn't already exist, a new + When used to set a parameter on a profile, if the profile doesn't already exist, a new one will be created automatically. </para> <para> @@ -258,8 +301,8 @@ <enum name="0"><para>Success</para></enum> <enum name="-1"><para>No or not enough parameters were supplied</para></enum> <enum name="-2"><para>There was an internal error finding or creating a profile</para></enum> - <enum name="-3"><para>There was an issue specific to the field specified - (value not valid or field name not found)</para></enum> + <enum name="-3"><para>There was an issue specific to the parameter specified + (value not valid or parameter name not found, etc.)</para></enum> </enumlist> </description> </function> diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c index 1deb76e654d88e7ade4c48213d0280db3d4fa176..864dd23b244919a749f4694dd2d17d08649a5662 100644 --- a/res/res_geolocation/geoloc_eprofile.c +++ b/res/res_geolocation/geoloc_eprofile.c @@ -156,6 +156,67 @@ int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile) return 0; } +struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *src) +{ + struct ast_geoloc_eprofile *eprofile; + const char *profile_id; + int rc = 0; + + if (!src) { + return NULL; + } + + profile_id = ast_strdupa(src->id); + + eprofile = ast_geoloc_eprofile_alloc(profile_id); + if (!eprofile) { + return NULL; + } + + eprofile->allow_routing_use = src->allow_routing_use; + eprofile->pidf_element = src->pidf_element; + eprofile->suppress_empty_ca_elements = src->suppress_empty_ca_elements; + eprofile->format = src->format; + eprofile->precedence = src->precedence; + + + rc = ast_string_field_set(eprofile, location_reference, src->location_reference); + if (rc == 0) { + ast_string_field_set(eprofile, notes, src->notes); + } + if (rc == 0) { + ast_string_field_set(eprofile, method, src->method); + } + if (rc == 0) { + ast_string_field_set(eprofile, location_source, src->location_source); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->location_info, src->location_info); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->effective_location, src->effective_location); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->location_refinement, src->location_refinement); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->location_variables, src->location_variables); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->usage_rules, src->usage_rules); + } + if (rc == 0) { + rc = DUP_VARS(eprofile->confidence, src->confidence); + } + if (rc != 0) { + ao2_ref(eprofile, -1); + return NULL; + } + + + return eprofile; +} + struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile) { struct ast_geoloc_eprofile *eprofile; @@ -287,7 +348,7 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri, return eprofile; } -static struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source, +struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source, struct ast_variable *variables, struct ast_channel *chan) { struct ast_variable *dest = NULL; diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h index 910dbc5a3a3e86c46e4014b77926bd6c8f71faea..0bd0797cb7dcb17ac249f075102fc6177ac3f417 100644 --- a/res/res_geolocation/geoloc_private.h +++ b/res/res_geolocation/geoloc_private.h @@ -155,4 +155,8 @@ int geoloc_eprofile_reload(void); 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); + + #endif /* GEOLOC_PRIVATE_H_ */ diff --git a/res/res_pjsip_geolocation.c b/res/res_pjsip_geolocation.c index 0ca1e589809f03d1bb271da594e6d787a3305d9a..d0e8d465d35b832c750415c7834ac2e9358c07c3 100644 --- a/res/res_pjsip_geolocation.c +++ b/res/res_pjsip_geolocation.c @@ -574,7 +574,10 @@ static void handle_outgoing_request(struct ast_sip_session *session, struct pjsi session_name); } - ast_geoloc_eprofile_refresh_location(final_eprofile); + if (!final_eprofile->effective_location) { + ast_geoloc_eprofile_refresh_location(final_eprofile); + } + if (final_eprofile->format == AST_GEOLOC_FORMAT_URI) { uri = ast_geoloc_eprofile_to_uri(final_eprofile, channel, &buf, session_name); if (!uri) {