diff --git a/CHANGES b/CHANGES
index 4237c82e29..7e54a20956 100644
--- a/CHANGES
+++ b/CHANGES
@@ -139,6 +139,14 @@ res_pjsip
* A new CLI command has been added: "pjsip show settings", which shows
both the global and system configuration settings.
+ * A new aor option has been added: "qualify_timeout", which sets the timeout
+ in seconds for a qualify. The default is 3 seconds. This overrides the
+ hard coded 32 seconds in pjproject.
+
+ * Endpoint status will now change to "Unreachable" when all contacts are
+ unavailable. When any contact becomes available, the endpoint will status
+ will change back to "Reachable".
+
res_ari_channels
------------------
* Two new events, 'ChannelHold' and 'ChannelUnhold', have been added to the
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index d3bb518f15..57b712a873 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -812,6 +812,7 @@
; (default: "no")
;type= ; Must be of type aor (default: "")
;qualify_frequency=0 ; Interval at which to qualify an AoR (default: "0")
+;qualify_timeout=3.0 ; Qualify timeout in fractional seconds (default: "3.0")
;authenticate_qualify=no ; Authenticates a qualify request if needed
; (default: "no")
;outbound_proxy= ; Outbound proxy used when sending OPTIONS request
diff --git a/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
new file mode 100644
index 0000000000..9600c04611
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
@@ -0,0 +1,25 @@
+"""add pjsip qualify_timeout
+
+Revision ID: 461d7d691209
+Revises: 31cd4f4891ec
+Create Date: 2015-04-15 13:54:08.047851
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '461d7d691209'
+down_revision = '31cd4f4891ec'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+ op.add_column('ps_aors', sa.Column('qualify_timeout', sa.Integer))
+ op.add_column('ps_contacts', sa.Column('qualify_timeout', sa.Integer))
+ pass
+
+
+def downgrade():
+ op.drop_column('ps_aors', 'qualify_timeout')
+ op.drop_column('ps_contacts', 'qualify_timeout')
+ pass
diff --git a/include/asterisk/endpoints.h b/include/asterisk/endpoints.h
index 663dd94d9c..c9cb6b9de7 100644
--- a/include/asterisk/endpoints.h
+++ b/include/asterisk/endpoints.h
@@ -159,6 +159,16 @@ const char *ast_endpoint_get_resource(const struct ast_endpoint *endpoint);
*/
const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint);
+/*!
+ * \brief Gets the state of the given endpoint.
+ *
+ * \param endpoint The endpoint.
+ * \return state.
+ * \return \c AST_ENDPOINT_UNKNOWN if endpoint is \c NULL.
+ * \since 13.4
+ */
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint);
+
/*!
* \brief Updates the state of the given endpoint.
*
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 0610c95e72..184cb57451 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -166,6 +166,8 @@ struct ast_sip_contact {
unsigned int qualify_frequency;
/*! If true authenticate the qualify if needed */
int authenticate_qualify;
+ /*! Qualify timeout. 0 is diabled. */
+ double qualify_timeout;
};
#define CONTACT_STATUS "contact_status"
@@ -192,6 +194,8 @@ struct ast_sip_contact_status {
struct timeval rtt_start;
/*! The round trip time in microseconds */
int64_t rtt;
+ /*! Last status for a contact (default - unavailable) */
+ enum ast_sip_contact_status_type last_status;
};
/*!
@@ -224,6 +228,8 @@ struct ast_sip_aor {
struct ao2_container *permanent_contacts;
/*! Determines whether SIP Path headers are supported */
unsigned int support_path;
+ /*! Qualify timeout. 0 is diabled. */
+ double qualify_timeout;
};
/*!
@@ -904,6 +910,15 @@ struct ao2_container *ast_sip_location_retrieve_aor_contacts(const struct ast_si
*/
struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list);
+/*!
+ * \brief Retrieve all contacts from a list of AORs
+ *
+ * \param aor_list A comma-separated list of AOR names
+ * \retval NULL if no contacts available
+ * \retval non-NULL container (which must be freed) if contacts available
+ */
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list);
+
/*!
* \brief Retrieve the first bound contact AND the AOR chosen from a list of AORs
*
diff --git a/main/endpoints.c b/main/endpoints.c
index c70170b41f..df9d289c7f 100644
--- a/main/endpoints.c
+++ b/main/endpoints.c
@@ -415,6 +415,14 @@ const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint)
return endpoint->id;
}
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint)
+{
+ if (!endpoint) {
+ return AST_ENDPOINT_UNKNOWN;
+ }
+ return endpoint->state;
+}
+
void ast_endpoint_set_state(struct ast_endpoint *endpoint,
enum ast_endpoint_state state)
{
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 108c5b32dd..4f77b51021 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1011,6 +1011,14 @@
If 0 never qualify. Time in seconds.
+
+ Timeout for qualify
+
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If 0 no timeout. Time in fractional seconds.
+
+
Outbound proxy used when sending OPTIONS request
@@ -1125,6 +1133,14 @@
If 0 never qualify. Time in seconds.
+
+ Timeout for qualify
+
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If 0 no timeout. Time in fractional seconds.
+
+
Authenticates a qualify request if needed
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 73ffdca0e1..f784cb40fb 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -188,6 +188,40 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
return contact;
}
+static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags);
+static int cli_contact_populate_container(void *obj, void *arg, int flags);
+
+static int gather_contacts_for_aor(void *obj, void *arg, int flags)
+{
+ struct ao2_container *aor_contacts;
+ struct ast_sip_aor *aor = obj;
+ struct ao2_container *container = arg;
+
+ aor_contacts = ast_sip_location_retrieve_aor_contacts(aor);
+ if (!aor_contacts) {
+ return 0;
+ }
+ ao2_callback(aor_contacts, OBJ_MULTIPLE | OBJ_NODATA, cli_contact_populate_container,
+ container);
+ ao2_ref(aor_contacts, -1);
+ return CMP_MATCH;
+}
+
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list)
+{
+ struct ao2_container *contacts;
+
+ contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
+ if (!contacts) {
+ return NULL;
+ }
+
+ ast_sip_for_each_aor(aor_list, gather_contacts_for_aor, contacts);
+
+ return contacts;
+}
+
struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name)
{
return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
@@ -208,6 +242,7 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
ast_string_field_set(contact, uri, uri);
contact->expiration_time = expiration_time;
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
if (path_info && aor->support_path) {
ast_string_field_set(contact, path, path_info);
@@ -853,7 +888,8 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 0, 0);
ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
- PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+ PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+ ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout));
ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
@@ -862,6 +898,7 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
+ ast_sorcery_object_field_register(sorcery, "aor", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_aor, qualify_timeout));
ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 0eecb5e0a2..ab0d084494 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -19,6 +19,7 @@
#include "asterisk/utils.h"
#include "asterisk/sorcery.h"
#include "asterisk/callerid.h"
+#include "asterisk/test.h"
/*! \brief Number of buckets for persistent endpoint information */
#define PERSISTENT_BUCKETS 53
@@ -59,31 +60,66 @@ static int persistent_endpoint_cmp(void *obj, void *arg, int flags)
static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
{
struct sip_persistent_endpoint *persistent = obj;
+ struct ast_endpoint *endpoint = persistent->endpoint;
char *aor = arg;
- RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
- RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+ struct ao2_container *contacts;
+ struct ast_json *blob;
+ struct ao2_iterator i;
+ struct ast_sip_contact *contact;
+ enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;
if (!ast_strlen_zero(aor) && !strstr(persistent->aors, aor)) {
return 0;
}
- if ((contact = ast_sip_location_retrieve_contact_from_aor_list(persistent->aors))) {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);
+ /* Find all the contacts for this endpoint. If ANY are available,
+ * mark the endpoint as ONLINE.
+ */
+ contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);
+ if (contacts) {
+ i = ao2_iterator_init(contacts, 0);
+ while ((contact = ao2_iterator_next(&i))
+ && state == AST_ENDPOINT_OFFLINE) {
+ struct ast_sip_contact_status *contact_status;
+ const char *contact_id = ast_sorcery_object_get_id(contact);
+
+ contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
+ CONTACT_STATUS, contact_id);
+
+ if (contact_status && contact_status->status == AVAILABLE) {
+ state = AST_ENDPOINT_ONLINE;
+ }
+ ao2_cleanup(contact_status);
+ ao2_ref(contact, -1);
+ }
+ ao2_iterator_destroy(&i);
+ ao2_ref(contacts, -1);
+ }
+
+ /* If there was no state change, don't publish anything. */
+ if (ast_endpoint_get_state(endpoint) == state) {
+ return 0;
+ }
+
+ if (state == AST_ENDPOINT_ONLINE) {
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Reachable");
+ ast_verb(1, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));
} else {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");
+ ast_verb(1, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));
}
- ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);
-
- ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));
+ ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);
+ ast_json_unref(blob);
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));
return 0;
}
/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
-static void persistent_endpoint_contact_observer(const void *object)
+static void persistent_endpoint_contact_created_observer(const void *object)
{
char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
@@ -92,12 +128,74 @@ static void persistent_endpoint_contact_observer(const void *object)
ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
}
+/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
+static void persistent_endpoint_contact_deleted_observer(const void *object)
+{
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_verb(1, "Contact %s/%s is now Unavailable\n", aor, contact);
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
/*! \brief Observer for contacts so state can be updated on respective endpoints */
static const struct ast_sorcery_observer state_contact_observer = {
- .created = persistent_endpoint_contact_observer,
- .deleted = persistent_endpoint_contact_observer,
+ .created = persistent_endpoint_contact_created_observer,
+ .deleted = persistent_endpoint_contact_deleted_observer,
};
+/*! \brief Function called when stuff relating to a contact status happens (updated) */
+static void persistent_endpoint_contact_status_observer(const void *object)
+{
+ const struct ast_sip_contact_status *contact_status = object;
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ /* If rtt_start is set (this is the outgoing OPTIONS) or
+ * there's no status change, ignore.
+ */
+ if (contact_status->rtt_start.tv_sec > 0
+ || contact_status->status == contact_status->last_status) {
+ return;
+ }
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",
+ "Contact: %s\r\n"
+ "Status: %s",
+ ast_sorcery_object_get_id(contact_status),
+ (contact_status->status == AVAILABLE ? "Available" : "Unavailable"));
+
+ ast_verb(1, "Contact %s/%s is now %s\n", aor, contact,
+ contact_status->status == AVAILABLE ? "Available" : "Unavailable");
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer state_contact_status_observer = {
+ .updated = persistent_endpoint_contact_status_observer,
+};
static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
@@ -1796,6 +1894,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
}
ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);
+ ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
if (ast_sip_initialize_sorcery_domain_alias()) {
ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");
@@ -1852,6 +1951,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
void ast_res_pjsip_destroy_configuration(void)
{
+ ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
+ ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);
ast_sip_destroy_sorcery_global();
ast_sip_destroy_sorcery_location();
ast_sip_destroy_sorcery_auth();
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 9794827b56..dc5a70175b 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -28,6 +28,7 @@
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "asterisk/time.h"
+#include "asterisk/test.h"
#include "include/res_pjsip_private.h"
#define DEFAULT_LANGUAGE "en"
@@ -110,18 +111,20 @@ static void update_contact_status(const struct ast_sip_contact *contact,
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->last_status = status->status;
update->status = value;
/* if the contact is available calculate the rtt as
@@ -131,13 +134,21 @@ static void update_contact_status(const struct ast_sip_contact *contact,
update->rtt_start = ast_tv(0, 0);
+ ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",
+ "Contact: %s\r\n"
+ "Status: %s\r\n"
+ "RTT: %ld",
+ ast_sorcery_object_get_id(update),
+ (update->status == AVAILABLE ? "Available" : "Unavailable"),
+ update->rtt);
+
if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
contact->uri);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -152,18 +163,22 @@ static void init_start_time(const struct ast_sip_contact *contact)
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->status = status->status;
+ update->last_status = status->last_status;
+ update->rtt = status->rtt;
update->rtt_start = ast_tvnow();
if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
@@ -171,8 +186,8 @@ static void init_start_time(const struct ast_sip_contact *contact)
contact->uri);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -320,7 +335,7 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
init_start_time(contact);
ao2_ref(contact, +1);
- if (ast_sip_send_request(tdata, NULL, endpoint_local, contact, qualify_contact_cb)
+ if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)
!= PJ_SUCCESS) {
ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
contact->uri);
@@ -923,6 +938,32 @@ static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)
return CMP_MATCH;
}
+static int rtt_start_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_sip_contact_status *status = obj;
+ long int sec, usec;
+
+ if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {
+ return -1;
+ }
+
+ status->rtt_start = ast_tv(sec, usec);
+
+ return 0;
+}
+
+static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_contact_status *status = obj;
+
+ if (ast_asprintf(buf, "%ld.%06ld", status->rtt_start.tv_sec, status->rtt_start.tv_usec) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
int ast_sip_initialize_sorcery_qualify(void)
{
struct ast_sorcery *sorcery = ast_sip_get_sorcery();
@@ -936,10 +977,14 @@ int ast_sip_initialize_sorcery_qualify(void)
return -1;
}
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, status));
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, rtt));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, last_status));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, status));
+ ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",
+ "0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, rtt));
return 0;
}
@@ -951,6 +996,7 @@ static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
int initial_interval;
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
/* Delay initial qualification by a random fraction of the specified interval */