From 25cb95f004e1c21573a063e4f3500436fa0c0e2a Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 17 Apr 2012 15:29:14 +0100 Subject: [PATCH 1/3] dbus_g_message_read_variants, dbus_g_message_write_variants: add and test These will let us send GVariant-based messages over libdbus connections, to facilitate porting from dbus-glib to GDBus. Signed-off-by: Simon McVittie --- configure.ac | 5 + dbus/Makefile.am | 2 + dbus/dbus-glib-lowlevel.h | 1 + dbus/dbus-gvariant.c | 517 +++++++++++++++++++++++++++++++++++++++++++++ dbus/dbus-gvariant.h | 29 +++ test/core/test-gvariant.c | 90 +++++++- 6 files changed, 643 insertions(+), 1 deletion(-) create mode 100644 dbus/dbus-gvariant.c create mode 100644 dbus/dbus-gvariant.h diff --git a/configure.ac b/configure.ac index caa65a0..3004094 100644 --- a/configure.ac +++ b/configure.ac @@ -335,6 +335,11 @@ PKG_CHECK_MODULES([DBUS], [dbus-1 >= 1.2.16]) AC_SUBST([DBUS_CFLAGS]) AC_SUBST([DBUS_LIBS]) +dbusglib_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $DBUS_CFLAGS" +AC_CHECK_TYPES([DBusBasicValue], [], [], [#include ]) +CFLAGS="$dbusglib_save_CFLAGS" + # Glib detection PKG_CHECK_MODULES([DBUS_GLIB], [gobject-2.0 >= 2.26, gio-2.0 >= 2.26]) PKG_CHECK_MODULES(DBUS_GLIB_THREADS, gthread-2.0 >= 2.6, have_glib_threads=yes, have_glib_threads=no) diff --git a/dbus/Makefile.am b/dbus/Makefile.am index b865286..2c10c1c 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -37,12 +37,14 @@ libdbus_glib_1_la_SOURCES = \ dbus-gvalue.c \ dbus-gvalue.h \ dbus-gvalue-parse-variant.c \ + dbus-gvariant.c \ dbus-gthread.c \ $(DBUS_GLIB_INTERNALS) libdbus_glib_HEADERS = \ dbus-gtype-specialized.h \ dbus-gvalue-parse-variant.h \ + dbus-gvariant.h \ dbus-glib.h \ dbus-glib-lowlevel.h diff --git a/dbus/dbus-glib-lowlevel.h b/dbus/dbus-glib-lowlevel.h index d08f951..e016865 100644 --- a/dbus/dbus-glib-lowlevel.h +++ b/dbus/dbus-glib-lowlevel.h @@ -26,6 +26,7 @@ #include #include +#include G_BEGIN_DECLS diff --git a/dbus/dbus-gvariant.c b/dbus/dbus-gvariant.c new file mode 100644 index 0000000..e3da291 --- /dev/null +++ b/dbus/dbus-gvariant.c @@ -0,0 +1,517 @@ +/* dbus-gvariant - GVariant to/from DBusMessageIter + * + * Copyright © 2012 Collabora Ltd. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "config.h" + +#include + +#ifndef HAVE_DBUSBASICVALUE +typedef union +{ + unsigned char bytes[8]; + dbus_int16_t i16; + dbus_uint16_t u16; + dbus_int32_t i32; + dbus_uint32_t u32; + dbus_bool_t bool_val; + dbus_int64_t i64; + dbus_uint64_t u64; + DBus8ByteStruct eight; + double dbl; + unsigned char byt; + char *str; + int fd; +} DBusBasicValue; +#endif + +static void oom (void) G_GNUC_NORETURN; +static void +oom (void) +{ + g_error ("no memory"); +} + +static GVariant *read_variant (DBusMessageIter *iter, GError **error); + +static GVariant * +fixed_array_to_variant (DBusMessageIter *iter, + int subtype, + gsize element_size) +{ + DBusMessageIter sub; + gconstpointer blob; + int n_elements; + gchar type_string[] = { subtype, 0 }; + + dbus_message_iter_recurse (iter, &sub); + dbus_message_iter_get_fixed_array (&sub, &blob, + &n_elements); + return g_variant_new_fixed_array (G_VARIANT_TYPE (type_string), + blob, n_elements, element_size); +} + +static GVariant * +iterable_to_variant (DBusMessage *message, + DBusMessageIter *iter, + GError **error) +{ + GVariantBuilder builder; + DBusMessageIter sub; + char *signature; + + g_assert (message != NULL || iter != NULL); + g_assert (message == NULL || iter == NULL); + + if (message != NULL) + { + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + } + else + { + signature = dbus_message_iter_get_signature (iter); + + if (!g_variant_type_string_is_valid (signature)) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "GVariant considers signature to be invalid: %s", signature); + dbus_free (signature); + return NULL; + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE (signature)); + + dbus_free (signature); + } + + if (message != NULL) + dbus_message_iter_init (message, &sub); + else + dbus_message_iter_recurse (iter, &sub); + + while (dbus_message_iter_get_arg_type (&sub) != + DBUS_TYPE_INVALID) + { + /* floating ref */ + GVariant *child = read_variant (&sub, error); + + if (child == NULL) + { + /* impossible to continue; stop trying */ + g_variant_builder_clear (&builder); + return NULL; + } + + /* consumes floating ref */ + g_variant_builder_add_value (&builder, child); + + if (!dbus_message_iter_next (&sub)) + break; + } + + return g_variant_builder_end (&builder); +} + +static GVariant * +read_variant (DBusMessageIter *iter, + GError **error) +{ + int type = dbus_message_iter_get_arg_type (iter); + + if (type == DBUS_TYPE_INVALID) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_FAILED, "End of message"); + return NULL; + } + else if (dbus_type_is_basic (type)) + { + DBusBasicValue u; + + dbus_message_iter_get_basic (iter, &u); + + switch (type) + { + case DBUS_TYPE_BYTE: + return g_variant_new_byte (u.byt); + + case DBUS_TYPE_BOOLEAN: + return g_variant_new_boolean (u.bool_val); + + case DBUS_TYPE_INT16: + return g_variant_new_int16 (u.i16); + + case DBUS_TYPE_UINT16: + return g_variant_new_uint16 (u.u16); + + case DBUS_TYPE_INT32: + return g_variant_new_int32 (u.i32); + + case DBUS_TYPE_UINT32: + return g_variant_new_uint32 (u.u32); + + case DBUS_TYPE_INT64: + return g_variant_new_int64 (u.i64); + + case DBUS_TYPE_UINT64: + return g_variant_new_uint64 (u.u64); + + case DBUS_TYPE_DOUBLE: + return g_variant_new_double (u.dbl); + + case DBUS_TYPE_STRING: + return g_variant_new_string (u.str); + + case DBUS_TYPE_OBJECT_PATH: + return g_variant_new_object_path (u.str); + + case DBUS_TYPE_SIGNATURE: + return g_variant_new_signature (u.str); + + default: + /* Unknown basic type */ + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "Unknown basic type %d", type); + return NULL; + } + } + else + { + switch (type) + { + case DBUS_TYPE_ARRAY: + { + int subtype = dbus_message_iter_get_element_type (iter); + + switch (subtype) + { + case DBUS_TYPE_BYTE: + return fixed_array_to_variant (iter, subtype, 1); + + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + return fixed_array_to_variant (iter, subtype, 2); + + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + /* but not BOOLEAN: it's 1 byte in GVariant but 4 bytes + * on D-Bus */ + return fixed_array_to_variant (iter, subtype, 4); + + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + return fixed_array_to_variant (iter, subtype, 8); + + default: + /* slow path */ + return iterable_to_variant (NULL, iter, error); + } + } + + case DBUS_TYPE_VARIANT: + case DBUS_TYPE_STRUCT: + case DBUS_TYPE_DICT_ENTRY: + return iterable_to_variant (NULL, iter, error); + + default: + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "Unknown non-basic type %d", type); + return NULL; + } + } +} + +GVariant * +dbus_g_message_read_variants (DBusMessage *message, + GError **error) +{ + GVariant *ret = iterable_to_variant (message, NULL, error); + + if (ret != NULL) + g_variant_ref_sink (ret); + + return ret; +} + +static gboolean write_variant (DBusMessageIter *iter, + GVariant *variant, + GVariantClass parent_class, + GError **error); + +static gboolean +write_iterable_variant (DBusMessage *message, + DBusMessageIter *iter, + GVariantClass cls, + const gchar *element_signature, + GVariant *variant, + GError **error) +{ + gsize n = g_variant_n_children (variant); + DBusMessageIter sub; + gsize i; + int dbus_type = cls; + + g_assert (message != NULL || iter != NULL); + g_assert (message == NULL || iter == NULL); + + if (cls == G_VARIANT_CLASS_TUPLE || cls == G_VARIANT_CLASS_DICT_ENTRY) + { + element_signature = NULL; + + if (cls == G_VARIANT_CLASS_TUPLE) + dbus_type = DBUS_TYPE_STRUCT; + else + dbus_type = DBUS_TYPE_DICT_ENTRY; + } + + if (cls == G_VARIANT_CLASS_TUPLE && n == 0) + { + /* Empty tuples are a GVariant extension */ + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "Empty tuples () are not supported on D-Bus"); + return FALSE; + } + + if (message == NULL) + { + if (!dbus_message_iter_open_container (iter, dbus_type, + element_signature, &sub)) + oom (); + } + else + { + dbus_message_iter_init_append (message, &sub); + } + + for (i = 0; i < n; i++) + { + GVariant *child = g_variant_get_child_value (variant, i); + + if (!write_variant (&sub, child, cls, error)) + { + g_variant_unref (child); + + if (message == NULL) + dbus_message_iter_abandon_container (iter, &sub); + + return FALSE; + } + + g_variant_unref (child); + } + + if (message == NULL && + !dbus_message_iter_close_container (iter, &sub)) + oom (); + + return TRUE; +} + +static gboolean +fixed_array_write_variant (DBusMessageIter *iter, + GVariant *variant, + int element_type, + gsize element_size, + GError **error) +{ + gsize n_elements = 0; + gconstpointer blob = g_variant_get_fixed_array (variant, &n_elements, + element_size); + DBusMessageIter sub; + char element_signature[] = { element_type, 0 }; + + if (n_elements > G_MAXINT32) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_LIMITS_EXCEEDED, + "Too many elements in array: %" G_GSIZE_FORMAT " > %d", + n_elements, G_MAXINT32); + return FALSE; + } + + if (!dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY, + element_signature, &sub)) + oom (); + + if (!dbus_message_iter_append_fixed_array (&sub, + element_type, &blob, n_elements)) + oom (); + + if (!dbus_message_iter_close_container (iter, &sub)) + oom (); + + return TRUE; +} + +#define WRITE_BASIC_CASE(TYPE, type, member) \ + case G_VARIANT_CLASS_ ## TYPE: \ + u.member = g_variant_get_ ## type (variant); \ + if (!dbus_message_iter_append_basic (iter, DBUS_TYPE_ ## TYPE, &u)) \ + oom (); \ + return TRUE; + +static gboolean +write_variant (DBusMessageIter *iter, + GVariant *variant, + GVariantClass parent_class, + GError **error) +{ + DBusBasicValue u; + GVariantClass cls = g_variant_classify (variant); + + if (cls == G_VARIANT_CLASS_DICT_ENTRY && + parent_class != G_VARIANT_CLASS_ARRAY) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "Cannot write a dict entry not contained in an array"); + return FALSE; + } + + switch (cls) + { + WRITE_BASIC_CASE (BOOLEAN, boolean, bool_val); + WRITE_BASIC_CASE (BYTE, byte, byt); + WRITE_BASIC_CASE (INT16, int16, i16); + WRITE_BASIC_CASE (UINT16, uint16, u16); + WRITE_BASIC_CASE (INT32, int32, i32); + WRITE_BASIC_CASE (UINT32, uint32, u32); + WRITE_BASIC_CASE (INT64, int64, i64); + WRITE_BASIC_CASE (UINT64, uint64, u64); + + WRITE_BASIC_CASE (DOUBLE, double, dbl); + + case G_VARIANT_CLASS_STRING: + case G_VARIANT_CLASS_OBJECT_PATH: + case G_VARIANT_CLASS_SIGNATURE: + u.str = (char *) g_variant_get_string (variant, NULL); + + if (cls == G_VARIANT_CLASS_SIGNATURE) + { + /* We need to validate it more strictly, because GVariant accepts + * some things in signatures that D-Bus doesn't. */ + DBusError e = DBUS_ERROR_INIT; + + if (!dbus_signature_validate (u.str, &e)) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "%s", e.message); + dbus_error_free (&e); + return FALSE; + } + } + + if (!dbus_message_iter_append_basic (iter, cls, &u)) + oom (); + + return TRUE; + + case G_VARIANT_CLASS_ARRAY: + { + const gchar *signature = g_variant_get_type_string (variant); + + g_assert (signature[0] == 'a'); + g_assert (signature[1] != '\0'); + + switch (signature[1]) + { + case DBUS_TYPE_BYTE: + return fixed_array_write_variant (iter, variant, signature[1], + 1, error); + + case DBUS_TYPE_INT16: + case DBUS_TYPE_UINT16: + return fixed_array_write_variant (iter, variant, signature[1], + 2, error); + + case DBUS_TYPE_INT32: + case DBUS_TYPE_UINT32: + /* but not BOOLEAN: it's 1 byte in GVariant but 4 bytes + * on D-Bus */ + return fixed_array_write_variant (iter, variant, signature[1], + 4, error); + + case DBUS_TYPE_INT64: + case DBUS_TYPE_UINT64: + case DBUS_TYPE_DOUBLE: + return fixed_array_write_variant (iter, variant, signature[1], + 8, error); + + default: + { + DBusError e = DBUS_ERROR_INIT; + + if (!dbus_signature_validate (signature, &e)) + { + g_set_error (error, DBUS_GERROR, + DBUS_GERROR_INVALID_SIGNATURE, "%s", e.message); + dbus_error_free (&e); + return FALSE; + } + + /* skip the 'a' at the beginning to get the contained + * type */ + return write_iterable_variant (NULL, iter, cls, + signature + 1, variant, error); + } + } + } + + case G_VARIANT_CLASS_VARIANT: + { + gboolean result; + GVariant *child = g_variant_get_variant (variant); + const gchar *sig = g_variant_get_type_string (child); + DBusError e = DBUS_ERROR_INIT; + + if (!dbus_signature_validate (sig, &e)) + { + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "%s", e.message); + dbus_error_free (&e); + return FALSE; + } + + result = write_iterable_variant (NULL, iter, cls, sig, + variant, error); + g_variant_unref (child); + return result; + } + + case G_VARIANT_CLASS_TUPLE: + case G_VARIANT_CLASS_DICT_ENTRY: + return write_iterable_variant (NULL, iter, cls, NULL, variant, error); + + case G_VARIANT_CLASS_HANDLE: + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "fd-passing is not supported in dbus-glib"); + return FALSE; + + case G_VARIANT_CLASS_MAYBE: + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "The maybe type is not supported in dbus-glib"); + return FALSE; + } + + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_SIGNATURE, + "Unknown GVariantClass %d", cls); + return FALSE; +} + +gboolean +dbus_g_message_write_variants (DBusMessage *message, + GVariant *tuple, + GError **error) +{ + g_return_val_if_fail (g_variant_classify (tuple) == G_VARIANT_CLASS_TUPLE, + FALSE); + + return write_iterable_variant (message, NULL, G_VARIANT_CLASS_TUPLE, NULL, + tuple, error); +} diff --git a/dbus/dbus-gvariant.h b/dbus/dbus-gvariant.h new file mode 100644 index 0000000..0ef0110 --- /dev/null +++ b/dbus/dbus-gvariant.h @@ -0,0 +1,29 @@ +/* dbus-gvariant - GVariant to/from DBusMessageIter + * + * Copyright © 2012 Collabora Ltd. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef DBUSGLIB_DBUS_GVARIANT_H +#define DBUSGLIB_DBUS_GVARIANT_H + +#include +#include + +G_BEGIN_DECLS + +GVariant *dbus_g_message_read_variants (DBusMessage *message, + GError **error); + +gboolean dbus_g_message_write_variants (DBusMessage *message, + GVariant *tuple, GError **error); + +G_END_DECLS + +#endif diff --git a/test/core/test-gvariant.c b/test/core/test-gvariant.c index fc1406e..3b2bbbd 100644 --- a/test/core/test-gvariant.c +++ b/test/core/test-gvariant.c @@ -28,6 +28,7 @@ #include #include +#include #include /** @@ -689,19 +690,85 @@ test_g (void) } static void -test_roundtrip (gconstpointer user_data) +test_roundtrip_via_value (gconstpointer user_data) { const gchar *text = user_data; GVariant *before, *after; GValue v = { 0 }; before = g_variant_new_parsed (text); + dbus_g_value_parse_g_variant (before, &v); after = dbus_g_value_build_g_variant (&v); g_value_unset (&v); assert_g_variant_equivalent (before, after); + g_variant_unref (after); + g_variant_unref (before); +} + +static void +test_roundtrip_via_message (gconstpointer user_data) +{ + const gchar *text = user_data; + GVariant *before, *after, *tuple; + DBusMessage *message; + GError *error = NULL; + + before = g_variant_new_parsed (text); + tuple = g_variant_new_tuple (&before, 1); + + message = dbus_message_new_signal ("/", "a.b", "c"); + dbus_g_message_write_variants (message, tuple, &error); + g_assert_no_error (error); + + after = dbus_g_message_read_variants (message, &error); + g_assert_no_error (error); + assert_g_variant_equivalent (tuple, after); g_variant_unref (after); + + if (g_variant_classify (before) == G_VARIANT_CLASS_TUPLE) + { + message = dbus_message_new_signal ("/", "a.b", "c"); + dbus_g_message_write_variants (message, before, &error); + g_assert_no_error (error); + + after = dbus_g_message_read_variants (message, &error); + g_assert_no_error (error); + assert_g_variant_equivalent (before, after); + g_variant_unref (after); + } + + g_variant_unref (tuple); +} + +static void +test_roundtrip (gconstpointer user_data) +{ + test_roundtrip_via_value (user_data); + test_roundtrip_via_message (user_data); +} + +static void +test_gvariant_only (gconstpointer user_data) +{ + const gchar *text = user_data; + GVariant *before, *tuple; + DBusMessage *message; + GError *error = NULL; + gboolean ok; + + before = g_variant_new_parsed (text); + tuple = g_variant_new_tuple (&before, 1); + + message = dbus_message_new_signal ("/", "a.b", "c"); + ok = dbus_g_message_write_variants (message, tuple, &error); + g_assert (!ok); + g_assert (error != NULL); + g_assert (error->domain == DBUS_GERROR); + g_clear_error (&error); + + g_variant_unref (tuple); } static void @@ -951,6 +1018,27 @@ main (int argc, g_test_add_data_func ("/roundtrip/empty_aad", "@aad [[]]", test_roundtrip); g_test_add_data_func ("/roundtrip/empty_aao", "@aao [[]]", test_roundtrip); g_test_add_data_func ("/roundtrip/empty_aag", "@aag [[]]", test_roundtrip); + g_test_add_data_func ("/roundtrip/ints", "(int32 1, int64 1)", + test_roundtrip); + g_test_add_data_func ("/roundtrip/uints", "(byte 1, uint32 1, uint64 1)", + test_roundtrip); + g_test_add_data_func ("/roundtrip/d", "1.5", test_roundtrip); + + /* the identity of 16-bit integers is lost when going via GValue, but + * not when going via a D-Bus message */ + g_test_add_data_func ("/roundtrip/sixteen", "(@n 1, @q 2)", + test_roundtrip_via_message); + + g_test_add_data_func ("/gvariant-only/triv", "()", test_gvariant_only); + g_test_add_data_func ("/gvariant-only/dict_entry", "@{ii} {1, 2}", + test_gvariant_only); + g_test_add_data_func ("/gvariant-only/var_triv", "<()>", test_gvariant_only); + g_test_add_data_func ("/gvariant-only/a_triv", "[()]", test_gvariant_only); + g_test_add_data_func ("/gvariant-only/a_a_var_triv", "[[<()>]]", + test_gvariant_only); + g_test_add_data_func ("/gvariant-only/g", "@g '()'", test_gvariant_only); + g_test_add_data_func ("/gvariant-only/mi", "@mi nothing", test_gvariant_only); + g_test_add_data_func ("/gvariant-only/h", "@h 1", test_gvariant_only); return g_test_run (); } -- 1.7.10