<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>