[Asterisk-code-review] res_geolocation: Initial commit (asterisk[development/16/geolocation])

George Joseph asteriskteam at digium.com
Fri Mar 25 06:31:22 CDT 2022


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

Change subject: res_geolocation:  Initial commit
......................................................................

res_geolocation:  Initial commit

This is the first commit for res_geolocation.  It is in no way
complete but what is here is functional.

Change-Id: Ieb6e3640f31a676da42d8c144ebbb31ad795d849
---
A include/asterisk/res_geolocation.h
M res/Makefile
A res/res_geolocation.c
A res/res_geolocation.exports.in
A res/res_geolocation/geoloc_channel.c
A res/res_geolocation/geoloc_civicaddr.c
A res/res_geolocation/geoloc_config.c
A res/res_geolocation/geoloc_dialplan.c
A res/res_geolocation/geoloc_doc.xml
A res/res_geolocation/geoloc_gml.c
A res/res_geolocation/geoloc_private.h
11 files changed, 1,767 insertions(+), 0 deletions(-)

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



diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
new file mode 100644
index 0000000..6230cf2
--- /dev/null
+++ b/include/asterisk/res_geolocation.h
@@ -0,0 +1,148 @@
+ /*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef INCLUDE_ASTERISK_RES_GEOLOCATION_H_
+#define INCLUDE_ASTERISK_RES_GEOLOCATION_H_
+
+#include "asterisk/sorcery.h"
+
+enum ast_geoloc_pidf_section {
+	AST_PIDF_SECTION_NONE = 0,
+	AST_PIDF_SECTION_TUPLE,
+	AST_PIDF_SECTION_DEVICE,
+	AST_PIDF_SECTION_PERSON
+};
+
+enum ast_geoloc_format {
+	AST_GEOLOC_FORMAT_NONE = 0,
+	AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
+	AST_GEOLOC_FORMAT_GML,
+	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,
+};
+
+struct ast_geoloc_location {
+	SORCERY_OBJECT(details);
+	enum ast_geoloc_format format;
+	struct ast_variable *location_vars;
+};
+
+struct ast_geoloc_profile {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(location_reference);
+	);
+	enum ast_geoloc_pidf_section pidf_section;
+	enum ast_geoloc_location_disposition location_disposition;
+	int geolocation_routing;
+	int send_location;
+	struct ast_variable *location_refinement;
+	struct ast_variable *location_variables;
+	struct ast_variable *usage_rules_vars;
+};
+
+struct ast_geoloc_effective_profile {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(id);
+		AST_STRING_FIELD(location_reference);
+	);
+	enum ast_geoloc_pidf_section pidf_section;
+	enum ast_geoloc_location_disposition location_disposition;
+	int geolocation_routing;
+	int send_location;
+	enum ast_geoloc_format format;
+	struct ast_variable *location_vars;
+	struct ast_variable *location_refinement;
+	struct ast_variable *location_variables;
+	struct ast_variable *effective_location;
+	struct ast_variable *usage_rules_vars;
+};
+
+
+/*!
+ * \brief Given an official civicAddress code, return its friendly name.
+ *
+ * \param code Pointer to the code to check
+ *
+ * \return Pointer to the friendly name or NULL if code wasn't found.
+ */
+const char *ast_geoloc_civicaddr_get_name_from_code(const char *code);
+
+/*!
+ * \brief Given a civicAddress friendly name, return its official code.
+ *
+ * \param name Pointer to the name to check
+ *
+ * \return Pointer to the official code or NULL if name wasn't found.
+ */
+const char *ast_geoloc_civicaddr_get_code_from_name(const char *name);
+
+/*!
+ * \brief Given an unknown location variable, return its official civicAddress code.
+ *
+ * \param variable Pointer to the name or code to check
+ *
+ * \return Pointer to the official code or NULL if variable wasn't a name or code.
+ */
+const char *ast_geoloc_civicaddr_resolve_variable(const char *variable);
+
+enum ast_geoloc_validate_result {
+	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);
+
+/*!
+ * \brief Validate that the names of the variables in the list are valid codes or synonyms
+ *
+ * \param varlist Variable list to check.
+ * \param result[OUT] Pointer to char * to receive failing item.
+ *
+ * \return result code.
+ */
+enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
+	const struct ast_variable *varlist, const char **result);
+
+/*!
+ * \brief Validate that the variables in the list represent a valid GML shape
+ *
+ * \param varlist Variable list to check.
+ * \param result[OUT] Pointer to char * to receive failing item.
+ *
+ * \return result code.
+ */
+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
+	const char **result);
+
+struct ast_datastore *ast_geoloc_datastore_create(const char *profile_name);
+
+struct ast_geoloc_effective_profile *ast_geoloc_effective_profile_create(struct ast_geoloc_profile *profile);
+
+#endif /* INCLUDE_ASTERISK_RES_GEOLOCATION_H_ */
diff --git a/res/Makefile b/res/Makefile
index cd6e8ad..a6dd181 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -67,6 +67,7 @@
 $(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)
 $(call MOD_ADD_C,res_stasis_recording,stasis_recording/stored.c)
 $(call MOD_ADD_C,res_stir_shaken,$(wildcard res_stir_shaken/*.c))
+$(call MOD_ADD_C,res_geolocation,$(wildcard res_geolocation/*.c))
 
 res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 snmp/agent.o: _ASTCFLAGS+=-fPIC
diff --git a/res/res_geolocation.c b/res/res_geolocation.c
new file mode 100644
index 0000000..09a9a4b
--- /dev/null
+++ b/res/res_geolocation.c
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+#include "res_geolocation/geoloc_private.h"
+
+static const char *result_names[] = {
+	"Success",
+	"Missing type",
+	"Invalid shape type",
+	"Invalid variable name",
+	"Not enough variables",
+	"Too many variables",
+	"Invalid variable value"
+};
+
+const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result)
+{
+	return result_names[result];
+}
+
+static int reload_module(void)
+{
+	int res = 0;
+
+	res = geoloc_civicaddr_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_gml_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_config_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_dialplan_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_channel_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	int res = 0;
+
+	res += geoloc_channel_reload();
+	res += geoloc_dialplan_unload();
+	res += geoloc_config_unload();
+	res += geoloc_gml_unload();
+	res += geoloc_civicaddr_unload();
+
+	return (res != 0);
+}
+
+static int load_module(void)
+{
+	int res = 0;
+
+	res = geoloc_gml_load();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_civicaddr_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_config_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_dialplan_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_channel_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "res_geolocation Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 10,
+);
diff --git a/res/res_geolocation.exports.in b/res/res_geolocation.exports.in
new file mode 100644
index 0000000..da0a981
--- /dev/null
+++ b/res/res_geolocation.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_geo*;
+	local:
+		*;
+};
diff --git a/res/res_geolocation/geoloc_channel.c b/res/res_geolocation/geoloc_channel.c
new file mode 100644
index 0000000..719e214
--- /dev/null
+++ b/res/res_geolocation/geoloc_channel.c
@@ -0,0 +1,92 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/datastore.h"
+#include "geoloc_private.h"
+
+
+struct ast_sorcery *geoloc_sorcery;
+
+static void geoloc_datastore_destructor(void *obj)
+{
+	struct ast_geoloc_effective_profile *eprofile = obj;
+	ao2_ref(eprofile, -1);
+}
+
+
+static const struct ast_datastore_info geoloc_datastore_info = {
+	.type = "geolocation",
+	.destroy = geoloc_datastore_destructor
+};
+
+
+struct ast_datastore *ast_geoloc_datastore_create(const char *profile_name)
+{
+	struct ast_datastore *ds = NULL;
+	struct ast_geoloc_effective_profile *eprofile = NULL;
+	struct ast_geoloc_profile *profile = NULL;
+
+	if (ast_strlen_zero(profile_name)) {
+		return NULL;
+	}
+
+	ds = ast_datastore_alloc(&geoloc_datastore_info, NULL);
+	if (!ds) {
+		ast_log(LOG_ERROR, "A datasatore couldn't be allocated for profile '%s'", profile_name);
+		return NULL;
+	}
+
+	profile = ast_sorcery_retrieve_by_id(geoloc_sorcery, "profile", profile_name);
+	if (!profile) {
+		ast_datastore_free(ds);
+		ast_log(LOG_ERROR, "A profile with the name '%s' was not found", profile_name);
+		return NULL;
+	}
+
+	eprofile = ast_geoloc_effective_profile_create(profile);
+	ao2_ref(profile, -1);
+	if (!eprofile) {
+		ast_datastore_free(ds);
+		ast_log(LOG_ERROR, "An effective profile with the name '%s' couldn't be allocated", profile_name);
+		return NULL;
+	}
+
+	ds->data = eprofile;
+
+	return ds;
+}
+
+int geoloc_channel_unload(void)
+{
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_channel_load(void)
+{
+	geoloc_sorcery = geoloc_get_sorcery();
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_channel_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_civicaddr.c b/res/res_geolocation/geoloc_civicaddr.c
new file mode 100644
index 0000000..6816ebd
--- /dev/null
+++ b/res/res_geolocation/geoloc_civicaddr.c
@@ -0,0 +1,173 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/res_geolocation.h"
+#include "geoloc_private.h"
+
+struct addr_field_entry {
+	const char *code;
+	const char *name;
+};
+
+static struct addr_field_entry addr_code_name_entries[] = {
+	{"A1", "state_province"},
+	{"A2", "county_district"},
+	{"A3", "city"},
+	{"A4", "city_district"},
+	{"A5", "neighborhood"},
+	{"A6", "street_group"},
+	{"ADDCODE", "additional_code"},
+	{"BLD", "building"},
+	{"country", "country"},
+	{"FLR", "floor"},
+	{"HNO", "house_number"},
+	{"HNS", "house_number_suffix"},
+	{"LMK", "landmark"},
+	{"LOC", "additional_location"},
+	{"NAM", "location_name"},
+	{"PC", "postal_code"},
+	{"PCN", "postal_community"},
+	{"PLC", "place_type"},
+	{"POBOX", "po_box"},
+	{"POD", "trailing_street_suffix"},
+	{"POM", "road_post_modifier"},
+	{"PRD", "leading_road_direction"},
+	{"PRM", "road_pre_modifier"},
+	{"RD", "road"},
+	{"RD", "street"},
+	{"RDBR", "road_branch"},
+	{"RDSEC", "road_section"},
+	{"RDSUBBR", "road_sub_branch"},
+	{"ROOM", "room"},
+	{"SEAT", "seat"},
+	{"STS", "street_suffix"},
+	{"UNIT", "unit"},
+};
+
+static struct addr_field_entry addr_name_code_entries[ARRAY_LEN(addr_code_name_entries)];
+
+static int compare_civicaddr_codes(const void *_a, const void *_b)
+{
+	const struct addr_field_entry *a = _a;
+	const struct addr_field_entry *b = _b;
+
+	return strcmp(a->code, b->code);
+}
+
+static int compare_civicaddr_names(const void *_a, const void *_b)
+{
+	const struct addr_field_entry *a = _a;
+	const struct addr_field_entry *b = _b;
+
+	return strcmp(a->name, b->name);
+}
+
+const char *ast_geoloc_civicaddr_get_name_from_code(const char *code)
+{
+	struct addr_field_entry key = { .code = code };
+	struct addr_field_entry *entry = bsearch(&key, addr_code_name_entries, ARRAY_LEN(addr_code_name_entries),
+		sizeof(struct addr_field_entry), compare_civicaddr_codes);
+	return entry ? entry->name : NULL;
+}
+
+const char *ast_geoloc_civicaddr_get_code_from_name(const char *name)
+{
+	struct addr_field_entry key = { .name = name };
+	struct addr_field_entry *entry = bsearch(&key, addr_name_code_entries, ARRAY_LEN(addr_name_code_entries),
+		sizeof(struct addr_field_entry), compare_civicaddr_names);
+	return entry ? entry->code : NULL;
+}
+
+const char *ast_geoloc_civicaddr_resolve_variable(const char *variable)
+{
+	const char *result = ast_geoloc_civicaddr_get_name_from_code(variable);
+	if (result) {
+		return result;
+	}
+	return ast_geoloc_civicaddr_get_code_from_name(variable);
+}
+
+enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
+	const struct ast_variable *varlist,	const char **result)
+{
+	const struct ast_variable *var = varlist;
+	for (; var; var = var->next) {
+		const char *newname = ast_geoloc_civicaddr_resolve_variable(var->name);
+		if (!newname) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
+		}
+	}
+	return AST_GEOLOC_VALIDATE_SUCCESS;
+}
+
+static char *handle_civicaddr_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	int i;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc show civicAddr_mappings";
+		e->usage =
+			"Usage: geoloc show civicAddr_mappings\n"
+			"       Show the mappings between civicAddress official codes and synonyms.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, "%-16s %-32s\n", "Official Code", "Synonym");
+	ast_cli(a->fd, "================ ================================\n");
+
+	for (i = 0; i < ARRAY_LEN(addr_code_name_entries); i++) {
+		ast_cli(a->fd, "%-16s %-32s\n", addr_code_name_entries[i].code, addr_code_name_entries[i].name);
+	}
+	ast_cli(a->fd, "\n");
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry geoloc_civicaddr_cli[] = {
+	AST_CLI_DEFINE(handle_civicaddr_show, "Show the mappings between civicAddress official codes and synonyms"),
+};
+
+int geoloc_civicaddr_unload(void)
+{
+	ast_cli_unregister_multiple(geoloc_civicaddr_cli, ARRAY_LEN(geoloc_civicaddr_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_civicaddr_load(void)
+{
+	memcpy(addr_name_code_entries, addr_code_name_entries, sizeof(addr_code_name_entries));
+	qsort(addr_code_name_entries, ARRAY_LEN(addr_code_name_entries), sizeof(struct addr_field_entry), compare_civicaddr_codes);
+	qsort(addr_name_code_entries, ARRAY_LEN(addr_name_code_entries), sizeof(struct addr_field_entry), compare_civicaddr_names);
+
+	ast_cli_register_multiple(geoloc_civicaddr_cli, ARRAY_LEN(geoloc_civicaddr_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_civicaddr_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
new file mode 100644
index 0000000..c2fa2d0
--- /dev/null
+++ b/res/res_geolocation/geoloc_config.c
@@ -0,0 +1,653 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "geoloc_private.h"
+#include "asterisk/res_geolocation.h"
+
+static struct ast_sorcery *geoloc_sorcery;
+
+static const char *pidf_section_names[] = {
+	"<none>",
+	"tuple",
+	"device",
+	"person"
+};
+
+static const char *format_names[] = {
+	"<none>",
+	"civicAddress",
+	"GML",
+	"URI",
+};
+
+static const char * location_disposition_names[] = {
+	"discard",
+	"append",
+	"prepend",
+	"replace"
+};
+
+CONFIG_ENUM(location, format)
+CONFIG_VAR_LIST(location, location_vars)
+
+static void geoloc_location_destructor(void *obj) {
+	struct ast_geoloc_location *location = obj;
+
+	ast_variables_destroy(location->location_vars);
+}
+
+static void *geoloc_location_alloc(const char *name)
+{
+	return ast_sorcery_generic_alloc(sizeof(struct ast_geoloc_location), geoloc_location_destructor);
+}
+
+
+CONFIG_ENUM(profile, pidf_section)
+CONFIG_ENUM(profile, location_disposition)
+CONFIG_VAR_LIST(profile, location_refinement)
+CONFIG_VAR_LIST(profile, location_variables)
+CONFIG_VAR_LIST(profile, usage_rules_vars)
+
+
+static void geoloc_profile_destructor(void *obj) {
+	struct ast_geoloc_profile *profile = obj;
+
+	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);
+}
+
+static void *geoloc_profile_alloc(const char *name)
+{
+	struct ast_geoloc_profile *profile = ast_sorcery_generic_alloc(sizeof(*profile), geoloc_profile_destructor);
+	if (profile) {
+		ast_string_field_init(profile, 128);
+	}
+
+	return profile;
+}
+
+static void geoloc_effective_profile_destructor(void *obj) {
+	struct ast_geoloc_effective_profile *eprofile = obj;
+
+	ast_string_field_free_memory(eprofile);
+	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);
+}
+
+static void *geoloc_effective_profile_alloc(const char *name)
+{
+	struct ast_geoloc_effective_profile *eprofile = ao2_alloc_options(sizeof(*eprofile),
+		geoloc_effective_profile_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	ast_string_field_init(eprofile, 256);
+	ast_string_field_set(eprofile, id, name);
+
+	return eprofile;
+}
+
+struct ast_geoloc_effective_profile *ast_geoloc_effective_profile_create(struct ast_geoloc_profile *profile)
+{
+	struct ast_geoloc_effective_profile *eprofile;
+	const char *profile_id;
+	struct ast_geoloc_location *loc = NULL;
+
+	if (!profile) {
+		return NULL;
+	}
+	profile_id = ast_sorcery_object_get_id(profile);
+	eprofile = geoloc_effective_profile_alloc(profile_id);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	ao2_lock(profile);
+	ast_string_field_set(eprofile, location_reference, profile->location_reference);
+	eprofile->geolocation_routing = profile->geolocation_routing;
+	eprofile->pidf_section = profile->pidf_section;
+	eprofile->location_refinement = profile->location_refinement ? ast_variables_dup(profile->location_refinement) : NULL;
+	eprofile->location_variables = profile->location_variables ? ast_variables_dup(profile->location_variables) : NULL;
+	eprofile->usage_rules_vars = profile->usage_rules_vars ? ast_variables_dup(profile->usage_rules_vars) : NULL;
+	eprofile->location_disposition = profile->location_disposition;
+	eprofile->send_location = profile->send_location;
+	ao2_unlock(profile);
+
+	if (!ast_strlen_zero(eprofile->location_reference)) {
+		loc = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", eprofile->location_reference);
+		if (loc) {
+			struct ast_variable *var;
+			eprofile->format = loc->format;
+			eprofile->location_vars = ast_variables_dup(loc->location_vars);
+			ao2_ref(loc, -1);
+
+			eprofile->effective_location = ast_variables_dup(loc->location_vars);
+			for (var = profile->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);
+				}
+			}
+		} else {
+			ast_log(LOG_ERROR, "Profile '%s' referenced location '%s' does not exist!", profile_id,
+				eprofile->location_reference);
+		}
+	}
+
+	return eprofile;
+}
+
+static int geoloc_location_apply_handler(const struct ast_sorcery *sorcery, void *obj)
+{
+	struct ast_geoloc_location *location = obj;
+	const char *location_id = ast_sorcery_object_get_id(location);
+	const char *failed;
+	const char *uri;
+	enum ast_geoloc_validate_result result;
+
+	switch (location->format) {
+	case AST_GEOLOC_FORMAT_NONE:
+		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);
+		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+			ast_log(LOG_ERROR, "Location '%s' has invalid item '%s' in the location\n",
+				location_id, failed);
+			return -1;
+		}
+		break;
+	case AST_GEOLOC_FORMAT_GML:
+		result = ast_geoloc_gml_validate_varlist(location->location_vars, &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);
+			return -1;
+		}
+
+		break;
+	case AST_GEOLOC_FORMAT_URI:
+		uri = ast_variable_find_in_list(location->location_vars, "URI");
+		if (!uri) {
+			struct ast_str *str = ast_variable_list_join(location->location_vars, ",", "=", "\"", 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));
+			ast_free(str);
+			return -1;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+static int geoloc_profile_apply_handler(const struct ast_sorcery *sorcery, void *obj)
+{
+	struct ast_geoloc_profile *profile = obj;
+	struct ast_geoloc_location *location;
+	const char *profile_id = ast_sorcery_object_get_id(profile);
+	const char *failed;
+	enum ast_geoloc_validate_result result;
+
+	if (ast_strlen_zero(profile->location_reference)) {
+		if (profile->location_refinement ||
+			profile->location_variables) {
+			ast_log(LOG_ERROR, "Profile '%s' can't have location_refinement or location_variables without a location_reference",
+				profile_id);
+			return -1;
+		}
+		return 0;
+	}
+
+	location = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", profile->location_reference);
+	if (!location) {
+		ast_log(LOG_ERROR, "Profile '%s' has a location_reference '%s' that doesn't exist",
+			profile_id, profile->location_reference);
+		return -1;
+	}
+
+	if (profile->location_refinement) {
+		switch (location->format) {
+		case AST_GEOLOC_FORMAT_NONE:
+			break;
+		case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
+			result = ast_geoloc_civicaddr_validate_varlist(profile->location_refinement, &failed);
+			if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+				ast_log(LOG_ERROR, "Profile '%s' error: %s: for item '%s' in the location_refinement\n",
+					profile_id,	ast_geoloc_validate_result_to_str(result), failed);
+				ao2_ref(location, -1);
+				return -1;
+			}
+			break;
+		case AST_GEOLOC_FORMAT_GML:
+			break;
+		case AST_GEOLOC_FORMAT_URI:
+			break;
+		}
+	}
+	ao2_ref(location, -1);
+
+	return 0;
+}
+
+struct ast_sorcery *geoloc_get_sorcery(void)
+{
+	ast_sorcery_ref(geoloc_sorcery);
+	return geoloc_sorcery;
+}
+
+static char *geoloc_config_list_locations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_location *loc;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	char *format_name;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc list locations";
+		e->usage = "Usage: geoloc list locations [ like <pattern> ]\n"
+		            "      List Geolocation Location Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Location Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "location", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "location",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Location Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Location Objects:\n\n");
+
+	ast_cli(a->fd,
+		"<Object ID...................................> <Format.....> <Details.............>\n"
+		"===================================================================================\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (loc = ao2_iterator_next(&iter)); ao2_ref(loc, -1)) {
+		struct ast_str *str;
+
+		ao2_lock(loc);
+		str = ast_variable_list_join(loc->location_vars, ",", "=", "\"", NULL);
+		if (!str) {
+			ao2_unlock(loc);
+			ao2_ref(loc, -1);
+			ast_cli(a->fd, "Geolocation Location Objects: Unable to allocate temp string for '%s'\n",
+				ast_sorcery_object_get_id(loc));
+			result = CLI_FAILURE;
+			break;
+		}
+
+		format_to_str(loc, NULL, &format_name);
+		ast_cli(a->fd, "%-46.46s %-13s %-s\n",
+			ast_sorcery_object_get_id(loc),
+			format_name,
+			ast_str_buffer(str));
+		ao2_unlock(loc);
+		ast_free(str);
+		ast_free(format_name);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Location Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_list_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_profile *profile;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	char *disposition;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc list profiles";
+		e->usage = "Usage: geoloc list profiles [ like <pattern> ]\n"
+		            "      List Geolocation Profile Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "profile", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "profile",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Profile Objects:\n\n");
+
+	ast_cli(a->fd,
+		"<Object ID...................................> <Disposition> <Send> <Location Reference> \n"
+		"=========================================================================================\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (profile = ao2_iterator_next(&iter)); ao2_ref(profile, -1)) {
+		ao2_lock(profile);
+
+		location_disposition_to_str(profile, NULL, &disposition);
+		ast_cli(a->fd, "%-46.46s %-13s %-6s %-s\n",
+			ast_sorcery_object_get_id(profile),
+			disposition,
+			profile->send_location ? "yes" : "no",
+			profile->location_reference);
+		ao2_unlock(profile);
+		ast_free(disposition);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Profile Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_profile *profile;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc show profiles";
+		e->usage = "Usage: geoloc show profiles [ like <pattern> ]\n"
+		            "      List Geolocation Profile Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "profile", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "profile",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Profile Objects:\n\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (profile = ao2_iterator_next(&iter)); ) {
+		char *disposition = NULL;
+		struct ast_str *loc_str = NULL;
+		struct ast_str *refinement_str = NULL;
+		struct ast_str *variables_str = NULL;
+		struct ast_str *resolved_str = NULL;
+		struct ast_geoloc_effective_profile *eprofile = ast_geoloc_effective_profile_create(profile);
+		ao2_ref(profile, -1);
+
+		if (!ast_strlen_zero(eprofile->location_reference)) {
+			loc_str = ast_variable_list_join(eprofile->location_vars, ",", "=", "\"", 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);
+
+		ast_cli(a->fd,
+			"id:                            %-s\n"
+			"received_location_disposition: %-s\n"
+			"send_location:                 %-s\n"
+			"pidf_section:                  %-s\n"
+			"location_reference:            %-s\n"
+			"Location_format:               %-s\n"
+			"location_reference_details:    %-s\n"
+			"location_refinement:           %-s\n"
+			"location_variables:            %-s\n"
+			"effective_location:            %-s\n\n",
+			eprofile->id,
+			disposition,
+			eprofile->send_location ? "yes" : "no",
+			pidf_section_names[eprofile->pidf_section],
+			S_OR(eprofile->location_reference, "<none>"),
+			format_names[eprofile->format],
+			S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
+			S_COR(refinement_str, ast_str_buffer(refinement_str), "<none>"),
+			S_COR(variables_str, ast_str_buffer(variables_str), "<none>"),
+			S_COR(resolved_str, ast_str_buffer(resolved_str), "<none>"));
+		ao2_ref(eprofile, -1);
+
+		ast_free(disposition);
+		ast_free(loc_str);
+		ast_free(refinement_str);
+		ast_free(variables_str);
+		ast_free(resolved_str);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Profile Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_cli_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	char *result = CLI_SUCCESS;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc reload";
+		e->usage = "Usage: geoloc reload\n"
+		            "      Reload Geolocation Configuration\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 2) {
+		return CLI_SHOWUSAGE;
+	}
+
+	geoloc_config_reload();
+	ast_cli(a->fd, "Geolocation Configuration reloaded.\n");
+
+	return result;
+}
+
+static struct ast_cli_entry geoloc_location_cli_commands[] = {
+	AST_CLI_DEFINE(geoloc_config_list_locations, "List Geolocation Location Objects"),
+	AST_CLI_DEFINE(geoloc_config_list_profiles, "List Geolocation Profile Objects"),
+	AST_CLI_DEFINE(geoloc_config_show_profiles, "Show Geolocation Profile Objects"),
+	AST_CLI_DEFINE(geoloc_config_cli_reload, "Reload Geolocation Configuration"),
+};
+
+int geoloc_config_reload(void)
+{
+	if (geoloc_sorcery) {
+		ast_sorcery_reload(geoloc_sorcery);
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_config_unload(void)
+{
+	ast_cli_unregister_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
+
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+	geoloc_sorcery = NULL;
+
+	return 0;
+}
+
+int geoloc_config_load(void)
+{
+	if (!(geoloc_sorcery = ast_sorcery_open())) {
+		ast_log(LOG_ERROR, "Failed to open geolocation sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_apply_default(geoloc_sorcery, "location", "config", "geolocation.conf,criteria=type=location");
+	if (ast_sorcery_object_register(geoloc_sorcery, "location", geoloc_location_alloc, NULL, geoloc_location_apply_handler)) {
+		ast_log(LOG_ERROR, "Failed to register geoloc location object with sorcery\n");
+		ast_sorcery_unref(geoloc_sorcery);
+		geoloc_sorcery = NULL;
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	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_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)) {
+		ast_log(LOG_ERROR, "Failed to register geoloc profile object with sorcery\n");
+		ast_sorcery_unref(geoloc_sorcery);
+		geoloc_sorcery = NULL;
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_lo_section", AST_PIDF_SECTION_NONE,
+		pidf_section_handler, pidf_section_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(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);
+	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,
+		location_variables_handler, location_variables_to_str, location_variables_dup, 0, 0);
+
+	ast_sorcery_load(geoloc_sorcery);
+
+	ast_cli_register_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c
new file mode 100644
index 0000000..776441c
--- /dev/null
+++ b/res/res_geolocation/geoloc_dialplan.c
@@ -0,0 +1,38 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/res_geolocation.h"
+#include "geoloc_private.h"
+
+int geoloc_dialplan_unload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_dialplan_load(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+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
new file mode 100644
index 0000000..36bfa3c
--- /dev/null
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE docs SYSTEM "appdocsxml.dtd">
+<docs>
+	<configInfo name="res_geolocation" language="en_US">
+		<synopsis>Core Geolocation Support</synopsis>
+		<configFile name="geolocation.conf">
+			<configObject name="location">
+				<synopsis>Location</synopsis>
+				<description>
+					<para>cffdffff</para>
+				</description>
+				<configOption name="type">
+					<synopsis>Must be of type 'location'.</synopsis>
+				</configOption>
+				<configOption name="format" default="">
+					<synopsis>Location specification type</synopsis>
+					<description>
+						<enumlist>
+							<enum name="civicAddress">
+								<para>
+									The
+									<literal>location</literal>
+									parameter must contain a comma separated list of IANA codes
+									describing this location.
+								</para>
+							</enum>
+							<enum name="GML">
+								<para>
+									The
+									<literal>location</literal> parameter must contain a comma
+									separated list valid GML elements describing this location.
+								</para>
+							</enum>
+							<enum name="URI">
+								<para>
+									The
+									<literal>location</literal> parameter must contain a single
+									URI parameter which contains an external URI describing this location.
+								</para>
+							</enum>
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="location" default="">
+					<synopsis>Location Data</synopsis>
+					<description>
+						<para>The contents of this parameter are specific to the
+							specification type.</para>
+					</description>
+				</configOption>
+			</configObject>	
+			<configObject name="profile">
+				<synopsis>Profile</synopsis>
+				<description>
+					<para>cffdffff</para>
+				</description>
+				<configOption name="type">
+					<synopsis>Must be of type 'profile'.</synopsis>
+				</configOption>
+				<configOption name="pidf_lo_section" default="device">
+					<synopsis>PIDF-LO element to place this profile in</synopsis>
+					<description>
+						<enumlist>
+							<enum name="tuple" />
+							<enum name="device" />
+							<enum name="person" />
+						</enumlist>
+						<para>
+							Based on RFC5491 (see below) the recommended and default element
+							is <literal>device</literal>.
+						</para>
+					</description>
+					<see-also>
+						<ref type="link">https://www.rfc-editor.org/rfc/rfc5491.html#section-3.4</ref>
+					</see-also>
+				</configOption>
+				<configOption name="location_reference" default="">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+				<configOption name="location_refinement" default="">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+				<configOption name="location_variables" default="">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+				<configOption name="usage_rules" default="yes">
+					<synopsis>location specification type</synopsis>
+					<description>
+						<para>xxxx</para>
+					</description>
+				</configOption>
+				<configOption name="received_location_disposition" default="no">
+					<synopsis>Determine whether the location information supplied to a
+						channel should be used</synopsis>
+					<description>
+						<enumlist>
+							<enum name="discard"/>
+							<enum name="append"/>
+							<enum name="prepend"/>
+							<enum name="replace"/>
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="send_location" default="no">
+					<synopsis>Determine whether the channel will send location
+						information</synopsis>
+					<description>
+						<para>
+							For an incoming call leg, this determines if location information
+							will be forwarded to the dialplan. If <replaceable>accept_location</replaceable>
+							is set to <literal>yes</literal> and location information was actually
+							received from the client, it will be forwarded to the dialplan and any location
+							information in this profile will be ignored. If <replaceable>accept_location</replaceable>
+							is set to <literal>no</literal> or no location information was received
+							from the client, location information from this profile, if any, will be
+							forwarded to the dialplan. Dialplan functions can modify this information before
+							forwarding to the outgoing call leg.
+						</para>
+						<para>
+							For an outgoing call leg, this determines if location information
+							will be forwarded to the remote UAS. If
+							<replaceable>accept_location</replaceable>
+							is set to <literal>yes</literal> and location information was actually received from
+							the dialplan, it will be sent and any location information in this
+							profile will be ignored. If <replaceable>accept_location</replaceable>
+							is set to <literal>no</literal> or no location information was received from
+							the dialplan, location information from this profile, if any, will be sent.
+						</para>
+					</description>
+					<see-also>
+						<ref type="configOption">accept_location</ref>
+					</see-also>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+</docs>
+
diff --git a/res/res_geolocation/geoloc_gml.c b/res/res_geolocation/geoloc_gml.c
new file mode 100644
index 0000000..04e363d
--- /dev/null
+++ b/res/res_geolocation/geoloc_gml.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#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;
+	int min_required;
+	int max_allowed;
+	int (*validator)(const char *value);
+};
+
+struct geoloc_gml_shape_def {
+	const char *shape_type;
+	struct geoloc_gml_attr required_attributes[8];
+};
+
+static int pos_validator(const char *value)
+{
+	float lat;
+	float lon;
+	return (sscanf(value, "%f %f", &lat, &lon) == 2);
+}
+
+static int pos3d_validator(const char *value)
+{
+	float lat;
+	float lon;
+	float alt;
+	return (sscanf(value, "%f %f %f", &lat, &lon, &alt) == 3);
+}
+
+static int float_validator(const char *value)
+{
+	float val;
+	return (sscanf(value, "%f", &val) == 1);
+}
+
+static int uom_validator(const char *value)
+{
+	return (ast_strings_equal(value, "degrees") || ast_strings_equal(value, "radians"));
+}
+
+
+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},
+		{"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} }},
+};
+
+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
+	const char **result)
+{
+	int def_index = -1;
+	const struct ast_variable *var;
+	int i;
+	const char *shape_type = ast_variable_find_in_list(varlist, "type");
+
+	if (!shape_type) {
+		return AST_GEOLOC_VALIDATE_MISSING_TYPE;
+	}
+
+	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;
+		}
+	}
+	if (def_index < 0) {
+		return AST_GEOLOC_VALIDATE_INVALID_TYPE;
+	}
+
+	for (var = varlist; var; var = var->next) {
+		int vname_index = -1;
+		if (ast_strings_equal("type", 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;
+			}
+		}
+		if (vname_index < 0) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
+		}
+		if (!gml_shape_defs[def_index].required_attributes[vname_index].validator(var->value)) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VALUE;
+		}
+	}
+
+	for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
+		int count = 0;
+		if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
+			break;
+		}
+
+		for (var = varlist; var; var = var->next) {
+			if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, 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;
+			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;
+			return AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES;
+		}
+	}
+	return AST_GEOLOC_VALIDATE_SUCCESS;
+}
+
+static char *handle_gml_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	int i;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc show gml_shape_defs";
+		e->usage =
+			"Usage: geoloc show gml_shape_defs\n"
+			"       Show the GML Shape definitions.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, "%-16s %-32s\n", "Shape", "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);
+		for (j = 0; j < ARRAY_LEN(gml_shape_defs[i].required_attributes); j++) {
+			if (gml_shape_defs[i].required_attributes[j].attribute == 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,
+					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,
+					gml_shape_defs[i].required_attributes[j].min_required);
+			}
+		}
+		ast_cli(a->fd, "\n");
+	}
+	ast_cli(a->fd, "\n");
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry geoloc_gml_cli[] = {
+	AST_CLI_DEFINE(handle_gml_show, "Show the GML Shape definitions"),
+};
+
+int geoloc_gml_unload(void)
+{
+	ast_cli_unregister_multiple(geoloc_gml_cli, ARRAY_LEN(geoloc_gml_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_gml_load(void)
+{
+	ast_cli_register_multiple(geoloc_gml_cli, ARRAY_LEN(geoloc_gml_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_gml_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
new file mode 100644
index 0000000..194d507
--- /dev/null
+++ b/res/res_geolocation/geoloc_private.h
@@ -0,0 +1,138 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph at sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef GEOLOC_PRIVATE_H_
+#define GEOLOC_PRIVATE_H_
+
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/lock.h"
+#include "asterisk/res_geolocation.h"
+
+#define CONFIG_STR_TO_ENUM(_stem) \
+static int _stem ## _str_to_enum(const char *str) \
+{ \
+	int i; \
+	for (i = 0; i < ARRAY_LEN(_stem ## _names); i++) { \
+		if (ast_strings_equal(str, _stem ## _names[i])) { \
+			return i; \
+		} \
+	} \
+	return -1; \
+}
+
+#define CONFIG_ENUM_HANDLER(_object, _stem) \
+static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
+{ \
+	struct ast_geoloc_ ## _object *_thisobject = obj; \
+	int enumval = _stem ## _str_to_enum(var->value); \
+	if (enumval == -1) { \
+		return -1; \
+	} \
+	_thisobject->_stem = enumval; \
+	return 0; \
+}
+
+#define CONFIG_ENUM_TO_STR(_object, _stem) \
+static int _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	if (!ARRAY_IN_BOUNDS(_thisobject->_stem, _stem ## _names)) { \
+		*buf = ast_strdup("none"); \
+	} else { \
+		*buf = ast_strdup(_stem ## _names[_thisobject->_stem]); \
+	} \
+	return 0; \
+}
+
+#define CONFIG_ENUM(_object, _stem) \
+CONFIG_STR_TO_ENUM(_stem) \
+CONFIG_ENUM_HANDLER(_object, _stem) \
+CONFIG_ENUM_TO_STR(_object, _stem)
+
+#define CONFIG_VAR_LIST_HANDLER(_object, _stem) \
+static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
+{ \
+	struct ast_geoloc_ ## _object *_thisobject = obj; \
+	struct ast_variable *new_var; \
+	char *item_string, *item, *item_name, *item_value; \
+	int rc = 0;\
+	if (ast_strlen_zero(var->value)) { return 0; } \
+	item_string = ast_strdupa(var->value); \
+	while ((item = ast_strsep(&item_string, ',', AST_STRSEP_ALL))) { \
+		item_name = ast_strsep(&item, '=', AST_STRSEP_ALL); \
+		item_value = ast_strsep(&item, '=', AST_STRSEP_ALL); \
+		new_var = ast_variable_new(item_name, item_value, ""); \
+		if (!new_var) { \
+			rc = -1; \
+			break; \
+		} \
+		ast_variable_list_append(&_thisobject->_stem, new_var); \
+	} \
+	return rc; \
+}
+
+#define CONFIG_VAR_LIST_DUP(_object, _stem) \
+static int _stem ## _dup(const void *obj, struct ast_variable **fields) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	if (_thisobject->_stem) { \
+		*fields = ast_variables_dup(_thisobject->_stem); \
+	} \
+	return 0; \
+}
+
+#define CONFIG_VAR_LIST_TO_STR(_object, _stem) \
+static int _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	struct ast_str *str = ast_variable_list_join(_thisobject->_stem, ",", "=", "\"", NULL); \
+	*buf = ast_strdup(ast_str_buffer(str)); \
+	ast_free(str); \
+	return 0; \
+}
+
+#define CONFIG_VAR_LIST(_object, _stem) \
+CONFIG_VAR_LIST_HANDLER(_object, _stem) \
+CONFIG_VAR_LIST_DUP(_object, _stem) \
+CONFIG_VAR_LIST_TO_STR(_object, _stem)
+
+int geoloc_config_load(void);
+int geoloc_config_reload(void);
+int geoloc_config_unload(void);
+
+int geoloc_civicaddr_load(void);
+int geoloc_civicaddr_unload(void);
+int geoloc_civicaddr_reload(void);
+
+int geoloc_gml_unload(void);
+int geoloc_gml_load(void);
+int geoloc_gml_reload(void);
+
+int geoloc_dialplan_unload(void);
+int geoloc_dialplan_load(void);
+int geoloc_dialplan_reload(void);
+
+int geoloc_channel_unload(void);
+int geoloc_channel_load(void);
+int geoloc_channel_reload(void);
+
+struct ast_sorcery *geoloc_get_sorcery(void);
+
+#endif /* GEOLOC_PRIVATE_H_ */

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18068
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: Ieb6e3640f31a676da42d8c144ebbb31ad795d849
Gerrit-Change-Number: 18068
Gerrit-PatchSet: 13
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Michael Bradeen <mbradeen at sangoma.com>
Gerrit-CC: Benjamin Keith Ford <bford at digium.com>
Gerrit-CC: N A <mail at interlinked.x10host.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220325/f92a8061/attachment-0001.html>


More information about the asterisk-code-review mailing list