1/* Integration tests for the eavesdrop=true|false keyword in DBusMatchRule
2 *
3 * Author: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
4 * Based on: tests/dbus-daemon.c by Simon McVittie
5 * Copyright © 2010-2011 Nokia Corporation
6 *
7 * Permission is hereby granted, free of charge, to any person
8 * obtaining a copy of this software and associated documentation files
9 * (the "Software"), to deal in the Software without restriction,
10 * including without limitation the rights to use, copy, modify, merge,
11 * publish, distribute, sublicense, and/or sell copies of the Software,
12 * and to permit persons to whom the Software is furnished to do so,
13 * subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 * SOFTWARE.
26 */
27
28#include <config.h>
29
30#include <glib.h>
31
32#include <dbus/dbus.h>
33#include <dbus/dbus-glib-lowlevel.h>
34
35#include <string.h>
36
37#ifdef DBUS_WIN
38# include <io.h>
39# include <windows.h>
40#else
41# include <signal.h>
42# include <unistd.h>
43#endif
44
45#define SENDER_NAME "test.eavesdrop.sender"
46#define SENDER_PATH "/test/eavesdrop/sender"
47#define SENDER_IFACE SENDER_NAME
48#define SENDER_SIGNAL_NAME "Signal"
49#define SENDER_STOPPER_NAME "Stopper"
50
51/* This rule is equivalent to the one added to a proxy connecting to
52 * SENDER_NAME+SENDER_IFACE, plus restricting on signal name.
53 * Being more restrictive, if the connection receives what we need, for sure
54 * the original proxy rule will match it */
55#define RECEIVER_RULE "sender='" SENDER_NAME "'," \
56  "interface='" SENDER_IFACE "'," \
57  "type='signal'," \
58  "member='" SENDER_SIGNAL_NAME "'"
59#define POLITELISTENER_RULE RECEIVER_RULE
60#define EAVESDROPPER_RULE RECEIVER_RULE ",eavesdrop=true"
61
62#define STOPPER_RULE "sender='" SENDER_NAME \
63  "',interface='" SENDER_IFACE "',type='signal',member='" SENDER_STOPPER_NAME "'"
64
65/* a connection received a signal to whom? */
66typedef enum {
67  NONE_YET = 0,
68  TO_ME,
69  TO_OTHER,
70  BROADCAST,
71} SignalDst;
72
73typedef struct {
74    DBusError e;
75    GError *ge;
76
77    GPid daemon_pid;
78
79    /* eavedrop keyword tests */
80    DBusConnection *sender;
81    DBusConnection *receiver;
82    SignalDst receiver_dst;
83    dbus_bool_t receiver_got_stopper;
84    DBusConnection *eavesdropper;
85    SignalDst eavesdropper_dst;
86    dbus_bool_t eavesdropper_got_stopper;
87    DBusConnection *politelistener;
88    SignalDst politelistener_dst;
89    dbus_bool_t politelistener_got_stopper;
90} Fixture;
91
92#define assert_no_error(e) _assert_no_error (e, __FILE__, __LINE__)
93static void
94_assert_no_error (const DBusError *e,
95    const char *file,
96    int line)
97{
98  if (G_UNLIKELY (dbus_error_is_set (e)))
99    g_error ("%s:%d: expected success but got error: %s: %s",
100        file, line, e->name, e->message);
101}
102
103static gchar *
104spawn_dbus_daemon (gchar *binary,
105    gchar *configuration,
106    GPid *daemon_pid)
107{
108  GError *error = NULL;
109  GString *address;
110  gint address_fd;
111  gchar *argv[] = {
112      binary,
113      configuration,
114      "--nofork",
115      "--print-address=1", /* stdout */
116      NULL
117  };
118
119  g_spawn_async_with_pipes (NULL, /* working directory */
120      argv,
121      NULL, /* envp */
122      G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
123      NULL, /* child_setup */
124      NULL, /* user data */
125      daemon_pid,
126      NULL, /* child's stdin = /dev/null */
127      &address_fd,
128      NULL, /* child's stderr = our stderr */
129      &error);
130  g_assert_no_error (error);
131
132  address = g_string_new (NULL);
133
134  /* polling until the dbus-daemon writes out its address is a bit stupid,
135   * but at least it's simple, unlike dbus-launch... in principle we could
136   * use select() here, but life's too short */
137  while (1)
138    {
139      gssize bytes;
140      gchar buf[4096];
141      gchar *newline;
142
143      bytes = read (address_fd, buf, sizeof (buf));
144
145      if (bytes > 0)
146        g_string_append_len (address, buf, bytes);
147
148      newline = strchr (address->str, '\n');
149
150      if (newline != NULL)
151        {
152          g_string_truncate (address, newline - address->str);
153          break;
154        }
155
156      g_usleep (G_USEC_PER_SEC / 10);
157    }
158
159  return g_string_free (address, FALSE);
160}
161
162static DBusConnection *
163connect_to_bus (const gchar *address)
164{
165  DBusConnection *conn;
166  DBusError error = DBUS_ERROR_INIT;
167  dbus_bool_t ok;
168
169  conn = dbus_connection_open_private (address, &error);
170  assert_no_error (&error);
171  g_assert (conn != NULL);
172
173  ok = dbus_bus_register (conn, &error);
174  assert_no_error (&error);
175  g_assert (ok);
176  g_assert (dbus_bus_get_unique_name (conn) != NULL);
177
178  dbus_connection_setup_with_g_main (conn, NULL);
179  return conn;
180}
181
182/* send a unicast signal to <self> to ensure that no other connection
183 * listening is the actual recipient for the signal */
184static DBusHandlerResult
185sender_send_unicast_to_sender (Fixture *f)
186{
187  DBusMessage *signal;
188
189  signal = dbus_message_new_signal (SENDER_PATH, SENDER_IFACE,
190      SENDER_SIGNAL_NAME);
191  dbus_message_set_destination (signal, dbus_bus_get_unique_name (f->sender));
192
193  if (signal == NULL)
194    g_error ("OOM");
195
196  if (!dbus_connection_send (f->sender, signal, NULL))
197    g_error ("OOM");
198
199  dbus_message_unref (signal);
200
201  return DBUS_HANDLER_RESULT_HANDLED;
202}
203
204/* send a unicast signal to <receiver>, making <politelistener> and
205 * <eavesdropper> not a actual recipient for it */
206static DBusHandlerResult
207sender_send_unicast_to_receiver (Fixture *f)
208{
209  DBusMessage *signal;
210
211  signal = dbus_message_new_signal (SENDER_PATH, SENDER_IFACE, SENDER_SIGNAL_NAME);
212  dbus_message_set_destination (signal, dbus_bus_get_unique_name (f->receiver));
213
214  if (signal == NULL)
215    g_error ("OOM");
216
217  if (!dbus_connection_send (f->sender, signal, NULL))
218    g_error ("OOM");
219
220  dbus_message_unref (signal);
221
222  return DBUS_HANDLER_RESULT_HANDLED;
223}
224
225static DBusHandlerResult
226sender_send_broadcast (Fixture *f)
227{
228  DBusMessage *signal;
229
230  signal = dbus_message_new_signal (SENDER_PATH, SENDER_IFACE, SENDER_SIGNAL_NAME);
231  dbus_message_set_destination (signal, NULL);
232
233  if (signal == NULL)
234    g_error ("OOM");
235
236  if (!dbus_connection_send (f->sender, signal, NULL))
237    g_error ("OOM");
238
239  dbus_message_unref (signal);
240
241  return DBUS_HANDLER_RESULT_HANDLED;
242}
243
244/* Send special broadcast signal to indicate that the connections can "stop"
245 * listening and check their results.
246 * DBus does not re-order messages, so when the three connections have received
247 * this signal, we are sure that any message sent before it has also been
248 * dispatched. */
249static DBusHandlerResult
250sender_send_stopper (Fixture *f)
251{
252  DBusMessage *signal;
253
254  signal = dbus_message_new_signal (SENDER_PATH, SENDER_IFACE, SENDER_STOPPER_NAME);
255  dbus_message_set_destination (signal, NULL);
256
257  if (signal == NULL)
258    g_error ("OOM");
259
260  if (!dbus_connection_send (f->sender, signal, NULL))
261    g_error ("OOM");
262
263  dbus_message_unref (signal);
264
265  return DBUS_HANDLER_RESULT_HANDLED;
266}
267
268/* Ignore NameAcquired, then depending on the signal received:
269 * - updates f-><conn>_dst based on the destination of the message
270 * - asserts that <conn> received the stop signal
271 */
272static DBusHandlerResult
273signal_filter (DBusConnection *connection,
274    DBusMessage *message,
275    void *user_data)
276{
277  Fixture *f = user_data;
278  SignalDst *dst = NULL;
279  DBusConnection **conn;
280  dbus_bool_t *got_stopper;
281
282  if (0 == strcmp (dbus_message_get_member (message), "NameAcquired"))
283    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
284
285  if (connection == f->receiver)
286    {
287      dst = &(f->receiver_dst);
288      conn = &(f->receiver);
289      got_stopper = &(f->receiver_got_stopper);
290    }
291  else if (connection == f->eavesdropper)
292    {
293      dst = &(f->eavesdropper_dst);
294      conn = &(f->eavesdropper);
295      got_stopper = &(f->eavesdropper_got_stopper);
296    }
297  else if (connection == f->politelistener)
298    {
299      dst = &(f->politelistener_dst);
300      conn = &(f->politelistener);
301      got_stopper = &(f->politelistener_got_stopper);
302    }
303  else
304    {
305      g_error ("connection not matching");
306    }
307
308  if (0 == strcmp (dbus_message_get_member (message), SENDER_SIGNAL_NAME))
309    {
310      if (dbus_message_get_destination (message) == NULL)
311        *dst = BROADCAST;
312      else if (0 == strcmp (dbus_message_get_destination (message), dbus_bus_get_unique_name (*conn)))
313        *dst = TO_ME;
314      else /* if (dbus_message_get_destination (message) != NULL) */
315        *dst = TO_OTHER;
316    }
317  else if (0 == strcmp (dbus_message_get_member (message), SENDER_STOPPER_NAME))
318    {
319      *got_stopper = TRUE;
320    }
321  else
322    {
323      g_error ("got unknown member from message: %s",
324          dbus_message_get_member (message));
325    }
326
327  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
328}
329
330static void
331add_receiver_filter (Fixture *f)
332{
333  DBusError e = DBUS_ERROR_INIT;
334
335  dbus_bus_add_match (f->receiver, RECEIVER_RULE, &e);
336  assert_no_error (&e);
337  dbus_bus_add_match (f->receiver, STOPPER_RULE, &e);
338  assert_no_error (&e);
339
340  if (!dbus_connection_add_filter (f->receiver,
341        signal_filter, f, NULL))
342    g_error ("OOM");
343}
344
345static void
346add_eavesdropper_filter (Fixture *f)
347{
348  DBusError e = DBUS_ERROR_INIT;
349
350  dbus_bus_add_match (f->eavesdropper, EAVESDROPPER_RULE, &e);
351  assert_no_error (&e);
352  dbus_bus_add_match (f->eavesdropper, STOPPER_RULE, &e);
353  assert_no_error (&e);
354
355  if (!dbus_connection_add_filter (f->eavesdropper,
356        signal_filter, f, NULL))
357    g_error ("OOM");
358}
359
360static void
361add_politelistener_filter (Fixture *f)
362{
363  DBusError e = DBUS_ERROR_INIT;
364
365  dbus_bus_add_match (f->politelistener, POLITELISTENER_RULE, &e);
366  assert_no_error (&e);
367  dbus_bus_add_match (f->politelistener, STOPPER_RULE, &e);
368  assert_no_error (&e);
369
370  if (!dbus_connection_add_filter (f->politelistener,
371        signal_filter, f, NULL))
372    g_error ("OOM");
373}
374
375static void
376setup (Fixture *f,
377    gconstpointer context G_GNUC_UNUSED)
378{
379  gchar *dbus_daemon;
380  gchar *config;
381  gchar *address;
382
383  f->ge = NULL;
384  dbus_error_init (&f->e);
385
386  dbus_daemon = g_strdup (g_getenv ("DBUS_TEST_DAEMON"));
387
388  if (dbus_daemon == NULL)
389    dbus_daemon = g_strdup ("dbus-daemon");
390
391  if (g_getenv ("DBUS_TEST_SYSCONFDIR") != NULL)
392    {
393      config = g_strdup_printf ("--config-file=%s/dbus-1/session.conf",
394          g_getenv ("DBUS_TEST_SYSCONFDIR"));
395    }
396  else if (g_getenv ("DBUS_TEST_DATA") != NULL)
397    {
398      config = g_strdup_printf (
399          "--config-file=%s/valid-config-files/session.conf",
400          g_getenv ("DBUS_TEST_DATA"));
401    }
402  else
403    {
404      config = g_strdup ("--session");
405    }
406
407  address = spawn_dbus_daemon (dbus_daemon, config, &f->daemon_pid);
408
409  g_free (dbus_daemon);
410  g_free (config);
411
412  f->sender = connect_to_bus (address);
413  dbus_bus_request_name (f->sender, SENDER_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE,
414      &(f->e));
415  f->receiver = connect_to_bus (address);
416  f->eavesdropper = connect_to_bus (address);
417  f->politelistener = connect_to_bus (address);
418  add_receiver_filter (f);
419  add_politelistener_filter (f);
420  add_eavesdropper_filter (f);
421
422  g_free (address);
423}
424
425static void
426test_eavesdrop_broadcast (Fixture *f,
427    gconstpointer context G_GNUC_UNUSED)
428{
429  sender_send_broadcast (f);
430  sender_send_stopper (f);
431
432  while (!f->receiver_got_stopper ||
433      !f->politelistener_got_stopper ||
434      !f->eavesdropper_got_stopper)
435    g_main_context_iteration (NULL, TRUE);
436
437  /* all the three connection can receive a broadcast */
438  g_assert_cmpint (f->receiver_dst, ==, BROADCAST);
439  g_assert_cmpint (f->politelistener_dst, ==, BROADCAST);
440  g_assert_cmpint (f->eavesdropper_dst, ==, BROADCAST);
441}
442
443/* a way to say that none of the listening connection are destination of the
444 * signal */
445static void
446test_eavesdrop_unicast_to_sender (Fixture *f,
447    gconstpointer context G_GNUC_UNUSED)
448{
449  sender_send_unicast_to_sender (f);
450  sender_send_stopper (f);
451
452  while (!f->receiver_got_stopper ||
453      !f->politelistener_got_stopper ||
454      !f->eavesdropper_got_stopper)
455    g_main_context_iteration (NULL, TRUE);
456
457  /* not directed to it and not broadcasted, they cannot receive it */
458  g_assert_cmpint (f->receiver_dst, ==, NONE_YET);
459  g_assert_cmpint (f->politelistener_dst, ==, NONE_YET);
460  /* eavesdrop=true, it will receive the signal even though it's not directed
461   * to it */
462  g_assert_cmpint (f->eavesdropper_dst, ==, TO_OTHER);
463}
464
465static void
466test_eavesdrop_unicast_to_receiver (Fixture *f,
467    gconstpointer context G_GNUC_UNUSED)
468{
469  sender_send_unicast_to_receiver (f);
470  sender_send_stopper (f);
471
472  while (!f->receiver_got_stopper ||
473      !f->politelistener_got_stopper ||
474      !f->eavesdropper_got_stopper)
475    g_main_context_iteration (NULL, TRUE);
476
477  /* direct to him */
478  g_assert_cmpint (f->receiver_dst, ==, TO_ME);
479  /* not directed to it and not broadcasted, it cannot receive it */
480  g_assert_cmpint (f->politelistener_dst, ==, NONE_YET);
481  /* eavesdrop=true, it will receive the signal even though it's not directed
482   * to it */
483  g_assert_cmpint (f->eavesdropper_dst, ==, TO_OTHER);
484}
485
486static void
487teardown (Fixture *f,
488    gconstpointer context G_GNUC_UNUSED)
489{
490  dbus_error_free (&f->e);
491  g_clear_error (&f->ge);
492
493  if (f->sender != NULL)
494    {
495      dbus_connection_close (f->sender);
496      dbus_connection_unref (f->sender);
497      f->sender = NULL;
498    }
499
500  if (f->receiver != NULL)
501    {
502      dbus_connection_remove_filter (f->receiver,
503          signal_filter, f);
504
505      dbus_connection_close (f->receiver);
506      dbus_connection_unref (f->receiver);
507      f->receiver = NULL;
508    }
509
510  if (f->politelistener != NULL)
511    {
512      dbus_connection_remove_filter (f->politelistener,
513          signal_filter, f);
514
515      dbus_connection_close (f->politelistener);
516      dbus_connection_unref (f->politelistener);
517      f->politelistener = NULL;
518    }
519
520  if (f->eavesdropper != NULL)
521    {
522      dbus_connection_remove_filter (f->eavesdropper,
523          signal_filter, f);
524
525      dbus_connection_close (f->eavesdropper);
526      dbus_connection_unref (f->eavesdropper);
527      f->eavesdropper = NULL;
528    }
529
530#ifdef DBUS_WIN
531  TerminateProcess (f->daemon_pid, 1);
532#else
533  kill (f->daemon_pid, SIGTERM);
534#endif
535
536  g_spawn_close_pid (f->daemon_pid);
537}
538
539int
540main (int argc,
541    char **argv)
542{
543  g_test_init (&argc, &argv, NULL);
544  g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id=");
545
546  g_test_add ("/eavedrop/match_keyword/broadcast", Fixture, NULL,
547      setup, test_eavesdrop_broadcast, teardown);
548  g_test_add ("/eavedrop/match_keyword/unicast_to_receiver", Fixture, NULL,
549      setup, test_eavesdrop_unicast_to_receiver,
550      teardown);
551  g_test_add ("/eavedrop/match_keyword/unicast_to_sender", Fixture, NULL,
552      setup, test_eavesdrop_unicast_to_sender, teardown);
553
554  return g_test_run ();
555}
556