GDBusMessage: fast-path decoding of fixed arrays
authorRyan Lortie <desrt@desrt.ca>
Fri, 4 Jul 2014 19:00:49 +0000 (15:00 -0400)
committerRyan Lortie <desrt@desrt.ca>
Wed, 9 Jul 2014 14:43:26 +0000 (10:43 -0400)
Instead of creating a separate GVariant for each of the 'y's in an 'ay',
use g_variant_new_fixed_array().

https://bugzilla.gnome.org/show_bug.cgi?id=732754

gio/gdbusmessage.c

index 50105e8..b9cb03e 100644 (file)
@@ -1205,6 +1205,27 @@ g_dbus_message_set_unix_fd_list (GDBusMessage  *message,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static guint
+get_type_fixed_size (const GVariantType *type)
+{
+  /* NB: we do not treat 'b' as fixed-size here because GVariant and
+   * D-Bus disagree about the size.
+   */
+  switch (*g_variant_type_peek_string (type))
+    {
+    case 'y':
+      return 1;
+    case 'n': case 'q':
+      return 2;
+    case 'i': case 'u': case 'h':
+      return 4;
+    case 'x': case 't': case 'd':
+      return 8;
+    default:
+      return 0;
+    }
+}
+
 static gboolean
 validate_headers (GDBusMessage  *message,
                   GError       **error)
@@ -1378,6 +1399,35 @@ read_string (GMemoryBuffer  *mbuf,
   return str;
 }
 
+static gconstpointer
+read_bytes (GMemoryBuffer  *mbuf,
+            gsize           len,
+            GError        **error)
+{
+  gconstpointer result;
+
+  if (mbuf->pos + len > mbuf->valid_len || mbuf->pos + len < mbuf->pos)
+    {
+      mbuf->pos = mbuf->valid_len;
+      /* G_GSIZE_FORMAT doesn't work with gettext, so we use %lu */
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   g_dngettext (GETTEXT_PACKAGE,
+                                "Wanted to read %lu byte but only got %lu",
+                                "Wanted to read %lu bytes but only got %lu",
+                                (gulong)len),
+                                (gulong)len,
+                   (gulong)(mbuf->valid_len - mbuf->pos));
+      return NULL;
+    }
+
+  result = mbuf->data + mbuf->pos;
+  mbuf->pos += len;
+
+  return result;
+}
+
 /* if just_align==TRUE, don't read a value, just align the input stream wrt padding */
 
 /* returns a non-floating GVariant! */
@@ -1588,10 +1638,8 @@ parse_value_from_blob (GMemoryBuffer       *buf,
       if (!just_align)
         {
           guint32 array_len;
-          goffset offset;
-          goffset target;
           const GVariantType *element_type;
-          GVariantBuilder builder;
+          guint fixed_size;
 
           array_len = g_memory_buffer_read_uint32 (buf);
 
@@ -1614,44 +1662,82 @@ parse_value_from_blob (GMemoryBuffer       *buf,
               goto fail;
             }
 
-          g_variant_builder_init (&builder, type);
           element_type = g_variant_type_element (type);
+          fixed_size = get_type_fixed_size (element_type);
 
-          if (array_len == 0)
+          /* Fast-path the cases like 'ay', etc. */
+          if (fixed_size != 0)
             {
-              GVariant *item;
-              item = parse_value_from_blob (buf,
-                                            element_type,
-                                            TRUE,
-                                            indent + 2,
-                                            NULL);
-              g_assert (item == NULL);
+              gconstpointer array_data;
+
+              if (array_len % fixed_size != 0)
+                {
+                  g_set_error (&local_error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_ARGUMENT,
+                               _("Encountered array of type 'a%c', expected to have a length a multiple "
+                                 "of %u bytes, but found to be %u bytes in length"),
+                               g_variant_type_peek_string (element_type)[0], fixed_size, array_len);
+                  goto fail;
+                }
+
+              ensure_input_padding (buf, fixed_size);
+              array_data = read_bytes (buf, array_len, &local_error);
+              if (array_data == NULL)
+                goto fail;
+
+              ret = g_variant_new_fixed_array (element_type, array_data, array_len / fixed_size, fixed_size);
+
+              if (g_memory_buffer_is_byteswapped (buf))
+                {
+                  GVariant *tmp = g_variant_ref_sink (ret);
+                  ret = g_variant_byteswap (tmp);
+                  g_variant_unref (tmp);
+                }
             }
           else
             {
-              /* TODO: optimize array of primitive types */
-              offset = buf->pos;
-              target = offset + array_len;
-              while (offset < target)
+              GVariantBuilder builder;
+              goffset offset;
+              goffset target;
+
+              g_variant_builder_init (&builder, type);
+
+              if (array_len == 0)
                 {
                   GVariant *item;
                   item = parse_value_from_blob (buf,
                                                 element_type,
-                                                FALSE,
+                                                TRUE,
                                                 indent + 2,
-                                                &local_error);
-                  if (item == NULL)
+                                                NULL);
+                  g_assert (item == NULL);
+                }
+              else
+                {
+                  offset = buf->pos;
+                  target = offset + array_len;
+                  while (offset < target)
                     {
-                      g_variant_builder_clear (&builder);
-                      goto fail;
+                      GVariant *item;
+                      item = parse_value_from_blob (buf,
+                                                    element_type,
+                                                    FALSE,
+                                                    indent + 2,
+                                                    &local_error);
+                      if (item == NULL)
+                        {
+                          g_variant_builder_clear (&builder);
+                          goto fail;
+                        }
+                      g_variant_builder_add_value (&builder, item);
+                      g_variant_unref (item);
+                      offset = buf->pos;
                     }
-                  g_variant_builder_add_value (&builder, item);
-                  g_variant_unref (item);
-                  offset = buf->pos;
                 }
-            }
 
-          ret = g_variant_builder_end (&builder);
+              ret = g_variant_builder_end (&builder);
+            }
         }
       break;
 
@@ -1812,12 +1898,9 @@ parse_value_from_blob (GMemoryBuffer       *buf,
   is_leaf = is_leaf; /* To avoid -Wunused-but-set-variable */
 #endif /* DEBUG_SERIALIZER */
 
-  /* sink the reference */
+  /* sink the reference, if floating */
   if (ret != NULL)
-    {
-      g_assert (g_variant_is_floating (ret));
-      g_variant_ref_sink (ret);
-    }
+    g_variant_take_ref (ret);
   return ret;
 
  fail: