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:
parent
bcc18ca9f5
commit
639d72e98c
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
=======================================================================
|
||||
--;
|
|
@ -0,0 +1,4 @@
|
|||
Subject: res_geolocation
|
||||
|
||||
Added res_geolocation which creates the core capabilities
|
||||
to manipulate Geolocation information on SIP INVITEs.
|
|
@ -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_ */
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
global:
|
||||
LINKER_SYMBOL_PREFIXast_geo*;
|
||||
local:
|
||||
*;
|
||||
};
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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><note-well></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
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue