[glib/wip/tingping/happy-eyeballs: 4/4] gnetworkaddress: Interleave ipv4 and ipv6 addresses



commit f91efd6fe7cc3eb60afff233fcb98a9a82d4b73b
Author: Patrick Griffis <tingping tingping se>
Date:   Tue Nov 6 13:36:19 2018 -0500

    gnetworkaddress: Interleave ipv4 and ipv6 addresses

 gio/gnetworkaddress.c       | 170 ++++++++++++++++++++++++++++++++++++++------
 gio/tests/network-address.c |  21 ++++++
 2 files changed, 171 insertions(+), 20 deletions(-)
---
diff --git a/gio/gnetworkaddress.c b/gio/gnetworkaddress.c
index a50f453a7..9b05ee459 100644
--- a/gio/gnetworkaddress.c
+++ b/gio/gnetworkaddress.c
@@ -874,12 +874,13 @@ g_network_address_get_scheme (GNetworkAddress *addr)
 typedef struct {
   GSocketAddressEnumerator parent_instance;
 
-  GNetworkAddress *addr;
-  GList *addresses;
-  GList *current_item;
-  GTask *queued_task;
-  GError *last_error;
-  GSource *wait_source;
+  GNetworkAddress *addr; /* (owned) */
+  GList *addresses; /* (owned) (nullable) */
+  GList *last_tail; /* (unowned) (nullable) */
+  GList *current_item; /* (unowned) (nullable) */
+  GTask *queued_task; /* (owned) (nullable) */
+  GError *last_error; /* (owned) (nullable) */
+  GSource *wait_source; /* (owned) (nullable) */
 } GNetworkAddressAddressEnumerator;
 
 typedef struct {
@@ -904,10 +905,130 @@ g_network_address_address_enumerator_finalize (GObject *object)
   g_clear_object (&addr_enum->queued_task);
   g_clear_error (&addr_enum->last_error);
   g_object_unref (addr_enum->addr);
+  g_clear_pointer (&addr_enum->addresses, g_list_free);
 
   G_OBJECT_CLASS (_g_network_address_address_enumerator_parent_class)->finalize (object);
 }
 
+static inline GSocketFamily
+get_address_family (GInetSocketAddress *address)
+{
+  return g_inet_address_get_family (g_inet_socket_address_get_address (address));
+}
+
+static void
+list_split_families (GList  *list,
+                     GList **out_ipv4,
+                     GList **out_ipv6)
+{
+  g_assert (out_ipv4);
+  g_assert (out_ipv6);
+
+  while (list)
+    {
+      GSocketFamily family = get_address_family (list->data);
+      switch (family)
+        {
+          case G_SOCKET_FAMILY_IPV4:
+            *out_ipv4 = g_list_prepend (*out_ipv4, list->data);
+            break;
+          case G_SOCKET_FAMILY_IPV6:
+            *out_ipv6 = g_list_prepend (*out_ipv6, list->data);
+            break;
+          case G_SOCKET_FAMILY_INVALID:
+          case G_SOCKET_FAMILY_UNIX:
+            g_assert_not_reached();
+        }
+
+      list = g_list_next (list);
+    }
+
+  *out_ipv4 = g_list_reverse (*out_ipv4);
+  *out_ipv6 = g_list_reverse (*out_ipv6);
+}
+
+static GList *
+list_interleave_families (GList *list1,
+                          GList *list2)
+{
+  GList *interleaved = NULL;
+
+  while (list1 || list2)
+    {
+      if (list1)
+        {
+          interleaved = g_list_append (interleaved, list1->data);
+          list1 = g_list_delete_link (list1, list1);
+        }
+      if (list2)
+        {
+          interleaved = g_list_append (interleaved, list2->data);
+          list2 = g_list_delete_link (list2, list2);
+        }
+    }
+
+  return interleaved;
+}
+
+/* list_copy_interleaved:
+ * @list: (transfer container): List to copy
+ *
+ * Does a shallow copy of a list with address families interleaved.
+ *
+ * For example:
+ *   Input: [ipv6, ipv6, ipv4, ipv4]
+ *   Output: [ipv6, ipv4, ipv6, ipv4]
+ *
+ * Returns: (transfer container): A new list
+ */
+static GList *
+list_copy_interleaved (GList *list)
+{
+  GList *ipv4 = NULL, *ipv6 = NULL;
+
+  list_split_families (list, &ipv4, &ipv6);
+  return list_interleave_families (ipv6, ipv4);
+}
+
+/* list_concat_interleaved:
+ * @current_item: (transfer container): Already existing list
+ * @new_list: (transfer none): New list to be interleaved and concatenated
+ *
+ * This differs from g_list_concat() + list_copy_interleaved() in that it sorts
+ * items in the previous list starting from @current_item.
+ *
+ * Returns: (transfer container): New start of list
+ */
+static GList *
+list_concat_interleaved (GList *current_item,
+                         GList *new_list)
+{
+  GList *ipv4 = NULL, *ipv6 = NULL, *interleaved, *trailing = NULL;
+  GSocketFamily last_family = G_SOCKET_FAMILY_IPV4; /* Default to starting with ipv6 */
+
+  if (current_item)
+    {
+      last_family = get_address_family (current_item->data);
+
+      /* Unused addresses will get removed, resorted, then readded */
+      trailing = g_list_next (current_item);
+      current_item->next = NULL;
+    }
+
+  list_split_families (trailing, &ipv4, &ipv6);
+  list_split_families (new_list, &ipv4, &ipv6);
+
+  if (trailing)
+    g_list_free (trailing);
+
+  if (last_family == G_SOCKET_FAMILY_IPV4)
+    interleaved = list_interleave_families (ipv6, ipv4);
+  else
+    interleaved = list_interleave_families (ipv4, ipv6);
+
+  return g_list_concat (current_item, interleaved);
+}
+
 static GSocketAddress *
 g_network_address_address_enumerator_next (GSocketAddressEnumerator  *enumerator,
                                            GCancellable              *cancellable,
@@ -949,8 +1070,8 @@ g_network_address_address_enumerator_next (GSocketAddressEnumerator  *enumerator
           g_network_address_add_addresses (addr, addresses, serial);
         }
 
-      addr_enum->addresses = addr->priv->sockaddrs;
-      addr_enum->current_item = addr_enum->addresses;
+      addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
+      addr_enum->last_tail = g_list_last (addr->priv->sockaddrs);
       g_object_unref (resolver);
     }
 
@@ -969,13 +1090,11 @@ complete_queued_task (GNetworkAddressAddressEnumerator *addr_enum,
 {
   GSocketAddress *sockaddr;
 
-  addr_enum->addresses = addr_enum->addr->priv->sockaddrs;
-  addr_enum->current_item = addr_enum->addresses;
+  addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr_enum->addr->priv->sockaddrs);
+  addr_enum->last_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
 
   if (addr_enum->current_item)
-    {
-      sockaddr = g_object_ref (addr_enum->current_item->data);
-    }
+    sockaddr = g_object_ref (addr_enum->current_item->data);
   else
     sockaddr = NULL;
 
@@ -1161,17 +1280,28 @@ g_network_address_address_enumerator_next_async (GSocketAddressEnumerator  *enum
   if (addr_enum->addresses == NULL)
     {
       g_assert (addr->priv->sockaddrs);
-      addr_enum->addresses = addr->priv->sockaddrs;
-      addr_enum->current_item = addr_enum->addresses;
+
+      addr_enum->current_item = addr_enum->addresses = list_copy_interleaved (addr->priv->sockaddrs);
       sockaddr = g_object_ref (addr_enum->current_item->data);
     }
-  else if (addr_enum->current_item->next)
+  else
     {
-      addr_enum->current_item = g_list_next (addr_enum->current_item);
-      sockaddr = g_object_ref (addr_enum->current_item->data);
+      GList *parent_tail = g_list_last (addr_enum->addr->priv->sockaddrs);
+
+      if (addr_enum->last_tail != parent_tail)
+        {
+          addr_enum->current_item = list_concat_interleaved (addr_enum->current_item, g_list_next 
(addr_enum->last_tail));
+          addr_enum->last_tail = parent_tail;
+        }
+
+      if (addr_enum->current_item->next)
+        {
+          addr_enum->current_item = g_list_next (addr_enum->current_item);
+          sockaddr = g_object_ref (addr_enum->current_item->data);
+        }
+      else
+        sockaddr = NULL;
     }
-  else
-    sockaddr = NULL;
 
   g_task_return_pointer (task, sockaddr, g_object_unref);
   g_object_unref (task);
diff --git a/gio/tests/network-address.c b/gio/tests/network-address.c
index e40f5d94d..238f2530e 100644
--- a/gio/tests/network-address.c
+++ b/gio/tests/network-address.c
@@ -555,6 +555,24 @@ assert_list_matches_expected (GList *result, GList *expected)
     }
 }
 
+static void
+assert_list_is_interleaved (GList *result)
+{
+  GSocketFamily last_family = -1;
+
+  while (result)
+    {
+      GInetAddress *address = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (result->data));
+      GSocketFamily family = g_inet_address_get_family (address);
+
+      if (last_family != -1)
+        g_assert_cmpint (family, !=, last_family);
+
+      last_family = family;
+      result = g_list_next (result);
+    }
+}
+
 typedef struct {
   MockResolver *mock_resolver;
   GResolver *original_resolver;
@@ -618,11 +636,13 @@ test_happy_eyeballs_basic (HappyEyeballsFixture *fixture,
   AsyncData data = { 0 };
 
   data.loop = fixture->loop;
+  data.delay_ms = 20;
 
   g_socket_address_enumerator_next_async (fixture->enumerator, NULL, got_addr, &data);
   g_main_loop_run (fixture->loop);
 
   assert_list_matches_expected (data.addrs, fixture->input_all_results);
+  assert_list_is_interleaved (data.addrs);
 }
 
 static void
@@ -657,6 +677,7 @@ test_happy_eyeballs_slow_ipv6 (HappyEyeballsFixture *fixture,
   g_main_loop_run (fixture->loop);
 
   assert_list_matches_expected (data.addrs, fixture->input_all_results);
+  assert_list_is_interleaved (data.addrs);
 }
 
 static void


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]