mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-06 17:36:14 +01:00
984354e02d
On GNOME/glib#3268 there was some concern about whether this would allow an attacker to send signals and have them be matched to a GDBusProxy in this situation, but it seems that was a false alarm. Signed-off-by: Simon McVittie <smcv@collabora.com>
1106 lines
33 KiB
C
1106 lines
33 KiB
C
/*
|
|
* Copyright 2024 Collabora Ltd.
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#include "gdbus-tests.h"
|
|
|
|
/* From the D-Bus Specification */
|
|
#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
|
|
|
|
#define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
|
|
#define DBUS_PATH_DBUS "/org/freedesktop/DBus"
|
|
#define DBUS_INTERFACE_DBUS DBUS_SERVICE_DBUS
|
|
|
|
/* 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;
|
|
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;
|
|
} TestSubscribe;
|
|
|
|
typedef struct
|
|
{
|
|
const char *name;
|
|
TestConn owner;
|
|
} 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;
|
|
} 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,
|
|
},
|
|
},
|
|
{
|
|
/* 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
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(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);
|
|
}
|
|
|
|
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);
|
|
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->arg0 != NULL)
|
|
{
|
|
g_test_message ("\tString argument 0: %s", signal->arg0);
|
|
/* floating */
|
|
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));
|
|
g_assert_cmpint (plan->steps[received->step].action,
|
|
==, TEST_ACTION_EMIT_SIGNAL);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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, f->finished_subscription);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (f->subscriptions); i++)
|
|
{
|
|
if (f->subscriptions[i] != 0)
|
|
g_dbus_connection_signal_unsubscribe (subscriber, 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);
|
|
|
|
return g_test_run();
|
|
}
|