[glib] GVariant: add loading, byteswapping, normalisation



commit 9dea0253a3ded0e32daa33f11850797109018326
Author: Ryan Lortie <desrt desrt ca>
Date:   Sun Mar 14 15:55:48 2010 -0400

    GVariant: add loading, byteswapping, normalisation

 docs/reference/glib/glib-sections.txt |    4 +
 glib/glib.symbols                     |    5 +
 glib/gvariant-core.c                  |   61 +++++++++-
 glib/gvariant.c                       |  207 +++++++++++++++++++++++++++++++++
 glib/gvariant.h                       |   10 ++
 glib/tests/gvariant.c                 |   72 ++++++++++++
 6 files changed, 355 insertions(+), 4 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 5ae7413..05b06cd 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -2857,6 +2857,10 @@ g_variant_get_fixed_array
 g_variant_get_size
 g_variant_get_data
 g_variant_store
+g_variant_new_from_data
+g_variant_byteswap
+g_variant_get_normal_form
+g_variant_is_normal_form
 
 <SUBSECTION>
 g_variant_hash
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 9d0c2a1..ee9da31 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -1705,6 +1705,7 @@ g_variant_get_child_value
 g_variant_get_size
 g_variant_get_data
 g_variant_store
+g_variant_is_normal_form
 #endif
 
 #if IN_FILE(__G_VARIANT_C__)
@@ -1788,6 +1789,10 @@ g_variant_builder_add
 g_variant_get_child
 g_variant_iter_next
 g_variant_iter_loop
+
+g_variant_new_from_data
+g_variant_get_normal_form
+g_variant_byteswap
 #endif
 #endif
 
diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
index 51e1fac..183fa09 100644
--- a/glib/gvariant-core.c
+++ b/glib/gvariant-core.c
@@ -709,10 +709,9 @@ g_variant_get_size (GVariant *value)
  * @returns: the serialised form of @value, or %NULL
  *
  * Returns a pointer to the serialised form of a #GVariant instance.
- * The returned data is in machine native byte order but may not be in
- * fully-normalised form if read from an untrusted source.  The returned
- * data must not be freed; it remains valid for as long as @value
- * exists.
+ * The returned data may not be in fully-normalised form if read from an
+ * untrusted source.  The returned data must not be freed; it remains
+ * valid for as long as @value exists.
  *
  * If @value is a fixed-sized value that was deserialised from a
  * corrupted serialised container then %NULL may be returned.  In this
@@ -875,5 +874,59 @@ g_variant_store (GVariant *value,
   g_variant_unlock (value);
 }
 
+/**
+ * g_variant_is_normal_form:
+ * @value: a #GVariant instance
+ * @returns: %TRUE if @value is in normal form
+ *
+ * Checks if @value is in normal form.
+ *
+ * The main reason to do this is to detect if a given chunk of
+ * serialised data is in normal form: load the data into a #GVariant
+ * using g_variant_create_from_data() and then use this function to
+ * check.
+ *
+ * If @value is found to be in normal form then it will be marked as
+ * being trusted.  If the value was already marked as being trusted then
+ * this function will immediately return %TRUE.
+ *
+ * Since: 2.24
+ **/
+gboolean
+g_variant_is_normal_form (GVariant *value)
+{
+  if (value->state & STATE_TRUSTED)
+    return TRUE;
+
+  g_variant_lock (value);
+
+  if (value->state & STATE_SERIALISED)
+    {
+      GVariantSerialised serialised = {
+        value->type_info,
+        (gpointer) value->contents.serialised.data,
+        value->size
+      };
+
+      if (g_variant_serialised_is_normal (serialised))
+        value->state |= STATE_TRUSTED;
+    }
+  else
+    {
+      gboolean normal = TRUE;
+      gsize i;
+
+      for (i = 0; i < value->contents.tree.n_children; i++)
+        normal &= g_variant_is_normal_form (value->contents.tree.children[i]);
+
+      if (normal)
+        value->state |= STATE_TRUSTED;
+    }
+
+  g_variant_unlock (value);
+
+  return (value->state & STATE_TRUSTED) != 0;
+}
+
 #define __G_VARIANT_CORE_C__
 #include "galiasdef.c"
diff --git a/glib/gvariant.c b/glib/gvariant.c
index 1cacb63..342eeeb 100644
--- a/glib/gvariant.c
+++ b/glib/gvariant.c
@@ -3912,6 +3912,213 @@ g_variant_iter_loop (GVariantIter *iter,
   return value != NULL;
 }
 
+/* Serialised data {{{1 */
+static GVariant *
+g_variant_deep_copy (GVariant *value)
+{
+  switch (g_variant_classify (value))
+    {
+    case G_VARIANT_CLASS_MAYBE:
+    case G_VARIANT_CLASS_ARRAY:
+    case G_VARIANT_CLASS_TUPLE:
+    case G_VARIANT_CLASS_DICT_ENTRY:
+    case G_VARIANT_CLASS_VARIANT:
+      {
+        GVariantBuilder builder;
+        GVariantIter iter;
+        GVariant *child;
+
+        g_variant_builder_init (&builder, g_variant_get_type (value));
+        g_variant_iter_init (&iter, value);
+
+        while ((child = g_variant_iter_next_value (&iter)))
+          {
+            g_variant_builder_add_value (&builder, g_variant_deep_copy (child));
+            g_variant_unref (child);
+          }
+
+        return g_variant_builder_end (&builder);
+      }
+
+    case G_VARIANT_CLASS_BOOLEAN:
+      return g_variant_new_boolean (g_variant_get_boolean (value));
+
+    case G_VARIANT_CLASS_BYTE:
+      return g_variant_new_byte (g_variant_get_byte (value));
+
+    case G_VARIANT_CLASS_INT16:
+      return g_variant_new_int16 (g_variant_get_int16 (value));
+
+    case G_VARIANT_CLASS_UINT16:
+      return g_variant_new_uint16 (g_variant_get_uint16 (value));
+
+    case G_VARIANT_CLASS_INT32:
+      return g_variant_new_int32 (g_variant_get_int32 (value));
+
+    case G_VARIANT_CLASS_UINT32:
+      return g_variant_new_uint32 (g_variant_get_uint32 (value));
+
+    case G_VARIANT_CLASS_INT64:
+      return g_variant_new_int64 (g_variant_get_int64 (value));
+
+    case G_VARIANT_CLASS_UINT64:
+      return g_variant_new_uint64 (g_variant_get_uint64 (value));
+
+    case G_VARIANT_CLASS_HANDLE:
+      return g_variant_new_handle (g_variant_get_handle (value));
+
+    case G_VARIANT_CLASS_DOUBLE:
+      return g_variant_new_double (g_variant_get_double (value));
+
+    case G_VARIANT_CLASS_STRING:
+      return g_variant_new_string (g_variant_get_string (value, NULL));
+
+    case G_VARIANT_CLASS_OBJECT_PATH:
+      return g_variant_new_object_path (g_variant_get_string (value, NULL));
+
+    case G_VARIANT_CLASS_SIGNATURE:
+      return g_variant_new_signature (g_variant_get_string (value, NULL));
+    }
+
+  g_assert_not_reached ();
+}
+
+/**
+ * g_variant_get_normal_form:
+ * @value: a #GVariant
+ * @returns: a trusted #GVariant
+ *
+ * Gets a #GVariant instance that has the same value as @value and is
+ * trusted to be in normal form.
+ *
+ * If @value is already trusted to be in normal form then a new
+ * reference to @value is returned.
+ *
+ * If @value is not already trusted, then it is scanned to check if it
+ * is in normal form.  If it is found to be in normal form then it is
+ * marked as trusted and a new reference to it is returned.
+ *
+ * If @value is found not to be in normal form then a new trusted
+ * #GVariant is created with the same value as @value.
+ *
+ * It makes sense to call this function if you've received #GVariant
+ * data from untrusted sources and you want to ensure your serialised
+ * output is definitely in normal form.
+ *
+ * Since: 2.24
+ **/
+GVariant *
+g_variant_get_normal_form (GVariant *value)
+{
+  GVariant *trusted;
+
+  if (g_variant_is_normal_form (value))
+    return g_variant_ref (value);
+
+  trusted = g_variant_deep_copy (value);
+  g_assert (g_variant_is_trusted (trusted));
+
+  return g_variant_ref_sink (trusted);
+}
+
+/**
+ * g_variant_byteswap:
+ * @value: a #GVariant
+ * @returns: the byteswapped form of @value
+ *
+ * Performs a byteswapping operation on the contents of @value.  The
+ * result is that all multi-byte numeric data contained in @value is
+ * byteswapped.  That includes 16, 32, and 64bit signed and unsigned
+ * integers as well as file handles and double precision floating point
+ * values.
+ *
+ * This function is an identity mapping on any value that does not
+ * contain multi-byte numeric data.  That include strings, booleans,
+ * bytes and containers containing only these things (recursively).
+ *
+ * The returned value is always in normal form and is marked as trusted.
+ *
+ * Since: 2.24
+ **/
+GVariant *
+g_variant_byteswap (GVariant *value)
+{
+  GVariantSerialised serialised;
+  GVariant *trusted;
+  GBuffer *buffer;
+  GVariant *new;
+
+  trusted = g_variant_get_normal_form (value);
+  serialised.type_info = g_variant_get_type_info (trusted);
+  serialised.size = g_variant_get_size (trusted);
+  serialised.data = g_malloc (serialised.size);
+  g_variant_store (trusted, serialised.data);
+  g_variant_unref (trusted);
+
+  g_variant_serialised_byteswap (serialised);
+
+  buffer = g_buffer_new_take_data (serialised.data, serialised.size);
+  new = g_variant_new_from_buffer (g_variant_get_type (value), buffer, TRUE);
+  g_buffer_unref (buffer);
+
+  return g_variant_ref_sink (new);
+}
+
+/**
+ * g_variant_new_from_data:
+ * @type: a #GVariantType
+ * @data: the serialised data
+ * @size: the size of @data
+ * @trusted: %TRUE if @data is definitely in normal form
+ * @notify: function to call when @data is no longer needed
+ * @user_data: data for @notify
+ * @returns: a new floating #GVariant of type @type
+ *
+ * Creates a new #GVariant instance from serialised data.
+ *
+ * @type is the type of #GVariant instance that will be constructed.
+ * The interpretation of @data depends on knowing the type.
+ *
+ * @data is not modified by this function and must remain valid with an
+ * unchanging value until such a time as @notify is called with
+ * @user_data.  If the contents of @data change before that time then
+ * the result is undefined.
+ *
+ * If @data is trusted to be serialised data in normal form then
+ * @trusted should be %TRUE.  This applies to serialised data created
+ * within this process or read from a trusted location on the disk (such
+ * as a file installed in /usr/lib alongside your application).  You
+ * should set trusted to %FALSE if @data is read from the network, a
+ * file in the user's home directory, etc.
+ *
+ * @notify will be called with @user_data when @data is no longer
+ * needed.  The exact time of this call is unspecified and might even be
+ * before this function returns.
+ *
+ * Since: 2.24
+ **/
+GVariant *
+g_variant_new_from_data (const GVariantType *type,
+                         gconstpointer       data,
+                         gsize               size,
+                         gboolean            trusted,
+                         GDestroyNotify      notify,
+                         gpointer            user_data)
+{
+  GVariant *value;
+  GBuffer *buffer;
+
+  if (notify)
+    buffer = g_buffer_new_from_pointer (data, size, notify, user_data);
+  else
+    buffer = g_buffer_new_from_static_data (data, size);
+
+  value = g_variant_new_from_buffer (type, buffer, trusted);
+  g_buffer_unref (buffer);
+
+  return value;
+}
+
 /* Epilogue {{{1 */
 #define __G_VARIANT_C__
 #include "galiasdef.c"
diff --git a/glib/gvariant.h b/glib/gvariant.h
index abf8102..19c57c6 100644
--- a/glib/gvariant.h
+++ b/glib/gvariant.h
@@ -139,6 +139,16 @@ guint                           g_variant_hash                          (gconstp
 gboolean                        g_variant_equal                         (gconstpointer         one,
                                                                          gconstpointer         two);
 
+GVariant *                      g_variant_get_normal_form               (GVariant             *variant);
+gboolean                        g_variant_is_normal_form                (GVariant             *variant);
+GVariant *                      g_variant_byteswap                      (GVariant             *variant);
+GVariant *                      g_variant_new_from_data                 (const GVariantType   *type,
+                                                                         gconstpointer         data,
+                                                                         gsize                 size,
+                                                                         gboolean              trusted,
+                                                                         GDestroyNotify        notify,
+                                                                         gpointer              user_data);
+
 typedef struct _GVariantIter GVariantIter;
 struct _GVariantIter {
   /*< private >*/
diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c
index af648a7..550fdac 100644
--- a/glib/tests/gvariant.c
+++ b/glib/tests/gvariant.c
@@ -3385,6 +3385,77 @@ test_hashing (void)
   g_variant_type_info_assert_no_infos ();
 }
 
+static void
+test_gv_byteswap ()
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+# define native16(x)  x, 0
+# define swapped16(x) 0, x
+#else
+# define native16(x)  0, x
+# define swapped16(x) x, 0
+#endif
+  /* all kinds of of crazy randomised testing already performed on the
+   * byteswapper in the /gvariant/serialiser/byteswap test and all kinds
+   * of crazy randomised testing performed against the serialiser
+   * normalisation functions in the /gvariant/serialiser/fuzz/ tests.
+   *
+   * just test a few simple cases here to make sure they each work
+   */
+  guchar valid_data[] = { 'a', '\0', swapped16(66), 2,
+                          0,
+                          'b', '\0', swapped16(77), 2,
+                          5, 11 };
+  guchar corrupt_data[] = { 'a', '\0', swapped16(66), 2,
+                            0,
+                            'b', '\0', swapped16(77), 2,
+                            6, 11 };
+  GVariant *value, *swapped, *reswapped;
+  gchar *string, *string2;
+
+
+  /* trusted */
+  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
+                                   valid_data, sizeof valid_data, TRUE,
+                                   NULL, NULL);
+  swapped = g_variant_byteswap (value);
+  g_variant_unref (value);
+  g_assert (g_variant_get_size (swapped) == 13);
+  string = g_variant_print (swapped, FALSE);
+  g_variant_unref (swapped);
+  g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]");
+  g_free (string);
+
+  /* untrusted but valid */
+  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
+                                   valid_data, sizeof valid_data, FALSE,
+                                   NULL, NULL);
+  swapped = g_variant_byteswap (value);
+  g_variant_unref (value);
+  g_assert (g_variant_get_size (swapped) == 13);
+  string = g_variant_print (swapped, FALSE);
+  g_variant_unref (swapped);
+  g_assert_cmpstr (string, ==, "[('a', 66), ('b', 77)]");
+  g_free (string);
+
+  /* untrusted, invalid */
+  value = g_variant_new_from_data (G_VARIANT_TYPE ("a(sn)"),
+                                   corrupt_data, sizeof corrupt_data, FALSE,
+                                   NULL, NULL);
+  string = g_variant_print (value, FALSE);
+  swapped = g_variant_byteswap (value);
+  g_variant_unref (value);
+  g_assert (g_variant_get_size (swapped) == 13);
+  value = g_variant_byteswap (swapped);
+  g_variant_unref (swapped);
+  string2 = g_variant_print (value, FALSE);
+  g_assert (g_variant_get_size (value) == 13);
+  g_variant_unref (value);
+  g_assert_cmpstr (string, ==, string2);
+  g_free (string2);
+  g_free (string);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -3418,6 +3489,7 @@ main (int argc, char **argv)
   g_test_add_func ("/gvariant/valist", test_valist);
   g_test_add_func ("/gvariant/builder-memory", test_builder_memory);
   g_test_add_func ("/gvariant/hashing", test_hashing);
+  g_test_add_func ("/gvariant/byteswap", test_gv_byteswap);
 
   return g_test_run ();
 }



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