ofdbus/dbus/bus/signals.c
changeset 0 e4d67989cc36
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ofdbus/dbus/bus/signals.c	Tue Feb 02 02:01:42 2010 +0200
@@ -0,0 +1,1960 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* signals.c  Bus signal connection implementation
+ *
+ * Copyright (C) 2003, 2005  Red Hat, Inc.
+ * Portion Copyright © 2008 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "signals.h"
+#include "services.h"
+#include "utils.h"
+#ifndef __SYMBIAN32__
+#include <dbus/dbus-marshal-validate.h>
+#else
+#include "dbus-marshal-validate.h"
+#include "config.h"
+#endif //__SYMBIAN32__
+
+struct BusMatchRule
+{
+  int refcount;       /**< reference count */
+
+  DBusConnection *matches_go_to; /**< Owner of the rule */
+
+  unsigned int flags; /**< BusMatchFlags */
+
+  int   message_type;
+  char *interface;
+  char *member;
+  char *sender;
+  char *destination;
+  char *path;
+
+  char **args;
+  int args_len;
+};
+
+BusMatchRule*
+bus_match_rule_new (DBusConnection *matches_go_to)
+{
+  BusMatchRule *rule;
+
+  rule = dbus_new0 (BusMatchRule, 1);
+  if (rule == NULL)
+    return NULL;
+
+  rule->refcount = 1;
+  rule->matches_go_to = matches_go_to;
+
+#ifndef DBUS_BUILD_TESTS
+  _dbus_assert (rule->matches_go_to != NULL);
+#endif
+  
+  return rule;
+}
+
+BusMatchRule *
+bus_match_rule_ref (BusMatchRule *rule)
+{
+  _dbus_assert (rule->refcount > 0);
+
+  rule->refcount += 1;
+
+  return rule;
+}
+
+void
+bus_match_rule_unref (BusMatchRule *rule)
+{
+  _dbus_assert (rule->refcount > 0);
+
+  rule->refcount -= 1;
+  if (rule->refcount == 0)
+    {
+      dbus_free (rule->interface);
+      dbus_free (rule->member);
+      dbus_free (rule->sender);
+      dbus_free (rule->destination);
+      dbus_free (rule->path);
+
+      /* can't use dbus_free_string_array() since there
+       * are embedded NULL
+       */
+      if (rule->args)
+        {
+          int i;
+
+          i = 0;
+          while (i < rule->args_len)
+            {
+              if (rule->args[i])
+                dbus_free (rule->args[i]);
+              ++i;
+            }
+
+          dbus_free (rule->args);
+        }
+      
+      dbus_free (rule);
+    }
+}
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+/* Note this function does not do escaping, so it's only
+ * good for debug spew at the moment
+ */
+static char*
+match_rule_to_string (BusMatchRule *rule)
+{
+  DBusString str;
+  char *ret;
+  
+  if (!_dbus_string_init (&str))
+    {
+      char *s;
+      while ((s = _dbus_strdup ("nomem")) == NULL)
+        ; /* only OK for debug spew... */
+      return s;
+    }
+  
+  if (rule->flags & BUS_MATCH_MESSAGE_TYPE)
+    {
+      /* FIXME make type readable */
+      if (!_dbus_string_append_printf (&str, "type='%d'", rule->message_type))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_INTERFACE)
+    {
+      if (_dbus_string_get_length (&str) > 0)
+        {
+          if (!_dbus_string_append (&str, ","))
+            goto nomem;
+        }
+      
+      if (!_dbus_string_append_printf (&str, "interface='%s'", rule->interface))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_MEMBER)
+    {
+      if (_dbus_string_get_length (&str) > 0)
+        {
+          if (!_dbus_string_append (&str, ","))
+            goto nomem;
+        }
+      
+      if (!_dbus_string_append_printf (&str, "member='%s'", rule->member))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_PATH)
+    {
+      if (_dbus_string_get_length (&str) > 0)
+        {
+          if (!_dbus_string_append (&str, ","))
+            goto nomem;
+        }
+      
+      if (!_dbus_string_append_printf (&str, "path='%s'", rule->path))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_SENDER)
+    {
+      if (_dbus_string_get_length (&str) > 0)
+        {
+          if (!_dbus_string_append (&str, ","))
+            goto nomem;
+        }
+      
+      if (!_dbus_string_append_printf (&str, "sender='%s'", rule->sender))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_DESTINATION)
+    {
+      if (_dbus_string_get_length (&str) > 0)
+        {
+          if (!_dbus_string_append (&str, ","))
+            goto nomem;
+        }
+      
+      if (!_dbus_string_append_printf (&str, "destination='%s'", rule->destination))
+        goto nomem;
+    }
+
+  if (rule->flags & BUS_MATCH_ARGS)
+    {
+      int i;
+      
+      _dbus_assert (rule->args != NULL);
+
+      i = 0;
+      while (i < rule->args_len)
+        {
+          if (rule->args[i] != NULL)
+            {
+              if (_dbus_string_get_length (&str) > 0)
+                {
+                  if (!_dbus_string_append (&str, ","))
+                    goto nomem;
+                }
+              
+              if (!_dbus_string_append_printf (&str,
+                                               "arg%d='%s'",
+                                               i,
+                                               rule->args[i]))
+                goto nomem;
+            }
+          
+          ++i;
+        }
+    }
+  
+  if (!_dbus_string_steal_data (&str, &ret))
+    goto nomem;
+
+  _dbus_string_free (&str);
+  return ret;
+  
+ nomem:
+  _dbus_string_free (&str);
+  {
+    char *s;
+    while ((s = _dbus_strdup ("nomem")) == NULL)
+      ;  /* only OK for debug spew... */
+    return s;
+  }
+}
+#endif /* DBUS_ENABLE_VERBOSE_MODE */
+
+dbus_bool_t
+bus_match_rule_set_message_type (BusMatchRule *rule,
+                                 int           type)
+{
+  rule->flags |= BUS_MATCH_MESSAGE_TYPE;
+
+  rule->message_type = type;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_interface (BusMatchRule *rule,
+                              const char   *interface)
+{
+  char *new;
+
+  _dbus_assert (interface != NULL);
+
+  new = _dbus_strdup (interface);
+  if (new == NULL)
+    return FALSE;
+
+  rule->flags |= BUS_MATCH_INTERFACE;
+  dbus_free (rule->interface);
+  rule->interface = new;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_member (BusMatchRule *rule,
+                           const char   *member)
+{
+  char *new;
+
+  _dbus_assert (member != NULL);
+
+  new = _dbus_strdup (member);
+  if (new == NULL)
+    return FALSE;
+
+  rule->flags |= BUS_MATCH_MEMBER;
+  dbus_free (rule->member);
+  rule->member = new;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_sender (BusMatchRule *rule,
+                           const char   *sender)
+{
+  char *new;
+
+  _dbus_assert (sender != NULL);
+
+  new = _dbus_strdup (sender);
+  if (new == NULL)
+    return FALSE;
+
+  rule->flags |= BUS_MATCH_SENDER;
+  dbus_free (rule->sender);
+  rule->sender = new;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_destination (BusMatchRule *rule,
+                                const char   *destination)
+{
+  char *new;
+
+  _dbus_assert (destination != NULL);
+
+  new = _dbus_strdup (destination);
+  if (new == NULL)
+    return FALSE;
+
+  rule->flags |= BUS_MATCH_DESTINATION;
+  dbus_free (rule->destination);
+  rule->destination = new;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_path (BusMatchRule *rule,
+                         const char   *path)
+{
+  char *new;
+
+  _dbus_assert (path != NULL);
+
+  new = _dbus_strdup (path);
+  if (new == NULL)
+    return FALSE;
+
+  rule->flags |= BUS_MATCH_PATH;
+  dbus_free (rule->path);
+  rule->path = new;
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_match_rule_set_arg (BusMatchRule *rule,
+                        int           arg,
+                        const char   *value)
+{
+  char *new;
+
+  _dbus_assert (value != NULL);
+
+  new = _dbus_strdup (value);
+  if (new == NULL)
+    return FALSE;
+
+  /* args_len is the number of args not including null termination
+   * in the char**
+   */
+  if (arg >= rule->args_len)
+    {
+      char **new_args;
+      int new_args_len;
+      int i;
+
+      new_args_len = arg + 1;
+
+      /* add another + 1 here for null termination */
+      new_args = dbus_realloc (rule->args,
+                               sizeof(rule->args[0]) * (new_args_len + 1));
+      if (new_args == NULL)
+        {
+          dbus_free (new);
+          return FALSE;
+        }
+
+      /* NULL the new slots */
+      i = rule->args_len;
+      while (i <= new_args_len) /* <= for null termination */
+        {
+          new_args[i] = NULL;
+          ++i;
+        }
+      
+      rule->args = new_args;
+      rule->args_len = new_args_len;
+    }
+
+  rule->flags |= BUS_MATCH_ARGS;
+
+  dbus_free (rule->args[arg]);
+  rule->args[arg] = new;
+
+  /* NULL termination didn't get busted */
+  _dbus_assert (rule->args[rule->args_len] == NULL);
+
+  return TRUE;
+}
+
+#define ISWHITE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+static dbus_bool_t
+find_key (const DBusString *str,
+          int               start,
+          DBusString       *key,
+          int              *value_pos,
+          DBusError        *error)
+{
+  const char *p;
+  const char *s;
+  const char *key_start;
+  const char *key_end;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  s = _dbus_string_get_const_data (str);
+
+  p = s + start;
+
+  while (*p && ISWHITE (*p))
+    ++p;
+
+  key_start = p;
+
+  while (*p && *p != '=' && !ISWHITE (*p))
+    ++p;
+
+  key_end = p;
+
+  while (*p && ISWHITE (*p))
+    ++p;
+  
+  if (key_start == key_end)
+    {
+      /* Empty match rules or trailing whitespace are OK */
+      *value_pos = p - s;
+      return TRUE;
+    }
+
+  if (*p != '=')
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Match rule has a key with no subsequent '=' character");
+      return FALSE;
+    }
+  ++p;
+  
+  if (!_dbus_string_append_len (key, key_start, key_end - key_start))
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  *value_pos = p - s;
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+find_value (const DBusString *str,
+            int               start,
+            const char       *key,
+            DBusString       *value,
+            int              *value_end,
+            DBusError        *error)
+{
+  const char *p;
+  const char *s;
+  char quote_char;
+  int orig_len;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  
+  orig_len = _dbus_string_get_length (value);
+  
+  s = _dbus_string_get_const_data (str);
+
+  p = s + start;
+
+  quote_char = '\0';
+
+  while (*p)
+    {
+      if (quote_char == '\0')
+        {
+          switch (*p)
+            {
+            case '\0':
+              goto done;
+
+            case '\'':
+              quote_char = '\'';
+              goto next;
+              
+            case ',':
+              ++p;
+              goto done;
+
+            case '\\':
+              quote_char = '\\';
+              goto next;
+              
+            default:
+              if (!_dbus_string_append_byte (value, *p))
+                {
+                  BUS_SET_OOM (error);
+                  goto failed;
+                }
+            }
+        }
+      else if (quote_char == '\\')
+        {
+          /* \ only counts as an escape if escaping a quote mark */
+          if (*p != '\'')
+            {
+              if (!_dbus_string_append_byte (value, '\\'))
+                {
+                  BUS_SET_OOM (error);
+                  goto failed;
+                }
+            }
+
+          if (!_dbus_string_append_byte (value, *p))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+          
+          quote_char = '\0';
+        }
+      else
+        {
+          _dbus_assert (quote_char == '\'');
+
+          if (*p == '\'')
+            {
+              quote_char = '\0';
+            }
+          else
+            {
+              if (!_dbus_string_append_byte (value, *p))
+                {
+                  BUS_SET_OOM (error);
+                  goto failed;
+                }
+            }
+        }
+
+    next:
+      ++p;
+    }
+
+ done:
+
+  if (quote_char == '\\')
+    {
+      if (!_dbus_string_append_byte (value, '\\'))
+        {
+          BUS_SET_OOM (error);
+          goto failed;
+        }
+    }
+  else if (quote_char == '\'')
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Unbalanced quotation marks in match rule");
+      goto failed;
+    }
+  else
+    _dbus_assert (quote_char == '\0');
+
+  /* Zero-length values are allowed */
+  
+  *value_end = p - s;
+  
+  return TRUE;
+
+ failed:
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+  _dbus_string_set_length (value, orig_len);
+  return FALSE;
+}
+
+/* duplicates aren't allowed so the real legitimate max is only 6 or
+ * so. Leaving extra so we don't have to bother to update it.
+ * FIXME this is sort of busted now with arg matching, but we let
+ * you match on up to 10 args for now
+ */
+#define MAX_RULE_TOKENS 16
+
+/* this is slightly too high level to be termed a "token"
+ * but let's not be pedantic.
+ */
+typedef struct
+{
+  char *key;
+  char *value;
+} RuleToken;
+
+static dbus_bool_t
+tokenize_rule (const DBusString *rule_text,
+               RuleToken         tokens[MAX_RULE_TOKENS],
+               DBusError        *error) 
+{
+  int i;
+  int pos;
+  DBusString key;
+  DBusString value;
+  dbus_bool_t retval;
+
+  retval = FALSE;
+  
+  if (!_dbus_string_init (&key))
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  if (!_dbus_string_init (&value))
+    {
+      _dbus_string_free (&key);
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  i = 0;
+  pos = 0;
+  while (i < MAX_RULE_TOKENS &&
+         pos < _dbus_string_get_length (rule_text))
+    {
+      _dbus_assert (tokens[i].key == NULL);
+      _dbus_assert (tokens[i].value == NULL);
+
+      if (!find_key (rule_text, pos, &key, &pos, error))
+        goto out;
+
+      if (_dbus_string_get_length (&key) == 0)
+        goto next;
+      
+      if (!_dbus_string_steal_data (&key, &tokens[i].key))
+        {
+          BUS_SET_OOM (error);
+          goto out;
+        }
+
+      if (!find_value (rule_text, pos, tokens[i].key, &value, &pos, error))
+        goto out;
+
+      if (!_dbus_string_steal_data (&value, &tokens[i].value))
+        {
+          BUS_SET_OOM (error);
+          goto out;
+        }
+
+    next:
+      ++i;
+    }
+
+  retval = TRUE;
+  
+ out:
+  if (!retval)
+    {
+      i = 0;
+      while (tokens[i].key || tokens[i].value)
+        {
+          dbus_free (tokens[i].key);
+          dbus_free (tokens[i].value);
+          tokens[i].key = NULL;
+          tokens[i].value = NULL;
+          ++i;
+        }
+    }
+  
+  _dbus_string_free (&key);
+  _dbus_string_free (&value);
+  
+  return retval;
+}
+
+static dbus_bool_t
+bus_match_rule_parse_arg_match (BusMatchRule     *rule,
+                                const char       *key,
+                                const DBusString *value,
+                                DBusError        *error)
+{
+  DBusString key_str;
+  unsigned long arg;
+  int end;
+
+  /* For now, arg0='foo' always implies that 'foo' is a
+   * DBUS_TYPE_STRING. Someday we could add an arg0type='int32' thing
+   * if we wanted, which would specify another type, in which case
+   * arg0='5' would have the 5 parsed as an int rather than string.
+   */
+  
+  /* First we need to parse arg0 = 0, arg27 = 27 */
+
+  _dbus_string_init_const (&key_str, key);
+
+  if (_dbus_string_get_length (&key_str) < 4)
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Key '%s' in match rule starts with 'arg' but lacks an arg number. Should be 'arg0' or 'arg7' for example.\n", key);
+      goto failed;
+    }
+
+  if (!_dbus_string_parse_uint (&key_str, 3, &arg, &end) ||
+      end != _dbus_string_get_length (&key_str))
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Key '%s' in match rule starts with 'arg' but could not parse arg number. Should be 'arg0' or 'arg7' for example.\n", key);
+      goto failed;
+    }
+
+  /* If we didn't check this we could allocate a huge amount of RAM */
+  if (arg > DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER)
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Key '%s' in match rule has arg number %lu but the maximum is %d.\n", key, (unsigned long) arg, DBUS_MAXIMUM_MATCH_RULE_ARG_NUMBER);
+      goto failed;
+    }
+  
+  if ((rule->flags & BUS_MATCH_ARGS) &&
+      rule->args_len > (int) arg &&
+      rule->args[arg] != NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                      "Key '%s' specified twice in match rule\n", key);
+      goto failed;
+    }
+  
+  if (!bus_match_rule_set_arg (rule, arg,
+                               _dbus_string_get_const_data (value)))
+    {
+      BUS_SET_OOM (error);
+      goto failed;
+    }
+
+  return TRUE;
+
+ failed:
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+  return FALSE;
+}
+
+/*
+ * The format is comma-separated with strings quoted with single quotes
+ * as for the shell (to escape a literal single quote, use '\'').
+ *
+ * type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',
+ * path='/bar/foo',destination=':452345.34'
+ *
+ */
+BusMatchRule*
+bus_match_rule_parse (DBusConnection   *matches_go_to,
+                      const DBusString *rule_text,
+                      DBusError        *error)
+{
+  BusMatchRule *rule;
+  RuleToken tokens[MAX_RULE_TOKENS+1]; /* NULL termination + 1 */
+  int i;
+  
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  if (_dbus_string_get_length (rule_text) > DBUS_MAXIMUM_MATCH_RULE_LENGTH)
+    {
+      dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
+                      "Match rule text is %d bytes, maximum is %d",
+                      _dbus_string_get_length (rule_text),
+                      DBUS_MAXIMUM_MATCH_RULE_LENGTH);
+      return NULL;
+    }
+  
+  memset (tokens, '\0', sizeof (tokens));
+  
+  rule = bus_match_rule_new (matches_go_to);
+  if (rule == NULL)
+    {
+      BUS_SET_OOM (error);
+      goto failed;
+    }
+  
+  if (!tokenize_rule (rule_text, tokens, error))
+    goto failed;
+  
+  i = 0;
+  while (tokens[i].key != NULL)
+    {
+      DBusString tmp_str;
+      int len;
+      const char *key = tokens[i].key;
+      const char *value = tokens[i].value;
+      
+      _dbus_string_init_const (&tmp_str, value);
+      len = _dbus_string_get_length (&tmp_str);
+
+      if (strcmp (key, "type") == 0)
+        {
+          int t;
+
+          if (rule->flags & BUS_MATCH_MESSAGE_TYPE)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+          
+          t = dbus_message_type_from_string (value);
+          
+          if (t == DBUS_MESSAGE_TYPE_INVALID)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Invalid message type (%s) in match rule\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_message_type (rule, t))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "sender") == 0)
+        {
+          if (rule->flags & BUS_MATCH_SENDER)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!_dbus_validate_bus_name (&tmp_str, 0, len))
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Sender name '%s' is invalid\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_sender (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "interface") == 0)
+        {
+          if (rule->flags & BUS_MATCH_INTERFACE)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!_dbus_validate_interface (&tmp_str, 0, len))
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Interface name '%s' is invalid\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_interface (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "member") == 0)
+        {
+          if (rule->flags & BUS_MATCH_MEMBER)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!_dbus_validate_member (&tmp_str, 0, len))
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Member name '%s' is invalid\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_member (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "path") == 0)
+        {
+          if (rule->flags & BUS_MATCH_PATH)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!_dbus_validate_path (&tmp_str, 0, len))
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Path '%s' is invalid\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_path (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strcmp (key, "destination") == 0)
+        {
+          if (rule->flags & BUS_MATCH_DESTINATION)
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Key %s specified twice in match rule\n", key);
+              goto failed;
+            }
+
+          if (!_dbus_validate_bus_name (&tmp_str, 0, len))
+            {
+              dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                              "Destination name '%s' is invalid\n", value);
+              goto failed;
+            }
+
+          if (!bus_match_rule_set_destination (rule, value))
+            {
+              BUS_SET_OOM (error);
+              goto failed;
+            }
+        }
+      else if (strncmp (key, "arg", 3) == 0)
+        {
+          if (!bus_match_rule_parse_arg_match (rule, key, &tmp_str, error))
+            goto failed;
+        }
+      else
+        {
+          dbus_set_error (error, DBUS_ERROR_MATCH_RULE_INVALID,
+                          "Unknown key \"%s\" in match rule",
+                          key);
+          goto failed;
+        }
+
+      ++i;
+    }
+  
+
+  goto out;
+  
+ failed:
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+  if (rule)
+    {
+      bus_match_rule_unref (rule);
+      rule = NULL;
+    }
+
+ out:
+  
+  i = 0;
+  while (tokens[i].key || tokens[i].value)
+    {
+      _dbus_assert (i < MAX_RULE_TOKENS);
+      dbus_free (tokens[i].key);
+      dbus_free (tokens[i].value);
+      ++i;
+    }
+  
+  return rule;
+}
+
+struct BusMatchmaker
+{
+  int refcount;
+
+  DBusList *all_rules;
+};
+
+BusMatchmaker*
+bus_matchmaker_new (void)
+{
+  BusMatchmaker *matchmaker;
+
+  matchmaker = dbus_new0 (BusMatchmaker, 1);
+  if (matchmaker == NULL)
+    return NULL;
+
+  matchmaker->refcount = 1;
+  
+  return matchmaker;
+}
+
+BusMatchmaker *
+bus_matchmaker_ref (BusMatchmaker *matchmaker)
+{
+  _dbus_assert (matchmaker->refcount > 0);
+
+  matchmaker->refcount += 1;
+
+  return matchmaker;
+}
+
+void
+bus_matchmaker_unref (BusMatchmaker *matchmaker)
+{
+  _dbus_assert (matchmaker->refcount > 0);
+
+  matchmaker->refcount -= 1;
+  if (matchmaker->refcount == 0)
+    {
+      while (matchmaker->all_rules != NULL)
+        {
+          BusMatchRule *rule;
+
+          rule = matchmaker->all_rules->data;
+          bus_match_rule_unref (rule);
+          _dbus_list_remove_link (&matchmaker->all_rules,
+                                  matchmaker->all_rules);
+        }
+
+      dbus_free (matchmaker);
+    }
+}
+
+/* The rule can't be modified after it's added. */
+dbus_bool_t
+bus_matchmaker_add_rule (BusMatchmaker   *matchmaker,
+                         BusMatchRule    *rule)
+{
+  _dbus_assert (bus_connection_is_active (rule->matches_go_to));
+
+  if (!_dbus_list_append (&matchmaker->all_rules, rule))
+    return FALSE;
+
+  if (!bus_connection_add_match_rule (rule->matches_go_to, rule))
+    {
+      _dbus_list_remove_last (&matchmaker->all_rules, rule);
+      return FALSE;
+    }
+  
+  bus_match_rule_ref (rule);
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  {
+    char *s = match_rule_to_string (rule);
+
+    _dbus_verbose ("Added match rule %s to connection %p\n",
+                   s, rule->matches_go_to);
+    dbus_free (s);
+  }
+#endif
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+match_rule_equal (BusMatchRule *a,
+                  BusMatchRule *b)
+{
+  if (a->flags != b->flags)
+    return FALSE;
+
+  if (a->matches_go_to != b->matches_go_to)
+    return FALSE;
+
+  if ((a->flags & BUS_MATCH_MESSAGE_TYPE) &&
+      a->message_type != b->message_type)
+    return FALSE;
+
+  if ((a->flags & BUS_MATCH_MEMBER) &&
+      strcmp (a->member, b->member) != 0)
+    return FALSE;
+
+  if ((a->flags & BUS_MATCH_PATH) &&
+      strcmp (a->path, b->path) != 0)
+    return FALSE;
+  
+  if ((a->flags & BUS_MATCH_INTERFACE) &&
+      strcmp (a->interface, b->interface) != 0)
+    return FALSE;
+
+  if ((a->flags & BUS_MATCH_SENDER) &&
+      strcmp (a->sender, b->sender) != 0)
+    return FALSE;
+
+  if ((a->flags & BUS_MATCH_DESTINATION) &&
+      strcmp (a->destination, b->destination) != 0)
+    return FALSE;
+
+  if (a->flags & BUS_MATCH_ARGS)
+    {
+      int i;
+      
+      if (a->args_len != b->args_len)
+        return FALSE;
+      
+      i = 0;
+      while (i < a->args_len)
+        {
+          if ((a->args[i] != NULL) != (b->args[i] != NULL))
+            return FALSE;
+
+          if (a->args[i] != NULL)
+            {
+              _dbus_assert (b->args[i] != NULL);
+              if (strcmp (a->args[i], b->args[i]) != 0)
+                return FALSE;
+            }
+          
+          ++i;
+        }
+    }
+  
+  return TRUE;
+}
+
+static void
+bus_matchmaker_remove_rule_link (BusMatchmaker   *matchmaker,
+                                 DBusList        *link)
+{
+  BusMatchRule *rule = link->data;
+  
+  bus_connection_remove_match_rule (rule->matches_go_to, rule);
+  _dbus_list_remove_link (&matchmaker->all_rules, link);
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  {
+    char *s = match_rule_to_string (rule);
+
+    _dbus_verbose ("Removed match rule %s for connection %p\n",
+                   s, rule->matches_go_to);
+    dbus_free (s);
+  }
+#endif
+  
+  bus_match_rule_unref (rule);  
+}
+
+void
+bus_matchmaker_remove_rule (BusMatchmaker   *matchmaker,
+                            BusMatchRule    *rule)
+{
+  bus_connection_remove_match_rule (rule->matches_go_to, rule);
+  _dbus_list_remove (&matchmaker->all_rules, rule);
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  {
+    char *s = match_rule_to_string (rule);
+
+    _dbus_verbose ("Removed match rule %s for connection %p\n",
+                   s, rule->matches_go_to);
+    dbus_free (s);
+  }
+#endif
+  
+  bus_match_rule_unref (rule);
+}
+
+/* Remove a single rule which is equal to the given rule by value */
+dbus_bool_t
+bus_matchmaker_remove_rule_by_value (BusMatchmaker   *matchmaker,
+                                     BusMatchRule    *value,
+                                     DBusError       *error)
+{
+  /* FIXME this is an unoptimized linear scan */
+
+  DBusList *link;
+
+  /* we traverse backward because bus_connection_remove_match_rule()
+   * removes the most-recently-added rule
+   */
+  link = _dbus_list_get_last_link (&matchmaker->all_rules);
+  while (link != NULL)
+    {
+      BusMatchRule *rule;
+      DBusList *prev;
+
+      rule = link->data;
+      prev = _dbus_list_get_prev_link (&matchmaker->all_rules, link);
+
+      if (match_rule_equal (rule, value))
+        {
+          bus_matchmaker_remove_rule_link (matchmaker, link);
+          break;
+        }
+
+      link = prev;
+    }
+
+  if (link == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_MATCH_RULE_NOT_FOUND,
+                      "The given match rule wasn't found and can't be removed");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+void
+bus_matchmaker_disconnected (BusMatchmaker   *matchmaker,
+                             DBusConnection  *disconnected)
+{
+  DBusList *link;
+
+  /* FIXME
+   *
+   * This scans all match rules on the bus. We could avoid that
+   * for the rules belonging to the connection, since we keep
+   * a list of those; but for the rules that just refer to
+   * the connection we'd need to do something more elaborate.
+   * 
+   */
+  
+  _dbus_assert (bus_connection_is_active (disconnected));
+
+  link = _dbus_list_get_first_link (&matchmaker->all_rules);
+  while (link != NULL)
+    {
+      BusMatchRule *rule;
+      DBusList *next;
+
+      rule = link->data;
+      next = _dbus_list_get_next_link (&matchmaker->all_rules, link);
+
+      if (rule->matches_go_to == disconnected)
+        {
+          bus_matchmaker_remove_rule_link (matchmaker, link);
+        }
+      else if (((rule->flags & BUS_MATCH_SENDER) && *rule->sender == ':') ||
+               ((rule->flags & BUS_MATCH_DESTINATION) && *rule->destination == ':'))
+        {
+          /* The rule matches to/from a base service, see if it's the
+           * one being disconnected, since we know this service name
+           * will never be recycled.
+           */
+          const char *name;
+
+          name = bus_connection_get_name (disconnected);
+          _dbus_assert (name != NULL); /* because we're an active connection */
+
+          if (((rule->flags & BUS_MATCH_SENDER) &&
+               strcmp (rule->sender, name) == 0) ||
+              ((rule->flags & BUS_MATCH_DESTINATION) &&
+               strcmp (rule->destination, name) == 0))
+            {
+              bus_matchmaker_remove_rule_link (matchmaker, link);
+            }
+        }
+
+      link = next;
+    }
+}
+
+static dbus_bool_t
+connection_is_primary_owner (DBusConnection *connection,
+                             const char     *service_name)
+{
+  BusService *service;
+  DBusString str;
+  BusRegistry *registry;
+
+  _dbus_assert (connection != NULL);
+  
+  registry = bus_connection_get_registry (connection);
+
+  _dbus_string_init_const (&str, service_name);
+  service = bus_registry_lookup (registry, &str);
+
+  if (service == NULL)
+    return FALSE; /* Service doesn't exist so connection can't own it. */
+
+  return bus_service_get_primary_owners_connection (service) == connection;
+}
+
+static dbus_bool_t
+match_rule_matches (BusMatchRule    *rule,
+                    DBusConnection  *sender,
+                    DBusConnection  *addressed_recipient,
+                    DBusMessage     *message)
+{
+  /* All features of the match rule are AND'd together,
+   * so FALSE if any of them don't match.
+   */
+
+  /* sender/addressed_recipient of #NULL may mean bus driver,
+   * or for addressed_recipient may mean a message with no
+   * specific recipient (i.e. a signal)
+   */
+  
+  if (rule->flags & BUS_MATCH_MESSAGE_TYPE)
+    {
+      _dbus_assert (rule->message_type != DBUS_MESSAGE_TYPE_INVALID);
+
+      if (rule->message_type != dbus_message_get_type (message))
+        return FALSE;
+    }
+
+  if (rule->flags & BUS_MATCH_INTERFACE)
+    {
+      const char *iface;
+
+      _dbus_assert (rule->interface != NULL);
+
+      iface = dbus_message_get_interface (message);
+      if (iface == NULL)
+        return FALSE;
+
+      if (strcmp (iface, rule->interface) != 0)
+        return FALSE;
+    }
+
+  if (rule->flags & BUS_MATCH_MEMBER)
+    {
+      const char *member;
+
+      _dbus_assert (rule->member != NULL);
+
+      member = dbus_message_get_member (message);
+      if (member == NULL)
+        return FALSE;
+
+      if (strcmp (member, rule->member) != 0)
+        return FALSE;
+    }
+
+  if (rule->flags & BUS_MATCH_SENDER)
+    {
+      _dbus_assert (rule->sender != NULL);
+
+      if (sender == NULL)
+        {
+          if (strcmp (rule->sender,
+                      DBUS_SERVICE_DBUS) != 0)
+            return FALSE;
+        }
+      else
+        {
+          if (!connection_is_primary_owner (sender, rule->sender))
+            return FALSE;
+        }
+    }
+
+  if (rule->flags & BUS_MATCH_DESTINATION)
+    {
+      const char *destination;
+
+      _dbus_assert (rule->destination != NULL);
+
+      destination = dbus_message_get_destination (message);
+      if (destination == NULL)
+        return FALSE;
+
+      if (addressed_recipient == NULL)
+        {          
+          if (strcmp (rule->destination,
+                      DBUS_SERVICE_DBUS) != 0)
+            return FALSE;
+        }
+      else
+        {
+          if (!connection_is_primary_owner (addressed_recipient, rule->destination))
+            return FALSE;
+        }
+    }
+
+  if (rule->flags & BUS_MATCH_PATH)
+    {
+      const char *path;
+
+      _dbus_assert (rule->path != NULL);
+
+      path = dbus_message_get_path (message);
+      if (path == NULL)
+        return FALSE;
+
+      if (strcmp (path, rule->path) != 0)
+        return FALSE;
+    }
+
+  if (rule->flags & BUS_MATCH_ARGS)
+    {
+      int i;
+      DBusMessageIter iter;
+      
+      _dbus_assert (rule->args != NULL);
+
+      dbus_message_iter_init (message, &iter);
+      
+      i = 0;
+      while (i < rule->args_len)
+        {
+          int current_type;
+          const char *expected_arg;
+
+          expected_arg = rule->args[i];
+          
+          current_type = dbus_message_iter_get_arg_type (&iter);
+
+          if (expected_arg != NULL)
+            {
+              const char *actual_arg;
+              
+              if (current_type != DBUS_TYPE_STRING)
+                return FALSE;
+
+              actual_arg = NULL;
+              dbus_message_iter_get_basic (&iter, &actual_arg);
+              _dbus_assert (actual_arg != NULL);
+
+              if (strcmp (expected_arg, actual_arg) != 0)
+                return FALSE;
+            }
+          
+          if (current_type != DBUS_TYPE_INVALID)
+            dbus_message_iter_next (&iter);
+
+          ++i;
+        }
+    }
+  
+  return TRUE;
+}
+
+dbus_bool_t
+bus_matchmaker_get_recipients (BusMatchmaker   *matchmaker,
+                               BusConnections  *connections,
+                               DBusConnection  *sender,
+                               DBusConnection  *addressed_recipient,
+                               DBusMessage     *message,
+                               DBusList       **recipients_p)
+{
+  /* FIXME for now this is a wholly unoptimized linear search */
+  /* Guessing the important optimization is to skip the signal-related
+   * match lists when processing method call and exception messages.
+   * So separate match rule lists for signals?
+   */
+  
+  DBusList *link;
+
+  _dbus_assert (*recipients_p == NULL);
+
+  /* This avoids sending same message to the same connection twice.
+   * Purpose of the stamp instead of a bool is to avoid iterating over
+   * all connections resetting the bool each time.
+   */
+  bus_connections_increment_stamp (connections);
+
+  /* addressed_recipient is already receiving the message, don't add to list.
+   * NULL addressed_recipient means either bus driver, or this is a signal
+   * and thus lacks a specific addressed_recipient.
+   */
+  if (addressed_recipient != NULL)
+    bus_connection_mark_stamp (addressed_recipient);
+
+  link = _dbus_list_get_first_link (&matchmaker->all_rules);
+  while (link != NULL)
+    {
+      BusMatchRule *rule;
+
+      rule = link->data;
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+      {
+        char *s = match_rule_to_string (rule);
+        
+        _dbus_verbose ("Checking whether message matches rule %s for connection %p\n",
+                       s, rule->matches_go_to);
+        dbus_free (s);
+      }
+#endif
+      
+      if (match_rule_matches (rule,
+                              sender, addressed_recipient, message))
+        {
+          _dbus_verbose ("Rule matched\n");
+          
+          /* Append to the list if we haven't already */
+          if (bus_connection_mark_stamp (rule->matches_go_to))
+            {
+              if (!_dbus_list_append (recipients_p, rule->matches_go_to))
+                goto nomem;
+            }
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+          else
+            {
+              _dbus_verbose ("Connection already receiving this message, so not adding again\n");
+            }
+#endif /* DBUS_ENABLE_VERBOSE_MODE */
+        }
+
+      link = _dbus_list_get_next_link (&matchmaker->all_rules, link);
+    }
+
+  return TRUE;
+
+ nomem:
+  _dbus_list_clear (recipients_p);
+  return FALSE;
+}
+
+#ifdef DBUS_BUILD_TESTS
+#include "test.h"
+#include <stdlib.h>
+
+static BusMatchRule*
+check_parse (dbus_bool_t should_succeed,
+             const char *text)
+{
+  BusMatchRule *rule;
+  DBusString str;
+  DBusError error;
+
+  dbus_error_init (&error);
+
+  _dbus_string_init_const (&str, text);
+  
+  rule = bus_match_rule_parse (NULL, &str, &error);
+  if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+    {
+      dbus_error_free (&error);
+      return NULL;
+    }
+
+  if (should_succeed && rule == NULL)
+    {
+      _dbus_warn ("Failed to parse: %s: %s: \"%s\"\n",
+                  error.name, error.message,
+                  _dbus_string_get_const_data (&str));
+      exit (1);
+    }
+
+  if (!should_succeed && rule != NULL)
+    {
+      _dbus_warn ("Failed to fail to parse: \"%s\"\n",
+                  _dbus_string_get_const_data (&str));
+      exit (1);
+    }
+
+  dbus_error_free (&error);
+
+  return rule;
+}
+
+static void
+assert_large_rule (BusMatchRule *rule)
+{
+  _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE);
+  _dbus_assert (rule->flags & BUS_MATCH_SENDER);
+  _dbus_assert (rule->flags & BUS_MATCH_INTERFACE);
+  _dbus_assert (rule->flags & BUS_MATCH_MEMBER);
+  _dbus_assert (rule->flags & BUS_MATCH_DESTINATION);
+  _dbus_assert (rule->flags & BUS_MATCH_PATH);
+
+  _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL);
+  _dbus_assert (rule->interface != NULL);
+  _dbus_assert (rule->member != NULL);
+  _dbus_assert (rule->sender != NULL);
+  _dbus_assert (rule->destination != NULL);
+  _dbus_assert (rule->path != NULL);
+
+  _dbus_assert (strcmp (rule->interface, "org.freedesktop.DBusInterface") == 0);
+  _dbus_assert (strcmp (rule->sender, "org.freedesktop.DBusSender") == 0);
+  _dbus_assert (strcmp (rule->member, "Foo") == 0);
+  _dbus_assert (strcmp (rule->path, "/bar/foo") == 0);
+  _dbus_assert (strcmp (rule->destination, ":452345.34") == 0);
+}
+
+static dbus_bool_t
+test_parsing (void *data)
+{
+  BusMatchRule *rule;
+
+  rule = check_parse (TRUE, "type='signal',sender='org.freedesktop.DBusSender',interface='org.freedesktop.DBusInterface',member='Foo',path='/bar/foo',destination=':452345.34'");
+  if (rule != NULL)
+    {
+      assert_large_rule (rule);
+      bus_match_rule_unref (rule);
+    }
+
+  /* With extra whitespace and useless quotes */
+  rule = check_parse (TRUE, "    type='signal',  \tsender='org.freedes''ktop.DBusSender',   interface='org.freedesktop.DBusInterface''''', \tmember='Foo',path='/bar/foo',destination=':452345.34'''''");
+  if (rule != NULL)
+    {
+      assert_large_rule (rule);
+      bus_match_rule_unref (rule);
+    }
+
+
+  /* A simple signal connection */
+  rule = check_parse (TRUE, "type='signal',path='/foo',interface='org.Bar'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags & BUS_MATCH_MESSAGE_TYPE);
+      _dbus_assert (rule->flags & BUS_MATCH_INTERFACE);
+      _dbus_assert (rule->flags & BUS_MATCH_PATH);
+
+      _dbus_assert (rule->message_type == DBUS_MESSAGE_TYPE_SIGNAL);
+      _dbus_assert (rule->interface != NULL);
+      _dbus_assert (rule->path != NULL);
+
+      _dbus_assert (strcmp (rule->interface, "org.Bar") == 0);
+      _dbus_assert (strcmp (rule->path, "/foo") == 0);
+  
+      bus_match_rule_unref (rule);
+    }
+
+  /* argN */
+  rule = check_parse (TRUE, "arg0='foo'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_ARGS);
+      _dbus_assert (rule->args != NULL);
+      _dbus_assert (rule->args_len == 1);
+      _dbus_assert (rule->args[0] != NULL);
+      _dbus_assert (rule->args[1] == NULL);
+      _dbus_assert (strcmp (rule->args[0], "foo") == 0);
+
+      bus_match_rule_unref (rule);
+    }
+  
+  rule = check_parse (TRUE, "arg1='foo'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_ARGS);
+      _dbus_assert (rule->args != NULL);
+      _dbus_assert (rule->args_len == 2);
+      _dbus_assert (rule->args[0] == NULL);
+      _dbus_assert (rule->args[1] != NULL);
+      _dbus_assert (rule->args[2] == NULL);
+      _dbus_assert (strcmp (rule->args[1], "foo") == 0);
+
+      bus_match_rule_unref (rule);
+    }
+
+  rule = check_parse (TRUE, "arg2='foo'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_ARGS);
+      _dbus_assert (rule->args != NULL);
+      _dbus_assert (rule->args_len == 3);
+      _dbus_assert (rule->args[0] == NULL);
+      _dbus_assert (rule->args[1] == NULL);
+      _dbus_assert (rule->args[2] != NULL);
+      _dbus_assert (rule->args[3] == NULL);
+      _dbus_assert (strcmp (rule->args[2], "foo") == 0);
+
+      bus_match_rule_unref (rule);
+    }
+  
+  rule = check_parse (TRUE, "arg40='foo'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_ARGS);
+      _dbus_assert (rule->args != NULL);
+      _dbus_assert (rule->args_len == 41);
+      _dbus_assert (rule->args[0] == NULL);
+      _dbus_assert (rule->args[1] == NULL);
+      _dbus_assert (rule->args[40] != NULL);
+      _dbus_assert (rule->args[41] == NULL);
+      _dbus_assert (strcmp (rule->args[40], "foo") == 0);
+
+      bus_match_rule_unref (rule);
+    }
+  
+  rule = check_parse (TRUE, "arg63='foo'");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == BUS_MATCH_ARGS);
+      _dbus_assert (rule->args != NULL);
+      _dbus_assert (rule->args_len == 64);
+      _dbus_assert (rule->args[0] == NULL);
+      _dbus_assert (rule->args[1] == NULL);
+      _dbus_assert (rule->args[63] != NULL);
+      _dbus_assert (rule->args[64] == NULL);
+      _dbus_assert (strcmp (rule->args[63], "foo") == 0);
+
+      bus_match_rule_unref (rule);
+    }
+  
+  /* Too-large argN */
+  rule = check_parse (FALSE, "arg300='foo'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "arg64='foo'");
+  _dbus_assert (rule == NULL);
+
+  /* No N in argN */
+  rule = check_parse (FALSE, "arg='foo'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "argv='foo'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "arg3junk='foo'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "argument='foo'");
+  _dbus_assert (rule == NULL);
+  
+  /* Reject duplicates */
+  rule = check_parse (FALSE, "type='signal',type='method_call'");
+  _dbus_assert (rule == NULL);
+
+  /* Duplicates with the argN code */
+  rule = check_parse (FALSE, "arg0='foo',arg0='bar'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "arg3='foo',arg3='bar'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "arg30='foo',arg30='bar'");
+  _dbus_assert (rule == NULL);
+  
+  /* Reject broken keys */
+  rule = check_parse (FALSE, "blah='signal'");
+  _dbus_assert (rule == NULL);
+
+  /* Reject broken values */
+  rule = check_parse (FALSE, "type='chouin'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "interface='abc@def++'");
+  _dbus_assert (rule == NULL);
+  rule = check_parse (FALSE, "service='youpi'");
+  _dbus_assert (rule == NULL);
+
+  /* Allow empty rule */
+  rule = check_parse (TRUE, "");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+
+  /* All-whitespace rule is the same as empty */
+  rule = check_parse (TRUE, "    \t");
+  if (rule != NULL)
+    {
+      _dbus_assert (rule->flags == 0);
+      
+      bus_match_rule_unref (rule);
+    }
+
+  /* But with non-whitespace chars and no =value, it's not OK */
+  rule = check_parse (FALSE, "type");
+  _dbus_assert (rule == NULL);
+  
+  return TRUE;
+}
+
+static struct {
+  const char *first;
+  const char *second;
+} equality_tests[] = {
+  { "type='signal'", "type='signal'" },
+  { "type='signal',interface='foo.bar'", "interface='foo.bar',type='signal'" },
+  { "type='signal',member='bar'", "member='bar',type='signal'" },
+  { "type='method_call',sender=':1.0'", "sender=':1.0',type='method_call'" },
+  { "type='method_call',destination=':1.0'", "destination=':1.0',type='method_call'" },
+  { "type='method_call',path='/foo/bar'", "path='/foo/bar',type='method_call'" },
+  { "type='method_call',arg0='blah'", "arg0='blah',type='method_call'" },
+  { "type='method_call',arg0='boo'", "arg0='boo',type='method_call'" },
+  { "type='method_call',arg0='blah',arg1='baz'", "arg0='blah',arg1='baz',type='method_call'" },
+  { "type='method_call',arg3='foosh'", "arg3='foosh',type='method_call'" },
+  { "arg3='fool'", "arg3='fool'" },
+  { "member='food'", "member='food'" }
+};
+
+static void
+test_equality (void)
+{
+  int i;
+  
+  i = 0;
+  while (i < _DBUS_N_ELEMENTS (equality_tests))
+    {
+      BusMatchRule *first;
+      BusMatchRule *second;
+      int j;
+      
+      first = check_parse (TRUE, equality_tests[i].first);
+      _dbus_assert (first != NULL);
+      second = check_parse (TRUE, equality_tests[i].second);
+      _dbus_assert (second != NULL);
+
+      if (!match_rule_equal (first, second))
+        {
+          _dbus_warn ("rule %s and %s should have been equal\n",
+                      equality_tests[i].first,
+                      equality_tests[i].second);
+          exit (1);
+        }
+
+      bus_match_rule_unref (second);
+
+      /* Check that the rule is not equal to any of the
+       * others besides its pair match
+       */
+      j = 0;
+      while (j < _DBUS_N_ELEMENTS (equality_tests))
+        {
+          if (i != j)
+            {
+              second = check_parse (TRUE, equality_tests[j].second);
+
+              if (match_rule_equal (first, second))
+                {
+                  _dbus_warn ("rule %s and %s should not have been equal\n",
+                              equality_tests[i].first,
+                              equality_tests[j].second);
+                  exit (1);
+                }
+              
+              bus_match_rule_unref (second);
+            }
+          
+          ++j;
+        }
+
+      bus_match_rule_unref (first);
+
+      ++i;
+    }
+}
+
+static const char*
+should_match_message_1[] = {
+  "type='signal'",
+  "member='Frobated'",
+  "arg0='foobar'",
+  "type='signal',member='Frobated'",
+  "type='signal',member='Frobated',arg0='foobar'",
+  "member='Frobated',arg0='foobar'",
+  "type='signal',arg0='foobar'",
+  NULL
+};
+
+static const char*
+should_not_match_message_1[] = {
+  "type='method_call'",
+  "type='error'",
+  "type='method_return'",
+  "type='signal',member='Oopsed'",
+  "arg0='blah'",
+  "arg1='foobar'",
+  "arg2='foobar'",
+  "arg3='foobar'",
+  "arg0='3'",
+  "arg1='3'",
+  "arg0='foobar',arg1='abcdef'",
+  "arg0='foobar',arg1='abcdef',arg2='abcdefghi',arg3='abcdefghi',arg4='abcdefghi'",
+  "arg0='foobar',arg1='abcdef',arg4='abcdefghi',arg3='abcdefghi',arg2='abcdefghi'",
+  NULL
+};
+
+static void
+check_matches (dbus_bool_t  expected_to_match,
+               int          number,
+               DBusMessage *message,
+               const char  *rule_text)
+{
+  BusMatchRule *rule;
+  dbus_bool_t matched;
+
+  rule = check_parse (TRUE, rule_text);
+  _dbus_assert (rule != NULL);
+
+  /* We can't test sender/destination rules since we pass NULL here */
+  matched = match_rule_matches (rule, NULL, NULL, message);
+
+  if (matched != expected_to_match)
+    {
+      _dbus_warn ("Expected rule %s to %s message %d, failed\n",
+                  rule_text, expected_to_match ?
+                  "match" : "not match", number);
+      exit (1);
+    }
+
+  bus_match_rule_unref (rule);
+}
+
+static void
+check_matching (DBusMessage *message,
+                int          number,
+                const char **should_match,
+                const char **should_not_match)
+{
+  int i;
+
+  i = 0;
+  while (should_match[i] != NULL)
+    {
+      check_matches (TRUE, number, message, should_match[i]);
+      ++i;
+    }
+
+  i = 0;
+  while (should_not_match[i] != NULL)
+    {
+      check_matches (FALSE, number, message, should_not_match[i]);
+      ++i;
+    }
+}
+
+static void
+test_matching (void)
+{
+  DBusMessage *message1;
+  const char *v_STRING;
+  dbus_int32_t v_INT32;
+
+  message1 = dbus_message_new (DBUS_MESSAGE_TYPE_SIGNAL);
+  _dbus_assert (message1 != NULL);
+  if (!dbus_message_set_member (message1, "Frobated"))
+    _dbus_assert_not_reached ("oom");
+
+  v_STRING = "foobar";
+  v_INT32 = 3;
+  if (!dbus_message_append_args (message1,
+                                 DBUS_TYPE_STRING, &v_STRING,
+                                 DBUS_TYPE_INT32, &v_INT32,
+                                 NULL))
+    _dbus_assert_not_reached ("oom");
+  
+  check_matching (message1, 1,
+                  should_match_message_1,
+                  should_not_match_message_1);
+  
+  dbus_message_unref (message1);
+}
+
+dbus_bool_t
+bus_signals_test (const DBusString *test_data_dir)
+{
+  BusMatchmaker *matchmaker;
+
+  matchmaker = bus_matchmaker_new ();
+  bus_matchmaker_ref (matchmaker);
+  bus_matchmaker_unref (matchmaker);
+  bus_matchmaker_unref (matchmaker);
+
+  if (!_dbus_test_oom_handling ("parsing match rules", test_parsing, NULL))
+    _dbus_assert_not_reached ("Parsing match rules test failed");
+
+  test_equality ();
+
+  test_matching ();
+  
+  return TRUE;
+}
+
+#endif /* DBUS_BUILD_TESTS */
+