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

@ -204,4 +204,19 @@ endif
$(ECHO_PREFIX) echo " [LD] $^ -> $@"
$(CMD_PREFIX) $(CXX) -o $@ $(PTHREAD_CFLAGS) $(_ASTLDFLAGS) $^ $(CXX_LIBS) $(ASTLDFLAGS)
# These CC commands just create an object file with the input file embedded in it.
# It can be access from code as follows:
# If your input file is named abc_def.xml...
#
# extern const uint8_t _binary_abc_def_xml_start[];
# extern const uint8_t _binary_abc_def_xml_end[];
# extern const size_t _binary_abc_def_xml_size;
%.o: %.xml
$(ECHO_PREFIX) echo " [LD] $^ -> $@"
$(CMD_PREFIX) $(CC) -g -nostartfiles -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
%.o: %.xslt
$(ECHO_PREFIX) echo " [LD] $^ -> $@"
$(CMD_PREFIX) $(CC) -g -nostartfiles -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
dist-clean:: clean

View File

@ -0,0 +1,264 @@
;--
Geolocation Profile Sample Configuration
--;
;--
=======================================================================
Overview
=======================================================================
Geolocation information is actually comprised of two objects, a
Location object, and a Profile object.
Location objects must contain one of the following:
- Location information specified in Geographic Markup Language
(GML) or civicAddress formats.
- A URI that points to externally hosted location information.
Profile objects contain instructions for the disposition of location
information, an optional reference to a Location object, and updates or
overrides to that Location object if specified.
Channel drivers and the dialplan functions are responsible for
associating Profiles to endpoints/devices and calls. Normally, two
profiles would be assigned to an endpoint to control behavior in each
direction and to optionally specify location information. One for
incoming calls (Asterisk is the UAS) and and one for outgoing calls
(Asterisk is the UAC).
NOTE:
See https://wiki.asterisk.org/wiki/display/AST/Geolocation for the most
complete and up-to-date information on valid values for the object
parameters and a full list of references.
GENERAL CAUTION: You must coordinate with your partners with regards
to what location information is expected by each party and how it should
be formatted. An outgoing configuration mismatch for instance, could
result in misinformation or no information being sent to an emergency
response center or even call failure for which you are solely responsible.
--;
;--
=======================================================================
Location Object Description
=======================================================================
[<location_id>]
-- type (required) ----------------------------------------------------
Defines the object type.
type = location
Must be "location" to identify this configuration section as a
Geolocation Location object.
-- format (required) --------------------------------------------------
Sets the format used to express the location.
format = < civicAddress | GML | URI >
Values:
civicAddress: [RFC4119] [RFC5139] [RFC5491]
The location information will be placed in an XML document
conforming to the PIDF-LO standard.
For chan_pjsip, this will be placed in the body of
outgoing INVITE messages in addition to any SDP.
GML: [RFC4119] [RFC5491] [GeoShape]
The location information will be placed in an XML document
conforming to the PIDF-LO standard.
For chan_pjsip, this will be placed in the body of
outgoing INVITE messages in addition to any SDP.
URI: [RFC6442]
The external URI at which the the location information
can be found. For chan_pjsip, this URI will be placed
in a "Geolocation" header in outgoing INVITE messages.
There is no default.
Example:
format = civicAddress
-- location_info (required) -------------------------------------------
The location-format-specific information describing the location.
location_info = <location_format_specific_description>
For readability, multiple "location" parameters can be specified and
they will be concatenated into one specification. The description may
contain replacement variables which may be the names of common channel
variables like ${EXTEN}, channel variables you may have added in the
dialplan, or variables you may have specified in the profile that
references this location object.
NOTE: See https://wiki.asterisk.org/wiki/display/AST/Geolocation for the
most complete and up-to-date information on valid values for the object
parameters and a full list of references.
WARNING: Asterisk can only validate that a particular sub-parameter
name is valid for a particular format. It can't validate the actual
value of the sub-parameter.
Example for civicAddress:
location_info = country=US
location_info = A1="New York", A3="New York", A4=Manhattan,
location_info = HNO=1633, PRD=W, RD=46th, STS=Street
location_info = PC=10222
Example for GML with replacement variables:
location_info = type=Point, crs=2d, pos="${mylat} ${mylon}"
Example for URI with replacement variables:
location_info = URI=https://some.company.com?number=${phone_number}
-- method (optional) --------------------------------------------------
The method used to determine the location_info
method = <"GPS" | "A-GPS" | "Manual" | "DHCP"
| "Triangulation" | "Cell" | "802.11">
Example:
method = Manual
-- location_source (optional) -----------------------------------------
Original source of the location-info.
location_source = < FQDN >
The value MUST be a FQDN. IP addresses are specifically not
allowed. See RFC8787.
Example:
location_source = sip1.myserver.net
-- Location Example ---------------------------------------------------
[mylocation]
type = location
format = civicAddress
location_info = country=US
location_info = A1="New York", A3="New York", A4=Manhattan
location_info = HNO=1633, PRD=W, RD=46th, STS=Street
location_info = PC=10222
method = Manual
location_source = sip1.myserver.net
=======================================================================
--;
;--
=======================================================================
Profile Object Descriptions
=======================================================================
[<profile_id>]
-- type (required) ----------------------------------------------------
Defines the object type.
type = profile
-- profile_action (optional) ------------------------------------------
Sets how to reconcile incoming and configured profiles.
profile_action = < prefer_incoming | prefer_config | discard_incoming
| discard_config >
On an incoming call leg, "incoming" is the location description
received in the SIP INVITE (if any) and "config" is this profile.
On an outgoing call leg, "incoming" is the location description
passed through the dialplan to this channel (if any) and "config"
is this profile.
Values:
prefer_incoming: If there's an incoming location description, use it
even if there's also a configured one.
prefer_config: If there's a configured location description, use it
even if there's also an incoming one.
discard_incoming: Discard any incoming location description. If there's
a configured one, use it. If not, no location
information is propagated.
discard_config: Discard any configured location description. If
there's an incoming one, use it. If not, no location
information is propagated.
discard_incoming is the default.
Example:
profile_action = prefer_config
-- pidf_element (optional) --------------------------------------------
PIDF-LO element in which to place the location description.
pidf_element = < tuple | device | person >
If the format is civicAddress or GML, this sets the PIDF element into
which the location information will be placed.
Values:
tuple: Places the information in a "tuple" element.
device: Places the information in a "device" element.
person: Places the information in a "person" element.
Per [RFC5491], "device" is preferred and therefore the default.
Example:
pidf_element = tuple
-- geolocation_routing (optional) -------------------------------------
Sets whether the "Geolocation-Routing" header is added to outgoing
requests.
geolocation_routing = < yes | no >
Set to "yes" to indicate that servers later in the path
can use the location information for routing purposes. Set to "no"
if they should not. If this value isn't specified, no
"Geolocation-Routing" header will be added.
Example:
geolocation_routing = yes
-- location_reference (optional) --------------------------------------
The name of an existing Location object.
location_reference = <location_id>
The location_info_refinement and location_variables parameters below can
be used to refine the location object for this specific profile.
Example:
location_reference = "my_building"
-- location_info_refinement (optional) --------------------------------
Location info to add to that already retrieved from the location object.
location_info_refinement = <location_format_specific_description>
The information in the referenced Location object can be refined on a
per-profile basis. For example, if the referenced Location object has a
civicAddress for a building, you could set location_refinement to add a
floor and room just for this profile
Example:
location_info_refinement = floor=20, room=20a2
-- location_variables -------------------------------------------------
If the referenced Location object uses any replacement variables, they
can be assigned here. There is no need to define variables that come
from the channel using this profile. They get assigned automatically.
location_variables = myfloor=20, myroom=222
-- Profile Example ----------------------------------------------------
[myprofile]
type = profile
location_reference = mylocation
location_info_refinement = floor=20, room=20a2
pidf_element = tuple
profile_action = discard_incoming
=======================================================================
--;

View File

@ -0,0 +1,4 @@
Subject: res_geolocation
Added res_geolocation which creates the core capabilities
to manipulate Geolocation information on SIP INVITEs.

View File

@ -0,0 +1,353 @@
/*
* 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 INCLUDE_ASTERISK_RES_GEOLOCATION_H_
#define INCLUDE_ASTERISK_RES_GEOLOCATION_H_
#include "asterisk/channel.h"
#include "asterisk/config.h"
#include "asterisk/sorcery.h"
#include "asterisk/xml.h"
#include "asterisk/optional_api.h"
#define AST_GEOLOC_INVALID_VALUE -1
enum ast_geoloc_pidf_element {
AST_PIDF_ELEMENT_NONE = 0,
AST_PIDF_ELEMENT_TUPLE,
AST_PIDF_ELEMENT_DEVICE,
AST_PIDF_ELEMENT_PERSON,
AST_PIDF_ELEMENT_LAST,
};
enum ast_geoloc_format {
AST_GEOLOC_FORMAT_NONE = 0,
AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
AST_GEOLOC_FORMAT_GML,
AST_GEOLOC_FORMAT_URI,
AST_GEOLOC_FORMAT_LAST,
};
enum ast_geoloc_action {
AST_GEOLOC_ACT_PREFER_INCOMING = 0,
AST_GEOLOC_ACT_PREFER_CONFIG,
AST_GEOLOC_ACT_DISCARD_INCOMING,
AST_GEOLOC_ACT_DISCARD_CONFIG,
};
struct ast_geoloc_location {
SORCERY_OBJECT(details);
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(method);
AST_STRING_FIELD(location_source);
);
enum ast_geoloc_format format;
struct ast_variable *location_info;
};
struct ast_geoloc_profile {
SORCERY_OBJECT(details);
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(location_reference);
AST_STRING_FIELD(notes);
);
enum ast_geoloc_pidf_element pidf_element;
enum ast_geoloc_action action;
int geolocation_routing;
struct ast_variable *location_refinement;
struct ast_variable *location_variables;
struct ast_variable *usage_rules;
};
struct ast_geoloc_eprofile {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(id);
AST_STRING_FIELD(location_reference);
AST_STRING_FIELD(location_source);
AST_STRING_FIELD(method);
AST_STRING_FIELD(notes);
);
enum ast_geoloc_pidf_element pidf_element;
enum ast_geoloc_action action;
int geolocation_routing;
enum ast_geoloc_format format;
struct ast_variable *location_info;
struct ast_variable *location_refinement;
struct ast_variable *location_variables;
struct ast_variable *effective_location;
struct ast_variable *usage_rules;
};
/*!
* \brief Check if res_geolocation is available
*
* \return 1 if available, 0 otherwise.
*/
AST_OPTIONAL_API(int, ast_geoloc_is_loaded, (void), { return 0; });
/*!
* \brief Retrieve a geolocation location object by id.
*
* \param id Location object id.
*
* \return Location object or NULL if not found.
*/
AST_OPTIONAL_API(struct ast_geoloc_location *, ast_geoloc_get_location,
(const char *id),
{ return NULL; });
/*!
* \brief Retrieve a geolocation profile by id.
*
* \param id profile id.
*
* \return Profile or NULL if not found.
*/
AST_OPTIONAL_API(struct ast_geoloc_profile *, ast_geoloc_get_profile,
(const char *id),
{ return NULL; });
/*!
* \brief Given a civicAddress code, check whether it's valid.
*
* \param code Pointer to the code to check
*
* \return 1 if valid, 0 otherwise.
*/
int ast_geoloc_civicaddr_is_code_valid(const char *code);
enum ast_geoloc_validate_result {
AST_GEOLOC_VALIDATE_INVALID_VALUE = -1,
AST_GEOLOC_VALIDATE_SUCCESS = 0,
AST_GEOLOC_VALIDATE_MISSING_SHAPE,
AST_GEOLOC_VALIDATE_INVALID_SHAPE,
AST_GEOLOC_VALIDATE_INVALID_VARNAME,
AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES,
AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES,
};
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);
/*!
* \brief Geolocation datastore Functions
* @{
*/
/*!
* \brief Create a geoloc datastore from a profile name
*
* \param profile_name The name of the profile to use.
*
* \return The datastore.
*/
struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name);
/*!
* \brief Create a geoloc datastore from an effective profile.
*
* \param eprofile The effective profile to use.
*
* \return The datastore.
*/
struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
struct ast_geoloc_eprofile *eprofile);
/*!
* \brief Create an empty geoloc datastore.
*
* \param id An id to use for the datastore.
*
* \return The datastore.
*/
struct ast_datastore *ast_geoloc_datastore_create(const char *id);
/*!
* \brief Retrieve a geoloc datastore's id.
*
* \param ds The datastore
*
* \return The datastore's id.
*/
const char *ast_geoloc_datastore_get_id(struct ast_datastore *ds);
/*!
* \brief Add an eprofile to a datastore
*
* \param ds The datastore
* \param eprofile The eprofile to add.
*
* \return The new number of eprofiles or -1 to indicate a failure.
*/
int ast_geoloc_datastore_add_eprofile(struct ast_datastore *ds,
struct ast_geoloc_eprofile *eprofile);
/*!
* \brief Insert an eprofile to a datastore at the specified position
*
* \param ds The datastore
* \param eprofile The eprofile to add.
* \param index The position to insert at. Existing eprofiles will
* be moved up to make room.
*
* \return The new number of eprofiles or -1 to indicate a failure.
*/
int ast_geoloc_datastore_insert_eprofile(struct ast_datastore *ds,
struct ast_geoloc_eprofile *eprofile, int index);
/*!
* \brief Retrieves the number of eprofiles in the datastore
*
* \param ds The datastore
*
* \return The number of eprofiles.
*/
int ast_geoloc_datastore_size(struct ast_datastore *ds);
/*!
* \brief Sets the inheritance flag on the datastore
*
* \param ds The datastore
* \param inherit 1 to allow the datastore to be inherited by other channels
* 0 to prevent the datastore to be inherited by other channels
*
* \return 0 if successful, -1 otherwise.
*/
int ast_geoloc_datastore_set_inheritance(struct ast_datastore *ds, int inherit);
/*!
* \brief Retrieve a specific eprofile from a datastore by index
*
* \param ds The datastore
* \param ix The index
*
* \return The effective profile ao2 object with its reference count bumped.
*/
struct ast_geoloc_eprofile *ast_geoloc_datastore_get_eprofile(struct ast_datastore *ds, int ix);
/*!
* \brief Delete a specific eprofile from a datastore by index
*
* \param ds The datastore
* \param ix The index
*
* \return 0 if succesful, -1 otherwise.
*/
int ast_geoloc_datastore_delete_eprofile(struct ast_datastore *ds, int ix);
/*!
* \brief Retrieves the geoloc datastore from a channel, if any
*
* \param chan Channel
*
* \return datastore if found, NULL otherwise.
*/
struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan);
/*!
* @}
*/
/*!
* \brief Geolocation Effective Profile Functions
* @{
*/
/*!
* \brief Allocate a new, empty effective profile.
*
* \param name The profile's name
*
* \return The effective profile ao2 object.
*/
struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name);
/*!
* \brief Allocate a new effective profile from an existing profile.
*
* \param profile The profile to use.
*
* \return The effective profile ao2 object.
*/
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile);
/*!
* \brief Allocate a new effective profile from an XML PIDF-LO document
*
* \param pidf_xmldoc The ast_xml_doc to use.
* \param geoloc_uri The URI that referenced this document.
* \param reference_string An identifying string to use in error messages.
*
* \return The effective profile ao2 object.
*/
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
struct ast_xml_doc *pidf_xmldoc, const char *geoloc_uri, const char *reference_string);
/*!
* \brief Allocate a new effective profile from a URI.
*
* \param uri The URI to use.
* \param reference_string An identifying string to use in error messages.
*
* \return The effective profile ao2 object.
*/
struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
const char *reference_string);
const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
struct ast_channel *chan, struct ast_str **buf, const char *ref_string);
const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
/*!
* \brief Refresh the effective profile with any changed info.
*
* \param eprofile The eprofile to refresh.
*
* \return 0 on success, any other value on error.
*/
int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile);
/*!
* @}
*/
#endif /* INCLUDE_ASTERISK_RES_GEOLOCATION_H_ */

View File

@ -70,6 +70,10 @@ $(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_aeap,$(wildcard res_aeap/*.c))
$(call MOD_ADD_C,res_geolocation,$(wildcard res_geolocation/*.c))
# These are the xml and xslt files to be embedded
res_geolocation.so: res_geolocation/pidf_lo_test.o res_geolocation/pidf_to_eprofile.o res_geolocation/eprofile_to_pidf.o
res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
snmp/agent.o: _ASTCFLAGS+=-fPIC
@ -77,3 +81,4 @@ res_snmp.o: _ASTCFLAGS+=-fPIC
# Dependencies for res_ari_*.so are generated, so they're in this file
include ari.make

125
res/res_geolocation.c Normal file
View File

@ -0,0 +1,125 @@
/*
* 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.
*/
/*** MODULEINFO
<depend>libxml2</depend>
<depend>libxslt</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#define AST_API_MODULE
#include "asterisk/res_geolocation.h"
#include "res_geolocation/geoloc_private.h"
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_eprofile_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_unload();
res += geoloc_dialplan_unload();
res += geoloc_eprofile_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_civicaddr_load();
if (res) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
}
res = geoloc_gml_load();
if (res) {
return AST_MODULE_LOAD_DECLINE;
}
res = geoloc_config_load();
if (res) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
}
res = geoloc_eprofile_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,
);

View File

@ -0,0 +1,6 @@
{
global:
LINKER_SYMBOL_PREFIXast_geo*;
local:
*;
};

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>