--- /dev/null
+/* Unit tests for detailed header field manipulation
+ *
+ * Copyright © 2017 Collabora Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+#include "dbus/dbus-internals.h"
+#include "dbus/dbus-marshal-recursive.h"
+#include "dbus/dbus-message-private.h"
+#include "dbus/dbus-string.h"
+#include "dbus/dbus-test-tap.h"
+#include "test-utils-glib.h"
+
+typedef struct {
+ const gchar *mode;
+ TestMainContext *ctx;
+ DBusConnection *left_conn;
+ DBusConnection *right_conn;
+ GPid daemon_pid;
+ gchar *address;
+ GQueue held_messages;
+ gboolean skip;
+} Fixture;
+
+static DBusHandlerResult
+hold_filter (DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ Fixture *f = user_data;
+
+ if (dbus_message_get_type (message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ g_queue_push_tail (&f->held_messages, dbus_message_ref (message));
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static void
+setup_dbus_daemon (Fixture *f,
+ gconstpointer context G_GNUC_UNUSED)
+{
+ f->address = test_get_dbus_daemon (NULL, TEST_USER_ME, NULL, &f->daemon_pid);
+
+ if (f->address == NULL)
+ {
+ f->skip = TRUE;
+ return;
+ }
+}
+
+static void
+teardown_dbus_daemon (Fixture *f,
+ gconstpointer context G_GNUC_UNUSED)
+{
+ if (f->daemon_pid != 0)
+ {
+ test_kill_pid (f->daemon_pid);
+ g_spawn_close_pid (f->daemon_pid);
+ f->daemon_pid = 0;
+ }
+
+ g_clear_pointer (&f->address, g_free);
+}
+
+/* Offset to byte order from start of header */
+#define BYTE_ORDER_OFFSET 0
+/* Offset to version from start of header */
+#define VERSION_OFFSET 3
+/* Offset to fields array length from start of header, in protocol v1 */
+#define FIELDS_ARRAY_LENGTH_OFFSET 12
+/** Offset to first field in header, in protocol v1 */
+#define FIRST_FIELD_OFFSET 16
+
+/* Offset from start of _dbus_header_signature_str to the signature of
+ * the fields array */
+#define FIELDS_ARRAY_SIGNATURE_OFFSET 6
+
+/* A byte that is not a DBUS_HEADER_FIELD_* */
+#define NOT_A_HEADER_FIELD 123
+
+_DBUS_STRING_DEFINE_STATIC(_dbus_header_signature_str, DBUS_HEADER_SIGNATURE);
+
+static void
+string_overwrite_n (DBusString *str,
+ int start,
+ const void *bytes,
+ int len)
+{
+ unsigned char *data = _dbus_string_get_udata_len (str, start, len);
+
+ g_assert (data != NULL);
+ memcpy (data, bytes, len);
+}
+
+static void
+steal_reply_cb (DBusPendingCall *pc,
+ void *data)
+{
+ DBusMessage **message_p = data;
+
+ g_assert (message_p != NULL);
+ g_assert (*message_p == NULL);
+ *message_p = dbus_pending_call_steal_reply (pc);
+ g_assert (*message_p != NULL);
+}
+
+/*
+ * Test the handling of unknown header fields.
+ *
+ * Return TRUE if the right thing happens, but the right thing might include
+ * OOM.
+ */
+static dbus_bool_t
+test_weird_header_field (void *user_data,
+ dbus_bool_t have_memory)
+{
+ Fixture *f = user_data;
+ const char *body = "hello";
+ const char *new_body = NULL;
+ DBusError error = DBUS_ERROR_INIT;
+ DBusMessage *original = NULL;
+ DBusMessage *modified = NULL;
+ DBusMessage *filtered = NULL;
+ DBusMessage *relayed = NULL;
+ DBusMessage *reply = NULL;
+ DBusPendingCall *pc = NULL;
+ char *blob = NULL;
+ int blob_len;
+ DBusString modified_blob = _DBUS_STRING_INIT_INVALID;
+ /* This is the serialization of a struct (uv), assumed to be at an
+ * 8-byte boundary. */
+ const unsigned char weird_header[8] = {
+ NOT_A_HEADER_FIELD, /*< type code */
+ 1, /*< length of signature */
+ 'u', /*< signature: uint32 */
+ '\0', /*< end of signature */
+ /* no padding required */
+ '\x12', /*< uint32 0x12345678 (BE) or 0x78563412 (LE) */
+ '\x34',
+ '\x56',
+ '\x78'
+ };
+ int bytes_needed;
+ DBusTypeReader reader;
+ DBusTypeReader array;
+ gboolean added_hold_filter = FALSE;
+ GError *gerror = NULL;
+
+ if (f->skip)
+ return TRUE;
+
+ /* We'd normally do this in setup_dbus_daemon(), but then we couldn't
+ * allocate memory that we want freed by dbus_shutdown(). */
+ g_assert_cmpint (_dbus_get_malloc_blocks_outstanding (), ==, 0);
+ f->ctx = test_main_context_try_get ();
+
+ if (f->ctx == NULL)
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ f->left_conn = test_try_connect_to_bus (f->ctx, f->address, &gerror);
+
+ if (f->left_conn == NULL)
+ {
+ g_assert_error (gerror, G_DBUS_ERROR, G_DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ f->right_conn = test_try_connect_to_bus (f->ctx, f->address, &gerror);
+
+ if (f->right_conn == NULL)
+ {
+ g_assert_error (gerror, G_DBUS_ERROR, G_DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ if (!dbus_connection_add_filter (f->right_conn, hold_filter, f, NULL))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ added_hold_filter = TRUE;
+
+ original = dbus_message_new_method_call (dbus_bus_get_unique_name (f->right_conn),
+ "/com/example/Path",
+ "com.example.Interface",
+ "Method");
+
+ if (original == NULL ||
+ !dbus_message_append_args (original,
+ DBUS_TYPE_STRING, &body,
+ DBUS_TYPE_INVALID))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* Messages with serial number 0 can't be demarshalled. */
+ dbus_message_set_serial (original, 42);
+
+ if (!dbus_message_marshal (original, &blob, &blob_len))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* We will add up to 8 bytes, so preallocate that much. */
+ if (!_dbus_string_init_preallocated (&modified_blob, blob_len + 8) ||
+ !_dbus_string_append_len (&modified_blob, blob, blob_len))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* If these are false then we need to change our byte-twiddling */
+ g_assert_cmpint (blob_len, >, FIRST_FIELD_OFFSET);
+ g_assert_cmpint (blob[VERSION_OFFSET], ==, 1);
+ g_assert_cmpint (blob[BYTE_ORDER_OFFSET], ==, DBUS_COMPILER_BYTE_ORDER);
+
+ if (f->mode == NULL)
+ {
+ /* Do nothing: don't insert a weird header field at all */
+ }
+ else if (g_str_equal (f->mode, "change"))
+ {
+ /* Replace the interface (which is optional anyway) with the
+ * weird header field */
+
+ _dbus_type_reader_init (&reader, DBUS_COMPILER_BYTE_ORDER,
+ &_dbus_header_signature_str,
+ FIELDS_ARRAY_SIGNATURE_OFFSET,
+ &modified_blob,
+ FIELDS_ARRAY_LENGTH_OFFSET);
+ _dbus_type_reader_recurse (&reader, &array);
+
+ while (_dbus_type_reader_get_current_type (&array) !=
+ DBUS_TYPE_INVALID)
+ {
+ DBusTypeReader sub;
+ unsigned char field_code;
+
+ _dbus_type_reader_recurse (&array, &sub);
+
+ g_assert_cmpint (_dbus_type_reader_get_current_type (&sub),
+ ==, DBUS_TYPE_BYTE);
+ _dbus_type_reader_read_basic (&sub, &field_code);
+
+ if (field_code == DBUS_HEADER_FIELD_INTERFACE)
+ {
+ _dbus_string_set_byte (&modified_blob,
+ _dbus_type_reader_get_value_pos (&sub),
+ NOT_A_HEADER_FIELD);
+ break;
+ }
+
+ _dbus_type_reader_next (&array);
+ }
+ }
+ else if (g_str_equal (f->mode, "prepend"))
+ {
+ dbus_uint32_t header_fields_length;
+
+ memcpy (&header_fields_length, &blob[FIELDS_ARRAY_LENGTH_OFFSET], 4);
+
+ /* Insert a weird header field at the beginning of the fields
+ * array. We do this by byte manipulation rather than by using a
+ * DBusTypeReader, because we eventually want to get rid of the
+ * counterintuitive ability for a DBusTypeReader to write to the
+ * message: https://bugs.freedesktop.org/show_bug.cgi?id=38288 */
+ if (!_dbus_string_insert_8_aligned (&modified_blob,
+ FIRST_FIELD_OFFSET,
+ weird_header))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ header_fields_length += 8;
+ string_overwrite_n (&modified_blob, FIELDS_ARRAY_LENGTH_OFFSET,
+ &header_fields_length, 4);
+ }
+ else if (g_str_equal (f->mode, "append"))
+ {
+ dbus_uint32_t header_fields_length;
+
+ memcpy (&header_fields_length, &blob[FIELDS_ARRAY_LENGTH_OFFSET], 4);
+
+ /* Insert a weird header field at the end of the fields
+ * array, after the padding (which was previously the padding between
+ * header and body, and is now the padding between the last-but-one
+ * header field and the new header field). For simplicity, we've
+ * used a weird header field that does not ever need to be followed
+ * by padding itself.
+ *
+ * Old:
+ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ * | ... 16-byte fixed-length part ... |
+ * | ... 16-byte fixed-length part ... |
+ * [A] first header field ... |
+ * | last header field [B] padding [C]
+ * [C] body...
+ *
+ * New:
+ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ * | ... 16-byte fixed-length part ... |
+ * | ... 16-byte fixed-length part ... |
+ * [A] first header field ... |
+ * | previously-last field [B] padding [C]
+ * | weird header field, exactly 8 bytes long [B'=C']
+ * [B'=C'] body...
+ *
+ */
+ header_fields_length = _DBUS_ALIGN_VALUE (header_fields_length, 8);
+ g_assert_cmpint (header_fields_length % 8, ==, 0);
+
+ if (!_dbus_string_insert_8_aligned (&modified_blob,
+ (FIRST_FIELD_OFFSET +
+ header_fields_length),
+ weird_header))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ header_fields_length += 8;
+ string_overwrite_n (&modified_blob, FIELDS_ARRAY_LENGTH_OFFSET,
+ &header_fields_length, 4);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ /* OK, now we've hacked up the message, compare it with the original. */
+ bytes_needed = dbus_message_demarshal_bytes_needed (_dbus_string_get_const_data (&modified_blob),
+ _dbus_string_get_length (&modified_blob));
+
+ if (f->mode == NULL || g_str_equal (f->mode, "change"))
+ {
+ /* We edited the message in-place so its effective length didn't
+ * change */
+ g_assert_cmpint (bytes_needed, ==, blob_len);
+ }
+ else
+ {
+ g_assert_cmpint (bytes_needed, ==, blob_len + 8);
+ }
+
+ g_assert_cmpint (_dbus_string_get_length (&modified_blob), ==,
+ bytes_needed);
+ modified = dbus_message_demarshal (_dbus_string_get_const_data (&modified_blob),
+ _dbus_string_get_length (&modified_blob),
+ &error);
+
+ if (modified == NULL)
+ {
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* The modified message has the same fields, except possibly the
+ * interface. */
+ g_assert_cmpint (dbus_message_get_type (modified), ==,
+ dbus_message_get_type (original));
+ g_assert_cmpstr (dbus_message_get_path (modified), ==,
+ dbus_message_get_path (original));
+ g_assert_cmpstr (dbus_message_get_member (modified), ==,
+ dbus_message_get_member (original));
+ g_assert_cmpstr (dbus_message_get_error_name (modified), ==,
+ dbus_message_get_error_name (original));
+ g_assert_cmpstr (dbus_message_get_destination (modified), ==,
+ dbus_message_get_destination (original));
+ g_assert_cmpstr (dbus_message_get_sender (modified), ==,
+ dbus_message_get_sender (original));
+ g_assert_cmpstr (dbus_message_get_signature (modified), ==,
+ dbus_message_get_signature (original));
+ g_assert_cmpint (dbus_message_get_no_reply (modified), ==,
+ dbus_message_get_no_reply (original));
+ g_assert_cmpint (dbus_message_get_serial (modified), ==,
+ dbus_message_get_serial (original));
+ g_assert_cmpint (dbus_message_get_reply_serial (modified), ==,
+ dbus_message_get_reply_serial (original));
+ g_assert_cmpint (dbus_message_get_auto_start (modified), ==,
+ dbus_message_get_auto_start (original));
+ g_assert_cmpint (dbus_message_get_allow_interactive_authorization (modified),
+ ==,
+ dbus_message_get_allow_interactive_authorization (original));
+
+ if (dbus_message_get_args (modified, &error,
+ DBUS_TYPE_STRING, &new_body,
+ DBUS_TYPE_INVALID))
+ {
+ g_assert_cmpstr (new_body, ==, body);
+ }
+ else
+ {
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ if (f->mode != NULL && g_str_equal (f->mode, "change"))
+ {
+ /* We edited the interface field in-place to turn it into the
+ * unknown field, so it doesn't have an interface any more. */
+ g_assert_cmpstr (dbus_message_get_interface (modified), ==, NULL);
+ }
+ else
+ {
+ /* We didn't change the interface. */
+ g_assert_cmpstr (dbus_message_get_interface (modified), ==,
+ dbus_message_get_interface (original));
+ }
+
+ /* Copy the modified message so we can filter it. */
+ filtered = dbus_message_demarshal (_dbus_string_get_const_data (&modified_blob),
+ _dbus_string_get_length (&modified_blob),
+ &error);
+
+ if (filtered == NULL)
+ {
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* TODO: Actually filter its header */
+
+ /* All known headers are the same as in the modified message that was
+ * deserialized from the same blob */
+ g_assert_cmpint (dbus_message_get_type (filtered), ==,
+ dbus_message_get_type (modified));
+ g_assert_cmpstr (dbus_message_get_path (filtered), ==,
+ dbus_message_get_path (modified));
+ g_assert_cmpstr (dbus_message_get_member (filtered), ==,
+ dbus_message_get_member (modified));
+ g_assert_cmpstr (dbus_message_get_error_name (filtered), ==,
+ dbus_message_get_error_name (modified));
+ g_assert_cmpstr (dbus_message_get_destination (filtered), ==,
+ dbus_message_get_destination (modified));
+ g_assert_cmpstr (dbus_message_get_sender (filtered), ==,
+ dbus_message_get_sender (modified));
+ g_assert_cmpstr (dbus_message_get_signature (filtered), ==,
+ dbus_message_get_signature (modified));
+ g_assert_cmpint (dbus_message_get_no_reply (filtered), ==,
+ dbus_message_get_no_reply (modified));
+ g_assert_cmpint (dbus_message_get_serial (filtered), ==,
+ dbus_message_get_serial (modified));
+ g_assert_cmpint (dbus_message_get_reply_serial (filtered), ==,
+ dbus_message_get_reply_serial (modified));
+ g_assert_cmpint (dbus_message_get_auto_start (filtered), ==,
+ dbus_message_get_auto_start (modified));
+ g_assert_cmpint (dbus_message_get_allow_interactive_authorization (modified),
+ ==,
+ dbus_message_get_allow_interactive_authorization (original));
+
+ /* The body is also the same */
+ if (dbus_message_get_args (filtered, &error,
+ DBUS_TYPE_STRING, &new_body,
+ DBUS_TYPE_INVALID))
+ {
+ g_assert_cmpstr (new_body, ==, body);
+ }
+ else
+ {
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ /* We can't use _dbus_header_get_field_raw() because it asserts that
+ * the field in question is in range. */
+ _dbus_type_reader_init (&reader, DBUS_COMPILER_BYTE_ORDER,
+ &_dbus_header_signature_str,
+ FIELDS_ARRAY_SIGNATURE_OFFSET,
+ &filtered->header.data,
+ FIELDS_ARRAY_LENGTH_OFFSET);
+ _dbus_type_reader_recurse (&reader, &array);
+
+ while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID)
+ {
+ DBusTypeReader sub;
+ unsigned char field_code;
+
+ _dbus_type_reader_recurse (&array, &sub);
+
+ g_assert_cmpint (_dbus_type_reader_get_current_type (&sub),
+ ==, DBUS_TYPE_BYTE);
+ _dbus_type_reader_read_basic (&sub, &field_code);
+
+ if (field_code == NOT_A_HEADER_FIELD)
+ {
+ g_test_message ("TODO: header field was not filtered out");
+ }
+
+ _dbus_type_reader_next (&array);
+ }
+
+ /* Sending a message through the dbus-daemon currently preserves
+ * unknown header fields, but it should not.
+ * https://bugs.freedesktop.org/show_bug.cgi?id=100317 */
+
+ /* We have to use send_with_reply because if we don't, we won't know
+ * if the message was dropped on the floor due to out-of-memory. */
+ if (!dbus_connection_send_with_reply (f->left_conn, modified, &pc, -1))
+ {
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ if (dbus_pending_call_get_completed (pc))
+ {
+ steal_reply_cb (pc, &reply);
+ }
+ else if (!dbus_pending_call_set_notify (pc, steal_reply_cb, &reply, NULL))
+ {
+ dbus_pending_call_cancel (pc);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ while (g_queue_get_length (&f->held_messages) < 1 && reply == NULL)
+ test_main_context_iterate (f->ctx, TRUE);
+
+ if (reply != NULL)
+ {
+ dbus_set_error_from_message (&error, reply);
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ relayed = g_queue_pop_head (&f->held_messages);
+ g_assert_cmpint (g_queue_get_length (&f->held_messages), ==, 0);
+
+ /* The message relayed through the dbus-daemon has the same known
+ * fields and content as the one we sent, except that it has a sender
+ * and a serial number. */
+ g_assert_cmpint (dbus_message_get_type (relayed), ==,
+ dbus_message_get_type (modified));
+ g_assert_cmpstr (dbus_message_get_path (relayed), ==,
+ dbus_message_get_path (modified));
+ g_assert_cmpstr (dbus_message_get_member (relayed), ==,
+ dbus_message_get_member (modified));
+ g_assert_cmpstr (dbus_message_get_error_name (relayed), ==,
+ dbus_message_get_error_name (modified));
+ g_assert_cmpstr (dbus_message_get_destination (relayed), ==,
+ dbus_message_get_destination (modified));
+ g_assert_cmpstr (dbus_message_get_sender (relayed), ==,
+ dbus_bus_get_unique_name (f->left_conn));
+ g_assert_cmpstr (dbus_message_get_signature (relayed), ==,
+ dbus_message_get_signature (modified));
+ g_assert_cmpint (dbus_message_get_no_reply (relayed), ==,
+ dbus_message_get_no_reply (modified));
+ g_assert_cmpint (dbus_message_get_serial (relayed), !=, 0);
+ g_assert_cmpint (dbus_message_get_reply_serial (relayed), ==,
+ dbus_message_get_reply_serial (modified));
+ g_assert_cmpint (dbus_message_get_auto_start (relayed), ==,
+ dbus_message_get_auto_start (modified));
+ g_assert_cmpint (dbus_message_get_allow_interactive_authorization (modified),
+ ==,
+ dbus_message_get_allow_interactive_authorization (original));
+
+ if (dbus_message_get_args (relayed, &error,
+ DBUS_TYPE_STRING, &new_body,
+ DBUS_TYPE_INVALID))
+ {
+ g_assert_cmpstr (new_body, ==, body);
+ }
+ else
+ {
+ g_assert_cmpstr (error.name, ==, DBUS_ERROR_NO_MEMORY);
+ g_assert_false (have_memory);
+ goto out;
+ }
+
+ _dbus_type_reader_init (&reader, DBUS_COMPILER_BYTE_ORDER,
+ &_dbus_header_signature_str,
+ FIELDS_ARRAY_SIGNATURE_OFFSET,
+ &relayed->header.data,
+ FIELDS_ARRAY_LENGTH_OFFSET);
+ _dbus_type_reader_recurse (&reader, &array);
+
+ while (_dbus_type_reader_get_current_type (&array) != DBUS_TYPE_INVALID)
+ {
+ DBusTypeReader sub;
+ unsigned char field_code;
+
+ _dbus_type_reader_recurse (&array, &sub);
+
+ g_assert_cmpint (_dbus_type_reader_get_current_type (&sub),
+ ==, DBUS_TYPE_BYTE);
+ _dbus_type_reader_read_basic (&sub, &field_code);
+
+ if (field_code == NOT_A_HEADER_FIELD)
+ {
+ g_test_message ("TODO: header field was passed through");
+ }
+
+ _dbus_type_reader_next (&array);
+ }
+
+ /* On success, we don't actually reply: it's one more thing that
+ * could hit OOM */
+
+out:
+ g_clear_error (&gerror);
+ _dbus_string_free (&modified_blob);
+ dbus_free (blob);
+
+ if (pc != NULL)
+ dbus_pending_call_cancel (pc);
+
+ dbus_clear_pending_call (&pc);
+ dbus_clear_message (&reply);
+ dbus_clear_message (&relayed);
+ dbus_clear_message (&filtered);
+ dbus_clear_message (&modified);
+ dbus_clear_message (&original);
+ dbus_error_free (&error);
+
+ /* We'd normally do this in teardown_dbus_daemon(), but for this test
+ * we want to do it here so we can dbus_shutdown(). */
+ if (f->left_conn != NULL)
+ {
+ dbus_connection_close (f->left_conn);
+ test_connection_shutdown (f->ctx, f->left_conn);
+ }
+
+ if (f->right_conn != NULL)
+ {
+ if (added_hold_filter)
+ dbus_connection_remove_filter (f->right_conn, hold_filter, f);
+
+ g_queue_foreach (&f->held_messages, (GFunc) dbus_message_unref, NULL);
+ g_queue_clear (&f->held_messages);
+
+ dbus_connection_close (f->right_conn);
+ test_connection_shutdown (f->ctx, f->right_conn);
+ }
+
+ dbus_clear_connection (&f->left_conn);
+ dbus_clear_connection (&f->right_conn);
+ g_clear_pointer (&f->ctx, test_main_context_unref);
+
+ dbus_shutdown ();
+ g_assert_cmpint (_dbus_get_malloc_blocks_outstanding (), ==, 0);
+
+ return !g_test_failed ();
+}
+
+typedef struct
+{
+ const gchar *name;
+ DBusTestMemoryFunction function;
+ const gchar *mode;
+} OOMTestCase;
+
+static void
+test_oom_wrapper (Fixture *f,
+ gconstpointer data)
+{
+ const OOMTestCase *test = data;
+
+ f->mode = test->mode;
+
+ if (g_test_slow ())
+ {
+ /* When we say slow, we mean it. */
+ test_timeout_reset (30);
+ }
+ else
+ {
+ test_timeout_reset (1);
+ }
+
+ if (!_dbus_test_oom_handling (test->name, test->function, f))
+ {
+ g_test_message ("OOM test failed");
+ g_test_fail ();
+ }
+}
+
+static GQueue *test_cases_to_free = NULL;
+
+static void
+add_oom_test (const gchar *name,
+ DBusTestMemoryFunction function,
+ const gchar *mode)
+{
+ /* By using GLib memory allocation here, we avoid being affected by
+ * dbus_shutdown() or contributing to
+ * _dbus_get_malloc_blocks_outstanding() */
+ OOMTestCase *test_case = g_new0 (OOMTestCase, 1);
+
+ test_case->name = name;
+ test_case->function = function;
+ test_case->mode = mode;
+ g_test_add (name, Fixture, test_case, setup_dbus_daemon,
+ test_oom_wrapper, teardown_dbus_daemon);
+ g_queue_push_tail (test_cases_to_free, test_case);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ int ret;
+
+ test_init (&argc, &argv);
+
+ /* Normally we test up to 4 consecutive malloc failures, but that's
+ * painfully slow here. */
+ if (g_getenv ("DBUS_TEST_MALLOC_FAILURES") == NULL)
+ {
+ if (!g_test_slow ())
+ g_setenv ("DBUS_TEST_MALLOC_FAILURES", "2", TRUE);
+ }
+
+ test_cases_to_free = g_queue_new ();
+ add_oom_test ("/message/weird-header-field/none", test_weird_header_field,
+ NULL);
+ add_oom_test ("/message/weird-header-field/append", test_weird_header_field,
+ "append");
+ add_oom_test ("/message/weird-header-field/change", test_weird_header_field,
+ "change");
+ add_oom_test ("/message/weird-header-field/prepend", test_weird_header_field,
+ "prepend");
+
+ ret = g_test_run ();
+
+ g_queue_free_full (test_cases_to_free, g_free);
+ return ret;
+}