GVariant: support serialising to GVariantVectors
[platform/upstream/glib.git] / glib / gvariant-serialiser.c
index b10e618..c25a7be 100644 (file)
@@ -13,9 +13,7 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: Ryan Lortie <desrt@desrt.ca>
  */
@@ -31,7 +29,6 @@
 
 #include <string.h>
 
-#include "galias.h"
 
 /* GVariantSerialiser
  *
  * values is permitted (eg: 0 to 255 is a valid byte).  Special checks
  * need to be performed for booleans (only 0 or 1 allowed), strings
  * (properly nul-terminated) and object paths and signature strings
- * (meeting the DBus specification requirements).
+ * (meeting the D-Bus specification requirements).
  */
 
 /* < private >
  * GVariantSerialised:
  * @type_info: the #GVariantTypeInfo of this value
- * @data: the serialised data of this value, or %NULL
+ * @data: (allow-none): the serialised data of this value, or %NULL
  * @size: the size of this value
  *
  * A structure representing a GVariant in serialised form.  This
@@ -139,6 +136,34 @@ g_variant_serialised_check (GVariantSerialised serialised)
   else
     g_assert (serialised.size == 0 || serialised.data != NULL);
 
+  /* Depending on the native alignment requirements of the machine, the
+   * compiler will insert either 3 or 7 padding bytes after the char.
+   * This will result in the sizeof() the struct being 12 or 16.
+   * Subtract 9 to get 3 or 7 which is a nice bitmask to apply to get
+   * the alignment bits that we "care about" being zero: in the
+   * 4-aligned case, we care about 2 bits, and in the 8-aligned case, we
+   * care about 3 bits.
+   */
+  alignment &= sizeof (struct {
+                         char a;
+                         union {
+                           guint64 x;
+                           void *y;
+                           gdouble z;
+                         } b;
+                       }
+                      ) - 9;
+
+  /* Some OSes (FreeBSD is a known example) have a malloc() that returns
+   * unaligned memory if you request small sizes.  'malloc (1);', for
+   * example, has been seen to return pointers aligned to 6 mod 16.
+   *
+   * Check if this is a small allocation and return without enforcing
+   * the alignment assertion if this is the case.
+   */
+  if (serialised.size <= alignment)
+    return;
+
   g_assert_cmpint (alignment & (gsize) serialised.data, ==, 0);
 }
 
@@ -267,6 +292,19 @@ gvs_fixed_sized_maybe_serialise (GVariantSerialised        value,
     }
 }
 
+static gsize
+gvs_fixed_sized_maybe_write_to_vectors (GVariantVectors  *vectors,
+                                        GVariantTypeInfo *type_info,
+                                        gsize             size,
+                                        const gpointer   *children,
+                                        gsize             n_children)
+{
+  if (!n_children)
+    return 0;
+
+  return g_variant_callback_write_to_vectors (vectors, children[0], NULL);
+}
+
 static gboolean
 gvs_fixed_sized_maybe_is_normal (GVariantSerialised value)
 {
@@ -333,7 +371,7 @@ gvs_variable_sized_maybe_needed_size (GVariantTypeInfo         *type_info,
 {
   if (n_children)
     {
-      GVariantSerialised child = {  };
+      GVariantSerialised child = { 0, };
 
       gvs_filler (&child, children[0]);
 
@@ -359,6 +397,20 @@ gvs_variable_sized_maybe_serialise (GVariantSerialised        value,
     }
 }
 
+static void
+gvs_variable_sized_maybe_write_to_vectors (GVariantVectors  *vectors,
+                                           GVariantTypeInfo *type_info,
+                                           gsize             size,
+                                           const gpointer   *children,
+                                           gsize             n_children)
+{
+  if (n_children)
+    {
+      g_variant_callback_write_to_vectors (vectors, children[0], NULL);
+      g_variant_vectors_append_copy (vectors, "", 1);
+    }
+}
+
 static gboolean
 gvs_variable_sized_maybe_is_normal (GVariantSerialised value)
 {
@@ -414,7 +466,7 @@ static GVariantSerialised
 gvs_fixed_sized_array_get_child (GVariantSerialised value,
                                  gsize              index_)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
 
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_query (child.type_info, NULL, &child.size);
@@ -443,7 +495,7 @@ gvs_fixed_sized_array_serialise (GVariantSerialised        value,
                                  const gpointer           *children,
                                  gsize                     n_children)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   gsize i;
 
   child.type_info = g_variant_type_info_element (value.type_info);
@@ -457,10 +509,23 @@ gvs_fixed_sized_array_serialise (GVariantSerialised        value,
     }
 }
 
+static void
+gvs_fixed_sized_array_write_to_vectors (GVariantVectors  *vectors,
+                                        GVariantTypeInfo *type_info,
+                                        gsize             size,
+                                        const gpointer   *children,
+                                        gsize             n_children)
+{
+  gsize i;
+
+  for (i = 0; i < n_children; i++)
+    g_variant_callback_write_to_vectors (vectors, children[i], NULL);
+}
+
 static gboolean
 gvs_fixed_sized_array_is_normal (GVariantSerialised value)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
 
   child.type_info = g_variant_type_info_element (value.type_info);
   g_variant_type_info_query (child.type_info, NULL, &child.size);
@@ -484,7 +549,7 @@ gvs_fixed_sized_array_is_normal (GVariantSerialised value)
  * Variable sized arrays, containing variable-sized elements, must be
  * able to determine the boundaries between the elements.  The items
  * cannot simply be concatenated.  Additionally, we are faced with the
- * fact that non-fixed-sized values do not neccessarily have a size that
+ * fact that non-fixed-sized values do not necessarily have a size that
  * is a multiple of their alignment requirement, so we may need to
  * insert zero-filled padding.
  *
@@ -525,6 +590,7 @@ gvs_fixed_sized_array_is_normal (GVariantSerialised value)
  * normal form and that is the one that the serialiser must produce.
  */
 
+/* bytes may be NULL if (size == 0). */
 static inline gsize
 gvs_read_unaligned_le (guchar *bytes,
                        guint   size)
@@ -536,7 +602,8 @@ gvs_read_unaligned_le (guchar *bytes,
   } tmpvalue;
 
   tmpvalue.integer = 0;
-  memcpy (&tmpvalue.bytes, bytes, size);
+  if (bytes != NULL)
+    memcpy (&tmpvalue.bytes, bytes, size);
 
   return GSIZE_FROM_LE (tmpvalue.integer);
 }
@@ -620,7 +687,7 @@ static GVariantSerialised
 gvs_variable_sized_array_get_child (GVariantSerialised value,
                                     gsize              index_)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   gsize offset_size;
   gsize last_end;
   gsize start;
@@ -676,7 +743,7 @@ gvs_variable_sized_array_needed_size (GVariantTypeInfo         *type_info,
 
   for (i = 0; i < n_children; i++)
     {
-      GVariantSerialised child = {  };
+      GVariantSerialised child = { 0, };
 
       offset += (-offset) & alignment;
       gvs_filler (&child, children[i]);
@@ -706,7 +773,7 @@ gvs_variable_sized_array_serialise (GVariantSerialised        value,
 
   for (i = 0; i < n_children; i++)
     {
-      GVariantSerialised child = {  };
+      GVariantSerialised child = { 0, };
 
       while (offset & alignment)
         value.data[offset++] = '\0';
@@ -720,10 +787,42 @@ gvs_variable_sized_array_serialise (GVariantSerialised        value,
     }
 }
 
+static void
+gvs_variable_sized_array_write_to_vectors (GVariantVectors  *vectors,
+                                           GVariantTypeInfo *type_info,
+                                           gsize             size,
+                                           const gpointer   *children,
+                                           gsize             n_children)
+{
+  guint offset_key;
+  guint alignment;
+  gsize offset;
+  gsize i;
+
+  if (n_children == 0)
+    return;
+
+  offset_key = g_variant_vectors_reserve_offsets (vectors, n_children, gvs_get_offset_size (size));
+  g_variant_type_info_query (type_info, &alignment, NULL);
+  offset = 0;
+
+  for (i = 0; i < n_children; i++)
+    {
+      if ((-offset) & alignment)
+        offset += g_variant_vectors_append_pad (vectors, (-offset) & alignment);
+
+      offset += g_variant_callback_write_to_vectors (vectors, children[i], NULL);
+
+      g_variant_vectors_write_to_offsets (vectors, i, offset, offset_key);
+    }
+
+  g_variant_vectors_commit_offsets (vectors, offset_key);
+}
+
 static gboolean
 gvs_variable_sized_array_is_normal (GVariantSerialised value)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   gsize offsets_array_size;
   guchar *offsets_array;
   guint offset_size;
@@ -825,7 +924,7 @@ gvs_tuple_get_child (GVariantSerialised value,
                      gsize              index_)
 {
   const GVariantMemberInfo *member_info;
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   gsize offset_size;
   gsize start, end;
 
@@ -835,7 +934,7 @@ gvs_tuple_get_child (GVariantSerialised value,
 
   /* tuples are the only (potentially) fixed-sized containers, so the
    * only ones that have to deal with the possibility of having %NULL
-   * data with a non-zero %size if errors occured elsewhere.
+   * data with a non-zero %size if errors occurred elsewhere.
    */
   if G_UNLIKELY (value.data == NULL && value.size != 0)
     {
@@ -891,7 +990,7 @@ gvs_tuple_get_child (GVariantSerialised value,
       child.size = fixed_size;
     }
 
-  else /* G_VARIANT_MEMEBER_ENDING_OFFSET */
+  else /* G_VARIANT_MEMBER_ENDING_OFFSET */
     end = gvs_read_unaligned_le (value.data + value.size -
                                  offset_size * (member_info->i + 2),
                                  offset_size);
@@ -936,7 +1035,7 @@ gvs_tuple_needed_size (GVariantTypeInfo         *type_info,
         offset += fixed_size;
       else
         {
-          GVariantSerialised child = {  };
+          GVariantSerialised child = { 0, };
 
           gvs_filler (&child, children[i]);
           offset += child.size;
@@ -962,7 +1061,7 @@ gvs_tuple_serialise (GVariantSerialised        value,
   for (i = 0; i < n_children; i++)
     {
       const GVariantMemberInfo *member_info;
-      GVariantSerialised child = {  };
+      GVariantSerialised child = { 0, };
       guint alignment;
 
       member_info = g_variant_type_info_member_info (value.type_info, i);
@@ -987,6 +1086,95 @@ gvs_tuple_serialise (GVariantSerialised        value,
     value.data[offset++] = '\0';
 }
 
+
+static void
+gvs_tuple_write_to_vectors (GVariantVectors  *vectors,
+                            GVariantTypeInfo *type_info,
+                            gsize             size,
+                            const gpointer   *children,
+                            gsize             n_children)
+{
+  const GVariantMemberInfo *member_info = NULL;
+  gsize fixed_size;
+  gsize offset;
+  gsize i;
+
+  if (n_children == 0)
+    {
+      g_variant_vectors_append_copy (vectors, "", 1);
+      return;
+    }
+
+  g_variant_type_info_query (type_info, NULL, &fixed_size);
+  offset = 0;
+
+  if (!fixed_size)
+    {
+      gsize n_offsets;
+
+      member_info = g_variant_type_info_member_info (type_info, n_children - 1);
+      n_offsets = member_info->i + 1;
+
+      if (n_offsets)
+        {
+          gsize offset_key = 0;
+
+          offset_key = g_variant_vectors_reserve_offsets (vectors, n_offsets, gvs_get_offset_size (size));
+
+          for (i = 0; i < n_children; i++)
+            {
+              guint alignment;
+
+              member_info = g_variant_type_info_member_info (type_info, i);
+              g_variant_type_info_query (member_info->type_info, &alignment, NULL);
+
+              if ((-offset) & alignment)
+                offset += g_variant_vectors_append_pad (vectors, (-offset) & alignment);
+
+              offset += g_variant_callback_write_to_vectors (vectors, children[i], NULL);
+
+              if (member_info->ending_type == G_VARIANT_MEMBER_ENDING_OFFSET)
+                g_variant_vectors_write_to_offsets (vectors, --n_offsets, offset, offset_key);
+            }
+
+          g_variant_vectors_commit_offsets (vectors, offset_key);
+        }
+      else
+        {
+          for (i = 0; i < n_children; i++)
+            {
+              guint alignment;
+
+              member_info = g_variant_type_info_member_info (type_info, i);
+              g_variant_type_info_query (member_info->type_info, &alignment, NULL);
+
+              if ((-offset) & alignment)
+                offset += g_variant_vectors_append_pad (vectors, (-offset) & alignment);
+
+              offset += g_variant_callback_write_to_vectors (vectors, children[i], NULL);
+            }
+        }
+    }
+  else
+    {
+      for (i = 0; i < n_children; i++)
+        {
+          guint alignment;
+
+          member_info = g_variant_type_info_member_info (type_info, i);
+          g_variant_type_info_query (member_info->type_info, &alignment, NULL);
+
+          if ((-offset) & alignment)
+            offset += g_variant_vectors_append_pad (vectors, (-offset) & alignment);
+
+          offset += g_variant_callback_write_to_vectors (vectors, children[i], NULL);
+        }
+
+      g_assert (fixed_size - offset < 8);
+      g_variant_vectors_append_pad (vectors, fixed_size - offset);
+    }
+}
+
 static gboolean
 gvs_tuple_is_normal (GVariantSerialised value)
 {
@@ -996,6 +1184,10 @@ gvs_tuple_is_normal (GVariantSerialised value)
   gsize offset;
   gsize i;
 
+  /* as per the comment in gvs_tuple_get_child() */
+  if G_UNLIKELY (value.data == NULL && value.size != 0)
+    return FALSE;
+
   offset_size = gvs_get_offset_size (value.size);
   length = g_variant_type_info_n_members (value.type_info);
   offset_ptr = value.size;
@@ -1041,6 +1233,9 @@ gvs_tuple_is_normal (GVariantSerialised value)
 
           end = gvs_read_unaligned_le (value.data + offset_ptr, offset_size);
           break;
+
+        default:
+          g_assert_not_reached ();
         }
 
       if (end < offset || end > offset_ptr)
@@ -1109,7 +1304,7 @@ static inline GVariantSerialised
 gvs_variant_get_child (GVariantSerialised value,
                        gsize              index_)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
 
   /* NOTE: not O(1) and impossible for it to be... */
   if (value.size)
@@ -1166,7 +1361,7 @@ gvs_variant_needed_size (GVariantTypeInfo         *type_info,
                          const gpointer           *children,
                          gsize                     n_children)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   const gchar *type_string;
 
   gvs_filler (&child, children[0]);
@@ -1181,7 +1376,7 @@ gvs_variant_serialise (GVariantSerialised        value,
                        const gpointer           *children,
                        gsize                     n_children)
 {
-  GVariantSerialised child = {  };
+  GVariantSerialised child = { 0, };
   const gchar *type_string;
 
   child.data = value.data;
@@ -1192,6 +1387,23 @@ gvs_variant_serialise (GVariantSerialised        value,
   memcpy (value.data + child.size + 1, type_string, strlen (type_string));
 }
 
+static void
+gvs_variant_write_to_vectors (GVariantVectors  *vectors,
+                              GVariantTypeInfo *type_info,
+                              gsize             size,
+                              const gpointer   *children,
+                              gsize             n_children)
+{
+  GVariantTypeInfo *child_type_info;
+  const gchar *type_string;
+
+  g_variant_callback_write_to_vectors (vectors, children[0], &child_type_info);
+  type_string = g_variant_type_info_get_type_string (child_type_info);
+
+  g_variant_vectors_append_copy (vectors, "", 1);
+  g_variant_vectors_append_copy (vectors, type_string, strlen (type_string));
+}
+
 static inline gboolean
 gvs_variant_is_normal (GVariantSerialised value)
 {
@@ -1270,11 +1482,12 @@ gvs_variant_is_normal (GVariantSerialised value)
 /* < private >
  * g_variant_serialised_n_children:
  * @serialised: a #GVariantSerialised
- * @returns: the number of children
  *
  * For serialised data that represents a container value (maybes,
  * tuples, arrays, variants), determine how many child items are inside
  * that container.
+ *
+ * Returns: the number of children
  */
 gsize
 g_variant_serialised_n_children (GVariantSerialised serialised)
@@ -1293,7 +1506,6 @@ g_variant_serialised_n_children (GVariantSerialised serialised)
  * g_variant_serialised_get_child:
  * @serialised: a #GVariantSerialised
  * @index_: the index of the child to fetch
- * @returns: a #GVariantSerialised for the child
  *
  * Extracts a child from a serialised data representing a container
  * value.
@@ -1308,6 +1520,8 @@ g_variant_serialised_n_children (GVariantSerialised serialised)
  * item of a variable-sized type is being returned.
  *
  * .data is never non-%NULL if size is 0.
+ *
+ * Returns: a #GVariantSerialised for the child
  */
 GVariantSerialised
 g_variant_serialised_get_child (GVariantSerialised serialised,
@@ -1399,7 +1613,21 @@ g_variant_serialiser_needed_size (GVariantTypeInfo         *type_info,
 
                   return gvs_/**/,/**/_needed_size (type_info, gvs_filler,
                                                     children, n_children);
+                 )
+  g_assert_not_reached ();
+}
 
+
+void
+g_variant_serialiser_write_to_vectors (GVariantVectors  *vectors,
+                                       GVariantTypeInfo *type_info,
+                                       gsize             size,
+                                       const gpointer   *children,
+                                       gsize             n_children)
+{
+  DISPATCH_CASES (type_info,
+                  gvs_/**/,/**/_write_to_vectors (vectors, type_info, size, children, n_children);
+                  return;
                  )
   g_assert_not_reached ();
 }
@@ -1514,6 +1742,9 @@ g_variant_serialised_is_normal (GVariantSerialised serialised)
 
                  )
 
+  if (serialised.data == NULL)
+    return FALSE;
+
   /* some hard-coded terminal cases */
   switch (g_variant_type_info_get_type_char (serialised.type_info))
     {
@@ -1558,25 +1789,30 @@ gboolean
 g_variant_serialiser_is_string (gconstpointer data,
                                 gsize         size)
 {
-  const gchar *string = data;
+  const gchar *expected_end;
+  const gchar *end;
 
   if (size == 0)
     return FALSE;
 
-  if (string[size - 1] != '\0')
+  expected_end = ((gchar *) data) + size - 1;
+
+  if (*expected_end != '\0')
     return FALSE;
 
-  return strlen (string) == size - 1;
+  g_utf8_validate (data, size, &end);
+
+  return end == expected_end;
 }
 
 /* < private >
  * g_variant_serialiser_is_object_path:
- * @data: a possible DBus object path
+ * @data: a possible D-Bus object path
  * @size: the size of @data
  *
  * Performs the checks for being a valid string.
  *
- * Also, ensures that @data is a valid DBus object path, as per the DBus
+ * Also, ensures that @data is a valid DBus object path, as per the D-Bus
  * specification.
  */
 gboolean
@@ -1623,13 +1859,13 @@ g_variant_serialiser_is_object_path (gconstpointer data,
 
 /* < private >
  * g_variant_serialiser_is_signature:
- * @data: a possible DBus signature
+ * @data: a possible D-Bus signature
  * @size: the size of @data
  *
  * Performs the checks for being a valid string.
  *
- * Also, ensures that @data is a valid DBus type signature, as per the
- * DBus specification.
+ * Also, ensures that @data is a valid D-Bus type signature, as per the
+ * D-Bus specification.
  */
 gboolean
 g_variant_serialiser_is_signature (gconstpointer data,
@@ -1654,4 +1890,5 @@ g_variant_serialiser_is_signature (gconstpointer data,
   return TRUE;
 }
 
+/* Epilogue {{{1 */
 /* vim:set foldmethod=marker: */