Geolocation: Core Capability Preview

This commit adds res_geolocation which creates the core capabilities
to manipulate Geolocation information on SIP INVITEs.

An upcoming commit will add res_pjsip_geolocation which will
allow the capabilities to be used with the pjsip channel driver.

This commit message is intentionally short because this isn't
a simple capability.  See the documentation at
https://wiki.asterisk.org/wiki/display/AST/Geolocation
for more information.

THE CAPABILITIES IMPLEMENTED HERE MAY CHANGE BASED ON
USER FEEDBACK!

ASTERISK-30127

Change-Id: Ibfde963121b1ecf57fd98ee7060c4f0808416303
This commit is contained in:
George Joseph
2022-02-15 06:29:50 -07:00
parent bcc18ca9f5
commit 639d72e98c
19 changed files with 4910 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
<?xml version="1.0"?>
<xsl:stylesheet version="1.1"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:gml="http://www.opengis.net/gml"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gs="http://www.opengis.net/pidflo/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- REMINDER: The "match" and "select" xpaths refer to the input document,
not the output document -->
<xsl:template match="presence">
<!-- xslt will take care of adding all of the namespace declarations
from the list above -->
<presence xmlns="urn:ietf:params:xml:ns:pidf" entity="{@entity}">
<xsl:apply-templates select="./device|tuple|person"/>
</presence>
</xsl:template>
<xsl:template match="device">
<dm:device>
<gp:geopriv>
<xsl:apply-templates select="./location-info"/>
<xsl:apply-templates select="./usage-rules"/>
<xsl:apply-templates select="./method"/>
<xsl:apply-templates select="./note-well"/>
</gp:geopriv>
<xsl:if test="./timestamp">
<dm:timestamp>
<xsl:value-of select="./timestamp"/>
</dm:timestamp>
</xsl:if>
<xsl:if test="./deviceID">
<dm:deviceID>
<xsl:value-of select="./deviceID"/>
</dm:deviceID>
</xsl:if>
</dm:device>
</xsl:template>
<xsl:template match="tuple">
<xsl:element name="tuple" namespace="urn:ietf:params:xml:ns:pidf">
<xsl:element name="status" namespace="urn:ietf:params:xml:ns:pidf">
<gp:geopriv>
<xsl:apply-templates select="./location-info"/>
<xsl:apply-templates select="./usage-rules"/>
<xsl:apply-templates select="./method"/>
<xsl:apply-templates select="./note-well"/>
</gp:geopriv>
</xsl:element>
<xsl:if test="./timestamp">
<xsl:element name="timestamp" namespace="urn:ietf:params:xml:ns:pidf">
<xsl:value-of select="./timestamp"/>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="person">
<dm:person>
<gp:geopriv>
<xsl:apply-templates select="./location-info"/>
<xsl:apply-templates select="./usage-rules"/>
<xsl:apply-templates select="./method"/>
<xsl:apply-templates select="./note-well"/>
</gp:geopriv>
<xsl:if test="./timestamp">
<dm:timestamp>
<xsl:value-of select="./timestamp"/>
</dm:timestamp>
</xsl:if>
</dm:person>
</xsl:template>
<xsl:template match="location-info">
<gp:location-info>
<xsl:apply-templates/>
</gp:location-info>
</xsl:template>
<!-- When we're using the civicAddress format, the translation is simple.
We add gp:location-info and ca:civicAddress, then we just copy in
each element, adding the "ca" namespace -->
<xsl:template match="civicAddress/*">
<xsl:element name="ca:{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="location-info/civicAddress">
<ca:civicAddress xml:lang="{@lang}">
<xsl:apply-templates/>
</ca:civicAddress>
</xsl:template>
<!-- All GML shapes share common processing for the "srsName" attribute -->
<xsl:template name="shape">
<xsl:choose>
<xsl:when test="@crs = '3d'">
<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4979</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4326</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- The GML shapes themselves. They don't all have the same namespace unfortunately... -->
<xsl:template match="Point|Circle|Ellipse|ArcBand|Sphere|Ellipsoid">
<xsl:variable name="namespace">
<xsl:choose>
<xsl:when test="name() = 'Point'">
<xsl:value-of select="'gml'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'gs'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$namespace}:{name()}">
<xsl:call-template name="shape"/>
<xsl:apply-templates select="./*"/>
</xsl:element>
</xsl:template>
<!-- ... and some are more complex than others. -->
<xsl:template match="Polygon">
<gml:Polygon>
<xsl:call-template name="shape"/>
<gml:exterior>
<gml:LinearRing>
<xsl:apply-templates select="./pos|posList"/>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</xsl:template>
<!-- Prism with a Polygon and height -->
<xsl:template match="Prism">
<gs:Prism>
<xsl:call-template name="shape"/>
<gs:base>
<gml:Polygon>
<gml:exterior>
<gml:LinearRing>
<xsl:apply-templates select="./pos|posList"/>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gs:base>
<xsl:apply-templates select="./height"/>
</gs:Prism>
</xsl:template>
<!-- method has no children so we add the "gp" namespace and copy in the value -->
<xsl:template match="method">
<gp:method>
<xsl:value-of select="."/>
</gp:method>
</xsl:template>
<!-- note-well has no children so we add the "gp" namespace and copy in the value -->
<xsl:template match="note-well">
<gp:note-well>
<xsl:value-of select="."/>
</gp:note-well>
</xsl:template>
<!-- usage-rules does have children so we add the "gp" namespace and copy in
the children, also adding the "gp" namespace -->
<xsl:template match="usage-rules">
<gp:usage-rules>
<xsl:for-each select="*">
<xsl:element name="gp:{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</gp:usage-rules>
</xsl:template>
<!-- These are the GML format primitives -->
<xsl:template name="name-value">
<xsl:element name="gml:{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template name="length">
<xsl:element name="gs:{name()}">
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9001</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template name="angle">
<xsl:element name="gs:{name()}">
<xsl:choose>
<xsl:when test="@uom = 'radians'">
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- These are the GML shape parameters -->
<xsl:template match="orientation"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="radius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="height"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="verticalAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="innerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="outerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="startAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="openingAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="pos"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template match="posList"><xsl:call-template name="name-value" /></xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,151 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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 "asterisk/xml.h"
#include "geoloc_private.h"
static const char *addr_code_name_entries[] = {
"country",
"A1",
"A2",
"A3",
"A4",
"A5",
"A6",
"ADDCODE",
"BLD",
"FLR",
"HNO",
"HNS",
"LMK",
"LOC",
"NAM",
"PC",
"PCN",
"PLC",
"POBOX",
"POD",
"POM",
"PRD",
"PRM",
"RD",
"RD",
"RDBR",
"RDSEC",
"RDSUBBR",
"ROOM",
"SEAT",
"STS",
"UNIT",
};
static int compare_civicaddr_codes(const void *_a, const void *_b)
{
/* See the man page for qsort(3) for an explanation of the casts */
int rc = strcmp(*(const char **)_a, *(const char **)_b);
return rc;
}
int ast_geoloc_civicaddr_is_code_valid(const char *code)
{
const char **entry = bsearch(&code, addr_code_name_entries, ARRAY_LEN(addr_code_name_entries),
sizeof(const char *), compare_civicaddr_codes);
return (entry != NULL);
}
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) {
int valid = ast_geoloc_civicaddr_is_code_valid(var->name);
if (!valid) {
*result = var->name;
return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
}
}
return AST_GEOLOC_VALIDATE_SUCCESS;
}
struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
const char *ref_string)
{
char *lang = NULL;
char *s = NULL;
struct ast_variable *var;
struct ast_xml_node *ca_node;
struct ast_xml_node *child_node;
int rc = 0;
SCOPE_ENTER(3, "%s", ref_string);
lang = (char *)ast_variable_find_in_list(resolved_location, "lang");
if (ast_strlen_zero(lang)) {
lang = ast_strdupa(ast_defaultlanguage);
for (s = lang; *s; s++) {
if (*s == '_') {
*s = '-';
}
}
}
ca_node = ast_xml_new_node("civicAddress");
if (!ca_node) {
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'civicAddress' XML node\n", ref_string);
}
rc = ast_xml_set_attribute(ca_node, "lang", lang);
if (rc != 0) {
ast_xml_free_node(ca_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'lang' XML attribute\n", ref_string);
}
for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
if (ast_strings_equal(var->name, "lang")) {
continue;
}
child_node = ast_xml_new_child(ca_node, var->name);
if (!child_node) {
ast_xml_free_node(ca_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
}
ast_xml_set_text(child_node, var->value);
}
SCOPE_EXIT_RTN_VALUE(ca_node, "%s: Done\n", ref_string);
}
int geoloc_civicaddr_unload(void)
{
return AST_MODULE_LOAD_SUCCESS;
}
int geoloc_civicaddr_load(void)
{
qsort(addr_code_name_entries, ARRAY_LEN(addr_code_name_entries), sizeof(const char *),
compare_civicaddr_codes);
return AST_MODULE_LOAD_SUCCESS;
}
int geoloc_civicaddr_reload(void)
{
return AST_MODULE_LOAD_SUCCESS;
}

View File

@@ -0,0 +1,36 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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 "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];
}

View File

@@ -0,0 +1,641 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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"
#define AST_API_MODULE
#include "geoloc_private.h"
static struct ast_sorcery *geoloc_sorcery;
static const char *pidf_element_names[] = {
"<none>",
"tuple",
"device",
"person"
};
static const char *format_names[] = {
"<none>",
"civicAddress",
"GML",
"URI",
};
static const char * action_names[] = {
"prefer_incoming",
"prefer_config",
"discard_incoming",
"discard_config",
};
CONFIG_ENUM(location, format)
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_info);
}
static void *geoloc_location_alloc(const char *name)
{
struct ast_geoloc_location *location = ast_sorcery_generic_alloc(sizeof(struct ast_geoloc_location), geoloc_location_destructor);
if (location) {
ast_string_field_init(location, 128);
}
return location;
}
CONFIG_ENUM(profile, pidf_element)
CONFIG_ENUM(profile, action)
CONFIG_VAR_LIST(profile, location_refinement)
CONFIG_VAR_LIST(profile, location_variables)
CONFIG_VAR_LIST(profile, usage_rules)
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);
}
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 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:
case AST_GEOLOC_FORMAT_LAST:
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_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);
return -1;
}
break;
case AST_GEOLOC_FORMAT_GML:
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);
return -1;
}
break;
case AST_GEOLOC_FORMAT_URI:
uri = ast_variable_find_in_list(location->location_info, "URI");
if (!uri) {
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));
ast_free(str);
return -1;
}
break;
}
if (!ast_strlen_zero(location->location_source)) {
struct ast_sockaddr loc_source_addr;
int rc = ast_sockaddr_parse(&loc_source_addr, location->location_source, PARSE_PORT_FORBID);
if (rc == 1) {
ast_log(LOG_ERROR, "Geolocation location '%s' location_source '%s' must be a FQDN."
" RFC8787 expressly forbids IP addresses.\n",
location_id, location->location_source);
return -1;
}
}
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:
case AST_GEOLOC_FORMAT_LAST:
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_info, ",", "=", "\"", 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 *action;
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...................................> <Profile Action> <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);
action_to_str(profile, NULL, &action);
ast_cli(a->fd, "%-46.46s %-16s %-s\n",
ast_sorcery_object_get_id(profile),
action,
profile->location_reference);
ao2_unlock(profile);
ast_free(action);
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;
}
/* Create an empty rb-tree container which always sorts its contents. */
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 an unsorted list of profile parameters */
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);
}
/* Copy the unsorted parameters into the rb-tree container which will sort them automatically. */
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 *action = 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_str *usage_rules_str = NULL;
struct ast_geoloc_eprofile *eprofile = ast_geoloc_eprofile_create_from_profile(profile);
ao2_ref(profile, -1);
if (!ast_strlen_zero(eprofile->location_reference)) {
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);
usage_rules_str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "\"", NULL);
action_to_str(eprofile, NULL, &action);
ast_cli(a->fd,
"id: %-s\n"
"profile_action: %-s\n"
"pidf_element: %-s\n"
"location_reference: %-s\n"
"Location_format: %-s\n"
"location_details: %-s\n"
"location_method: %-s\n"
"location_refinement: %-s\n"
"location_variables: %-s\n"
"effective_location: %-s\n"
"usage_rules: %-s\n"
"notes: %-s\n",
eprofile->id,
action,
pidf_element_names[eprofile->pidf_element],
S_OR(eprofile->location_reference, "<none>"),
format_names[eprofile->format],
S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
S_OR(eprofile->method, "<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>"),
S_COR(usage_rules_str, ast_str_buffer(usage_rules_str), "<none>"),
S_OR(eprofile->notes, "<none>")
);
ao2_ref(eprofile, -1);
ast_free(action);
ast_free(loc_str);
ast_free(refinement_str);
ast_free(variables_str);
ast_free(resolved_str);
ast_free(usage_rules_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"),
};
struct ast_geoloc_location * AST_OPTIONAL_API_NAME(ast_geoloc_get_location)(const char *id)
{
if (ast_strlen_zero(id)) {
return NULL;
}
return ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", id);
}
struct ast_geoloc_profile * AST_OPTIONAL_API_NAME(ast_geoloc_get_profile)(const char *id)
{
if (ast_strlen_zero(id)) {
return NULL;
}
return ast_sorcery_retrieve_by_id(geoloc_sorcery, "profile", id);
}
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));
ast_sorcery_object_unregister(geoloc_sorcery, "profile");
ast_sorcery_object_unregister(geoloc_sorcery, "location");
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_info", NULL,
location_info_handler, location_info_to_str, location_info_dup, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "location", "location_source", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_location, location_source));
ast_sorcery_object_field_register(geoloc_sorcery, "location", "method", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_location, method));
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_element",
pidf_element_names[AST_PIDF_ELEMENT_DEVICE], 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", "profile_action", "discard_incoming",
action_handler, action_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "usage_rules", NULL,
usage_rules_handler, usage_rules_to_str, usage_rules_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info_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_object_field_register(geoloc_sorcery, "profile", "notes", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, notes));
ast_sorcery_load(geoloc_sorcery);
ast_cli_register_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
return AST_MODULE_LOAD_SUCCESS;
}
int AST_OPTIONAL_API_NAME(ast_geoloc_is_loaded)(void)
{
return 1;
}

View File

@@ -0,0 +1,325 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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/astobj2.h"
#include "asterisk/datastore.h"
#include "asterisk/channel.h"
#include "asterisk/res_geolocation.h"
#include "asterisk/vector.h"
#include "geoloc_private.h"
#define GEOLOC_DS_TYPE "geoloc_eprofiles"
struct ast_sorcery *geoloc_sorcery;
struct eprofiles_datastore {
const char *id;
AST_VECTOR(geoloc_eprofiles, struct ast_geoloc_eprofile *) eprofiles;
};
static void geoloc_datastore_free(void *obj)
{
struct eprofiles_datastore *eds = obj;
AST_VECTOR_RESET(&eds->eprofiles, ao2_cleanup);
AST_VECTOR_FREE(&eds->eprofiles);
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++) {
struct ast_geoloc_eprofile *ep = AST_VECTOR_GET(&in_eds->eprofiles, i);
rc = AST_VECTOR_APPEND(&out_eds->eprofiles, ao2_bump(ep));
if (rc != 0) {
/* This will clean up the bumped reference to the eprofile */
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,
.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;
struct eprofiles_datastore *eds = NULL;
int rc = 0;
if (ast_strlen_zero(id)) {
ast_log(LOG_ERROR, "A geoloc datastore can't be allocated with a NULL or empty id\n");
return NULL;
}
ds = ast_datastore_alloc(&geoloc_datastore_info, NULL);
if (!ds) {
ast_log(LOG_ERROR, "Geoloc datastore '%s' couldn't be allocated\n", id);
return NULL;
}
eds = ast_calloc(1, sizeof(*eds));
if (!eds) {
ast_datastore_free(ds);
ast_log(LOG_ERROR, "Private structure for geoloc datastore '%s' couldn't be allocated\n", id);
return NULL;
}
ds->data = eds;
rc = AST_VECTOR_INIT(&eds->eprofiles, 2);
if (rc != 0) {
ast_datastore_free(ds);
ast_log(LOG_ERROR, "Vector for geoloc datastore '%s' couldn't be initialized\n", id);
return NULL;
}
return ds;
}
int ast_geoloc_datastore_add_eprofile(struct ast_datastore *ds,
struct ast_geoloc_eprofile *eprofile)
{
struct eprofiles_datastore *eds = NULL;
int rc = 0;
if (!IS_GEOLOC_DS(ds) || !eprofile) {
return -1;
}
eds = ds->data;
rc = AST_VECTOR_APPEND(&eds->eprofiles, ao2_bump(eprofile));
if (rc != 0) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "Couldn't add eprofile '%s' to geoloc datastore '%s'\n", eprofile->id, eds->id);
return -1;
}
return AST_VECTOR_SIZE(&eds->eprofiles);
}
int ast_geoloc_datastore_insert_eprofile(struct ast_datastore *ds,
struct ast_geoloc_eprofile *eprofile, int index)
{
struct eprofiles_datastore *eds = NULL;
int rc = 0;
if (!IS_GEOLOC_DS(ds) || !eprofile) {
return -1;
}
eds = ds->data;
rc = AST_VECTOR_INSERT_AT(&eds->eprofiles, index, ao2_bump(eprofile));
if (rc != 0) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "Couldn't add eprofile '%s' to geoloc datastore '%s' in position '%d'\n",
eprofile->id, eds->id, index);
return -1;
}
return AST_VECTOR_SIZE(&eds->eprofiles);
}
int ast_geoloc_datastore_size(struct ast_datastore *ds)
{
struct eprofiles_datastore *eds = NULL;
if (!IS_GEOLOC_DS(ds)) {
return -1;
}
eds = ds->data;
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 (!IS_GEOLOC_DS(ds)) {
return NULL;
}
eds = ds->data;
if (ix >= AST_VECTOR_SIZE(&eds->eprofiles)) {
return NULL;
}
eprofile = AST_VECTOR_GET(&eds->eprofiles, ix);
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;
}
ao2_ref(AST_VECTOR_REMOVE(&eds->eprofiles, ix, 1), -1);
return 0;
}
struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
struct ast_geoloc_eprofile *eprofile)
{
struct ast_datastore *ds;
int rc = 0;
if (!eprofile) {
return NULL;
}
ds = ast_geoloc_datastore_create(eprofile->id);
if (!ds) {
return NULL;
}
rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
if (rc != 0) {
ast_datastore_free(ds);
ds = NULL;
}
return ds;
}
struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name)
{
struct ast_datastore *ds = NULL;
struct ast_geoloc_eprofile *eprofile = NULL;
struct ast_geoloc_profile *profile = NULL;
int rc = 0;
if (ast_strlen_zero(profile_name)) {
return NULL;
}
profile = ast_sorcery_retrieve_by_id(geoloc_sorcery, "profile", profile_name);
if (!profile) {
ast_log(LOG_ERROR, "A profile with the name '%s' was not found\n", profile_name);
return NULL;
}
ds = ast_geoloc_datastore_create(profile_name);
if (!ds) {
ast_log(LOG_ERROR, "A datastore couldn't be allocated for profile '%s'\n", profile_name);
ao2_ref(profile, -1);
return NULL;
}
eprofile = ast_geoloc_eprofile_create_from_profile(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\n", profile_name);
return NULL;
}
rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
ao2_ref(eprofile, -1);
if (rc != 0) {
ast_datastore_free(ds);
ds = NULL;
}
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;
}

View File

@@ -0,0 +1,457 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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/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, "profile_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_source")) {
ast_str_append(buf, len, "%s", eprofile->location_source);
} 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_info_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_quoted_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, "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, "profile_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_source")) {
ast_string_field_set(eprofile, location_source, value);
} else if (ast_strings_equal(args.field, "location_info_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,
};
#define profile_create "GeolocProfileCreate"
static int geoloc_eprofile_create(struct ast_channel *chan, const char *data)
{
char *parsed_data = ast_strdupa(data);
struct ast_datastore *ds;
struct ast_geoloc_eprofile * eprofile;
int profile_count = 0;
int index = -1;
int rc = 0;
struct ast_str *new_size;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(id);
AST_APP_ARG(index);
);
/* Check for zero arguments */
if (ast_strlen_zero(parsed_data)) {
ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", profile_create);
return -1;
}
AST_STANDARD_APP_ARGS(args, parsed_data);
if (ast_strlen_zero(args.id)) {
ast_log(LOG_ERROR, "%s: Cannot call without an id field\n", profile_create);
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", profile_create, args.index);
return -1;
}
} else {
index = -1;
}
ds = ast_geoloc_datastore_find(chan);
if (!ds) {
ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", profile_create);
return -1;
}
profile_count = ast_geoloc_datastore_size(ds);
if (index < -1 || index >= profile_count) {
ast_log(LOG_ERROR, "%s: Invalid insert_before index '%d'. It must be 0 to insert at the beginning of the list or -1 to append to the end of the list\n", profile_create, index);
return -1;
}
eprofile = ast_geoloc_eprofile_alloc(args.id);
if (!eprofile) {
ast_log(LOG_ERROR, "%s: Could not allocate eprofile '%s'\n", profile_create, args.id);
return -1;
}
ds = ast_geoloc_datastore_find(chan);
if (!ds) {
ds = ast_geoloc_datastore_create_from_eprofile(eprofile);
if (!ds) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "%s: Could not create datastore for eprofile '%s'\n", profile_create, args.id);
return -1;
}
rc = 1;
ast_channel_datastore_add(chan, ds);
} else if (index < 0) {
rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
if (rc <= 0) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "%s: Could not add eprofile '%s' to datastore\n", profile_create, args.id);
return -1;
}
} else {
rc = ast_geoloc_datastore_insert_eprofile(ds, eprofile, index);
if (rc <= 0) {
ao2_ref(eprofile, -1);
ast_log(LOG_ERROR, "%s: Could not insert eprofile '%s' to datastore\n", profile_create, args.id);
return -1;
}
}
new_size = ast_str_alloca(16);
ast_str_append(&new_size, 0, "%d", rc);
pbx_builtin_setvar_helper(chan, "GEOLOC_PROFILE_COUNT", ast_str_buffer(new_size));
return 0;
}
#define profile_delete "GeolocProfileDelete"
static int geoloc_eprofile_delete(struct ast_channel *chan, const char *data)
{
char *parsed_data = ast_strdupa(data);
struct ast_datastore *ds;
int profile_count = 0;
int index = -1;
struct ast_str *new_size;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(index);
);
/* Check for zero arguments */
if (ast_strlen_zero(parsed_data)) {
ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", profile_delete);
return -1;
}
AST_STANDARD_APP_ARGS(args, parsed_data);
if (!ast_strlen_zero(args.index)) {
if (sscanf(args.index, "%30d", &index) != 1) {
ast_log(LOG_ERROR, "%s: profile_index '%s' is invalid\n", profile_delete, args.index);
return -1;
}
} else {
ast_log(LOG_ERROR, "%s: A profile_index is required\n", profile_delete);
return -1;
}
ds = ast_geoloc_datastore_find(chan);
if (!ds) {
ast_log(LOG_WARNING, "%s: There are no geoloc profiles on this channel\n", profile_delete);
return -1;
}
profile_count = ast_geoloc_datastore_size(ds);
if (index < -1 || index >= profile_count) {
ast_log(LOG_ERROR, "%s: Invalid profile_index '%d'. It must be between 0 and %d\n",
profile_create, index, profile_count - 1);
return -1;
}
ast_geoloc_datastore_delete_eprofile(ds, index);
profile_count = ast_geoloc_datastore_size(ds);
new_size = ast_str_alloca(16);
ast_str_append(&new_size, 0, "%d", profile_count);
pbx_builtin_setvar_helper(chan, "GEOLOC_PROFILE_COUNT", ast_str_buffer(new_size));
return 0;
}
int geoloc_dialplan_unload(void)
{
ast_unregister_application(profile_delete);
ast_unregister_application(profile_create);
ast_custom_function_unregister(&geoloc_function);
return AST_MODULE_LOAD_SUCCESS;
}
int geoloc_dialplan_load(void)
{
int res = 0;
res = ast_custom_function_register(&geoloc_function);
if (res == 0) {
res = ast_register_application_xml(profile_create, geoloc_eprofile_create);
}
if (res == 0) {
res = ast_register_application_xml(profile_delete, geoloc_eprofile_delete);
}
return res == 0 ? AST_MODULE_LOAD_SUCCESS : AST_MODULE_LOAD_DECLINE;
}
int geoloc_dialplan_reload(void)
{
return AST_MODULE_LOAD_SUCCESS;
}

View File

@@ -0,0 +1,235 @@
<?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_info</literal>
parameter must contain a comma separated list of IANA codes
or synonyms describing the civicAddress of this location.
The IANA codes and synonyms can be obtained by executing
the CLI command <literal>geoloc show civicAddr_mapping</literal>.
</para>
</enum>
<enum name="GML">
<para>
The
<literal>location_info</literal> parameter must contain a comma
separated list valid GML elements describing this location.
</para>
</enum>
<enum name="URI">
<para>
The
<literal>location_info</literal> parameter must contain a single
URI parameter which contains an external URI describing this location.
</para>
</enum>
</enumlist>
</description>
</configOption>
<configOption name="location_info" default="">
<synopsis>Location information</synopsis>
<description>
<para>The contents of this parameter are specific to the
location <literal>format</literal>.</para>
<enumlist>
<enum name="civicAddress">
<para>
location_info = country=US,A1="New York",city_district=Manhattan,
A3="New York", house_number=1633, street=46th, street_suffix = Street,
postal_code=10222,floor=20,room=20A2
</para>
</enum>
<enum name="GML">
<para>
location_info = Shape=Sphere, pos3d="39.12345 -105.98766 1920", radius=200
</para>
</enum>
<enum name="URI">
<para>
location_info = URI=https:/something.com?exten=${EXTEN}
</para>
</enum>
</enumlist>
</description>
</configOption>
<configOption name="location_source" default="">
<synopsis>Fully qualified host name</synopsis>
<description>
<para>This parameter isn't required but if provided, RFC8787 says it MUST be a fully
qualified host name. IP addresses are specifically NOT allowed. The value will be placed
in a <literal>loc-src</literal> parameter appended to the URI in the <literal>
Geolocation</literal> header.</para>
</description>
</configOption>
<configOption name="method" default="">
<synopsis>Location determination method</synopsis>
<description>
<para>This is a rarely used field in the specification that would
indicate the method used to determine the location. Its usage and values should be
pre-negotiated with any recipients.</para>
<enumlist>
<enum name="GPS"/>
<enum name="A-GPS"/>
<enum name="Manual"/>
<enum name="DHCP"/>
<enum name="Triangulation"/>
<enum name="Cell"/>
<enum name="802.11"/>
</enumlist>
</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_element" 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_info_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="notes" default="">
<synopsis>Notes to be added to the outgoing PIDF-LO document</synopsis>
<description>
<para>The specification of this parameter will cause a
<literal>&lt;note-well&gt;</literal> element to be added to the
outgoing PIDF-LO document. Its usage should be pre-negotiated with
any recipients.</para>
</description>
</configOption>
<configOption name="profile_action" default="discard_incoming">
<synopsis>Determine which profile on a channel should be used</synopsis>
<description>
<enumlist>
<enum name="prefer_incoming">
<para>Use the incoming profile if it exists and has location information, otherwise use the
configured profile if it exists and has location information. If neither profile has location
information, nothing is sent.
</para></enum>
<enum name="prefer_config">
<para>Use the configured profile if it exists and has location information, otherwise use the
incoming profile if it exists and has location information. If neither profile has location
information, nothing is sent.
</para></enum>
<enum name="discard_incoming"
><para>Discard any incoming profile and use the configured profile if it exists and
it has location information. If the configured profile doesn't exist or has no
location information, nothing is sent.
</para></enum>
<enum name="discard_config">
<para>Discard any configured profile and use the incoming profile if it exists and
it has location information. If the incoming profile doesn't exist or has no
location information, nothing is sent.
</para></enum>
</enumlist>
</description>
</configOption>
</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="profile_index" required="false">
<para>The index of the profile to operate on. Not required for the special fields.</para>
</parameter>
</syntax>
</function>
<application name="GeolocProfileCreate" language="en_US">
<synopsis>
Create a new, empty Geolocation Profile on a channel
</synopsis>
<syntax>
<parameter name="id" required="true"><para>
The id of the new profile.
</para></parameter>
<parameter name="profile_index" required="false"><para>
The position at which to insert the new eprofile.
Existing profiles will be moved forward to make room.
Leave empty to append to the end of the list.
</para></parameter>
</syntax>
<description>
<para>This application adds a new, empty Geolocation Profile to a channel.</para>
<para>The following variable is set:</para>
<variablelist>
<variable name="GEOLOC_PROFILE_COUNT">
<para>The number of profiles on the channel after the new one is created</para>
</variable>
</variablelist>
</description>
</application>
<application name="GeolocProfileDelete" language="en_US">
<synopsis>
Delete a Geolocation Profile from a channel
</synopsis>
<syntax>
<parameter name="profile_index" required="true"><para>
The position of the profile to be deleted
Existing profiles will be moved back.
</para></parameter>
</syntax>
<description>
<para>This application deletes a Geolocation Profile from a channel.</para>
<para>The following variable is set:</para>
<variablelist>
<variable name="GEOLOC_PROFILE_COUNT">
<para>The number of profiles left on the channel after the delete.</para>
</variable>
</variablelist>
</description>
</application>
</docs>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,362 @@
/*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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, "shape");
if (!shape_type) {
return AST_GEOLOC_VALIDATE_MISSING_SHAPE;
}
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_SHAPE;
}
for (var = varlist; var; var = var->next) {
int vname_index = -1;
if (ast_strings_equal("shape", var->name)) {
continue;
}
for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
break;
}
if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, var->name)) {
vname_index = i;
break;
}
}
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"),
};
struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
const char *ref_string)
{
const char *shape;
char *crs;
struct ast_variable *var;
struct ast_xml_node *gml_node;
struct ast_xml_node *child_node;
int rc = 0;
SCOPE_ENTER(3, "%s", ref_string);
shape = ast_variable_find_in_list(resolved_location, "shape");
if (ast_strlen_zero(shape)) {
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: There's no 'shape' parameter\n",
ref_string);
}
crs = (char *)ast_variable_find_in_list(resolved_location, "crs");
if (ast_strlen_zero(crs)) {
crs = "2d";
}
gml_node = ast_xml_new_node(shape);
if (!gml_node) {
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", shape, ref_string);
}
rc = ast_xml_set_attribute(gml_node, "crs", crs);
if (rc != 0) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'crs' XML attribute\n", ref_string);
}
for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
RAII_VAR(char *, value, NULL, ast_free);
char *uom = NULL;
if (ast_strings_equal(var->name, "shape") || ast_strings_equal(var->name, "crs")) {
continue;
}
value = ast_strdup(var->value);
if (ast_strings_equal(var->name, "orientation") || ast_strings_equal(var->name, "startAngle")
|| ast_strings_equal(var->name, "openingAngle")) {
char *a = NULL;
char *junk = NULL;
float angle;
uom = value;
/* 'a' should now be the angle and 'uom' should be the uom */
a = strsep(&uom, " ");
angle = strtof(a, &junk);
/*
* strtof sets junk to the first non-valid character so if it's
* not empty after the conversion, there were unrecognized
* characters in the angle. It'll point to the NULL terminator
* if angle was completely converted.
*/
if (!ast_strlen_zero(junk)) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: The angle portion of parameter '%s' ('%s') is malformed\n",
ref_string, var->name, var->value);
}
if (ast_strlen_zero(uom)) {
uom = "degrees";
}
if (ast_begins_with(uom, "deg")) {
if (angle > 360.0) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
"Degrees can't be > 360.0\n",
ref_string, var->name, var->value);
}
} else if (ast_begins_with(uom, "rad")) {
if(angle > 100.0) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
"Radians can't be > 100.0\n",
ref_string, var->name, var->value);
}
} else {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
"The unit of measure must be 'deg[rees]' or 'rad[ians]'\n",
ref_string, var->name, var->value);
}
}
child_node = ast_xml_new_child(gml_node, var->name);
if (!child_node) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
}
if (!ast_strlen_zero(uom)) {
rc = ast_xml_set_attribute(child_node, "uom", uom);
if (rc != 0) {
ast_xml_free_node(gml_node);
SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'uom' XML attribute\n", ref_string);
}
}
ast_xml_set_text(child_node, value);
}
SCOPE_EXIT_RTN_VALUE(gml_node, "%s: Done\n", ref_string);
}
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;
}

View File

@@ -0,0 +1,168 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2022, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@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_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(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(action);
#define CONFIG_STR_TO_ENUM(_stem) \
int geoloc_ ## _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 = geoloc_ ## _stem ## _str_to_enum(var->value); \
if (enumval == -1) { \
return -1; \
} \
_thisobject->_stem = enumval; \
return 0; \
}
#define GEOLOC_ENUM_TO_NAME(_stem) \
const char * geoloc_ ## _stem ## _to_name(int ix) \
{ \
if (!ARRAY_IN_BOUNDS(ix, _stem ## _names)) { \
return "none"; \
} else { \
return _stem ## _names[ix]; \
} \
}
#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) \
GEOLOC_ENUM_TO_NAME(_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);
struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
const char *ref_string);
int geoloc_civicaddr_load(void);
int geoloc_civicaddr_unload(void);
int geoloc_civicaddr_reload(void);
struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
const char *ref_string);
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);
int geoloc_eprofile_unload(void);
int geoloc_eprofile_load(void);
int geoloc_eprofile_reload(void);
struct ast_sorcery *geoloc_get_sorcery(void);
#endif /* GEOLOC_PRIVATE_H_ */

View File

@@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8"?>
<presence entity="pres:alice@asterisk.org"
xmlns="urn:ietf:params:xml:ns:pidf"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:gml="http://www.opengis.net/gml"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gs="http://www.opengis.net/pidflo/1.0">
<tuple id="point-2d">
<status>
<gp:geopriv>
<gp:location-info>
<gml:Point srsName="urn:ogc:def:crs:EPSG::4326">
<gml:pos>-34.410649 150.87651</gml:pos>
</gml:Point>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>Manual</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<dm:person id="point-3d">
<gp:geopriv>
<gp:location-info>
<gml:Point srsName="urn:ogc:def:crs:EPSG::4979">
<gml:pos>-34.410649 150.87651 1800</gml:pos>
</gml:Point>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>802.11</gp:method>
</gp:geopriv>
<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
</dm:person>
<tuple id="circle-2d">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Circle srsName="urn:ogc:def:crs:EPSG::4326">
<gml:pos>-34.410649 150.87651</gml:pos>
<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
</gs:Circle>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>802.11</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<tuple id="circle-3d">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Circle srsName="urn:ogc:def:crs:EPSG::4979">
<gml:pos>-34.410649 150.87651 1800</gml:pos>
<gs:radius uom="urn:ogc:def:uom:EPSG::9001">30</gs:radius>
</gs:Circle>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>802.11</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<dm:person id="polygon-2d">
<gp:geopriv>
<gp:location-info>
<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
<gml:exterior>
<gml:LinearRing>
<gml:pos>43.311 -73.422</gml:pos>
<gml:pos>43.111 -73.322</gml:pos>
<gml:pos>43.111 -73.222</gml:pos>
<gml:pos>43.311 -73.122</gml:pos>
<gml:pos>43.411 -73.222</gml:pos>
<gml:pos>43.411 -73.322</gml:pos>
<gml:pos>43.311 -73.422</gml:pos>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>802.11</gp:method>
</gp:geopriv>
<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
</dm:person>
<dm:person id="polygon-3d">
<gp:geopriv>
<gp:location-info>
<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
<gml:exterior>
<gml:LinearRing>
<gml:pos>43.311 -73.422 1800</gml:pos>
<gml:pos>43.111 -73.322 1800</gml:pos>
<gml:pos>43.111 -73.222 1800</gml:pos>
<gml:pos>43.311 -73.122 1800</gml:pos>
<gml:pos>43.411 -73.222 1800</gml:pos>
<gml:pos>43.411 -73.322 1800</gml:pos>
<gml:pos>43.311 -73.422 1800</gml:pos>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>802.11</gp:method>
</gp:geopriv>
<dm:timestamp>2007-06-24T12:28:04Z</dm:timestamp>
</dm:person>
<tuple id="polygon-poslist-2d">
<status>
<gp:geopriv>
<gp:location-info>
<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4326">
<gml:exterior>
<gml:LinearRing>
<gml:posList>
43.311 -73.422 43.111 -73.322
43.111 -73.222 43.311 -73.122
43.411 -73.222 43.411 -73.322
43.311 -73.422
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>Wiremap</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<tuple id="polygon-poslist-3d">
<status>
<gp:geopriv>
<gp:location-info>
<gml:Polygon srsName="urn:ogc:def:crs:EPSG::4979">
<gml:exterior>
<gml:LinearRing>
<gml:posList>
43.311 -73.422 1800 43.111 -73.322 1800
43.111 -73.222 1800 43.311 -73.122 1800
43.411 -73.222 1800 43.411 -73.322 1800
43.311 -73.422 1800
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>Wiremap</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<tuple id="ellipse-2d">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Ellipse srsName="urn:ogc:def:crs:EPSG::4326">
<gml:pos>42.5463 -73.2512</gml:pos>
<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">1275</gs:semiMajorAxis>
<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">670</gs:semiMinorAxis>
<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">43.2</gs:orientation>
</gs:Ellipse>
</gp:location-info>
<gp:usage-rules/>
<gp:method>Device-Assisted_A-GPS</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<dm:device id="arcband-2d">
<gp:geopriv>
<gp:location-info>
<gs:ArcBand srsName="urn:ogc:def:crs:EPSG::4326">
<gml:pos>-43.5723 153.21760</gml:pos>
<gs:innerRadius uom="urn:ogc:def:uom:EPSG::9001">3594</gs:innerRadius>
<gs:outerRadius uom="urn:ogc:def:uom:EPSG::9001">4148</gs:outerRadius>
<gs:startAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:startAngle>
<gs:openingAngle uom="urn:ogc:def:uom:EPSG::9102">20</gs:openingAngle>
</gs:ArcBand>
</gp:location-info>
<gp:usage-rules>
<gp:retransmission-allowed>yes</gp:retransmission-allowed>
<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
</gp:usage-rules>
<gp:method>TA-NMR</gp:method>
</gp:geopriv>
<dm:deviceID>mac:1234567890ab</dm:deviceID>
<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
</dm:device>
<tuple id="sphere">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Sphere srsName="urn:ogc:def:crs:EPSG::4979">
<gml:pos>42.5463 -73.2512 26.3</gml:pos>
<gs:radius uom="urn:ogc:def:uom:EPSG::9001">850.24</gs:radius>
</gs:Sphere>
</gp:location-info>
<gp:usage-rules>
<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
</gp:usage-rules>
<gp:method>Device-Based_A-GPS</gp:method>
</gp:geopriv>
</status>
</tuple>
<tuple id="ellipsoid">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Ellipsoid srsName="urn:ogc:def:crs:EPSG::4979">
<gml:pos>42.5463 -73.2512 26.3</gml:pos>
<gs:semiMajorAxis uom="urn:ogc:def:uom:EPSG::9001">7.7156</gs:semiMajorAxis>
<gs:semiMinorAxis uom="urn:ogc:def:uom:EPSG::9001">3.31</gs:semiMinorAxis>
<gs:verticalAxis uom="urn:ogc:def:uom:EPSG::9001">28.7</gs:verticalAxis>
<gs:orientation uom="urn:ogc:def:uom:EPSG::9102">90</gs:orientation>
</gs:Ellipsoid>
</gp:location-info>
<gp:usage-rules/>
<gp:method>Hybrid_A-GPS</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<tuple id="prism">
<status>
<gp:geopriv>
<gp:location-info>
<gs:Prism srsName="urn:ogc:def:crs:EPSG::4979">
<gs:base>
<gml:Polygon>
<gml:exterior>
<gml:LinearRing>
<gml:posList>
42.556844 -73.248157 36.6 <!--A -->
42.656844 -73.248157 36.6 <!--B -->
42.656844 -73.348157 36.6 <!--C -->
42.556844 -73.348157 36.6 <!--D -->
42.556844 -73.248157 36.6 <!--A -->
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gs:base>
<gs:height uom="urn:ogc:def:uom:EPSG::9001">2.4</gs:height>
</gs:Prism>
</gp:location-info>
<gp:usage-rules/>
<gp:method>Wiremap</gp:method>
</gp:geopriv>
</status>
<timestamp>2007-06-22T20:57:29Z</timestamp>
</tuple>
<dm:device>
<gp:geopriv>
<gp:location-info>
<ca:civicAddress xml:lang="en-AU">
<ca:country>AU</ca:country>
<ca:A1>NSW</ca:A1>
<ca:A3>Wollongong</ca:A3>
<ca:A4>North Wollongong</ca:A4>
<ca:RD>Flinders</ca:RD>
<ca:STS>Street</ca:STS>
<ca:RDBR>Campbell Street</ca:RDBR>
<ca:LMK>Gilligan's Island</ca:LMK>
<ca:LOC>Corner</ca:LOC>
<ca:NAM> Video Rental Store </ca:NAM>
<ca:PC>2500</ca:PC>
<ca:ROOM> Westerns and Classics </ca:ROOM>
<ca:PLC>store</ca:PLC>
<ca:POBOX>Private Box 15</ca:POBOX>
</ca:civicAddress>
</gp:location-info>
<gp:usage-rules>
<gp:retransmission-allowed>yes</gp:retransmission-allowed>
<gp:ruleset-preference>https:/www/more.com</gp:ruleset-preference>
<gp:retention-expires>2007-06-22T20:57:29Z</gp:retention-expires>
</gp:usage-rules>
<gp:method>GPS</gp:method>
</gp:geopriv>
<dm:deviceID>mac:1234567890ab</dm:deviceID>
<dm:timestamp>2007-06-22T20:57:29Z</dm:timestamp>
</dm:device>
</presence>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:def="urn:ietf:params:xml:ns:pidf"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:gml="http://www.opengis.net/gml"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gs="http://www.opengis.net/pidflo/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--
The whole purpose of this stylesheet is to convert a PIDF-LO document into a simple,
common XML document that is easily parsable by geoloc_eprofile into an eprofile.
For example:
<presence>
<device>
<location-info format="GML">shape="Point", crs="2d", pos="38.456 -105.678"</location-info>
<usage-rules>retransmission-allowed=no</usage-rules>
<method>GPS</method>
</device>
</presence>
WARNING: Don't mess with this stylesheet before brushing up your
XPath and XSLT expertise.
-->
<!--
All of the namespaces that could be in the incoming PIDF-LO document
have to be declared above. All matching is done based on the URI, not
the prefix so we can use whatever prefixes we want. For instance,
even if "urn:ietf:params:xml:ns:pidf:data-model" were declared with
the "pdm" prefix in the incoming document and with "dm" here,
"dm:device" would match "pdm:device" in the document.
-->
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="path"/>
<!--
Even though the "presence", "tuple", and "status" elements won't have namespaces in the
incoming PIDF document, we have to use the pseudo-namespace "def" here because of namespace
processing quirks in libxml2 and libxslt.
We don't use namespace prefixes in the output document at all.
-->
<xsl:template match="/def:presence">
<xsl:element name="presence">
<xsl:attribute name="entity"><xsl:value-of select="@entity"/></xsl:attribute>
<xsl:apply-templates select="$path"/>
</xsl:element>
</xsl:template>
<xsl:template match="dm:device">
<xsl:element name="device">
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:apply-templates select=".//gp:location-info"/>
<xsl:apply-templates select=".//gp:usage-rules"/>
<xsl:apply-templates select=".//gp:method"/>
<xsl:apply-templates select=".//gp:note-well"/>
<xsl:if test="./dm:timestamp">
<timestamp>
<xsl:value-of select="./dm:timestamp"/>
</timestamp>
</xsl:if>
<xsl:if test="./dm:deviceID">
<deviceID>
<xsl:value-of select="./dm:deviceID"/>
</deviceID>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="def:tuple">
<xsl:element name="tuple">
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:apply-templates select=".//gp:location-info"/>
<xsl:apply-templates select=".//gp:usage-rules"/>
<xsl:apply-templates select=".//gp:method"/>
<xsl:apply-templates select=".//gp:note-well"/>
<xsl:if test="./timestamp">
<timestamp>
<xsl:value-of select="./timestamp"/>
</timestamp>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="dm:person">
<xsl:element name="person">
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
<xsl:apply-templates select=".//gp:location-info"/>
<xsl:apply-templates select=".//gp:usage-rules"/>
<xsl:apply-templates select=".//gp:method"/>
<xsl:apply-templates select=".//gp:note-well"/>
<xsl:if test="./dm:timestamp">
<timestamp>
<xsl:value-of select="./dm:timestamp"/>
</timestamp>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="gp:location-info/gml:*">
<xsl:element name="location-info">
<xsl:attribute name="format">gml</xsl:attribute>
<xsl:call-template name="shape" />
</xsl:element>
</xsl:template>
<xsl:template match="gp:location-info/gs:*">
<xsl:element name="location-info">
<xsl:attribute name="format">gml</xsl:attribute>
<xsl:call-template name="shape" />
</xsl:element>
</xsl:template>
<xsl:template match="gp:location-info/ca:civicAddress">
<xsl:element name="location-info">
<xsl:attribute name="format">civicAddress</xsl:attribute>
<xsl:call-template name="civicAddress" />
</xsl:element>
</xsl:template>
<!--
All of the "following-sibling" things just stick a comma after the value if there's another
element after it. The result should be...
name1="value1", name2="value2"
-->
<xsl:template name="name-value">
<xsl:element name="{local-name(.)}">
<xsl:value-of select="normalize-space(.)"/>
</xsl:element>
</xsl:template>
<xsl:template name="length"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template name="angle">
<xsl:element name="{local-name(.)}">
<xsl:choose>
<xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'">
<xsl:attribute name="uom">radians</xsl:attribute></xsl:when>
<xsl:otherwise>
<xsl:attribute name="uom">degrees</xsl:attribute></xsl:otherwise>
</xsl:choose>
<xsl:value-of select="normalize-space(.)"/>
</xsl:element>
</xsl:template>
<xsl:template match="gs:orientation"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="gs:radius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:height"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:verticalAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:innerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:outerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="gs:startAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="gs:openingAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="gml:pos"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template match="gml:posList"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template name="shape">
<xsl:element name="{local-name(.)}">
<xsl:choose>
<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4326'">
<xsl:attribute name="srsName">2d</xsl:attribute>
</xsl:when>
<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4979'">
<xsl:attribute name="srsName">3d</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="srsName">unknown</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="ca:civicAddress/*"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template name="civicAddress">
<xsl:element name="{local-name(.)}">
<xsl:attribute name="lang"><xsl:value-of select="@xml:lang"/></xsl:attribute>
<xsl:apply-templates select="./*"/>
</xsl:element>
</xsl:template>
<xsl:template match="gp:usage-rules/*">
<xsl:call-template name="name-value" />
</xsl:template>
<xsl:template match="gp:usage-rules">
<xsl:element name="usage-rules">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="gp:method">
<xsl:element name="method">
<xsl:value-of select="normalize-space(.)" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>