[Asterisk-code-review] res_geolocation: Dialplan function initial commit (asterisk[development/16/geolocation])
George Joseph
asteriskteam at digium.com
Wed Mar 9 11:46:49 CST 2022
George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/18192 )
Change subject: res_geolocation: Dialplan function initial commit
......................................................................
res_geolocation: Dialplan function initial commit
Also did more renames and added additional error checking
to ast_geoloc_eprofile_refresh_location().
Change-Id: Ibbf9bebaf510fd2fc1f535d17cda5d3bb1fa2009
---
M include/asterisk/res_geolocation.h
M main/config.c
M res/res_geolocation/geoloc_config.c
M res/res_geolocation/geoloc_datastore.c
M res/res_geolocation/geoloc_dialplan.c
M res/res_geolocation/geoloc_doc.xml
M res/res_geolocation/geoloc_eprofile.c
M res/res_geolocation/geoloc_private.h
M res/res_pjsip_geolocation.c
9 files changed, 590 insertions(+), 94 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/92/18192/1
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
index 80f1caf..139eb74 100644
--- a/include/asterisk/res_geolocation.h
+++ b/include/asterisk/res_geolocation.h
@@ -24,6 +24,8 @@
#include "asterisk/xml.h"
#include "asterisk/optional_api.h"
+#define AST_GEOLOC_INVALID_VALUE -1
+
enum ast_geoloc_pidf_element {
AST_PIDF_ELEMENT_NONE = 0,
AST_PIDF_ELEMENT_TUPLE,
@@ -38,11 +40,11 @@
AST_GEOLOC_FORMAT_URI,
};
-enum ast_geoloc_location_disposition {
- AST_GEOLOC_LOC_DISP_DISCARD = 0,
- AST_GEOLOC_LOC_DISP_APPEND,
- AST_GEOLOC_LOC_DISP_PREPEND,
- AST_GEOLOC_LOC_DISP_REPLACE,
+enum ast_geoloc_action {
+ AST_GEOLOC_ACTION_DISCARD = 0,
+ AST_GEOLOC_ACTION_APPEND,
+ AST_GEOLOC_ACTION_PREPEND,
+ AST_GEOLOC_ACTION_REPLACE,
};
struct ast_geoloc_location {
@@ -51,7 +53,7 @@
AST_STRING_FIELD(method);
);
enum ast_geoloc_format format;
- struct ast_variable *location_vars;
+ struct ast_variable *location_info;
};
struct ast_geoloc_profile {
@@ -60,12 +62,12 @@
AST_STRING_FIELD(location_reference);
);
enum ast_geoloc_pidf_element pidf_element;
- enum ast_geoloc_location_disposition location_disposition;
+ enum ast_geoloc_action action;
int geolocation_routing;
int send_location;
struct ast_variable *location_refinement;
struct ast_variable *location_variables;
- struct ast_variable *usage_rules_vars;
+ struct ast_variable *usage_rules;
};
struct ast_geoloc_eprofile {
@@ -75,15 +77,15 @@
AST_STRING_FIELD(method);
);
enum ast_geoloc_pidf_element pidf_element;
- enum ast_geoloc_location_disposition location_disposition;
+ enum ast_geoloc_action action;
int geolocation_routing;
int send_location;
enum ast_geoloc_format format;
- struct ast_variable *location_vars;
+ struct ast_variable *location_info;
struct ast_variable *location_refinement;
struct ast_variable *location_variables;
struct ast_variable *effective_location;
- struct ast_variable *usage_rules_vars;
+ struct ast_variable *usage_rules;
};
/*!
@@ -143,13 +145,13 @@
const char *ast_geoloc_civicaddr_resolve_variable(const char *variable);
enum ast_geoloc_validate_result {
+ AST_GEOLOC_VALIDATE_INVALID_VALUE = -1,
AST_GEOLOC_VALIDATE_SUCCESS = 0,
AST_GEOLOC_VALIDATE_MISSING_TYPE,
AST_GEOLOC_VALIDATE_INVALID_TYPE,
AST_GEOLOC_VALIDATE_INVALID_VARNAME,
AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES,
AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES,
- AST_GEOLOC_VALIDATE_INVALID_VALUE,
};
const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result);
@@ -176,6 +178,12 @@
enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
const char **result);
+
+/*!
+ * \brief Geolocation datastore Functions
+ * @{
+ */
+
/*!
* \brief Create a geoloc datastore from a profile name
*
@@ -195,13 +203,94 @@
struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
struct ast_geoloc_eprofile *eprofile);
+/*!
+ * \brief Create an empty geoloc datastore.
+ *
+ * \param id An id to use for the datastore.
+ *
+ * \return The datastore.
+ */
struct ast_datastore *ast_geoloc_datastore_create(const char *id);
+
+/*!
+ * \brief Retrieve a geoloc datastore's id.
+ *
+ * \param ds The datastore
+ *
+ * \return The datastore's id.
+ */
+const char *ast_geoloc_datastore_get_id(struct ast_datastore *ds);
+
+/*!
+ * \brief Add an eprofile to a datastore
+ *
+ * \param ds The datastore
+ * \param eprofile The eprofile to add.
+ *
+ * \return The number of eprofiles.
+ */
int ast_geoloc_datastore_add_eprofile(struct ast_datastore *ds,
struct ast_geoloc_eprofile *eprofile);
+
+/*!
+ * \brief Retrieves the number of eprofiles in the datastore
+ *
+ * \param ds The datastore
+ *
+ * \return The number of eprofiles.
+ */
int ast_geoloc_datastore_size(struct ast_datastore *ds);
+
+/*!
+ * \brief Sets the inheritance flag on the datastore
+ *
+ * \param ds The datastore
+ * \param inherit 1 to allow the datastore to be inherited by other channels
+ * 0 to prevent the datastore to be inherited by other channels
+ *
+ * \return 0 if successful, -1 otherwise.
+ */
+int ast_geoloc_datastore_set_inheritance(struct ast_datastore *ds, int inherit);
+
+/*!
+ * \brief Retrieve a specific eprofile from a datastore by index
+ *
+ * \param ds The datastore
+ * \param ix The index
+ *
+ * \return The effective profile ao2 object with its reference count bumped.
+ */
struct ast_geoloc_eprofile *ast_geoloc_datastore_get_eprofile(struct ast_datastore *ds, int ix);
/*!
+ * \brief Delete a specific eprofile from a datastore by index
+ *
+ * \param ds The datastore
+ * \param ix The index
+ *
+ * \return 0 if succesful, -1 otherwise.
+ */
+int ast_geoloc_datastore_delete_eprofile(struct ast_datastore *ds, int ix);
+
+/*!
+ * \brief Retrieves the geoloc datastore from a channel, if any
+ *
+ * \param chan Channel
+ *
+ * \return datastore if found, NULL otherwise.
+ */
+struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan);
+
+/*!
+ * @}
+ */
+
+/*!
+ * \brief Geolocation Effective Profile Functions
+ * @{
+ */
+
+/*!
* \brief Allocate a new, empty effective profile.
*
* \param name The profile's name
@@ -250,4 +339,8 @@
*/
int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile);
+/*!
+ * @}
+ */
+
#endif /* INCLUDE_ASTERISK_RES_GEOLOCATION_H_ */
diff --git a/main/config.c b/main/config.c
index 07925bd..dcf1b9a 100644
--- a/main/config.c
+++ b/main/config.c
@@ -746,8 +746,16 @@
while ((item = ast_strsep_strict(&item_string, item_sep, quote, AST_STRSEP_ALL))) {
item_name = ast_strsep_strict(&item, nv_sep, quote, AST_STRSEP_ALL);
+ if (!item_name) {
+ ast_variables_destroy(new_list);
+ return NULL;
+ }
item_value = ast_strsep_strict(&item, nv_sep, quote, AST_STRSEP_ALL);
+ if (!item_value) {
+ ast_variables_destroy(new_list);
+ return NULL;
+ }
new_var = ast_variable_new(item_name, item_value, "");
if (!new_var) {
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
index 309df22..2350463 100644
--- a/res/res_geolocation/geoloc_config.c
+++ b/res/res_geolocation/geoloc_config.c
@@ -38,7 +38,7 @@
"URI",
};
-static const char * location_disposition_names[] = {
+static const char * action_names[] = {
"discard",
"append",
"prepend",
@@ -46,13 +46,13 @@
};
CONFIG_ENUM(location, format)
-CONFIG_VAR_LIST(location, location_vars)
+CONFIG_VAR_LIST(location, location_info)
static void geoloc_location_destructor(void *obj) {
struct ast_geoloc_location *location = obj;
ast_string_field_free_memory(location);
- ast_variables_destroy(location->location_vars);
+ ast_variables_destroy(location->location_info);
}
static void *geoloc_location_alloc(const char *name)
@@ -67,10 +67,10 @@
CONFIG_ENUM(profile, pidf_element)
-CONFIG_ENUM(profile, location_disposition)
+CONFIG_ENUM(profile, action)
CONFIG_VAR_LIST(profile, location_refinement)
CONFIG_VAR_LIST(profile, location_variables)
-CONFIG_VAR_LIST(profile, usage_rules_vars)
+CONFIG_VAR_LIST(profile, usage_rules)
static void geoloc_profile_destructor(void *obj) {
@@ -79,7 +79,7 @@
ast_string_field_free_memory(profile);
ast_variables_destroy(profile->location_refinement);
ast_variables_destroy(profile->location_variables);
- ast_variables_destroy(profile->usage_rules_vars);
+ ast_variables_destroy(profile->usage_rules);
}
static void *geoloc_profile_alloc(const char *name)
@@ -105,7 +105,7 @@
ast_log(LOG_ERROR, "Location '%s' must have a format\n", location_id);
return -1;
case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
- result = ast_geoloc_civicaddr_validate_varlist(location->location_vars, &failed);
+ result = ast_geoloc_civicaddr_validate_varlist(location->location_info, &failed);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
ast_log(LOG_ERROR, "Location '%s' has invalid item '%s' in the location\n",
location_id, failed);
@@ -113,7 +113,7 @@
}
break;
case AST_GEOLOC_FORMAT_GML:
- result = ast_geoloc_gml_validate_varlist(location->location_vars, &failed);
+ result = ast_geoloc_gml_validate_varlist(location->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, location_id);
@@ -122,9 +122,9 @@
break;
case AST_GEOLOC_FORMAT_URI:
- uri = ast_variable_find_in_list(location->location_vars, "URI");
+ uri = ast_variable_find_in_list(location->location_info, "URI");
if (!uri) {
- struct ast_str *str = ast_variable_list_join(location->location_vars, ",", "=", "\"", NULL);
+ struct ast_str *str = ast_variable_list_join(location->location_info, ",", "=", "\"", NULL);
ast_log(LOG_ERROR, "Geolocation location '%s' format is set to '%s' but no 'URI' was found in location parameter '%s'\n",
location_id, format_names[AST_GEOLOC_FORMAT_URI], ast_str_buffer(str));
@@ -259,7 +259,7 @@
struct ast_str *str;
ao2_lock(loc);
- str = ast_variable_list_join(loc->location_vars, ",", "=", "\"", NULL);
+ str = ast_variable_list_join(loc->location_info, ",", "=", "\"", NULL);
if (!str) {
ao2_unlock(loc);
ao2_ref(loc, -1);
@@ -295,7 +295,7 @@
int using_regex = 0;
char *result = CLI_SUCCESS;
int ret = 0;
- char *disposition;
+ char *action;
int count = 0;
switch (cmd) {
@@ -352,14 +352,14 @@
for (; (profile = ao2_iterator_next(&iter)); ao2_ref(profile, -1)) {
ao2_lock(profile);
- location_disposition_to_str(profile, NULL, &disposition);
+ action_to_str(profile, NULL, &action);
ast_cli(a->fd, "%-46.46s %-13s %-6s %-s\n",
ast_sorcery_object_get_id(profile),
- disposition,
+ action,
profile->send_location ? "yes" : "no",
profile->location_reference);
ao2_unlock(profile);
- ast_free(disposition);
+ ast_free(action);
count++;
}
ao2_iterator_destroy(&iter);
@@ -428,7 +428,7 @@
iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
for (; (profile = ao2_iterator_next(&iter)); ) {
- char *disposition = NULL;
+ char *action = NULL;
struct ast_str *loc_str = NULL;
struct ast_str *refinement_str = NULL;
struct ast_str *variables_str = NULL;
@@ -437,14 +437,14 @@
ao2_ref(profile, -1);
if (!ast_strlen_zero(eprofile->location_reference)) {
- loc_str = ast_variable_list_join(eprofile->location_vars, ",", "=", "\"", NULL);
+ loc_str = ast_variable_list_join(eprofile->location_info, ",", "=", "\"", NULL);
resolved_str = ast_variable_list_join(eprofile->effective_location, ",", "=", "\"", NULL);
}
refinement_str = ast_variable_list_join(eprofile->location_refinement, ",", "=", "\"", NULL);
variables_str = ast_variable_list_join(eprofile->location_variables, ",", "=", "\"", NULL);
- location_disposition_to_str(eprofile, NULL, &disposition);
+ action_to_str(eprofile, NULL, &action);
ast_cli(a->fd,
"id: %-s\n"
@@ -458,7 +458,7 @@
"location_variables: %-s\n"
"effective_location: %-s\n\n",
eprofile->id,
- disposition,
+ action,
eprofile->send_location ? "yes" : "no",
pidf_element_names[eprofile->pidf_element],
S_OR(eprofile->location_reference, "<none>"),
@@ -469,7 +469,7 @@
S_COR(resolved_str, ast_str_buffer(resolved_str), "<none>"));
ao2_ref(eprofile, -1);
- ast_free(disposition);
+ ast_free(action);
ast_free(loc_str);
ast_free(refinement_str);
ast_free(variables_str);
@@ -573,8 +573,8 @@
ast_sorcery_object_field_register(geoloc_sorcery, "location", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "format", AST_GEOLOC_FORMAT_NONE,
format_handler, format_to_str, NULL, 0, 0);
- ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "location", NULL,
- location_vars_handler, location_vars_to_str, location_vars_dup, 0, 0);
+ ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "location_info", NULL,
+ location_info_handler, location_info_to_str, location_info_dup, 0, 0);
ast_sorcery_apply_default(geoloc_sorcery, "profile", "config", "geolocation.conf,criteria=type=profile");
if (ast_sorcery_object_register(geoloc_sorcery, "profile", geoloc_profile_alloc, NULL, geoloc_profile_apply_handler)) {
@@ -589,12 +589,12 @@
pidf_element_handler, pidf_element_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_reference", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, location_reference));
- ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "received_location_disposition", "discard",
- location_disposition_handler, location_disposition_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "action", "discard",
+ action_handler, action_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "send_location", "no",
OPT_BOOL_T, 1, FLDSET(struct ast_geoloc_profile, send_location));
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "usage_rules", NULL,
- usage_rules_vars_handler, usage_rules_vars_to_str, usage_rules_vars_dup, 0, 0);
+ usage_rules_handler, usage_rules_to_str, usage_rules_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_refinement", NULL,
location_refinement_handler, location_refinement_to_str, location_refinement_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_variables", NULL,
diff --git a/res/res_geolocation/geoloc_datastore.c b/res/res_geolocation/geoloc_datastore.c
index 17e2847..e920659 100644
--- a/res/res_geolocation/geoloc_datastore.c
+++ b/res/res_geolocation/geoloc_datastore.c
@@ -19,6 +19,7 @@
#include "asterisk.h"
#include "asterisk/astobj2.h"
#include "asterisk/datastore.h"
+#include "asterisk/channel.h"
#include "asterisk/res_geolocation.h"
#include "asterisk/vector.h"
#include "geoloc_private.h"
@@ -41,11 +42,58 @@
ast_free(eds);
}
+static void *geoloc_datastore_duplicate(void *obj)
+{
+ struct eprofiles_datastore *in_eds = obj;
+ struct eprofiles_datastore *out_eds;
+ int rc = 0;
+ int i = 0;
+ int eprofile_count = 0;
+
+ out_eds = ast_calloc(1, sizeof(*out_eds));
+ if (!out_eds) {
+ return NULL;
+ }
+
+ rc = AST_VECTOR_INIT(&out_eds->eprofiles, 2);
+ if (rc != 0) {
+ ast_free(out_eds);
+ return NULL;
+ }
+
+ eprofile_count = AST_VECTOR_SIZE(&in_eds->eprofiles);
+ for (i = 0; i < eprofile_count; i++) {
+ rc = AST_VECTOR_APPEND(&out_eds->eprofiles, ao2_bump(AST_VECTOR_GET(&in_eds->eprofiles, i)));
+ if (rc != 0) {
+ geoloc_datastore_free(out_eds);
+ return NULL;
+ }
+ }
+
+ return out_eds;
+}
+
static const struct ast_datastore_info geoloc_datastore_info = {
.type = GEOLOC_DS_TYPE,
- .destroy = geoloc_datastore_free
+ .destroy = geoloc_datastore_free,
+ .duplicate = geoloc_datastore_duplicate,
};
+#define IS_GEOLOC_DS(_ds) (_ds && _ds->data && ast_strings_equal(_ds->info->type, GEOLOC_DS_TYPE))
+
+const char *ast_geoloc_datastore_get_id(struct ast_datastore *ds)
+{
+ struct eprofiles_datastore *eds = NULL;
+
+ if (!IS_GEOLOC_DS(ds)) {
+ return NULL;
+ }
+
+ eds = (struct eprofiles_datastore *)ds->data;
+
+ return eds->id;
+}
+
struct ast_datastore *ast_geoloc_datastore_create(const char *id)
{
struct ast_datastore *ds = NULL;
@@ -88,12 +136,12 @@
struct eprofiles_datastore *eds = NULL;
int rc = 0;
- if (!ds || !ast_strings_equal(ds->info->type, GEOLOC_DS_TYPE) || !ds->data || !eprofile) {
+ if (!IS_GEOLOC_DS(ds) || !eprofile) {
return -1;
}
eds = ds->data;
- rc = AST_VECTOR_APPEND(&eds->eprofiles, eprofile);
+ rc = AST_VECTOR_APPEND(&eds->eprofiles, ao2_bump(eprofile));
if (rc != 0) {
ast_log(LOG_ERROR, "Couldn't add eprofile '%s' to geoloc datastore '%s'\n", eprofile->id, eds->id);
}
@@ -105,7 +153,7 @@
{
struct eprofiles_datastore *eds = NULL;
- if (!ds || !ast_strings_equal(ds->info->type, GEOLOC_DS_TYPE) || !ds->data) {
+ if (!IS_GEOLOC_DS(ds)) {
return -1;
}
@@ -114,12 +162,21 @@
return AST_VECTOR_SIZE(&eds->eprofiles);
}
+int ast_geoloc_datastore_set_inheritance(struct ast_datastore *ds, int inherit)
+{
+ if (!IS_GEOLOC_DS(ds)) {
+ return -1;
+ }
+ ds->inheritance = inherit ? DATASTORE_INHERIT_FOREVER : 0;
+ return 0;
+}
+
struct ast_geoloc_eprofile *ast_geoloc_datastore_get_eprofile(struct ast_datastore *ds, int ix)
{
struct eprofiles_datastore *eds = NULL;
struct ast_geoloc_eprofile *eprofile;
- if (!ds || !ast_strings_equal(ds->info->type, GEOLOC_DS_TYPE) || !ds->data) {
+ if (!IS_GEOLOC_DS(ds)) {
return NULL;
}
@@ -133,6 +190,29 @@
return ao2_bump(eprofile);
}
+struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan)
+{
+ return ast_channel_datastore_find(chan, &geoloc_datastore_info, NULL);
+}
+
+int ast_geoloc_datastore_delete_eprofile(struct ast_datastore *ds, int ix)
+{
+ struct eprofiles_datastore *eds = NULL;
+
+ if (!IS_GEOLOC_DS(ds)) {
+ return -1;
+ }
+
+ eds = ds->data;
+
+ if (ix >= AST_VECTOR_SIZE(&eds->eprofiles)) {
+ return -1;
+ }
+
+ AST_VECTOR_REMOVE(&eds->eprofiles, ix, 1);
+ return 0;
+}
+
struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
struct ast_geoloc_eprofile *eprofile)
{
diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c
index 776441c..5f5e507 100644
--- a/res/res_geolocation/geoloc_dialplan.c
+++ b/res/res_geolocation/geoloc_dialplan.c
@@ -19,20 +19,297 @@
#include "asterisk.h"
#include "asterisk/config.h"
#include "asterisk/cli.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#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)
+{
+ struct ast_variable *var = list;
+
+ for (; var; var = var->next) {
+ ast_str_append(buf, len, "%s=\"%s\"%s", var->name, var->value, var->next ? "," : "");
+ }
+}
+
+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);
+ int index = -1;
+ struct ast_datastore *ds;
+ struct ast_geoloc_eprofile *eprofile = NULL;
+ int profile_count = 0;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(field);
+ AST_APP_ARG(index);
+ );
+
+ /* Check for zero arguments */
+ if (ast_strlen_zero(parsed_data)) {
+ ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", cmd);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, parsed_data);
+
+ if (ast_strlen_zero(args.field)) {
+ ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", cmd);
+ return -1;
+ }
+
+ if (!ast_strlen_zero(args.index)) {
+ if (sscanf(args.index, "%30d", &index) != 1) {
+ ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", cmd, args.index);
+ return -1;
+ }
+ }
+
+ ds = ast_geoloc_datastore_find(chan);
+ if (!ds) {
+ ast_log(LOG_NOTICE, "%s: There are no geoloc profiles on this channel\n", cmd);
+ return -1;
+ }
+
+ profile_count = ast_geoloc_datastore_size(ds);
+
+ if (index < 0) {
+ if (ast_strings_equal(args.field, "count")) {
+ ast_str_append(buf, len, "%d", profile_count);
+ } else if (ast_strings_equal(args.field, "inheritable")) {
+ ast_str_append(buf, len, "%d", ds->inheritance ? 1 : 0);
+ } else {
+ ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (index >= profile_count) {
+ ast_log(LOG_ERROR, "%s: index %d is out of range 0 -> %d\n", cmd, index, profile_count);
+ return -1;
+ }
+
+ eprofile = ast_geoloc_datastore_get_eprofile(ds, index);
+ if (!eprofile) {
+ ast_log(LOG_ERROR, "%s: Internal Error. Profile at index %d couldn't be retrieved.\n", cmd, index);
+ return -1;
+ }
+
+ if (ast_strings_equal(args.field, "id")) {
+ ast_str_append(buf, len, "%s", eprofile->id);
+ } else if (ast_strings_equal(args.field, "location_reference")) {
+ ast_str_append(buf, len, "%s", eprofile->location_reference);
+ } else if (ast_strings_equal(args.field, "method")) {
+ ast_str_append(buf, len, "%s", eprofile->method);
+ } else if (ast_strings_equal(args.field, "geolocation_routing")) {
+ ast_str_append(buf, len, "%s", eprofile->geolocation_routing ? "yes" : "no");
+ } else if (ast_strings_equal(args.field, "send_location")) {
+ ast_str_append(buf, len, "%s", eprofile->send_location ? "yes" : "no");
+ } else if (ast_strings_equal(args.field, "action")) {
+ ast_str_append(buf, len, "%s", geoloc_action_to_name(eprofile->action));
+ } else if (ast_strings_equal(args.field, "format")) {
+ ast_str_append(buf, len, "%s", geoloc_format_to_name(eprofile->format));
+ } else if (ast_strings_equal(args.field, "pidf_element")) {
+ ast_str_append(buf, len, "%s", geoloc_pidf_element_to_name(eprofile->pidf_element));
+ } else if (ast_strings_equal(args.field, "location_info")) {
+ varlist_to_str(eprofile->location_info, buf, len);
+ } else if (ast_strings_equal(args.field, "location_refinement")) {
+ varlist_to_str(eprofile->location_refinement, buf, len);
+ } else if (ast_strings_equal(args.field, "location_variables")) {
+ varlist_to_str(eprofile->location_variables, buf, len);
+ } else if (ast_strings_equal(args.field, "effective_location")) {
+ varlist_to_str(eprofile->effective_location, buf, len);
+ } else if (ast_strings_equal(args.field, "usage_rules")) {
+ varlist_to_str(eprofile->usage_rules, buf, len);
+ } else {
+ ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
+ return -1;
+ }
+
+ ao2_ref(eprofile, -1);
+ return 0;
+}
+
+#define TEST_ENUM_VALUE(_cmd, _ep, _field, _value) \
+({ \
+ enum ast_geoloc_ ## _field v; \
+ if (!_ep) { \
+ ast_log(LOG_ERROR, "%s: Field %s requires a valid index\n", _cmd, #_field); \
+ return -1; \
+ } \
+ v = geoloc_ ## _field ## _str_to_enum(_value); \
+ if (v == AST_GEOLOC_INVALID_VALUE) { \
+ ast_log(LOG_ERROR, "%s: %s '%s' is invalid\n", _cmd, #_field, value); \
+ return -1; \
+ } \
+ _ep->_field = v; \
+})
+
+#define TEST_VARLIST(_cmd, _ep, _field, _value) \
+({ \
+ struct ast_variable *_list; \
+ if (!_ep) { \
+ ast_log(LOG_ERROR, "%s: Field %s requires a valid index\n", _cmd, #_field); \
+ return -1; \
+ } \
+ _list = ast_variable_list_from_string(_value, ",", "=", "\"" ); \
+ if (!_list) { \
+ ast_log(LOG_ERROR, "%s: %s '%s' is malformed or contains invalid values", _cmd, #_field, _value); \
+ return -1; \
+ } \
+ ast_variables_destroy(_ep->_field); \
+ _ep->_field = _list; \
+})
+
+static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char *data,
+ const char *value)
+{
+ char *parsed_data = ast_strdupa(data);
+ struct ast_datastore *ds;
+ RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup);
+ int profile_count = 0;
+ int index = -1;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(field);
+ AST_APP_ARG(index);
+ );
+
+ /* Check for zero arguments */
+ if (ast_strlen_zero(parsed_data)) {
+ ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", cmd);
+ return -1;
+ }
+
+ AST_STANDARD_APP_ARGS(args, parsed_data);
+
+ if (ast_strlen_zero(args.field)) {
+ ast_log(LOG_ERROR, "%s: Cannot call without a field to set\n", cmd);
+ return -1;
+ }
+
+ if (!ast_strlen_zero(args.index)) {
+ if (sscanf(args.index, "%30d", &index) != 1) {
+ ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", cmd, args.index);
+ return -1;
+ }
+ }
+
+ ds = ast_geoloc_datastore_find(chan);
+ if (!ds) {
+ ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", cmd);
+ return -1;
+ }
+
+ profile_count = ast_geoloc_datastore_size(ds);
+
+ if (index >= 0 && index < profile_count) {
+ eprofile = ast_geoloc_datastore_get_eprofile(ds, index);
+ if (!eprofile) {
+ ast_log(LOG_ERROR, "%s: Internal Error. Profile at index %d couldn't be retrieved.\n", cmd, index);
+ return -1;
+ }
+ } else if (index >= profile_count) {
+ ast_log(LOG_ERROR, "%s: index %d is out of range 0 -> %d\n", cmd, index, profile_count);
+ return -1;
+ } else {
+ if (ast_strings_equal(args.field, "inheritable")) {
+ ast_geoloc_datastore_set_inheritance(ds, ast_true(value));
+ } else {
+ ast_log(LOG_ERROR, "%s: Field '%s' is not valid or requires a profile index\n", cmd, args.field);
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (ast_strings_equal(args.field, "all")) {
+ char *v = ast_strdupa(value);
+ if (ast_strlen_zero(ast_strip_quoted(v, "'\"", "'\""))) {
+ ast_geoloc_datastore_delete_eprofile(ds, index);
+ return 0;
+ } else {
+ ast_log(LOG_ERROR, "%s: Only an empty string is valid for field 'all'\n", cmd);
+ return -1;
+ }
+
+ } else if (ast_strings_equal(args.field, "location_reference")) {
+ struct ast_geoloc_location *loc = ast_geoloc_get_location(value);
+ ao2_cleanup(loc);
+ if (!loc) {
+ ast_log(LOG_ERROR, "%s: Location reference '%s' doesn't exist\n", cmd, value);
+ return -1;
+ }
+ ast_string_field_set(eprofile, location_reference, value);
+ } else if (ast_strings_equal(args.field, "method")) {
+ ast_string_field_set(eprofile, method, value);
+
+ } else if (ast_strings_equal(args.field, "geolocation_routing")) {
+ eprofile->geolocation_routing = ast_true(value);
+
+ } else if (ast_strings_equal(args.field, "send_location")) {
+ eprofile->send_location = ast_true(value);
+
+ } else if (ast_strings_equal(args.field, "action")) {
+ TEST_ENUM_VALUE(cmd, eprofile, action, value);
+
+ } else if (ast_strings_equal(args.field, "format")) {
+ TEST_ENUM_VALUE(cmd, eprofile, format, value);
+
+ } else if (ast_strings_equal(args.field, "pidf_element")) {
+ TEST_ENUM_VALUE(cmd, eprofile, pidf_element, value);
+
+ } else if (ast_strings_equal(args.field, "location_info")) {
+ TEST_VARLIST(cmd, eprofile, location_info, value);
+ } else if (ast_strings_equal(args.field, "location_refinement")) {
+ TEST_VARLIST(cmd, eprofile, location_refinement, value);
+ } else if (ast_strings_equal(args.field, "location_variables")) {
+ TEST_VARLIST(cmd, eprofile, location_variables, value);
+ } else if (ast_strings_equal(args.field, "effective_location")) {
+ TEST_VARLIST(cmd, eprofile, effective_location, value);
+ } else if (ast_strings_equal(args.field, "usage_rules")) {
+ TEST_VARLIST(cmd, eprofile, usage_rules, value);
+ } else {
+ ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", cmd, args.field);
+ return -1;
+ }
+
+ ast_geoloc_eprofile_refresh_location(eprofile);
+ return 0;
+}
+
+static struct ast_custom_function geoloc_function = {
+ .name = "GEOLOC_PROFILE",
+ .read2 = geoloc_profile_read,
+ .write = geoloc_profile_write,
+};
+
int geoloc_dialplan_unload(void)
{
+ ast_custom_function_unregister(&geoloc_function);
+
return AST_MODULE_LOAD_SUCCESS;
}
int geoloc_dialplan_load(void)
{
- return AST_MODULE_LOAD_SUCCESS;
+ int res = 0;
+
+ res = ast_custom_function_register(&geoloc_function);
+
+ return res == 0 ? AST_MODULE_LOAD_SUCCESS : AST_MODULE_LOAD_DECLINE;
}
int geoloc_dialplan_reload(void)
{
return AST_MODULE_LOAD_SUCCESS;
}
+
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
index 8322585..45f1fc9 100644
--- a/res/res_geolocation/geoloc_doc.xml
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -41,8 +41,8 @@
</enumlist>
</description>
</configOption>
- <configOption name="location" default="">
- <synopsis>Location Data</synopsis>
+ <configOption name="location_info" default="">
+ <synopsis>Location information</synopsis>
<description>
<para>The contents of this parameter are specific to the
specification type.</para>
@@ -89,7 +89,7 @@
<para>xxxx</para>
</description>
</configOption>
- <configOption name="received_location_disposition" default="no">
+ <configOption name="action" default="no">
<synopsis>Determine whether the location information supplied to a
channel should be used</synopsis>
<description>
@@ -134,5 +134,18 @@
</configObject>
</configFile>
</configInfo>
+ <function name="GEOLOC_PROFILE" language="en_US">
+ <synopsis>
+ Get or Set a field in a geolocation profile
+ </synopsis>
+ <syntax>
+ <parameter name="field" required="true">
+ <para>The profile field to operate on.</para>
+ </parameter>
+ <parameter name="index" required="false">
+ <para>The index of the profile to operate on. Not required for the special fields.</para>
+ </parameter>
+ </syntax>
+ </function>
</docs>
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
index 2fecd62..0c09d9d 100644
--- a/res/res_geolocation/geoloc_eprofile.c
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -32,15 +32,29 @@
static struct ast_sorcery *geoloc_sorcery;
+#define DUP_VARS(_dest, _source) \
+({ \
+ int _rc = 0; \
+ if (_source) { \
+ struct ast_variable *_vars = ast_variables_dup(_source); \
+ if (!_vars) { \
+ _rc = -1; \
+ } else { \
+ _dest = _vars; \
+ } \
+ } \
+ (_rc); \
+})
+
static void geoloc_effective_profile_destructor(void *obj) {
struct ast_geoloc_eprofile *eprofile = obj;
ast_string_field_free_memory(eprofile);
- ast_variables_destroy(eprofile->location_vars);
+ ast_variables_destroy(eprofile->location_info);
ast_variables_destroy(eprofile->location_refinement);
ast_variables_destroy(eprofile->location_variables);
ast_variables_destroy(eprofile->effective_location);
- ast_variables_destroy(eprofile->usage_rules_vars);
+ ast_variables_destroy(eprofile->usage_rules);
}
struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name)
@@ -57,7 +71,14 @@
int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile)
{
struct ast_geoloc_location *loc = NULL;
+ struct ast_variable *temp_locinfo = NULL;
+ struct ast_variable *temp_effloc = NULL;
struct ast_variable *var;
+ int rc = 0;
+
+ if (!eprofile) {
+ return -1;
+ }
if (!ast_strlen_zero(eprofile->location_reference)) {
loc = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", eprofile->location_reference);
@@ -68,45 +89,46 @@
}
eprofile->format = loc->format;
-
- ast_variables_destroy(eprofile->location_vars);
- eprofile->location_vars = loc->location_vars ? ast_variables_dup(loc->location_vars) : NULL;
+ rc = DUP_VARS(temp_locinfo, loc->location_info);
ao2_ref(loc, -1);
- loc = NULL;
+ if (rc != 0) {
+ return -1;
+ }
+ } else {
+ temp_locinfo = eprofile->location_info;
}
- ast_variables_destroy(eprofile->effective_location);
- eprofile->effective_location = eprofile->location_vars ? ast_variables_dup(eprofile->location_vars) : NULL;
+ rc = DUP_VARS(temp_effloc, temp_locinfo);
+ if (rc != 0) {
+ ast_variables_destroy(temp_locinfo);
+ return -1;
+ }
if (eprofile->location_refinement) {
for (var = eprofile->location_refinement; var; var = var->next) {
struct ast_variable *newvar = ast_variable_new(var->name, var->value, "");
- if (ast_variable_list_replace(&eprofile->effective_location, newvar)) {
- ast_variable_list_append(&eprofile->effective_location, newvar);
+ if (!newvar) {
+ ast_variables_destroy(temp_locinfo);
+ ast_variables_destroy(temp_effloc);
+ return -1;
+ }
+ if (ast_variable_list_replace(&temp_effloc, newvar)) {
+ ast_variable_list_append(&temp_effloc, newvar);
}
}
}
+ ast_variables_destroy(eprofile->location_info);
+ eprofile->location_info = temp_locinfo;
+ ast_variables_destroy(eprofile->effective_location);
+ eprofile->effective_location = temp_effloc;
+
return 0;
}
-#define DUP_VARS(_dest, _source) \
-({ \
- int _rc = 0; \
- if (_source) { \
- struct ast_variable *_vars = ast_variables_dup(_source); \
- if (!_vars) { \
- _rc = -1; \
- } else { \
- _dest = _vars; \
- } \
- } \
- (_rc); \
-})
-
-struct ast_geoloc_effective_profile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile)
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile)
{
- struct ast_geoloc_effective_profile *eprofile;
+ struct ast_geoloc_eprofile *eprofile;
const char *profile_id;
int rc = 0;
@@ -131,7 +153,7 @@
rc = DUP_VARS(eprofile->location_variables, profile->location_variables);
}
if (rc == 0) {
- rc = DUP_VARS(eprofile->usage_rules_vars, profile->usage_rules_vars);
+ rc = DUP_VARS(eprofile->usage_rules, profile->usage_rules);
}
if (rc != 0) {
ao2_unlock(profile);
@@ -139,7 +161,7 @@
return NULL;
}
- eprofile->location_disposition = profile->location_disposition;
+ eprofile->action = profile->action;
eprofile->send_location = profile->send_location;
ao2_unlock(profile);
@@ -176,7 +198,7 @@
}
eprofile->format = AST_GEOLOC_FORMAT_URI;
- eprofile->location_vars = ast_variable_new("URI", local_uri, "");
+ eprofile->location_info = ast_variable_new("URI", local_uri, "");
return eprofile;
}
@@ -227,8 +249,8 @@
location_str = ast_xml_get_text(location_info);
duped = ast_strdupa(location_str);
- eprofile->location_vars = ast_variable_list_from_string(duped, ",", "=", "\"");
- if (!eprofile->location_vars) {
+ eprofile->location_info = ast_variable_list_from_string(duped, ",", "=", "\"");
+ if (!eprofile->location_info) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "%s: Unable to create location variables from '%s'\n", reference_string, location_str);
return NULL;
@@ -236,7 +258,7 @@
usage_str = ast_xml_get_text(usage_rules);
duped = ast_strdupa(usage_str);
- eprofile->usage_rules_vars = ast_variable_list_from_string(duped, ",", "=", "\"");
+ eprofile->usage_rules = ast_variable_list_from_string(duped, ",", "=", "\"");
method_str = ast_xml_get_text(method);
ast_string_field_set(eprofile, method, method_str);
@@ -382,8 +404,8 @@
eprofile = ast_geoloc_eprofile_create_from_uri("http://some_uri&a=b", __func__);
ast_test_validate(test, eprofile != NULL);
ast_test_validate(test, eprofile->format == AST_GEOLOC_FORMAT_URI);
- ast_test_validate(test, eprofile->location_vars != NULL);
- uri = ast_variable_find_in_list(eprofile->location_vars, "URI");
+ ast_test_validate(test, eprofile->location_info != NULL);
+ uri = ast_variable_find_in_list(eprofile->location_info, "URI");
ast_test_validate(test, uri != NULL);
ast_test_validate(test, strcmp(uri, "http://some_uri&a=b") == 0);
@@ -426,13 +448,13 @@
ast_test_validate(test, eprofile->format == format);
ast_test_validate(test, ast_strings_equal(eprofile->method, method));
- str = ast_variable_list_join(eprofile->location_vars, ",", "=", NULL, NULL);
+ str = ast_variable_list_join(eprofile->location_info, ",", "=", NULL, NULL);
ast_test_validate(test, str != NULL);
ast_test_status_update(test, "location_vars: %s\n", ast_str_buffer(str));
ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), location));
ast_free(str);
- str = ast_variable_list_join(eprofile->usage_rules_vars, ",", "=", "'", NULL);
+ str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "'", NULL);
ast_test_validate(test, str != NULL);
ast_test_status_update(test, "usage_rules: %s\n", ast_str_buffer(str));
ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), usage));
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
index c5ea981..18b51a7 100644
--- a/res/res_geolocation/geoloc_private.h
+++ b/res/res_geolocation/geoloc_private.h
@@ -28,11 +28,11 @@
#define CONFIG_STR_TO_ENUM_DECL(_stem) int geoloc_ ## _stem ## _str_to_enum(const char *str);
CONFIG_STR_TO_ENUM_DECL(pidf_element)
CONFIG_STR_TO_ENUM_DECL(format);
-CONFIG_STR_TO_ENUM_DECL(location_disposition);
+CONFIG_STR_TO_ENUM_DECL(action);
#define GEOLOC_ENUM_TO_NAME_DECL(_stem) const char * geoloc_ ## _stem ## _to_name(int ix);
GEOLOC_ENUM_TO_NAME_DECL(pidf_element)
GEOLOC_ENUM_TO_NAME_DECL(format);
-GEOLOC_ENUM_TO_NAME_DECL(location_disposition);
+GEOLOC_ENUM_TO_NAME_DECL(action);
#define CONFIG_STR_TO_ENUM(_stem) \
diff --git a/res/res_pjsip_geolocation.c b/res/res_pjsip_geolocation.c
index cd8ba79..8e3156b 100644
--- a/res/res_pjsip_geolocation.c
+++ b/res/res_pjsip_geolocation.c
@@ -145,7 +145,7 @@
"%s: Couldn't allocate a geoloc datastore\n", session_name);
}
- if (config_profile->location_disposition == AST_GEOLOC_LOC_DISP_DISCARD) {
+ if (config_profile->action == AST_GEOLOC_ACTION_DISCARD) {
ast_trace(4, "%s: Profile '%s' location_disposition is 'discard' so "
"discarding Geolocation: " PJSTR_PRINTF_SPEC, session_name,
ast_sorcery_object_get_id(config_profile),
@@ -164,17 +164,17 @@
config_eprofile->id);
}
+ ast_channel_lock(channel);
ast_channel_datastore_add(channel, ds);
+ ast_channel_unlock(channel);
/*
- * We gave our eprofile reference to the datastore and the
- * datastore to the channel so don't let RAII_VAR clean them up.
+ * We gave the datastore to the channel so don't let RAII_VAR clean it up.
*/
- config_eprofile = NULL;
ds = NULL;
SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with 1 eprofile\n",
session_name);
- } else if (config_profile->location_disposition == AST_GEOLOC_LOC_DISP_PREPEND) {
+ } else if (config_profile->action == AST_GEOLOC_ACTION_PREPEND) {
ast_trace(4, "%s: Profile '%s' location_disposition is 'prepend' so "
"adding to datastore first", session_name, ast_sorcery_object_get_id(config_profile));
@@ -190,16 +190,17 @@
"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
config_eprofile->id);
}
- config_eprofile = NULL;
if (!geoloc_hdr) {
+ ast_channel_lock(channel);
ast_channel_datastore_add(channel, ds);
+ ast_channel_unlock(channel);
ds = NULL;
SCOPE_EXIT_RTN_VALUE(0, "%s: No Geolocation header so just adding config profile "
"'%s' to datastore\n", session_name, ast_sorcery_object_get_id(config_profile));
}
- } else if (config_profile->location_disposition == AST_GEOLOC_LOC_DISP_REPLACE) {
+ } else if (config_profile->action == AST_GEOLOC_ACTION_REPLACE) {
if (geoloc_hdr) {
ast_trace(4, "%s: Profile '%s' location_disposition is 'replace' so "
"we don't need to do anything with the configured profile", session_name,
@@ -275,19 +276,19 @@
eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, session_name);
}
- eprofile->location_disposition = config_profile->location_disposition;
+ eprofile->action = config_profile->action;
eprofile->send_location = config_profile->send_location;
ast_trace(4, "Processing URI '%s'. Adding to datastore\n", geoloc_uri);
rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+ ao2_ref(eprofile, -1);
if (rc != 0) {
- ao2_ref(eprofile, -1);
ast_log(LOG_WARNING, "%s: Unable to add effective profile for URI '%s' to datastore. Skipping\n",
session_name, geoloc_uri);
}
}
- if (config_profile->location_disposition == AST_GEOLOC_LOC_DISP_APPEND) {
+ if (config_profile->action == AST_GEOLOC_ACTION_APPEND) {
ast_trace(4, "%s: Profile '%s' location_disposition is 'prepend' so "
"adding to datastore first", session_name, ast_sorcery_object_get_id(config_profile));
@@ -313,7 +314,9 @@
session_name);
}
+ ast_channel_lock(channel);
ast_channel_datastore_add(channel, ds);
+ ast_channel_unlock(channel);
ds = NULL;
SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with %" PRIu64 " eprofiles\n",
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18192
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: development/16/geolocation
Gerrit-Change-Id: Ibbf9bebaf510fd2fc1f535d17cda5d3bb1fa2009
Gerrit-Change-Number: 18192
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220309/367d6375/attachment-0001.html>
More information about the asterisk-code-review
mailing list