diff -r 000000000000 -r e4d67989cc36 ofdbus/dbus/bus/signals.c --- /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 +#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 + +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 */ +