<p>Joshua Colp has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/7710">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">pjsip: Rewrite OPTIONS support with new eyes.<br><br>The OPTIONS support in PJSIP has organically grown, like<br>many things in Asterisk. It has been tweaked, changed, and<br>adapted based on situations run into. Unfortunately this has<br>taken its toll. Configuration file based objects have poor<br>performance and even dynamic ones aren't that great.<br><br>This change scraps the existing code and starts fresh<br>with new eyes. It leverages all of the APIs made available<br>such as sorcery observers and serializers to provide a<br>better implementation.<br><br>1. The state of contacts, AORs, and endpoints relevant to<br>the qualify process is maintained. This state can be updated<br>by external forces (such as a device registering/unregistering)<br>and also the reload process. This state also includes the<br>association between endpoints and AORs.<br><br>2. AORs are scheduled and not contacts. This reduces the amount<br>of work spent juggling scheduled items.<br><br>3. Manipulation of which AORs are being qualified and the<br>endpoint states all occur within a serializer to reduce the<br>conflict that can occur with multiple threads attempting to<br>modify things.<br><br>4. Operations regarding an AOR use a serializer specific to that<br>AOR.<br><br>5. AORs and endpoint state act as state compositors. They take<br>input from lower level objects (contacts feed AORs, AORs feed<br>endpoint state) and determine if a sufficient enough change has<br>occurred to be fed further up the chain.<br><br>6. Realtime is supported by using observers to know when a contact<br>has been registered. If state does not exist for the associated<br>AOR then it is retrieved and becomes active as appropriate.<br><br>The end result of all of this is best shown with a configuration file<br>of 3000 endpoints each with an AOR that has a static contact. In<br>the old code it would take over a minute to load and use all 8 of<br>my cores. This new code takes 2-3 seconds and barely touches the<br>CPU even while dealing with all of the OPTIONS requests.<br><br>ASTERISK-26806<br><br>Change-Id: I6a5ebbfca9001dfe933eaeac4d3babd8d2e6f082<br>---<br>M funcs/func_pjsip_contact.c<br>M include/asterisk/res_pjsip.h<br>M res/res_pjsip/location.c<br>M res/res_pjsip/pjsip_configuration.c<br>M res/res_pjsip/pjsip_options.c<br>5 files changed, 1,867 insertions(+), 1,456 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/10/7710/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/funcs/func_pjsip_contact.c b/funcs/func_pjsip_contact.c<br>index 005f29b..2ad3951 100644<br>--- a/funcs/func_pjsip_contact.c<br>+++ b/funcs/func_pjsip_contact.c<br>@@ -144,7 +144,7 @@<br> return -1;<br> }<br> <br>- contact_status = ast_sorcery_retrieve_by_id(pjsip_sorcery, CONTACT_STATUS, ast_sorcery_object_get_id(contact_obj));<br>+ contact_status = ast_sip_get_contact_status(contact_obj);<br> <br> if (!strcmp(args.field_name, "status")) {<br> ast_str_set(buf, len, "%s", ast_sip_get_contact_status_label(contact_status->status));<br>diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h<br>index 931757f..f9507c5 100644<br>--- a/include/asterisk/res_pjsip.h<br>+++ b/include/asterisk/res_pjsip.h<br>@@ -283,8 +283,6 @@<br> int prune_on_boot;<br> };<br> <br>-#define CONTACT_STATUS "contact_status"<br>-<br> /*!<br> * \brief Status type for a contact.<br> */<br>@@ -303,10 +301,9 @@<br> * if available.<br> */<br> struct ast_sip_contact_status {<br>- SORCERY_OBJECT(details);<br> /*! Current status for a contact (default - unavailable) */<br> enum ast_sip_contact_status_type status;<br>- /*! The round trip start time set before sending a qualify request */<br>+ /*! UNUSED: The round trip start time set before sending a qualify request */<br> struct timeval rtt_start;<br> /*! The round trip time in microseconds */<br> int64_t rtt;<br>@@ -316,8 +313,10 @@<br> char *aor;<br> /*! The original contact's URI */<br> char *uri;<br>- /*! TRUE if the contact was refreshed. e.g., re-registered */<br>+ /*! UNUSED: TRUE if the contact was refreshed. e.g., re-registered */<br> unsigned int refresh:1;<br>+ /*! Name of the contact */<br>+ char name[0];<br> };<br> <br> /*!<br>@@ -1051,7 +1050,7 @@<br> /*!<br> * \brief Change state of a persistent endpoint.<br> *<br>- * \param endpoint The SIP endpoint name to change state.<br>+ * \param endpoint_name The SIP endpoint name to change state.<br> * \param state The new state<br> * \retval 0 Success<br> * \retval -1 Endpoint not found<br>@@ -1059,6 +1058,25 @@<br> int ast_sip_persistent_endpoint_update_state(const char *endpoint_name, enum ast_endpoint_state state);<br> <br> /*!<br>+ * \brief Publish the change of state for a contact.<br>+ *<br>+ * \param endpoint_name The SIP endpoint name.<br>+ * \param contact_status The contact status.<br>+ * \retval 0 Success<br>+ * \retval -1 Endpoint not found<br>+ */<br>+int ast_sip_persistent_endpoint_publish_contact_state(const char *endpoint_name, const struct ast_sip_contact_status *contact_status);<br>+<br>+/*!<br>+ * \brief Retrieve the current status for a contact.<br>+ *<br>+ * \param contact The contact.<br>+ * \retval non-NULL Success<br>+ * \retval NULL Status information not found<br>+ */<br>+struct ast_sip_contact_status *ast_sip_get_contact_status(const struct ast_sip_contact *contact);<br>+<br>+/*!<br> * \brief Get a pointer to the PJSIP endpoint.<br> *<br> * This is useful when modules have specific information they need<br>diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c<br>index 2bd40e8..093ab75 100644<br>--- a/res/res_pjsip/location.c<br>+++ b/res/res_pjsip/location.c<br>@@ -179,7 +179,7 @@<br> struct ast_sip_contact_status *status;<br> int unreachable;<br> <br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ status = ast_sip_get_contact_status(contact);<br> if (!status) {<br> return 0;<br> }<br>@@ -1077,7 +1077,7 @@<br> ast_assert(contact->uri != NULL);<br> ast_assert(context->output_buffer != NULL);<br> <br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS, contact_id);<br>+ status = ast_sip_get_contact_status(contact);<br> <br> indent = CLI_INDENT_TO_SPACES(context->indent_level);<br> flexwidth = CLI_LAST_TABSTOP - indent - 9 - strlen(contact->aor) + 1;<br>diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c<br>index 9aab75b..8c4a814 100644<br>--- a/res/res_pjsip/pjsip_configuration.c<br>+++ b/res/res_pjsip/pjsip_configuration.c<br>@@ -69,242 +69,6 @@<br> return !strcmp(ast_endpoint_get_resource(persistent1->endpoint), id) ? CMP_MATCH | CMP_STOP : 0;<br> }<br> <br>-/*! \brief Internal function for changing the state of an endpoint */<br>-static void endpoint_update_state(struct ast_endpoint *endpoint, enum ast_endpoint_state state)<br>-{<br>- struct ast_json *blob;<br>- char *regcontext;<br>-<br>- /* If there was no state change, don't publish anything. */<br>- if (ast_endpoint_get_state(endpoint) == state) {<br>- return;<br>- }<br>-<br>- regcontext = ast_sip_get_regcontext();<br>-<br>- if (state == AST_ENDPOINT_ONLINE) {<br>- ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);<br>- blob = ast_json_pack("{s: s}", "peer_status", "Reachable");<br>-<br>- if (!ast_strlen_zero(regcontext)) {<br>- if (!ast_exists_extension(NULL, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL)) {<br>- ast_add_extension(regcontext, 1, ast_endpoint_get_resource(endpoint), 1, NULL, NULL,<br>- "Noop", ast_strdup(ast_endpoint_get_resource(endpoint)), ast_free_ptr, "SIP");<br>- }<br>- }<br>-<br>- ast_verb(2, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));<br>- } else {<br>- ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);<br>- blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");<br>-<br>- if (!ast_strlen_zero(regcontext)) {<br>- struct pbx_find_info q = { .stacklen = 0 };<br>-<br>- if (pbx_find_extension(NULL, NULL, &q, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL, "", E_MATCH)) {<br>- ast_context_remove_extension(regcontext, ast_endpoint_get_resource(endpoint), 1, NULL);<br>- }<br>- }<br>-<br>- ast_verb(2, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));<br>- }<br>-<br>- ast_free(regcontext);<br>-<br>- ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);<br>- ast_json_unref(blob);<br>- ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));<br>-}<br>-<br>-static void endpoint_publish_contact_status(struct ast_endpoint *endpoint, struct ast_sip_contact_status *contact)<br>-{<br>- struct ast_json *blob;<br>- char rtt[32];<br>-<br>- snprintf(rtt, sizeof(rtt), "%" PRId64, contact->rtt);<br>- blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",<br>- "contact_status", ast_sip_get_contact_status_label(contact->status),<br>- "aor", contact->aor,<br>- "uri", contact->uri,<br>- "roundtrip_usec", rtt,<br>- "endpoint_name", ast_endpoint_get_resource(endpoint));<br>- if (blob) {<br>- ast_endpoint_blob_publish(endpoint, ast_endpoint_contact_state_type(), blob);<br>- ast_json_unref(blob);<br>- }<br>-}<br>-<br>-/*! \brief Callback function for publishing the status of an endpoint */<br>-static int persistent_endpoint_publish_status(void *obj, void *arg, int flags)<br>-{<br>- struct sip_persistent_endpoint *persistent = obj;<br>- struct ast_endpoint *endpoint = persistent->endpoint;<br>- struct ast_sip_contact_status *status = arg;<br>-<br>- /* If the status' aor isn't one of the endpoint's, we skip */<br>- if (!strstr(persistent->aors, status->aor)) {<br>- return 0;<br>- }<br>-<br>- endpoint_publish_contact_status(endpoint, status);<br>- return 0;<br>-}<br>-<br>-/*! \brief Callback function for changing the state of an endpoint */<br>-static int persistent_endpoint_update_state(void *obj, void *arg, int flags)<br>-{<br>- struct sip_persistent_endpoint *persistent = obj;<br>- struct ast_endpoint *endpoint = persistent->endpoint;<br>- struct ast_sip_contact_status *status = arg;<br>- struct ao2_container *contacts;<br>- struct ao2_iterator iter;<br>- struct ast_sip_contact *contact;<br>- enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;<br>-<br>- /* If the status' aor isn't one of the endpoint's, we skip */<br>- if (!strstr(persistent->aors, status->aor)) {<br>- return 0;<br>- }<br>-<br>- endpoint_publish_contact_status(endpoint, status);<br>-<br>- /* Find all the contacts for this endpoint. If ANY are available,<br>- * mark the endpoint as ONLINE.<br>- */<br>- contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);<br>- if (contacts) {<br>- iter = ao2_iterator_init(contacts, 0);<br>- while (state == AST_ENDPOINT_OFFLINE && (contact = ao2_iterator_next(&iter))) {<br>- struct ast_sip_contact_status *contact_status;<br>- const char *contact_id = ast_sorcery_object_get_id(contact);<br>-<br>- contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),<br>- CONTACT_STATUS, contact_id);<br>- if (contact_status && contact_status->status != UNAVAILABLE) {<br>- state = AST_ENDPOINT_ONLINE;<br>- }<br>- ao2_cleanup(contact_status);<br>- ao2_ref(contact, -1);<br>- }<br>- ao2_iterator_destroy(&iter);<br>- ao2_ref(contacts, -1);<br>- }<br>-<br>- endpoint_update_state(endpoint, state);<br>-<br>- return 0;<br>-}<br>-<br>-/*! \brief Function called when a contact is created */<br>-static void persistent_endpoint_contact_created_observer(const void *object)<br>-{<br>- const struct ast_sip_contact *contact = object;<br>- struct ast_sip_contact_status *contact_status;<br>-<br>- contact_status = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!contact_status) {<br>- ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return;<br>- }<br>- contact_status->uri = ast_strdup(contact->uri);<br>- if (!contact_status->uri) {<br>- ao2_cleanup(contact_status);<br>- return;<br>- }<br>- contact_status->status = CREATED;<br>-<br>- ast_verb(2, "Contact %s/%s has been created\n", contact->aor, contact->uri);<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, contact_status);<br>- ao2_cleanup(contact_status);<br>-}<br>-<br>-/*! \brief Function called when a contact is deleted */<br>-static void persistent_endpoint_contact_deleted_observer(const void *object)<br>-{<br>- const struct ast_sip_contact *contact = object;<br>- struct ast_sip_contact_status *contact_status;<br>-<br>- contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS, ast_sorcery_object_get_id(contact));<br>- if (!contact_status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return;<br>- }<br>-<br>- ast_verb(2, "Contact %s/%s has been deleted\n", contact->aor, contact->uri);<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "-1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(REMOVED));<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, contact_status);<br>- ast_sorcery_delete(ast_sip_get_sorcery(), contact_status);<br>- ao2_cleanup(contact_status);<br>-}<br>-<br>-/*! \brief Observer for contacts so state can be updated on respective endpoints */<br>-static const struct ast_sorcery_observer state_contact_observer = {<br>- .created = persistent_endpoint_contact_created_observer,<br>- .deleted = persistent_endpoint_contact_deleted_observer,<br>-};<br>-<br>-/*! \brief Function called when a contact_status is updated */<br>-static void persistent_endpoint_contact_status_observer(const void *object)<br>-{<br>- struct ast_sip_contact_status *contact_status = (struct ast_sip_contact_status *)object;<br>-<br>- if (contact_status->refresh) {<br>- /* We are only re-publishing the contact status. */<br>- ao2_callback(persistent_endpoints, OBJ_NODATA,<br>- persistent_endpoint_publish_status, contact_status);<br>- return;<br>- }<br>-<br>- /* If rtt_start is set (this is the outgoing OPTIONS), ignore. */<br>- if (contact_status->rtt_start.tv_sec > 0) {<br>- return;<br>- }<br>-<br>- if (contact_status->status != contact_status->last_status) {<br>- ast_verb(3, "Contact %s/%s is now %s. RTT: %.3f msec\n",<br>- contact_status->aor, contact_status->uri,<br>- ast_sip_get_contact_status_label(contact_status->status),<br>- contact_status->rtt / 1000.0);<br>-<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "-1", 1.0, ast_sip_get_contact_status_label(contact_status->last_status));<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>-<br>- ast_test_suite_event_notify("AOR_CONTACT_UPDATE",<br>- "Contact: %s\r\n"<br>- "Status: %s",<br>- ast_sorcery_object_get_id(contact_status),<br>- ast_sip_get_contact_status_label(contact_status->status));<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state,<br>- contact_status);<br>- } else {<br>- ast_debug(3, "Contact %s/%s status didn't change: %s, RTT: %.3f msec\n",<br>- contact_status->aor, contact_status->uri,<br>- ast_sip_get_contact_status_label(contact_status->status),<br>- contact_status->rtt / 1000.0);<br>- }<br>-<br>- ast_statsd_log_full_va("PJSIP.contacts.%s.rtt", AST_STATSD_TIMER,<br>- contact_status->status != AVAILABLE ? -1 : contact_status->rtt / 1000,<br>- 1.0,<br>- ast_sorcery_object_get_id(contact_status));<br>-}<br>-<br>-/*! \brief Observer for contacts so state can be updated on respective endpoints */<br>-static const struct ast_sorcery_observer state_contact_status_observer = {<br>- .updated = persistent_endpoint_contact_status_observer,<br>-};<br>-<br> static void endpoint_deleted_observer(const void *object)<br> {<br> const struct ast_sip_endpoint *endpoint = object;<br>@@ -1289,21 +1053,91 @@<br> struct sip_persistent_endpoint *persistent = obj;<br> <br> ast_endpoint_shutdown(persistent->endpoint);<br>- ast_free(persistent->aors);<br> }<br> <br> int ast_sip_persistent_endpoint_update_state(const char *endpoint_name, enum ast_endpoint_state state)<br> {<br> struct sip_persistent_endpoint *persistent;<br>+ struct ast_json *blob;<br>+ char *regcontext;<br> <br>- ao2_lock(persistent_endpoints);<br>- persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>- if (persistent) {<br>- endpoint_update_state(persistent->endpoint, state);<br>- ao2_ref(persistent, -1);<br>+ persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY);<br>+ if (!persistent) {<br>+ return -1;<br> }<br>- ao2_unlock(persistent_endpoints);<br>- return persistent ? 0 : -1;<br>+<br>+ /* If there was no state change, don't publish anything. */<br>+ if (ast_endpoint_get_state(persistent->endpoint) == state) {<br>+ ao2_ref(persistent, -1);<br>+ return 0;<br>+ }<br>+<br>+ regcontext = ast_sip_get_regcontext();<br>+<br>+ if (state == AST_ENDPOINT_ONLINE) {<br>+ ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);<br>+ blob = ast_json_pack("{s: s}", "peer_status", "Reachable");<br>+<br>+ if (!ast_strlen_zero(regcontext)) {<br>+ if (!ast_exists_extension(NULL, regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL)) {<br>+ ast_add_extension(regcontext, 1, ast_endpoint_get_resource(persistent->endpoint), 1, NULL, NULL,<br>+ "Noop", ast_strdup(ast_endpoint_get_resource(persistent->endpoint)), ast_free_ptr, "SIP");<br>+ }<br>+ }<br>+<br>+ ast_verb(2, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(persistent->endpoint));<br>+ } else {<br>+ ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);<br>+ blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");<br>+<br>+ if (!ast_strlen_zero(regcontext)) {<br>+ struct pbx_find_info q = { .stacklen = 0 };<br>+<br>+ if (pbx_find_extension(NULL, NULL, &q, regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL, "", E_MATCH)) {<br>+ ast_context_remove_extension(regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL);<br>+ }<br>+ }<br>+<br>+ ast_verb(2, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(persistent->endpoint));<br>+ }<br>+<br>+ ast_free(regcontext);<br>+<br>+ ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);<br>+ ast_json_unref(blob);<br>+ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));<br>+<br>+ ao2_ref(persistent, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+int ast_sip_persistent_endpoint_publish_contact_state(const char *endpoint_name, const struct ast_sip_contact_status *contact_status)<br>+{<br>+ struct sip_persistent_endpoint *persistent;<br>+ struct ast_json *blob;<br>+ char rtt[32];<br>+<br>+ persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY);<br>+ if (!persistent) {<br>+ return -1;<br>+ }<br>+<br>+ snprintf(rtt, sizeof(rtt), "%" PRId64, contact_status->rtt);<br>+ blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",<br>+ "contact_status", ast_sip_get_contact_status_label(contact_status->status),<br>+ "aor", contact_status->aor,<br>+ "uri", contact_status->uri,<br>+ "roundtrip_usec", rtt,<br>+ "endpoint_name", ast_endpoint_get_resource(persistent->endpoint));<br>+ if (blob) {<br>+ ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_contact_state_type(), blob);<br>+ ast_json_unref(blob);<br>+ }<br>+<br>+ ao2_ref(persistent, -1);<br>+<br>+ return 0;<br> }<br> <br> /*! \brief Internal function which finds (or creates) persistent endpoint information */<br>@@ -1327,22 +1161,9 @@<br> return NULL;<br> }<br> <br>- persistent->aors = ast_strdup(endpoint->aors);<br>- if (!persistent->aors) {<br>- return NULL;<br>- }<br>-<br> ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);<br> <br> ao2_link_flags(persistent_endpoints, persistent, OBJ_NOLOCK);<br>- } else if (strcmp(persistent->aors, endpoint->aors)) {<br>- char *new_aors = ast_strdup(endpoint->aors);<br>-<br>- /* make sure we don't NULL persistent->aors if allocation fails. */<br>- if (new_aors) {<br>- ast_free(persistent->aors);<br>- persistent->aors = new_aors;<br>- }<br> }<br> <br> ao2_ref(persistent->endpoint, +1);<br>@@ -1994,16 +1815,7 @@<br> return -1;<br> }<br> <br>- if (ast_sip_initialize_sorcery_qualify()) {<br>- ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n");<br>- ast_sorcery_unref(sip_sorcery);<br>- sip_sorcery = NULL;<br>- return -1;<br>- }<br>-<br> ast_sorcery_observer_add(sip_sorcery, "endpoint", &endpoint_observers);<br>- ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);<br>- ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);<br> <br> if (ast_sip_initialize_sorcery_domain_alias()) {<br> ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");<br>@@ -2052,8 +1864,6 @@<br> return;<br> }<br> <br>- ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);<br>- ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);<br> ast_sip_destroy_sorcery_global();<br> ast_sip_destroy_sorcery_location();<br> ast_sip_destroy_sorcery_auth();<br>diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c<br>index 1eafb4a..9bec5de 100644<br>--- a/res/res_pjsip/pjsip_options.c<br>+++ b/res/res_pjsip/pjsip_options.c<br>@@ -1,9 +1,9 @@<br> /*<br> * Asterisk -- An open source telephony toolkit.<br> *<br>- * Copyright (C) 2013, Digium, Inc.<br>+ * Copyright (C) 2017, Digium, Inc.<br> *<br>- * Matt Jordan <mjordan@digium.com><br>+ * Joshua Colp <jcolp@digium.com><br> *<br> * See http://www.asterisk.org for more information about<br> * the Asterisk project. Please do not directly contact<br>@@ -33,673 +33,135 @@<br> #include "include/res_pjsip_private.h"<br> #include "asterisk/taskprocessor.h"<br> <br>+/*<br>+ * This implementation for OPTIONS support is based around the idea that realistically<br>+ * an AOR generally has very few contacts and is referenced by only a few endpoints.<br>+ * While it is perfectly fine for use in opposite scenarios it works best in the above<br>+ * case. It is also not shy to keeping state but it is reactive to outside changes so<br>+ * it can be updated.<br>+ *<br>+ * The lowest level object in here is a contact and its associated contact status. The<br>+ * result of an OPTIONS request to a contact is reflected in the contact status. The<br>+ * scheduling of these OPTIONS request is driven by the AOR. The AOR periodicially<br>+ * (according to configuration) sends OPTIONS requests out to any contacts associated<br>+ * with it. Contacts themselves are not individually scheduled. Contacts can be added or<br>+ * deleted as appropriate with no requirement to reschedule.<br>+ *<br>+ * The next level object up is the AOR itself. The result of a contact status change is<br>+ * fed into it and the result composited with all other contacts. This may result in the<br>+ * AOR itself changing state (it can be either AVAILABLE or UNAVAILABLE).<br>+ *<br>+ * The highest level object up is the endpoint state compositor. The result of AOR state<br>+ * changes is fed into it and the result composited with all other referenced AORs. This<br>+ * may result in the endpoint itself changing state (it can be either ONLINE or OFFLINE).<br>+ * If this occurs the permanent endpoint is updated to reflect it.<br>+ *<br>+ * The threading model errs on the side of a world where things are not constantly changing.<br>+ * That is: A world where AORs and endpoints are not being constantly added/removed. This<br>+ * more closely mirrors the usage of the vast majority of people. This scenario can still be<br>+ * done but it may not be applied immediately.<br>+ *<br>+ * Manipulation of which AORs and endpoint state compositors exist is done within a single<br>+ * serializer. This ensures that no matter the source threads order is preserved and you<br>+ * won't get into a weird situation where things are referencing other things that should<br>+ * have already been destroyed.<br>+ *<br>+ * Operations which impact the state of an AOR are done within a serializer that is<br>+ * specific to the AOR. This includes the result of a contact status change. This change<br>+ * is queued and executed on the AOR serializer afterwards.<br>+ *<br>+ * Operations which impact an endpoint state compositor are protected by a lock. This is<br>+ * done as the endpoint state compositor usage is minimal and the overhead of using a serializer<br>+ * and queueing things is not warranted.<br>+ *<br>+ * AORs which do not have a qualify frequency are also kept in here but do not require<br>+ * the same criteria as qualified AORs to be considered available. In their case as long as<br>+ * at least 1 contact is configured on the AOR (or added to it by registration) it is<br>+ * considered available.<br>+ */<br>+<br> #define DEFAULT_LANGUAGE "en"<br> #define DEFAULT_ENCODING "text/plain"<br>-#define QUALIFIED_BUCKETS 211<br> <br>-static const char *status_map [] = {<br>- [UNAVAILABLE] = "Unreachable",<br>- [AVAILABLE] = "Reachable",<br>- [UNKNOWN] = "Unknown",<br>- [CREATED] = "Created",<br>- [REMOVED] = "Removed",<br>+/*! \brief These are the number of buckets to store AORs in */<br>+#define AOR_BUCKETS 211<br>+<br>+/*! \brief These are the number of buckets (per AOR) to use to store contacts */<br>+#define CONTACT_BUCKETS 13<br>+<br>+/*! \brief These are the number of buckets to store endpoint state compositors */<br>+#define ENDPOINT_STATE_COMPOSITOR_BUCKETS 13<br>+<br>+/*! \brief The initial vector size for the endpoint state compositors on an AOR */<br>+#define ENDPOINT_STATE_COMPOSITOR_INITIAL_SIZE 1<br>+<br>+/*!<br>+ * \brief Structure which contains composites information for endpoint state<br>+ */<br>+struct sip_options_endpoint_state_compositor {<br>+ /*! \brief The names of the AORs feeding this compositor */<br>+ struct ao2_container *aors;<br>+ /*! \brief The number of AORs that are available */<br>+ unsigned int available;<br>+ /*! \brief The name of the endpoint */<br>+ char name[0];<br> };<br> <br>-static const char *short_status_map [] = {<br>- [UNAVAILABLE] = "Unavail",<br>- [AVAILABLE] = "Avail",<br>- [UNKNOWN] = "Unknown",<br>- [CREATED] = "Created",<br>- [REMOVED] = "Removed",<br>+/*!<br>+ * \brief Structure which contains an AOR and contacts for qualifying purposes<br>+ */<br>+struct sip_options_aor {<br>+ /*! \brief The scheduler ID for this AOR */<br>+ int sched_id;<br>+ /*! \brief The serializer for this AOR */<br>+ struct ast_taskprocessor *serializer;<br>+ /*! \brief Permanent contacts associated with this AOR */<br>+ struct ao2_container *permanent_contacts;<br>+ /*! \brief Dynamic contacts associated with this AOR */<br>+ struct ao2_container *dynamic_contacts;<br>+ /*! \brief The endpoint state compositors we are feeding */<br>+ AST_VECTOR(, struct sip_options_endpoint_state_compositor *) compositors;<br>+ /*! \brief The number of available contacts on this AOR */<br>+ unsigned int available;<br>+ /*! \brief Frequency to send OPTIONS requests to AOR contacts. 0 is disabled. */<br>+ unsigned int qualify_frequency;<br>+ /*! \brief If true authenticate the qualify if needed */<br>+ int authenticate_qualify;<br>+ /*! \brief Qualify timeout. 0 is diabled. */<br>+ double qualify_timeout;<br>+ /*! \brief The name of the AOR */<br>+ char name[0];<br> };<br> <br>-static void contact_deleted(const void *obj);<br>-static void qualify_and_schedule(struct ast_sip_contact *contact);<br>-<br>-const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)<br>-{<br>- return status_map[status];<br>-}<br>-<br>-const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status)<br>-{<br>- return short_status_map[status];<br>-}<br>+/*!<br>+ * \internal<br>+ * \brief Container of active SIP AORs for qualifying<br>+ */<br>+static struct ao2_container *sip_options_aors;<br> <br> /*!<br> * \internal<br>- * \brief Destroy a ast_sip_contact_status object.<br>+ * \brief Container of contact statuses<br> */<br>-static void contact_status_destroy(void * obj)<br>-{<br>- struct ast_sip_contact_status *status = obj;<br>-<br>- ast_free(status->aor);<br>- ast_free(status->uri);<br>-}<br>+static struct ao2_container *sip_options_contact_statuses;<br> <br> /*!<br> * \internal<br>- * \brief Create a ast_sip_contact_status object.<br>+ * \brief Container of endpoint state compositors<br> */<br>-static void *contact_status_alloc(const char *name)<br>-{<br>- struct ast_sip_contact_status *status = ast_sorcery_generic_alloc(sizeof(*status), contact_status_destroy);<br>- char *id = ast_strdupa(name);<br>- char *aor = id;<br>- char *aor_separator = NULL;<br>-<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n");<br>- return NULL;<br>- }<br>-<br>- /* Dynamic contacts are delimited with ";@" and static ones with "@@" */<br>- if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) {<br>- *aor_separator = '\0';<br>- }<br>- ast_assert(aor_separator != NULL);<br>-<br>- status->aor = ast_strdup(aor);<br>- if (!status->aor) {<br>- ao2_cleanup(status);<br>- return NULL;<br>- }<br>-<br>- return status;<br>-}<br>-<br>-static int qualify_and_schedule_aor_contact(void *obj)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor;<br>-<br>- if (!contact || ast_strlen_zero(contact->aor) ||<br>- !(aor = ast_sip_location_retrieve_aor(contact->aor))) {<br>- ao2_ref(contact, -1);<br>- return -1;<br>- }<br>-<br>- contact->qualify_frequency = aor->qualify_frequency;<br>- contact->qualify_timeout = aor->qualify_timeout;<br>- contact->authenticate_qualify = aor->authenticate_qualify;<br>-<br>- ao2_ref(aor, -1);<br>-<br>- qualify_and_schedule(contact);<br>- ao2_ref(contact, -1);<br>-<br>- return 0;<br>-}<br>-<br>-AST_MUTEX_DEFINE_STATIC(creation_lock);<br>-<br>-/*!<br>- * \brief Retrieve a ast_sip_contact_status object from sorcery creating<br>- * one if not found.<br>- */<br>-struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact)<br>-{<br>- struct ast_sip_contact_status *status;<br>- SCOPED_MUTEX(lock, &creation_lock);<br>-<br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (status) {<br>- return status;<br>- }<br>-<br>- status = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return NULL;<br>- }<br>-<br>- status->uri = ast_strdup(contact->uri);<br>- if (!status->uri) {<br>- ao2_cleanup(status);<br>- return NULL;<br>- }<br>-<br>- status->rtt_start = ast_tv(0, 0);<br>- status->rtt = 0;<br>-<br>- if (ast_sorcery_create(ast_sip_get_sorcery(), status)) {<br>- ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- ao2_ref(status, -1);<br>- return NULL;<br>- }<br>-<br>- /* The permanent contact added after asterisk start should be qualified. */<br>- if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED) && ast_tvzero(contact->expiration_time)) {<br>- /*<br>- * The FULLY_BOOTED to filter out contacts that already existed when asterisk started.<br>- * The zero expiration_time to select only permanent contacts.<br>- */<br>- ao2_ref((struct ast_sip_contact *) contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_and_schedule_aor_contact, (struct ast_sip_contact *) contact)) {<br>- ao2_ref((struct ast_sip_contact *) contact, -1);<br>- }<br>- }<br>-<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(status->status));<br>-<br>- return status;<br>-}<br>+static struct ao2_container *sip_options_endpoint_state_compositors;<br> <br> /*!<br> * \internal<br>- * \brief Update an ast_sip_contact_status's elements.<br>- */<br>-static void update_contact_status(const struct ast_sip_contact *contact,<br>- enum ast_sip_contact_status_type value, int is_contact_refresh)<br>-{<br>- RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_sip_contact_status *, update, NULL, ao2_cleanup);<br>-<br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- if (is_contact_refresh<br>- && status->status == CREATED) {<br>- /*<br>- * The contact status hasn't been updated since creation<br>- * and we don't want to re-send a created status.<br>- */<br>- if (contact->qualify_frequency<br>- || status->rtt_start.tv_sec > 0) {<br>- /* Ignore, the status will change soon. */<br>- return;<br>- }<br>-<br>- /*<br>- * Convert to a regular contact status update<br>- * because the status may never change.<br>- */<br>- is_contact_refresh = 0;<br>- value = UNKNOWN;<br>- }<br>-<br>- update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(status));<br>- if (!update) {<br>- ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- update->uri = ast_strdup(contact->uri);<br>- if (!update->uri) {<br>- return;<br>- }<br>-<br>- if (is_contact_refresh) {<br>- /* Copy everything just to set the refresh flag. */<br>- update->status = status->status;<br>- update->last_status = status->last_status;<br>- update->rtt = status->rtt;<br>- update->rtt_start = status->rtt_start;<br>- update->refresh = 1;<br>- } else {<br>- update->last_status = status->status;<br>- update->status = value;<br>-<br>- /*<br>- * if the contact is available calculate the rtt as<br>- * the diff between the last start time and "now"<br>- */<br>- update->rtt = update->status == AVAILABLE && status->rtt_start.tv_sec > 0<br>- ? ast_tvdiff_us(ast_tvnow(), status->rtt_start)<br>- : 0;<br>- update->rtt_start = ast_tv(0, 0);<br>-<br>- ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",<br>- "Contact: %s\r\n"<br>- "Status: %s\r\n"<br>- "RTT: %" PRId64,<br>- ast_sorcery_object_get_id(update),<br>- ast_sip_get_contact_status_label(update->status),<br>- update->rtt);<br>- }<br>-<br>- if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {<br>- ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Initialize the start time on a contact status so the round<br>- * trip time can be calculated upon a valid response.<br>- */<br>-static void init_start_time(const struct ast_sip_contact *contact)<br>-{<br>- RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_sip_contact_status *, update, NULL, ao2_cleanup);<br>-<br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(status));<br>- if (!update) {<br>- ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- update->uri = ast_strdup(contact->uri);<br>- if (!update->uri) {<br>- return;<br>- }<br>-<br>- update->status = status->status;<br>- update->last_status = status->last_status;<br>- update->rtt = status->rtt;<br>- update->rtt_start = ast_tvnow();<br>-<br>- if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {<br>- ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief For an endpoint try to match the given contact->aor.<br>- */<br>-static int on_endpoint(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_endpoint *endpoint = obj;<br>- char *contact_aor = arg;<br>- char *aor_name;<br>- char *aors;<br>-<br>- if (!arg || ast_strlen_zero(endpoint->aors)) {<br>- return 0;<br>- }<br>-<br>- aors = ast_strdupa(endpoint->aors);<br>- while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- if (!strcmp(contact_aor, aor_name)) {<br>- return CMP_MATCH;<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Find an endpoint associated with the given contact.<br>- */<br>-static struct ast_sip_endpoint *find_an_endpoint(struct ast_sip_contact *contact)<br>-{<br>- struct ao2_container *endpoints;<br>- struct ast_sip_endpoint *endpoint;<br>- struct ast_variable *var;<br>- char *aor = ast_alloca(strlen(contact->aor) + 3);<br>-<br>- sprintf(aor, "%%%s%%", contact->aor);<br>- var = ast_variable_new("aors LIKE", aor, "");<br>- endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "endpoint", AST_RETRIEVE_FLAG_MULTIPLE, var);<br>-<br>- ast_variables_destroy(var);<br>-<br>- /*<br>- * Because aors are a string list, we have to use a pattern match but since a simple<br>- * pattern match could return an endpoint that has an aor of "aaabccc" when searching<br>- * for "abc", we still have to iterate over them to find an exact aor match.<br>- */<br>- endpoint = ao2_callback(endpoints, 0, on_endpoint, contact->aor);<br>- ao2_ref(endpoints, -1);<br>-<br>- return endpoint;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Receive a response to the qualify contact request.<br>- */<br>-static void qualify_contact_cb(void *token, pjsip_event *e)<br>-{<br>- struct ast_sip_contact *contact = token;<br>-<br>- switch(e->body.tsx_state.type) {<br>- default:<br>- ast_log(LOG_ERROR, "Unexpected PJSIP event %u\n", e->body.tsx_state.type);<br>- /* Fall through */<br>- case PJSIP_EVENT_TRANSPORT_ERROR:<br>- case PJSIP_EVENT_TIMER:<br>- update_contact_status(contact, UNAVAILABLE, 0);<br>- break;<br>- case PJSIP_EVENT_RX_MSG:<br>- update_contact_status(contact, AVAILABLE, 0);<br>- break;<br>- }<br>- ao2_cleanup(contact);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Attempt to qualify the contact<br>- *<br>- * \details Sends a SIP OPTIONS request to the given contact in order to make<br>- * sure that contact is available.<br>- */<br>-static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact)<br>-{<br>- pjsip_tx_data *tdata;<br>- RAII_VAR(struct ast_sip_endpoint *, endpoint_local, NULL, ao2_cleanup);<br>-<br>- if (endpoint) {<br>- endpoint_local = ao2_bump(endpoint);<br>- } else {<br>- if (!ast_strlen_zero(contact->endpoint_name)) {<br>- endpoint_local = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);<br>- }<br>- if (!endpoint_local) {<br>- endpoint_local = find_an_endpoint(contact);<br>- }<br>- if (!endpoint_local) {<br>- ast_log(LOG_WARNING, "Unable to find an endpoint to qualify contact %s. Deleting this contact\n",<br>- contact->uri);<br>- contact_deleted(contact);<br>- return -1;<br>- }<br>- }<br>-<br>- if (ast_sip_create_request("OPTIONS", NULL, endpoint_local, NULL, contact, &tdata)) {<br>- ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n",<br>- contact->uri);<br>- return -1;<br>- }<br>-<br>- /* If an outbound proxy is specified set it on this request */<br>- if (!ast_strlen_zero(contact->outbound_proxy) &&<br>- ast_sip_set_outbound_proxy(tdata, contact->outbound_proxy)) {<br>- pjsip_tx_data_dec_ref(tdata);<br>- ast_log(LOG_ERROR, "Unable to apply outbound proxy on request to qualify contact %s\n",<br>- contact->uri);<br>- return -1;<br>- }<br>-<br>- init_start_time(contact);<br>-<br>- ao2_ref(contact, +1);<br>- if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)<br>- != PJ_SUCCESS) {<br>- ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",<br>- contact->uri);<br>- update_contact_status(contact, UNAVAILABLE, 0);<br>- ao2_ref(contact, -1);<br>- return -1;<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Scheduling context for sending QUALIFY request at specified intervals.<br>+ * \brief Scheduling context for sending OPTIONS requests at specified intervals.<br> */<br> static struct ast_sched_context *sched;<br> <br> /*!<br> * \internal<br>- * \brief Container to hold all actively scheduled qualifies.<br>+ * \brief Serializer for AOR and endpoint state compositor existence management<br> */<br>-static struct ao2_container *sched_qualifies;<br>-<br>-/*!<br>- * \internal<br>- * \brief Structure to hold qualify contact scheduling information.<br>- */<br>-struct sched_data {<br>- /*! The scheduling id */<br>- int id;<br>- /*! The the contact being checked */<br>- struct ast_sip_contact *contact;<br>-};<br>-<br>-/*!<br>- * \internal<br>- * \brief Destroy the scheduled data and remove from scheduler.<br>- */<br>-static void sched_data_destructor(void *obj)<br>-{<br>- struct sched_data *data = obj;<br>-<br>- ao2_cleanup(data->contact);<br>-}<br>-/*!<br>- * \internal<br>- * \brief Create the scheduling data object.<br>- */<br>-static struct sched_data *sched_data_create(struct ast_sip_contact *contact)<br>-{<br>- struct sched_data *data;<br>-<br>- data = ao2_t_alloc(sizeof(*data), sched_data_destructor, contact->uri);<br>- if (!data) {<br>- ast_log(LOG_ERROR, "Unable to create schedule qualify data for contact %s\n",<br>- contact->uri);<br>- return NULL;<br>- }<br>-<br>- data->contact = contact;<br>- ao2_ref(data->contact, +1);<br>-<br>- return data;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Send a qualify contact request within a threaded task.<br>- */<br>-static int qualify_contact_task(void *obj)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- int res;<br>-<br>- res = qualify_contact(NULL, contact);<br>- ao2_ref(contact, -1);<br>- return res;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Send a scheduled qualify contact request.<br>- */<br>-static int qualify_contact_sched(const void *obj)<br>-{<br>- struct sched_data *data = (struct sched_data *) obj;<br>-<br>- ao2_ref(data->contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) {<br>- ao2_ref(data->contact, -1);<br>- }<br>-<br>- /*<br>- * Always reschedule rather than have a potential race cleaning<br>- * up the data object ref between self deletion and an external<br>- * deletion.<br>- */<br>- return data->contact->qualify_frequency * 1000;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Set up a scheduled qualify contact check.<br>- */<br>-static void schedule_qualify(struct ast_sip_contact *contact, int initial_interval)<br>-{<br>- struct sched_data *data;<br>-<br>- data = sched_data_create(contact);<br>- if (!data) {<br>- return;<br>- }<br>-<br>- ast_assert(contact->qualify_frequency != 0);<br>-<br>- ao2_t_ref(data, +1, "Ref for qualify_contact_sched() scheduler entry");<br>- data->id = ast_sched_add_variable(sched, initial_interval,<br>- qualify_contact_sched, data, 1);<br>- if (data->id < 0) {<br>- ao2_t_ref(data, -1, "Cleanup failed scheduler add");<br>- ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n",<br>- contact->uri);<br>- } else if (!ao2_link(sched_qualifies, data)) {<br>- AST_SCHED_DEL_UNREF(sched, data->id,<br>- ao2_t_ref(data, -1, "Cleanup scheduler for failed ao2_link"));<br>- }<br>- ao2_t_ref(data, -1, "Done setting up scheduler entry");<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Remove the contact from the scheduler.<br>- */<br>-static void unschedule_qualify(struct ast_sip_contact *contact)<br>-{<br>- struct sched_data *data;<br>-<br>- data = ao2_find(sched_qualifies, contact, OBJ_UNLINK | OBJ_SEARCH_KEY);<br>- if (!data) {<br>- return;<br>- }<br>-<br>- AST_SCHED_DEL_UNREF(sched, data->id,<br>- ao2_t_ref(data, -1, "Delete scheduler entry ref"));<br>- ao2_t_ref(data, -1, "Done with ao2_find ref");<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Qualify the given contact and set up scheduling if configured.<br>- */<br>-static void qualify_and_schedule(struct ast_sip_contact *contact)<br>-{<br>- unschedule_qualify(contact);<br>-<br>- if (contact->qualify_frequency) {<br>- ao2_ref(contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, contact)) {<br>- ao2_ref(contact, -1);<br>- }<br>-<br>- schedule_qualify(contact, contact->qualify_frequency * 1000);<br>- } else {<br>- update_contact_status(contact, UNKNOWN, 0);<br>- }<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A new contact has been created make sure it is available.<br>- */<br>-static void contact_created(const void *obj)<br>-{<br>- qualify_and_schedule((struct ast_sip_contact *) obj);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A contact has been updated.<br>- */<br>-static void contact_updated(const void *obj)<br>-{<br>- update_contact_status(obj, AVAILABLE, 1);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A contact has been deleted remove status tracking.<br>- */<br>-static void contact_deleted(const void *obj)<br>-{<br>- struct ast_sip_contact *contact = (struct ast_sip_contact *) obj;<br>- struct ast_sip_contact_status *status;<br>-<br>- unschedule_qualify(contact);<br>-<br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!status) {<br>- return;<br>- }<br>-<br>- if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) {<br>- ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>- ao2_ref(status, -1);<br>-}<br>-<br>-static const struct ast_sorcery_observer contact_observer = {<br>- .created = contact_created,<br>- .updated = contact_updated,<br>- .deleted = contact_deleted,<br>-};<br>-<br>-static pj_bool_t options_start(void)<br>-{<br>- sched = ast_sched_context_create();<br>- if (!sched) {<br>- return -1;<br>- }<br>- if (ast_sched_start_thread(sched)) {<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- return -1;<br>- }<br>-<br>- if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer)) {<br>- ast_log(LOG_WARNING, "Unable to add contact observer\n");<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- return -1;<br>- }<br>-<br>- return PJ_SUCCESS;<br>-}<br>-<br>-static int sched_qualifies_empty(void *obj, void *arg, int flags)<br>-{<br>- ao2_t_ref(obj, -1, "Release ref held by destroyed scheduler context.");<br>- return CMP_MATCH;<br>-}<br>-<br>-static pj_bool_t options_stop(void)<br>-{<br>- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer);<br>-<br>- if (sched) {<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- }<br>-<br>- /* Empty the container of scheduling data refs. */<br>- ao2_callback(sched_qualifies, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,<br>- sched_qualifies_empty, NULL);<br>-<br>- return PJ_SUCCESS;<br>-}<br>+static struct ast_taskprocessor *management_serializer;<br> <br> static pj_status_t send_options_response(pjsip_rx_data *rdata, int code)<br> {<br>@@ -803,102 +265,1573 @@<br> .name = {"Options Module", 14},<br> .id = -1,<br> .priority = PJSIP_MOD_PRIORITY_APPLICATION,<br>- .start = options_start,<br>- .stop = options_stop,<br> .on_rx_request = options_on_rx_request,<br> };<br> <br>-/*!<br>- * \internal<br>- * \brief Send qualify request to the given contact.<br>- */<br>-static int cli_on_contact(void *obj, void *arg, void *data, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_endpoint *endpoint = data;<br>- int *cli_fd = arg;<br>-<br>- ast_cli(*cli_fd, " contact %s\n", contact->uri);<br>- qualify_contact(endpoint, contact);<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \brief Data pushed to threadpool to qualify endpoints from the CLI<br>- */<br>-struct qualify_data {<br>- /*! Endpoint that is being qualified */<br>- struct ast_sip_endpoint *endpoint;<br>- /*! CLI File descriptor for printing messages */<br>- int cli_fd;<br>+static const char *status_map [] = {<br>+ [UNAVAILABLE] = "Unreachable",<br>+ [AVAILABLE] = "Reachable",<br>+ [UNKNOWN] = "Unknown",<br>+ [CREATED] = "Created",<br>+ [REMOVED] = "Removed",<br> };<br> <br>-static struct qualify_data *qualify_data_alloc(struct ast_sip_endpoint *endpoint, int cli_fd)<br>-{<br>- struct qualify_data *qual_data;<br>+static const char *short_status_map [] = {<br>+ [UNAVAILABLE] = "Unavail",<br>+ [AVAILABLE] = "Avail",<br>+ [UNKNOWN] = "Unknown",<br>+ [CREATED] = "Created",<br>+ [REMOVED] = "Removed",<br>+};<br> <br>- qual_data = ast_malloc(sizeof(*qual_data));<br>- if (!qual_data) {<br>+const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)<br>+{<br>+ return status_map[status];<br>+}<br>+<br>+const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status)<br>+{<br>+ return short_status_map[status];<br>+}<br>+<br>+/*! \brief Hashing function for contact statuses */<br>+static int sip_contact_status_hash(const void *obj, const int flags)<br>+{<br>+ const struct ast_sip_contact_status *object;<br>+ const char *key;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_KEY:<br>+ key = obj;<br>+ break;<br>+ case OBJ_SEARCH_OBJECT:<br>+ object = obj;<br>+ key = object->name;<br>+ break;<br>+ default:<br>+ ast_assert(0);<br>+ return 0;<br>+ }<br>+ return ast_str_hash(key);<br>+}<br>+<br>+/*! \brief Comparator function for contact statuses */<br>+static int sip_contact_status_cmp(void *obj, void *arg, int flags)<br>+{<br>+ const struct ast_sip_contact_status *object_left = obj;<br>+ const struct ast_sip_contact_status *object_right = arg;<br>+ const char *right_key = arg;<br>+ int cmp;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_OBJECT:<br>+ right_key = object_right->name;<br>+ /* Fall through */<br>+ case OBJ_SEARCH_KEY:<br>+ cmp = strcmp(object_left->name, right_key);<br>+ break;<br>+ case OBJ_SEARCH_PARTIAL_KEY:<br>+ cmp = strncmp(object_left->name, right_key, strlen(right_key));<br>+ break;<br>+ default:<br>+ cmp = 0;<br>+ break;<br>+ }<br>+ if (cmp) {<br>+ return 0;<br>+ }<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Destructor for contact statuses */<br>+static void sip_contact_status_destroy(void *obj)<br>+{<br>+ struct ast_sip_contact_status *contact_status = obj;<br>+<br>+ ast_free(contact_status->aor);<br>+ ast_free(contact_status->uri);<br>+}<br>+<br>+struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact)<br>+{<br>+ struct ast_sip_contact_status *contact_status;<br>+ size_t size = sizeof(*contact_status) + strlen(ast_sorcery_object_get_id(contact)) + 1;<br>+<br>+ /* At startup it is possible for contact status to be retrieved before we are ready, if this occurs then<br>+ * allocate the container here. Since we don't actually trigger qualify or anything as a result it is safe<br>+ * to do so. They'll just get back a contact status that will be updated later.<br>+ */<br>+ if (!sip_options_contact_statuses) {<br>+ sip_options_contact_statuses = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, CONTACT_BUCKETS,<br>+ sip_contact_status_hash, NULL, sip_contact_status_cmp);<br>+ if (!sip_options_contact_statuses) {<br>+ return NULL;<br>+ }<br>+ }<br>+<br>+ ao2_lock(sip_options_contact_statuses);<br>+<br>+ /* If contact status for this contact already exists just return it */<br>+ contact_status = ao2_find(sip_options_contact_statuses, ast_sorcery_object_get_id(contact), OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>+ if (contact_status) {<br>+ ao2_unlock(sip_options_contact_statuses);<br>+ return contact_status;<br>+ }<br>+<br>+ /* Otherwise we have to create and store a new contact status */<br>+ contact_status = ao2_alloc(size, sip_contact_status_destroy);<br>+ if (!contact_status) {<br>+ ao2_unlock(sip_options_contact_statuses);<br> return NULL;<br> }<br> <br>- qual_data->endpoint = ao2_bump(endpoint);<br>- qual_data->cli_fd = cli_fd;<br>- return qual_data;<br>+ /* This will get later updated by the result of the OPTIONS request<br>+ * or it will forever stay in CREATED.<br>+ */<br>+ contact_status->status = CREATED;<br>+ contact_status->aor = ast_strdup(contact->aor);<br>+ contact_status->uri = ast_strdup(contact->uri);<br>+ if (!contact_status->aor || !contact_status->uri) {<br>+ ao2_unlock(sip_options_contact_statuses);<br>+ ao2_ref(contact_status, -1);<br>+ return NULL;<br>+ }<br>+<br>+ contact_status->rtt = 0;<br>+ strcpy(contact_status->name, ast_sorcery_object_get_id(contact)); /* SAFE */<br>+<br>+ ao2_link_flags(sip_options_contact_statuses, contact_status, OBJ_NOLOCK);<br>+ ao2_unlock(sip_options_contact_statuses);<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>+<br>+ return contact_status;<br> }<br> <br>-static void qualify_data_destroy(struct qualify_data *qual_data)<br>+struct ast_sip_contact_status *ast_sip_get_contact_status(const struct ast_sip_contact *contact)<br> {<br>- ao2_cleanup(qual_data->endpoint);<br>- ast_free(qual_data);<br>+ return ao2_find(sip_options_contact_statuses, ast_sorcery_object_get_id(contact), OBJ_SEARCH_KEY);<br> }<br> <br>-/*!<br>- * \internal<br>- * \brief For an endpoint iterate over and qualify all aors/contacts<br>- */<br>-static int cli_qualify_contacts(void *data)<br>+/*! \brief Hashing function for OPTIONS AORs */<br>+static int sip_options_aor_hash(const void *obj, const int flags)<br> {<br>- char *aors;<br>- char *aor_name;<br>- RAII_VAR(struct qualify_data *, qual_data, data, qualify_data_destroy);<br>- struct ast_sip_endpoint *endpoint = qual_data->endpoint;<br>- int cli_fd = qual_data->cli_fd;<br>- const char *endpoint_name = ast_sorcery_object_get_id(endpoint);<br>+ const struct sip_options_aor *object;<br>+ const char *key;<br> <br>- if (ast_strlen_zero(endpoint->aors)) {<br>- ast_cli(cli_fd, "Endpoint %s has no AoR's configured\n",<br>- endpoint_name);<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_KEY:<br>+ key = obj;<br>+ break;<br>+ case OBJ_SEARCH_OBJECT:<br>+ object = obj;<br>+ key = object->name;<br>+ break;<br>+ default:<br>+ ast_assert(0);<br>+ return 0;<br>+ }<br>+ return ast_str_hash(key);<br>+}<br>+<br>+/*! \brief Comparator function for SIP OPTIONS AORs */<br>+static int sip_options_aor_cmp(void *obj, void *arg, int flags)<br>+{<br>+ const struct sip_options_aor *object_left = obj;<br>+ const struct sip_options_aor *object_right = arg;<br>+ const char *right_key = arg;<br>+ int cmp;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_OBJECT:<br>+ right_key = object_right->name;<br>+ /* Fall through */<br>+ case OBJ_SEARCH_KEY:<br>+ cmp = strcmp(object_left->name, right_key);<br>+ break;<br>+ case OBJ_SEARCH_PARTIAL_KEY:<br>+ cmp = strncmp(object_left->name, right_key, strlen(right_key));<br>+ break;<br>+ default:<br>+ cmp = 0;<br>+ break;<br>+ }<br>+ if (cmp) {<br>+ return 0;<br>+ }<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Forward declaration of this helpful function */<br>+static int sip_options_remove_contact(void *obj, void *arg, int flags);<br>+<br>+/*! \brief Destructor function for SIP OPTIONS AORs */<br>+static void sip_options_aor_destroy(void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ /* Any contacts are unreachable since the AOR is being destroyed so remove their contact status */<br>+ ao2_callback(aor_options->permanent_contacts, OBJ_NODATA | OBJ_UNLINK, sip_options_remove_contact, aor_options);<br>+ ao2_callback(aor_options->dynamic_contacts, OBJ_NODATA | OBJ_UNLINK, sip_options_remove_contact, aor_options);<br>+<br>+ ast_taskprocessor_unreference(aor_options->serializer);<br>+ AST_VECTOR_FREE(&aor_options->compositors);<br>+ ao2_cleanup(aor_options->permanent_contacts);<br>+ ao2_cleanup(aor_options->dynamic_contacts);<br>+}<br>+<br>+/*! \brief Hashing function for contacts */<br>+static int sip_contact_hash(const void *obj, const int flags)<br>+{<br>+ const struct ast_sip_contact *object;<br>+ const char *key;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_KEY:<br>+ key = obj;<br>+ break;<br>+ case OBJ_SEARCH_OBJECT:<br>+ object = obj;<br>+ key = ast_sorcery_object_get_id(object);<br>+ break;<br>+ default:<br>+ ast_assert(0);<br>+ return 0;<br>+ }<br>+ return ast_str_hash(key);<br>+}<br>+<br>+/*! \brief Comparator function for contacts */<br>+static int sip_contact_cmp(void *obj, void *arg, int flags)<br>+{<br>+ const struct ast_sip_contact *object_left = obj;<br>+ const struct ast_sip_contact *object_right = arg;<br>+ const char *right_key = arg;<br>+ int cmp;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_OBJECT:<br>+ right_key = ast_sorcery_object_get_id(object_right);<br>+ /* Fall through */<br>+ case OBJ_SEARCH_KEY:<br>+ cmp = strcmp(ast_sorcery_object_get_id(object_left), right_key);<br>+ break;<br>+ case OBJ_SEARCH_PARTIAL_KEY:<br>+ cmp = strncmp(ast_sorcery_object_get_id(object_left), right_key, strlen(right_key));<br>+ break;<br>+ default:<br>+ cmp = 0;<br>+ break;<br>+ }<br>+ if (cmp) {<br>+ return 0;<br>+ }<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Hashing function for endpoint state compositors */<br>+static int sip_endpoint_state_compositor_hash(const void *obj, const int flags)<br>+{<br>+ const struct sip_options_endpoint_state_compositor *object;<br>+ const char *key;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_KEY:<br>+ key = obj;<br>+ break;<br>+ case OBJ_SEARCH_OBJECT:<br>+ object = obj;<br>+ key = object->name;<br>+ break;<br>+ default:<br>+ ast_assert(0);<br>+ return 0;<br>+ }<br>+ return ast_str_hash(key);<br>+}<br>+<br>+/*! \brief Comparator function for endpoint state compositors */<br>+static int sip_endpoint_state_compositor_cmp(void *obj, void *arg, int flags)<br>+{<br>+ const struct sip_options_endpoint_state_compositor *object_left = obj;<br>+ const struct sip_options_endpoint_state_compositor *object_right = arg;<br>+ const char *right_key = arg;<br>+ int cmp;<br>+<br>+ switch (flags & OBJ_SEARCH_MASK) {<br>+ case OBJ_SEARCH_OBJECT:<br>+ right_key = object_right->name;<br>+ /* Fall through */<br>+ case OBJ_SEARCH_KEY:<br>+ cmp = strcmp(object_left->name, right_key);<br>+ break;<br>+ case OBJ_SEARCH_PARTIAL_KEY:<br>+ cmp = strncmp(object_left->name, right_key, strlen(right_key));<br>+ break;<br>+ default:<br>+ cmp = 0;<br>+ break;<br>+ }<br>+ if (cmp) {<br>+ return 0;<br>+ }<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Structure used to contain information for an OPTIONS callback */<br>+struct sip_options_contact_callback_data {<br>+ /*! \brief The contact we qualified */<br>+ struct ast_sip_contact *contact;<br>+ /*! \brief The AOR options */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The contact status this relates to */<br>+ struct ast_sip_contact_status *contact_status;<br>+ /*! \brief The time at which this OPTIONS attempt was started */<br>+ struct timeval rtt_start;<br>+ /*! \brief The new status of the contact */<br>+ enum ast_sip_contact_status_type status;<br>+};<br>+<br>+/*! \brief Function which notifies endpoint state compositors of a state change of an AOR */<br>+static void sip_options_notify_endpoint_state_compositors(struct sip_options_aor *aor_options,<br>+ enum ast_sip_contact_status_type status)<br>+{<br>+ int i;<br>+<br>+ /* Iterate through the associated endpoint state compositors updating them */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+<br>+ /* During synchronization we can still have a reference to an endpoint state compositor but<br>+ * it may be in a state where all state updates to it are suspended. We know this because it<br>+ * will have no AORs feeding it.<br>+ */<br>+ if (ao2_container_count(endpoint_state_compositor->aors)) {<br>+ if (status == AVAILABLE) {<br>+ endpoint_state_compositor->available++;<br>+<br>+ /* If we were previously unavailable change the underlying endpoint state */<br>+ if (endpoint_state_compositor->available == 1) {<br>+ ast_debug(3, "Endpoint state compositor '%s' has become available, updating endpoint state\n",<br>+ endpoint_state_compositor->name);<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name, AST_ENDPOINT_ONLINE);<br>+ }<br>+ } else if (status == UNAVAILABLE) {<br>+ endpoint_state_compositor->available--;<br>+<br>+ /* If we were previously available change the underlying endpoint state */<br>+ if (!endpoint_state_compositor->available) {<br>+ ast_debug(3, "Endpoint state compositor '%s' has become unavailable, updating endpoint state\n",<br>+ endpoint_state_compositor->name);<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name, AST_ENDPOINT_OFFLINE);<br>+ }<br>+ }<br>+<br>+ ast_debug(3, "Endpoint state compositor '%s' now has %d available AORs\n", endpoint_state_compositor->name,<br>+ endpoint_state_compositor->available);<br>+ }<br>+<br>+ ao2_unlock(endpoint_state_compositor);<br>+ }<br>+}<br>+<br>+/*! \brief Function which publishes a contact status update to all interested endpoints */<br>+static void sip_options_publish_contact_state(struct sip_options_aor *aor_options,<br>+ const struct ast_sip_contact_status *contact_status)<br>+{<br>+ int i;<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&aor_options->compositors, i);<br>+<br>+ ast_sip_persistent_endpoint_publish_contact_state(endpoint_state_compositor->name, contact_status);<br>+ }<br>+}<br>+<br>+/*! \brief Task to notify an AOR of a contact status change */<br>+static int sip_options_contact_status_notify_task(void *obj)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = obj;<br>+ struct ast_sip_contact *contact;<br>+<br>+ /* Determine if this is a late arriving notification, as it is possible that we get<br>+ * a callback from PJSIP giving us contact status but in the mean time said contact<br>+ * has been removed from the underlying AOR.<br>+ */<br>+ if (!contact_callback_data->aor_options->qualify_frequency) {<br> return 0;<br> }<br> <br>- aors = ast_strdupa(endpoint->aors);<br>- while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- struct ast_sip_aor *aor;<br>+ contact = ao2_find(contact_callback_data->aor_options->permanent_contacts, contact_callback_data->contact, OBJ_SEARCH_OBJECT);<br>+ if (!contact) {<br>+ contact = ao2_find(contact_callback_data->aor_options->dynamic_contacts, contact_callback_data->contact, OBJ_SEARCH_OBJECT);<br>+ if (!contact) {<br>+ return 0;<br>+ }<br>+ }<br>+ ao2_ref(contact, -1);<br>+<br>+ /* Update the contact specific status information */<br>+ contact_callback_data->contact_status->last_status = contact_callback_data->contact_status->status;<br>+ contact_callback_data->contact_status->status = contact_callback_data->status;<br>+ contact_callback_data->contact_status->rtt = contact_callback_data->contact_status->status == AVAILABLE<br>+ && contact_callback_data->rtt_start.tv_sec > 0<br>+ ? ast_tvdiff_us(ast_tvnow(), contact_callback_data->rtt_start)<br>+ : 0;<br>+<br>+ /* If the status has changed then notify the endpoint state compositors and publish our events. */<br>+ if (contact_callback_data->contact_status->last_status != contact_callback_data->contact_status->status) {<br>+ if (contact_callback_data->contact_status->status == AVAILABLE) {<br>+ /* If this is the first available contact then the AOR has become available */<br>+ contact_callback_data->aor_options->available++;<br>+ if (contact_callback_data->aor_options->available == 1) {<br>+ sip_options_notify_endpoint_state_compositors(contact_callback_data->aor_options, AVAILABLE);<br>+ }<br>+ } else if (contact_callback_data->contact_status->last_status == AVAILABLE &&<br>+ contact_callback_data->contact_status->status == UNAVAILABLE) {<br>+ /* If there are no more available contacts then this AOR is unavailable */<br>+ contact_callback_data->aor_options->available--;<br>+ if (!contact_callback_data->aor_options->available) {<br>+ sip_options_notify_endpoint_state_compositors(contact_callback_data->aor_options, UNAVAILABLE);<br>+ }<br>+ }<br>+<br>+ sip_options_publish_contact_state(contact_callback_data->aor_options, contact_callback_data->contact_status);<br>+<br>+ ast_verb(3, "Contact %s/%s is now %s. RTT: %.3f msec\n",<br>+ contact_callback_data->contact_status->aor,contact_callback_data->contact_status->uri,<br>+ ast_sip_get_contact_status_label(contact_callback_data->contact_status->status),<br>+ contact_callback_data->contact_status->rtt / 1000.0);<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "-1", 1.0, ast_sip_get_contact_status_label(contact_callback_data->contact_status->last_status));<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(contact_callback_data->contact_status->status));<br>+<br>+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",<br>+ "Contact: %s\r\n"<br>+ "Status: %s",<br>+ contact_callback_data->contact_status->name,<br>+ ast_sip_get_contact_status_label(contact_callback_data->contact_status->status));<br>+ } else {<br>+ ast_debug(3, "Contact %s/%s status didn't change: %s, RTT: %.3f msec\n",<br>+ contact_callback_data->contact_status->aor, contact_callback_data->contact_status->uri,<br>+ ast_sip_get_contact_status_label(contact_callback_data->contact_status->status),<br>+ contact_callback_data->contact_status->rtt / 1000.0);<br>+ }<br>+<br>+ ast_statsd_log_full_va("PJSIP.contacts.%s.rtt", AST_STATSD_TIMER,<br>+ contact_callback_data->contact_status->status != AVAILABLE ? -1 : contact_callback_data->contact_status->rtt / 1000,<br>+ 1.0,<br>+ contact_callback_data->contact_status->name);<br>+<br>+ ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",<br>+ "Contact: %s\r\n"<br>+ "Status: %s\r\n"<br>+ "RTT: %" PRId64,<br>+ contact_callback_data->contact_status->name,<br>+ ast_sip_get_contact_status_label(contact_callback_data->contact_status->status),<br>+ contact_callback_data->contact_status->rtt);<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n", contact_callback_data->aor_options->name,<br>+ contact_callback_data->aor_options->available);<br>+<br>+ ao2_ref(contact_callback_data, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Callback for when we get a result from a SIP OPTIONS request (a response or a timeout) */<br>+static void qualify_contact_cb(void *token, pjsip_event *e)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = token;<br>+ enum ast_sip_contact_status_type status;<br>+<br>+ switch(e->body.tsx_state.type) {<br>+ default:<br>+ ast_log(LOG_ERROR, "Unexpected PJSIP event %u\n", e->body.tsx_state.type);<br>+ /* Fall through */<br>+ case PJSIP_EVENT_TRANSPORT_ERROR:<br>+ case PJSIP_EVENT_TIMER:<br>+ status = UNAVAILABLE;<br>+ break;<br>+ case PJSIP_EVENT_RX_MSG:<br>+ status = AVAILABLE;<br>+ break;<br>+ }<br>+<br>+ /* Update the callback data with the new status, this will get handled in the AOR serializer */<br>+ contact_callback_data->status = status;<br>+<br>+ if (ast_sip_push_task(contact_callback_data->aor_options->serializer, sip_options_contact_status_notify_task,<br>+ contact_callback_data)) {<br>+ ast_log(LOG_NOTICE, "Unable to queue contact status update for '%s' on AOR '%s', state will be incorrect\n",<br>+ contact_callback_data->contact_status->name, contact_callback_data->aor_options->name);<br>+ ao2_ref(contact_callback_data, -1);<br>+ }<br>+<br>+ /* The task inherited our reference so we don't unreference here */<br>+}<br>+<br>+/*! \brief Destructor for contact callback data */<br>+static void sip_options_contact_callback_data_destroy(void *obj)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = obj;<br>+<br>+ ao2_cleanup(contact_callback_data->contact);<br>+ ao2_cleanup(contact_callback_data->aor_options);<br>+ ao2_cleanup(contact_callback_data->contact_status);<br>+}<br>+<br>+/*! \brief Contact callback data allocator */<br>+static struct sip_options_contact_callback_data *sip_options_contact_callback_data_alloc(<br>+ struct ast_sip_contact *contact, struct sip_options_aor *aor_options,<br>+ struct ast_sip_contact_status *contact_status)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data;<br>+<br>+ contact_callback_data = ao2_alloc_options(sizeof(*contact_callback_data), sip_options_contact_callback_data_destroy,<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!contact_callback_data) {<br>+ return NULL;<br>+ }<br>+<br>+ contact_callback_data->contact = ao2_bump(contact);<br>+ contact_callback_data->aor_options = ao2_bump(aor_options);<br>+ contact_callback_data->contact_status = ao2_bump(contact_status);<br>+ contact_callback_data->rtt_start = ast_tvnow();<br>+<br>+ return contact_callback_data;<br>+}<br>+<br>+/*! \brief Send a SIP OPTIONS request for a contact */<br>+static int sip_options_qualify_contact(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct sip_options_aor *aor_options = arg;<br>+ struct ast_sip_endpoint *endpoint = NULL;<br>+ pjsip_tx_data *tdata;<br>+ struct ast_sip_contact_status *contact_status;<br>+ struct sip_options_contact_callback_data *contact_callback_data;<br>+<br>+ ast_debug(3, "Qualifying contact '%s' on AOR '%s'\n", ast_sorcery_object_get_id(contact), aor_options->name);<br>+<br>+ if (!ast_strlen_zero(contact->endpoint_name)) {<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);<br>+ }<br>+ if (!endpoint && AST_VECTOR_SIZE(&aor_options->compositors)) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&aor_options->compositors, 0);<br>+<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_state_compositor->name);<br>+ }<br>+ if (!endpoint) {<br>+ return 0;<br>+ }<br>+<br>+ if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, contact, &tdata)) {<br>+ ast_log(LOG_ERROR, "Unable to create request to qualify contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ ao2_ref(endpoint, -1);<br>+ return 0;<br>+ }<br>+<br>+ /* If an outbound proxy is specified set it on this request */<br>+ if (!ast_strlen_zero(contact->outbound_proxy) &&<br>+ ast_sip_set_outbound_proxy(tdata, contact->outbound_proxy)) {<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ ast_log(LOG_ERROR, "Unable to apply outbound proxy on request to qualify contact %s\n",<br>+ contact->uri);<br>+ return 0;<br>+ }<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ if (!contact_status) {<br>+ ast_log(LOG_ERROR, "Unable to retrieve contact status information for contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ ao2_ref(endpoint, -1);<br>+ return 0;<br>+ }<br>+<br>+ contact_callback_data = sip_options_contact_callback_data_alloc(contact, aor_options, contact_status);<br>+ if (!contact_callback_data) {<br>+ ast_log(LOG_ERROR, "Unable to create object to contain callback data for contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ ao2_ref(endpoint, -1);<br>+ ao2_ref(contact_status, -1);<br>+ return 0;<br>+ }<br>+<br>+ ao2_ref(contact_status, -1);<br>+<br>+ if (ast_sip_send_out_of_dialog_request(tdata, endpoint, (int)(aor_options->qualify_timeout * 1000), contact_callback_data, qualify_contact_cb) != PJ_SUCCESS) {<br>+ ast_log(LOG_ERROR, "Unable to send request to qualify contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ ao2_ref(contact_callback_data, -1);<br>+ ao2_ref(endpoint, -1);<br>+ return 0;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Task to qualify contacts of an AOR */<br>+static int sip_options_qualify_aor_task(void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ ast_debug(3, "Qualifying all contacts on AOR '%s'\n", aor_options->name);<br>+<br>+ /* Attempt to send an OPTIONS request to every contact on this AOR */<br>+ ao2_callback(aor_options->permanent_contacts, OBJ_NODATA, sip_options_qualify_contact, (struct sip_options_aor *)aor_options);<br>+ ao2_callback(aor_options->dynamic_contacts, OBJ_NODATA, sip_options_qualify_contact, (struct sip_options_aor *)aor_options);<br>+<br>+ ao2_ref(aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Scheduler callback for qualifying contacts of an AOR */<br>+static int sip_options_qualify_aor(const void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = (struct sip_options_aor *)obj;<br>+<br>+ if (ast_sip_push_task(aor_options->serializer, sip_options_qualify_aor_task, ao2_bump(aor_options))) {<br>+ ao2_ref(aor_options, -1);<br>+ }<br>+<br>+ /* Always reschedule to the frequency we should go */<br>+ return aor_options->qualify_frequency * 1000;<br>+}<br>+<br>+/*! \brief Allocator for AOR OPTIONS */<br>+static struct sip_options_aor *sip_options_aor_alloc(struct ast_sip_aor *aor)<br>+{<br>+ struct sip_options_aor *aor_options;<br>+ char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];<br>+<br>+ aor_options = ao2_alloc_options(sizeof(*aor_options) + strlen(ast_sorcery_object_get_id(aor)) + 1, sip_options_aor_destroy,<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!aor_options) {<br>+ return NULL;<br>+ }<br>+<br>+ ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/options/%s", ast_sorcery_object_get_id(aor));<br>+ aor_options->serializer = ast_sip_create_serializer_named(tps_name);<br>+ if (!aor_options->serializer) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ if (AST_VECTOR_INIT(&aor_options->compositors, ENDPOINT_STATE_COMPOSITOR_INITIAL_SIZE)) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ aor_options->permanent_contacts = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, CONTACT_BUCKETS,<br>+ sip_contact_hash, NULL, sip_contact_cmp);<br>+ if (!aor_options->permanent_contacts) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ aor_options->dynamic_contacts = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, CONTACT_BUCKETS,<br>+ sip_contact_hash, NULL, sip_contact_cmp);<br>+ if (!aor_options->dynamic_contacts) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ aor_options->sched_id = -1;<br>+ strcpy(aor_options->name, ast_sorcery_object_get_id(aor)); /* SAFE */<br>+<br>+ return aor_options;<br>+}<br>+<br>+/*! \brief Remove contact status for a hint */<br>+static void sip_options_remove_contact_status(struct sip_options_aor *aor_options,<br>+ struct ast_sip_contact *contact)<br>+{<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ contact_status = ao2_find(sip_options_contact_statuses, ast_sorcery_object_get_id(contact), OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!contact_status) {<br>+ ast_debug(3, "Attempted to remove contact status for '%s' but it does not exist\n",<br>+ ast_sorcery_object_get_id(contact));<br>+ return;<br>+ }<br>+<br>+ ast_verb(2, "Contact %s/%s has been deleted\n", contact->aor, contact->uri);<br>+<br>+ /* Update the contact status to reflect its new state */<br>+ contact_status->last_status = contact_status->status;<br>+ contact_status->status = REMOVED;<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "-1", 1.0, ast_sip_get_contact_status_label(contact_status->last_status));<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>+<br>+ sip_options_publish_contact_state(aor_options, contact_status);<br>+<br>+ /* The only time we need to update the AOR is if this contact was available and qualify<br>+ * is in use, otherwise we can just stop early.<br>+ */<br>+ if (!aor_options->qualify_frequency || (contact_status->last_status != AVAILABLE)) {<br>+ ao2_ref(contact_status, -1);<br>+ return;<br>+ }<br>+<br>+ aor_options->available--;<br>+ if (!aor_options->available) {<br>+ sip_options_notify_endpoint_state_compositors(aor_options, UNAVAILABLE);<br>+ }<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n", aor_options->name,<br>+ aor_options->available);<br>+<br>+ ao2_ref(contact_status, -1);<br>+}<br>+<br>+/*! \brief Task data for AOR creation or updating */<br>+struct sip_options_synchronize_aor_task_data {<br>+ /*! \brief The AOR options for this AOR */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The AOR which contains the new configuraton */<br>+ struct ast_sip_aor *aor;<br>+ /*! \brief Optional container of existing AOR s*/<br>+ struct ao2_container *existing;<br>+ /*! \brief Whether this AOR is being added */<br>+ int added;<br>+};<br>+<br>+/*! \brief Callback function to remove a contact and its contact status from an AOR */<br>+static int sip_options_remove_contact(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct sip_options_aor *aor_options = arg;<br>+<br>+ sip_options_remove_contact_status(aor_options, contact);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Determine an initial time for scheduling AOR qualifying */<br>+static int sip_options_determine_initial_qualify_time(int qualify_frequency)<br>+{<br>+ int initial_interval;<br>+ int max_time = ast_sip_get_max_initial_qualify_time();<br>+<br>+ if (max_time && max_time < qualify_frequency) {<br>+ initial_interval = max_time;<br>+ } else {<br>+ initial_interval = qualify_frequency;<br>+ }<br>+<br>+ return (int)((initial_interval * 1000) * ast_random_double());<br>+}<br>+<br>+/*! \brief Function which applies configuration to an AOR options structure */<br>+static int sip_options_apply_aor_configuration(struct sip_options_aor *aor_options, struct ast_sip_aor *aor,<br>+ int new)<br>+{<br>+ struct ao2_container *existing_permanent_contacts;<br>+<br>+ ast_debug(3, "Configuring AOR '%s' with current state of configuration and world\n",<br>+ aor_options->name);<br>+<br>+ /* Permanent contacts, since we receive no notification that they are gone, follow the same<br>+ * approach as AORs. We create a copy of the existing container and any reused contacts are<br>+ * removed from it. Any contacts remaining in the container are no longer configured and we<br>+ * need to remove their state.<br>+ */<br>+ existing_permanent_contacts = ao2_container_clone(aor_options->permanent_contacts, 0);<br>+ if (!existing_permanent_contacts) {<br>+ ast_log(LOG_WARNING, "Synchronization of AOR '%s' failed for qualify, retaining existing state\n",<br>+ aor_options->name);<br>+ return -1;<br>+ }<br>+<br>+ ao2_callback(aor_options->permanent_contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, NULL, NULL);<br>+<br>+ if (aor->permanent_contacts) {<br>+ struct ao2_iterator it_contacts;<br>+ struct ast_sip_contact *contact;<br>+<br>+ it_contacts = ao2_iterator_init(aor->permanent_contacts, 0);<br>+ for (; (contact = ao2_iterator_next(&it_contacts)); ao2_ref(contact, -1)) {<br>+ ao2_link(aor_options->permanent_contacts, contact);<br>+ ao2_find(existing_permanent_contacts, ast_sorcery_object_get_id(contact), OBJ_NODATA | OBJ_UNLINK);<br>+ }<br>+ ao2_iterator_destroy(&it_contacts);<br>+ }<br>+<br>+ /* Any contact left is no longer configured, so raise events and make it disappear */<br>+ ao2_callback(existing_permanent_contacts, OBJ_NODATA | OBJ_UNLINK, sip_options_remove_contact, aor_options);<br>+ ao2_ref(existing_permanent_contacts, -1);<br>+<br>+ /* If this is newly added we need to see if there are any existing dynamic contacts<br>+ * to add. Ones that are added after creation will occur as a result of the contact<br>+ * observer creation callback.<br>+ */<br>+ if (new) {<br>+ size_t prefix_len = strlen(ast_sorcery_object_get_id(aor)) + sizeof(";@") - 1;<br>+ char prefix[prefix_len + 1];<br> struct ao2_container *contacts;<br> <br>- aor = ast_sip_location_retrieve_aor(aor_name);<br>- if (!aor) {<br>+ sprintf(prefix, "%s;@", ast_sorcery_object_get_id(aor)); /* Safe */<br>+ contacts = ast_sorcery_retrieve_by_prefix(ast_sip_get_sorcery(), "contact", prefix, prefix_len);<br>+<br>+ if (contacts) {<br>+ ao2_container_dup(aor_options->dynamic_contacts, contacts, 0);<br>+ ao2_ref(contacts, -1);<br>+ }<br>+ }<br>+<br>+ /* Update the available count if we transition between qualified and unqualified. In the qualified case<br>+ * we need to start with 0 available as the qualify process will take care of it. In the unqualified<br>+ * case it is based on the number of contacts present.<br>+ */<br>+ if (!aor->qualify_frequency) {<br>+ aor_options->available = ao2_container_count(aor_options->dynamic_contacts) +<br>+ ao2_container_count(aor_options->permanent_contacts);<br>+ ast_debug(3, "AOR '%s' is unqualified, number of available contacts is therefore '%d'\n",<br>+ aor_options->name, aor_options->available);<br>+ } else if (!aor_options->qualify_frequency && aor->qualify_frequency) {<br>+ aor_options->available = 0;<br>+ ast_debug(3, "AOR '%s' has transitioned from unqualified to qualified, reset available contacts to 0\n",<br>+ aor_options->name);<br>+ }<br>+<br>+ /* If we need to stop or start the scheduled callback then do so. This occurs due to the following:<br>+ * 1. The qualify frequency has changed<br>+ * 2. Contacts were added when previously there were none<br>+ * 3. There are no contacts but previously there were some<br>+ */<br>+ if ((aor_options->qualify_frequency != aor->qualify_frequency) ||<br>+ (aor_options->sched_id == -1 && (ao2_container_count(aor_options->permanent_contacts) ||<br>+ ao2_container_count(aor_options->dynamic_contacts))) ||<br>+ (aor_options->sched_id != -1 && (ao2_container_count(aor_options->permanent_contacts) &&<br>+ ao2_container_count(aor_options->dynamic_contacts)))) {<br>+ AST_SCHED_DEL_UNREF(sched, aor_options->sched_id, ao2_t_ref(aor_options, -1, "Delete scheduler entry ref"));<br>+<br>+ /* If there is still a qualify frequency then schedule this */<br>+ if (aor->qualify_frequency && (ao2_container_count(aor_options->permanent_contacts) ||<br>+ ao2_container_count(aor_options->dynamic_contacts))) {<br>+ aor_options->sched_id = ast_sched_add_variable(sched, sip_options_determine_initial_qualify_time(aor->qualify_frequency),<br>+ sip_options_qualify_aor, ao2_bump(aor_options), 1);<br>+ if (aor_options->sched_id < 0) {<br>+ ao2_t_ref(aor_options, -1, "Cleanup failed scheduler add");<br>+ ast_log(LOG_ERROR, "Unable to schedule qualify for contacts of AOR '%s'\n", aor_options->name);<br>+ }<br>+ }<br>+ }<br>+<br>+ /* Update the AOR information with the state */<br>+ aor_options->qualify_frequency = aor->qualify_frequency;<br>+ aor_options->qualify_timeout = aor->qualify_timeout;<br>+ aor_options->authenticate_qualify = aor->authenticate_qualify;<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n", aor_options->name,<br>+ aor_options->available);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Task to synchronize an AOR with our local state */<br>+static int sip_options_synchronize_aor_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_aor_task_data *task_data = obj;<br>+ int i;<br>+<br>+ ast_debug(3, "Synchronizing AOR '%s' with current state of configuration and world\n",<br>+ task_data->aor_options->name);<br>+<br>+ sip_options_apply_aor_configuration(task_data->aor_options, task_data->aor, task_data->added);<br>+<br>+ /* Endpoint state compositors are removed in this operation but not added. To reduce the amount of<br>+ * work done they are done later. In the mean time things can still qualify and once an endpoint<br>+ * state compositor is added to the AOR it will be updated with the current state.<br>+ */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&task_data->aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&task_data->aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+<br>+ /* This purposely does not notify the endpoint state compositor to prevent flapping from<br>+ * available, to unavailable, to available. Instead we just change it to a fresh state which<br>+ * prevents all other AORs from updating it as well. When endpoint state compositor mappings<br>+ * are re-established it will be updated to reflect the correct state. This will cause a notify<br>+ * to go to the endpoint state compositor but the upper level endpoint state implementation<br>+ * will ignore it unless the state has actually changed.<br>+ */<br>+ ao2_callback(endpoint_state_compositor->aors, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, NULL, NULL);<br>+ endpoint_state_compositor->available = 0;<br>+<br>+ ao2_unlock(endpoint_state_compositor);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ }<br>+ AST_VECTOR_RESET(&task_data->aor_options->compositors, AST_VECTOR_ELEM_CLEANUP_NOOP);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Synchronize an AOR with our local state */<br>+static int sip_options_synchronize_aor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_synchronize_aor_task_data task_data = { .aor = obj, .existing = arg, };<br>+<br>+ task_data.aor_options = ao2_find(sip_options_aors, ast_sorcery_object_get_id(task_data.aor), OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ task_data.aor_options = sip_options_aor_alloc(task_data.aor);<br>+ if (!task_data.aor_options) {<br>+ return 0;<br>+ }<br>+<br>+ task_data.added = 1;<br>+ }<br>+<br>+ if (task_data.added) {<br>+ /* Nothing is aware of this AOR yet so we can just update it in this thread */<br>+ sip_options_synchronize_aor_task(&task_data);<br>+ ao2_link(sip_options_aors, task_data.aor_options);<br>+ } else {<br>+ /* This AOR already exists so we have to do manipulation in its serializer */<br>+ ast_sip_push_task_synchronous(task_data.aor_options->serializer, sip_options_synchronize_aor_task, &task_data);<br>+ }<br>+<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ if (task_data.existing) {<br>+ ao2_find(task_data.existing, (char *)ast_sorcery_object_get_id(task_data.aor), OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Destructor for endpoint state compositors */<br>+static void sip_options_endpoint_state_compositor_destroy(void *obj)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor = obj;<br>+<br>+ ao2_cleanup(endpoint_state_compositor->aors);<br>+}<br>+<br>+/*! \brief Find (or create) an endpoint state compositor */<br>+static struct sip_options_endpoint_state_compositor *sip_options_endpoint_state_compositor_find_or_alloc(const struct ast_sip_endpoint *endpoint)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ ao2_lock(sip_options_endpoint_state_compositors);<br>+ endpoint_state_compositor = ao2_find(sip_options_endpoint_state_compositors, ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>+ if (endpoint_state_compositor) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ return endpoint_state_compositor;<br>+ }<br>+<br>+ endpoint_state_compositor = ao2_alloc(sizeof(*endpoint_state_compositor) + strlen(ast_sorcery_object_get_id(endpoint)) + 1,<br>+ sip_options_endpoint_state_compositor_destroy);<br>+ if (!endpoint_state_compositor) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ return NULL;<br>+ }<br>+<br>+ endpoint_state_compositor->aors = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1);<br>+ if (!endpoint_state_compositor->aors) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ return NULL;<br>+ }<br>+<br>+ strcpy(endpoint_state_compositor->name, ast_sorcery_object_get_id(endpoint)); /* SAFE */<br>+<br>+ ao2_link_flags(sip_options_endpoint_state_compositors, endpoint_state_compositor, OBJ_NOLOCK);<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+<br>+ return endpoint_state_compositor;<br>+}<br>+<br>+/*! \brief Task details for adding an AOR to an endpoint state compositor */<br>+struct sip_options_endpoint_compositor_task_data {<br>+ /*! \brief The AOR options that the endpoint state compositor should be added to */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The endpoint state compositor */<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+};<br>+<br>+/*! \brief Task which adds an AOR to an endpoint state compositor */<br>+static int sip_options_endpoint_compositor_add_task(void *obj)<br>+{<br>+ struct sip_options_endpoint_compositor_task_data *task_data = obj;<br>+<br>+ ast_debug(3, "Adding endpoint compositor '%s' to AOR '%s'\n", task_data->endpoint_state_compositor->name,<br>+ task_data->aor_options->name);<br>+<br>+ AST_VECTOR_APPEND(&task_data->aor_options->compositors, ao2_bump(task_data->endpoint_state_compositor));<br>+<br>+ ao2_lock(task_data->endpoint_state_compositor);<br>+ ast_str_container_add(task_data->endpoint_state_compositor->aors, task_data->aor_options->name);<br>+ if (task_data->aor_options->available) {<br>+ task_data->endpoint_state_compositor->available++;<br>+ }<br>+ ao2_unlock(task_data->endpoint_state_compositor);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Task which adds removes an AOR from an endpoint state compositor */<br>+static int sip_options_endpoint_compositor_remove_task(void *obj)<br>+{<br>+ struct sip_options_endpoint_compositor_task_data *task_data = obj;<br>+ int i;<br>+<br>+ ast_debug(3, "Removing endpoint compositor '%s' from AOR '%s'\n", task_data->endpoint_state_compositor->name,<br>+ task_data->aor_options->name);<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&task_data->aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&task_data->aor_options->compositors, i);<br>+<br>+ if (endpoint_state_compositor != task_data->endpoint_state_compositor) {<br> continue;<br> }<br> <br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ast_cli(cli_fd, "Sending qualify to endpoint %s\n", endpoint_name);<br>- ao2_callback_data(contacts, OBJ_NODATA, cli_on_contact, &cli_fd, endpoint);<br>- ao2_ref(contacts, -1);<br>- }<br>+ AST_VECTOR_REMOVE(&task_data->aor_options->compositors, i, 0);<br>+ ao2_ref(endpoint_state_compositor, -1);<br> <br>- ao2_ref(aor, -1);<br>+ break;<br> }<br>+<br> return 0;<br> }<br>+<br>+/*! \brief Synchronize an endpoint with our local state */<br>+static int sip_options_synchronize_endpoint(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_endpoint *endpoint = obj;<br>+ struct ast_sip_aor *aor = arg;<br>+ char *aors;<br>+ char *aor_name;<br>+ struct sip_options_endpoint_compositor_task_data task_data = { NULL, };<br>+<br>+ if (ast_strlen_zero(endpoint->aors)) {<br>+ /* There are no AORs, so really... who the heck knows */<br>+ ast_debug(3, "Endpoint '%s' is not interested in any AORs so not creating endpoint state compositor\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "Synchronizing endpoint '%s' with AORs '%s'\n", ast_sorcery_object_get_id(endpoint),<br>+ endpoint->aors);<br>+<br>+ aors = ast_strdupa(endpoint->aors);<br>+ while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>+ if (aor && strcasecmp(ast_sorcery_object_get_id(aor), aor_name)) {<br>+ ast_debug(3, "Filtered AOR '%s' on endpoint '%s' as we are looking for '%s'\n",<br>+ aor_name, ast_sorcery_object_get_id(endpoint), ast_sorcery_object_get_id(aor));<br>+ continue;<br>+ }<br>+<br>+ task_data.aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ /* They have referenced an invalid AOR. We don't know if they are online or offline. */<br>+ ast_debug(3, "Endpoint '%s' referenced invalid AOR '%s' so defaulting to initial offline state\n",<br>+ ast_sorcery_object_get_id(endpoint), aor_name);<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint), AST_ENDPOINT_OFFLINE);<br>+ continue;<br>+ }<br>+<br>+ if (!task_data.endpoint_state_compositor) {<br>+ /* We create an endpoint state compositor only after we know for sure we need it */<br>+ task_data.endpoint_state_compositor = sip_options_endpoint_state_compositor_find_or_alloc(endpoint);<br>+ if (!task_data.endpoint_state_compositor) {<br>+ ast_log(LOG_WARNING, "Could not create endpoint state compositor for '%s', endpoint state will be incorrect\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ ao2_ref(task_data.aor_options, -1);<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint), AST_ENDPOINT_OFFLINE);<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ /* We use a synchronous task so that we don't flood the system */<br>+ ast_sip_push_task_synchronous(task_data.aor_options->serializer, sip_options_endpoint_compositor_add_task, &task_data);<br>+<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ /* If we filtered on a specific AOR name then the endpoint can only reference it once so break early */<br>+ if (aor) {<br>+ break;<br>+ }<br>+ }<br>+<br>+ /* If an endpoint state compositor is present determine the current state of the endpoint and update it */<br>+ if (task_data.endpoint_state_compositor) {<br>+ ao2_lock(task_data.endpoint_state_compositor);<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint), task_data.endpoint_state_compositor->available ? AST_ENDPOINT_ONLINE : AST_ENDPOINT_OFFLINE);<br>+ ao2_unlock(task_data.endpoint_state_compositor);<br>+ ao2_ref(task_data.endpoint_state_compositor, -1);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Callback which removes any unused AORs that remained after reloading */<br>+static int sip_options_unused_aor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+ int i;<br>+<br>+ ast_debug(3, "AOR '%s' is no longer configured, removing it\n", aor_options->name);<br>+<br>+ /* This AOR is no longer present so remove itself from the endpoint state compositor. If this<br>+ * results in the endpoint state compositor no longer having any AORs feeding it then it'll go away<br>+ * shortly after.<br>+ */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+<br>+ endpoint_state_compositor->aors = 0;<br>+ endpoint_state_compositor->available = 0;<br>+<br>+ ao2_unlock(endpoint_state_compositor);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ }<br>+<br>+ AST_SCHED_DEL_UNREF(sched, aor_options->sched_id, ao2_t_ref(aor_options, -1, "Delete scheduler entry ref"));<br>+ ao2_unlink(sip_options_aors, aor_options);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Callback function used to unlink and remove event state compositors that have no AORs feeding them */<br>+static int sip_options_unused_endpoint_state_compositor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor = obj;<br>+<br>+ if (endpoint_state_compositor->aors) {<br>+ return 0;<br>+ }<br>+<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name, AST_ENDPOINT_OFFLINE);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Structure which contains information required to synchronize */<br>+struct sip_options_synchronize_task_data {<br>+ /*! \brief Whether this is a reload or not */<br>+ int reload;<br>+};<br>+<br>+/*! \brief Task which synchronizse our local container of AORs and endpoint state compositors with the current configuration */<br>+static int sip_options_synchronize_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_task_data *task_data = obj;<br>+ struct ao2_container *existing = NULL;<br>+ struct ao2_container *objects;<br>+<br>+ /* When reloading we keep track of the existing AORs so we can terminate old ones that are<br>+ * no longer referenced or used.<br>+ */<br>+ if (task_data->reload) {<br>+ existing = ao2_container_clone(sip_options_aors, 0);<br>+ if (!existing) {<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ objects = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "aor",<br>+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (objects) {<br>+ /* Go through the returned AORs and synchronize with our local state */<br>+ ao2_callback(objects, OBJ_NODATA, sip_options_synchronize_aor, existing);<br>+ ao2_ref(objects, -1);<br>+ }<br>+<br>+ /* Any AORs remaining in existing are no longer referenced by the current container of<br>+ * AORs we retrieved, so remove them.<br>+ */<br>+ if (existing) {<br>+ ao2_callback(existing, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, sip_options_unused_aor, NULL);<br>+ ao2_ref(existing, -1);<br>+ }<br>+<br>+ objects = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "endpoint",<br>+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (objects) {<br>+ /* Go through the provided endpoints and update AORs */<br>+ ao2_callback(objects, OBJ_NODATA, sip_options_synchronize_endpoint, NULL);<br>+ ao2_ref(objects, -1);<br>+ }<br>+<br>+ /* All endpoint state compositors that don't have any AORs feeding them information<br>+ * can be removed. If they end up getting needed later they'll just be recreated.<br>+ */<br>+ ao2_callback(sip_options_endpoint_state_compositors, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK,<br>+ sip_options_unused_endpoint_state_compositor, NULL);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Synchronize our local container of AORs and endpoint state compositors with the current configuration */<br>+static void sip_options_synchronize(int reload)<br>+{<br>+ struct sip_options_synchronize_task_data task_data = { .reload = reload, };<br>+<br>+ ast_sip_push_task_synchronous(management_serializer, sip_options_synchronize_task, &task_data);<br>+}<br>+<br>+/*! \brief Task which deletes an endpoint from the known universe in the management serializer */<br>+static int sip_options_endpoint_observer_deleted_task(void *obj)<br>+{<br>+ struct ast_sip_endpoint *endpoint = obj;<br>+ struct sip_options_endpoint_compositor_task_data task_data = { NULL, };<br>+ struct ao2_iterator it_aors;<br>+ char *aor;<br>+<br>+ task_data.endpoint_state_compositor = ao2_find(sip_options_endpoint_state_compositors, ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!task_data.endpoint_state_compositor) {<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "Endpoint '%s' has been deleted, removing endpoint state compositor from AORs\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+<br>+ it_aors = ao2_iterator_init(task_data.endpoint_state_compositor->aors, 0);<br>+ for (; (aor = ao2_iterator_next(&it_aors)); ao2_ref(aor, -1)) {<br>+ task_data.aor_options = ao2_find(sip_options_aors, aor, OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ continue;<br>+ }<br>+<br>+ ast_debug(3, "Removing endpoint state compositor '%s' from AOR '%s'\n",<br>+ ast_sorcery_object_get_id(endpoint), aor);<br>+ ast_sip_push_task_synchronous(task_data.aor_options->serializer, sip_options_endpoint_compositor_remove_task, &task_data);<br>+ ao2_ref(task_data.aor_options, -1);<br>+ }<br>+ ao2_iterator_destroy(&it_aors);<br>+<br>+ ao2_ref(task_data.endpoint_state_compositor, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on endpoint deletion */<br>+static void endpoint_observer_deleted(const void *obj)<br>+{<br>+ ast_sip_push_task_synchronous(management_serializer, sip_options_endpoint_observer_deleted_task, (void *)obj);<br>+}<br>+<br>+<br>+/*! \brief Task which synchronizes the endpoint in the management serializer */<br>+static int sip_options_endpoint_observer_modified_task(void *obj)<br>+{<br>+ /* Instead of trying to reconcile the existing endpoint state compositor we simply delete the old one<br>+ * and create a new one. Since this is only for a single endpoint and state will still be correct afterwards<br>+ * it is easiest and makes the most sense.<br>+ */<br>+ sip_options_endpoint_observer_deleted_task(obj);<br>+ sip_options_synchronize_endpoint(obj, NULL, 0);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on endpoint creation or modification */<br>+static void endpoint_observer_modified(const void *obj)<br>+{<br>+ ast_sip_push_task_synchronous(management_serializer, sip_options_endpoint_observer_modified_task, (void *)obj);<br>+}<br>+<br>+/*! \brief Observer callbacks for endpoints */<br>+static const struct ast_sorcery_observer endpoint_observer_callbacks = {<br>+ .created = endpoint_observer_modified,<br>+ .updated = endpoint_observer_modified,<br>+ .deleted = endpoint_observer_deleted,<br>+};<br>+<br>+/*! \brief Task to synchronize an AOR with our local state */<br>+static int sip_options_update_aor_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_aor_task_data *task_data = obj;<br>+ int available = task_data->aor_options->available;<br>+<br>+ ast_debug(3, "Individually updating AOR '%s' with current state of configuration and world\n",<br>+ task_data->aor_options->name);<br>+<br>+ sip_options_apply_aor_configuration(task_data->aor_options, task_data->aor, task_data->added);<br>+<br>+ if (!available && task_data->aor_options->available) {<br>+ ast_debug(3, "After modifying AOR '%s' it has now become available\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, AVAILABLE);<br>+ } else if (available && !task_data->aor_options->available) {<br>+ ast_debug(3, "After modifying AOR '%s' it has become unavailable\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, UNAVAILABLE);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Task which synchronizes the AOR in the management serializer */<br>+static int sip_options_aor_observer_modified_task(void *obj)<br>+{<br>+ struct ast_sip_aor *aor = obj;<br>+ struct sip_options_aor *aor_options;<br>+ int added = 0;<br>+<br>+ aor_options = ao2_find(sip_options_aors, ast_sorcery_object_get_id(aor), OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br>+ aor_options = sip_options_aor_alloc(aor);<br>+ if (!aor_options) {<br>+ return 0;<br>+ }<br>+ added = 1;<br>+ }<br>+<br>+ /* If this is a newly added AOR we need to establish any endpoint state compositors<br>+ * that may reference only the AOR. If these need to be updated later then they'll be done<br>+ * by modifying the endpoint or issuing a reload.<br>+ */<br>+ if (added) {<br>+ struct ao2_container *endpoints;<br>+<br>+ sip_options_apply_aor_configuration(aor_options, aor, added);<br>+ ao2_link(sip_options_aors, aor_options);<br>+<br>+ /* Using LIKE doesn't seem to work very well with non-realtime so we pull everything<br>+ * right now and do a filter on our side.<br>+ */<br>+ endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>+ "endpoint", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (endpoints) {<br>+ ao2_callback(endpoints, OBJ_NODATA, sip_options_synchronize_endpoint, aor);<br>+ ao2_ref(endpoints, -1);<br>+ }<br>+ } else {<br>+ struct sip_options_synchronize_aor_task_data task_data = { .aor_options = aor_options, .aor = aor, };<br>+<br>+ /* If this AOR was modified we have to do our work in its serializer instead of this<br>+ * thread to ensure that things aren't modified by multiple threads.<br>+ */<br>+ ast_sip_push_task_synchronous(aor_options->serializer, sip_options_update_aor_task, &task_data);<br>+ }<br>+<br>+ ao2_ref(aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on AOR creation or modification */<br>+static void aor_observer_modified(const void *obj)<br>+{<br>+ ast_sip_push_task_synchronous(management_serializer, sip_options_aor_observer_modified_task, (void *)obj);<br>+}<br>+<br>+/*! \brief Task which deletes an AOR from the known universe in the management serializer */<br>+static int sip_options_aor_observer_deleted_task(void *obj)<br>+{<br>+ struct ast_sip_aor *aor = obj;<br>+ struct sip_options_aor *aor_options;<br>+ int i;<br>+<br>+ aor_options = ao2_find(sip_options_aors, ast_sorcery_object_get_id(aor), OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!aor_options) {<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "AOR '%s' has been deleted, removing it\n", aor_options->name);<br>+<br>+ /* This AOR is no longer present so remove itself from the endpoint state compositor. If this<br>+ * results in the endpoint state compositor no longer having any AORs feeding it then it'll go away<br>+ * shortly after.<br>+ */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor =<br>+ AST_VECTOR_GET(&aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+<br>+ if (aor_options->available) {<br>+ endpoint_state_compositor->available--;<br>+ }<br>+ ast_str_container_remove(endpoint_state_compositor->aors, aor_options->name);<br>+<br>+ ao2_unlock(endpoint_state_compositor);<br>+<br>+ /* If there are no other AORs feeding this endpoint state compositor then away it goes. */<br>+ if (!ao2_container_count(endpoint_state_compositor->aors)) {<br>+ ast_debug(3, "Endpoint state compositor '%s' is being removed as the last AOR '%s' referencing it is being removed\n",<br>+ endpoint_state_compositor->name, aor_options->name);<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name, AST_ENDPOINT_OFFLINE);<br>+ ao2_unlink(sip_options_endpoint_state_compositors, endpoint_state_compositor);<br>+ } else if (!endpoint_state_compositor->available) {<br>+ ast_debug(3, "Endpoint state compositor '%s' has become unavailable due to AOR being deleted, updating endpoint state\n",<br>+ endpoint_state_compositor->name);<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name, AST_ENDPOINT_OFFLINE);<br>+ }<br>+<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ }<br>+<br>+ AST_SCHED_DEL_UNREF(sched, aor_options->sched_id, ao2_t_ref(aor_options, -1, "Delete scheduler entry ref"));<br>+ ao2_ref(aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on AOR deletion */<br>+static void aor_observer_deleted(const void *obj)<br>+{<br>+ ast_sip_push_task_synchronous(management_serializer, sip_options_aor_observer_deleted_task, (void *)obj);<br>+}<br>+<br>+/*! \brief Observer callbacks for AORs */<br>+static const struct ast_sorcery_observer aor_observer_callbacks = {<br>+ .created = aor_observer_modified,<br>+ .updated = aor_observer_modified,<br>+ .deleted = aor_observer_deleted,<br>+};<br>+<br>+/*! \brief Task details for adding an AOR to an endpoint state compositor */<br>+struct sip_options_contact_observer_task_data {<br>+ /*! \brief The AOR options that the contact is referring to */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The contact itself */<br>+ struct ast_sip_contact *contact;<br>+};<br>+<br>+/*! \brief Task which adds a dynamic contact to an AOR */<br>+static int sip_options_contact_add_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data = obj;<br>+<br>+ ao2_link(task_data->aor_options->dynamic_contacts, task_data->contact);<br>+<br>+ if (task_data->aor_options->qualify_frequency) {<br>+ /* If this is the first contact we need to schedule up qualification */<br>+ if ((ao2_container_count(task_data->aor_options->dynamic_contacts) + ao2_container_count(task_data->aor_options->permanent_contacts)) == 1) {<br>+ ast_debug(3, "Starting scheduled callback on AOR '%s' for qualifying as there is now a contact on it\n",<br>+ task_data->aor_options->name);<br>+ /* We immediately schedule the initial qualify so that we get reachable/unreachable as soon as possible.<br>+ * Realistically since they pretty much just registered they should be reachable.<br>+ */<br>+ task_data->aor_options->sched_id = ast_sched_add_variable(sched, 1, sip_options_qualify_aor,<br>+ ao2_bump(task_data->aor_options), 1);<br>+ if (task_data->aor_options->sched_id < 0) {<br>+ ao2_t_ref(task_data->aor_options, -1, "Cleanup failed scheduler add");<br>+ ast_log(LOG_ERROR, "Unable to schedule qualify for contacts of AOR '%s'\n", task_data->aor_options->name);<br>+ }<br>+ }<br>+ } else {<br>+ /* If this was the first contact added to a non-qualified AOR then<br>+ * it should become available.<br>+ */<br>+ task_data->aor_options->available++;<br>+ if (task_data->aor_options->available == 1) {<br>+ ast_debug(3, "An unqualified contact has been added to AOR '%s' so it is now available\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, AVAILABLE);<br>+ }<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on contact creation */<br>+static void contact_observer_created(const void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data task_data;<br>+<br>+ task_data.contact = (struct ast_sip_contact *)obj;<br>+ task_data.aor_options = ao2_find(sip_options_aors, task_data.contact->aor, OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ struct ast_sip_aor *aor;<br>+<br>+ /* The only reason this would occur is if the AOR was sourced after the last reload<br>+ * happened. To handle this we pull the AOR and treat it as if we received notification<br>+ * that it had been created. This will cause it to get created for qualify and any<br>+ * endpoint state compositors to also get created.<br>+ */<br>+ aor = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", task_data.contact->aor);<br>+ if (aor) {<br>+ aor_observer_modified(aor);<br>+ ao2_ref(aor, -1);<br>+ }<br>+ return;<br>+ }<br>+<br>+ ast_sip_push_task_synchronous(task_data.aor_options->serializer, sip_options_contact_add_task, &task_data);<br>+ ao2_ref(task_data.aor_options, -1);<br>+}<br>+<br>+/*! \brief Task which deletes a dynamic contact from an AOR */<br>+static int sip_options_contact_delete_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data = obj;<br>+<br>+ ao2_find(task_data->aor_options->dynamic_contacts, task_data->contact, OBJ_NODATA | OBJ_UNLINK | OBJ_SEARCH_OBJECT);<br>+<br>+ sip_options_remove_contact_status(task_data->aor_options, task_data->contact);<br>+<br>+ if (task_data->aor_options->qualify_frequency) {<br>+ /* If this is the last contact then we need to stop the scheduled callback */<br>+ if ((ao2_container_count(task_data->aor_options->dynamic_contacts) + ao2_container_count(task_data->aor_options->permanent_contacts)) == 0) {<br>+ ast_debug(3, "Terminating scheduled callback on AOR '%s' as there are no contacts to qualify\n",<br>+ task_data->aor_options->name);<br>+ AST_SCHED_DEL_UNREF(sched, task_data->aor_options->sched_id, ao2_t_ref(task_data->aor_options, -1, "Delete scheduler entry ref"));<br>+ }<br>+ } else {<br>+ task_data->aor_options->available--;<br>+ if (!task_data->aor_options->available) {<br>+ ast_debug(3, "An unqualified contact has been removed from AOR '%s' leaving no remaining contacts\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, UNAVAILABLE);<br>+ }<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on contact deletion */<br>+static void contact_observer_deleted(const void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data task_data;<br>+<br>+ task_data.contact = (struct ast_sip_contact *)obj;<br>+ task_data.aor_options = ao2_find(sip_options_aors, task_data.contact->aor, OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ /* For contacts that are deleted we don't really care if there is no AOR locally */<br>+ return;<br>+ }<br>+<br>+ ast_sip_push_task_synchronous(task_data.aor_options->serializer, sip_options_contact_delete_task, &task_data);<br>+ ao2_ref(task_data.aor_options, -1);<br>+}<br>+<br>+/*! \brief Observer callbacks for contacts */<br>+static const struct ast_sorcery_observer contact_observer_callbacks = {<br>+ .created = contact_observer_created,<br>+ .deleted = contact_observer_deleted,<br>+};<br> <br> static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)<br> {<br> RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);<br> const char *endpoint_name;<br>- struct qualify_data *qual_data;<br>+ char *aors;<br>+ char *aor_name;<br> <br> switch (cmd) {<br> case CLI_INIT:<br>@@ -917,38 +1850,34 @@<br> <br> endpoint_name = a->argv[2];<br> <br>- if (!(endpoint = ast_sorcery_retrieve_by_id(<br>- ast_sip_get_sorcery(), "endpoint", endpoint_name))) {<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",<br>+ endpoint_name);<br>+ if (!endpoint) {<br> ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);<br> return CLI_FAILURE;<br> }<br> <br>- qual_data = qualify_data_alloc(endpoint, a->fd);<br>- if (!qual_data) {<br>+ if (ast_strlen_zero(endpoint->aors)) {<br>+ ast_cli(a->fd, "No AORs configured for endpoint '%s'\n", endpoint_name);<br> return CLI_FAILURE;<br> }<br> <br>- if (ast_sip_push_task(NULL, cli_qualify_contacts, qual_data)) {<br>- qualify_data_destroy(qual_data);<br>- return CLI_FAILURE;<br>+ aors = ast_strdupa(endpoint->aors);<br>+ while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>+ struct sip_options_aor *aor_options;<br>+<br>+ aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br>+ continue;<br>+ }<br>+<br>+ ast_cli(a->fd, "Qualifying AOR '%s' on endpoint '%s'\n", aor_name, endpoint_name);<br>+<br>+ ast_sip_push_task_synchronous(aor_options->serializer, sip_options_qualify_aor_task, aor_options);<br>+ /* The synchronous task will release the reference */<br> }<br> <br> return CLI_SUCCESS;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Send qualify request to the given contact.<br>- */<br>-static int ami_contact_cb(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>-<br>- ao2_ref(contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, contact)) {<br>- ao2_ref(contact, -1);<br>- }<br>- return 0;<br> }<br> <br> static int ami_sip_qualify(struct mansession *s, const struct message *m)<br>@@ -978,21 +1907,15 @@<br> <br> aors = ast_strdupa(endpoint->aors);<br> while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- struct ast_sip_aor *aor;<br>- struct ao2_container *contacts;<br>+ struct sip_options_aor *aor_options;<br> <br>- aor = ast_sip_location_retrieve_aor(aor_name);<br>- if (!aor) {<br>+ aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br> continue;<br> }<br> <br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, ami_contact_cb, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-<br>- ao2_ref(aor, -1);<br>+ ast_sip_push_task_synchronous(aor_options->serializer, sip_options_qualify_aor_task, aor_options);<br>+ /* The synchronous task will release the reference */<br> }<br> <br> astman_send_ack(s, m, "Endpoint found, will qualify");<br>@@ -1003,233 +1926,6 @@<br> AST_CLI_DEFINE(cli_qualify, "Send an OPTIONS request to a PJSIP endpoint")<br> };<br> <br>-static int sched_qualifies_hash_fn(const void *obj, int flags)<br>-{<br>- const struct sched_data *object;<br>- const struct ast_sip_contact *key;<br>-<br>- switch (flags & OBJ_SEARCH_MASK) {<br>- case OBJ_SEARCH_KEY:<br>- key = obj;<br>- break;<br>- case OBJ_SEARCH_OBJECT:<br>- object = obj;<br>- key = object->contact;<br>- break;<br>- default:<br>- /* Hash can only work on something with a full key. */<br>- ast_assert(0);<br>- return 0;<br>- }<br>- return ast_str_hash(ast_sorcery_object_get_id(key));<br>-}<br>-<br>-static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)<br>-{<br>- const struct sched_data *object_left = obj;<br>- const struct sched_data *object_right = arg;<br>- struct ast_sip_contact *right_key = arg;<br>- int cmp;<br>-<br>- switch (flags & OBJ_SEARCH_MASK) {<br>- case OBJ_SEARCH_OBJECT:<br>- right_key = object_right->contact;<br>- /* Fall through */<br>- case OBJ_SEARCH_KEY:<br>- cmp = strcmp(ast_sorcery_object_get_id(object_left->contact),<br>- ast_sorcery_object_get_id(right_key));<br>- break;<br>- case OBJ_SEARCH_PARTIAL_KEY:<br>- /* Not supported by container. */<br>- ast_assert(0);<br>- return 0;<br>- default:<br>- /*<br>- * What arg points to is specific to this traversal callback<br>- * and has no special meaning to astobj2.<br>- */<br>- cmp = 0;<br>- break;<br>- }<br>- if (cmp) {<br>- return 0;<br>- }<br>- /*<br>- * At this point the traversal callback is identical to a sorted<br>- * container.<br>- */<br>- return CMP_MATCH;<br>-}<br>-<br>-static int rtt_start_handler(const struct aco_option *opt,<br>- struct ast_variable *var, void *obj)<br>-{<br>- struct ast_sip_contact_status *status = obj;<br>- long int sec, usec;<br>-<br>- if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {<br>- return -1;<br>- }<br>-<br>- status->rtt_start = ast_tv(sec, usec);<br>-<br>- return 0;<br>-}<br>-<br>-static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)<br>-{<br>- const struct ast_sip_contact_status *status = obj;<br>-<br>- if (ast_asprintf(buf, "%ld.%06ld", (long)status->rtt_start.tv_sec, (long)status->rtt_start.tv_usec) == -1) {<br>- return -1;<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static char status_value_unknown[2];<br>-static char status_value_created[2];<br>-<br>-int ast_sip_initialize_sorcery_qualify(void)<br>-{<br>- struct ast_sorcery *sorcery = ast_sip_get_sorcery();<br>-<br>- /* initialize sorcery ast_sip_contact_status resource */<br>- ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL);<br>- ast_sorcery_object_set_congestion_levels(sorcery, CONTACT_STATUS, -1,<br>- 3 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);<br>-<br>- if (ast_sorcery_internal_object_register(sorcery, CONTACT_STATUS,<br>- contact_status_alloc, NULL, NULL)) {<br>- ast_log(LOG_ERROR, "Unable to register ast_sip_contact_status in sorcery\n");<br>- return -1;<br>- }<br>-<br>- snprintf(status_value_unknown, sizeof(status_value_unknown), "%u", UNKNOWN);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",<br>- status_value_unknown, OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, last_status));<br>- snprintf(status_value_created, sizeof(status_value_created), "%u", CREATED);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",<br>- status_value_created, OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, status));<br>- ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",<br>- "0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",<br>- "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, rtt));<br>-<br>- return 0;<br>-}<br>-<br>-static void qualify_and_schedule_contact(struct ast_sip_contact *contact)<br>-{<br>- int initial_interval;<br>- int max_time = ast_sip_get_max_initial_qualify_time();<br>-<br>- /* Delay initial qualification by a random fraction of the specified interval */<br>- if (max_time && max_time < contact->qualify_frequency) {<br>- initial_interval = max_time;<br>- } else {<br>- initial_interval = contact->qualify_frequency;<br>- }<br>-<br>- initial_interval = (int)((initial_interval * 1000) * ast_random_double());<br>-<br>- unschedule_qualify(contact);<br>- if (contact->qualify_frequency) {<br>- schedule_qualify(contact, initial_interval);<br>- } else {<br>- update_contact_status(contact, UNKNOWN, 0);<br>- }<br>-}<br>-<br>-static int qualify_and_schedule_cb_with_aor(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor = arg;<br>-<br>- contact->qualify_frequency = aor->qualify_frequency;<br>- contact->qualify_timeout = aor->qualify_timeout;<br>- contact->authenticate_qualify = aor->authenticate_qualify;<br>-<br>- qualify_and_schedule_contact(contact);<br>-<br>- return 0;<br>-}<br>-<br>-static int qualify_and_schedule_cb_without_aor(void *obj, void *arg, int flags)<br>-{<br>- /*<br>- * These are really dynamic contacts. We need to retrieve the aor associated<br>- * with the contact since it's possible some of the aor's fields were updated<br>- * since last load.<br>- */<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor = ast_sip_location_retrieve_aor(contact->aor);<br>-<br>- if (aor) {<br>- qualify_and_schedule_cb_with_aor(obj, aor, flags);<br>- ao2_ref(aor, -1);<br>- } else {<br>- ast_log(LOG_WARNING, "Unable to locate AOR for contact '%s'. Keeping old "<br>- "associated settings: frequency=%d, timeout=%f, authenticate=%s\n",<br>- contact->uri, contact->qualify_frequency, contact->qualify_timeout,<br>- contact->authenticate_qualify ? "yes" : "no");<br>- qualify_and_schedule_contact(contact);<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Qualify and schedule an aor's contacts<br>- *<br>- * \details For the given aor check if it has permanent contacts,<br>- * qualify all contacts and schedule for checks if configured.<br>- */<br>-static int qualify_and_schedule_all_cb(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_aor *aor = obj;<br>- struct ao2_container *contacts;<br>-<br>- if (aor->permanent_contacts) {<br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_with_aor, aor);<br>- ao2_ref(contacts, -1);<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static void qualify_and_schedule_all(void)<br>-{<br>- struct ao2_container *aors;<br>- struct ao2_container *contacts;<br>-<br>- /*<br>- * It's possible that the AOR had some of it's fields updated prior to a<br>- * reload. For instance qualifying could have been turned on or off by<br>- * setting the qualify_frequency. Due to this we have to iterate through<br>- * all contacts (static and dynamic), and not just ones where the frequency<br>- * is greater than zero, updating any contact fields with the AOR's values.<br>- */<br>-<br>- aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "aor", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>-<br>- if (aors) {<br>- ao2_callback(aors, OBJ_NODATA, qualify_and_schedule_all_cb, NULL);<br>- ao2_ref(aors, -1);<br>- }<br>-<br>- contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "contact", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_without_aor, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br> <br> int ast_sip_format_contact_ami(void *obj, void *arg, int flags)<br> {<br>@@ -1245,9 +1941,7 @@<br> return -1;<br> }<br> <br>- status = ast_sorcery_retrieve_by_id(<br>- ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>+ status = ast_sip_get_contact_status(contact);<br> <br> ast_str_append(&buf, 0, "AOR: %s\r\n", wrapper->aor_id);<br> ast_str_append(&buf, 0, "URI: %s\r\n", contact->uri);<br>@@ -1281,7 +1975,7 @@<br> <br> astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));<br> ami->count++;<br>- <br>+<br> ast_free(buf);<br> ao2_cleanup(status);<br> return 0;<br>@@ -1305,218 +1999,107 @@<br> .format_ami = format_ami_contact_status<br> };<br> <br>-static void aor_observer_modified(const void *obj)<br>-{<br>- struct ast_sip_aor *aor = (void *)obj;<br>- struct ao2_container *contacts;<br>-<br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_with_aor, aor);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br>-<br>-static int unschedule_contact_cb(void *obj, void *arg, int flags)<br>-{<br>- unschedule_qualify(obj);<br>-<br>- return CMP_MATCH;<br>-}<br>-<br>-static void aor_observer_deleted(const void *obj)<br>-{<br>- const struct ast_sip_aor *aor = obj;<br>- struct ao2_container *contacts;<br>-<br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, unschedule_contact_cb, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br>-<br>-static const struct ast_sorcery_observer observer_callbacks_options = {<br>- .created = aor_observer_modified,<br>- .updated = aor_observer_modified,<br>- .deleted = aor_observer_deleted<br>-};<br>-<br>-static int aor_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_endpoint *endpoint = obj;<br>- const char *endpoint_name = ast_sorcery_object_get_id(endpoint);<br>- char *aor = arg;<br>- char *endpoint_aor;<br>- char *endpoint_aors;<br>-<br>- if (ast_strlen_zero(aor) || ast_strlen_zero(endpoint->aors)) {<br>- return 0;<br>- }<br>-<br>- endpoint_aors = ast_strdupa(endpoint->aors);<br>- while ((endpoint_aor = ast_strip(strsep(&endpoint_aors, ",")))) {<br>- if (!strcmp(aor, endpoint_aor)) {<br>- if (ast_sip_persistent_endpoint_update_state(endpoint_name, AST_ENDPOINT_ONLINE) == -1) {<br>- ast_log(LOG_WARNING, "Unable to find persistent endpoint '%s' for aor '%s'\n",<br>- endpoint_name, aor);<br>- }<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static int on_aor_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_aor *aor = obj;<br>- struct ao2_container *endpoints;<br>- RAII_VAR(struct ast_variable *, var, NULL, ast_variables_destroy);<br>- const char *aor_name = ast_sorcery_object_get_id(aor);<br>- char *aor_like;<br>-<br>- if (ast_strlen_zero(aor_name)) {<br>- return -1;<br>- }<br>-<br>- if (aor->permanent_contacts && ((int)(aor->qualify_frequency * 1000)) <= 0) {<br>- aor_like = ast_alloca(strlen(aor_name) + 3);<br>- sprintf(aor_like, "%%%s%%", aor_name);<br>- var = ast_variable_new("aors LIKE", aor_like, "");<br>- if (!var) {<br>- return -1;<br>- }<br>- endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "endpoint", AST_RETRIEVE_FLAG_MULTIPLE, var);<br>-<br>- if (endpoints) {<br>- /*<br>- * Because aors are a string list, we have to use a pattern match but since a simple<br>- * pattern match could return an endpoint that has an aor of "aaabccc" when searching<br>- * for "abc", we still have to iterate over them to find an exact aor match.<br>- */<br>- ao2_callback(endpoints, 0, aor_update_endpoint_state, (char *)aor_name);<br>- ao2_ref(endpoints, -1);<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static int contact_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- const struct ast_sip_contact *contact = obj;<br>- struct timeval tv = ast_tvnow();<br>-<br>- if (!ast_strlen_zero(contact->endpoint_name) && ((int)(contact->qualify_frequency * 1000)) <= 0 &&<br>- contact->expiration_time.tv_sec > tv.tv_sec) {<br>-<br>- if (ast_sip_persistent_endpoint_update_state(contact->endpoint_name, AST_ENDPOINT_ONLINE) == -1) {<br>- ast_log(LOG_WARNING, "Unable to find persistent endpoint '%s' for contact '%s/%s'\n",<br>- contact->endpoint_name, contact->aor, contact->uri);<br>- return -1;<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static void update_all_unqualified_endpoints(void)<br>-{<br>- struct ao2_container *aors;<br>- struct ao2_container *contacts;<br>- RAII_VAR(struct ast_variable *, var_aor, NULL, ast_variables_destroy);<br>- RAII_VAR(struct ast_variable *, var_contact, NULL, ast_variables_destroy);<br>- RAII_VAR(char *, time_now, NULL, ast_free);<br>- struct timeval tv = ast_tvnow();<br>-<br>- if (!(var_aor = ast_variable_new("contact !=", "", ""))) {<br>- return;<br>- }<br>- if (!(var_aor->next = ast_variable_new("qualify_frequency <=", "0", ""))) {<br>- return;<br>- }<br>-<br>- if (ast_asprintf(&time_now, "%ld", tv.tv_sec) == -1) {<br>- return;<br>- }<br>- if (!(var_contact = ast_variable_new("expiration_time >", time_now, ""))) {<br>- return;<br>- }<br>- if (!(var_contact->next = ast_variable_new("qualify_frequency <=", "0", ""))) {<br>- return;<br>- }<br>-<br>- aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "aor", AST_RETRIEVE_FLAG_MULTIPLE, var_aor);<br>- if (aors) {<br>- ao2_callback(aors, OBJ_NODATA, on_aor_update_endpoint_state, NULL);<br>- ao2_ref(aors, -1);<br>- }<br>-<br>- contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "contact", AST_RETRIEVE_FLAG_MULTIPLE, var_contact);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, contact_update_endpoint_state, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br>-<br>-int ast_res_pjsip_init_options_handling(int reload)<br>-{<br>- static const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };<br>-<br>- if (reload) {<br>- qualify_and_schedule_all();<br>- return 0;<br>- }<br>-<br>- sched_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS,<br>- sched_qualifies_hash_fn, sched_qualifies_cmp_fn,<br>- "Create container for scheduled qualifies");<br>- if (!sched_qualifies) {<br>- return -1;<br>- }<br>-<br>- if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW,<br>- NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {<br>- pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "aor", &observer_callbacks_options)) {<br>- pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- internal_sip_register_endpoint_formatter(&contact_status_formatter);<br>- ast_manager_register2("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_sip_qualify, NULL, NULL, NULL);<br>- ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));<br>-<br>- update_all_unqualified_endpoints();<br>- qualify_and_schedule_all();<br>-<br>- return 0;<br>-}<br>-<br> void ast_res_pjsip_cleanup_options_handling(void)<br> {<br> ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));<br> ast_manager_unregister("PJSIPQualify");<br> internal_sip_unregister_endpoint_formatter(&contact_status_formatter);<br> <br>- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &observer_callbacks_options);<br>+ if (sched) {<br>+ ast_sched_context_destroy(sched);<br>+ }<br>+<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer_callbacks);<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &aor_observer_callbacks);<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "endpoint", &endpoint_observer_callbacks);<br>+ ast_taskprocessor_unreference(management_serializer);<br>+ ao2_cleanup(sip_options_aors);<br>+ ao2_cleanup(sip_options_contact_statuses);<br>+ ao2_cleanup(sip_options_endpoint_state_compositors);<br> pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>+}<br>+<br>+int ast_res_pjsip_init_options_handling(int reload)<br>+{<br>+ static const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };<br>+ char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];<br>+<br>+ if (reload) {<br>+ sip_options_synchronize(1);<br>+ return 0;<br>+ }<br>+<br>+ if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {<br>+ return -1;<br>+ }<br>+<br>+ if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW,<br>+ NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ sip_options_aors = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, AOR_BUCKETS,<br>+ sip_options_aor_hash, NULL, sip_options_aor_cmp);<br>+ if (!sip_options_aors) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ if (!sip_options_contact_statuses) {<br>+ sip_options_contact_statuses = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, CONTACT_BUCKETS,<br>+ sip_contact_status_hash, NULL, sip_contact_status_cmp);<br>+ if (!sip_options_contact_statuses) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+ }<br>+<br>+ sip_options_endpoint_state_compositors = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, ENDPOINT_STATE_COMPOSITOR_BUCKETS,<br>+ sip_endpoint_state_compositor_hash, NULL, sip_endpoint_state_compositor_cmp);<br>+ if (!sip_options_endpoint_state_compositors) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/options/manage");<br>+ management_serializer = ast_sip_create_serializer_named(tps_name);<br>+ if (!management_serializer) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "endpoint", &endpoint_observer_callbacks)) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "aor", &aor_observer_callbacks)) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer_callbacks)) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ sched = ast_sched_context_create();<br>+ if (!sched) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sched_start_thread(sched)) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ sip_options_synchronize(0);<br>+<br>+ internal_sip_register_endpoint_formatter(&contact_status_formatter);<br>+ ast_manager_register2("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_sip_qualify, NULL, NULL, NULL);<br>+ ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));<br>+<br>+ return 0;<br> }<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/7710">change 7710</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/7710"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 13 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I6a5ebbfca9001dfe933eaeac4d3babd8d2e6f082 </div>
<div style="display:none"> Gerrit-Change-Number: 7710 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Joshua Colp <jcolp@digium.com> </div>