mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-11-04 10:08:56 +01:00 
			
		
		
		
	This makes no functional changes, but does tidy the code up a bit and means `g_steal_handle_id()` gets a bit more testing. Signed-off-by: Philip Withnall <pwithnall@gnome.org>
		
			
				
	
	
		
			1338 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1338 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright 2024 Collabora Ltd.
 | 
						|
 * SPDX-License-Identifier: LGPL-2.1-or-later
 | 
						|
 */
 | 
						|
 | 
						|
#include <gio/gio.h>
 | 
						|
 | 
						|
#include "gdbusprivate.h"
 | 
						|
#include "gdbus-tests.h"
 | 
						|
 | 
						|
#define NAME_OWNER_CHANGED "NameOwnerChanged"
 | 
						|
 | 
						|
/* A signal that each connection emits to indicate that it has finished
 | 
						|
 * emitting other signals */
 | 
						|
#define FINISHED_PATH "/org/gtk/Test/Finished"
 | 
						|
#define FINISHED_INTERFACE "org.gtk.Test.Finished"
 | 
						|
#define FINISHED_SIGNAL "Finished"
 | 
						|
 | 
						|
/* A signal emitted during testing */
 | 
						|
#define EXAMPLE_PATH "/org/gtk/GDBus/ExampleInterface"
 | 
						|
#define EXAMPLE_INTERFACE "org.gtk.GDBus.ExampleInterface"
 | 
						|
#define FOO_SIGNAL "Foo"
 | 
						|
 | 
						|
#define ALREADY_OWNED_NAME "org.gtk.Test.AlreadyOwned"
 | 
						|
#define OWNED_LATER_NAME "org.gtk.Test.OwnedLater"
 | 
						|
 | 
						|
/* Log @s in a debug message. */
 | 
						|
static inline const char *
 | 
						|
nonnull (const char *s,
 | 
						|
         const char *if_null)
 | 
						|
{
 | 
						|
  return (s == NULL) ? if_null : s;
 | 
						|
}
 | 
						|
 | 
						|
typedef enum
 | 
						|
{
 | 
						|
  TEST_CONN_NONE,
 | 
						|
  TEST_CONN_FIRST,
 | 
						|
  /* A connection that subscribes to signals */
 | 
						|
  TEST_CONN_SUBSCRIBER = TEST_CONN_FIRST,
 | 
						|
  /* A mockup of a legitimate service */
 | 
						|
  TEST_CONN_SERVICE,
 | 
						|
  /* A mockup of a second legitimate service */
 | 
						|
  TEST_CONN_SERVICE2,
 | 
						|
  /* A connection that tries to trick @subscriber into processing its signals
 | 
						|
   * as if they came from @service */
 | 
						|
  TEST_CONN_ATTACKER,
 | 
						|
  NUM_TEST_CONNS
 | 
						|
} TestConn;
 | 
						|
 | 
						|
static const char * const test_conn_descriptions[NUM_TEST_CONNS] =
 | 
						|
{
 | 
						|
  "(unused)",
 | 
						|
  "subscriber",
 | 
						|
  "service",
 | 
						|
  "service 2",
 | 
						|
  "attacker"
 | 
						|
};
 | 
						|
 | 
						|
typedef enum
 | 
						|
{
 | 
						|
  SUBSCRIPTION_MODE_CONN,
 | 
						|
  SUBSCRIPTION_MODE_PROXY,
 | 
						|
  SUBSCRIPTION_MODE_PARALLEL
 | 
						|
} SubscriptionMode;
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  GDBusProxy *received_by_proxy;
 | 
						|
  TestConn sender;
 | 
						|
  char *path;
 | 
						|
  char *iface;
 | 
						|
  char *member;
 | 
						|
  GVariant *parameters;
 | 
						|
  char *arg0;
 | 
						|
  guint32 step;
 | 
						|
} ReceivedMessage;
 | 
						|
 | 
						|
static void
 | 
						|
received_message_free (ReceivedMessage *self)
 | 
						|
{
 | 
						|
 | 
						|
  g_clear_object (&self->received_by_proxy);
 | 
						|
  g_free (self->path);
 | 
						|
  g_free (self->iface);
 | 
						|
  g_free (self->member);
 | 
						|
  g_clear_pointer (&self->parameters, g_variant_unref);
 | 
						|
  g_free (self->arg0);
 | 
						|
  g_free (self);
 | 
						|
}
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  TestConn sender;
 | 
						|
  TestConn unicast_to;
 | 
						|
  const char *path;
 | 
						|
  const char *iface;
 | 
						|
  const char *member;
 | 
						|
  const char *arg0;
 | 
						|
  const char *args;
 | 
						|
  guint received_by_conn;
 | 
						|
  guint received_by_proxy;
 | 
						|
} TestEmitSignal;
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  const char *string_sender;
 | 
						|
  TestConn unique_sender;
 | 
						|
  const char *path;
 | 
						|
  const char *iface;
 | 
						|
  const char *member;
 | 
						|
  const char *arg0;
 | 
						|
  GDBusSignalFlags flags;
 | 
						|
  gboolean unsubscribe_immediately;
 | 
						|
} TestSubscribe;
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  const char *name;
 | 
						|
  TestConn owner;
 | 
						|
  guint received_by_conn;
 | 
						|
  guint received_by_proxy;
 | 
						|
} TestOwnName;
 | 
						|
 | 
						|
typedef enum
 | 
						|
{
 | 
						|
  TEST_ACTION_NONE = 0,
 | 
						|
  TEST_ACTION_SUBSCRIBE,
 | 
						|
  TEST_ACTION_EMIT_SIGNAL,
 | 
						|
  TEST_ACTION_OWN_NAME,
 | 
						|
} TestAction;
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  TestAction action;
 | 
						|
  union {
 | 
						|
    TestEmitSignal signal;
 | 
						|
    TestSubscribe subscribe;
 | 
						|
    TestOwnName own_name;
 | 
						|
    guint unsubscribe_undo_step;
 | 
						|
  } u;
 | 
						|
} TestStep;
 | 
						|
 | 
						|
/* Arbitrary, extend as necessary to accommodate the longest test */
 | 
						|
#define MAX_TEST_STEPS 10
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  const char *description;
 | 
						|
  TestStep steps[MAX_TEST_STEPS];
 | 
						|
} TestPlan;
 | 
						|
 | 
						|
static const TestPlan plan_simple =
 | 
						|
{
 | 
						|
  .description = "A broadcast is only received after subscribing to it",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* We don't receive a signal if we haven't subscribed yet */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Now it works */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        /* The proxy can't be used in this case, because it needs
 | 
						|
         * a bus name to subscribe to */
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_broadcast_from_anyone =
 | 
						|
{
 | 
						|
  .description = "A subscription with NULL sender accepts broadcast and unicast",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from anyone */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* First service sends a broadcast */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Second service also sends a broadcast */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE2,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* First service sends a unicast signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Second service also sends a unicast signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE2,
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_match_twice =
 | 
						|
{
 | 
						|
  .description = "A message matching more than one subscription is received "
 | 
						|
                 "once per subscription",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .unique_sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .unique_sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 4,
 | 
						|
        /* Only the first and last work with GDBusProxy */
 | 
						|
        .received_by_proxy = 2
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_limit_by_unique_name =
 | 
						|
{
 | 
						|
  .description = "A subscription via a unique name only accepts messages "
 | 
						|
                 "sent by that same unique name",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from service */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .unique_sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker wants to trick subscriber into thinking that service
 | 
						|
       * sent a signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker tries harder, by sending a signal unicast directly to
 | 
						|
       * the subscriber */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* When the real service sends a signal, it should still get through */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 1
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_nonexistent_unique_name =
 | 
						|
{
 | 
						|
  .description = "A subscription via a unique name that doesn't exist "
 | 
						|
                 "accepts no messages",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from service */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        /* This relies on the implementation detail that the dbus-daemon
 | 
						|
         * (and presumably other bus implementations) never actually generates
 | 
						|
         * a unique name in this format */
 | 
						|
        .string_sender = ":0.this.had.better.not.exist",
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker wants to trick subscriber into thinking that service
 | 
						|
       * sent a signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker tries harder, by sending a signal unicast directly to
 | 
						|
       * the subscriber */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_limit_by_well_known_name =
 | 
						|
{
 | 
						|
  .description = "A subscription via a well-known name only accepts messages "
 | 
						|
                 "sent by the owner of that well-known name",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Service already owns one name */
 | 
						|
      .action = TEST_ACTION_OWN_NAME,
 | 
						|
      .u.own_name = {
 | 
						|
        .name = ALREADY_OWNED_NAME,
 | 
						|
        .owner = TEST_CONN_SERVICE
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from service */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .string_sender = ALREADY_OWNED_NAME,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from service by another name */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .string_sender = OWNED_LATER_NAME,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker wants to trick subscriber into thinking that service
 | 
						|
       * sent a signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker tries harder, by sending a signal unicast directly to
 | 
						|
       * the subscriber */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* When the service sends a signal with the name it already owns,
 | 
						|
       * it should get through */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 1
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Service claims another name */
 | 
						|
      .action = TEST_ACTION_OWN_NAME,
 | 
						|
      .u.own_name = {
 | 
						|
        .name = OWNED_LATER_NAME,
 | 
						|
        .owner = TEST_CONN_SERVICE
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Now the subscriber gets this signal twice, once for each
 | 
						|
       * subscription; and similarly each of the two proxies gets this
 | 
						|
       * signal twice */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 2,
 | 
						|
        .received_by_proxy = 2
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_unsubscribe_immediately =
 | 
						|
{
 | 
						|
  .description = "Unsubscribing before GetNameOwner can return doesn't result in a crash",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Service already owns one name */
 | 
						|
      .action = TEST_ACTION_OWN_NAME,
 | 
						|
      .u.own_name = {
 | 
						|
        .name = ALREADY_OWNED_NAME,
 | 
						|
        .owner = TEST_CONN_SERVICE
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .string_sender = ALREADY_OWNED_NAME,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .unsubscribe_immediately = TRUE
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_SERVICE,
 | 
						|
        .path = EXAMPLE_PATH,
 | 
						|
        .iface = EXAMPLE_INTERFACE,
 | 
						|
        .member = FOO_SIGNAL,
 | 
						|
        .received_by_conn = 0,
 | 
						|
        /* The proxy can't unsubscribe, except by destroying the proxy
 | 
						|
         * completely, which we don't currently implement in this test */
 | 
						|
        .received_by_proxy = 1
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
static const TestPlan plan_limit_to_message_bus =
 | 
						|
{
 | 
						|
  .description = "A subscription to the message bus only accepts messages "
 | 
						|
                 "from the message bus",
 | 
						|
  .steps = {
 | 
						|
    {
 | 
						|
      /* Subscriber wants to receive signals from the message bus itself */
 | 
						|
      .action = TEST_ACTION_SUBSCRIBE,
 | 
						|
      .u.subscribe = {
 | 
						|
        .string_sender = DBUS_SERVICE_DBUS,
 | 
						|
        .path = DBUS_PATH_DBUS,
 | 
						|
        .iface = DBUS_INTERFACE_DBUS,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker wants to trick subscriber into thinking that the message
 | 
						|
       * bus sent a signal */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .path = DBUS_PATH_DBUS,
 | 
						|
        .iface = DBUS_INTERFACE_DBUS,
 | 
						|
        .member = NAME_OWNER_CHANGED,
 | 
						|
        .arg0 = "would I lie to you?",
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* Attacker tries harder, by sending a signal unicast directly to
 | 
						|
       * the subscriber, and using more realistic arguments */
 | 
						|
      .action = TEST_ACTION_EMIT_SIGNAL,
 | 
						|
      .u.signal = {
 | 
						|
        .unicast_to = TEST_CONN_SUBSCRIBER,
 | 
						|
        .sender = TEST_CONN_ATTACKER,
 | 
						|
        .path = DBUS_PATH_DBUS,
 | 
						|
        .iface = DBUS_INTERFACE_DBUS,
 | 
						|
        .member = NAME_OWNER_CHANGED,
 | 
						|
        .args = "('com.example.Name', '', ':1.12')",
 | 
						|
        .received_by_conn = 0,
 | 
						|
        .received_by_proxy = 0
 | 
						|
      },
 | 
						|
    },
 | 
						|
    {
 | 
						|
      /* When the message bus sends a signal (in this case triggered by
 | 
						|
       * owning a name), it should still get through */
 | 
						|
      .action = TEST_ACTION_OWN_NAME,
 | 
						|
      .u.own_name = {
 | 
						|
        .name = OWNED_LATER_NAME,
 | 
						|
        .owner = TEST_CONN_SERVICE,
 | 
						|
        .received_by_conn = 1,
 | 
						|
        .received_by_proxy = 1
 | 
						|
      },
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
  const TestPlan *plan;
 | 
						|
  SubscriptionMode mode;
 | 
						|
  GError *error;
 | 
						|
  /* (element-type ReceivedMessage) */
 | 
						|
  GPtrArray *received;
 | 
						|
  /* conns[TEST_CONN_NONE] is unused and remains NULL */
 | 
						|
  GDBusConnection *conns[NUM_TEST_CONNS];
 | 
						|
  /* Proxies on conns[TEST_CONN_SUBSCRIBER] */
 | 
						|
  GPtrArray *proxies;
 | 
						|
  /* unique_names[TEST_CONN_NONE] is unused and remains NULL */
 | 
						|
  const char *unique_names[NUM_TEST_CONNS];
 | 
						|
  /* finished[TEST_CONN_NONE] is unused and remains FALSE */
 | 
						|
  gboolean finished[NUM_TEST_CONNS];
 | 
						|
  /* Remains 0 for any step that is not a subscription */
 | 
						|
  guint subscriptions[MAX_TEST_STEPS];
 | 
						|
  /* Number of times the signal from step n was received */
 | 
						|
  guint received_by_conn[MAX_TEST_STEPS];
 | 
						|
  /* Number of times the signal from step n was received */
 | 
						|
  guint received_by_proxy[MAX_TEST_STEPS];
 | 
						|
  guint finished_subscription;
 | 
						|
} Fixture;
 | 
						|
 | 
						|
/* Wait for asynchronous messages from @conn to have been processed
 | 
						|
 * by the message bus, as a sequence point so that we can make
 | 
						|
 * "happens before" and "happens after" assertions relative to this.
 | 
						|
 * The easiest way to achieve this is to call a message bus method that has
 | 
						|
 * no arguments and wait for it to return: because the message bus processes
 | 
						|
 * messages in-order, anything we sent before this must have been processed
 | 
						|
 * by the time this call arrives. */
 | 
						|
static void
 | 
						|
connection_wait_for_bus (GDBusConnection *conn)
 | 
						|
{
 | 
						|
  GError *error = NULL;
 | 
						|
  GVariant *call_result;
 | 
						|
 | 
						|
  call_result = g_dbus_connection_call_sync (conn,
 | 
						|
                                             DBUS_SERVICE_DBUS,
 | 
						|
                                             DBUS_PATH_DBUS,
 | 
						|
                                             DBUS_INTERFACE_DBUS,
 | 
						|
                                             "GetId",
 | 
						|
                                             NULL,   /* arguments */
 | 
						|
                                             NULL,   /* result type */
 | 
						|
                                             G_DBUS_CALL_FLAGS_NONE,
 | 
						|
                                             -1,
 | 
						|
                                             NULL,
 | 
						|
                                             &error);
 | 
						|
  g_assert_no_error (error);
 | 
						|
  g_assert_nonnull (call_result);
 | 
						|
  g_variant_unref (call_result);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Called when the subscriber receives a message from any connection
 | 
						|
 * announcing that it has emitted all the signals that it plans to emit.
 | 
						|
 */
 | 
						|
static void
 | 
						|
subscriber_finished_cb (GDBusConnection *conn,
 | 
						|
                        const char      *sender_name,
 | 
						|
                        const char      *path,
 | 
						|
                        const char      *iface,
 | 
						|
                        const char      *member,
 | 
						|
                        GVariant        *parameters,
 | 
						|
                        void            *user_data)
 | 
						|
{
 | 
						|
  Fixture *f = user_data;
 | 
						|
  GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
 | 
						|
  guint i;
 | 
						|
 | 
						|
  g_assert_true (conn == subscriber);
 | 
						|
 | 
						|
  for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
    {
 | 
						|
      if (g_str_equal (sender_name, f->unique_names[i]))
 | 
						|
        {
 | 
						|
          g_assert_false (f->finished[i]);
 | 
						|
          f->finished[i] = TRUE;
 | 
						|
 | 
						|
          g_test_message ("Received Finished signal from %s %s",
 | 
						|
                          test_conn_descriptions[i], sender_name);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  g_error ("Received Finished signal from unknown sender %s", sender_name);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Called when we receive a signal, either via the GDBusProxy (proxy != NULL)
 | 
						|
 * or via the GDBusConnection (proxy == NULL).
 | 
						|
 */
 | 
						|
static void
 | 
						|
fixture_received_signal (Fixture    *f,
 | 
						|
                         GDBusProxy *proxy,
 | 
						|
                         const char *sender_name,
 | 
						|
                         const char *path,
 | 
						|
                         const char *iface,
 | 
						|
                         const char *member,
 | 
						|
                         GVariant   *parameters)
 | 
						|
{
 | 
						|
  guint i;
 | 
						|
  ReceivedMessage *received;
 | 
						|
 | 
						|
  /* Ignore the Finished signal if it matches a wildcard subscription */
 | 
						|
  if (g_str_equal (member, FINISHED_SIGNAL))
 | 
						|
    return;
 | 
						|
 | 
						|
  received = g_new0 (ReceivedMessage, 1);
 | 
						|
 | 
						|
  if (proxy != NULL)
 | 
						|
    received->received_by_proxy = g_object_ref (proxy);
 | 
						|
  else
 | 
						|
    received->received_by_proxy = NULL;
 | 
						|
 | 
						|
  received->path = g_strdup (path);
 | 
						|
  received->iface = g_strdup (iface);
 | 
						|
  received->member = g_strdup (member);
 | 
						|
  received->parameters = g_variant_ref (parameters);
 | 
						|
 | 
						|
  for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
    {
 | 
						|
      if (g_str_equal (sender_name, f->unique_names[i]))
 | 
						|
        {
 | 
						|
          received->sender = i;
 | 
						|
          g_assert_false (f->finished[i]);
 | 
						|
          break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  if (g_str_equal (sender_name, DBUS_SERVICE_DBUS))
 | 
						|
    {
 | 
						|
      g_test_message ("Signal received from message bus %s",
 | 
						|
                      sender_name);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      g_test_message ("Signal received from %s %s",
 | 
						|
                      test_conn_descriptions[received->sender],
 | 
						|
                      sender_name);
 | 
						|
      g_assert_cmpint (received->sender, !=, TEST_CONN_NONE);
 | 
						|
    }
 | 
						|
 | 
						|
  g_test_message ("Signal received from %s %s via %s",
 | 
						|
                  test_conn_descriptions[received->sender],
 | 
						|
                  sender_name,
 | 
						|
                  proxy != NULL ? "proxy" : "connection");
 | 
						|
  g_test_message ("\tPath: %s", path);
 | 
						|
  g_test_message ("\tInterface: %s", iface);
 | 
						|
  g_test_message ("\tMember: %s", member);
 | 
						|
 | 
						|
  if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(su)")))
 | 
						|
    {
 | 
						|
      g_variant_get (parameters, "(su)", &received->arg0, &received->step);
 | 
						|
      g_test_message ("\tString argument 0: %s", received->arg0);
 | 
						|
      g_test_message ("\tSent in step: %u", received->step);
 | 
						|
    }
 | 
						|
  else if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
 | 
						|
    {
 | 
						|
      g_variant_get (parameters, "(uu)", NULL, &received->step);
 | 
						|
      g_test_message ("\tArgument 0: (not a string)");
 | 
						|
      g_test_message ("\tSent in step: %u", received->step);
 | 
						|
    }
 | 
						|
  else if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
 | 
						|
    {
 | 
						|
      const char *name;
 | 
						|
      const char *old_owner;
 | 
						|
      const char *new_owner;
 | 
						|
 | 
						|
      /* The only signal of this signature that we legitimately receive
 | 
						|
       * during this test is NameOwnerChanged, so just assert that it
 | 
						|
       * is from the message bus and can be matched to a plausible step.
 | 
						|
       * (This is less thorough than the above, and will not work if we
 | 
						|
       * add a test scenario where a name's ownership is repeatedly
 | 
						|
       * changed while watching NameOwnerChanged - so don't do that.) */
 | 
						|
      g_assert_cmpstr (sender_name, ==, DBUS_SERVICE_DBUS);
 | 
						|
      g_assert_cmpstr (path, ==, DBUS_PATH_DBUS);
 | 
						|
      g_assert_cmpstr (iface, ==, DBUS_INTERFACE_DBUS);
 | 
						|
      g_assert_cmpstr (member, ==, NAME_OWNER_CHANGED);
 | 
						|
 | 
						|
      g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
 | 
						|
 | 
						|
      for (i = 0; i < G_N_ELEMENTS (f->plan->steps); i++)
 | 
						|
        {
 | 
						|
          const TestStep *step = &f->plan->steps[i];
 | 
						|
 | 
						|
          if (step->action == TEST_ACTION_OWN_NAME)
 | 
						|
            {
 | 
						|
              const TestOwnName *own_name = &step->u.own_name;
 | 
						|
 | 
						|
              if (g_str_equal (name, own_name->name)
 | 
						|
                  && g_str_equal (new_owner, f->unique_names[own_name->owner])
 | 
						|
                  && own_name->received_by_conn > 0)
 | 
						|
                {
 | 
						|
                  received->step = i;
 | 
						|
                  break;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
          if (i >= G_N_ELEMENTS (f->plan->steps))
 | 
						|
            g_error ("Could not match message to a test step");
 | 
						|
        }
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      g_error ("Unexpected message received");
 | 
						|
    }
 | 
						|
 | 
						|
  g_ptr_array_add (f->received, g_steal_pointer (&received));
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
proxy_signal_cb (GDBusProxy *proxy,
 | 
						|
                 const char *sender_name,
 | 
						|
                 const char *member,
 | 
						|
                 GVariant   *parameters,
 | 
						|
                 void       *user_data)
 | 
						|
{
 | 
						|
  Fixture *f = user_data;
 | 
						|
 | 
						|
  fixture_received_signal (f, proxy, sender_name,
 | 
						|
                           g_dbus_proxy_get_object_path (proxy),
 | 
						|
                           g_dbus_proxy_get_interface_name (proxy),
 | 
						|
                           member, parameters);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
subscribed_signal_cb (GDBusConnection *conn,
 | 
						|
                      const char      *sender_name,
 | 
						|
                      const char      *path,
 | 
						|
                      const char      *iface,
 | 
						|
                      const char      *member,
 | 
						|
                      GVariant        *parameters,
 | 
						|
                      void            *user_data)
 | 
						|
{
 | 
						|
  Fixture *f = user_data;
 | 
						|
  GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
 | 
						|
 | 
						|
  g_assert_true (conn == subscriber);
 | 
						|
 | 
						|
  fixture_received_signal (f, NULL, sender_name, path, iface, member, parameters);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fixture_subscribe (Fixture             *f,
 | 
						|
                   const TestSubscribe *subscribe,
 | 
						|
                   guint                step_number)
 | 
						|
{
 | 
						|
  GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
 | 
						|
  const char *sender;
 | 
						|
 | 
						|
  if (subscribe->string_sender != NULL)
 | 
						|
    {
 | 
						|
      sender = subscribe->string_sender;
 | 
						|
      g_test_message ("\tSender: %s", sender);
 | 
						|
    }
 | 
						|
  else if (subscribe->unique_sender != TEST_CONN_NONE)
 | 
						|
    {
 | 
						|
      sender = f->unique_names[subscribe->unique_sender];
 | 
						|
      g_test_message ("\tSender: %s %s",
 | 
						|
                      test_conn_descriptions[subscribe->unique_sender],
 | 
						|
                      sender);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      sender = NULL;
 | 
						|
      g_test_message ("\tSender: (any)");
 | 
						|
    }
 | 
						|
 | 
						|
  g_test_message ("\tPath: %s", nonnull (subscribe->path, "(any)"));
 | 
						|
  g_test_message ("\tInterface: %s",
 | 
						|
                  nonnull (subscribe->iface, "(any)"));
 | 
						|
  g_test_message ("\tMember: %s",
 | 
						|
                  nonnull (subscribe->member, "(any)"));
 | 
						|
  g_test_message ("\tString argument 0: %s",
 | 
						|
                  nonnull (subscribe->arg0, "(any)"));
 | 
						|
  g_test_message ("\tFlags: %x", subscribe->flags);
 | 
						|
 | 
						|
  if (f->mode != SUBSCRIPTION_MODE_PROXY)
 | 
						|
    {
 | 
						|
      /* CONN or PARALLEL */
 | 
						|
      guint id;
 | 
						|
 | 
						|
      g_test_message ("\tSubscribing via connection");
 | 
						|
      id = g_dbus_connection_signal_subscribe (subscriber,
 | 
						|
                                               sender,
 | 
						|
                                               subscribe->iface,
 | 
						|
                                               subscribe->member,
 | 
						|
                                               subscribe->path,
 | 
						|
                                               subscribe->arg0,
 | 
						|
                                               subscribe->flags,
 | 
						|
                                               subscribed_signal_cb,
 | 
						|
                                               f, NULL);
 | 
						|
 | 
						|
      g_assert_cmpuint (id, !=, 0);
 | 
						|
 | 
						|
      if (subscribe->unsubscribe_immediately)
 | 
						|
        {
 | 
						|
          g_test_message ("\tImmediately unsubscribing");
 | 
						|
          g_dbus_connection_signal_unsubscribe (subscriber, g_steal_handle_id (&id));
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          f->subscriptions[step_number] = id;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  if (f->mode != SUBSCRIPTION_MODE_CONN)
 | 
						|
    {
 | 
						|
      /* PROXY or PARALLEL */
 | 
						|
 | 
						|
      if (sender == NULL)
 | 
						|
        {
 | 
						|
          g_test_message ("\tCannot subscribe via proxy: no bus name");
 | 
						|
        }
 | 
						|
      else if (subscribe->path == NULL)
 | 
						|
        {
 | 
						|
          g_test_message ("\tCannot subscribe via proxy: no path");
 | 
						|
        }
 | 
						|
      else if (subscribe->iface == NULL)
 | 
						|
        {
 | 
						|
          g_test_message ("\tCannot subscribe via proxy: no interface");
 | 
						|
        }
 | 
						|
      else
 | 
						|
        {
 | 
						|
          GDBusProxy *proxy;
 | 
						|
 | 
						|
          g_test_message ("\tSubscribing via proxy");
 | 
						|
          proxy = g_dbus_proxy_new_sync (subscriber,
 | 
						|
                                         (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
 | 
						|
                                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
 | 
						|
                                         NULL,    /* GDBusInterfaceInfo */
 | 
						|
                                         sender,
 | 
						|
                                         subscribe->path,
 | 
						|
                                         subscribe->iface,
 | 
						|
                                         NULL,    /* GCancellable */
 | 
						|
                                         &f->error);
 | 
						|
          g_assert_no_error (f->error);
 | 
						|
          g_assert_nonnull (proxy);
 | 
						|
          g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal_cb), f);
 | 
						|
          g_ptr_array_add (f->proxies, g_steal_pointer (&proxy));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  /* As in setup(), we need to wait for AddMatch to happen. */
 | 
						|
  g_test_message ("Waiting for AddMatch to be processed");
 | 
						|
  connection_wait_for_bus (subscriber);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fixture_emit_signal (Fixture              *f,
 | 
						|
                     const TestEmitSignal *signal,
 | 
						|
                     guint                 step_number)
 | 
						|
{
 | 
						|
  GVariant *body;
 | 
						|
  const char *destination;
 | 
						|
  gboolean ok;
 | 
						|
 | 
						|
  g_test_message ("\tSender: %s",
 | 
						|
                  test_conn_descriptions[signal->sender]);
 | 
						|
 | 
						|
  if (signal->unicast_to != TEST_CONN_NONE)
 | 
						|
    {
 | 
						|
      destination = f->unique_names[signal->unicast_to];
 | 
						|
      g_test_message ("\tDestination: %s %s",
 | 
						|
                      test_conn_descriptions[signal->unicast_to],
 | 
						|
                      destination);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      destination = NULL;
 | 
						|
      g_test_message ("\tDestination: (broadcast)");
 | 
						|
    }
 | 
						|
 | 
						|
  g_assert_nonnull (signal->path);
 | 
						|
  g_test_message ("\tPath: %s", signal->path);
 | 
						|
  g_assert_nonnull (signal->iface);
 | 
						|
  g_test_message ("\tInterface: %s", signal->iface);
 | 
						|
  g_assert_nonnull (signal->member);
 | 
						|
  g_test_message ("\tMember: %s", signal->member);
 | 
						|
 | 
						|
  /* If arg0 is non-NULL, put it in the message's argument 0.
 | 
						|
   * Otherwise put something that will not match any arg0.
 | 
						|
   * Either way, put the sequence number in argument 1 so we can
 | 
						|
   * correlate sent messages with received messages later. */
 | 
						|
  if (signal->args != NULL)
 | 
						|
    {
 | 
						|
      /* floating */
 | 
						|
      body = g_variant_new_parsed (signal->args);
 | 
						|
      g_assert_nonnull (body);
 | 
						|
    }
 | 
						|
  else if (signal->arg0 != NULL)
 | 
						|
    {
 | 
						|
      g_test_message ("\tString argument 0: %s", signal->arg0);
 | 
						|
      body = g_variant_new ("(su)", signal->arg0, (guint32) step_number);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      g_test_message ("\tArgument 0: (not a string)");
 | 
						|
      body = g_variant_new ("(uu)", (guint32) 0, (guint32) step_number);
 | 
						|
    }
 | 
						|
 | 
						|
  ok = g_dbus_connection_emit_signal (f->conns[signal->sender],
 | 
						|
                                      destination,
 | 
						|
                                      signal->path,
 | 
						|
                                      signal->iface,
 | 
						|
                                      signal->member,
 | 
						|
                                      /* steals floating reference */
 | 
						|
                                      g_steal_pointer (&body),
 | 
						|
                                      &f->error);
 | 
						|
  g_assert_no_error (f->error);
 | 
						|
  g_assert_true (ok);
 | 
						|
 | 
						|
  /* Emitting the signal is asynchronous, so if we want subsequent steps
 | 
						|
   * to be guaranteed to happen after the signal from the message bus's
 | 
						|
   * perspective, we have to do a round-trip to the message bus to sync up. */
 | 
						|
  g_test_message ("Waiting for signal to reach message bus");
 | 
						|
  connection_wait_for_bus (f->conns[signal->sender]);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fixture_own_name (Fixture *f,
 | 
						|
                  const TestOwnName *own_name)
 | 
						|
{
 | 
						|
  GVariant *call_result;
 | 
						|
  guint32 flags;
 | 
						|
  guint32 result_code;
 | 
						|
 | 
						|
  g_test_message ("\tName: %s", own_name->name);
 | 
						|
  g_test_message ("\tOwner: %s",
 | 
						|
                  test_conn_descriptions[own_name->owner]);
 | 
						|
 | 
						|
  /* For simplicity, we do this via a direct bus call rather than
 | 
						|
   * using g_bus_own_name_on_connection(). The flags in
 | 
						|
   * GBusNameOwnerFlags are numerically equal to those in the
 | 
						|
   * D-Bus wire protocol. */
 | 
						|
  flags = G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE;
 | 
						|
  call_result = g_dbus_connection_call_sync (f->conns[own_name->owner],
 | 
						|
                                             DBUS_SERVICE_DBUS,
 | 
						|
                                             DBUS_PATH_DBUS,
 | 
						|
                                             DBUS_INTERFACE_DBUS,
 | 
						|
                                             "RequestName",
 | 
						|
                                             g_variant_new ("(su)",
 | 
						|
                                                           own_name->name,
 | 
						|
                                                           flags),
 | 
						|
                                             G_VARIANT_TYPE ("(u)"),
 | 
						|
                                             G_DBUS_CALL_FLAGS_NONE,
 | 
						|
                                             -1,
 | 
						|
                                             NULL,
 | 
						|
                                             &f->error);
 | 
						|
  g_assert_no_error (f->error);
 | 
						|
  g_assert_nonnull (call_result);
 | 
						|
  g_variant_get (call_result, "(u)", &result_code);
 | 
						|
  g_assert_cmpuint (result_code, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
 | 
						|
  g_variant_unref (call_result);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fixture_run_plan (Fixture          *f,
 | 
						|
                  const TestPlan   *plan,
 | 
						|
                  SubscriptionMode  mode)
 | 
						|
{
 | 
						|
  guint i;
 | 
						|
 | 
						|
  G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->subscriptions));
 | 
						|
  G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_conn));
 | 
						|
  G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_proxy));
 | 
						|
 | 
						|
  f->mode = mode;
 | 
						|
  f->plan = plan;
 | 
						|
 | 
						|
  g_test_summary (plan->description);
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (plan->steps); i++)
 | 
						|
    {
 | 
						|
      const TestStep *step = &plan->steps[i];
 | 
						|
 | 
						|
      switch (step->action)
 | 
						|
        {
 | 
						|
          case TEST_ACTION_SUBSCRIBE:
 | 
						|
            g_test_message ("Step %u: adding subscription", i);
 | 
						|
            fixture_subscribe (f, &step->u.subscribe, i);
 | 
						|
            break;
 | 
						|
 | 
						|
          case TEST_ACTION_EMIT_SIGNAL:
 | 
						|
            g_test_message ("Step %u: emitting signal", i);
 | 
						|
            fixture_emit_signal (f, &step->u.signal, i);
 | 
						|
            break;
 | 
						|
 | 
						|
          case TEST_ACTION_OWN_NAME:
 | 
						|
            g_test_message ("Step %u: claiming bus name", i);
 | 
						|
            fixture_own_name (f, &step->u.own_name);
 | 
						|
            break;
 | 
						|
 | 
						|
          case TEST_ACTION_NONE:
 | 
						|
            /* Padding to fill the rest of the array, do nothing */
 | 
						|
            break;
 | 
						|
 | 
						|
          default:
 | 
						|
            g_return_if_reached ();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
  /* Now that we have done everything we wanted to do, emit Finished
 | 
						|
   * from each connection. */
 | 
						|
  for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
    {
 | 
						|
      gboolean ok;
 | 
						|
 | 
						|
      ok = g_dbus_connection_emit_signal (f->conns[i],
 | 
						|
                                          NULL,
 | 
						|
                                          FINISHED_PATH,
 | 
						|
                                          FINISHED_INTERFACE,
 | 
						|
                                          FINISHED_SIGNAL,
 | 
						|
                                          NULL,
 | 
						|
                                          &f->error);
 | 
						|
      g_assert_no_error (f->error);
 | 
						|
      g_assert_true (ok);
 | 
						|
    }
 | 
						|
 | 
						|
  /* Wait until we have seen the Finished signal from each sender */
 | 
						|
  while (TRUE)
 | 
						|
    {
 | 
						|
      gboolean all_finished = TRUE;
 | 
						|
 | 
						|
      for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
        all_finished = all_finished && f->finished[i];
 | 
						|
 | 
						|
      if (all_finished)
 | 
						|
        break;
 | 
						|
 | 
						|
      g_main_context_iteration (NULL, TRUE);
 | 
						|
    }
 | 
						|
 | 
						|
  /* Assert that the correct things happened before each Finished signal */
 | 
						|
  for (i = 0; i < f->received->len; i++)
 | 
						|
    {
 | 
						|
      const ReceivedMessage *received = g_ptr_array_index (f->received, i);
 | 
						|
 | 
						|
      g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_conn));
 | 
						|
      g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_proxy));
 | 
						|
 | 
						|
      if (received->received_by_proxy != NULL)
 | 
						|
        f->received_by_proxy[received->step] += 1;
 | 
						|
      else
 | 
						|
        f->received_by_conn[received->step] += 1;
 | 
						|
    }
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (plan->steps); i++)
 | 
						|
    {
 | 
						|
      const TestStep *step = &plan->steps[i];
 | 
						|
 | 
						|
      if (step->action == TEST_ACTION_EMIT_SIGNAL)
 | 
						|
        {
 | 
						|
          const TestEmitSignal *signal = &plan->steps[i].u.signal;
 | 
						|
 | 
						|
          if (mode != SUBSCRIPTION_MODE_PROXY)
 | 
						|
            {
 | 
						|
              g_test_message ("Signal from step %u was received %u times by "
 | 
						|
                              "GDBusConnection, expected %u",
 | 
						|
                              i, f->received_by_conn[i], signal->received_by_conn);
 | 
						|
              g_assert_cmpuint (f->received_by_conn[i], ==, signal->received_by_conn);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              g_assert_cmpuint (f->received_by_conn[i], ==, 0);
 | 
						|
            }
 | 
						|
 | 
						|
          if (mode != SUBSCRIPTION_MODE_CONN)
 | 
						|
            {
 | 
						|
              g_test_message ("Signal from step %u was received %u times by "
 | 
						|
                              "GDBusProxy, expected %u",
 | 
						|
                              i, f->received_by_proxy[i], signal->received_by_proxy);
 | 
						|
              g_assert_cmpuint (f->received_by_proxy[i], ==, signal->received_by_proxy);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              g_assert_cmpuint (f->received_by_proxy[i], ==, 0);
 | 
						|
            }
 | 
						|
        }
 | 
						|
      else if (step->action == TEST_ACTION_OWN_NAME)
 | 
						|
        {
 | 
						|
          const TestOwnName *own_name = &plan->steps[i].u.own_name;
 | 
						|
 | 
						|
          if (mode != SUBSCRIPTION_MODE_PROXY)
 | 
						|
            {
 | 
						|
              g_test_message ("NameOwnerChanged from step %u was received %u "
 | 
						|
                              "times by GDBusConnection, expected %u",
 | 
						|
                              i, f->received_by_conn[i], own_name->received_by_conn);
 | 
						|
              g_assert_cmpuint (f->received_by_conn[i], ==, own_name->received_by_conn);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              g_assert_cmpuint (f->received_by_conn[i], ==, 0);
 | 
						|
            }
 | 
						|
 | 
						|
          if (mode != SUBSCRIPTION_MODE_CONN)
 | 
						|
            {
 | 
						|
              g_test_message ("NameOwnerChanged from step %u was received %u "
 | 
						|
                              "times by GDBusProxy, expected %u",
 | 
						|
                              i, f->received_by_proxy[i], own_name->received_by_proxy);
 | 
						|
              g_assert_cmpuint (f->received_by_proxy[i], ==, own_name->received_by_proxy);
 | 
						|
            }
 | 
						|
          else
 | 
						|
            {
 | 
						|
              g_assert_cmpuint (f->received_by_proxy[i], ==, 0);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
setup (Fixture *f,
 | 
						|
       G_GNUC_UNUSED const void *context)
 | 
						|
{
 | 
						|
  GDBusConnection *subscriber;
 | 
						|
  guint i;
 | 
						|
 | 
						|
  session_bus_up ();
 | 
						|
 | 
						|
  f->proxies = g_ptr_array_new_full (MAX_TEST_STEPS, g_object_unref);
 | 
						|
  f->received = g_ptr_array_new_full (MAX_TEST_STEPS,
 | 
						|
                                      (GDestroyNotify) received_message_free);
 | 
						|
 | 
						|
  for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
    {
 | 
						|
      f->conns[i] = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &f->error);
 | 
						|
      g_assert_no_error (f->error);
 | 
						|
      g_assert_nonnull (f->conns[i]);
 | 
						|
 | 
						|
      f->unique_names[i] = g_dbus_connection_get_unique_name (f->conns[i]);
 | 
						|
      g_assert_nonnull (f->unique_names[i]);
 | 
						|
      g_test_message ("%s is %s",
 | 
						|
                      test_conn_descriptions[i],
 | 
						|
                      f->unique_names[i]);
 | 
						|
    }
 | 
						|
 | 
						|
  subscriber = f->conns[TEST_CONN_SUBSCRIBER];
 | 
						|
 | 
						|
  /* Used to wait for all connections to finish sending whatever they
 | 
						|
   * wanted to send */
 | 
						|
  f->finished_subscription = g_dbus_connection_signal_subscribe (subscriber,
 | 
						|
                                                                 NULL,
 | 
						|
                                                                 FINISHED_INTERFACE,
 | 
						|
                                                                 FINISHED_SIGNAL,
 | 
						|
                                                                 FINISHED_PATH,
 | 
						|
                                                                 NULL,
 | 
						|
                                                                 G_DBUS_SIGNAL_FLAGS_NONE,
 | 
						|
                                                                 subscriber_finished_cb,
 | 
						|
                                                                 f, NULL);
 | 
						|
  /* AddMatch is sent asynchronously, so we don't know how
 | 
						|
   * soon it will be processed. Before emitting signals, we
 | 
						|
   * need to wait for the message bus to get as far as processing
 | 
						|
   * AddMatch. */
 | 
						|
  g_test_message ("Waiting for AddMatch to be processed");
 | 
						|
  connection_wait_for_bus (subscriber);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_conn_subscribe (Fixture *f,
 | 
						|
                     const void *context)
 | 
						|
{
 | 
						|
  fixture_run_plan (f, context, SUBSCRIPTION_MODE_CONN);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_proxy_subscribe (Fixture *f,
 | 
						|
                      const void *context)
 | 
						|
{
 | 
						|
  fixture_run_plan (f, context, SUBSCRIPTION_MODE_PROXY);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
test_parallel_subscribe (Fixture *f,
 | 
						|
                         const void *context)
 | 
						|
{
 | 
						|
  fixture_run_plan (f, context, SUBSCRIPTION_MODE_PARALLEL);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
teardown (Fixture *f,
 | 
						|
          G_GNUC_UNUSED const void *context)
 | 
						|
{
 | 
						|
  GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER];
 | 
						|
  guint i;
 | 
						|
 | 
						|
  g_ptr_array_unref (f->proxies);
 | 
						|
 | 
						|
  if (f->finished_subscription != 0)
 | 
						|
    g_dbus_connection_signal_unsubscribe (subscriber, g_steal_handle_id (&f->finished_subscription));
 | 
						|
 | 
						|
  for (i = 0; i < G_N_ELEMENTS (f->subscriptions); i++)
 | 
						|
    {
 | 
						|
      if (f->subscriptions[i] != 0)
 | 
						|
        g_dbus_connection_signal_unsubscribe (subscriber, g_steal_handle_id (&f->subscriptions[i]));
 | 
						|
    }
 | 
						|
 | 
						|
  g_ptr_array_unref (f->received);
 | 
						|
 | 
						|
  for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++)
 | 
						|
    g_clear_object (&f->conns[i]);
 | 
						|
 | 
						|
  g_clear_error (&f->error);
 | 
						|
 | 
						|
  session_bus_down ();
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main (int   argc,
 | 
						|
      char *argv[])
 | 
						|
{
 | 
						|
  g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
 | 
						|
 | 
						|
  g_test_dbus_unset ();
 | 
						|
 | 
						|
#define ADD_SUBSCRIBE_TEST(name) \
 | 
						|
  do { \
 | 
						|
    g_test_add ("/gdbus/subscribe/conn/" #name, \
 | 
						|
                Fixture, &plan_ ## name, \
 | 
						|
                setup, test_conn_subscribe, teardown); \
 | 
						|
    g_test_add ("/gdbus/subscribe/proxy/" #name, \
 | 
						|
                Fixture, &plan_ ## name, \
 | 
						|
                setup, test_proxy_subscribe, teardown); \
 | 
						|
    g_test_add ("/gdbus/subscribe/parallel/" #name, \
 | 
						|
                Fixture, &plan_ ## name, \
 | 
						|
                setup, test_parallel_subscribe, teardown); \
 | 
						|
  } while (0)
 | 
						|
 | 
						|
  ADD_SUBSCRIBE_TEST (simple);
 | 
						|
  ADD_SUBSCRIBE_TEST (broadcast_from_anyone);
 | 
						|
  ADD_SUBSCRIBE_TEST (match_twice);
 | 
						|
  ADD_SUBSCRIBE_TEST (limit_by_unique_name);
 | 
						|
  ADD_SUBSCRIBE_TEST (nonexistent_unique_name);
 | 
						|
  ADD_SUBSCRIBE_TEST (limit_by_well_known_name);
 | 
						|
  ADD_SUBSCRIBE_TEST (limit_to_message_bus);
 | 
						|
  ADD_SUBSCRIBE_TEST (unsubscribe_immediately);
 | 
						|
 | 
						|
  return g_test_run();
 | 
						|
}
 |