[glib/gdbus-merge] Initial GDBus code-drop from GDBus-standalone repo



commit d0a14469d09d5fe23de219ba293fd4a266b02ced
Author: David Zeuthen <davidz redhat com>
Date:   Thu May 6 14:13:59 2010 -0400

    Initial GDBus code-drop from GDBus-standalone repo
    
    Things compile and the test-suite passes. Still need to hook up
    gio.symbols and docs. There are still a bunch of TODOs left in the
    sources that needs to be addressed.
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 configure.in                             |   10 +
 gio/Makefile.am                          |   58 +-
 gio/gcredentials.c                       |  427 +++
 gio/gcredentials.h                       |  104 +
 gio/gdbus-bash-completion.sh             |   33 +
 gio/gdbus-tool.c                         | 1491 +++++++++
 gio/gdbusaddress.c                       | 1004 ++++++
 gio/gdbusaddress.h                       |   54 +
 gio/gdbusauth.c                          | 1538 +++++++++
 gio/gdbusauth.h                          |   86 +
 gio/gdbusauthmechanism.c                 |  342 ++
 gio/gdbusauthmechanism.h                 |  174 +
 gio/gdbusauthmechanismanon.c             |  327 ++
 gio/gdbusauthmechanismanon.h             |   82 +
 gio/gdbusauthmechanismexternal.c         |  416 +++
 gio/gdbusauthmechanismexternal.h         |   82 +
 gio/gdbusauthmechanismsha1.c             | 1216 +++++++
 gio/gdbusauthmechanismsha1.h             |   82 +
 gio/gdbusauthobserver.c                  |  218 ++
 gio/gdbusauthobserver.h                  |  100 +
 gio/gdbusconnection.c                    | 5280 ++++++++++++++++++++++++++++++
 gio/gdbusconnection.h                    |  467 +++
 gio/gdbuserror.c                         |  847 +++++
 gio/gdbuserror.h                         |   92 +
 gio/gdbusintrospection.c                 | 2009 ++++++++++++
 gio/gdbusintrospection.h                 |  255 ++
 gio/gdbusmessage.c                       | 2421 ++++++++++++++
 gio/gdbusmessage.h                       |  172 +
 gio/gdbusmethodinvocation.c              |  795 +++++
 gio/gdbusmethodinvocation.h              |  119 +
 gio/gdbusnameowning.c                    |  713 ++++
 gio/gdbusnameowning.h                    |   88 +
 gio/gdbusnamewatching.c                  |  620 ++++
 gio/gdbusnamewatching.h                  |   68 +
 gio/gdbusprivate.c                       | 1040 ++++++
 gio/gdbusprivate.h                       |   83 +
 gio/gdbusproxy.c                         | 1542 +++++++++
 gio/gdbusproxy.h                         |  146 +
 gio/gdbusproxywatching.c                 |  397 +++
 gio/gdbusproxywatching.h                 |   77 +
 gio/gdbusserver.c                        | 1043 ++++++
 gio/gdbusserver.h                        |   97 +
 gio/gdbusutils.c                         |  364 ++
 gio/gdbusutils.h                         |   42 +
 gio/gio-marshal.list                     |    2 +
 gio/gio.h                                |   16 +
 gio/gioenums.h                           |  372 +++-
 gio/giotypes.h                           |   19 +
 gio/gunixcredentialsmessage.c            |  341 ++
 gio/gunixcredentialsmessage.h            |   68 +
 gio/tests/Makefile.am                    |   98 +-
 gio/tests/gdbus-addresses.c              |   77 +
 gio/tests/gdbus-connection.c             |  653 ++++
 gio/tests/gdbus-error.c                  |  198 ++
 gio/tests/gdbus-example-own-name.c       |   99 +
 gio/tests/gdbus-example-peer.c           |  318 ++
 gio/tests/gdbus-example-server.c         |  388 +++
 gio/tests/gdbus-example-subtree.c        |  410 +++
 gio/tests/gdbus-example-unix-fd-client.c |  145 +
 gio/tests/gdbus-example-watch-name.c     |  101 +
 gio/tests/gdbus-example-watch-proxy.c    |  205 ++
 gio/tests/gdbus-exit-on-close.c          |   82 +
 gio/tests/gdbus-export.c                 | 1410 ++++++++
 gio/tests/gdbus-introspection.c          |  169 +
 gio/tests/gdbus-names.c                  |  749 +++++
 gio/tests/gdbus-peer.c                   |  746 +++++
 gio/tests/gdbus-proxy.c                  |  455 +++
 gio/tests/gdbus-serialization.c          |  650 ++++
 gio/tests/gdbus-sessionbus.c             |  342 ++
 gio/tests/gdbus-sessionbus.h             |   38 +
 gio/tests/gdbus-tests.c                  |  218 ++
 gio/tests/gdbus-tests.h                  |  146 +
 gio/tests/gdbus-testserver.py            |  270 ++
 gio/tests/gdbus-threading.c              |  532 +++
 74 files changed, 35927 insertions(+), 11 deletions(-)
---
diff --git a/configure.in b/configure.in
index 05bfad7..882bbc0 100644
--- a/configure.in
+++ b/configure.in
@@ -3496,6 +3496,16 @@ if test x$glib_win32_static_compilation = xyes; then
 fi
 ])
 
+# Check for libdbus1 - Optional - is only used in the GDBus test cases
+#
+PKG_CHECK_MODULES(DBUS1,
+                  dbus-1,
+                  [AC_DEFINE(HAVE_DBUS1, 1, [Define if dbus-1 is available]) have_dbus1=yes],
+                  have_dbus1=no)
+AC_SUBST(DBUS1_CFLAGS)
+AC_SUBST(DBUS1_LIBS)
+AM_CONDITIONAL(HAVE_DBUS1, [test "x$have_dbus1" = "xyes"])
+
 AC_CONFIG_FILES([
 glib-2.0.pc
 glib-2.0-uninstalled.pc
diff --git a/gio/Makefile.am b/gio/Makefile.am
index bdc897d..1479749 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -79,6 +79,48 @@ gio-marshal.c: gio-marshal.h gio-marshal.list
 	$(glib_genmarshal) --prefix=_gio_marshal $(srcdir)/gio-marshal.list --body --internal) > $  tmp && \
 	  mv $  tmp $@
 
+gdbus_headers = 			\
+	gdbusauthobserver.h		\
+	gcredentials.h			\
+	gunixcredentialsmessage.h	\
+	gdbusutils.h			\
+	gdbuserror.h			\
+	gdbusaddress.h			\
+	gdbusconnection.h		\
+	gdbusmessage.h			\
+	gdbusnameowning.h		\
+	gdbusnamewatching.h		\
+	gdbusproxywatching.h		\
+	gdbusproxy.h			\
+	gdbusintrospection.h		\
+	gdbusmethodinvocation.h		\
+	gdbusserver.h			\
+	$(NULL)
+
+gdbus_sources = 							\
+	gdbusutils.h			gdbusutils.c			\
+	gcredentials.h			gcredentials.c			\
+	gunixcredentialsmessage.h	gunixcredentialsmessage.c	\
+	gdbusaddress.h			gdbusaddress.c			\
+	gdbusauthobserver.h		gdbusauthobserver.c		\
+	gdbusauth.h			gdbusauth.c			\
+	gdbusauthmechanism.h		gdbusauthmechanism.c		\
+	gdbusauthmechanismanon.h	gdbusauthmechanismanon.c	\
+	gdbusauthmechanismexternal.h	gdbusauthmechanismexternal.c	\
+	gdbusauthmechanismsha1.h	gdbusauthmechanismsha1.c	\
+	gdbuserror.h			gdbuserror.c			\
+	gdbusconnection.h		gdbusconnection.c		\
+	gdbusmessage.h			gdbusmessage.c			\
+	gdbusnameowning.h		gdbusnameowning.c		\
+	gdbusnamewatching.h		gdbusnamewatching.c		\
+	gdbusproxywatching.h		gdbusproxywatching.c		\
+	gdbusproxy.h			gdbusproxy.c			\
+	gdbusprivate.h			gdbusprivate.c			\
+	gdbusintrospection.h		gdbusintrospection.c		\
+	gdbusmethodinvocation.h		gdbusmethodinvocation.c		\
+	gdbusserver.h			gdbusserver.c			\
+	$(NULL)
+
 settings_headers = \
 	gsettingsbackend.h		\
 	gsettings.h
@@ -327,6 +369,7 @@ libgio_2_0_la_SOURCES =		\
 	$(unix_sources) 	\
 	$(win32_sources) 	\
 	$(settings_sources) 	\
+	$(gdbus_sources) 	\
 	$(local_sources) 	\
 	$(marshal_sources) 	\
 	$(NULL)
@@ -454,6 +497,7 @@ gio_headers =			\
 	gzlibcompressor.h	\
 	gzlibdecompressor.h	\
 	$(settings_headers)	\
+	$(gdbus_headers)	\
 	$(NULL)
 
 gioincludedir=$(includedir)/glib-2.0/gio/
@@ -527,10 +571,22 @@ gsettings_LDADD = \
 	libgio-2.0.la
 gsettings_SOURCES = gsettings-tool.c
 
-
 schemadir = $(datadir)/glib-2.0/schemas
 dist_schema_DATA = gschema.dtd
 
+# ------------------------------------------------------------------------
+# gdbus(1) tool
+
+bin_PROGRAMS += gdbus
+gdbus_SOURCES = gdbus-tool.c
+gdbus_LDADD = libgio-2.0.la
+
+completiondir = $(sysconfdir)/bash_completion.d
+completion_SCRIPTS = gdbus-bash-completion.sh
+EXTRA_DIST += $(completion_SCRIPTS)
+
+# ------------------------------------------------------------------------
+
 dist-hook: $(BUILT_EXTRA_DIST) ../build/win32/vs9/gio.vcproj
 	files='$(BUILT_EXTRA_DIST)'; \
 	for f in $$files; do \
diff --git a/gio/gcredentials.c b/gio/gcredentials.c
new file mode 100644
index 0000000..e1712fe
--- /dev/null
+++ b/gio/gcredentials.c
@@ -0,0 +1,427 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "gcredentials.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+/**
+ * SECTION:gcredentials
+ * @short_description: Credentials
+ * @include: gdbus/gdbus.h
+ *
+ * The #GCredentials type is used for storing information that can be
+ * used for identifying, authenticating and authorizing processes.
+ *
+ * Most UNIX and UNIX-like operating systems support a secure exchange
+ * of credentials over a Unix Domain Socket, see
+ * #GUnixCredentialsMessage, g_unix_connection_send_credentials() and
+ * g_unix_connection_receive_credentials() for details.
+ */
+
+struct _GCredentialsPrivate
+{
+  gint64 unix_user;
+  gint64 unix_group;
+  gint64 unix_process;
+  gchar *windows_user;
+};
+
+G_DEFINE_TYPE (GCredentials, g_credentials, G_TYPE_OBJECT);
+
+static void
+g_credentials_finalize (GObject *object)
+{
+  GCredentials *credentials = G_CREDENTIALS (object);
+
+  g_free (credentials->priv->windows_user);
+
+  if (G_OBJECT_CLASS (g_credentials_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_credentials_parent_class)->finalize (object);
+}
+
+
+static void
+g_credentials_class_init (GCredentialsClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GCredentialsPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = g_credentials_finalize;
+}
+
+static void
+g_credentials_init (GCredentials *credentials)
+{
+  credentials->priv = G_TYPE_INSTANCE_GET_PRIVATE (credentials, G_TYPE_CREDENTIALS, GCredentialsPrivate);
+
+  credentials->priv->unix_user = -1;
+  credentials->priv->unix_group = -1;
+  credentials->priv->unix_process = -1;
+  credentials->priv->windows_user = NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new:
+ *
+ * Creates a new empty credentials object.
+ *
+ * Returns: A #GCredentials. Free with g_object_unref().
+ */
+GCredentials *
+g_credentials_new (void)
+{
+  return g_object_new (G_TYPE_CREDENTIALS, NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+static GCredentials *
+g_credentials_new_for_unix_process (void)
+{
+  GCredentials *credentials;
+  credentials = g_credentials_new ();
+  credentials->priv->unix_user = getuid ();
+  credentials->priv->unix_group = getgid ();
+  credentials->priv->unix_process = getpid ();
+  return credentials;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new_for_process:
+ *
+ * Gets the credentials for the current process. Note that the exact
+ * set of credentials in the returned object vary according to
+ * platform.
+ *
+ * Returns: A #GCredentials. Free with g_object_unref().
+ */
+GCredentials *
+g_credentials_new_for_process (void)
+{
+#ifdef G_OS_UNIX
+  return g_credentials_new_for_unix_process ();
+#elif G_OS_WIN32
+  return g_credentials_new_for_win32_process ();
+#else
+#warning Please implement g_credentials_new_for_process() for your OS. For now g_credentials_new_for_process() will return empty credentials.
+  return g_credentials_new ();
+#endif
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_new_for_string:
+ * @str: A string returned from g_credentials_to_string().
+ * @error: Return location for error.
+ *
+ * Constructs a #GCredentials instance from @str.
+ *
+ * Returns: A #GCredentials or %NULL if @error is set. The return
+ * object must be freed with g_object_unref().
+ */
+GCredentials *
+g_credentials_new_for_string (const gchar  *str,
+                              GError      **error)
+{
+  GCredentials *credentials;
+  gchar **tokens;
+  guint n;
+
+  g_return_val_if_fail (str != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  tokens = NULL;
+  credentials = g_credentials_new ();
+
+  if (!g_str_has_prefix (str, "GCredentials:"))
+    goto fail;
+
+  tokens = g_strsplit (str + sizeof "GCredentials:" - 1, ",", 0);
+  for (n = 0; tokens[n] != NULL; n++)
+    {
+      const gchar *token = tokens[n];
+      if (g_str_has_prefix (token, "unix-user:"))
+        g_credentials_set_unix_user (credentials, atoi (token + sizeof ("unix-user:") - 1));
+      else if (g_str_has_prefix (token, "unix-group:"))
+        g_credentials_set_unix_group (credentials, atoi (token + sizeof ("unix-group:") - 1));
+      else if (g_str_has_prefix (token, "unix-process:"))
+        g_credentials_set_unix_process (credentials, atoi (token + sizeof ("unix-process:") - 1));
+      else if (g_str_has_prefix (token, "windows-user:"))
+        g_credentials_set_windows_user (credentials, token + sizeof ("windows-user:"));
+      else
+        goto fail;
+    }
+  g_strfreev (tokens);
+  return credentials;
+
+ fail:
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_FAILED,
+               _("The string `%s' is not a valid credentials string"),
+               str);
+  g_object_unref (credentials);
+  g_strfreev (tokens);
+  return NULL;
+}
+
+/**
+ * g_credentials_to_string:
+ * @credentials: A #GCredentials object.
+ *
+ * Serializes @credentials to a string that can be used with
+ * g_credentials_new_for_string().
+ *
+ * Returns: A string that should be freed with g_free().
+ */
+gchar *
+g_credentials_to_string (GCredentials *credentials)
+{
+  GString *ret;
+
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+
+  ret = g_string_new ("GCredentials:");
+  if (credentials->priv->unix_user != -1)
+    g_string_append_printf (ret, "unix-user=%" G_GINT64_FORMAT ",", credentials->priv->unix_user);
+  if (credentials->priv->unix_group != -1)
+    g_string_append_printf (ret, "unix-group=%" G_GINT64_FORMAT ",", credentials->priv->unix_group);
+  if (credentials->priv->unix_process != -1)
+    g_string_append_printf (ret, "unix-process=%" G_GINT64_FORMAT ",", credentials->priv->unix_process);
+  if (credentials->priv->windows_user != NULL)
+    g_string_append_printf (ret, "windows-user=%s,", credentials->priv->windows_user);
+  if (ret->str[ret->len - 1] == ',')
+    ret->str[ret->len - 1] = '\0';
+
+  return g_string_free (ret, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_user:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX user credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_user (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+  return credentials->priv->unix_user != -1;
+}
+
+/**
+ * g_credentials_get_unix_user:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX user identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_user (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+  return credentials->priv->unix_user;
+}
+
+/**
+ * g_credentials_set_unix_user:
+ * @credentials: A #GCredentials.
+ * @user_id: A UNIX user identifier (typically type #uid_t) or -1 to unset it.
+ *
+ * Sets the UNIX user identifier.
+ */
+void
+g_credentials_set_unix_user (GCredentials *credentials,
+                             gint64        user_id)
+{
+  g_return_if_fail (G_IS_CREDENTIALS (credentials));
+  credentials->priv->unix_user = user_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_group:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX group credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_group (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+  return credentials->priv->unix_group != -1;
+}
+
+/**
+ * g_credentials_get_unix_group:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX group identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_group (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+  return credentials->priv->unix_group;
+}
+
+/**
+ * g_credentials_set_unix_group:
+ * @credentials: A #GCredentials.
+ * @group_id: A UNIX group identifier (typically type #gid_t) or -1 to unset.
+ *
+ * Sets the UNIX group identifier.
+ */
+void
+g_credentials_set_unix_group (GCredentials *credentials,
+                              gint64        group_id)
+{
+  g_return_if_fail (G_IS_CREDENTIALS (credentials));
+  credentials->priv->unix_group = group_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_unix_process:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a UNIX process credential.
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_unix_process (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+  return credentials->priv->unix_process != -1;
+}
+
+/**
+ * g_credentials_get_unix_process:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the UNIX process identifier from @credentials.
+ *
+ * Returns: The identifier or -1 if unset.
+ */
+gint64
+g_credentials_get_unix_process (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), -1);
+  return credentials->priv->unix_process;
+}
+
+/**
+ * g_credentials_set_unix_process:
+ * @credentials: A #GCredentials.
+ * @process_id: A UNIX process identifier (typically type #pid_t/#GPid) or -1 to unset.
+ *
+ * Sets the UNIX process identifier.
+ */
+void
+g_credentials_set_unix_process (GCredentials *credentials,
+                                gint64        process_id)
+{
+  g_return_if_fail (G_IS_CREDENTIALS (credentials));
+  credentials->priv->unix_process = process_id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_credentials_has_windows_user:
+ * @credentials: A #GCredentials.
+ *
+ * Checks if @credentials has a Windows user SID (Security Identifier).
+ *
+ * Returns: %TRUE if @credentials has this type of credential, %FALSE otherwise.
+ */
+gboolean
+g_credentials_has_windows_user  (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+  return credentials->priv->windows_user != NULL;
+}
+
+/**
+ * g_credentials_get_windows_user:
+ * @credentials: A #GCredentials.
+ *
+ * Gets the Windows User SID from @credentials.
+ *
+ * Returns: A string or %NULL if unset. Do not free, the string is owned by @credentials.
+ */
+const gchar *
+g_credentials_get_windows_user  (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+  return credentials->priv->windows_user;
+}
+
+/**
+ * g_credentials_set_windows_user:
+ * @credentials: A #GCredentials.
+ * @user_sid: The Windows User SID or %NULL to unset.
+ *
+ * Sets the Windows User SID.
+ */
+void
+g_credentials_set_windows_user (GCredentials    *credentials,
+                                const gchar     *user_sid)
+{
+  g_return_if_fail (G_IS_CREDENTIALS (credentials));
+  g_free (credentials->priv->windows_user);
+  credentials->priv->windows_user = g_strdup (user_sid);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gcredentials.h b/gio/gcredentials.h
new file mode 100644
index 0000000..a5bab7b
--- /dev/null
+++ b/gio/gcredentials.h
@@ -0,0 +1,104 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_CREDENTIALS_H__
+#define __G_CREDENTIALS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_CREDENTIALS         (g_credentials_get_type ())
+#define G_CREDENTIALS(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_CREDENTIALS, GCredentials))
+#define G_CREDENTIALS_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_CREDENTIALS, GCredentialsClass))
+#define G_CREDENTIALS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_CREDENTIALS, GCredentialsClass))
+#define G_IS_CREDENTIALS(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_CREDENTIALS))
+#define G_IS_CREDENTIALS_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_CREDENTIALS))
+
+typedef struct _GCredentialsClass   GCredentialsClass;
+typedef struct _GCredentialsPrivate GCredentialsPrivate;
+
+/**
+ * GCredentials:
+ *
+ * The #GCredentials structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GCredentials
+{
+  /*< private >*/
+  GObject parent_instance;
+  GCredentialsPrivate *priv;
+};
+
+/**
+ * GCredentialsClass:
+ *
+ * Class structure for #GCredentials.
+ */
+struct _GCredentialsClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType            g_credentials_get_type           (void) G_GNUC_CONST;
+
+GCredentials    *g_credentials_new                (void);
+GCredentials    *g_credentials_new_for_process    (void);
+GCredentials    *g_credentials_new_for_string     (const gchar  *str,
+                                                   GError      **error);
+gchar           *g_credentials_to_string          (GCredentials    *credentials);
+
+gboolean         g_credentials_has_unix_user      (GCredentials    *credentials);
+gint64           g_credentials_get_unix_user      (GCredentials    *credentials);
+void             g_credentials_set_unix_user      (GCredentials    *credentials,
+                                                   gint64           user_id);
+
+gboolean         g_credentials_has_unix_group     (GCredentials    *credentials);
+gint64           g_credentials_get_unix_group     (GCredentials    *credentials);
+void             g_credentials_set_unix_group     (GCredentials    *credentials,
+                                                   gint64           group_id);
+
+gboolean         g_credentials_has_unix_process   (GCredentials    *credentials);
+gint64           g_credentials_get_unix_process   (GCredentials    *credentials);
+void             g_credentials_set_unix_process   (GCredentials    *credentials,
+                                                   gint64           process_id);
+
+gboolean         g_credentials_has_windows_user   (GCredentials    *credentials);
+const gchar     *g_credentials_get_windows_user   (GCredentials    *credentials);
+void             g_credentials_set_windows_user   (GCredentials    *credentials,
+                                                   const gchar     *user_sid);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_H__ */
diff --git a/gio/gdbus-bash-completion.sh b/gio/gdbus-bash-completion.sh
new file mode 100644
index 0000000..79f4cb4
--- /dev/null
+++ b/gio/gdbus-bash-completion.sh
@@ -0,0 +1,33 @@
+
+# Check for bash
+[ -z "$BASH_VERSION" ] && return
+
+####################################################################################################
+
+
+__gdbus() {
+    local IFS=$'\n'
+    local cur=`_get_cword :`
+
+    local suggestions=$(gdbus complete "${COMP_LINE}" ${COMP_POINT})
+    COMPREPLY=($(compgen -W "$suggestions" -- "$cur"))
+
+    # Remove colon-word prefix from COMPREPLY items
+    case "$cur" in
+        *:*)
+            case "$COMP_WORDBREAKS" in
+                *:*)
+                    local colon_word=${cur%${cur##*:}}
+                    local i=${#COMPREPLY[*]}
+                    while [ $((--i)) -ge 0 ]; do
+                        COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
+                    done
+                    ;;
+            esac
+            ;;
+    esac
+}
+
+####################################################################################################
+
+complete -o nospace -F __gdbus gdbus
diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c
new file mode 100644
index 0000000..b3233cf
--- /dev/null
+++ b/gio/gdbus-tool.c
@@ -0,0 +1,1491 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <gio/gio.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_GNUC_UNUSED static void completion_debug (const gchar *format, ...);
+
+/* Uncomment to get debug traces in /tmp/gdbus-completion-debug.txt (nice
+ * to not have it interfere with stdout/stderr)
+ */
+#if 0
+G_GNUC_UNUSED static void
+completion_debug (const gchar *format, ...)
+{
+  va_list var_args;
+  gchar *s;
+  static FILE *f = NULL;
+
+  va_start (var_args, format);
+  s = g_strdup_vprintf (format, var_args);
+  if (f == NULL)
+    {
+      f = fopen ("/tmp/gdbus-completion-debug.txt", "a+");
+    }
+  fprintf (f, "%s\n", s);
+  g_free (s);
+}
+#else
+static void
+completion_debug (const gchar *format, ...)
+{
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static void
+remove_arg (gint num, gint *argc, gchar **argv[])
+{
+  gint n;
+
+  g_assert (num <= (*argc));
+
+  for (n = num; (*argv)[n] != NULL; n++)
+    (*argv)[n] = (*argv)[n+1];
+  (*argv)[n] = NULL;
+  (*argc) = (*argc) - 1;
+}
+
+static void
+usage (gint *argc, gchar **argv[], gboolean use_stdout)
+{
+  GOptionContext *o;
+  gchar *s;
+  gchar *program_name;
+
+  o = g_option_context_new (_("COMMAND"));
+  g_option_context_set_help_enabled (o, FALSE);
+  /* Ignore parsing result */
+  g_option_context_parse (o, argc, argv, NULL);
+  program_name = g_path_get_basename ((*argv)[0]);
+  s = g_strdup_printf (_("Commands:\n"
+                         "  help         Shows this information\n"
+                         "  introspect   Introspect a remote object\n"
+                         "  call         Invoke a method on a remote object\n"
+                         "\n"
+                         "Use \"%s COMMAND --help\" to get help on each command.\n"),
+                       program_name);
+  g_free (program_name);
+  g_option_context_set_description (o, s);
+  g_free (s);
+  s = g_option_context_get_help (o, FALSE, NULL);
+  if (use_stdout)
+    g_print ("%s", s);
+  else
+    g_printerr ("%s", s);
+  g_free (s);
+  g_option_context_free (o);
+}
+
+static void
+modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command)
+{
+  gchar *s;
+  gchar *program_name;
+
+  /* TODO:
+   *  1. get a g_set_prgname() ?; or
+   *  2. save old argv[0] and restore later
+   */
+
+  g_assert (g_strcmp0 ((*argv)[1], command) == 0);
+  remove_arg (1, argc, argv);
+
+  program_name = g_path_get_basename ((*argv)[0]);
+  s = g_strdup_printf ("%s %s", (*argv)[0], command);
+  (*argv)[0] = s;
+  g_free (program_name);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+print_methods (GDBusConnection *c,
+               const gchar *name,
+               const gchar *path)
+{
+  GVariant *result;
+  GError *error;
+  const gchar *xml_data;
+  GDBusNodeInfo *node;
+  guint n;
+  guint m;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 name,
+                                                 path,
+                                                 "org.freedesktop.DBus.Introspectable",
+                                                 "Introspect",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 secs */
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+    {
+      g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+                  g_variant_get_type_string (result));
+      g_variant_unref (result);
+      goto out;
+    }
+  g_variant_get (result, "(s)", &xml_data);
+
+  error = NULL;
+  node = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_variant_unref (result);
+  if (node == NULL)
+    {
+      g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  for (n = 0; node->interfaces != NULL && node->interfaces[n] != NULL; n++)
+    {
+      const GDBusInterfaceInfo *iface = node->interfaces[n];
+      for (m = 0; iface->methods != NULL && iface->methods[m] != NULL; m++)
+        {
+          const GDBusMethodInfo *method = iface->methods[m];
+          g_print ("%s.%s \n", iface->name, method->name);
+        }
+    }
+  g_dbus_node_info_unref (node);
+
+ out:
+  ;
+}
+
+static void
+print_paths (GDBusConnection *c,
+             const gchar *name,
+             const gchar *path)
+{
+  GVariant *result;
+  GError *error;
+  const gchar *xml_data;
+  GDBusNodeInfo *node;
+  guint n;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 name,
+                                                 path,
+                                                 "org.freedesktop.DBus.Introspectable",
+                                                 "Introspect",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 secs */
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+    {
+      g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+                  g_variant_get_type_string (result));
+      g_variant_unref (result);
+      goto out;
+    }
+  g_variant_get (result, "(s)", &xml_data);
+
+  error = NULL;
+  node = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_variant_unref (result);
+  if (node == NULL)
+    {
+      g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  //g_printerr ("xml=`%s'", xml_data);
+
+  //g_printerr ("bar `%s'\n", path);
+
+  if (node->interfaces != NULL)
+    g_print ("%s \n", path);
+
+  for (n = 0; node->nodes != NULL && node->nodes[n] != NULL; n++)
+    {
+      gchar *s;
+
+      //g_printerr ("foo `%s'\n", node->nodes[n].path);
+
+      if (g_strcmp0 (path, "/") == 0)
+        s = g_strdup_printf ("/%s", node->nodes[n]->path);
+      else
+        s = g_strdup_printf ("%s/%s", path, node->nodes[n]->path);
+
+      print_paths (c, name, s);
+
+      g_free (s);
+    }
+  g_dbus_node_info_unref (node);
+
+ out:
+  ;
+}
+
+static void
+print_names (GDBusConnection *c,
+             gboolean         include_unique_names)
+{
+  GVariant *result;
+  GError *error;
+  GVariantIter *iter;
+  gchar *str;
+  GHashTable *name_set;
+  GList *keys;
+  GList *l;
+
+  name_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",
+                                                 "/org/freedesktop/DBus",
+                                                 "org.freedesktop.DBus",
+                                                 "ListNames",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 secs */
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)")))
+    {
+      g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result));
+      g_variant_unref (result);
+      goto out;
+    }
+  g_variant_get (result, "(as)", &iter);
+  while (g_variant_iter_loop (iter, "s", &str))
+    g_hash_table_insert (name_set, g_strdup (str), NULL);
+  g_variant_iter_free (iter);
+  g_variant_unref (result);
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",
+                                                 "/org/freedesktop/DBus",
+                                                 "org.freedesktop.DBus",
+                                                 "ListActivatableNames",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 secs */
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(as)")))
+    {
+      g_printerr (_("Error: Result is type `%s', expected `(as)'\n"), g_variant_get_type_string (result));
+      g_variant_unref (result);
+      goto out;
+    }
+  g_variant_get (result, "(as)", &iter);
+  while (g_variant_iter_loop (iter, "s", &str))
+    g_hash_table_insert (name_set, g_strdup (str), NULL);
+  g_variant_iter_free (iter);
+  g_variant_unref (result);
+
+  keys = g_hash_table_get_keys (name_set);
+  keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
+  for (l = keys; l != NULL; l = l->next)
+    {
+      const gchar *name = l->data;
+      if (!include_unique_names && g_str_has_prefix (name, ":"))
+        continue;
+
+      g_print ("%s \n", name);
+    }
+  g_list_free (keys);
+
+ out:
+  g_hash_table_unref (name_set);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean  opt_connection_system  = FALSE;
+static gboolean  opt_connection_session = FALSE;
+static gchar    *opt_connection_address = NULL;
+
+static const GOptionEntry connection_entries[] =
+{
+  { "system", 'y', 0, G_OPTION_ARG_NONE, &opt_connection_system, N_("Connect to the system bus"), NULL},
+  { "session", 'e', 0, G_OPTION_ARG_NONE, &opt_connection_session, N_("Connect to the session bus"), NULL},
+  { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_connection_address, N_("Connect to given D-Bus address"), NULL},
+  { NULL }
+};
+
+static GOptionGroup *
+connection_get_group (void)
+{
+  static GOptionGroup *g;
+
+  g = g_option_group_new ("connection",
+                          N_("Connection Endpoint Options:"),
+                          N_("Options specifying the connection endpoint"),
+                          NULL,
+                          NULL);
+  g_option_group_add_entries (g, connection_entries);
+  return g;
+}
+
+static GDBusConnection *
+connection_get_dbus_connection (GError **error)
+{
+  GDBusConnection *c;
+
+  c = NULL;
+
+  /* First, ensure we have exactly one connect */
+  if (!opt_connection_system && !opt_connection_session && opt_connection_address == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("No connection endpoint specified"));
+      goto out;
+    }
+  else if ((opt_connection_system && (opt_connection_session || opt_connection_address != NULL)) ||
+           (opt_connection_session && (opt_connection_system || opt_connection_address != NULL)) ||
+           (opt_connection_address != NULL && (opt_connection_system || opt_connection_session)))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("Multiple connection endpoints specified"));
+      goto out;
+    }
+
+  if (opt_connection_system)
+    {
+      c = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
+    }
+  else if (opt_connection_session)
+    {
+      c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+    }
+  else if (opt_connection_address != NULL)
+    {
+      c = g_dbus_connection_new_for_address_sync (opt_connection_address,
+                                                  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                                  NULL, /* GCancellable */
+                                                  error);
+    }
+
+ out:
+  return c;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+call_helper_get_method_in_signature (GDBusConnection  *c,
+                                     const gchar      *dest,
+                                     const gchar      *path,
+                                     const gchar      *interface_name,
+                                     const gchar      *method_name,
+                                     GError          **error)
+{
+  GPtrArray *ret;
+  GVariant *result;
+  GDBusNodeInfo *node_info;
+  const gchar *xml_data;
+  const GDBusInterfaceInfo *interface_info;
+  const GDBusMethodInfo *method_info;
+  guint n;
+
+  ret = NULL;
+  result = NULL;
+  node_info = NULL;
+
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 dest,
+                                                 path,
+                                                 "org.freedesktop.DBus.Introspectable",
+                                                 "Introspect",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 secs */
+                                                 NULL,
+                                                 error);
+  if (result == NULL)
+    goto out;
+
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Error: Result is type `%s', expected `(s)'\n"),
+                   g_variant_get_type_string (result));
+      goto out;
+    }
+
+  g_variant_get (result, "(s)", &xml_data);
+  node_info = g_dbus_node_info_new_for_xml (xml_data, error);
+  if (node_info == NULL)
+      goto out;
+
+  interface_info = g_dbus_node_info_lookup_interface (node_info, interface_name);
+  if (interface_info == NULL)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Warning: According to introspection data, interface `%s' does not exist\n"),
+                   interface_name);
+      goto out;
+    }
+
+  method_info = g_dbus_interface_info_lookup_method (interface_info, method_name);
+  if (method_info == NULL)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   _("Warning: According to introspection data, method `%s' does not exist on interface `%s'\n"),
+                   method_name,
+                   interface_name);
+      goto out;
+    }
+
+  ret = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_type_free);
+  for (n = 0; method_info->in_args != NULL && method_info->in_args[n] != NULL; n++)
+    {
+      g_ptr_array_add (ret, g_variant_type_new (method_info->in_args[n]->signature));
+    }
+
+ out:
+  if (node_info != NULL)
+    g_dbus_node_info_unref (node_info);
+  if (result != NULL)
+    g_variant_unref (result);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GVariant *
+_g_variant_parse_me_harder (GVariantType   *type,
+                            const gchar    *given_str,
+                            GError        **error)
+{
+  GVariant *value;
+  gchar *s;
+  guint n;
+  GString *str;
+
+  str = g_string_new ("\"");
+  for (n = 0; given_str[n] != '\0'; n++)
+    {
+      if (G_UNLIKELY (given_str[n] == '\"'))
+        g_string_append (str, "\\\"");
+      else
+        g_string_append_c (str, given_str[n]);
+    }
+  g_string_append_c (str, '"');
+  s = g_string_free (str, FALSE);
+
+  value = g_variant_parse (type,
+                           s,
+                           NULL,
+                           NULL,
+                           error);
+  g_free (s);
+
+  return value;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *opt_call_dest = NULL;
+static gchar *opt_call_object_path = NULL;
+static gchar *opt_call_method = NULL;
+
+static const GOptionEntry call_entries[] =
+{
+  { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_call_dest, N_("Destination name to invoke method on"), NULL},
+  { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_call_object_path, N_("Object path to invoke method on"), NULL},
+  { "method", 'm', 0, G_OPTION_ARG_STRING, &opt_call_method, N_("Method and interface name"), NULL},
+  { NULL }
+};
+
+static gboolean
+handle_call (gint        *argc,
+             gchar      **argv[],
+             gboolean     request_completion,
+             const gchar *completion_cur,
+             const gchar *completion_prev)
+{
+  gint ret;
+  GOptionContext *o;
+  gchar *s;
+  GError *error;
+  GDBusConnection *c;
+  GVariant *parameters;
+  gchar *interface_name;
+  gchar *method_name;
+  GVariant *result;
+  GPtrArray *in_signature_types;
+  gboolean complete_names;
+  gboolean complete_paths;
+  gboolean complete_methods;
+  GVariantBuilder builder;
+  guint n;
+
+  ret = FALSE;
+  c = NULL;
+  parameters = NULL;
+  interface_name = NULL;
+  method_name = NULL;
+  result = NULL;
+  in_signature_types = NULL;
+
+  modify_argv0_for_command (argc, argv, "call");
+
+  o = g_option_context_new (NULL);
+  g_option_context_set_help_enabled (o, FALSE);
+  g_option_context_set_summary (o, _("Invoke a method on a remote object."));
+  g_option_context_add_main_entries (o, call_entries, NULL /* GETTEXT_PACKAGE*/);
+  g_option_context_add_group (o, connection_get_group ());
+
+  complete_names = FALSE;
+  if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
+    {
+      complete_names = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  complete_paths = FALSE;
+  if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
+    {
+      complete_paths = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  complete_methods = FALSE;
+  if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--method") == 0)
+    {
+      complete_methods = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  if (!g_option_context_parse (o, argc, argv, NULL))
+    {
+      if (!request_completion)
+        {
+          s = g_option_context_get_help (o, FALSE, NULL);
+          g_printerr ("%s", s);
+          g_free (s);
+          goto out;
+        }
+    }
+
+  error = NULL;
+  c = connection_get_dbus_connection (&error);
+  if (c == NULL)
+    {
+      if (request_completion)
+        {
+          if (g_strcmp0 (completion_prev, "--address") == 0)
+            {
+              g_print ("unix:\n"
+                       "tcp:\n"
+                       "nonce-tcp:\n");
+            }
+          else
+            {
+              g_print ("--system \n--session \n--address \n");
+            }
+        }
+      else
+        {
+          g_printerr (_("Error connecting: %s\n"), error->message);
+          g_error_free (error);
+        }
+      goto out;
+    }
+
+  /* validate and complete destination (bus name) */
+  if (g_dbus_connection_get_unique_name (c) != NULL)
+    {
+      /* this only makes sense on message bus connections */
+      if (complete_names)
+        {
+          print_names (c, FALSE);
+          goto out;
+        }
+      if (opt_call_dest == NULL)
+        {
+          if (request_completion)
+            g_print ("--dest \n");
+          else
+            g_printerr (_("Error: Destination is not specified\n"));
+          goto out;
+        }
+      if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
+        {
+          print_names (c, g_str_has_prefix (opt_call_dest, ":"));
+          goto out;
+        }
+    }
+
+  /* validate and complete object path */
+  if (complete_paths)
+    {
+      print_paths (c, opt_call_dest, "/");
+      goto out;
+    }
+  if (opt_call_object_path == NULL)
+    {
+      if (request_completion)
+        g_print ("--object-path \n");
+      else
+        g_printerr (_("Error: Object path is not specified\n"));
+      goto out;
+    }
+  if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
+    {
+      gchar *p;
+      s = g_strdup (opt_call_object_path);
+      p = strrchr (s, '/');
+      if (p != NULL)
+        {
+          if (p == s)
+            p++;
+          *p = '\0';
+        }
+      print_paths (c, opt_call_dest, s);
+      g_free (s);
+      goto out;
+    }
+  if (!request_completion && !g_variant_is_object_path (opt_call_object_path))
+    {
+      g_printerr (_("Error: %s is not a valid object path\n"), opt_call_object_path);
+      goto out;
+    }
+
+  /* validate and complete method (interface + method name) */
+  if (complete_methods)
+    {
+      print_methods (c, opt_call_dest, opt_call_object_path);
+      goto out;
+    }
+  if (opt_call_method == NULL)
+    {
+      if (request_completion)
+        g_print ("--method \n");
+      else
+        g_printerr (_("Error: Method name is not specified\n"));
+      goto out;
+    }
+  if (request_completion && g_strcmp0 ("--method", completion_prev) == 0)
+    {
+      print_methods (c, opt_call_dest, opt_call_object_path);
+      goto out;
+    }
+  s = strrchr (opt_call_method, '.');
+  if (!request_completion && s == NULL)
+    {
+      g_printerr (_("Error: Method name `%s' is invalid\n"), opt_call_method);
+      goto out;
+    }
+  method_name = g_strdup (s + 1);
+  interface_name = g_strndup (opt_call_method, s - opt_call_method);
+
+  /* All done with completion now */
+  if (request_completion)
+    goto out;
+
+  /* Introspect, for easy conversion - it's not fatal if we can't do this */
+  in_signature_types = call_helper_get_method_in_signature (c,
+                                                            opt_call_dest,
+                                                            opt_call_object_path,
+                                                            interface_name,
+                                                            method_name,
+                                                            &error);
+  if (in_signature_types == NULL)
+    {
+      //g_printerr ("Error getting introspection data: %s\n", error->message);
+      g_error_free (error);
+      error = NULL;
+    }
+
+  /* Read parameters */
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
+  for (n = 1; n < (guint) *argc; n++)
+    {
+      GVariant *value;
+      GVariantType *type;
+
+      type = NULL;
+      if (in_signature_types != NULL)
+        {
+          if (n - 1 >= in_signature_types->len)
+            {
+              /* Only warn for the first param */
+              if (n - 1 == in_signature_types->len)
+                {
+                  g_printerr ("Warning: Introspection data indicates %d parameters but more was passed\n",
+                              in_signature_types->len);
+                }
+            }
+          else
+            {
+              type = in_signature_types->pdata[n - 1];
+            }
+        }
+
+      error = NULL;
+      value = g_variant_parse (type,
+                               (*argv)[n],
+                               NULL,
+                               NULL,
+                               &error);
+      if (value == NULL)
+        {
+          g_error_free (error);
+          error = NULL;
+          value = _g_variant_parse_me_harder (type, (*argv)[n], &error);
+          if (value == NULL)
+            {
+              if (type != NULL)
+                {
+                  s = g_variant_type_dup_string (type);
+                  g_printerr (_("Error parsing parameter %d of type `%s': %s\n"),
+                              n,
+                              s,
+                              error->message);
+                  g_free (s);
+                }
+              else
+                {
+                  g_printerr (_("Error parsing parameter %d: %s\n"),
+                              n,
+                              error->message);
+                }
+              g_error_free (error);
+              g_variant_builder_clear (&builder);
+              goto out;
+            }
+        }
+      g_variant_builder_add_value (&builder, value);
+    }
+  parameters = g_variant_builder_end (&builder);
+
+  if (parameters != NULL)
+    parameters = g_variant_ref_sink (parameters);
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 opt_call_dest,
+                                                 opt_call_object_path,
+                                                 interface_name,
+                                                 method_name,
+                                                 parameters,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      if (in_signature_types != NULL)
+        {
+          GString *s;
+          s = g_string_new (NULL);
+          for (n = 0; n < in_signature_types->len; n++)
+            {
+              GVariantType *type = in_signature_types->pdata[n];
+              g_string_append_len (s,
+                                   g_variant_type_peek_string (type),
+                                   g_variant_type_get_string_length (type));
+            }
+          g_printerr ("(According to introspection data, you need to pass `%s')\n", s->str);
+          g_string_free (s, TRUE);
+        }
+      goto out;
+    }
+
+  s = g_variant_print (result, TRUE);
+  g_print ("%s\n", s);
+  g_free (s);
+
+  ret = TRUE;
+
+ out:
+  if (in_signature_types != NULL)
+    g_ptr_array_unref (in_signature_types);
+  if (result != NULL)
+    g_variant_unref (result);
+  if (c != NULL)
+    g_object_unref (c);
+  if (parameters != NULL)
+    g_variant_unref (parameters);
+  g_free (interface_name);
+  g_free (method_name);
+  g_option_context_free (o);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: dump annotations */
+
+static void
+dump_arg (const GDBusArgInfo *o,
+          guint indent,
+          const gchar *direction,
+          gboolean ignore_indent,
+          gboolean include_newline)
+{
+  g_print ("%*s%s%s %s%s",
+           ignore_indent ? 0 : indent, "",
+           direction,
+           o->signature,
+           o->name,
+           include_newline ? ",\n" : "");
+}
+
+static guint
+count_args (GDBusArgInfo **args)
+{
+  guint n;
+  n = 0;
+  if (args == NULL)
+    goto out;
+  while (args[n] != NULL)
+    n++;
+ out:
+  return n;
+}
+
+static void
+dump_method (const GDBusMethodInfo *o,
+             guint                  indent)
+{
+  guint n;
+  guint m;
+  guint name_len;
+  guint total_num_args;
+  g_print ("%*s%s(", indent, "", o->name);
+  name_len = strlen (o->name);
+  total_num_args = count_args (o->in_args) + count_args (o->out_args);
+  for (n = 0, m = 0; o->in_args != NULL && o->in_args[n] != NULL; n++, m++)
+    {
+      gboolean ignore_indent = (m == 0);
+      gboolean include_newline = (m != total_num_args - 1);
+      dump_arg (o->in_args[n],
+                indent + name_len + 1,
+                "in  ",
+                ignore_indent,
+                include_newline);
+    }
+  for (n = 0; o->out_args != NULL && o->out_args[n] != NULL; n++, m++)
+    {
+      gboolean ignore_indent = (m == 0);
+      gboolean include_newline = (m != total_num_args - 1);
+      dump_arg (o->out_args[n],
+                indent + name_len + 1,
+                "out ",
+                ignore_indent,
+                include_newline);
+    }
+  g_print (");\n");
+}
+
+static void
+dump_signal (const GDBusSignalInfo *o,
+             guint                  indent)
+{
+  guint n;
+  guint name_len;
+  guint total_num_args;
+  g_print ("%*s%s(", indent, "", o->name);
+  name_len = strlen (o->name);
+  total_num_args = count_args (o->args);
+  for (n = 0; o->args != NULL && o->args[n] != NULL; n++)
+    {
+      gboolean ignore_indent = (n == 0);
+      gboolean include_newline = (n != total_num_args - 1);
+      dump_arg (o->args[n],
+                indent + name_len + 1,
+                "",
+                ignore_indent,
+                include_newline);
+    }
+  g_print (");\n");
+}
+
+static void
+dump_property (const GDBusPropertyInfo *o,
+               guint                    indent,
+               GVariant                *value)
+{
+  const gchar *access;
+  if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+    access = "readonly";
+  else if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
+    access = "writeonly";
+  else if (o->flags == (G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+    access = "readwrite";
+  else
+    g_assert_not_reached ();
+  if (value != NULL)
+    {
+      gchar *s = g_variant_print (value, FALSE);
+      g_print ("%*s%s %s %s = %s;\n", indent, "", access, o->signature, o->name, s);
+      g_free (s);
+    }
+  else
+    {
+      g_print ("%*s%s %s %s;\n", indent, "", access, o->signature, o->name);
+    }
+}
+
+static void
+dump_interface (GDBusConnection          *c,
+                const gchar              *name,
+                const GDBusInterfaceInfo *o,
+                guint                     indent,
+                const gchar              *object_path)
+{
+  guint n;
+  GHashTable *properties;
+
+  properties = g_hash_table_new_full (g_str_hash,
+                                      g_str_equal,
+                                      g_free,
+                                      (GDestroyNotify) g_variant_unref);
+
+  /* Try to get properties */
+  if (c != NULL && name != NULL && object_path != NULL)
+    {
+      GVariant *result;
+      result = g_dbus_connection_invoke_method_sync (c,
+                                                     name,
+                                                     object_path,
+                                                     "org.freedesktop.DBus.Properties",
+                                                     "GetAll",
+                                                     g_variant_new ("(s)", o->name),
+                                                     G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                     3000,
+                                                     NULL,
+                                                     NULL);
+      if (result != NULL)
+        {
+          if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(a{sv})")))
+            {
+              GVariantIter *iter;
+              GVariant *item;
+              g_variant_get (result,
+                             "(a{sv})",
+                             &iter);
+              while ((item = g_variant_iter_next_value (iter)))
+                {
+                  const gchar *key;
+                  GVariant *value;
+                  g_variant_get (item,
+                                 "{sv}",
+                                 &key,
+                                 &value);
+
+                  g_hash_table_insert (properties, g_strdup (key), g_variant_ref (value));
+                }
+            }
+          g_variant_unref (result);
+        }
+    }
+
+  g_print ("%*sinterface %s {\n", indent, "", o->name);
+  if (o->methods != NULL)
+    {
+      g_print ("%*s  methods:\n", indent, "");
+      for (n = 0; o->methods[n] != NULL; n++)
+        dump_method (o->methods[n], indent + 4);
+    }
+  if (o->signals != NULL)
+    {
+      g_print ("%*s  signals:\n", indent, "");
+      for (n = 0; o->signals[n] != NULL; n++)
+        dump_signal (o->signals[n], indent + 4);
+    }
+  if (o->properties != NULL)
+    {
+      g_print ("%*s  properties:\n", indent, "");
+      for (n = 0; o->properties[n] != NULL; n++)
+        {
+          dump_property (o->properties[n],
+                         indent + 4,
+                         g_hash_table_lookup (properties, (o->properties[n])->name));
+        }
+    }
+  g_print ("%*s};\n",
+           indent, "");
+
+  g_hash_table_unref (properties);
+}
+
+static void
+dump_node (GDBusConnection      *c,
+           const gchar          *name,
+           const GDBusNodeInfo  *o,
+           guint                 indent,
+           const gchar          *object_path)
+{
+  guint n;
+  const gchar *object_path_to_print;
+
+  object_path_to_print = object_path;
+  if (o->path != NULL)
+    object_path_to_print = o->path;
+
+  g_print ("%*snode %s", indent, "", object_path_to_print != NULL ? object_path_to_print : "(not set)");
+  if (o->interfaces != NULL || o->nodes != NULL)
+    {
+      g_print (" {\n");
+      for (n = 0; o->interfaces != NULL && o->interfaces[n] != NULL; n++)
+        dump_interface (c, name, o->interfaces[n], indent + 2, object_path);
+      for (n = 0; o->nodes != NULL && o->nodes[n] != NULL; n++)
+        dump_node (NULL, NULL, o->nodes[n], indent + 2, NULL);
+      g_print ("%*s};\n",
+               indent, "");
+    }
+  else
+    {
+      g_print ("\n");
+    }
+}
+
+static gchar *opt_introspect_dest = NULL;
+static gchar *opt_introspect_object_path = NULL;
+
+static const GOptionEntry introspect_entries[] =
+{
+  { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_introspect_dest, N_("Destination name to introspect"), NULL},
+  { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_introspect_object_path, N_("Object path to introspect"), NULL},
+  { NULL }
+};
+
+static gboolean
+handle_introspect (gint        *argc,
+                   gchar      **argv[],
+                   gboolean     request_completion,
+                   const gchar *completion_cur,
+                   const gchar *completion_prev)
+{
+  gint ret;
+  GOptionContext *o;
+  gchar *s;
+  GError *error;
+  GDBusConnection *c;
+  GVariant *result;
+  const gchar *xml_data;
+  GDBusNodeInfo *node;
+  gboolean complete_names;
+  gboolean complete_paths;
+
+  ret = FALSE;
+  c = NULL;
+  node = NULL;
+  result = NULL;
+
+  modify_argv0_for_command (argc, argv, "introspect");
+
+  o = g_option_context_new (NULL);
+  if (request_completion)
+    g_option_context_set_ignore_unknown_options (o, TRUE);
+  g_option_context_set_help_enabled (o, FALSE);
+  g_option_context_set_summary (o, _("Introspect a remote object."));
+  g_option_context_add_main_entries (o, introspect_entries, NULL /* GETTEXT_PACKAGE*/);
+  g_option_context_add_group (o, connection_get_group ());
+
+  complete_names = FALSE;
+  if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
+    {
+      complete_names = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  complete_paths = FALSE;
+  if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
+    {
+      complete_paths = TRUE;
+      remove_arg ((*argc) - 1, argc, argv);
+    }
+
+  if (!g_option_context_parse (o, argc, argv, NULL))
+    {
+      if (!request_completion)
+        {
+          s = g_option_context_get_help (o, FALSE, NULL);
+          g_printerr ("%s", s);
+          g_free (s);
+          goto out;
+        }
+    }
+
+  error = NULL;
+  c = connection_get_dbus_connection (&error);
+  if (c == NULL)
+    {
+      if (request_completion)
+        {
+          if (g_strcmp0 (completion_prev, "--address") == 0)
+            {
+              g_print ("unix:\n"
+                       "tcp:\n"
+                       "nonce-tcp:\n");
+            }
+          else
+            {
+              g_print ("--system \n--session \n--address \n");
+            }
+        }
+      else
+        {
+          g_printerr (_("Error connecting: %s\n"), error->message);
+          g_error_free (error);
+        }
+      goto out;
+    }
+
+  if (g_dbus_connection_get_unique_name (c) != NULL)
+    {
+      if (complete_names)
+        {
+          print_names (c, FALSE);
+          goto out;
+        }
+      /* this only makes sense on message bus connections */
+      if (opt_introspect_dest == NULL)
+        {
+          if (request_completion)
+            g_print ("--dest \n");
+          else
+            g_printerr (_("Error: Destination is not specified\n"));
+          goto out;
+        }
+      if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
+        {
+          print_names (c, g_str_has_prefix (opt_introspect_dest, ":"));
+          goto out;
+        }
+    }
+  if (complete_paths)
+    {
+      print_paths (c, opt_introspect_dest, "/");
+      goto out;
+    }
+  if (opt_introspect_object_path == NULL)
+    {
+      if (request_completion)
+        g_print ("--object-path \n");
+      else
+        g_printerr (_("Error: Object path is not specified\n"));
+      goto out;
+    }
+  if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
+    {
+      gchar *p;
+      s = g_strdup (opt_introspect_object_path);
+      p = strrchr (s, '/');
+      if (p != NULL)
+        {
+          if (p == s)
+            p++;
+          *p = '\0';
+        }
+      print_paths (c, opt_introspect_dest, s);
+      g_free (s);
+      goto out;
+    }
+  if (!request_completion && !g_variant_is_object_path (opt_introspect_object_path))
+    {
+      g_printerr (_("Error: %s is not a valid object path\n"), opt_introspect_object_path);
+      goto out;
+    }
+
+  /* All done with completion now */
+  if (request_completion)
+    goto out;
+
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 opt_introspect_dest,
+                                                 opt_introspect_object_path,
+                                                 "org.freedesktop.DBus.Introspectable",
+                                                 "Introspect",
+                                                 NULL,
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 3000, /* 3 sec */
+                                                 NULL,
+                                                 &error);
+  if (result == NULL)
+    {
+      g_printerr (_("Error: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(s)")))
+    {
+      g_printerr (_("Error: Result is type `%s', expected `(s)'\n"),
+                  g_variant_get_type_string (result));
+      goto out;
+    }
+  g_variant_get (result, "(s)", &xml_data);
+
+  error = NULL;
+  node = g_dbus_node_info_new_for_xml (xml_data, &error);
+  if (node == NULL)
+    {
+      g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  dump_node (c, opt_introspect_dest, node, 0, opt_introspect_object_path);
+
+  ret = TRUE;
+
+ out:
+  if (node != NULL)
+    g_dbus_node_info_unref (node);
+  if (result != NULL)
+    g_variant_unref (result);
+  if (c != NULL)
+    g_object_unref (c);
+  g_option_context_free (o);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+pick_word_at (const gchar  *s,
+              gint          cursor,
+              gint         *out_word_begins_at)
+{
+  gint begin;
+  gint end;
+
+  if (s[0] == '\0')
+    {
+      if (out_word_begins_at != NULL)
+        *out_word_begins_at = -1;
+      return NULL;
+    }
+
+  if (g_ascii_isspace (s[cursor]) && ((cursor > 0 && g_ascii_isspace(s[cursor-1])) || cursor == 0))
+    {
+      if (out_word_begins_at != NULL)
+        *out_word_begins_at = cursor;
+      return g_strdup ("");
+    }
+
+  while (!g_ascii_isspace (s[cursor - 1]) && cursor > 0)
+    cursor--;
+  begin = cursor;
+
+  end = begin;
+  while (!g_ascii_isspace (s[end]) && s[end] != '\0')
+    end++;
+
+  if (out_word_begins_at != NULL)
+    *out_word_begins_at = begin;
+
+  return g_strndup (s + begin, end - begin);
+}
+
+gint
+main (gint argc, gchar *argv[])
+{
+  gint ret;
+  const gchar *command;
+  gboolean request_completion;
+  gchar *completion_cur;
+  gchar *completion_prev;
+
+  ret = 1;
+  completion_cur = NULL;
+  completion_prev = NULL;
+
+  g_type_init ();
+
+  if (argc < 2)
+    {
+      usage (&argc, &argv, FALSE);
+      goto out;
+    }
+
+  request_completion = FALSE;
+
+  //completion_debug ("---- argc=%d --------------------------------------------------------", argc);
+
+ again:
+  command = argv[1];
+  if (g_strcmp0 (command, "help") == 0)
+    {
+      if (request_completion)
+        {
+          /* do nothing */
+        }
+      else
+        {
+          usage (&argc, &argv, TRUE);
+          ret = 0;
+        }
+      goto out;
+    }
+  else if (g_strcmp0 (command, "call") == 0)
+    {
+      if (handle_call (&argc,
+                       &argv,
+                       request_completion,
+                       completion_cur,
+                       completion_prev))
+        ret = 0;
+      goto out;
+    }
+  else if (g_strcmp0 (command, "introspect") == 0)
+    {
+      if (handle_introspect (&argc,
+                             &argv,
+                             request_completion,
+                             completion_cur,
+                             completion_prev))
+        ret = 0;
+      goto out;
+    }
+  else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion)
+    {
+      const gchar *completion_line;
+      gchar **completion_argv;
+      gint completion_argc;
+      gint completion_point;
+      gchar *endp;
+      gint cur_begin;
+
+      request_completion = TRUE;
+
+      completion_line = argv[2];
+      completion_point = strtol (argv[3], &endp, 10);
+      if (endp == argv[3] || *endp != '\0')
+        goto out;
+
+#if 0
+      completion_debug ("completion_point=%d", completion_point);
+      completion_debug ("----");
+      completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789");
+      completion_debug ("`%s'", completion_line);
+      completion_debug (" %*s^",
+                         completion_point, "");
+      completion_debug ("----");
+#endif
+
+      if (!g_shell_parse_argv (completion_line,
+                               &completion_argc,
+                               &completion_argv,
+                               NULL))
+        {
+          /* it's very possible the command line can't be parsed (for
+           * example, missing quotes etc) - in that case, we just
+           * don't autocomplete at all
+           */
+          goto out;
+        }
+
+      /* compute cur and prev */
+      completion_prev = NULL;
+      completion_cur = pick_word_at (completion_line, completion_point, &cur_begin);
+      if (cur_begin > 0)
+        {
+          gint prev_end;
+          for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--)
+            {
+              if (!g_ascii_isspace (completion_line[prev_end]))
+                {
+                  completion_prev = pick_word_at (completion_line, prev_end, NULL);
+                  break;
+                }
+            }
+        }
+#if 0
+      completion_debug (" cur=`%s'", completion_cur);
+      completion_debug ("prev=`%s'", completion_prev);
+#endif
+
+      argc = completion_argc;
+      argv = completion_argv;
+
+      ret = 0;
+
+      goto again;
+    }
+  else
+    {
+      if (request_completion)
+        {
+          g_print ("help \ncall \nintrospect \n");
+          ret = 0;
+          goto out;
+        }
+      else
+        {
+          g_printerr ("Unknown command `%s'\n", command);
+          usage (&argc, &argv, FALSE);
+          goto out;
+        }
+    }
+
+ out:
+  g_free (completion_cur);
+  g_free (completion_prev);
+  return ret;
+}
diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c
new file mode 100644
index 0000000..e50e737
--- /dev/null
+++ b/gio/gdbusaddress.c
@@ -0,0 +1,1004 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusaddress.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gdbusprivate.h"
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/**
+ * SECTION:gdbusaddress
+ * @title: D-Bus Addresses
+ * @short_description: D-Bus connection endpoints
+ * @include: gdbus/gdbus.h
+ *
+ * Routines for working with D-Bus addresses.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_is_address:
+ * @string: A string.
+ *
+ * Checks if @string is a D-Bus address.
+ *
+ * This doesn't check if @string is actually supported by #GDBusServer
+ * or #GDBusConnection - use g_dbus_is_supported_address() to do more
+ * checks.
+ *
+ * Returns: %TRUE if @string is a valid D-Bus address, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_address (const gchar *string)
+{
+  guint n;
+  gchar **a;
+  gboolean ret;
+
+  ret = FALSE;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  a = g_strsplit (string, ";", 0);
+  for (n = 0; a[n] != NULL; n++)
+    {
+      if (!_g_dbus_address_parse_entry (a[n],
+                                        NULL,
+                                        NULL,
+                                        NULL))
+        goto out;
+    }
+
+  ret = TRUE;
+
+ out:
+  g_strfreev (a);
+  return ret;
+}
+
+static gboolean
+is_valid_unix (const gchar  *address_entry,
+               GHashTable   *key_value_pairs,
+               GError      **error)
+{
+  gboolean ret;
+  GList *keys;
+  GList *l;
+  const gchar *path;
+  const gchar *tmpdir;
+  const gchar *abstract;
+
+  ret = FALSE;
+  keys = NULL;
+  path = NULL;
+  tmpdir = NULL;
+  abstract = NULL;
+
+  keys = g_hash_table_get_keys (key_value_pairs);
+  for (l = keys; l != NULL; l = l->next)
+    {
+      const gchar *key = l->data;
+      if (g_strcmp0 (key, "path") == 0)
+        path = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "tmpdir") == 0)
+        tmpdir = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "abstract") == 0)
+        abstract = g_hash_table_lookup (key_value_pairs, key);
+      else
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Unsupported key `%s' in address entry `%s'"),
+                       key,
+                       address_entry);
+          goto out;
+        }
+    }
+
+  if (path != NULL)
+    {
+      if (tmpdir != NULL || abstract != NULL)
+        goto meaningless;
+      /* TODO: validate path */
+    }
+  else if (tmpdir != NULL)
+    {
+      if (path != NULL || abstract != NULL)
+        goto meaningless;
+      /* TODO: validate tmpdir */
+    }
+  else if (abstract != NULL)
+    {
+      if (path != NULL || tmpdir != NULL)
+        goto meaningless;
+      /* TODO: validate abstract */
+    }
+  else
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Address `%s' is invalid (need exactly one of path, tmpdir or abstract keys"),
+                   address_entry);
+      goto out;
+    }
+
+
+  ret= TRUE;
+  goto out;
+
+ meaningless:
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_INVALID_ARGUMENT,
+               _("Meaningless key/value pair combination in address entry `%s'"),
+               address_entry);
+
+ out:
+  g_list_free (keys);
+
+  return ret;
+}
+
+static gboolean
+is_valid_nonce_tcp (const gchar  *address_entry,
+                    GHashTable   *key_value_pairs,
+                    GError      **error)
+{
+  gboolean ret;
+  GList *keys;
+  GList *l;
+  const gchar *host;
+  const gchar *port;
+  const gchar *family;
+  const gchar *nonce_file;
+  gint port_num;
+  gchar *endp;
+
+  ret = FALSE;
+  keys = NULL;
+  host = NULL;
+  port = NULL;
+  family = NULL;
+  nonce_file = NULL;
+
+  keys = g_hash_table_get_keys (key_value_pairs);
+  for (l = keys; l != NULL; l = l->next)
+    {
+      const gchar *key = l->data;
+      if (g_strcmp0 (key, "host") == 0)
+        host = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "port") == 0)
+        port = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "family") == 0)
+        family = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "noncefile") == 0)
+        nonce_file = g_hash_table_lookup (key_value_pairs, key);
+      else
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Unsupported key `%s' in address entry `%s'"),
+                       key,
+                       address_entry);
+          goto out;
+        }
+    }
+
+  if (port != NULL)
+    {
+      port_num = strtol (port, &endp, 10);
+      if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Error in address `%s' - the port attribute is malformed"),
+                       address_entry);
+          goto out;
+        }
+    }
+
+  if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Error in address `%s' - the family attribute is malformed"),
+                   address_entry);
+      goto out;
+    }
+
+  ret= TRUE;
+
+ out:
+  g_list_free (keys);
+
+  return ret;
+}
+
+static gboolean
+is_valid_tcp (const gchar  *address_entry,
+              GHashTable   *key_value_pairs,
+              GError      **error)
+{
+  gboolean ret;
+  GList *keys;
+  GList *l;
+  const gchar *host;
+  const gchar *port;
+  const gchar *family;
+  gint port_num;
+  gchar *endp;
+
+  ret = FALSE;
+  keys = NULL;
+  host = NULL;
+  port = NULL;
+  family = NULL;
+
+  keys = g_hash_table_get_keys (key_value_pairs);
+  for (l = keys; l != NULL; l = l->next)
+    {
+      const gchar *key = l->data;
+      if (g_strcmp0 (key, "host") == 0)
+        host = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "port") == 0)
+        port = g_hash_table_lookup (key_value_pairs, key);
+      else if (g_strcmp0 (key, "family") == 0)
+        family = g_hash_table_lookup (key_value_pairs, key);
+      else
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Unsupported key `%s' in address entry `%s'"),
+                       key,
+                       address_entry);
+          goto out;
+        }
+    }
+
+  if (port != NULL)
+    {
+      port_num = strtol (port, &endp, 10);
+      if ((*port == '\0' || *endp != '\0') || port_num < 0 || port_num >= 65536)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Error in address `%s' - the port attribute is malformed"),
+                       address_entry);
+          goto out;
+        }
+    }
+
+  if (family != NULL && !(g_strcmp0 (family, "ipv4") == 0 || g_strcmp0 (family, "ipv6") == 0))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Error in address `%s' - the family attribute is malformed"),
+                   address_entry);
+      goto out;
+    }
+
+  ret= TRUE;
+
+ out:
+  g_list_free (keys);
+
+  return ret;
+}
+
+/**
+ * g_dbus_is_supported_address:
+ * @string: A string.
+ * @error: Return location for error or %NULL.
+ *
+ * Like g_dbus_is_address() but also checks if the library suppors the
+ * transports in @string and that key/value pairs for each transport
+ * are valid.
+ *
+ * Returns: %TRUE if @string is a valid D-Bus address that is
+ * supported by this library, %FALSE if @error is set.
+ */
+gboolean
+g_dbus_is_supported_address (const gchar  *string,
+                             GError      **error)
+{
+  guint n;
+  gchar **a;
+  gboolean ret;
+
+  ret = FALSE;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  a = g_strsplit (string, ";", 0);
+  for (n = 0; a[n] != NULL; n++)
+    {
+      gchar *transport_name;
+      GHashTable *key_value_pairs;
+      gboolean supported;
+
+      if (!_g_dbus_address_parse_entry (a[n],
+                                        &transport_name,
+                                        &key_value_pairs,
+                                        error))
+        goto out;
+
+      supported = FALSE;
+      if (g_strcmp0 (transport_name, "unix") == 0)
+        supported = is_valid_unix (a[n], key_value_pairs, error);
+      else if (g_strcmp0 (transport_name, "tcp") == 0)
+        supported = is_valid_tcp (a[n], key_value_pairs, error);
+      else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
+        supported = is_valid_nonce_tcp (a[n], key_value_pairs, error);
+
+      g_free (transport_name);
+      g_hash_table_unref (key_value_pairs);
+
+      if (!supported)
+        goto out;
+    }
+
+  ret = TRUE;
+
+ out:
+  g_strfreev (a);
+
+  g_assert (ret || (!ret && (error == NULL || *error != NULL)));
+
+  return ret;
+}
+
+gboolean
+_g_dbus_address_parse_entry (const gchar   *address_entry,
+                             gchar        **out_transport_name,
+                             GHashTable   **out_key_value_pairs,
+                             GError       **error)
+{
+  gboolean ret;
+  GHashTable *key_value_pairs;
+  gchar *transport_name;
+  gchar **kv_pairs;
+  const gchar *s;
+  guint n;
+
+  ret = FALSE;
+  kv_pairs = NULL;
+  transport_name = NULL;
+  key_value_pairs = NULL;
+
+  s = strchr (address_entry, ':');
+  if (s == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Address element `%s', does not contain a colon (:)"),
+                   address_entry);
+      goto out;
+    }
+
+  transport_name = g_strndup (address_entry, s - address_entry);
+  key_value_pairs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  kv_pairs = g_strsplit (s + 1, ",", 0);
+  for (n = 0; kv_pairs != NULL && kv_pairs[n] != NULL; n++)
+    {
+      const gchar *kv_pair = kv_pairs[n];
+      gchar *key;
+      gchar *value;
+
+      s = strchr (kv_pair, '=');
+      if (s == NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Key/Value pair %d, `%s', in address element `%s', does not contain an equal sign"),
+                       n,
+                       kv_pair,
+                       address_entry);
+          goto out;
+        }
+
+      /* TODO: actually validate that no illegal characters are present before and after then '=' sign */
+      key = g_uri_unescape_segment (kv_pair, s, NULL);
+      value = g_uri_unescape_segment (s + 1, kv_pair + strlen (kv_pair), NULL);
+      g_hash_table_insert (key_value_pairs, key, value);
+    }
+
+  ret = TRUE;
+
+out:
+  g_strfreev (kv_pairs);
+  if (ret)
+    {
+      if (out_transport_name != NULL)
+        *out_transport_name = transport_name;
+      else
+        g_free (transport_name);
+      if (out_key_value_pairs != NULL)
+        *out_key_value_pairs = key_value_pairs;
+      else if (key_value_pairs != NULL)
+        g_hash_table_unref (key_value_pairs);
+    }
+  else
+    {
+      g_free (transport_name);
+      if (key_value_pairs != NULL)
+        g_hash_table_unref (key_value_pairs);
+    }
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: Declare an extension point called GDBusTransport (or similar)
+ * and move code below to extensions implementing said extension
+ * point. That way we can implement a D-Bus transport over X11 without
+ * making libgio link to libX11...
+ */
+static GIOStream *
+g_dbus_address_connect (const gchar    *address_entry,
+                        const gchar    *transport_name,
+                        GHashTable     *key_value_pairs,
+                        GCancellable   *cancellable,
+                        GError        **error)
+{
+  GIOStream *ret;
+  GSocketConnectable *connectable;
+  const gchar *nonce_file;
+
+  connectable = NULL;
+  ret = NULL;
+  nonce_file = NULL;
+
+  if (FALSE)
+    {
+    }
+#ifdef G_OS_UNIX
+  else if (g_strcmp0 (transport_name, "unix") == 0)
+    {
+      const gchar *path;
+      const gchar *abstract;
+      path = g_hash_table_lookup (key_value_pairs, "path");
+      abstract = g_hash_table_lookup (key_value_pairs, "abstract");
+      if ((path == NULL && abstract == NULL) || (path != NULL && abstract != NULL))
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Error in address `%s' - the unix transport requires exactly one of the "
+                         "keys `path' or `abstract' to be set"),
+                       address_entry);
+        }
+      else if (path != NULL)
+        {
+          connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new (path));
+        }
+      else if (abstract != NULL)
+        {
+          connectable = G_SOCKET_CONNECTABLE (g_unix_socket_address_new_with_type (abstract,
+                                                                                   -1,
+                                                                                   G_UNIX_SOCKET_ADDRESS_ABSTRACT));
+        }
+      else
+        {
+          g_assert_not_reached ();
+        }
+    }
+#endif
+  else if (g_strcmp0 (transport_name, "tcp") == 0 || g_strcmp0 (transport_name, "nonce-tcp") == 0)
+    {
+      const gchar *s;
+      const gchar *host;
+      guint port;
+      gchar *endp;
+      gboolean is_nonce;
+
+      is_nonce = (g_strcmp0 (transport_name, "nonce-tcp") == 0);
+
+      host = g_hash_table_lookup (key_value_pairs, "host");
+      if (host == NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Error in address `%s' - the host attribute is missing or malformed"),
+                       address_entry);
+          goto out;
+        }
+
+      s = g_hash_table_lookup (key_value_pairs, "port");
+      if (s == NULL)
+        s = "0";
+      port = strtol (s, &endp, 10);
+      if ((*s == '\0' || *endp != '\0') || port < 0 || port >= 65536)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Error in address `%s' - the port attribute is missing or malformed"),
+                       address_entry);
+          goto out;
+        }
+
+
+      if (is_nonce)
+        {
+          nonce_file = g_hash_table_lookup (key_value_pairs, "noncefile");
+          if (nonce_file == NULL)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Error in address `%s' - the noncefile attribute is missing or malformed"),
+                           address_entry);
+              goto out;
+            }
+        }
+
+      /* TODO: deal with family */
+      connectable = g_network_address_new (host, port);
+    }
+  else
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Unknown or unsupported transport `%s' for address `%s'"),
+                   transport_name,
+                   address_entry);
+    }
+
+  if (connectable != NULL)
+    {
+      GSocketClient *client;
+      GSocketConnection *connection;
+
+      g_assert (ret == NULL);
+      client = g_socket_client_new ();
+      connection = g_socket_client_connect (client,
+                                            connectable,
+                                            cancellable,
+                                            error);
+      g_object_unref (connectable);
+      g_object_unref (client);
+      if (connection == NULL)
+        goto out;
+
+      ret = G_IO_STREAM (connection);
+
+      if (nonce_file != NULL)
+        {
+          gchar *nonce_contents;
+          gsize nonce_length;
+
+          /* TODO: too dangerous to read the entire file? (think denial-of-service etc.) */
+          if (!g_file_get_contents (nonce_file,
+                                    &nonce_contents,
+                                    &nonce_length,
+                                    error))
+            {
+              g_prefix_error (error, _("Error reading nonce file `%s':"), nonce_file);
+              g_object_unref (ret);
+              ret = NULL;
+              goto out;
+            }
+
+          if (nonce_length != 16)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("The nonce-file `%s' was %" G_GSIZE_FORMAT " bytes. Expected 16 bytes."),
+                           nonce_file,
+                           nonce_length);
+              g_free (nonce_contents);
+              g_object_unref (ret);
+              ret = NULL;
+              goto out;
+            }
+
+          if (!g_output_stream_write_all (g_io_stream_get_output_stream (ret),
+                                          nonce_contents,
+                                          nonce_length,
+                                          NULL,
+                                          cancellable,
+                                          error))
+            {
+              g_prefix_error (error, _("Error write contents of nonce file `%s' to stream:"), nonce_file);
+              g_object_unref (ret);
+              ret = NULL;
+              g_free (nonce_contents);
+              goto out;
+            }
+          g_free (nonce_contents);
+        }
+    }
+
+ out:
+
+  return ret;
+}
+
+static GIOStream *
+g_dbus_address_try_connect_one (const gchar         *address_entry,
+                                gchar              **out_guid,
+                                GCancellable        *cancellable,
+                                GError             **error)
+{
+  GIOStream *ret;
+  GHashTable *key_value_pairs;
+  gchar *transport_name;
+  const gchar *guid;
+
+  ret = NULL;
+  transport_name = NULL;
+  key_value_pairs = NULL;
+
+  if (!_g_dbus_address_parse_entry (address_entry,
+                                    &transport_name,
+                                    &key_value_pairs,
+                                    error))
+    goto out;
+
+  ret = g_dbus_address_connect (address_entry,
+                                transport_name,
+                                key_value_pairs,
+                                cancellable,
+                                error);
+  if (ret == NULL)
+    goto out;
+
+  /* TODO: validate that guid is of correct format */
+  guid = g_hash_table_lookup (key_value_pairs, "guid");
+  if (guid != NULL && out_guid != NULL)
+    *out_guid = g_strdup (guid);
+
+out:
+  g_free (transport_name);
+  if (key_value_pairs != NULL)
+    g_hash_table_unref (key_value_pairs);
+  return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  gchar *address;
+  GIOStream *stream;
+  gchar *guid;
+} GetStreamData;
+
+static void
+get_stream_data_free (GetStreamData *data)
+{
+  g_free (data->address);
+  if (data->stream != NULL)
+    g_object_unref (data->stream);
+  g_free (data->guid);
+  g_free (data);
+}
+
+static void
+get_stream_thread_func (GSimpleAsyncResult *res,
+                        GObject            *object,
+                        GCancellable       *cancellable)
+{
+  GetStreamData *data;
+  GError *error;
+
+  data = g_simple_async_result_get_op_res_gpointer (res);
+
+  error = NULL;
+  data->stream = g_dbus_address_get_stream_sync (data->address,
+                                                 &data->guid,
+                                                 cancellable,
+                                                 &error);
+  if (data->stream == NULL)
+    {
+      g_simple_async_result_set_from_error (res, error);
+      g_error_free (error);
+    }
+}
+
+/**
+ * g_dbus_address_get_stream:
+ * @address: A valid D-Bus address.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: Data to pass to @callback.
+ *
+ * Asynchronously connects to an endpoint specified by @address and
+ * sets up the connection so it is in a state to run the client-side
+ * of the D-Bus authentication conversation.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_address_get_stream_finish() to get the result of
+ * the operation.
+ *
+ * This is an asynchronous failable function. See
+ * g_dbus_address_get_stream_sync() for the synchronous version.
+ */
+void
+g_dbus_address_get_stream (const gchar         *address,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  GSimpleAsyncResult *res;
+  GetStreamData *data;
+
+  g_return_if_fail (address != NULL);
+
+  res = g_simple_async_result_new (NULL,
+                                   callback,
+                                   user_data,
+                                   g_dbus_address_get_stream);
+  data = g_new0 (GetStreamData, 1);
+  data->address = g_strdup (address);
+  g_simple_async_result_set_op_res_gpointer (res,
+                                             data,
+                                             (GDestroyNotify) get_stream_data_free);
+  g_simple_async_result_run_in_thread (res,
+                                       get_stream_thread_func,
+                                       G_PRIORITY_DEFAULT,
+                                       cancellable);
+  g_object_unref (res);
+}
+
+/**
+ * g_dbus_address_get_stream_finish:
+ * @res: A #GAsyncResult obtained from the GAsyncReadyCallback passed to g_dbus_address_get_stream().
+ * @out_guid: %NULL or return location to store the GUID extracted from @address, if any.
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_address_get_stream().
+ *
+ * Returns: A #GIOStream or %NULL if @error is set.
+ */
+GIOStream *
+g_dbus_address_get_stream_finish (GAsyncResult        *res,
+                                  gchar              **out_guid,
+                                  GError             **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GetStreamData *data;
+  GIOStream *ret;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_address_get_stream);
+
+  ret = NULL;
+
+  data = g_simple_async_result_get_op_res_gpointer (simple);
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  ret = g_object_ref (data->stream);
+  if (out_guid != NULL)
+    *out_guid = g_strdup (data->guid);
+
+ out:
+  return ret;
+}
+
+/**
+ * g_dbus_address_get_stream_sync:
+ * @address: A valid D-Bus address.
+ * @out_guid: %NULL or return location to store the GUID extracted from @address, if any.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects to an endpoint specified by @address and
+ * sets up the connection so it is in a state to run the client-side
+ * of the D-Bus authentication conversation.
+ *
+ * This is a synchronous failable function. See
+ * g_dbus_address_get_stream() for the asynchronous version.
+ *
+ * Returns: A #GIOStream or %NULL if @error is set.
+ */
+GIOStream *
+g_dbus_address_get_stream_sync (const gchar         *address,
+                                gchar              **out_guid,
+                                GCancellable        *cancellable,
+                                GError             **error)
+{
+  GIOStream *ret;
+  gchar **addr_array;
+  guint n;
+  GError *last_error;
+
+  g_return_val_if_fail (address != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  ret = NULL;
+  last_error = NULL;
+
+  addr_array = g_strsplit (address, ";", 0);
+  last_error = NULL;
+  for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++)
+    {
+      const gchar *addr = addr_array[n];
+      GError *this_error;
+      this_error = NULL;
+      ret = g_dbus_address_try_connect_one (addr,
+                                            out_guid,
+                                            cancellable,
+                                            &this_error);
+      if (ret != NULL)
+        {
+          goto out;
+        }
+      else
+        {
+          g_assert (this_error != NULL);
+          if (last_error != NULL)
+            g_error_free (last_error);
+          last_error = this_error;
+        }
+    }
+
+ out:
+  if (ret != NULL)
+    {
+      if (last_error != NULL)
+        g_error_free (last_error);
+    }
+  else
+    {
+      g_assert (last_error != NULL);
+      g_propagate_error (error, last_error);
+    }
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: implement for UNIX, Win32 and OS X */
+static gchar *
+get_session_address_platform_specific (void)
+{
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_address_get_for_bus_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously looks up the D-Bus address for the well-known message
+ * bus instance specified by @bus_type. This may involve using various
+ * platform specific mechanisms.
+ *
+ * Returns: A valid D-Bus address string for @bus_type or %NULL if @error is set.
+ */
+gchar *
+g_dbus_address_get_for_bus_sync (GBusType       bus_type,
+                                 GCancellable  *cancellable,
+                                 GError       **error)
+{
+  gchar *ret;
+  const gchar *starter_bus;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  ret = NULL;
+
+  switch (bus_type)
+    {
+    case G_BUS_TYPE_SYSTEM:
+      ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"));
+      if (ret == NULL)
+        {
+          ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket");
+        }
+      break;
+
+    case G_BUS_TYPE_SESSION:
+      ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
+      if (ret == NULL)
+        {
+          ret = get_session_address_platform_specific ();
+          if (ret == NULL)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Cannot determine session bus address (TODO: run dbus-launch to find out)"));
+            }
+        }
+      break;
+
+    case G_BUS_TYPE_STARTER:
+      starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE");
+      if (g_strcmp0 (starter_bus, "session") == 0)
+        {
+          ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, cancellable, error);
+          goto out;
+        }
+      else if (g_strcmp0 (starter_bus, "system") == 0)
+        {
+          ret = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
+          goto out;
+        }
+      else
+        {
+          if (starter_bus != NULL)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable"
+                             " - unknown value `%s'"),
+                           starter_bus);
+            }
+          else
+            {
+              g_set_error_literal (error,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_FAILED,
+                                   _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment "
+                                     "variable is not set"));
+            }
+        }
+      break;
+
+    default:
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("Unknown bus type %d"),
+                   bus_type);
+      break;
+    }
+
+ out:
+  return ret;
+}
diff --git a/gio/gdbusaddress.h b/gio/gdbusaddress.h
new file mode 100644
index 0000000..feac1a5
--- /dev/null
+++ b/gio/gdbusaddress.h
@@ -0,0 +1,54 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_ADDRESS_H__
+#define __G_DBUS_ADDRESS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+gboolean g_dbus_is_address (const gchar *string);
+gboolean g_dbus_is_supported_address (const gchar  *string,
+                                      GError      **error);
+
+void                 g_dbus_address_get_stream        (const gchar          *address,
+                                                       GCancellable         *cancellable,
+                                                       GAsyncReadyCallback   callback,
+                                                       gpointer              user_data);
+
+GIOStream           *g_dbus_address_get_stream_finish (GAsyncResult         *res,
+                                                       gchar               **out_guid,
+                                                       GError              **error);
+
+GIOStream           *g_dbus_address_get_stream_sync   (const gchar          *address,
+                                                       gchar               **out_guid,
+                                                       GCancellable         *cancellable,
+                                                       GError              **error);
+
+gchar               *g_dbus_address_get_for_bus_sync  (GBusType              bus_type,
+                                                       GCancellable  *cancellable,
+                                                       GError              **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_ADDRESS_H__ */
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
new file mode 100644
index 0000000..850db9a
--- /dev/null
+++ b/gio/gdbusauth.c
@@ -0,0 +1,1538 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauth.h"
+#include "gdbusauthmechanismanon.h"
+#include "gdbusauthmechanismexternal.h"
+#include "gdbusauthmechanismsha1.h"
+
+#include "gdbusauthobserver.h"
+
+#include "gdbuserror.h"
+#include "gdbusutils.h"
+#include "gioenumtypes.h"
+#include "gcredentials.h"
+#include "gdbusprivate.h"
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include "gunixcredentialsmessage.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+#define DEBUG_ENABLED 1
+
+static void
+debug_print (const gchar *message, ...)
+{
+#if DEBUG_ENABLED
+  if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+    {
+      gchar *s;
+      GString *str;
+      va_list var_args;
+      guint n;
+
+      va_start (var_args, message);
+      s = g_strdup_vprintf (message, var_args);
+      va_end (var_args);
+
+      str = g_string_new (NULL);
+      for (n = 0; s[n] != '\0'; n++)
+        {
+          if (G_UNLIKELY (s[n] == '\r'))
+            g_string_append (str, "\\r");
+          else if (G_UNLIKELY (s[n] == '\n'))
+            g_string_append (str, "\\n");
+          else
+            g_string_append_c (str, s[n]);
+        }
+      g_print ("GDBus-debug:Auth: %s\n", str->str);
+      g_string_free (str, TRUE);
+      g_free (s);
+    }
+#endif
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* TODO: move to gio */
+
+/**
+ * g_unix_connection_send_credentials:
+ * @connection: A #GUnixConnection.
+ * @credentials: A #GCredentials to send.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Passes the credentials stored in @credentials to the recieving side
+ * of the connection. The recieving end has to call
+ * g_unix_connection_receive_credentials() (or similar) to accept the
+ * credentials.
+ *
+ * The credentials which the sender specifies are checked by the
+ * kernel.  A process with effective user ID 0 is allowed to specify
+ * values that do not match its own. This means that the credentials
+ * can be used to authenticate other connections.
+ *
+ * As well as sending the credentials this also writes a single NUL
+ * byte to the stream, as this is required for credentials passing to
+ * work on some implementations.
+ *
+ * Returns: %TRUE on success, %FALSE if @error is set.
+ *
+ * Since: 2.26
+ */
+static gboolean
+g_unix_connection_send_credentials (GUnixConnection      *connection,
+                                    GCredentials         *credentials,
+                                    GCancellable         *cancellable,
+                                    GError              **error)
+{
+  GSocketControlMessage *scm;
+  GSocket *socket;
+  gboolean ret;
+  GOutputVector vector;
+  guchar nul_byte[1] = {'\0'};
+
+  g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  ret = FALSE;
+
+  vector.buffer = &nul_byte;
+  vector.size = 1;
+  scm = g_unix_credentials_message_new_with_credentials (credentials);
+  g_object_get (connection, "socket", &socket, NULL);
+  if (g_socket_send_message (socket,
+                             NULL, /* address */
+                             &vector,
+                             1,
+                             &scm,
+                             1,
+                             G_SOCKET_MSG_NONE,
+                             cancellable,
+                             error) != 1)
+    {
+      g_prefix_error (error, _("Error sending credentials: "));
+      goto out;
+    }
+
+  ret = TRUE;
+
+ out:
+  g_object_unref (socket);
+  g_object_unref (scm);
+  return ret;
+}
+
+/**
+ * g_unix_connection_receive_credentials:
+ * @connection: A #GUnixConnection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Receives credentials from the sending end of the connection.  The
+ * sending end has to call g_unix_connection_send_credentials() (or
+ * similar) for this to work.
+ *
+ * As well as reading the credentials this also reads (and discards) a
+ * single byte from the stream, as this is required for credentials
+ * passing to work on some implementations.
+ *
+ * Returns: Received credentials on success (free with
+ * g_object_unref()), %NULL if @error is set.
+ *
+ * Since: 2.26
+ */
+static GCredentials *
+g_unix_connection_receive_credentials (GUnixConnection      *connection,
+                                       GCancellable         *cancellable,
+                                       GError              **error)
+{
+  GCredentials *ret;
+  GSocketControlMessage **scms;
+  gint nscm;
+  GSocket *socket;
+  gint n;
+  volatile GType credentials_message_gtype;
+  gssize num_bytes_read;
+
+  g_return_val_if_fail (G_IS_UNIX_CONNECTION (connection), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  ret = NULL;
+  scms = NULL;
+
+  g_object_get (connection, "socket", &socket, NULL);
+
+#if 1
+  /* TODO: Move this to gsocket.c... */
+  {
+    int opt_val = 1;
+    if (setsockopt (g_socket_get_fd (socket),
+                    SOL_SOCKET,
+                    SO_PASSCRED,
+                    &opt_val,
+                    sizeof opt_val) != 0)
+      {
+        g_warning ("boo, error setting SO_PASSCRED: %m");
+      }
+  }
+#endif
+
+  /* ensure the type of GUnixCredentialsMessage has been registered with the type system */
+  credentials_message_gtype = G_TYPE_UNIX_CREDENTIALS_MESSAGE;
+  num_bytes_read = g_socket_receive_message (socket,
+                                             NULL, /* GSocketAddress **address */
+                                             NULL,
+                                             0,
+                                             &scms,
+                                             &nscm,
+                                             NULL,
+                                             cancellable,
+                                             error);
+  if (num_bytes_read != 1)
+    {
+      /* Handle situation where g_socket_receive_message() returns
+       * 0 bytes and not setting @error
+       */
+      if (num_bytes_read == 0 && error != NULL && *error == NULL)
+        {
+          g_set_error_literal (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               _("Expecting to read a single byte for receiving credentials but read zero bytes"));
+        }
+      goto out;
+    }
+
+  if (nscm != 1)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+		   _("Expecting 1 control message, got %d"),
+                   nscm);
+      goto out;
+    }
+
+  if (!G_IS_UNIX_CREDENTIALS_MESSAGE (scms[0]))
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+			   _("Unexpected type of ancillary data"));
+      goto out;
+    }
+
+  ret = g_unix_credentials_message_get_credentials (G_UNIX_CREDENTIALS_MESSAGE (scms[0]));
+  g_object_ref (ret);
+
+ out:
+  if (scms != NULL)
+    {
+      for (n = 0; n < nscm; n++)
+        g_object_unref (scms[n]);
+      g_free (scms);
+    }
+  g_object_unref (socket);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  const gchar *name;
+  gint priority;
+  GType gtype;
+} Mechanism;
+
+static void mechanism_free (Mechanism *m);
+
+struct _GDBusAuthPrivate
+{
+  GIOStream *stream;
+
+  /* A list of available Mechanism, sorted according to priority  */
+  GList *available_mechanisms;
+};
+
+enum
+{
+  PROP_0,
+  PROP_STREAM
+};
+
+G_DEFINE_TYPE (GDBusAuth, _g_dbus_auth, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_finalize (GObject *object)
+{
+  GDBusAuth *auth = G_DBUS_AUTH (object);
+
+  if (auth->priv->stream != NULL)
+    g_object_unref (auth->priv->stream);
+  g_list_foreach (auth->priv->available_mechanisms, (GFunc) mechanism_free, NULL);
+  g_list_free (auth->priv->available_mechanisms);
+
+  if (G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GDBusAuth *auth = G_DBUS_AUTH (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      g_value_set_object (value, auth->priv->stream);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_g_dbus_auth_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GDBusAuth *auth = G_DBUS_AUTH (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      auth->priv->stream = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_g_dbus_auth_class_init (GDBusAuthClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->get_property = _g_dbus_auth_get_property;
+  gobject_class->set_property = _g_dbus_auth_set_property;
+  gobject_class->finalize     = _g_dbus_auth_finalize;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_STREAM,
+                                   g_param_spec_object ("stream",
+                                                        _("IO Stream"),
+                                                        _("The underlying GIOStream used for I/O"),
+                                                        G_TYPE_IO_STREAM,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+}
+
+static void
+mechanism_free (Mechanism *m)
+{
+  g_free (m);
+}
+
+static void
+add_mechanism (GDBusAuth *auth,
+               GType      mechanism_type)
+{
+  Mechanism *m;
+
+  m = g_new0 (Mechanism, 1);
+  m->name = _g_dbus_auth_mechanism_get_name (mechanism_type);
+  m->priority = _g_dbus_auth_mechanism_get_priority (mechanism_type);
+  m->gtype = mechanism_type;
+
+  auth->priv->available_mechanisms = g_list_prepend (auth->priv->available_mechanisms, m);
+}
+
+static gint
+mech_compare_func (Mechanism *a, Mechanism *b)
+{
+  gint ret;
+  /* ensure deterministic order */
+  ret = b->priority - a->priority;
+  if (ret == 0)
+    ret = g_strcmp0 (b->name, a->name);
+  return ret;
+}
+
+static void
+_g_dbus_auth_init (GDBusAuth *auth)
+{
+  auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, G_TYPE_DBUS_AUTH, GDBusAuthPrivate);
+
+  /* TODO: trawl extension points */
+  add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_ANON);
+  add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_SHA1);
+  add_mechanism (auth, G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL);
+
+  auth->priv->available_mechanisms = g_list_sort (auth->priv->available_mechanisms,
+                                                  (GCompareFunc) mech_compare_func);
+}
+
+static GType
+find_mech_by_name (GDBusAuth *auth,
+                   const gchar *name)
+{
+  GType ret;
+  GList *l;
+
+  ret = (GType) 0;
+
+  for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
+    {
+      Mechanism *m = l->data;
+      if (g_strcmp0 (name, m->name) == 0)
+        {
+          ret = m->gtype;
+          goto out;
+        }
+    }
+
+ out:
+  return ret;
+}
+
+GDBusAuth  *
+_g_dbus_auth_new (GIOStream *stream)
+{
+  return g_object_new (G_TYPE_DBUS_AUTH,
+                       "stream", stream,
+                       NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* like g_data_input_stream_read_line() but sets error if there's no content to read */
+static gchar *
+_my_g_data_input_stream_read_line (GDataInputStream  *dis,
+                                   gsize             *out_line_length,
+                                   GCancellable      *cancellable,
+                                   GError           **error)
+{
+  gchar *ret;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  ret = g_data_input_stream_read_line (dis,
+                                       out_line_length,
+                                       cancellable,
+                                       error);
+  if (ret == NULL && error != NULL && *error == NULL)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Unexpected lack of content trying to read a line"));
+    }
+
+  return ret;
+}
+
+/* This function is to avoid situations like this
+ *
+ * BEGIN\r\nl\0\0\1...
+ *
+ * e.g. where we read into the first D-Bus message while waiting for
+ * the final line from the client (TODO: file bug against gio for
+ * this)
+ */
+static gchar *
+_my_g_input_stream_read_line_safe (GInputStream  *i,
+                                   gsize         *out_line_length,
+                                   GCancellable  *cancellable,
+                                   GError       **error)
+{
+  GString *str;
+  gchar c;
+  gssize num_read;
+  gboolean last_was_cr;
+
+  str = g_string_new (NULL);
+
+  last_was_cr = FALSE;
+  while (TRUE)
+    {
+      num_read = g_input_stream_read (i,
+                                      &c,
+                                      1,
+                                      cancellable,
+                                      error);
+      if (num_read == -1)
+        goto fail;
+      if (num_read == 0)
+        {
+          if (error != NULL && *error == NULL)
+            {
+              g_set_error_literal (error,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_FAILED,
+                                   _("Unexpected lack of content trying to (safely) read a line"));
+            }
+          goto fail;
+        }
+
+      g_string_append_c (str, (gint) c);
+      if (last_was_cr)
+        {
+          if (c == 0x0a)
+            {
+              g_assert (str->len >= 2);
+              g_string_set_size (str, str->len - 2);
+              goto out;
+            }
+        }
+      last_was_cr = (c == 0x0d);
+    }
+
+ out:
+  if (out_line_length != NULL)
+    *out_line_length = str->len;
+  return g_string_free (str, FALSE);
+
+ fail:
+  g_assert (error == NULL || *error != NULL);
+  g_string_free (str, TRUE);
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+append_nibble (GString *s, gint val)
+{
+  g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
+}
+
+static gchar *
+hexdecode (const gchar  *str,
+           gsize        *out_len,
+           GError      **error)
+{
+  gchar *ret;
+  GString *s;
+  guint n;
+
+  ret = NULL;
+  s = g_string_new (NULL);
+
+  for (n = 0; str[n] != '\0'; n += 2)
+    {
+      gint upper_nibble;
+      gint lower_nibble;
+      guint value;
+
+      upper_nibble = g_ascii_xdigit_value (str[n]);
+      lower_nibble = g_ascii_xdigit_value (str[n + 1]);
+      if (upper_nibble == -1 || lower_nibble == -1)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       "Error hexdecoding string `%s' around position %d",
+                       str, n);
+          goto out;
+        }
+      value = (upper_nibble<<4) | lower_nibble;
+      g_string_append_c (s, value);
+    }
+
+  ret = g_string_free (s, FALSE);
+  s = NULL;
+
+ out:
+  if (s != NULL)
+    g_string_free (s, TRUE);
+  return ret;
+}
+
+/* TODO: take len */
+static gchar *
+hexencode (const gchar *str)
+{
+  guint n;
+  GString *s;
+
+  s = g_string_new (NULL);
+  for (n = 0; str[n] != '\0'; n++)
+    {
+      gint val;
+      gint upper_nibble;
+      gint lower_nibble;
+
+      val = ((const guchar *) str)[n];
+      upper_nibble = val >> 4;
+      lower_nibble = val & 0x0f;
+
+      append_nibble (s, upper_nibble);
+      append_nibble (s, lower_nibble);
+    }
+
+  return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanism *
+client_choose_mech_and_send_initial_response (GDBusAuth           *auth,
+                                              GCredentials        *credentials_that_were_sent,
+                                              const gchar* const  *supported_auth_mechs,
+                                              GPtrArray           *attempted_auth_mechs,
+                                              GDataOutputStream   *dos,
+                                              GCancellable        *cancellable,
+                                              GError             **error)
+{
+  GDBusAuthMechanism *mech;
+  GType auth_mech_to_use_gtype;
+  guint n;
+  guint m;
+  gchar *initial_response;
+  gsize initial_response_len;
+  gchar *encoded;
+  gchar *s;
+
+ again:
+  mech = NULL;
+
+  debug_print ("CLIENT: Trying to choose mechanism");
+
+  /* find an authentication mechanism to try, if any */
+  auth_mech_to_use_gtype = (GType) 0;
+  for (n = 0; supported_auth_mechs[n] != NULL; n++)
+    {
+      gboolean attempted_already;
+      attempted_already = FALSE;
+      for (m = 0; m < attempted_auth_mechs->len; m++)
+        {
+          if (g_strcmp0 (supported_auth_mechs[n], attempted_auth_mechs->pdata[m]) == 0)
+            {
+              attempted_already = TRUE;
+              break;
+            }
+        }
+      if (!attempted_already)
+        {
+          auth_mech_to_use_gtype = find_mech_by_name (auth, supported_auth_mechs[n]);
+          if (auth_mech_to_use_gtype != (GType) 0)
+            break;
+        }
+    }
+
+  if (auth_mech_to_use_gtype == (GType) 0)
+    {
+      guint n;
+      gchar *available;
+      GString *tried_str;
+
+      debug_print ("CLIENT: Exhausted all available mechanisms");
+
+      available = g_strjoinv (", ", (gchar **) supported_auth_mechs);
+
+      tried_str = g_string_new (NULL);
+      for (n = 0; n < attempted_auth_mechs->len; n++)
+        {
+          if (n > 0)
+            g_string_append (tried_str, ", ");
+          g_string_append (tried_str, attempted_auth_mechs->pdata[n]);
+        }
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("Exhausted all available authentication mechanisms (tried: %s) (available: %s)"),
+                   tried_str->str,
+                   available);
+      g_string_free (tried_str, TRUE);
+      g_free (available);
+      goto out;
+    }
+
+  /* OK, decided on a mechanism - let's do this thing */
+  mech = g_object_new (auth_mech_to_use_gtype,
+                       "stream", auth->priv->stream,
+                       "credentials", credentials_that_were_sent,
+                       NULL);
+  debug_print ("CLIENT: Trying mechanism `%s'", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+  g_ptr_array_add (attempted_auth_mechs, (gpointer) _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+
+  /* the auth mechanism may not be supported
+   * (for example, EXTERNAL only works if credentials were exchanged)
+   */
+  if (!_g_dbus_auth_mechanism_is_supported (mech))
+    {
+      debug_print ("CLIENT: Mechanism `%s' says it is not supported", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+      g_object_unref (mech);
+      mech = NULL;
+      goto again;
+    }
+
+  initial_response_len = -1;
+  initial_response = _g_dbus_auth_mechanism_client_initiate (mech,
+                                                             &initial_response_len);
+#if 0
+  g_printerr ("using auth mechanism with name `%s' of type `%s' with initial response `%s'\n",
+              _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
+              g_type_name (G_TYPE_FROM_INSTANCE (mech)),
+              initial_response);
+#endif
+  if (initial_response != NULL)
+    {
+      //g_printerr ("initial_response = `%s'\n", initial_response);
+      encoded = hexencode (initial_response);
+      s = g_strdup_printf ("AUTH %s %s\r\n",
+                           _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
+                           encoded);
+      g_free (initial_response);
+      g_free (encoded);
+    }
+  else
+    {
+      s = g_strdup_printf ("AUTH %s\r\n", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
+    }
+  debug_print ("CLIENT: writing `%s'", s);
+  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+    {
+      g_object_unref (mech);
+      mech = NULL;
+      g_free (s);
+      goto out;
+    }
+  g_free (s);
+
+ out:
+  return mech;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  CLIENT_STATE_WAITING_FOR_DATA,
+  CLIENT_STATE_WAITING_FOR_OK,
+  CLIENT_STATE_WAITING_FOR_REJECT,
+  CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD
+} ClientState;
+
+gchar *
+_g_dbus_auth_run_client (GDBusAuth     *auth,
+                         GDBusCapabilityFlags offered_capabilities,
+                         GDBusCapabilityFlags *out_negotiated_capabilities,
+                         GCancellable  *cancellable,
+                         GError       **error)
+{
+  gchar *s;
+  GDataInputStream *dis;
+  GDataOutputStream *dos;
+  GCredentials *credentials;
+  gchar *ret_guid;
+  gchar *line;
+  gsize line_length;
+  gchar **supported_auth_mechs;
+  GPtrArray *attempted_auth_mechs;
+  GDBusAuthMechanism *mech;
+  ClientState state;
+  GDBusCapabilityFlags negotiated_capabilities;
+
+  debug_print ("CLIENT: initiating");
+
+  ret_guid = NULL;
+  supported_auth_mechs = NULL;
+  attempted_auth_mechs = g_ptr_array_new ();
+  mech = NULL;
+  negotiated_capabilities = 0;
+  credentials = NULL;
+
+  dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
+  dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
+
+  g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+#ifdef G_OS_UNIX
+  if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ())
+    {
+      credentials = g_credentials_new_for_process ();
+      if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (auth->priv->stream),
+                                               credentials,
+                                               cancellable,
+                                               error))
+        goto out;
+    }
+  else
+    {
+      if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
+        goto out;
+    }
+#else
+  if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
+    goto out;
+#endif
+
+  if (credentials != NULL)
+    {
+      if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+        {
+          s = g_credentials_to_string (credentials);
+          debug_print ("CLIENT: sent credentials `%s'", s);
+          g_free (s);
+        }
+    }
+  else
+    {
+      debug_print ("CLIENT: didn't send any credentials");
+    }
+
+  /* TODO: to reduce rountrips, try to pick an auth mechanism to start with */
+
+  /* Get list of supported authentication mechanisms */
+  s = "AUTH\r\n";
+  debug_print ("CLIENT: writing `%s'", s);
+  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+    goto out;
+  state = CLIENT_STATE_WAITING_FOR_REJECT;
+
+  while (TRUE)
+    {
+      switch (state)
+        {
+        case CLIENT_STATE_WAITING_FOR_REJECT:
+          debug_print ("CLIENT: WaitingForReject");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          if (line == NULL)
+            goto out;
+          debug_print ("CLIENT: WaitingForReject, read '%s'", line);
+        foobar:
+          if (!g_str_has_prefix (line, "REJECTED "))
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "In WaitingForReject: Expected `REJECTED am1 am2 ... amN', got `%s'",
+                           line);
+              g_free (line);
+              goto out;
+            }
+          if (supported_auth_mechs == NULL)
+            {
+              supported_auth_mechs = g_strsplit (line + sizeof ("REJECTED ") - 1, " ", 0);
+#if 0
+              for (n = 0; supported_auth_mechs != NULL && supported_auth_mechs[n] != NULL; n++)
+                g_printerr ("supported_auth_mechs[%d] = `%s'\n", n, supported_auth_mechs[n]);
+#endif
+            }
+          g_free (line);
+          mech = client_choose_mech_and_send_initial_response (auth,
+                                                               credentials,
+                                                               (const gchar* const *) supported_auth_mechs,
+                                                               attempted_auth_mechs,
+                                                               dos,
+                                                               cancellable,
+                                                               error);
+          if (mech == NULL)
+            goto out;
+          if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA)
+            state = CLIENT_STATE_WAITING_FOR_DATA;
+          else
+            state = CLIENT_STATE_WAITING_FOR_OK;
+          break;
+
+        case CLIENT_STATE_WAITING_FOR_OK:
+          debug_print ("CLIENT: WaitingForOK");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          if (line == NULL)
+            goto out;
+          debug_print ("CLIENT: WaitingForOK, read `%s'", line);
+          if (g_str_has_prefix (line, "OK "))
+            {
+              if (!g_dbus_is_guid (line + 3))
+                {
+                  g_set_error (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Invalid OK response `%s'",
+                               line);
+                  g_free (line);
+                  goto out;
+                }
+              ret_guid = g_strdup (line + 3);
+              g_free (line);
+
+              if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+                {
+                  s = "NEGOTIATE_UNIX_FD\r\n";
+                  debug_print ("CLIENT: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    goto out;
+                  state = CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD;
+                }
+              else
+                {
+                  s = "BEGIN\r\n";
+                  debug_print ("CLIENT: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    goto out;
+                  /* and we're done! */
+                  goto out;
+                }
+            }
+          else if (g_str_has_prefix (line, "REJECTED "))
+            {
+              goto foobar;
+            }
+          else
+            {
+              /* TODO: handle other valid responses */
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "In WaitingForOk: unexpected response `%s'",
+                           line);
+              g_free (line);
+              goto out;
+            }
+          break;
+
+        case CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD:
+          debug_print ("CLIENT: WaitingForAgreeUnixFD");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          if (line == NULL)
+            goto out;
+          debug_print ("CLIENT: WaitingForAgreeUnixFD, read=`%s'", line);
+          if (g_strcmp0 (line, "AGREE_UNIX_FD") == 0)
+            {
+              negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+              s = "BEGIN\r\n";
+              debug_print ("CLIENT: writing `%s'", s);
+              if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                goto out;
+              /* and we're done! */
+              goto out;
+            }
+          else if (g_str_has_prefix (line, "ERROR") && (line[5] == 0 || g_ascii_isspace (line[5])))
+            {
+              //g_strstrip (line + 5); g_debug ("bah, no unix_fd: `%s'", line + 5);
+              g_free (line);
+              s = "BEGIN\r\n";
+              debug_print ("CLIENT: writing `%s'", s);
+              if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                goto out;
+              /* and we're done! */
+              goto out;
+            }
+          else
+            {
+              /* TODO: handle other valid responses */
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "In WaitingForAgreeUnixFd: unexpected response `%s'",
+                           line);
+              g_free (line);
+              goto out;
+            }
+          break;
+
+        case CLIENT_STATE_WAITING_FOR_DATA:
+          debug_print ("CLIENT: WaitingForData");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          if (line == NULL)
+            goto out;
+          debug_print ("CLIENT: WaitingForData, read=`%s'", line);
+          if (g_str_has_prefix (line, "DATA "))
+            {
+              gchar *encoded;
+              gchar *decoded_data;
+              gsize decoded_data_len;
+
+              encoded = g_strdup (line + 5);
+              g_free (line);
+              g_strstrip (encoded);
+              decoded_data = hexdecode (encoded, &decoded_data_len, error);
+              g_free (encoded);
+              if (decoded_data == NULL)
+                {
+                  g_prefix_error (error, "DATA response is malformed: ");
+                  /* invalid encoding, disconnect! */
+                  goto out;
+                }
+              _g_dbus_auth_mechanism_client_data_receive (mech, decoded_data, decoded_data_len);
+              g_free (decoded_data);
+
+              if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND)
+                {
+                  gchar *data;
+                  gsize data_len;
+                  gchar *encoded_data;
+                  data = _g_dbus_auth_mechanism_client_data_send (mech, &data_len);
+                  encoded_data = hexencode (data);
+                  s = g_strdup_printf ("DATA %s\r\n", encoded_data);
+                  g_free (encoded_data);
+                  g_free (data);
+                  debug_print ("CLIENT: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    {
+                      g_free (s);
+                      goto out;
+                    }
+                  g_free (s);
+                }
+              state = CLIENT_STATE_WAITING_FOR_OK;
+            }
+          else
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "In WaitingForData: unexpected response `%s'",
+                           line);
+              g_free (line);
+              goto out;
+            }
+          break;
+
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+
+    }; /* main authentication client loop */
+
+ out:
+  if (mech != NULL)
+    g_object_unref (mech);
+  g_ptr_array_unref (attempted_auth_mechs);
+  g_strfreev (supported_auth_mechs);
+  g_object_ref (dis);
+  g_object_ref (dos);
+
+  /* ensure return value is NULL if error is set */
+  if (error != NULL && *error != NULL)
+    {
+      g_free (ret_guid);
+      ret_guid = NULL;
+    }
+
+  if (ret_guid != NULL)
+    {
+      if (out_negotiated_capabilities != NULL)
+        *out_negotiated_capabilities = negotiated_capabilities;
+    }
+
+  if (credentials != NULL)
+    g_object_unref (credentials);
+
+  debug_print ("CLIENT: Done, authenticated=%d", ret_guid != NULL);
+
+  return ret_guid;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_auth_mechanisms (GDBusAuth     *auth,
+                     gboolean       allow_anonymous,
+                     const gchar   *prefix,
+                     const gchar   *suffix,
+                     const gchar   *separator)
+{
+  GList *l;
+  GString *str;
+  gboolean need_sep;
+
+  str = g_string_new (prefix);
+  need_sep = FALSE;
+  for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
+    {
+      Mechanism *m = l->data;
+
+      if (!allow_anonymous && g_strcmp0 (m->name, "ANONYMOUS") == 0)
+        continue;
+
+      if (need_sep)
+        g_string_append (str, separator);
+      g_string_append (str, m->name);
+      need_sep = TRUE;
+    }
+
+  g_string_append (str, suffix);
+  return g_string_free (str, FALSE);
+}
+
+
+typedef enum
+{
+  SERVER_STATE_WAITING_FOR_AUTH,
+  SERVER_STATE_WAITING_FOR_DATA,
+  SERVER_STATE_WAITING_FOR_BEGIN
+} ServerState;
+
+gboolean
+_g_dbus_auth_run_server (GDBusAuth              *auth,
+                         GDBusAuthObserver      *observer,
+                         const gchar            *guid,
+                         gboolean                allow_anonymous,
+                         GDBusCapabilityFlags    offered_capabilities,
+                         GDBusCapabilityFlags   *out_negotiated_capabilities,
+                         GCredentials          **out_received_credentials,
+                         GCancellable           *cancellable,
+                         GError                **error)
+{
+  gboolean ret;
+  ServerState state;
+  GDataInputStream *dis;
+  GDataOutputStream *dos;
+  GError *local_error;
+  guchar byte;
+  gchar *line;
+  gsize line_length;
+  GDBusAuthMechanism *mech;
+  gchar *s;
+  GDBusCapabilityFlags negotiated_capabilities;
+  GCredentials *credentials;
+
+  debug_print ("SERVER: initiating");
+
+  ret = FALSE;
+  dis = NULL;
+  dos = NULL;
+  mech = NULL;
+  negotiated_capabilities = 0;
+  credentials = NULL;
+
+  if (!g_dbus_is_guid (guid))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "The given guid `%s' is not valid",
+                   guid);
+      goto out;
+    }
+
+  dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
+  dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
+
+  g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+  /* first read the NUL-byte (TODO: read credentials if using a unix domain socket) */
+#ifdef G_OS_UNIX
+  if (G_IS_UNIX_CONNECTION (auth->priv->stream) && g_unix_credentials_message_is_supported ())
+    {
+      local_error = NULL;
+      credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream),
+                                                           cancellable,
+                                                           &local_error);
+      if (credentials == NULL)
+        {
+          g_propagate_error (error, local_error);
+          goto out;
+        }
+    }
+  else
+    {
+      local_error = NULL;
+      byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
+      if (local_error != NULL)
+        {
+          g_propagate_error (error, local_error);
+          goto out;
+        }
+    }
+#else
+  local_error = NULL;
+  byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
+  if (local_error != NULL)
+    {
+      g_propagate_error (error, local_error);
+      goto out;
+    }
+#endif
+  if (credentials != NULL)
+    {
+      if (G_UNLIKELY (_g_dbus_debug_authentication ()))
+        {
+          s = g_credentials_to_string (credentials);
+          debug_print ("SERVER: received credentials `%s'", s);
+          g_free (s);
+        }
+    }
+  else
+    {
+      debug_print ("SERVER: didn't receive any credentials");
+    }
+
+  state = SERVER_STATE_WAITING_FOR_AUTH;
+  while (TRUE)
+    {
+      switch (state)
+        {
+        case SERVER_STATE_WAITING_FOR_AUTH:
+          debug_print ("SERVER: WaitingForAuth");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          debug_print ("SERVER: WaitingForAuth, read `%s'", line);
+          if (line == NULL)
+            goto out;
+          if (g_strcmp0 (line, "AUTH") == 0)
+            {
+              s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+              debug_print ("SERVER: writing `%s'", s);
+              if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                {
+                  g_free (s);
+                  goto out;
+                }
+              g_free (s);
+              g_free (line);
+            }
+          else if (g_str_has_prefix (line, "AUTH "))
+            {
+              gchar **tokens;
+              const gchar *encoded;
+              const gchar *mech_name;
+              GType auth_mech_to_use_gtype;
+
+              tokens = g_strsplit (line, " ", 0);
+              g_free (line);
+
+              switch (g_strv_length (tokens))
+                {
+                case 2:
+                  /* no initial response */
+                  mech_name = tokens[1];
+                  encoded = NULL;
+                  break;
+
+                case 3:
+                  /* initial response */
+                  mech_name = tokens[1];
+                  encoded = tokens[2];
+                  break;
+
+                default:
+                  g_set_error (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Unexpected line `%s' while in WaitingForAuth state",
+                               line);
+                  g_strfreev (tokens);
+                  goto out;
+                }
+
+              /* TODO: record that the client has attempted to use this mechanism */
+              //g_debug ("client is trying `%s'", mech_name);
+
+              auth_mech_to_use_gtype = find_mech_by_name (auth, mech_name);
+              if ((auth_mech_to_use_gtype == (GType) 0) ||
+                  (!allow_anonymous && g_strcmp0 (mech_name, "ANONYMOUS") == 0))
+                {
+                  /* We don't support this auth mechanism */
+                  g_strfreev (tokens);
+                  s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+                  debug_print ("SERVER: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    {
+                      g_free (s);
+                      goto out;
+                    }
+                  g_free (s);
+
+                  /* stay in WAITING FOR AUTH */
+                  state = SERVER_STATE_WAITING_FOR_AUTH;
+                }
+              else
+                {
+                  gchar *initial_response;
+                  gsize initial_response_len;
+
+                  mech = g_object_new (auth_mech_to_use_gtype,
+                                       "stream", auth->priv->stream,
+                                       "credentials", credentials,
+                                       NULL);
+
+                  initial_response = NULL;
+                  initial_response_len = 0;
+                  if (encoded != NULL)
+                    {
+                      initial_response = hexdecode (encoded, &initial_response_len, error);
+                      if (initial_response == NULL)
+                        {
+                          g_prefix_error (error, "Initial response is malformed: ");
+                          /* invalid encoding, disconnect! */
+                          g_strfreev (tokens);
+                          goto out;
+                        }
+                    }
+
+                  _g_dbus_auth_mechanism_server_initiate (mech,
+                                                          initial_response,
+                                                          initial_response_len);
+                  g_free (initial_response);
+                  g_strfreev (tokens);
+
+                change_state:
+                  switch (_g_dbus_auth_mechanism_server_get_state (mech))
+                    {
+                    case G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED:
+                      if (observer != NULL &&
+                          g_dbus_auth_observer_deny_authenticated_peer (observer,
+                                                                        auth->priv->stream,
+                                                                        credentials))
+                        {
+                          /* disconnect */
+                          g_set_error_literal (error,
+                                               G_IO_ERROR,
+                                               G_IO_ERROR_FAILED,
+                                               _("Cancelled via GDBusAuthObserver::deny-authenticated-peer"));
+                          goto out;
+                        }
+                      else
+                        {
+                          s = g_strdup_printf ("OK %s\r\n", guid);
+                          debug_print ("SERVER: writing `%s'", s);
+                          if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                            {
+                              g_free (s);
+                              goto out;
+                            }
+                          g_free (s);
+                          state = SERVER_STATE_WAITING_FOR_BEGIN;
+                        }
+                      break;
+
+                    case G_DBUS_AUTH_MECHANISM_STATE_REJECTED:
+                      s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
+                      debug_print ("SERVER: writing `%s'", s);
+                      if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                        {
+                          g_free (s);
+                          goto out;
+                        }
+                      g_free (s);
+                      state = SERVER_STATE_WAITING_FOR_AUTH;
+                      break;
+
+                    case G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA:
+                      state = SERVER_STATE_WAITING_FOR_DATA;
+                      break;
+
+                    case G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND:
+                      {
+                        gchar *data;
+                        gsize data_len;
+                        gchar *encoded_data;
+                        data = _g_dbus_auth_mechanism_server_data_send (mech, &data_len);
+                        encoded_data = hexencode (data);
+                        s = g_strdup_printf ("DATA %s\r\n", encoded_data);
+                        g_free (encoded_data);
+                        g_free (data);
+                        debug_print ("SERVER: writing `%s'", s);
+                        if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                          {
+                            g_free (s);
+                            goto out;
+                          }
+                        g_free (s);
+                      }
+                      goto change_state;
+                      break;
+
+                    default:
+                      /* TODO */
+                      g_assert_not_reached ();
+                      break;
+                    }
+                }
+            }
+          else
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "Unexpected line `%s' while in WaitingForAuth state",
+                           line);
+              g_free (line);
+              goto out;
+            }
+          break;
+
+        case SERVER_STATE_WAITING_FOR_DATA:
+          debug_print ("SERVER: WaitingForData");
+          line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
+          debug_print ("SERVER: WaitingForData, read `%s'", line);
+          if (line == NULL)
+            goto out;
+          if (g_str_has_prefix (line, "DATA "))
+            {
+              gchar *encoded;
+              gchar *decoded_data;
+              gsize decoded_data_len;
+
+              encoded = g_strdup (line + 5);
+              g_free (line);
+              g_strstrip (encoded);
+              decoded_data = hexdecode (encoded, &decoded_data_len, error);
+              g_free (encoded);
+              if (decoded_data == NULL)
+                {
+                  g_prefix_error (error, "DATA response is malformed: ");
+                  /* invalid encoding, disconnect! */
+                  goto out;
+                }
+              _g_dbus_auth_mechanism_server_data_receive (mech, decoded_data, decoded_data_len);
+              g_free (decoded_data);
+              /* oh man, this goto-crap is so ugly.. really need to rewrite the state machine */
+              goto change_state;
+            }
+          else
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "Unexpected line `%s' while in WaitingForData state",
+                           line);
+              g_free (line);
+            }
+          goto out;
+
+        case SERVER_STATE_WAITING_FOR_BEGIN:
+          debug_print ("SERVER: WaitingForBegin");
+          /* Use extremely slow (but reliable) line reader - this basically
+           * does a recvfrom() system call per character
+           *
+           * (the problem with using GDataInputStream's read_line is that because of
+           * buffering it might start reading into the first D-Bus message that
+           * appears after "BEGIN\r\n"....)
+           */
+          line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
+                                                    &line_length,
+                                                    cancellable,
+                                                    error);
+          debug_print ("SERVER: WaitingForBegin, read `%s'", line);
+          if (line == NULL)
+            goto out;
+          if (g_strcmp0 (line, "BEGIN") == 0)
+            {
+              /* YAY, done! */
+              ret = TRUE;
+              g_free (line);
+              goto out;
+            }
+          else if (g_strcmp0 (line, "NEGOTIATE_UNIX_FD") == 0)
+            {
+              if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+                {
+                  negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+                  s = "AGREE_UNIX_FD\r\n";
+                  debug_print ("SERVER: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    goto out;
+                }
+              else
+                {
+                  s = "ERROR \"fd passing not offered\"\r\n";
+                  debug_print ("SERVER: writing `%s'", s);
+                  if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                    goto out;
+                }
+            }
+          else
+            {
+              g_debug ("Unexpected line `%s' while in WaitingForBegin state", line);
+              g_free (line);
+              s = "ERROR \"Unknown Command\"\r\n";
+              debug_print ("SERVER: writing `%s'", s);
+              if (!g_data_output_stream_put_string (dos, s, cancellable, error))
+                goto out;
+            }
+          break;
+
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+
+
+  g_set_error_literal (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       "Not implemented (server)");
+
+ out:
+  if (mech != NULL)
+    g_object_unref (mech);
+  if (dis != NULL)
+    g_object_ref (dis);
+  if (dis != NULL)
+    g_object_ref (dos);
+
+  /* ensure return value is FALSE if error is set */
+  if (error != NULL && *error != NULL)
+    {
+      ret = FALSE;
+    }
+
+  if (ret)
+    {
+      if (out_negotiated_capabilities != NULL)
+        *out_negotiated_capabilities = negotiated_capabilities;
+      if (out_received_credentials != NULL)
+        *out_received_credentials = credentials != NULL ? g_object_ref (credentials) : NULL;
+    }
+
+  if (credentials != NULL)
+    g_object_unref (credentials);
+
+  debug_print ("SERVER: Done, authenticated=%d", ret);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauth.h b/gio/gdbusauth.h
new file mode 100644
index 0000000..2fc7500
--- /dev/null
+++ b/gio/gdbusauth.h
@@ -0,0 +1,86 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauth.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_H__
+#define __G_DBUS_AUTH_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH         (_g_dbus_auth_get_type ())
+#define G_DBUS_AUTH(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH, GDBusAuth))
+#define G_DBUS_AUTH_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH, GDBusAuthClass))
+#define G_DBUS_AUTH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH, GDBusAuthClass))
+#define G_IS_DBUS_AUTH(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH))
+#define G_IS_DBUS_AUTH_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH))
+
+typedef struct _GDBusAuth        GDBusAuth;
+typedef struct _GDBusAuthClass   GDBusAuthClass;
+typedef struct _GDBusAuthPrivate GDBusAuthPrivate;
+
+struct _GDBusAuthClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+};
+
+struct _GDBusAuth
+{
+  GObject parent_instance;
+  GDBusAuthPrivate *priv;
+};
+
+GType       _g_dbus_auth_get_type (void) G_GNUC_CONST;
+GDBusAuth  *_g_dbus_auth_new      (GIOStream *stream);
+
+/* TODO: need a way to set allowed authentication mechanisms */
+
+/* TODO: need a way to convey credentials etc. */
+
+/* TODO: need a way to convey negotiated features (e.g. returning flags from e.g. GDBusConnectionFeatures) */
+
+/* TODO: need to expose encode()/decode() from the AuthMechanism (and whether it is needed at all) */
+
+gboolean    _g_dbus_auth_run_server (GDBusAuth             *auth,
+                                     GDBusAuthObserver     *observer,
+                                     const gchar           *guid,
+                                     gboolean               allow_anonymous,
+                                     GDBusCapabilityFlags   offered_capabilities,
+                                     GDBusCapabilityFlags  *out_negotiated_capabilities,
+                                     GCredentials         **out_received_credentials,
+                                     GCancellable          *cancellable,
+                                     GError               **error);
+
+gchar      *_g_dbus_auth_run_client (GDBusAuth     *auth,
+                                     GDBusCapabilityFlags offered_capabilities,
+                                     GDBusCapabilityFlags *out_negotiated_capabilities,
+                                     GCancellable  *cancellable,
+                                     GError       **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_H__ */
diff --git a/gio/gdbusauthmechanism.c b/gio/gdbusauthmechanism.c
new file mode 100644
index 0000000..72eff74
--- /dev/null
+++ b/gio/gdbusauthmechanism.c
@@ -0,0 +1,342 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanism.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "giostream.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _GDBusAuthMechanismPrivate
+{
+  GIOStream *stream;
+  GCredentials *credentials;
+};
+
+enum
+{
+  PROP_0,
+  PROP_STREAM,
+  PROP_CREDENTIALS
+};
+
+G_DEFINE_ABSTRACT_TYPE (GDBusAuthMechanism, _g_dbus_auth_mechanism, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_finalize (GObject *object)
+{
+  GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+  if (mechanism->priv->stream != NULL)
+    g_object_unref (mechanism->priv->stream);
+  if (mechanism->priv->credentials != NULL)
+    g_object_unref (mechanism->priv->credentials);
+
+  if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (_g_dbus_auth_mechanism_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      g_value_set_object (value, mechanism->priv->stream);
+      break;
+
+    case PROP_CREDENTIALS:
+      g_value_set_object (value, mechanism->priv->credentials);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_g_dbus_auth_mechanism_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GDBusAuthMechanism *mechanism = G_DBUS_AUTH_MECHANISM (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      mechanism->priv->stream = g_value_dup_object (value);
+      break;
+
+    case PROP_CREDENTIALS:
+      mechanism->priv->credentials = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+_g_dbus_auth_mechanism_class_init (GDBusAuthMechanismClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthMechanismPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->get_property = _g_dbus_auth_mechanism_get_property;
+  gobject_class->set_property = _g_dbus_auth_mechanism_set_property;
+  gobject_class->finalize     = _g_dbus_auth_mechanism_finalize;
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_STREAM,
+                                   g_param_spec_object ("stream",
+                                                        _("IO Stream"),
+                                                        _("The underlying GIOStream used for I/O"),
+                                                        G_TYPE_IO_STREAM,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusAuthMechanism:credentials:
+   *
+   * If authenticating as a server, this property contains the
+   * received credentials, if any.
+   *
+   * If authenticating as a client, the property contains the
+   * credentials that were sent, if any.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CREDENTIALS,
+                                   g_param_spec_object ("credentials",
+                                                        _("Credentials"),
+                                                        _("The credentials of the remote peer"),
+                                                        G_TYPE_CREDENTIALS,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+}
+
+static void
+_g_dbus_auth_mechanism_init (GDBusAuthMechanism *mechanism)
+{
+  /* not used for now */
+  mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+                                                 G_TYPE_DBUS_AUTH_MECHANISM,
+                                                 GDBusAuthMechanismPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GIOStream *
+_g_dbus_auth_mechanism_get_stream (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return mechanism->priv->stream;
+}
+
+GCredentials *
+_g_dbus_auth_mechanism_get_credentials (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return mechanism->priv->credentials;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+const gchar *
+_g_dbus_auth_mechanism_get_name (GType mechanism_type)
+{
+  const gchar *name;
+  GDBusAuthMechanismClass *klass;
+
+  g_return_val_if_fail (g_type_is_a (mechanism_type,  G_TYPE_DBUS_AUTH_MECHANISM), NULL);
+
+  klass = g_type_class_ref (mechanism_type);
+  g_assert (klass != NULL);
+  name = klass->get_name ();
+  //g_type_class_unref (klass);
+
+  return name;
+}
+
+gint
+_g_dbus_auth_mechanism_get_priority (GType mechanism_type)
+{
+  gint priority;
+  GDBusAuthMechanismClass *klass;
+
+  g_return_val_if_fail (g_type_is_a (mechanism_type,  G_TYPE_DBUS_AUTH_MECHANISM), 0);
+
+  klass = g_type_class_ref (mechanism_type);
+  g_assert (klass != NULL);
+  priority = klass->get_priority ();
+  //g_type_class_unref (klass);
+
+  return priority;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean
+_g_dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), FALSE);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->is_supported (mechanism);
+}
+
+gchar *
+_g_dbus_auth_mechanism_encode_data (GDBusAuthMechanism   *mechanism,
+                                    const gchar          *data,
+                                    gsize                 data_len,
+                                    gsize                *out_data_len)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->encode_data (mechanism, data, data_len, out_data_len);
+}
+
+
+gchar *
+_g_dbus_auth_mechanism_decode_data (GDBusAuthMechanism   *mechanism,
+                                    const gchar          *data,
+                                    gsize                 data_len,
+                                    gsize                *out_data_len)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->decode_data (mechanism, data, data_len, out_data_len);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusAuthMechanismState
+_g_dbus_auth_mechanism_server_get_state (GDBusAuthMechanism   *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_state (mechanism);
+}
+
+void
+_g_dbus_auth_mechanism_server_initiate (GDBusAuthMechanism   *mechanism,
+                                        const gchar          *initial_response,
+                                        gsize                 initial_response_len)
+{
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+  G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_initiate (mechanism, initial_response, initial_response_len);
+}
+
+void
+_g_dbus_auth_mechanism_server_data_receive (GDBusAuthMechanism   *mechanism,
+                                            const gchar          *data,
+                                            gsize                 data_len)
+{
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+  G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_receive (mechanism, data, data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_server_data_send (GDBusAuthMechanism   *mechanism,
+                                         gsize                *out_data_len)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_data_send (mechanism, out_data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_get_reject_reason (mechanism);
+}
+
+void
+_g_dbus_auth_mechanism_server_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+  G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->server_shutdown (mechanism);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusAuthMechanismState
+_g_dbus_auth_mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_state (mechanism);
+}
+
+gchar *
+_g_dbus_auth_mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                                        gsize                *out_initial_response_len)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_initiate (mechanism,
+                                                                       out_initial_response_len);
+}
+
+void
+_g_dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
+                                            const gchar          *data,
+                                            gsize                 data_len)
+{
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+  G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_receive (mechanism, data, data_len);
+}
+
+gchar *
+_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
+                                         gsize                *out_data_len)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL);
+  return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_send (mechanism, out_data_len);
+}
+
+void
+_g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism));
+  G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_shutdown (mechanism);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h
new file mode 100644
index 0000000..e00cb17
--- /dev/null
+++ b/gio/gdbusauthmechanism.h
@@ -0,0 +1,174 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanism.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_H__
+#define __G_DBUS_AUTH_MECHANISM_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM         (_g_dbus_auth_mechanism_get_type ())
+#define G_DBUS_AUTH_MECHANISM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanism))
+#define G_DBUS_AUTH_MECHANISM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass))
+#define G_DBUS_AUTH_MECHANISM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM, GDBusAuthMechanismClass))
+#define G_IS_DBUS_AUTH_MECHANISM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM))
+#define G_IS_DBUS_AUTH_MECHANISM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM))
+
+typedef struct _GDBusAuthMechanism        GDBusAuthMechanism;
+typedef struct _GDBusAuthMechanismClass   GDBusAuthMechanismClass;
+typedef struct _GDBusAuthMechanismPrivate GDBusAuthMechanismPrivate;
+
+typedef enum {
+  G_DBUS_AUTH_MECHANISM_STATE_INVALID,
+  G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA,
+  G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND,
+  G_DBUS_AUTH_MECHANISM_STATE_REJECTED,
+  G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED,
+} GDBusAuthMechanismState;
+
+struct _GDBusAuthMechanismClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+
+  /* VTable */
+
+  /* TODO: server_initiate and client_initiate probably needs to have a
+   * GCredentials parameter...
+   */
+
+  gint                      (*get_priority)             (void);
+  const gchar              *(*get_name)                 (void);
+
+  /* functions shared by server/client */
+  gboolean                  (*is_supported)             (GDBusAuthMechanism   *mechanism);
+  gchar                    *(*encode_data)              (GDBusAuthMechanism   *mechanism,
+                                                         const gchar          *data,
+                                                         gsize                 data_len,
+                                                         gsize                *out_data_len);
+  gchar                    *(*decode_data)              (GDBusAuthMechanism   *mechanism,
+                                                         const gchar          *data,
+                                                         gsize                 data_len,
+                                                         gsize                *out_data_len);
+
+  /* functions for server-side authentication */
+  GDBusAuthMechanismState   (*server_get_state)         (GDBusAuthMechanism   *mechanism);
+  void                      (*server_initiate)          (GDBusAuthMechanism   *mechanism,
+                                                         const gchar          *initial_response,
+                                                         gsize                 initial_response_len);
+  void                      (*server_data_receive)      (GDBusAuthMechanism   *mechanism,
+                                                         const gchar          *data,
+                                                         gsize                 data_len);
+  gchar                    *(*server_data_send)         (GDBusAuthMechanism   *mechanism,
+                                                         gsize                *out_data_len);
+  gchar                    *(*server_get_reject_reason) (GDBusAuthMechanism   *mechanism);
+  void                      (*server_shutdown)          (GDBusAuthMechanism   *mechanism);
+
+  /* functions for client-side authentication */
+  GDBusAuthMechanismState   (*client_get_state)         (GDBusAuthMechanism   *mechanism);
+  gchar                    *(*client_initiate)          (GDBusAuthMechanism   *mechanism,
+                                                         gsize                *out_initial_response_len);
+  void                      (*client_data_receive)      (GDBusAuthMechanism   *mechanism,
+                                                         const gchar          *data,
+                                                         gsize                 data_len);
+  gchar                    *(*client_data_send)         (GDBusAuthMechanism   *mechanism,
+                                                         gsize                *out_data_len);
+  void                      (*client_shutdown)          (GDBusAuthMechanism   *mechanism);
+
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+  void (*_g_reserved9) (void);
+  void (*_g_reserved10) (void);
+  void (*_g_reserved11) (void);
+  void (*_g_reserved12) (void);
+  void (*_g_reserved13) (void);
+  void (*_g_reserved14) (void);
+  void (*_g_reserved15) (void);
+  void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanism
+{
+  GObject parent_instance;
+  GDBusAuthMechanismPrivate *priv;
+};
+
+GType                     _g_dbus_auth_mechanism_get_type                 (void) G_GNUC_CONST;
+
+gint                      _g_dbus_auth_mechanism_get_priority             (GType                 mechanism_type);
+const gchar              *_g_dbus_auth_mechanism_get_name                 (GType                 mechanism_type);
+
+GIOStream                *_g_dbus_auth_mechanism_get_stream               (GDBusAuthMechanism   *mechanism);
+GCredentials             *_g_dbus_auth_mechanism_get_credentials          (GDBusAuthMechanism   *mechanism);
+
+gboolean                  _g_dbus_auth_mechanism_is_supported             (GDBusAuthMechanism   *mechanism);
+gchar                    *_g_dbus_auth_mechanism_encode_data              (GDBusAuthMechanism   *mechanism,
+                                                                           const gchar          *data,
+                                                                           gsize                 data_len,
+                                                                           gsize                *out_data_len);
+gchar                    *_g_dbus_auth_mechanism_decode_data              (GDBusAuthMechanism   *mechanism,
+                                                                           const gchar          *data,
+                                                                           gsize                 data_len,
+                                                                           gsize                *out_data_len);
+
+GDBusAuthMechanismState   _g_dbus_auth_mechanism_server_get_state         (GDBusAuthMechanism   *mechanism);
+void                      _g_dbus_auth_mechanism_server_initiate          (GDBusAuthMechanism   *mechanism,
+                                                                           const gchar          *initial_response,
+                                                                           gsize                 initial_response_len);
+void                      _g_dbus_auth_mechanism_server_data_receive      (GDBusAuthMechanism   *mechanism,
+                                                                           const gchar          *data,
+                                                                           gsize                 data_len);
+gchar                    *_g_dbus_auth_mechanism_server_data_send         (GDBusAuthMechanism   *mechanism,
+                                                                           gsize                *out_data_len);
+gchar                    *_g_dbus_auth_mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism);
+void                      _g_dbus_auth_mechanism_server_shutdown          (GDBusAuthMechanism   *mechanism);
+
+GDBusAuthMechanismState   _g_dbus_auth_mechanism_client_get_state         (GDBusAuthMechanism   *mechanism);
+gchar                    *_g_dbus_auth_mechanism_client_initiate          (GDBusAuthMechanism   *mechanism,
+                                                                           gsize                *out_initial_response_len);
+void                      _g_dbus_auth_mechanism_client_data_receive      (GDBusAuthMechanism   *mechanism,
+                                                                           const gchar          *data,
+                                                                           gsize                 data_len);
+gchar                    *_g_dbus_auth_mechanism_client_data_send         (GDBusAuthMechanism   *mechanism,
+                                                                          gsize                *out_data_len);
+void                      _g_dbus_auth_mechanism_client_shutdown          (GDBusAuthMechanism   *mechanism);
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_H__ */
diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c
new file mode 100644
index 0000000..4c666ec
--- /dev/null
+++ b/gio/gdbusauthmechanismanon.c
@@ -0,0 +1,327 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismanon.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+
+struct _GDBusAuthMechanismAnonPrivate
+{
+  gboolean is_client;
+  gboolean is_server;
+  GDBusAuthMechanismState state;
+};
+
+static gint                     mechanism_get_priority              (void);
+static const gchar             *mechanism_get_name                  (void);
+
+static gboolean                 mechanism_is_supported              (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_encode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_decode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static GDBusAuthMechanismState  mechanism_server_get_state          (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *initial_response,
+                                                                     gsize                 initial_response_len);
+static void                     mechanism_server_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_server_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
+static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_initial_response_len);
+static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_client_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static void                     mechanism_client_shutdown           (GDBusAuthMechanism   *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismAnon, _g_dbus_auth_mechanism_anon, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_anon_finalize (GObject *object)
+{
+  //GDBusAuthMechanismAnon *mechanism = G_DBUS_AUTH_MECHANISM_ANON (object);
+
+  if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (_g_dbus_auth_mechanism_anon_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_anon_class_init (GDBusAuthMechanismAnonClass *klass)
+{
+  GObjectClass *gobject_class;
+  GDBusAuthMechanismClass *mechanism_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthMechanismAnonPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = _g_dbus_auth_mechanism_anon_finalize;
+
+  mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+  mechanism_class->get_priority              = mechanism_get_priority;
+  mechanism_class->get_name                  = mechanism_get_name;
+  mechanism_class->is_supported              = mechanism_is_supported;
+  mechanism_class->encode_data               = mechanism_encode_data;
+  mechanism_class->decode_data               = mechanism_decode_data;
+  mechanism_class->server_get_state          = mechanism_server_get_state;
+  mechanism_class->server_initiate           = mechanism_server_initiate;
+  mechanism_class->server_data_receive       = mechanism_server_data_receive;
+  mechanism_class->server_data_send          = mechanism_server_data_send;
+  mechanism_class->server_get_reject_reason  = mechanism_server_get_reject_reason;
+  mechanism_class->server_shutdown           = mechanism_server_shutdown;
+  mechanism_class->client_get_state          = mechanism_client_get_state;
+  mechanism_class->client_initiate           = mechanism_client_initiate;
+  mechanism_class->client_data_receive       = mechanism_client_data_receive;
+  mechanism_class->client_data_send          = mechanism_client_data_send;
+  mechanism_class->client_shutdown           = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_anon_init (GDBusAuthMechanismAnon *mechanism)
+{
+  mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+                                                 G_TYPE_DBUS_AUTH_MECHANISM_ANON,
+                                                 GDBusAuthMechanismAnonPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+static gint
+mechanism_get_priority (void)
+{
+  /* We prefer ANONYMOUS to most other mechanism (such as DBUS_COOKIE_SHA1) but not to EXTERNAL */
+  return 50;
+}
+
+
+static const gchar *
+mechanism_get_name (void)
+{
+  return "ANONYMOUS";
+}
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), FALSE);
+  return TRUE;
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism   *mechanism,
+                           const gchar          *initial_response,
+                           gsize                 initial_response_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+  g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+  if (initial_response != NULL)
+    g_debug ("ANONYMOUS: initial_response was `%s'", initial_response);
+
+  m->priv->is_server = TRUE;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  /* can never end up here because we are never in the WAITING_FOR_DATA state */
+  g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+  /* can never end up here because we are never in the REJECTED state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+  m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                           gsize                *out_initial_response_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+  g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+  m->priv->is_client = TRUE;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+  *out_initial_response_len = -1;
+
+  /* just return our library name and version */
+  return g_strdup ("GDBus 0.1");
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  /* can never end up here because we are never in the WAITING_FOR_DATA state */
+  g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+  m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismanon.h b/gio/gdbusauthmechanismanon.h
new file mode 100644
index 0000000..d3c2c24
--- /dev/null
+++ b/gio/gdbusauthmechanismanon.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismanon.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_ANON_H__
+#define __G_DBUS_AUTH_MECHANISM_ANON_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_ANON         (_g_dbus_auth_mechanism_anon_get_type ())
+#define G_DBUS_AUTH_MECHANISM_ANON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnon))
+#define G_DBUS_AUTH_MECHANISM_ANON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass))
+#define G_DBUS_AUTH_MECHANISM_ANON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON, GDBusAuthMechanismAnonClass))
+#define G_IS_DBUS_AUTH_MECHANISM_ANON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_ANON))
+#define G_IS_DBUS_AUTH_MECHANISM_ANON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_ANON))
+
+typedef struct _GDBusAuthMechanismAnon        GDBusAuthMechanismAnon;
+typedef struct _GDBusAuthMechanismAnonClass   GDBusAuthMechanismAnonClass;
+typedef struct _GDBusAuthMechanismAnonPrivate GDBusAuthMechanismAnonPrivate;
+
+struct _GDBusAuthMechanismAnonClass
+{
+  /*< private >*/
+  GDBusAuthMechanismClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+  void (*_g_reserved9) (void);
+  void (*_g_reserved10) (void);
+  void (*_g_reserved11) (void);
+  void (*_g_reserved12) (void);
+  void (*_g_reserved13) (void);
+  void (*_g_reserved14) (void);
+  void (*_g_reserved15) (void);
+  void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismAnon
+{
+  GDBusAuthMechanism parent_instance;
+  GDBusAuthMechanismAnonPrivate *priv;
+};
+
+GType _g_dbus_auth_mechanism_anon_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_ANON_H__ */
diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c
new file mode 100644
index 0000000..bf8d931
--- /dev/null
+++ b/gio/gdbusauthmechanismexternal.c
@@ -0,0 +1,416 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismexternal.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+struct _GDBusAuthMechanismExternalPrivate
+{
+  gboolean is_client;
+  gboolean is_server;
+  GDBusAuthMechanismState state;
+};
+
+static gint                     mechanism_get_priority              (void);
+static const gchar             *mechanism_get_name                  (void);
+
+static gboolean                 mechanism_is_supported              (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_encode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_decode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static GDBusAuthMechanismState  mechanism_server_get_state          (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *initial_response,
+                                                                     gsize                 initial_response_len);
+static void                     mechanism_server_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_server_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
+static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_initial_response_len);
+static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_client_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static void                     mechanism_client_shutdown           (GDBusAuthMechanism   *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismExternal, _g_dbus_auth_mechanism_external, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_external_finalize (GObject *object)
+{
+  //GDBusAuthMechanismExternal *mechanism = G_DBUS_AUTH_MECHANISM_EXTERNAL (object);
+
+  if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (_g_dbus_auth_mechanism_external_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *klass)
+{
+  GObjectClass *gobject_class;
+  GDBusAuthMechanismClass *mechanism_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthMechanismExternalPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = _g_dbus_auth_mechanism_external_finalize;
+
+  mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+  mechanism_class->get_name                  = mechanism_get_name;
+  mechanism_class->get_priority              = mechanism_get_priority;
+  mechanism_class->is_supported              = mechanism_is_supported;
+  mechanism_class->encode_data               = mechanism_encode_data;
+  mechanism_class->decode_data               = mechanism_decode_data;
+  mechanism_class->server_get_state          = mechanism_server_get_state;
+  mechanism_class->server_initiate           = mechanism_server_initiate;
+  mechanism_class->server_data_receive       = mechanism_server_data_receive;
+  mechanism_class->server_data_send          = mechanism_server_data_send;
+  mechanism_class->server_get_reject_reason  = mechanism_server_get_reject_reason;
+  mechanism_class->server_shutdown           = mechanism_server_shutdown;
+  mechanism_class->client_get_state          = mechanism_client_get_state;
+  mechanism_class->client_initiate           = mechanism_client_initiate;
+  mechanism_class->client_data_receive       = mechanism_client_data_receive;
+  mechanism_class->client_data_send          = mechanism_client_data_send;
+  mechanism_class->client_shutdown           = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_external_init (GDBusAuthMechanismExternal *mechanism)
+{
+  mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+                                                 G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL,
+                                                 GDBusAuthMechanismExternalPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), FALSE);
+  /* This mechanism is only available if credentials has been exchanged */
+  if (_g_dbus_auth_mechanism_get_credentials (mechanism) != NULL)
+    return TRUE;
+  else
+    return FALSE;
+}
+
+static gint
+mechanism_get_priority (void)
+{
+  /* We prefer EXTERNAL to most other mechanism (DBUS_COOKIE_SHA1 and ANONYMOUS) */
+  return 100;
+}
+
+static const gchar *
+mechanism_get_name (void)
+{
+  return "EXTERNAL";
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static gboolean
+data_matches_credentials (const gchar *data,
+                          GCredentials *credentials)
+{
+  gboolean match;
+
+  match = FALSE;
+
+  if (credentials == NULL)
+    goto out;
+
+  if (data == NULL || strlen (data) == 0)
+    goto out;
+
+#if defined(G_OS_UNIX)
+  {
+    gint64 alleged_uid;
+    gchar *endp;
+
+    /* on UNIX, this is the uid as a string in base 10 */
+    alleged_uid = g_ascii_strtoll (data, &endp, 10);
+    if (*endp == '\0')
+      {
+        if (g_credentials_has_unix_user (credentials) &&
+            g_credentials_get_unix_user (credentials) == alleged_uid)
+          {
+            match = TRUE;
+          }
+      }
+  }
+#elif defined(G_OS_WIN32)
+  {
+    const gchar *alleged_sid;
+
+    /* on Win32, this is the User SID */
+    alleged_sid = data;
+    if (g_credentials_has_windows_user (credentials) &&
+        g_strcmp0 (g_credentials_get_windows_user (credentials), alleged_sid) == 0)
+      {
+        match = TRUE;
+      }
+  }
+#else
+#error Dont know how to read credentials on this OS. Please implement.
+#endif
+
+ out:
+  return match;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism   *mechanism,
+                           const gchar          *initial_response,
+                           gsize                 initial_response_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+  g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+  m->priv->is_server = TRUE;
+
+  if (initial_response != NULL)
+    {
+      if (data_matches_credentials (initial_response, _g_dbus_auth_mechanism_get_credentials (mechanism)))
+        {
+          m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+        }
+      else
+        {
+          m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+        }
+    }
+  else
+    {
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+    }
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  if (data_matches_credentials (data, _g_dbus_auth_mechanism_get_credentials (mechanism)))
+    {
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+    }
+  else
+    {
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+    }
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+  /* can never end up here because we are never in the REJECTED state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+  m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                           gsize                *out_initial_response_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+  gchar *initial_response;
+  GCredentials *credentials;
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+  g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+  m->priv->is_client = TRUE;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+  *out_initial_response_len = -1;
+
+  credentials = _g_dbus_auth_mechanism_get_credentials (mechanism);
+  g_assert (credentials != NULL);
+
+  /* return the uid */
+#if defined(G_OS_UNIX)
+  initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, g_credentials_get_unix_user (credentials));
+#elif defined(G_OS_WIN32)
+  initial_response = g_strdup_printf ("%s", g_credentials_get_windows_user ());
+#else
+#warning Dont know how to send credentials on this OS. Please implement.
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+#endif
+  return initial_response;
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  /* can never end up here because we are never in the WAITING_FOR_DATA state */
+  g_assert_not_reached ();
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+  m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismexternal.h b/gio/gdbusauthmechanismexternal.h
new file mode 100644
index 0000000..39e7fa2
--- /dev/null
+++ b/gio/gdbusauthmechanismexternal.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismexternal.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__
+#define __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL         (_g_dbus_auth_mechanism_external_get_type ())
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternal))
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass))
+#define G_DBUS_AUTH_MECHANISM_EXTERNAL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL, GDBusAuthMechanismExternalClass))
+#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL))
+#define G_IS_DBUS_AUTH_MECHANISM_EXTERNAL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL))
+
+typedef struct _GDBusAuthMechanismExternal        GDBusAuthMechanismExternal;
+typedef struct _GDBusAuthMechanismExternalClass   GDBusAuthMechanismExternalClass;
+typedef struct _GDBusAuthMechanismExternalPrivate GDBusAuthMechanismExternalPrivate;
+
+struct _GDBusAuthMechanismExternalClass
+{
+  /*< private >*/
+  GDBusAuthMechanismClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+  void (*_g_reserved9) (void);
+  void (*_g_reserved10) (void);
+  void (*_g_reserved11) (void);
+  void (*_g_reserved12) (void);
+  void (*_g_reserved13) (void);
+  void (*_g_reserved14) (void);
+  void (*_g_reserved15) (void);
+  void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismExternal
+{
+  GDBusAuthMechanism parent_instance;
+  GDBusAuthMechanismExternalPrivate *priv;
+};
+
+GType _g_dbus_auth_mechanism_external_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_EXTERNAL_H__ */
diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c
new file mode 100644
index 0000000..ee94e49
--- /dev/null
+++ b/gio/gdbusauthmechanismsha1.c
@@ -0,0 +1,1216 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthmechanismsha1.h"
+#include "gcredentials.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#endif
+
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+struct _GDBusAuthMechanismSha1Private
+{
+  gboolean is_client;
+  gboolean is_server;
+  GDBusAuthMechanismState state;
+
+  /* used on the client side */
+  gchar *to_send;
+
+  /* used on the server side */
+  gchar *cookie;
+  gchar *server_challenge;
+};
+
+static gint                     mechanism_get_priority              (void);
+static const gchar             *mechanism_get_name                  (void);
+
+static gboolean                 mechanism_is_supported              (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_encode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_decode_data               (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len,
+                                                                     gsize                *out_data_len);
+static GDBusAuthMechanismState  mechanism_server_get_state          (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *initial_response,
+                                                                     gsize                 initial_response_len);
+static void                     mechanism_server_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_server_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMechanism   *mechanism);
+static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
+static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
+static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_initial_response_len);
+static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
+                                                                     const gchar          *data,
+                                                                     gsize                 data_len);
+static gchar                   *mechanism_client_data_send          (GDBusAuthMechanism   *mechanism,
+                                                                     gsize                *out_data_len);
+static void                     mechanism_client_shutdown           (GDBusAuthMechanism   *mechanism);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_DEFINE_TYPE (GDBusAuthMechanismSha1, _g_dbus_auth_mechanism_sha1, G_TYPE_DBUS_AUTH_MECHANISM);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_auth_mechanism_sha1_finalize (GObject *object)
+{
+  GDBusAuthMechanismSha1 *mechanism = G_DBUS_AUTH_MECHANISM_SHA1 (object);
+
+  g_free (mechanism->priv->to_send);
+
+  g_free (mechanism->priv->cookie);
+  g_free (mechanism->priv->server_challenge);
+
+  if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize (object);
+}
+
+static void
+_g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass)
+{
+  GObjectClass *gobject_class;
+  GDBusAuthMechanismClass *mechanism_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthMechanismSha1Private));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = _g_dbus_auth_mechanism_sha1_finalize;
+
+  mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass);
+  mechanism_class->get_priority              = mechanism_get_priority;
+  mechanism_class->get_name                  = mechanism_get_name;
+  mechanism_class->is_supported              = mechanism_is_supported;
+  mechanism_class->encode_data               = mechanism_encode_data;
+  mechanism_class->decode_data               = mechanism_decode_data;
+  mechanism_class->server_get_state          = mechanism_server_get_state;
+  mechanism_class->server_initiate           = mechanism_server_initiate;
+  mechanism_class->server_data_receive       = mechanism_server_data_receive;
+  mechanism_class->server_data_send          = mechanism_server_data_send;
+  mechanism_class->server_get_reject_reason  = mechanism_server_get_reject_reason;
+  mechanism_class->server_shutdown           = mechanism_server_shutdown;
+  mechanism_class->client_get_state          = mechanism_client_get_state;
+  mechanism_class->client_initiate           = mechanism_client_initiate;
+  mechanism_class->client_data_receive       = mechanism_client_data_receive;
+  mechanism_class->client_data_send          = mechanism_client_data_send;
+  mechanism_class->client_shutdown           = mechanism_client_shutdown;
+}
+
+static void
+_g_dbus_auth_mechanism_sha1_init (GDBusAuthMechanismSha1 *mechanism)
+{
+  mechanism->priv = G_TYPE_INSTANCE_GET_PRIVATE (mechanism,
+                                                 G_TYPE_DBUS_AUTH_MECHANISM_SHA1,
+                                                 GDBusAuthMechanismSha1Private);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+mechanism_get_priority (void)
+{
+  return 0;
+}
+
+static const gchar *
+mechanism_get_name (void)
+{
+  return "DBUS_COOKIE_SHA1";
+}
+
+static gboolean
+mechanism_is_supported (GDBusAuthMechanism *mechanism)
+{
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), FALSE);
+  return TRUE;
+}
+
+static gchar *
+mechanism_encode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+
+static gchar *
+mechanism_decode_data (GDBusAuthMechanism   *mechanism,
+                       const gchar          *data,
+                       gsize                 data_len,
+                       gsize                *out_data_len)
+{
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gint
+random_ascii (void)
+{
+  gint ret;
+  ret = g_random_int_range (0, 60);
+  if (ret < 25)
+    ret += 'A';
+  else if (ret < 50)
+    ret += 'a' - 25;
+  else
+    ret += '0' - 50;
+  return ret;
+}
+
+static gchar *
+random_ascii_string (guint len)
+{
+  GString *challenge;
+  guint n;
+
+  challenge = g_string_new (NULL);
+  for (n = 0; n < len; n++)
+    g_string_append_c (challenge, random_ascii ());
+  return g_string_free (challenge, FALSE);
+}
+
+static gchar *
+random_blob (guint len)
+{
+  GString *challenge;
+  guint n;
+
+  challenge = g_string_new (NULL);
+  for (n = 0; n < len; n++)
+    g_string_append_c (challenge, g_random_int_range (0, 256));
+  return g_string_free (challenge, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* ensure keyring dir exists and permissions are correct */
+static gchar *
+ensure_keyring_directory (GError **error)
+{
+  gchar *path;
+  const gchar *e;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  e = g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR");
+  if (e != NULL)
+    {
+      path = g_strdup (e);
+    }
+  else
+    {
+      path = g_build_filename (g_get_home_dir (),
+                               ".dbus-keyrings",
+                               NULL);
+    }
+
+  if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+    {
+      if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL)
+        {
+#ifdef G_OS_UNIX
+          struct stat statbuf;
+          if (stat (path, &statbuf) != 0)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           _("Error statting directory `%s': %s"),
+                           path,
+                           strerror (errno));
+              g_free (path);
+              path = NULL;
+              goto out;
+            }
+          if ((statbuf.st_mode  & 0777) != 0700)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Permissions on directory `%s' are malformed. Expected mode 0700, got 0%o"),
+                           path,
+                           statbuf.st_mode & 0777);
+              g_free (path);
+              path = NULL;
+              goto out;
+            }
+#else
+#error Please implement permission checking on non-UNIX platforms
+#endif
+        }
+        goto out;
+    }
+
+  if (g_mkdir (path, 0700) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   _("Error creating directory `%s': %s"),
+                   path,
+                   strerror (errno));
+      g_free (path);
+      path = NULL;
+      goto out;
+    }
+
+out:
+  return path;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+append_nibble (GString *s, gint val)
+{
+  g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
+}
+
+static gchar *
+hexencode (const gchar *str,
+           gssize       len)
+{
+  guint n;
+  GString *s;
+
+  if (len == -1)
+    len = strlen (str);
+
+  s = g_string_new (NULL);
+  for (n = 0; n < len; n++)
+    {
+      gint val;
+      gint upper_nibble;
+      gint lower_nibble;
+
+      val = ((const guchar *) str)[n];
+      upper_nibble = val >> 4;
+      lower_nibble = val & 0x0f;
+
+      append_nibble (s, upper_nibble);
+      append_nibble (s, lower_nibble);
+    }
+
+  return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* looks up an entry in the keyring */
+static gchar *
+keyring_lookup_entry (const gchar  *cookie_context,
+                      gint          cookie_id,
+                      GError      **error)
+{
+  gchar *ret;
+  gchar *keyring_dir;
+  gchar *contents;
+  gchar *path;
+  guint n;
+  gchar **lines;
+
+  g_return_val_if_fail (cookie_context != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  ret = NULL;
+  path = NULL;
+  contents = NULL;
+  lines = NULL;
+
+  keyring_dir = ensure_keyring_directory (error);
+  if (keyring_dir == NULL)
+    goto out;
+
+  path = g_build_filename (keyring_dir, cookie_context, NULL);
+
+  if (!g_file_get_contents (path,
+                            &contents,
+                            NULL,
+                            error))
+    {
+      g_prefix_error (error,
+                      _("Error opening keyring `%s' for reading: "),
+                      path);
+      goto out;
+    }
+  g_assert (contents != NULL);
+
+  lines = g_strsplit (contents, "\n", 0);
+  for (n = 0; lines[n] != NULL; n++)
+    {
+      const gchar *line = lines[n];
+      gchar **tokens;
+      gchar *endp;
+      gint line_id;
+      guint64 line_when;
+
+      if (line[0] == '\0')
+        continue;
+
+      tokens = g_strsplit (line, " ", 0);
+      if (g_strv_length (tokens) != 3)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       _("Line %d of the keyring at `%s' with content `%s' is malformed"),
+                       n + 1,
+                       path,
+                       line);
+          g_strfreev (tokens);
+          goto out;
+        }
+
+      line_id = g_ascii_strtoll (tokens[0], &endp, 10);
+      if (*endp != '\0')
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       _("First token of line %d of the keyring at `%s' with content `%s' is malformed"),
+                       n + 1,
+                       path,
+                       line);
+          g_strfreev (tokens);
+          goto out;
+        }
+
+      line_when = g_ascii_strtoll (tokens[1], &endp, 10);
+      if (*endp != '\0')
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_FAILED,
+                       _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"),
+                       n + 1,
+                       path,
+                       line);
+          g_strfreev (tokens);
+          goto out;
+        }
+
+      if (line_id == cookie_id)
+        {
+          /* YAY, success */
+          ret = tokens[2]; /* steal pointer */
+          tokens[2] = NULL;
+          g_strfreev (tokens);
+          goto out;
+        }
+
+      g_strfreev (tokens);
+    }
+
+  /* BOOH, didn't find the cookie */
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_FAILED,
+               _("Didn't find cookie with id %d in the keyring at `%s'"),
+               cookie_id,
+               path);
+
+ out:
+  g_free (keyring_dir);
+  g_free (path);
+  g_free (contents);
+  g_strfreev (lines);
+  return ret;
+}
+
+/* function for logging important events that the system administrator should take notice of */
+static void
+_log (const gchar *message,
+      ...)
+{
+  gchar *s;
+  va_list var_args;
+
+  va_start (var_args, message);
+  s = g_strdup_vprintf (message, var_args);
+  va_end (var_args);
+
+  /* TODO: might want to send this to syslog instead */
+  g_printerr ("GDBus-DBUS_COOKIE_SHA1: %s\n", s);
+  g_free (s);
+}
+
+static gint
+keyring_acquire_lock (const gchar  *path,
+                      GError      **error)
+{
+  gchar *lock;
+  gint ret;
+  guint num_tries;
+  guint num_create_tries;
+
+  g_return_val_if_fail (path != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  ret = -1;
+  lock = g_strdup_printf ("%s.lock", path);
+
+  /* This is what the D-Bus spec says
+   *
+   *  Create a lockfile name by appending ".lock" to the name of the
+   *  cookie file. The server should attempt to create this file using
+   *  O_CREAT | O_EXCL. If file creation fails, the lock
+   *  fails. Servers should retry for a reasonable period of time,
+   *  then they may choose to delete an existing lock to keep users
+   *  from having to manually delete a stale lock. [1]
+   *
+   *  [1] : Lockfiles are used instead of real file locking fcntl() because
+   *         real locking implementations are still flaky on network filesystems
+   */
+
+  num_create_tries = 0;
+#ifdef EEXISTS
+ again:
+#endif
+  num_tries = 0;
+  while (g_file_test (lock, G_FILE_TEST_EXISTS))
+    {
+      /* sleep 10ms, then try again */
+      g_usleep (1000*10);
+      num_tries++;
+      if (num_tries == 50)
+        {
+          /* ok, we slept 50*10ms = 0.5 seconds.. Conclude that the lock-file must be
+           * stale (nuke the it from orbit)
+           */
+          if (g_unlink (lock) != 0)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           _("Error deleting stale lock-file `%s': %s"),
+                           lock,
+                           strerror (errno));
+              goto out;
+            }
+          _log ("Deleted stale lock-file `%s'", lock);
+          break;
+        }
+    }
+
+  ret = g_open (lock, O_CREAT |
+#ifdef O_EXCL
+                O_EXCL,
+#else
+                0,
+#endif
+                0700);
+  if (ret == -1)
+    {
+#ifdef EEXISTS
+      /* EEXIST: pathname already exists and O_CREAT and O_EXCL were used. */
+      if (errno == EEXISTS)
+        {
+          num_create_tries++;
+          if (num_create_tries < 5)
+            goto again;
+        }
+#endif
+      g_set_error (error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   _("Error creating lock-file `%s': %s"),
+                   lock,
+                   strerror (errno));
+      goto out;
+    }
+
+ out:
+  g_free (lock);
+  return ret;
+}
+
+static gboolean
+keyring_release_lock (const gchar  *path,
+                      gint          lock_fd,
+                      GError      **error)
+{
+  gchar *lock;
+  gboolean ret;
+
+  g_return_val_if_fail (path != NULL, FALSE);
+  g_return_val_if_fail (lock_fd != -1, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  ret = FALSE;
+  lock = g_strdup_printf ("%s.lock", path);
+  if (g_unlink (lock) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   _("Error unlinking lock-file `%s': %s"),
+                   lock,
+                   strerror (errno));
+      goto out;
+    }
+  if (close (lock_fd) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   g_io_error_from_errno (errno),
+                   _("Error closing (unlinked) lock-file `%s': %s"),
+                   lock,
+                   strerror (errno));
+      goto out;
+    }
+
+  ret = TRUE;
+
+ out:
+  g_free (lock);
+  return ret;
+}
+
+
+/* adds an entry to the keyring, taking care of locking and deleting stale/future entries */
+static gboolean
+keyring_generate_entry (const gchar  *cookie_context,
+                        gint         *out_id,
+                        gchar       **out_cookie,
+                        GError      **error)
+{
+  gboolean ret;
+  gchar *keyring_dir;
+  gchar *path;
+  gchar *contents;
+  GError *local_error;
+  gchar **lines;
+  gint max_line_id;
+  GString *new_contents;
+  guint64 now;
+  gboolean have_id;
+  gint use_id;
+  gchar *use_cookie;
+  gboolean changed_file;
+  gint lock_fd;
+
+  g_return_val_if_fail (cookie_context != NULL, FALSE);
+  g_return_val_if_fail (out_id != NULL, FALSE);
+  g_return_val_if_fail (out_cookie != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  ret = FALSE;
+  path = NULL;
+  contents = NULL;
+  lines = NULL;
+  new_contents = NULL;
+  have_id = FALSE;
+  use_cookie = NULL;
+  lock_fd = -1;
+
+  keyring_dir = ensure_keyring_directory (error);
+  if (keyring_dir == NULL)
+    goto out;
+
+  path = g_build_filename (keyring_dir, cookie_context, NULL);
+
+  lock_fd = keyring_acquire_lock (path, error);
+  if (lock_fd == -1)
+    goto out;
+
+  local_error = NULL;
+  contents = NULL;
+  if (!g_file_get_contents (path,
+                            &contents,
+                            NULL,
+                            &local_error))
+    {
+      if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT)
+        {
+          /* file doesn't have to exist */
+          g_error_free (local_error);
+        }
+      else
+        {
+          g_propagate_prefixed_error (error,
+                                      local_error,
+                                      _("Error opening keyring `%s' for writing: "),
+                                      path);
+          goto out;
+        }
+    }
+
+  new_contents = g_string_new (NULL);
+  now = time (NULL);
+  changed_file = FALSE;
+
+  max_line_id = 0;
+  if (contents != NULL)
+    {
+      guint n;
+      lines = g_strsplit (contents, "\n", 0);
+      for (n = 0; lines[n] != NULL; n++)
+        {
+          const gchar *line = lines[n];
+          gchar **tokens;
+          gchar *endp;
+          gint line_id;
+          guint64 line_when;
+          gboolean keep_entry;
+
+          if (line[0] == '\0')
+            continue;
+
+          tokens = g_strsplit (line, " ", 0);
+          if (g_strv_length (tokens) != 3)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Line %d of the keyring at `%s' with content `%s' is malformed"),
+                           n + 1,
+                           path,
+                           line);
+              g_strfreev (tokens);
+              goto out;
+            }
+
+          line_id = g_ascii_strtoll (tokens[0], &endp, 10);
+          if (*endp != '\0')
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("First token of line %d of the keyring at `%s' with content `%s' is malformed"),
+                           n + 1,
+                           path,
+                           line);
+              g_strfreev (tokens);
+              goto out;
+            }
+
+          line_when = g_ascii_strtoll (tokens[1], &endp, 10);
+          if (*endp != '\0')
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           _("Second token of line %d of the keyring at `%s' with content `%s' is malformed"),
+                           n + 1,
+                           path,
+                           line);
+              g_strfreev (tokens);
+              goto out;
+            }
+
+          /* D-Bus spec says:
+           *
+           *  Once the lockfile has been created, the server loads the
+           *  cookie file. It should then delete any cookies that are
+           *  old (the timeout can be fairly short), or more than a
+           *  reasonable time in the future (so that cookies never
+           *  accidentally become permanent, if the clock was set far
+           *  into the future at some point). If no recent keys remain,
+           *  the server may generate a new key.
+           *
+           */
+          keep_entry = TRUE;
+          if (line_when > now)
+            {
+              /* Oddball case: entry is more recent than our current wall-clock time..
+               * This is OK, it means that another server on another machine but with
+               * same $HOME wrote the entry.
+               *
+               * So discard the entry if it's more than 1 day in the future ("reasonable
+               * time in the future").
+               */
+              if (line_when - now > 24*60*60)
+                {
+                  keep_entry = FALSE;
+                  _log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now);
+                }
+            }
+          else
+            {
+              /* Discard entry if it's older than 15 minutes ("can be fairly short") */
+              if (now - line_when > 15*60)
+                {
+                  keep_entry = FALSE;
+                }
+            }
+
+          if (!keep_entry)
+            {
+              changed_file = FALSE;
+            }
+          else
+            {
+              g_string_append_printf (new_contents,
+                                      "%d %" G_GUINT64_FORMAT " %s\n",
+                                      line_id,
+                                      line_when,
+                                      tokens[2]);
+              max_line_id = MAX (line_id, max_line_id);
+              /* Only reuse entry if not older than 10 minutes.
+               *
+               * (We need a bit of grace time compared to 15 minutes above.. otherwise
+               * there's a race where we reuse the 14min59.9 secs old entry and a
+               * split-second later another server purges the now 15 minute old entry.)
+               */
+              if (now - line_when < 10 * 60)
+                {
+                  if (!have_id)
+                    {
+                      use_id = line_id;
+                      use_cookie = tokens[2]; /* steal memory */
+                      tokens[2] = NULL;
+                      have_id = TRUE;
+                    }
+                }
+            }
+          g_strfreev (tokens);
+        }
+    } /* for each line */
+
+  ret = TRUE;
+
+  if (have_id)
+    {
+      *out_id = use_id;
+      *out_cookie = use_cookie;
+      use_cookie = NULL;
+    }
+  else
+    {
+      gchar *raw_cookie;
+      *out_id = max_line_id + 1;
+      raw_cookie = random_blob (32);
+      *out_cookie = hexencode (raw_cookie, 32);
+      g_free (raw_cookie);
+
+      g_string_append_printf (new_contents,
+                              "%d %" G_GUINT64_FORMAT " %s\n",
+                              *out_id,
+                              (guint64) time (NULL),
+                              *out_cookie);
+      changed_file = TRUE;
+    }
+
+  /* and now actually write the cookie file if there are changes (this is atomic) */
+  if (changed_file)
+    {
+      if (!g_file_set_contents (path,
+                                new_contents->str,
+                                -1,
+                                error))
+        {
+          *out_id = 0;
+          *out_cookie = 0;
+          g_free (*out_cookie);
+          ret = FALSE;
+          goto out;
+        }
+    }
+
+ out:
+
+  if (lock_fd != -1)
+    {
+      GError *local_error;
+      local_error = NULL;
+      if (!keyring_release_lock (path, lock_fd, &local_error))
+        {
+          if (error != NULL)
+            {
+              if (*error == NULL)
+                {
+                  *error = local_error;
+                }
+              else
+                {
+                  g_prefix_error (error,
+                                  _("(Additionally, releasing the lock for `%s' also failed: %s) "),
+                                  path,
+                                  local_error->message);
+                }
+            }
+          else
+            {
+              g_error_free (local_error);
+            }
+        }
+    }
+
+  g_free (keyring_dir);
+  g_free (path);
+  g_strfreev (lines);
+  g_free (contents);
+  if (new_contents != NULL)
+    g_string_free (new_contents, TRUE);
+  g_free (use_cookie);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+generate_sha1 (const gchar *server_challenge,
+               const gchar *client_challenge,
+               const gchar *cookie)
+{
+  GString *str;
+  gchar *sha1;
+
+  str = g_string_new (server_challenge);
+  g_string_append_c (str, ':');
+  g_string_append (str, client_challenge);
+  g_string_append_c (str, ':');
+  g_string_append (str, cookie);
+  sha1 = g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, -1);
+  g_string_free (str, TRUE);
+
+  return sha1;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_server_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static void
+mechanism_server_initiate (GDBusAuthMechanism   *mechanism,
+                           const gchar          *initial_response,
+                           gsize                 initial_response_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+  g_return_if_fail (!m->priv->is_server && !m->priv->is_client);
+
+  m->priv->is_server = TRUE;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+
+  if (initial_response != NULL && strlen (initial_response) > 0)
+    {
+#ifdef G_OS_UNIX
+      gint64 uid;
+      gchar *endp;
+
+      uid = g_ascii_strtoll (initial_response, &endp, 10);
+      if (*endp == '\0')
+        {
+          if (uid == getuid ())
+            {
+              m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+            }
+        }
+#elif defined(G_OS_WIN32)
+      GCredentials *credentials;
+      credentials = g_credentials_new_for_process ();
+      if (g_strcmp0 (g_credentials_get_windows_user (credentials), initial_response) == 0)
+        m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+      g_object_unref (credentials);
+#else
+#error Please implement for your OS
+#endif
+    }
+}
+
+static void
+mechanism_server_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+  gchar **tokens;
+  const gchar *client_challenge;
+  const gchar *alleged_sha1;
+  gchar *sha1;
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  tokens = NULL;
+  sha1 = NULL;
+
+  tokens = g_strsplit (data, " ", 0);
+  if (g_strv_length (tokens) != 2)
+    {
+      g_warning ("Malformed data `%s'", data);
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+      goto out;
+    }
+
+  client_challenge = tokens[0];
+  alleged_sha1 = tokens[1];
+
+  sha1 = generate_sha1 (m->priv->server_challenge, client_challenge, m->priv->cookie);
+
+  if (g_strcmp0 (sha1, alleged_sha1) == 0)
+    {
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+    }
+  else
+    {
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+    }
+
+ out:
+  g_strfreev (tokens);
+  g_free (sha1);
+}
+
+static gchar *
+mechanism_server_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+  gchar *s;
+  gint cookie_id;
+  const gchar *cookie_context;
+  GError *error;
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  s = NULL;
+
+  /* TODO: use GDBusAuthObserver here to get the cookie context to use? */
+  cookie_context = "org_gtk_gdbus_general";
+
+  error = NULL;
+  if (!keyring_generate_entry (cookie_context,
+                               &cookie_id,
+                               &m->priv->cookie,
+                               &error))
+    {
+      g_warning ("Error adding entry to keyring: %s", error->message);
+      g_error_free (error);
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+      goto out;
+    }
+
+  m->priv->server_challenge = random_ascii_string (16);
+  s = g_strdup_printf ("%s %d %s",
+                       cookie_context,
+                       cookie_id,
+                       m->priv->server_challenge);
+
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+
+ out:
+  return s;
+}
+
+static gchar *
+mechanism_server_get_reject_reason (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL);
+
+  /* can never end up here because we are never in the REJECTED state */
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+mechanism_server_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+  g_return_if_fail (m->priv->is_server && !m->priv->is_client);
+
+  m->priv->is_server = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAuthMechanismState
+mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID);
+
+  return m->priv->state;
+}
+
+static gchar *
+mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                           gsize                *out_initial_response_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+  gchar *initial_response;
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+  g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
+
+  m->priv->is_client = TRUE;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
+
+  *out_initial_response_len = -1;
+
+#ifdef G_OS_UNIX
+  initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ());
+#elif defined (G_OS_WIN32)
+  {
+    GCredentials *credentials;
+    credentials = g_credentials_new_for_process ();
+    initial_response = g_strdup (g_credentials_get_windows_user (credentials));
+    g_object_unref (credentials);
+  }
+#else
+#endif
+  g_assert (initial_response != NULL);
+
+  return initial_response;
+}
+
+static void
+mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
+                               const gchar          *data,
+                               gsize                 data_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+  gchar **tokens;
+  const gchar *cookie_context;
+  guint cookie_id;
+  const gchar *server_challenge;
+  gchar *client_challenge;
+  gchar *endp;
+  gchar *cookie;
+  GError *error;
+  gchar *sha1;
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+  g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
+
+  tokens = NULL;
+  cookie = NULL;
+  client_challenge = NULL;
+
+  tokens = g_strsplit (data, " ", 0);
+  if (g_strv_length (tokens) != 3)
+    {
+      g_warning ("Malformed data `%s'", data);
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+      goto out;
+    }
+
+  cookie_context = tokens[0];
+  cookie_id = g_ascii_strtoll (tokens[1], &endp, 10);
+  if (*endp != '\0')
+    {
+      g_warning ("Malformed cookie_id `%s'", tokens[1]);
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+      goto out;
+    }
+  server_challenge = tokens[2];
+
+  error = NULL;
+  cookie = keyring_lookup_entry (cookie_context, cookie_id, &error);
+  if (cookie == NULL)
+    {
+      g_warning ("Problems looking up entry in keyring: %s", error->message);
+      g_error_free (error);
+      m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+      goto out;
+    }
+
+  client_challenge = random_ascii_string (16);
+  sha1 = generate_sha1 (server_challenge, client_challenge, cookie);
+  m->priv->to_send = g_strdup_printf ("%s %s", client_challenge, sha1);
+  g_free (sha1);
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
+
+ out:
+  g_strfreev (tokens);
+  g_free (cookie);
+  g_free (client_challenge);
+}
+
+static gchar *
+mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
+                            gsize                *out_data_len)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL);
+  g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
+  g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
+
+  g_assert (m->priv->to_send != NULL);
+
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
+
+  return g_strdup (m->priv->to_send);
+}
+
+static void
+mechanism_client_shutdown (GDBusAuthMechanism   *mechanism)
+{
+  GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism);
+
+  g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism));
+  g_return_if_fail (m->priv->is_client && !m->priv->is_server);
+
+  m->priv->is_client = FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusauthmechanismsha1.h b/gio/gdbusauthmechanismsha1.h
new file mode 100644
index 0000000..67839fd
--- /dev/null
+++ b/gio/gdbusauthmechanismsha1.h
@@ -0,0 +1,82 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusauthmechanismsha1.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_AUTH_MECHANISM_SHA1_H__
+#define __G_DBUS_AUTH_MECHANISM_SHA1_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusauthmechanism.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_MECHANISM_SHA1         (_g_dbus_auth_mechanism_sha1_get_type ())
+#define G_DBUS_AUTH_MECHANISM_SHA1(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1))
+#define G_DBUS_AUTH_MECHANISM_SHA1_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class))
+#define G_DBUS_AUTH_MECHANISM_SHA1_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1, GDBusAuthMechanismSha1Class))
+#define G_IS_DBUS_AUTH_MECHANISM_SHA1(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_MECHANISM_SHA1))
+#define G_IS_DBUS_AUTH_MECHANISM_SHA1_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_MECHANISM_SHA1))
+
+typedef struct _GDBusAuthMechanismSha1        GDBusAuthMechanismSha1;
+typedef struct _GDBusAuthMechanismSha1Class   GDBusAuthMechanismSha1Class;
+typedef struct _GDBusAuthMechanismSha1Private GDBusAuthMechanismSha1Private;
+
+struct _GDBusAuthMechanismSha1Class
+{
+  /*< private >*/
+  GDBusAuthMechanismClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+  void (*_g_reserved9) (void);
+  void (*_g_reserved10) (void);
+  void (*_g_reserved11) (void);
+  void (*_g_reserved12) (void);
+  void (*_g_reserved13) (void);
+  void (*_g_reserved14) (void);
+  void (*_g_reserved15) (void);
+  void (*_g_reserved16) (void);
+};
+
+struct _GDBusAuthMechanismSha1
+{
+  GDBusAuthMechanism parent_instance;
+  GDBusAuthMechanismSha1Private *priv;
+};
+
+GType _g_dbus_auth_mechanism_sha1_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_AUTH_MECHANISM_SHA1_H__ */
diff --git a/gio/gdbusauthobserver.c b/gio/gdbusauthobserver.c
new file mode 100644
index 0000000..f0411ab
--- /dev/null
+++ b/gio/gdbusauthobserver.c
@@ -0,0 +1,218 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+#include "gcredentials.h"
+#include "gioenumtypes.h"
+#include "giostream.h"
+
+/**
+ * SECTION:gdbusauthobserver
+ * @short_description: Object used for authenticating connections
+ * @include: gdbus/gdbus.h
+ *
+ * The #GDBusAuthObserver type provides a mechanism for participating
+ * in how a #GDBusServer (or a #GDBusConnection) authenticates remote
+ * peers. Simply instantiate a #GDBusAuthObserver and connect to the
+ * signals you are interested in. Note that new signals may be added
+ * in the future
+ *
+ * For example, if you only want to allow D-Bus connections from
+ * processes owned by the same uid as the server, you would do this:
+ * <example id="auth-observer"><title>Controlling Authentication</title><programlisting>
+ * static gboolean
+ * on_deny_authenticated_peer (GDBusAuthObserver *observer,
+ *                             GIOStream         *stream,
+ *                             GCredentials      *credentials,
+ *                             gpointer           user_data)
+ * {
+ *   gboolean deny;
+ *   deny = TRUE;
+ *   if (credentials != NULL &&
+ *       g_credentials_has_unix_user (credentials) &&
+ *       g_credentials_get_unix_user (credentials) == getuid ())
+ *     deny = FALSE;
+ *   return deny;
+ * }
+ *
+ * static gboolean
+ * on_new_connection (GDBusServer     *server,
+ *                    GDBusConnection *connection,
+ *                    gpointer         user_data)
+ * {
+ *   /<!-- -->* Guaranteed here that @connection is from a process owned by the same user *<!-- -->/
+ * }
+ *
+ * [...]
+ *
+ * GDBusAuthObserver *observer;
+ * GDBusServer *server;
+ * GError *error;
+ *
+ * error = NULL;
+ * observer = g_dbus_auth_observer_new ();
+ * server = g_dbus_server_new_sync ("unix:tmpdir=/tmp/my-app-name",
+ *                                  G_DBUS_SERVER_FLAGS_NONE,
+ *                                  observer,
+ *                                  NULL, /<!-- -->* GCancellable *<!-- -->/
+ *                                  &error);
+ * g_signal_connect (observer,
+ *                   "deny-authenticated-peer",
+ *                   G_CALLBACK (on_deny_authenticated_peer),
+ *                   NULL);
+ * g_signal_connect (server,
+ *                   "new-connection",
+ *                   G_CALLBACK (on_new_connection),
+ *                   NULL);
+ * g_object_unref (observer);
+ * g_dbus_server_start (server);
+ * </programlisting></example>
+ */
+
+struct _GDBusAuthObserverPrivate
+{
+  gint foo;
+};
+
+enum
+{
+  DENY_AUTHENTICATED_PEER_SIGNAL,
+  LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GDBusAuthObserver, g_dbus_auth_observer, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_auth_observer_finalize (GObject *object)
+{
+  //GDBusAuthObserver *observer = G_DBUS_AUTH_OBSERVER (object);
+
+  if (G_OBJECT_CLASS (g_dbus_auth_observer_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_auth_observer_parent_class)->finalize (object);
+}
+
+static gboolean
+g_dbus_auth_observer_deny_authenticated_peer_real (GDBusAuthObserver  *observer,
+                                                   GIOStream          *stream,
+                                                   GCredentials       *credentials)
+{
+  return FALSE;
+}
+
+static void
+g_dbus_auth_observer_class_init (GDBusAuthObserverClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_auth_observer_finalize;
+
+  klass->deny_authenticated_peer = g_dbus_auth_observer_deny_authenticated_peer_real;
+
+  /**
+   * GDBusAuthObserver::deny-authenticated-peer:
+   * @observer: The #GDBusAuthObserver emitting the signal.
+   * @stream: A #GIOStream for the #GDBusConnection.
+   * @credentials: Credentials received from the peer or %NULL.
+   *
+   * Emitted to check if a peer that is successfully authenticated
+   * should be denied.
+   *
+   * Returns: %TRUE if the peer should be denied, %FALSE otherwise.
+   */
+  signals[DENY_AUTHENTICATED_PEER_SIGNAL] =
+    g_signal_new ("deny-authenticated-peer",
+                  G_TYPE_DBUS_AUTH_OBSERVER,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GDBusAuthObserverClass, deny_authenticated_peer),
+                  g_signal_accumulator_true_handled,
+                  NULL, /* accu_data */
+                  _gio_marshal_BOOLEAN__OBJECT_OBJECT,
+                  G_TYPE_BOOLEAN,
+                  2,
+                  G_TYPE_IO_STREAM,
+                  G_TYPE_CREDENTIALS);
+
+
+  g_type_class_add_private (klass, sizeof (GDBusAuthObserverPrivate));
+}
+
+static void
+g_dbus_auth_observer_init (GDBusAuthObserver *observer)
+{
+  /* not used for now */
+  observer->priv = G_TYPE_INSTANCE_GET_PRIVATE (observer,
+                                                G_TYPE_DBUS_AUTH_OBSERVER,
+                                                GDBusAuthObserverPrivate);;
+}
+
+/**
+ * g_dbus_auth_observer_new:
+ *
+ * Creates a new #GDBusAuthObserver object.
+ *
+ * Returns: A #GDBusAuthObserver. Free with g_object_unref().
+ */
+GDBusAuthObserver *
+g_dbus_auth_observer_new (void)
+{
+  return g_object_new (G_TYPE_DBUS_AUTH_OBSERVER, NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_auth_observer_deny_authenticated_peer:
+ * @observer: A #GDBusAuthObserver.
+ * @stream: A #GIOStream for the #GDBusConnection.
+ * @credentials: Credentials received from the peer or %NULL.
+ *
+ * Emits the #GDBusAuthObserver::deny-authenticated-peer signal on @observer.
+ *
+ * Returns: %TRUE if the peer should be denied, %FALSE otherwise.
+ */
+gboolean
+g_dbus_auth_observer_deny_authenticated_peer (GDBusAuthObserver  *observer,
+                                              GIOStream          *stream,
+                                              GCredentials       *credentials)
+{
+  gboolean denied;
+
+  denied = FALSE;
+  g_signal_emit (observer,
+                 signals[DENY_AUTHENTICATED_PEER_SIGNAL],
+                 0,
+                 stream,
+                 credentials,
+                 &denied);
+  return denied;
+}
+
+
diff --git a/gio/gdbusauthobserver.h b/gio/gdbusauthobserver.h
new file mode 100644
index 0000000..6ed52e8
--- /dev/null
+++ b/gio/gdbusauthobserver.h
@@ -0,0 +1,100 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_AUTH_OBSERVER_H__
+#define __G_DBUS_AUTH_OBSERVER_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_AUTH_OBSERVER         (g_dbus_auth_observer_get_type ())
+#define G_DBUS_AUTH_OBSERVER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserver))
+#define G_DBUS_AUTH_OBSERVER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass))
+#define G_DBUS_AUTH_OBSERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_AUTH_OBSERVER, GDBusAuthObserverClass))
+#define G_IS_DBUS_AUTH_OBSERVER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_AUTH_OBSERVER))
+#define G_IS_DBUS_AUTH_OBSERVER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_AUTH_OBSERVER))
+
+typedef struct _GDBusAuthObserverClass   GDBusAuthObserverClass;
+typedef struct _GDBusAuthObserverPrivate GDBusAuthObserverPrivate;
+
+
+/**
+ * GDBusAuthObserverClass:
+ * @deny_authenticated_peer: Signal class handler for the #GDBusAuthObserver::deny-authenticated-peer signal.
+ *
+ * Class structure for #GDBusAuthObserverClass.
+ */
+struct _GDBusAuthObserverClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+
+  /* Signals */
+  gboolean (*deny_authenticated_peer) (GDBusAuthObserver  *observer,
+                                       GIOStream          *stream,
+                                       GCredentials       *credentials);
+
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+  void (*_g_reserved9) (void);
+  void (*_g_reserved10) (void);
+  void (*_g_reserved11) (void);
+  void (*_g_reserved12) (void);
+  void (*_g_reserved13) (void);
+  void (*_g_reserved14) (void);
+  void (*_g_reserved15) (void);
+  void (*_g_reserved16) (void);
+};
+
+/**
+ * GDBusAuthObserver:
+ *
+ * The #GDBusAuthObserver structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusAuthObserver
+{
+  GObject parent_instance;
+  GDBusAuthObserverPrivate *priv;
+};
+
+GType              g_dbus_auth_observer_get_type                (void) G_GNUC_CONST;
+GDBusAuthObserver *g_dbus_auth_observer_new                     (void);
+gboolean           g_dbus_auth_observer_deny_authenticated_peer (GDBusAuthObserver  *observer,
+                                                                 GIOStream          *stream,
+                                                                 GCredentials       *credentials);
+
+G_END_DECLS
+
+#endif /* _G_DBUS_AUTH_OBSERVER_H__ */
diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c
new file mode 100644
index 0000000..0bcd58f
--- /dev/null
+++ b/gio/gdbusconnection.c
@@ -0,0 +1,5280 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+/*
+ * TODO for GDBus:
+ *
+ * - would be nice to expose GDBusAuthMechanism and an extension point
+ *
+ * - need to expose an extension point for resolving D-Bus address and
+ *   turning them into GIOStream objects. This will allow us to implement
+ *   e.g. X11 D-Bus transports without dlopen()'ing or linking against
+ *   libX11 from libgio.
+ *   - see g_dbus_address_connect() in gdbusaddress.c
+ *
+ * - would be cute to use kernel-specific APIs to resolve fds for
+ *   debug output when using G_DBUS_DEBUG=messages, e.g. in addition to
+ *
+ *     fd 21: dev=8:1,mode=0100644,ino=1171231,uid=0,gid=0,rdev=0:0,size=234,atime=1273070640,mtime=1267126160,ctime=1267126160
+ *
+ *   maybe we can show more information about what fd 21 really is.
+ *   Ryan suggests looking in /proc/self/fd for clues / symlinks!
+ *   Initial experiments on Linux 2.6 suggests that the symlink looks
+ *   like this:
+ *
+ *    3 -> /proc/18068/fd
+ *
+ *   e.g. not of much use.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include <gio/gunixfdmessage.h>
+#endif
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "gdbusauth.h"
+
+#include "gdbusutils.h"
+#include "gdbusaddress.h"
+#include "gdbusmessage.h"
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "gdbusintrospection.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusprivate.h"
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+
+/**
+ * SECTION:gdbusconnection
+ * @short_description: D-Bus Connections
+ * @include: gdbus/gdbus.h
+ *
+ * <para><note>
+ * This class is rarely used directly in D-Bus clients. If you are
+ * writing an D-Bus client, it is often easier to use the
+ * g_bus_own_name(), g_bus_watch_name() or g_bus_watch_proxy() APIs.
+ * </note></para>
+ *
+ * The #GDBusConnection type is used for D-Bus connections to remote
+ * peers such as a message buses.
+ *
+ * <example id="gdbus-server"><title>D-Bus server example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-server.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * <example id="gdbus-subtree-server"><title>D-Bus subtree example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-subtree.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ *
+ * <example id="gdbus-unix-fd-client"><title>D-Bus UNIX File Descriptor example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-unix-fd-client.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (message_bus_lock);
+
+static GDBusConnection *the_session_bus = NULL;
+static GDBusConnection *the_system_bus = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+                    const gchar *needle)
+{
+  guint n;
+
+  for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+    {
+      if (g_strcmp0 (haystack[n], needle) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_WIN32
+#define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE)
+#else
+// TODO: for some reason this doesn't work on Windows
+#define CONNECTION_ENSURE_LOCK(obj) do {                                \
+    if (G_UNLIKELY (g_mutex_trylock((obj)->priv->lock)))                \
+      {                                                                 \
+        g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+                             "CONNECTION_ENSURE_LOCK: GDBusConnection object lock is not locked"); \
+      }                                                                 \
+  } while (FALSE)
+#endif
+
+#define CONNECTION_LOCK(obj) do {                                       \
+    g_mutex_lock ((obj)->priv->lock);                                   \
+  } while (FALSE)
+
+#define CONNECTION_UNLOCK(obj) do {                                     \
+    g_mutex_unlock ((obj)->priv->lock);                                 \
+  } while (FALSE)
+
+struct _GDBusConnectionPrivate
+{
+  /* ------------------------------------------------------------------------ */
+  /* -- General object state ------------------------------------------------ */
+  /* ------------------------------------------------------------------------ */
+
+  /* object-wide lock */
+  GMutex *lock;
+
+  /* A lock used in the init() method of the GInitable interface - see comments
+   * in initable_init() for why a separate lock is needed
+   */
+  GMutex *init_lock;
+
+  /* Set (by loading the contents of /var/lib/dbus/machine-id) the first time
+   * someone calls org.freedesktop.DBus.GetMachineId()
+   */
+  gchar *machine_id;
+
+  /* The underlying stream used for communication */
+  GIOStream *stream;
+
+  /* The object used for authentication (if any) */
+  GDBusAuth *auth;
+
+  /* Set to TRUE if the connection has been closed */
+  gboolean closed;
+
+  /* Last serial used */
+  guint32 last_serial;
+
+  /* The object used to send/receive message */
+  GDBusWorker *worker;
+
+  /* If connected to a message bus, this contains the unique name assigned to
+   * us by the bus (e.g. ":1.42")
+   */
+  gchar *bus_unique_name;
+
+  /* The GUID returned by the other side if we authenticed as a client or
+   * the GUID to use if authenticating as a server
+   */
+  gchar *guid;
+
+  /* set to TRUE exactly when initable_init() has finished running */
+  gboolean is_initialized;
+
+  /* If the connection could not be established during initable_init(), this GError will set */
+  GError *initialization_error;
+
+  /* The result of g_main_context_get_thread_default() when the object
+   * was created (the GObject _init() function) - this is used for delivery
+   * of the :closed GObject signal.
+   */
+  GMainContext *main_context_at_construction;
+
+  /* construct properties */
+  gchar *address;
+  GDBusConnectionFlags flags;
+
+  /* Map used for managing method replies */
+  GHashTable *map_method_serial_to_send_message_data;  /* guint32 -> SendMessageData* */
+
+  /* Maps used for managing signal subscription */
+  GHashTable *map_rule_to_signal_data;          /* gchar* -> SignalData */
+  GHashTable *map_id_to_signal_data;            /* guint  -> SignalData */
+  GHashTable *map_sender_to_signal_data_array;  /* gchar* -> GPtrArray* of SignalData */
+
+  /* Maps used for managing exported objects and subtrees */
+  GHashTable *map_object_path_to_eo;  /* gchar* -> ExportedObject* */
+  GHashTable *map_id_to_ei;           /* guint  -> ExportedInterface* */
+  GHashTable *map_object_path_to_es;  /* gchar* -> ExportedSubtree* */
+  GHashTable *map_id_to_es;           /* guint  -> ExportedSubtree* */
+
+  /* Structure used for message filters */
+  GPtrArray *filters;
+
+  /* Whether to exit on close */
+  gboolean exit_on_close;
+
+  /* Capabilities negotiated during authentication */
+  GDBusCapabilityFlags capabilities;
+
+  GDBusAuthObserver *authentication_observer;
+  GCredentials *crendentials;
+};
+
+typedef struct ExportedObject ExportedObject;
+static void exported_object_free (ExportedObject *eo);
+
+typedef struct ExportedSubtree ExportedSubtree;
+static void exported_subtree_free (ExportedSubtree *es);
+
+enum
+{
+  CLOSED_SIGNAL,
+  LAST_SIGNAL,
+};
+
+enum
+{
+  PROP_0,
+  PROP_STREAM,
+  PROP_ADDRESS,
+  PROP_FLAGS,
+  PROP_GUID,
+  PROP_UNIQUE_NAME,
+  PROP_CLOSED,
+  PROP_EXIT_ON_CLOSE,
+  PROP_CAPABILITY_FLAGS,
+  PROP_AUTHENTICATION_OBSERVER,
+};
+
+static void distribute_signals (GDBusConnection  *connection,
+                                GDBusMessage     *message);
+
+static void distribute_method_call (GDBusConnection  *connection,
+                                    GDBusMessage     *message);
+
+static gboolean handle_generic_unlocked (GDBusConnection *connection,
+                                         GDBusMessage    *message);
+
+
+static void purge_all_signal_subscriptions (GDBusConnection *connection);
+static void purge_all_filters (GDBusConnection *connection);
+
+#define _G_ENSURE_LOCK(name) do {                                       \
+    if (G_UNLIKELY (G_TRYLOCK(name)))                                   \
+      {                                                                 \
+        g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+                             "_G_ENSURE_LOCK: Lock `" #name "' is not locked"); \
+      }                                                                 \
+  } while (FALSE)                                                       \
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusConnection, g_dbus_connection, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                         );
+
+static void
+g_dbus_connection_dispose (GObject *object)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+  G_LOCK (message_bus_lock);
+  //g_debug ("disposing %p", connection);
+  if (connection == the_session_bus)
+    {
+      the_session_bus = NULL;
+    }
+  else if (connection == the_system_bus)
+    {
+      the_system_bus = NULL;
+    }
+  if (connection->priv->worker != NULL)
+    {
+      _g_dbus_worker_stop (connection->priv->worker);
+      connection->priv->worker = NULL;
+    }
+  G_UNLOCK (message_bus_lock);
+
+  if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose != NULL)
+    G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose (object);
+}
+
+static void
+g_dbus_connection_finalize (GObject *object)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+  if (connection->priv->authentication_observer != NULL)
+    g_object_unref (connection->priv->authentication_observer);
+
+  if (connection->priv->auth != NULL)
+    g_object_unref (connection->priv->auth);
+
+  //g_debug ("finalizing %p", connection);
+  if (connection->priv->stream != NULL)
+    {
+      /* We don't really care if closing the stream succeeds or not */
+      g_io_stream_close_async (connection->priv->stream,
+                               G_PRIORITY_DEFAULT,
+                               NULL,  /* GCancellable */
+                               NULL,  /* GAsyncReadyCallback */
+                               NULL); /* userdata */
+      g_object_unref (connection->priv->stream);
+      connection->priv->stream = NULL;
+    }
+
+  g_free (connection->priv->address);
+
+  g_free (connection->priv->guid);
+  g_free (connection->priv->bus_unique_name);
+
+  if (connection->priv->initialization_error != NULL)
+    g_error_free (connection->priv->initialization_error);
+
+  g_hash_table_unref (connection->priv->map_method_serial_to_send_message_data);
+
+  purge_all_signal_subscriptions (connection);
+  g_hash_table_unref (connection->priv->map_rule_to_signal_data);
+  g_hash_table_unref (connection->priv->map_id_to_signal_data);
+  g_hash_table_unref (connection->priv->map_sender_to_signal_data_array);
+
+  g_hash_table_unref (connection->priv->map_id_to_ei);
+  g_hash_table_unref (connection->priv->map_object_path_to_eo);
+  g_hash_table_unref (connection->priv->map_id_to_es);
+  g_hash_table_unref (connection->priv->map_object_path_to_es);
+
+  purge_all_filters (connection);
+  g_ptr_array_unref (connection->priv->filters);
+
+  if (connection->priv->main_context_at_construction != NULL)
+    g_main_context_unref (connection->priv->main_context_at_construction);
+
+  g_free (connection->priv->machine_id);
+
+  g_mutex_free (connection->priv->init_lock);
+  g_mutex_free (connection->priv->lock);
+
+  if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_connection_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      g_value_set_object (value, g_dbus_connection_get_stream (connection));
+      break;
+
+    case PROP_GUID:
+      g_value_set_string (value, g_dbus_connection_get_guid (connection));
+      break;
+
+    case PROP_UNIQUE_NAME:
+      g_value_set_string (value, g_dbus_connection_get_unique_name (connection));
+      break;
+
+    case PROP_CLOSED:
+      g_value_set_boolean (value, g_dbus_connection_is_closed (connection));
+      break;
+
+    case PROP_EXIT_ON_CLOSE:
+      g_value_set_boolean (value, g_dbus_connection_get_exit_on_close (connection));
+      break;
+
+    case PROP_CAPABILITY_FLAGS:
+      g_value_set_flags (value, g_dbus_connection_get_capabilities (connection));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_connection_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_STREAM:
+      connection->priv->stream = g_value_dup_object (value);
+      break;
+
+    case PROP_GUID:
+      connection->priv->guid = g_value_dup_string (value);
+      break;
+
+    case PROP_ADDRESS:
+      connection->priv->address = g_value_dup_string (value);
+      break;
+
+    case PROP_FLAGS:
+      connection->priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_EXIT_ON_CLOSE:
+      g_dbus_connection_set_exit_on_close (connection, g_value_get_boolean (value));
+      break;
+
+    case PROP_AUTHENTICATION_OBSERVER:
+      connection->priv->authentication_observer = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_connection_real_closed (GDBusConnection *connection,
+                               gboolean         remote_peer_vanished,
+                               GError          *error)
+{
+  if (remote_peer_vanished && connection->priv->exit_on_close)
+    {
+      g_print ("%s: Remote peer vanished. Exiting.\n", G_STRFUNC);
+      raise (SIGTERM);
+    }
+}
+
+static void
+g_dbus_connection_class_init (GDBusConnectionClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusConnectionPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_connection_finalize;
+  gobject_class->dispose      = g_dbus_connection_dispose;
+  gobject_class->set_property = g_dbus_connection_set_property;
+  gobject_class->get_property = g_dbus_connection_get_property;
+
+  klass->closed = g_dbus_connection_real_closed;
+
+  /**
+   * GDBusConnection:stream:
+   *
+   * The underlying #GIOStream used for I/O.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_STREAM,
+                                   g_param_spec_object ("stream",
+                                                        _("IO Stream"),
+                                                        _("The underlying streams used for I/O"),
+                                                        G_TYPE_IO_STREAM,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:address:
+   *
+   * A D-Bus address specifying potential endpoints that can be used
+   * when establishing the connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_ADDRESS,
+                                   g_param_spec_string ("address",
+                                                        _("Address"),
+                                                        _("D-Bus address specifying potential socket endpoints"),
+                                                        NULL,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:flags:
+   *
+   * Flags from the #GDBusConnectionFlags enumeration.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_FLAGS,
+                                   g_param_spec_flags ("flags",
+                                                       _("Flags"),
+                                                       _("Flags"),
+                                                       G_TYPE_DBUS_CONNECTION_FLAGS,
+                                                       G_DBUS_CONNECTION_FLAGS_NONE,
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:guid:
+   *
+   * The GUID of the peer performing the role of server when
+   * authenticating.
+   *
+   * If you are constructing a #GDBusConnection and pass
+   * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER in the
+   * #GDBusConnection:flags property then you MUST also set this
+   * property to a valid guid.
+   *
+   * If you are constructing a #GDBusConnection and pass
+   * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT in the
+   * #GDBusConnection:flags property you will be able to read the GUID
+   * of the other peer here after the connection has been succesfully
+   * initialized.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_GUID,
+                                   g_param_spec_string ("guid",
+                                                        _("GUID"),
+                                                        _("GUID of the server peer"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:unique-name:
+   *
+   * The unique name as assigned by the message bus or %NULL if the
+   * connection is not open or not a message bus connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_UNIQUE_NAME,
+                                   g_param_spec_string ("unique-name",
+                                                        _("unique-name"),
+                                                        _("Unique name of bus connection"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:closed:
+   *
+   * A boolean specifying whether the connection has been closed.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CLOSED,
+                                   g_param_spec_boolean ("closed",
+                                                         _("Closed"),
+                                                         _("Whether the connection is closed"),
+                                                         FALSE,
+                                                         G_PARAM_READABLE |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_BLURB |
+                                                         G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:exit-on-close:
+   *
+   * A boolean specifying whether the process will be terminated (by
+   * calling <literal>raise(SIGTERM)</literal>) if the connection
+   * is closed by the remote peer.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_EXIT_ON_CLOSE,
+                                   g_param_spec_boolean ("exit-on-close",
+                                                         _("Exit on close"),
+                                                         _("Whether the process is terminated when the connection is closed"),
+                                                         FALSE,
+                                                         G_PARAM_READABLE |
+                                                         G_PARAM_WRITABLE |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_BLURB |
+                                                         G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:capabilities:
+   *
+   * Flags from the #GDBusCapabilityFlags enumeration
+   * representing connection features negotiated with the other peer.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CAPABILITY_FLAGS,
+                                   g_param_spec_flags ("capabilities",
+                                                       _("Capabilities"),
+                                                       _("Capabilities"),
+                                                       G_TYPE_DBUS_CAPABILITY_FLAGS,
+                                                       G_DBUS_CAPABILITY_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection:authentication-observer:
+   *
+   * A #GDBusAuthObserver object to assist in the authentication process or %NULL.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_AUTHENTICATION_OBSERVER,
+                                   g_param_spec_object ("authentication-observer",
+                                                        _("Authentication Observer"),
+                                                        _("Object used to assist in the authentication process"),
+                                                        G_TYPE_DBUS_AUTH_OBSERVER,
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusConnection::closed:
+   * @connection: The #GDBusConnection emitting the signal.
+   * @remote_peer_vanished: %TRUE if @connection is closed because the
+   * remote peer closed its end of the connection.
+   * @error: A #GError with more details about the event or %NULL.
+   *
+   * Emitted when the connection is closed.
+   *
+   * The cause of this event can be
+   * <itemizedlist>
+   * <listitem><para>
+   *    If g_dbus_connection_close() is called. In this case
+   *    @remote_peer_vanished is set to %FALSE and @error is %NULL.
+   * </para></listitem>
+   * <listitem><para>
+   *    If the remote peer closes the connection. In this case
+   *    @remote_peer_vanished is set to %TRUE and @error is set.
+   * </para></listitem>
+   * <listitem><para>
+   *    If the remote peer sends invalid or malformed data. In this
+   *    case @remote_peer_vanished is set to %FALSE and @error
+   *    is set.
+   * </para></listitem>
+   * </itemizedlist>
+   *
+   * Upon receiving this signal, you should give up your reference to
+   * @connection. You are guaranteed that this signal is emitted only
+   * once.
+   */
+  signals[CLOSED_SIGNAL] = g_signal_new ("closed",
+                                         G_TYPE_DBUS_CONNECTION,
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (GDBusConnectionClass, closed),
+                                         NULL,
+                                         NULL,
+                                         _gio_marshal_VOID__BOOLEAN_BOXED,
+                                         G_TYPE_NONE,
+                                         2,
+                                         G_TYPE_BOOLEAN,
+                                         G_TYPE_ERROR);
+}
+
+static void
+g_dbus_connection_init (GDBusConnection *connection)
+{
+  connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, G_TYPE_DBUS_CONNECTION, GDBusConnectionPrivate);
+
+  connection->priv->lock = g_mutex_new ();
+  connection->priv->init_lock = g_mutex_new ();
+
+  connection->priv->map_method_serial_to_send_message_data = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  connection->priv->map_rule_to_signal_data = g_hash_table_new (g_str_hash,
+                                                                g_str_equal);
+  connection->priv->map_id_to_signal_data = g_hash_table_new (g_direct_hash,
+                                                              g_direct_equal);
+  connection->priv->map_sender_to_signal_data_array = g_hash_table_new_full (g_str_hash,
+                                                                             g_str_equal,
+                                                                             g_free,
+                                                                             NULL);
+
+  connection->priv->map_object_path_to_eo = g_hash_table_new_full (g_str_hash,
+                                                                   g_str_equal,
+                                                                   NULL,
+                                                                   (GDestroyNotify) exported_object_free);
+
+  connection->priv->map_id_to_ei = g_hash_table_new (g_direct_hash,
+                                                     g_direct_equal);
+
+  connection->priv->map_object_path_to_es = g_hash_table_new_full (g_str_hash,
+                                                                   g_str_equal,
+                                                                   NULL,
+                                                                   (GDestroyNotify) exported_subtree_free);
+
+  connection->priv->map_id_to_es = g_hash_table_new (g_direct_hash,
+                                                     g_direct_equal);
+
+  connection->priv->main_context_at_construction = g_main_context_get_thread_default ();
+  if (connection->priv->main_context_at_construction != NULL)
+    g_main_context_ref (connection->priv->main_context_at_construction);
+
+  connection->priv->filters = g_ptr_array_new ();
+}
+
+GIOStream *
+g_dbus_connection_get_stream (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  return connection->priv->stream;
+}
+
+
+/**
+ * g_dbus_connection_is_closed:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets whether @connection is closed.
+ *
+ * Returns: %TRUE if the connection is closed, %FALSE otherwise.
+ **/
+gboolean
+g_dbus_connection_is_closed (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  return connection->priv->closed;
+}
+
+/**
+ * g_dbus_connection_get_capabilities:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the capabilities negotiated with the remote peer
+ *
+ * Returns: One or more flags from the #GDBusCapabilityFlags enumeration.
+ */
+GDBusCapabilityFlags
+g_dbus_connection_get_capabilities (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CAPABILITY_FLAGS_NONE);
+  return connection->priv->capabilities;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GDBusConnection *connection;
+  GError *error;
+  gboolean remote_peer_vanished;
+} EmitClosedData;
+
+static void
+emit_closed_data_free (EmitClosedData *data)
+{
+  g_object_unref (data->connection);
+  if (data->error != NULL)
+    g_error_free (data->error);
+  g_free (data);
+}
+
+static gboolean
+emit_closed_in_idle (gpointer user_data)
+{
+  EmitClosedData *data = user_data;
+  gboolean result;
+
+  g_object_notify (G_OBJECT (data->connection), "closed");
+  g_signal_emit (data->connection,
+                 signals[CLOSED_SIGNAL],
+                 0,
+                 data->remote_peer_vanished,
+                 data->error,
+                 &result);
+  return FALSE;
+}
+
+/* Can be called from any thread, must hold lock */
+static void
+set_closed_unlocked (GDBusConnection *connection,
+                     gboolean  remote_peer_vanished,
+                     GError   *error)
+{
+  GSource *idle_source;
+  EmitClosedData *data;
+
+  CONNECTION_ENSURE_LOCK (connection);
+
+  g_assert (!connection->priv->closed);
+
+  connection->priv->closed = TRUE;
+
+  data = g_new0 (EmitClosedData, 1);
+  data->connection = g_object_ref (connection);
+  data->remote_peer_vanished = remote_peer_vanished;
+  data->error = error != NULL ? g_error_copy (error) : NULL;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         emit_closed_in_idle,
+                         data,
+                         (GDestroyNotify) emit_closed_data_free);
+  g_source_attach (idle_source, connection->priv->main_context_at_construction);
+  g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_close:
+ * @connection: A #GDBusConnection.
+ *
+ * Closes @connection. Note that this never causes the process to
+ * exit (this might only happen if the other end of a shared message
+ * bus connection disconnects).
+ *
+ * If @connection is already closed, this method does nothing.
+ */
+void
+g_dbus_connection_close (GDBusConnection *connection)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  CONNECTION_LOCK (connection);
+  if (!connection->priv->closed)
+  {
+    GError *error = NULL;
+
+    /* TODO: do this async */
+    //g_debug ("closing connection %p's stream %p", connection, connection->priv->stream);
+    if (!g_io_stream_close (connection->priv->stream, NULL, &error))
+      {
+        g_warning ("Error closing stream: %s", error->message);
+        g_error_free (error);
+      }
+
+    set_closed_unlocked (connection, FALSE, NULL);
+  }
+  CONNECTION_UNLOCK (connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+g_dbus_connection_send_message_unlocked (GDBusConnection     *connection,
+                                         GDBusMessage        *message,
+                                         volatile guint32    *out_serial,
+                                         GError             **error)
+{
+  guchar *blob;
+  gsize blob_size;
+  guint32 serial_to_use;
+  gboolean ret;
+
+  CONNECTION_ENSURE_LOCK (connection);
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+
+  /* TODO: check all necessary headers are present */
+
+  ret = FALSE;
+  blob = NULL;
+
+  if (out_serial != NULL)
+    *out_serial = 0;
+
+  if (connection->priv->closed)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_CLOSED,
+                           _("The connection is closed"));
+      goto out;
+    }
+
+  blob = g_dbus_message_to_blob (message,
+                                 &blob_size,
+                                 error);
+  if (blob == NULL)
+    goto out;
+
+  serial_to_use = ++connection->priv->last_serial; /* TODO: handle overflow */
+
+  switch (blob[0])
+    {
+    case 'l':
+      ((guint32 *) blob)[2] = GUINT32_TO_LE (serial_to_use);
+      break;
+    case 'B':
+      ((guint32 *) blob)[2] = GUINT32_TO_BE (serial_to_use);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+#if 0
+  g_printerr ("Writing message of %" G_GSIZE_FORMAT " bytes (serial %d) on %p:\n",
+              blob_size, serial_to_use, connection);
+  g_printerr ("----\n");
+  hexdump (blob, blob_size);
+  g_printerr ("----\n");
+#endif
+
+  /* TODO: use connection->priv->auth to encode the blob */
+
+  if (out_serial != NULL)
+    {
+      *out_serial = serial_to_use;
+    }
+  g_dbus_message_set_serial (message, serial_to_use);
+
+  _g_dbus_worker_send_message (connection->priv->worker,
+                               message,
+                               (gchar*) blob,
+                               blob_size);
+  blob = NULL; /* since _g_dbus_worker_send_message() steals the blob */
+
+  ret = TRUE;
+
+ out:
+  g_free (blob);
+
+  return ret;
+}
+
+/**
+ * g_dbus_connection_send_message:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Asynchronously sends @message to the peer represented by @connection.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: %TRUE if the message was well-formed and queued for
+ * transmission, %FALSE if @error is set.
+ */
+gboolean
+g_dbus_connection_send_message (GDBusConnection     *connection,
+                                GDBusMessage        *message,
+                                volatile guint32    *out_serial,
+                                GError             **error)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  CONNECTION_LOCK (connection);
+  ret = g_dbus_connection_send_message_unlocked (connection, message, out_serial, error);
+  CONNECTION_UNLOCK (connection);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  volatile gint ref_count;
+  GDBusConnection *connection;
+  guint32 serial;
+  GSimpleAsyncResult *simple;
+
+  GMainContext *main_context;
+
+  GCancellable *cancellable;
+
+  gulong cancellable_handler_id;
+
+  GSource *timeout_source;
+
+  gboolean delivered;
+} SendMessageData;
+
+static SendMessageData *
+send_message_data_ref (SendMessageData *data)
+{
+  g_atomic_int_inc (&data->ref_count);
+  return data;
+}
+
+static void
+send_message_data_unref (SendMessageData *data)
+{
+  if (g_atomic_int_dec_and_test (&data->ref_count))
+    {
+      g_assert (data->timeout_source == NULL);
+      g_assert (data->simple == NULL);
+      g_assert (data->cancellable_handler_id == 0);
+      g_object_unref (data->connection);
+      if (data->cancellable != NULL)
+        g_object_unref (data->cancellable);
+      if (data->main_context != NULL)
+        g_main_context_unref (data->main_context);
+      g_free (data);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* can be called from any thread with lock held - caller must have prepared GSimpleAsyncResult already */
+static void
+send_message_with_reply_deliver (SendMessageData *data)
+{
+  CONNECTION_ENSURE_LOCK (data->connection);
+
+  g_assert (!data->delivered);
+
+  data->delivered = TRUE;
+
+  g_simple_async_result_complete_in_idle (data->simple);
+  g_object_unref (data->simple);
+  data->simple = NULL;
+
+  if (data->timeout_source != NULL)
+    {
+      g_source_destroy (data->timeout_source);
+      data->timeout_source = NULL;
+    }
+  if (data->cancellable_handler_id > 0)
+    {
+      g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id);
+      data->cancellable_handler_id = 0;
+    }
+
+  g_warn_if_fail (g_hash_table_remove (data->connection->priv->map_method_serial_to_send_message_data,
+                                       GUINT_TO_POINTER (data->serial)));
+
+  send_message_data_unref (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock */
+static void
+send_message_data_deliver_reply_unlocked (SendMessageData *data,
+                                          GDBusMessage    *reply)
+{
+  if (data->delivered)
+    goto out;
+
+  g_simple_async_result_set_op_res_gpointer (data->simple,
+                                             g_object_ref (reply),
+                                             g_object_unref);
+
+  send_message_with_reply_deliver (data);
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+send_message_with_reply_cancelled_idle_cb (gpointer user_data)
+{
+  SendMessageData *data = user_data;
+
+  CONNECTION_LOCK (data->connection);
+  if (data->delivered)
+    goto out;
+
+  g_simple_async_result_set_error (data->simple,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_CANCELLED,
+                                   _("Operation was cancelled"));
+
+  send_message_with_reply_deliver (data);
+
+ out:
+  CONNECTION_UNLOCK (data->connection);
+  return FALSE;
+}
+
+/* Can be called from any thread with or without lock held */
+static void
+send_message_with_reply_cancelled_cb (GCancellable *cancellable,
+                                      gpointer      user_data)
+{
+  SendMessageData *data = user_data;
+  GSource *idle_source;
+
+  /* postpone cancellation to idle handler since we may be called directly
+   * via g_cancellable_connect() (e.g. holding lock)
+   */
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         send_message_with_reply_cancelled_idle_cb,
+                         send_message_data_ref (data),
+                         (GDestroyNotify) send_message_data_unref);
+  g_source_attach (idle_source, data->main_context);
+  g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+send_message_with_reply_timeout_cb (gpointer user_data)
+{
+  SendMessageData *data = user_data;
+
+  CONNECTION_LOCK (data->connection);
+  if (data->delivered)
+    goto out;
+
+  g_simple_async_result_set_error (data->simple,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_TIMED_OUT,
+                                   _("Timeout was reached"));
+
+  send_message_with_reply_deliver (data);
+
+ out:
+  CONNECTION_UNLOCK (data->connection);
+
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_connection_send_message_with_reply_unlocked (GDBusConnection     *connection,
+                                                    GDBusMessage        *message,
+                                                    gint                 timeout_msec,
+                                                    volatile guint32    *out_serial,
+                                                    GCancellable        *cancellable,
+                                                    GAsyncReadyCallback  callback,
+                                                    gpointer             user_data)
+{
+  GSimpleAsyncResult *simple;
+  SendMessageData *data;
+  GError *error;
+  volatile guint32 serial;
+
+  data = NULL;
+
+  if (out_serial == NULL)
+    out_serial = &serial;
+
+  if (timeout_msec == -1)
+    timeout_msec = 30 * 1000; /* TODO: check 30 secs is the default timeout */
+
+  simple = g_simple_async_result_new (G_OBJECT (connection),
+                                      callback,
+                                      user_data,
+                                      g_dbus_connection_send_message_with_reply);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      g_simple_async_result_set_error (simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_CANCELLED,
+                                       _("Operation was cancelled"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  if (connection->priv->closed)
+    {
+      g_simple_async_result_set_error (simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_CLOSED,
+                                       _("The connection is closed"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  error = NULL;
+  if (!g_dbus_connection_send_message_unlocked (connection, message, out_serial, &error))
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  data = g_new0 (SendMessageData, 1);
+  data->ref_count = 1;
+  data->connection = g_object_ref (connection);
+  data->simple = simple;
+  data->serial = *out_serial;
+  data->main_context = g_main_context_get_thread_default ();
+  if (data->main_context != NULL)
+    g_main_context_ref (data->main_context);
+
+  if (cancellable != NULL)
+    {
+      data->cancellable = g_object_ref (cancellable);
+      data->cancellable_handler_id = g_cancellable_connect (cancellable,
+                                                            G_CALLBACK (send_message_with_reply_cancelled_cb),
+                                                            send_message_data_ref (data),
+                                                            (GDestroyNotify) send_message_data_unref);
+      g_object_set_data_full (G_OBJECT (simple),
+                              "cancellable",
+                              g_object_ref (cancellable),
+                              (GDestroyNotify) g_object_unref);
+    }
+
+  data->timeout_source = g_timeout_source_new (timeout_msec);
+  g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (data->timeout_source,
+                         send_message_with_reply_timeout_cb,
+                         send_message_data_ref (data),
+                         (GDestroyNotify) send_message_data_unref);
+  g_source_attach (data->timeout_source, data->main_context);
+  g_source_unref (data->timeout_source);
+
+  g_hash_table_insert (connection->priv->map_method_serial_to_send_message_data,
+                       GUINT_TO_POINTER (*out_serial),
+                       data);
+
+ out:
+  ;
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously sends @message to the peer represented by @connection.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_connection_send_message_with_reply_finish() to get the result of the operation.
+ * See g_dbus_connection_send_message_with_reply_sync() for the synchronous version.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ */
+void
+g_dbus_connection_send_message_with_reply (GDBusConnection     *connection,
+                                           GDBusMessage        *message,
+                                           gint                 timeout_msec,
+                                           volatile guint32    *out_serial,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1);
+
+  CONNECTION_LOCK (connection);
+  g_dbus_connection_send_message_with_reply_unlocked (connection,
+                                                      message,
+                                                      timeout_msec,
+                                                      out_serial,
+                                                      cancellable,
+                                                      callback,
+                                                      user_data);
+  CONNECTION_UNLOCK (connection);
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_send_message_with_reply().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_send_message_with_reply().
+ *
+ * Note that @error is only set if a local in-process error
+ * occured. That is to say that the returned #GDBusMessage object may
+ * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use
+ * g_dbus_message_to_gerror() to transcode this to a #GError.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage or %NULL if @error is set.
+ */
+GDBusMessage *
+g_dbus_connection_send_message_with_reply_finish (GDBusConnection     *connection,
+                                                  GAsyncResult        *res,
+                                                  GError             **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GDBusMessage *reply;
+  GCancellable *cancellable;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  reply = NULL;
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_connection_send_message_with_reply);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  reply = g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+  cancellable = g_object_get_data (G_OBJECT (simple), "cancellable");
+  if (cancellable != NULL && g_cancellable_is_cancelled (cancellable))
+    {
+      g_object_unref (reply);
+      reply = NULL;
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_CANCELLED,
+                           _("Operation was cancelled"));
+    }
+ out:
+  return reply;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GAsyncResult *res;
+  GMainContext *context;
+  GMainLoop *loop;
+} SendMessageSyncData;
+
+static void
+send_message_with_reply_sync_cb (GDBusConnection *connection,
+                                 GAsyncResult    *res,
+                                 gpointer         user_data)
+{
+  SendMessageSyncData *data = user_data;
+  data->res = g_object_ref (res);
+  g_main_loop_quit (data->loop);
+}
+
+/**
+ * g_dbus_connection_send_message_with_reply_sync:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @out_serial: Return location for serial number assigned to @message when sending it or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously sends @message to the peer represented by @connection
+ * and blocks the calling thread until a reply is received or the
+ * timeout is reached. See g_dbus_connection_send_message_with_reply()
+ * for the asynchronous version of this method.
+ *
+ * If @out_serial is not %NULL, then the serial number assigned to
+ * @message by @connection will be written to this location prior to
+ * submitting the message to the underlying transport.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed,
+ * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * Note that @error is only set if a local in-process error
+ * occured. That is to say that the returned #GDBusMessage object may
+ * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use
+ * g_dbus_message_to_gerror() to transcode this to a #GError.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage that is the reply to @message or %NULL if @error is set.
+ */
+GDBusMessage *
+g_dbus_connection_send_message_with_reply_sync (GDBusConnection     *connection,
+                                                GDBusMessage        *message,
+                                                gint                 timeout_msec,
+                                                volatile guint32    *out_serial,
+                                                GCancellable        *cancellable,
+                                                GError             **error)
+{
+  SendMessageSyncData *data;
+  GDBusMessage *reply;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  data = g_new0 (SendMessageSyncData, 1);
+  data->context = g_main_context_new ();
+  data->loop = g_main_loop_new (data->context, FALSE);
+
+  g_main_context_push_thread_default (data->context);
+
+  g_dbus_connection_send_message_with_reply (connection,
+                                             message,
+                                             timeout_msec,
+                                             out_serial,
+                                             cancellable,
+                                             (GAsyncReadyCallback) send_message_with_reply_sync_cb,
+                                             data);
+  g_main_loop_run (data->loop);
+  reply = g_dbus_connection_send_message_with_reply_finish (connection,
+                                                            data->res,
+                                                            error);
+
+  g_main_context_pop_thread_default (data->context);
+
+  g_main_context_unref (data->context);
+  g_main_loop_unref (data->loop);
+  g_object_unref (data->res);
+  g_free (data);
+
+  return reply;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GDBusMessageFilterFunction func;
+  gpointer user_data;
+} FilterCallback;
+
+typedef struct
+{
+  guint                       id;
+  GDBusMessageFilterFunction  filter_function;
+  gpointer                    user_data;
+  GDestroyNotify              user_data_free_func;
+} FilterData;
+
+/* Called in worker's thread - we must not block */
+static void
+on_worker_message_received (GDBusWorker  *worker,
+                            GDBusMessage *message,
+                            gpointer      user_data)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+  FilterCallback *filters;
+  gboolean consumed_by_filter;
+  guint num_filters;
+  guint n;
+
+  //g_debug ("in on_worker_message_received");
+
+  g_object_ref (connection);
+
+  /* First collect the set of callback functions */
+  CONNECTION_LOCK (connection);
+  num_filters = connection->priv->filters->len;
+  filters = g_new0 (FilterCallback, num_filters);
+  for (n = 0; n < num_filters; n++)
+    {
+      FilterData *data = connection->priv->filters->pdata[n];
+      filters[n].func = data->filter_function;
+      filters[n].user_data = data->user_data;
+    }
+  CONNECTION_UNLOCK (connection);
+
+  /* the call the filters in order (without holding the lock) */
+  consumed_by_filter = FALSE;
+  for (n = 0; n < num_filters; n++)
+    {
+      consumed_by_filter = filters[n].func (connection,
+                                            message,
+                                            filters[n].user_data);
+      if (consumed_by_filter)
+        break;
+    }
+
+  /* Standard dispatch unless the filter ate the message */
+  if (!consumed_by_filter)
+    {
+      GDBusMessageType message_type;
+
+      message_type = g_dbus_message_get_type (message);
+      if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN || message_type == G_DBUS_MESSAGE_TYPE_ERROR)
+        {
+          guint32 reply_serial;
+          SendMessageData *send_message_data;
+
+          reply_serial = g_dbus_message_get_reply_serial (message);
+          CONNECTION_LOCK (connection);
+          send_message_data = g_hash_table_lookup (connection->priv->map_method_serial_to_send_message_data,
+                                                   GUINT_TO_POINTER (reply_serial));
+          if (send_message_data != NULL)
+            {
+              //g_debug ("delivering reply/error for serial %d for %p", reply_serial, connection);
+              send_message_data_deliver_reply_unlocked (send_message_data, message);
+            }
+          else
+            {
+              //g_debug ("message reply/error for serial %d but no SendMessageData found for %p", reply_serial, connection);
+            }
+          CONNECTION_UNLOCK (connection);
+        }
+      else if (message_type == G_DBUS_MESSAGE_TYPE_SIGNAL)
+        {
+          CONNECTION_LOCK (connection);
+          distribute_signals (connection, message);
+          CONNECTION_UNLOCK (connection);
+        }
+      else if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_CALL)
+        {
+          CONNECTION_LOCK (connection);
+          distribute_method_call (connection, message);
+          CONNECTION_UNLOCK (connection);
+        }
+    }
+
+  g_object_unref (connection);
+  g_free (filters);
+}
+
+/* Called in worker's thread - we must not block */
+static void
+on_worker_closed (GDBusWorker *worker,
+                  gboolean     remote_peer_vanished,
+                  GError      *error,
+                  gpointer     user_data)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+
+  //g_debug ("in on_worker_closed: %s", error->message);
+
+  CONNECTION_LOCK (connection);
+  if (!connection->priv->closed)
+    set_closed_unlocked (connection, remote_peer_vanished, error);
+  CONNECTION_UNLOCK (connection);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Determines the biggest set of capabilities we can support on this connection */
+static GDBusCapabilityFlags
+get_offered_capabilities_max (GDBusConnection *connection)
+{
+      GDBusCapabilityFlags ret;
+      ret = G_DBUS_CAPABILITY_FLAGS_NONE;
+#ifdef G_OS_UNIX
+      if (G_IS_UNIX_CONNECTION (connection->priv->stream))
+        {
+          ret |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
+        }
+#endif
+      return ret;
+}
+
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (initable);
+  gboolean ret;
+
+  /* This method needs to be idempotent to work with the singleton
+   * pattern. See the docs for g_initable_init(). We implement this by
+   * locking.
+   *
+   * Unfortunately we can't use the main lock since the on_worker_*()
+   * callbacks above needs the lock during initialization (for message
+   * bus connections we do a synchronous Hello() call on the bus).
+   */
+  g_mutex_lock (connection->priv->init_lock);
+
+  ret = FALSE;
+
+  if (connection->priv->is_initialized)
+    {
+      if (connection->priv->stream != NULL)
+        ret = TRUE;
+      else
+        g_assert (connection->priv->initialization_error != NULL);
+      goto out;
+    }
+  g_assert (connection->priv->initialization_error == NULL);
+
+  /* The user can pass multiple (but mutally exclusive) construct
+   * properties:
+   *
+   *  - stream (of type GIOStream)
+   *  - address (of type gchar*)
+   *
+   * At the end of the day we end up with a non-NULL GIOStream
+   * object in connection->priv->stream.
+   */
+  if (connection->priv->address != NULL)
+    {
+      g_assert (connection->priv->stream == NULL);
+
+      if ((connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) ||
+          (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS))
+        {
+          g_set_error_literal (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_ARGUMENT,
+                               _("Unsupported flags encountered when constructing a client-side connection"));
+          goto out;
+        }
+
+      connection->priv->stream = g_dbus_address_get_stream_sync (connection->priv->address,
+                                                                 NULL, /* TODO: out_guid */
+                                                                 cancellable,
+                                                                 &connection->priv->initialization_error);
+      if (connection->priv->stream == NULL)
+        goto out;
+    }
+  else if (connection->priv->stream != NULL)
+    {
+      /* nothing to do */
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  /* Authenticate the connection */
+  if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER)
+    {
+      g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT));
+      g_assert (connection->priv->guid != NULL);
+      connection->priv->auth = _g_dbus_auth_new (connection->priv->stream);
+      if (!_g_dbus_auth_run_server (connection->priv->auth,
+                                    connection->priv->authentication_observer,
+                                    connection->priv->guid,
+                                    (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS),
+                                    get_offered_capabilities_max (connection),
+                                    &connection->priv->capabilities,
+                                    &connection->priv->crendentials,
+                                    cancellable,
+                                    &connection->priv->initialization_error))
+        goto out;
+    }
+  else if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT)
+    {
+      g_assert (!(connection->priv->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER));
+      g_assert (connection->priv->guid == NULL);
+      connection->priv->auth = _g_dbus_auth_new (connection->priv->stream);
+      connection->priv->guid = _g_dbus_auth_run_client (connection->priv->auth,
+                                                        get_offered_capabilities_max (connection),
+                                                        &connection->priv->capabilities,
+                                                        cancellable,
+                                                        &connection->priv->initialization_error);
+      if (connection->priv->guid == NULL)
+        goto out;
+    }
+
+  if (connection->priv->authentication_observer != NULL)
+    {
+      g_object_unref (connection->priv->authentication_observer);
+      connection->priv->authentication_observer = NULL;
+    }
+
+  //g_output_stream_flush (G_SOCKET_CONNECTION (connection->priv->stream)
+
+  //g_debug ("haz unix fd passing powers: %d", connection->priv->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+  /* Hack used until
+   *
+   *  https://bugzilla.gnome.org/show_bug.cgi?id=616458
+   *
+   * has been resolved
+   */
+  if (G_IS_SOCKET_CONNECTION (connection->priv->stream))
+    {
+      g_socket_set_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection->priv->stream)), FALSE);
+    }
+
+  connection->priv->worker = _g_dbus_worker_new (connection->priv->stream,
+                                                 connection->priv->capabilities,
+                                                 on_worker_message_received,
+                                                 on_worker_closed,
+                                                 connection);
+
+  /* if a bus connection, invoke org.freedesktop.DBus.Hello - this is how we're getting a name */
+  if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+    {
+      GVariant *hello_result;
+      const gchar *s;
+
+      hello_result = g_dbus_connection_invoke_method_sync (connection,
+                                                           "org.freedesktop.DBus", /* name */
+                                                           "/org/freedesktop/DBus", /* path */
+                                                           "org.freedesktop.DBus", /* interface */
+                                                           "Hello",
+                                                           NULL, /* parameters */
+                                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                           -1,
+                                                           NULL, /* TODO: cancellable */
+                                                           &connection->priv->initialization_error);
+      if (hello_result == NULL)
+        goto out;
+
+      g_variant_get (hello_result, "(s)", &s);
+      connection->priv->bus_unique_name = g_strdup (s);
+      g_variant_unref (hello_result);
+      //g_debug ("unique name is `%s'", connection->priv->bus_unique_name);
+    }
+
+  connection->priv->is_initialized = TRUE;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    {
+      g_assert (connection->priv->initialization_error != NULL);
+      g_propagate_error (error, g_error_copy (connection->priv->initialization_error));
+    }
+
+  g_mutex_unlock (connection->priv->init_lock);
+
+  return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+async_init_thread (GSimpleAsyncResult *res,
+		   GObject            *object,
+		   GCancellable       *cancellable)
+{
+  GError *error = NULL;
+
+  if (!g_initable_init (G_INITABLE (object), cancellable, &error))
+    {
+      g_simple_async_result_set_from_error (res, error);
+      g_error_free (error);
+    }
+}
+
+static void
+async_initable_init_async (GAsyncInitable     *initable,
+                           gint                io_priority,
+                           GCancellable       *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer            user_data)
+{
+  GSimpleAsyncResult *res;
+
+  g_return_if_fail (G_IS_INITABLE (initable));
+
+  res = g_simple_async_result_new (G_OBJECT (initable), callback, user_data,
+				   async_initable_init_async);
+  g_simple_async_result_run_in_thread (res, async_init_thread,
+				       io_priority, cancellable);
+  g_object_unref (res);
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable  *initable,
+                            GAsyncResult    *res,
+                            GError         **error)
+{
+  return TRUE; /* Errors handled by base impl */
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+  /* We basically just want to use GIO's default implementation - though that one is
+   * unfortunately broken, see #615111. So we copy-paste a fixed-up version.
+   */
+  async_initable_iface->init_async = async_initable_init_async;
+  async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_new:
+ * @stream: A #GIOStream.
+ * @guid: The GUID to use if a authenticating as a server or %NULL.
+ * @flags: Flags describing how to make the connection.
+ * @authentication_observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously sets up a D-Bus connection for exchanging D-Bus messages
+ * with the end represented by @stream.
+ *
+ * If %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER is set in @flags,
+ * @auth_observer (if not %NULL) is used to assist in the client
+ * authentication process.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_connection_new_finish() to get the result of the
+ * operation.
+ *
+ * This is a asynchronous failable constructor. See
+ * g_dbus_connection_new_sync() for the synchronous
+ * version.
+ */
+void
+g_dbus_connection_new (GIOStream              *stream,
+                       const gchar            *guid,
+                       GDBusConnectionFlags    flags,
+                       GDBusAuthObserver      *authentication_observer,
+                       GCancellable           *cancellable,
+                       GAsyncReadyCallback     callback,
+                       gpointer                user_data)
+{
+  g_return_if_fail (G_IS_IO_STREAM (stream));
+  g_async_initable_new_async (G_TYPE_DBUS_CONNECTION,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "stream", stream,
+                              "guid", guid,
+                              "flags", flags,
+                              "authentication-observer", authentication_observer,
+                              NULL);
+}
+
+/**
+ * g_dbus_connection_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_new().
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_finish (GAsyncResult        *res,
+                              GError             **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+  if (object != NULL)
+    return G_DBUS_CONNECTION (object);
+  else
+    return NULL;
+}
+
+/**
+ * g_dbus_connection_new_sync:
+ * @stream: A #GIOStream.
+ * @guid: The GUID to use if a authenticating as a server or %NULL.
+ * @flags: Flags describing how to make the connection.
+ * @authentication_observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously sets up a D-Bus connection for exchanging D-Bus messages
+ * with the end represented by @stream.
+ *
+ * If %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER is set in @flags,
+ * @auth_observer (if not %NULL) is used to assist in the client
+ * authentication process.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_connection_new() for the asynchronous version.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_sync (GIOStream              *stream,
+                            const gchar            *guid,
+                            GDBusConnectionFlags    flags,
+                            GDBusAuthObserver      *authentication_observer,
+                            GCancellable           *cancellable,
+                            GError                **error)
+{
+  g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  return g_initable_new (G_TYPE_DBUS_CONNECTION,
+                         cancellable,
+                         error,
+                         "stream", stream,
+                         "guid", guid,
+                         "flags", flags,
+                         "authentication-observer", authentication_observer,
+                         NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_new_for_address:
+ * @address: A D-Bus address.
+ * @flags: Flags describing how to make the connection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects and sets up a D-Bus client connection for
+ * exchanging D-Bus messages with an endpoint specified by @address
+ * which must be in the D-Bus address format.
+ *
+ * This constructor can only be used to initiate client-side
+ * connections - use g_dbus_connection_new() if you need to act as the
+ * server. In particular, @flags cannot contain the
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_dbus_connection_new_finish() to get the result of the
+ * operation.
+ *
+ * This is a asynchronous failable constructor. See
+ * g_dbus_connection_new_for_address_sync() for the synchronous
+ * version.
+ */
+void
+g_dbus_connection_new_for_address (const gchar            *address,
+                                   GDBusConnectionFlags    flags,
+                                   GCancellable           *cancellable,
+                                   GAsyncReadyCallback     callback,
+                                   gpointer                user_data)
+{
+  g_return_if_fail (address != NULL);
+  g_async_initable_new_async (G_TYPE_DBUS_CONNECTION,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "address", address,
+                              "flags", flags,
+                              NULL);
+}
+
+/**
+ * g_dbus_connection_new_for_address_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_new_for_address().
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_for_address_finish (GAsyncResult        *res,
+                                          GError             **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+  if (object != NULL)
+    return G_DBUS_CONNECTION (object);
+  else
+    return NULL;
+}
+
+/**
+ * g_dbus_connection_new_for_address_sync:
+ * @address: A D-Bus address.
+ * @flags: Flags describing how to make the connection.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects and sets up a D-Bus client connection for
+ * exchanging D-Bus messages with an endpoint specified by @address
+ * which must be in the D-Bus address format.
+ *
+ * This constructor can only be used to initiate client-side
+ * connections - use g_dbus_connection_new_sync() if you need to act
+ * as the server. In particular, @flags cannot contain the
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER or
+ * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS flags.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_connection_new_for_address() for the asynchronous version.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_dbus_connection_new_for_address_sync (const gchar            *address,
+                                        GDBusConnectionFlags    flags,
+                                        GCancellable           *cancellable,
+                                        GError                **error)
+{
+  g_return_val_if_fail (address != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  return g_initable_new (G_TYPE_DBUS_CONNECTION,
+                         cancellable,
+                         error,
+                         "address", address,
+                         "flags", flags,
+                         NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_set_exit_on_close:
+ * @connection: A #GDBusConnection.
+ * @exit_on_close: Whether the process should be terminated
+ * when @connection is closed by the remote peer.
+ *
+ * Sets whether the process should be terminated when @connection is
+ * closed by the remote peer. See #GDBusConnection:exit-on-close for
+ * more details.
+ */
+void
+g_dbus_connection_set_exit_on_close (GDBusConnection *connection,
+                                     gboolean         exit_on_close)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  connection->priv->exit_on_close = exit_on_close;
+}
+
+/**
+ * g_dbus_connection_get_exit_on_close:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets whether the process is terminated when @connection is
+ * closed by the remote peer. See
+ * #GDBusConnection:exit-on-close for more details.
+ *
+ * Returns: Whether the process is terminated when @connection is
+ * closed by the remote peer.
+ */
+gboolean
+g_dbus_connection_get_exit_on_close (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  return connection->priv->exit_on_close;
+}
+
+/**
+ * g_dbus_connection_get_guid:
+ * @connection: A #GDBusConnection.
+ *
+ * The GUID of the peer performing the role of server when
+ * authenticating. See #GDBusConnection:guid for more details.
+ *
+ * Returns: The GUID. Do not free this string, it is owned by
+ * @connection.
+ **/
+const gchar *
+g_dbus_connection_get_guid (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  return connection->priv->guid;
+}
+
+/**
+ * g_dbus_connection_get_unique_name:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the unique name of @connection as assigned by the message
+ * bus. This can also be used to figure out if @connection is a
+ * message bus connection.
+ *
+ * Returns: The unique name or %NULL if @connection is not a message
+ * bus connection. Do not free this string, it is owned by
+ * @connection.
+ **/
+const gchar *
+g_dbus_connection_get_unique_name (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  return connection->priv->bus_unique_name;
+}
+
+/**
+ * g_dbus_connection_get_peer_credentials:
+ * @connection: A #GDBusConnection.
+ *
+ * Gets the credentials of the authenticated peer. This will always
+ * return %NULL unless @connection acted as a server
+ * (e.g. %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER was passed)
+ * when set up and the client passed credentials as part of the
+ * authentication process.
+ *
+ * In a message bus setup, the message bus is always the server and
+ * each application is a client. So this method will always return
+ * %NULL for message bus clients.
+ *
+ * Returns: A #GCredentials or %NULL if not available. Do not free
+ * this object, it is owned by @connection.
+ */
+GCredentials *
+g_dbus_connection_get_peer_credentials (GDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  return connection->priv->crendentials;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint _global_filter_id = 1;
+
+/**
+ * g_dbus_connection_add_filter:
+ * @connection: A #GDBusConnection.
+ * @filter_function: A filter function.
+ * @user_data: User data to pass to @filter_function.
+ * @user_data_free_func: Function to free @user_data with when filter
+ * is removed or %NULL.
+ *
+ * Adds a message filter. Filters are handlers that are run on all
+ * incoming messages, prior to standard dispatch. Filters are run in
+ * the order that they were added.  The same handler can be added as a
+ * filter more than once, in which case it will be run more than once.
+ * Filters added during a filter callback won't be run on the message
+ * being processed.
+ *
+ * Note that filters are run in a dedicated message handling thread so
+ * they can't block and, generally, can't do anything but signal a
+ * worker thread. Also note that filters are rarely needed - use API
+ * such as g_dbus_connection_send_message_with_reply(),
+ * g_dbus_connection_signal_subscribe() or
+ * g_dbus_connection_invoke_method() instead.
+ *
+ * Returns: A filter identifier that can be used with
+ * g_dbus_connection_remove_filter().
+ */
+guint
+g_dbus_connection_add_filter (GDBusConnection            *connection,
+                              GDBusMessageFilterFunction  filter_function,
+                              gpointer                    user_data,
+                              GDestroyNotify              user_data_free_func)
+{
+  FilterData *data;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (filter_function != NULL, 0);
+
+  CONNECTION_LOCK (connection);
+  data = g_new0 (FilterData, 1);
+  data->id = _global_filter_id++; /* TODO: overflow etc. */
+  data->filter_function = filter_function;
+  data->user_data = user_data;
+  data->user_data_free_func = user_data_free_func;
+  g_ptr_array_add (connection->priv->filters, data);
+  CONNECTION_UNLOCK (connection);
+
+  return data->id;
+}
+
+/* only called from finalize(), removes all filters */
+static void
+purge_all_filters (GDBusConnection *connection)
+{
+  guint n;
+  for (n = 0; n < connection->priv->filters->len; n++)
+    {
+      FilterData *data = connection->priv->filters->pdata[n];
+      if (data->user_data_free_func != NULL)
+        data->user_data_free_func (data->user_data);
+      g_free (data);
+    }
+}
+
+void
+g_dbus_connection_remove_filter (GDBusConnection    *connection,
+                                 guint               filter_id)
+{
+  guint n;
+  FilterData *to_destroy;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  CONNECTION_LOCK (connection);
+  to_destroy = NULL;
+  for (n = 0; n < connection->priv->filters->len; n++)
+    {
+      FilterData *data = connection->priv->filters->pdata[n];
+      if (data->id == filter_id)
+        {
+          g_ptr_array_remove_index (connection->priv->filters, n);
+          to_destroy = data;
+          break;
+        }
+    }
+  CONNECTION_UNLOCK (connection);
+
+  /* do free without holding lock */
+  if (to_destroy != NULL)
+    {
+      if (to_destroy->user_data_free_func != NULL)
+        to_destroy->user_data_free_func (to_destroy->user_data);
+      g_free (to_destroy);
+    }
+  else
+    {
+      g_warning ("g_dbus_connection_remove_filter: No filter found for filter_id %d", filter_id);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gchar *rule;
+  gchar *sender;
+  gchar *interface_name;
+  gchar *member;
+  gchar *object_path;
+  gchar *arg0;
+  GArray *subscribers;
+} SignalData;
+
+typedef struct
+{
+  GDBusSignalCallback callback;
+  gpointer user_data;
+  GDestroyNotify user_data_free_func;
+  guint id;
+  GMainContext *context;
+} SignalSubscriber;
+
+static void
+signal_data_free (SignalData *data)
+{
+  g_free (data->rule);
+  g_free (data->sender);
+  g_free (data->interface_name);
+  g_free (data->member);
+  g_free (data->object_path);
+  g_free (data->arg0);
+  g_array_free (data->subscribers, TRUE);
+  g_free (data);
+}
+
+static gchar *
+args_to_rule (const gchar         *sender,
+              const gchar         *interface_name,
+              const gchar         *member,
+              const gchar         *object_path,
+              const gchar         *arg0)
+{
+  GString *rule;
+
+  rule = g_string_new ("type='signal'");
+  if (sender != NULL)
+    g_string_append_printf (rule, ",sender='%s'", sender);
+  if (interface_name != NULL)
+    g_string_append_printf (rule, ",interface='%s'", interface_name);
+  if (member != NULL)
+    g_string_append_printf (rule, ",member='%s'", member);
+  if (object_path != NULL)
+    g_string_append_printf (rule, ",path='%s'", object_path);
+  if (arg0 != NULL)
+    g_string_append_printf (rule, ",arg0='%s'", arg0);
+
+  return g_string_free (rule, FALSE);
+}
+
+static guint _global_subscriber_id = 1;
+static guint _global_registration_id = 1;
+static guint _global_subtree_registration_id = 1;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling */
+static void
+add_match_rule (GDBusConnection *connection,
+                const gchar     *match_rule)
+{
+  GError *error;
+  GDBusMessage *message;
+
+  message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+                                            "/org/freedesktop/DBus", /* path */
+                                            "org.freedesktop.DBus", /* interface */
+                                            "AddMatch");
+  g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule));
+
+  error = NULL;
+  if (!g_dbus_connection_send_message_unlocked (connection,
+                                                message,
+                                                NULL,
+                                                &error))
+    {
+      g_critical ("Error while sending AddMatch() message: %s", error->message);
+      g_error_free (error);
+    }
+  g_object_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling */
+static void
+remove_match_rule (GDBusConnection *connection,
+                   const gchar     *match_rule)
+{
+  GError *error;
+  GDBusMessage *message;
+
+  message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+                                            "/org/freedesktop/DBus", /* path */
+                                            "org.freedesktop.DBus", /* interface */
+                                            "RemoveMatch");
+  g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule));
+
+  error = NULL;
+  if (!g_dbus_connection_send_message_unlocked (connection,
+                                                message,
+                                                NULL,
+                                                &error))
+    {
+      g_critical ("Error while sending RemoveMatch() message: %s", error->message);
+      g_error_free (error);
+    }
+  g_object_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_signal_data_for_name_lost_or_acquired (SignalData *signal_data)
+{
+  return g_strcmp0 (signal_data->sender, "org.freedesktop.DBus") == 0 &&
+    g_strcmp0 (signal_data->interface_name, "org.freedesktop.DBus") == 0 &&
+    g_strcmp0 (signal_data->object_path, "/org/freedesktop/DBus") == 0 &&
+    (g_strcmp0 (signal_data->member, "NameLost") == 0 ||
+     g_strcmp0 (signal_data->member, "NameAcquired") == 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_signal_subscribe:
+ * @connection: A #GDBusConnection.
+ * @sender: Sender name to match on. Must be either <literal>org.freedesktop.DBus</literal> (for listening to signals from the message bus daemon) or a unique name or %NULL to listen from all senders.
+ * @interface_name: D-Bus interface name to match on or %NULL to match on all interfaces.
+ * @member: D-Bus signal name to match on or %NULL to match on all signals.
+ * @object_path: Object path to match on or %NULL to match on all object paths.
+ * @arg0: Contents of first string argument to match on or %NULL to match on all kinds of arguments.
+ * @callback: Callback to invoke when there is a signal matching the requested data.
+ * @user_data: User data to pass to @callback.
+ * @user_data_free_func: Function to free @user_data with when subscription is removed or %NULL.
+ *
+ * Subscribes to signals on @connection and invokes @callback with a
+ * whenever the signal is received. Note that @callback
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * It is considered a programming error to use this function if @connection is closed.
+ *
+ * Note that if @sender is not <literal>org.freedesktop.DBus</literal> (for listening to signals from the
+ * message bus daemon), then it needs to be a unique bus name or %NULL (for listening to signals from any
+ * name) - you cannot pass a name like <literal>com.example.MyApp</literal>.
+ * Use e.g. g_bus_watch_name() to find the unique name for the owner of the name you are interested in. Also note
+ * that this function does not remove a subscription if @sender vanishes from the bus. You have to manually
+ * call g_dbus_connection_signal_unsubscribe() to remove a subscription.
+ *
+ * Returns: A subscription identifier that can be used with g_dbus_connection_signal_unsubscribe().
+ **/
+guint
+g_dbus_connection_signal_subscribe (GDBusConnection     *connection,
+                                    const gchar         *sender,
+                                    const gchar         *interface_name,
+                                    const gchar         *member,
+                                    const gchar         *object_path,
+                                    const gchar         *arg0,
+                                    GDBusSignalCallback  callback,
+                                    gpointer             user_data,
+                                    GDestroyNotify       user_data_free_func)
+{
+  gchar *rule;
+  SignalData *signal_data;
+  SignalSubscriber subscriber;
+  GPtrArray *signal_data_array;
+
+  /* Right now we abort if AddMatch() fails since it can only fail with the bus being in
+   * an OOM condition. We might want to change that but that would involve making
+   * g_dbus_connection_signal_subscribe() asynchronous and having the call sites
+   * handle that. And there's really no sensible way of handling this short of retrying
+   * to add the match rule... and then there's the little thing that, hey, maybe there's
+   * a reason the bus in an OOM condition.
+   *
+   * Doable, but not really sure it's worth it...
+   */
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+  g_return_val_if_fail (sender == NULL || ((strcmp (sender, "org.freedesktop.DBus") == 0 || sender[0] == ':') &&
+                                           (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)), 0);
+  g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0);
+  g_return_val_if_fail (member == NULL || g_dbus_is_member_name (member), 0);
+  g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), 0);
+  g_return_val_if_fail (callback != NULL, 0);
+
+  CONNECTION_LOCK (connection);
+
+  rule = args_to_rule (sender, interface_name, member, object_path, arg0);
+
+  if (sender == NULL)
+    sender = "";
+
+  subscriber.callback = callback;
+  subscriber.user_data = user_data;
+  subscriber.user_data_free_func = user_data_free_func;
+  subscriber.id = _global_subscriber_id++; /* TODO: overflow etc. */
+  subscriber.context = g_main_context_get_thread_default ();
+  if (subscriber.context != NULL)
+    g_main_context_ref (subscriber.context);
+
+  /* see if we've already have this rule */
+  signal_data = g_hash_table_lookup (connection->priv->map_rule_to_signal_data, rule);
+  if (signal_data != NULL)
+    {
+      g_array_append_val (signal_data->subscribers, subscriber);
+      g_free (rule);
+      goto out;
+    }
+
+  signal_data = g_new0 (SignalData, 1);
+  signal_data->rule           = rule;
+  signal_data->sender         = g_strdup (sender);
+  signal_data->interface_name = g_strdup (interface_name);
+  signal_data->member         = g_strdup (member);
+  signal_data->object_path    = g_strdup (object_path);
+  signal_data->arg0           = g_strdup (arg0);
+  signal_data->subscribers    = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+  g_array_append_val (signal_data->subscribers, subscriber);
+
+  g_hash_table_insert (connection->priv->map_rule_to_signal_data,
+                       signal_data->rule,
+                       signal_data);
+
+  /* Add the match rule to the bus...
+   *
+   * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
+   * always send such messages to to us.
+   */
+  if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+    {
+      if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+        {
+          add_match_rule (connection, signal_data->rule);
+        }
+    }
+
+ out:
+  g_hash_table_insert (connection->priv->map_id_to_signal_data,
+                       GUINT_TO_POINTER (subscriber.id),
+                       signal_data);
+
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                           signal_data->sender);
+  if (signal_data_array == NULL)
+    {
+      signal_data_array = g_ptr_array_new ();
+      g_hash_table_insert (connection->priv->map_sender_to_signal_data_array,
+                           g_strdup (signal_data->sender),
+                           signal_data_array);
+    }
+  g_ptr_array_add (signal_data_array, signal_data);
+
+  CONNECTION_UNLOCK (connection);
+
+  return subscriber.id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling this */
+static void
+unsubscribe_id_internal (GDBusConnection    *connection,
+                         guint               subscription_id,
+                         GArray             *out_removed_subscribers)
+{
+  SignalData *signal_data;
+  GPtrArray *signal_data_array;
+  guint n;
+
+  signal_data = g_hash_table_lookup (connection->priv->map_id_to_signal_data,
+                                     GUINT_TO_POINTER (subscription_id));
+  if (signal_data == NULL)
+    {
+      /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */
+      goto out;
+    }
+
+  for (n = 0; n < signal_data->subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+
+      subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n));
+      if (subscriber->id != subscription_id)
+        continue;
+
+      g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_signal_data,
+                                           GUINT_TO_POINTER (subscription_id)));
+      g_array_append_val (out_removed_subscribers, *subscriber);
+      g_array_remove_index (signal_data->subscribers, n);
+
+      if (signal_data->subscribers->len == 0)
+        g_warn_if_fail (g_hash_table_remove (connection->priv->map_rule_to_signal_data, signal_data->rule));
+
+      signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                               signal_data->sender);
+      g_warn_if_fail (signal_data_array != NULL);
+      g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data));
+
+      if (signal_data_array->len == 0)
+        {
+          g_warn_if_fail (g_hash_table_remove (connection->priv->map_sender_to_signal_data_array, signal_data->sender));
+
+          /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
+          if (connection->priv->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)
+            {
+              if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+                {
+                  remove_match_rule (connection, signal_data->rule);
+                }
+            }
+
+          signal_data_free (signal_data);
+        }
+
+      goto out;
+    }
+
+  g_assert_not_reached ();
+
+ out:
+  ;
+}
+
+/**
+ * g_dbus_connection_signal_unsubscribe:
+ * @connection: A #GDBusConnection.
+ * @subscription_id: A subscription id obtained from g_dbus_connection_signal_subscribe().
+ *
+ * Unsubscribes from signals.
+ **/
+void
+g_dbus_connection_signal_unsubscribe (GDBusConnection    *connection,
+                                      guint               subscription_id)
+{
+  GArray *subscribers;
+  guint n;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+
+  CONNECTION_LOCK (connection);
+  unsubscribe_id_internal (connection,
+                           subscription_id,
+                           subscribers);
+  CONNECTION_UNLOCK (connection);
+
+  /* invariant */
+  g_assert (subscribers->len == 0 || subscribers->len == 1);
+
+  /* call GDestroyNotify without lock held */
+  for (n = 0; n < subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+      subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+      if (subscriber->user_data_free_func != NULL)
+        subscriber->user_data_free_func (subscriber->user_data);
+      if (subscriber->context != NULL)
+        g_main_context_unref (subscriber->context);
+    }
+
+  g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  guint                subscription_id;
+  GDBusSignalCallback  callback;
+  gpointer             user_data;
+  GDBusMessage        *message;
+  GDBusConnection     *connection;
+  const gchar         *sender;
+  const gchar         *path;
+  const gchar         *interface;
+  const gchar         *member;
+} SignalInstance;
+
+/* called on delivery thread (e.g. where g_dbus_connection_signal_subscribe() was called) with
+ * no locks held
+ */
+static gboolean
+emit_signal_instance_in_idle_cb (gpointer data)
+{
+  SignalInstance *signal_instance = data;
+  GVariant *parameters;
+  gboolean has_subscription;
+
+  parameters = g_dbus_message_get_body (signal_instance->message);
+  if (parameters == NULL)
+    {
+      parameters = g_variant_new ("()");
+      g_variant_ref_sink (parameters);
+    }
+  else
+    {
+      g_variant_ref_sink (parameters);
+    }
+
+#if 0
+  g_debug ("in emit_signal_instance_in_idle_cb (sender=%s path=%s interface=%s member=%s params=%s)",
+           signal_instance->sender,
+           signal_instance->path,
+           signal_instance->interface,
+           signal_instance->member,
+           g_variant_print (parameters, TRUE));
+#endif
+
+  /* Careful here, don't do the callback if we no longer has the subscription */
+  CONNECTION_LOCK (signal_instance->connection);
+  has_subscription = FALSE;
+  if (g_hash_table_lookup (signal_instance->connection->priv->map_id_to_signal_data,
+                           GUINT_TO_POINTER (signal_instance->subscription_id)) != NULL)
+    has_subscription = TRUE;
+  CONNECTION_UNLOCK (signal_instance->connection);
+
+  if (has_subscription)
+    {
+      signal_instance->callback (signal_instance->connection,
+                                 signal_instance->sender,
+                                 signal_instance->path,
+                                 signal_instance->interface,
+                                 signal_instance->member,
+                                 parameters,
+                                 signal_instance->user_data);
+    }
+   if (parameters != NULL)
+    g_variant_unref (parameters);
+
+  return FALSE;
+}
+
+static void
+signal_instance_free (SignalInstance *signal_instance)
+{
+  g_object_unref (signal_instance->message);
+  g_object_unref (signal_instance->connection);
+  g_free (signal_instance);
+}
+
+/* called in message handler thread WITH lock held */
+static void
+schedule_callbacks (GDBusConnection *connection,
+                    GPtrArray       *signal_data_array,
+                    GDBusMessage    *message,
+                    const gchar     *sender)
+{
+  guint n, m;
+  const gchar *interface;
+  const gchar *member;
+  const gchar *path;
+  const gchar *arg0;
+
+  interface = NULL;
+  member = NULL;
+  path = NULL;
+  arg0 = NULL;
+
+  interface = g_dbus_message_get_interface (message);
+  member = g_dbus_message_get_member (message);
+  path = g_dbus_message_get_path (message);
+  arg0 = g_dbus_message_get_arg0 (message);
+
+#if 0
+  g_debug ("sender    = `%s'", sender);
+  g_debug ("interface = `%s'", interface);
+  g_debug ("member    = `%s'", member);
+  g_debug ("path      = `%s'", path);
+  g_debug ("arg0      = `%s'", arg0);
+#endif
+
+  /* TODO: if this is slow, then we can change signal_data_array into
+   *       map_object_path_to_signal_data_array or something.
+   */
+  for (n = 0; n < signal_data_array->len; n++)
+    {
+      SignalData *signal_data = signal_data_array->pdata[n];
+
+      if (signal_data->interface_name != NULL && g_strcmp0 (signal_data->interface_name, interface) != 0)
+        continue;
+
+      if (signal_data->member != NULL && g_strcmp0 (signal_data->member, member) != 0)
+        continue;
+
+      if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0)
+        continue;
+
+      if (signal_data->arg0 != NULL && g_strcmp0 (signal_data->arg0, arg0) != 0)
+        continue;
+
+      for (m = 0; m < signal_data->subscribers->len; m++)
+        {
+          SignalSubscriber *subscriber;
+          GSource *idle_source;
+          SignalInstance *signal_instance;
+
+          subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m));
+
+          signal_instance = g_new0 (SignalInstance, 1);
+          signal_instance->subscription_id = subscriber->id;
+          signal_instance->callback = subscriber->callback;
+          signal_instance->user_data = subscriber->user_data;
+          signal_instance->message = g_object_ref (message);
+          signal_instance->connection = g_object_ref (connection);
+          signal_instance->sender = sender;
+          signal_instance->path = path;
+          signal_instance->interface = interface;
+          signal_instance->member = member;
+
+          idle_source = g_idle_source_new ();
+          g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+          g_source_set_callback (idle_source,
+                                 emit_signal_instance_in_idle_cb,
+                                 signal_instance,
+                                 (GDestroyNotify) signal_instance_free);
+          g_source_attach (idle_source, subscriber->context);
+          g_source_unref (idle_source);
+        }
+    }
+}
+
+/* called in message handler thread with lock held */
+static void
+distribute_signals (GDBusConnection  *connection,
+                    GDBusMessage     *message)
+{
+  GPtrArray *signal_data_array;
+  const gchar *sender;
+
+  sender = g_dbus_message_get_sender (message);
+
+  /* collect subcsribers that match on sender */
+  if (sender != NULL)
+    {
+      signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, sender);
+      if (signal_data_array != NULL) {
+        schedule_callbacks (connection, signal_data_array, message, sender);
+      }
+    }
+
+  /* collect subcsribers not matching on sender */
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, "");
+  if (signal_data_array != NULL)
+    {
+      schedule_callbacks (connection, signal_data_array, message, sender);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* only called from finalize(), removes all subscriptions */
+static void
+purge_all_signal_subscriptions (GDBusConnection *connection)
+{
+  GHashTableIter iter;
+  gpointer key;
+  GArray *ids;
+  GArray *subscribers;
+  guint n;
+
+  ids = g_array_new (FALSE, FALSE, sizeof (guint));
+  g_hash_table_iter_init (&iter, connection->priv->map_id_to_signal_data);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
+    {
+      guint subscription_id = GPOINTER_TO_UINT (key);
+      g_array_append_val (ids, subscription_id);
+    }
+
+  subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+  for (n = 0; n < ids->len; n++)
+    {
+      guint subscription_id = g_array_index (ids, guint, n);
+      unsubscribe_id_internal (connection,
+                               subscription_id,
+                               subscribers);
+    }
+  g_array_free (ids, TRUE);
+
+  /* call GDestroyNotify without lock held */
+  for (n = 0; n < subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+      subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+      if (subscriber->user_data_free_func != NULL)
+        subscriber->user_data_free_func (subscriber->user_data);
+      if (subscriber->context != NULL)
+        g_main_context_unref (subscriber->context);
+    }
+
+  g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedObject
+{
+  gchar *object_path;
+  GDBusConnection *connection;
+
+  /* maps gchar* -> ExportedInterface* */
+  GHashTable *map_if_name_to_ei;
+};
+
+/* only called with lock held */
+static void
+exported_object_free (ExportedObject *eo)
+{
+  g_free (eo->object_path);
+  g_hash_table_unref (eo->map_if_name_to_ei);
+  g_free (eo);
+}
+
+typedef struct
+{
+  ExportedObject *eo;
+
+  guint                       id;
+  gchar                      *interface_name;
+  const GDBusInterfaceVTable *vtable;
+  const GDBusInterfaceInfo   *introspection_data;
+
+  GMainContext               *context;
+  gpointer                    user_data;
+  GDestroyNotify              user_data_free_func;
+} ExportedInterface;
+
+/* called with lock held */
+static void
+exported_interface_free (ExportedInterface *ei)
+{
+  if (ei->user_data_free_func != NULL)
+    {
+      /* TODO: push to thread-default mainloop */
+      ei->user_data_free_func (ei->user_data);
+    }
+  if (ei->context != NULL)
+    {
+      g_main_context_unref (ei->context);
+    }
+  g_free (ei->interface_name);
+  g_free (ei);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GDBusConnection *connection;
+  GDBusMessage *message;
+  gpointer user_data;
+  const char *property_name;
+  const GDBusInterfaceVTable *vtable;
+  const GDBusInterfaceInfo *interface_info;
+  const GDBusPropertyInfo *property_info;
+} PropertyData;
+
+static void
+property_data_free (PropertyData *data)
+{
+  g_object_unref (data->connection);
+  g_object_unref (data->message);
+  g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_property_in_idle_cb (gpointer _data)
+{
+  PropertyData *data = _data;
+  GVariant *value;
+  GError *error;
+  GDBusMessage *reply;
+
+  error = NULL;
+  value = data->vtable->get_property (data->connection,
+                                      g_dbus_message_get_sender (data->message),
+                                      g_dbus_message_get_path (data->message),
+                                      data->interface_info->name,
+                                      data->property_name,
+                                      &error,
+                                      data->user_data);
+
+
+  if (value != NULL)
+    {
+      g_assert_no_error (error);
+
+      g_variant_ref_sink (value);
+      reply = g_dbus_message_new_method_reply (data->message);
+      g_dbus_message_set_body (reply, g_variant_new ("(v)", value));
+      g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+      g_variant_unref (value);
+      g_object_unref (reply);
+    }
+  else
+    {
+      gchar *dbus_error_name;
+
+      g_assert (error != NULL);
+
+      dbus_error_name = g_dbus_error_encode_gerror (error);
+      reply = g_dbus_message_new_method_error_literal (data->message,
+                                                       dbus_error_name,
+                                                       error->message);
+      g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+      g_free (dbus_error_name);
+      g_error_free (error);
+      g_object_unref (reply);
+    }
+
+  return FALSE;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_set_property_in_idle_cb (gpointer _data)
+{
+  PropertyData *data = _data;
+  GError *error;
+  GDBusMessage *reply;
+  GVariant *value;
+
+  error = NULL;
+  value = NULL;
+
+  g_variant_get (g_dbus_message_get_body (data->message),
+                 "(ssv)",
+                 NULL,
+                 NULL,
+                 &value);
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the type
+   * of the given value is wrong
+   */
+  if (g_strcmp0 (g_variant_get_type_string (value), data->property_info->signature) != 0)
+    {
+      reply = g_dbus_message_new_method_error (data->message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("Error setting property `%s': Expected type `%s' but got `%s'"),
+                                               data->property_info->name,
+                                               data->property_info->signature,
+                                               g_variant_get_type_string (value));
+      goto out;
+    }
+
+  if (!data->vtable->set_property (data->connection,
+                                   g_dbus_message_get_sender (data->message),
+                                   g_dbus_message_get_path (data->message),
+                                   data->interface_info->name,
+                                   data->property_name,
+                                   value,
+                                   &error,
+                                   data->user_data))
+    {
+      gchar *dbus_error_name;
+      g_assert (error != NULL);
+      dbus_error_name = g_dbus_error_encode_gerror (error);
+      reply = g_dbus_message_new_method_error_literal (data->message,
+                                                       dbus_error_name,
+                                                       error->message);
+      g_free (dbus_error_name);
+      g_error_free (error);
+    }
+  else
+    {
+      reply = g_dbus_message_new_method_reply (data->message);
+    }
+
+ out:
+  g_assert (reply != NULL);
+  g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+  g_object_unref (reply);
+
+  return FALSE;
+}
+
+/* called with lock held */
+static gboolean
+validate_and_maybe_schedule_property_getset (GDBusConnection            *connection,
+                                             GDBusMessage               *message,
+                                             gboolean                    is_get,
+                                             const GDBusInterfaceInfo   *introspection_data,
+                                             const GDBusInterfaceVTable *vtable,
+                                             GMainContext               *main_context,
+                                             gpointer                    user_data)
+{
+  gboolean handled;
+  const char *interface_name;
+  const char *property_name;
+  const GDBusPropertyInfo *property_info;
+  GSource *idle_source;
+  PropertyData *property_data;
+  GDBusMessage *reply;
+
+  handled = FALSE;
+
+  if (is_get)
+    g_variant_get (g_dbus_message_get_body (message),
+                   "(ss)",
+                   &interface_name,
+                   &property_name);
+  else
+    g_variant_get (g_dbus_message_get_body (message),
+                   "(ssv)",
+                   &interface_name,
+                   &property_name,
+                   NULL);
+
+
+  if (is_get)
+    {
+      if (vtable == NULL || vtable->get_property == NULL)
+        goto out;
+    }
+  else
+    {
+      if (vtable == NULL || vtable->set_property == NULL)
+        goto out;
+    }
+
+  /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs
+   */
+  property_info = NULL;
+
+  /* TODO: the cost of this is O(n) - it might be worth caching the result */
+  property_info = g_dbus_interface_info_lookup_property (introspection_data, property_name);
+  if (property_info == NULL)
+    {
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("No such property `%s'"),
+                                               property_name);
+      g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+
+  if (is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+    {
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("Property `%s' is not readable"),
+                                               property_name);
+      g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+  else if (!is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+    {
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("Property `%s' is not writable"),
+                                               property_name);
+      g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+
+  /* ok, got the property info - call user code in an idle handler */
+  property_data = g_new0 (PropertyData, 1);
+  property_data->connection = g_object_ref (connection);
+  property_data->message = g_object_ref (message);
+  property_data->user_data = user_data;
+  property_data->property_name = property_name;
+  property_data->vtable = vtable;
+  property_data->interface_info = introspection_data;
+  property_data->property_info = property_info;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         is_get ? invoke_get_property_in_idle_cb : invoke_set_property_in_idle_cb,
+                         property_data,
+                         (GDestroyNotify) property_data_free);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  handled = TRUE;
+
+ out:
+  return handled;
+}
+
+/* called with lock held */
+static gboolean
+handle_getset_property (GDBusConnection *connection,
+                        ExportedObject  *eo,
+                        GDBusMessage    *message,
+                        gboolean         is_get)
+{
+  ExportedInterface *ei;
+  gboolean handled;
+  const char *interface_name;
+  const char *property_name;
+
+  handled = FALSE;
+
+  if (is_get)
+    g_variant_get (g_dbus_message_get_body (message),
+                   "(ss)",
+                   &interface_name,
+                   &property_name);
+  else
+    g_variant_get (g_dbus_message_get_body (message),
+                   "(ssv)",
+                   &interface_name,
+                   &property_name,
+                   NULL);
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+   * no such interface registered
+   */
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei == NULL)
+    {
+      GDBusMessage *reply;
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("No such interface `%s'"),
+                                               interface_name);
+      g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+
+  handled = validate_and_maybe_schedule_property_getset (eo->connection,
+                                                         message,
+                                                         is_get,
+                                                         ei->introspection_data,
+                                                         ei->vtable,
+                                                         ei->context,
+                                                         ei->user_data);
+ out:
+  return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GDBusConnection *connection;
+  GDBusMessage *message;
+  gpointer user_data;
+  const GDBusInterfaceVTable *vtable;
+  const GDBusInterfaceInfo *interface_info;
+} PropertyGetAllData;
+
+static void
+property_get_all_data_free (PropertyData *data)
+{
+  g_object_unref (data->connection);
+  g_object_unref (data->message);
+  g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_all_properties_in_idle_cb (gpointer _data)
+{
+  PropertyGetAllData *data = _data;
+  GVariantBuilder *builder;
+  GVariant *packed;
+  GVariant *result;
+  GError *error;
+  GDBusMessage *reply;
+  guint n;
+
+  error = NULL;
+
+  /* TODO: Right now we never fail this call - we just omit values if
+   *       a get_property() call is failing.
+   *
+   *       We could fail the whole call if just a single get_property() call
+   *       returns an error. We need clarification in the D-Bus spec about this.
+   */
+  builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+  for (n = 0; data->interface_info->properties != NULL && data->interface_info->properties[n] != NULL; n++)
+    {
+      const GDBusPropertyInfo *property_info = data->interface_info->properties[n];
+      GVariant *value;
+
+      if (!(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+        continue;
+
+      value = data->vtable->get_property (data->connection,
+                                          g_dbus_message_get_sender (data->message),
+                                          g_dbus_message_get_path (data->message),
+                                          data->interface_info->name,
+                                          property_info->name,
+                                          NULL,
+                                          data->user_data);
+
+      if (value == NULL)
+        continue;
+
+      g_variant_ref_sink (value);
+      g_variant_builder_add (builder,
+                             "{sv}",
+                             property_info->name,
+                             value);
+      g_variant_unref (value);
+    }
+  result = g_variant_builder_end (builder);
+
+  builder = g_variant_builder_new (G_VARIANT_TYPE_TUPLE);
+  g_variant_builder_add_value (builder, result); /* steals result since result is floating */
+  packed = g_variant_builder_end (builder);
+
+  reply = g_dbus_message_new_method_reply (data->message);
+  g_dbus_message_set_body (reply, packed);
+  g_dbus_connection_send_message (data->connection, reply, NULL, NULL);
+  g_object_unref (reply);
+
+  return FALSE;
+}
+
+/* called with lock held */
+static gboolean
+validate_and_maybe_schedule_property_get_all (GDBusConnection            *connection,
+                                              GDBusMessage               *message,
+                                              const GDBusInterfaceInfo   *introspection_data,
+                                              const GDBusInterfaceVTable *vtable,
+                                              GMainContext               *main_context,
+                                              gpointer                    user_data)
+{
+  gboolean handled;
+  const char *interface_name;
+  GSource *idle_source;
+  PropertyGetAllData *property_get_all_data;
+
+  handled = FALSE;
+
+  g_variant_get (g_dbus_message_get_body (message),
+                 "(s)",
+                 &interface_name);
+
+  if (vtable == NULL || vtable->get_property == NULL)
+    goto out;
+
+  /* ok, got the property info - call user in an idle handler */
+  property_get_all_data = g_new0 (PropertyGetAllData, 1);
+  property_get_all_data->connection = g_object_ref (connection);
+  property_get_all_data->message = g_object_ref (message);
+  property_get_all_data->user_data = user_data;
+  property_get_all_data->vtable = vtable;
+  property_get_all_data->interface_info = introspection_data;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         invoke_get_all_properties_in_idle_cb,
+                         property_get_all_data,
+                         (GDestroyNotify) property_get_all_data_free);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  handled = TRUE;
+
+ out:
+  return handled;
+}
+
+/* called with lock held */
+static gboolean
+handle_get_all_properties (GDBusConnection *connection,
+                           ExportedObject  *eo,
+                           GDBusMessage    *message)
+{
+  ExportedInterface *ei;
+  gboolean handled;
+  const char *interface_name;
+
+  handled = FALSE;
+
+  g_variant_get (g_dbus_message_get_body (message),
+                 "(s)",
+                 &interface_name);
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+   * no such interface registered
+   */
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei == NULL)
+    {
+      GDBusMessage *reply;
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("No such interface"),
+                                               interface_name);
+      g_dbus_connection_send_message_unlocked (eo->connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+
+  handled = validate_and_maybe_schedule_property_get_all (eo->connection,
+                                                          message,
+                                                          ei->introspection_data,
+                                                          ei->vtable,
+                                                          ei->context,
+                                                          ei->user_data);
+ out:
+  return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_append_header (GString *s)
+{
+  g_string_append (s,
+                   "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\";>\n"
+                   "<!-- GDBus 0.1 -->\n"
+                   "<node>\n");
+}
+
+static void
+introspect_append_standard_interfaces (GString *s)
+{
+  g_string_append (s,
+                   "  <interface name=\"org.freedesktop.DBus.Properties\">\n"
+                   "    <method name=\"Get\">\n"
+                   "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                   "      <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+                   "      <arg type=\"v\" name=\"value\" direction=\"out\"/>\n"
+                   "    </method>\n"
+                   "    <method name=\"GetAll\">\n"
+                   "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                   "      <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n"
+                   "    </method>\n"
+                   "    <method name=\"Set\">\n"
+                   "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                   "      <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+                   "      <arg type=\"v\" name=\"value\" direction=\"in\"/>\n"
+                   "    </method>\n"
+                   "    <signal name=\"PropertiesChanged\">\n"
+                   "      <arg type=\"s\" name=\"interface_name\"/>\n"
+                   "      <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"
+                   "    </signal>\n"
+                   "  </interface>\n"
+                   "  <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+                   "    <method name=\"Introspect\">\n"
+                   "      <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n"
+                   "    </method>\n"
+                   "  </interface>\n"
+                   "  <interface name=\"org.freedesktop.DBus.Peer\">\n"
+                   "    <method name=\"Ping\"/>\n"
+                   "    <method name=\"GetMachineId\">\n"
+                   "      <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n"
+                   "    </method>\n"
+                   "  </interface>\n");
+}
+
+static void
+maybe_add_path (const gchar *path, gsize path_len, const gchar *object_path, GHashTable *set)
+{
+  if (g_str_has_prefix (object_path, path) && strlen (object_path) >= path_len)
+    {
+      const gchar *begin;
+      const gchar *end;
+      gchar *s;
+
+      begin = object_path + path_len;
+      end = strchr (begin, '/');
+
+      if (end != NULL)
+        {
+          s = g_strndup (begin, end - begin);
+        }
+      else
+        {
+          s = g_strdup (begin);
+        }
+
+      if (g_hash_table_lookup (set, s) == NULL)
+        {
+          g_hash_table_insert (set, s, GUINT_TO_POINTER (1));
+        }
+      else
+        {
+          g_free (s);
+        }
+    }
+}
+
+/* TODO: we want a nicer public interface for this */
+static gchar **
+g_dbus_connection_list_registered_unlocked (GDBusConnection *connection,
+                                            const gchar     *path)
+{
+  GPtrArray *p;
+  gchar **ret;
+  GHashTableIter hash_iter;
+  const gchar *object_path;
+  gsize path_len;
+  GHashTable *set;
+  GList *keys;
+  GList *l;
+
+  CONNECTION_ENSURE_LOCK (connection);
+
+  path_len = strlen (path);
+  if (path_len > 1)
+    path_len++;
+
+  set = g_hash_table_new (g_str_hash, g_str_equal);
+
+  g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_eo);
+  while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL))
+    maybe_add_path (path, path_len, object_path, set);
+
+  g_hash_table_iter_init (&hash_iter, connection->priv->map_object_path_to_es);
+  while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL))
+    maybe_add_path (path, path_len, object_path, set);
+
+  p = g_ptr_array_new ();
+  keys = g_hash_table_get_keys (set);
+  for (l = keys; l != NULL; l = l->next)
+    {
+      g_ptr_array_add (p, l->data);
+    }
+  g_hash_table_unref (set);
+  g_list_free (keys);
+
+  g_ptr_array_add (p, NULL);
+  ret = (gchar **) g_ptr_array_free (p, FALSE);
+  return ret;
+}
+
+static gchar **
+g_dbus_connection_list_registered (GDBusConnection *connection,
+                                   const gchar     *path)
+{
+  gchar **ret;
+  CONNECTION_LOCK (connection);
+  ret = g_dbus_connection_list_registered_unlocked (connection, path);
+  CONNECTION_UNLOCK (connection);
+  return ret;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+handle_introspect (GDBusConnection *connection,
+                   ExportedObject  *eo,
+                   GDBusMessage    *message)
+{
+  guint n;
+  GString *s;
+  GDBusMessage *reply;
+  GHashTableIter hash_iter;
+  ExportedInterface *ei;
+  gchar **registered;
+
+  /* first the header with the standard interfaces */
+  s = g_string_new (NULL);
+  introspect_append_header (s);
+  introspect_append_standard_interfaces (s);
+
+  /* then include the registered interfaces */
+  g_hash_table_iter_init (&hash_iter, eo->map_if_name_to_ei);
+  while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &ei))
+    {
+      g_dbus_interface_info_generate_xml (ei->introspection_data, 2, s);
+    }
+
+  /* finally include nodes registered below us */
+  registered = g_dbus_connection_list_registered_unlocked (connection, eo->object_path);
+  for (n = 0; registered != NULL && registered[n] != NULL; n++)
+    {
+      g_string_append_printf (s, "  <node name=\"%s\"/>\n", registered[n]);
+    }
+  g_strfreev (registered);
+  g_string_append (s, "</node>\n");
+
+  reply = g_dbus_message_new_method_reply (message);
+  g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+  g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+  g_object_unref (reply);
+  g_string_free (s, TRUE);
+
+  return TRUE;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_method_in_idle_cb (gpointer user_data)
+{
+  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data);
+  GDBusInterfaceVTable *vtable;
+
+  vtable = g_object_get_data (G_OBJECT (invocation), "g-dbus-interface-vtable");
+  g_assert (vtable != NULL && vtable->method_call != NULL);
+
+  vtable->method_call (g_dbus_method_invocation_get_connection (invocation),
+                       g_dbus_method_invocation_get_sender (invocation),
+                       g_dbus_method_invocation_get_object_path (invocation),
+                       g_dbus_method_invocation_get_interface_name (invocation),
+                       g_dbus_method_invocation_get_method_name (invocation),
+                       g_dbus_method_invocation_get_parameters (invocation),
+                       g_object_ref (invocation),
+                       g_dbus_method_invocation_get_user_data (invocation));
+
+  return FALSE;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+validate_and_maybe_schedule_method_call (GDBusConnection            *connection,
+                                         GDBusMessage               *message,
+                                         const GDBusInterfaceInfo   *introspection_data,
+                                         const GDBusInterfaceVTable *vtable,
+                                         GMainContext               *main_context,
+                                         gpointer                    user_data)
+{
+  GDBusMethodInvocation *invocation;
+  const GDBusMethodInfo *method_info;
+  GDBusMessage *reply;
+  GVariant *parameters;
+  GSource *idle_source;
+  gboolean handled;
+  gchar *in_signature;
+
+  handled = FALSE;
+
+  /* TODO: the cost of this is O(n) - it might be worth caching the result */
+  method_info = g_dbus_interface_info_lookup_method (introspection_data, g_dbus_message_get_member (message));
+
+  /* if the method doesn't exist, return the org.freedesktop.DBus.Error.UnknownMethod
+   * error to the caller
+   */
+  if (method_info == NULL)
+    {
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.UnknownMethod",
+                                               _("No such method `%s'"),
+                                               g_dbus_message_get_member (message));
+      g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      handled = TRUE;
+      goto out;
+    }
+
+  /* Check that the incoming args are of the right type - if they are not, return
+   * the org.freedesktop.DBus.Error.InvalidArgs error to the caller
+   *
+   * TODO: might also be worth caching the combined signature.
+   */
+  in_signature = _g_dbus_compute_complete_signature (method_info->in_args, FALSE);
+  if (g_strcmp0 (g_dbus_message_get_signature (message), in_signature) != 0)
+    {
+      reply = g_dbus_message_new_method_error (message,
+                                               "org.freedesktop.DBus.Error.InvalidArgs",
+                                               _("Signature of message, `%s', does not match expected signature `%s'"),
+                                               g_dbus_message_get_signature (message),
+                                               in_signature);
+      g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+      g_object_unref (reply);
+      g_free (in_signature);
+      handled = TRUE;
+      goto out;
+    }
+  g_free (in_signature);
+
+  parameters = g_dbus_message_get_body (message);
+  if (parameters == NULL)
+    {
+      parameters = g_variant_new ("()");
+      g_variant_ref_sink (parameters);
+    }
+  else
+    {
+      g_variant_ref (parameters);
+    }
+
+  /* schedule the call in idle */
+  invocation = g_dbus_method_invocation_new (g_dbus_message_get_sender (message),
+                                             g_dbus_message_get_path (message),
+                                             g_dbus_message_get_interface (message),
+                                             g_dbus_message_get_member (message),
+                                             method_info,
+                                             connection,
+                                             message,
+                                             parameters,
+                                             user_data);
+  g_variant_unref (parameters);
+  g_object_set_data (G_OBJECT (invocation),
+                     "g-dbus-interface-vtable",
+                     (gpointer) vtable);
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         invoke_method_in_idle_cb,
+                         invocation,
+                         g_object_unref);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  handled = TRUE;
+
+ out:
+
+  return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in message handler thread with lock held */
+static gboolean
+obj_message_func (GDBusConnection *connection,
+                  ExportedObject  *eo,
+                  GDBusMessage    *message)
+{
+  const gchar *interface_name;
+  const gchar *member;
+  const gchar *signature;
+  gboolean handled;
+
+  handled = FALSE;
+
+  interface_name = g_dbus_message_get_interface (message);
+  member = g_dbus_message_get_member (message);
+  signature = g_dbus_message_get_signature (message);
+
+  /* see if we have an interface for handling this call */
+  if (interface_name != NULL)
+    {
+      ExportedInterface *ei;
+      ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+      if (ei != NULL)
+        {
+          /* we do - invoke the handler in idle in the right thread */
+
+          /* handle no vtable or handler being present */
+          if (ei->vtable == NULL || ei->vtable->method_call == NULL)
+            goto out;
+
+          handled = validate_and_maybe_schedule_method_call (connection,
+                                                             message,
+                                                             ei->introspection_data,
+                                                             ei->vtable,
+                                                             ei->context,
+                                                             ei->user_data);
+          goto out;
+        }
+    }
+
+  if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
+      g_strcmp0 (member, "Introspect") == 0 &&
+      g_strcmp0 (signature, "") == 0)
+    {
+      handled = handle_introspect (connection, eo, message);
+      goto out;
+    }
+  else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+           g_strcmp0 (member, "Get") == 0 &&
+           g_strcmp0 (signature, "ss") == 0)
+    {
+      handled = handle_getset_property (connection, eo, message, TRUE);
+      goto out;
+    }
+  else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+           g_strcmp0 (member, "Set") == 0 &&
+           g_strcmp0 (signature, "ssv") == 0)
+    {
+      handled = handle_getset_property (connection, eo, message, FALSE);
+      goto out;
+    }
+  else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 &&
+           g_strcmp0 (member, "GetAll") == 0 &&
+           g_strcmp0 (signature, "s") == 0)
+    {
+      handled = handle_get_all_properties (connection, eo, message);
+      goto out;
+    }
+
+ out:
+  return handled;
+}
+
+/**
+ * g_dbus_connection_register_object:
+ * @connection: A #GDBusConnection.
+ * @object_path: The object path to register at.
+ * @interface_name: The D-Bus interface to register.
+ * @introspection_data: Introspection data for the interface.
+ * @vtable: A #GDBusInterfaceVTable to call into or %NULL.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the object path is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers callbacks for exported objects at @object_path with the
+ * D-Bus interface @interface_name.
+ *
+ * Calls to functions in @vtable (and @user_data_free_func) will
+ * happen in the <link linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * Note that all #GVariant values passed to functions in @vtable will match
+ * the signature given in @introspection_data - if a remote caller passes
+ * incorrect values, the <literal>org.freedesktop.DBus.Error.InvalidArgs</literal>
+ * is returned to the remote caller.
+ *
+ * Additionally, if the remote caller attempts to invoke methods or
+ * access properties not mentioned in @introspection_data the
+ * <literal>org.freedesktop.DBus.Error.UnknownMethod</literal> resp.
+ * <literal>org.freedesktop.DBus.Error.InvalidArgs</literal> errors
+ * are returned to the caller.
+ *
+ * It is considered a programming error if the
+ * #GDBusInterfaceGetPropertyFunc function in @vtable returns a
+ * #GVariant of incorrect type.
+ *
+ * If an existing callback is already registered at @object_path and
+ * @interface_name, then @error is set to #G_IO_ERROR_EXISTS.
+ *
+ * See <xref linkend="gdbus-server"/> for an example of how to use this method.
+ *
+ * Returns: 0 if @error is set, otherwise a registration id (never 0)
+ * that can be used with g_dbus_connection_unregister_object() .
+ */
+guint
+g_dbus_connection_register_object (GDBusConnection            *connection,
+                                   const gchar                *object_path,
+                                   const gchar                *interface_name,
+                                   const GDBusInterfaceInfo   *introspection_data,
+                                   const GDBusInterfaceVTable *vtable,
+                                   gpointer                    user_data,
+                                   GDestroyNotify              user_data_free_func,
+                                   GError                    **error)
+{
+  ExportedObject *eo;
+  ExportedInterface *ei;
+  guint ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+  g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0);
+  g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0);
+  g_return_val_if_fail (introspection_data != NULL, 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  ret = 0;
+
+  CONNECTION_LOCK (connection);
+
+  eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path);
+  if (eo == NULL)
+    {
+      eo = g_new0 (ExportedObject, 1);
+      eo->object_path = g_strdup (object_path);
+      eo->connection = connection;
+      eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash,
+                                                     g_str_equal,
+                                                     NULL,
+                                                     (GDestroyNotify) exported_interface_free);
+      g_hash_table_insert (connection->priv->map_object_path_to_eo, eo->object_path, eo);
+    }
+
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei != NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_EXISTS,
+                   _("An object is already exported for the interface %s at %s"),
+                   interface_name,
+                   object_path);
+      goto out;
+    }
+
+  ei = g_new0 (ExportedInterface, 1);
+  ei->id = _global_registration_id++; /* TODO: overflow etc. */
+  ei->eo = eo;
+  ei->user_data = user_data;
+  ei->user_data_free_func = user_data_free_func;
+  ei->vtable = vtable;
+  ei->introspection_data = introspection_data;
+  ei->interface_name = g_strdup (interface_name);
+  ei->context = g_main_context_get_thread_default ();
+  if (ei->context != NULL)
+    g_main_context_ref (ei->context);
+
+  g_hash_table_insert (eo->map_if_name_to_ei,
+                       (gpointer) ei->interface_name,
+                       ei);
+  g_hash_table_insert (connection->priv->map_id_to_ei,
+                       GUINT_TO_POINTER (ei->id),
+                       ei);
+
+  ret = ei->id;
+
+ out:
+  CONNECTION_UNLOCK (connection);
+
+  return ret;
+}
+
+/**
+ * g_dbus_connection_unregister_object:
+ * @connection: A #GDBusConnection.
+ * @registration_id: A registration id obtained from g_dbus_connection_register_object().
+ *
+ * Unregisters an object.
+ *
+ * Returns: %TRUE if the object was unregistered, %FALSE otherwise.
+ */
+gboolean
+g_dbus_connection_unregister_object (GDBusConnection *connection,
+                                     guint            registration_id)
+{
+  ExportedInterface *ei;
+  ExportedObject *eo;
+  gboolean ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  ret = FALSE;
+
+  CONNECTION_LOCK (connection);
+
+  ei = g_hash_table_lookup (connection->priv->map_id_to_ei,
+                            GUINT_TO_POINTER (registration_id));
+  if (ei == NULL)
+    {
+      goto out;
+    }
+
+  eo = ei->eo;
+
+  g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_ei, GUINT_TO_POINTER (ei->id)));
+  g_warn_if_fail (g_hash_table_remove (eo->map_if_name_to_ei, ei->interface_name));
+  /* unregister object path if we have no more exported interfaces */
+  if (g_hash_table_size (eo->map_if_name_to_ei) == 0)
+    {
+      g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_eo,
+                                           eo->object_path));
+    }
+
+  ret = TRUE;
+
+ out:
+  CONNECTION_UNLOCK (connection);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_emit_signal:
+ * @connection: A #GDBusConnection.
+ * @destination_bus_name: The unique bus name for the destination for the signal or %NULL to emit to all listeners.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to emit a signal on.
+ * @signal_name: The name of the signal to emit.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @error: Return location for error or %NULL.
+ *
+ * Emits a signal.
+ *
+ * This can only fail if @parameters is not compatible with the D-Bus protocol.
+ *
+ * Returns: %TRUE unless @error is set.
+ */
+gboolean
+g_dbus_connection_emit_signal (GDBusConnection    *connection,
+                               const gchar        *destination_bus_name,
+                               const gchar        *object_path,
+                               const gchar        *interface_name,
+                               const gchar        *signal_name,
+                               GVariant           *parameters,
+                               GError            **error)
+{
+  GDBusMessage *message;
+  gboolean ret;
+
+  message = NULL;
+  ret = FALSE;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (destination_bus_name == NULL || g_dbus_is_name (destination_bus_name), FALSE);
+  g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), FALSE);
+  g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), FALSE);
+  g_return_val_if_fail (signal_name != NULL && g_dbus_is_member_name (signal_name), FALSE);
+  g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), FALSE);
+
+  message = g_dbus_message_new_signal (object_path,
+                                       interface_name,
+                                       signal_name);
+
+  if (destination_bus_name != NULL)
+    {
+      g_dbus_message_set_header (message,
+                                 G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION,
+                                 g_variant_new_string (destination_bus_name));
+    }
+
+  if (parameters != NULL)
+    g_dbus_message_set_body (message, parameters);
+
+  ret = g_dbus_connection_send_message (connection, message, NULL, error);
+  g_object_unref (message);
+
+  return ret;
+}
+
+static void
+add_invoke_method_flags (GDBusMessage *message,
+                         GDBusInvokeMethodFlags flags)
+{
+  if (flags & G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START)
+    g_dbus_message_set_flags (message, G_DBUS_MESSAGE_FLAGS_NO_AUTO_START);
+}
+
+/**
+ * g_dbus_connection_invoke_method:
+ * @connection: A #GDBusConnection.
+ * @bus_name: A unique or well-known bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will
+ * fail with %G_IO_ERROR_CANCELLED. If @parameters contains a value
+ * not compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_connection_invoke_method_finish() to get the result of the operation.
+ * See g_dbus_connection_invoke_method_sync() for the synchronous version of this
+ * function.
+ */
+void
+g_dbus_connection_invoke_method (GDBusConnection       *connection,
+                                 const gchar           *bus_name,
+                                 const gchar           *object_path,
+                                 const gchar           *interface_name,
+                                 const gchar           *method_name,
+                                 GVariant              *parameters,
+                                 GDBusInvokeMethodFlags flags,
+                                 gint                   timeout_msec,
+                                 GCancellable          *cancellable,
+                                 GAsyncReadyCallback    callback,
+                                 gpointer               user_data)
+{
+  GDBusMessage *message;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (bus_name == NULL || g_dbus_is_name (bus_name));
+  g_return_if_fail (object_path != NULL && g_variant_is_object_path (object_path));
+  g_return_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name));
+  g_return_if_fail (method_name != NULL && g_dbus_is_member_name (method_name));
+  g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1);
+  g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+
+  message = g_dbus_message_new_method_call (bus_name,
+                                            object_path,
+                                            interface_name,
+                                            method_name);
+  add_invoke_method_flags (message, flags);
+  if (parameters != NULL)
+    g_dbus_message_set_body (message, parameters);
+
+  g_dbus_connection_send_message_with_reply (connection,
+                                             message,
+                                             timeout_msec,
+                                             NULL, /* volatile guint32 *out_serial */
+                                             cancellable,
+                                             callback,
+                                             user_data);
+
+   if (message != NULL)
+    g_object_unref (message);
+}
+
+static GVariant *
+decode_method_reply (GDBusMessage *reply, GError **error)
+{
+  GVariant *result;
+
+  result = NULL;
+  switch (g_dbus_message_get_type (reply))
+    {
+    case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+      result = g_dbus_message_get_body (reply);
+      if (result == NULL)
+        {
+          result = g_variant_new ("()");
+          g_variant_ref_sink (result);
+        }
+      else
+        {
+          g_variant_ref (result);
+        }
+      break;
+    case G_DBUS_MESSAGE_TYPE_ERROR:
+      g_dbus_message_to_gerror (reply, error);
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return result;
+}
+
+/**
+ * g_dbus_connection_invoke_method_finish:
+ * @connection: A #GDBusConnection.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_connection_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_connection_invoke_method_finish (GDBusConnection    *connection,
+                                        GAsyncResult       *res,
+                                        GError            **error)
+{
+  GDBusMessage *reply;
+  GVariant *result;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  result = NULL;
+
+  reply = g_dbus_connection_send_message_with_reply_finish (connection, res, error);
+  if (reply == NULL)
+    goto out;
+
+  result = decode_method_reply (reply, error);
+
+  g_object_unref (reply);
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_invoke_method_sync:
+ * @connection: A #GDBusConnection.
+ * @bus_name: A unique or well-known bus name.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is closed then the operation will fail with
+ * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the
+ * operation will fail with %G_IO_ERROR_CANCELLED. If @parameters
+ * contains a value not compatible with the D-Bus protocol, the operation
+ * fails with %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * g_dbus_connection_invoke_method() for the asynchronous version of
+ * this method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_connection_invoke_method_sync (GDBusConnection       *connection,
+                                      const gchar           *bus_name,
+                                      const gchar           *object_path,
+                                      const gchar           *interface_name,
+                                      const gchar           *method_name,
+                                      GVariant              *parameters,
+                                      GDBusInvokeMethodFlags flags,
+                                      gint                   timeout_msec,
+                                      GCancellable          *cancellable,
+                                      GError               **error)
+{
+  GDBusMessage *message;
+  GDBusMessage *reply;
+  GVariant *result;
+
+  message = NULL;
+  reply = NULL;
+  result = NULL;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (bus_name == NULL || g_dbus_is_name (bus_name), NULL);
+  g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), NULL);
+  g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), NULL);
+  g_return_val_if_fail (method_name != NULL && g_dbus_is_member_name (method_name), NULL);
+  g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL);
+  g_return_val_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+
+  message = g_dbus_message_new_method_call (bus_name,
+                                            object_path,
+                                            interface_name,
+                                            method_name);
+  add_invoke_method_flags (message, flags);
+  if (parameters != NULL)
+    g_dbus_message_set_body (message, parameters);
+
+  reply = g_dbus_connection_send_message_with_reply_sync (connection,
+                                                          message,
+                                                          timeout_msec,
+                                                          NULL, /* volatile guint32 *out_serial */
+                                                          cancellable,
+                                                          error);
+
+  if (reply == NULL)
+    goto out;
+
+  result = decode_method_reply (reply, error);
+
+ out:
+  if (message != NULL)
+    g_object_unref (message);
+  if (reply != NULL)
+    g_object_unref (reply);
+
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedSubtree
+{
+  guint                     id;
+  gchar                    *object_path;
+  GDBusConnection          *connection;
+  const GDBusSubtreeVTable *vtable;
+  GDBusSubtreeFlags         flags;
+
+  GMainContext             *context;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+};
+
+static void
+exported_subtree_free (ExportedSubtree *es)
+{
+  if (es->user_data_free_func != NULL)
+    {
+      /* TODO: push to thread-default mainloop */
+      es->user_data_free_func (es->user_data);
+    }
+  if (es->context != NULL)
+    {
+      g_main_context_unref (es->context);
+    }
+  g_free (es->object_path);
+  g_free (es);
+}
+
+/* called without lock held */
+static gboolean
+handle_subtree_introspect (GDBusConnection  *connection,
+                           ExportedSubtree  *es,
+                           GDBusMessage     *message)
+{
+  GString *s;
+  gboolean handled;
+  GDBusMessage *reply;
+  gchar **children;
+  gboolean is_root;
+  const gchar *sender;
+  const gchar *requested_object_path;
+  const gchar *requested_node;
+  GPtrArray *interfaces;
+  guint n;
+  gchar **subnode_paths;
+
+  handled = FALSE;
+
+  requested_object_path = g_dbus_message_get_path (message);
+  sender = g_dbus_message_get_sender (message);
+  is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+  s = g_string_new (NULL);
+  introspect_append_header (s);
+
+  /* Strictly we don't need the children in dynamic mode, but we avoid the
+   * conditionals to preserve code clarity
+   */
+  children = es->vtable->enumerate (es->connection,
+                                    sender,
+                                    es->object_path,
+                                    es->user_data);
+
+  if (!is_root)
+    {
+      requested_node = strrchr (requested_object_path, '/') + 1;
+
+      /* Assert existence of object if we are not dynamic */
+      if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+          !_g_strv_has_string ((const gchar * const *) children, requested_node))
+        goto out;
+    }
+  else
+    {
+      requested_node = "/";
+    }
+
+  interfaces = es->vtable->introspect (es->connection,
+                                       sender,
+                                       es->object_path,
+                                       requested_node,
+                                       es->user_data);
+  if (interfaces != NULL)
+    {
+      if (interfaces->len > 0)
+        {
+          /* we're in business */
+          introspect_append_standard_interfaces (s);
+
+          for (n = 0; n < interfaces->len; n++)
+            {
+              const GDBusInterfaceInfo *interface_info = interfaces->pdata[n];
+              g_dbus_interface_info_generate_xml (interface_info, 2, s);
+            }
+        }
+      g_ptr_array_unref (interfaces);
+    }
+
+  /* then include <node> entries from the Subtree for the root */
+  if (is_root)
+    {
+      for (n = 0; children != NULL && children[n] != NULL; n++)
+        {
+          g_string_append_printf (s, "  <node name=\"%s\"/>\n", children[n]);
+        }
+    }
+
+  /* finally include nodes registered below us */
+  subnode_paths = g_dbus_connection_list_registered (es->connection, requested_object_path);
+  for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++)
+    {
+      g_string_append_printf (s, "  <node name=\"%s\"/>\n", subnode_paths[n]);
+    }
+  g_strfreev (subnode_paths);
+
+  g_string_append (s, "</node>\n");
+
+  reply = g_dbus_message_new_method_reply (message);
+  g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+  g_dbus_connection_send_message (connection, reply, NULL, NULL);
+  g_object_unref (reply);
+
+  handled = TRUE;
+
+ out:
+  g_string_free (s, TRUE);
+  g_strfreev (children);
+  return handled;
+}
+
+/* called without lock held */
+static gboolean
+handle_subtree_method_invocation (GDBusConnection  *connection,
+                                  ExportedSubtree  *es,
+                                  GDBusMessage     *message)
+{
+  gboolean handled;;
+  const gchar *sender;
+  const gchar *interface_name;
+  const gchar *member;
+  const gchar *signature;
+  const gchar *requested_object_path;
+  const gchar *requested_node;
+  gboolean is_root;
+  gchar **children;
+  const GDBusInterfaceInfo *introspection_data;
+  const GDBusInterfaceVTable *interface_vtable;
+  gpointer interface_user_data;
+  guint n;
+  GPtrArray *interfaces;
+  gboolean is_property_get;
+  gboolean is_property_set;
+  gboolean is_property_get_all;
+
+  handled = FALSE;
+  interfaces = NULL;
+
+  requested_object_path = g_dbus_message_get_path (message);
+  sender = g_dbus_message_get_sender (message);
+  interface_name = g_dbus_message_get_interface (message);
+  member = g_dbus_message_get_member (message);
+  signature = g_dbus_message_get_signature (message);
+  is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+  is_property_get = FALSE;
+  is_property_set = FALSE;
+  is_property_get_all = FALSE;
+  if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0)
+    {
+      if (g_strcmp0 (member, "Get") == 0 && g_strcmp0 (signature, "ss") == 0)
+        {
+          is_property_get = TRUE;
+        }
+      else if (g_strcmp0 (member, "Set") == 0 && g_strcmp0 (signature, "ssv") == 0)
+        {
+          is_property_set = TRUE;
+        }
+      else if (g_strcmp0 (member, "GetAll") == 0 && g_strcmp0 (signature, "s") == 0)
+        {
+          is_property_get_all = TRUE;
+        }
+    }
+
+  children = es->vtable->enumerate (es->connection,
+                                    sender,
+                                    es->object_path,
+                                    es->user_data);
+
+  if (!is_root)
+    {
+      requested_node = strrchr (requested_object_path, '/') + 1;
+
+      /* If not dynamic, skip if requested node is not part of children */
+      if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+          !_g_strv_has_string ((const gchar * const *) children, requested_node))
+        goto out;
+    }
+  else
+    {
+      requested_node = "/";
+    }
+
+  /* get introspection data for the node */
+  interfaces = es->vtable->introspect (es->connection,
+                                       sender,
+                                       requested_object_path,
+                                       requested_node,
+                                       es->user_data);
+  g_assert (interfaces != NULL);
+  introspection_data = NULL;
+  for (n = 0; n < interfaces->len; n++)
+    {
+      const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n];
+      if (g_strcmp0 (id_n->name, interface_name) == 0)
+        {
+          introspection_data = id_n;
+        }
+    }
+
+  /* dispatch the call if the user wants to handle it */
+  if (introspection_data != NULL)
+    {
+      /* figure out where to dispatch the method call */
+      interface_user_data = NULL;
+      interface_vtable = es->vtable->dispatch (es->connection,
+                                               sender,
+                                               es->object_path,
+                                               interface_name,
+                                               requested_node,
+                                               &interface_user_data,
+                                               es->user_data);
+      if (interface_vtable == NULL)
+        goto out;
+
+      CONNECTION_LOCK (connection);
+      handled = validate_and_maybe_schedule_method_call (es->connection,
+                                                         message,
+                                                         introspection_data,
+                                                         interface_vtable,
+                                                         es->context,
+                                                         interface_user_data);
+      CONNECTION_UNLOCK (connection);
+    }
+  /* handle org.freedesktop.DBus.Properties interface if not explicitly handled */
+  else if (is_property_get || is_property_set || is_property_get_all)
+    {
+      if (is_property_get)
+        g_variant_get (g_dbus_message_get_body (message), "(ss)", &interface_name, NULL);
+      else if (is_property_set)
+        g_variant_get (g_dbus_message_get_body (message), "(ssv)", &interface_name, NULL, NULL);
+      else if (is_property_get_all)
+        g_variant_get (g_dbus_message_get_body (message), "(s)", &interface_name, NULL, NULL);
+      else
+        g_assert_not_reached ();
+
+      /* see if the object supports this interface at all */
+      for (n = 0; n < interfaces->len; n++)
+        {
+          const GDBusInterfaceInfo *id_n = (const GDBusInterfaceInfo *) interfaces->pdata[n];
+          if (g_strcmp0 (id_n->name, interface_name) == 0)
+            {
+              introspection_data = id_n;
+            }
+        }
+
+      /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the user-code
+       * claims it won't support the interface
+       */
+      if (introspection_data == NULL)
+        {
+          GDBusMessage *reply;
+          reply = g_dbus_message_new_method_error (message,
+                                                   "org.freedesktop.DBus.Error.InvalidArgs",
+                                                   _("No such interface `%s'"),
+                                                   interface_name);
+          g_dbus_connection_send_message (es->connection, reply, NULL, NULL);
+          g_object_unref (reply);
+          handled = TRUE;
+          goto out;
+        }
+
+      /* figure out where to dispatch the property get/set/getall calls */
+      interface_user_data = NULL;
+      interface_vtable = es->vtable->dispatch (es->connection,
+                                               sender,
+                                               es->object_path,
+                                               interface_name,
+                                               requested_node,
+                                               &interface_user_data,
+                                               es->user_data);
+      if (interface_vtable == NULL)
+        goto out;
+
+      if (is_property_get || is_property_set)
+        {
+          CONNECTION_LOCK (connection);
+          handled = validate_and_maybe_schedule_property_getset (es->connection,
+                                                                 message,
+                                                                 is_property_get,
+                                                                 introspection_data,
+                                                                 interface_vtable,
+                                                                 es->context,
+                                                                 interface_user_data);
+          CONNECTION_UNLOCK (connection);
+        }
+      else if (is_property_get_all)
+        {
+          CONNECTION_LOCK (connection);
+          handled = validate_and_maybe_schedule_property_get_all (es->connection,
+                                                                  message,
+                                                                  introspection_data,
+                                                                  interface_vtable,
+                                                                  es->context,
+                                                                  interface_user_data);
+          CONNECTION_UNLOCK (connection);
+        }
+    }
+
+ out:
+  if (interfaces != NULL)
+    g_ptr_array_unref (interfaces);
+  g_strfreev (children);
+  return handled;
+}
+
+typedef struct
+{
+  GDBusMessage *message;
+  ExportedSubtree *es;
+} SubtreeDeferredData;
+
+static void
+subtree_deferred_data_free (SubtreeDeferredData *data)
+{
+  g_object_unref (data->message);
+  g_free (data);
+}
+
+/* called without lock held in the thread where the caller registered the subtree */
+static gboolean
+process_subtree_vtable_message_in_idle_cb (gpointer _data)
+{
+  SubtreeDeferredData *data = _data;
+  gboolean handled;
+
+  handled = FALSE;
+
+  if (g_strcmp0 (g_dbus_message_get_interface (data->message), "org.freedesktop.DBus.Introspectable") == 0 &&
+      g_strcmp0 (g_dbus_message_get_member (data->message), "Introspect") == 0 &&
+      g_strcmp0 (g_dbus_message_get_signature (data->message), "") == 0)
+    {
+      handled = handle_subtree_introspect (data->es->connection,
+                                           data->es,
+                                           data->message);
+    }
+  else
+    {
+      handled = handle_subtree_method_invocation (data->es->connection,
+                                                  data->es,
+                                                  data->message);
+    }
+
+
+  if (!handled)
+    {
+      CONNECTION_LOCK (data->es->connection);
+      handled = handle_generic_unlocked (data->es->connection, data->message);
+      CONNECTION_UNLOCK (data->es->connection);
+    }
+
+  /* if we couldn't handle the request, just bail with the UnknownMethod error */
+  if (!handled)
+    {
+      GDBusMessage *reply;
+      reply = g_dbus_message_new_method_error (data->message,
+                                               "org.freedesktop.DBus.Error.UnknownMethod",
+                                               _("Method `%s' on interface `%s' with signature `%s' does not exist"),
+                                               g_dbus_message_get_member (data->message),
+                                               g_dbus_message_get_interface (data->message),
+                                               g_dbus_message_get_signature (data->message));
+      g_dbus_connection_send_message (data->es->connection, reply, NULL, NULL);
+      g_object_unref (reply);
+    }
+
+  return FALSE;
+}
+
+/* called in message handler thread with lock held */
+static gboolean
+subtree_message_func (GDBusConnection *connection,
+                      ExportedSubtree *es,
+                      GDBusMessage    *message)
+{
+  GSource *idle_source;
+  SubtreeDeferredData *data;
+
+  data = g_new0 (SubtreeDeferredData, 1);
+  data->message = g_object_ref (message);
+  data->es = es;
+
+  /* defer this call to an idle handler in the right thread */
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         process_subtree_vtable_message_in_idle_cb,
+                         data,
+                         (GDestroyNotify) subtree_deferred_data_free);
+  g_source_attach (idle_source, es->context);
+  g_source_unref (idle_source);
+
+  /* since we own the entire subtree, handlers for objects not in the subtree have been
+   * tried already by libdbus-1 - so we just need to ensure that we're always going
+   * to reply to the message
+   */
+  return TRUE;
+}
+
+/**
+ * g_dbus_connection_register_subtree:
+ * @connection: A #GDBusConnection.
+ * @object_path: The object path to register the subtree at.
+ * @vtable: A #GDBusSubtreeVTable to enumerate, introspect and dispatch nodes in the subtree.
+ * @flags: Flags used to fine tune the behavior of the subtree.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the subtree is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers a whole subtree of <quote>dynamic</quote> objects.
+ *
+ * The @enumerate and @introspection functions in @vtable are used to
+ * convey, to remote callers, what nodes exist in the subtree rooted
+ * by @object_path.
+ *
+ * When handling remote calls into any node in the subtree, first the
+ * @enumerate function is used to check if the node exists. If the node exists
+ * or the #G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set
+ * the @introspection function is used to check if the node supports the
+ * requested method. If so, the @dispatch function is used to determine
+ * where to dispatch the call. The collected #GDBusInterfaceVTable and
+ * #gpointer will be used to call into the interface vtable for processing
+ * the request.
+ *
+ * All calls into user-provided code will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * If an existing subtree is already registered at @object_path or
+ * then @error is set to #G_IO_ERROR_EXISTS.
+ *
+ * Note that it is valid to register regular objects (using
+ * g_dbus_connection_register_object()) in a subtree registered with
+ * g_dbus_connection_register_subtree() - if so, the subtree handler
+ * is tried as the last resort. One way to think about a subtree
+ * handler is to consider it a <quote>fallback handler</quote>
+ * for object paths not registered via g_dbus_connection_register_object()
+ * or other bindings.
+ *
+ * See <xref linkend="gdbus-subtree-server"/> for an example of how to use this method.
+ *
+ * Returns: 0 if @error is set, otherwise a subtree registration id (never 0)
+ * that can be used with g_dbus_connection_unregister_subtree() .
+ */
+guint
+g_dbus_connection_register_subtree (GDBusConnection            *connection,
+                                    const gchar                *object_path,
+                                    const GDBusSubtreeVTable   *vtable,
+                                    GDBusSubtreeFlags           flags,
+                                    gpointer                    user_data,
+                                    GDestroyNotify              user_data_free_func,
+                                    GError                    **error)
+{
+  guint ret;
+  ExportedSubtree *es;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0);
+  g_return_val_if_fail (vtable != NULL, 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  ret = 0;
+
+  CONNECTION_LOCK (connection);
+
+  es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path);
+  if (es != NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_EXISTS,
+                   _("A subtree is already exported for %s"),
+                   object_path);
+      goto out;
+    }
+
+  es = g_new0 (ExportedSubtree, 1);
+  es->object_path = g_strdup (object_path);
+  es->connection = connection;
+
+  es->vtable = vtable;
+  es->flags = flags;
+  es->id = _global_subtree_registration_id++; /* TODO: overflow etc. */
+  es->user_data = user_data;
+  es->user_data_free_func = user_data_free_func;
+  es->context = g_main_context_get_thread_default ();
+  if (es->context != NULL)
+    g_main_context_ref (es->context);
+
+  g_hash_table_insert (connection->priv->map_object_path_to_es, es->object_path, es);
+  g_hash_table_insert (connection->priv->map_id_to_es,
+                       GUINT_TO_POINTER (es->id),
+                       es);
+
+  ret = es->id;
+
+ out:
+  CONNECTION_UNLOCK (connection);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_connection_unregister_subtree:
+ * @connection: A #GDBusConnection.
+ * @registration_id: A subtree registration id obtained from g_dbus_connection_register_subtree().
+ *
+ * Unregisters a subtree.
+ *
+ * Returns: %TRUE if the subtree was unregistered, %FALSE otherwise.
+ */
+gboolean
+g_dbus_connection_unregister_subtree (GDBusConnection *connection,
+                                      guint            registration_id)
+{
+  ExportedSubtree *es;
+  gboolean ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  ret = FALSE;
+
+  CONNECTION_LOCK (connection);
+
+  es = g_hash_table_lookup (connection->priv->map_id_to_es,
+                            GUINT_TO_POINTER (registration_id));
+  if (es == NULL)
+    {
+      goto out;
+    }
+
+  g_warn_if_fail (g_hash_table_remove (connection->priv->map_id_to_es, GUINT_TO_POINTER (es->id)));
+  g_warn_if_fail (g_hash_table_remove (connection->priv->map_object_path_to_es, es->object_path));
+
+  ret = TRUE;
+
+ out:
+  CONNECTION_UNLOCK (connection);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must be called with lock held */
+static void
+handle_generic_ping_unlocked (GDBusConnection  *connection,
+                              const gchar      *object_path,
+                              GDBusMessage     *message)
+{
+  GDBusMessage *reply;
+  reply = g_dbus_message_new_method_reply (message);
+  g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+  g_object_unref (reply);
+}
+
+/* must be called with lock held */
+static void
+handle_generic_get_machine_id_unlocked (GDBusConnection  *connection,
+                                        const gchar      *object_path,
+                                        GDBusMessage     *message)
+{
+  GDBusMessage *reply;
+
+  reply = NULL;
+  if (connection->priv->machine_id == NULL)
+    {
+      GError *error;
+      error = NULL;
+      /* TODO: use PACKAGE_LOCALSTATEDIR ? */
+      if (!g_file_get_contents ("/var/lib/dbus/machine-id",
+                                &connection->priv->machine_id,
+                                NULL,
+                                &error))
+        {
+          reply = g_dbus_message_new_method_error (message,
+                                                   "org.freedesktop.DBus.Error.Failed",
+                                                   _("Unable to load /var/lib/dbus/machine-id: %s"),
+                                                   error->message);
+          g_error_free (error);
+        }
+      else
+        {
+          g_strstrip (connection->priv->machine_id);
+          /* TODO: validate value */
+        }
+    }
+
+  if (reply == NULL)
+    {
+      reply = g_dbus_message_new_method_reply (message);
+      g_dbus_message_set_body (reply, g_variant_new ("(s)", connection->priv->machine_id));
+    }
+  g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+  g_object_unref (reply);
+}
+
+/* must be called with lock held */
+static void
+handle_generic_introspect_unlocked (GDBusConnection  *connection,
+                                    const gchar      *object_path,
+                                    GDBusMessage     *message)
+{
+  guint n;
+  GString *s;
+  gchar **registered;
+  GDBusMessage *reply;
+
+  /* first the header */
+  s = g_string_new (NULL);
+  introspect_append_header (s);
+
+  registered = g_dbus_connection_list_registered_unlocked (connection, object_path);
+  for (n = 0; registered != NULL && registered[n] != NULL; n++)
+    {
+      g_string_append_printf (s, "  <node name=\"%s\"/>\n", registered[n]);
+    }
+  g_strfreev (registered);
+  g_string_append (s, "</node>\n");
+
+  reply = g_dbus_message_new_method_reply (message);
+  g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str));
+  g_dbus_connection_send_message_unlocked (connection, reply, NULL, NULL);
+  g_object_unref (reply);
+  g_string_free (s, TRUE);
+}
+
+/* must be called with lock held */
+static gboolean
+handle_generic_unlocked (GDBusConnection *connection,
+                         GDBusMessage    *message)
+{
+  gboolean handled;
+  const gchar *interface_name;
+  const gchar *member;
+  const gchar *signature;
+  const gchar *path;
+
+  CONNECTION_ENSURE_LOCK (connection);
+
+  handled = FALSE;
+
+  interface_name = g_dbus_message_get_interface (message);
+  member = g_dbus_message_get_member (message);
+  signature = g_dbus_message_get_signature (message);
+  path = g_dbus_message_get_path (message);
+
+  if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 &&
+      g_strcmp0 (member, "Introspect") == 0 &&
+      g_strcmp0 (signature, "") == 0)
+    {
+      handle_generic_introspect_unlocked (connection, path, message);
+      handled = TRUE;
+    }
+  else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 &&
+           g_strcmp0 (member, "Ping") == 0 &&
+           g_strcmp0 (signature, "") == 0)
+    {
+      handle_generic_ping_unlocked (connection, path, message);
+      handled = TRUE;
+    }
+  else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 &&
+           g_strcmp0 (member, "GetMachineId") == 0 &&
+           g_strcmp0 (signature, "") == 0)
+    {
+      handle_generic_get_machine_id_unlocked (connection, path, message);
+      handled = TRUE;
+    }
+
+  return handled;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in message handler thread with lock held */
+static void
+distribute_method_call (GDBusConnection  *connection,
+                        GDBusMessage     *message)
+{
+  ExportedObject *eo;
+  ExportedSubtree *es;
+  const gchar *object_path;
+  const gchar *interface_name;
+  const gchar *member;
+  const gchar *signature;
+  const gchar *path;
+  gchar *subtree_path;
+  gchar *needle;
+
+  g_assert (g_dbus_message_get_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+  interface_name = g_dbus_message_get_interface (message);
+  member = g_dbus_message_get_member (message);
+  signature = g_dbus_message_get_signature (message);
+  path = g_dbus_message_get_path (message);
+  subtree_path = g_strdup (path);
+  needle = strrchr (subtree_path, '/');
+  if (needle != NULL && needle != subtree_path)
+    {
+      *needle = '\0';
+    }
+  else
+    {
+      g_free (subtree_path);
+      subtree_path = NULL;
+    }
+
+#if 0
+  g_debug ("interface    = `%s'", interface_name);
+  g_debug ("member       = `%s'", member);
+  g_debug ("signature    = `%s'", signature);
+  g_debug ("path         = `%s'", path);
+  g_debug ("subtree_path = `%s'", subtree_path != NULL ? subtree_path : "N/A");
+#endif
+
+  object_path = g_dbus_message_get_path (message);
+  g_assert (object_path != NULL);
+
+  eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path);
+  if (eo != NULL)
+    {
+      if (obj_message_func (connection, eo, message))
+        goto out;
+    }
+
+  es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path);
+  if (es != NULL)
+    {
+      if (subtree_message_func (connection, es, message))
+        goto out;
+    }
+
+  if (subtree_path != NULL)
+    {
+      es = g_hash_table_lookup (connection->priv->map_object_path_to_es, subtree_path);
+      if (es != NULL)
+        {
+          if (subtree_message_func (connection, es, message))
+            goto out;
+        }
+    }
+
+  if (handle_generic_unlocked (connection, message))
+    goto out;
+
+  /* if we end up here, the message has not been not handled */
+
+ out:
+  g_free (subtree_path);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusConnection **
+message_bus_get_singleton (GBusType   bus_type,
+                           GError   **error)
+{
+  GDBusConnection **ret;
+  const gchar *starter_bus;
+
+  ret = NULL;
+
+  switch (bus_type)
+    {
+    default:
+      g_assert_not_reached ();
+      break;
+
+    case G_BUS_TYPE_SESSION:
+      ret = &the_session_bus;
+      break;
+
+    case G_BUS_TYPE_SYSTEM:
+      ret = &the_system_bus;
+      break;
+
+    case G_BUS_TYPE_STARTER:
+      starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE");
+      if (g_strcmp0 (starter_bus, "session") == 0)
+        {
+          ret = message_bus_get_singleton (G_BUS_TYPE_SESSION, error);
+          goto out;
+        }
+      else if (g_strcmp0 (starter_bus, "system") == 0)
+        {
+          ret = message_bus_get_singleton (G_BUS_TYPE_SYSTEM, error);
+          goto out;
+        }
+      else
+        {
+          if (starter_bus != NULL)
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable"
+                             " - unknown value `%s'"),
+                           starter_bus);
+            }
+          else
+            {
+              g_set_error_literal (error,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_INVALID_ARGUMENT,
+                                   _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment "
+                                     "variable is not set"));
+            }
+        }
+      break;
+    }
+ out:
+  return ret;
+}
+
+static GDBusConnection *
+get_uninitialized_connection (GBusType            bus_type,
+                              GCancellable       *cancellable,
+                              GError            **error)
+{
+  GDBusConnection **singleton;
+  GDBusConnection *ret;
+
+  ret = NULL;
+
+  G_LOCK (message_bus_lock);
+  singleton = message_bus_get_singleton (bus_type, error);
+  if (singleton == NULL)
+    goto out;
+
+  if (*singleton == NULL)
+    {
+      gchar *address;
+      address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error);
+      if (address == NULL)
+        goto out;
+      ret = *singleton = g_object_new (G_TYPE_DBUS_CONNECTION,
+                                       "address", address,
+                                       "flags", G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                                G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                       "exit-on-close", TRUE,
+                                       NULL);
+    }
+  else
+    {
+      ret = g_object_ref (*singleton);
+    }
+
+  g_assert (ret != NULL);
+
+ out:
+  G_UNLOCK (message_bus_lock);
+  return ret;
+}
+
+/**
+ * g_bus_get_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects to the message bus specified by @bus_type.
+ * Note that the returned object may shared with other callers,
+ * e.g. if two separate parts of a process calls this function with
+ * the same @bus_type, they will share the same object.
+ *
+ * This is a synchronous failable function. See g_bus_get() and
+ * g_bus_get_finish() for the asynchronous version.
+ *
+ * The returned object is a singleton, that is, shared with other
+ * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the
+ * event that you need a private message bus connection, use
+ * g_dbus_address_get_for_bus() and
+ * g_dbus_connection_new_for_address().
+ *
+ * Note that the returned #GDBusConnection object will (usually) have
+ * the #GDBusConnection:exit-on-close property set to %TRUE.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_bus_get_sync (GBusType            bus_type,
+                GCancellable       *cancellable,
+                GError            **error)
+{
+  GDBusConnection *connection;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  connection = get_uninitialized_connection (bus_type, cancellable, error);
+  if (connection == NULL)
+    goto out;
+
+  if (!g_initable_init (G_INITABLE (connection), cancellable, error))
+    {
+      g_object_unref (connection);
+      connection = NULL;
+    }
+
+ out:
+  return connection;
+}
+
+static void
+bus_get_async_initable_cb (GObject      *source_object,
+                           GAsyncResult *res,
+                           gpointer      user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  GError *error;
+
+  error = NULL;
+  if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source_object),
+                                     res,
+                                     &error))
+    {
+      g_assert (error != NULL);
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+      g_object_unref (source_object);
+    }
+  else
+    {
+      g_simple_async_result_set_op_res_gpointer (simple,
+                                                 source_object,
+                                                 g_object_unref);
+    }
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+}
+
+/**
+ * g_bus_get:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects to the message bus specified by @bus_type.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call g_bus_get_finish() to get the result of the operation.
+ *
+ * This is a asynchronous failable function. See g_bus_get_sync() for
+ * the synchronous version.
+ */
+void
+g_bus_get (GBusType             bus_type,
+           GCancellable        *cancellable,
+           GAsyncReadyCallback  callback,
+           gpointer             user_data)
+{
+  GDBusConnection *connection;
+  GSimpleAsyncResult *simple;
+  GError *error;
+
+  simple = g_simple_async_result_new (NULL,
+                                      callback,
+                                      user_data,
+                                      g_bus_get);
+
+  error = NULL;
+  connection = get_uninitialized_connection (bus_type, cancellable, &error);
+  if (connection == NULL)
+    {
+      g_assert (error != NULL);
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+  else
+    {
+      g_async_initable_init_async (G_ASYNC_INITABLE (connection),
+                                   G_PRIORITY_DEFAULT,
+                                   cancellable,
+                                   bus_get_async_initable_cb,
+                                   simple);
+    }
+}
+
+/**
+ * g_bus_get_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_bus_get().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_bus_get().
+ *
+ * The returned object is a singleton, that is, shared with other
+ * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the
+ * event that you need a private message bus connection, use
+ * g_dbus_address_get_for_bus() and
+ * g_dbus_connection_new_for_address().
+ *
+ * Note that the returned #GDBusConnection object will (usually) have
+ * the #GDBusConnection:exit-on-close property set to %TRUE.
+ *
+ * Returns: A #GDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+GDBusConnection *
+g_bus_get_finish (GAsyncResult  *res,
+                  GError       **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GObject *object;
+  GDBusConnection *ret;
+
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_bus_get);
+
+  ret = NULL;
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  object = g_simple_async_result_get_op_res_gpointer (simple);
+  g_assert (object != NULL);
+  ret = g_object_ref (G_DBUS_CONNECTION (object));
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h
new file mode 100644
index 0000000..4eeed48
--- /dev/null
+++ b/gio/gdbusconnection.h
@@ -0,0 +1,467 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_CONNECTION_H__
+#define __G_DBUS_CONNECTION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_CONNECTION         (g_dbus_connection_get_type ())
+#define G_DBUS_CONNECTION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_CONNECTION, GDBusConnection))
+#define G_DBUS_CONNECTION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass))
+#define G_DBUS_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_CONNECTION, GDBusConnectionClass))
+#define G_IS_DBUS_CONNECTION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_CONNECTION))
+#define G_IS_DBUS_CONNECTION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_CONNECTION))
+
+typedef struct _GDBusConnectionClass   GDBusConnectionClass;
+typedef struct _GDBusConnectionPrivate GDBusConnectionPrivate;
+
+/**
+ * GDBusConnection:
+ *
+ * The #GDBusConnection structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusConnection
+{
+  /*< private >*/
+  GObject parent_instance;
+  GDBusConnectionPrivate *priv;
+};
+
+/**
+ * GDBusConnectionClass:
+ * @closed: Signal class handler for the #GDBusConnection::closed signal.
+ *
+ * Class structure for #GDBusConnection.
+ */
+struct _GDBusConnectionClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  /* Signals */
+  void (*closed) (GDBusConnection *connection,
+                  gboolean         remote_peer_vanished,
+                  GError          *error);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType            g_dbus_connection_get_type                   (void) G_GNUC_CONST;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void              g_bus_get                    (GBusType             bus_type,
+                                                GCancellable        *cancellable,
+                                                GAsyncReadyCallback  callback,
+                                                gpointer             user_data);
+GDBusConnection  *g_bus_get_finish             (GAsyncResult        *res,
+                                                GError             **error);
+GDBusConnection  *g_bus_get_sync               (GBusType            bus_type,
+                                                GCancellable       *cancellable,
+                                                GError            **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void             g_dbus_connection_new                        (GIOStream              *stream,
+                                                               const gchar            *guid,
+                                                               GDBusConnectionFlags    flags,
+                                                               GDBusAuthObserver      *auth_observer,
+                                                               GCancellable           *cancellable,
+                                                               GAsyncReadyCallback     callback,
+                                                               gpointer                user_data);
+GDBusConnection *g_dbus_connection_new_finish                 (GAsyncResult           *res,
+                                                               GError                **error);
+GDBusConnection *g_dbus_connection_new_sync                   (GIOStream              *stream,
+                                                               const gchar            *guid,
+                                                               GDBusConnectionFlags    flags,
+                                                               GDBusAuthObserver      *auth_observer,
+                                                               GCancellable           *cancellable,
+                                                               GError                **error);
+
+void             g_dbus_connection_new_for_address            (const gchar            *address,
+                                                               GDBusConnectionFlags    flags,
+                                                               GCancellable           *cancellable,
+                                                               GAsyncReadyCallback     callback,
+                                                               gpointer                user_data);
+GDBusConnection *g_dbus_connection_new_for_address_finish     (GAsyncResult           *res,
+                                                               GError                **error);
+GDBusConnection *g_dbus_connection_new_for_address_sync       (const gchar            *address,
+                                                               GDBusConnectionFlags    flags,
+                                                               GCancellable           *cancellable,
+                                                               GError                **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean         g_dbus_connection_is_closed                  (GDBusConnection    *connection);
+void             g_dbus_connection_close                      (GDBusConnection    *connection);
+GIOStream       *g_dbus_connection_get_stream                 (GDBusConnection    *connection);
+const gchar     *g_dbus_connection_get_guid                   (GDBusConnection    *connection);
+const gchar     *g_dbus_connection_get_unique_name            (GDBusConnection    *connection);
+GCredentials    *g_dbus_connection_get_peer_credentials       (GDBusConnection    *connection);
+gboolean         g_dbus_connection_get_exit_on_close          (GDBusConnection    *connection);
+void             g_dbus_connection_set_exit_on_close          (GDBusConnection    *connection,
+                                                               gboolean            exit_on_close);
+GDBusCapabilityFlags  g_dbus_connection_get_capabilities      (GDBusConnection    *connection);
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean         g_dbus_connection_send_message                   (GDBusConnection     *connection,
+                                                                   GDBusMessage        *message,
+                                                                   volatile guint32    *out_serial,
+                                                                   GError             **error);
+void             g_dbus_connection_send_message_with_reply        (GDBusConnection     *connection,
+                                                                   GDBusMessage        *message,
+                                                                   gint                 timeout_msec,
+                                                                   volatile guint32    *out_serial,
+                                                                   GCancellable        *cancellable,
+                                                                   GAsyncReadyCallback  callback,
+                                                                   gpointer             user_data);
+GDBusMessage    *g_dbus_connection_send_message_with_reply_finish (GDBusConnection     *connection,
+                                                                   GAsyncResult        *res,
+                                                                   GError             **error);
+GDBusMessage    *g_dbus_connection_send_message_with_reply_sync   (GDBusConnection     *connection,
+                                                                   GDBusMessage        *message,
+                                                                   gint                 timeout_msec,
+                                                                   volatile guint32    *out_serial,
+                                                                   GCancellable        *cancellable,
+                                                                   GError             **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gboolean  g_dbus_connection_emit_signal                       (GDBusConnection    *connection,
+                                                               const gchar        *destination_bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *signal_name,
+                                                               GVariant           *parameters,
+                                                               GError            **error);
+void      g_dbus_connection_invoke_method                     (GDBusConnection    *connection,
+                                                               const gchar        *bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *method_name,
+                                                               GVariant           *parameters,
+                                                               GDBusInvokeMethodFlags flags,
+                                                               gint                timeout_msec,
+                                                               GCancellable       *cancellable,
+                                                               GAsyncReadyCallback callback,
+                                                               gpointer            user_data);
+GVariant *g_dbus_connection_invoke_method_finish              (GDBusConnection    *connection,
+                                                               GAsyncResult       *res,
+                                                               GError            **error);
+GVariant *g_dbus_connection_invoke_method_sync                (GDBusConnection    *connection,
+                                                               const gchar        *bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *method_name,
+                                                               GVariant           *parameters,
+                                                               GDBusInvokeMethodFlags flags,
+                                                               gint                timeout_msec,
+                                                               GCancellable       *cancellable,
+                                                               GError            **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+/**
+ * GDBusInterfaceMethodCallFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @parameters: A #GVariant tuple with parameters.
+ * @invocation: A #GDBusMethodInvocation object that can be used to return a value or error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @method_call function in #GDBusInterfaceVTable.
+ */
+typedef void (*GDBusInterfaceMethodCallFunc) (GDBusConnection       *connection,
+                                              const gchar           *sender,
+                                              const gchar           *object_path,
+                                              const gchar           *interface_name,
+                                              const gchar           *method_name,
+                                              GVariant              *parameters,
+                                              GDBusMethodInvocation *invocation,
+                                              gpointer               user_data);
+
+/**
+ * GDBusInterfaceGetPropertyFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @error: Return location for error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @get_property function in #GDBusInterfaceVTable.
+ *
+ * Returns: A newly-allocated #GVariant with the value for @property_name or %NULL if @error is set.
+ */
+typedef GVariant *(*GDBusInterfaceGetPropertyFunc) (GDBusConnection       *connection,
+                                                    const gchar           *sender,
+                                                    const gchar           *object_path,
+                                                    const gchar           *interface_name,
+                                                    const gchar           *property_name,
+                                                    GError               **error,
+                                                    gpointer               user_data);
+
+/**
+ * GDBusInterfaceSetPropertyFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @value: The value to set the property to.
+ * @error: Return location for error.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * The type of the @set_property function in #GDBusInterfaceVTable.
+ *
+ * Returns: %TRUE if the property was set to @value, %FALSE if @error is set.
+ */
+typedef gboolean  (*GDBusInterfaceSetPropertyFunc) (GDBusConnection       *connection,
+                                                    const gchar           *sender,
+                                                    const gchar           *object_path,
+                                                    const gchar           *interface_name,
+                                                    const gchar           *property_name,
+                                                    GVariant              *value,
+                                                    GError               **error,
+                                                    gpointer               user_data);
+
+/**
+ * GDBusInterfaceVTable:
+ * @method_call: Function for handling incoming method calls.
+ * @get_property: Function for getting a property.
+ * @set_property: Function for setting a property.
+ *
+ * Virtual table for handling properties and method calls for a D-Bus
+ * interface.
+ *
+ * If you want to handle getting/setting D-Bus properties asynchronously, simply
+ * register an object with the <literal>org.freedesktop.DBus.Properties</literal>
+ * D-Bus interface using g_dbus_connection_register_object().
+ */
+struct _GDBusInterfaceVTable
+{
+  GDBusInterfaceMethodCallFunc  method_call;
+  GDBusInterfaceGetPropertyFunc get_property;
+  GDBusInterfaceSetPropertyFunc set_property;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+guint            g_dbus_connection_register_object            (GDBusConnection            *connection,
+                                                               const gchar                *object_path,
+                                                               const gchar                *interface_name,
+                                                               const GDBusInterfaceInfo   *introspection_data,
+                                                               const GDBusInterfaceVTable *vtable,
+                                                               gpointer                    user_data,
+                                                               GDestroyNotify              user_data_free_func,
+                                                               GError                    **error);
+gboolean         g_dbus_connection_unregister_object          (GDBusConnection            *connection,
+                                                               guint                       registration_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusSubtreeEnumerateFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @enumerate function in #GDBusSubtreeVTable.
+ *
+ * Returns: A newly allocated array of strings for node names that are children of @object_path.
+ */
+typedef gchar** (*GDBusSubtreeEnumerateFunc) (GDBusConnection       *connection,
+                                              const gchar           *sender,
+                                              const gchar           *object_path,
+                                              gpointer               user_data);
+
+/**
+ * GDBusSubtreeIntrospectFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @introspect function in #GDBusSubtreeVTable.
+ *
+ * Returns: A newly-allocated #GPtrArray with pointers to #GDBusInterfaceInfo describing
+ * the interfaces implemented by @node.
+ */
+typedef GPtrArray *(*GDBusSubtreeIntrospectFunc) (GDBusConnection       *connection,
+                                                  const gchar           *sender,
+                                                  const gchar           *object_path,
+                                                  const gchar           *node,
+                                                  gpointer               user_data);
+
+/**
+ * GDBusSubtreeDispatchFunc:
+ * @connection: A #GDBusConnection.
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with g_dbus_connection_register_subtree().
+ * @interface_name: The D-Bus interface name that the method call or property access is for.
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ * @out_user_data: Return location for user data to pass to functions in the returned #GDBusInterfaceVTable (never %NULL).
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_subtree().
+ *
+ * The type of the @dispatch function in #GDBusSubtreeVTable.
+ *
+ * Returns: A #GDBusInterfaceVTable or %NULL if you don't want to handle the methods.
+ */
+typedef const GDBusInterfaceVTable * (*GDBusSubtreeDispatchFunc) (GDBusConnection             *connection,
+                                                                  const gchar                 *sender,
+                                                                  const gchar                 *object_path,
+                                                                  const gchar                 *interface_name,
+                                                                  const gchar                 *node,
+                                                                  gpointer                    *out_user_data,
+                                                                  gpointer                     user_data);
+
+/**
+ * GDBusSubtreeVTable:
+ * @enumerate: Function for enumerating child nodes.
+ * @introspect: Function for introspecting a child node.
+ * @dispatch: Function for dispatching a remote call on a child node.
+ *
+ * Virtual table for handling subtrees registered with g_dbus_connection_register_subtree().
+ */
+struct _GDBusSubtreeVTable
+{
+  GDBusSubtreeEnumerateFunc  enumerate;
+  GDBusSubtreeIntrospectFunc introspect;
+  GDBusSubtreeDispatchFunc   dispatch;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+guint            g_dbus_connection_register_subtree           (GDBusConnection            *connection,
+                                                               const gchar                *object_path,
+                                                               const GDBusSubtreeVTable   *vtable,
+                                                               GDBusSubtreeFlags           flags,
+                                                               gpointer                    user_data,
+                                                               GDestroyNotify              user_data_free_func,
+                                                               GError                    **error);
+gboolean         g_dbus_connection_unregister_subtree         (GDBusConnection            *connection,
+                                                               guint                       registration_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusSignalCallback:
+ * @connection: A #GDBusConnection.
+ * @sender_name: The unique bus name of the sender of the signal.
+ * @object_path: The object path that the signal was emitted on.
+ * @interface_name: The name of the signal.
+ * @signal_name: The name of the signal.
+ * @parameters: A #GVariant tuple with parameters for the signal.
+ * @user_data: User data passed when subscribing to the signal.
+ *
+ * Signature for callback function used in g_dbus_connection_signal_subscribe().
+ */
+typedef void (*GDBusSignalCallback) (GDBusConnection  *connection,
+                                     const gchar      *sender_name,
+                                     const gchar      *object_path,
+                                     const gchar      *interface_name,
+                                     const gchar      *signal_name,
+                                     GVariant         *parameters,
+                                     gpointer          user_data);
+
+guint            g_dbus_connection_signal_subscribe           (GDBusConnection     *connection,
+                                                               const gchar         *sender,
+                                                               const gchar         *interface_name,
+                                                               const gchar         *member,
+                                                               const gchar         *object_path,
+                                                               const gchar         *arg0,
+                                                               GDBusSignalCallback  callback,
+                                                               gpointer             user_data,
+                                                               GDestroyNotify       user_data_free_func);
+void             g_dbus_connection_signal_unsubscribe         (GDBusConnection     *connection,
+                                                               guint                subscription_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * GDBusMessageFilterFunction:
+ * @connection: A #GDBusConnection.
+ * @message: A #GDBusMessage.
+ * @user_data: User data passed when adding the filter.
+ *
+ * Signature for function used in g_dbus_connection_add_filter().
+ *
+ * Returns: %TRUE if the filter handled @message, %FALSE to let other
+ * handlers run.
+ */
+typedef gboolean (*GDBusMessageFilterFunction) (GDBusConnection *connection,
+                                                GDBusMessage    *message,
+                                                gpointer         user_data);
+
+guint g_dbus_connection_add_filter (GDBusConnection            *connection,
+                                    GDBusMessageFilterFunction  filter_function,
+                                    gpointer                    user_data,
+                                    GDestroyNotify              user_data_free_func);
+
+void  g_dbus_connection_remove_filter (GDBusConnection    *connection,
+                                       guint               filter_id);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+G_END_DECLS
+
+#endif /* __G_DBUS_CONNECTION_H__ */
diff --git a/gio/gdbuserror.c b/gio/gdbuserror.c
new file mode 100644
index 0000000..3b0e080
--- /dev/null
+++ b/gio/gdbuserror.c
@@ -0,0 +1,847 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbuserror.h"
+#include "gioenums.h"
+#include "gioenumtypes.h"
+#include "gioerror.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbuserror
+ * @title: GDBusError
+ * @short_description: Mapping D-Bus errors to and from #GError
+ * @include: gdbus/gdbus.h
+ *
+ * All facilities that return errors from remote methods (such as
+ * g_dbus_connection_invoke_method_sync()) use #GError to represent
+ * both D-Bus errors (e.g. errors returned from the other peer) and
+ * locally in-process generated errors.
+ *
+ * To check if a returned #GError is an error from a remote peer, use
+ * g_dbus_error_is_remote_error(). To get the actual D-Bus error name,
+ * use g_dbus_error_get_remote_error(). Before presenting an error,
+ * always use g_dbus_error_strip_remote_error().
+ *
+ * In addition, facilities used to return errors to a remote peer also
+ * use #GError. See g_dbus_method_invocation_return_error() for
+ * discussion about how the D-Bus error name is set.
+ *
+ * Applications can associate a #GError error domain with a set of D-Bus errors in order to
+ * automatically map from D-Bus errors to #GError and back. This
+ * is typically done in the function returning the #GQuark for the
+ * error domain:
+ * <example id="error-registration"><title>Error Registration</title><programlisting>
+ * /<!-- -->* foo-bar-error.h: *<!-- -->/
+ *
+ * #define FOO_BAR_ERROR (foo_bar_error_quark ())
+ * GQuark foo_bar_error_quark (void);
+ *
+ * typedef enum
+ * {
+ *   FOO_BAR_ERROR_FAILED,
+ *   FOO_BAR_ERROR_ANOTHER_ERROR,
+ *   FOO_BAR_ERROR_SOME_THIRD_ERROR,
+ * } FooBarError;
+ *
+ * /<!-- -->* foo-bar-error.c: *<!-- -->/
+ *
+ * static const GDBusErrorEntry foo_bar_error_entries[] =
+ * {
+ *   {FOO_BAR_ERROR_FAILED,           "org.project.Foo.Bar.Error.Failed"},
+ *   {FOO_BAR_ERROR_ANOTHER_ERROR,    "org.project.Foo.Bar.Error.AnotherError"},
+ *   {FOO_BAR_ERROR_SOME_THIRD_ERROR, "org.project.Foo.Bar.Error.SomeThirdError"},
+ * };
+ *
+ * GQuark
+ * foo_bar_error_quark (void)
+ * {
+ *   static volatile gsize quark_volatile = 0;
+ *   g_dbus_error_register_error_domain ("foo-bar-error-quark",
+ *                                       &quark_volatile,
+ *                                       foo_bar_error_entries,
+ *                                       G_N_ELEMENTS (foo_bar_error_entries));
+ *   G_STATIC_ASSERT (G_N_ELEMENTS (foo_bar_error_entries) - 1 == FOO_BAR_ERROR_SOME_THIRD_ERROR);
+ *   return (GQuark) quark_volatile;
+ * }
+ * </programlisting></example>
+ * With this setup, a D-Bus peer can transparently pass e.g. %FOO_BAR_ERROR_ANOTHER_ERROR and
+ * other peers will see the D-Bus error name <literal>org.project.Foo.Bar.Error.AnotherError</literal>.
+ * If the other peer is using GDBus, the peer will see also %FOO_BAR_ERROR_ANOTHER_ERROR instead
+ * of %G_IO_ERROR_DBUS_ERROR. Note that GDBus clients can still recover
+ * <literal>org.project.Foo.Bar.Error.AnotherError</literal> using g_dbus_error_get_remote_error().
+ *
+ * Note that errors in the %G_DBUS_ERROR error domain is intended only
+ * for returning errors from a remote message bus process. Errors
+ * generated locally in-process by e.g. #GDBusConnection is from the
+ * %G_IO_ERROR domain.
+ */
+
+static const GDBusErrorEntry g_dbus_error_entries[] =
+{
+  {G_DBUS_ERROR_FAILED,                           "org.freedesktop.DBus.Error.Failed"},
+  {G_DBUS_ERROR_NO_MEMORY,                        "org.freedesktop.DBus.Error.NoMemory"},
+  {G_DBUS_ERROR_SERVICE_UNKNOWN,                  "org.freedesktop.DBus.Error.ServiceUnknown"},
+  {G_DBUS_ERROR_NAME_HAS_NO_OWNER,                "org.freedesktop.DBus.Error.NameHasNoOwner"},
+  {G_DBUS_ERROR_NO_REPLY,                         "org.freedesktop.DBus.Error.NoReply"},
+  {G_DBUS_ERROR_IO_ERROR,                         "org.freedesktop.DBus.Error.IOError"},
+  {G_DBUS_ERROR_BAD_ADDRESS,                      "org.freedesktop.DBus.Error.BadAddress"},
+  {G_DBUS_ERROR_NOT_SUPPORTED,                    "org.freedesktop.DBus.Error.NotSupported"},
+  {G_DBUS_ERROR_LIMITS_EXCEEDED,                  "org.freedesktop.DBus.Error.LimitsExceeded"},
+  {G_DBUS_ERROR_ACCESS_DENIED,                    "org.freedesktop.DBus.Error.AccessDenied"},
+  {G_DBUS_ERROR_AUTH_FAILED,                      "org.freedesktop.DBus.Error.AuthFailed"},
+  {G_DBUS_ERROR_NO_SERVER,                        "org.freedesktop.DBus.Error.NoServer"},
+  {G_DBUS_ERROR_TIMEOUT,                          "org.freedesktop.DBus.Error.Timeout"},
+  {G_DBUS_ERROR_NO_NETWORK,                       "org.freedesktop.DBus.Error.NoNetwork"},
+  {G_DBUS_ERROR_ADDRESS_IN_USE,                   "org.freedesktop.DBus.Error.AddressInUse"},
+  {G_DBUS_ERROR_DISCONNECTED,                     "org.freedesktop.DBus.Error.Disconnected"},
+  {G_DBUS_ERROR_INVALID_ARGS,                     "org.freedesktop.DBus.Error.InvalidArgs"},
+  {G_DBUS_ERROR_FILE_NOT_FOUND,                   "org.freedesktop.DBus.Error.FileNotFound"},
+  {G_DBUS_ERROR_FILE_EXISTS,                      "org.freedesktop.DBus.Error.FileExists"},
+  {G_DBUS_ERROR_UNKNOWN_METHOD,                   "org.freedesktop.DBus.Error.UnknownMethod"},
+  {G_DBUS_ERROR_TIMED_OUT,                        "org.freedesktop.DBus.Error.TimedOut"},
+  {G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,             "org.freedesktop.DBus.Error.MatchRuleNotFound"},
+  {G_DBUS_ERROR_MATCH_RULE_INVALID,               "org.freedesktop.DBus.Error.MatchRuleInvalid"},
+  {G_DBUS_ERROR_SPAWN_EXEC_FAILED,                "org.freedesktop.DBus.Error.Spawn.ExecFailed"},
+  {G_DBUS_ERROR_SPAWN_FORK_FAILED,                "org.freedesktop.DBus.Error.Spawn.ForkFailed"},
+  {G_DBUS_ERROR_SPAWN_CHILD_EXITED,               "org.freedesktop.DBus.Error.Spawn.ChildExited"},
+  {G_DBUS_ERROR_SPAWN_CHILD_SIGNALED,             "org.freedesktop.DBus.Error.Spawn.ChildSignaled"},
+  {G_DBUS_ERROR_SPAWN_FAILED,                     "org.freedesktop.DBus.Error.Spawn.Failed"},
+  {G_DBUS_ERROR_SPAWN_SETUP_FAILED,               "org.freedesktop.DBus.Error.Spawn.FailedToSetup"},
+  {G_DBUS_ERROR_SPAWN_CONFIG_INVALID,             "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"},
+  {G_DBUS_ERROR_SPAWN_SERVICE_INVALID,            "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"},
+  {G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,          "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"},
+  {G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,        "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"},
+  {G_DBUS_ERROR_SPAWN_FILE_INVALID,               "org.freedesktop.DBus.Error.Spawn.FileInvalid"},
+  {G_DBUS_ERROR_SPAWN_NO_MEMORY,                  "org.freedesktop.DBus.Error.Spawn.NoMemory"},
+  {G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,          "org.freedesktop.DBus.Error.UnixProcessIdUnknown"},
+  {G_DBUS_ERROR_INVALID_SIGNATURE,                "org.freedesktop.DBus.Error.InvalidSignature"},
+  {G_DBUS_ERROR_INVALID_FILE_CONTENT,             "org.freedesktop.DBus.Error.InvalidFileContent"},
+  {G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"},
+  {G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN,           "org.freedesktop.DBus.Error.AdtAuditDataUnknown"},
+  {G_DBUS_ERROR_OBJECT_PATH_IN_USE,               "org.freedesktop.DBus.Error.ObjectPathInUse"},
+};
+
+GQuark
+g_dbus_error_quark (void)
+{
+  static volatile gsize quark_volatile = 0;
+  g_dbus_error_register_error_domain ("g-dbus-error-quark",
+                                      &quark_volatile,
+                                      g_dbus_error_entries,
+                                      G_N_ELEMENTS (g_dbus_error_entries));
+  G_STATIC_ASSERT (G_N_ELEMENTS (g_dbus_error_entries) - 1 == G_DBUS_ERROR_OBJECT_PATH_IN_USE);
+  return (GQuark) quark_volatile;
+}
+
+/**
+ * g_dbus_error_register_error_domain:
+ * @error_domain_quark_name: The error domain name.
+ * @quark_volatile: A pointer where to store the #GQuark.
+ * @entries: A pointer to @num_entries #GDBusErrorEntry struct items.
+ * @num_entries: Number of items to register.
+ *
+ * Helper function for associating a #GError error domain with D-Bus error names.
+ */
+void
+g_dbus_error_register_error_domain (const gchar           *error_domain_quark_name,
+                                    volatile gsize        *quark_volatile,
+                                    const GDBusErrorEntry *entries,
+                                    guint                  num_entries)
+{
+  g_return_if_fail (error_domain_quark_name != NULL);
+  g_return_if_fail (quark_volatile != NULL);
+  g_return_if_fail (entries != NULL);
+  g_return_if_fail (num_entries > 0);
+
+  if (g_once_init_enter (quark_volatile))
+    {
+      guint n;
+      GQuark quark;
+
+      quark = g_quark_from_static_string (error_domain_quark_name);
+
+      for (n = 0; n < num_entries; n++)
+        {
+          g_warn_if_fail (g_dbus_error_register_error (quark,
+                                                       entries[n].error_code,
+                                                       entries[n].dbus_error_name));
+        }
+      g_once_init_leave (quark_volatile, quark);
+    }
+}
+
+static gboolean
+_g_dbus_error_decode_gerror (const gchar *dbus_name,
+                             GQuark      *out_error_domain,
+                             gint        *out_error_code)
+{
+  gboolean ret;
+  guint n;
+  GString *s;
+  gchar *domain_quark_string;
+
+  ret = FALSE;
+  s = NULL;
+
+  if (g_str_has_prefix (dbus_name, "org.gtk.GDBus.UnmappedGError.Quark._"))
+    {
+      s = g_string_new (NULL);
+
+      for (n = sizeof "org.gtk.GDBus.UnmappedGError.Quark._" - 1;
+           dbus_name[n] != '.' && dbus_name[n] != '\0';
+           n++)
+        {
+          if (g_ascii_isalnum (dbus_name[n]))
+            {
+              g_string_append_c (s, dbus_name[n]);
+            }
+          else if (dbus_name[n] == '_')
+            {
+              guint nibble_top;
+              guint nibble_bottom;
+
+              n++;
+
+              nibble_top = dbus_name[n];
+              if (nibble_top >= '0' && nibble_top <= '9')
+                nibble_top -= '0';
+              else if (nibble_top >= 'a' && nibble_top <= 'f')
+                nibble_top -= ('a' - 10);
+              else
+                goto not_mapped;
+
+              n++;
+
+              nibble_bottom = dbus_name[n];
+              if (nibble_bottom >= '0' && nibble_bottom <= '9')
+                nibble_bottom -= '0';
+              else if (nibble_bottom >= 'a' && nibble_bottom <= 'f')
+                nibble_bottom -= ('a' - 10);
+              else
+                goto not_mapped;
+
+              g_string_append_c (s, (nibble_top<<4) | nibble_bottom);
+            }
+          else
+            {
+              goto not_mapped;
+            }
+        }
+
+      if (!g_str_has_prefix (dbus_name + n, ".Code"))
+        goto not_mapped;
+
+      domain_quark_string = g_string_free (s, FALSE);
+      s = NULL;
+
+      if (out_error_domain != NULL)
+        *out_error_domain = g_quark_from_string (domain_quark_string);
+      g_free (domain_quark_string);
+
+      if (out_error_code != NULL)
+        *out_error_code = atoi (dbus_name + n + sizeof ".Code" - 1);
+
+      ret = TRUE;
+    }
+
+ not_mapped:
+
+  if (s != NULL)
+    g_string_free (s, TRUE);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GQuark error_domain;
+  gint   error_code;
+} QuarkCodePair;
+
+static guint
+quark_code_pair_hash_func (const QuarkCodePair *pair)
+{
+  gint val;
+  val = pair->error_domain + pair->error_code;
+  return g_int_hash (&val);
+}
+
+static gboolean
+quark_code_pair_equal_func (const QuarkCodePair *a,
+                            const QuarkCodePair *b)
+{
+  return (a->error_domain == b->error_domain) && (a->error_code == b->error_code);
+}
+
+typedef struct
+{
+  QuarkCodePair pair;
+  gchar *dbus_error_name;
+} RegisteredError;
+
+static void
+registered_error_free (RegisteredError *re)
+{
+  g_free (re->dbus_error_name);
+  g_free (re);
+}
+
+G_LOCK_DEFINE_STATIC (error_lock);
+
+/* maps from QuarkCodePair* -> RegisteredError* */
+static GHashTable *quark_code_pair_to_re = NULL;
+
+/* maps from gchar* -> RegisteredError* */
+static GHashTable *dbus_error_name_to_re = NULL;
+
+/**
+ * g_dbus_error_register_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Creates an association to map between @dbus_error_name and
+ * #GError<!-- -->s specified by @error_domain and @error_code.
+ *
+ * This is typically done in the routine that returns the #GQuark for
+ * an error domain.
+ *
+ * Returns: %TRUE if the association was created, %FALSE if it already
+ * exists.
+ */
+gboolean
+g_dbus_error_register_error (GQuark       error_domain,
+                             gint         error_code,
+                             const gchar *dbus_error_name)
+{
+  gboolean ret;
+  QuarkCodePair pair;
+  RegisteredError *re;
+
+  g_return_val_if_fail (dbus_error_name != NULL, FALSE);
+
+  ret = FALSE;
+
+  G_LOCK (error_lock);
+
+  if (quark_code_pair_to_re == NULL)
+    {
+      g_assert (dbus_error_name_to_re == NULL); /* check invariant */
+      quark_code_pair_to_re = g_hash_table_new ((GHashFunc) quark_code_pair_hash_func,
+                                                (GEqualFunc) quark_code_pair_equal_func);
+      dbus_error_name_to_re = g_hash_table_new_full (g_str_hash,
+                                                     g_str_equal,
+                                                     NULL,
+                                                     (GDestroyNotify) registered_error_free);
+    }
+
+  if (g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name) != NULL)
+    goto out;
+
+  pair.error_domain = error_domain;
+  pair.error_code = error_code;
+
+  if (g_hash_table_lookup (quark_code_pair_to_re, &pair) != NULL)
+    goto out;
+
+  re = g_new0 (RegisteredError, 1);
+  re->pair = pair;
+  re->dbus_error_name = g_strdup (dbus_error_name);
+
+  g_hash_table_insert (quark_code_pair_to_re, &(re->pair), re);
+  g_hash_table_insert (dbus_error_name_to_re, re->dbus_error_name, re);
+
+  ret = TRUE;
+
+ out:
+  G_UNLOCK (error_lock);
+  return ret;
+}
+
+/**
+ * g_dbus_error_unregister_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Destroys an association previously set up with g_dbus_error_register_error().
+ *
+ * Returns: %TRUE if the association was destroyed, %FALSE if it wasn't found.
+ */
+gboolean
+g_dbus_error_unregister_error (GQuark       error_domain,
+                               gint         error_code,
+                               const gchar *dbus_error_name)
+{
+  gboolean ret;
+  RegisteredError *re;
+  guint hash_size;
+
+  g_return_val_if_fail (dbus_error_name != NULL, FALSE);
+
+  ret = FALSE;
+
+  G_LOCK (error_lock);
+
+  if (dbus_error_name_to_re == NULL)
+    {
+      g_assert (quark_code_pair_to_re == NULL); /* check invariant */
+      goto out;
+    }
+
+  re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+  if (re == NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error_domain;
+      pair.error_code = error_code;
+      g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &pair) == NULL); /* check invariant */
+      goto out;
+    }
+  g_warn_if_fail (g_hash_table_lookup (quark_code_pair_to_re, &(re->pair)) == re); /* check invariant */
+
+  g_warn_if_fail (g_hash_table_remove (quark_code_pair_to_re, &(re->pair)));
+  g_warn_if_fail (g_hash_table_remove (dbus_error_name_to_re, re));
+
+  /* destroy hashes if empty */
+  hash_size = g_hash_table_size (dbus_error_name_to_re);
+  if (hash_size == 0)
+    {
+      g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == 0); /* check invariant */
+
+      g_hash_table_unref (dbus_error_name_to_re);
+      dbus_error_name_to_re = NULL;
+      g_hash_table_unref (quark_code_pair_to_re);
+      quark_code_pair_to_re = NULL;
+    }
+  else
+    {
+      g_warn_if_fail (g_hash_table_size (quark_code_pair_to_re) == hash_size); /* check invariant */
+    }
+
+ out:
+  G_UNLOCK (error_lock);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_error_is_remote_error:
+ * @error: A #GError.
+ *
+ * Checks if @error represents an error received via D-Bus from a remote peer. If so,
+ * use g_dbus_error_get_remote_error() to get the name of the error.
+ *
+ * Returns: %TRUE if @error represents an error from a remote peer,
+ * %FALSE otherwise.
+ */
+gboolean
+g_dbus_error_is_remote_error (const GError *error)
+{
+  g_return_val_if_fail (error != NULL, FALSE);
+  return g_str_has_prefix (error->message, "GDBus.Error:");
+}
+
+
+/**
+ * g_dbus_error_get_remote_error:
+ * @error: A #GError.
+ *
+ * Gets the D-Bus error name used for @error, if any.
+ *
+ * This function is guaranteed to return a D-Bus error name for all #GError<!-- -->s returned from
+ * functions handling remote method calls (e.g. g_dbus_connection_invoke_method_finish())
+ * unless g_dbus_error_strip_remote_error() has been used on @error.
+ *
+ * Returns: An allocated string or %NULL if the D-Bus error name could not be found. Free with g_free().
+ */
+gchar *
+g_dbus_error_get_remote_error (const GError *error)
+{
+  RegisteredError *re;
+  gchar *ret;
+
+  g_return_val_if_fail (error != NULL, NULL);
+
+  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+  _g_dbus_initialize ();
+
+  ret = NULL;
+
+  G_LOCK (error_lock);
+
+  re = NULL;
+  if (quark_code_pair_to_re != NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error->domain;
+      pair.error_code = error->code;
+      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+    }
+
+  if (re != NULL)
+    {
+      ret = g_strdup (re->dbus_error_name);
+    }
+  else
+    {
+      if (g_str_has_prefix (error->message, "GDBus.Error:"))
+        {
+          const gchar *begin;
+          const gchar *end;
+          begin = error->message + sizeof ("GDBus.Error:") -1;
+          end = strstr (begin, ":");
+          if (end != NULL && end[1] == ' ')
+            {
+              ret = g_strndup (begin, end - begin);
+            }
+        }
+    }
+
+  G_UNLOCK (error_lock);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_error_new_for_dbus_error:
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ *
+ * Creates a #GError based on the contents of @dbus_error_name and
+ * @dbus_error_message.
+ *
+ * Errors registered with g_dbus_error_register_error() will be looked
+ * up using @dbus_error_name and if a match is found, the error domain
+ * and code is used. Applications can use g_dbus_error_get_remote_error()
+ * to recover @dbus_error_name.
+ *
+ * If a match against a registered error is not found and the D-Bus
+ * error name is in a form as returned by g_dbus_error_encode_gerror()
+ * the error domain and code encoded in the name is used to
+ * create the #GError. Also, @dbus_error_name is added to the error message
+ * such that it can be recovered with g_dbus_error_get_remote_error().
+ *
+ * Otherwise, a #GError with the error code %G_IO_ERROR_DBUS_ERROR
+ * in the #G_IO_ERROR error domain is returned. Also, @dbus_error_name is
+ * added to the error message such that it can be recovered with
+ * g_dbus_error_get_remote_error().
+ *
+ * In all three cases, @dbus_error_name can always be recovered from the
+ * returned #GError using the g_dbus_error_get_remote_error() function
+ * (unless g_dbus_error_strip_remote_error() hasn't been used on the returned error).
+ *
+ * This function is typically only used in object mappings to prepare
+ * #GError instances for applications. Regular applications should not use
+ * it.
+ *
+ * Returns: An allocated #GError. Free with g_error_free().
+ */
+GError *
+g_dbus_error_new_for_dbus_error (const gchar *dbus_error_name,
+                                 const gchar *dbus_error_message)
+{
+  GError *error;
+  RegisteredError *re;
+
+  g_return_val_if_fail (dbus_error_name != NULL, NULL);
+  g_return_val_if_fail (dbus_error_message != NULL, NULL);
+
+  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+  _g_dbus_initialize ();
+
+  G_LOCK (error_lock);
+
+  re = NULL;
+  if (dbus_error_name_to_re != NULL)
+    {
+      g_assert (quark_code_pair_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+    }
+
+  if (re != NULL)
+    {
+      error = g_error_new (re->pair.error_domain,
+                           re->pair.error_code,
+                           "GDBus.Error:%s: %s",
+                           dbus_error_name,
+                           dbus_error_message);
+    }
+  else
+    {
+      GQuark error_domain = 0;
+      gint error_code = 0;
+
+      if (_g_dbus_error_decode_gerror (dbus_error_name,
+                                       &error_domain,
+                                       &error_code))
+        {
+          error = g_error_new (error_domain,
+                               error_code,
+                               "GDBus.Error:%s: %s",
+                               dbus_error_name,
+                               dbus_error_message);
+        }
+      else
+        {
+          error = g_error_new (G_IO_ERROR,
+                               G_IO_ERROR_DBUS_ERROR,
+                               "GDBus.Error:%s: %s",
+                               dbus_error_name,
+                               dbus_error_message);
+        }
+    }
+
+  G_UNLOCK (error_lock);
+  return error;
+}
+
+/**
+ * g_dbus_error_set_dbus_error:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @...: Arguments for @format.
+ *
+ * Does nothing if @error is %NULL. Otherwise sets * error to
+ * a new #GError created with g_dbus_error_new_for_dbus_error()
+ * with @dbus_error_message prepend with @format (unless %NULL).
+ */
+void
+g_dbus_error_set_dbus_error (GError      **error,
+                             const gchar  *dbus_error_name,
+                             const gchar  *dbus_error_message,
+                             const gchar  *format,
+                             ...)
+{
+  g_return_if_fail (error == NULL || *error == NULL);
+  g_return_if_fail (dbus_error_name != NULL);
+  g_return_if_fail (dbus_error_message != NULL);
+
+  if (error == NULL)
+    return;
+
+  if (format == NULL)
+    {
+      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+    }
+  else
+    {
+      va_list var_args;
+      va_start (var_args, format);
+      g_dbus_error_set_dbus_error_valist (error,
+                                          dbus_error_name,
+                                          dbus_error_message,
+                                          format,
+                                          var_args);
+      va_end (var_args);
+    }
+}
+
+/**
+ * g_dbus_error_set_dbus_error_valist:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @var_args: Arguments for @format.
+ *
+ * Like g_dbus_error_set_dbus_error() but intended for language bindings.
+ */
+void
+g_dbus_error_set_dbus_error_valist (GError      **error,
+                                    const gchar  *dbus_error_name,
+                                    const gchar  *dbus_error_message,
+                                    const gchar  *format,
+                                    va_list       var_args)
+{
+  g_return_if_fail (error == NULL || *error == NULL);
+  g_return_if_fail (dbus_error_name != NULL);
+  g_return_if_fail (dbus_error_message != NULL);
+
+  if (error == NULL)
+    return;
+
+  if (format != NULL)
+    {
+      gchar *message;
+      gchar *s;
+      message = g_strdup_vprintf (format, var_args);
+      s = g_strdup_printf ("%s: %s", message, dbus_error_message);
+      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, s);
+      g_free (s);
+      g_free (message);
+    }
+  else
+    {
+      *error = g_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+    }
+}
+
+/**
+ * g_dbus_error_strip_remote_error:
+ * @error: A #GError.
+ *
+ * Looks for extra information in the error message used to recover
+ * the D-Bus error name and strips it if found. If stripped, the
+ * message field in @error will correspond exactly to what was
+ * received on the wire.
+ *
+ * This is typically used when presenting errors to the end user.
+ *
+ * Returns: %TRUE if information was stripped, %FALSE otherwise.
+ */
+gboolean
+g_dbus_error_strip_remote_error (GError *error)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (error != NULL, FALSE);
+
+  ret = FALSE;
+
+  if (g_str_has_prefix (error->message, "GDBus.Error:"))
+    {
+      const gchar *begin;
+      const gchar *end;
+      gchar *new_message;
+
+      begin = error->message + sizeof ("GDBus.Error:") -1;
+      end = strstr (begin, ":");
+      if (end != NULL && end[1] == ' ')
+        {
+          new_message = g_strdup (end + 2);
+          g_free (error->message);
+          error->message = new_message;
+          ret = TRUE;
+        }
+    }
+
+  return ret;
+}
+
+/**
+ * g_dbus_error_encode_gerror:
+ * @error: A #GError.
+ *
+ * Creates a D-Bus error name to use for @error. If @error matches
+ * a registered error (cf. g_dbus_error_register_error()), the corresponding
+ * D-Bus error name will be returned.
+ *
+ * Otherwise the a name of the form
+ * <literal>org.gtk.GDBus.UnmappedGError.Quark._ESCAPED_QUARK_NAME.Code_ERROR_CODE</literal>
+ * will be used. This allows other GDBus applications to map the error
+ * on the wire back to a #GError using g_dbus_error_new_for_dbus_error().
+ *
+ * This function is typically only used in object mappings to put a
+ * #GError on the wire. Regular applications should not use it.
+ *
+ * Returns: A D-Bus error name (never %NULL). Free with g_free().
+ */
+gchar *
+g_dbus_error_encode_gerror (const GError *error)
+{
+  RegisteredError *re;
+  gchar *error_name;
+
+  g_return_val_if_fail (error != NULL, NULL);
+
+  /* Ensure that e.g. G_DBUS_ERROR is registered using g_dbus_error_register_error() */
+  _g_dbus_initialize ();
+
+  error_name = NULL;
+
+  G_LOCK (error_lock);
+  re = NULL;
+  if (quark_code_pair_to_re != NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error->domain;
+      pair.error_code = error->code;
+      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+    }
+  if (re != NULL)
+    {
+      error_name = g_strdup (re->dbus_error_name);
+      G_UNLOCK (error_lock);
+    }
+  else
+    {
+      const gchar *domain_as_string;
+      GString *s;
+      guint n;
+
+      G_UNLOCK (error_lock);
+
+      /* We can't make a lot of assumptions about what domain_as_string
+       * looks like and D-Bus is extremely picky about error names so
+       * hex-encode it for transport across the wire.
+       */
+      domain_as_string = g_quark_to_string (error->domain);
+      s = g_string_new ("org.gtk.GDBus.UnmappedGError.Quark._");
+      for (n = 0; domain_as_string[n] != 0; n++)
+        {
+          gint c = domain_as_string[n];
+          if (g_ascii_isalnum (c))
+            {
+              g_string_append_c (s, c);
+            }
+          else
+            {
+              guint nibble_top;
+              guint nibble_bottom;
+              g_string_append_c (s, '_');
+              nibble_top = ((int) domain_as_string[n]) >> 4;
+              nibble_bottom = ((int) domain_as_string[n]) & 0x0f;
+              if (nibble_top < 10)
+                nibble_top += '0';
+              else
+                nibble_top += 'a' - 10;
+              if (nibble_bottom < 10)
+                nibble_bottom += '0';
+              else
+                nibble_bottom += 'a' - 10;
+              g_string_append_c (s, nibble_top);
+              g_string_append_c (s, nibble_bottom);
+            }
+        }
+      g_string_append_printf (s, ".Code%d", error->code);
+      error_name = g_string_free (s, FALSE);
+    }
+
+  return error_name;
+}
diff --git a/gio/gdbuserror.h b/gio/gdbuserror.h
new file mode 100644
index 0000000..d15724c
--- /dev/null
+++ b/gio/gdbuserror.h
@@ -0,0 +1,92 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_ERROR_H__
+#define __G_DBUS_ERROR_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * G_DBUS_ERROR:
+ *
+ * Error domain for errors generated by a remote message bus. Errors
+ * in this domain will be from the #GDBusError enumeration.  See
+ * #GError for more information on error domains.
+ *
+ * Note that errors in this error domain is intended only for
+ * returning errors from a remote message bus process. Errors
+ * generated locally in-process by e.g. #GDBusConnection is from the
+ * %G_IO_ERROR domain.
+ */
+#define G_DBUS_ERROR g_dbus_error_quark()
+
+GQuark g_dbus_error_quark (void);
+
+/* Used by applications to check, get and strip the D-Bus error name */
+gboolean g_dbus_error_is_remote_error       (const GError    *error);
+gchar   *g_dbus_error_get_remote_error      (const GError    *error);
+gboolean g_dbus_error_strip_remote_error    (GError          *error);
+
+/**
+ * GDBusErrorEntry:
+ * @error_code: An error code.
+ * @dbus_error_name: The D-Bus error name to associate with @error_code.
+ *
+ * Struct used in g_dbus_error_register_error_domain().
+ */
+struct _GDBusErrorEntry
+{
+  gint         error_code;
+  const gchar *dbus_error_name;
+};
+
+gboolean g_dbus_error_register_error        (GQuark                 error_domain,
+                                             gint                   error_code,
+                                             const gchar           *dbus_error_name);
+gboolean g_dbus_error_unregister_error      (GQuark                 error_domain,
+                                             gint                   error_code,
+                                             const gchar           *dbus_error_name);
+void     g_dbus_error_register_error_domain (const gchar           *error_domain_quark_name,
+                                             volatile gsize        *quark_volatile,
+                                             const GDBusErrorEntry *entries,
+                                             guint                  num_entries);
+
+/* Only used by object mappings to map back and forth to GError */
+GError  *g_dbus_error_new_for_dbus_error    (const gchar     *dbus_error_name,
+                                             const gchar     *dbus_error_message);
+void     g_dbus_error_set_dbus_error        (GError         **error,
+                                             const gchar     *dbus_error_name,
+                                             const gchar     *dbus_error_message,
+                                             const gchar     *format,
+                                             ...);
+void     g_dbus_error_set_dbus_error_valist (GError         **error,
+                                             const gchar     *dbus_error_name,
+                                             const gchar     *dbus_error_message,
+                                             const gchar     *format,
+                                             va_list          var_args);
+gchar   *g_dbus_error_encode_gerror         (const GError    *error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_ERROR_H__ */
diff --git a/gio/gdbusintrospection.c b/gio/gdbusintrospection.c
new file mode 100644
index 0000000..63b945e
--- /dev/null
+++ b/gio/gdbusintrospection.c
@@ -0,0 +1,2009 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusintrospection.h"
+
+/**
+ * SECTION:gdbusintrospection
+ * @title: Introspection XML
+ * @short_description: Parse and Generate Introspection XML
+ * @include: gdbus/gdbus.h
+ *
+ * Various data structures and convenience routines to parse and
+ * generate D-Bus introspection XML.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* See also https://bugzilla.gnome.org/show_bug.cgi?id=449565 ... */
+#define _MY_DEFINE_BOXED_TYPE(TypeName, type_name)                     \
+  GType                                                                 \
+  type_name##_get_type (void)                                           \
+  {                                                                     \
+    static volatile gsize type_volatile = 0;                            \
+    if (g_once_init_enter (&type_volatile))                             \
+      {                                                                 \
+        GType type = g_boxed_type_register_static (g_intern_static_string (#TypeName),  \
+                                                   (GBoxedCopyFunc) type_name##_ref,    \
+                                                   (GBoxedFreeFunc) type_name##_unref); \
+        g_once_init_leave (&type_volatile, type);                       \
+      }                                                                 \
+    return (GType) type_volatile;                                       \
+  }
+
+_MY_DEFINE_BOXED_TYPE (GDBusNodeInfo,       g_dbus_node_info);
+_MY_DEFINE_BOXED_TYPE (GDBusInterfaceInfo,  g_dbus_interface_info);
+_MY_DEFINE_BOXED_TYPE (GDBusMethodInfo,     g_dbus_method_info);
+_MY_DEFINE_BOXED_TYPE (GDBusPropertyInfo,   g_dbus_property_info);
+_MY_DEFINE_BOXED_TYPE (GDBusArgInfo,        g_dbus_arg_info);
+_MY_DEFINE_BOXED_TYPE (GDBusAnnotationInfo, g_dbus_annotation_info);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  /* stuff we are currently collecting */
+  GPtrArray *args;
+  GPtrArray *out_args;
+  GPtrArray *methods;
+  GPtrArray *signals;
+  GPtrArray *properties;
+  GPtrArray *interfaces;
+  GPtrArray *nodes;
+  GPtrArray *annotations;
+
+  /* A list of GPtrArray's containing annotations */
+  GSList *annotations_stack;
+
+  /* A list of GPtrArray's containing interfaces */
+  GSList *interfaces_stack;
+
+  /* A list of GPtrArray's containing nodes */
+  GSList *nodes_stack;
+
+  /* Whether the direction was "in" for last parsed arg */
+  gboolean last_arg_was_in;
+
+  /* Number of args currently being collected; used for assigning
+   * names to args without a "name" attribute
+   */
+  guint num_args;
+
+} ParseData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_ref:
+ * @info: A #GDBusNodeInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusNodeInfo *
+g_dbus_node_info_ref (GDBusNodeInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_interface_info_ref:
+ * @info: A #GDBusInterfaceInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusInterfaceInfo *
+g_dbus_interface_info_ref (GDBusInterfaceInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_method_info_ref:
+ * @info: A #GDBusMethodInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusMethodInfo *
+g_dbus_method_info_ref (GDBusMethodInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_signal_info_ref:
+ * @info: A #GDBusSignalInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusSignalInfo *
+g_dbus_signal_info_ref (GDBusSignalInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_property_info_ref:
+ * @info: A #GDBusPropertyInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusPropertyInfo *
+g_dbus_property_info_ref (GDBusPropertyInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_arg_info_ref:
+ * @info: A #GDBusArgInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusArgInfo *
+g_dbus_arg_info_ref (GDBusArgInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/**
+ * g_dbus_node_info_ref:
+ * @info: A #GDBusNodeInfo
+ *
+ * If @info is statically allocated does nothing. Otherwise increases
+ * the reference count.
+ *
+ * Returns: The same @info.
+ */
+GDBusAnnotationInfo *
+g_dbus_annotation_info_ref (GDBusAnnotationInfo *info)
+{
+  if (info->ref_count == -1)
+    return info;
+  g_atomic_int_inc (&info->ref_count);
+  return info;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+free_null_terminated_array (gpointer array, GDestroyNotify unref_func)
+{
+  guint n;
+  gpointer *p = array;
+  if (p == NULL)
+    return;
+  for (n = 0; p[n] != NULL; n++)
+    unref_func (p[n]);
+  g_free (p);
+}
+
+/**
+ * g_dbus_annotation_info_unref:
+ * @info: A #GDBusAnnotationInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_annotation_info_unref (GDBusAnnotationInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->key);
+      g_free (info->value);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_arg_info_unref:
+ * @info: A #GDBusArgInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_arg_info_unref (GDBusArgInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->name);
+      g_free (info->signature);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_method_info_unref:
+ * @info: A #GDBusMethodInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_method_info_unref (GDBusMethodInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->name);
+      free_null_terminated_array (info->in_args, (GDestroyNotify) g_dbus_arg_info_unref);
+      free_null_terminated_array (info->out_args, (GDestroyNotify) g_dbus_arg_info_unref);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_signal_info_unref:
+ * @info: A #GDBusSignalInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_signal_info_unref (GDBusSignalInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->name);
+      free_null_terminated_array (info->args, (GDestroyNotify) g_dbus_arg_info_unref);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_property_info_unref:
+ * @info: A #GDBusPropertyInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_property_info_unref (GDBusPropertyInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->name);
+      g_free (info->signature);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_interface_info_unref:
+ * @info: A #GDBusInterfaceInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_interface_info_unref (GDBusInterfaceInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->name);
+      free_null_terminated_array (info->methods, (GDestroyNotify) g_dbus_method_info_unref);
+      free_null_terminated_array (info->signals, (GDestroyNotify) g_dbus_signal_info_unref);
+      free_null_terminated_array (info->properties, (GDestroyNotify) g_dbus_property_info_unref);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/**
+ * g_dbus_node_info_unref:
+ * @info: A #GDBusNodeInfo.
+ *
+ * If @info is statically allocated, does nothing. Otherwise decreases
+ * the reference count of @info. When its reference count drops to 0,
+ * the memory used is freed.
+ */
+void
+g_dbus_node_info_unref (GDBusNodeInfo *info)
+{
+  if (info->ref_count == -1)
+    return;
+  if (g_atomic_int_dec_and_test (&info->ref_count))
+    {
+      g_free (info->path);
+      free_null_terminated_array (info->interfaces, (GDestroyNotify) g_dbus_interface_info_unref);
+      free_null_terminated_array (info->nodes, (GDestroyNotify) g_dbus_node_info_unref);
+      free_null_terminated_array (info->annotations, (GDestroyNotify) g_dbus_annotation_info_unref);
+      g_free (info);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_annotation_info_set (ParseData                      *data,
+                            GDBusAnnotationInfo            *info,
+                            const gchar                    *key,
+                            const gchar                    *value,
+                            GDBusAnnotationInfo **embedded_annotations)
+{
+  info->ref_count = 1;
+
+  if (key != NULL)
+    info->key = g_strdup (key);
+
+  if (value != NULL)
+    info->value = g_strdup (value);
+
+  if (embedded_annotations != NULL)
+    info->annotations = embedded_annotations;
+}
+
+static void
+g_dbus_arg_info_set (ParseData            *data,
+                     GDBusArgInfo         *info,
+                     const gchar          *name,
+                     const gchar          *signature,
+                     GDBusAnnotationInfo **annotations)
+{
+  info->ref_count = 1;
+
+  /* name may be NULL - TODO: compute name? */
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (signature != NULL)
+    info->signature = g_strdup (signature);
+
+  if (annotations != NULL)
+    info->annotations = annotations;
+}
+
+static void
+g_dbus_method_info_set (ParseData            *data,
+                        GDBusMethodInfo      *info,
+                        const gchar          *name,
+                        guint                 in_num_args,
+                        GDBusArgInfo        **in_args,
+                        guint                 out_num_args,
+                        GDBusArgInfo        **out_args,
+                        GDBusAnnotationInfo **annotations)
+{
+  info->ref_count = 1;
+
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (in_num_args != 0)
+    {
+      //info->in_num_args = in_num_args;
+      info->in_args = in_args;
+    }
+
+  if (out_num_args != 0)
+    {
+      //info->out_num_args = out_num_args;
+      info->out_args = out_args;
+    }
+
+  if (annotations != NULL)
+    info->annotations = annotations;
+}
+
+static void
+g_dbus_signal_info_set (ParseData            *data,
+                        GDBusSignalInfo      *info,
+                        const gchar          *name,
+                        guint                 num_args,
+                        GDBusArgInfo        **args,
+                        GDBusAnnotationInfo **annotations)
+{
+  info->ref_count = 1;
+
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (num_args != 0)
+    {
+      //info->num_args = num_args;
+      info->args = args;
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+g_dbus_property_info_set (ParseData               *data,
+                          GDBusPropertyInfo       *info,
+                          const gchar             *name,
+                          const gchar             *signature,
+                          GDBusPropertyInfoFlags   flags,
+                          GDBusAnnotationInfo    **annotations)
+{
+  info->ref_count = 1;
+
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (flags != G_DBUS_PROPERTY_INFO_FLAGS_NONE)
+    info->flags = flags;
+
+  if (signature != NULL)
+    {
+      info->signature = g_strdup (signature);
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+g_dbus_interface_info_set (ParseData            *data,
+                           GDBusInterfaceInfo   *info,
+                           const gchar          *name,
+                           guint                 num_methods,
+                           GDBusMethodInfo     **methods,
+                           guint                 num_signals,
+                           GDBusSignalInfo     **signals,
+                           guint                 num_properties,
+                           GDBusPropertyInfo   **properties,
+                           GDBusAnnotationInfo **annotations)
+{
+  info->ref_count = 1;
+
+  if (name != NULL)
+    {
+      info->name = g_strdup (name);
+    }
+
+  if (num_methods != 0)
+    {
+      //info->num_methods    = num_methods;
+      info->methods        = methods;
+    }
+
+  if (num_signals != 0)
+    {
+      //info->num_signals    = num_signals;
+      info->signals        = signals;
+    }
+
+  if (num_properties != 0)
+    {
+      //info->num_properties = num_properties;
+      info->properties     = properties;
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+g_dbus_node_info_set (ParseData            *data,
+                      GDBusNodeInfo        *info,
+                      const gchar          *path,
+                      guint                 num_interfaces,
+                      GDBusInterfaceInfo  **interfaces,
+                      guint                 num_nodes,
+                      GDBusNodeInfo       **nodes,
+                      GDBusAnnotationInfo **annotations)
+{
+  info->ref_count = 1;
+
+  if (path != NULL)
+    {
+      info->path = g_strdup (path);
+      /* TODO: relative / absolute path snafu */
+    }
+
+  if (num_interfaces != 0)
+    {
+      //info->num_interfaces = num_interfaces;
+      info->interfaces     = interfaces;
+    }
+
+  if (num_nodes != 0)
+    {
+      //info->num_nodes      = num_nodes;
+      info->nodes          = nodes;
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_annotation_info_generate_xml (const GDBusAnnotationInfo  *info,
+                                     guint                       indent,
+                                     GString                    *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<annotation name=\"%s\" value=\"%s\"",
+                          indent, "",
+                          info->key,
+                          info->value);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                             indent + 2,
+                                             string_builder);
+
+      g_string_append_printf (string_builder, "%*s</annotation>\n",
+                              indent, "");
+    }
+
+}
+
+static void
+g_dbus_arg_info_generate_xml (const GDBusArgInfo  *info,
+                              guint                indent,
+                              const gchar         *extra_attributes,
+                              GString             *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<arg type=\"%s\"",
+                          indent, "",
+                          info->signature);
+
+  if (info->name != NULL)
+    g_string_append_printf (string_builder, " name=\"%s\"", info->name);
+
+  if (extra_attributes != NULL)
+    g_string_append_printf (string_builder, " %s", extra_attributes);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                             indent + 2,
+                                             string_builder);
+
+      g_string_append_printf (string_builder, "%*s</arg>\n", indent, "");
+    }
+
+}
+
+static void
+g_dbus_method_info_generate_xml (const GDBusMethodInfo  *info,
+                                 guint                   indent,
+                                 GString                *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<method name=\"%s\"",
+                          indent, "",
+                          info->name);
+
+  if (info->annotations == NULL && info->in_args == NULL && info->out_args == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; info->in_args != NULL && info->in_args[n] != NULL; n++)
+        g_dbus_arg_info_generate_xml (info->in_args[n],
+                                      indent + 2,
+                                      "direction=\"in\"",
+                                      string_builder);
+
+      for (n = 0; info->out_args != NULL && info->out_args[n] != NULL; n++)
+        g_dbus_arg_info_generate_xml (info->out_args[n],
+                                      indent + 2,
+                                      "direction=\"out\"",
+                                      string_builder);
+
+      g_string_append_printf (string_builder, "%*s</method>\n", indent, "");
+    }
+}
+
+static void
+g_dbus_signal_info_generate_xml (const GDBusSignalInfo  *info,
+                                 guint                   indent,
+                                 GString                *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<signal name=\"%s\"",
+                          indent, "",
+                          info->name);
+
+  if (info->annotations == NULL && info->args == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; info->args != NULL && info->args[n] != NULL; n++)
+        g_dbus_arg_info_generate_xml (info->args[n],
+                                      indent + 2,
+                                      NULL,
+                                      string_builder);
+
+      g_string_append_printf (string_builder, "%*s</signal>\n", indent, "");
+    }
+}
+
+static void
+g_dbus_property_info_generate_xml (const GDBusPropertyInfo  *info,
+                                   guint                     indent,
+                                   GString                  *string_builder)
+{
+  guint n;
+  const gchar *access_string;
+
+  if ((info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) &&
+      (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+    {
+      access_string = "readwrite";
+    }
+  else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+    {
+      access_string = "read";
+    }
+  else if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
+    {
+      access_string = "write";
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  g_string_append_printf (string_builder, "%*s<property type=\"%s\" name=\"%s\" access=\"%s\"",
+                          indent, "",
+                          info->signature,
+                          info->name,
+                          access_string);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                               indent + 2,
+                                               string_builder);
+
+      g_string_append_printf (string_builder, "%*s</property>\n", indent, "");
+    }
+
+}
+
+/**
+ * g_dbus_interface_info_generate_xml:
+ * @info: A #GDBusNodeInfo
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML
+ * documents at run-time for handling the
+ * <literal>org.freedesktop.DBus.Introspectable.Introspect</literal>
+ * method.
+ */
+void
+g_dbus_interface_info_generate_xml (const GDBusInterfaceInfo  *info,
+                                    guint                      indent,
+                                    GString                   *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<interface name=\"%s\">\n",
+                          indent, "",
+                          info->name);
+
+  for (n = 0; info->annotations != NULL && info->annotations[n] != NULL; n++)
+    g_dbus_annotation_info_generate_xml (info->annotations[n],
+                                         indent + 2,
+                                         string_builder);
+
+  for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++)
+    g_dbus_method_info_generate_xml (info->methods[n],
+                                     indent + 2,
+                                     string_builder);
+
+  for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++)
+    g_dbus_signal_info_generate_xml (info->signals[n],
+                                     indent + 2,
+                                     string_builder);
+
+  for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++)
+    g_dbus_property_info_generate_xml (info->properties[n],
+                                       indent + 2,
+                                       string_builder);
+
+  g_string_append_printf (string_builder, "%*s</interface>\n", indent, "");
+}
+
+/**
+ * g_dbus_node_info_generate_xml:
+ * @node_info: A #GDBusNodeInfo.
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @node_info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML documents at run-time for
+ * handling the <literal>org.freedesktop.DBus.Introspectable.Introspect</literal> method.
+ */
+void
+g_dbus_node_info_generate_xml (const GDBusNodeInfo  *node_info,
+                               guint                 indent,
+                               GString              *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<node", indent, "");
+  if (node_info->path != NULL)
+    g_string_append_printf (string_builder, " name=\"%s\"", node_info->path);
+
+  if (node_info->interfaces == NULL && node_info->nodes == NULL && node_info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; node_info->annotations != NULL && node_info->annotations[n] != NULL; n++)
+        g_dbus_annotation_info_generate_xml (node_info->annotations[n],
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; node_info->interfaces != NULL && node_info->interfaces[n] != NULL; n++)
+        g_dbus_interface_info_generate_xml (node_info->interfaces[n],
+                                            indent + 2,
+                                            string_builder);
+
+      for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++)
+        g_dbus_node_info_generate_xml (node_info->nodes[n],
+                                       indent + 2,
+                                       string_builder);
+
+      g_string_append_printf (string_builder, "%*s</node>\n", indent, "");
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo **
+parse_data_steal_annotations (ParseData *data, guint *out_num_elements)
+{
+  GDBusAnnotationInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->annotations->len;
+  if (data->annotations == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->annotations, NULL);
+      ret = (GDBusAnnotationInfo **) g_ptr_array_free (data->annotations, FALSE);
+    }
+  data->annotations = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusArgInfo **
+parse_data_steal_args (ParseData *data, guint *out_num_elements)
+{
+  GDBusArgInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->args->len;
+  if (data->args == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->args, NULL);
+      ret = (GDBusArgInfo **) g_ptr_array_free (data->args, FALSE);
+    }
+  data->args = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusArgInfo **
+parse_data_steal_out_args (ParseData *data, guint *out_num_elements)
+{
+  GDBusArgInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->out_args->len;
+  if (data->out_args == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->out_args, NULL);
+      ret = (GDBusArgInfo **) g_ptr_array_free (data->out_args, FALSE);
+    }
+  data->out_args = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusMethodInfo **
+parse_data_steal_methods (ParseData *data, guint *out_num_elements)
+{
+  GDBusMethodInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->methods->len;
+  if (data->methods == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->methods, NULL);
+      ret = (GDBusMethodInfo **) g_ptr_array_free (data->methods, FALSE);
+    }
+  data->methods = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusSignalInfo **
+parse_data_steal_signals (ParseData *data, guint *out_num_elements)
+{
+  GDBusSignalInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->signals->len;
+  if (data->signals == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->signals, NULL);
+      ret = (GDBusSignalInfo **) g_ptr_array_free (data->signals, FALSE);
+    }
+  data->signals = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusPropertyInfo **
+parse_data_steal_properties (ParseData *data, guint *out_num_elements)
+{
+  GDBusPropertyInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->properties->len;
+  if (data->properties == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->properties, NULL);
+      ret = (GDBusPropertyInfo **) g_ptr_array_free (data->properties, FALSE);
+    }
+  data->properties = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusInterfaceInfo **
+parse_data_steal_interfaces (ParseData *data, guint *out_num_elements)
+{
+  GDBusInterfaceInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->interfaces->len;
+  if (data->interfaces == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->interfaces, NULL);
+      ret = (GDBusInterfaceInfo **) g_ptr_array_free (data->interfaces, FALSE);
+    }
+  data->interfaces = g_ptr_array_new ();
+  return ret;
+}
+
+static GDBusNodeInfo **
+parse_data_steal_nodes (ParseData *data, guint *out_num_elements)
+{
+  GDBusNodeInfo **ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->nodes->len;
+  if (data->nodes == NULL)
+    ret = NULL;
+  else
+    {
+      g_ptr_array_add (data->nodes, NULL);
+      ret = (GDBusNodeInfo **) g_ptr_array_free (data->nodes, FALSE);
+    }
+  data->nodes = g_ptr_array_new ();
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parse_data_free_annotations (ParseData *data)
+{
+  if (data->annotations == NULL)
+    return;
+  g_ptr_array_foreach (data->annotations, (GFunc) g_dbus_annotation_info_unref, NULL);
+  g_ptr_array_free (data->annotations, TRUE);
+  data->annotations = NULL;
+}
+
+static void
+parse_data_free_args (ParseData *data)
+{
+  if (data->args == NULL)
+    return;
+  g_ptr_array_foreach (data->args, (GFunc) g_dbus_arg_info_unref, NULL);
+  g_ptr_array_free (data->args, TRUE);
+  data->args = NULL;
+}
+
+static void
+parse_data_free_out_args (ParseData *data)
+{
+  if (data->out_args == NULL)
+    return;
+  g_ptr_array_foreach (data->out_args, (GFunc) g_dbus_arg_info_unref, NULL);
+  g_ptr_array_free (data->out_args, TRUE);
+  data->out_args = NULL;
+}
+
+static void
+parse_data_free_methods (ParseData *data)
+{
+  if (data->methods == NULL)
+    return;
+  g_ptr_array_foreach (data->methods, (GFunc) g_dbus_method_info_unref, NULL);
+  g_ptr_array_free (data->methods, TRUE);
+  data->methods = NULL;
+}
+
+static void
+parse_data_free_signals (ParseData *data)
+{
+  if (data->signals == NULL)
+    return;
+  g_ptr_array_foreach (data->signals, (GFunc) g_dbus_signal_info_unref, NULL);
+  g_ptr_array_free (data->signals, TRUE);
+  data->signals = NULL;
+}
+
+static void
+parse_data_free_properties (ParseData *data)
+{
+  if (data->properties == NULL)
+    return;
+  g_ptr_array_foreach (data->properties, (GFunc) g_dbus_property_info_unref, NULL);
+  g_ptr_array_free (data->properties, TRUE);
+  data->properties = NULL;
+}
+
+static void
+parse_data_free_interfaces (ParseData *data)
+{
+  if (data->interfaces == NULL)
+    return;
+  g_ptr_array_foreach (data->interfaces, (GFunc) g_dbus_interface_info_unref, NULL);
+  g_ptr_array_free (data->interfaces, TRUE);
+  data->interfaces = NULL;
+}
+
+static void
+parse_data_free_nodes (ParseData *data)
+{
+  if (data->nodes == NULL)
+    return;
+  g_ptr_array_foreach (data->nodes, (GFunc) g_dbus_node_info_unref, NULL);
+  g_ptr_array_free (data->nodes, TRUE);
+  data->nodes = NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo *
+parse_data_get_annotation (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->annotations, g_new0 (GDBusAnnotationInfo, 1));
+  return data->annotations->pdata[data->annotations->len - 1];
+}
+
+static GDBusArgInfo *
+parse_data_get_arg (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->args, g_new0 (GDBusArgInfo, 1));
+  return data->args->pdata[data->args->len - 1];
+}
+
+static GDBusArgInfo *
+parse_data_get_out_arg (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->out_args, g_new0 (GDBusArgInfo, 1));
+  return data->out_args->pdata[data->out_args->len - 1];
+}
+
+static GDBusMethodInfo *
+parse_data_get_method (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->methods, g_new0 (GDBusMethodInfo, 1));
+  return data->methods->pdata[data->methods->len - 1];
+}
+
+static GDBusSignalInfo *
+parse_data_get_signal (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->signals, g_new0 (GDBusSignalInfo, 1));
+  return data->signals->pdata[data->signals->len - 1];
+}
+
+static GDBusPropertyInfo *
+parse_data_get_property (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->properties, g_new0 (GDBusPropertyInfo, 1));
+  return data->properties->pdata[data->properties->len - 1];
+}
+
+static GDBusInterfaceInfo *
+parse_data_get_interface (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->interfaces, g_new0 (GDBusInterfaceInfo, 1));
+  return data->interfaces->pdata[data->interfaces->len - 1];
+}
+
+static GDBusNodeInfo *
+parse_data_get_node (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_ptr_array_add (data->nodes, g_new0 (GDBusNodeInfo, 1));
+  return data->nodes->pdata[data->nodes->len - 1];
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static ParseData *
+parse_data_new (void)
+{
+  ParseData *data;
+
+  data = g_new0 (ParseData, 1);
+
+  /* initialize arrays */
+  parse_data_steal_annotations (data, NULL);
+  parse_data_steal_args (data, NULL);
+  parse_data_steal_out_args (data, NULL);
+  parse_data_steal_methods (data, NULL);
+  parse_data_steal_signals (data, NULL);
+  parse_data_steal_properties (data, NULL);
+  parse_data_steal_interfaces (data, NULL);
+  parse_data_steal_nodes (data, NULL);
+
+  return data;
+}
+
+static void
+parse_data_free (ParseData *data)
+{
+  GSList *l;
+
+  /* free stack of annotation arrays */
+  for (l = data->annotations_stack; l != NULL; l = l->next)
+    {
+      GPtrArray *annotations = l->data;
+      g_ptr_array_foreach (annotations, (GFunc) g_dbus_annotation_info_unref, NULL);
+      g_ptr_array_free (annotations, TRUE);
+    }
+  g_slist_free (data->annotations_stack);
+
+  /* free stack of interface arrays */
+  for (l = data->interfaces_stack; l != NULL; l = l->next)
+    {
+      GPtrArray *interfaces = l->data;
+      g_ptr_array_foreach (interfaces, (GFunc) g_dbus_interface_info_unref, NULL);
+      g_ptr_array_free (interfaces, TRUE);
+    }
+  g_slist_free (data->interfaces_stack);
+
+  /* free stack of node arrays */
+  for (l = data->nodes_stack; l != NULL; l = l->next)
+    {
+      GPtrArray *nodes = l->data;
+      g_ptr_array_foreach (nodes, (GFunc) g_dbus_node_info_unref, NULL);
+      g_ptr_array_free (nodes, TRUE);
+    }
+  g_slist_free (data->nodes_stack);
+
+  /* free arrays (data->annotations, data->interfaces and data->nodes have been freed above) */
+  parse_data_free_args (data);
+  parse_data_free_out_args (data);
+  parse_data_free_methods (data);
+  parse_data_free_signals (data);
+  parse_data_free_properties (data);
+
+  g_free (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_start_element (GMarkupParseContext *context,
+                      const gchar         *element_name,
+                      const gchar        **attribute_names,
+                      const gchar        **attribute_values,
+                      gpointer             user_data,
+                      GError             **error)
+{
+  ParseData *data = user_data;
+  GSList *stack;
+  const gchar *name;
+  const gchar *type;
+  const gchar *access;
+  const gchar *direction;
+  const gchar *value;
+
+  name = NULL;
+  type = NULL;
+  access = NULL;
+  direction = NULL;
+  value = NULL;
+
+  stack = (GSList *) g_markup_parse_context_get_element_stack (context);
+
+  /* ---------------------------------------------------------------------------------------------------- */
+  if (strcmp (element_name, "node") == 0)
+    {
+      if (!(g_slist_length (stack) >= 1 || strcmp (stack->next->data, "node") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<node> elements can only be top-level or embedded in other <node> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+                                        /* some hand-written introspection XML documents use this */
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "xmlns:doc", NULL,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      g_dbus_node_info_set (data,
+                            parse_data_get_node (data, TRUE),
+                            name,
+                            0, NULL,
+                            0, NULL,
+                            NULL);
+
+      /* push the currently retrieved interfaces and nodes on the stack and prepare new arrays */
+      data->interfaces_stack = g_slist_prepend (data->interfaces_stack, data->interfaces);
+      data->interfaces = NULL;
+      parse_data_steal_interfaces (data, NULL);
+
+      data->nodes_stack = g_slist_prepend (data->nodes_stack, data->nodes);
+      data->nodes = NULL;
+      parse_data_steal_nodes (data, NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "interface") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "node") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<interface> elements can only be embedded in <node> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      g_dbus_interface_info_set (data,
+                                 parse_data_get_interface (data, TRUE),
+                                 name,
+                                 0, NULL,
+                                 0, NULL,
+                                 0, NULL,
+                                 NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "method") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<method> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      g_dbus_method_info_set (data,
+                              parse_data_get_method (data, TRUE),
+                              name,
+                              0, NULL,
+                              0, NULL,
+                              NULL);
+
+      data->num_args = 0;
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "signal") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<signal> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      g_dbus_signal_info_set (data,
+                              parse_data_get_signal (data, TRUE),
+                              name,
+                              0, NULL,
+                              NULL);
+
+      data->num_args = 0;
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "property") == 0)
+    {
+      GDBusPropertyInfoFlags flags;
+
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<property> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_STRING, "type", &type,
+                                        G_MARKUP_COLLECT_STRING, "access", &access,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      if (strcmp (access, "read") == 0)
+        flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE;
+      else if (strcmp (access, "write") == 0)
+        flags = G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+      else if (strcmp (access, "readwrite") == 0)
+        flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+      else
+        {
+          g_set_error (error,
+                       G_MARKUP_ERROR,
+                       G_MARKUP_ERROR_INVALID_CONTENT,
+                       "Unknown value '%s' of access attribute for element <property>",
+                       access);
+          goto out;
+        }
+
+      g_dbus_property_info_set (data,
+                                parse_data_get_property (data, TRUE),
+                                name,
+                                type,
+                                flags,
+                                NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "arg") == 0)
+    {
+      gboolean is_in;
+      gchar *name_to_use;
+
+      if (g_slist_length (stack) < 2 ||
+          (strcmp (stack->next->data, "method") != 0 &&
+           strcmp (stack->next->data, "signal") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<arg> elements can only be embedded in <method> or <signal> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "direction", &direction,
+                                        G_MARKUP_COLLECT_STRING, "type", &type,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      is_in = FALSE;
+      if (direction != NULL)
+        {
+          if (strcmp (direction, "in") == 0)
+            is_in = TRUE;
+          else if (strcmp (direction, "out") == 0)
+            is_in = FALSE;
+          else
+            {
+              g_set_error (error,
+                           G_MARKUP_ERROR,
+                           G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Unknown value '%s' of direction attribute",
+                           direction);
+              goto out;
+            }
+        }
+
+      if (is_in && strcmp (stack->next->data, "signal") == 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "Only direction 'out' is allowed for <arg> elements embedded in <signal>");
+          goto out;
+        }
+
+      if (name == NULL)
+        name_to_use = g_strdup_printf ("arg_%d", data->num_args);
+      else
+        name_to_use = g_strdup (name);
+      data->num_args++;
+
+      if (is_in)
+        {
+          g_dbus_arg_info_set (data,
+                               parse_data_get_arg (data, TRUE),
+                               name_to_use,
+                               type,
+                               NULL);
+          data->last_arg_was_in = TRUE;
+        }
+      else
+        {
+          g_dbus_arg_info_set (data,
+                               parse_data_get_out_arg (data, TRUE),
+                               name_to_use,
+                               type,
+                               NULL);
+          data->last_arg_was_in = FALSE;
+
+        }
+
+      g_free (name_to_use);
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "annotation") == 0)
+    {
+      if (g_slist_length (stack) < 2 ||
+          (strcmp (stack->next->data, "node") != 0 &&
+           strcmp (stack->next->data, "interface") != 0 &&
+           strcmp (stack->next->data, "signal") != 0 &&
+           strcmp (stack->next->data, "method") != 0 &&
+           strcmp (stack->next->data, "property") != 0 &&
+           strcmp (stack->next->data, "arg") != 0 &&
+           strcmp (stack->next->data, "annotation") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<annotation> elements can only be embedded in <node>, <interface>, <signal>, <method>, <property>, <arg> or <annotation> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_STRING, "value", &value,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      g_dbus_annotation_info_set (data,
+                                  parse_data_get_annotation (data, TRUE),
+                                  name,
+                                  value,
+                                  NULL);
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else
+    {
+      /* don't bail on unknown elements; just ignore them */
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  /* push the currently retrieved annotations on the stack and prepare a new one */
+  data->annotations_stack = g_slist_prepend (data->annotations_stack, data->annotations);
+  data->annotations = NULL;
+  parse_data_steal_annotations (data, NULL);
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusAnnotationInfo **
+steal_annotations (ParseData *data)
+{
+  return parse_data_steal_annotations (data, NULL);
+}
+
+
+static void
+parser_end_element (GMarkupParseContext *context,
+                    const gchar         *element_name,
+                    gpointer             user_data,
+                    GError             **error)
+{
+  ParseData *data = user_data;
+  gboolean have_popped_annotations;
+
+  have_popped_annotations = FALSE;
+
+  if (strcmp (element_name, "node") == 0)
+    {
+      guint num_nodes;
+      guint num_interfaces;
+      GDBusNodeInfo **nodes;
+      GDBusInterfaceInfo **interfaces;
+
+      nodes = parse_data_steal_nodes (data, &num_nodes);
+      interfaces = parse_data_steal_interfaces (data, &num_interfaces);
+
+      /* destroy the nodes, interfaces for scope we're exiting and and pop the nodes, interfaces from the
+       * scope we're reentering
+       */
+      parse_data_free_interfaces (data);
+      data->interfaces = (GPtrArray *) data->interfaces_stack->data;
+      data->interfaces_stack = g_slist_remove (data->interfaces_stack, data->interfaces_stack->data);
+
+      parse_data_free_nodes (data);
+      data->nodes = (GPtrArray *) data->nodes_stack->data;
+      data->nodes_stack = g_slist_remove (data->nodes_stack, data->nodes_stack->data);
+
+      g_dbus_node_info_set (data,
+                            parse_data_get_node (data, FALSE),
+                            NULL,
+                            num_interfaces,
+                            interfaces,
+                            num_nodes,
+                            nodes,
+                            steal_annotations (data));
+
+    }
+  else if (strcmp (element_name, "interface") == 0)
+    {
+      guint num_methods;
+      guint num_signals;
+      guint num_properties;
+      GDBusMethodInfo **methods;
+      GDBusSignalInfo **signals;
+      GDBusPropertyInfo **properties;
+
+      methods    = parse_data_steal_methods    (data, &num_methods);
+      signals    = parse_data_steal_signals    (data, &num_signals);
+      properties = parse_data_steal_properties (data, &num_properties);
+
+      g_dbus_interface_info_set (data,
+                                 parse_data_get_interface (data, FALSE),
+                                 NULL,
+                                 num_methods,
+                                 methods,
+                                 num_signals,
+                                 signals,
+                                 num_properties,
+                                 properties,
+                                 steal_annotations (data));
+
+    }
+  else if (strcmp (element_name, "method") == 0)
+    {
+      guint in_num_args;
+      guint out_num_args;
+      GDBusArgInfo **in_args;
+      GDBusArgInfo **out_args;
+
+      in_args  = parse_data_steal_args     (data, &in_num_args);
+      out_args = parse_data_steal_out_args (data, &out_num_args);
+
+      g_dbus_method_info_set (data,
+                              parse_data_get_method (data, FALSE),
+                              NULL,
+                              in_num_args,
+                              in_args,
+                              out_num_args,
+                              out_args,
+                              steal_annotations (data));
+    }
+  else if (strcmp (element_name, "signal") == 0)
+    {
+      guint num_args;
+      GDBusArgInfo **args;
+
+      args = parse_data_steal_out_args (data, &num_args);
+
+      g_dbus_signal_info_set (data,
+                              parse_data_get_signal (data, FALSE),
+                              NULL,
+                              num_args,
+                              args,
+                              steal_annotations (data));
+    }
+  else if (strcmp (element_name, "property") == 0)
+    {
+      g_dbus_property_info_set (data,
+                                parse_data_get_property (data, FALSE),
+                                NULL,
+                                NULL,
+                                G_DBUS_PROPERTY_INFO_FLAGS_NONE,
+                                steal_annotations (data));
+    }
+  else if (strcmp (element_name, "arg") == 0)
+    {
+      g_dbus_arg_info_set (data,
+                           data->last_arg_was_in ? parse_data_get_arg (data, FALSE) : parse_data_get_out_arg (data, FALSE),
+                           NULL,
+                           NULL,
+                           steal_annotations (data));
+    }
+  else if (strcmp (element_name, "annotation") == 0)
+    {
+      GDBusAnnotationInfo **embedded_annotations;
+
+      embedded_annotations = steal_annotations (data);
+
+      /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+      parse_data_free_annotations (data);
+      data->annotations = (GPtrArray *) data->annotations_stack->data;
+      data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+
+      have_popped_annotations = TRUE;
+
+      g_dbus_annotation_info_set (data,
+                                  parse_data_get_annotation (data, FALSE),
+                                  NULL,
+                                  NULL,
+                                  embedded_annotations);
+    }
+  else
+    {
+      /* don't bail on unknown elements; just ignore them */
+    }
+
+  if (!have_popped_annotations)
+    {
+      /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+      parse_data_free_annotations (data);
+      data->annotations = (GPtrArray *) data->annotations_stack->data;
+      data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_error (GMarkupParseContext *context,
+              GError              *error,
+              gpointer             user_data)
+{
+  gint line_number;
+  gint char_number;
+
+  g_markup_parse_context_get_position (context, &line_number, &char_number);
+
+  g_prefix_error (&error, "%d:%d: ",
+                  line_number,
+                  char_number);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_new_for_xml:
+ * @xml_data: Valid D-Bus introspection XML.
+ * @error: Return location for error.
+ *
+ * Parses @xml_data and returns a #GDBusNodeInfo representing the data.
+ *
+ * Returns: A #GDBusNodeInfo structure or %NULL if @error is set. Free
+ * with g_dbus_node_info_unref().
+ */
+GDBusNodeInfo *
+g_dbus_node_info_new_for_xml (const gchar  *xml_data,
+                              GError      **error)
+{
+  GDBusNodeInfo *ret;
+  GMarkupParseContext *context;
+  GMarkupParser *parser;
+  guint num_nodes;
+  ParseData *data;
+
+  ret = NULL;
+  parser = NULL;
+  context = NULL;
+
+  parser = g_new0 (GMarkupParser, 1);
+  parser->start_element = parser_start_element;
+  parser->end_element   = parser_end_element;
+  parser->error         = parser_error;
+
+  data = parse_data_new ();
+  context = g_markup_parse_context_new (parser,
+                                        0,
+                                        data,
+                                        (GDestroyNotify) parse_data_free);
+
+  if (!g_markup_parse_context_parse (context,
+                                     xml_data,
+                                     strlen (xml_data),
+                                     error))
+    goto out;
+
+  GDBusNodeInfo **ughret;
+  ughret = parse_data_steal_nodes (data, &num_nodes);
+
+  if (num_nodes != 1)
+    {
+      guint n;
+
+      g_set_error (error,
+                   G_MARKUP_ERROR,
+                   G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Expected a single node in introspection XML, found %d",
+                   num_nodes);
+
+      /* clean up */
+      for (n = 0; n < num_nodes; n++)
+        {
+          for (n = 0; n < num_nodes; n++)
+            g_dbus_node_info_unref (&(ret[n]));
+        }
+      g_free (ret);
+      ret = NULL;
+    }
+
+  ret = ughret[0];
+  g_free (ughret);
+
+ out:
+  if (parser != NULL)
+    g_free (parser);
+  if (context != NULL)
+    g_markup_parse_context_free (context);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_annotation_info_lookup:
+ * @annotations: A %NULL-terminated array of annotations or %NULL.
+ * @name: The name of the annotation to look up.
+ *
+ * Looks up the value of an annotation.
+ *
+ * This cost of this function is O(n) in number of annotations.
+ *
+ * Returns: The value or %NULL if not found. Do not free, it is owned by @annotations.
+ */
+const gchar *
+g_dbus_annotation_info_lookup (const GDBusAnnotationInfo **annotations,
+                               const gchar                *name)
+{
+  guint n;
+  const gchar *ret;
+
+  ret = NULL;
+  for (n = 0; annotations != NULL && annotations[n]->key != NULL; n++)
+    {
+      if (g_strcmp0 (annotations[n]->key, name) == 0)
+        {
+          ret = annotations[n]->value;
+          goto out;
+        }
+    }
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_method:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus method name (typically in CamelCase)
+ *
+ * Looks up information about a method.
+ *
+ * This cost of this function is O(n) in number of methods.
+ *
+ * Returns: A #GDBusMethodInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusMethodInfo *
+g_dbus_interface_info_lookup_method (const GDBusInterfaceInfo *info,
+                                     const gchar              *name)
+{
+  guint n;
+  const GDBusMethodInfo *result;
+
+  for (n = 0; info->methods != NULL && info->methods[n] != NULL; n++)
+    {
+      const GDBusMethodInfo *i = info->methods[n];
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_signal:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus signal name (typically in CamelCase)
+ *
+ * Looks up information about a signal.
+ *
+ * This cost of this function is O(n) in number of signals.
+ *
+ * Returns: A #GDBusSignalInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusSignalInfo *
+g_dbus_interface_info_lookup_signal (const GDBusInterfaceInfo *info,
+                                     const gchar              *name)
+{
+  guint n;
+  const GDBusSignalInfo *result;
+
+  for (n = 0; info->signals != NULL && info->signals[n] != NULL; n++)
+    {
+      const GDBusSignalInfo *i = info->signals[n];
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_interface_info_lookup_property:
+ * @info: A #GDBusInterfaceInfo.
+ * @name: A D-Bus property name (typically in CamelCase).
+ *
+ * Looks up information about a property.
+ *
+ * This cost of this function is O(n) in number of properties.
+ *
+ * Returns: A #GDBusPropertyInfo or %NULL if not found. Do not free, it is owned by @info.
+ **/
+const GDBusPropertyInfo *
+g_dbus_interface_info_lookup_property (const GDBusInterfaceInfo *info,
+                                       const gchar              *name)
+{
+  guint n;
+  const GDBusPropertyInfo *result;
+
+  for (n = 0; info->properties != NULL && info->properties[n] != NULL; n++)
+    {
+      const GDBusPropertyInfo *i = info->properties[n];
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_node_info_lookup_interface:
+ * @node_info: A #GDBusNodeInfo.
+ * @name: A D-Bus interface name.
+ *
+ * Looks up information about an interface.
+ *
+ * This cost of this function is O(n) in number of interfaces.
+ *
+ * Returns: A #GDBusInterfaceInfo or %NULL if not found. Do not free, it is owned by @node_info.
+ **/
+const GDBusInterfaceInfo *
+g_dbus_node_info_lookup_interface (const GDBusNodeInfo *node_info,
+                                   const gchar         *name)
+{
+  guint n;
+  const GDBusInterfaceInfo *result;
+
+  for (n = 0; node_info->interfaces != NULL && node_info->interfaces[n] != NULL; n++)
+    {
+      const GDBusInterfaceInfo *i = node_info->interfaces[n];
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
diff --git a/gio/gdbusintrospection.h b/gio/gdbusintrospection.h
new file mode 100644
index 0000000..7aea8d9
--- /dev/null
+++ b/gio/gdbusintrospection.h
@@ -0,0 +1,255 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_INTROSPECTION_H__
+#define __G_DBUS_INTROSPECTION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GDBusAnnotationInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @key: The name of the annotation, e.g. "org.freedesktop.DBus.Deprecated".
+ * @value: The value of the annotation.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about an annotation.
+ */
+struct _GDBusAnnotationInfo
+{
+  volatile gint         ref_count;
+  gchar                *key;
+  gchar                *value;
+  GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusArgInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: Name of the argument, e.g. @unix_user_id.
+ * @signature: D-Bus signature of the argument (a single complete type).
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about an argument for a method or a signal.
+ */
+struct _GDBusArgInfo
+{
+  volatile gint         ref_count;
+  gchar                *name;
+  gchar                *signature;
+  GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusMethodInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus method, e.g. @RequestName.
+ * @in_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no in arguments.
+ * @out_args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no out arguments.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a method on an D-Bus interface.
+ */
+struct _GDBusMethodInfo
+{
+  volatile gint         ref_count;
+  gchar                *name;
+  GDBusArgInfo        **in_args;
+  GDBusArgInfo        **out_args;
+  GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusSignalInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus signal, e.g. "NameOwnerChanged".
+ * @args: A pointer to a %NULL-terminated array of pointers to #GDBusArgInfo structures or %NULL if there are no arguments.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a signal on a D-Bus interface.
+ */
+struct _GDBusSignalInfo
+{
+  volatile gint         ref_count;
+  gchar                *name;
+  GDBusArgInfo        **args;
+  GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusPropertyInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus property, e.g. "SupportedFilesystems".
+ * @signature: The D-Bus signature of the property (a single complete type).
+ * @flags: Access control flags for the property.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus property on a D-Bus interface.
+ */
+struct _GDBusPropertyInfo
+{
+  volatile gint             ref_count;
+  gchar                    *name;
+  gchar                    *signature;
+  GDBusPropertyInfoFlags    flags;
+  GDBusAnnotationInfo     **annotations;
+};
+
+/**
+ * GDBusInterfaceInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @name: The name of the D-Bus interface, e.g. "org.freedesktop.DBus.Properties".
+ * @methods: A pointer to a %NULL-terminated array of pointers to #GDBusMethodInfo structures or %NULL if there are no methods.
+ * @signals: A pointer to a %NULL-terminated array of pointers to #GDBusSignalInfo structures or %NULL if there are no signals.
+ * @properties: A pointer to a %NULL-terminated array of pointers to #GDBusPropertyInfo structures or %NULL if there are no properties.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus interface.
+ */
+struct _GDBusInterfaceInfo
+{
+  volatile gint         ref_count;
+  gchar                *name;
+  GDBusMethodInfo     **methods;
+  GDBusSignalInfo     **signals;
+  GDBusPropertyInfo   **properties;
+  GDBusAnnotationInfo **annotations;
+};
+
+/**
+ * GDBusNodeInfo:
+ * @ref_count: The reference count or -1 if statically allocated.
+ * @path: The path of the node or %NULL if omitted. Note that this may be a relative path. See the D-Bus specification for more details.
+ * @interfaces: A pointer to a %NULL-terminated array of pointers to #GDBusInterfaceInfo structures or %NULL if there are no interfaces.
+ * @nodes: A pointer to a %NULL-terminated array of pointers to #GDBusNodeInfo structures or %NULL if there are no nodes.
+ * @annotations: A pointer to a %NULL-terminated array of pointers to #GDBusAnnotationInfo structures or %NULL if there are no annotations.
+ *
+ * Information about nodes in a remote object hierarchy.
+ */
+struct _GDBusNodeInfo
+{
+  volatile gint         ref_count;
+  gchar                *path;
+  GDBusInterfaceInfo  **interfaces;
+  GDBusNodeInfo       **nodes;
+  GDBusAnnotationInfo **annotations;
+};
+
+const gchar              *g_dbus_annotation_info_lookup          (const GDBusAnnotationInfo **annotations,
+                                                                  const gchar               *name);
+const GDBusMethodInfo    *g_dbus_interface_info_lookup_method    (const GDBusInterfaceInfo  *info,
+                                                                  const gchar               *name);
+const GDBusSignalInfo    *g_dbus_interface_info_lookup_signal    (const GDBusInterfaceInfo  *info,
+                                                                  const gchar               *name);
+const GDBusPropertyInfo  *g_dbus_interface_info_lookup_property  (const GDBusInterfaceInfo  *info,
+                                                                  const gchar               *name);
+void                      g_dbus_interface_info_generate_xml     (const GDBusInterfaceInfo  *info,
+                                                                  guint                      indent,
+                                                                  GString                   *string_builder);
+
+GDBusNodeInfo            *g_dbus_node_info_new_for_xml           (const gchar               *xml_data,
+                                                                  GError                   **error);
+const GDBusInterfaceInfo *g_dbus_node_info_lookup_interface      (const GDBusNodeInfo       *info,
+                                                                  const gchar               *name);
+void                      g_dbus_node_info_generate_xml          (const GDBusNodeInfo       *info,
+                                                                  guint                      indent,
+                                                                  GString                   *string_builder);
+
+GDBusNodeInfo            *g_dbus_node_info_ref                   (GDBusNodeInfo             *info);
+GDBusInterfaceInfo       *g_dbus_interface_info_ref              (GDBusInterfaceInfo        *info);
+GDBusMethodInfo          *g_dbus_method_info_ref                 (GDBusMethodInfo           *info);
+GDBusSignalInfo          *g_dbus_signal_info_ref                 (GDBusSignalInfo           *info);
+GDBusPropertyInfo        *g_dbus_property_info_ref               (GDBusPropertyInfo         *info);
+GDBusArgInfo             *g_dbus_arg_info_ref                    (GDBusArgInfo              *info);
+GDBusAnnotationInfo      *g_dbus_annotation_info_ref             (GDBusAnnotationInfo       *info);
+
+void                      g_dbus_node_info_unref                 (GDBusNodeInfo             *info);
+void                      g_dbus_interface_info_unref            (GDBusInterfaceInfo        *info);
+void                      g_dbus_method_info_unref               (GDBusMethodInfo           *info);
+void                      g_dbus_signal_info_unref               (GDBusSignalInfo           *info);
+void                      g_dbus_property_info_unref             (GDBusPropertyInfo         *info);
+void                      g_dbus_arg_info_unref                  (GDBusArgInfo              *info);
+void                      g_dbus_annotation_info_unref           (GDBusAnnotationInfo       *info);
+
+
+/**
+ * G_TYPE_DBUS_NODE_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusNodeInfo.
+ */
+#define G_TYPE_DBUS_NODE_INFO       (g_dbus_node_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_INTERFACE_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusInterfaceInfo.
+ */
+#define G_TYPE_DBUS_INTERFACE_INFO  (g_dbus_interface_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_METHOD_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusMethodInfo.
+ */
+#define G_TYPE_DBUS_METHOD_INFO     (g_dbus_method_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_SIGNAL_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusSignalInfo.
+ */
+#define G_TYPE_DBUS_SIGNAL_INFO     (g_dbus_signal_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_PROPERTY_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusPropertyInfo.
+ */
+#define G_TYPE_DBUS_PROPERTY_INFO   (g_dbus_property_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_ARG_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusArgInfo.
+ */
+#define G_TYPE_DBUS_ARG_INFO        (g_dbus_arg_info_get_type ())
+
+/**
+ * G_TYPE_DBUS_ANNOTATION_INFO:
+ *
+ * The #GType for a boxed type holding a #GDBusAnnotationInfo.
+ */
+#define G_TYPE_DBUS_ANNOTATION_INFO (g_dbus_annotation_info_get_type ())
+
+GType g_dbus_node_info_get_type       (void) G_GNUC_CONST;
+GType g_dbus_interface_info_get_type  (void) G_GNUC_CONST;
+GType g_dbus_method_info_get_type     (void) G_GNUC_CONST;
+GType g_dbus_signal_info_get_type     (void) G_GNUC_CONST;
+GType g_dbus_property_info_get_type   (void) G_GNUC_CONST;
+GType g_dbus_arg_info_get_type        (void) G_GNUC_CONST;
+GType g_dbus_annotation_info_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __G_DBUS_INTROSPECTION_H__ */
diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c
new file mode 100644
index 0000000..668b1c8
--- /dev/null
+++ b/gio/gdbusmessage.c
@@ -0,0 +1,2421 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusmessage.h"
+#include "gdbuserror.h"
+#include "gioenumtypes.h"
+#include "ginputstream.h"
+#include "gdatainputstream.h"
+#include "gmemoryinputstream.h"
+#include "goutputstream.h"
+#include "gdataoutputstream.h"
+#include "gmemoryoutputstream.h"
+#include "gseekable.h"
+#include "gioerror.h"
+
+#ifdef G_OS_UNIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+
+
+/**
+ * SECTION:gdbusmessage
+ * @short_description: D-Bus Message
+ * @include: gdbus/gdbus.h
+ *
+ * A type for representing D-Bus messages that can be sent or received
+ * on a #GDBusConnection.
+ */
+
+struct _GDBusMessagePrivate
+{
+  GDBusMessageType type;
+  GDBusMessageFlags flags;
+  guchar major_protocol_version;
+  guint32 serial;
+  GHashTable *headers;
+  GVariant *body;
+#ifdef G_OS_UNIX
+  GUnixFDList *fd_list;
+#endif
+};
+
+#define g_dbus_message_get_type g_dbus_message_get_gtype
+G_DEFINE_TYPE (GDBusMessage, g_dbus_message, G_TYPE_OBJECT);
+#undef g_dbus_message_get_type
+
+static void
+g_dbus_message_finalize (GObject *object)
+{
+  GDBusMessage *message = G_DBUS_MESSAGE (object);
+
+  if (message->priv->headers != NULL)
+    g_hash_table_unref (message->priv->headers);
+  if (message->priv->body != NULL)
+    g_variant_unref (message->priv->body);
+#ifdef G_OS_UNIX
+  if (message->priv->fd_list != NULL)
+    g_object_unref (message->priv->fd_list);
+#endif
+
+  if (G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_message_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_message_class_init (GDBusMessageClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GDBusMessagePrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_message_finalize;
+}
+
+static void
+g_dbus_message_init (GDBusMessage *message)
+{
+  message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message, G_TYPE_DBUS_MESSAGE, GDBusMessagePrivate);
+
+  message->priv->headers = g_hash_table_new_full (g_direct_hash,
+                                                  g_direct_equal,
+                                                  NULL,
+                                                  (GDestroyNotify) g_variant_unref);
+}
+
+/**
+ * g_dbus_message_new:
+ *
+ * Creates a new empty #GDBusMessage.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new (void)
+{
+  return g_object_new (G_TYPE_DBUS_MESSAGE, NULL);
+}
+
+/**
+ * g_dbus_message_new_method_call:
+ * @name: A valid D-Bus name or %NULL.
+ * @path: A valid object path.
+ * @interface: A valid D-Bus interface name or %NULL.
+ * @method: A valid method name.
+ *
+ * Creates a new #GDBusMessage for a method call.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_call (const gchar *name,
+                                const gchar *path,
+                                const gchar *interface,
+                                const gchar *method)
+{
+  GDBusMessage *message;
+
+  g_return_val_if_fail (name == NULL || g_dbus_is_name (name), NULL);
+  g_return_val_if_fail (g_variant_is_object_path (path), NULL);
+  g_return_val_if_fail (g_dbus_is_member_name (method), NULL);
+  g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL);
+
+  message = g_dbus_message_new ();
+  message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_CALL;
+
+  if (name != NULL)
+    g_dbus_message_set_destination (message, name);
+  g_dbus_message_set_path (message, path);
+  g_dbus_message_set_member (message, method);
+  if (interface != NULL)
+    g_dbus_message_set_interface (message, interface);
+
+  return message;
+}
+
+/**
+ * g_dbus_message_new_signal:
+ * @path: A valid object path.
+ * @interface: A valid D-Bus interface name or %NULL.
+ * @signal: A valid signal name.
+ *
+ * Creates a new #GDBusMessage for a signal emission.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_signal (const gchar  *path,
+                           const gchar  *interface,
+                           const gchar  *signal)
+{
+  GDBusMessage *message;
+
+  g_return_val_if_fail (g_variant_is_object_path (path), NULL);
+  g_return_val_if_fail (g_dbus_is_member_name (signal), NULL);
+  g_return_val_if_fail (interface == NULL || g_dbus_is_interface_name (interface), NULL);
+
+  message = g_dbus_message_new ();
+  message->priv->type = G_DBUS_MESSAGE_TYPE_SIGNAL;
+  message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+  g_dbus_message_set_path (message, path);
+  g_dbus_message_set_member (message, signal);
+
+  if (interface != NULL)
+    g_dbus_message_set_interface (message, interface);
+
+  return message;
+}
+
+
+/**
+ * g_dbus_message_new_method_reply:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ *
+ * Creates a new #GDBusMessage that is a reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_reply (GDBusMessage *method_call_message)
+{
+  GDBusMessage *message;
+  const gchar *sender;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL);
+  g_return_val_if_fail (g_dbus_message_get_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL);
+  g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL);
+
+  message = g_dbus_message_new ();
+  message->priv->type = G_DBUS_MESSAGE_TYPE_METHOD_RETURN;
+  message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+  g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message));
+  sender = g_dbus_message_get_sender (method_call_message);
+  if (sender != NULL)
+    g_dbus_message_set_destination (message, sender);
+
+  return message;
+}
+
+/**
+ * g_dbus_message_new_method_error:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message_format: The D-Bus error message in a printf() format.
+ * @...: Arguments for @error_message_format.
+ *
+ * Creates a new #GDBusMessage that is an error reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error (GDBusMessage             *method_call_message,
+                                 const gchar              *error_name,
+                                 const gchar              *error_message_format,
+                                 ...)
+{
+  GDBusMessage *ret;
+  va_list var_args;
+
+  va_start (var_args, error_message_format);
+  ret = g_dbus_message_new_method_error_valist (method_call_message,
+                                                error_name,
+                                                error_message_format,
+                                                var_args);
+  va_end (var_args);
+
+  return ret;
+}
+
+/**
+ * g_dbus_message_new_method_error_literal:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message: The D-Bus error message.
+ *
+ * Creates a new #GDBusMessage that is an error reply to @method_call_message.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error_literal (GDBusMessage  *method_call_message,
+                                         const gchar   *error_name,
+                                         const gchar   *error_message)
+{
+  GDBusMessage *message;
+  const gchar *sender;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (method_call_message), NULL);
+  g_return_val_if_fail (g_dbus_message_get_type (method_call_message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL, NULL);
+  g_return_val_if_fail (g_dbus_message_get_serial (method_call_message) != 0, NULL);
+  g_return_val_if_fail (g_dbus_is_name (error_name), NULL);
+  g_return_val_if_fail (error_message != NULL, NULL);
+
+  message = g_dbus_message_new ();
+  message->priv->type = G_DBUS_MESSAGE_TYPE_ERROR;
+  message->priv->flags = G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
+
+  g_dbus_message_set_reply_serial (message, g_dbus_message_get_serial (method_call_message));
+  g_dbus_message_set_error_name (message, error_name);
+  g_dbus_message_set_body (message, g_variant_new ("(s)", error_message));
+
+  sender = g_dbus_message_get_sender (method_call_message);
+  if (sender != NULL)
+    g_dbus_message_set_destination (message, sender);
+
+  return message;
+}
+
+/**
+ * g_dbus_message_new_method_error_valist:
+ * @method_call_message: A message of type %G_DBUS_MESSAGE_TYPE_METHOD_CALL to
+ * create a reply message to.
+ * @error_name: A valid D-Bus error name.
+ * @error_message_format: The D-Bus error message in a printf() format.
+ * @var_args: Arguments for @error_message_format.
+ *
+ * Like g_dbus_message_new_method_error() but intended for language bindings.
+ *
+ * Returns: A #GDBusMessage. Free with g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_method_error_valist (GDBusMessage             *method_call_message,
+                                        const gchar              *error_name,
+                                        const gchar              *error_message_format,
+                                        va_list                   var_args)
+{
+  GDBusMessage *ret;
+  gchar *error_message;
+  error_message = g_strdup_vprintf (error_message_format, var_args);
+  ret = g_dbus_message_new_method_error_literal (method_call_message,
+                                                 error_name,
+                                                 error_message);
+  g_free (error_message);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for the type */
+
+/**
+ * g_dbus_message_get_type:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the type of @message.
+ *
+ * Returns: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration).
+ */
+GDBusMessageType
+g_dbus_message_get_type (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_TYPE_INVALID);
+  return message->priv->type;
+}
+
+/**
+ * g_dbus_message_set_type:
+ * @message: A #GDBusMessage.
+ * @type: A 8-bit unsigned integer (typically a value from the #GDBusMessageType enumeration).
+ *
+ * Sets @message to be of @type.
+ */
+void
+g_dbus_message_set_type (GDBusMessage      *message,
+                         GDBusMessageType   type)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (type >=0 && type < 256);
+  message->priv->type = type;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for flags */
+
+/**
+ * g_dbus_message_get_flags:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the flags for @message.
+ *
+ * Returns: Flags that are set (typically values from the #GDBusMessageFlags enumeration bitwise ORed together).
+ */
+GDBusMessageFlags
+g_dbus_message_get_flags (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), G_DBUS_MESSAGE_FLAGS_NONE);
+  return message->priv->flags;
+}
+
+/**
+ * g_dbus_message_set_flags:
+ * @message: A #GDBusMessage.
+ * @flags: Flags for @message that are set (typically values from the #GDBusMessageFlags
+ * enumeration bitwise ORed together).
+ *
+ * Sets the flags to set on @message.
+ */
+void
+g_dbus_message_set_flags (GDBusMessage       *message,
+                          GDBusMessageFlags   flags)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (flags >=0 && flags < 256);
+  message->priv->flags = flags;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_serial:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the serial for @message.
+ *
+ * Returns: A #guint32.
+ */
+guint32
+g_dbus_message_get_serial (GDBusMessage *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+  return message->priv->serial;
+}
+
+/**
+ * g_dbus_message_set_serial:
+ * @message: A #GDBusMessage.
+ * @serial: A #guint32.
+ *
+ * Sets the serial for @message.
+ */
+void
+g_dbus_message_set_serial (GDBusMessage  *message,
+                           guint32        serial)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  message->priv->serial = serial;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: need GI annotations to specify that any guchar value goes for header_field */
+
+/**
+ * g_dbus_message_get_header:
+ * @message: A #GDBusMessage.
+ * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration)
+ *
+ * Gets a header field on @message.
+ *
+ * Returns: A #GVariant with the value if the header was found, %NULL
+ * otherwise. Do not free, it is owned by @message.
+ */
+GVariant *
+g_dbus_message_get_header (GDBusMessage             *message,
+                           GDBusMessageHeaderField   header_field)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  g_return_val_if_fail (header_field >=0 && header_field < 256, NULL);
+  return g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+}
+
+/**
+ * g_dbus_message_set_header:
+ * @message: A #GDBusMessage.
+ * @header_field: A 8-bit unsigned integer (typically a value from the #GDBusMessageHeaderField enumeration)
+ * @value: A #GVariant to set the header field or %NULL to clear the header field.
+ *
+ * Sets a header field on @message.
+ *
+ * If @value is floating, @message assumes ownership of @value.
+ */
+void
+g_dbus_message_set_header (GDBusMessage             *message,
+                           GDBusMessageHeaderField   header_field,
+                           GVariant                 *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (header_field >=0 && header_field < 256);
+  if (value == NULL)
+    {
+      g_hash_table_remove (message->priv->headers, GUINT_TO_POINTER (header_field));
+    }
+  else
+    {
+      g_hash_table_insert (message->priv->headers, GUINT_TO_POINTER (header_field), g_variant_ref_sink (value));
+    }
+}
+
+/**
+ * g_dbus_message_get_header_fields:
+ * @message: A #GDBusMessage.
+ *
+ * Gets an array of all header fields on @message that are set.
+ *
+ * Returns: An array of header fields terminated by
+ * %G_DBUS_MESSAGE_HEADER_FIELD_INVALID.  Each element is a
+ * #guchar. Free with g_free().
+ */
+guchar *
+g_dbus_message_get_header_fields (GDBusMessage  *message)
+{
+  GList *keys;
+  guchar *ret;
+  guint num_keys;
+  GList *l;
+  guint n;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+  keys = g_hash_table_get_keys (message->priv->headers);
+  num_keys = g_list_length (keys);
+  ret = g_new (guchar, num_keys + 1);
+  for (l = keys, n = 0; l != NULL; l = l->next, n++)
+    ret[n] = GPOINTER_TO_UINT (l->data);
+  g_assert (n == num_keys);
+  ret[n] = G_DBUS_MESSAGE_HEADER_FIELD_INVALID;
+  g_list_free (keys);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_body:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the body of a message.
+ *
+ * Returns: A #GVariant or %NULL if the body is empty. Do not free, it is owned by @message.
+ */
+GVariant *
+g_dbus_message_get_body (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return message->priv->body;
+}
+
+/**
+ * g_dbus_message_set_body:
+ * @message: A #GDBusMessage.
+ * @body: Either %NULL or a #GVariant that is a tuple.
+ *
+ * Sets the body @message. As a side-effect the
+ * %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field is set to the
+ * type string of @body (or cleared if @body is %NULL).
+ *
+ * If @body is floating, @message assumes ownership of @body.
+ */
+void
+g_dbus_message_set_body (GDBusMessage  *message,
+                         GVariant      *body)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail ((body == NULL) || g_variant_is_of_type (body, G_VARIANT_TYPE_TUPLE));
+
+  if (message->priv->body != NULL)
+    g_variant_unref (message->priv->body);
+  if (body == NULL)
+    {
+      message->priv->body = NULL;
+      g_dbus_message_set_signature (message, NULL);
+    }
+  else
+    {
+      const gchar *type_string;
+      gsize type_string_len;
+      gchar *signature;
+
+      message->priv->body = g_variant_ref_sink (body);
+
+      type_string = g_variant_get_type_string (body);
+      type_string_len = strlen (type_string);
+      g_assert (type_string_len >= 2);
+      signature = g_strndup (type_string + 1, type_string_len - 2);
+      g_dbus_message_set_signature (message, signature);
+      g_free (signature);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+/**
+ * g_dbus_message_get_unix_fd_list:
+ * @message: A #GDBusMessage.
+ *
+ * Gets the UNIX file descriptors associated with @message, if any.
+ *
+ * This method is only available on UNIX.
+ *
+ * Returns: A #GUnixFDList or %NULL if no file descriptors are
+ * associated. Do not free, this object is owned by @message.
+ */
+GUnixFDList *
+g_dbus_message_get_unix_fd_list (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return message->priv->fd_list;
+}
+
+/**
+ * g_dbus_message_set_unix_fd_list:
+ * @message: A #GDBusMessage.
+ * @fd_list: A #GUnixFDList or %NULL.
+ *
+ * Sets the UNIX file descriptors associated with @message. As a
+ * side-effect the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header
+ * field is set to the number of fds in @fd_list (or cleared if
+ * @fd_list is %NULL).
+ *
+ * This method is only available on UNIX.
+ */
+void
+g_dbus_message_set_unix_fd_list (GDBusMessage  *message,
+                                 GUnixFDList   *fd_list)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list));
+  if (message->priv->fd_list != NULL)
+    g_object_unref (message->priv->fd_list);
+  if (fd_list != NULL)
+    {
+      message->priv->fd_list = g_object_ref (fd_list);
+      g_dbus_message_set_num_unix_fds (message, g_unix_fd_list_get_length (fd_list));
+    }
+  else
+    {
+      message->priv->fd_list = NULL;
+      g_dbus_message_set_num_unix_fds (message, 0);
+    }
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+ensure_input_padding (GMemoryInputStream   *mis,
+                      gsize                 padding_size,
+                      GError              **error)
+{
+  gsize offset;
+  gsize wanted_offset;
+
+  offset = g_seekable_tell (G_SEEKABLE (mis));
+  wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size;
+
+  return g_seekable_seek (G_SEEKABLE (mis), wanted_offset, G_SEEK_SET, NULL, error);
+}
+
+static gchar *
+read_string (GMemoryInputStream    *mis,
+             GDataInputStream      *dis,
+             gsize                  len,
+             GError               **error)
+{
+  GString *s;
+  gchar buf[256];
+  gsize remaining;
+  guchar nul;
+  GError *local_error;
+
+  s = g_string_new (NULL);
+
+  remaining = len;
+  while (remaining > 0)
+    {
+      gsize to_read;
+      gssize num_read;
+
+      to_read = MIN (remaining, sizeof (buf));
+      num_read = g_input_stream_read (G_INPUT_STREAM (mis),
+                                      buf,
+                                      to_read,
+                                      NULL,
+                                      error);
+      if (num_read < 0)
+        goto fail;
+      if (num_read == 0)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Wanted to read %" G_GSIZE_FORMAT " bytes but got EOF"),
+                       to_read);
+          goto fail;
+        }
+
+      remaining -= num_read;
+      g_string_append_len (s, buf, num_read);
+    }
+
+  local_error = NULL;
+  nul = g_data_input_stream_read_byte (dis, NULL, &local_error);
+  if (local_error != NULL)
+    {
+      g_propagate_error (error, local_error);
+      goto fail;
+    }
+  if (nul != '\0')
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Expected NUL byte after the string `%s' but found `%c' (%d)"),
+                   s->str, nul, nul);
+      goto fail;
+    }
+
+  return g_string_free (s, FALSE);
+
+ fail:
+  g_string_free (s, TRUE);
+  return NULL;
+}
+
+static GVariant *
+parse_value_from_blob (GMemoryInputStream    *mis,
+                       GDataInputStream      *dis,
+                       const GVariantType    *type,
+                       GError               **error)
+{
+  GVariant *ret;
+  GError *local_error;
+
+  local_error = NULL;
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+    {
+      gboolean v;
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_boolean (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+    {
+      guchar v;
+      v = g_data_input_stream_read_byte (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_byte (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+    {
+      gint16 v;
+      if (!ensure_input_padding (mis, 2, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_int16 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_int16 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+    {
+      guint16 v;
+      if (!ensure_input_padding (mis, 2, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_uint16 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_uint16 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+    {
+      gint32 v;
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_int32 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_int32 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+    {
+      guint32 v;
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_uint32 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+    {
+      gint64 v;
+      if (!ensure_input_padding (mis, 8, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_int64 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_int64 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+    {
+      guint64 v;
+      if (!ensure_input_padding (mis, 8, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_uint64 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      ret = g_variant_new_uint64 (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+    {
+      guint64 v;
+      gdouble *encoded;
+      if (!ensure_input_padding (mis, 8, &local_error))
+        goto fail;
+      v = g_data_input_stream_read_uint64 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      /* TODO: hmm */
+      encoded = (gdouble *) &v;
+      ret = g_variant_new_double (*encoded);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+    {
+      guint32 len;
+      gchar *v;
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      v = read_string (mis, dis, (gsize) len, &local_error);
+      if (v == NULL)
+        goto fail;
+      ret = g_variant_new_string (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+    {
+      guint32 len;
+      gchar *v;
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      v = read_string (mis, dis, (gsize) len, &local_error);
+      if (v == NULL)
+        goto fail;
+      if (!g_variant_is_object_path (v))
+        {
+          g_set_error (&local_error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Parsed value `%s' is not a valid D-Bus object path"),
+                       v);
+          goto fail;
+        }
+      ret = g_variant_new_object_path (v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+    {
+      guchar len;
+      gchar *v;
+      len = g_data_input_stream_read_byte (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      v = read_string (mis, dis, (gsize) len, &local_error);
+      if (v == NULL)
+        goto fail;
+      if (!g_variant_is_signature (v))
+        {
+          g_set_error (&local_error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Parsed value `%s' is not a valid D-Bus signature"),
+                       v);
+          goto fail;
+        }
+      ret = g_variant_new_signature (v);
+    }
+  else if (g_variant_type_is_array (type))
+    {
+      guint32 array_len;
+      goffset offset;
+      goffset target;
+      const GVariantType *element_type;
+      GVariantBuilder *builder;
+
+      if (!ensure_input_padding (mis, 4, &local_error))
+        goto fail;
+      array_len = g_data_input_stream_read_uint32 (dis, NULL, &local_error);
+
+      if (array_len > (2<<26))
+        {
+          g_set_error (&local_error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Encountered array of length %" G_GUINT32_FORMAT " bytes. Maximum length is 2<<26 bytes."),
+                       array_len);
+          goto fail;
+        }
+
+      builder = g_variant_builder_new (type);
+      element_type = g_variant_type_element (type);
+
+      /* TODO: optimize array of primitive types */
+
+      offset = g_seekable_tell (G_SEEKABLE (mis));
+      target = offset + array_len;
+      while (offset < target)
+        {
+          GVariant *item;
+          item = parse_value_from_blob (mis, dis, element_type, &local_error);
+          if (item == NULL)
+            {
+              g_variant_builder_unref (builder);
+              goto fail;
+            }
+          g_variant_builder_add_value (builder, item);
+          offset = g_seekable_tell (G_SEEKABLE (mis));
+        }
+
+      ret = g_variant_builder_end (builder);
+    }
+  else if (g_variant_type_is_dict_entry (type))
+    {
+      const GVariantType *key_type;
+      const GVariantType *value_type;
+      GVariant *key;
+      GVariant *value;
+
+      if (!ensure_input_padding (mis, 8, &local_error))
+        goto fail;
+
+      key_type = g_variant_type_key (type);
+      key = parse_value_from_blob (mis, dis, key_type, &local_error);
+      if (key == NULL)
+        goto fail;
+
+      value_type = g_variant_type_value (type);
+      value = parse_value_from_blob (mis, dis, value_type, &local_error);
+      if (value == NULL)
+        {
+          g_variant_unref (key);
+          goto fail;
+        }
+      ret = g_variant_new_dict_entry (key, value);
+    }
+  else if (g_variant_type_is_tuple (type))
+    {
+      const GVariantType *element_type;
+      GVariantBuilder *builder;
+
+      if (!ensure_input_padding (mis, 8, &local_error))
+        goto fail;
+
+      builder = g_variant_builder_new (type);
+      element_type = g_variant_type_first (type);
+      while (element_type != NULL)
+        {
+          GVariant *item;
+          item = parse_value_from_blob (mis, dis, element_type, &local_error);
+          if (item == NULL)
+            {
+              g_variant_builder_unref (builder);
+              goto fail;
+            }
+          g_variant_builder_add_value (builder, item);
+
+          element_type = g_variant_type_next (element_type);
+        }
+      ret = g_variant_builder_end (builder);
+    }
+  else if (g_variant_type_is_variant (type))
+    {
+      guchar siglen;
+      gchar *sig;
+      GVariantType *variant_type;
+      GVariant *value;
+
+      siglen = g_data_input_stream_read_byte (dis, NULL, &local_error);
+      if (local_error != NULL)
+        goto fail;
+      sig = read_string (mis, dis, (gsize) siglen, &local_error);
+      if (sig == NULL)
+        goto fail;
+      if (!g_variant_is_signature (sig))
+        {
+          g_set_error (&local_error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Parsed value `%s' for variant is not a valid D-Bus signature"),
+                       sig);
+          goto fail;
+        }
+      variant_type = g_variant_type_new (sig);
+      value = parse_value_from_blob (mis, dis, variant_type, &local_error);
+      g_variant_type_free (variant_type);
+      if (value == NULL)
+        goto fail;
+      ret = g_variant_new_variant (value);
+    }
+  else
+    {
+      gchar *s;
+      s = g_variant_type_dup_string (type);
+      g_set_error (&local_error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Error deserializing GVariant with type-string `%s' from the D-Bus wire format"),
+                   s);
+      g_free (s);
+      goto fail;
+    }
+
+  g_assert (ret != NULL);
+  return ret;
+
+ fail:
+  g_propagate_error (error, local_error);
+  return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* message_header must be at least 16 bytes */
+
+/**
+ * g_dbus_message_bytes_needed:
+ * @blob: A blob represent a binary D-Bus message.
+ * @blob_len: The length of @blob (must be at least 16).
+ * @error: Return location for error or %NULL.
+ *
+ * Utility function to calculate how many bytes are needed to
+ * completely deserialize the D-Bus message stored at @blob.
+ *
+ * Returns: Number of bytes needed or -1 if @error is set (e.g. if
+ * @blob contains invalid data or not enough data is available to
+ * determine the size).
+ */
+gssize
+g_dbus_message_bytes_needed (guchar  *blob,
+                             gsize    blob_len,
+                             GError **error)
+{
+  gssize ret;
+
+  ret = -1;
+
+  g_return_val_if_fail (blob != NULL, -1);
+  g_return_val_if_fail (error == NULL || *error == NULL, -1);
+  g_return_val_if_fail (blob_len >= 16, -1);
+
+  if (blob[0] == 'l')
+    {
+      /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */
+      ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]);
+      /* round up so it's a multiple of 8 */
+      ret = 8 * ((ret + 7)/8);
+      /* finally add the body size */
+      ret += GUINT32_FROM_LE (((guint32 *) blob)[1]);
+    }
+  else if (blob[0] == 'B')
+    {
+      /* TODO */
+      g_assert_not_reached ();
+    }
+  else
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   "Unable to determine message blob length - given blob is malformed");
+    }
+
+  if (ret > (2<<27))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   "Blob indicates that message exceeds maximum message length (128MiB)");
+      ret = -1;
+    }
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_new_from_blob:
+ * @blob: A blob represent a binary D-Bus message.
+ * @blob_len: The length of @blob.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a new #GDBusMessage from the data stored at @blob.
+ *
+ * Returns: A new #GDBusMessage or %NULL if @error is set. Free with
+ * g_object_unref().
+ */
+GDBusMessage *
+g_dbus_message_new_from_blob (guchar    *blob,
+                              gsize      blob_len,
+                              GError   **error)
+{
+  gboolean ret;
+  GMemoryInputStream *mis;
+  GDataInputStream *dis;
+  GDBusMessage *message;
+  guchar endianness;
+  guchar major_protocol_version;
+  GDataStreamByteOrder byte_order;
+  guint32 message_body_len;
+  GVariant *headers;
+  GVariant *item;
+  GVariantIter iter;
+  GVariant *signature;
+
+  ret = FALSE;
+
+  g_return_val_if_fail (blob != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  g_return_val_if_fail (blob_len >= 12, NULL);
+
+  message = g_dbus_message_new ();
+
+  mis = G_MEMORY_INPUT_STREAM (g_memory_input_stream_new_from_data (blob, blob_len, NULL));
+  dis = g_data_input_stream_new (G_INPUT_STREAM (mis));
+
+  endianness = g_data_input_stream_read_byte (dis, NULL, NULL);
+  switch (endianness)
+    {
+    case 'l':
+      byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN;
+      break;
+    case 'B':
+      byte_order = G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN;
+      break;
+    default:
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Invalid endianness value. Expected 'l' or 'B' but found '%c' (%d)"),
+                   endianness, endianness);
+      goto out;
+    }
+  g_data_input_stream_set_byte_order (dis, byte_order);
+
+  message->priv->type = g_data_input_stream_read_byte (dis, NULL, NULL);
+  message->priv->flags = g_data_input_stream_read_byte (dis, NULL, NULL);
+  major_protocol_version = g_data_input_stream_read_byte (dis, NULL, NULL);
+  if (major_protocol_version != 1)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Invalid major protocol version. Expected 1 but found %d"),
+                   major_protocol_version);
+      goto out;
+    }
+  message_body_len = g_data_input_stream_read_uint32 (dis, NULL, NULL);
+  message->priv->serial = g_data_input_stream_read_uint32 (dis, NULL, NULL);
+
+  headers = parse_value_from_blob (mis,
+                                   dis,
+                                   G_VARIANT_TYPE ("a{yv}"),
+                                   error);
+  if (headers == NULL)
+    goto out;
+  g_variant_ref_sink (headers);
+  g_variant_iter_init (&iter, headers);
+  while ((item = g_variant_iter_next_value (&iter)))
+    {
+      guchar header_field;
+      GVariant *value;
+      g_variant_get (item,
+                     "{yv}",
+                     &header_field,
+                     &value);
+      g_dbus_message_set_header (message, header_field, value);
+    }
+  g_variant_unref (headers);
+
+  signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+  if (signature != NULL)
+    {
+      const gchar *signature_str;
+      gsize signature_str_len;
+
+      signature_str = g_variant_get_string (signature, NULL);
+      signature_str_len = strlen (signature_str);
+
+      /* signature but no body */
+      if (message_body_len == 0 && signature_str_len > 0)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Signature header with signature `%s' found but message body is empty"),
+                       signature_str);
+          goto out;
+        }
+      else if (signature_str_len > 0)
+        {
+          GVariantType *variant_type;
+          gchar *tupled_signature_str;
+
+          if (!g_variant_is_signature (signature_str))
+            {
+              g_set_error (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Parsed value `%s' is not a valid D-Bus signature (for body)"),
+                           signature_str);
+              goto out;
+            }
+          tupled_signature_str = g_strdup_printf ("(%s)", signature_str);
+          variant_type = g_variant_type_new (tupled_signature_str);
+          g_free (tupled_signature_str);
+          message->priv->body = parse_value_from_blob (mis,
+                                                       dis,
+                                                       variant_type,
+                                                       error);
+          if (message->priv->body == NULL)
+            {
+              g_variant_type_free (variant_type);
+              goto out;
+            }
+          g_variant_ref_sink (message->priv->body);
+          g_variant_type_free (variant_type);
+        }
+    }
+  else
+    {
+      /* no signature, this is only OK if the body is empty */
+      if (message_body_len != 0)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("No signature header in message but the message body is %" G_GUINT32_FORMAT " bytes"),
+                       message_body_len);
+          goto out;
+        }
+    }
+
+
+  ret = TRUE;
+
+ out:
+  g_object_unref (dis);
+  g_object_unref (mis);
+
+  if (ret)
+    {
+      return message;
+    }
+  else
+    {
+      if (message != NULL)
+        g_object_unref (message);
+      return NULL;
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gsize
+ensure_output_padding (GMemoryOutputStream  *mos,
+                       GDataOutputStream    *dos,
+                       gsize                 padding_size)
+{
+  gsize offset;
+  gsize wanted_offset;
+  gsize padding_needed;
+  guint n;
+
+  offset = g_memory_output_stream_get_data_size (mos);
+  wanted_offset = ((offset + padding_size - 1) / padding_size) * padding_size;
+  padding_needed = wanted_offset - offset;
+
+  for (n = 0; n < padding_needed; n++)
+    g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+
+  return padding_needed;
+}
+
+static gboolean
+append_value_to_blob (GVariant             *value,
+                      GMemoryOutputStream  *mos,
+                      GDataOutputStream    *dos,
+                      gsize                *out_padding_added,
+                      GError              **error)
+{
+  const GVariantType *type;
+  gsize padding_added;
+
+  padding_added = 0;
+
+  type = g_variant_get_type (value);
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+    {
+      gboolean v = g_variant_get_boolean (value);
+      padding_added = ensure_output_padding (mos, dos, 4);
+      g_data_output_stream_put_uint32 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+    {
+      guint8 v = g_variant_get_byte (value);
+      g_data_output_stream_put_byte (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+    {
+      gint16 v = g_variant_get_int16 (value);
+      padding_added = ensure_output_padding (mos, dos, 2);
+      g_data_output_stream_put_int16 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+    {
+      guint16 v = g_variant_get_uint16 (value);
+      padding_added = ensure_output_padding (mos, dos, 2);
+      g_data_output_stream_put_uint16 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+    {
+      gint32 v = g_variant_get_int32 (value);
+      padding_added = ensure_output_padding (mos, dos, 4);
+      g_data_output_stream_put_int32 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+    {
+      guint32 v = g_variant_get_uint32 (value);
+      padding_added = ensure_output_padding (mos, dos, 4);
+      g_data_output_stream_put_uint32 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+    {
+      gint64 v = g_variant_get_int64 (value);
+      padding_added = ensure_output_padding (mos, dos, 8);
+      g_data_output_stream_put_int64 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+    {
+      guint64 v = g_variant_get_uint64 (value);
+      padding_added = ensure_output_padding (mos, dos, 8);
+      g_data_output_stream_put_uint64 (dos, v, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+    {
+      guint64 *encoded;
+      gdouble v = g_variant_get_double (value);
+      padding_added = ensure_output_padding (mos, dos, 8);
+      /* TODO: hmm */
+      encoded = (guint64 *) &v;
+      g_data_output_stream_put_uint64 (dos, *encoded, NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *v = g_variant_get_string (value, NULL);
+      gsize len;
+      padding_added = ensure_output_padding (mos, dos, 4);
+      len = strlen (v);
+      g_data_output_stream_put_uint32 (dos, len, NULL, NULL);
+      g_data_output_stream_put_string (dos, v, NULL, NULL);
+      g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+    {
+      /* TODO: validate object path */
+      const gchar *v = g_variant_get_string (value, NULL);
+      gsize len;
+      padding_added = ensure_output_padding (mos, dos, 4);
+      len = strlen (v);
+      g_data_output_stream_put_uint32 (dos, len, NULL, NULL);
+      g_data_output_stream_put_string (dos, v, NULL, NULL);
+      g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+    {
+      /* TODO: validate signature (including max len being 255) */
+      const gchar *v = g_variant_get_string (value, NULL);
+      gsize len;
+      len = strlen (v);
+      g_data_output_stream_put_byte (dos, len, NULL, NULL);
+      g_data_output_stream_put_string (dos, v, NULL, NULL);
+      g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+    }
+  else if (g_variant_type_is_array (type))
+    {
+      GVariant *item;
+      GVariantIter iter;
+      goffset array_len_offset;
+      goffset array_payload_begin_offset;
+      goffset cur_offset;
+      gsize array_len;
+      guint n;
+
+      padding_added = ensure_output_padding (mos, dos, 4);
+
+      /* array length - will be filled in later */
+      array_len_offset = g_memory_output_stream_get_data_size (mos);
+      g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL);
+
+      /* From the D-Bus spec:
+       *
+       *   "A UINT32 giving the length of the array data in bytes,
+       *    followed by alignment padding to the alignment boundary of
+       *    the array element type, followed by each array element. The
+       *    array length is from the end of the alignment padding to
+       *    the end of the last element, i.e. it does not include the
+       *    padding after the length, or any padding after the last
+       *    element."
+       *
+       * Thus, we need to count how much padding the first element
+       * contributes and subtract that from the array length.
+       */
+      array_payload_begin_offset = g_memory_output_stream_get_data_size (mos);
+
+      g_variant_iter_init (&iter, value);
+      n = 0;
+      while ((item = g_variant_iter_next_value (&iter)))
+        {
+          gsize padding_added_for_item;
+          if (!append_value_to_blob (item, mos, dos, &padding_added_for_item, error))
+            goto fail;
+          if (n == 0)
+            {
+              array_payload_begin_offset += padding_added_for_item;
+            }
+          n++;
+        }
+
+      cur_offset = g_memory_output_stream_get_data_size (mos);
+
+      array_len = cur_offset - array_payload_begin_offset;
+
+      if (!g_seekable_seek (G_SEEKABLE (mos), array_len_offset, G_SEEK_SET, NULL, error))
+        goto fail;
+
+      g_data_output_stream_put_uint32 (dos, array_len, NULL, NULL);
+
+      if (!g_seekable_seek (G_SEEKABLE (mos), cur_offset, G_SEEK_SET, NULL, error))
+        goto fail;
+    }
+  else if (g_variant_type_is_dict_entry (type) || g_variant_type_is_tuple (type))
+    {
+      GVariant *item;
+      GVariantIter iter;
+
+      padding_added = ensure_output_padding (mos, dos, 8);
+
+      g_variant_iter_init (&iter, value);
+
+      while ((item = g_variant_iter_next_value (&iter)))
+        {
+          if (!append_value_to_blob (item, mos, dos, NULL, error))
+            goto fail;
+        }
+    }
+  else if (g_variant_type_is_variant (type))
+    {
+      GVariant *child;
+      const gchar *signature;
+      child = g_variant_get_child_value (value, 0);
+      signature = g_variant_get_type_string (child);
+      /* TODO: validate signature (including max len being 255) */
+      g_data_output_stream_put_byte (dos, strlen (signature), NULL, NULL);
+      g_data_output_stream_put_string (dos, signature, NULL, NULL);
+      g_data_output_stream_put_byte (dos, '\0', NULL, NULL);
+      if (!append_value_to_blob (child, mos, dos, NULL, error))
+        {
+          g_variant_unref (child);
+          goto fail;
+        }
+      g_variant_unref (child);
+    }
+  else
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Error serializing GVariant with type-string `%s' to the D-Bus wire format"),
+                   g_variant_get_type_string (value));
+      goto fail;
+    }
+
+  if (out_padding_added != NULL)
+    *out_padding_added = padding_added;
+
+  return TRUE;
+
+ fail:
+  return FALSE;
+}
+
+static gboolean
+append_body_to_blob (GVariant             *value,
+                     GMemoryOutputStream  *mos,
+                     GDataOutputStream    *dos,
+                     GError              **error)
+{
+  gboolean ret;
+  GVariant *item;
+  GVariantIter iter;
+
+  ret = FALSE;
+
+  if (!g_variant_is_of_type (value, G_VARIANT_TYPE_TUPLE))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   "Expected a tuple for the body of the GDBusMessage.");
+      goto fail;
+    }
+
+  g_variant_iter_init (&iter, value);
+  while ((item = g_variant_iter_next_value (&iter)))
+    {
+      if (!append_value_to_blob (item, mos, dos, NULL, error))
+        goto fail;
+    }
+  return TRUE;
+
+ fail:
+  return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_to_blob:
+ * @message: A #GDBusMessage.
+ * @out_size: Return location for size of generated blob.
+ * @error: Return location for error.
+ *
+ * Serializes @message to a blob.
+ *
+ * Returns: A pointer to a valid binary D-Bus message of @out_size bytes
+ * generated by @message or %NULL if @error is set. Free with g_free().
+ */
+guchar *
+g_dbus_message_to_blob (GDBusMessage   *message,
+                        gsize          *out_size,
+                        GError        **error)
+{
+  GMemoryOutputStream *mos;
+  GDataOutputStream *dos;
+  guchar *ret;
+  gsize size;
+  GDataStreamByteOrder byte_order;
+  goffset body_len_offset;
+  goffset body_start_offset;
+  gsize body_size;
+  GVariant *header_fields;
+  GVariantBuilder *builder;
+  GHashTableIter hash_iter;
+  gpointer key;
+  GVariant *header_value;
+  GVariant *signature;
+  const gchar *signature_str;
+  gint num_fds_in_message;
+  gint num_fds_according_to_header;
+
+  ret = NULL;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  g_return_val_if_fail (out_size != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  mos = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, g_free));
+  dos = g_data_output_stream_new (G_OUTPUT_STREAM (mos));
+
+  /* TODO: detect endianess... */
+  byte_order = G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN;
+  g_data_output_stream_set_byte_order (dos, byte_order);
+
+  /* Core header */
+  g_data_output_stream_put_byte (dos, byte_order == G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN ? 'l' : 'B', NULL, NULL);
+  g_data_output_stream_put_byte (dos, message->priv->type, NULL, NULL);
+  g_data_output_stream_put_byte (dos, message->priv->flags, NULL, NULL);
+  g_data_output_stream_put_byte (dos, 1, NULL, NULL); /* major protocol version */
+  body_len_offset = g_memory_output_stream_get_data_size (mos);
+  /* body length - will be filled in later */
+  g_data_output_stream_put_uint32 (dos, 0xF00DFACE, NULL, NULL);
+  g_data_output_stream_put_uint32 (dos, message->priv->serial, NULL, NULL);
+
+  num_fds_in_message = 0;
+#ifdef G_OS_UNIX
+  if (message->priv->fd_list != NULL)
+    num_fds_in_message = g_unix_fd_list_get_length (message->priv->fd_list);
+#endif
+  num_fds_according_to_header = g_dbus_message_get_num_unix_fds (message);
+  /* TODO: check we have all the right header fields and that they are the correct value etc etc */
+  if (num_fds_in_message != num_fds_according_to_header)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Message has %d fds but the header field indicates %d fds"),
+                   num_fds_in_message,
+                   num_fds_according_to_header);
+      goto out;
+    }
+
+  builder = g_variant_builder_new (G_VARIANT_TYPE ("a{yv}"));//G_VARIANT_TYPE_ARRAY);
+  g_hash_table_iter_init (&hash_iter, message->priv->headers);
+  while (g_hash_table_iter_next (&hash_iter, &key, (gpointer) &header_value))
+    {
+      g_variant_builder_add (builder,
+                             "{yv}",
+                             (guchar) GPOINTER_TO_UINT (key),
+                             header_value);
+    }
+  header_fields = g_variant_new ("a{yv}", builder);
+
+  if (!append_value_to_blob (header_fields, mos, dos, NULL, error))
+    {
+      g_variant_unref (header_fields);
+      goto out;
+    }
+  g_variant_unref (header_fields);
+
+  /* header size must be a multiple of 8 */
+  ensure_output_padding (mos, dos, 8);
+
+  body_start_offset = g_memory_output_stream_get_data_size (mos);
+
+  signature = g_dbus_message_get_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+  signature_str = NULL;
+  if (signature != NULL)
+      signature_str = g_variant_get_string (signature, NULL);
+  if (message->priv->body != NULL)
+    {
+      gchar *tupled_signature_str;
+      tupled_signature_str = g_strdup_printf ("(%s)", signature_str);
+      if (signature == NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Message body has signature `%s' but there is no signature header"),
+                       signature_str);
+          g_free (tupled_signature_str);
+          goto out;
+        }
+      else if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->priv->body)) != 0)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Message body has type signature `%s' but signature in the header field is `%s'"),
+                       tupled_signature_str, g_variant_get_type_string (message->priv->body));
+          g_free (tupled_signature_str);
+          goto out;
+        }
+      g_free (tupled_signature_str);
+      if (!append_body_to_blob (message->priv->body, mos, dos, error))
+        goto out;
+    }
+  else
+    {
+      if (signature != NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_ARGUMENT,
+                       _("Message body is empty but signature in the header field is `(%s)'"),
+                       signature_str);
+          goto out;
+        }
+    }
+
+  /* OK, we're done writing the message - set the body length */
+  size = g_memory_output_stream_get_data_size (mos);
+  body_size = size - body_start_offset;
+
+  if (!g_seekable_seek (G_SEEKABLE (mos), body_len_offset, G_SEEK_SET, NULL, error))
+    goto out;
+
+  g_data_output_stream_put_uint32 (dos, body_size, NULL, NULL);
+
+  *out_size = size;
+  ret = g_memdup (g_memory_output_stream_get_data (mos), size);
+
+ out:
+  g_object_unref (dos);
+  g_object_unref (mos);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint32
+get_uint32_header (GDBusMessage            *message,
+                   GDBusMessageHeaderField  header_field)
+{
+  GVariant *value;
+  guint32 ret;
+
+  ret = 0;
+  value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+  if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32))
+    ret = g_variant_get_uint32 (value);
+
+  return ret;
+}
+
+static const gchar *
+get_string_header (GDBusMessage            *message,
+                   GDBusMessageHeaderField  header_field)
+{
+  GVariant *value;
+  const gchar *ret;
+
+  ret = NULL;
+  value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+  if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+    ret = g_variant_get_string (value, NULL);
+
+  return ret;
+}
+
+static const gchar *
+get_object_path_header (GDBusMessage            *message,
+                        GDBusMessageHeaderField  header_field)
+{
+  GVariant *value;
+  const gchar *ret;
+
+  ret = NULL;
+  value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+  if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH))
+    ret = g_variant_get_string (value, NULL);
+
+  return ret;
+}
+
+static const gchar *
+get_signature_header (GDBusMessage            *message,
+                      GDBusMessageHeaderField  header_field)
+{
+  GVariant *value;
+  const gchar *ret;
+
+  ret = NULL;
+  value = g_hash_table_lookup (message->priv->headers, GUINT_TO_POINTER (header_field));
+  if (value != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_SIGNATURE))
+    ret = g_variant_get_string (value, NULL);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+set_uint32_header (GDBusMessage             *message,
+                   GDBusMessageHeaderField   header_field,
+                   guint32                   value)
+{
+  g_dbus_message_set_header (message,
+                             header_field,
+                             g_variant_new_uint32 (value));
+}
+
+static void
+set_string_header (GDBusMessage             *message,
+                   GDBusMessageHeaderField   header_field,
+                   const gchar              *value)
+{
+  g_dbus_message_set_header (message,
+                             header_field,
+                             value == NULL ? NULL : g_variant_new_string (value));
+}
+
+static void
+set_object_path_header (GDBusMessage             *message,
+                        GDBusMessageHeaderField   header_field,
+                        const gchar              *value)
+{
+  g_dbus_message_set_header (message,
+                             header_field,
+                             value == NULL ? NULL : g_variant_new_object_path (value));
+}
+
+static void
+set_signature_header (GDBusMessage             *message,
+                      GDBusMessageHeaderField   header_field,
+                      const gchar              *value)
+{
+  g_dbus_message_set_header (message,
+                             header_field,
+                             value == NULL ? NULL : g_variant_new_signature (value));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_reply_serial:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field.
+ *
+ * Returns: The value.
+ */
+guint32
+g_dbus_message_get_reply_serial (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+  return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL);
+}
+
+/**
+ * g_dbus_message_set_reply_serial:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL header field.
+ */
+void
+g_dbus_message_set_reply_serial (GDBusMessage  *message,
+                                 guint32        value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_interface:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_interface (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE);
+}
+
+/**
+ * g_dbus_message_set_interface:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE header field.
+ */
+void
+g_dbus_message_set_interface (GDBusMessage  *message,
+                              const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_dbus_is_interface_name (value));
+  set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_member:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_member (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER);
+}
+
+/**
+ * g_dbus_message_set_member:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_MEMBER header field.
+ */
+void
+g_dbus_message_set_member (GDBusMessage  *message,
+                           const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_dbus_is_member_name (value));
+  set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_path:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_path (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH);
+}
+
+/**
+ * g_dbus_message_set_path:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_PATH header field.
+ */
+void
+g_dbus_message_set_path (GDBusMessage  *message,
+                         const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_variant_is_object_path (value));
+  set_object_path_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_sender:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_sender (GDBusMessage *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER);
+}
+
+/**
+ * g_dbus_message_set_sender:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SENDER header field.
+ */
+void
+g_dbus_message_set_sender (GDBusMessage  *message,
+                           const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_dbus_is_name (value));
+  set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SENDER, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_destination:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_destination (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION);
+}
+
+/**
+ * g_dbus_message_set_destination:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION header field.
+ */
+void
+g_dbus_message_set_destination (GDBusMessage  *message,
+                                const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_dbus_is_name (value));
+  set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_error_name:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_error_name (GDBusMessage  *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  return get_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME);
+}
+
+/**
+ * g_dbus_message_set_error_name:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field.
+ */
+void
+g_dbus_message_set_error_name (GDBusMessage  *message,
+                               const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_dbus_is_interface_name (value));
+  set_string_header (message, G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_signature:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field.
+ *
+ * Returns: The value.
+ */
+const gchar *
+g_dbus_message_get_signature (GDBusMessage  *message)
+{
+  const gchar *ret;
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  ret = get_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE);
+  if (ret == NULL)
+    ret = "";
+  return ret;
+}
+
+/**
+ * g_dbus_message_set_signature:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE header field.
+ */
+void
+g_dbus_message_set_signature (GDBusMessage  *message,
+                              const gchar   *value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (value == NULL || g_variant_is_signature (value));
+  set_signature_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_arg0:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience to get the first item in the body of @message.
+ *
+ * Returns: The string item or %NULL if the first item in the body of
+ * @message is not a string.
+ */
+const gchar *
+g_dbus_message_get_arg0 (GDBusMessage  *message)
+{
+  const gchar *ret;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+  ret = NULL;
+
+  if (message->priv->body != NULL && g_variant_is_of_type (message->priv->body, G_VARIANT_TYPE_TUPLE))
+    {
+      GVariant *item;
+      item = g_variant_get_child_value (message->priv->body, 0);
+      if (g_variant_is_of_type (item, G_VARIANT_TYPE_STRING))
+        ret = g_variant_get_string (item, NULL);
+    }
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_get_num_unix_fds:
+ * @message: A #GDBusMessage.
+ *
+ * Convenience getter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field.
+ *
+ * Returns: The value.
+ */
+guint32
+g_dbus_message_get_num_unix_fds (GDBusMessage *message)
+{
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), 0);
+  return get_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS);
+}
+
+/**
+ * g_dbus_message_set_num_unix_fds:
+ * @message: A #GDBusMessage.
+ * @value: The value to set.
+ *
+ * Convenience setter for the %G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS header field.
+ */
+void
+g_dbus_message_set_num_unix_fds (GDBusMessage  *message,
+                                 guint32        value)
+{
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  set_uint32_header (message, G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS, value);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_message_to_gerror:
+ * @message: A #GDBusMessage.
+ * @error: The #GError to set.
+ *
+ * If @message is not of type %G_DBUS_MESSAGE_TYPE_ERROR does
+ * nothing and returns %FALSE.
+ *
+ * Otherwise this method encodes the error in @message as a #GError
+ * using g_dbus_error_set_dbus_error() using the information in the
+ * %G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME header field of @message as
+ * well as the first string item in @message's body.
+ *
+ * Returns: %TRUE if @error was set, %FALSE otherwise.
+ */
+gboolean
+g_dbus_message_to_gerror (GDBusMessage   *message,
+                          GError        **error)
+{
+  gboolean ret;
+  const gchar *error_name;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE);
+
+  ret = FALSE;
+  if (message->priv->type != G_DBUS_MESSAGE_TYPE_ERROR)
+    goto out;
+
+  error_name = g_dbus_message_get_error_name (message);
+  if (error_name != NULL)
+    {
+      GVariant *body;
+
+      body = g_dbus_message_get_body (message);
+
+      if (body != NULL && g_variant_is_of_type (body, G_VARIANT_TYPE ("(s)")))
+        {
+          const gchar *error_message;
+          g_variant_get (body, "(s)", &error_message);
+          g_dbus_error_set_dbus_error (error,
+                                       error_name,
+                                       error_message,
+                                       NULL);
+        }
+      else
+        {
+          /* these two situations are valid, yet pretty rare */
+          if (body != NULL)
+            {
+              g_dbus_error_set_dbus_error (error,
+                                           error_name,
+                                           "",
+                                           _("Error return with body of type `%s'"),
+                                           g_variant_get_type_string (body));
+            }
+          else
+            {
+              g_dbus_error_set_dbus_error (error,
+                                           error_name,
+                                           "",
+                                           _("Error return with empty body"));
+            }
+        }
+    }
+  else
+    {
+      /* TOOD: this shouldn't happen - should check this at message serialization
+       * time and disconnect the peer.
+       */
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Error return without error-name header!");
+    }
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+enum_to_string (GType enum_type, gint value)
+{
+  gchar *ret;
+  GEnumClass *klass;
+  GEnumValue *enum_value;
+
+  klass = g_type_class_ref (enum_type);
+  enum_value = g_enum_get_value (klass, value);
+  if (enum_value != NULL)
+    ret = g_strdup (enum_value->value_nick);
+  else
+    ret = g_strdup_printf ("unknown (value %d)", value);
+  g_type_class_unref (klass);
+  return ret;
+}
+
+static gchar *
+flags_to_string (GType flags_type, guint value)
+{
+  GString *s;
+  GFlagsClass *klass;
+  guint n;
+
+  klass = g_type_class_ref (flags_type);
+  s = g_string_new (NULL);
+  for (n = 0; n < 32; n++)
+    {
+      if ((value & (1<<n)) != 0)
+        {
+          GFlagsValue *flags_value;
+          flags_value = g_flags_get_first_value (klass, (1<<n));
+          if (s->len > 0)
+            g_string_append_c (s, ',');
+          if (flags_value != NULL)
+            g_string_append (s, flags_value->value_nick);
+          else
+            g_string_append_printf (s, "unknown (bit %d)", n);
+        }
+    }
+  if (s->len == 0)
+    g_string_append (s, "none");
+  g_type_class_unref (klass);
+  return g_string_free (s, FALSE);;
+}
+
+static gint
+_sort_keys_func (gconstpointer a,
+                 gconstpointer b)
+{
+  gint ia;
+  gint ib;
+
+  ia = GPOINTER_TO_INT (a);
+  ib = GPOINTER_TO_INT (b);
+
+  return ia - ib;
+}
+
+/**
+ * g_dbus_message_print:
+ * @message: A #GDBusMessage.
+ * @indent: Indentation level.
+ *
+ * Produces a human-readable multi-line description of @message.
+ *
+ * The contents of the description has no ABI guarantees, the contents
+ * and formatting is subject to change at any time. Typical output
+ * looks something like this:
+ * <programlisting>
+ * Type:    method-call
+ * Flags:   none
+ * Version: 0
+ * Serial:  4
+ * Headers:
+ *   path -> objectpath '/org/gtk/GDBus/TestObject'
+ *   interface -> 'org.gtk.GDBus.TestInterface'
+ *   member -> 'GimmeStdout'
+ *   destination -> ':1.146'
+ * Body: ()
+ * UNIX File Descriptors:
+ *   (none)
+ * </programlisting>
+ * or
+ * <programlisting>
+ * Type:    method-return
+ * Flags:   no-reply-expected
+ * Version: 0
+ * Serial:  477
+ * Headers:
+ *   reply-serial -> uint32 4
+ *   destination -> ':1.159'
+ *   sender -> ':1.146'
+ *   num-unix-fds -> uint32 1
+ * Body: ()
+ * UNIX File Descriptors:
+ *   fd 12: dev=0:10,mode=020620,ino=5,uid=500,gid=5,rdev=136:2,size=0,atime=1273085037,mtime=1273085851,ctime=1272982635
+ * </programlisting>
+ *
+ * Returns: A string that should be freed with g_free().
+ */
+gchar *
+g_dbus_message_print (GDBusMessage *message,
+                      guint         indent)
+{
+  GString *str;
+  gchar *s;
+  GList *keys;
+  GList *l;
+
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+
+  str = g_string_new (NULL);
+
+  s = enum_to_string (G_TYPE_DBUS_MESSAGE_TYPE, message->priv->type);
+  g_string_append_printf (str, "%*sType:    %s\n", indent, "", s);
+  g_free (s);
+  s = flags_to_string (G_TYPE_DBUS_MESSAGE_FLAGS, message->priv->flags);
+  g_string_append_printf (str, "%*sFlags:   %s\n", indent, "", s);
+  g_free (s);
+  g_string_append_printf (str, "%*sVersion: %d\n", indent, "", message->priv->major_protocol_version);
+  g_string_append_printf (str, "%*sSerial:  %d\n", indent, "", message->priv->serial);
+
+  g_string_append_printf (str, "%*sHeaders:\n", indent, "");
+  keys = g_hash_table_get_keys (message->priv->headers);
+  keys = g_list_sort (keys, _sort_keys_func);
+  if (keys != NULL)
+    {
+      for (l = keys; l != NULL; l = l->next)
+        {
+          gint key = GPOINTER_TO_INT (l->data);
+          GVariant *value;
+          gchar *value_str;
+
+          value = g_hash_table_lookup (message->priv->headers, l->data);
+          g_assert (value != NULL);
+
+          s = enum_to_string (G_TYPE_DBUS_MESSAGE_HEADER_FIELD, key);
+          value_str = g_variant_print (value, TRUE);
+          g_string_append_printf (str, "%*s  %s -> %s\n", indent, "", s, value_str);
+          g_free (s);
+          g_free (value_str);
+        }
+    }
+  else
+    {
+      g_string_append_printf (str, "%*s  (none)\n", indent, "");
+    }
+  g_string_append_printf (str, "%*sBody: ", indent, "");
+  if (message->priv->body != NULL)
+    {
+      g_variant_print_string (message->priv->body,
+                              str,
+                              TRUE);
+    }
+  else
+    {
+      g_string_append (str, "()");
+    }
+  g_string_append (str, "\n");
+#ifdef G_OS_UNIX
+  g_string_append_printf (str, "%*sUNIX File Descriptors:\n", indent, "");
+  if (message->priv->fd_list != NULL)
+    {
+      gint num_fds;
+      const gint *fds;
+      gint n;
+
+      fds = g_unix_fd_list_peek_fds (message->priv->fd_list, &num_fds);
+      if (num_fds > 0)
+        {
+          for (n = 0; n < num_fds; n++)
+            {
+              GString *fs;
+              struct stat statbuf;
+              fs = g_string_new (NULL);
+              if (fstat (fds[n], &statbuf) == 0)
+                {
+                  g_string_append_printf (fs, "%s" "dev=%d:%d", fs->len > 0 ? "," : "",
+                                          major (statbuf.st_dev), minor (statbuf.st_dev));
+                  g_string_append_printf (fs, "%s" "mode=0%o", fs->len > 0 ? "," : "",
+                                          statbuf.st_mode);
+                  g_string_append_printf (fs, "%s" "ino=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+                                          (guint64) statbuf.st_ino);
+                  g_string_append_printf (fs, "%s" "uid=%d", fs->len > 0 ? "," : "",
+                                          statbuf.st_uid);
+                  g_string_append_printf (fs, "%s" "gid=%d", fs->len > 0 ? "," : "",
+                                          statbuf.st_gid);
+                  g_string_append_printf (fs, "%s" "rdev=%d:%d", fs->len > 0 ? "," : "",
+                                          major (statbuf.st_rdev), minor (statbuf.st_rdev));
+                  g_string_append_printf (fs, "%s" "size=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+                                          (guint64) statbuf.st_size);
+                  g_string_append_printf (fs, "%s" "atime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+                                          (guint64) statbuf.st_atime);
+                  g_string_append_printf (fs, "%s" "mtime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+                                          (guint64) statbuf.st_mtime);
+                  g_string_append_printf (fs, "%s" "ctime=%" G_GUINT64_FORMAT, fs->len > 0 ? "," : "",
+                                          (guint64) statbuf.st_ctime);
+                }
+              else
+                {
+                  g_string_append_printf (fs, "(fstat failed: %s)", strerror (errno));
+                }
+              g_string_append_printf (str, "%*s  fd %d: %s\n", indent, "", fds[n], fs->str);
+              g_string_free (fs, TRUE);
+            }
+        }
+      else
+        {
+          g_string_append_printf (str, "%*s  (empty)\n", indent, "");
+        }
+    }
+  else
+    {
+      g_string_append_printf (str, "%*s  (none)\n", indent, "");
+    }
+#endif
+
+  return g_string_free (str, FALSE);
+}
+
diff --git a/gio/gdbusmessage.h b/gio/gdbusmessage.h
new file mode 100644
index 0000000..4fb1873
--- /dev/null
+++ b/gio/gdbusmessage.h
@@ -0,0 +1,172 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_MESSAGE_H__
+#define __G_DBUS_MESSAGE_H__
+
+#include <gio/giotypes.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixfdlist.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_MESSAGE         (g_dbus_message_get_gtype ())
+#define G_DBUS_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_MESSAGE, GDBusMessage))
+#define G_DBUS_MESSAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_MESSAGE, GDBusMessageClass))
+#define G_DBUS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_MESSAGE, GDBusMessageClass))
+#define G_IS_DBUS_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_MESSAGE))
+#define G_IS_DBUS_MESSAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_MESSAGE))
+
+typedef struct _GDBusMessageClass   GDBusMessageClass;
+typedef struct _GDBusMessagePrivate GDBusMessagePrivate;
+
+/**
+ * GDBusMessageClass:
+ *
+ * Class structure for #GDBusMessage.
+ */
+struct _GDBusMessageClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+};
+
+/**
+ * GDBusMessage:
+ *
+ * The #GDBusMessage structure contains only private data and should
+ * only be accessed using the provided API.
+ */
+struct _GDBusMessage
+{
+  /*< private >*/
+  GObject parent_instance;
+  GDBusMessagePrivate *priv;
+};
+
+GType                     g_dbus_message_get_gtype          (void) G_GNUC_CONST;
+GDBusMessage             *g_dbus_message_new                (void);
+GDBusMessage             *g_dbus_message_new_signal         (const gchar              *path,
+                                                             const gchar              *interface,
+                                                             const gchar              *signal);
+GDBusMessage             *g_dbus_message_new_method_call    (const gchar              *name,
+                                                             const gchar              *path,
+                                                             const gchar              *interface,
+                                                             const gchar              *method);
+GDBusMessage             *g_dbus_message_new_method_reply   (GDBusMessage             *method_call_message);
+GDBusMessage             *g_dbus_message_new_method_error   (GDBusMessage             *method_call_message,
+                                                             const gchar              *error_name,
+                                                             const gchar              *error_message_format,
+                                                             ...);
+GDBusMessage             *g_dbus_message_new_method_error_valist (GDBusMessage             *method_call_message,
+                                                                  const gchar              *error_name,
+                                                                  const gchar              *error_message_format,
+                                                                  va_list                   var_args);
+GDBusMessage             *g_dbus_message_new_method_error_literal (GDBusMessage             *method_call_message,
+                                                                   const gchar              *error_name,
+                                                                   const gchar              *error_message);
+gchar                    *g_dbus_message_print              (GDBusMessage             *message,
+                                                             guint                     indent);
+
+GDBusMessageType          g_dbus_message_get_type           (GDBusMessage             *message);
+void                      g_dbus_message_set_type           (GDBusMessage             *message,
+                                                             GDBusMessageType          type);
+GDBusMessageFlags         g_dbus_message_get_flags          (GDBusMessage             *message);
+void                      g_dbus_message_set_flags          (GDBusMessage             *message,
+                                                             GDBusMessageFlags         flags);
+guint32                   g_dbus_message_get_serial         (GDBusMessage             *message);
+void                      g_dbus_message_set_serial         (GDBusMessage             *message,
+                                                             guint32                   serial);
+GVariant                 *g_dbus_message_get_header         (GDBusMessage             *message,
+                                                             GDBusMessageHeaderField   header_field);
+void                      g_dbus_message_set_header         (GDBusMessage             *message,
+                                                             GDBusMessageHeaderField   header_field,
+                                                             GVariant                 *value);
+guchar                   *g_dbus_message_get_header_fields  (GDBusMessage             *message);
+GVariant                 *g_dbus_message_get_body           (GDBusMessage             *message);
+void                      g_dbus_message_set_body           (GDBusMessage             *message,
+                                                             GVariant                 *body);
+#ifdef G_OS_UNIX
+GUnixFDList              *g_dbus_message_get_unix_fd_list   (GDBusMessage             *message);
+void                      g_dbus_message_set_unix_fd_list   (GDBusMessage             *message,
+                                                             GUnixFDList              *fd_list);
+#endif
+
+guint32                   g_dbus_message_get_reply_serial   (GDBusMessage             *message);
+void                      g_dbus_message_set_reply_serial   (GDBusMessage             *message,
+                                                             guint32                   value);
+
+const gchar              *g_dbus_message_get_interface      (GDBusMessage             *message);
+void                      g_dbus_message_set_interface      (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_member         (GDBusMessage             *message);
+void                      g_dbus_message_set_member         (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_path           (GDBusMessage             *message);
+void                      g_dbus_message_set_path           (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_sender         (GDBusMessage             *message);
+void                      g_dbus_message_set_sender         (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_destination    (GDBusMessage             *message);
+void                      g_dbus_message_set_destination    (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_error_name     (GDBusMessage             *message);
+void                      g_dbus_message_set_error_name     (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+const gchar              *g_dbus_message_get_signature      (GDBusMessage             *message);
+void                      g_dbus_message_set_signature      (GDBusMessage             *message,
+                                                             const gchar              *value);
+
+guint32                   g_dbus_message_get_num_unix_fds   (GDBusMessage             *message);
+void                      g_dbus_message_set_num_unix_fds   (GDBusMessage             *message,
+                                                             guint32                   value);
+
+const gchar              *g_dbus_message_get_arg0           (GDBusMessage             *message);
+
+
+GDBusMessage             *g_dbus_message_new_from_blob      (guchar                   *blob,
+                                                             gsize                     blob_len,
+                                                             GError                  **error);
+
+gssize                    g_dbus_message_bytes_needed       (guchar                   *blob,
+                                                             gsize                     blob_len,
+                                                             GError                  **error);
+
+guchar                   *g_dbus_message_to_blob            (GDBusMessage             *message,
+                                                             gsize                    *out_size,
+                                                             GError                  **error);
+
+gboolean                  g_dbus_message_to_gerror          (GDBusMessage             *message,
+                                                             GError                  **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_MESSAGE_H__ */
diff --git a/gio/gdbusmethodinvocation.c b/gio/gdbusmethodinvocation.c
new file mode 100644
index 0000000..dc6950b
--- /dev/null
+++ b/gio/gdbusmethodinvocation.c
@@ -0,0 +1,795 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusconnection.h"
+#include "gdbusmessage.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusintrospection.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusmethodinvocation
+ * @short_description: Object for handling remote calls
+ * @include: gdbus/gdbus.h
+ *
+ * Instances of the #GDBusMethodInvocation class are used when
+ * handling D-Bus method calls. It provides a way to asynchronously
+ * return results and errors.
+ */
+
+struct _GDBusMethodInvocationPrivate
+{
+  /* construct-only properties */
+  gchar           *sender;
+  gchar           *object_path;
+  gchar           *interface_name;
+  gchar           *method_name;
+  const GDBusMethodInfo *method_info;
+  GDBusConnection *connection;
+  GDBusMessage    *message;
+  GVariant        *parameters;
+  gpointer         user_data;
+};
+
+enum
+{
+  PROP_0,
+  PROP_SENDER,
+  PROP_OBJECT_PATH,
+  PROP_INTERFACE_NAME,
+  PROP_METHOD_NAME,
+  PROP_METHOD_INFO,
+  PROP_CONNECTION,
+  PROP_PARAMETERS,
+  PROP_MESSAGE,
+  PROP_USER_DATA
+};
+
+G_DEFINE_TYPE (GDBusMethodInvocation, g_dbus_method_invocation, G_TYPE_OBJECT);
+
+static void
+g_dbus_method_invocation_finalize (GObject *object)
+{
+  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+  g_free (invocation->priv->sender);
+  g_free (invocation->priv->object_path);
+  g_free (invocation->priv->interface_name);
+  g_free (invocation->priv->method_name);
+  g_object_unref (invocation->priv->connection);
+  g_object_unref (invocation->priv->message);
+  g_variant_unref (invocation->priv->parameters);
+
+  if (G_OBJECT_CLASS (g_dbus_method_invocation_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_method_invocation_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_method_invocation_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_SENDER:
+      g_value_set_string (value, g_dbus_method_invocation_get_sender (invocation));
+      break;
+
+    case PROP_OBJECT_PATH:
+      g_value_set_string (value, g_dbus_method_invocation_get_object_path (invocation));
+      break;
+
+    case PROP_INTERFACE_NAME:
+      g_value_set_string (value, g_dbus_method_invocation_get_interface_name (invocation));
+      break;
+
+    case PROP_METHOD_NAME:
+      g_value_set_string (value, g_dbus_method_invocation_get_method_name (invocation));
+      break;
+
+    case PROP_METHOD_INFO:
+      g_value_set_boxed (value, g_dbus_method_invocation_get_method_info (invocation));
+      break;
+
+    case PROP_CONNECTION:
+      g_value_set_object (value, g_dbus_method_invocation_get_connection (invocation));
+      break;
+
+    case PROP_PARAMETERS:
+      g_value_set_boxed (value, g_dbus_method_invocation_get_parameters (invocation));
+      break;
+
+    case PROP_MESSAGE:
+      g_value_set_object (value, g_dbus_method_invocation_get_message (invocation));
+      break;
+
+    case PROP_USER_DATA:
+      g_value_set_pointer (value, g_dbus_method_invocation_get_user_data (invocation));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_method_invocation_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_SENDER:
+      invocation->priv->sender = g_value_dup_string (value);
+      break;
+
+    case PROP_OBJECT_PATH:
+      invocation->priv->object_path = g_value_dup_string (value);
+      break;
+
+    case PROP_INTERFACE_NAME:
+      invocation->priv->interface_name = g_value_dup_string (value);
+      break;
+
+    case PROP_METHOD_NAME:
+      invocation->priv->method_name = g_value_dup_string (value);
+      break;
+
+    case PROP_METHOD_INFO:
+      invocation->priv->method_info = g_value_dup_boxed (value);
+      break;
+
+    case PROP_CONNECTION:
+      invocation->priv->connection = g_value_dup_object (value);
+      break;
+
+    case PROP_PARAMETERS:
+      invocation->priv->parameters = g_value_dup_boxed (value);
+      break;
+
+    case PROP_MESSAGE:
+      invocation->priv->message = g_value_dup_object (value);
+      break;
+
+    case PROP_USER_DATA:
+      invocation->priv->user_data = g_value_get_pointer (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+g_dbus_method_invocation_class_init (GDBusMethodInvocationClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_method_invocation_finalize;
+  gobject_class->set_property = g_dbus_method_invocation_set_property;
+  gobject_class->get_property = g_dbus_method_invocation_get_property;
+
+  /**
+   * GDBusMethodInvocation:sender:
+   *
+   * The bus name that invoked the method or %NULL if the connection is not a bus connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_SENDER,
+                                   g_param_spec_string ("sender",
+                                                        _("Sender"),
+                                                        _("The bus name that invoked the method."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:object-path:
+   *
+   * The object path the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_OBJECT_PATH,
+                                   g_param_spec_string ("object-path",
+                                                        _("Object Path"),
+                                                        _("The object path the method was invoked on."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:interface-name:
+   *
+   * The name of the D-Bus interface the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_INTERFACE_NAME,
+                                   g_param_spec_string ("interface-name",
+                                                        _("Interface Name"),
+                                                        _("The name of the D-Bus interface the method was invoked on."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:method-name:
+   *
+   * The name of the method that was invoked.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_METHOD_NAME,
+                                   g_param_spec_string ("method-name",
+                                                        _("Method Name"),
+                                                        _("The name of the method that was invoked."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:method-info:
+   *
+   * Information about the method that was invoked, if any.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_METHOD_INFO,
+                                   g_param_spec_boxed ("method-info",
+                                                       _("Method Info"),
+                                                       _("Information about the method that was invoked, if any."),
+                                                       G_TYPE_DBUS_METHOD_INFO,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:connection:
+   *
+   * The #GDBusConnection the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CONNECTION,
+                                   g_param_spec_object ("connection",
+                                                        _("Connection"),
+                                                        _("The #GDBusConnection the method was invoked on."),
+                                                        G_TYPE_DBUS_CONNECTION,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:message:
+   *
+   * The D-Bus message.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_MESSAGE,
+                                   g_param_spec_object ("message",
+                                                        _("Message"),
+                                                        _("The D-Bus Message."),
+                                                        G_TYPE_DBUS_MESSAGE,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:parameters:
+   *
+   * The parameters as a #GVariant tuple.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_PARAMETERS,
+                                   g_param_spec_boxed ("parameters",
+                                                       _("Parameters"),
+                                                       _("The parameters as a #GVariant tuple."),
+                                                       G_TYPE_VARIANT,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusMethodInvocation:user-data:
+   *
+   * The @user_data #gpointer passed to g_dbus_connection_register_object().
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_USER_DATA,
+                                   g_param_spec_pointer ("user-data",
+                                                        _("User Data"),
+                                                        _("The gpointer passed to g_dbus_connection_register_object()."),
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  g_type_class_add_private (klass, sizeof (GDBusMethodInvocationPrivate));
+}
+
+static void
+g_dbus_method_invocation_init (GDBusMethodInvocation *invocation)
+{
+  invocation->priv = G_TYPE_INSTANCE_GET_PRIVATE (invocation,
+                                                  G_TYPE_DBUS_METHOD_INVOCATION,
+                                                  GDBusMethodInvocationPrivate);
+}
+
+/**
+ * g_dbus_method_invocation_get_sender:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the bus name that invoked the method.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_sender (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->sender;
+}
+
+/**
+ * g_dbus_method_invocation_get_object_path:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the object path the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->object_path;
+}
+
+/**
+ * g_dbus_method_invocation_get_interface_name:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the name of the D-Bus interface the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->interface_name;
+}
+
+/**
+ * g_dbus_method_invocation_get_method_info:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets information about the method call, if any.
+ *
+ * Returns: A #GDBusMethodInfo or %NULL. Do not free, it is owned by @invocation.
+ */
+const GDBusMethodInfo *
+g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->method_info;
+}
+
+/**
+ * g_dbus_method_invocation_get_method_name:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the name of the method that was invoked.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+g_dbus_method_invocation_get_method_name (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->method_name;
+}
+
+/**
+ * g_dbus_method_invocation_get_connection:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the #GDBusConnection the method was invoked on.
+ *
+ * Returns: A #GDBusConnection. Do not free, it is owned by @invocation.
+ */
+GDBusConnection *
+g_dbus_method_invocation_get_connection (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->connection;
+}
+
+/**
+ * g_dbus_method_invocation_get_message:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the #GDBusMessage for the method invocation. This is useful if
+ * you need to use low-level protocol features, such as UNIX file
+ * descriptor passing, that cannot be properly expressed in the
+ * #GVariant API.
+ *
+ * See <xref linkend="gdbus-server"/> and <xref
+ * linkend="gdbus-unix-fd-client"/> for an example of how to use this
+ * low-level API to send and receive UNIX file descriptors.
+ *
+ * Returns: A #GDBusMessage. Do not free, it is owned by @invocation.
+ */
+GDBusMessage *
+g_dbus_method_invocation_get_message (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->message;
+}
+
+/**
+ * g_dbus_method_invocation_get_parameters:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the parameters of the method invocation.
+ *
+ * Returns: A #GVariant. Do not free, it is owned by @invocation.
+ */
+GVariant *
+g_dbus_method_invocation_get_parameters (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->parameters;
+}
+
+/**
+ * g_dbus_method_invocation_get_user_data:
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Gets the @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * Returns: A #gpointer.
+ */
+gpointer
+g_dbus_method_invocation_get_user_data (GDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->user_data;
+}
+
+/**
+ * g_dbus_method_invocation_new:
+ * @sender: The bus name that invoked the method or %NULL if @connection is not a bus connection.
+ * @object_path: The object path the method was invoked on.
+ * @interface_name: The name of the D-Bus interface the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @method_info: Information about the method call or %NULL.
+ * @connection: The #GDBusConnection the method was invoked on.
+ * @message: The D-Bus message as a #GDBusMessage.
+ * @parameters: The parameters as a #GVariant tuple.
+ * @user_data: The @user_data #gpointer passed to g_dbus_connection_register_object().
+ *
+ * Creates a new #GDBusMethodInvocation object.
+ *
+ * Returns: A #GDBusMethodInvocation. Free with g_object_unref().
+ */
+GDBusMethodInvocation *
+g_dbus_method_invocation_new (const gchar            *sender,
+                              const gchar            *object_path,
+                              const gchar            *interface_name,
+                              const gchar            *method_name,
+                              const GDBusMethodInfo  *method_info,
+                              GDBusConnection        *connection,
+                              GDBusMessage           *message,
+                              GVariant               *parameters,
+                              gpointer                user_data)
+{
+  g_return_val_if_fail (sender == NULL || g_dbus_is_name (sender), NULL);
+  g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
+  g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), NULL);
+  g_return_val_if_fail (g_dbus_is_member_name (method_name), NULL);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL);
+  g_return_val_if_fail (g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+
+  return G_DBUS_METHOD_INVOCATION (g_object_new (G_TYPE_DBUS_METHOD_INVOCATION,
+                                                 "sender", sender,
+                                                 "object-path", object_path,
+                                                 "interface-name", interface_name,
+                                                 "method-name", method_name,
+                                                 "method-info", method_info,
+                                                 "connection", connection,
+                                                 "message", message,
+                                                 "parameters", parameters,
+                                                 "user-data", user_data,
+                                                 NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_method_invocation_return_value:
+ * @invocation: A #GDBusMethodInvocation.
+ * @parameters: A #GVariant tuple with out parameters for the method or %NULL if not passing any parameters.
+ *
+ * Finishes handling a D-Bus method call by returning @parameters.
+ *
+ * It is an error if @parameters is not of the right format.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_value (GDBusMethodInvocation *invocation,
+                                       GVariant              *parameters)
+{
+  GDBusMessage *reply;
+  GError *error;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+
+  if (parameters != NULL)
+    g_variant_ref_sink (parameters);
+
+  /* if we have introspection data, check that the signature of @parameters is correct */
+  if (invocation->priv->method_info != NULL)
+    {
+      gchar *signature;
+      const gchar *type_string;
+
+      type_string = "()";
+      if (parameters != NULL)
+        type_string = g_variant_get_type_string (parameters);
+      signature = _g_dbus_compute_complete_signature (invocation->priv->method_info->out_args, TRUE);
+
+      if (g_strcmp0 (type_string, signature) != 0)
+        {
+          g_warning (_("Type of return value is incorrect, got `%s', expected  `%s'"),
+                     type_string,
+                     signature);
+          g_free (signature);
+          goto out;
+        }
+      g_free (signature);
+    }
+
+  reply = g_dbus_message_new_method_reply (invocation->priv->message);
+  g_dbus_message_set_body (reply, parameters);
+  error = NULL;
+  if (!g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, &error))
+    {
+      g_warning (_("Error sending message: %s"), error->message);
+      g_error_free (error);
+    }
+  g_object_unref (reply);
+
+ out:
+  g_object_unref (invocation);
+  if (parameters != NULL)
+    g_variant_unref (parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_method_invocation_return_error:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @...: Parameters for @format.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * See g_dbus_error_encode_gerror() for details about what error name
+ * will be returned on the wire. In a nutshell, if the given error is
+ * registered using g_dbus_error_register_error() the name given
+ * during registration is used. Otherwise, a name of the form
+ * <literal>org.gtk.GDBus.UnmappedGError.Quark...</literal> is
+ * used. This provides transparent mapping of #GError between
+ * applications using GDBus.
+ *
+ * If you are writing an application intended to be portable,
+ * <emphasis>always</emphasis> register errors with g_dbus_error_register_error()
+ * or use g_dbus_method_invocation_return_dbus_error().
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error (GDBusMethodInvocation *invocation,
+                                       GQuark                 domain,
+                                       gint                   code,
+                                       const gchar           *format,
+                                       ...)
+{
+  va_list var_args;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (format != NULL);
+
+  va_start (var_args, format);
+  g_dbus_method_invocation_return_error_valist (invocation,
+                                                domain,
+                                                code,
+                                                format,
+                                                var_args);
+  va_end (var_args);
+}
+
+/**
+ * g_dbus_method_invocation_return_error_valist:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @var_args: #va_list of parameters for @format.
+ *
+ * Like g_dbus_method_invocation_return_error() but intended for
+ * language bindings.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error_valist (GDBusMethodInvocation *invocation,
+                                              GQuark                 domain,
+                                              gint                   code,
+                                              const gchar           *format,
+                                              va_list                var_args)
+{
+  gchar *literal_message;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (format != NULL);
+
+  literal_message = g_strdup_vprintf (format, var_args);
+  g_dbus_method_invocation_return_error_literal (invocation,
+                                                 domain,
+                                                 code,
+                                                 literal_message);
+  g_free (literal_message);
+}
+
+/**
+ * g_dbus_method_invocation_return_error_literal:
+ * @invocation: A #GDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @message: The error message.
+ *
+ * Like g_dbus_method_invocation_return_error() but without printf()-style formatting.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation,
+                                               GQuark                 domain,
+                                               gint                   code,
+                                               const gchar           *message)
+{
+  GError *error;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (message != NULL);
+
+  error = g_error_new_literal (domain, code, message);
+  g_dbus_method_invocation_return_gerror (invocation, error);
+  g_error_free (error);
+}
+
+/**
+ * g_dbus_method_invocation_return_gerror:
+ * @invocation: A #GDBusMethodInvocation.
+ * @error: A #GError.
+ *
+ * Like g_dbus_method_invocation_return_error() but takes a #GError
+ * instead of the error domain, error code and message.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_gerror (GDBusMethodInvocation *invocation,
+                                        const GError          *error)
+{
+  gchar *dbus_error_name;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (error != NULL);
+
+  dbus_error_name = g_dbus_error_encode_gerror (error);
+
+  g_dbus_method_invocation_return_dbus_error (invocation,
+                                              dbus_error_name,
+                                              error->message);
+  g_free (dbus_error_name);
+}
+
+/**
+ * g_dbus_method_invocation_return_dbus_error:
+ * @invocation: A #GDBusMethodInvocation.
+ * @error_name: A valid D-Bus error name.
+ * @error_message: A valid D-Bus error message.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+g_dbus_method_invocation_return_dbus_error (GDBusMethodInvocation *invocation,
+                                            const gchar           *error_name,
+                                            const gchar           *error_message)
+{
+  GDBusMessage *reply;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (error_name != NULL && g_dbus_is_name (error_name));
+  g_return_if_fail (error_message != NULL);
+
+  reply = g_dbus_message_new_method_error_literal (invocation->priv->message,
+                                                   error_name,
+                                                   error_message);
+  g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, NULL, NULL);
+  g_object_unref (reply);
+
+  g_object_unref (invocation);
+}
diff --git a/gio/gdbusmethodinvocation.h b/gio/gdbusmethodinvocation.h
new file mode 100644
index 0000000..65d0f99
--- /dev/null
+++ b/gio/gdbusmethodinvocation.h
@@ -0,0 +1,119 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_METHOD_INVOCATION_H__
+#define __G_DBUS_METHOD_INVOCATION_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_METHOD_INVOCATION         (g_dbus_method_invocation_get_type ())
+#define G_DBUS_METHOD_INVOCATION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocation))
+#define G_DBUS_METHOD_INVOCATION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass))
+#define G_DBUS_METHOD_INVOCATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocationClass))
+#define G_IS_DBUS_METHOD_INVOCATION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_METHOD_INVOCATION))
+#define G_IS_DBUS_METHOD_INVOCATION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_METHOD_INVOCATION))
+
+typedef struct _GDBusMethodInvocationClass   GDBusMethodInvocationClass;
+typedef struct _GDBusMethodInvocationPrivate GDBusMethodInvocationPrivate;
+
+/**
+ * GDBusMethodInvocation:
+ *
+ * The #GDBusMethodInvocation structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusMethodInvocation
+{
+  /*< private >*/
+  GObject parent_instance;
+  GDBusMethodInvocationPrivate *priv;
+};
+
+/**
+ * GDBusMethodInvocationClass:
+ *
+ * Class structure for #GDBusMethodInvocation.
+ */
+struct _GDBusMethodInvocationClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType                  g_dbus_method_invocation_get_type             (void) G_GNUC_CONST;
+GDBusMethodInvocation *g_dbus_method_invocation_new                  (const gchar           *sender,
+                                                                      const gchar           *object_path,
+                                                                      const gchar           *interface_name,
+                                                                      const gchar           *method_name,
+                                                                      const GDBusMethodInfo *method_info,
+                                                                      GDBusConnection       *connection,
+                                                                      GDBusMessage          *message,
+                                                                      GVariant              *parameters,
+                                                                      gpointer               user_data);
+const gchar           *g_dbus_method_invocation_get_sender           (GDBusMethodInvocation *invocation);
+const gchar           *g_dbus_method_invocation_get_object_path      (GDBusMethodInvocation *invocation);
+const gchar           *g_dbus_method_invocation_get_interface_name   (GDBusMethodInvocation *invocation);
+const gchar           *g_dbus_method_invocation_get_method_name      (GDBusMethodInvocation *invocation);
+const GDBusMethodInfo *g_dbus_method_invocation_get_method_info      (GDBusMethodInvocation *invocation);
+GDBusConnection       *g_dbus_method_invocation_get_connection       (GDBusMethodInvocation *invocation);
+GDBusMessage          *g_dbus_method_invocation_get_message          (GDBusMethodInvocation *invocation);
+GVariant              *g_dbus_method_invocation_get_parameters       (GDBusMethodInvocation *invocation);
+gpointer               g_dbus_method_invocation_get_user_data        (GDBusMethodInvocation *invocation);
+
+void                   g_dbus_method_invocation_return_value         (GDBusMethodInvocation *invocation,
+                                                                      GVariant              *parameters);
+void                   g_dbus_method_invocation_return_error         (GDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *format,
+                                                                      ...);
+void                   g_dbus_method_invocation_return_error_valist  (GDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *format,
+                                                                      va_list                var_args);
+void                   g_dbus_method_invocation_return_error_literal (GDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *message);
+void                   g_dbus_method_invocation_return_gerror        (GDBusMethodInvocation *invocation,
+                                                                      const GError          *error);
+void                   g_dbus_method_invocation_return_dbus_error    (GDBusMethodInvocation *invocation,
+                                                                      const gchar           *error_name,
+                                                                      const gchar           *error_message);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_METHOD_INVOCATION_H__ */
diff --git a/gio/gdbusnameowning.c b/gio/gdbusnameowning.c
new file mode 100644
index 0000000..3090165
--- /dev/null
+++ b/gio/gdbusnameowning.c
@@ -0,0 +1,713 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnameowning.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnameowning
+ * @title: Owning Bus Names
+ * @short_description: Simple API for owning bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for owning bus names.
+ *
+ * <example id="gdbus-owning-names"><title>Simple application owning a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-own-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  PREVIOUS_CALL_NONE = 0,
+  PREVIOUS_CALL_ACQUIRED,
+  PREVIOUS_CALL_LOST,
+} PreviousCall;
+
+typedef struct
+{
+  volatile gint             ref_count;
+  guint                     id;
+  GBusNameOwnerFlags        flags;
+  gchar                    *name;
+  GBusAcquiredCallback      bus_acquired_handler;
+  GBusNameAcquiredCallback  name_acquired_handler;
+  GBusNameLostCallback      name_lost_handler;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+  GMainContext             *main_context;
+
+  PreviousCall              previous_call;
+
+  GDBusConnection          *connection;
+  gulong                    disconnected_signal_handler_id;
+  guint                     name_acquired_subscription_id;
+  guint                     name_lost_subscription_id;
+
+  gboolean                  cancelled;
+
+  gboolean                  needs_release;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+
+static Client *
+client_ref (Client *client)
+{
+  g_atomic_int_inc (&client->ref_count);
+  return client;
+}
+
+static void
+client_unref (Client *client)
+{
+  if (g_atomic_int_dec_and_test (&client->ref_count))
+    {
+      if (client->connection != NULL)
+        {
+          if (client->disconnected_signal_handler_id > 0)
+            g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+          if (client->name_acquired_subscription_id > 0)
+            g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+          if (client->name_lost_subscription_id > 0)
+            g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+          g_object_unref (client->connection);
+        }
+      if (client->main_context != NULL)
+        g_main_context_unref (client->main_context);
+      g_free (client->name);
+      if (client->user_data_free_func != NULL)
+        client->user_data_free_func (client->user_data);
+      g_free (client);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+typedef enum
+{
+  CALL_TYPE_NAME_ACQUIRED,
+  CALL_TYPE_NAME_LOST
+} CallType;
+
+typedef struct
+{
+  Client *client;
+
+  /* keep this separate because client->connection may
+   * be set to NULL after scheduling the call
+   */
+  GDBusConnection *connection;
+
+  /* set to TRUE to call acquired */
+  CallType call_type;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+  if (data->connection != NULL)
+    g_object_unref (data->connection);
+  client_unref (data->client);
+  g_free (data);
+}
+
+static void
+actually_do_call (Client *client, GDBusConnection *connection, CallType call_type)
+{
+  switch (call_type)
+    {
+    case CALL_TYPE_NAME_ACQUIRED:
+      if (client->name_acquired_handler != NULL)
+        {
+          client->name_acquired_handler (connection,
+                                         client->name,
+                                         client->user_data);
+        }
+      break;
+
+    case CALL_TYPE_NAME_LOST:
+      if (client->name_lost_handler != NULL)
+        {
+          client->name_lost_handler (connection,
+                                     client->name,
+                                     client->user_data);
+        }
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+  CallHandlerData *data = _data;
+  actually_do_call (data->client, data->connection, data->call_type);
+  return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client, CallType  call_type)
+{
+  CallHandlerData *data;
+  GSource *idle_source;
+
+  data = g_new0 (CallHandlerData, 1);
+  data->client = client_ref (client);
+  data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+  data->call_type = call_type;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         call_in_idle_cb,
+                         data,
+                         (GDestroyNotify) call_handler_data_free);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+static void
+do_call (Client *client, CallType call_type)
+{
+  /* only schedule in idle if we're not in the right thread */
+  if (g_main_context_get_thread_default () != client->main_context)
+    schedule_call_in_idle (client, call_type);
+  else
+    actually_do_call (client, client->connection, call_type);
+}
+
+static void
+call_acquired_handler (Client *client)
+{
+  if (client->previous_call != PREVIOUS_CALL_ACQUIRED)
+    {
+      client->previous_call = PREVIOUS_CALL_ACQUIRED;
+      if (!client->cancelled)
+        {
+          do_call (client, CALL_TYPE_NAME_ACQUIRED);
+        }
+    }
+}
+
+static void
+call_lost_handler (Client  *client)
+{
+  if (client->previous_call != PREVIOUS_CALL_LOST)
+    {
+      client->previous_call = PREVIOUS_CALL_LOST;
+      if (!client->cancelled)
+        {
+          do_call (client, CALL_TYPE_NAME_LOST);
+        }
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_lost_or_acquired (GDBusConnection  *connection,
+                          const gchar      *sender_name,
+                          const gchar      *object_path,
+                          const gchar      *interface_name,
+                          const gchar      *signal_name,
+                          GVariant         *parameters,
+                          gpointer          user_data)
+{
+  Client *client = user_data;
+  const gchar *name;
+
+  if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+      g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+      g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+    goto out;
+
+  if (g_strcmp0 (signal_name, "NameLost") == 0)
+    {
+      g_variant_get (parameters, "(s)", &name);
+      if (g_strcmp0 (name, client->name) == 0)
+        {
+          call_lost_handler (client);
+        }
+    }
+  else if (g_strcmp0 (signal_name, "NameAcquired") == 0)
+    {
+      g_variant_get (parameters, "(s)", &name);
+      if (g_strcmp0 (name, client->name) == 0)
+        {
+          call_acquired_handler (client);
+        }
+    }
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+request_name_cb (GObject      *source_object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+  Client *client = user_data;
+  GVariant *result;
+  guint32 request_name_reply;
+  gboolean subscribe;
+
+  request_name_reply = 0;
+  result = NULL;
+
+  result = g_dbus_connection_invoke_method_finish (client->connection,
+                                                   res,
+                                                   NULL);
+  if (result != NULL)
+    {
+      g_variant_get (result, "(u)", &request_name_reply);
+      g_variant_unref (result);
+    }
+
+  subscribe = FALSE;
+
+  switch (request_name_reply)
+    {
+    case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */
+      /* We got the name - now listen for NameLost and NameAcquired */
+      call_acquired_handler (client);
+      subscribe = TRUE;
+      client->needs_release = TRUE;
+      break;
+
+    case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */
+      /* Waiting in line - listen for NameLost and NameAcquired */
+      call_lost_handler (client);
+      subscribe = TRUE;
+      client->needs_release = TRUE;
+      break;
+
+    default:
+      /* assume we couldn't get the name - explicit fallthrough */
+    case 3: /* DBUS_REQUEST_NAME_REPLY_EXISTS */
+    case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */
+      /* Some other part of the process is already owning the name */
+      call_lost_handler (client);
+      break;
+    }
+
+  if (subscribe)
+    {
+      /* start listening to NameLost and NameAcquired messages */
+      client->name_lost_subscription_id =
+        g_dbus_connection_signal_subscribe (client->connection,
+                                            "org.freedesktop.DBus",
+                                            "org.freedesktop.DBus",
+                                            "NameLost",
+                                            "/org/freedesktop/DBus",
+                                            client->name,
+                                            on_name_lost_or_acquired,
+                                            client,
+                                            NULL);
+      client->name_acquired_subscription_id =
+        g_dbus_connection_signal_subscribe (client->connection,
+                                            "org.freedesktop.DBus",
+                                            "org.freedesktop.DBus",
+                                            "NameAcquired",
+                                            "/org/freedesktop/DBus",
+                                            client->name,
+                                            on_name_lost_or_acquired,
+                                            client,
+                                            NULL);
+    }
+
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (GDBusConnection *connection,
+                            gboolean         remote_peer_vanished,
+                            GError          *error,
+                            gpointer         user_data)
+{
+  Client *client = user_data;
+
+  if (client->disconnected_signal_handler_id > 0)
+    g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+  if (client->name_acquired_subscription_id > 0)
+    g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+  if (client->name_lost_subscription_id > 0)
+    g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+  g_object_unref (client->connection);
+  client->disconnected_signal_handler_id = 0;
+  client->name_acquired_subscription_id = 0;
+  client->name_lost_subscription_id = 0;
+  client->connection = NULL;
+
+  call_lost_handler (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+  /* listen for disconnection */
+  client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+                                                             "closed",
+                                                             G_CALLBACK (on_connection_disconnected),
+                                                             client);
+
+  /* attempt to acquire the name */
+  g_dbus_connection_invoke_method (client->connection,
+                                   "org.freedesktop.DBus",  /* bus name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "RequestName",           /* method name */
+                                   g_variant_new ("(su)",
+                                                  client->name,
+                                                  client->flags),
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) request_name_cb,
+                                   client_ref (client));
+}
+
+
+static void
+connection_get_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+
+  client->connection = g_bus_get_finish (res, NULL);
+  if (client->connection == NULL)
+    {
+      call_lost_handler (client);
+      goto out;
+    }
+
+  /* No need to schedule this in idle as we're already in the thread
+   * that the user called g_bus_own_name() from. This is because
+   * g_bus_get() guarantees that.
+   *
+   * Also, we need to ensure that the handler is invoked *before*
+   * we call RequestName(). Otherwise there is a race.
+   */
+  if (client->bus_acquired_handler != NULL)
+    {
+      client->bus_acquired_handler (client->connection,
+                                    client->name,
+                                    client->user_data);
+    }
+
+  has_connection (client);
+
+ out:
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_bus_own_name_on_connection:
+ * @connection: A #GDBusConnection that is not closed.
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL.
+ * @name_lost_handler: Handler to invoke when @name is lost or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Like g_bus_own_name() but takes a #GDBusConnection instead of a
+ * #GBusType.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unown_name() to stop owning the name.
+ **/
+guint
+g_bus_own_name_on_connection (GDBusConnection          *connection,
+                              const gchar              *name,
+                              GBusNameOwnerFlags        flags,
+                              GBusNameAcquiredCallback  name_acquired_handler,
+                              GBusNameLostCallback      name_lost_handler,
+                              gpointer                  user_data,
+                              GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!g_dbus_connection_is_closed (connection), 0);
+  g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->flags = flags;
+  client->name_acquired_handler = name_acquired_handler;
+  client->name_lost_handler = name_lost_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  client->connection = g_object_ref (connection);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  G_UNLOCK (lock);
+
+  has_connection (client);
+
+  return client->id;
+}
+
+/**
+ * g_bus_own_name:
+ * @bus_type: The type of bus to own a name on.
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @bus_acquired_handler: Handler to invoke when connected to the bus of type @bus_type or %NULL.
+ * @name_acquired_handler: Handler to invoke when @name is acquired or %NULL.
+ * @name_lost_handler: Handler to invoke when @name is lost or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts acquiring @name on the bus specified by @bus_type and calls
+ * @name_acquired_handler and @name_lost_handler when the name is
+ * acquired respectively lost. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the @name_acquired_handler and @name_lost_handler
+ * callbacks will be invoked after calling this function - there are three
+ * possible cases:
+ * <itemizedlist>
+ *   <listitem><para>
+ *     @name_lost_handler with a %NULL connection (if a connection to the bus can't be made).
+ *   </para></listitem>
+ *   <listitem><para>
+ *     @bus_acquired_handler then @name_lost_handler (if the name can't be obtained)
+ *   </para></listitem>
+ *   <listitem><para>
+ *     @bus_acquired_handler then @name_acquired_handler (if the name was obtained).
+ *   </para></listitem>
+ * </itemizedlist>
+ * When you are done owning the name, just call g_bus_unown_name()
+ * with the owner id this function returns.
+ *
+ * If the name is acquired or lost (for example another application
+ * could acquire the name if you allow replacement or the application
+ * currently owning the name exits), the handlers are also invoked. If the
+ * #GDBusConnection that is used for attempting to own the name
+ * closes, then @name_lost_handler is invoked since it is no
+ * longer possible for other processes to access the process.
+ *
+ * You cannot use g_bus_own_name() several times (unless interleaved
+ * with calls to g_bus_unown_name()) - only the first call will work.
+ *
+ * Another guarantee is that invocations of @name_acquired_handler
+ * and @name_lost_handler are guaranteed to alternate; that
+ * is, if @name_acquired_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_lost_handler. The reverse is also true.
+ *
+ * If you plan on exporting objects (using e.g. g_dbus_connection_register_object()), note
+ * that it is generally too late to export the objects in @name_acquired_handler. Instead,
+ * you can do this in @bus_acquired_handler since you are guaranteed that this will
+ * run before @name is requested from the bus.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to own names and export objects, see <xref linkend="gdbus-owning-names"/>. Simply
+ * register objects to be exported in @bus_acquired_handler and
+ * unregister the objects (if any) in @name_lost_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unown_name() to stop owning the name.
+ **/
+guint
+g_bus_own_name (GBusType                  bus_type,
+                const gchar              *name,
+                GBusNameOwnerFlags        flags,
+                GBusAcquiredCallback      bus_acquired_handler,
+                GBusNameAcquiredCallback  name_acquired_handler,
+                GBusNameLostCallback      name_lost_handler,
+                gpointer                  user_data,
+                GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->flags = flags;
+  client->bus_acquired_handler = bus_acquired_handler;
+  client->name_acquired_handler = name_acquired_handler;
+  client->name_lost_handler = name_lost_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  g_bus_get (bus_type,
+             NULL,
+             connection_get_cb,
+             client_ref (client));
+
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * g_bus_unown_name:
+ * @owner_id: An identifier obtained from g_bus_own_name()
+ *
+ * Stops owning a name.
+ */
+void
+g_bus_unown_name (guint owner_id)
+{
+  Client *client;
+
+  g_return_if_fail (owner_id > 0);
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (owner_id == 0 || map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (owner_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to g_bus_unown_name()", owner_id);
+      goto out;
+    }
+
+  client->cancelled = TRUE;
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (owner_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  /* do callback without holding lock */
+  if (client != NULL)
+    {
+      /* Release the name if needed */
+      if (client->needs_release && client->connection != NULL)
+        {
+          GVariant *result;
+          GError *error;
+          guint32 release_name_reply;
+
+          /* TODO: it kinda sucks having to do a sync call to release the name - but if
+           * we don't, then a subsequent grab of the name will make the bus daemon return
+           * IN_QUEUE which will trigger name_lost().
+           *
+           * I believe this is a bug in the bus daemon.
+           */
+          error = NULL;
+          result = g_dbus_connection_invoke_method_sync (client->connection,
+                                                         "org.freedesktop.DBus",  /* bus name */
+                                                         "/org/freedesktop/DBus", /* object path */
+                                                         "org.freedesktop.DBus",  /* interface name */
+                                                         "ReleaseName",           /* method name */
+                                                         g_variant_new ("(s)", client->name),
+                                                         G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                         -1,
+                                                         NULL,
+                                                         &error);
+          if (result == NULL)
+            {
+              g_warning ("Error releasing name %s: %s", client->name, error->message);
+              g_error_free (error);
+            }
+          else
+            {
+              g_variant_get (result, "(u)", &release_name_reply);
+              if (release_name_reply != 1 /* DBUS_RELEASE_NAME_REPLY_RELEASED */)
+                {
+                  g_warning ("Unexpected reply %d when releasing name %s", release_name_reply, client->name);
+                }
+              g_variant_unref (result);
+            }
+        }
+
+      if (client->disconnected_signal_handler_id > 0)
+        g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+      if (client->name_acquired_subscription_id > 0)
+        g_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+      if (client->name_lost_subscription_id > 0)
+        g_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+      client->disconnected_signal_handler_id = 0;
+      client->name_acquired_subscription_id = 0;
+      client->name_lost_subscription_id = 0;
+      if (client->connection != NULL)
+        {
+          g_object_unref (client->connection);
+          client->connection = NULL;
+        }
+
+      client_unref (client);
+    }
+}
diff --git a/gio/gdbusnameowning.h b/gio/gdbusnameowning.h
new file mode 100644
index 0000000..fc063e0
--- /dev/null
+++ b/gio/gdbusnameowning.h
@@ -0,0 +1,88 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_NAME_OWNING_H__
+#define __G_DBUS_NAME_OWNING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusAcquiredCallback:
+ * @connection: The #GDBusConnection to a message bus.
+ * @name: The name that is requested to be owned.
+ * @user_data: User data passed to g_bus_own_name().
+ *
+ * Invoked when a connection to a message bus has been obtained.
+ */
+typedef void (*GBusAcquiredCallback) (GDBusConnection *connection,
+                                      const gchar     *name,
+                                      gpointer         user_data);
+
+/**
+ * GBusNameAcquiredCallback:
+ * @connection: The #GDBusConnection on which to acquired the name.
+ * @name: The name being owned.
+ * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection().
+ *
+ * Invoked when the name is acquired.
+ */
+typedef void (*GBusNameAcquiredCallback) (GDBusConnection *connection,
+                                          const gchar     *name,
+                                          gpointer         user_data);
+
+/**
+ * GBusNameLostCallback:
+ * @connection: The #GDBusConnection on which to acquire the name or %NULL if
+ * the connection was disconnected.
+ * @name: The name being owned.
+ * @user_data: User data passed to g_bus_own_name() or g_bus_own_name_on_connection().
+ *
+ * Invoked when the name is lost or @connection has been closed.
+ */
+typedef void (*GBusNameLostCallback) (GDBusConnection *connection,
+                                      const gchar     *name,
+                                      gpointer         user_data);
+
+guint g_bus_own_name                 (GBusType                  bus_type,
+                                      const gchar              *name,
+                                      GBusNameOwnerFlags        flags,
+                                      GBusAcquiredCallback      bus_acquired_handler,
+                                      GBusNameAcquiredCallback  name_acquired_handler,
+                                      GBusNameLostCallback      name_lost_handler,
+                                      gpointer                  user_data,
+                                      GDestroyNotify            user_data_free_func);
+
+guint g_bus_own_name_on_connection   (GDBusConnection          *connection,
+                                      const gchar              *name,
+                                      GBusNameOwnerFlags        flags,
+                                      GBusNameAcquiredCallback  name_acquired_handler,
+                                      GBusNameLostCallback      name_lost_handler,
+                                      gpointer                  user_data,
+                                      GDestroyNotify            user_data_free_func);
+
+void  g_bus_unown_name               (guint                     owner_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_NAME_OWNING_H__ */
diff --git a/gio/gdbusnamewatching.c b/gio/gdbusnamewatching.c
new file mode 100644
index 0000000..92e04cc
--- /dev/null
+++ b/gio/gdbusnamewatching.c
@@ -0,0 +1,620 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnamewatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnamewatching
+ * @title: Watching Bus Names
+ * @short_description: Simple API for watching bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus names.
+ *
+ * <example id="gdbus-watching-names"><title>Simple application watching a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-watch-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  PREVIOUS_CALL_NONE = 0,
+  PREVIOUS_CALL_APPEARED,
+  PREVIOUS_CALL_VANISHED,
+} PreviousCall;
+
+typedef struct
+{
+  volatile gint             ref_count;
+  guint                     id;
+  gchar                    *name;
+  GBusNameWatcherFlags      flags;
+  gchar                    *name_owner;
+  GBusNameAppearedCallback  name_appeared_handler;
+  GBusNameVanishedCallback  name_vanished_handler;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+  GMainContext             *main_context;
+
+  GDBusConnection          *connection;
+  gulong                    disconnected_signal_handler_id;
+  guint                     name_owner_changed_subscription_id;
+
+  PreviousCall              previous_call;
+
+  gboolean                  cancelled;
+  gboolean                  initialized;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+static Client *
+client_ref (Client *client)
+{
+  g_atomic_int_inc (&client->ref_count);
+  return client;
+}
+
+static void
+client_unref (Client *client)
+{
+  if (g_atomic_int_dec_and_test (&client->ref_count))
+    {
+      if (client->connection != NULL)
+        {
+          if (client->name_owner_changed_subscription_id > 0)
+            g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+          if (client->disconnected_signal_handler_id > 0)
+            g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+          g_object_unref (client->connection);
+        }
+      g_free (client->name);
+      g_free (client->name_owner);
+      if (client->main_context != NULL)
+        g_main_context_unref (client->main_context);
+      if (client->user_data_free_func != NULL)
+        client->user_data_free_func (client->user_data);
+      g_free (client);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  CALL_TYPE_NAME_APPEARED,
+  CALL_TYPE_NAME_VANISHED
+} CallType;
+
+typedef struct
+{
+  Client *client;
+
+  /* keep this separate because client->connection may
+   * be set to NULL after scheduling the call
+   */
+  GDBusConnection *connection;
+
+  /* ditto */
+  gchar *name_owner;
+
+  CallType call_type;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+  if (data->connection != NULL)
+    g_object_unref (data->connection);
+  g_free (data->name_owner);
+  client_unref (data->client);
+  g_free (data);
+}
+
+static void
+actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type)
+{
+  switch (call_type)
+    {
+    case CALL_TYPE_NAME_APPEARED:
+      if (client->name_appeared_handler != NULL)
+        {
+          client->name_appeared_handler (connection,
+                                         client->name,
+                                         name_owner,
+                                         client->user_data);
+        }
+      break;
+
+    case CALL_TYPE_NAME_VANISHED:
+      if (client->name_vanished_handler != NULL)
+        {
+          client->name_vanished_handler (connection,
+                                         client->name,
+                                         client->user_data);
+        }
+      break;
+
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+  CallHandlerData *data = _data;
+  actually_do_call (data->client, data->connection, data->name_owner, data->call_type);
+  return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client, CallType call_type)
+{
+  CallHandlerData *data;
+  GSource *idle_source;
+
+  data = g_new0 (CallHandlerData, 1);
+  data->client = client_ref (client);
+  data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+  data->name_owner = g_strdup (client->name_owner);
+  data->call_type = call_type;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         call_in_idle_cb,
+                         data,
+                         (GDestroyNotify) call_handler_data_free);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+static void
+do_call (Client *client, CallType call_type)
+{
+  /* only schedule in idle if we're not in the right thread */
+  if (g_main_context_get_thread_default () != client->main_context)
+    schedule_call_in_idle (client, call_type);
+  else
+    actually_do_call (client, client->connection, client->name_owner, call_type);
+}
+
+static void
+call_appeared_handler (Client *client)
+{
+  if (client->previous_call != PREVIOUS_CALL_APPEARED)
+    {
+      client->previous_call = PREVIOUS_CALL_APPEARED;
+      if (!client->cancelled && client->name_appeared_handler != NULL)
+        {
+          do_call (client, CALL_TYPE_NAME_APPEARED);
+        }
+    }
+}
+
+static void
+call_vanished_handler (Client  *client,
+                       gboolean ignore_cancelled)
+{
+  if (client->previous_call != PREVIOUS_CALL_VANISHED)
+    {
+      client->previous_call = PREVIOUS_CALL_VANISHED;
+      if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL)
+        {
+          do_call (client, CALL_TYPE_NAME_VANISHED);
+        }
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (GDBusConnection *connection,
+                            gboolean         remote_peer_vanished,
+                            GError          *error,
+                            gpointer         user_data)
+{
+  Client *client = user_data;
+
+  if (client->name_owner_changed_subscription_id > 0)
+    g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+  if (client->disconnected_signal_handler_id > 0)
+    g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+  g_object_unref (client->connection);
+  client->disconnected_signal_handler_id = 0;
+  client->name_owner_changed_subscription_id = 0;
+  client->connection = NULL;
+
+  call_vanished_handler (client, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_owner_changed (GDBusConnection *connection,
+                       const gchar      *sender_name,
+                       const gchar      *object_path,
+                       const gchar      *interface_name,
+                       const gchar      *signal_name,
+                       GVariant         *parameters,
+                       gpointer          user_data)
+{
+  Client *client = user_data;
+  const gchar *name;
+  const gchar *old_owner;
+  const gchar *new_owner;
+
+  if (!client->initialized)
+    goto out;
+
+  if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+      g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+      g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+    goto out;
+
+  g_variant_get (parameters,
+                 "(sss)",
+                 &name,
+                 &old_owner,
+                 &new_owner);
+
+  /* we only care about a specific name */
+  if (g_strcmp0 (name, client->name) != 0)
+    goto out;
+
+  if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
+    {
+      g_free (client->name_owner);
+      client->name_owner = NULL;
+      call_vanished_handler (client, FALSE);
+    }
+
+  if (new_owner != NULL && strlen (new_owner) > 0)
+    {
+      g_warn_if_fail (client->name_owner == NULL);
+      g_free (client->name_owner);
+      client->name_owner = g_strdup (new_owner);
+      call_appeared_handler (client);
+    }
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_name_owner_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+  GVariant *result;
+  const char *name_owner;
+
+  name_owner = NULL;
+  result = NULL;
+
+  result = g_dbus_connection_invoke_method_finish (client->connection,
+                                                   res,
+                                                   NULL);
+  if (result != NULL)
+    {
+      g_variant_get (result, "(s)", &name_owner);
+    }
+
+  if (name_owner != NULL)
+    {
+      g_warn_if_fail (client->name_owner == NULL);
+      client->name_owner = g_strdup (name_owner);
+      call_appeared_handler (client);
+    }
+  else
+    {
+      call_vanished_handler (client, FALSE);
+    }
+
+  client->initialized = TRUE;
+
+  if (result != NULL)
+    g_variant_unref (result);
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+invoke_get_name_owner (Client *client)
+{
+  g_dbus_connection_invoke_method (client->connection,
+                                   "org.freedesktop.DBus",  /* bus name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetNameOwner",          /* method name */
+                                   g_variant_new ("(s)", client->name),
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) get_name_owner_cb,
+                                   client_ref (client));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+start_service_by_name_cb (GObject      *source_object,
+                          GAsyncResult *res,
+                          gpointer      user_data)
+{
+  Client *client = user_data;
+  GVariant *result;
+
+  result = NULL;
+
+  result = g_dbus_connection_invoke_method_finish (client->connection,
+                                                   res,
+                                                   NULL);
+  if (result != NULL)
+    {
+      guint32 start_service_result;
+      g_variant_get (result, "(u)", &start_service_result);
+
+      if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */
+        {
+          invoke_get_name_owner (client);
+        }
+      else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */
+        {
+          invoke_get_name_owner (client);
+        }
+      else
+        {
+          g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result);
+          call_vanished_handler (client, FALSE);
+          client->initialized = TRUE;
+        }
+    }
+  else
+    {
+      /* Errors are not unexpected; the bus will reply e.g.
+       *
+       *   org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2
+       *   was not provided by any .service files
+       *
+       * so just report vanished.
+       */
+      call_vanished_handler (client, FALSE);
+      client->initialized = TRUE;
+    }
+
+  if (result != NULL)
+    g_variant_unref (result);
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+  /* listen for disconnection */
+  client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+                                                             "closed",
+                                                             G_CALLBACK (on_connection_disconnected),
+                                                             client);
+
+  /* start listening to NameOwnerChanged messages immediately */
+  client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection,
+                                                                                   "org.freedesktop.DBus",  /* name */
+                                                                                   "org.freedesktop.DBus",  /* if */
+                                                                                   "NameOwnerChanged",      /* signal */
+                                                                                   "/org/freedesktop/DBus", /* path */
+                                                                                   client->name,
+                                                                                   on_name_owner_changed,
+                                                                                   client,
+                                                                                   NULL);
+
+  if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
+    {
+      g_dbus_connection_invoke_method (client->connection,
+                                       "org.freedesktop.DBus",  /* bus name */
+                                       "/org/freedesktop/DBus", /* object path */
+                                       "org.freedesktop.DBus",  /* interface name */
+                                       "StartServiceByName",    /* method name */
+                                       g_variant_new ("(su)", client->name, 0),
+                                       G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                       -1,
+                                       NULL,
+                                       (GAsyncReadyCallback) start_service_by_name_cb,
+                                       client_ref (client));
+    }
+  else
+    {
+      /* check owner */
+      invoke_get_name_owner (client);
+    }
+}
+
+
+static void
+connection_get_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+
+  client->connection = g_bus_get_finish (res, NULL);
+  if (client->connection == NULL)
+    {
+      call_vanished_handler (client, FALSE);
+      goto out;
+    }
+
+  has_connection (client);
+
+ out:
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_bus_watch_name:
+ * @bus_type: The type of bus to watch a name on.
+ * @name: The name (well-known or unique) to watch.
+ * @flags: Flags from the #GBusNameWatcherFlags enumeration.
+ * @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL.
+ * @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching @name on the bus specified by @bus_type and calls
+ * @name_appeared_handler and @name_vanished_handler when the name is
+ * known to have a owner respectively known to lose its
+ * owner. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the handlers will be invoked after
+ * calling this function. When you are done watching the name, just
+ * call g_bus_unwatch_name() with the watcher id this function
+ * returns.
+ *
+ * If the name vanishes or appears (for example the application owning
+ * the name could restart), the handlers are also invoked. If the
+ * #GDBusConnection that is used for watching the name disconnects, then
+ * @name_vanished_handler is invoked since it is no longer
+ * possible to access the name.
+ *
+ * Another guarantee is that invocations of @name_appeared_handler
+ * and @name_vanished_handler are guaranteed to alternate; that
+ * is, if @name_appeared_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_vanished_handler. The reverse is also true.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to take action when a certain name exists, see <xref
+ * linkend="gdbus-watching-names"/>. Basically, the application
+ * should create object proxies in @name_appeared_handler and destroy
+ * them again (if any) in @name_vanished_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * g_bus_unwatch_name() to stop watching the name.
+ **/
+guint
+g_bus_watch_name (GBusType                  bus_type,
+                  const gchar              *name,
+                  GBusNameWatcherFlags      flags,
+                  GBusNameAppearedCallback  name_appeared_handler,
+                  GBusNameVanishedCallback  name_vanished_handler,
+                  gpointer                  user_data,
+                  GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (g_dbus_is_name (name), 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->flags = flags;
+  client->name_appeared_handler = name_appeared_handler;
+  client->name_vanished_handler = name_vanished_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  g_bus_get (bus_type,
+             NULL,
+             connection_get_cb,
+             client_ref (client));
+
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * g_bus_unwatch_name:
+ * @watcher_id: An identifier obtained from g_bus_watch_name()
+ *
+ * Stops watching a name.
+ **/
+void
+g_bus_unwatch_name (guint watcher_id)
+{
+  Client *client;
+
+  g_return_if_fail (watcher_id > 0);
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (watcher_id == 0 ||
+      map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id);
+      goto out;
+    }
+
+  client->cancelled = TRUE;
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  /* do callback without holding lock */
+  if (client != NULL)
+    {
+      client_unref (client);
+    }
+}
diff --git a/gio/gdbusnamewatching.h b/gio/gdbusnamewatching.h
new file mode 100644
index 0000000..a724d42
--- /dev/null
+++ b/gio/gdbusnamewatching.h
@@ -0,0 +1,68 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_NAME_WATCHING_H__
+#define __G_DBUS_NAME_WATCHING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusNameAppearedCallback:
+ * @connection: The #GDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @user_data: User data passed to g_bus_watch_name().
+ *
+ * Invoked when the name being watched is known to have to have a owner.
+ */
+typedef void (*GBusNameAppearedCallback) (GDBusConnection *connection,
+                                          const gchar     *name,
+                                          const gchar     *name_owner,
+                                          gpointer         user_data);
+
+/**
+ * GBusNameVanishedCallback:
+ * @connection: The #GDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to g_bus_watch_name().
+ *
+ * Invoked when the name being watched is known not to have to have a owner.
+ */
+typedef void (*GBusNameVanishedCallback) (GDBusConnection *connection,
+                                          const gchar     *name,
+                                          gpointer         user_data);
+
+
+guint g_bus_watch_name   (GBusType                  bus_type,
+                          const gchar              *name,
+                          GBusNameWatcherFlags      flags,
+                          GBusNameAppearedCallback  name_appeared_handler,
+                          GBusNameVanishedCallback  name_vanished_handler,
+                          gpointer                  user_data,
+                          GDestroyNotify            user_data_free_func);
+void  g_bus_unwatch_name (guint                     watcher_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_NAME_WATCHING_H__ */
diff --git a/gio/gdbusprivate.c b/gio/gdbusprivate.c
new file mode 100644
index 0000000..adc6e12
--- /dev/null
+++ b/gio/gdbusprivate.c
@@ -0,0 +1,1040 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixconnection.h>
+#include <gio/gunixfdmessage.h>
+#include "gunixcredentialsmessage.h"
+#include <unistd.h>
+#endif
+
+#include "giotypes.h"
+#include "gdbusprivate.h"
+#include "gdbusmessage.h"
+#include "gdbuserror.h"
+#include "gdbusintrospection.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+hexdump (const gchar *data, gsize len, guint indent)
+{
+ guint n, m;
+ GString *ret;
+
+ ret = g_string_new (NULL);
+
+ for (n = 0; n < len; n += 16)
+   {
+     g_string_append_printf (ret, "%*s%04x: ", indent, "", n);
+
+     for (m = n; m < n + 16; m++)
+       {
+         if (m > n && (m%4) == 0)
+           g_string_append_c (ret, ' ');
+         if (m < len)
+           g_string_append_printf (ret, "%02x ", (guchar) data[m]);
+         else
+           g_string_append (ret, "   ");
+       }
+
+     g_string_append (ret, "   ");
+
+     for (m = n; m < len && m < n + 16; m++)
+       g_string_append_c (ret, g_ascii_isprint (data[m]) ? data[m] : '.');
+
+     g_string_append_c (ret, '\n');
+   }
+
+ return g_string_free (ret, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Unfortunately ancillary messages are discarded when reading from a
+ * socket using the GSocketInputStream abstraction. So we provide a
+ * very GInputStream-ish API that uses GSocket in this case (very
+ * similar to GSocketInputStream).
+ */
+
+typedef struct
+{
+  GSocket *socket;
+  GCancellable *cancellable;
+
+  void *buffer;
+  gsize count;
+
+  GSocketControlMessage ***messages;
+  gint *num_messages;
+
+  GSimpleAsyncResult *simple;
+
+  gboolean from_mainloop;
+} ReadWithControlData;
+
+static void
+read_with_control_data_free (ReadWithControlData *data)
+{
+  g_object_unref (data->socket);
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+  g_free (data);
+}
+
+static gboolean
+_g_socket_read_with_control_messages_ready (GSocket      *socket,
+                                            GIOCondition  condition,
+                                            gpointer      user_data)
+{
+  ReadWithControlData *data = user_data;
+  GError *error;
+  gssize result;
+  GInputVector vector;
+
+  error = NULL;
+  vector.buffer = data->buffer;
+  vector.size = data->count;
+  result = g_socket_receive_message (data->socket,
+                                     NULL, /* address */
+                                     &vector,
+                                     1,
+                                     data->messages,
+                                     data->num_messages,
+                                     NULL,
+                                     data->cancellable,
+                                     &error);
+  if (result >= 0)
+    {
+      g_simple_async_result_set_op_res_gssize (data->simple, result);
+    }
+  else
+    {
+      g_assert (error != NULL);
+      g_simple_async_result_set_from_error (data->simple, error);
+      g_error_free (error);
+    }
+
+  if (data->from_mainloop)
+    g_simple_async_result_complete (data->simple);
+  else
+    g_simple_async_result_complete_in_idle (data->simple);
+
+  return FALSE;
+}
+
+static void
+_g_socket_read_with_control_messages (GSocket                 *socket,
+                                      void                    *buffer,
+                                      gsize                    count,
+                                      GSocketControlMessage ***messages,
+                                      gint                    *num_messages,
+                                      gint                     io_priority,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
+{
+  ReadWithControlData *data;
+
+  data = g_new0 (ReadWithControlData, 1);
+  data->socket = g_object_ref (socket);
+  data->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL;
+  data->buffer = buffer;
+  data->count = count;
+  data->messages = messages;
+  data->num_messages = num_messages;
+
+  data->simple = g_simple_async_result_new (G_OBJECT (socket),
+                                            callback,
+                                            user_data,
+                                            _g_socket_read_with_control_messages);
+
+  if (!g_socket_condition_check (socket, G_IO_IN))
+    {
+      GSource *source;
+      data->from_mainloop = TRUE;
+      source = g_socket_create_source (data->socket,
+                                       G_IO_IN | G_IO_HUP | G_IO_ERR,
+                                       cancellable);
+      g_source_set_callback (source,
+                             (GSourceFunc) _g_socket_read_with_control_messages_ready,
+                             data,
+                             (GDestroyNotify) read_with_control_data_free);
+      g_source_attach (source, g_main_context_get_thread_default ());
+      g_source_unref (source);
+    }
+  else
+    {
+      _g_socket_read_with_control_messages_ready (data->socket, G_IO_IN, data);
+      read_with_control_data_free (data);
+    }
+}
+
+static gssize
+_g_socket_read_with_control_messages_finish (GSocket       *socket,
+                                             GAsyncResult  *result,
+                                             GError       **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+  g_return_val_if_fail (G_IS_SOCKET (socket), -1);
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == _g_socket_read_with_control_messages);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+      return -1;
+  else
+    return g_simple_async_result_get_op_res_gssize (simple);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (shared_thread_lock);
+
+typedef struct
+{
+  gint num_users;
+  GThread *thread;
+  GMainContext *context;
+  GMainLoop *loop;
+} SharedThreadData;
+
+static SharedThreadData *shared_thread_data = NULL;
+
+static gpointer
+shared_thread_func (gpointer data)
+{
+  g_main_context_push_thread_default (shared_thread_data->context);
+  g_main_loop_run (shared_thread_data->loop);
+  g_main_context_pop_thread_default (shared_thread_data->context);
+  return NULL;
+}
+
+typedef void (*GDBusSharedThreadFunc) (gpointer user_data);
+
+typedef struct
+{
+  GDBusSharedThreadFunc func;
+  gpointer              user_data;
+  gboolean              done;
+} CallerData;
+
+static gboolean
+invoke_caller (gpointer user_data)
+{
+  CallerData *data = user_data;
+  data->func (data->user_data);
+  data->done = TRUE;
+  return FALSE;
+}
+
+static void
+_g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
+                           gpointer              user_data)
+{
+  GError *error;
+  GSource *idle_source;
+  CallerData *data;
+
+  G_LOCK (shared_thread_lock);
+
+  if (shared_thread_data != NULL)
+    {
+      shared_thread_data->num_users += 1;
+      goto have_thread;
+    }
+
+  shared_thread_data = g_new0 (SharedThreadData, 1);
+  shared_thread_data->num_users = 1;
+
+  error = NULL;
+  shared_thread_data->context = g_main_context_new ();
+  shared_thread_data->loop = g_main_loop_new (shared_thread_data->context, FALSE);
+  shared_thread_data->thread = g_thread_create (shared_thread_func,
+                                                NULL,
+                                                TRUE,
+                                                &error);
+  g_assert_no_error (error);
+
+ have_thread:
+
+  data = g_new0 (CallerData, 1);
+  data->func = func;
+  data->user_data = user_data;
+  data->done = FALSE;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         invoke_caller,
+                         data,
+                         NULL);
+  g_source_attach (idle_source, shared_thread_data->context);
+  g_source_unref (idle_source);
+
+  /* wait for the user code to run.. hmm.. probably use a condition variable instead */
+  while (!data->done)
+    g_thread_yield ();
+
+  g_free (data);
+
+  G_UNLOCK (shared_thread_lock);
+}
+
+static void
+_g_dbus_shared_thread_unref (void)
+{
+  /* TODO: actually destroy the shared thread here */
+#if 0
+  G_LOCK (shared_thread_lock);
+  g_assert (shared_thread_data != NULL);
+  shared_thread_data->num_users -= 1;
+  if (shared_thread_data->num_users == 0)
+    {
+      g_main_loop_quit (shared_thread_data->loop);
+      //g_thread_join (shared_thread_data->thread);
+      g_main_loop_unref (shared_thread_data->loop);
+      g_main_context_unref (shared_thread_data->context);
+      g_free (shared_thread_data);
+      shared_thread_data = NULL;
+      G_UNLOCK (shared_thread_lock);
+    }
+  else
+    {
+      G_UNLOCK (shared_thread_lock);
+    }
+#endif
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct GDBusWorker
+{
+  volatile gint                       ref_count;
+  gboolean                            stopped;
+  GIOStream                          *stream;
+  GDBusCapabilityFlags                capabilities;
+  GCancellable                       *cancellable;
+  GDBusWorkerMessageReceivedCallback  message_received_callback;
+  GDBusWorkerDisconnectedCallback     disconnected_callback;
+  gpointer                            user_data;
+
+  GThread                            *thread;
+
+  /* if not NULL, stream is GSocketConnection */
+  GSocket *socket;
+
+  /* used for reading */
+  GMutex                             *read_lock;
+  gchar                              *read_buffer;
+  gsize                               read_buffer_allocated_size;
+  gsize                               read_buffer_cur_size;
+  gsize                               read_buffer_bytes_wanted;
+  GUnixFDList                        *read_fd_list;
+  GSocketControlMessage             **read_ancillary_messages;
+  gint                                read_num_ancillary_messages;
+
+  /* used for writing */
+  GMutex                             *write_lock;
+  GQueue                             *write_queue;
+  gboolean                            write_is_pending;
+};
+
+struct _MessageToWriteData ;
+typedef struct _MessageToWriteData MessageToWriteData;
+
+static void message_to_write_data_free (MessageToWriteData *data);
+
+static GDBusWorker *
+_g_dbus_worker_ref (GDBusWorker *worker)
+{
+  g_atomic_int_inc (&worker->ref_count);
+  return worker;
+}
+
+static void
+_g_dbus_worker_unref (GDBusWorker *worker)
+{
+  if (g_atomic_int_dec_and_test (&worker->ref_count))
+    {
+      _g_dbus_shared_thread_unref ();
+
+      g_object_unref (worker->stream);
+
+      g_mutex_free (worker->read_lock);
+      g_object_unref (worker->cancellable);
+      if (worker->read_fd_list != NULL)
+        g_object_unref (worker->read_fd_list);
+
+      g_mutex_free (worker->write_lock);
+      g_queue_foreach (worker->write_queue,
+                       (GFunc) message_to_write_data_free,
+                       NULL);
+      g_queue_free (worker->write_queue);
+      g_free (worker);
+    }
+}
+
+static void
+_g_dbus_worker_emit_disconnected (GDBusWorker  *worker,
+                                  gboolean      remote_peer_vanished,
+                                  GError       *error)
+{
+  if (!worker->stopped)
+    worker->disconnected_callback (worker, remote_peer_vanished, error, worker->user_data);
+}
+
+static void
+_g_dbus_worker_emit_message (GDBusWorker  *worker,
+                             GDBusMessage *message)
+{
+  if (!worker->stopped)
+    worker->message_received_callback (worker, message, worker->user_data);
+}
+
+static void _g_dbus_worker_do_read_unlocked (GDBusWorker *worker);
+
+/* called in private thread shared by all GDBusConnection instances (without read-lock held) */
+static void
+_g_dbus_worker_do_read_cb (GInputStream  *input_stream,
+                           GAsyncResult  *res,
+                           gpointer       user_data)
+{
+  GDBusWorker *worker = user_data;
+  GError *error;
+  gssize bytes_read;
+
+  g_mutex_lock (worker->read_lock);
+
+  /* If already stopped, don't even process the reply */
+  if (worker->stopped)
+    goto out;
+
+  error = NULL;
+  if (worker->socket == NULL)
+    bytes_read = g_input_stream_read_finish (g_io_stream_get_input_stream (worker->stream),
+                                             res,
+                                             &error);
+  else
+    bytes_read = _g_socket_read_with_control_messages_finish (worker->socket,
+                                                              res,
+                                                              &error);
+  if (worker->read_num_ancillary_messages > 0)
+    {
+      gint n;
+      for (n = 0; n < worker->read_num_ancillary_messages; n++)
+        {
+          GSocketControlMessage *control_message = G_SOCKET_CONTROL_MESSAGE (worker->read_ancillary_messages[n]);
+
+          if (FALSE)
+            {
+            }
+#ifdef G_OS_UNIX
+          else if (G_IS_UNIX_FD_MESSAGE (control_message))
+            {
+              GUnixFDMessage *fd_message;
+              gint *fds;
+              gint num_fds;
+
+              fd_message = G_UNIX_FD_MESSAGE (control_message);
+              fds = g_unix_fd_message_steal_fds (fd_message, &num_fds);
+              if (worker->read_fd_list == NULL)
+                {
+                  worker->read_fd_list = g_unix_fd_list_new_from_array (fds, num_fds);
+                }
+              else
+                {
+                  gint n;
+                  for (n = 0; n < num_fds; n++)
+                    {
+                      /* TODO: really want a append_steal() */
+                      g_unix_fd_list_append (worker->read_fd_list, fds[n], NULL);
+                      close (fds[n]);
+                    }
+                }
+              g_free (fds);
+            }
+          else if (G_IS_UNIX_CREDENTIALS_MESSAGE (control_message))
+            {
+              /* do nothing */
+            }
+#endif
+          else
+            {
+              if (error == NULL)
+                {
+                  g_set_error (&error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Unexpected ancillary message of type %s received from peer",
+                               g_type_name (G_TYPE_FROM_INSTANCE (control_message)));
+                  _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+                  g_error_free (error);
+                  g_object_unref (control_message);
+                  n++;
+                  while (n < worker->read_num_ancillary_messages)
+                    g_object_unref (worker->read_ancillary_messages[n++]);
+                  g_free (worker->read_ancillary_messages);
+                  goto out;
+                }
+            }
+          g_object_unref (control_message);
+        }
+      g_free (worker->read_ancillary_messages);
+    }
+
+  if (bytes_read == -1)
+    {
+      _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+      g_error_free (error);
+      goto out;
+    }
+
+#if 0
+  g_debug ("read %d bytes (is_closed=%d blocking=%d condition=0x%02x) stream %p, %p",
+           (gint) bytes_read,
+           g_socket_is_closed (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
+           g_socket_get_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream))),
+           g_socket_condition_check (g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream)),
+                                     G_IO_IN | G_IO_OUT | G_IO_HUP),
+           worker->stream,
+           worker);
+#endif
+
+  /* TODO: hmm, hmm... */
+  if (bytes_read == 0)
+    {
+      g_set_error (&error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Underlying GIOStream returned 0 bytes on an async read");
+      _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+      g_error_free (error);
+      goto out;
+    }
+
+  worker->read_buffer_cur_size += bytes_read;
+  if (worker->read_buffer_bytes_wanted == worker->read_buffer_cur_size)
+    {
+      /* OK, got what we asked for! */
+      if (worker->read_buffer_bytes_wanted == 16)
+        {
+          gssize message_len;
+          /* OK, got the header - determine how many more bytes are needed */
+          error = NULL;
+          message_len = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer,
+                                                     16,
+                                                     &error);
+          if (message_len == -1)
+            {
+              g_warning ("_g_dbus_worker_do_read_cb: error determing bytes needed: %s", error->message);
+              _g_dbus_worker_emit_disconnected (worker, FALSE, error);
+              g_error_free (error);
+              goto out;
+            }
+
+          worker->read_buffer_bytes_wanted = message_len;
+          _g_dbus_worker_do_read_unlocked (worker);
+        }
+      else
+        {
+          GDBusMessage *message;
+          error = NULL;
+
+          /* TODO: use connection->priv->auth to decode the message */
+
+          message = g_dbus_message_new_from_blob ((guchar *) worker->read_buffer,
+                                                  worker->read_buffer_cur_size,
+                                                  &error);
+          if (message == NULL)
+            {
+              _g_dbus_worker_emit_disconnected (worker, FALSE, error);
+              g_error_free (error);
+              goto out;
+            }
+
+          if (worker->read_fd_list != NULL)
+            {
+              g_dbus_message_set_unix_fd_list (message, worker->read_fd_list);
+              worker->read_fd_list = NULL;
+            }
+
+          if (G_UNLIKELY (_g_dbus_debug_message ()))
+            {
+              gchar *s;
+              g_print ("========================================================================\n"
+                       "GDBus-debug:Message:\n"
+                       "  <<<< RECEIVED D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
+                       worker->read_buffer_cur_size);
+              s = g_dbus_message_print (message, 2);
+              g_print ("%s", s);
+              g_free (s);
+              s = hexdump (worker->read_buffer, worker->read_buffer_cur_size, 2);
+              g_print ("%s\n", s);
+              g_free (s);
+            }
+
+          /* yay, got a message, go deliver it */
+          _g_dbus_worker_emit_message (worker, message);
+          g_object_unref (message);
+
+          /* start reading another message! */
+          worker->read_buffer_bytes_wanted = 0;
+          worker->read_buffer_cur_size = 0;
+          _g_dbus_worker_do_read_unlocked (worker);
+        }
+    }
+  else
+    {
+      /* didn't get all the bytes we requested - so repeat the request... */
+      _g_dbus_worker_do_read_unlocked (worker);
+    }
+
+ out:
+  g_mutex_unlock (worker->read_lock);
+
+  /* gives up the reference acquired when calling g_input_stream_read_async() */
+  _g_dbus_worker_unref (worker);
+}
+
+/* called in private thread shared by all GDBusConnection instances (with read-lock held) */
+static void
+_g_dbus_worker_do_read_unlocked (GDBusWorker *worker)
+{
+  /* if bytes_wanted is zero, it means start reading a message */
+  if (worker->read_buffer_bytes_wanted == 0)
+    {
+      worker->read_buffer_cur_size = 0;
+      worker->read_buffer_bytes_wanted = 16;
+    }
+
+  /* ensure we have a (big enough) buffer */
+  if (worker->read_buffer == NULL || worker->read_buffer_bytes_wanted > worker->read_buffer_allocated_size)
+    {
+      /* TODO: 4096 is randomly chosen; might want a better chosen default minimum */
+      worker->read_buffer_allocated_size = MAX (worker->read_buffer_bytes_wanted, 4096);
+      worker->read_buffer = g_realloc (worker->read_buffer, worker->read_buffer_allocated_size);
+    }
+
+  if (worker->socket == NULL)
+    g_input_stream_read_async (g_io_stream_get_input_stream (worker->stream),
+                               worker->read_buffer + worker->read_buffer_cur_size,
+                               worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size,
+                               G_PRIORITY_DEFAULT,
+                               worker->cancellable,
+                               (GAsyncReadyCallback) _g_dbus_worker_do_read_cb,
+                               _g_dbus_worker_ref (worker));
+  else
+    {
+      worker->read_ancillary_messages = NULL;
+      worker->read_num_ancillary_messages = 0;
+      _g_socket_read_with_control_messages (worker->socket,
+                                            worker->read_buffer + worker->read_buffer_cur_size,
+                                            worker->read_buffer_bytes_wanted - worker->read_buffer_cur_size,
+                                            &worker->read_ancillary_messages,
+                                            &worker->read_num_ancillary_messages,
+                                            G_PRIORITY_DEFAULT,
+                                            worker->cancellable,
+                                            (GAsyncReadyCallback) _g_dbus_worker_do_read_cb,
+                                            _g_dbus_worker_ref (worker));
+    }
+}
+
+/* called in private thread shared by all GDBusConnection instances (without read-lock held) */
+static void
+_g_dbus_worker_do_read (GDBusWorker *worker)
+{
+  g_mutex_lock (worker->read_lock);
+  _g_dbus_worker_do_read_unlocked (worker);
+  g_mutex_unlock (worker->read_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct _MessageToWriteData
+{
+  GDBusMessage *message;
+  gchar        *blob;
+  gsize         blob_size;
+};
+
+static void
+message_to_write_data_free (MessageToWriteData *data)
+{
+  g_object_unref (data->message);
+  g_free (data->blob);
+  g_free (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in private thread shared by all GDBusConnection instances (with write-lock held) */
+static gboolean
+write_message (GDBusWorker         *worker,
+               MessageToWriteData  *data,
+               GError             **error)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (data->blob_size > 16, FALSE);
+
+  ret = FALSE;
+
+  /* First, the initial 16 bytes - special case UNIX sockets here
+   * since it may involve writing an ancillary message with file
+   * descriptors
+   */
+#ifdef G_OS_UNIX
+  {
+    GOutputVector vector;
+    GSocketControlMessage *message;
+    GUnixFDList *fd_list;
+    gssize bytes_written;
+
+    fd_list = g_dbus_message_get_unix_fd_list (data->message);
+
+    message = NULL;
+    if (fd_list != NULL)
+      {
+        if (!G_IS_UNIX_CONNECTION (worker->stream))
+          {
+            g_set_error (error,
+                         G_IO_ERROR,
+                         G_IO_ERROR_INVALID_ARGUMENT,
+                         "Tried sending a file descriptor on unsupported stream of type %s",
+                         g_type_name (G_TYPE_FROM_INSTANCE (worker->stream)));
+            goto out;
+          }
+        else if (!(worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING))
+          {
+            g_set_error_literal (error,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_INVALID_ARGUMENT,
+                                 "Tried sending a file descriptor but remote peer does not support this capability");
+            goto out;
+          }
+        message = g_unix_fd_message_new_with_fd_list (fd_list);
+      }
+
+    vector.buffer = data->blob;
+    vector.size = 16;
+
+    bytes_written = g_socket_send_message (worker->socket,
+                                           NULL, /* address */
+                                           &vector,
+                                           1,
+                                           message != NULL ? &message : NULL,
+                                           message != NULL ? 1 : 0,
+                                           G_SOCKET_MSG_NONE,
+                                           worker->cancellable,
+                                           error);
+    if (bytes_written == -1)
+      {
+        g_prefix_error (error, _("Error writing first 16 bytes of message to socket: "));
+        if (message != NULL)
+          g_object_unref (message);
+        goto out;
+      }
+    if (message != NULL)
+      g_object_unref (message);
+
+    if (bytes_written < 16)
+      {
+        /* TODO: I think this needs to be handled ... are we guaranteed that the ancillary
+         * messages are sent?
+         */
+        g_assert_not_reached ();
+      }
+  }
+#else
+  /* write the first 16 bytes (guaranteed to return an error if everything can't be written) */
+  if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
+                                  (const gchar *) data->blob,
+                                  16,
+                                  NULL, /* bytes_written */
+                                  worker->cancellable, /* cancellable */
+                                  error))
+    goto out;
+#endif
+
+  /* Then write the rest of the message (guaranteed to return an error if everything can't be written) */
+  if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
+                                  (const gchar *) data->blob + 16,
+                                  data->blob_size - 16,
+                                  NULL, /* bytes_written */
+                                  worker->cancellable, /* cancellable */
+                                  error))
+    goto out;
+
+  ret = TRUE;
+
+  if (G_UNLIKELY (_g_dbus_debug_message ()))
+    {
+      gchar *s;
+      g_print ("========================================================================\n"
+               "GDBus-debug:Message:\n"
+               "  >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
+               data->blob_size);
+      s = g_dbus_message_print (data->message, 2);
+      g_print ("%s", s);
+      g_free (s);
+      s = hexdump (data->blob, data->blob_size, 2);
+      g_print ("%s\n", s);
+      g_free (s);
+    }
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
+static gboolean
+write_message_in_idle_cb (gpointer user_data)
+{
+  GDBusWorker *worker = user_data;
+  gboolean more_writes_are_pending;
+  MessageToWriteData *data;
+  GError *error;
+
+  g_mutex_lock (worker->write_lock);
+
+  data = g_queue_pop_head (worker->write_queue);
+  g_assert (data != NULL);
+
+  error = NULL;
+  if (!write_message (worker,
+                      data,
+                      &error))
+    {
+      /* TODO: handle */
+      _g_dbus_worker_emit_disconnected (worker, TRUE, error);
+      g_error_free (error);
+    }
+  message_to_write_data_free (data);
+
+  more_writes_are_pending = (g_queue_get_length (worker->write_queue) > 0);
+
+  worker->write_is_pending = more_writes_are_pending;
+  g_mutex_unlock (worker->write_lock);
+
+  return more_writes_are_pending;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* can be called from any thread - steals blob */
+void
+_g_dbus_worker_send_message (GDBusWorker    *worker,
+                             GDBusMessage   *message,
+                             gchar          *blob,
+                             gsize           blob_len)
+{
+  MessageToWriteData *data;
+
+  g_return_if_fail (G_IS_DBUS_MESSAGE (message));
+  g_return_if_fail (blob != NULL);
+  g_return_if_fail (blob_len > 16);
+
+  data = g_new0 (MessageToWriteData, 1);
+  data->message = g_object_ref (message);
+  data->blob = blob; /* steal! */
+  data->blob_size = blob_len;
+
+  g_mutex_lock (worker->write_lock);
+  g_queue_push_tail (worker->write_queue, data);
+  if (!worker->write_is_pending)
+    {
+      GSource *idle_source;
+
+      worker->write_is_pending = TRUE;
+
+      idle_source = g_idle_source_new ();
+      g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (idle_source,
+                             write_message_in_idle_cb,
+                             _g_dbus_worker_ref (worker),
+                             (GDestroyNotify) _g_dbus_worker_unref);
+      g_source_attach (idle_source, shared_thread_data->context);
+      g_source_unref (idle_source);
+    }
+  g_mutex_unlock (worker->write_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+_g_dbus_worker_thread_begin_func (gpointer user_data)
+{
+  GDBusWorker *worker = user_data;
+
+  worker->thread = g_thread_self ();
+
+  /* begin reading */
+  _g_dbus_worker_do_read (worker);
+}
+
+GDBusWorker *
+_g_dbus_worker_new (GIOStream                          *stream,
+                    GDBusCapabilityFlags                capabilities,
+                    GDBusWorkerMessageReceivedCallback  message_received_callback,
+                    GDBusWorkerDisconnectedCallback     disconnected_callback,
+                    gpointer                            user_data)
+{
+  GDBusWorker *worker;
+
+  g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL);
+  g_return_val_if_fail (message_received_callback != NULL, NULL);
+  g_return_val_if_fail (disconnected_callback != NULL, NULL);
+
+  worker = g_new0 (GDBusWorker, 1);
+  worker->ref_count = 1;
+
+  worker->read_lock = g_mutex_new ();
+  worker->message_received_callback = message_received_callback;
+  worker->disconnected_callback = disconnected_callback;
+  worker->user_data = user_data;
+  worker->stream = g_object_ref (stream);
+  worker->capabilities = capabilities;
+  worker->cancellable = g_cancellable_new ();
+
+  worker->write_lock = g_mutex_new ();
+  worker->write_queue = g_queue_new ();
+
+  if (G_IS_SOCKET_CONNECTION (worker->stream))
+    worker->socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (worker->stream));
+
+  _g_dbus_shared_thread_ref (_g_dbus_worker_thread_begin_func, worker);
+
+  return worker;
+}
+
+/* This can be called from any thread - frees worker - guarantees no callbacks
+ * will ever be issued again
+ */
+void
+_g_dbus_worker_stop (GDBusWorker *worker)
+{
+  /* If we're called in the worker thread it means we are called from
+   * a worker callback. This is fine, we just can't lock in that case since
+   * we're already holding the lock...
+   */
+  if (g_thread_self () != worker->thread)
+    g_mutex_lock (worker->read_lock);
+  worker->stopped = TRUE;
+  if (g_thread_self () != worker->thread)
+    g_mutex_unlock (worker->read_lock);
+
+  g_cancellable_cancel (worker->cancellable);
+  _g_dbus_worker_unref (worker);
+}
+
+#define G_DBUS_DEBUG_AUTHENTICATION (1<<0)
+#define G_DBUS_DEBUG_MESSAGE        (1<<1)
+#define G_DBUS_DEBUG_ALL            0xffffffff
+static gint _gdbus_debug_flags = 0;
+
+gboolean
+_g_dbus_debug_authentication (void)
+{
+  _g_dbus_initialize ();
+  return (_gdbus_debug_flags & G_DBUS_DEBUG_AUTHENTICATION) != 0;
+}
+
+gboolean
+_g_dbus_debug_message (void)
+{
+  _g_dbus_initialize ();
+  return (_gdbus_debug_flags & G_DBUS_DEBUG_MESSAGE) != 0;
+}
+
+/**
+ * _g_dbus_initialize:
+ *
+ * Does various one-time init things such as
+ *
+ *  - registering the G_DBUS_ERROR error domain
+ *  - parses the G_DBUS_DEBUG environment variable
+ */
+void
+_g_dbus_initialize (void)
+{
+  static volatile gsize initialized = 0;
+
+  if (g_once_init_enter (&initialized))
+    {
+      volatile GQuark g_dbus_error_domain;
+      const gchar *debug;
+
+      g_dbus_error_domain = G_DBUS_ERROR;
+
+      debug = g_getenv ("G_DBUS_DEBUG");
+      if (debug != NULL)
+        {
+          gchar **tokens;
+          guint n;
+          tokens = g_strsplit (debug, ",", 0);
+          for (n = 0; tokens[n] != NULL; n++)
+            {
+              if (g_strcmp0 (tokens[n], "authentication") == 0)
+                _gdbus_debug_flags |= G_DBUS_DEBUG_AUTHENTICATION;
+              else if (g_strcmp0 (tokens[n], "message") == 0)
+                _gdbus_debug_flags |= G_DBUS_DEBUG_MESSAGE;
+              else if (g_strcmp0 (tokens[n], "all") == 0)
+                _gdbus_debug_flags |= G_DBUS_DEBUG_ALL;
+            }
+          g_strfreev (tokens);
+        }
+
+      g_once_init_leave (&initialized, 1);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gchar *
+_g_dbus_compute_complete_signature (GDBusArgInfo **args,
+                                    gboolean       include_parentheses)
+{
+  GString *s;
+  guint n;
+
+  if (include_parentheses)
+    s = g_string_new ("(");
+  else
+    s = g_string_new ("");
+  if (args != NULL)
+    for (n = 0; args[n] != NULL; n++)
+      g_string_append (s, args[n]->signature);
+
+  if (include_parentheses)
+    g_string_append_c (s, ')');
+
+  return g_string_free (s, FALSE);
+}
diff --git a/gio/gdbusprivate.h b/gio/gdbusprivate.h
new file mode 100644
index 0000000..ef7fa0a
--- /dev/null
+++ b/gio/gdbusprivate.h
@@ -0,0 +1,83 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (GIO_COMPILATION)
+#error "gdbusprivate.h is a private header file."
+#endif
+
+#ifndef __G_DBUS_PRIVATE_H__
+#define __G_DBUS_PRIVATE_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct GDBusWorker GDBusWorker;
+
+typedef void (*GDBusWorkerMessageReceivedCallback) (GDBusWorker   *worker,
+                                                    GDBusMessage  *message,
+                                                    gpointer       user_data);
+
+typedef void (*GDBusWorkerDisconnectedCallback)    (GDBusWorker   *worker,
+                                                    gboolean       remote_peer_vanished,
+                                                    GError        *error,
+                                                    gpointer       user_data);
+
+/* This function may be called from any thread - callbacks will be in the shared private message thread
+ * and must not block.
+ */
+GDBusWorker *_g_dbus_worker_new          (GIOStream                          *stream,
+                                          GDBusCapabilityFlags                capabilities,
+                                          GDBusWorkerMessageReceivedCallback  message_received_callback,
+                                          GDBusWorkerDisconnectedCallback     disconnected_callback,
+                                          gpointer                            user_data);
+
+/* can be called from any thread - steals blob */
+void         _g_dbus_worker_send_message (GDBusWorker    *worker,
+                                          GDBusMessage   *message,
+                                          gchar          *blob,
+                                          gsize           blob_len);
+
+/* can be called from any thread */
+void         _g_dbus_worker_stop         (GDBusWorker    *worker);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void _g_dbus_initialize (void);
+gboolean _g_dbus_debug_authentication (void);
+gboolean _g_dbus_debug_message (void);
+
+gboolean _g_dbus_address_parse_entry (const gchar  *address_entry,
+                                      gchar       **out_transport_name,
+                                      GHashTable  **out_key_value_pairs,
+                                      GError      **error);
+
+gchar * _g_dbus_compute_complete_signature (GDBusArgInfo **args,
+                                            gboolean       include_parentheses);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PRIVATE_H__ */
diff --git a/gio/gdbusproxy.c b/gio/gdbusproxy.c
new file mode 100644
index 0000000..39094d0
--- /dev/null
+++ b/gio/gdbusproxy.c
@@ -0,0 +1,1542 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "gdbusutils.h"
+#include "gdbusproxy.h"
+#include "gioenumtypes.h"
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gio-marshal.h"
+#include "ginitable.h"
+#include "gasyncinitable.h"
+#include "gioerror.h"
+#include "gasyncresult.h"
+#include "gsimpleasyncresult.h"
+
+/**
+ * SECTION:gdbusproxy
+ * @short_description: Base class for proxies
+ * @include: gdbus/gdbus.h
+ *
+ * #GDBusProxy is a base class used for proxies to access a D-Bus
+ * interface on a remote object. A #GDBusProxy can only be constructed
+ * for unique name bus and does not track whether the name
+ * vanishes. Use g_bus_watch_proxy() to construct #GDBusProxy proxies
+ * for owners of a well-known names.
+ */
+
+struct _GDBusProxyPrivate
+{
+  GDBusConnection *connection;
+  GDBusProxyFlags flags;
+  gchar *unique_bus_name;
+  gchar *object_path;
+  gchar *interface_name;
+  gint timeout_msec;
+
+  /* gchar* -> GVariant* */
+  GHashTable *properties;
+
+  GDBusInterfaceInfo *expected_interface;
+
+  guint properties_changed_subscriber_id;
+  guint signals_subscriber_id;
+};
+
+enum
+{
+  PROP_0,
+  PROP_G_CONNECTION,
+  PROP_G_UNIQUE_BUS_NAME,
+  PROP_G_FLAGS,
+  PROP_G_OBJECT_PATH,
+  PROP_G_INTERFACE_NAME,
+  PROP_G_DEFAULT_TIMEOUT,
+  PROP_G_INTERFACE_INFO
+};
+
+enum
+{
+  PROPERTIES_CHANGED_SIGNAL,
+  SIGNAL_SIGNAL,
+  LAST_SIGNAL,
+};
+
+static void g_dbus_proxy_constructed (GObject *object);
+
+guint signals[LAST_SIGNAL] = {0};
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusProxy, g_dbus_proxy, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                         );
+
+static void
+g_dbus_proxy_finalize (GObject *object)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+  if (proxy->priv->properties_changed_subscriber_id > 0)
+    {
+      g_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+                                            proxy->priv->properties_changed_subscriber_id);
+    }
+
+  if (proxy->priv->signals_subscriber_id > 0)
+    {
+      g_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+                                            proxy->priv->signals_subscriber_id);
+    }
+
+  g_object_unref (proxy->priv->connection);
+  g_free (proxy->priv->unique_bus_name);
+  g_free (proxy->priv->object_path);
+  g_free (proxy->priv->interface_name);
+  if (proxy->priv->properties != NULL)
+    g_hash_table_unref (proxy->priv->properties);
+
+  if (proxy->priv->expected_interface != NULL)
+    g_dbus_interface_info_unref (proxy->priv->expected_interface);
+
+  if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_proxy_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_proxy_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+  switch (prop_id)
+    {
+    case PROP_G_CONNECTION:
+      g_value_set_object (value, proxy->priv->connection);
+      break;
+
+    case PROP_G_FLAGS:
+      g_value_set_flags (value, proxy->priv->flags);
+      break;
+
+    case PROP_G_UNIQUE_BUS_NAME:
+      g_value_set_string (value, proxy->priv->unique_bus_name);
+      break;
+
+    case PROP_G_OBJECT_PATH:
+      g_value_set_string (value, proxy->priv->object_path);
+      break;
+
+    case PROP_G_INTERFACE_NAME:
+      g_value_set_string (value, proxy->priv->interface_name);
+      break;
+
+    case PROP_G_DEFAULT_TIMEOUT:
+      g_value_set_int (value, proxy->priv->timeout_msec);
+      break;
+
+    case PROP_G_INTERFACE_INFO:
+      g_value_set_boxed (value, g_dbus_proxy_get_interface_info (proxy));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_proxy_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (object);
+
+  switch (prop_id)
+    {
+    case PROP_G_CONNECTION:
+      proxy->priv->connection = g_value_dup_object (value);
+      break;
+
+    case PROP_G_FLAGS:
+      proxy->priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_G_UNIQUE_BUS_NAME:
+      proxy->priv->unique_bus_name = g_value_dup_string (value);
+      break;
+
+    case PROP_G_OBJECT_PATH:
+      proxy->priv->object_path = g_value_dup_string (value);
+      break;
+
+    case PROP_G_INTERFACE_NAME:
+      proxy->priv->interface_name = g_value_dup_string (value);
+      break;
+
+    case PROP_G_DEFAULT_TIMEOUT:
+      g_dbus_proxy_set_default_timeout (proxy, g_value_get_int (value));
+      break;
+
+    case PROP_G_INTERFACE_INFO:
+      g_dbus_proxy_set_interface_info (proxy, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_proxy_class_init (GDBusProxyClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_proxy_finalize;
+  gobject_class->set_property = g_dbus_proxy_set_property;
+  gobject_class->get_property = g_dbus_proxy_get_property;
+  gobject_class->constructed  = g_dbus_proxy_constructed;
+
+  /* Note that all property names are prefixed to avoid collisions with D-Bus property names
+   * in derived classes */
+
+  /**
+   * GDBusProxy:g-interface-info:
+   *
+   * Ensure that interactions with this proxy conform to the given
+   * interface.  For example, when completing a method call, if the
+   * type signature of the message isn't what's expected, the given
+   * #GError is set.  Signals that have a type signature mismatch are
+   * simply dropped.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_INTERFACE_INFO,
+                                   g_param_spec_boxed ("g-interface-info",
+                                                       _("Interface Information"),
+                                                       _("Interface Information"),
+                                                       G_TYPE_DBUS_INTERFACE_INFO,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-connection:
+   *
+   * The #GDBusConnection the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_CONNECTION,
+                                   g_param_spec_object ("g-connection",
+                                                        _("g-connection"),
+                                                        _("The connection the proxy is for"),
+                                                        G_TYPE_DBUS_CONNECTION,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-flags:
+   *
+   * Flags from the #GDBusProxyFlags enumeration.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_FLAGS,
+                                   g_param_spec_flags ("g-flags",
+                                                       _("g-flags"),
+                                                       _("Flags for the proxy"),
+                                                       G_TYPE_DBUS_PROXY_FLAGS,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-unique-bus-name:
+   *
+   * The unique bus name the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_UNIQUE_BUS_NAME,
+                                   g_param_spec_string ("g-unique-bus-name",
+                                                        _("g-unique-bus-name"),
+                                                        _("The unique bus name the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-object-path:
+   *
+   * The object path the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_OBJECT_PATH,
+                                   g_param_spec_string ("g-object-path",
+                                                        _("g-object-path"),
+                                                        _("The object path the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-interface-name:
+   *
+   * The D-Bus interface name the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_INTERFACE_NAME,
+                                   g_param_spec_string ("g-interface-name",
+                                                        _("g-interface-name"),
+                                                        _("The D-Bus interface name the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy:g-default-timeout:
+   *
+   * The timeout to use if -1 (specifying default timeout) is passed
+   * as @timeout_msec in the g_dbus_proxy_invoke_method() and
+   * g_dbus_proxy_invoke_method_sync() functions.
+   *
+   * This allows applications to set a proxy-wide timeout for all
+   * remote method invocations on the proxy. If this property is -1,
+   * the default timeout (typically 25 seconds) is used. If set to
+   * %G_MAXINT, then no timeout is used.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_DEFAULT_TIMEOUT,
+                                   g_param_spec_int ("g-default-timeout",
+                                                     _("Default Timeout"),
+                                                     _("Timeout for remote method invocation"),
+                                                     -1,
+                                                     G_MAXINT,
+                                                     -1,
+                                                     G_PARAM_READABLE |
+                                                     G_PARAM_WRITABLE |
+                                                     G_PARAM_CONSTRUCT |
+                                                     G_PARAM_STATIC_NAME |
+                                                     G_PARAM_STATIC_BLURB |
+                                                     G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusProxy::g-properties-changed:
+   * @proxy: The #GDBusProxy emitting the signal.
+   * @changed_properties: A #GHashTable containing the properties that changed.
+   *
+   * Emitted when one or more D-Bus properties on @proxy changes. The cached properties
+   * are already replaced when this signal fires.
+   */
+  signals[PROPERTIES_CHANGED_SIGNAL] = g_signal_new ("g-properties-changed",
+                                                     G_TYPE_DBUS_PROXY,
+                                                     G_SIGNAL_RUN_LAST,
+                                                     G_STRUCT_OFFSET (GDBusProxyClass, g_properties_changed),
+                                                     NULL,
+                                                     NULL,
+                                                     g_cclosure_marshal_VOID__BOXED,
+                                                     G_TYPE_NONE,
+                                                     1,
+                                                     G_TYPE_HASH_TABLE);
+
+  /**
+   * GDBusProxy::g-signal:
+   * @proxy: The #GDBusProxy emitting the signal.
+   * @sender_name: The sender of the signal or %NULL if the connection is not a bus connection.
+   * @signal_name: The name of the signal.
+   * @parameters: A #GVariant tuple with parameters for the signal.
+   *
+   * Emitted when a signal from the remote object and interface that @proxy is for, has been received.
+   **/
+  signals[SIGNAL_SIGNAL] = g_signal_new ("g-signal",
+                                         G_TYPE_DBUS_PROXY,
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (GDBusProxyClass, g_signal),
+                                         NULL,
+                                         NULL,
+                                         _gio_marshal_VOID__STRING_STRING_BOXED,
+                                         G_TYPE_NONE,
+                                         3,
+                                         G_TYPE_STRING,
+                                         G_TYPE_STRING,
+                                         G_TYPE_VARIANT);
+
+
+  g_type_class_add_private (klass, sizeof (GDBusProxyPrivate));
+}
+
+static void
+g_dbus_proxy_init (GDBusProxy *proxy)
+{
+  proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, G_TYPE_DBUS_PROXY, GDBusProxyPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_get_cached_property_names:
+ * @proxy: A #GDBusProxy.
+ * @error: Return location for error or %NULL.
+ *
+ * Gets the names of all cached properties on @proxy.
+ *
+ * Returns: A %NULL-terminated array of strings or %NULL if @error is set. Free with
+ * g_strfreev().
+ */
+gchar **
+g_dbus_proxy_get_cached_property_names (GDBusProxy          *proxy,
+                                        GError             **error)
+{
+  gchar **names;
+  GPtrArray *p;
+  GHashTableIter iter;
+  const gchar *key;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  names = NULL;
+
+  if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("Properties are not available (proxy created with G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+      goto out;
+    }
+
+  p = g_ptr_array_new ();
+
+  g_hash_table_iter_init (&iter, proxy->priv->properties);
+  while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL))
+    {
+      g_ptr_array_add (p, g_strdup (key));
+    }
+  g_ptr_array_sort (p, (GCompareFunc) g_strcmp0);
+  g_ptr_array_add (p, NULL);
+
+  names = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+  return names;
+}
+
+/**
+ * g_dbus_proxy_get_cached_property:
+ * @proxy: A #GDBusProxy.
+ * @property_name: Property name.
+ * @error: Return location for error or %NULL.
+ *
+ * Looks up the value for a property from the cache. This call does no blocking IO.
+ *
+ * Normally you will not need to modify the returned variant since it is updated automatically
+ * in response to <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+ * D-Bus signals (which also causes #GDBusProxy::g-properties-changed to be emitted).
+ *
+ * However, for properties for which said D-Bus signal is not emitted, you
+ * can catch other signals and modify the returned variant accordingly (remember to emit
+ * #GDBusProxy::g-properties-changed yourself).
+ *
+ * Returns: A reference to the #GVariant instance that holds the value for @property_name or
+ * %NULL if @error is set. Free the reference with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_get_cached_property (GDBusProxy          *proxy,
+                                  const gchar         *property_name,
+                                  GError             **error)
+{
+  GVariant *value;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  value = NULL;
+
+  if (proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("Properties are not available (proxy created with G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+      goto out;
+    }
+
+  value = g_hash_table_lookup (proxy->priv->properties, property_name);
+  if (value == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   _("No property with name %s"),
+                   property_name);
+      goto out;
+    }
+
+  g_variant_ref (value);
+
+ out:
+
+  return value;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_signal_received (GDBusConnection  *connection,
+                    const gchar      *sender_name,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *signal_name,
+                    GVariant         *parameters,
+                    gpointer          user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+
+  g_signal_emit (proxy,
+                 signals[SIGNAL_SIGNAL],
+                 0,
+                 sender_name,
+                 signal_name,
+                 parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_properties_changed (GDBusConnection  *connection,
+                       const gchar      *sender_name,
+                       const gchar      *object_path,
+                       const gchar      *interface_name,
+                       const gchar      *signal_name,
+                       GVariant         *parameters,
+                       gpointer          user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (user_data);
+  GError *error;
+  const gchar *interface_name_for_signal;
+  GVariantIter *iter;
+  GVariant *item;
+  GHashTable *changed_properties;
+
+  error = NULL;
+  iter = NULL;
+
+#if 0 // TODO!
+  /* Ignore this signal if properties are not yet available
+   *
+   * (can happen in the window between subscribing to PropertiesChanged() and until
+   *  org.freedesktop.DBus.Properties.GetAll() returns)
+   */
+  if (!proxy->priv->properties_available)
+    goto out;
+#endif
+
+  if (strcmp (g_variant_get_type_string (parameters), "(sa{sv})") != 0)
+    {
+      g_warning ("Value for PropertiesChanged signal with type `%s' does not match `(sa{sv})'",
+                 g_variant_get_type_string (parameters));
+      goto out;
+    }
+
+  g_variant_get (parameters,
+                 "(sa{sv})",
+                 &interface_name_for_signal,
+                 &iter);
+
+  if (g_strcmp0 (interface_name_for_signal, proxy->priv->interface_name) != 0)
+    goto out;
+
+  changed_properties = g_hash_table_new_full (g_str_hash,
+                                              g_str_equal,
+                                              g_free,
+                                              (GDestroyNotify) g_variant_unref);
+
+  while ((item = g_variant_iter_next_value (iter)))
+    {
+      const gchar *key;
+      GVariant *value;
+
+      g_variant_get (item,
+                     "{sv}",
+                     &key,
+                     &value);
+
+      g_hash_table_insert (proxy->priv->properties,
+                           g_strdup (key),
+                           value); /* steals value */
+
+      g_hash_table_insert (changed_properties,
+                           g_strdup (key),
+                           g_variant_ref (value));
+    }
+
+
+  /* emit signal */
+  g_signal_emit (proxy, signals[PROPERTIES_CHANGED_SIGNAL], 0, changed_properties);
+
+  g_hash_table_unref (changed_properties);
+
+ out:
+  if (iter != NULL)
+    g_variant_iter_free (iter);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_dbus_proxy_constructed (GObject *object)
+{
+  if (G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (g_dbus_proxy_parent_class)->constructed (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+subscribe_to_signals (GDBusProxy *proxy)
+{
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* subscribe to PropertiesChanged() */
+      proxy->priv->properties_changed_subscriber_id =
+        g_dbus_connection_signal_subscribe (proxy->priv->connection,
+                                            proxy->priv->unique_bus_name,
+                                            "org.freedesktop.DBus.Properties",
+                                            "PropertiesChanged",
+                                            proxy->priv->object_path,
+                                            proxy->priv->interface_name,
+                                            on_properties_changed,
+                                            proxy,
+                                            NULL);
+    }
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS))
+    {
+      /* subscribe to all signals for the object */
+      proxy->priv->signals_subscriber_id =
+        g_dbus_connection_signal_subscribe (proxy->priv->connection,
+                                            proxy->priv->unique_bus_name,
+                                            proxy->priv->interface_name,
+                                            NULL,                        /* member */
+                                            proxy->priv->object_path,
+                                            NULL,                        /* arg0 */
+                                            on_signal_received,
+                                            proxy,
+                                            NULL);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+process_get_all_reply (GDBusProxy *proxy,
+                       GVariant   *result)
+{
+  GVariantIter iter;
+  GVariant *item;
+
+  if (strcmp (g_variant_get_type_string (result), "(a{sv})") != 0)
+    {
+      g_warning ("Value for GetAll reply with type `%s' does not match `(a{sv})'",
+                 g_variant_get_type_string (result));
+      goto out;
+    }
+
+  proxy->priv->properties = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   g_free,
+                                                   (GDestroyNotify) g_variant_unref);
+
+  g_variant_iter_init (&iter, g_variant_get_child_value (result, 0));
+  while ((item = g_variant_iter_next_value (&iter)) != NULL)
+    {
+      const gchar *key;
+      GVariant *value;
+
+      g_variant_get (item,
+                     "{sv}",
+                     &key,
+                     &value);
+      //g_print ("got %s -> %s\n", key, g_variant_markup_print (value, FALSE, 0, 0));
+
+      g_hash_table_insert (proxy->priv->properties,
+                           g_strdup (key),
+                           value); /* steals value */
+    }
+ out:
+  ;
+}
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (initable);
+  GVariant *result;
+  gboolean ret;
+
+  ret = FALSE;
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* load all properties synchronously */
+      result = g_dbus_connection_invoke_method_sync (proxy->priv->connection,
+                                                     proxy->priv->unique_bus_name,
+                                                     proxy->priv->object_path,
+                                                     "org.freedesktop.DBus.Properties",
+                                                     "GetAll",
+                                                     g_variant_new ("(s)", proxy->priv->interface_name),
+                                                     G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                     -1,           /* timeout */
+                                                     cancellable,
+                                                     error);
+      if (result == NULL)
+        goto out;
+
+      process_get_all_reply (proxy, result);
+
+      g_variant_unref (result);
+    }
+
+  subscribe_to_signals (proxy);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_all_cb (GDBusConnection *connection,
+            GAsyncResult    *res,
+            gpointer         user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  GVariant *result;
+  GError *error;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  if (result == NULL)
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+    }
+  else
+    {
+      g_simple_async_result_set_op_res_gpointer (simple,
+                                                 result,
+                                                 (GDestroyNotify) g_variant_unref);
+    }
+
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+}
+
+static void
+async_initable_init_async (GAsyncInitable     *initable,
+                           gint                io_priority,
+                           GCancellable       *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer            user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (initable);
+  GSimpleAsyncResult *simple;
+
+  simple = g_simple_async_result_new (G_OBJECT (proxy),
+                                      callback,
+                                      user_data,
+                                      NULL);
+
+  if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* load all properties asynchronously */
+      g_dbus_connection_invoke_method (proxy->priv->connection,
+                                       proxy->priv->unique_bus_name,
+                                       proxy->priv->object_path,
+                                       "org.freedesktop.DBus.Properties",
+                                       "GetAll",
+                                       g_variant_new ("(s)", proxy->priv->interface_name),
+                                       G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                       -1,           /* timeout */
+                                       cancellable,
+                                       (GAsyncReadyCallback) get_all_cb,
+                                       simple);
+    }
+  else
+    {
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable  *initable,
+                            GAsyncResult    *res,
+                            GError         **error)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (initable);
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GVariant *result;
+  gboolean ret;
+
+  ret = FALSE;
+
+  result = g_simple_async_result_get_op_res_gpointer (simple);
+  if (result == NULL)
+    {
+      if (!(proxy->priv->flags & G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+        {
+          g_simple_async_result_propagate_error (simple, error);
+          goto out;
+        }
+    }
+  else
+    {
+      process_get_all_reply (proxy, result);
+    }
+
+  subscribe_to_signals (proxy);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+  async_initable_iface->init_async = async_initable_init_async;
+  async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_new:
+ * @connection: A #GDBusConnection.
+ * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: Callback function to invoke when the proxy is ready.
+ * @user_data: User data to pass to @callback.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and asynchronously loads D-Bus properties unless the
+ * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used. Connect to the
+ * #GDBusProxy::g-properties-changed signal to get notified about property changes.
+ *
+ * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #GDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a failable asynchronous constructor - when the proxy is
+ * ready, @callback will be invoked and you can use
+ * g_dbus_proxy_new_finish() to get the result.
+ *
+ * See g_dbus_proxy_new_sync() and for a synchronous version of this constructor.
+ **/
+void
+g_dbus_proxy_new (GDBusConnection     *connection,
+                  GType                object_type,
+                  GDBusProxyFlags      flags,
+                  GDBusInterfaceInfo  *info,
+                  const gchar         *unique_bus_name,
+                  const gchar         *object_path,
+                  const gchar         *interface_name,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY));
+  g_return_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
+                    g_dbus_is_unique_name (unique_bus_name));
+  g_return_if_fail (g_variant_is_object_path (object_path));
+  g_return_if_fail (g_dbus_is_interface_name (interface_name));
+
+  g_async_initable_new_async (object_type,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "g-flags", flags,
+                              "g-interface-info", info,
+                              "g-unique-bus-name", unique_bus_name,
+                              "g-connection", connection,
+                              "g-object-path", object_path,
+                              "g-interface-name", interface_name,
+                              NULL);
+}
+
+/**
+ * g_dbus_proxy_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to g_dbus_proxy_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes creating a #GDBusProxy.
+ *
+ * Returns: A #GDBusProxy or %NULL if @error is set. Free with g_object_unref().
+ **/
+GDBusProxy *
+g_dbus_proxy_new_finish (GAsyncResult  *res,
+                         GError       **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+
+  if (object != NULL)
+    return G_DBUS_PROXY (object);
+  else
+    return NULL;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_new_sync:
+ * @connection: A #GDBusConnection.
+ * @object_type: Either #G_TYPE_DBUS_PROXY or the #GType for the #GDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @info: A #GDBusInterfaceInfo specifying the minimal interface that @proxy conforms to or %NULL.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and synchronously loads D-Bus properties unless the
+ * #G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used.
+ *
+ * If the #G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #GDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a synchronous failable constructor. See g_dbus_proxy_new()
+ * and g_dbus_proxy_new_finish() for the asynchronous version.
+ *
+ * Returns: A #GDBusProxy or %NULL if error is set. Free with g_object_unref().
+ **/
+GDBusProxy *
+g_dbus_proxy_new_sync (GDBusConnection     *connection,
+                       GType                object_type,
+                       GDBusProxyFlags      flags,
+                       GDBusInterfaceInfo  *info,
+                       const gchar         *unique_bus_name,
+                       const gchar         *object_path,
+                       const gchar         *interface_name,
+                       GCancellable        *cancellable,
+                       GError             **error)
+{
+  GInitable *initable;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_DBUS_PROXY), NULL);
+  g_return_val_if_fail ((unique_bus_name == NULL && g_dbus_connection_get_unique_name (connection) == NULL) ||
+                        g_dbus_is_unique_name (unique_bus_name), NULL);
+  g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
+  g_return_val_if_fail (g_dbus_is_interface_name (interface_name), NULL);
+
+  initable = g_initable_new (object_type,
+                             cancellable,
+                             error,
+                             "g-flags", flags,
+                             "g-interface-info", info,
+                             "g-unique-bus-name", unique_bus_name,
+                             "g-connection", connection,
+                             "g-object-path", object_path,
+                             "g-interface-name", interface_name,
+                             NULL);
+  if (initable != NULL)
+    return G_DBUS_PROXY (initable);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_dbus_proxy_get_connection:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the connection @proxy is for.
+ *
+ * Returns: A #GDBusConnection owned by @proxy. Do not free.
+ **/
+GDBusConnection *
+g_dbus_proxy_get_connection (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->connection;
+}
+
+/**
+ * g_dbus_proxy_get_flags:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the flags that @proxy was constructed with.
+ *
+ * Returns: Flags from the #GDBusProxyFlags enumeration.
+ **/
+GDBusProxyFlags
+g_dbus_proxy_get_flags (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), 0);
+  return proxy->priv->flags;
+}
+
+/**
+ * g_dbus_proxy_get_unique_bus_name:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the unique bus name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_unique_bus_name (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->unique_bus_name;
+}
+
+/**
+ * g_dbus_proxy_get_object_path:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the object path @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_object_path (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->object_path;
+}
+
+/**
+ * g_dbus_proxy_get_interface_name:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the D-Bus interface name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+g_dbus_proxy_get_interface_name (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->interface_name;
+}
+
+/**
+ * g_dbus_proxy_get_default_timeout:
+ * @proxy: A #GDBusProxy.
+ *
+ * Gets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the g_dbus_proxy_invoke_method() and
+ * g_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #GDBusProxy:g-default-timeout property for more details.
+ *
+ * Returns: Timeout to use for @proxy.
+ */
+gint
+g_dbus_proxy_get_default_timeout (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), -1);
+  return proxy->priv->timeout_msec;
+}
+
+/**
+ * g_dbus_proxy_set_default_timeout:
+ * @proxy: A #GDBusProxy.
+ * @timeout_msec: Timeout in milliseconds.
+ *
+ * Sets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the g_dbus_proxy_invoke_method() and
+ * g_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #GDBusProxy:g-default-timeout property for more details.
+ */
+void
+g_dbus_proxy_set_default_timeout (GDBusProxy *proxy,
+                                  gint        timeout_msec)
+{
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0);
+
+  /* TODO: locking? */
+  if (proxy->priv->timeout_msec != timeout_msec)
+    {
+      proxy->priv->timeout_msec = timeout_msec;
+      g_object_notify (G_OBJECT (proxy), "g-default-timeout");
+    }
+}
+
+/**
+ * g_dbus_proxy_get_interface_info:
+ * @proxy: A #GDBusProxy
+ *
+ * Returns the #GDBusInterfaceInfo, if any, specifying the minimal
+ * interface that @proxy conforms to.
+ *
+ * See the #GDBusProxy:g-interface-info property for more details.
+ *
+ * Returns: A #GDBusInterfaceInfo or %NULL. Do not unref the returned
+ * object, it is owned by @proxy.
+ */
+GDBusInterfaceInfo *
+g_dbus_proxy_get_interface_info (GDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->expected_interface;
+}
+
+/**
+ * g_dbus_proxy_set_interface_info:
+ * @proxy: A #GDBusProxy
+ * @info: Minimum interface this proxy conforms to or %NULL to unset.
+ *
+ * Ensure that interactions with @proxy conform to the given
+ * interface.  For example, when completing a method call, if the type
+ * signature of the message isn't what's expected, the given #GError
+ * is set.  Signals that have a type signature mismatch are simply
+ * dropped.
+ *
+ * See the #GDBusProxy:g-interface-info property for more details.
+ */
+void
+g_dbus_proxy_set_interface_info (GDBusProxy         *proxy,
+                                 GDBusInterfaceInfo *info)
+{
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  if (proxy->priv->expected_interface != NULL)
+    g_dbus_interface_info_unref (proxy->priv->expected_interface);
+  proxy->priv->expected_interface = info != NULL ? g_dbus_interface_info_ref (info) : NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+maybe_split_method_name (const gchar   *method_name,
+                         gchar        **out_interface_name,
+                         const gchar  **out_method_name)
+{
+  gboolean was_split;
+
+  was_split = FALSE;
+  g_assert (out_interface_name != NULL);
+  g_assert (out_method_name != NULL);
+  *out_interface_name = NULL;
+  *out_method_name = NULL;
+
+  if (strchr (method_name, '.') != NULL)
+    {
+      gchar *p;
+      gchar *last_dot;
+
+      p = g_strdup (method_name);
+      last_dot = strrchr (p, '.');
+      *last_dot = '\0';
+
+      *out_interface_name = p;
+      *out_method_name = last_dot + 1;
+
+      was_split = TRUE;
+    }
+
+  return was_split;
+}
+
+
+static void
+reply_cb (GDBusConnection *connection,
+          GAsyncResult    *res,
+          gpointer         user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  GVariant *value;
+  GError *error;
+
+  error = NULL;
+  value = g_dbus_connection_invoke_method_finish (connection,
+                                                  res,
+                                                  &error);
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (simple,
+                                            error);
+      g_error_free (error);
+    }
+  else
+    {
+      g_simple_async_result_set_op_res_gpointer (simple,
+                                                 value,
+                                                 (GDestroyNotify) g_variant_unref);
+    }
+
+  /* no need to complete in idle since the method GDBusConnection already does */
+  g_simple_async_result_complete (simple);
+}
+
+static const GDBusMethodInfo *
+lookup_method_info_or_warn (GDBusProxy     *proxy,
+                            const char     *method_name)
+{
+  const GDBusMethodInfo *info;
+
+  if (!proxy->priv->expected_interface)
+    return NULL;
+
+  info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name);
+  if (!info)
+    g_warning ("Trying to invoke method %s which isn't in expected interface %s",
+               method_name, proxy->priv->expected_interface->name);
+
+  return info;
+}
+
+static gboolean
+validate_method_return (const char             *method_name,
+                        GVariant               *value,
+                        const GDBusMethodInfo  *expected_method_info,
+                        GError                **error)
+{
+  const gchar *type_string;
+  gchar *signature;
+  gboolean ret;
+
+  ret = TRUE;
+  signature = NULL;
+
+  if (value == NULL || expected_method_info == NULL)
+    goto out;
+
+  /* Shouldn't happen... */
+  if (g_variant_classify (value) != G_VARIANT_CLASS_TUPLE)
+    goto out;
+
+  type_string = g_variant_get_type_string (value);
+  signature = _g_dbus_compute_complete_signature (expected_method_info->out_args, TRUE);
+  if (g_strcmp0 (type_string, signature) != 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("Method `%s' returned signature `%s', but expected `%s'"),
+                   method_name,
+                   type_string,
+                   signature);
+      ret = FALSE;
+    }
+
+ out:
+  g_free (signature);
+  return ret;
+}
+
+/**
+ * g_dbus_proxy_invoke_method:
+ * @proxy: A #GDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #GDBusConnection associated with @proxy is closed then
+ * the operation will fail with %G_IO_ERROR_CLOSED. If
+ * @cancellable is canceled, the operation will fail with
+ * %G_IO_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * g_dbus_proxy_invoke_method_finish() to get the result of the operation.
+ * See g_dbus_proxy_invoke_method_sync() for the
+ * synchronous version of this method.
+ */
+void
+g_dbus_proxy_invoke_method (GDBusProxy          *proxy,
+                            const gchar         *method_name,
+                            GVariant            *parameters,
+                            GDBusInvokeMethodFlags flags,
+                            gint                 timeout_msec,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  GSimpleAsyncResult *simple;
+  gboolean was_split;
+  gchar *split_interface_name;
+  const gchar *split_method_name;
+  const GDBusMethodInfo *expected_method_info;
+  const gchar *target_method_name;
+  const gchar *target_interface_name;
+
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_return_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name));
+  g_return_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE));
+  g_return_if_fail (timeout_msec == -1 || timeout_msec >= 0);
+
+  simple = g_simple_async_result_new (G_OBJECT (proxy),
+                                      callback,
+                                      user_data,
+                                      g_dbus_proxy_invoke_method);
+
+  was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+  target_method_name = was_split ? split_method_name : method_name;
+  target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name;
+
+  g_object_set_data_full (G_OBJECT (simple), "-gdbus-proxy-method-name", g_strdup (target_method_name), g_free);
+
+  /* Just warn here */
+  expected_method_info = lookup_method_info_or_warn (proxy, target_method_name);
+
+  g_dbus_connection_invoke_method (proxy->priv->connection,
+                                   proxy->priv->unique_bus_name,
+                                   proxy->priv->object_path,
+                                   target_interface_name,
+                                   target_method_name,
+                                   parameters,
+                                   flags,
+                                   timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+                                   cancellable,
+                                   (GAsyncReadyCallback) reply_cb,
+                                   simple);
+
+  g_free (split_interface_name);
+}
+
+/**
+ * g_dbus_proxy_invoke_method_finish:
+ * @proxy: A #GDBusProxy.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_proxy_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with g_dbus_proxy_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_invoke_method_finish (GDBusProxy    *proxy,
+                                   GAsyncResult  *res,
+                                   GError       **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  GVariant *value;
+  const char *method_name;
+  const GDBusMethodInfo *expected_method_info;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_dbus_proxy_invoke_method);
+
+  value = NULL;
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  value = g_simple_async_result_get_op_res_gpointer (simple);
+  method_name = g_object_get_data (G_OBJECT (simple), "-gdbus-proxy-method-name");
+
+  /* We may not have a method name for internally-generated proxy calls like GetAll */
+  if (value && method_name && proxy->priv->expected_interface)
+    {
+      expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, method_name);
+      if (!validate_method_return (method_name, value, expected_method_info, error))
+        {
+          g_variant_unref (value);
+          value = NULL;
+        }
+    }
+
+ out:
+  return value;
+}
+
+/**
+ * g_dbus_proxy_invoke_method_sync:
+ * @proxy: A #GDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #GVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @flags: Flags from the #GDBusInvokeMethodFlags enumeration.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #GDBusConnection associated with @proxy is disconnected then
+ * the operation will fail with %G_IO_ERROR_CLOSED. If
+ * @cancellable is canceled, the operation will fail with
+ * %G_IO_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %G_IO_ERROR_INVALID_ARGUMENT.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * g_dbus_proxy_invoke_method() for the asynchronous version of this
+ * method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
+ * return values. Free with g_variant_unref().
+ */
+GVariant *
+g_dbus_proxy_invoke_method_sync (GDBusProxy     *proxy,
+                                 const gchar    *method_name,
+                                 GVariant       *parameters,
+                                 GDBusInvokeMethodFlags flags,
+                                 gint            timeout_msec,
+                                 GCancellable   *cancellable,
+                                 GError        **error)
+{
+  GVariant *ret;
+  gboolean was_split;
+  gchar *split_interface_name;
+  const gchar *split_method_name;
+  const GDBusMethodInfo *expected_method_info;
+  const char *target_method_name;
+  const char *target_interface_name;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (g_dbus_is_member_name (method_name) || g_dbus_is_interface_name (method_name), NULL);
+  g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL);
+  g_return_val_if_fail (timeout_msec == -1 || timeout_msec >= 0, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+  target_method_name = was_split ? split_method_name : method_name;
+  target_interface_name = was_split ? split_interface_name : proxy->priv->interface_name;
+
+  if (proxy->priv->expected_interface)
+    {
+      expected_method_info = g_dbus_interface_info_lookup_method (proxy->priv->expected_interface, target_method_name);
+      if (!expected_method_info)
+        g_warning ("Trying to invoke method %s which isn't in expected interface %s",
+                   target_method_name, target_interface_name);
+    }
+  else
+    {
+      expected_method_info = NULL;
+    }
+
+  ret = g_dbus_connection_invoke_method_sync (proxy->priv->connection,
+                                              proxy->priv->unique_bus_name,
+                                              proxy->priv->object_path,
+                                              target_interface_name,
+                                              target_method_name,
+                                              parameters,
+                                              flags,
+                                              timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+                                              cancellable,
+                                              error);
+  if (!validate_method_return (target_method_name, ret, expected_method_info, error))
+    {
+      g_variant_unref (ret);
+      ret = NULL;
+    }
+
+  g_free (split_interface_name);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusproxy.h b/gio/gdbusproxy.h
new file mode 100644
index 0000000..1bc9902
--- /dev/null
+++ b/gio/gdbusproxy.h
@@ -0,0 +1,146 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_PROXY_H__
+#define __G_DBUS_PROXY_H__
+
+#include <gio/giotypes.h>
+#include <gio/gdbusintrospection.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_PROXY         (g_dbus_proxy_get_type ())
+#define G_DBUS_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_PROXY, GDBusProxy))
+#define G_DBUS_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_PROXY, GDBusProxyClass))
+#define G_DBUS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_PROXY, GDBusProxyClass))
+#define G_IS_DBUS_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_PROXY))
+#define G_IS_DBUS_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_PROXY))
+
+typedef struct _GDBusProxyClass   GDBusProxyClass;
+typedef struct _GDBusProxyPrivate GDBusProxyPrivate;
+
+/**
+ * GDBusProxy:
+ *
+ * The #GDBusProxy structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusProxy
+{
+  /*< private >*/
+  GObject parent_instance;
+  GDBusProxyPrivate *priv;
+};
+
+/**
+ * GDBusProxyClass:
+ * @g_properties_changed: Signal class handler for the #GDBusProxy::g-properties-changed signal.
+ * @g_signal: Signal class handler for the #GDBusProxy::g-signal signal.
+ *
+ * Class structure for #GDBusProxy.
+ */
+struct _GDBusProxyClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  /* Signals */
+  void (*g_properties_changed) (GDBusProxy   *proxy,
+                                GHashTable   *changed_properties);
+  void (*g_signal)             (GDBusProxy   *proxy,
+                                const gchar  *sender_name,
+                                const gchar  *signal_name,
+                                GVariant     *parameters);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType            g_dbus_proxy_get_type                  (void) G_GNUC_CONST;
+void             g_dbus_proxy_new                       (GDBusConnection     *connection,
+                                                         GType                object_type,
+                                                         GDBusProxyFlags      flags,
+                                                         GDBusInterfaceInfo *info,
+                                                         const gchar         *unique_bus_name,
+                                                         const gchar         *object_path,
+                                                         const gchar         *interface_name,
+                                                         GCancellable        *cancellable,
+                                                         GAsyncReadyCallback  callback,
+                                                         gpointer             user_data);
+GDBusProxy      *g_dbus_proxy_new_finish                (GAsyncResult        *res,
+                                                         GError             **error);
+GDBusProxy      *g_dbus_proxy_new_sync                  (GDBusConnection     *connection,
+                                                         GType                object_type,
+                                                         GDBusProxyFlags      flags,
+                                                         GDBusInterfaceInfo *info,
+                                                         const gchar         *unique_bus_name,
+                                                         const gchar         *object_path,
+                                                         const gchar         *interface_name,
+                                                         GCancellable        *cancellable,
+                                                         GError             **error);
+GDBusConnection *g_dbus_proxy_get_connection            (GDBusProxy          *proxy);
+GDBusProxyFlags  g_dbus_proxy_get_flags                 (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_unique_bus_name       (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_object_path           (GDBusProxy          *proxy);
+const gchar     *g_dbus_proxy_get_interface_name        (GDBusProxy          *proxy);
+gint             g_dbus_proxy_get_default_timeout       (GDBusProxy          *proxy);
+void             g_dbus_proxy_set_default_timeout       (GDBusProxy          *proxy,
+                                                         gint                 timeout_msec);
+GDBusInterfaceInfo *g_dbus_proxy_get_interface_info     (GDBusProxy          *proxy);
+void             g_dbus_proxy_set_interface_info        (GDBusProxy           *proxy,
+                                                         GDBusInterfaceInfo   *info);
+GVariant        *g_dbus_proxy_get_cached_property       (GDBusProxy          *proxy,
+                                                         const gchar         *property_name,
+                                                         GError             **error);
+gchar          **g_dbus_proxy_get_cached_property_names (GDBusProxy          *proxy,
+                                                         GError             **error);
+void             g_dbus_proxy_invoke_method             (GDBusProxy          *proxy,
+                                                         const gchar         *method_name,
+                                                         GVariant            *parameters,
+                                                         GDBusInvokeMethodFlags flags,
+                                                         gint                 timeout_msec,
+                                                         GCancellable        *cancellable,
+                                                         GAsyncReadyCallback  callback,
+                                                         gpointer             user_data);
+GVariant        *g_dbus_proxy_invoke_method_finish      (GDBusProxy          *proxy,
+                                                         GAsyncResult        *res,
+                                                         GError             **error);
+GVariant        *g_dbus_proxy_invoke_method_sync        (GDBusProxy          *proxy,
+                                                         const gchar         *method_name,
+                                                         GVariant            *parameters,
+                                                         GDBusInvokeMethodFlags flags,
+                                                         gint                 timeout_msec,
+                                                         GCancellable        *cancellable,
+                                                         GError             **error);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_H__ */
diff --git a/gio/gdbusproxywatching.c b/gio/gdbusproxywatching.c
new file mode 100644
index 0000000..4f85c1d
--- /dev/null
+++ b/gio/gdbusproxywatching.c
@@ -0,0 +1,397 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+#include "gdbusnamewatching.h"
+#include "gdbusproxywatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusproxy.h"
+#include "gdbusnamewatching.h"
+#include "gcancellable.h"
+
+/**
+ * SECTION:gdbusproxywatching
+ * @title: Watching Proxies
+ * @short_description: Simple API for watching proxies
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus proxies.
+ *
+ * <example id="gdbus-watching-proxy"><title>Simple application watching a proxy</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-watch-proxy.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  guint                      id;
+  GBusProxyAppearedCallback  proxy_appeared_handler;
+  GBusProxyVanishedCallback  proxy_vanished_handler;
+  gpointer                   user_data;
+  GDestroyNotify             user_data_free_func;
+  GMainContext              *main_context;
+
+  gchar                     *name;
+  gchar                     *name_owner;
+  GDBusConnection           *connection;
+  guint                      name_watcher_id;
+
+  GCancellable              *cancellable;
+
+  gchar                     *object_path;
+  gchar                     *interface_name;
+  GType                      interface_type;
+  GDBusProxyFlags            proxy_flags;
+  GDBusProxy                *proxy;
+
+  gboolean initial_construction;
+} Client;
+
+static void
+client_unref (Client *client)
+{
+  /* ensure we're only called from g_bus_unwatch_proxy */
+  g_assert (client->name_watcher_id == 0);
+
+  g_free (client->name_owner);
+  if (client->connection != NULL)
+    g_object_unref (client->connection);
+  if (client->proxy != NULL)
+    g_object_unref (client->proxy);
+
+  g_free (client->name);
+  g_free (client->object_path);
+  g_free (client->interface_name);
+
+  if (client->main_context != NULL)
+    g_main_context_unref (client->main_context);
+
+  if (client->user_data_free_func != NULL)
+    client->user_data_free_func (client->user_data);
+  g_free (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+proxy_constructed_cb (GObject       *source_object,
+                      GAsyncResult  *res,
+                      gpointer       user_data)
+{
+  Client *client = user_data;
+  GDBusProxy *proxy;
+  GError *error;
+
+  error = NULL;
+  proxy = g_dbus_proxy_new_finish (res, &error);
+  if (proxy == NULL)
+    {
+      /* g_warning ("error while constructing proxy: %s", error->message); */
+      g_error_free (error);
+
+      /* handle initial construction, send out vanished if the name
+       * is there but we constructing a proxy fails
+       */
+      if (client->initial_construction)
+        {
+          if (client->proxy_vanished_handler != NULL)
+            {
+              client->proxy_vanished_handler (client->connection,
+                                              client->name,
+                                              client->user_data);
+            }
+          client->initial_construction = FALSE;
+        }
+    }
+  else
+    {
+      g_assert (client->proxy == NULL);
+      g_assert (client->cancellable != NULL);
+      client->proxy = G_DBUS_PROXY (proxy);
+
+      g_object_unref (client->cancellable);
+      client->cancellable = NULL;
+
+      /* perform callback */
+      if (client->proxy_appeared_handler != NULL)
+        {
+          client->proxy_appeared_handler (client->connection,
+                                          client->name,
+                                          client->name_owner,
+                                          client->proxy,
+                                          client->user_data);
+        }
+      client->initial_construction = FALSE;
+    }
+}
+
+static void
+on_name_appeared (GDBusConnection *connection,
+                  const gchar *name,
+                  const gchar *name_owner,
+                  gpointer user_data)
+{
+  Client *client = user_data;
+
+  //g_debug ("\n\nname appeared (owner `%s')", name_owner);
+
+  /* invariants */
+  g_assert (client->name_owner == NULL);
+  g_assert (client->connection == NULL);
+  g_assert (client->cancellable == NULL);
+
+  client->name_owner = g_strdup (name_owner);
+  client->connection = g_object_ref (connection);
+  client->cancellable = g_cancellable_new ();
+
+  g_dbus_proxy_new (client->connection,
+                    client->interface_type,
+                    client->proxy_flags,
+                    NULL, /* GDBusInterfaceInfo */
+                    client->name_owner,
+                    client->object_path,
+                    client->interface_name,
+                    client->cancellable,
+                    proxy_constructed_cb,
+                    client);
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+                  const gchar *name,
+                  gpointer user_data)
+{
+  Client *client = user_data;
+
+  /*g_debug ("\n\nname vanished");*/
+
+  g_free (client->name_owner);
+  if (client->connection != NULL)
+    g_object_unref (client->connection);
+  client->name_owner = NULL;
+  client->connection = NULL;
+
+  /* free the proxy if we have it */
+  if (client->proxy != NULL)
+    {
+      g_assert (client->cancellable == NULL);
+
+      g_object_unref (client->proxy);
+      client->proxy = NULL;
+
+      /* if we have the proxy, it means we last sent out a 'appeared'
+       * callback - so send out a 'vanished' callback
+       */
+      if (client->proxy_vanished_handler != NULL)
+        {
+          client->proxy_vanished_handler (client->connection,
+                                          client->name,
+                                          client->user_data);
+        }
+      client->initial_construction = FALSE;
+    }
+  else
+    {
+      /* otherwise cancel construction of the proxy if applicable */
+      if (client->cancellable != NULL)
+        {
+          g_cancellable_cancel (client->cancellable);
+          g_object_unref (client->cancellable);
+          client->cancellable = NULL;
+        }
+      else
+        {
+          /* handle initial construction, send out vanished if
+           * the name isn't there
+           */
+          if (client->initial_construction)
+            {
+              if (client->proxy_vanished_handler != NULL)
+                {
+                  client->proxy_vanished_handler (client->connection,
+                                                  client->name,
+                                                  client->user_data);
+                }
+              client->initial_construction = FALSE;
+            }
+        }
+    }
+}
+
+/**
+ * g_bus_watch_proxy:
+ * @bus_type: The type of bus to watch a name on (can't be #G_BUS_TYPE_NONE).
+ * @name: The name (well-known or unique) to watch.
+ * @flags: Flags from the #GBusNameWatcherFlags enumeration.
+ * @object_path: The object path of the remote object to watch.
+ * @interface_name: The D-Bus interface name for the proxy.
+ * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type.
+ * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy.
+ * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the
+ * requested proxy is available.
+ * @proxy_vanished_handler: Handler to invoke when @name is known to not exist
+ * and the previously created proxy is no longer available.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching a remote object at @object_path owned by @name on
+ * the bus specified by @bus_type. When the object is available, a
+ * #GDBusProxy (or derived class cf. @interface_type) instance is
+ * constructed for the @interface_name D-Bus interface and then
+ * @proxy_appeared_handler will be called when the proxy is ready and
+ * all properties have been loaded. When @name vanishes,
+ * @proxy_vanished_handler is called.
+ *
+ * This function makes it very simple to write applications that wants
+ * to watch a well-known remote object on a well-known name, see <xref
+ * linkend="gdbus-watching-proxy"/>. Basically, the application simply
+ * starts using the proxy when @proxy_appeared_handler is called and
+ * stops using it when @proxy_vanished_handler is called. Callbacks
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * Applications typically use this function to watch the
+ * <quote>manager</quote> object of a well-known name. Upon acquiring
+ * a proxy for the manager object, applications typically construct
+ * additional proxies in response to the result of enumeration methods
+ * on the manager object.
+ *
+ * Many of the comment that applies to g_bus_watch_name() also applies
+ * here. For example, you are guaranteed that one of the handlers will
+ * be invoked (on the main thread) after calling this function and
+ * also that the two handlers alternate. When you are done watching the
+ * proxy, just call g_bus_unwatch_proxy().
+ *
+ * Returns: An identifier (never 0) that can be used with
+ * g_bus_unwatch_proxy() to stop watching the remote object.
+ **/
+guint
+g_bus_watch_proxy (GBusType                   bus_type,
+                   const gchar               *name,
+                   GBusNameWatcherFlags       flags,
+                   const gchar               *object_path,
+                   const gchar               *interface_name,
+                   GType                      interface_type,
+                   GDBusProxyFlags            proxy_flags,
+                   GBusProxyAppearedCallback  proxy_appeared_handler,
+                   GBusProxyVanishedCallback  proxy_vanished_handler,
+                   gpointer                   user_data,
+                   GDestroyNotify             user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (g_dbus_is_name (name), 0);
+  g_return_val_if_fail (g_variant_is_object_path (object_path), 0);
+  g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0);
+  g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->proxy_appeared_handler = proxy_appeared_handler;
+  client->proxy_vanished_handler = proxy_vanished_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+  client->name_watcher_id = g_bus_watch_name (bus_type,
+                                              name,
+                                              flags,
+                                              on_name_appeared,
+                                              on_name_vanished,
+                                              client,
+                                              NULL);
+
+  client->object_path = g_strdup (object_path);
+  client->interface_name = g_strdup (interface_name);
+  client->interface_type = interface_type;
+  client->proxy_flags = proxy_flags;
+  client->initial_construction = TRUE;
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * g_bus_unwatch_proxy:
+ * @watcher_id: An identifier obtained from g_bus_watch_proxy()
+ *
+ * Stops watching proxy.
+ */
+void
+g_bus_unwatch_proxy (guint watcher_id)
+{
+  Client *client;
+
+  g_return_if_fail (watcher_id > 0);
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (watcher_id == 0 ||
+      map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to g_bus_unwatch_proxy()", watcher_id);
+      goto out;
+    }
+
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  if (client != NULL)
+    {
+      g_bus_unwatch_name (client->name_watcher_id);
+      client->name_watcher_id = 0;
+      client_unref (client);
+    }
+}
diff --git a/gio/gdbusproxywatching.h b/gio/gdbusproxywatching.h
new file mode 100644
index 0000000..3544269
--- /dev/null
+++ b/gio/gdbusproxywatching.h
@@ -0,0 +1,77 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_PROXY_WATCHING_H__
+#define __G_DBUS_PROXY_WATCHING_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusProxyAppearedCallback:
+ * @connection: The #GDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @proxy: A #GDBusProxy (or derived) instance with all properties loaded.
+ * @user_data: User data passed to g_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched is ready for use - the passed
+ * @proxy object is valid until the #GBusProxyVanishedCallback
+ * callback is invoked.
+ */
+typedef void (*GBusProxyAppearedCallback) (GDBusConnection *connection,
+                                           const gchar     *name,
+                                           const gchar     *name_owner,
+                                           GDBusProxy      *proxy,
+                                           gpointer         user_data);
+
+/**
+ * GBusProxyVanishedCallback:
+ * @connection: The #GDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to g_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched has vanished. The #GDBusProxy
+ * object passed in the #GBusProxyAppearedCallback callback is no
+ * longer valid.
+ */
+typedef void (*GBusProxyVanishedCallback) (GDBusConnection *connection,
+                                           const gchar     *name,
+                                           gpointer         user_data);
+
+guint g_bus_watch_proxy   (GBusType                   bus_type,
+                           const gchar               *name,
+                           GBusNameWatcherFlags       flags,
+                           const gchar               *object_path,
+                           const gchar               *interface_name,
+                           GType                      interface_type,
+                           GDBusProxyFlags            proxy_flags,
+                           GBusProxyAppearedCallback  proxy_appeared_handler,
+                           GBusProxyVanishedCallback  proxy_vanished_handler,
+                           gpointer                   user_data,
+                           GDestroyNotify             user_data_free_func);
+void  g_bus_unwatch_proxy (guint                      watcher_id);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_PROXY_WATCHING_H__ */
diff --git a/gio/gdbusserver.c b/gio/gdbusserver.c
new file mode 100644
index 0000000..0d0deea
--- /dev/null
+++ b/gio/gdbusserver.c
@@ -0,0 +1,1043 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+#include "giotypes.h"
+#include "gdbusaddress.h"
+#include "gdbusutils.h"
+#include "gdbusconnection.h"
+#include "gdbusserver.h"
+#include "gioenumtypes.h"
+#include "gdbusprivate.h"
+#include "gdbusauthobserver.h"
+#include "gio-marshal.h"
+
+/**
+ * SECTION:gdbusserver
+ * @short_description: Helper for accepting connections
+ * @include: gdbus/gdbus.h
+ *
+ * #GDBusServer is a helper for listening to and accepting D-Bus
+ * connections.
+ *
+ * <example id="gdbus-peer-to-peer"><title>D-Bus peer-to-peer example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../../gio/tests/gdbus-example-peer.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+struct _GDBusServerPrivate
+{
+  GDBusServerFlags flags;
+  gchar *address;
+  gchar *guid;
+
+  guchar *nonce;
+  gchar *nonce_file;
+
+  gchar *client_address;
+
+  GSocketListener *listener;
+  gboolean is_using_listener;
+
+  /* The result of g_main_context_get_thread_default() when the object
+   * was created (the GObject _init() function) - this is used for delivery
+   * of the :new-connection GObject signal.
+   */
+  GMainContext *main_context_at_construction;
+
+  gboolean active;
+
+  GDBusAuthObserver *authentication_observer;
+};
+
+enum
+{
+  PROP_0,
+  PROP_ADDRESS,
+  PROP_CLIENT_ADDRESS,
+  PROP_FLAGS,
+  PROP_GUID,
+  PROP_ACTIVE,
+  PROP_AUTHENTICATION_OBSERVER,
+};
+
+enum
+{
+  NEW_CONNECTION_SIGNAL,
+  LAST_SIGNAL,
+};
+
+guint _signals[LAST_SIGNAL] = {0};
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (GDBusServer, g_dbus_server, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         );
+
+static void
+g_dbus_server_finalize (GObject *object)
+{
+  GDBusServer *server = G_DBUS_SERVER (object);
+
+  if (server->priv->authentication_observer != NULL)
+    g_object_unref (server->priv->authentication_observer);
+
+  if (server->priv->listener != NULL)
+    g_object_unref (server->priv->listener);
+
+  g_free (server->priv->address);
+  g_free (server->priv->guid);
+  g_free (server->priv->client_address);
+  if (server->priv->nonce != NULL)
+    {
+      memset (server->priv->nonce, '\0', 16);
+      g_free (server->priv->nonce);
+    }
+  /* we could unlink the nonce file but I don't
+   * think it's really worth the effort/risk
+   */
+  g_free (server->priv->nonce_file);
+
+  if (server->priv->main_context_at_construction != NULL)
+    g_main_context_unref (server->priv->main_context_at_construction);
+
+  if (G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize (object);
+}
+
+static void
+g_dbus_server_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GDBusServer *server = G_DBUS_SERVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      g_value_set_flags (value, server->priv->flags);
+      break;
+
+    case PROP_GUID:
+      g_value_set_string (value, server->priv->guid);
+      break;
+
+    case PROP_ADDRESS:
+      g_value_set_string (value, server->priv->address);
+      break;
+
+    case PROP_CLIENT_ADDRESS:
+      g_value_set_string (value, server->priv->client_address);
+      break;
+
+    case PROP_ACTIVE:
+      g_value_set_boolean (value, server->priv->active);
+      break;
+
+    case PROP_AUTHENTICATION_OBSERVER:
+      g_value_set_object (value, server->priv->authentication_observer);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_server_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GDBusServer *server = G_DBUS_SERVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      server->priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_GUID:
+      server->priv->guid = g_value_dup_string (value);
+      break;
+
+    case PROP_ADDRESS:
+      server->priv->address = g_value_dup_string (value);
+      break;
+
+    case PROP_AUTHENTICATION_OBSERVER:
+      server->priv->authentication_observer = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_dbus_server_class_init (GDBusServerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_dbus_server_finalize;
+  gobject_class->set_property = g_dbus_server_set_property;
+  gobject_class->get_property = g_dbus_server_get_property;
+
+  /**
+   * GDBusServer:flags:
+   *
+   * Flags from the #GDBusServerFlags enumeration.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_FLAGS,
+                                   g_param_spec_flags ("flags",
+                                                       _("Flags"),
+                                                       _("Flags for the server"),
+                                                       G_TYPE_DBUS_SERVER_FLAGS,
+                                                       G_DBUS_SERVER_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer:guid:
+   *
+   * The guid of the server.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_GUID,
+                                   g_param_spec_string ("guid",
+                                                        _("GUID"),
+                                                        _("The guid of the server"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer:address:
+   *
+   * The D-Bus address to listen on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_ADDRESS,
+                                   g_param_spec_string ("address",
+                                                        _("Address"),
+                                                        _("The address to listen on"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer:client-address:
+   *
+   * The D-Bus address that clients can use.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CLIENT_ADDRESS,
+                                   g_param_spec_string ("client-address",
+                                                        _("Client Address"),
+                                                        _("The address clients can use"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer:active:
+   *
+   * Whether the server is currently active.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_ACTIVE,
+                                   g_param_spec_string ("active",
+                                                        _("Active"),
+                                                        _("Whether the server is currently active"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer:authentication-observer:
+   *
+   * A #GDBusAuthObserver object to assist in the authentication process or %NULL.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_AUTHENTICATION_OBSERVER,
+                                   g_param_spec_object ("authentication-observer",
+                                                        _("Authentication Observer"),
+                                                        _("Object used to assist in the authentication process"),
+                                                        G_TYPE_DBUS_AUTH_OBSERVER,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * GDBusServer::new-connection:
+   * @server: The #GDBusServer emitting the signal.
+   * @connection: A #GDBusConnection for the new connection.
+   *
+   * Emitted when a new authenticated connection has been made. Use
+   * g_dbus_connection_get_peer_credentials() to figure out what
+   * identity (if any), was authenticated.
+   *
+   * If you want to accept the connection, simply ref the @connection
+   * object. Then call g_dbus_connection_close() and unref it when you
+   * are done with it. A typical thing to do when accepting a
+   * connection is to listen to the #GDBusConnection::closed signal.
+   *
+   * If #GDBusServer:flags contains %G_DBUS_SERVER_FLAGS_RUN_IN_THREAD
+   * then the signal is emitted in a new thread dedicated to the
+   * connection. Otherwise the signal is emitted in the <link
+   * linkend="g-main-context-push-thread-default">thread-default main
+   * loop</link> of the thread that @server was constructed in.
+   */
+  _signals[NEW_CONNECTION_SIGNAL] = g_signal_new ("new-connection",
+                                                  G_TYPE_DBUS_SERVER,
+                                                  G_SIGNAL_RUN_LAST,
+                                                  G_STRUCT_OFFSET (GDBusServerClass, new_connection),
+                                                  NULL,
+                                                  NULL,
+                                                  g_cclosure_marshal_VOID__OBJECT,
+                                                  G_TYPE_NONE,
+                                                  1,
+                                                  G_TYPE_DBUS_CONNECTION);
+
+
+  g_type_class_add_private (klass, sizeof (GDBusServerPrivate));
+}
+
+static void
+g_dbus_server_init (GDBusServer *server)
+{
+  server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, G_TYPE_DBUS_SERVER, GDBusServerPrivate);
+
+  server->priv->main_context_at_construction = g_main_context_get_thread_default ();
+  if (server->priv->main_context_at_construction != NULL)
+    g_main_context_ref (server->priv->main_context_at_construction);
+}
+
+static gboolean
+on_run (GSocketService    *service,
+        GSocketConnection *socket_connection,
+        GObject           *source_object,
+        gpointer           user_data);
+
+/**
+ * g_dbus_server_new_sync:
+ * @address: A D-Bus address.
+ * @flags: Flags from the #GDBusServerFlags enumeration.
+ * @guid: A D-Bus GUID.
+ * @observer: A #GDBusAuthObserver or %NULL.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for server or %NULL.
+ *
+ * Creates a new D-Bus server that listens on the first address in
+ * @address that works.
+ *
+ * Once constructed, you can use g_dbus_server_get_client_address() to
+ * get a D-Bus address string that clients can use to connect.
+ *
+ * Connect to the #GDBusServer::new-connection signal to handle
+ * incoming connections.
+ *
+ * The returned #GDBusServer isn't active - you have to start it with
+ * g_dbus_server_start().
+ *
+ * See <xref linkend="gdbus-peer-to-peer"/> for how #GDBusServer can
+ * be used.
+ *
+ * This is a synchronous failable constructor. See
+ * g_dbus_server_new() for the asynchronous version.
+ *
+ * Returns: A #GDBusServer or %NULL if @error is set. Free with
+ * g_object_unref().
+ */
+GDBusServer *
+g_dbus_server_new_sync (const gchar          *address,
+                        GDBusServerFlags      flags,
+                        const gchar          *guid,
+                        GDBusAuthObserver    *observer,
+                        GCancellable         *cancellable,
+                        GError              **error)
+{
+  GDBusServer *server;
+
+  g_return_val_if_fail (address != NULL, NULL);
+  g_return_val_if_fail (g_dbus_is_guid (guid), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  server = g_initable_new (G_TYPE_DBUS_SERVER,
+                           cancellable,
+                           error,
+                           "address", address,
+                           "flags", flags,
+                           "guid", guid,
+                           "authentication-observer", observer,
+                           NULL);
+  if (server != NULL)
+    {
+      /* Right now we don't have any transport not using the listener... */
+      g_assert (server->priv->is_using_listener);
+      g_signal_connect (G_SOCKET_SERVICE (server->priv->listener),
+                        "run",
+                        G_CALLBACK (on_run),
+                        server);
+    }
+
+  return server;
+}
+
+/**
+ * g_dbus_server_get_client_address:
+ * @server: A #GDBusServer.
+ *
+ * Gets a D-Bus address string that can be used by clients to connect
+ * to @server.
+ *
+ * Returns: A D-Bus address string. Do not free, the string is owned
+ * by @server.
+ */
+const gchar *
+g_dbus_server_get_client_address (GDBusServer *server)
+{
+  g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
+  return server->priv->client_address;
+}
+
+/**
+ * g_dbus_server_get_guid:
+ * @server: A #GDBusServer.
+ *
+ * Gets the GUID for @server.
+ *
+ * Returns: A D-Bus GUID. Do not free this string, it is owned by @server.
+ */
+const gchar *
+g_dbus_server_get_guid (GDBusServer *server)
+{
+  g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
+  return server->priv->guid;
+}
+
+/**
+ * g_dbus_server_get_flags:
+ * @server: A #GDBusServer.
+ *
+ * Gets the flags for @server.
+ *
+ * Returns: A set of flags from the #GDBusServerFlags enumeration.
+ */
+GDBusServerFlags
+g_dbus_server_get_flags (GDBusServer *server)
+{
+  g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
+  return server->priv->flags;
+}
+
+/**
+ * g_dbus_server_is_active:
+ * @server: A #GDBusServer.
+ *
+ * Gets whether @server is active.
+ *
+ * Returns: %TRUE if server is active, %FALSE otherwise.
+ */
+gboolean
+g_dbus_server_is_active (GDBusServer *server)
+{
+  g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
+  return server->priv->active;
+}
+
+/**
+ * g_dbus_server_start:
+ * @server: A #GDBusServer.
+ *
+ * Starts @server.
+ */
+void
+g_dbus_server_start (GDBusServer *server)
+{
+  g_return_if_fail (G_IS_DBUS_SERVER (server));
+  if (server->priv->active)
+    return;
+  /* Right now we don't have any transport not using the listener... */
+  g_assert (server->priv->is_using_listener);
+  g_socket_service_start (G_SOCKET_SERVICE (server->priv->listener));
+  server->priv->active = TRUE;
+  g_object_notify (G_OBJECT (server), "active");
+}
+
+/**
+ * g_dbus_server_stop:
+ * @server: A #GDBusServer.
+ *
+ * Stops @server.
+ */
+void
+g_dbus_server_stop (GDBusServer *server)
+{
+  g_return_if_fail (G_IS_DBUS_SERVER (server));
+  if (!server->priv->active)
+    return;
+  /* Right now we don't have any transport not using the listener... */
+  g_assert (server->priv->is_using_listener);
+  g_socket_service_stop (G_SOCKET_SERVICE (server->priv->listener));
+  server->priv->active = FALSE;
+  g_object_notify (G_OBJECT (server), "active");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+
+static gint
+random_ascii (void)
+{
+  gint ret;
+  ret = g_random_int_range (0, 60);
+  if (ret < 25)
+    ret += 'A';
+  else if (ret < 50)
+    ret += 'a' - 25;
+  else
+    ret += '0' - 50;
+  return ret;
+}
+
+/* note that address_entry has already been validated => exactly one of path, tmpdir or abstract keys are set */
+static gboolean
+try_unix (GDBusServer  *server,
+          const gchar  *address_entry,
+          GHashTable   *key_value_pairs,
+          GError      **error)
+{
+  gboolean ret;
+  const gchar *path;
+  const gchar *tmpdir;
+  const gchar *abstract;
+  GSocketAddress *address;
+
+  ret = FALSE;
+  address = NULL;
+
+  path = g_hash_table_lookup (key_value_pairs, "path");
+  tmpdir = g_hash_table_lookup (key_value_pairs, "tmpdir");
+  abstract = g_hash_table_lookup (key_value_pairs, "abstract");
+
+  if (path != NULL)
+    {
+      address = g_unix_socket_address_new (path);
+    }
+  else if (tmpdir != NULL)
+    {
+      gint n;
+      GString *s;
+      GError *local_error;
+
+    retry:
+      s = g_string_new (tmpdir);
+      g_string_append (s, "/dbus-");
+      for (n = 0; n < 8; n++)
+        g_string_append_c (s, random_ascii ());
+
+      /* prefer abstract namespace if available */
+      if (g_unix_socket_address_abstract_names_supported ())
+        address = g_unix_socket_address_new_with_type (s->str,
+                                                       -1,
+                                                       G_UNIX_SOCKET_ADDRESS_ABSTRACT);
+      else
+        address = g_unix_socket_address_new (s->str);
+      g_string_free (s, TRUE);
+
+      local_error = NULL;
+      if (!g_socket_listener_add_address (server->priv->listener,
+                                          address,
+                                          G_SOCKET_TYPE_STREAM,
+                                          G_SOCKET_PROTOCOL_DEFAULT,
+                                          NULL, /* source_object */
+                                          NULL, /* effective_address */
+                                          &local_error))
+        {
+          if (local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_ADDRESS_IN_USE)
+            {
+              g_error_free (local_error);
+              goto retry;
+            }
+          g_propagate_error (error, local_error);
+          goto out;
+        }
+      ret = TRUE;
+      goto out;
+    }
+  else if (abstract != NULL)
+    {
+      if (!g_unix_socket_address_abstract_names_supported ())
+        {
+          g_set_error_literal (error,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               _("Abstract name space not supported"));
+          goto out;
+        }
+      address = g_unix_socket_address_new_with_type (abstract,
+                                                     -1,
+                                                     G_UNIX_SOCKET_ADDRESS_ABSTRACT);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  if (!g_socket_listener_add_address (server->priv->listener,
+                                      address,
+                                      G_SOCKET_TYPE_STREAM,
+                                      G_SOCKET_PROTOCOL_DEFAULT,
+                                      NULL, /* source_object */
+                                      NULL, /* effective_address */
+                                      error))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+
+  if (address != NULL)
+    {
+      /* Fill out client_address if the connection attempt worked */
+      if (ret)
+        {
+          server->priv->is_using_listener = TRUE;
+
+          switch (g_unix_socket_address_get_address_type (G_UNIX_SOCKET_ADDRESS (address)))
+            {
+            case G_UNIX_SOCKET_ADDRESS_ABSTRACT:
+              server->priv->client_address = g_strdup_printf ("unix:abstract=%s",
+                                                              g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address)));
+              break;
+
+            case G_UNIX_SOCKET_ADDRESS_PATH:
+              server->priv->client_address = g_strdup_printf ("unix:path=%s",
+                                                              g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address)));
+              break;
+
+            default:
+              g_assert_not_reached ();
+              break;
+            }
+        }
+      g_object_unref (address);
+    }
+  return ret;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* note that address_entry has already been validated =>
+ *  both host and port (guranteed to be a number in [0, 65535]) are set (family is optional)
+ */
+static gboolean
+try_tcp (GDBusServer  *server,
+         const gchar  *address_entry,
+         GHashTable   *key_value_pairs,
+         gboolean      do_nonce,
+         GError      **error)
+{
+  gboolean ret;
+  const gchar *host;
+  const gchar *port;
+  const gchar *family;
+  gint port_num;
+  GSocketAddress *address;
+  GResolver *resolver;
+  GList *resolved_addresses;
+  GList *l;
+
+  ret = FALSE;
+  address = NULL;
+  resolver = NULL;
+  resolved_addresses = NULL;
+
+  host = g_hash_table_lookup (key_value_pairs, "host");
+  port = g_hash_table_lookup (key_value_pairs, "port");
+  family = g_hash_table_lookup (key_value_pairs, "family");
+  if (g_hash_table_lookup (key_value_pairs, "noncefile") != NULL)
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Cannot specify nonce file when creating a server"));
+      goto out;
+    }
+
+  if (host == NULL)
+    host = "localhost";
+  if (port == NULL)
+    port = "0";
+  port_num = strtol (port, NULL, 10);
+
+  resolver = g_resolver_get_default ();
+  resolved_addresses = g_resolver_lookup_by_name (resolver,
+                                                  host,
+                                                  NULL,
+                                                  error);
+  if (resolved_addresses == NULL)
+    {
+      goto out;
+    }
+  /* TODO: handle family */
+  for (l = resolved_addresses; l != NULL; l = l->next)
+    {
+      GInetAddress *address = G_INET_ADDRESS (l->data);
+      GSocketAddress *socket_address;
+      GSocketAddress *effective_address;
+
+      socket_address = g_inet_socket_address_new (address, port_num);
+      if (!g_socket_listener_add_address (server->priv->listener,
+                                          socket_address,
+                                          G_SOCKET_TYPE_STREAM,
+                                          G_SOCKET_PROTOCOL_TCP,
+                                          NULL, /* GObject *source_object */
+                                          &effective_address,
+                                          error))
+        {
+          g_object_unref (socket_address);
+          goto out;
+        }
+      if (port_num == 0)
+        {
+          /* make sure we allocate the same port number for other listeners */
+          port_num = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (effective_address));
+        }
+      g_object_unref (effective_address);
+      g_object_unref (socket_address);
+    }
+
+  if (do_nonce)
+    {
+      gint fd;
+      guint n;
+      gsize bytes_written;
+      gsize bytes_remaining;
+
+      server->priv->nonce = g_new0 (guchar, 16);
+      for (n = 0; n < 16; n++)
+        server->priv->nonce[n] = g_random_int_range (0, 256);
+      fd = g_file_open_tmp ("gdbus-nonce-file-XXXXXX",
+                            &server->priv->nonce_file,
+                            error);
+      if (fd == -1)
+        {
+          g_socket_listener_close (server->priv->listener);
+          goto out;
+        }
+    again:
+      bytes_written = 0;
+      bytes_remaining = 16;
+      while (bytes_remaining > 0)
+        {
+          gssize ret;
+          ret = write (fd, server->priv->nonce + bytes_written, bytes_remaining);
+          if (ret == -1)
+            {
+              if (errno == EINTR)
+                goto again;
+              g_set_error (error,
+                           G_IO_ERROR,
+                           g_io_error_from_errno (errno),
+                           _("Error writing nonce file at `%s': %s"),
+                           server->priv->nonce_file,
+                           strerror (errno));
+              goto out;
+            }
+          bytes_written += ret;
+          bytes_remaining -= ret;
+        }
+      close (fd);
+      server->priv->client_address = g_strdup_printf ("nonce-tcp:host=%s,port=%d,noncefile=%s",
+                                                      host,
+                                                      port_num,
+                                                      server->priv->nonce_file);
+    }
+  else
+    {
+      server->priv->client_address = g_strdup_printf ("tcp:host=%s,port=%d", host, port_num);
+    }
+  server->priv->is_using_listener = TRUE;
+  ret = TRUE;
+
+ out:
+  g_list_foreach (resolved_addresses, (GFunc) g_object_unref, NULL);
+  g_list_free (resolved_addresses);
+  g_object_unref (resolver);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GDBusServer *server;
+  GDBusConnection *connection;
+} EmitIdleData;
+
+static void
+emit_idle_data_free (EmitIdleData *data)
+{
+  g_object_unref (data->server);
+  g_object_unref (data->connection);
+  g_free (data);
+}
+
+static gboolean
+emit_new_connection_in_idle (gpointer user_data)
+{
+  EmitIdleData *data = user_data;
+
+  g_signal_emit (data->server,
+                 _signals[NEW_CONNECTION_SIGNAL],
+                 0,
+                 data->connection);
+  g_object_unref (data->connection);
+
+  return FALSE;
+}
+
+/* Called in new thread */
+static gboolean
+on_run (GSocketService    *service,
+        GSocketConnection *socket_connection,
+        GObject           *source_object,
+        gpointer           user_data)
+{
+  GDBusServer *server = G_DBUS_SERVER (user_data);
+  GDBusConnection *connection;
+  GDBusConnectionFlags connection_flags;
+
+  if (server->priv->nonce != NULL)
+    {
+      gchar buf[16];
+      gsize bytes_read;
+
+      if (!g_input_stream_read_all (g_io_stream_get_input_stream (G_IO_STREAM (socket_connection)),
+                                    buf,
+                                    16,
+                                    &bytes_read,
+                                    NULL,  /* GCancellable */
+                                    NULL)) /* GError */
+        goto out;
+
+      if (bytes_read != 16)
+        goto out;
+
+      if (memcmp (buf, server->priv->nonce, 16) != 0)
+        goto out;
+    }
+
+  connection_flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER;
+  if (server->priv->flags & G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS)
+    connection_flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
+
+  connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
+                                           server->priv->guid,
+                                           connection_flags,
+                                           server->priv->authentication_observer,
+                                           NULL,  /* GCancellable */
+                                           NULL); /* GError */
+  if (connection == NULL)
+      goto out;
+
+  if (server->priv->flags & G_DBUS_SERVER_FLAGS_RUN_IN_THREAD)
+    {
+      g_signal_emit (server,
+                     _signals[NEW_CONNECTION_SIGNAL],
+                     0,
+                     connection);
+      g_object_unref (connection);
+    }
+  else
+    {
+      GSource *idle_source;
+      EmitIdleData *data;
+
+      data = g_new0 (EmitIdleData, 1);
+      data->server = g_object_ref (server);
+      data->connection = g_object_ref (connection);
+
+      idle_source = g_idle_source_new ();
+      g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (idle_source,
+                             emit_new_connection_in_idle,
+                             data,
+                             (GDestroyNotify) emit_idle_data_free);
+      g_source_attach (idle_source, server->priv->main_context_at_construction);
+      g_source_unref (idle_source);
+    }
+
+ out:
+  return TRUE;
+}
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  GDBusServer *server = G_DBUS_SERVER (initable);
+  gboolean ret;
+  guint n;
+  gchar **addr_array;
+  GError *last_error;
+
+  ret = FALSE;
+  last_error = NULL;
+
+  if (!g_dbus_is_guid (server->priv->guid))
+    {
+      g_set_error (&last_error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   _("The string `%s' is not a valid D-Bus GUID"),
+                   server->priv->guid);
+      goto out;
+    }
+
+  server->priv->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1));
+
+  addr_array = g_strsplit (server->priv->address, ";", 0);
+  last_error = NULL;
+  for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++)
+    {
+      const gchar *address_entry = addr_array[n];
+      GHashTable *key_value_pairs;
+      gchar *transport_name;
+      GError *this_error;
+
+      this_error = NULL;
+      if (g_dbus_is_supported_address (address_entry,
+                                       &this_error) &&
+          _g_dbus_address_parse_entry (address_entry,
+                                       &transport_name,
+                                       &key_value_pairs,
+                                       &this_error))
+        {
+
+          if (FALSE)
+            {
+            }
+#ifdef G_OS_UNIX
+          else if (g_strcmp0 (transport_name, "unix") == 0)
+            {
+              ret = try_unix (server, address_entry, key_value_pairs, &this_error);
+            }
+#endif
+          else if (g_strcmp0 (transport_name, "tcp") == 0)
+            {
+              ret = try_tcp (server, address_entry, key_value_pairs, FALSE, &this_error);
+            }
+          else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
+            {
+              ret = try_tcp (server, address_entry, key_value_pairs, TRUE, &this_error);
+            }
+          else
+            {
+              g_set_error (&this_error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_ARGUMENT,
+                           _("Cannot listen on unsupported transport `%s'"),
+                           transport_name);
+            }
+
+          g_free (transport_name);
+          if (key_value_pairs != NULL)
+            g_hash_table_unref (key_value_pairs);
+
+          if (ret)
+            {
+              g_assert (this_error == NULL);
+              goto out;
+            }
+        }
+
+      if (this_error != NULL)
+        {
+          if (last_error != NULL)
+            g_error_free (last_error);
+          last_error = this_error;
+        }
+    }
+
+  if (!ret)
+    goto out;
+
+ out:
+  if (ret)
+    {
+      if (last_error != NULL)
+        g_error_free (last_error);
+    }
+  else
+    {
+      g_assert (last_error != NULL);
+      g_propagate_error (error, last_error);
+    }
+  return ret;
+}
+
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusserver.h b/gio/gdbusserver.h
new file mode 100644
index 0000000..4043854
--- /dev/null
+++ b/gio/gdbusserver.h
@@ -0,0 +1,97 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_SERVER_H__
+#define __G_DBUS_SERVER_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_DBUS_SERVER         (g_dbus_server_get_type ())
+#define G_DBUS_SERVER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_SERVER, GDBusServer))
+#define G_DBUS_SERVER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_DBUS_SERVER, GDBusServerClass))
+#define G_DBUS_SERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_DBUS_SERVER, GDBusServerClass))
+#define G_IS_DBUS_SERVER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_SERVER))
+#define G_IS_DBUS_SERVER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_DBUS_SERVER))
+
+typedef struct _GDBusServerClass   GDBusServerClass;
+typedef struct _GDBusServerPrivate GDBusServerPrivate;
+
+/**
+ * GDBusServer:
+ *
+ * The #GDBusServer structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _GDBusServer
+{
+  /*< private >*/
+  GObject parent_instance;
+  GDBusServerPrivate *priv;
+};
+
+/**
+ * GDBusServerClass:
+ * @new_connection: Signal class handler for the #GDBusServer::new-connection signal.
+ *
+ * Class structure for #GDBusServer.
+ */
+struct _GDBusServerClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  /* Signals */
+  void (*new_connection) (GDBusServer      *server,
+                          GDBusConnection  *connection);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType             g_dbus_server_get_type           (void) G_GNUC_CONST;
+GDBusServer      *g_dbus_server_new_sync           (const gchar       *address,
+                                                    GDBusServerFlags   flags,
+                                                    const gchar       *guid,
+                                                    GDBusAuthObserver *observer,
+                                                    GCancellable      *cancellable,
+                                                    GError           **error);
+const gchar      *g_dbus_server_get_client_address (GDBusServer       *server);
+const gchar      *g_dbus_server_get_guid           (GDBusServer       *server);
+GDBusServerFlags  g_dbus_server_get_flags          (GDBusServer       *server);
+void              g_dbus_server_start              (GDBusServer       *server);
+void              g_dbus_server_stop               (GDBusServer       *server);
+gboolean          g_dbus_server_is_active          (GDBusServer       *server);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_SERVER_H__ */
diff --git a/gio/gdbusutils.c b/gio/gdbusutils.c
new file mode 100644
index 0000000..b1ec0ea
--- /dev/null
+++ b/gio/gdbusutils.c
@@ -0,0 +1,364 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusutils.h"
+
+/**
+ * SECTION:gdbusutils
+ * @title: D-Bus Utilities
+ * @short_description: Various utilities related to D-Bus.
+ * @include: gdbus/gdbus.h
+ *
+ * Various utility routines related to D-Bus.
+ */
+
+static gboolean
+is_valid_bus_name_character (gint c,
+                             gboolean allow_hyphen)
+{
+  return
+    (c >= '0' && c <= '9') ||
+    (c >= 'A' && c <= 'Z') ||
+    (c >= 'a' && c <= 'z') ||
+    (c == '_') ||
+    (allow_hyphen && c == '-');
+}
+
+static gboolean
+is_valid_initial_bus_name_character (gint c,
+                                     gboolean allow_initial_digit,
+                                     gboolean allow_hyphen)
+{
+  if (allow_initial_digit)
+    return is_valid_bus_name_character (c, allow_hyphen);
+  else
+    return
+      (c >= 'A' && c <= 'Z') ||
+      (c >= 'a' && c <= 'z') ||
+      (c == '_') ||
+      (allow_hyphen && c == '-');
+}
+
+static gboolean
+is_valid_name (const gchar *start,
+               guint len,
+               gboolean allow_initial_digit,
+               gboolean allow_hyphen)
+{
+  gboolean ret;
+  const gchar *s;
+  const gchar *end;
+  gboolean has_dot;
+
+  ret = FALSE;
+
+  if (len == 0)
+    goto out;
+
+  s = start;
+  end = s + len;
+  has_dot = FALSE;
+  while (s != end)
+    {
+      if (*s == '.')
+        {
+          s += 1;
+          if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, allow_initial_digit, allow_hyphen)))
+            goto out;
+          has_dot = TRUE;
+        }
+      else if (G_UNLIKELY (!is_valid_bus_name_character (*s, allow_hyphen)))
+        {
+          goto out;
+        }
+      s += 1;
+    }
+
+  if (G_UNLIKELY (!has_dot))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+/**
+ * g_dbus_is_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus bus name (either unique or well-known).
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_name (const gchar *string)
+{
+  guint len;
+  gboolean ret;
+  const gchar *s;
+  const gchar *end;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  len = strlen (string);
+  if (G_UNLIKELY (len == 0 || len > 255))
+    goto out;
+
+  s = string;
+  end = s + len;
+  if (*s == ':')
+    {
+      /* handle unique name */
+      if (!is_valid_name (s + 1, len - 1, TRUE, TRUE))
+        goto out;
+      ret = TRUE;
+      goto out;
+    }
+  else if (G_UNLIKELY (*s == '.'))
+    {
+      /* can't start with a . */
+      goto out;
+    }
+  else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, TRUE)))
+    goto out;
+
+  ret = is_valid_name (s + 1, len - 1, FALSE, TRUE);
+
+ out:
+  return ret;
+}
+
+/**
+ * g_dbus_is_unique_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus unique bus name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_unique_name (const gchar *string)
+{
+  gboolean ret;
+  guint len;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  len = strlen (string);
+  if (G_UNLIKELY (len == 0 || len > 255))
+    goto out;
+
+  if (G_UNLIKELY (*string != ':'))
+    goto out;
+
+  if (G_UNLIKELY (!is_valid_name (string + 1, len - 1, TRUE, TRUE)))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+/**
+ * g_dbus_is_member_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus member (e.g. signal or method) name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_member_name (const gchar *string)
+{
+  gboolean ret;
+  guint n;
+
+  ret = FALSE;
+  if (G_UNLIKELY (string == NULL))
+    goto out;
+
+  if (G_UNLIKELY (!is_valid_initial_bus_name_character (string[0], FALSE, FALSE)))
+    goto out;
+
+  for (n = 1; string[n] != '\0'; n++)
+    {
+      if (G_UNLIKELY (!is_valid_bus_name_character (string[n], FALSE)))
+        {
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+/**
+ * g_dbus_is_interface_name:
+ * @string: The string to check.
+ *
+ * Checks if @string is a valid D-Bus interface name.
+ *
+ * Returns: %TRUE if valid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_interface_name (const gchar *string)
+{
+  guint len;
+  gboolean ret;
+  const gchar *s;
+  const gchar *end;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  len = strlen (string);
+  if (G_UNLIKELY (len == 0 || len > 255))
+    goto out;
+
+  s = string;
+  end = s + len;
+  if (G_UNLIKELY (*s == '.'))
+    {
+      /* can't start with a . */
+      goto out;
+    }
+  else if (G_UNLIKELY (!is_valid_initial_bus_name_character (*s, FALSE, FALSE)))
+    goto out;
+
+  ret = is_valid_name (s + 1, len - 1, FALSE, FALSE);
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* TODO: maybe move to glib? if so, it should conform to http://en.wikipedia.org/wiki/Guid and/or
+ *       http://tools.ietf.org/html/rfc4122 - specifically it should have hyphens then.
+ */
+
+/**
+ * g_dbus_generate_guid:
+ *
+ * Generate a D-Bus GUID that can be used with
+ * e.g. g_dbus_connection_new().
+ *
+ * See the D-Bus specification regarding what strings are valid D-Bus
+ * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant).
+ *
+ * Returns: A valid D-Bus GUID. Free with g_free().
+ */
+gchar *
+g_dbus_generate_guid (void)
+{
+  GString *s;
+  GTimeVal now;
+  guint32 r1;
+  guint32 r2;
+  guint32 r3;
+
+  s = g_string_new (NULL);
+
+  r1 = g_random_int ();
+  r2 = g_random_int ();
+  r3 = g_random_int ();
+  g_get_current_time (&now);
+
+  g_string_append_printf (s, "%08x", r1);
+  g_string_append_printf (s, "%08x", r2);
+  g_string_append_printf (s, "%08x", r3);
+  g_string_append_printf (s, "%08x", (guint32) now.tv_sec);
+
+  return g_string_free (s, FALSE);
+}
+
+/**
+ * g_dbus_is_guid:
+ * @string: The string to check.
+ *
+ * Checks if @string is a D-Bus GUID.
+ *
+ * See the D-Bus specification regarding what strings are valid D-Bus
+ * GUID (for example, D-Bus GUIDs are not RFC-4122 compliant).
+ *
+ * Returns: %TRUE if @string is a guid, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_guid (const gchar *string)
+{
+  gboolean ret;
+  guint n;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  ret = FALSE;
+
+  for (n = 0; n < 32; n++)
+    {
+      if (!g_ascii_isxdigit (string[n]))
+        goto out;
+    }
+  if (string[32] != '\0')
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+/**
+ * g_dbus_is_activated:
+ *
+ * Determine if the process has been activated by a message bus.
+ *
+ * Returns: %TRUE if this process has been started by a message bus, %FALSE otherwise.
+ */
+gboolean
+g_dbus_is_activated (void)
+{
+  /* TODO: technically this will make child processes forked by us
+   * return TRUE too..
+   */
+  return g_getenv ("DBUS_STARTER_BUS_TYPE") != NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/gdbusutils.h b/gio/gdbusutils.h
new file mode 100644
index 0000000..e3e606b
--- /dev/null
+++ b/gio/gdbusutils.h
@@ -0,0 +1,42 @@
+/* GDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_DBUS_UTILS_H__
+#define __G_DBUS_UTILS_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+gboolean g_dbus_is_guid (const gchar *string);
+gchar *g_dbus_generate_guid (void);
+
+gboolean g_dbus_is_name (const gchar *string);
+gboolean g_dbus_is_unique_name (const gchar *string);
+gboolean g_dbus_is_member_name (const gchar *string);
+gboolean g_dbus_is_interface_name (const gchar *string);
+
+gboolean g_dbus_is_activated (void);
+
+G_END_DECLS
+
+#endif /* __G_DBUS_UTILS_H__ */
diff --git a/gio/gio-marshal.list b/gio/gio-marshal.list
index 8439304..8473ea7 100644
--- a/gio/gio-marshal.list
+++ b/gio/gio-marshal.list
@@ -6,3 +6,5 @@ BOOLEAN:OBJECT,OBJECT
 VOID:STRING,BOXED,BOXED
 BOOL:POINTER,INT
 BOOL:UINT
+VOID:STRING,STRING,BOXED
+VOID:BOOL,BOXED
diff --git a/gio/gio.h b/gio/gio.h
index ac0dc7f..ca55cd6 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -97,6 +97,22 @@
 #include <gio/gzlibcompressor.h>
 #include <gio/gzlibdecompressor.h>
 
+#include <gio/gdbusutils.h>
+#include <gio/gdbusaddress.h>
+#include <gio/gdbusmessage.h>
+#include <gio/gdbusconnection.h>
+#include <gio/gdbuserror.h>
+#include <gio/gdbusnameowning.h>
+#include <gio/gdbusnamewatching.h>
+#include <gio/gdbusproxywatching.h>
+#include <gio/gdbusproxy.h>
+#include <gio/gdbusintrospection.h>
+#include <gio/gdbusmethodinvocation.h>
+#include <gio/gdbusserver.h>
+#include <gio/gcredentials.h>
+#include <gio/gunixcredentialsmessage.h>
+#include <gio/gdbusauthobserver.h>
+
 #undef __GIO_GIO_H_INSIDE__
 
 #endif /* __G_IO_H__ */
diff --git a/gio/gioenums.h b/gio/gioenums.h
index 9c98ffd..c1d2e9f 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -429,6 +429,11 @@ typedef enum {
  * @G_IO_ERROR_ADDRESS_IN_USE: The requested address is already in use. Since 2.22
  * @G_IO_ERROR_PARTIAL_INPUT: Need more input to finish operation. Since 2.24
  * @G_IO_ERROR_INVALID_DATA: There input data was invalid. Since 2.24
+ * @G_IO_ERROR_DBUS_ERROR: A remote object generated an error that
+ *     doesn't correspond to a locally registered #GError error
+ *     domain. Use g_dbus_error_get_remote_error() to extract the D-Bus
+ *     error name and g_dbus_error_strip_remote_error() to fix up the
+ *     message so it matches what was received on the wire. Since 2.26.
  *
  * Error codes returned by GIO functions.
  *
@@ -469,7 +474,8 @@ typedef enum {
   G_IO_ERROR_NOT_INITIALIZED,
   G_IO_ERROR_ADDRESS_IN_USE,
   G_IO_ERROR_PARTIAL_INPUT,
-  G_IO_ERROR_INVALID_DATA
+  G_IO_ERROR_INVALID_DATA,
+  G_IO_ERROR_DBUS_ERROR
 } GIOErrorEnum;
 
 
@@ -732,6 +738,370 @@ typedef enum {
   G_UNIX_SOCKET_ADDRESS_ABSTRACT_PADDED
 } GUnixSocketAddressType;
 
+/**
+ * GBusType:
+ * @G_BUS_TYPE_NONE: Not a message bus connection.
+ * @G_BUS_TYPE_SESSION: The login session message bus.
+ * @G_BUS_TYPE_SYSTEM: The system-wide message bus.
+ * @G_BUS_TYPE_STARTER: Connect to the bus that activated the program.
+ *
+ * An enumeration to specify the type of a #GDBusConnection.
+ */
+typedef enum
+{
+  G_BUS_TYPE_NONE    = -1,
+  G_BUS_TYPE_SESSION = 0,
+  G_BUS_TYPE_SYSTEM  = 1,
+  G_BUS_TYPE_STARTER = 2
+} GBusType;
+
+/**
+ * GBusNameOwnerFlags:
+ * @G_BUS_NAME_OWNER_FLAGS_NONE: No flags set.
+ * @G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT: Allow another message bus connection to claim the the name.
+ * @G_BUS_NAME_OWNER_FLAGS_REPLACE: If another message bus connection owns the name and have
+ * specified #G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then take the name from the other connection.
+ *
+ * Flags used in g_bus_own_name().
+ */
+typedef enum
+{
+  G_BUS_NAME_OWNER_FLAGS_NONE = 0,                    /*< nick=none >*/
+  G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT = (1<<0),  /*< nick=allow-replacement >*/
+  G_BUS_NAME_OWNER_FLAGS_REPLACE = (1<<1),            /*< nick=replace >*/
+} GBusNameOwnerFlags;
+
+/**
+ * GBusNameWatcherFlags:
+ * @G_BUS_NAME_WATCHER_FLAGS_NONE: No flags set.
+ * @G_BUS_NAME_WATCHER_FLAGS_AUTO_START: If no-one owns the name when
+ * beginning to watch the name, ask the bus to launch an owner for the
+ * name.
+ *
+ * Flags used in g_bus_watch_name().
+ */
+typedef enum
+{
+  G_BUS_NAME_WATCHER_FLAGS_NONE = 0,
+  G_BUS_NAME_WATCHER_FLAGS_AUTO_START = (1<<0)
+} GBusNameWatcherFlags;
+
+/**
+ * GDBusProxyFlags:
+ * @G_DBUS_PROXY_FLAGS_NONE: No flags set.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES: Don't load properties.
+ * @G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS: Don't connect to signals on the remote object.
+ *
+ * Flags used when constructing an instance of a #GDBusProxy derived class.
+ */
+typedef enum
+{
+  G_DBUS_PROXY_FLAGS_NONE = 0,                        /*< nick=none >*/
+  G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0), /*< nick=do-not-load-properties >*/
+  G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), /*< nick=do-not-connect-signals >*/
+} GDBusProxyFlags;
+
+/**
+ * GDBusError:
+ * @G_DBUS_ERROR_FAILED:
+ * A generic error; "something went wrong" - see the error message for
+ * more.
+ * @G_DBUS_ERROR_NO_MEMORY:
+ * There was not enough memory to complete an operation.
+ * @G_DBUS_ERROR_SERVICE_UNKNOWN:
+ * The bus doesn't know how to launch a service to supply the bus name
+ * you wanted.
+ * @G_DBUS_ERROR_NAME_HAS_NO_OWNER:
+ * The bus name you referenced doesn't exist (i.e. no application owns
+ * it).
+ * @G_DBUS_ERROR_NO_REPLY:
+ * No reply to a message expecting one, usually means a timeout occurred.
+ * @G_DBUS_ERROR_IO_ERROR:
+ * Something went wrong reading or writing to a socket, for example.
+ * @G_DBUS_ERROR_BAD_ADDRESS:
+ * A D-Bus bus address was malformed.
+ * @G_DBUS_ERROR_NOT_SUPPORTED:
+ * Requested operation isn't supported (like ENOSYS on UNIX).
+ * @G_DBUS_ERROR_LIMITS_EXCEEDED:
+ * Some limited resource is exhausted.
+ * @G_DBUS_ERROR_ACCESS_DENIED:
+ * Security restrictions don't allow doing what you're trying to do.
+ * @G_DBUS_ERROR_AUTH_FAILED:
+ * Authentication didn't work.
+ * @G_DBUS_ERROR_NO_SERVER:
+ * Unable to connect to server (probably caused by ECONNREFUSED on a
+ * socket).
+ * @G_DBUS_ERROR_TIMEOUT:
+ * Certain timeout errors, possibly ETIMEDOUT on a socket.  Note that
+ * %G_DBUS_ERROR_NO_REPLY is used for message reply timeouts. Warning:
+ * this is confusingly-named given that %G_DBUS_ERROR_TIMED_OUT also
+ * exists. We can't fix it for compatibility reasons so just be
+ * careful.
+ * @G_DBUS_ERROR_NO_NETWORK:
+ * No network access (probably ENETUNREACH on a socket).
+ * @G_DBUS_ERROR_ADDRESS_IN_USE:
+ * Can't bind a socket since its address is in use (i.e. EADDRINUSE).
+ * @G_DBUS_ERROR_DISCONNECTED:
+ * The connection is disconnected and you're trying to use it.
+ * @G_DBUS_ERROR_INVALID_ARGS:
+ * Invalid arguments passed to a method call.
+ * @G_DBUS_ERROR_FILE_NOT_FOUND:
+ * Missing file.
+ * @G_DBUS_ERROR_FILE_EXISTS:
+ * Existing file and the operation you're using does not silently overwrite.
+ * @G_DBUS_ERROR_UNKNOWN_METHOD:
+ * Method name you invoked isn't known by the object you invoked it on.
+ * @G_DBUS_ERROR_TIMED_OUT:
+ * Certain timeout errors, e.g. while starting a service. Warning: this is
+ * confusingly-named given that %G_DBUS_ERROR_TIMEOUT also exists. We
+ * can't fix it for compatibility reasons so just be careful.
+ * @G_DBUS_ERROR_MATCH_RULE_NOT_FOUND:
+ * Tried to remove or modify a match rule that didn't exist.
+ * @G_DBUS_ERROR_MATCH_RULE_INVALID:
+ * The match rule isn't syntactically valid.
+ * @G_DBUS_ERROR_SPAWN_EXEC_FAILED:
+ * While starting a new process, the exec() call failed.
+ * @G_DBUS_ERROR_SPAWN_FORK_FAILED:
+ * While starting a new process, the fork() call failed.
+ * @G_DBUS_ERROR_SPAWN_CHILD_EXITED:
+ * While starting a new process, the child exited with a status code.
+ * @G_DBUS_ERROR_SPAWN_CHILD_SIGNALED:
+ * While starting a new process, the child exited on a signal.
+ * @G_DBUS_ERROR_SPAWN_FAILED:
+ * While starting a new process, something went wrong.
+ * @G_DBUS_ERROR_SPAWN_SETUP_FAILED:
+ * We failed to setup the environment correctly.
+ * @G_DBUS_ERROR_SPAWN_CONFIG_INVALID:
+ * We failed to setup the config parser correctly.
+ * @G_DBUS_ERROR_SPAWN_SERVICE_INVALID:
+ * Bus name was not valid.
+ * @G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+ * Service file not found in system-services directory.
+ * @G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID:
+ * Permissions are incorrect on the setuid helper.
+ * @G_DBUS_ERROR_SPAWN_FILE_INVALID:
+ * Service file invalid (Name, User or Exec missing).
+ * @G_DBUS_ERROR_SPAWN_NO_MEMORY:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @G_DBUS_ERROR_INVALID_SIGNATURE:
+ * A type signature is not valid.
+ * @G_DBUS_ERROR_INVALID_FILE_CONTENT:
+ * A file contains invalid syntax or is otherwise broken.
+ * @G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN:
+ * Asked for SELinux security context and it wasn't available.
+ * @G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN:
+ * Asked for ADT audit data and it wasn't available.
+ * @G_DBUS_ERROR_OBJECT_PATH_IN_USE:
+ * There's already an object with the requested object path.
+ *
+ * Error codes for the %G_DBUS_ERROR error domain.
+ */
+typedef enum
+{
+  /* Well-known errors in the org.freedesktop.DBus.Error namespace */
+  G_DBUS_ERROR_FAILED,                           /* org.freedesktop.DBus.Error.Failed */
+  G_DBUS_ERROR_NO_MEMORY,                        /* org.freedesktop.DBus.Error.NoMemory */
+  G_DBUS_ERROR_SERVICE_UNKNOWN,                  /* org.freedesktop.DBus.Error.ServiceUnknown */
+  G_DBUS_ERROR_NAME_HAS_NO_OWNER,                /* org.freedesktop.DBus.Error.NameHasNoOwner */
+  G_DBUS_ERROR_NO_REPLY,                         /* org.freedesktop.DBus.Error.NoReply */
+  G_DBUS_ERROR_IO_ERROR,                         /* org.freedesktop.DBus.Error.IOError */
+  G_DBUS_ERROR_BAD_ADDRESS,                      /* org.freedesktop.DBus.Error.BadAddress */
+  G_DBUS_ERROR_NOT_SUPPORTED,                    /* org.freedesktop.DBus.Error.NotSupported */
+  G_DBUS_ERROR_LIMITS_EXCEEDED,                  /* org.freedesktop.DBus.Error.LimitsExceeded */
+  G_DBUS_ERROR_ACCESS_DENIED,                    /* org.freedesktop.DBus.Error.AccessDenied */
+  G_DBUS_ERROR_AUTH_FAILED,                      /* org.freedesktop.DBus.Error.AuthFailed */
+  G_DBUS_ERROR_NO_SERVER,                        /* org.freedesktop.DBus.Error.NoServer */
+  G_DBUS_ERROR_TIMEOUT,                          /* org.freedesktop.DBus.Error.Timeout */
+  G_DBUS_ERROR_NO_NETWORK,                       /* org.freedesktop.DBus.Error.NoNetwork */
+  G_DBUS_ERROR_ADDRESS_IN_USE,                   /* org.freedesktop.DBus.Error.AddressInUse */
+  G_DBUS_ERROR_DISCONNECTED,                     /* org.freedesktop.DBus.Error.Disconnected */
+  G_DBUS_ERROR_INVALID_ARGS,                     /* org.freedesktop.DBus.Error.InvalidArgs */
+  G_DBUS_ERROR_FILE_NOT_FOUND,                   /* org.freedesktop.DBus.Error.FileNotFound */
+  G_DBUS_ERROR_FILE_EXISTS,                      /* org.freedesktop.DBus.Error.FileExists */
+  G_DBUS_ERROR_UNKNOWN_METHOD,                   /* org.freedesktop.DBus.Error.UnknownMethod */
+  G_DBUS_ERROR_TIMED_OUT,                        /* org.freedesktop.DBus.Error.TimedOut */
+  G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,             /* org.freedesktop.DBus.Error.MatchRuleNotFound */
+  G_DBUS_ERROR_MATCH_RULE_INVALID,               /* org.freedesktop.DBus.Error.MatchRuleInvalid */
+  G_DBUS_ERROR_SPAWN_EXEC_FAILED,                /* org.freedesktop.DBus.Error.Spawn.ExecFailed */
+  G_DBUS_ERROR_SPAWN_FORK_FAILED,                /* org.freedesktop.DBus.Error.Spawn.ForkFailed */
+  G_DBUS_ERROR_SPAWN_CHILD_EXITED,               /* org.freedesktop.DBus.Error.Spawn.ChildExited */
+  G_DBUS_ERROR_SPAWN_CHILD_SIGNALED,             /* org.freedesktop.DBus.Error.Spawn.ChildSignaled */
+  G_DBUS_ERROR_SPAWN_FAILED,                     /* org.freedesktop.DBus.Error.Spawn.Failed */
+  G_DBUS_ERROR_SPAWN_SETUP_FAILED,               /* org.freedesktop.DBus.Error.Spawn.FailedToSetup */
+  G_DBUS_ERROR_SPAWN_CONFIG_INVALID,             /* org.freedesktop.DBus.Error.Spawn.ConfigInvalid */
+  G_DBUS_ERROR_SPAWN_SERVICE_INVALID,            /* org.freedesktop.DBus.Error.Spawn.ServiceNotValid */
+  G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,          /* org.freedesktop.DBus.Error.Spawn.ServiceNotFound */
+  G_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,        /* org.freedesktop.DBus.Error.Spawn.PermissionsInvalid */
+  G_DBUS_ERROR_SPAWN_FILE_INVALID,               /* org.freedesktop.DBus.Error.Spawn.FileInvalid */
+  G_DBUS_ERROR_SPAWN_NO_MEMORY,                  /* org.freedesktop.DBus.Error.Spawn.NoMemory */
+  G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,          /* org.freedesktop.DBus.Error.UnixProcessIdUnknown */
+  G_DBUS_ERROR_INVALID_SIGNATURE,                /* org.freedesktop.DBus.Error.InvalidSignature */
+  G_DBUS_ERROR_INVALID_FILE_CONTENT,             /* org.freedesktop.DBus.Error.InvalidFileContent */
+  G_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, /* org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown */
+  G_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN,           /* org.freedesktop.DBus.Error.AdtAuditDataUnknown */
+  G_DBUS_ERROR_OBJECT_PATH_IN_USE,               /* org.freedesktop.DBus.Error.ObjectPathInUse */
+} GDBusError;
+/* TODO: remember to update g_dbus_error_quark() in gdbuserror.c if you extend this enumeration */
+
+/**
+ * GDBusConnectionFlags:
+ * @G_DBUS_CONNECTION_FLAGS_NONE: No flags set.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT: Perform authentication against server.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER: Perform authentication against client.
+ * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: When
+ * authenticating as a server, allow the anonymous authentication
+ * method.
+ * @G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION: Pass this flag if connecting to a peer that is a
+ * message bus. This means that the Hello() method will be invoked as part of the connection setup.
+ *
+ * Flags used when creating a new #GDBusConnection.
+ */
+typedef enum {
+  G_DBUS_CONNECTION_FLAGS_NONE = 0,
+  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT = (1<<0),
+  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER = (1<<1),
+  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<2),
+  G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION = (1<<3)
+} GDBusConnectionFlags;
+
+/**
+ * GDBusCapabilityFlags:
+ * @G_DBUS_CAPABILITY_FLAGS_NONE: No flags set.
+ * @G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING: The connection
+ * supports exchanging UNIX file descriptors with the remote peer.
+ *
+ * Capabilities negotiated with the remote peer.
+ */
+typedef enum {
+  G_DBUS_CAPABILITY_FLAGS_NONE = 0,
+  G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING = (1<<0),
+} GDBusCapabilityFlags;
+
+/**
+ * GDBusInvokeMethodFlags:
+ * @G_DBUS_INVOKE_METHOD_FLAGS_NONE: No flags set.
+ * @G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START: The bus must not launch
+ * an owner for the destination name in response to this method
+ * invocation.
+ *
+ * Flags used in g_dbus_connection_invoke_method() and similar APIs.
+ */
+typedef enum {
+  G_DBUS_INVOKE_METHOD_FLAGS_NONE = 0,
+  G_DBUS_INVOKE_METHOD_FLAGS_NO_AUTO_START = (1<<0),
+} GDBusInvokeMethodFlags;
+
+/**
+ * GDBusMessageType:
+ * @G_DBUS_MESSAGE_TYPE_INVALID: Message is of invalid type.
+ * @G_DBUS_MESSAGE_TYPE_METHOD_CALL: Method call.
+ * @G_DBUS_MESSAGE_TYPE_METHOD_RETURN: Method reply.
+ * @G_DBUS_MESSAGE_TYPE_ERROR: Error reply.
+ * @G_DBUS_MESSAGE_TYPE_SIGNAL: Signal emission.
+ *
+ * Message types used in #GDBusMessage.
+ */
+typedef enum {
+  G_DBUS_MESSAGE_TYPE_INVALID,
+  G_DBUS_MESSAGE_TYPE_METHOD_CALL,
+  G_DBUS_MESSAGE_TYPE_METHOD_RETURN,
+  G_DBUS_MESSAGE_TYPE_ERROR,
+  G_DBUS_MESSAGE_TYPE_SIGNAL
+} GDBusMessageType;
+
+/**
+ * GDBusMessageFlags:
+ * @G_DBUS_MESSAGE_FLAGS_NONE: No flags set.
+ * @G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED: A reply is not expected.
+ * @G_DBUS_MESSAGE_FLAGS_NO_AUTO_START: The bus must not launch an
+ * owner for the destination name in response to this message.
+ *
+ * Message flags used in #GDBusMessage.
+ */
+typedef enum {
+  G_DBUS_MESSAGE_FLAGS_NONE = 0,
+  G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED = (1<<0),
+  G_DBUS_MESSAGE_FLAGS_NO_AUTO_START = (1<<1)
+} GDBusMessageFlags;
+
+/**
+ * GDBusMessageHeaderField:
+ * @G_DBUS_MESSAGE_HEADER_FIELD_INVALID: Not a valid header field.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_PATH: The object path.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE: The interface name.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_MEMBER: The method or signal name.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME: The name of the error that occurred.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL: The serial number the message is a reply to.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION: The name the message is intended for.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_SENDER: Unique name of the sender of the message (filled in by the bus).
+ * @G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE: The signature of the message body.
+ * @G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS: The number of UNIX file descriptors that accompany the message.
+ *
+ * Header fields used in #GDBusMessage.
+ */
+typedef enum {
+  G_DBUS_MESSAGE_HEADER_FIELD_INVALID,
+  G_DBUS_MESSAGE_HEADER_FIELD_PATH,
+  G_DBUS_MESSAGE_HEADER_FIELD_INTERFACE,
+  G_DBUS_MESSAGE_HEADER_FIELD_MEMBER,
+  G_DBUS_MESSAGE_HEADER_FIELD_ERROR_NAME,
+  G_DBUS_MESSAGE_HEADER_FIELD_REPLY_SERIAL,
+  G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION,
+  G_DBUS_MESSAGE_HEADER_FIELD_SENDER,
+  G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE,
+  G_DBUS_MESSAGE_HEADER_FIELD_NUM_UNIX_FDS
+} GDBusMessageHeaderField;
+
+/**
+ * GDBusPropertyInfoFlags:
+ * @G_DBUS_PROPERTY_INFO_FLAGS_NONE: No flags set.
+ * @G_DBUS_PROPERTY_INFO_FLAGS_READABLE: Property is readable.
+ * @G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE: Property is writable.
+ *
+ * Flags describing the access control of a D-Bus property.
+ */
+typedef enum
+{
+  G_DBUS_PROPERTY_INFO_FLAGS_NONE = 0,
+  G_DBUS_PROPERTY_INFO_FLAGS_READABLE = (1<<0),
+  G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE = (1<<1),
+} GDBusPropertyInfoFlags;
+
+/**
+ * GDBusSubtreeFlags:
+ * @G_DBUS_SUBTREE_FLAGS_NONE: No flags set.
+ * @G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES: Method calls to objects not in the enumerated range
+ *                                                       will still be dispatched. This is useful if you want
+ *                                                       to dynamically spawn objects in the subtree.
+ *
+ * Flags passed to g_dbus_connection_register_subtree().
+ */
+typedef enum
+{
+  G_DBUS_SUBTREE_FLAGS_NONE = 0,
+  G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES = (1<<0),
+} GDBusSubtreeFlags;
+
+/**
+ * GDBusServerFlags:
+ * @G_DBUS_SERVER_FLAGS_NONE: No flags set.
+ * @G_DBUS_SERVER_FLAGS_RUN_IN_THREAD: All #GDBusServer::new-connection
+ * signals will run in separated dedicated threads (see signal for
+ * details).
+ * @G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS: Allow the anonymous
+ * authentication method.
+ *
+ * Flags used when creating a #GDBusServer.
+ */
+typedef enum
+{
+  G_DBUS_SERVER_FLAGS_NONE = 0,
+  G_DBUS_SERVER_FLAGS_RUN_IN_THREAD = (1<<0),
+  G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<1)
+} GDBusServerFlags;
+
 G_END_DECLS
 
 #endif /* __GIO_ENUMS_H__ */
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 2abc87d..22a0026 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -333,6 +333,25 @@ struct _GOutputVector {
   gsize size;
 };
 
+typedef struct _GCredentials                  GCredentials;
+typedef struct _GUnixCredentialsMessage       GUnixCredentialsMessage;
+typedef struct _GDBusMessage                  GDBusMessage;
+typedef struct _GDBusConnection               GDBusConnection;
+typedef struct _GMessageBusConnection         GMessageBusConnection;
+typedef struct _GDBusProxy                    GDBusProxy;
+typedef struct _GDBusMethodInvocation         GDBusMethodInvocation;
+typedef struct _GDBusServer                   GDBusServer;
+typedef struct _GDBusAuthObserver             GDBusAuthObserver;
+typedef struct _GDBusErrorEntry               GDBusErrorEntry;
+typedef struct _GDBusInterfaceVTable          GDBusInterfaceVTable;
+typedef struct _GDBusSubtreeVTable            GDBusSubtreeVTable;
+typedef struct _GDBusAnnotationInfo           GDBusAnnotationInfo;
+typedef struct _GDBusArgInfo                  GDBusArgInfo;
+typedef struct _GDBusMethodInfo               GDBusMethodInfo;
+typedef struct _GDBusSignalInfo               GDBusSignalInfo;
+typedef struct _GDBusPropertyInfo             GDBusPropertyInfo;
+typedef struct _GDBusInterfaceInfo            GDBusInterfaceInfo;
+typedef struct _GDBusNodeInfo                 GDBusNodeInfo;
 
 G_END_DECLS
 
diff --git a/gio/gunixcredentialsmessage.c b/gio/gunixcredentialsmessage.c
new file mode 100644
index 0000000..6af8c29
--- /dev/null
+++ b/gio/gunixcredentialsmessage.c
@@ -0,0 +1,341 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2009 Codethink Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: David Zeuthen <davidz redhat com>
+ */
+
+/**
+ * SECTION: gunixcredentialsmessage
+ * @title: GUnixCredentialsMessage
+ * @short_description: A GSocketControlMessage containing credentials
+ * @see_also: #GUnixConnection, #GSocketControlMessage
+ *
+ * This #GSocketControlMessage contains a #GCredentials instance.  It
+ * may be sent using g_socket_send_message() and received using
+ * g_socket_receive_message() over UNIX sockets (ie: sockets in the
+ * %G_SOCKET_ADDRESS_UNIX family).
+ *
+ * For an easier way to send and receive credentials over
+ * stream-oriented UNIX sockets, see g_unix_connection_send_credentials() and
+ * g_unix_connection_receive_credentials().
+ **/
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+#ifdef __linux__
+
+#define _GNU_SOURCE
+#define __USE_GNU
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <fcntl.h>
+#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 1
+
+#else
+/* TODO: please add support for your UNIX flavor */
+#define G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 0
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#include <string.h>
+#include <errno.h>
+
+#include "gunixcredentialsmessage.h"
+#include "gcredentials.h"
+
+struct _GUnixCredentialsMessagePrivate
+{
+  GCredentials *credentials;
+};
+
+enum
+{
+  PROP_0,
+  PROP_CREDENTIALS
+};
+
+G_DEFINE_TYPE (GUnixCredentialsMessage, g_unix_credentials_message, G_TYPE_SOCKET_CONTROL_MESSAGE);
+
+static gsize
+g_unix_credentials_message_get_size (GSocketControlMessage *message)
+{
+#ifdef __linux__
+  return sizeof (struct ucred);
+#else
+  return 0;
+#endif
+}
+
+static int
+g_unix_credentials_message_get_level (GSocketControlMessage *message)
+{
+  return SOL_SOCKET;
+}
+
+static int
+g_unix_credentials_message_get_msg_type (GSocketControlMessage *message)
+{
+#ifdef __linux__
+  return SCM_CREDENTIALS;
+#else
+  return 0;
+#endif
+}
+
+static GSocketControlMessage *
+g_unix_credentials_message_deserialize (gint     level,
+                                        gint     type,
+                                        gsize    size,
+                                        gpointer data)
+{
+  GSocketControlMessage *message;
+
+  message = NULL;
+
+#ifdef __linux__
+  {
+    GCredentials *credentials;
+    struct ucred *ucred;
+
+    if (level != SOL_SOCKET || type != SCM_CREDENTIALS)
+      goto out;
+
+    if (size != sizeof (struct ucred))
+      {
+        g_warning ("Expected a struct ucred (%" G_GSIZE_FORMAT " bytes) but "
+                   "got %" G_GSIZE_FORMAT " bytes of data",
+                   sizeof (struct ucred),
+                   size);
+        goto out;
+      }
+
+    ucred = data;
+
+    credentials = g_credentials_new ();
+    g_credentials_set_unix_user (credentials, ucred->uid);
+    g_credentials_set_unix_group (credentials, ucred->gid);
+    g_credentials_set_unix_process (credentials, ucred->pid);
+    message = g_unix_credentials_message_new_with_credentials (credentials);
+    g_object_unref (credentials);
+ out:
+    ;
+  }
+#endif
+
+  return message;
+}
+
+static void
+g_unix_credentials_message_serialize (GSocketControlMessage *_message,
+                                      gpointer               data)
+{
+  GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (_message);
+#ifdef __linux__
+  {
+    struct ucred *ucred = data;
+    ucred->uid = g_credentials_get_unix_user (message->priv->credentials);
+    ucred->gid = g_credentials_get_unix_group (message->priv->credentials);
+    ucred->pid = g_credentials_get_unix_process (message->priv->credentials);
+  }
+#endif
+}
+
+static void
+g_unix_credentials_message_finalize (GObject *object)
+{
+  GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+  if (message->priv->credentials != NULL)
+    g_object_unref (message->priv->credentials);
+
+  if (G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->finalize (object);
+}
+
+static void
+g_unix_credentials_message_init (GUnixCredentialsMessage *message)
+{
+  message->priv = G_TYPE_INSTANCE_GET_PRIVATE (message,
+                                               G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+                                               GUnixCredentialsMessagePrivate);
+}
+
+static void
+g_unix_credentials_message_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_CREDENTIALS:
+      g_value_set_object (value, message->priv->credentials);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_unix_credentials_message_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_CREDENTIALS:
+      message->priv->credentials = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+g_unix_credentials_message_constructed (GObject *object)
+{
+  GUnixCredentialsMessage *message = G_UNIX_CREDENTIALS_MESSAGE (object);
+
+  if (message->priv->credentials == NULL)
+    message->priv->credentials = g_credentials_new_for_process ();
+
+  if (G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (g_unix_credentials_message_parent_class)->constructed (object);
+}
+
+static void
+g_unix_credentials_message_class_init (GUnixCredentialsMessageClass *class)
+{
+  GSocketControlMessageClass *scm_class;
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (class, sizeof (GUnixCredentialsMessagePrivate));
+
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->get_property = g_unix_credentials_message_get_property;
+  gobject_class->set_property = g_unix_credentials_message_set_property;
+  gobject_class->finalize = g_unix_credentials_message_finalize;
+  gobject_class->constructed = g_unix_credentials_message_constructed;
+
+  scm_class = G_SOCKET_CONTROL_MESSAGE_CLASS (class);
+  scm_class->get_size = g_unix_credentials_message_get_size;
+  scm_class->get_level = g_unix_credentials_message_get_level;
+  scm_class->get_type = g_unix_credentials_message_get_msg_type;
+  scm_class->serialize = g_unix_credentials_message_serialize;
+  scm_class->deserialize = g_unix_credentials_message_deserialize;
+
+  /**
+   * GUnixCredentialsMessage:credentials:
+   *
+   * The credentials stored in the message.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CREDENTIALS,
+                                   g_param_spec_object ("credentials",
+                                                        _("Credentials"),
+                                                        _("The credentials stored in the message"),
+                                                        G_TYPE_CREDENTIALS,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_unix_credentials_message_is_supported:
+ *
+ * Checks if passing a #GCredential on a #GSocket is supported on this platform.
+ *
+ * Returns: %TRUE if supported, %FALSE otherwise
+ *
+ * Since: 2.26
+ */
+gboolean
+g_unix_credentials_message_is_supported (void)
+{
+  return G_UNIX_CREDENTIALS_MESSAGE_SUPPORTED;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_unix_credentials_message_new:
+ *
+ * Creates a new #GUnixCredentialsMessage with credentials matching the current processes.
+ *
+ * Returns: a new #GUnixCredentialsMessage
+ *
+ * Since: 2.26
+ */
+GSocketControlMessage *
+g_unix_credentials_message_new (void)
+{
+  g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL);
+  return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+                       NULL);
+}
+
+/**
+ * g_unix_credentials_message_new:
+ * @credentials: A #GCredentials object.
+ *
+ * Creates a new #GUnixCredentialsMessage holding @credentials.
+ *
+ * Returns: a new #GUnixCredentialsMessage
+ *
+ * Since: 2.26
+ */
+GSocketControlMessage *
+g_unix_credentials_message_new_with_credentials (GCredentials *credentials)
+{
+  g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL);
+  g_return_val_if_fail (g_unix_credentials_message_is_supported (), NULL);
+  return g_object_new (G_TYPE_UNIX_CREDENTIALS_MESSAGE,
+                       "credentials", credentials,
+                       NULL);
+}
+
+/**
+ * g_unix_credentials_message_get_credentials:
+ * @message: A #GUnixCredentialsMessage.
+ *
+ * Gets the credentials stored in @message.
+ *
+ * Returns: A #GCredentials instance. Do not free, it is owned by @message.
+ */
+GCredentials *
+g_unix_credentials_message_get_credentials (GUnixCredentialsMessage *message)
+{
+  g_return_val_if_fail (G_IS_UNIX_CREDENTIALS_MESSAGE (message), NULL);
+  return message->priv->credentials;
+}
+
diff --git a/gio/gunixcredentialsmessage.h b/gio/gunixcredentialsmessage.h
new file mode 100644
index 0000000..1db0146
--- /dev/null
+++ b/gio/gunixcredentialsmessage.h
@@ -0,0 +1,68 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2009 Codethink Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Authors: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_UNIX_CREDENTIALS_MESSAGE_H__
+#define __G_UNIX_CREDENTIALS_MESSAGE_H__
+
+#include <gio/giotypes.h>
+#include <gio/gsocketcontrolmessage.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_UNIX_CREDENTIALS_MESSAGE         (g_unix_credentials_message_get_type ())
+#define G_UNIX_CREDENTIALS_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessage))
+#define G_UNIX_CREDENTIALS_MESSAGE_CLASS(c)     (G_TYPE_CHECK_CLASS_CAST ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass))
+#define G_IS_UNIX_CREDENTIALS_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE))
+#define G_IS_UNIX_CREDENTIALS_MESSAGE_CLASS(c)  (G_TYPE_CHECK_CLASS_TYPE ((c), G_TYPE_UNIX_CREDENTIALS_MESSAGE))
+#define G_UNIX_CREDENTIALS_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_UNIX_CREDENTIALS_MESSAGE, GUnixCredentialsMessageClass))
+
+typedef struct _GUnixCredentialsMessagePrivate  GUnixCredentialsMessagePrivate;
+typedef struct _GUnixCredentialsMessageClass    GUnixCredentialsMessageClass;
+
+struct _GUnixCredentialsMessageClass
+{
+  GSocketControlMessageClass parent_class;
+
+  /*< private >*/
+
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+};
+
+struct _GUnixCredentialsMessage
+{
+  GSocketControlMessage parent_instance;
+  GUnixCredentialsMessagePrivate *priv;
+};
+
+GType                  g_unix_credentials_message_get_type             (void) G_GNUC_CONST;
+GSocketControlMessage *g_unix_credentials_message_new                  (void);
+GSocketControlMessage *g_unix_credentials_message_new_with_credentials (GCredentials *credentials);
+GCredentials          *g_unix_credentials_message_get_credentials      (GUnixCredentialsMessage *message);
+
+gboolean               g_unix_credentials_message_is_supported         (void);
+
+G_END_DECLS
+
+#endif /* __G_UNIX_CREDENTIALS_MESSAGE_H__ */
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 8be6313..4aab457 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -1,3 +1,5 @@
+NULL =
+
 include $(top_srcdir)/Makefile.decl
 
 INCLUDES = 			\
@@ -35,19 +37,39 @@ TEST_PROGS +=	 		\
 	srvtarget		\
 	contexts		\
 	gsettings		\
-	gschema-compile
+	gschema-compile		\
+	gdbus-addresses		\
+	gdbus-connection	\
+	gdbus-names		\
+	gdbus-proxy		\
+	gdbus-introspection	\
+	gdbus-threading		\
+	gdbus-export		\
+	gdbus-error		\
+	gdbus-peer		\
+	gdbus-exit-on-close	\
+	$(NULL)
+
+SAMPLE_PROGS = 				\
+	resolver			\
+	socket-server			\
+	socket-client			\
+	echo-server			\
+	httpd				\
+	send-data			\
+	filter-cat			\
+	gdbus-example-own-name		\
+	gdbus-example-watch-name	\
+	gdbus-example-watch-proxy	\
+	gdbus-example-server		\
+	gdbus-example-subtree		\
+	gdbus-example-peer		\
+	$(NULL)
 
-SAMPLE_PROGS = 			\
-	resolver		\
-	socket-server		\
-	socket-client		\
-	echo-server		\
-	httpd			\
-	send-data		\
-	filter-cat
 
 if OS_UNIX
 TEST_PROGS += live-g-file desktop-app-info unix-fd #unix-streams
+SAMPLE_PROGS +=	gdbus-example-unix-fd-client
 endif
 
 if OS_WIN32
@@ -150,6 +172,64 @@ gsettings_LDADD		  = $(progs_ldadd)
 gschema_compile_SOURCES	  = gschema-compile.c
 gschema_compile_LDADD	  = $(progs_ldadd)
 
+if HAVE_DBUS1
+TEST_PROGS += gdbus-serialization
+gdbus_serialization_SOURCES = gdbus-serialization.c gdbus-tests.h gdbus-tests.c
+gdbus_serialization_CFLAGS = $(DBUS1_CFLAGS)
+gdbus_serialization_LDADD = $(progs_ldadd) $(DBUS1_LIBS)
+endif
+
+gdbus_addresses_SOURCES = gdbus-addresses.c
+gdbus_addresses_LDADD = $(progs_ldadd)
+
+gdbus_connection_SOURCES = gdbus-connection.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_connection_LDADD = $(progs_ldadd)
+
+gdbus_names_SOURCES = gdbus-names.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_names_LDADD = $(progs_ldadd)
+
+gdbus_proxy_SOURCES = gdbus-proxy.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_proxy_LDADD = $(progs_ldadd)
+
+gdbus_introspection_SOURCES = gdbus-introspection.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_introspection_LDADD = $(progs_ldadd)
+
+gdbus_threading_SOURCES = gdbus-threading.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_threading_LDADD = $(progs_ldadd)
+
+gdbus_export_SOURCES = gdbus-export.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_export_LDADD = $(progs_ldadd)
+
+gdbus_error_SOURCES = gdbus-error.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_error_LDADD = $(progs_ldadd)
+
+gdbus_peer_SOURCES = gdbus-peer.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_peer_LDADD = $(progs_ldadd)
+
+gdbus_exit_on_close_SOURCES = gdbus-exit-on-close.c gdbus-sessionbus.c gdbus-sessionbus.h gdbus-tests.h gdbus-tests.c
+gdbus_exit_on_close_LDADD = $(progs_ldadd)
+
+gdbus_example_watch_name_SOURCES = gdbus-example-watch-name.c
+gdbus_example_watch_name_LDADD   = $(progs_ldadd)
+
+gdbus_example_watch_proxy_SOURCES = gdbus-example-watch-proxy.c
+gdbus_example_watch_proxy_LDADD   = $(progs_ldadd)
+
+gdbus_example_own_name_SOURCES = gdbus-example-own-name.c
+gdbus_example_own_name_LDADD   = $(progs_ldadd)
+
+gdbus_example_server_SOURCES = gdbus-example-server.c
+gdbus_example_server_LDADD   = $(progs_ldadd)
+
+gdbus_example_unix_fd_client_SOURCES = gdbus-example-unix-fd-client.c
+gdbus_example_unix_fd_client_LDADD   = $(progs_ldadd)
+
+gdbus_example_subtree_SOURCES = gdbus-example-subtree.c
+gdbus_example_subtree_LDADD   = $(progs_ldadd)
+
+gdbus_example_peer_SOURCES = gdbus-example-peer.c
+gdbus_example_peer_LDADD   = $(progs_ldadd)
+
 EXTRA_DIST += \
 	socket-common.c						\
 	org.gtk.test.gschema					\
diff --git a/gio/tests/gdbus-addresses.c b/gio/tests/gdbus-addresses.c
new file mode 100644
index 0000000..97e5922
--- /dev/null
+++ b/gio/tests/gdbus-addresses.c
@@ -0,0 +1,77 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#ifdef G_OS_UNIX
+static void
+test_unix_address (void)
+{
+  g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar", NULL));
+  g_assert (g_dbus_is_supported_address ("unix:path=/tmp/dbus-test", NULL));
+  g_assert (g_dbus_is_supported_address ("unix:abstract=/tmp/dbus-another-test", NULL));
+  g_assert (g_dbus_is_address ("unix:foo=bar"));
+  g_assert (!g_dbus_is_supported_address ("unix:foo=bar", NULL));
+  g_assert (!g_dbus_is_address ("unix:path=/foo;abstract=/bar"));
+  g_assert (!g_dbus_is_supported_address ("unix:path=/foo;abstract=/bar", NULL));
+  g_assert (g_dbus_is_supported_address ("unix:path=/tmp/concrete;unix:abstract=/tmp/abstract", NULL));
+  g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar"));
+
+  g_assert (g_dbus_is_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid"));
+  g_assert (!g_dbus_is_supported_address ("some-imaginary-transport:foo=bar;unix:path=/this/is/valid", NULL));
+}
+#endif
+
+static void
+test_nonce_tcp_address (void)
+{
+  g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar", NULL));
+  g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv6", NULL));
+  g_assert (g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=ipv4", NULL));
+
+  g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=42,noncefile=/foo/bar,family=blah", NULL));
+  g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=localhost,port=420000,noncefile=/foo/bar,family=ipv4", NULL));
+  g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=x42,noncefile=/foo/bar,family=ipv4", NULL));
+  g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=42x,noncefile=/foo/bar,family=ipv4", NULL));
+  g_assert (!g_dbus_is_supported_address ("nonce-tcp:host=,port=420000,noncefile=/foo/bar,family=ipv4", NULL));
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+#ifdef G_OS_UNIX
+  g_test_add_func ("/gdbus/unix-address", test_unix_address);
+#endif
+  g_test_add_func ("/gdbus/nonce-tcp-address", test_nonce_tcp_address);
+  return g_test_run();
+}
+
diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c
new file mode 100644
index 0000000..1174d6f
--- /dev/null
+++ b/gio/tests/gdbus-connection.c
@@ -0,0 +1,653 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection life-cycle testing */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_life_cycle (void)
+{
+  GDBusConnection *c;
+  GDBusConnection *c2;
+  GError *error;
+
+  error = NULL;
+
+  /*
+   * Check for correct behavior when no bus is present
+   *
+   */
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  _g_assert_error_domain (error, G_IO_ERROR);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_assert (c == NULL);
+  g_error_free (error);
+  error = NULL;
+
+  /*
+   *  Check for correct behavior when a bus is present
+   */
+  session_bus_up ();
+  /* case 1 */
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+  g_assert (!g_dbus_connection_is_closed (c));
+
+  /*
+   * Check that singleton handling work
+   */
+  c2 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (c == c2);
+  g_object_unref (c2);
+
+  /*
+   * Check that private connections work
+   */
+  c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (c != c2);
+  g_object_unref (c2);
+
+  c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (!g_dbus_connection_is_closed (c2));
+  g_dbus_connection_close (c2);
+  _g_assert_signal_received (c2, "closed");
+  g_assert (g_dbus_connection_is_closed (c2));
+  g_object_unref (c2);
+
+  /*
+   *  Check for correct behavior when the bus goes away
+   *
+   */
+  g_assert (!g_dbus_connection_is_closed (c));
+  g_dbus_connection_set_exit_on_close (c, FALSE);
+  session_bus_down ();
+  if (!g_dbus_connection_is_closed (c))
+    _g_assert_signal_received (c, "closed");
+  g_assert (g_dbus_connection_is_closed (c));
+
+  _g_object_wait_for_single_ref (c);
+  g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that sending and receiving messages work as expected */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+msg_cb_expect_error_disconnected (GDBusConnection *connection,
+                                  GAsyncResult    *res,
+                                  gpointer         user_data)
+{
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_unknown_method (GDBusConnection *connection,
+                                    GAsyncResult    *res,
+                                    gpointer         user_data)
+{
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
+  g_assert (g_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_success (GDBusConnection *connection,
+                       GAsyncResult    *res,
+                       gpointer         user_data)
+{
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (GDBusConnection *connection,
+                               GAsyncResult    *res,
+                               gpointer         user_data)
+{
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_cancelled_2 (GDBusConnection *connection,
+                                 GAsyncResult    *res,
+                                 gpointer         user_data)
+{
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_send (void)
+{
+  GDBusConnection *c;
+  GCancellable *ca;
+
+  session_bus_up ();
+
+  /* First, get an unopened connection */
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c != NULL);
+  g_assert (!g_dbus_connection_is_closed (c));
+
+  /*
+   * Check that we never actually send a message if the GCancellable
+   * is already cancelled - i.e.  we should get #G_IO_ERROR_CANCELLED
+   * when the actual connection is not up.
+   */
+  ca = g_cancellable_new ();
+  g_cancellable_cancel (ca);
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   NULL);
+  g_main_loop_run (loop);
+  g_object_unref (ca);
+
+  /*
+   * Check that we get a reply to the GetId() method call.
+   */
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_success,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  /*
+   * Check that we get an error reply to the NonExistantMethod() method call.
+   */
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "NonExistantMethod",     /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_unknown_method,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  /*
+   * Check that cancellation works when the message is already in flight.
+   */
+  ca = g_cancellable_new ();
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled_2,
+                                   NULL);
+  g_cancellable_cancel (ca);
+  g_main_loop_run (loop);
+  g_object_unref (ca);
+
+  /*
+   * Check that we get an error when sending to a connection that is disconnected.
+   */
+  g_dbus_connection_set_exit_on_close (c, FALSE);
+  session_bus_down ();
+  _g_assert_signal_received (c, "closed");
+  g_assert (g_dbus_connection_is_closed (c));
+
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_disconnected,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  _g_object_wait_for_single_ref (c);
+  g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection signal tests */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_signal_handler (GDBusConnection  *connection,
+                                const gchar      *sender_name,
+                                const gchar      *object_path,
+                                const gchar      *interface_name,
+                                const gchar      *signal_name,
+                                GVariant         *parameters,
+                                gpointer         user_data)
+{
+  gint *counter = user_data;
+  *counter += 1;
+
+  /*g_debug ("in test_connection_signal_handler (sender=%s path=%s interface=%s member=%s)",
+           sender_name,
+           object_path,
+           interface_name,
+           signal_name);*/
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+test_connection_signal_quit_mainloop (gpointer user_data)
+{
+  gboolean *quit_mainloop_fired = user_data;
+  *quit_mainloop_fired = TRUE;
+  g_main_loop_quit (loop);
+  return TRUE;
+}
+
+static void
+test_connection_signals (void)
+{
+  GDBusConnection *c1;
+  GDBusConnection *c2;
+  GDBusConnection *c3;
+  guint s1;
+  guint s2;
+  guint s3;
+  gint count_s1;
+  gint count_s2;
+  gint count_name_owner_changed;
+  GError *error;
+  gboolean ret;
+  GVariant *result;
+
+  error = NULL;
+
+  /*
+   * Bring up first separate connections
+   */
+  session_bus_up ();
+  /* if running with dbus-monitor, it claims the name :1.0 - so if we don't run with the monitor
+   * emulate this
+   */
+  if (g_getenv ("G_DBUS_MONITOR") == NULL)
+    {
+      c1 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+      g_assert (c1 != NULL);
+      g_assert (!g_dbus_connection_is_closed (c1));
+      g_object_unref (c1);
+    }
+  c1 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c1 != NULL);
+  g_assert (!g_dbus_connection_is_closed (c1));
+  g_assert_cmpstr (g_dbus_connection_get_unique_name (c1), ==, ":1.1");
+
+  /*
+   * Install two signal handlers for the first connection
+   *
+   *  - Listen to the signal "Foo" from :1.2 (e.g. c2)
+   *  - Listen to the signal "Foo" from anyone (e.g. both c2 and c3)
+   *
+   * and then count how many times this signal handler was invoked.
+   */
+  s1 = g_dbus_connection_signal_subscribe (c1,
+                                           ":1.2",
+                                           "org.gtk.GDBus.ExampleInterface",
+                                           "Foo",
+                                           "/org/gtk/GDBus/ExampleInterface",
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_s1,
+                                           NULL);
+  s2 = g_dbus_connection_signal_subscribe (c1,
+                                           NULL, /* match any sender */
+                                           "org.gtk.GDBus.ExampleInterface",
+                                           "Foo",
+                                           "/org/gtk/GDBus/ExampleInterface",
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_s2,
+                                           NULL);
+  s3 = g_dbus_connection_signal_subscribe (c1,
+                                           "org.freedesktop.DBus",  /* sender */
+                                           "org.freedesktop.DBus",  /* interface */
+                                           "NameOwnerChanged",      /* member */
+                                           "/org/freedesktop/DBus", /* path */
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_name_owner_changed,
+                                           NULL);
+  g_assert (s1 != 0);
+  g_assert (s2 != 0);
+  g_assert (s3 != 0);
+
+  count_s1 = 0;
+  count_s2 = 0;
+  count_name_owner_changed = 0;
+
+  /*
+   * Bring up two other connections
+   */
+  c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c2 != NULL);
+  g_assert (!g_dbus_connection_is_closed (c2));
+  g_assert_cmpstr (g_dbus_connection_get_unique_name (c2), ==, ":1.2");
+  c3 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c3 != NULL);
+  g_assert (!g_dbus_connection_is_closed (c3));
+  g_assert_cmpstr (g_dbus_connection_get_unique_name (c3), ==, ":1.3");
+
+  /*
+   * Make c2 emit "Foo" - we should catch it twice
+   *
+   * Note that there is no way to be sure that the signal subscriptions
+   * on c1 are effective yet - for all we know, the AddMatch() messages
+   * could sit waiting in a buffer somewhere between this process and
+   * the message bus. And emitting signals on c2 (a completely other
+   * socket!) will not necessarily change this.
+   *
+   * To ensure this is not the case, do a synchronous call on c1.
+   */
+  result = g_dbus_connection_invoke_method_sync (c1,
+                                                 "org.freedesktop.DBus",  /* bus name */
+                                                 "/org/freedesktop/DBus", /* object path */
+                                                 "org.freedesktop.DBus",  /* interface name */
+                                                 "GetId",                 /* method name */
+                                                 NULL,                    /* parameters */
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_unref (result);
+  /* now, emit the signal on c2 */
+  ret = g_dbus_connection_emit_signal (c2,
+                                       NULL, /* destination bus name */
+                                       "/org/gtk/GDBus/ExampleInterface",
+                                       "org.gtk.GDBus.ExampleInterface",
+                                       "Foo",
+                                       NULL,
+                                       &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+  while (!(count_s1 == 1 && count_s2 == 1))
+    g_main_loop_run (loop);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 1);
+
+  /*
+   * Make c3 emit "Foo" - we should catch it only once
+   */
+  ret = g_dbus_connection_emit_signal (c3,
+                                       NULL, /* destination bus name */
+                                       "/org/gtk/GDBus/ExampleInterface",
+                                       "org.gtk.GDBus.ExampleInterface",
+                                       "Foo",
+                                       NULL,
+                                       &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+  while (!(count_s1 == 1 && count_s2 == 2))
+    g_main_loop_run (loop);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 2);
+
+  /*
+   * Also to check the total amount of NameOwnerChanged signals - use a 5 second ceiling
+   * to avoid spinning forever
+   */
+  gboolean quit_mainloop_fired;
+  guint quit_mainloop_id;
+  quit_mainloop_fired = FALSE;
+  quit_mainloop_id = g_timeout_add (5000, test_connection_signal_quit_mainloop, &quit_mainloop_fired);
+  while (count_name_owner_changed != 2 && !quit_mainloop_fired)
+    g_main_loop_run (loop);
+  g_source_remove (quit_mainloop_id);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 2);
+  g_assert_cmpint (count_name_owner_changed, ==, 2);
+
+  g_dbus_connection_signal_unsubscribe (c1, s1);
+  g_dbus_connection_signal_unsubscribe (c1, s2);
+  g_dbus_connection_signal_unsubscribe (c1, s3);
+
+  _g_object_wait_for_single_ref (c1);
+  _g_object_wait_for_single_ref (c2);
+  _g_object_wait_for_single_ref (c3);
+
+  g_object_unref (c1);
+  g_object_unref (c2);
+  g_object_unref (c3);
+
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  guint num_handled;
+  guint32 serial;
+} FilterData;
+
+static gboolean
+filter_func (GDBusConnection *connection,
+             GDBusMessage    *message,
+             gpointer         user_data)
+{
+  FilterData *data = user_data;
+  guint32 reply_serial;
+
+  reply_serial = g_dbus_message_get_reply_serial (message);
+  if (reply_serial == data->serial)
+    data->num_handled += 1;
+
+  return FALSE;
+}
+
+static void
+test_connection_filter (void)
+{
+  GDBusConnection *c;
+  FilterData data;
+  GDBusMessage *m;
+  GDBusMessage *r;
+  GError *error;
+  guint filter_id;
+
+  memset (&data, '\0', sizeof (FilterData));
+
+  session_bus_up ();
+
+  error = NULL;
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+
+  filter_id = g_dbus_connection_add_filter (c,
+                                            filter_func,
+                                            &data,
+                                            NULL);
+
+  m = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
+                                      "/org/freedesktop/DBus", /* path */
+                                      "org.freedesktop.DBus", /* interface */
+                                      "GetNameOwner");
+  g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus"));
+  error = NULL;
+  g_dbus_connection_send_message (c, m, &data.serial, &error);
+  g_assert_no_error (error);
+
+  while (data.num_handled == 0)
+    g_thread_yield ();
+
+  g_dbus_connection_send_message (c, m, &data.serial, &error);
+  g_assert_no_error (error);
+
+  while (data.num_handled == 1)
+    g_thread_yield ();
+
+  r = g_dbus_connection_send_message_with_reply_sync (c,
+                                                      m,
+                                                      -1,
+                                                      &data.serial,
+                                                      NULL, /* GCancellable */
+                                                      &error);
+  g_assert_no_error (error);
+  g_assert (r != NULL);
+  g_object_unref (r);
+  g_assert_cmpint (data.num_handled, ==, 3);
+
+  g_dbus_connection_remove_filter (c, filter_id);
+
+  r = g_dbus_connection_send_message_with_reply_sync (c,
+                                                      m,
+                                                      -1,
+                                                      &data.serial,
+                                                      NULL, /* GCancellable */
+                                                      &error);
+  g_assert_no_error (error);
+  g_assert (r != NULL);
+  g_object_unref (r);
+  g_assert_cmpint (data.num_handled, ==, 3);
+
+  _g_object_wait_for_single_ref (c);
+  g_object_unref (c);
+  g_object_unref (m);
+
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/connection-life-cycle", test_connection_life_cycle);
+  g_test_add_func ("/gdbus/connection-send", test_connection_send);
+  g_test_add_func ("/gdbus/connection-signals", test_connection_signals);
+  g_test_add_func ("/gdbus/connection-filter", test_connection_filter);
+  return g_test_run();
+}
diff --git a/gio/tests/gdbus-error.c b/gio/tests/gdbus-error.c
new file mode 100644
index 0000000..3427710
--- /dev/null
+++ b/gio/tests/gdbus-error.c
@@ -0,0 +1,198 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that registered errors are properly mapped */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_registered_error (const gchar *given_dbus_error_name,
+                        GQuark       error_domain,
+                        gint         error_code)
+{
+  GError *error;
+  gchar *dbus_error_name;
+
+  error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, error_domain, error_code);
+  g_assert (g_dbus_error_is_remote_error (error));
+  g_assert (g_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+  dbus_error_name = g_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+  g_error_free (error);
+}
+
+static void
+test_registered_errors (void)
+{
+  /* Here we check that we are able to map to GError and back for registered
+   * errors.
+   *
+   * For example, if "org.freedesktop.DBus.Error.AddressInUse" is
+   * associated with (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED), check
+   * that
+   *
+   *  - Creating a GError for e.g. "org.freedesktop.DBus.Error.AddressInUse"
+   *    has (error_domain, code) == (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED)
+   *
+   *  - That it is possible to recover e.g. "org.freedesktop.DBus.Error.AddressInUse"
+   *    as the D-Bus error name when dealing with an error with (error_domain, code) ==
+   *    (G_DBUS_ERROR, G_DBUS_ERROR_DBUS_FAILED)
+   *
+   * We just check a couple of well-known errors.
+   */
+  check_registered_error ("org.freedesktop.DBus.Error.Failed",
+                          G_DBUS_ERROR,
+                          G_DBUS_ERROR_FAILED);
+  check_registered_error ("org.freedesktop.DBus.Error.AddressInUse",
+                          G_DBUS_ERROR,
+                          G_DBUS_ERROR_ADDRESS_IN_USE);
+  check_registered_error ("org.freedesktop.DBus.Error.UnknownMethod",
+                          G_DBUS_ERROR,
+                          G_DBUS_ERROR_UNKNOWN_METHOD);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_unregistered_error (const gchar *given_dbus_error_name)
+{
+  GError *error;
+  gchar *dbus_error_name;
+
+  error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+  g_assert (g_dbus_error_is_remote_error (error));
+  dbus_error_name = g_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+
+  /* strip the message */
+  g_assert (g_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+
+  /* check that we can no longer recover the D-Bus error name */
+  g_assert (g_dbus_error_get_remote_error (error) == NULL);
+
+  g_error_free (error);
+
+}
+
+static void
+test_unregistered_errors (void)
+{
+  /* Here we check that we are able to map to GError and back for unregistered
+   * errors.
+   *
+   * For example, if "com.example.Error.Failed" is not registered, then check
+   *
+   *  - Creating a GError for e.g. "com.example.Error.Failed" has (error_domain, code) ==
+   *    (G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)
+   *
+   *  - That it is possible to recover e.g. "com.example.Error.Failed" from that
+   *    GError.
+   *
+   * We just check a couple of random errors.
+   */
+
+  check_unregistered_error ("com.example.Error.Failed");
+  check_unregistered_error ("foobar.buh");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_transparent_gerror (GQuark error_domain,
+                          gint   error_code)
+{
+  GError *error;
+  gchar *given_dbus_error_name;
+  gchar *dbus_error_name;
+
+  error = g_error_new (error_domain, error_code, "test message");
+  given_dbus_error_name = g_dbus_error_encode_gerror (error);
+  g_assert (g_str_has_prefix (given_dbus_error_name, "org.gtk.GDBus.UnmappedGError.Quark"));
+  g_error_free (error);
+
+  error = g_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, error_domain, error_code);
+  g_assert (g_dbus_error_is_remote_error (error));
+  dbus_error_name = g_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+  g_free (given_dbus_error_name);
+
+  /* strip the message */
+  g_assert (g_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+
+  /* check that we can no longer recover the D-Bus error name */
+  g_assert (g_dbus_error_get_remote_error (error) == NULL);
+
+  g_error_free (error);
+}
+
+static void
+test_transparent_gerror (void)
+{
+  /* Here we check that we are able to transparent pass unregistered GError's
+   * over the wire.
+   *
+   * For example, if G_IO_ERROR_FAILED is not registered, then check
+   *
+   *  - g_dbus_error_encode_gerror() returns something of the form
+   *    org.gtk.GDBus.UnmappedGError.Quark_HEXENCODED_QUARK_NAME_.Code_ERROR_CODE
+   *
+   *  - mapping back the D-Bus error name gives us G_IO_ERROR_FAILED
+   *
+   *  - That it is possible to recover the D-Bus error name from the
+   *    GError.
+   *
+   * We just check a couple of random errors.
+   */
+
+  check_transparent_gerror (G_IO_ERROR, G_IO_ERROR_FAILED);
+  check_transparent_gerror (G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/gdbus/registered-errors", test_registered_errors);
+  g_test_add_func ("/gdbus/unregistered-errors", test_unregistered_errors);
+  g_test_add_func ("/gdbus/transparent-gerror", test_transparent_gerror);
+
+  return g_test_run();
+}
diff --git a/gio/tests/gdbus-example-own-name.c b/gio/tests/gdbus-example-own-name.c
new file mode 100644
index 0000000..733c29e
--- /dev/null
+++ b/gio/tests/gdbus-example-own-name.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const gchar     *name,
+                 gpointer         user_data)
+{
+  /* This is where we'd export some objects on the bus */
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  g_print ("Acquired the name %s on the session bus\n", name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  g_print ("Lost the name %s on the session bus\n", name);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+  GBusNameOwnerFlags flags;
+  gboolean opt_replace;
+  gboolean opt_allow_replacement;
+  gchar *opt_name;
+  GOptionContext *opt_context;
+  GError *error;
+  GOptionEntry opt_entries[] =
+    {
+      { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing name if possible", NULL },
+      { "allow-replacement", 'a', 0, G_OPTION_ARG_NONE, &opt_allow_replacement, "Allow replacement", NULL },
+      { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to acquire", NULL },
+      { NULL}
+    };
+
+  g_type_init ();
+
+  error = NULL;
+  opt_name = NULL;
+  opt_replace = FALSE;
+  opt_allow_replacement = FALSE;
+  opt_context = g_option_context_new ("g_bus_own_name() example");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      return 1;
+    }
+  if (opt_name == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      return 1;
+    }
+
+  flags = G_BUS_NAME_OWNER_FLAGS_NONE;
+  if (opt_replace)
+    flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+  if (opt_allow_replacement)
+    flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+
+  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                             opt_name,
+                             flags,
+                             on_bus_acquired,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unown_name (owner_id);
+
+  return 0;
+}
diff --git a/gio/tests/gdbus-example-peer.c b/gio/tests/gdbus-example-peer.c
new file mode 100644
index 0000000..a1041d9
--- /dev/null
+++ b/gio/tests/gdbus-example-peer.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+/*
+
+Usage examples (modulo addresses / credentials).
+
+UNIX domain socket transport:
+
+ Server:
+   $ ./gdbus-example-peer --server --address unix:abstract=myaddr
+   Server is listening at: unix:abstract=myaddr
+   Client connected.
+   Peer credentials: GCredentials:unix-user=500,unix-group=500,unix-process=13378
+   Negotiated capabilities: unix-fd-passing=1
+   Client said: Hey, it's 1273093080 already!
+
+ Client:
+   $ ./gdbus-example-peer --address unix:abstract=myaddr
+   Connected.
+   Negotiated capabilities: unix-fd-passing=1
+   Server said: You said 'Hey, it's 1273093080 already!'. KTHXBYE!
+
+Nonce-secured TCP transport on the same host:
+
+ Server:
+   $ ./gdbus-example-peer --server --address nonce-tcp:
+   Server is listening at: nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV
+   Client connected.
+   Peer credentials: (no credentials received)
+   Negotiated capabilities: unix-fd-passing=0
+   Client said: Hey, it's 1273093206 already!
+
+ Client:
+   $ ./gdbus-example-peer -address nonce-tcp:host=localhost,port=43077,noncefile=/tmp/gdbus-nonce-file-X1ZNCV
+   Connected.
+   Negotiated capabilities: unix-fd-passing=0
+   Server said: You said 'Hey, it's 1273093206 already!'. KTHXBYE!
+
+TCP transport on two different hosts with a shared home directory:
+
+ Server:
+   host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0
+   Server is listening at: tcp:host=0.0.0.0,port=46314
+   Client connected.
+   Peer credentials: (no credentials received)
+   Negotiated capabilities: unix-fd-passing=0
+   Client said: Hey, it's 1273093337 already!
+
+ Client:
+   host2 $ ./gdbus-example-peer -a tcp:host=host1,port=46314
+   Connected.
+   Negotiated capabilities: unix-fd-passing=0
+   Server said: You said 'Hey, it's 1273093337 already!'. KTHXBYE!
+
+TCP transport on two different hosts without authentication:
+
+ Server:
+   host1 $ ./gdbus-example-peer --server --address tcp:host=0.0.0.0 --allow-anonymous
+   Server is listening at: tcp:host=0.0.0.0,port=59556
+   Client connected.
+   Peer credentials: (no credentials received)
+   Negotiated capabilities: unix-fd-passing=0
+   Client said: Hey, it's 1273093652 already!
+
+ Client:
+   host2 $ ./gdbus-example-peer -a tcp:host=host1,port=59556
+   Connected.
+   Negotiated capabilities: unix-fd-passing=0
+   Server said: You said 'Hey, it's 1273093652 already!'. KTHXBYE!
+
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gtk.GDBus.TestPeerInterface'>"
+  "    <method name='HelloWorld'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "  </interface>"
+  "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+  if (g_strcmp0 (method_name, "HelloWorld") == 0)
+    {
+      const gchar *greeting;
+      gchar *response;
+
+      g_variant_get (parameters, "(s)", &greeting);
+      response = g_strdup_printf ("You said '%s'. KTHXBYE!", greeting);
+      g_dbus_method_invocation_return_value (invocation,
+                                             g_variant_new ("(s)", response));
+      g_free (response);
+      g_print ("Client said: %s\n", greeting);
+    }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+  handle_method_call,
+  NULL,
+  NULL,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_new_connection (GDBusServer *server,
+                   GDBusConnection *connection,
+                   gpointer user_data)
+{
+  guint registration_id;
+  GCredentials *credentials;
+  gchar *s;
+
+  credentials = g_dbus_connection_get_peer_credentials (connection);
+  if (credentials == NULL)
+    s = g_strdup ("(no credentials received)");
+  else
+    s = g_credentials_to_string (credentials);
+
+
+  g_print ("Client connected.\n"
+           "Peer credentials: %s\n"
+           "Negotiated capabilities: unix-fd-passing=%d\n",
+           s,
+           g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+  g_object_ref (connection);
+  registration_id = g_dbus_connection_register_object (connection,
+                                                       "/org/gtk/GDBus/TestObject",
+                                                       "org.gtk.GDBus.TestPeerInterface",
+                                                       introspection_data->interfaces[0],
+                                                       &interface_vtable,
+                                                       NULL,  /* user_data */
+                                                       NULL,  /* user_data_free_func */
+                                                       NULL); /* GError** */
+  g_assert (registration_id > 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc, char *argv[])
+{
+  gint ret;
+  gboolean opt_server;
+  gchar *opt_address;
+  GOptionContext *opt_context;
+  gboolean opt_allow_anonymous;
+  GError *error;
+  GOptionEntry opt_entries[] =
+    {
+      { "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL },
+      { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use", NULL },
+      { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL },
+      { NULL}
+    };
+
+  ret = 1;
+
+  g_type_init ();
+
+  opt_address = NULL;
+  opt_server = FALSE;
+  opt_allow_anonymous = FALSE;
+
+  opt_context = g_option_context_new ("peer-to-peer example");
+  error = NULL;
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s\n", error->message);
+      g_error_free (error);
+      goto out;
+    }
+  if (opt_address == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      goto out;
+    }
+  if (!opt_server && opt_allow_anonymous)
+    {
+      g_printerr ("The --allow-anonymous option only makes sense when used with --server.\n");
+      goto out;
+    }
+
+  /* We are lazy here - we don't want to manually provide
+   * the introspection data structures - so we just build
+   * them from XML.
+   */
+  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+
+  if (opt_server)
+    {
+      GDBusServer *server;
+      gchar *guid;
+      GMainLoop *loop;
+      GDBusServerFlags server_flags;
+
+      guid = g_dbus_generate_guid ();
+
+      server_flags = G_DBUS_SERVER_FLAGS_NONE;
+      if (opt_allow_anonymous)
+        server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
+
+      error = NULL;
+      server = g_dbus_server_new_sync (opt_address,
+                                       server_flags,
+                                       guid,
+                                       NULL, /* GDBusAuthObserver */
+                                       NULL, /* GCancellable */
+                                       &error);
+      g_dbus_server_start (server);
+      g_free (guid);
+
+      if (server == NULL)
+        {
+          g_printerr ("Error creating server at address %s: %s\n", opt_address, error->message);
+          g_error_free (error);
+          goto out;
+        }
+      g_print ("Server is listening at: %s\n", g_dbus_server_get_client_address (server));
+      g_signal_connect (server,
+                        "new-connection",
+                        G_CALLBACK (on_new_connection),
+                        NULL);
+
+      loop = g_main_loop_new (NULL, FALSE);
+      g_main_loop_run (loop);
+
+      g_object_unref (server);
+      g_main_loop_unref (loop);
+    }
+  else
+    {
+      GDBusConnection *connection;
+      const gchar *greeting_response;
+      GVariant *value;
+      gchar *greeting;
+
+      error = NULL;
+      connection = g_dbus_connection_new_for_address_sync (opt_address,
+                                                           G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                                           NULL, /* GCancellable */
+                                                           &error);
+      if (connection == NULL)
+        {
+          g_printerr ("Error connecting to D-Bus address %s: %s\n", opt_address, error->message);
+          g_error_free (error);
+          goto out;
+        }
+
+      g_print ("Connected.\n"
+               "Negotiated capabilities: unix-fd-passing=%d\n",
+               g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+      greeting = g_strdup_printf ("Hey, it's %" G_GUINT64_FORMAT " already!", (guint64) time (NULL));
+      value = g_dbus_connection_invoke_method_sync (connection,
+                                                    NULL, /* bus_name */
+                                                    "/org/gtk/GDBus/TestObject",
+                                                    "org.gtk.GDBus.TestPeerInterface",
+                                                    "HelloWorld",
+                                                    g_variant_new ("(s)", greeting),
+                                                    G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                    -1,
+                                                    NULL,
+                                                    &error);
+      if (value == NULL)
+        {
+          g_printerr ("Error invoking HelloWorld(): %s\n", error->message);
+          g_error_free (error);
+          goto out;
+        }
+      g_variant_get (value, "(s)", &greeting_response);
+      g_print ("Server said: %s\n", greeting_response);
+      g_variant_unref (value);
+
+      g_object_unref (connection);
+    }
+  g_dbus_node_info_unref (introspection_data);
+
+  ret = 0;
+
+ out:
+  return ret;
+}
diff --git a/gio/tests/gdbus-example-server.c b/gio/tests/gdbus-example-server.c
new file mode 100644
index 0000000..fe667c0
--- /dev/null
+++ b/gio/tests/gdbus-example-server.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+
+#ifdef G_OS_UNIX
+/* For STDOUT_FILENO */
+#include <unistd.h>
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gtk.GDBus.TestInterface'>"
+  "    <method name='HelloWorld'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <method name='EmitSignal'>"
+  "      <arg type='d' name='speed_in_mph' direction='in'/>"
+  "    </method>"
+  "    <method name='GimmeStdout'/>"
+  "    <signal name='VelocityChanged'>"
+  "      <arg type='d' name='speed_in_mph'/>"
+  "      <arg type='s' name='speed_as_string'/>"
+  "    </signal>"
+  "    <property type='s' name='FluxCapicitorName' access='read'/>"
+  "    <property type='s' name='Title' access='readwrite'/>"
+  "    <property type='s' name='ReadingAlwaysThrowsError' access='read'/>"
+  "    <property type='s' name='WritingAlwaysThrowsError' access='readwrite'/>"
+  "    <property type='s' name='OnlyWritable' access='write'/>"
+  "    <property type='s' name='Foo' access='read'/>"
+  "    <property type='s' name='Bar' access='read'/>"
+  "  </interface>"
+  "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    GVariant              *parameters,
+                    GDBusMethodInvocation *invocation,
+                    gpointer               user_data)
+{
+  if (g_strcmp0 (method_name, "HelloWorld") == 0)
+    {
+      const gchar *greeting;
+
+      g_variant_get (parameters, "(s)", &greeting);
+
+      if (g_strcmp0 (greeting, "Return Unregistered") == 0)
+        {
+          g_dbus_method_invocation_return_error (invocation,
+                                                 G_IO_ERROR,
+                                                 G_IO_ERROR_FAILED_HANDLED,
+                                                 "As requested, here's a GError not registered (G_IO_ERROR_FAILED_HANDLED)");
+        }
+      else if (g_strcmp0 (greeting, "Return Registered") == 0)
+        {
+          g_dbus_method_invocation_return_error (invocation,
+                                                 G_DBUS_ERROR,
+                                                 G_DBUS_ERROR_MATCH_RULE_NOT_FOUND,
+                                                 "As requested, here's a GError that is registered (G_DBUS_ERROR_MATCH_RULE_NOT_FOUND)");
+        }
+      else if (g_strcmp0 (greeting, "Return Raw") == 0)
+        {
+          g_dbus_method_invocation_return_dbus_error (invocation,
+                                                      "org.gtk.GDBus.SomeErrorName",
+                                                      "As requested, here's a raw D-Bus error");
+        }
+      else
+        {
+          gchar *response;
+          response = g_strdup_printf ("You greeted me with '%s'. Thanks!", greeting);
+          g_dbus_method_invocation_return_value (invocation,
+                                                 g_variant_new ("(s)", response));
+          g_free (response);
+        }
+    }
+  else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+    {
+      GError *local_error;
+      gdouble speed_in_mph;
+      gchar *speed_as_string;
+
+      g_variant_get (parameters, "(d)", &speed_in_mph);
+      speed_as_string = g_strdup_printf ("%g mph!", speed_in_mph);
+
+      local_error = NULL;
+      g_dbus_connection_emit_signal (connection,
+                                     NULL,
+                                     object_path,
+                                     interface_name,
+                                     "VelocityChanged",
+                                     g_variant_new ("(ds)",
+                                                    speed_in_mph,
+                                                    speed_as_string),
+                                     &local_error);
+      g_assert_no_error (local_error);
+      g_free (speed_as_string);
+
+      g_dbus_method_invocation_return_value (invocation, NULL);
+    }
+  else if (g_strcmp0 (method_name, "GimmeStdout") == 0)
+    {
+#ifdef G_OS_UNIX
+      if (g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
+        {
+          GDBusMessage *reply;
+          GUnixFDList *fd_list;
+          GError *error;
+
+          fd_list = g_unix_fd_list_new ();
+          error = NULL;
+          g_unix_fd_list_append (fd_list, STDOUT_FILENO, &error);
+          g_assert_no_error (error);
+
+          reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
+          g_dbus_message_set_unix_fd_list (reply, fd_list);
+
+          error = NULL;
+          g_dbus_connection_send_message (connection,
+                                          reply,
+                                          NULL, /* out_serial */
+                                          &error);
+          g_assert_no_error (error);
+
+          g_object_unref (invocation);
+          g_object_unref (fd_list);
+          g_object_unref (reply);
+        }
+      else
+        {
+          g_dbus_method_invocation_return_dbus_error (invocation,
+                                                      "org.gtk.GDBus.Failed",
+                                                      "Your message bus daemon does not support file descriptor passing (need D-Bus >= 1.3.0)");
+        }
+#else
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.gtk.GDBus.NotOnUnix",
+                                                  "Your OS does not support file descriptor passing");
+#endif
+    }
+}
+
+static gchar *_global_title = NULL;
+
+static gboolean swap_a_and_b = FALSE;
+
+static GVariant *
+handle_get_property (GDBusConnection  *connection,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     GError          **error,
+                     gpointer          user_data)
+{
+  GVariant *ret;
+
+  ret = NULL;
+  if (g_strcmp0 (property_name, "FluxCapicitorName") == 0)
+    {
+      ret = g_variant_new_string ("DeLorean");
+    }
+  else if (g_strcmp0 (property_name, "Title") == 0)
+    {
+      if (_global_title == NULL)
+        _global_title = g_strdup ("Back To C!");
+      ret = g_variant_new_string (_global_title);
+    }
+  else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Hello %s. I thought I said reading this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+  else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+    {
+      ret = g_variant_new_string ("There's no home like home");
+    }
+  else if (g_strcmp0 (property_name, "Foo") == 0)
+    {
+      ret = g_variant_new_string (swap_a_and_b ? "Tock" : "Tick");
+    }
+  else if (g_strcmp0 (property_name, "Bar") == 0)
+    {
+      ret = g_variant_new_string (swap_a_and_b ? "Tick" : "Tock");
+    }
+
+  return ret;
+}
+
+static gboolean
+handle_set_property (GDBusConnection  *connection,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     GVariant         *value,
+                     GError          **error,
+                     gpointer          user_data)
+{
+  if (g_strcmp0 (property_name, "Title") == 0)
+    {
+      if (g_strcmp0 (_global_title, g_variant_get_string (value, NULL)) != 0)
+        {
+          GVariantBuilder *builder;
+          GError *local_error;
+
+          g_free (_global_title);
+          _global_title = g_variant_dup_string (value, NULL);
+
+          local_error = NULL;
+          builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+          g_variant_builder_add (builder,
+                                 "{sv}",
+                                 "Title",
+                                 g_variant_new_string (_global_title));
+          g_dbus_connection_emit_signal (connection,
+                                         NULL,
+                                         object_path,
+                                         "org.freedesktop.DBus.Properties",
+                                         "PropertiesChanged",
+                                         g_variant_new ("(sa{sv})",
+                                                        interface_name,
+                                                        builder),
+                                         &local_error);
+          g_assert_no_error (local_error);
+        }
+    }
+  else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+    {
+      /* do nothing - they can't read it after all! */
+    }
+  else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Hello AGAIN %s. I thought I said writing this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+
+  return *error == NULL;
+}
+
+
+/* for now */
+static const GDBusInterfaceVTable interface_vtable =
+{
+  handle_method_call,
+  handle_get_property,
+  handle_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_timeout_cb (gpointer user_data)
+{
+  GDBusConnection *connection = G_DBUS_CONNECTION (user_data);
+  GVariantBuilder *builder;
+  GError *error;
+
+  swap_a_and_b = !swap_a_and_b;
+
+  error = NULL;
+  builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+  g_variant_builder_add (builder,
+                         "{sv}",
+                         "Foo",
+                         g_variant_new_string (swap_a_and_b ? "Tock" : "Tick"));
+  g_variant_builder_add (builder,
+                         "{sv}",
+                         "Bar",
+                         g_variant_new_string (swap_a_and_b ? "Tick" : "Tock"));
+  g_dbus_connection_emit_signal (connection,
+                                 NULL,
+                                 "/org/gtk/GDBus/TestObject",
+                                 "org.freedesktop.DBus.Properties",
+                                 "PropertiesChanged",
+                                 g_variant_new ("(sa{sv})",
+                                                "org.gtk.GDBus.TestInterface",
+                                                builder),
+                                 &error);
+  g_assert_no_error (error);
+
+
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const gchar     *name,
+                 gpointer         user_data)
+{
+  guint registration_id;
+
+  registration_id = g_dbus_connection_register_object (connection,
+                                                       "/org/gtk/GDBus/TestObject",
+                                                       "org.gtk.GDBus.TestInterface",
+                                                       introspection_data->interfaces[0],
+                                                       &interface_vtable,
+                                                       NULL,  /* user_data */
+                                                       NULL,  /* user_data_free_func */
+                                                       NULL); /* GError** */
+  g_assert (registration_id > 0);
+
+  /* swap value of properties Foo and Bar every two seconds */
+  g_timeout_add_seconds (2,
+                         on_timeout_cb,
+                         connection);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+
+  g_type_init ();
+
+  /* We are lazy here - we don't want to manually provide
+   * the introspection data structures - so we just build
+   * them from XML.
+   */
+  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+
+  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.GDBus.TestServer",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             on_bus_acquired,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unown_name (owner_id);
+
+  g_dbus_node_info_unref (introspection_data);
+
+  return 0;
+}
diff --git a/gio/tests/gdbus-example-subtree.c b/gio/tests/gdbus-example-subtree.c
new file mode 100644
index 0000000..e8da6da
--- /dev/null
+++ b/gio/tests/gdbus-example-subtree.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+static const GDBusInterfaceInfo *manager_interface_info = NULL;
+static const GDBusInterfaceInfo *block_interface_info = NULL;
+static const GDBusInterfaceInfo *partition_interface_info = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gtk.GDBus.Example.Manager'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "  </interface>"
+  "  <interface name='org.gtk.GDBus.Example.Block'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <property type='i' name='Major' access='read'/>"
+  "    <property type='i' name='Minor' access='read'/>"
+  "    <property type='s' name='Notes' access='readwrite'/>"
+  "  </interface>"
+  "  <interface name='org.gtk.GDBus.Example.Partition'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <property type='i' name='PartitionNumber' access='read'/>"
+  "    <property type='s' name='Notes' access='readwrite'/>"
+  "  </interface>"
+  "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+manager_method_call (GDBusConnection       *connection,
+                     const gchar           *sender,
+                     const gchar           *object_path,
+                     const gchar           *interface_name,
+                     const gchar           *method_name,
+                     GVariant              *parameters,
+                     GDBusMethodInvocation *invocation,
+                     gpointer               user_data)
+{
+  const gchar *greeting;
+  gchar *response;
+
+  g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Manager");
+  g_assert_cmpstr (method_name, ==, "Hello");
+
+  g_variant_get (parameters, "(s)", &greeting);
+
+  response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                              interface_name,
+                              method_name,
+                              (const gchar *) user_data,
+                              object_path,
+                              greeting);
+  g_dbus_method_invocation_return_value (invocation,
+                                         g_variant_new ("(s)", response));
+  g_free (response);
+}
+
+const GDBusInterfaceVTable manager_vtable =
+{
+  manager_method_call,
+  NULL,                 /* get_property */
+  NULL                  /* set_property */
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+block_method_call (GDBusConnection       *connection,
+                   const gchar           *sender,
+                   const gchar           *object_path,
+                   const gchar           *interface_name,
+                   const gchar           *method_name,
+                   GVariant              *parameters,
+                   GDBusMethodInvocation *invocation,
+                   gpointer               user_data)
+{
+  g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Block");
+
+  if (g_strcmp0 (method_name, "Hello") == 0)
+    {
+      const gchar *greeting;
+      gchar *response;
+
+      g_variant_get (parameters, "(s)", &greeting);
+
+      response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                                  interface_name,
+                                  method_name,
+                                  (const gchar *) user_data,
+                                  object_path,
+                                  greeting);
+      g_dbus_method_invocation_return_value (invocation,
+                                             g_variant_new ("(s)", response));
+      g_free (response);
+    }
+  else if (g_strcmp0 (method_name, "DoStuff") == 0)
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.gtk.GDBus.TestSubtree.Error.Failed",
+                                                  "This method intentionally always fails");
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static GVariant *
+block_get_property (GDBusConnection  *connection,
+                    const gchar      *sender,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *property_name,
+                    GError          **error,
+                    gpointer          user_data)
+{
+  GVariant *ret;
+  const gchar *node;
+  gint major;
+  gint minor;
+
+  node = strrchr (object_path, '/') + 1;
+  if (g_str_has_prefix (node, "sda"))
+    major = 8;
+  else
+    major = 9;
+  if (strlen (node) == 4)
+    minor = node[3] - '0';
+  else
+    minor = 0;
+
+  ret = NULL;
+  if (g_strcmp0 (property_name, "Major") == 0)
+    {
+      ret = g_variant_new_int32 (major);
+    }
+  else if (g_strcmp0 (property_name, "Minor") == 0)
+    {
+      ret = g_variant_new_int32 (minor);
+    }
+  else if (g_strcmp0 (property_name, "Notes") == 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "Hello %s. I thought I said reading this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return ret;
+}
+
+static gboolean
+block_set_property (GDBusConnection  *connection,
+                    const gchar      *sender,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *property_name,
+                    GVariant         *value,
+                    GError          **error,
+                    gpointer          user_data)
+{
+  /* TODO */
+  g_assert_not_reached ();
+}
+
+const GDBusInterfaceVTable block_vtable =
+{
+  block_method_call,
+  block_get_property,
+  block_set_property,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+partition_method_call (GDBusConnection       *connection,
+                       const gchar           *sender,
+                       const gchar           *object_path,
+                       const gchar           *interface_name,
+                       const gchar           *method_name,
+                       GVariant              *parameters,
+                       GDBusMethodInvocation *invocation,
+                       gpointer               user_data)
+{
+  const gchar *greeting;
+  gchar *response;
+
+  g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.Example.Partition");
+  g_assert_cmpstr (method_name, ==, "Hello");
+
+  g_variant_get (parameters, "(s)", &greeting);
+
+  response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                              interface_name,
+                              method_name,
+                              (const gchar *) user_data,
+                              object_path,
+                              greeting);
+  g_dbus_method_invocation_return_value (invocation,
+                                         g_variant_new ("(s)", response));
+  g_free (response);
+}
+
+const GDBusInterfaceVTable partition_vtable =
+{
+  partition_method_call,
+  //partition_get_property,
+  //partition_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar **
+subtree_enumerate (GDBusConnection       *connection,
+                   const gchar           *sender,
+                   const gchar           *object_path,
+                   gpointer               user_data)
+{
+  gchar **nodes;
+  GPtrArray *p;
+
+  p = g_ptr_array_new ();
+  g_ptr_array_add (p, g_strdup ("sda"));
+  g_ptr_array_add (p, g_strdup ("sda1"));
+  g_ptr_array_add (p, g_strdup ("sda2"));
+  g_ptr_array_add (p, g_strdup ("sda3"));
+  g_ptr_array_add (p, g_strdup ("sdb"));
+  g_ptr_array_add (p, g_strdup ("sdb1"));
+  g_ptr_array_add (p, g_strdup ("sdc"));
+  g_ptr_array_add (p, g_strdup ("sdc1"));
+  g_ptr_array_add (p, NULL);
+  nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+  return nodes;
+}
+
+static GPtrArray *
+subtree_introspect (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *node,
+                    gpointer               user_data)
+{
+  GPtrArray *p;
+
+  p = g_ptr_array_new ();
+  if (g_strcmp0 (node, "/") == 0)
+    {
+      g_ptr_array_add (p, (gpointer) manager_interface_info);
+    }
+  else
+    {
+      g_ptr_array_add (p, (gpointer) block_interface_info);
+      if (strlen (node) == 4)
+        g_ptr_array_add (p, (gpointer) partition_interface_info);
+    }
+
+  return p;
+}
+
+static const GDBusInterfaceVTable *
+subtree_dispatch (GDBusConnection             *connection,
+                  const gchar                 *sender,
+                  const gchar                 *object_path,
+                  const gchar                 *interface_name,
+                  const gchar                 *node,
+                  gpointer                    *out_user_data,
+                  gpointer                     user_data)
+{
+  const GDBusInterfaceVTable *vtable_to_return;
+  gpointer user_data_to_return;
+
+  if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Manager") == 0)
+    {
+      user_data_to_return = "The Root";
+      vtable_to_return = &manager_vtable;
+    }
+  else
+    {
+      if (strlen (node) == 4)
+        user_data_to_return = "A partition";
+      else
+        user_data_to_return = "A block device";
+
+      if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Block") == 0)
+        vtable_to_return = &block_vtable;
+      else if (g_strcmp0 (interface_name, "org.gtk.GDBus.Example.Partition") == 0)
+        vtable_to_return = &partition_vtable;
+      else
+        g_assert_not_reached ();
+    }
+
+  *out_user_data = user_data_to_return;
+
+  return vtable_to_return;
+}
+
+const GDBusSubtreeVTable subtree_vtable =
+{
+  subtree_enumerate,
+  subtree_introspect,
+  subtree_dispatch
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const gchar     *name,
+                 gpointer         user_data)
+{
+  guint registration_id;
+
+  registration_id = g_dbus_connection_register_subtree (connection,
+                                                        "/org/gtk/GDBus/TestSubtree/Devices",
+                                                        &subtree_vtable,
+                                                        G_DBUS_SUBTREE_FLAGS_NONE,
+                                                        NULL,  /* user_data */
+                                                        NULL,  /* user_data_free_func */
+                                                        NULL); /* GError** */
+  g_assert (registration_id > 0);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+
+  g_type_init ();
+
+  /* We are lazy here - we don't want to manually provide
+   * the introspection data structures - so we just build
+   * them from XML.
+   */
+  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+
+  manager_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Manager");
+  block_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Block");
+  partition_interface_info = g_dbus_node_info_lookup_interface (introspection_data, "org.gtk.GDBus.Example.Partition");
+  g_assert (manager_interface_info != NULL);
+  g_assert (block_interface_info != NULL);
+  g_assert (partition_interface_info != NULL);
+
+  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.GDBus.TestSubtree",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             on_bus_acquired,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unown_name (owner_id);
+
+  g_dbus_node_info_unref (introspection_data);
+
+  return 0;
+}
diff --git a/gio/tests/gdbus-example-unix-fd-client.c b/gio/tests/gdbus-example-unix-fd-client.c
new file mode 100644
index 0000000..6a66431
--- /dev/null
+++ b/gio/tests/gdbus-example-unix-fd-client.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <time.h>
+
+#include <gio/gio.h>
+
+/* see gdbus-example-server.c for the server implementation */
+static gint
+get_server_stdout (GDBusConnection  *connection,
+                   const gchar      *name_owner,
+                   GError          **error)
+{
+  GDBusMessage *method_call_message;
+  GDBusMessage *method_reply_message;
+  GUnixFDList *fd_list;
+  gint fd;
+
+  fd = -1;
+  method_call_message = NULL;
+  method_reply_message = NULL;
+
+  method_call_message = g_dbus_message_new_method_call (name_owner,
+                                                        "/org/gtk/GDBus/TestObject",
+                                                        "org.gtk.GDBus.TestInterface",
+                                                        "GimmeStdout");
+  method_reply_message = g_dbus_connection_send_message_with_reply_sync (connection,
+                                                                         method_call_message,
+                                                                         -1,
+                                                                         NULL, /* out_serial */
+                                                                         NULL, /* cancellable */
+                                                                         error);
+  if (method_reply_message == NULL)
+      goto out;
+
+  if (g_dbus_message_get_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_ERROR)
+    {
+      g_dbus_message_to_gerror (method_reply_message, error);
+      goto out;
+    }
+
+  fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
+  fd = g_unix_fd_list_get (fd_list, 0, error);
+
+ out:
+  g_object_unref (method_call_message);
+  g_object_unref (method_reply_message);
+
+  return fd;
+}
+
+static void
+on_name_appeared (GDBusConnection *connection,
+                  const gchar     *name,
+                  const gchar     *name_owner,
+                  gpointer         user_data)
+{
+  gint fd;
+  GError *error;
+
+  error = NULL;
+  fd = get_server_stdout (connection, name_owner, &error);
+  if (fd == -1)
+    {
+      g_printerr ("Error invoking GimmeStdout(): %s\n",
+                  error->message);
+      g_error_free (error);
+      exit (1);
+    }
+  else
+    {
+      gchar now_buf[256];
+      time_t now;
+      gssize len;
+      gchar *str;
+
+      now = time (NULL);
+      strftime (now_buf,
+                sizeof now_buf,
+                "%c",
+                localtime (&now));
+
+      str = g_strdup_printf ("On %s, gdbus-example-unix-fd-client with pid %d was here!\n",
+                             now_buf,
+                             (gint) getpid ());
+      len = strlen (str);
+      g_warn_if_fail (write (fd, str, len) == len);
+      close (fd);
+
+      g_print ("Wrote the following on server's stdout:\n%s", str);
+
+      g_free (str);
+      exit (0);
+    }
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  g_printerr ("Failed to get name owner for %s\n"
+              "Is ./gdbus-example-server running?\n",
+              name);
+  exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint watcher_id;
+  GMainLoop *loop;
+
+  g_type_init ();
+
+  watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                                 "org.gtk.GDBus.TestServer",
+                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                 on_name_appeared,
+                                 on_name_vanished,
+                                 NULL,
+                                 NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_name (watcher_id);
+  return 0;
+}
diff --git a/gio/tests/gdbus-example-watch-name.c b/gio/tests/gdbus-example-watch-name.c
new file mode 100644
index 0000000..39d0aed
--- /dev/null
+++ b/gio/tests/gdbus-example-watch-name.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+
+static gchar *opt_name         = NULL;
+static gboolean opt_system_bus = FALSE;
+static gboolean opt_auto_start = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+  { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to watch", NULL },
+  { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+  { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL},
+  { NULL}
+};
+
+static void
+on_name_appeared (GDBusConnection *connection,
+                  const gchar     *name,
+                  const gchar     *name_owner,
+                  gpointer         user_data)
+{
+  g_print ("Name %s on %s is owned by %s\n",
+           name,
+           opt_system_bus ? "the system bus" : "the session bus",
+           name_owner);
+}
+
+static void
+on_name_vanished (GDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  g_print ("Name %s does not exist on %s\n",
+           name,
+           opt_system_bus ? "the system bus" : "the session bus");
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint watcher_id;
+  GMainLoop *loop;
+  GOptionContext *opt_context;
+  GError *error;
+  GBusNameWatcherFlags flags;
+
+  g_type_init ();
+
+  error = NULL;
+  opt_context = g_option_context_new ("g_bus_watch_name() example");
+  g_option_context_set_summary (opt_context,
+                                "Example: to watch the power manager on the session bus, use:\n"
+                                "\n"
+                                "  ./example-watch-name -n org.gnome.PowerManager");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      goto out;
+    }
+  if (opt_name == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      goto out;
+    }
+
+  flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
+  if (opt_auto_start)
+    flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START;
+
+  watcher_id = g_bus_watch_name (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+                                 opt_name,
+                                 flags,
+                                 on_name_appeared,
+                                 on_name_vanished,
+                                 NULL,
+                                 NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_name (watcher_id);
+
+ out:
+  g_option_context_free (opt_context);
+  g_free (opt_name);
+
+  return 0;
+}
diff --git a/gio/tests/gdbus-example-watch-proxy.c b/gio/tests/gdbus-example-watch-proxy.c
new file mode 100644
index 0000000..9a6176e
--- /dev/null
+++ b/gio/tests/gdbus-example-watch-proxy.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+
+static gchar *opt_name         = NULL;
+static gchar *opt_object_path  = NULL;
+static gchar *opt_interface    = NULL;
+static gboolean opt_system_bus = FALSE;
+static gboolean opt_auto_start = FALSE;
+static gboolean opt_no_properties = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+  { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name of the remote object to watch", NULL },
+  { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "Object path of the remote object", NULL },
+  { "interface", 'i', 0, G_OPTION_ARG_STRING, &opt_interface, "D-Bus interface of remote object", NULL },
+  { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+  { "auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_auto_start, "Instruct the bus to launch an owner for the name", NULL},
+  { "no-properties", 'p', 0, G_OPTION_ARG_NONE, &opt_no_properties, "Do not load properties", NULL},
+  { NULL}
+};
+
+static void
+print_properties (GDBusProxy *proxy)
+{
+  gchar **property_names;
+  guint n;
+
+  g_print ("    properties:\n");
+
+  property_names = g_dbus_proxy_get_cached_property_names (proxy, NULL);
+  for (n = 0; property_names != NULL && property_names[n] != NULL; n++)
+    {
+      const gchar *key = property_names[n];
+      GVariant *value;
+      gchar *value_str;
+      value = g_dbus_proxy_get_cached_property (proxy, key, NULL);
+      value_str = g_variant_print (value, TRUE);
+      g_print ("      %s -> %s\n", key, value_str);
+      g_variant_unref (value);
+      g_free (value_str);
+    }
+  g_strfreev (property_names);
+}
+
+static void
+on_properties_changed (GDBusProxy *proxy,
+                       GHashTable *changed_properties,
+                       gpointer    user_data)
+{
+  GHashTableIter iter;
+  const gchar *key;
+  GVariant *value;
+
+  g_print (" *** Properties Changed:\n");
+
+  g_hash_table_iter_init (&iter, changed_properties);
+  while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value))
+    {
+      gchar *value_str;
+      value_str = g_variant_print (value, TRUE);
+      g_print ("      %s -> %s\n", key, value_str);
+      g_free (value_str);
+    }
+}
+
+static void
+on_signal (GDBusProxy *proxy,
+           gchar      *sender_name,
+           gchar      *signal_name,
+           GVariant   *parameters,
+           gpointer    user_data)
+{
+  gchar *parameters_str;
+
+  parameters_str = g_variant_print (parameters, TRUE);
+  g_print (" *** Received Signal: %s: %s\n",
+           signal_name,
+           parameters_str);
+  g_free (parameters_str);
+}
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   GDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  g_print ("+++ Acquired proxy object for remote object owned by %s\n"
+           "    bus:          %s\n"
+           "    name:         %s\n"
+           "    object path:  %s\n"
+           "    interface:    %s\n",
+           name_owner,
+           opt_system_bus ? "System Bus" : "Session Bus",
+           opt_name,
+           opt_object_path,
+           opt_interface);
+
+  print_properties (proxy);
+
+  g_signal_connect (proxy,
+                    "g-properties-changed",
+                    G_CALLBACK (on_properties_changed),
+                    NULL);
+
+  g_signal_connect (proxy,
+                    "g-signal",
+                    G_CALLBACK (on_signal),
+                    NULL);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+  g_print ("--- Cannot create proxy object for\n"
+           "    bus:          %s\n"
+           "    name:         %s\n"
+           "    object path:  %s\n"
+           "    interface:    %s\n",
+           opt_system_bus ? "System Bus" : "Session Bus",
+           opt_name,
+           opt_object_path,
+           opt_interface);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint watcher_id;
+  GMainLoop *loop;
+  GOptionContext *opt_context;
+  GError *error;
+  GBusNameWatcherFlags flags;
+  GDBusProxyFlags proxy_flags;
+
+  g_type_init ();
+
+  opt_context = g_option_context_new ("g_bus_watch_proxy() example");
+  g_option_context_set_summary (opt_context,
+                                "Example: to watch the object of gdbus-example-server, use:\n"
+                                "\n"
+                                "  ./gdbus-example-watch-proxy -n org.gtk.GDBus.TestServer  \\\n"
+                                "                              -o /org/gtk/GDBus/TestObject \\\n"
+                                "                              -i org.gtk.GDBus.TestInterface");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  error = NULL;
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      goto out;
+    }
+  if (opt_name == NULL || opt_object_path == NULL || opt_interface == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      goto out;
+    }
+
+  flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
+  if (opt_auto_start)
+    flags |= G_BUS_NAME_WATCHER_FLAGS_AUTO_START;
+
+  proxy_flags = G_DBUS_PROXY_FLAGS_NONE;
+  if (opt_no_properties)
+    proxy_flags |= G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
+
+  watcher_id = g_bus_watch_proxy (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+                                  opt_name,
+                                  flags,
+                                  opt_object_path,
+                                  opt_interface,
+                                  G_TYPE_DBUS_PROXY,
+                                  proxy_flags,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_proxy (watcher_id);
+
+ out:
+  g_option_context_free (opt_context);
+  g_free (opt_name);
+  g_free (opt_object_path);
+  g_free (opt_interface);
+
+  return 0;
+}
diff --git a/gio/tests/gdbus-exit-on-close.c b/gio/tests/gdbus-exit-on-close.c
new file mode 100644
index 0000000..7f75519
--- /dev/null
+++ b/gio/tests/gdbus-exit-on-close.c
@@ -0,0 +1,82 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+nuke_session_bus_cb (gpointer data)
+{
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+test_exit_on_close (void)
+{
+  if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDOUT | G_TEST_TRAP_SILENCE_STDERR))
+    {
+      GDBusConnection *c;
+      session_bus_up ();
+      c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+      g_assert (c != NULL);
+      g_assert (!g_dbus_connection_is_closed (c));
+      g_timeout_add (50,
+                     nuke_session_bus_cb,
+                     NULL);
+      g_main_loop_run (loop);
+      session_bus_down ();
+      g_main_loop_run (loop);
+    }
+  g_test_trap_assert_stdout ("*Remote peer vanished. Exiting.*");
+  g_test_trap_assert_failed();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/exit-on-close", test_exit_on_close);
+  return g_test_run();
+}
diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c
new file mode 100644
index 0000000..6e33d2f
--- /dev/null
+++ b/gio/tests/gdbus-export.c
@@ -0,0 +1,1410 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+static GDBusConnection *c = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that we can export objects, the hierarchy is correct and the right handlers are invoked */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const GDBusArgInfo foo_method1_in_args =
+{
+  -1,
+  "an_input_string",
+  "s",
+  NULL
+};
+static const GDBusArgInfo * const foo_method1_in_arg_pointers[] = {&foo_method1_in_args, NULL};
+
+static const GDBusArgInfo foo_method1_out_args =
+{
+  -1,
+  "an_output_string",
+  "s",
+  NULL
+};
+static const GDBusArgInfo * const foo_method1_out_arg_pointers[] = {&foo_method1_out_args, NULL};
+
+static const GDBusMethodInfo foo_method_info_method1 =
+{
+  -1,
+  "Method1",
+  (GDBusArgInfo **) &foo_method1_in_arg_pointers,
+  (GDBusArgInfo **) &foo_method1_out_arg_pointers,
+  NULL
+};
+static const GDBusMethodInfo foo_method_info_method2 =
+{
+  -1,
+  "Method2",
+  NULL,
+  NULL,
+  NULL
+};
+static const GDBusMethodInfo * const foo_method_info_pointers[] = {&foo_method_info_method1, &foo_method_info_method2, NULL};
+
+static const GDBusSignalInfo foo_signal_info =
+{
+  -1,
+  "SignalAlpha",
+  NULL,
+  NULL
+};
+static const GDBusSignalInfo * const foo_signal_info_pointers[] = {&foo_signal_info, NULL};
+
+static const GDBusPropertyInfo foo_property_info[] =
+{
+  {
+    -1,
+    "PropertyUno",
+    "s",
+    G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
+    NULL
+  },
+  {
+    -1,
+    "NotWritable",
+    "s",
+    G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  },
+  {
+    -1,
+    "NotReadable",
+    "s",
+    G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
+    NULL
+  }
+};
+static const GDBusPropertyInfo * const foo_property_info_pointers[] =
+{
+  &foo_property_info[0],
+  &foo_property_info[1],
+  &foo_property_info[2],
+  NULL
+};
+
+static const GDBusInterfaceInfo foo_interface_info =
+{
+  -1,
+  "org.example.Foo",
+  (GDBusMethodInfo **) &foo_method_info_pointers,
+  (GDBusSignalInfo **) &foo_signal_info_pointers,
+  (GDBusPropertyInfo **)&foo_property_info_pointers,
+  NULL,
+};
+
+static void
+foo_method_call (GDBusConnection       *connection,
+                 const gchar           *sender,
+                 const gchar           *object_path,
+                 const gchar           *interface_name,
+                 const gchar           *method_name,
+                 GVariant              *parameters,
+                 GDBusMethodInvocation *invocation,
+                 gpointer               user_data)
+{
+  if (g_strcmp0 (method_name, "Method1") == 0)
+    {
+      const gchar *input;
+      gchar *output;
+      g_variant_get (parameters, "(s)", &input);
+      output = g_strdup_printf ("You passed the string `%s'. Jolly good!", input);
+      g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", output));
+      g_free (output);
+    }
+  else
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.example.SomeError",
+                                                  "How do you like them apples, buddy!");
+    }
+}
+
+static GVariant *
+foo_get_property (GDBusConnection       *connection,
+                  const gchar           *sender,
+                  const gchar           *object_path,
+                  const gchar           *interface_name,
+                  const gchar           *property_name,
+                  GError               **error,
+                  gpointer               user_data)
+{
+  GVariant *ret;
+  gchar *s;
+  s = g_strdup_printf ("Property `%s' Is What It Is!", property_name);
+  ret = g_variant_new_string (s);
+  g_free (s);
+  return ret;
+}
+
+static gboolean
+foo_set_property (GDBusConnection       *connection,
+                  const gchar           *sender,
+                  const gchar           *object_path,
+                  const gchar           *interface_name,
+                  const gchar           *property_name,
+                  GVariant              *value,
+                  GError               **error,
+                  gpointer               user_data)
+{
+  gchar *s;
+  s = g_variant_print (value, TRUE);
+  g_set_error (error,
+               G_DBUS_ERROR,
+               G_DBUS_ERROR_SPAWN_FILE_INVALID,
+               "Returning some error instead of writing the value `%s' to the property `%s'",
+               property_name, s);
+  g_free (s);
+  return FALSE;
+}
+
+static const GDBusInterfaceVTable foo_vtable =
+{
+  foo_method_call,
+  foo_get_property,
+  foo_set_property
+};
+
+/* -------------------- */
+
+static const GDBusMethodInfo bar_method_info[] =
+{
+  {
+    -1,
+    "MethodA",
+    NULL,
+    NULL,
+    NULL
+  },
+  {
+    -1,
+    "MethodB",
+    NULL,
+    NULL,
+    NULL
+  }
+};
+static const GDBusMethodInfo * const bar_method_info_pointers[] = {&bar_method_info[0], &bar_method_info[1], NULL};
+
+static const GDBusSignalInfo bar_signal_info[] =
+{
+  {
+    -1,
+    "SignalMars",
+    NULL,
+    NULL
+  }
+};
+static const GDBusSignalInfo * const bar_signal_info_pointers[] = {&bar_signal_info[0], NULL};
+
+static const GDBusPropertyInfo bar_property_info[] =
+{
+  {
+    -1,
+    "PropertyDuo",
+    "s",
+    G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  }
+};
+static const GDBusPropertyInfo * const bar_property_info_pointers[] = {&bar_property_info[0], NULL};
+
+static const GDBusInterfaceInfo bar_interface_info =
+{
+  -1,
+  "org.example.Bar",
+  (GDBusMethodInfo **) bar_method_info_pointers,
+  (GDBusSignalInfo **) bar_signal_info_pointers,
+  (GDBusPropertyInfo **) bar_property_info_pointers,
+  NULL,
+};
+
+/* -------------------- */
+
+static const GDBusMethodInfo dyna_method_info[] =
+{
+  {
+    -1,
+    "DynaCyber",
+    NULL,
+    NULL,
+    NULL
+  }
+};
+static const GDBusMethodInfo * const dyna_method_info_pointers[] = {&dyna_method_info[0], NULL};
+
+static const GDBusInterfaceInfo dyna_interface_info =
+{
+  -1,
+  "org.example.Dyna",
+  (GDBusMethodInfo **) dyna_method_info_pointers,
+  NULL, /* no signals */
+  NULL, /* no properties */
+  NULL,
+};
+
+static void
+dyna_cyber (GDBusConnection *connection,
+            const gchar *sender,
+            const gchar *object_path,
+            const gchar *interface_name,
+            const gchar *method_name,
+            GVariant *parameters,
+            GDBusMethodInvocation *invocation,
+            gpointer user_data)
+{
+  GPtrArray *data = user_data;
+  gchar *node_name;
+  guint n;
+
+  node_name = strrchr (object_path, '/') + 1;
+
+  /* Add new node if it is not already known */
+  for (n = 0; n < data->len ; n++)
+    {
+      if (g_strcmp0 (g_ptr_array_index (data, n), node_name) == 0)
+        goto out;
+    }
+  g_ptr_array_add (data, g_strdup(node_name));
+
+  out:
+    g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static const GDBusInterfaceVTable dyna_interface_vtable =
+{
+  dyna_cyber,
+  NULL,
+  NULL
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_callback (GDBusProxy   *proxy,
+                     GAsyncResult *res,
+                     gpointer      user_data)
+{
+  const gchar *s;
+  gchar **xml_data = user_data;
+  GVariant *result;
+  GError *error;
+
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_get (result, "(s)", &s);
+  *xml_data = g_strdup (s);
+  g_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static gchar **
+get_nodes_at (GDBusConnection  *c,
+              const gchar      *object_path)
+{
+  GError *error;
+  GDBusProxy *proxy;
+  gchar *xml_data;
+  GPtrArray *p;
+  GDBusNodeInfo *node_info;
+  guint n;
+
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (c,
+                                 G_TYPE_DBUS_PROXY,
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 NULL,
+                                 g_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  g_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  p = g_ptr_array_new ();
+  for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++)
+    {
+      const GDBusNodeInfo *sub_node_info = node_info->nodes[n];
+      g_ptr_array_add (p, g_strdup (sub_node_info->path));
+    }
+  g_ptr_array_add (p, NULL);
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  g_dbus_node_info_unref (node_info);
+
+  return (gchar **) g_ptr_array_free (p, FALSE);
+}
+
+static gboolean
+has_interface (GDBusConnection *c,
+               const gchar     *object_path,
+               const gchar     *interface_name)
+{
+  GError *error;
+  GDBusProxy *proxy;
+  gchar *xml_data;
+  GDBusNodeInfo *node_info;
+  gboolean ret;
+
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (c,
+                                 G_TYPE_DBUS_PROXY,
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 NULL,
+                                 g_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  g_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  ret = (g_dbus_node_info_lookup_interface (node_info, interface_name) != NULL);
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  g_dbus_node_info_unref (node_info);
+
+  return ret;
+}
+
+static guint
+count_interfaces (GDBusConnection *c,
+                  const gchar     *object_path)
+{
+  GError *error;
+  GDBusProxy *proxy;
+  gchar *xml_data;
+  GDBusNodeInfo *node_info;
+  guint ret;
+
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (c,
+                                 G_TYPE_DBUS_PROXY,
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 NULL,
+                                 g_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  g_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  ret = 0;
+  while (node_info->interfaces != NULL && node_info->interfaces[ret] != NULL)
+    ret++;
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  g_dbus_node_info_unref (node_info);
+
+  return ret;
+}
+
+static void
+dyna_create_callback (GDBusProxy   *proxy,
+                      GAsyncResult  *res,
+                      gpointer      user_data)
+{
+  GVariant *result;
+  GError *error;
+
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+/* Dynamically create @object_name under /foo/dyna */
+static void
+dyna_create (GDBusConnection *c,
+             const gchar     *object_name)
+{
+  GError *error;
+  GDBusProxy *proxy;
+  gchar *object_path;
+
+  object_path = g_strconcat ("/foo/dyna/", object_name, NULL);
+
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (c,
+                                 G_TYPE_DBUS_PROXY,
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 NULL,
+                                 g_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.example.Dyna",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  g_dbus_proxy_invoke_method (proxy,
+                              "DynaCyber",
+                              g_variant_new ("()"),
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) dyna_create_callback,
+                              NULL);
+  g_main_loop_run (loop);
+
+  g_assert_no_error (error);
+
+  g_object_unref (proxy);
+  g_free (object_path);
+
+  return;
+}
+
+typedef struct
+{
+  guint num_unregistered_calls;
+  guint num_unregistered_subtree_calls;
+  guint num_subtree_nodes;
+} ObjectRegistrationData;
+
+static void
+on_object_unregistered (gpointer user_data)
+{
+  ObjectRegistrationData *data = user_data;
+
+  data->num_unregistered_calls++;
+}
+
+static void
+on_subtree_unregistered (gpointer user_data)
+{
+  ObjectRegistrationData *data = user_data;
+
+  data->num_unregistered_subtree_calls++;
+}
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+                    const gchar *needle)
+{
+  guint n;
+
+  for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+    {
+      if (g_strcmp0 (haystack[n], needle) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+/* -------------------- */
+
+static gchar **
+subtree_enumerate (GDBusConnection       *connection,
+                   const gchar           *sender,
+                   const gchar           *object_path,
+                   gpointer               user_data)
+{
+  ObjectRegistrationData *data = user_data;
+  GPtrArray *p;
+  gchar **nodes;
+  guint n;
+
+  p = g_ptr_array_new ();
+
+  for (n = 0; n < data->num_subtree_nodes; n++)
+    {
+      g_ptr_array_add (p, g_strdup_printf ("vp%d", n));
+      g_ptr_array_add (p, g_strdup_printf ("evp%d", n));
+    }
+  g_ptr_array_add (p, NULL);
+
+  nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+  return nodes;
+}
+
+/* Only allows certain objects, and aborts on unknowns */
+static GPtrArray *
+subtree_introspect (GDBusConnection       *connection,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *node,
+                    gpointer               user_data)
+{
+  GPtrArray *interfaces;
+
+  /* VPs implement the Foo interface, EVPs implement the Bar interface. The root
+   * does not implement any interfaces
+   */
+  interfaces = g_ptr_array_new ();
+  if (g_str_has_prefix (node, "vp"))
+    {
+      g_ptr_array_add (interfaces, (gpointer) &foo_interface_info);
+    }
+  else if (g_str_has_prefix (node, "evp"))
+    {
+      g_ptr_array_add (interfaces, (gpointer) &bar_interface_info);
+    }
+  else if (g_strcmp0 (node, "/") == 0)
+    {
+      /* do nothing */
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return interfaces;
+}
+
+static const GDBusInterfaceVTable *
+subtree_dispatch (GDBusConnection             *connection,
+                  const gchar                 *sender,
+                  const gchar                 *object_path,
+                  const gchar                 *interface_name,
+                  const gchar                 *node,
+                  gpointer                    *out_user_data,
+                  gpointer                     user_data)
+{
+  if (g_strcmp0 (interface_name, "org.example.Foo") == 0)
+    return &foo_vtable;
+  else
+    return NULL;
+}
+
+static const GDBusSubtreeVTable subtree_vtable =
+{
+  subtree_enumerate,
+  subtree_introspect,
+  subtree_dispatch
+};
+
+/* -------------------- */
+
+static gchar **
+dynamic_subtree_enumerate (GDBusConnection       *connection,
+                           const gchar           *sender,
+                           const gchar           *object_path,
+                           gpointer               user_data)
+{
+  GPtrArray *data = user_data;
+  gchar **nodes = g_new (gchar*, data->len + 1);
+  guint n;
+
+  for (n = 0; n < data->len; n++)
+    {
+      nodes[n] = g_strdup (g_ptr_array_index (data, n));
+    }
+  nodes[data->len] = NULL;
+
+  return nodes;
+}
+
+/* Allow all objects to be introspected */
+static GPtrArray *
+dynamic_subtree_introspect (GDBusConnection       *connection,
+                            const gchar           *sender,
+                            const gchar           *object_path,
+                            const gchar           *node,
+                            gpointer               user_data)
+{
+  GPtrArray *interfaces;
+
+  /* All nodes (including the root node) implements the Dyna interface */
+  interfaces = g_ptr_array_new ();
+  g_ptr_array_add (interfaces, (gpointer) &dyna_interface_info);
+
+  return interfaces;
+}
+
+static const GDBusInterfaceVTable *
+dynamic_subtree_dispatch (GDBusConnection             *connection,
+                          const gchar                 *sender,
+                          const gchar                 *object_path,
+                          const gchar                 *interface_name,
+                          const gchar                 *node,
+                          gpointer                    *out_user_data,
+                          gpointer                     user_data)
+{
+  *out_user_data = user_data;
+  return &dyna_interface_vtable;
+}
+
+static const GDBusSubtreeVTable dynamic_subtree_vtable =
+{
+  dynamic_subtree_enumerate,
+  dynamic_subtree_introspect,
+  dynamic_subtree_dispatch
+};
+
+/* -------------------- */
+
+static gpointer
+test_dispatch_thread_func (gpointer user_data)
+{
+  const gchar *object_path = user_data;
+  GDBusProxy *foo_proxy;
+  GVariant *value;
+  GVariant *inner;
+  GError *error;
+  gchar *s;
+  const gchar *value_str;
+
+  foo_proxy = g_dbus_proxy_new_sync (c,
+                                     G_TYPE_DBUS_PROXY,
+                                     G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                     NULL,
+                                     g_dbus_connection_get_unique_name (c),
+                                     object_path,
+                                     "org.example.Foo",
+                                     NULL,
+                                     &error);
+  g_assert (foo_proxy != NULL);
+
+  /* generic interfaces */
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Peer.Ping",
+                                           NULL,
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert (value != NULL);
+  g_variant_unref (value);
+
+  /* user methods */
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "Method1",
+                                           g_variant_new ("(s)", "winwinwin"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert (value != NULL);
+  g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)")));
+  g_variant_get (value, "(s)", &value_str);
+  g_assert_cmpstr (value_str, ==, "You passed the string `winwinwin'. Jolly good!");
+  g_variant_unref (value);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "Method2",
+                                           NULL,
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.example.SomeError: How do you like them apples, buddy!");
+  g_error_free (error);
+  g_assert (value == NULL);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "Method2",
+                                           g_variant_new ("(s)", "failfailfail"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Signature of message, `s', does not match expected signature `'");
+  g_error_free (error);
+  g_assert (value == NULL);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "NonExistantMethod",
+                                           NULL,
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method `NonExistantMethod'");
+  g_error_free (error);
+  g_assert (value == NULL);
+
+  /* user properties */
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.Get",
+                                           g_variant_new ("(ss)",
+                                                          "org.example.Foo",
+                                                          "PropertyUno"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert (value != NULL);
+  g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(v)")));
+  g_variant_get (value, "(v)", &inner);
+  g_assert (g_variant_is_of_type (inner, G_VARIANT_TYPE_STRING));
+  g_assert_cmpstr (g_variant_get_string (inner, NULL), ==, "Property `PropertyUno' Is What It Is!");
+  g_variant_unref (value);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.Get",
+                                           g_variant_new ("(ss)",
+                                                          "org.example.Foo",
+                                                          "ThisDoesntExist"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert (value == NULL);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: No such property `ThisDoesntExist'");
+  g_error_free (error);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.Get",
+                                           g_variant_new ("(ss)",
+                                                          "org.example.Foo",
+                                                          "NotReadable"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert (value == NULL);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotReadable' is not readable");
+  g_error_free (error);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.Set",
+                                           g_variant_new ("(ssv)",
+                                                          "org.example.Foo",
+                                                          "NotReadable",
+                                                          g_variant_new_string ("But Writable you are!")),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert (value == NULL);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value `NotReadable' to the property `'But Writable you are!''");
+  g_error_free (error);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.Set",
+                                           g_variant_new ("(ssv)",
+                                                          "org.example.Foo",
+                                                          "NotWritable",
+                                                          g_variant_new_uint32 (42)),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert (value == NULL);
+  g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
+  g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property `NotWritable' is not writable");
+  g_error_free (error);
+
+  error = NULL;
+  value = g_dbus_proxy_invoke_method_sync (foo_proxy,
+                                           "org.freedesktop.DBus.Properties.GetAll",
+                                           g_variant_new ("(s)",
+                                                          "org.example.Foo"),
+                                           G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                           -1,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert (value != NULL);
+  g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(a{sv})")));
+  s = g_variant_print (value, TRUE);
+  g_assert_cmpstr (s, ==, "({'PropertyUno': <\"Property `PropertyUno' Is What It Is!\">, 'NotWritable': <\"Property `NotWritable' Is What It Is!\">},)");
+  g_free (s);
+  g_variant_unref (value);
+
+  g_object_unref (foo_proxy);
+
+  g_main_loop_quit (loop);
+  return NULL;
+}
+
+static void
+test_dispatch (const gchar *object_path)
+{
+  GThread *thread;
+  GError *error;
+
+  /* run this in a thread to avoid deadlocks */
+  error = NULL;
+  thread = g_thread_create (test_dispatch_thread_func,
+                            (gpointer) object_path,
+                            TRUE,
+                            &error);
+  g_assert_no_error (error);
+  g_assert (thread != NULL);
+  g_main_loop_run (loop);
+  g_thread_join (thread);
+}
+
+static void
+test_object_registration (void)
+{
+  GError *error;
+  ObjectRegistrationData data;
+  GPtrArray *dyna_data;
+  gchar **nodes;
+  guint boss_foo_reg_id;
+  guint boss_bar_reg_id;
+  guint worker1_foo_reg_id;
+  guint worker2_bar_reg_id;
+  guint intern1_foo_reg_id;
+  guint intern2_bar_reg_id;
+  guint intern2_foo_reg_id;
+  guint intern3_bar_reg_id;
+  guint registration_id;
+  guint subtree_registration_id;
+  guint non_subtree_object_path_foo_reg_id;
+  guint non_subtree_object_path_bar_reg_id;
+  guint dyna_subtree_registration_id;
+  guint num_successful_registrations;
+
+  data.num_unregistered_calls = 0;
+  data.num_unregistered_subtree_calls = 0;
+  data.num_subtree_nodes = 0;
+
+  num_successful_registrations = 0;
+
+  error = NULL;
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       &foo_vtable,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  boss_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  boss_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/worker1",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  worker1_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/worker2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  worker2_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern1",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern1_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* ... and try again at another path */
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* register at the same path/interface - this should fail */
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  error = NULL;
+  g_assert (registration_id == 0);
+
+  /* register at different interface - shouldn't fail */
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* unregister it via the id */
+  g_assert (g_dbus_connection_unregister_object (c, registration_id));
+  g_assert_cmpint (data.num_unregistered_calls, ==, 1);
+  intern2_foo_reg_id = 0;
+
+  /* register it back */
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern3",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern3_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* now register a whole subtree at /foo/boss/executives */
+  subtree_registration_id = g_dbus_connection_register_subtree (c,
+                                                                "/foo/boss/executives",
+                                                                &subtree_vtable,
+                                                                G_DBUS_SUBTREE_FLAGS_NONE,
+                                                                &data,
+                                                                on_subtree_unregistered,
+                                                                &error);
+  g_assert_no_error (error);
+  g_assert (subtree_registration_id > 0);
+  /* try registering it again.. this should fail */
+  registration_id = g_dbus_connection_register_subtree (c,
+                                                        "/foo/boss/executives",
+                                                        &subtree_vtable,
+                                                        G_DBUS_SUBTREE_FLAGS_NONE,
+                                                        &data,
+                                                        on_subtree_unregistered,
+                                                        &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  error = NULL;
+  g_assert (registration_id == 0);
+
+  /* unregister it, then register it again */
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 0);
+  g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+  subtree_registration_id = g_dbus_connection_register_subtree (c,
+                                                                "/foo/boss/executives",
+                                                                &subtree_vtable,
+                                                                G_DBUS_SUBTREE_FLAGS_NONE,
+                                                                &data,
+                                                                on_subtree_unregistered,
+                                                                &error);
+  g_assert_no_error (error);
+  g_assert (subtree_registration_id > 0);
+
+  /* try to register something under /foo/boss/executives - this should work
+   * because registered subtrees and registered objects can coexist.
+   *
+   * Make the exported object implement *two* interfaces so we can check
+   * that the right introspection handler is invoked.
+   */
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/executives/non_subtree_object",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  non_subtree_object_path_bar_reg_id = registration_id;
+  num_successful_registrations++;
+  registration_id = g_dbus_connection_register_object (c,
+                                                       "/foo/boss/executives/non_subtree_object",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  non_subtree_object_path_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* now register a dynamic subtree, spawning objects as they are called */
+  dyna_data = g_ptr_array_new ();
+  dyna_subtree_registration_id = g_dbus_connection_register_subtree (c,
+                                                                     "/foo/dyna",
+                                                                     &dynamic_subtree_vtable,
+                                                                     G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
+                                                                     dyna_data,
+                                                                     (GDestroyNotify)g_ptr_array_unref,
+                                                                     &error);
+  g_assert_no_error (error);
+  g_assert (dyna_subtree_registration_id > 0);
+
+  /* First assert that we have no nodes in the dynamic subtree */
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 0);
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna"), ==, 4);
+
+  /* Install three nodes in the dynamic subtree via the dyna_data backdoor and
+   * assert that they show up correctly in the introspection data */
+  g_ptr_array_add (dyna_data, "lol");
+  g_ptr_array_add (dyna_data, "cat");
+  g_ptr_array_add (dyna_data, "cheezburger");
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 3);
+  g_assert_cmpstr (nodes[0], ==, "lol");
+  g_assert_cmpstr (nodes[1], ==, "cat");
+  g_assert_cmpstr (nodes[2], ==, "cheezburger");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/cheezburger"), ==, 4);
+
+  /* Call a non-existing object path and assert that it has been created */
+  dyna_create (c, "dynamicallycreated");
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 4);
+  g_assert_cmpstr (nodes[0], ==, "lol");
+  g_assert_cmpstr (nodes[1], ==, "cat");
+  g_assert_cmpstr (nodes[2], ==, "cheezburger");
+  g_assert_cmpstr (nodes[3], ==, "dynamicallycreated");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4);
+
+  /* now check that the object hierarachy is properly generated... yes, it's a bit
+   * perverse that we round-trip to the bus to introspect ourselves ;-)
+   */
+  nodes = get_nodes_at (c, "/");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_assert_cmpstr (nodes[0], ==, "foo");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/"), ==, 0);
+
+  nodes = get_nodes_at (c, "/foo");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 2);
+  g_assert_cmpstr (nodes[0], ==, "boss");
+  g_assert_cmpstr (nodes[1], ==, "dyna");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo"), ==, 0);
+
+  nodes = get_nodes_at (c, "/foo/boss");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 4);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives"));
+  g_strfreev (nodes);
+  /* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5);
+  g_assert (has_interface (c, "/foo/boss", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss", bar_interface_info.name));
+
+  /* check subtree nodes - we should have only non_subtree_object in /foo/boss/executives
+   * because data.num_subtree_nodes is 0
+   */
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0);
+
+  /* now change data.num_subtree_nodes and check */
+  data.num_subtree_nodes = 2;
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 5);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+  /* check that /foo/boss/executives/non_subtree_object is not handled by the
+   * subtree handlers - we can do this because objects from subtree handlers
+   * has exactly one interface and non_subtree_object has two
+   */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/non_subtree_object"), ==, 5);
+  g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", bar_interface_info.name));
+  /* check that the vp and evp objects are handled by the subtree handlers */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp0"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp1"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp0"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp1"), ==, 4);
+  g_assert (has_interface (c, "/foo/boss/executives/vp0", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/vp1", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/evp0", bar_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/evp1", bar_interface_info.name));
+  g_strfreev (nodes);
+  data.num_subtree_nodes = 3;
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 7);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2"));
+  g_strfreev (nodes);
+
+  /* check that calls are properly dispatched to the functions in foo_vtable for objects
+   * implementing the org.example.Foo interface
+   *
+   * We do this for both a regular registered object (/foo/boss) and also for an object
+   * registered through the subtree mechanism.
+   */
+  test_dispatch ("/foo/boss");
+  test_dispatch ("/foo/boss/executives/vp0");
+
+  /* To prevent from exiting and attaching a D-Bus tool like D-Feet; uncomment: */
+#if 0
+  g_debug ("Point D-feet or other tool at: %s", session_bus_get_temporary_address());
+  g_main_loop_run (loop);
+#endif
+
+  /* check that unregistering the subtree handler works */
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+  g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 2);
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_strfreev (nodes);
+
+  g_assert (g_dbus_connection_unregister_object (c, boss_foo_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, boss_bar_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, worker1_foo_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, worker2_bar_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, intern1_foo_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, intern2_bar_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, intern2_foo_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, intern3_bar_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_bar_reg_id));
+  g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_foo_reg_id));
+
+  g_assert_cmpint (data.num_unregistered_calls, ==, num_successful_registrations);
+
+  /* check that we no longer export any objects - TODO: it looks like there's a bug in
+   * libdbus-1 here: libdbus still reports the '/foo' object; so disable the test for now
+   */
+#if 0
+  nodes = get_nodes_at (c, "/");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 0);
+  g_strfreev (nodes);
+#endif
+
+  g_object_unref (c);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  g_test_add_func ("/gdbus/object-registration", test_object_registration);
+  /* TODO: check that we spit out correct introspection data */
+  /* TODO: check that registering a whole subtree works */
+
+  ret = g_test_run();
+
+  /* tear down bus */
+  session_bus_down ();
+
+  return ret;
+}
diff --git a/gio/tests/gdbus-introspection.c b/gio/tests/gdbus-introspection.c
new file mode 100644
index 0000000..1b3d7a5
--- /dev/null
+++ b/gio/tests/gdbus-introspection.c
@@ -0,0 +1,169 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test introspection parser */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspection_on_proxy_appeared (GDBusConnection *connection,
+                                 const gchar     *name,
+                                 const gchar     *name_owner,
+                                 GDBusProxy      *proxy,
+                                 gpointer         user_data)
+{
+  GError *error;
+  const gchar *xml_data;
+  GDBusNodeInfo *node_info;
+  const GDBusInterfaceInfo *interface_info;
+  const GDBusMethodInfo *method_info;
+  const GDBusSignalInfo *signal_info;
+  GVariant *result;
+
+  error = NULL;
+
+  /*
+   * Invoke Introspect(), then parse the output.
+   */
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "org.freedesktop.DBus.Introspectable.Introspect",
+                                            NULL,
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_get (result, "(s)", &xml_data);
+
+  node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  /* for now we only check a couple of things. TODO: check more things */
+
+  interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.NonExistantInterface");
+  g_assert (interface_info == NULL);
+
+  interface_info = g_dbus_node_info_lookup_interface (node_info, "org.freedesktop.DBus.Introspectable");
+  g_assert (interface_info != NULL);
+  method_info = g_dbus_interface_info_lookup_method (interface_info, "NonExistantMethod");
+  g_assert (method_info == NULL);
+  method_info = g_dbus_interface_info_lookup_method (interface_info, "Introspect");
+  g_assert (method_info != NULL);
+  g_assert (method_info->in_args == NULL);
+  g_assert (method_info->out_args != NULL);
+  g_assert (method_info->out_args[0] != NULL);
+  g_assert (method_info->out_args[1] == NULL);
+  g_assert_cmpstr (method_info->out_args[0]->signature, ==, "s");
+
+  interface_info = g_dbus_node_info_lookup_interface (node_info, "com.example.Frob");
+  g_assert (interface_info != NULL);
+  signal_info = g_dbus_interface_info_lookup_signal (interface_info, "TestSignal");
+  g_assert (signal_info != NULL);
+  g_assert (signal_info->args != NULL);
+  g_assert (signal_info->args[0] != NULL);
+  g_assert_cmpstr (signal_info->args[0]->signature, ==, "s");
+  g_assert (signal_info->args[1] != NULL);
+  g_assert_cmpstr (signal_info->args[1]->signature, ==, "o");
+  g_assert (signal_info->args[2] != NULL);
+  g_assert_cmpstr (signal_info->args[2]->signature, ==, "v");
+  g_assert (signal_info->args[3] == NULL);
+
+  g_dbus_node_info_unref (node_info);
+  g_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+introspection_on_proxy_vanished (GDBusConnection *connection,
+                                 const gchar     *name,
+                                 gpointer         user_data)
+{
+}
+
+static void
+test_introspection_parser (void)
+{
+  guint watcher_id;
+
+  session_bus_up ();
+
+  watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  G_TYPE_DBUS_PROXY,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  introspection_on_proxy_appeared,
+                                  introspection_on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_proxy (watcher_id);
+
+  /* tear down bus */
+  session_bus_down ();
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/introspection-parser", test_introspection_parser);
+  return g_test_run();
+}
diff --git a/gio/tests/gdbus-names.c b/gio/tests/gdbus-names.c
new file mode 100644
index 0000000..9008fa0
--- /dev/null
+++ b/gio/tests/gdbus-names.c
@@ -0,0 +1,749 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that g_bus_own_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean expect_null_connection;
+  guint num_bus_acquired;
+  guint num_acquired;
+  guint num_lost;
+  guint num_free_func;
+} OwnNameData;
+
+static void
+own_name_data_free_func (OwnNameData *data)
+{
+  data->num_free_func++;
+  g_main_loop_quit (loop);
+}
+
+static void
+bus_acquired_handler (GDBusConnection *connection,
+                      const gchar     *name,
+                      gpointer         user_data)
+{
+  OwnNameData *data = user_data;
+  g_dbus_connection_set_exit_on_close (connection, FALSE);
+  data->num_bus_acquired += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_acquired_handler (GDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  OwnNameData *data = user_data;
+  data->num_acquired += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_lost_handler (GDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+  OwnNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      g_dbus_connection_set_exit_on_close (connection, FALSE);
+    }
+  data->num_lost += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+test_bus_own_name (void)
+{
+  guint id;
+  guint id2;
+  OwnNameData data;
+  OwnNameData data2;
+  const gchar *name;
+  GDBusConnection *c;
+  GError *error;
+  gboolean name_has_owner_reply;
+  GDBusConnection *c2;
+  GVariant *result;
+
+  error = NULL;
+  name = "org.gtk.GDBus.Name1";
+
+  /*
+   * First check that name_lost_handler() is invoked if there is no bus.
+   *
+   * Also make sure name_lost_handler() isn't invoked when unowning the name.
+   */
+  data.num_bus_acquired = 0;
+  data.num_free_func = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = TRUE;
+  id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       bus_acquired_handler,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_bus_unown_name (id);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /*
+   * Bring up a bus, then own a name and check bus_acquired_handler() then name_acquired_handler() is invoked.
+   */
+  session_bus_up ();
+  data.num_bus_acquired = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       bus_acquired_handler,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /*
+   * Check that the name was actually acquired.
+   */
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c != NULL);
+  g_assert (!g_dbus_connection_is_closed (c));
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",  /* bus name */
+                                                 "/org/freedesktop/DBus", /* object path */
+                                                 "org.freedesktop.DBus",  /* interface name */
+                                                 "NameHasOwner",          /* method name */
+                                                 g_variant_new ("(s)", name),
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_get (result, "(b)", &name_has_owner_reply);
+  g_assert (name_has_owner_reply);
+  g_variant_unref (result);
+
+  /*
+   * Stop owning the name - this should invoke our free func
+   */
+  g_bus_unown_name (id);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+  /*
+   * Check that the name was actually released.
+   */
+  result = g_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",  /* bus name */
+                                                 "/org/freedesktop/DBus", /* object path */
+                                                 "org.freedesktop.DBus",  /* interface name */
+                                                 "NameHasOwner",          /* method name */
+                                                 g_variant_new ("(s)", name),
+                                                 G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_get (result, "(b)", &name_has_owner_reply);
+  g_assert (!name_has_owner_reply);
+  g_variant_unref (result);
+
+  /*
+   * Own the name again.
+   */
+  data.num_bus_acquired = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       bus_acquired_handler,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /*
+   * Try owning the name with another object on the same connection  - this should
+   * fail because we already own the name.
+   */
+  data2.num_free_func = 0;
+  data2.num_bus_acquired = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  id2 = g_bus_own_name (G_BUS_TYPE_SESSION,
+                        name,
+                        G_BUS_NAME_OWNER_FLAGS_NONE,
+                        bus_acquired_handler,
+                        name_acquired_handler,
+                        name_lost_handler,
+                        &data2,
+                        (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+
+  /*
+   * Create a secondary (e.g. private) connection and try owning the name on that
+   * connection. This should fail both with and without _REPLACE because we
+   * didn't specify ALLOW_REPLACEMENT.
+   */
+  c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c2 != NULL);
+  g_assert (!g_dbus_connection_is_closed (c2));
+  /* first without _REPLACE */
+  data2.num_bus_acquired = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = g_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  /* then with _REPLACE */
+  data2.num_bus_acquired = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = g_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+
+  /*
+   * Stop owning the name and grab it again with _ALLOW_REPLACEMENT.
+   */
+  data.expect_null_connection = FALSE;
+  g_bus_unown_name (id);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 3);
+  /* grab it again */
+  data.num_bus_acquired = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+                       bus_acquired_handler,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_bus_acquired, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /*
+   * Now try to grab the name from the secondary connection.
+   *
+   */
+  /* first without _REPLACE - this won't make us acquire the name */
+  data2.num_bus_acquired = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = g_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  /* then with _REPLACE - here we should acquire the name - e.g. owner should lose it
+   * and owner2 should acquire it  */
+  data2.num_bus_acquired = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = g_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  /* wait for handlers for both owner and owner2 to fire */
+  while (data.num_lost == 0 || data2.num_acquired == 0)
+    g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_acquired, ==, 1);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_assert_cmpint (data2.num_bus_acquired, ==, 0);
+  /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */
+  g_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 2);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+
+  /*
+   * Finally, nuke the bus and check name_lost_handler() is invoked.
+   *
+   */
+  data.expect_null_connection = TRUE;
+  session_bus_down ();
+  while (data.num_lost != 2)
+    g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 2);
+  g_assert_cmpint (data.num_lost,     ==, 2);
+  g_bus_unown_name (id);
+  g_assert_cmpint (data.num_free_func, ==, 4);
+
+  g_object_unref (c);
+  g_object_unref (c2);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that g_bus_watch_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gboolean expect_null_connection;
+  guint num_acquired;
+  guint num_lost;
+  guint num_appeared;
+  guint num_vanished;
+  guint num_free_func;
+} WatchNameData;
+
+static void
+watch_name_data_free_func (WatchNameData *data)
+{
+  data->num_free_func++;
+  g_main_loop_quit (loop);
+}
+
+static void
+w_bus_acquired_handler (GDBusConnection *connection,
+                        const gchar     *name,
+                        gpointer         user_data)
+{
+}
+
+static void
+w_name_acquired_handler (GDBusConnection *connection,
+                         const gchar     *name,
+                         gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  data->num_acquired += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+w_name_lost_handler (GDBusConnection *connection,
+                     const gchar     *name,
+                     gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  data->num_lost += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_appeared_handler (GDBusConnection *connection,
+                       const gchar     *name,
+                       const gchar     *name_owner,
+                       gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      g_dbus_connection_set_exit_on_close (connection, FALSE);
+    }
+  data->num_appeared += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_vanished_handler (GDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      g_dbus_connection_set_exit_on_close (connection, FALSE);
+    }
+  data->num_vanished += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+test_bus_watch_name (void)
+{
+  WatchNameData data;
+  guint id;
+  guint owner_id;
+
+  /*
+   * First check that name_vanished_handler() is invoked if there is no bus.
+   *
+   * Also make sure name_vanished_handler() isn't invoked when unwatching the name.
+   */
+  data.num_free_func = 0;
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  data.expect_null_connection = TRUE;
+  id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.GDBus.Name1",
+                         G_BUS_NAME_WATCHER_FLAGS_NONE,
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+  g_bus_unwatch_name (id);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /*
+   * Now bring up a bus, own a name, and then start watching it.
+   */
+  session_bus_up ();
+  /* own the name */
+  data.num_free_func = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.GDBus.Name1",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             w_bus_acquired_handler,
+                             w_name_acquired_handler,
+                             w_name_lost_handler,
+                             &data,
+                             (GDestroyNotify) watch_name_data_free_func);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  /* now watch the name */
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.GDBus.Name1",
+                         G_BUS_NAME_WATCHER_FLAGS_NONE,
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+
+  /*
+   * Unwatch the name.
+   */
+  g_bus_unwatch_name (id);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /* unown the name */
+  g_bus_unown_name (owner_id);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+  /*
+   * Create a watcher and then make a name be owned.
+   *
+   * This should trigger name_appeared_handler() ...
+   */
+  /* watch the name */
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  data.num_free_func = 0;
+  id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.GDBus.Name1",
+                         G_BUS_NAME_WATCHER_FLAGS_NONE,
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+
+  /* own the name */
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.GDBus.Name1",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             w_bus_acquired_handler,
+                             w_name_acquired_handler,
+                             w_name_lost_handler,
+                             &data,
+                             (GDestroyNotify) watch_name_data_free_func);
+  while (data.num_acquired == 0 || data.num_appeared == 0)
+    g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_assert_cmpint (data.num_appeared, ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+
+  /*
+   * Nuke the bus and check that the name vanishes and is lost.
+   */
+  data.expect_null_connection = TRUE;
+  session_bus_down ();
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 2);
+
+  g_bus_unwatch_name (id);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  g_bus_unown_name (owner_id);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_validate_names (void)
+{
+  guint n;
+  static const struct
+  {
+    gboolean name;
+    gboolean unique;
+    gboolean interface;
+    const gchar *string;
+  } names[] = {
+    { 1, 0, 1, "valid.well_known.name"},
+    { 1, 0, 0, "valid.well-known.name"},
+    { 1, 1, 0, ":valid.unique.name"},
+    { 0, 0, 0, "invalid.5well_known.name"},
+    { 0, 0, 0, "4invalid.5well_known.name"},
+    { 1, 1, 0, ":4valid.5unique.name"},
+    { 0, 0, 0, ""},
+    { 1, 0, 1, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name1"}, /* 255 */
+    { 0, 0, 0, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name12"}, /* 256 - too long! */
+    { 0, 0, 0, ".starts.with.a.dot"},
+    { 0, 0, 0, "contains.invalid;.characters"},
+    { 0, 0, 0, "contains.inva/lid.characters"},
+    { 0, 0, 0, "contains.inva[lid.characters"},
+    { 0, 0, 0, "contains.inva]lid.characters"},
+    { 0, 0, 0, "contains.inva_æøå_lid.characters"},
+    { 1, 1, 0, ":1.1"},
+  };
+
+  for (n = 0; n < G_N_ELEMENTS (names); n++)
+    {
+      if (names[n].name)
+        g_assert (g_dbus_is_name (names[n].string));
+      else
+        g_assert (!g_dbus_is_name (names[n].string));
+
+      if (names[n].unique)
+        g_assert (g_dbus_is_unique_name (names[n].string));
+      else
+        g_assert (!g_dbus_is_unique_name (names[n].string));
+
+      if (names[n].interface)
+        g_assert (g_dbus_is_interface_name (names[n].string));
+      else
+        g_assert (!g_dbus_is_interface_name (names[n].string));
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/validate-names", test_validate_names);
+  g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name);
+  g_test_add_func ("/gdbus/bus-watch-name", test_bus_watch_name);
+
+  ret = g_test_run();
+
+  g_main_loop_unref (loop);
+
+  return ret;
+}
diff --git a/gio/tests/gdbus-peer.c b/gio/tests/gdbus-peer.c
new file mode 100644
index 0000000..ae6d68d
--- /dev/null
+++ b/gio/tests/gdbus-peer.c
@@ -0,0 +1,746 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+/* for open(2) */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gio/gunixsocketaddress.h>
+
+#include "gdbus-tests.h"
+
+
+#ifdef G_OS_UNIX
+static gboolean is_unix = TRUE;
+#else
+static gboolean is_unix = FALSE;
+#endif
+
+static gchar *test_guid = NULL;
+static GMainLoop *service_loop = NULL;
+static GDBusServer *server = NULL;
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that peer-to-peer connections work */
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+typedef struct
+{
+  gboolean accept_connection;
+  gint num_connection_attempts;
+  GPtrArray *current_connections;
+  guint num_method_calls;
+  gboolean signal_received;
+} PeerData;
+
+static const gchar *test_interface_introspection_xml =
+  "<node>"
+  "  <interface name='org.gtk.GDBus.PeerTestInterface'>"
+  "    <method name='HelloPeer'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <method name='EmitSignal'/>"
+  "    <method name='OpenFile'>"
+  "      <arg type='s' name='path' direction='in'/>"
+  "    </method>"
+  "    <signal name='PeerSignal'>"
+  "      <arg type='s' name='a_string'/>"
+  "    </signal>"
+  "    <property type='s' name='PeerProperty' access='read'/>"
+  "  </interface>"
+  "</node>";
+static const GDBusInterfaceInfo *test_interface_introspection_data = NULL;
+
+static void
+test_interface_method_call (GDBusConnection       *connection,
+                            const gchar           *sender,
+                            const gchar           *object_path,
+                            const gchar           *interface_name,
+                            const gchar           *method_name,
+                            GVariant              *parameters,
+                            GDBusMethodInvocation *invocation,
+                            gpointer               user_data)
+{
+  PeerData *data = user_data;
+
+  data->num_method_calls++;
+
+  g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject");
+  g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface");
+
+  if (g_strcmp0 (method_name, "HelloPeer") == 0)
+    {
+      const gchar *greeting;
+      gchar *response;
+
+      g_variant_get (parameters, "(s)", &greeting);
+
+      response = g_strdup_printf ("You greeted me with '%s'.",
+                                  greeting);
+      g_dbus_method_invocation_return_value (invocation,
+                                             g_variant_new ("(s)", response));
+      g_free (response);
+    }
+  else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+    {
+      GError *error;
+
+      error = NULL;
+      g_dbus_connection_emit_signal (connection,
+                                     NULL,
+                                     "/org/gtk/GDBus/PeerTestObject",
+                                     "org.gtk.GDBus.PeerTestInterface",
+                                     "PeerSignal",
+                                     NULL,
+                                     &error);
+      g_assert_no_error (error);
+      g_dbus_method_invocation_return_value (invocation, NULL);
+    }
+  else if (g_strcmp0 (method_name, "OpenFile") == 0)
+    {
+      const gchar *path;
+      GDBusMessage *reply;
+      GError *error;
+      gint fd;
+      GUnixFDList *fd_list;
+
+      g_variant_get (parameters, "(s)", &path);
+
+      fd_list = g_unix_fd_list_new ();
+
+      error = NULL;
+
+      fd = open (path, O_RDONLY);
+      g_unix_fd_list_append (fd_list, fd, &error);
+      g_assert_no_error (error);
+      close (fd);
+
+      reply = g_dbus_message_new_method_reply (g_dbus_method_invocation_get_message (invocation));
+      g_dbus_message_set_unix_fd_list (reply, fd_list);
+      g_object_unref (invocation);
+
+      error = NULL;
+      g_dbus_connection_send_message (connection,
+                                      reply,
+                                      NULL, /* out_serial */
+                                      &error);
+      g_assert_no_error (error);
+      g_object_unref (reply);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static GVariant *
+test_interface_get_property (GDBusConnection  *connection,
+                             const gchar      *sender,
+                             const gchar      *object_path,
+                             const gchar      *interface_name,
+                             const gchar      *property_name,
+                             GError          **error,
+                             gpointer          user_data)
+{
+  g_assert_cmpstr (object_path, ==, "/org/gtk/GDBus/PeerTestObject");
+  g_assert_cmpstr (interface_name, ==, "org.gtk.GDBus.PeerTestInterface");
+  g_assert_cmpstr (property_name, ==, "PeerProperty");
+
+  return g_variant_new_string ("ThePropertyValue");
+}
+
+
+static const GDBusInterfaceVTable test_interface_vtable =
+{
+  test_interface_method_call,
+  test_interface_get_property,
+  NULL  /* set_property */
+};
+
+static void
+on_proxy_signal_received (GDBusProxy *proxy,
+                          gchar      *sender_name,
+                          gchar      *signal_name,
+                          GVariant   *parameters,
+                          gpointer    user_data)
+{
+  PeerData *data = user_data;
+
+  data->signal_received = TRUE;
+
+  g_assert (sender_name == NULL);
+  g_assert_cmpstr (signal_name, ==, "PeerSignal");
+  g_main_loop_quit (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_deny_authenticated_peer (GDBusAuthObserver *observer,
+                            GIOStream         *stream,
+                            GCredentials      *credentials,
+                            gpointer           user_data)
+{
+  PeerData *data = user_data;
+  gboolean deny_peer;
+
+  data->num_connection_attempts++;
+
+  deny_peer = FALSE;
+  if (!data->accept_connection)
+    {
+      deny_peer = TRUE;
+      g_main_loop_quit (loop);
+    }
+
+  return deny_peer;
+}
+
+/* Runs in thread we created GDBusServer in (since we didn't pass G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) */
+static void
+on_new_connection (GDBusServer *server,
+                   GDBusConnection *connection,
+                   gpointer user_data)
+{
+  PeerData *data = user_data;
+  GError *error;
+  guint reg_id;
+
+  //g_print ("Client connected.\n"
+  //         "Negotiated capabilities: unix-fd-passing=%d\n",
+  //         g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
+
+  g_ptr_array_add (data->current_connections, g_object_ref (connection));
+
+  /* export object on the newly established connection */
+  error = NULL;
+  reg_id = g_dbus_connection_register_object (connection,
+                                              "/org/gtk/GDBus/PeerTestObject",
+                                              "org.gtk.GDBus.PeerTestInterface",
+                                              test_interface_introspection_data,
+                                              &test_interface_vtable,
+                                              data,
+                                              NULL, /* GDestroyNotify for data */
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (reg_id > 0);
+
+  g_main_loop_quit (loop);
+}
+
+static gpointer
+service_thread_func (gpointer user_data)
+{
+  PeerData *data = user_data;
+  GMainContext *service_context;
+  GDBusAuthObserver *observer;
+  GError *error;
+
+  service_context = g_main_context_new ();
+  g_main_context_push_thread_default (service_context);
+
+  error = NULL;
+  observer = g_dbus_auth_observer_new ();
+  server = g_dbus_server_new_sync (is_unix ? "unix:tmpdir=/tmp/gdbus-test-" : "nonce-tcp:",
+                                   G_DBUS_SERVER_FLAGS_NONE,
+                                   test_guid,
+                                   observer,
+                                   NULL, /* cancellable */
+                                   &error);
+  g_assert_no_error (error);
+
+  g_signal_connect (server,
+                    "new-connection",
+                    G_CALLBACK (on_new_connection),
+                    data);
+  g_signal_connect (observer,
+                    "deny-authenticated-peer",
+                    G_CALLBACK (on_deny_authenticated_peer),
+                    data);
+  g_object_unref (observer);
+
+  g_dbus_server_start (server);
+
+  service_loop = g_main_loop_new (service_context, FALSE);
+  g_main_loop_run (service_loop);
+
+  g_main_context_pop_thread_default (service_context);
+
+  g_main_loop_unref (service_loop);
+  g_main_context_unref (service_context);
+
+  /* test code specifically unrefs the server - see below */
+  g_assert (server == NULL);
+
+  return NULL;
+}
+
+#if 0
+static gboolean
+on_incoming_connection (GSocketService     *service,
+                        GSocketConnection  *socket_connection,
+                        GObject            *source_object,
+                        gpointer           user_data)
+{
+  PeerData *data = user_data;
+
+  if (data->accept_connection)
+    {
+      GError *error;
+      guint reg_id;
+      GDBusConnection *connection;
+
+      error = NULL;
+      connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
+                                               test_guid,
+                                               G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+                                               NULL, /* cancellable */
+                                               &error);
+      g_assert_no_error (error);
+
+      g_ptr_array_add (data->current_connections, connection);
+
+      /* export object on the newly established connection */
+      error = NULL;
+      reg_id = g_dbus_connection_register_object (connection,
+                                                  "/org/gtk/GDBus/PeerTestObject",
+                                                  "org.gtk.GDBus.PeerTestInterface",
+                                                  &test_interface_introspection_data,
+                                                  &test_interface_vtable,
+                                                  data,
+                                                  NULL, /* GDestroyNotify for data */
+                                                  &error);
+      g_assert_no_error (error);
+      g_assert (reg_id > 0);
+
+    }
+  else
+    {
+      /* don't do anything */
+    }
+
+  data->num_connection_attempts++;
+
+  g_main_loop_quit (loop);
+
+  /* stops other signal handlers from being invoked */
+  return TRUE;
+}
+
+static gpointer
+service_thread_func (gpointer data)
+{
+  GMainContext *service_context;
+  gchar *socket_path;
+  GSocketAddress *address;
+  GError *error;
+
+  service_context = g_main_context_new ();
+  g_main_context_push_thread_default (service_context);
+
+  socket_path = g_strdup_printf ("/tmp/gdbus-test-pid-%d", getpid ());
+  address = g_unix_socket_address_new (socket_path);
+
+  service = g_socket_service_new ();
+  error = NULL;
+  g_socket_listener_add_address (G_SOCKET_LISTENER (service),
+                                 address,
+                                 G_SOCKET_TYPE_STREAM,
+                                 G_SOCKET_PROTOCOL_DEFAULT,
+                                 NULL, /* source_object */
+                                 NULL, /* effective_address */
+                                 &error);
+  g_assert_no_error (error);
+  g_signal_connect (service,
+                    "incoming",
+                    G_CALLBACK (on_incoming_connection),
+                    data);
+  g_socket_service_start (service);
+
+  service_loop = g_main_loop_new (service_context, FALSE);
+  g_main_loop_run (service_loop);
+
+  g_main_context_pop_thread_default (service_context);
+
+  g_main_loop_unref (service_loop);
+  g_main_context_unref (service_context);
+
+  g_object_unref (address);
+  g_free (socket_path);
+  return NULL;
+}
+#endif
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#if 0
+static gboolean
+check_connection (gpointer user_data)
+{
+  PeerData *data = user_data;
+  guint n;
+
+  for (n = 0; n < data->current_connections->len; n++)
+    {
+      GDBusConnection *c;
+      GIOStream *stream;
+
+      c = G_DBUS_CONNECTION (data->current_connections->pdata[n]);
+      stream = g_dbus_connection_get_stream (c);
+
+      g_debug ("In check_connection for %d: connection %p, stream %p", n, c, stream);
+      g_debug ("closed = %d", g_io_stream_is_closed (stream));
+
+      GSocket *socket;
+      socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
+      g_debug ("socket_closed = %d", g_socket_is_closed (socket));
+      g_debug ("socket_condition_check = %d", g_socket_condition_check (socket, G_IO_IN|G_IO_OUT|G_IO_ERR|G_IO_HUP));
+
+      gchar buf[128];
+      GError *error;
+      gssize num_read;
+      error = NULL;
+      num_read = g_input_stream_read (g_io_stream_get_input_stream (stream),
+                                      buf,
+                                      128,
+                                      NULL,
+                                      &error);
+      if (num_read < 0)
+        {
+          g_debug ("error: %s", error->message);
+          g_error_free (error);
+        }
+      else
+        {
+          g_debug ("no error, read %d bytes", (gint) num_read);
+        }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+on_do_disconnect_in_idle (gpointer data)
+{
+  GDBusConnection *c = G_DBUS_CONNECTION (data);
+  g_debug ("GDC %p has ref_count %d", c, G_OBJECT (c)->ref_count);
+  g_dbus_connection_disconnect (c);
+  g_object_unref (c);
+  return FALSE;
+}
+#endif
+
+static void
+test_peer (void)
+{
+  GDBusConnection *c;
+  GDBusConnection *c2;
+  GDBusProxy *proxy;
+  GError *error;
+  PeerData data;
+  GVariant *value;
+  GVariant *result;
+  const gchar *s;
+  GThread *service_thread;
+
+  memset (&data, '\0', sizeof (PeerData));
+  data.current_connections = g_ptr_array_new_with_free_func (g_object_unref);
+
+  /* first try to connect when there is no server */
+  error = NULL;
+  c = g_dbus_connection_new_for_address_sync (is_unix ? "unix:path=/tmp/gdbus-test-does-not-exist-pid" :
+                                              /* NOTE: Even if something is listening on port 12345 the connection
+                                               * will fail because the nonce file doesn't exist */
+                                              "nonce-tcp:host=localhost,port=12345,noncefile=this-does-not-exist-gdbus",
+                                              G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                              NULL, /* cancellable */
+                                              &error);
+  _g_assert_error_domain (error, G_IO_ERROR);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_clear_error (&error);
+  g_assert (c == NULL);
+
+  /* bring up a server - we run the server in a different thread to avoid deadlocks */
+  error = NULL;
+  service_thread = g_thread_create (service_thread_func,
+                                    &data,
+                                    TRUE,
+                                    &error);
+  while (service_loop == NULL)
+    g_thread_yield ();
+  g_assert (server != NULL);
+
+  /* bring up a connection and accept it */
+  data.accept_connection = TRUE;
+  error = NULL;
+  c = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+                                              G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                              NULL, /* cancellable */
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+  while (data.current_connections->len < 1)
+    g_main_loop_run (loop);
+  g_assert_cmpint (data.current_connections->len, ==, 1);
+  g_assert_cmpint (data.num_connection_attempts, ==, 1);
+  //g_assert (g_dbus_connection_get_bus_type (c) == G_BUS_TYPE_NONE);
+  g_assert (g_dbus_connection_get_unique_name (c) == NULL);
+  g_assert_cmpstr (g_dbus_connection_get_guid (c), ==, test_guid);
+
+  /* check that we create a proxy, read properties, receive signals and invoke
+   * the HelloPeer() method. Since the server runs in another thread it's fine
+   * to use synchronous blocking API here.
+   */
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (c,
+                                 G_TYPE_DBUS_PROXY,
+                                 G_DBUS_PROXY_FLAGS_NONE,
+                                 NULL,
+                                 NULL, /* bus_name */
+                                 "/org/gtk/GDBus/PeerTestObject",
+                                 "org.gtk.GDBus.PeerTestInterface",
+                                 NULL, /* GCancellable */
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+  error = NULL;
+  value = g_dbus_proxy_get_cached_property (proxy, "PeerProperty", &error);
+  g_assert_no_error (error);
+  g_assert_cmpstr (g_variant_get_string (value, NULL), ==, "ThePropertyValue");
+
+  /* try invoking a method */
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloPeer",
+                                            g_variant_new ("(s)", "Hey Peer!"),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,  /* GCancellable */
+                                            &error);
+  g_assert_no_error (error);
+  g_variant_get (result, "(s)", &s);
+  g_assert_cmpstr (s, ==, "You greeted me with 'Hey Peer!'.");
+  g_variant_unref (result);
+  g_assert_cmpint (data.num_method_calls, ==, 1);
+
+  /* make the other peer emit a signal - catch it */
+  g_signal_connect (proxy,
+                    "g-signal",
+                    G_CALLBACK (on_proxy_signal_received),
+                    &data);
+  g_assert (!data.signal_received);
+  g_dbus_proxy_invoke_method (proxy,
+                              "EmitSignal",
+                              NULL,  /* no arguments */
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,  /* GCancellable */
+                              NULL,  /* GAsyncReadyCallback - we don't care about the result */
+                              NULL); /* user_data */
+  g_main_loop_run (loop);
+  g_assert (data.signal_received);
+  g_assert_cmpint (data.num_method_calls, ==, 2);
+
+  /* check for UNIX fd passing */
+#ifdef G_OS_UNIX
+  {
+    GDBusMessage *method_call_message;
+    GDBusMessage *method_reply_message;
+    GUnixFDList *fd_list;
+    gint fd;
+    gchar buf[1024];
+    gssize len;
+    gchar *buf2;
+    gsize len2;
+
+    method_call_message = g_dbus_message_new_method_call (NULL, /* name */
+                                                          "/org/gtk/GDBus/PeerTestObject",
+                                                          "org.gtk.GDBus.PeerTestInterface",
+                                                          "OpenFile");
+    g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", "/etc/hosts"));
+    error = NULL;
+    method_reply_message = g_dbus_connection_send_message_with_reply_sync (c,
+                                                                           method_call_message,
+                                                                           -1,
+                                                                           NULL, /* out_serial */
+                                                                           NULL, /* cancellable */
+                                                                           &error);
+    g_assert_no_error (error);
+    g_assert (g_dbus_message_get_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
+    fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
+    g_assert (fd_list != NULL);
+    g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1);
+    error = NULL;
+    fd = g_unix_fd_list_get (fd_list, 0, &error);
+    g_assert_no_error (error);
+    g_object_unref (method_call_message);
+    g_object_unref (method_reply_message);
+
+    memset (buf, '\0', sizeof (buf));
+    len = read (fd, buf, sizeof (buf) - 1);
+    close (fd);
+
+    error = NULL;
+    g_file_get_contents ("/etc/hosts",
+                         &buf2,
+                         &len2,
+                         &error);
+    g_assert_no_error (error);
+    if (len2 > sizeof (buf))
+      buf2[sizeof (buf)] = '\0';
+    g_assert_cmpstr (buf, ==, buf2);
+    g_free (buf2);
+  }
+#endif /* G_OS_UNIX */
+
+
+  /* bring up a connection - don't accept it - this should fail
+   */
+  data.accept_connection = FALSE;
+  error = NULL;
+  c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+                                               G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                               NULL, /* cancellable */
+                                               &error);
+  _g_assert_error_domain (error, G_IO_ERROR);
+  g_assert (c2 == NULL);
+
+#if 0
+  /* TODO: THIS TEST DOESN'T WORK YET */
+
+  /* bring up a connection - accept it.. then disconnect from the client side - check
+   * that the server side gets the disconnect signal.
+   */
+  error = NULL;
+  data.accept_connection = TRUE;
+  c2 = g_dbus_connection_new_for_address_sync (g_dbus_server_get_client_address (server),
+                                               G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                               NULL, /* cancellable */
+                                               &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (!g_dbus_connection_get_is_disconnected (c2));
+  while (data.num_connection_attempts < 3)
+    g_main_loop_run (loop);
+  g_assert_cmpint (data.current_connections->len, ==, 2);
+  g_assert_cmpint (data.num_connection_attempts, ==, 3);
+  g_assert (!g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1])));
+  g_idle_add (on_do_disconnect_in_idle, c2);
+  g_debug ("==================================================");
+  g_debug ("==================================================");
+  g_debug ("==================================================");
+  g_debug ("waiting for disconnect on connection %p, stream %p",
+           data.current_connections->pdata[1],
+           g_dbus_connection_get_stream (data.current_connections->pdata[1]));
+
+  g_timeout_add (2000, check_connection, &data);
+  //_g_assert_signal_received (G_DBUS_CONNECTION (data.current_connections->pdata[1]), "closed");
+  g_main_loop_run (loop);
+  g_assert (g_dbus_connection_get_is_disconnected (G_DBUS_CONNECTION (data.current_connections->pdata[1])));
+  g_ptr_array_set_size (data.current_connections, 1); /* remove disconnected connection object */
+#endif
+
+  /* unref the server and stop listening for new connections
+   *
+   * This won't bring down the established connections - check that c is still connected
+   * by invoking a method
+   */
+  //g_socket_service_stop (service);
+  //g_object_unref (service);
+  g_dbus_server_stop (server);
+  g_object_unref (server);
+  server = NULL;
+
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloPeer",
+                                            g_variant_new ("(s)", "Hey Again Peer!"),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,  /* GCancellable */
+                                            &error);
+  g_assert_no_error (error);
+  g_variant_get (result, "(s)", &s);
+  g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'.");
+  g_variant_unref (result);
+  g_assert_cmpint (data.num_method_calls, ==, 4);
+
+#if 0
+  /* TODO: THIS TEST DOESN'T WORK YET */
+
+  /* now disconnect from the server side - check that the client side gets the signal */
+  g_assert_cmpint (data.current_connections->len, ==, 1);
+  g_assert (G_DBUS_CONNECTION (data.current_connections->pdata[0]) != c);
+  g_dbus_connection_disconnect (G_DBUS_CONNECTION (data.current_connections->pdata[0]));
+  if (!g_dbus_connection_get_is_disconnected (c))
+    _g_assert_signal_received (c, "closed");
+  g_assert (g_dbus_connection_get_is_disconnected (c));
+#endif
+
+  g_object_unref (c);
+  g_ptr_array_unref (data.current_connections);
+  g_object_unref (proxy);
+
+  g_main_loop_quit (service_loop);
+  g_thread_join (service_thread);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+  GDBusNodeInfo *introspection_data = NULL;
+
+  g_type_init ();
+  g_thread_init (NULL);
+  g_test_init (&argc, &argv, NULL);
+
+  introspection_data = g_dbus_node_info_new_for_xml (test_interface_introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+  test_interface_introspection_data = introspection_data->interfaces[0];
+
+  test_guid = g_dbus_generate_guid ();
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  g_test_add_func ("/gdbus/peer-to-peer", test_peer);
+
+  ret = g_test_run();
+
+  g_main_loop_unref (loop);
+  g_free (test_guid);
+  g_dbus_node_info_unref (introspection_data);
+
+  return ret;
+}
diff --git a/gio/tests/gdbus-proxy.c b/gio/tests/gdbus-proxy.c
new file mode 100644
index 0000000..6e45f42
--- /dev/null
+++ b/gio/tests/gdbus-proxy.c
@@ -0,0 +1,455 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the method aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_methods (GDBusConnection *connection,
+              const gchar     *name,
+              const gchar     *name_owner,
+              GDBusProxy      *proxy)
+{
+  GVariant *result;
+  GError *error;
+  const gchar *str;
+  gchar *dbus_error_name;
+
+  /* check that we can invoke a method */
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloWorld",
+                                            g_variant_new ("(s)", "Hey"),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "(s)");
+  g_variant_get (result, "(s)", &str);
+  g_assert_cmpstr (str, ==, "You greeted me with 'Hey'. Thanks!");
+  g_variant_unref (result);
+
+  /* Check that we can completely recover the returned error */
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloWorld",
+                                            g_variant_new ("(s)", "Yo"),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+  g_assert (g_dbus_error_is_remote_error (error));
+  g_assert (g_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  dbus_error_name = g_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, "com.example.TestException");
+  g_free (dbus_error_name);
+  g_assert (g_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "Yo is not a proper greeting");
+  g_clear_error (&error);
+
+  /* Check that we get a timeout if the method handling is taking longer than timeout */
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            g_variant_new ("(i)", 500 /* msec */),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            100 /* msec */,
+                                            NULL,
+                                            &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  g_clear_error (&error);
+
+  /* Check that proxy-default timeouts work. */
+  g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, -1);
+
+  /* the default timeout is 25000 msec so this should work */
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            g_variant_new ("(i)", 500 /* msec */),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1, /* use proxy default (e.g. -1 -> e.g. 25000 msec) */
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+  g_variant_unref (result);
+
+  /* now set the proxy-default timeout to 250 msec and try the 500 msec call - this should FAIL */
+  g_dbus_proxy_set_default_timeout (proxy, 250);
+  g_assert_cmpint (g_dbus_proxy_get_default_timeout (proxy), ==, 250);
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            g_variant_new ("(i)", 500 /* msec */),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1, /* use proxy default (e.g. 250 msec) */
+                                            NULL,
+                                            &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  g_clear_error (&error);
+
+  /* clean up after ourselves */
+  g_dbus_proxy_set_default_timeout (proxy, -1);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the property aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_properties (GDBusConnection *connection,
+                 const gchar     *name,
+                 const gchar     *name_owner,
+                 GDBusProxy      *proxy)
+{
+  GError *error;
+  GVariant *variant;
+  GVariant *variant2;
+  GVariant *result;
+
+  error = NULL;
+
+  /*
+   * Check that we can read cached properties.
+   *
+   * No need to test all properties - GVariant has already been tested
+   */
+  variant = g_dbus_proxy_get_cached_property (proxy, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (g_variant_get_byte (variant), ==, 1);
+  g_variant_unref (variant);
+  variant = g_dbus_proxy_get_cached_property (proxy, "o", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpstr (g_variant_get_string (variant, NULL), ==, "/some/path");
+  g_variant_unref (variant);
+
+  /*
+   * Now ask the service to change a property and check that #GDBusProxy::g-property-changed
+   * is received. Also check that the cache is updated.
+   */
+  variant2 = g_variant_new_byte (42);
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "FrobSetProperty",
+                                            g_variant_new ("(sv)",
+                                                           "y",
+                                                           variant2),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+  g_variant_unref (result);
+  _g_assert_signal_received (proxy, "g-properties-changed");
+  variant = g_dbus_proxy_get_cached_property (proxy, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (g_variant_get_byte (variant), ==, 42);
+  g_variant_unref (variant);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the signal aspects of GDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_proxy_signals_on_signal (GDBusProxy  *proxy,
+                              const gchar *sender_name,
+                              const gchar *signal_name,
+                              GVariant    *parameters,
+                              gpointer     user_data)
+{
+  GString *s = user_data;
+
+  g_assert_cmpstr (signal_name, ==, "TestSignal");
+  g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(sov)");
+
+  g_variant_print_string (parameters, s, TRUE);
+}
+
+typedef struct
+{
+  GMainLoop *internal_loop;
+  GString *s;
+} TestSignalData;
+
+static void
+test_proxy_signals_on_emit_signal_cb (GDBusProxy   *proxy,
+                                      GAsyncResult *res,
+                                      gpointer      user_data)
+{
+  TestSignalData *data = user_data;
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+  g_variant_unref (result);
+
+  /* check that the signal was recieved before we got the method result */
+  g_assert (strlen (data->s->str) > 0);
+
+  /* break out of the loop */
+  g_main_loop_quit (data->internal_loop);
+}
+
+static void
+test_signals (GDBusConnection *connection,
+              const gchar     *name,
+              const gchar     *name_owner,
+              GDBusProxy      *proxy)
+{
+  GError *error;
+  GString *s;
+  gulong signal_handler_id;
+  TestSignalData data;
+  GVariant *result;
+
+  error = NULL;
+
+  /*
+   * Ask the service to emit a signal and check that we receive it.
+   *
+   * Note that blocking calls don't block in the mainloop so wait for the signal (which
+   * is dispatched before the method reply)
+   */
+  s = g_string_new (NULL);
+  signal_handler_id = g_signal_connect (proxy,
+                                        "g-signal",
+                                        G_CALLBACK (test_proxy_signals_on_signal),
+                                        s);
+
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "EmitSignal",
+                                            g_variant_new ("(so)",
+                                                           "Accept the next proposition you hear",
+                                                           "/some/path"),
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+  g_variant_unref (result);
+  /* check that we haven't received the signal just yet */
+  g_assert (strlen (s->str) == 0);
+  /* and now wait for the signal */
+  _g_assert_signal_received (proxy, "g-signal");
+  g_assert_cmpstr (s->str,
+                   ==,
+                   "('Accept the next proposition you hear .. in bed!', objectpath '/some/path/in/bed', <'a variant'>)");
+  g_signal_handler_disconnect (proxy, signal_handler_id);
+  g_string_free (s, TRUE);
+
+  /*
+   * Now do this async to check the signal is received before the method returns.
+   */
+  s = g_string_new (NULL);
+  data.internal_loop = g_main_loop_new (NULL, FALSE);
+  data.s = s;
+  signal_handler_id = g_signal_connect (proxy,
+                                        "g-signal",
+                                        G_CALLBACK (test_proxy_signals_on_signal),
+                                        s);
+  g_dbus_proxy_invoke_method (proxy,
+                              "EmitSignal",
+                              g_variant_new ("(so)",
+                                             "You will make a great programmer",
+                                             "/some/other/path"),
+                              G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) test_proxy_signals_on_emit_signal_cb,
+                              &data);
+  g_main_loop_run (data.internal_loop);
+  g_main_loop_unref (data.internal_loop);
+  g_assert_cmpstr (s->str,
+                   ==,
+                   "('You will make a great programmer .. in bed!', objectpath '/some/other/path/in/bed', <'a variant'>)");
+  g_signal_handler_disconnect (proxy, signal_handler_id);
+  g_string_free (s, TRUE);
+}
+
+static void
+test_bogus_method_return (GDBusConnection *connection,
+                          const gchar     *name,
+                          const gchar     *name_owner,
+                          GDBusProxy      *proxy)
+{
+  GError *error = NULL;
+  GVariant *result;
+
+  result = g_dbus_proxy_invoke_method_sync (proxy,
+                                            "PairReturn",
+                                            NULL,
+                                            G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
+  g_assert (result == NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *frob_dbus_interface_xml =
+  "<node>"
+  "  <interface name='com.example.Frob'>"
+  /* Deliberately different from gdbus-testserver.py's definition */
+  "    <method name='PairReturn'>"
+  "      <arg type='u' name='somenumber' direction='in'/>"
+  "      <arg type='s' name='somestring' direction='out'/>"
+  "    </method>"
+  "    <method name='HelloWorld'>"
+  "      <arg type='s' name='somestring' direction='in'/>"
+  "      <arg type='s' name='somestring' direction='out'/>"
+  "    </method>"
+  "    <method name='Sleep'>"
+  "      <arg type='i' name='timeout' direction='in'/>"
+  "    </method>"
+  "  </interface>"
+  "</node>";
+static GDBusInterfaceInfo *frob_dbus_interface_info;
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   GDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  test_methods (connection, name, name_owner, proxy);
+  test_properties (connection, name, name_owner, proxy);
+  test_signals (connection, name, name_owner, proxy);
+
+  /* Now repeat the method tests, with an expected interface set */
+  g_dbus_proxy_set_interface_info (proxy, frob_dbus_interface_info);
+  test_methods (connection, name, name_owner, proxy);
+
+  /* And now one more test where we deliberately set the expected
+   * interface definition incorrectly
+   */
+  test_bogus_method_return (connection, name, name_owner, proxy);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+}
+
+static void
+test_proxy (void)
+{
+  guint watcher_id;
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  G_TYPE_DBUS_PROXY,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_proxy (watcher_id);
+
+  /* tear down bus */
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+  GDBusNodeInfo *introspection_data = NULL;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  introspection_data = g_dbus_node_info_new_for_xml (frob_dbus_interface_xml, NULL);
+  g_assert (introspection_data != NULL);
+  frob_dbus_interface_info = introspection_data->interfaces[0];
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/proxy", test_proxy);
+
+  ret = g_test_run();
+
+  g_dbus_node_info_unref (introspection_data);
+  return ret;
+}
diff --git a/gio/tests/gdbus-serialization.c b/gio/tests/gdbus-serialization.c
new file mode 100644
index 0000000..91b79f8
--- /dev/null
+++ b/gio/tests/gdbus-serialization.c
@@ -0,0 +1,650 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+
+#include <string.h>
+#include <unistd.h>
+#include <dbus/dbus.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+hexdump (const guchar *str, gsize len)
+{
+  const guchar *data = (const guchar *) str;
+  guint n, m;
+
+  for (n = 0; n < len; n += 16)
+    {
+      g_printerr ("%04x: ", n);
+
+      for (m = n; m < n + 16; m++)
+        {
+          if (m > n && (m%4) == 0)
+            g_printerr (" ");
+          if (m < len)
+            g_printerr ("%02x ", data[m]);
+          else
+            g_printerr ("   ");
+        }
+
+      g_printerr ("   ");
+
+      for (m = n; m < len && m < n + 16; m++)
+        g_printerr ("%c", g_ascii_isprint (data[m]) ? data[m] : '.');
+
+      g_printerr ("\n");
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+append_gv_to_dbus_iter (DBusMessageIter  *iter,
+                        GVariant         *value,
+                        GError          **error)
+{
+  const GVariantType *type;
+
+  type = g_variant_get_type (value);
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_BOOLEAN))
+    {
+      dbus_bool_t v = g_variant_get_boolean (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_BOOLEAN, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_BYTE))
+    {
+      guint8 v = g_variant_get_byte (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_BYTE, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+    {
+      gint16 v = g_variant_get_int16 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_INT16, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+    {
+      guint16 v = g_variant_get_uint16 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT16, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+    {
+      gint32 v = g_variant_get_int32 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_INT32, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+    {
+      guint32 v = g_variant_get_uint32 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT32, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+    {
+      gint64 v = g_variant_get_int64 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_INT64, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+    {
+      guint64 v = g_variant_get_uint64 (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT64, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+    {
+      gdouble v = g_variant_get_double (value);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_DOUBLE, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_STRING))
+    {
+      const gchar *v = g_variant_get_string (value, NULL);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_STRING, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_OBJECT_PATH))
+    {
+      const gchar *v = g_variant_get_string (value, NULL);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_OBJECT_PATH, &v);
+    }
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_SIGNATURE))
+    {
+      const gchar *v = g_variant_get_string (value, NULL);
+      dbus_message_iter_append_basic (iter, DBUS_TYPE_SIGNATURE, &v);
+    }
+  else if (g_variant_type_is_variant (type))
+    {
+      DBusMessageIter sub;
+      GVariant *child;
+
+      child = g_variant_get_child_value (value, 0);
+      dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT,
+                                        g_variant_get_type_string (child),
+                                        &sub);
+      if (!append_gv_to_dbus_iter (&sub, child, error))
+        {
+            g_variant_unref (child);
+            goto fail;
+        }
+      dbus_message_iter_close_container (iter, &sub);
+      g_variant_unref (child);
+    }
+  else if (g_variant_type_is_array (type))
+    {
+      DBusMessageIter dbus_iter;
+      const gchar *type_string;
+      GVariantIter gv_iter;
+      GVariant *item;
+
+      type_string = g_variant_get_type_string (value);
+      type_string++; /* skip the 'a' */
+
+      dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY,
+                                        type_string, &dbus_iter);
+      g_variant_iter_init (&gv_iter, value);
+
+      while ((item = g_variant_iter_next_value (&gv_iter)))
+        {
+          if (!append_gv_to_dbus_iter (&dbus_iter, item, error))
+            {
+              goto fail;
+            }
+        }
+
+      dbus_message_iter_close_container (iter, &dbus_iter);
+    }
+  else if (g_variant_type_is_tuple (type))
+    {
+      DBusMessageIter dbus_iter;
+      GVariantIter gv_iter;
+      GVariant *item;
+
+      dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT,
+                                        NULL, &dbus_iter);
+      g_variant_iter_init (&gv_iter, value);
+
+      while ((item = g_variant_iter_next_value (&gv_iter)))
+        {
+          if (!append_gv_to_dbus_iter (&dbus_iter, item, error))
+            goto fail;
+        }
+
+      dbus_message_iter_close_container (iter, &dbus_iter);
+    }
+  else if (g_variant_type_is_dict_entry (type))
+    {
+      DBusMessageIter dbus_iter;
+      GVariant *key, *val;
+
+      dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY,
+                                        NULL, &dbus_iter);
+      key = g_variant_get_child_value (value, 0);
+      if (!append_gv_to_dbus_iter (&dbus_iter, key, error))
+        {
+          g_variant_unref (key);
+          goto fail;
+        }
+      g_variant_unref (key);
+
+      val = g_variant_get_child_value (value, 1);
+      if (!append_gv_to_dbus_iter (&dbus_iter, val, error))
+        {
+          g_variant_unref (val);
+          goto fail;
+        }
+      g_variant_unref (val);
+
+      dbus_message_iter_close_container (iter, &dbus_iter);
+    }
+  else
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_ARGUMENT,
+                   "Error serializing GVariant with type-string `%s' to a D-Bus message",
+                   g_variant_get_type_string (value));
+      goto fail;
+    }
+
+  return TRUE;
+
+ fail:
+  return FALSE;
+}
+
+static gboolean
+append_gv_to_dbus_message (DBusMessage  *message,
+                           GVariant     *value,
+                           GError      **error)
+{
+  gboolean ret;
+  guint n;
+
+  ret = FALSE;
+
+  if (value != NULL)
+    {
+      DBusMessageIter iter;
+      GVariantIter gv_iter;
+      GVariant *item;
+
+      dbus_message_iter_init_append (message, &iter);
+
+      g_variant_iter_init (&gv_iter, value);
+      n = 0;
+      while ((item = g_variant_iter_next_value (&gv_iter)))
+        {
+          if (!append_gv_to_dbus_iter (&iter, item, error))
+            {
+              g_prefix_error (error,
+                              "Error encoding in-arg %d: ",
+                              n);
+              goto out;
+            }
+          n++;
+        }
+    }
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+print_gv_dbus_message (GVariant *value)
+{
+  DBusMessage *message;
+  char *blob;
+  int blob_len;
+  GError *error;
+
+  message = dbus_message_new (DBUS_MESSAGE_TYPE_METHOD_CALL);
+  dbus_message_set_serial (message, 0x41);
+  dbus_message_set_path (message, "/foo/bar");
+  dbus_message_set_member (message, "Member");
+
+  error = NULL;
+  if (!append_gv_to_dbus_message (message, value, &error))
+    {
+      g_printerr ("Error printing GVariant as DBusMessage: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  dbus_message_marshal (message, &blob, &blob_len);
+  g_printerr ("\n");
+  hexdump ((guchar *) blob, blob_len);
+ out:
+  dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+dbus_1_message_append (GString *s,
+                       guint indent,
+                       DBusMessageIter *iter)
+{
+  gint arg_type;
+  DBusMessageIter sub;
+
+  g_string_append_printf (s, "%*s", indent, "");
+
+  arg_type = dbus_message_iter_get_arg_type (iter);
+  switch (arg_type)
+    {
+     case DBUS_TYPE_BOOLEAN:
+      {
+        dbus_bool_t value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "bool: %s\n", value ? "true" : "false");
+        break;
+      }
+
+     case DBUS_TYPE_BYTE:
+      {
+        guchar value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "byte: 0x%02x\n", (guint) value);
+        break;
+      }
+
+     case DBUS_TYPE_INT16:
+      {
+        gint16 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "int16: %" G_GINT16_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_UINT16:
+      {
+        guint16 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "uint16: %" G_GUINT16_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_INT32:
+      {
+        gint32 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "int32: %" G_GINT32_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_UINT32:
+      {
+        guint32 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "uint32: %" G_GUINT32_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_INT64:
+      {
+        gint64 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "int64: %" G_GINT64_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_UINT64:
+      {
+        guint64 value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "uint64: %" G_GUINT64_FORMAT "\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_DOUBLE:
+      {
+        gdouble value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "double: %f\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_STRING:
+      {
+        const gchar *value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "string: `%s'\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_OBJECT_PATH:
+      {
+        const gchar *value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "object_path: `%s'\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_SIGNATURE:
+      {
+        const gchar *value;
+        dbus_message_iter_get_basic (iter, &value);
+        g_string_append_printf (s, "signature: `%s'\n", value);
+        break;
+      }
+
+     case DBUS_TYPE_VARIANT:
+       g_string_append_printf (s, "variant:\n");
+       dbus_message_iter_recurse (iter, &sub);
+       while (dbus_message_iter_get_arg_type (&sub))
+         {
+           dbus_1_message_append (s, indent + 2, &sub);
+           dbus_message_iter_next (&sub);
+         }
+       break;
+
+     case DBUS_TYPE_ARRAY:
+       g_string_append_printf (s, "array:\n");
+       dbus_message_iter_recurse (iter, &sub);
+       while (dbus_message_iter_get_arg_type (&sub))
+         {
+           dbus_1_message_append (s, indent + 2, &sub);
+           dbus_message_iter_next (&sub);
+         }
+       break;
+
+     case DBUS_TYPE_STRUCT:
+       g_string_append_printf (s, "struct:\n");
+       dbus_message_iter_recurse (iter, &sub);
+       while (dbus_message_iter_get_arg_type (&sub))
+         {
+           dbus_1_message_append (s, indent + 2, &sub);
+           dbus_message_iter_next (&sub);
+         }
+       break;
+
+     case DBUS_TYPE_DICT_ENTRY:
+       g_string_append_printf (s, "dict_entry:\n");
+       dbus_message_iter_recurse (iter, &sub);
+       while (dbus_message_iter_get_arg_type (&sub))
+         {
+           dbus_1_message_append (s, indent + 2, &sub);
+           dbus_message_iter_next (&sub);
+         }
+       break;
+
+     default:
+       g_printerr ("Error serializing D-Bus message to GVariant. Unsupported arg type `%c' (%d)",
+                   arg_type,
+                   arg_type);
+       g_assert_not_reached ();
+       break;
+    }
+}
+
+static gchar *
+dbus_1_message_print (DBusMessage *message)
+{
+  GString *s;
+  guint n;
+  DBusMessageIter iter;
+
+  s = g_string_new (NULL);
+  n = 0;
+  dbus_message_iter_init (message, &iter);
+  while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID)
+    {
+      g_string_append_printf (s, "value %d: ", n);
+      dbus_1_message_append (s, 2, &iter);
+      dbus_message_iter_next (&iter);
+      n++;
+    }
+
+  return g_string_free (s, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_body_signature (GVariant *value)
+{
+  const gchar *s;
+  gsize len;
+  gchar *ret;
+
+  s = g_variant_get_type_string (value);
+  len = strlen (s);
+  g_assert (len>=2);
+
+  ret = g_strndup (s + 1, len - 2);
+
+  return ret;
+}
+
+static void
+check_serialization (GVariant *value,
+                     const gchar *expected_dbus_1_output)
+{
+  guchar *blob;
+  gsize blob_size;
+  DBusMessage *dbus_1_message;
+  GDBusMessage *message;
+  GDBusMessage *recovered_message;
+  GError *error;
+  DBusError dbus_error;
+  gchar *s;
+  gchar *s1;
+
+  message = g_dbus_message_new ();
+  g_dbus_message_set_body (message, value);
+  g_dbus_message_set_type (message, G_DBUS_MESSAGE_TYPE_METHOD_CALL);
+  g_dbus_message_set_serial (message, 0x41);
+  s = get_body_signature (value);
+  g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_PATH, g_variant_new_object_path ("/foo/bar"));
+  g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_MEMBER, g_variant_new_string ("Member"));
+  g_dbus_message_set_header (message, G_DBUS_MESSAGE_HEADER_FIELD_SIGNATURE, g_variant_new_signature (s));
+  g_free (s);
+
+  /* First check that the serialization to the D-Bus wire format is correct */
+
+  error = NULL;
+  blob = g_dbus_message_to_blob (message,
+                                 &blob_size,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (blob != NULL);
+
+  dbus_error_init (&dbus_error);
+  dbus_1_message = dbus_message_demarshal ((char *) blob, blob_size, &dbus_error);
+  if (dbus_error_is_set (&dbus_error))
+    {
+      g_printerr ("Error calling dbus_message_demarshal() on this blob: %s: %s\n",
+                  dbus_error.name,
+                  dbus_error.message);
+      hexdump (blob, blob_size);
+      dbus_error_free (&dbus_error);
+
+      s = g_variant_print (value, TRUE);
+      g_printerr ("\nThe blob was generated from the following GVariant value:\n%s\n\n", s);
+      g_free (s);
+
+      g_printerr ("If the blob was encoded using DBusMessageIter, the payload would have been:\n");
+      print_gv_dbus_message (value);
+
+      g_assert_not_reached ();
+    }
+
+  s = dbus_1_message_print (dbus_1_message);
+  dbus_message_unref (dbus_1_message);
+
+  g_assert_cmpstr (s, ==, expected_dbus_1_output);
+  g_free (s);
+
+  /* Then serialize back and check that the body is identical */
+
+  error = NULL;
+  recovered_message = g_dbus_message_new_from_blob (blob, blob_size, &error);
+  g_assert_no_error (error);
+  g_assert (recovered_message != NULL);
+  g_assert (g_dbus_message_get_body (recovered_message) != NULL);
+
+  if (!g_variant_equal (g_dbus_message_get_body (recovered_message), value))
+    {
+      s = g_variant_print (g_dbus_message_get_body (recovered_message), TRUE);
+      s1 = g_variant_print (value, TRUE);
+      g_printerr ("Recovered value:\n%s\ndoes not match given value\n%s\n",
+                  s,
+                  s1);
+      g_free (s);
+      g_free (s1);
+      g_assert_not_reached ();
+    }
+  g_object_unref (message);
+  g_object_unref (recovered_message);
+}
+
+static void
+message_serialize_basic (void)
+{
+  check_serialization (g_variant_new ("(sogybnqiuxtd)",
+                                      "this is a string",
+                                      "/this/is/a/path",
+                                      "sad",
+                                      42,
+                                      TRUE,
+                                      -42,
+                                      60000,
+                                      -44,
+                                      100000,
+                                      -G_GINT64_CONSTANT(2)<<34,
+                                      G_GUINT64_CONSTANT(0xffffffffffffffff),
+                                      42.5),
+                       "value 0:   string: `this is a string'\n"
+                       "value 1:   object_path: `/this/is/a/path'\n"
+                       "value 2:   signature: `sad'\n"
+                       "value 3:   byte: 0x2a\n"
+                       "value 4:   bool: true\n"
+                       "value 5:   int16: -42\n"
+                       "value 6:   uint16: 60000\n"
+                       "value 7:   int32: -44\n"
+                       "value 8:   uint32: 100000\n"
+                       "value 9:   int64: -34359738368\n"
+                       "value 10:   uint64: 18446744073709551615\n"
+                       "value 11:   double: 42.500000\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+message_serialize_complex (void)
+{
+  GError *error;
+  GVariant *value;
+
+  error = NULL;
+  value = g_variant_parse (G_VARIANT_TYPE ("(aia{ss})"),
+                           "([1, 2, 3], {'one': 'white', 'two': 'black'})",
+                           NULL, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (value != NULL);
+  check_serialization (value,
+                       "value 0:   array:\n"
+                       "    int32: 1\n"
+                       "    int32: 2\n"
+                       "    int32: 3\n"
+                       "value 1:   array:\n"
+                       "    dict_entry:\n"
+                       "      string: `one'\n"
+                       "      string: `white'\n"
+                       "    dict_entry:\n"
+                       "      string: `two'\n"
+                       "      string: `black'\n");
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/gdbus/message-serialize-basic", message_serialize_basic);
+  g_test_add_func ("/gdbus/message-serialize-complex", message_serialize_complex);
+  return g_test_run();
+}
+
diff --git a/gio/tests/gdbus-sessionbus.c b/gio/tests/gdbus-sessionbus.c
new file mode 100644
index 0000000..1e16edd
--- /dev/null
+++ b/gio/tests/gdbus-sessionbus.c
@@ -0,0 +1,342 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "gdbus-sessionbus.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Utilities for bringing up and tearing down session message bus instances */
+
+static void
+watch_parent (gint fd)
+{
+  GPollFD fds[1];
+  gint num_events;
+  gchar buf[512];
+  guint bytes_read;
+  GArray *buses_to_kill_array;
+
+  fds[0].fd = fd;
+  fds[0].events = G_IO_HUP | G_IO_IN;
+  fds[0].revents = 0;
+
+  buses_to_kill_array = g_array_new (FALSE, TRUE, sizeof (guint));
+
+  do
+    {
+      guint pid;
+      guint n;
+
+      num_events = g_poll (fds, 1, -1);
+      if (num_events == 0)
+        continue;
+
+      if (fds[0].revents == G_IO_HUP)
+        {
+          for (n = 0; n < buses_to_kill_array->len; n++)
+            {
+              pid = g_array_index (buses_to_kill_array, guint, n);
+              g_print ("cleaning up bus with pid %d\n", pid);
+              kill (pid, SIGTERM);
+            }
+          g_array_free (buses_to_kill_array, TRUE);
+          exit (0);
+        }
+
+      //g_debug ("data from parent");
+
+      memset (buf, '\0', sizeof buf);
+    again:
+      bytes_read = read (fds[0].fd, buf, sizeof buf);
+      if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+        goto again;
+
+      if (sscanf (buf, "add %d\n", &pid) == 1)
+        {
+          g_array_append_val (buses_to_kill_array, pid);
+        }
+      else if (sscanf (buf, "remove %d\n", &pid) == 1)
+        {
+          for (n = 0; n < buses_to_kill_array->len; n++)
+            {
+              if (g_array_index (buses_to_kill_array, guint, n) == pid)
+                {
+                  g_array_remove_index (buses_to_kill_array, n);
+                  pid = 0;
+                  break;
+                }
+            }
+          if (pid != 0)
+            {
+              g_warning ("unknown pid %d to remove", pid);
+            }
+        }
+      else
+        {
+          g_warning ("unknown command from parent '%s'", buf);
+        }
+    }
+  while (TRUE);
+
+}
+
+static GHashTable *session_bus_address_to_pid = NULL;
+static gint pipe_fds[2];
+
+const gchar *
+session_bus_up_with_address (const gchar *given_address)
+{
+  gchar *address;
+  int stdout_fd;
+  GError *error;
+  gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
+  GPid pid;
+  gchar buf[512];
+  ssize_t bytes_read;
+  gchar *config_file_name;
+  gint config_file_fd;
+  GString *config_file_contents;
+
+  address = NULL;
+  error = NULL;
+  config_file_name = NULL;
+  config_file_fd = -1;
+  argv[2] = NULL;
+
+  config_file_fd = g_file_open_tmp ("g-dbus-tests-XXXXXX",
+                                    &config_file_name,
+                                    &error);
+  if (config_file_fd < 0)
+    {
+      g_warning ("Error creating temporary config file: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  config_file_contents = g_string_new (NULL);
+  g_string_append        (config_file_contents, "<busconfig>\n");
+  g_string_append        (config_file_contents, "  <type>session</type>\n");
+  g_string_append_printf (config_file_contents, "  <listen>%s</listen>\n", given_address);
+  g_string_append        (config_file_contents,
+                          "  <policy context=\"default\">\n"
+                          "    <!-- Allow everything to be sent -->\n"
+                          "    <allow send_destination=\"*\" eavesdrop=\"true\"/>\n"
+                          "    <!-- Allow everything to be received -->\n"
+                          "    <allow eavesdrop=\"true\"/>\n"
+                          "    <!-- Allow anyone to own anything -->\n"
+                          "    <allow own=\"*\"/>\n"
+                          "  </policy>\n");
+  g_string_append        (config_file_contents, "</busconfig>\n");
+
+  if (write (config_file_fd, config_file_contents->str, config_file_contents->len) != (gssize) config_file_contents->len)
+    {
+      g_warning ("Error writing %d bytes to config file: %m", (gint) config_file_contents->len);
+      g_string_free (config_file_contents, TRUE);
+      goto out;
+    }
+  g_string_free (config_file_contents, TRUE);
+
+  argv[2] = g_strdup_printf ("--config-file=%s", config_file_name);
+
+  if (session_bus_address_to_pid == NULL)
+    {
+      /* keep a mapping from session bus address to the pid */
+      session_bus_address_to_pid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+      /* fork a child to clean up session buses when we are killed */
+      if (pipe (pipe_fds) != 0)
+        {
+          g_warning ("pipe() failed: %m");
+          g_assert_not_reached ();
+        }
+      switch (fork ())
+        {
+        case -1:
+          g_warning ("fork() failed: %m");
+          g_assert_not_reached ();
+          break;
+
+        case 0:
+          /* child */
+          close (pipe_fds[1]);
+          watch_parent (pipe_fds[0]);
+          break;
+
+        default:
+          /* parent */
+          close (pipe_fds[0]);
+          break;
+        }
+
+      //atexit (cleanup_session_buses);
+      /* TODO: need to handle the cases where we crash */
+    }
+  else
+    {
+      /* check if we already have a bus running for this address */
+      if (g_hash_table_lookup (session_bus_address_to_pid, given_address) != NULL)
+        {
+          g_warning ("Already have a bus instance for the given address %s", given_address);
+          goto out;
+        }
+    }
+
+  if (!g_spawn_async_with_pipes (NULL,
+                                 argv,
+                                 NULL,
+                                 G_SPAWN_SEARCH_PATH,
+                                 NULL,
+                                 NULL,
+                                 &pid,
+                                 NULL,
+                                 &stdout_fd,
+                                 NULL,
+                                 &error))
+    {
+      g_warning ("Error spawning dbus-daemon: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  memset (buf, '\0', sizeof buf);
+ again:
+  bytes_read = read (stdout_fd, buf, sizeof buf);
+  if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+    goto again;
+  close (stdout_fd);
+
+  if (bytes_read == 0 || bytes_read == sizeof buf)
+    {
+      g_warning ("Error reading address from dbus daemon, %d bytes read", (gint) bytes_read);
+      kill (SIGTERM, pid);
+      goto out;
+    }
+
+  address = g_strdup (buf);
+  g_strstrip (address);
+
+  /* write the pid to the child so it can kill it when we die */
+  g_snprintf (buf, sizeof buf, "add %d\n", (guint) pid);
+  write (pipe_fds[1], buf, strlen (buf));
+
+  /* start dbus-monitor */
+  if (g_getenv ("G_DBUS_MONITOR") != NULL)
+    {
+      g_spawn_command_line_async ("dbus-monitor --session", NULL);
+      usleep (500 * 1000);
+    }
+
+  g_hash_table_insert (session_bus_address_to_pid, address, GUINT_TO_POINTER (pid));
+
+ out:
+  if (config_file_fd > 0)
+    {
+      if (close (config_file_fd) != 0)
+        {
+          g_warning ("Error closing fd for config file %s: %m", config_file_name);
+        }
+      g_assert (config_file_name != NULL);
+      if (unlink (config_file_name) != 0)
+        {
+          g_warning ("Error unlinking config file %s: %m", config_file_name);
+        }
+    }
+  g_free (argv[2]);
+  g_free (config_file_name);
+  return address;
+}
+
+void
+session_bus_down_with_address (const gchar *address)
+{
+  gpointer value;
+  GPid pid;
+  gchar buf[512];
+
+  g_assert (address != NULL);
+  g_assert (session_bus_address_to_pid != NULL);
+
+  value = g_hash_table_lookup (session_bus_address_to_pid, address);
+  g_assert (value != NULL);
+
+  pid = GPOINTER_TO_UINT (g_hash_table_lookup (session_bus_address_to_pid, address));
+
+  kill (pid, SIGTERM);
+
+  /* write the pid to the child so it won't kill it when we die */
+  g_snprintf (buf, sizeof buf, "remove %d\n", (guint) pid);
+  write (pipe_fds[1], buf, strlen (buf));
+
+  g_hash_table_remove (session_bus_address_to_pid, address);
+}
+
+static gchar *temporary_address = NULL;
+static gchar *temporary_address_used_by_bus = NULL;
+
+const gchar *
+session_bus_get_temporary_address (void)
+{
+  if (temporary_address == NULL)
+    {
+      /* TODO: maybe use a more random name etc etc */
+      temporary_address = g_strdup_printf ("unix:path=/tmp/g-dbus-tests-pid-%d", getpid ());
+    }
+
+  return temporary_address;
+}
+
+const gchar *
+session_bus_up (void)
+{
+  if (temporary_address_used_by_bus != NULL)
+    {
+      g_warning ("There is already a session bus up");
+      goto out;
+    }
+
+  temporary_address_used_by_bus = g_strdup (session_bus_up_with_address (session_bus_get_temporary_address ()));
+
+ out:
+  return temporary_address_used_by_bus;
+}
+
+void
+session_bus_down (void)
+{
+  if (temporary_address_used_by_bus == NULL)
+    {
+      g_warning ("There is not a session bus up");
+    }
+  else
+    {
+      session_bus_down_with_address (temporary_address_used_by_bus);
+      g_free (temporary_address_used_by_bus);
+      temporary_address_used_by_bus = NULL;
+    }
+}
diff --git a/gio/tests/gdbus-sessionbus.h b/gio/tests/gdbus-sessionbus.h
new file mode 100644
index 0000000..e107e06
--- /dev/null
+++ b/gio/tests/gdbus-sessionbus.h
@@ -0,0 +1,38 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __SESSION_BUS_H__
+#define __SESSION_BUS_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+const gchar *session_bus_up_with_address       (const gchar *given_address);
+void         session_bus_down_with_address     (const gchar *address);
+const gchar *session_bus_get_temporary_address (void);
+const gchar *session_bus_up                    (void);
+void         session_bus_down                  (void);
+
+G_END_DECLS
+
+#endif /* __SESSION_BUS_H__ */
diff --git a/gio/tests/gdbus-tests.c b/gio/tests/gdbus-tests.c
new file mode 100644
index 0000000..b8ac849
--- /dev/null
+++ b/gio/tests/gdbus-tests.c
@@ -0,0 +1,218 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+
+#include "gdbus-tests.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean   timed_out;
+} PropertyNotifyData;
+
+static void
+on_property_notify (GObject    *object,
+                    GParamSpec *pspec,
+                    gpointer    user_data)
+{
+  PropertyNotifyData *data = user_data;
+  g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_property_notify_timeout (gpointer user_data)
+{
+  PropertyNotifyData *data = user_data;
+  data->timed_out = TRUE;
+  g_main_loop_quit (data->loop);
+  return TRUE;
+}
+
+gboolean
+_g_assert_property_notify_run (gpointer     object,
+                               const gchar *property_name)
+{
+  gchar *s;
+  gulong handler_id;
+  guint timeout_id;
+  PropertyNotifyData data;
+
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.timed_out = FALSE;
+  s = g_strdup_printf ("notify::%s", property_name);
+  handler_id = g_signal_connect (object,
+                                 s,
+                                 G_CALLBACK (on_property_notify),
+                                 &data);
+  g_free (s);
+  timeout_id = g_timeout_add (5 * 1000,
+                              on_property_notify_timeout,
+                              &data);
+  g_main_loop_run (data.loop);
+  g_signal_handler_disconnect (object, handler_id);
+  g_source_remove (timeout_id);
+  g_main_loop_unref (data.loop);
+
+  return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean   timed_out;
+} SignalReceivedData;
+
+static void
+on_signal_received (gpointer user_data)
+{
+  SignalReceivedData *data = user_data;
+  g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_signal_received_timeout (gpointer user_data)
+{
+  SignalReceivedData *data = user_data;
+  data->timed_out = TRUE;
+  g_main_loop_quit (data->loop);
+  return TRUE;
+}
+
+gboolean
+_g_assert_signal_received_run (gpointer     object,
+                               const gchar *signal_name)
+{
+  gulong handler_id;
+  guint timeout_id;
+  SignalReceivedData data;
+
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.timed_out = FALSE;
+  handler_id = g_signal_connect_swapped (object,
+                                         signal_name,
+                                         G_CALLBACK (on_signal_received),
+                                         &data);
+  timeout_id = g_timeout_add (5 * 1000,
+                              on_signal_received_timeout,
+                              &data);
+  g_main_loop_run (data.loop);
+  g_signal_handler_disconnect (object, handler_id);
+  g_source_remove (timeout_id);
+  g_main_loop_unref (data.loop);
+
+  return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GDBusConnection *
+_g_bus_get_priv (GBusType            bus_type,
+                 GCancellable       *cancellable,
+                 GError            **error)
+{
+  gchar *address;
+  GDBusConnection *ret;
+
+  ret = NULL;
+
+  address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error);
+  if (address == NULL)
+    goto out;
+
+  ret = g_dbus_connection_new_for_address_sync (address,
+                                                G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                                G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                                cancellable,
+                                                error);
+  g_free (address);
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean   timed_out;
+} WaitSingleRefData;
+
+static gboolean
+on_wait_single_ref_timeout (gpointer user_data)
+{
+  WaitSingleRefData *data = user_data;
+  data->timed_out = TRUE;
+  g_main_loop_quit (data->loop);
+  return TRUE;
+}
+
+static void
+on_wait_for_single_ref_toggled (gpointer   user_data,
+                                GObject   *object,
+                                gboolean   is_last_ref)
+{
+  WaitSingleRefData *data = user_data;
+  g_main_loop_quit (data->loop);
+}
+
+gboolean
+_g_object_wait_for_single_ref_do (gpointer object)
+{
+  WaitSingleRefData data;
+  guint timeout_id;
+
+  data.timed_out = FALSE;
+
+  if (G_OBJECT (object)->ref_count == 1)
+    goto out;
+
+  data.loop = g_main_loop_new (NULL, FALSE);
+  timeout_id = g_timeout_add (5 * 1000,
+                              on_wait_single_ref_timeout,
+                              &data);
+
+  g_object_add_toggle_ref (G_OBJECT (object),
+                           on_wait_for_single_ref_toggled,
+                           &data);
+  g_object_unref (object);
+
+  g_main_loop_run (data.loop);
+
+  g_object_ref (object);
+  g_object_remove_toggle_ref (object,
+                              on_wait_for_single_ref_toggled,
+                              &data);
+
+  g_source_remove (timeout_id);
+  g_main_loop_unref (data.loop);
+ out:
+  return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/gio/tests/gdbus-tests.h b/gio/tests/gdbus-tests.h
new file mode 100644
index 0000000..ffcc57c
--- /dev/null
+++ b/gio/tests/gdbus-tests.h
@@ -0,0 +1,146 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __TESTS_H__
+#define __TESTS_H__
+
+#include <gio/gio.h>
+#include "gdbus-sessionbus.h"
+
+G_BEGIN_DECLS
+
+/* TODO: clean up and move to gtestutils.c
+ *
+ * This is needed because libdbus-1 does not give predictable error messages - e.g. you
+ * get a different error message on connecting to a bus if the socket file is there vs
+ * if the socket file is missing.
+ */
+
+#define _g_assert_error_domain(err, dom)	do { if (!err || (err)->domain != dom) \
+      g_assertion_message_error (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+                                 #err, err, dom, -1); } while (0)
+
+#define _g_assert_property_notify(object, property_name)                \
+  do                                                                    \
+    {                                                                   \
+      if (!G_IS_OBJECT (object))                                        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Not a GObject instance");               \
+        }                                                               \
+      if (g_object_class_find_property (G_OBJECT_GET_CLASS (object),    \
+                                        property_name) == NULL)         \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Property " property_name " does not "   \
+                               "exist on object");                      \
+        }                                                               \
+      if (_g_assert_property_notify_run (object, property_name))        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Timed out waiting for notification "    \
+                               "on property " property_name);           \
+        }                                                               \
+    }                                                                   \
+  while (FALSE)
+
+#define _g_assert_signal_received(object, signal_name)                  \
+  do                                                                    \
+    {                                                                   \
+      if (!G_IS_OBJECT (object))                                        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Not a GObject instance");               \
+        }                                                               \
+      if (g_signal_lookup (signal_name,                                 \
+                           G_TYPE_FROM_INSTANCE (object)) == 0)         \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Signal `" signal_name "' does not "     \
+                               "exist on object");                      \
+        }                                                               \
+      if (_g_assert_signal_received_run (object, signal_name))          \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Timed out waiting for signal `"         \
+                               signal_name "'");                        \
+        }                                                               \
+    }                                                                   \
+  while (FALSE)
+
+gboolean _g_assert_property_notify_run (gpointer     object,
+                                        const gchar *property_name);
+
+
+gboolean _g_assert_signal_received_run (gpointer     object,
+                                        const gchar *signal_name);
+
+GDBusConnection *_g_bus_get_priv (GBusType            bus_type,
+                                  GCancellable       *cancellable,
+                                  GError            **error);
+
+
+#define _g_object_wait_for_single_ref(object) \
+  do \
+    { \
+      if (!G_IS_OBJECT (object))                                        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Not a GObject instance");               \
+        }                                                               \
+      if (_g_object_wait_for_single_ref_do (object))                    \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Timed out waiting for single ref");     \
+        }                                                               \
+    }                                                                   \
+  while (FALSE)
+
+gboolean _g_object_wait_for_single_ref_do (gpointer object);
+
+G_END_DECLS
+
+#endif /* __TESTS_H__ */
diff --git a/gio/tests/gdbus-testserver.py b/gio/tests/gdbus-testserver.py
new file mode 100755
index 0000000..04d654e
--- /dev/null
+++ b/gio/tests/gdbus-testserver.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+
+import gobject
+import time
+
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class TestException(dbus.DBusException):
+    _dbus_error_name = 'com.example.TestException'
+
+
+class TestService(dbus.service.Object):
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='s', out_signature='s')
+    def HelloWorld(self, hello_message):
+        if str(hello_message) == 'Yo':
+            raise TestException('Yo is not a proper greeting')
+        else:
+            return "You greeted me with '%s'. Thanks!"%(str(hello_message))
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='ss', out_signature='ss')
+    def DoubleHelloWorld(self, hello1, hello2):
+        return ("You greeted me with '%s'. Thanks!"%(str(hello1)), "Yo dawg, you uttered '%s'. Thanks!"%(str(hello2)))
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='', out_signature='su')
+    def PairReturn(self):
+        return ("foo", 42)
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='ybnqiuxtdsog', out_signature='ybnqiuxtdsog')
+    def TestPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double, val_string, val_objpath, val_signature):
+        return val_byte + 1, not val_boolean, val_int16 + 1, val_uint16 + 1, val_int32 + 1, val_uint32 + 1, val_int64 + 1, val_uint64 + 1, -val_double + 0.123, val_string * 2, val_objpath + "/modified", val_signature * 2
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='ayabanaqaiauaxatad', out_signature='ayabanaqaiauaxatad')
+    def TestArrayOfPrimitiveTypes(self, val_byte, val_boolean, val_int16, val_uint16, val_int32, val_uint32, val_int64, val_uint64, val_double):
+        return val_byte*2, val_boolean*2, val_int16*2, val_uint16*2, val_int32*2, val_uint32*2, val_int64*2, val_uint64*2, val_double*2
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='asaoag', out_signature='asaoag')
+    def TestArrayOfStringTypes(self, val_string, val_objpath, val_signature):
+        return val_string * 2, val_objpath * 2, val_signature * 2
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature  = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}',
+                         out_signature = 'a{yy}a{bb}a{nn}a{qq}a{ii}a{uu}a{xx}a{tt}a{dd}a{ss}a{oo}a{gg}')
+    def TestHashTables(self, hyy, hbb, hnn, hqq, hii, huu, hxx, htt, hdd, hss, hoo, hgg):
+
+        ret_hyy = {}
+        for i in hyy:
+            ret_hyy[i*2] = (hyy[i]*3) & 255
+
+        ret_hbb = {}
+        for i in hbb:
+            ret_hbb[i] = True
+
+        ret_hnn = {}
+        for i in hnn:
+            ret_hnn[i*2] = hnn[i]*3
+
+        ret_hqq = {}
+        for i in hqq:
+            ret_hqq[i*2] = hqq[i]*3
+
+        ret_hii = {}
+        for i in hii:
+            ret_hii[i*2] = hii[i]*3
+
+        ret_huu = {}
+        for i in huu:
+            ret_huu[i*2] = huu[i]*3
+
+        ret_hxx = {}
+        for i in hxx:
+            ret_hxx[i + 2] = hxx[i] + 1
+
+        ret_htt = {}
+        for i in htt:
+            ret_htt[i + 2] = htt[i] + 1
+
+        ret_hdd = {}
+        for i in hdd:
+            ret_hdd[i + 2.5] = hdd[i] + 5.0
+
+        ret_hss = {}
+        for i in hss:
+            ret_hss[i + "mod"] = hss[i]*2
+
+        ret_hoo = {}
+        for i in hoo:
+            ret_hoo[i + "/mod"] = hoo[i] + "/mod2"
+
+        ret_hgg = {}
+        for i in hgg:
+            ret_hgg[i + "assgit"] = hgg[i]*2
+
+        return ret_hyy, ret_hbb, ret_hnn, ret_hqq, ret_hii, ret_huu, ret_hxx, ret_htt, ret_hdd, ret_hss, ret_hoo, ret_hgg
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='(ii)(s(ii)aya{ss})', out_signature='(ii)(s(ii)aya{ss})')
+    def TestStructureTypes(self, s1, s2):
+        (x, y) = s1;
+        (desc, (x1, y1), ay, hss) = s2;
+        ret_hss = {}
+        for i in hss:
+            ret_hss[i] = hss[i] + " ... in bed!"
+        return (x + 1, y + 1), (desc + " ... in bed!", (x1 + 2, y1 + 2), ay * 2, ret_hss)
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='vb', out_signature='v')
+    def TestVariant(self, v, modify):
+
+        if modify:
+            if type(v)==dbus.Boolean:
+                ret = False
+            elif type(v)==dbus.Dictionary:
+                ret = {}
+                for i in v:
+                    ret[i] = v[i] * 2
+            elif type(v)==dbus.Struct:
+                ret = ["other struct", dbus.Int16(100)]
+            else:
+                ret = v * 2
+        else:
+            ret = v
+        return (type(v))(ret)
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                         in_signature='a(ii)aa(ii)aasaa{ss}aayavaav', out_signature='a(ii)aa(ii)aasaa{ss}aayavaav')
+    def TestComplexArrays(self, aii, aaii, aas, ahashes, aay, av, aav):
+        return aii * 2, aaii * 2, aas * 2, ahashes * 2, aay * 2, av *2, aav * 2
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}',
+                         out_signature='a{s(ii)}a{sv}a{sav}a{saav}a{sa(ii)}a{sa{ss}}')
+    def TestComplexHashTables(self, h_str_to_pair, h_str_to_variant, h_str_to_av, h_str_to_aav,
+                              h_str_to_array_of_pairs, hash_of_hashes):
+
+        ret_h_str_to_pair = {}
+        for i in h_str_to_pair:
+            ret_h_str_to_pair[i + "_baz"] = h_str_to_pair[i]
+
+        ret_h_str_to_variant = {}
+        for i in h_str_to_variant:
+            ret_h_str_to_variant[i + "_baz"] = h_str_to_variant[i]
+
+        return ret_h_str_to_pair, ret_h_str_to_variant, h_str_to_av, h_str_to_aav, h_str_to_array_of_pairs, hash_of_hashes
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='', out_signature='')
+    def Quit(self):
+        mainloop.quit()
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='sv', out_signature='')
+    def FrobSetProperty(self, prop_name, prop_value):
+        self.frob_props[prop_name] = prop_value
+        message = dbus.lowlevel.SignalMessage("/com/example/TestObject",
+                                              "org.freedesktop.DBus.Properties",
+                                              "PropertiesChanged")
+        message.append("com.example.Frob")
+        message.append({prop_name : prop_value})
+        session_bus.send_message(message)
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.signal("com.example.Frob",
+                         signature="sov")
+    def TestSignal(self, str1, objpath1, variant1):
+        pass
+
+    @dbus.service.method("com.example.Frob",
+                          in_signature='so', out_signature='')
+    def EmitSignal(self, str1, objpath1):
+        self.TestSignal (str1 + " .. in bed!", objpath1 + "/in/bed", "a variant")
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("com.example.Frob", in_signature='i', out_signature='',
+                         async_callbacks=('return_cb', 'raise_cb'))
+    def Sleep(self, msec, return_cb, raise_cb):
+        def return_from_async_wait():
+            return_cb()
+            return False
+        gobject.timeout_add(msec, return_from_async_wait)
+
+    # ----------------------------------------------------------------------------------------------------
+
+    @dbus.service.method("org.freedesktop.DBus.Properties",
+                         in_signature  = 'ss',
+                         out_signature = 'v')
+    def Get(self, interface_name, property_name):
+
+        if interface_name == "com.example.Frob":
+            return self.frob_props[property_name]
+        else:
+            raise TestException("No such interface " + interface_name)
+
+    @dbus.service.method("org.freedesktop.DBus.Properties",
+                         in_signature  = 's',
+                         out_signature = 'a{sv}')
+    def GetAll(self, interface_name):
+        if interface_name == "com.example.Frob":
+            return self.frob_props
+        else:
+            raise TestException("No such interface " + interface_name)
+
+if __name__ == '__main__':
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+    session_bus = dbus.SessionBus()
+    name = dbus.service.BusName("com.example.TestService", session_bus)
+
+    obj = TestService(session_bus, '/com/example/TestObject')
+
+    #print "Our unique name is %s"%(session_bus.get_unique_name())
+
+    obj.frob_props = {}
+    obj.frob_props["y"] = dbus.Byte(1)
+    obj.frob_props["b"] = dbus.Boolean(True)
+    obj.frob_props["n"] = dbus.Int16(2)
+    obj.frob_props["q"] = dbus.UInt16(3)
+    obj.frob_props["i"] = dbus.Int32(4)
+    obj.frob_props["u"] = dbus.UInt32(5)
+    obj.frob_props["x"] = dbus.Int64(6)
+    obj.frob_props["t"] = dbus.UInt64(7)
+    obj.frob_props["d"] = dbus.Double(7.5)
+    obj.frob_props["s"] = dbus.String("a string")
+    obj.frob_props["o"] = dbus.ObjectPath("/some/path")
+    obj.frob_props["ay"] = [dbus.Byte(1), dbus.Byte(11)]
+    obj.frob_props["ab"] = [dbus.Boolean(True), dbus.Boolean(False)]
+    obj.frob_props["an"] = [dbus.Int16(2), dbus.Int16(12)]
+    obj.frob_props["aq"] = [dbus.UInt16(3), dbus.UInt16(13)]
+    obj.frob_props["ai"] = [dbus.Int32(4), dbus.Int32(14)]
+    obj.frob_props["au"] = [dbus.UInt32(5), dbus.UInt32(15)]
+    obj.frob_props["ax"] = [dbus.Int64(6), dbus.Int64(16)]
+    obj.frob_props["at"] = [dbus.UInt64(7), dbus.UInt64(17)]
+    obj.frob_props["ad"] = [dbus.Double(7.5), dbus.Double(17.5)]
+    obj.frob_props["as"] = [dbus.String("a string"), dbus.String("another string")]
+    obj.frob_props["ao"] = [dbus.ObjectPath("/some/path"), dbus.ObjectPath("/another/path")]
+    obj.frob_props["foo"] = "a frobbed string"
+
+    mainloop = gobject.MainLoop()
+    mainloop.run()
diff --git a/gio/tests/gdbus-threading.c b/gio/tests/gdbus-threading.c
new file mode 100644
index 0000000..0747907
--- /dev/null
+++ b/gio/tests/gdbus-threading.c
@@ -0,0 +1,532 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "gdbus-tests.h"
+
+/* all tests rely on a global connection */
+static GDBusConnection *c = NULL;
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Ensure that signal and method replies are delivered in the right thread */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  GThread *thread;
+  GMainLoop *thread_loop;
+  guint signal_count;
+} DeliveryData;
+
+static void
+msg_cb_expect_success (GDBusConnection *connection,
+                       GAsyncResult    *res,
+                       gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_variant_unref (result);
+
+  g_assert (g_thread_self () == data->thread);
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (GDBusConnection *connection,
+                               GAsyncResult    *res,
+                               gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_assert (!g_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_assert (g_thread_self () == data->thread);
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static void
+signal_handler (GDBusConnection *connection,
+                const gchar      *sender_name,
+                const gchar      *object_path,
+                const gchar      *interface_name,
+                const gchar      *signal_name,
+                GVariant         *parameters,
+                gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+
+  g_assert (g_thread_self () == data->thread);
+
+  data->signal_count++;
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static gpointer
+test_delivery_in_thread_func (gpointer _data)
+{
+  GMainLoop *thread_loop;
+  GMainContext *thread_context;
+  DeliveryData data;
+  GCancellable *ca;
+  guint subscription_id;
+  GDBusConnection *priv_c;
+  GError *error;
+
+  error = NULL;
+
+  thread_context = g_main_context_new ();
+  thread_loop = g_main_loop_new (thread_context, FALSE);
+  g_main_context_push_thread_default (thread_context);
+
+  data.thread = g_thread_self ();
+  data.thread_loop = thread_loop;
+  data.signal_count = 0;
+
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  /*
+   * Check that we get a reply to the GetId() method call.
+   */
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_success,
+                                   &data);
+  g_main_loop_run (thread_loop);
+
+  /*
+   * Check that we never actually send a message if the GCancellable
+   * is already cancelled - i.e.  we should get #G_IO_ERROR_CANCELLED
+   * when the actual connection is not up.
+   */
+  ca = g_cancellable_new ();
+  g_cancellable_cancel (ca);
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   &data);
+  g_main_loop_run (thread_loop);
+  g_object_unref (ca);
+
+  /*
+   * Check that cancellation works when the message is already in flight.
+   */
+  ca = g_cancellable_new ();
+  g_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   &data);
+  g_cancellable_cancel (ca);
+  g_main_loop_run (thread_loop);
+  g_object_unref (ca);
+
+  /*
+   * Check that signals are delivered to the correct thread.
+   *
+   * First we subscribe to the signal, then we create a a private
+   * connection. This should cause a NameOwnerChanged message from
+   * the message bus.
+   */
+  subscription_id = g_dbus_connection_signal_subscribe (c,
+                                                        "org.freedesktop.DBus",  /* sender */
+                                                        "org.freedesktop.DBus",  /* interface */
+                                                        "NameOwnerChanged",      /* member */
+                                                        "/org/freedesktop/DBus", /* path */
+                                                        NULL,
+                                                        signal_handler,
+                                                        &data,
+                                                        NULL);
+  g_assert (subscription_id != 0);
+  g_assert (data.signal_count == 0);
+
+  priv_c = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (priv_c != NULL);
+
+  g_main_loop_run (thread_loop);
+  g_assert (data.signal_count == 1);
+
+  g_object_unref (priv_c);
+
+  g_dbus_connection_signal_unsubscribe (c, subscription_id);
+
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  g_main_context_pop_thread_default (thread_context);
+  g_main_loop_unref (thread_loop);
+  g_main_context_unref (thread_context);
+
+  g_main_loop_quit (loop);
+
+  return NULL;
+}
+
+static void
+test_delivery_in_thread (void)
+{
+  GError *error;
+  GThread *thread;
+
+  error = NULL;
+  thread = g_thread_create (test_delivery_in_thread_func,
+                            NULL,
+                            TRUE,
+                            &error);
+  g_assert_no_error (error);
+  g_assert (thread != NULL);
+
+  /* run the event loop - it is needed to dispatch D-Bus messages */
+  g_main_loop_run (loop);
+
+  g_thread_join (thread);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  GDBusProxy *proxy;
+  gint msec;
+  guint num;
+  gboolean async;
+
+  GMainLoop *thread_loop;
+  GThread *thread;
+
+  gboolean done;
+} SyncThreadData;
+
+static void
+sleep_cb (GDBusProxy   *proxy,
+          GAsyncResult *res,
+          gpointer      user_data)
+{
+  SyncThreadData *data = user_data;
+  GError *error;
+  GVariant *result;
+
+  error = NULL;
+  result = g_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+  g_variant_unref (result);
+
+  g_assert (data->thread == g_thread_self ());
+
+  g_main_loop_quit (data->thread_loop);
+
+  //g_debug ("async cb (%p)", g_thread_self ());
+}
+
+static gpointer
+test_sleep_in_thread_func (gpointer _data)
+{
+  SyncThreadData *data = _data;
+  GMainContext *thread_context;
+  guint n;
+
+  thread_context = g_main_context_new ();
+  data->thread_loop = g_main_loop_new (thread_context, FALSE);
+  g_main_context_push_thread_default (thread_context);
+
+  data->thread = g_thread_self ();
+
+  for (n = 0; n < data->num; n++)
+    {
+      if (data->async)
+        {
+          //g_debug ("invoking async (%p)", g_thread_self ());
+          g_dbus_proxy_invoke_method (data->proxy,
+                                      "Sleep",
+                                      g_variant_new ("(i)", data->msec),
+                                      G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                      -1,
+                                      NULL,
+                                      (GAsyncReadyCallback) sleep_cb,
+                                      data);
+          g_main_loop_run (data->thread_loop);
+          g_print ("A");
+          //g_debug ("done invoking async (%p)", g_thread_self ());
+        }
+      else
+        {
+          GError *error;
+          GVariant *result;
+
+          error = NULL;
+          //g_debug ("invoking sync (%p)", g_thread_self ());
+          result = g_dbus_proxy_invoke_method_sync (data->proxy,
+                                                    "Sleep",
+                                                    g_variant_new ("(i)", data->msec),
+                                                    G_DBUS_INVOKE_METHOD_FLAGS_NONE,
+                                                    -1,
+                                                    NULL,
+                                                    &error);
+          g_print ("S");
+          //g_debug ("done invoking sync (%p)", g_thread_self ());
+          g_assert_no_error (error);
+          g_assert (result != NULL);
+          g_assert_cmpstr (g_variant_get_type_string (result), ==, "()");
+          g_variant_unref (result);
+        }
+    }
+
+  g_main_context_pop_thread_default (thread_context);
+  g_main_loop_unref (data->thread_loop);
+  g_main_context_unref (thread_context);
+
+  data->done = TRUE;
+  g_main_loop_quit (loop);
+
+  return NULL;
+}
+
+static void
+on_proxy_appeared (GDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   GDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  guint n;
+
+  /*
+   * Check that multiple threads can do calls without interferring with
+   * each other. We do this by creating three threads that call the
+   * Sleep() method on the server (which handles it asynchronously, e.g.
+   * it won't block other requests) with different sleep durations and
+   * a number of times. We do this so each set of calls add up to 4000
+   * milliseconds.
+   *
+   * We run this test twice - first with async calls in each thread, then
+   * again with sync calls
+   */
+
+  for (n = 0; n < 2; n++)
+    {
+      gboolean do_async;
+      GThread *thread1;
+      GThread *thread2;
+      GThread *thread3;
+      SyncThreadData data1;
+      SyncThreadData data2;
+      SyncThreadData data3;
+      GError *error;
+      GTimeVal start_time;
+      GTimeVal end_time;
+      guint elapsed_msec;
+
+      error = NULL;
+      do_async = (n == 0);
+
+      g_get_current_time (&start_time);
+
+      data1.proxy = proxy;
+      data1.msec = 40;
+      data1.num = 100;
+      data1.async = do_async;
+      data1.done = FALSE;
+      thread1 = g_thread_create (test_sleep_in_thread_func,
+                                 &data1,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread1 != NULL);
+
+      data2.proxy = proxy;
+      data2.msec = 20;
+      data2.num = 200;
+      data2.async = do_async;
+      data2.done = FALSE;
+      thread2 = g_thread_create (test_sleep_in_thread_func,
+                                 &data2,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread2 != NULL);
+
+      data3.proxy = proxy;
+      data3.msec = 100;
+      data3.num = 40;
+      data3.async = do_async;
+      data3.done = FALSE;
+      thread3 = g_thread_create (test_sleep_in_thread_func,
+                                 &data3,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread3 != NULL);
+
+      /* we handle messages in the main loop - threads will quit it when they are done */
+      while (!(data1.done && data2.done && data3.done))
+        g_main_loop_run (loop);
+
+      g_thread_join (thread1);
+      g_thread_join (thread2);
+      g_thread_join (thread3);
+
+      g_get_current_time (&end_time);
+
+      elapsed_msec = ((end_time.tv_sec * G_USEC_PER_SEC + end_time.tv_usec) -
+                      (start_time.tv_sec * G_USEC_PER_SEC + start_time.tv_usec)) / 1000;
+
+      //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec);
+
+      /* elapsed_msec should be 4000 msec + change for overhead */
+      g_assert_cmpint (elapsed_msec, >=, 4000);
+      g_assert_cmpint (elapsed_msec,  <, 5000);
+
+      g_print (" ");
+    }
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (GDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+}
+
+static void
+test_method_calls_in_thread (void)
+{
+  guint watcher_id;
+
+  watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  G_TYPE_DBUS_PROXY,
+                                  G_DBUS_PROXY_FLAGS_NONE,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  g_main_loop_run (loop);
+
+  g_bus_unwatch_proxy (watcher_id);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  GError *error;
+  gint ret;
+
+  g_type_init ();
+  g_thread_init (NULL);
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./gdbus-testserver.py", NULL));
+
+  /* wait for the service to come up */
+  usleep (500 * 1000);
+
+  /* Create the connection in the main thread */
+  error = NULL;
+  c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+
+  g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread);
+  g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread);
+
+  ret = g_test_run();
+
+  g_object_unref (c);
+
+  /* tear down bus */
+  session_bus_down ();
+
+  return ret;
+}



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