/*
 * Support for communicating with BlueZ over DBus.
 *
 * Copyright 2024-2026 Vibhav Pant
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */


#if 0
#pragma makedep unix
#endif

#include "config.h"

#include <stdlib.h>
#include <dlfcn.h>
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>

#ifdef SONAME_LIBDBUS_1
#include <dbus/dbus.h>
#endif

#include <ntstatus.h>
#include <windef.h>
#include <winternl.h>
#include <winbase.h>
#include <bthsdpdef.h>
#include <bluetoothapis.h>
#include <bthledef.h>
#include <bthdef.h>
#include <wine/winebth.h>

#include <wine/debug.h>

#include "winebth_priv.h"

#include "unixlib.h"
#include "unixlib_priv.h"
#include "dbus.h"

#ifdef SONAME_LIBDBUS_1

/* BlueZ is the userspace Bluetooth management daemon for Linux systems, which provides an
 * object-based API for interacting with Bluetooth adapters and devices on top of DBus. Every DBus
 * object has one or more "interfaces", with each interface have its own set of methods and signals
 * that we can call or listen for, respectively. Objects are identified by their "path", which
 * provide a way to organize objects under a hierarchy. For instance, a bluetooth adapter/radio will
 * have a path of the form "/org/bluez/hciX", with any remote devices discovered by or bonded to it
 * having the path "/org/bluez/hciX/XX:XX:XX:XX:XX:XX". Similarly, any LE GATT services,
 * characteristics or descriptors specific to this remote device are represented as objects under
 * the device's path. Among BlueZ's own interfaces, the other important interfaces we rely on are:
 *
 * org.freedesktop.DBus.Properties: Allows us to read and (optionally) modify properties specific
 * to an object's interface using the "Get" and "Set" methods. Additionally, some objects may
 * broadcast a "PropertiesChanged" signal if any of their properties change, which gets used by
 * bluez_filter to let the driver know to appropriately update the equivalent PDO. The "GetAll"
 * method provides a convenient way to get all properties for an interface.
 *
 * org.freedesktop.DBus.ObjectManager: This contains the "GetManagedObjects" method, that returns a
 * list of all known objects under the object's path namespace, with the interfaces they implement
 * (and any properties those interfaces might have). This is used to build a list of initially known
 * devices in bluez_build_initial_device_lists. The "InterfacesAdded" and "InterfacesRemoved"
 * signals are used to detect when a bluetooth adapter or device has been addeed/removed in
 * bluez_filter, which we relay to the bluetooth driver to add/remove the corresponding PDO.
 */

WINE_DEFAULT_DEBUG_CHANNEL( winebth );
WINE_DECLARE_DEBUG_CHANNEL( dbus );

const int bluez_timeout = -1;

#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded"
#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved"
#define DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged"
#define DBUS_DBUS_SIGNAL_NAMEOWNERCHANGED "NameOwnerChanged"

#define DBUS_INTERFACES_ADDED_SIGNATURE                                                            \
    DBUS_TYPE_OBJECT_PATH_AS_STRING                                                                \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING                                                           \
    DBUS_TYPE_STRING_AS_STRING                                                                     \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING    \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING                                                             \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING

#define DBUS_INTERFACES_REMOVED_SIGNATURE                                                          \
    DBUS_TYPE_OBJECT_PATH_AS_STRING                                                                \
    DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING

#define DBUS_PROPERTIES_CHANGED_SIGNATURE                                                          \
    DBUS_TYPE_STRING_AS_STRING                                                                     \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING                                                           \
    DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING                                         \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING                                                             \
    DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING

#define DBUS_NAMEOWNERCHANGED_SIGNATURE \
    DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING

#define BLUEZ_DEST "org.bluez"
#define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1"
#define BLUEZ_INTERFACE_DEVICE  "org.bluez.Device1"
#define BLUEZ_INTERFACE_AGENT_MANAGER "org.bluez.AgentManager1"
#define BLUEZ_INTERFACE_AGENT "org.bluez.Agent1"
#define BLUEZ_INTERFACE_GATT_SERVICE "org.bluez.GattService1"
#define BLUEZ_INTERFACE_GATT_CHARACTERISTICS "org.bluez.GattCharacteristic1"

#define DO_FUNC( f ) typeof( f ) (*p_##f)
DBUS_FUNCS;
#undef DO_FUNC

static BOOL load_dbus_functions( void )
{
    void *handle = dlopen( SONAME_LIBDBUS_1, RTLD_NOW );

    if (handle == NULL) goto failed;

#define DO_FUNC( f )                                                                               \
    if (!( p_##f = dlsym( handle, #f ) ))                                                          \
    {                                                                                              \
    ERR( "failed to load symbol %s: %s\n", #f, dlerror() );                                        \
        goto failed;                                                                               \
    }
    DBUS_FUNCS;
#undef DO_FUNC
    return TRUE;

failed:
    WARN( "failed to load DBus support: %s\n", dlerror() );
    return FALSE;
}

static NTSTATUS bluez_dbus_error_to_ntstatus( const DBusError *error )
{

#define DBUS_ERROR_CASE(n, s) if (p_dbus_error_has_name( error, (n)) ) return (s)

    DBUS_ERROR_CASE( "org.bluez.Error.Failed", STATUS_INTERNAL_ERROR);
    DBUS_ERROR_CASE( "org.bluez.Error.NotReady", STATUS_DEVICE_NOT_READY );
    DBUS_ERROR_CASE( "org.bluez.Error.NotAuthorized", STATUS_ACCESS_DENIED );
    DBUS_ERROR_CASE( "org.bluez.Error.InvalidArguments", STATUS_INVALID_PARAMETER );
    DBUS_ERROR_CASE( "org.bluez.Error.AlreadyExists", STATUS_NO_MORE_ENTRIES );
    DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationCanceled", STATUS_CANCELLED );
    DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationFailed", STATUS_INTERNAL_ERROR );
    DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationRejected", STATUS_INTERNAL_ERROR );
    DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationTimeout", STATUS_TIMEOUT );
    DBUS_ERROR_CASE( "org.bluez.Error.ConnectionAttemptFailed", STATUS_DEVICE_NOT_CONNECTED);
    DBUS_ERROR_CASE( "org.bluez.Error.NotConnected", STATUS_DEVICE_NOT_CONNECTED );
    DBUS_ERROR_CASE( "org.bluez.Error.InProgress", STATUS_OPERATION_IN_PROGRESS );
    DBUS_ERROR_CASE( DBUS_ERROR_UNKNOWN_OBJECT, STATUS_INVALID_PARAMETER );
    DBUS_ERROR_CASE( DBUS_ERROR_NO_MEMORY, STATUS_NO_MEMORY );
    DBUS_ERROR_CASE( DBUS_ERROR_NOT_SUPPORTED, STATUS_NOT_SUPPORTED );
    DBUS_ERROR_CASE( DBUS_ERROR_ACCESS_DENIED, STATUS_ACCESS_DENIED );
    return STATUS_INTERNAL_ERROR;
#undef DBUS_ERROR_CASE
}

static const char *bluez_next_dict_entry( DBusMessageIter *iter, DBusMessageIter *variant )
{
    DBusMessageIter sub;
    const char *name;

    if (p_dbus_message_iter_get_arg_type( iter ) != DBUS_TYPE_DICT_ENTRY)
        return NULL;

    p_dbus_message_iter_recurse( iter, &sub );
    p_dbus_message_iter_next( iter );
    p_dbus_message_iter_get_basic( &sub, &name );
    p_dbus_message_iter_next( &sub );
    p_dbus_message_iter_recurse( &sub, variant );
    return name;
}

/* Adds an entry to a dict iterator of type {sv} */
static BOOL bluez_variant_dict_add_entry( DBusMessageIter *dict, const char *key, int value_type,
                                          const char *value_type_str, const void *value )
{
    DBusMessageIter entry, variant;

    if (!p_dbus_message_iter_open_container( dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry )) return FALSE;
    if (!p_dbus_message_iter_append_basic( &entry, DBUS_TYPE_STRING, &key ))
    {
        p_dbus_message_iter_abandon_container( dict, &entry );
        return FALSE;
    }

    if (!p_dbus_message_iter_open_container( &entry, DBUS_TYPE_VARIANT, value_type_str, &variant ))
    {
        p_dbus_message_iter_abandon_container( dict, &entry );
        return FALSE;
    }
    if (!p_dbus_message_iter_append_basic( &variant, value_type, value ))
    {
        p_dbus_message_iter_abandon_container( &entry, &variant );
        p_dbus_message_iter_abandon_container( dict, &entry );
        return FALSE;
    }
    if (!p_dbus_message_iter_close_container( &entry, &variant ))
    {
        p_dbus_message_iter_abandon_container( dict, &entry );
        return FALSE;
    }

    return !!p_dbus_message_iter_close_container( dict, &entry );
}

static const char *dbgstr_dbus_message( DBusMessage *message )
{
    const char *interface;
    const char *member;
    const char *path;
    const char *sender;
    const char *signature;
    int type;

    interface = p_dbus_message_get_interface( message );
    member = p_dbus_message_get_member( message );
    path = p_dbus_message_get_path( message );
    sender = p_dbus_message_get_sender( message );
    type = p_dbus_message_get_type( message );
    signature = p_dbus_message_get_signature( message );

    switch (type)
    {
    case DBUS_MESSAGE_TYPE_METHOD_CALL:
        return wine_dbg_sprintf( "{method_call sender=%s interface=%s member=%s path=%s signature=%s}",
                                 debugstr_a( sender ), debugstr_a( interface ), debugstr_a( member ),
                                 debugstr_a( path ), debugstr_a( signature ) );
    case DBUS_MESSAGE_TYPE_SIGNAL:
        return wine_dbg_sprintf( "{signal sender=%s interface=%s member=%s path=%s signature=%s}",
                                 debugstr_a( sender ), debugstr_a( interface ), debugstr_a( member ),
                                 debugstr_a( path ), debugstr_a( signature ) );
    default:
        return wine_dbg_sprintf( "%p", message );
    }
}

static inline const char *dbgstr_dbus_connection( DBusConnection *connection )
{
    return wine_dbg_sprintf( "{%p connected=%d name=%s}", connection,
                             p_dbus_connection_get_is_connected( connection ),
                             debugstr_a( p_dbus_bus_get_unique_name( connection ) ) );
}

static const char *dbgstr_dbus_error( const DBusError *error )
{
    return wine_dbg_sprintf( "{%s: %s}", debugstr_a( error->name ), debugstr_a( error->message ) );
}

static const char *dbgstr_dbus_iter( DBusMessageIter *iter )
{
    int arg_type, next;

    if (!iter) return "(null)";

    arg_type = p_dbus_message_iter_get_arg_type( iter );
    if (arg_type == DBUS_TYPE_INVALID)
        return wine_dbg_sprintf( "{%p arg=DBUS_TYPE_INVALID}", iter );

    next = p_dbus_message_iter_has_next( iter );
    if (arg_type == DBUS_TYPE_ARRAY)
    {
        int elem_type, count;

        elem_type = p_dbus_message_iter_get_element_type( iter );
        count = p_dbus_message_iter_get_element_count( iter );
        return wine_dbg_sprintf( "{%p arg=%c elem=%c count=%d next=%d}", iter, arg_type, elem_type,
                                 count, next );
    }
    return wine_dbg_sprintf( "{%p arg=%c next=%d}", iter, arg_type, next );
}

static NTSTATUS bluez_get_objects_async( DBusConnection *connection, DBusPendingCall **call )
{
    DBusMessage *request;
    dbus_bool_t success;

    request = p_dbus_message_new_method_call( BLUEZ_DEST, "/", DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects" );
    if (!request)
        return STATUS_NO_MEMORY;

    success = p_dbus_connection_send_with_reply( connection, request, call, -1 );
    p_dbus_message_unref( request );
    return success ? (*call ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR) : STATUS_NO_MEMORY;
}

#define WINE_BLUEZ_AUTH_AGENT_PATH "/org/winehq/wine/winebth/AuthAgent"

static void bluez_register_auth_agent_callback( DBusPendingCall *call, void *data )
{
    DBusMessage *reply = p_dbus_pending_call_steal_reply( call );
    DBusError error;

    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
        ERR( "Failed to register authentication agent with BlueZ, expect issues with pairing: %s\n",
             dbgstr_dbus_error( &error ) );
    else
        TRACE( "Registered authentication agent %s with BlueZ\n", WINE_BLUEZ_AUTH_AGENT_PATH );
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
}

static NTSTATUS bluez_register_auth_agent_async( DBusConnection *connection )
{
    static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
    static const char *capability = "KeyboardDisplay";
    DBusPendingCall *call;
    DBusMessage *request;
    dbus_bool_t success;

    request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER, "RegisterAgent" );
    if (!request)
        return STATUS_NO_MEMORY;

    success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path, DBUS_TYPE_STRING,
                                          &capability, DBUS_TYPE_INVALID );
    if (!success)
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    success = p_dbus_connection_send_with_reply( connection, request, &call, -1 );
    p_dbus_message_unref( request );
    if (!success)
        return STATUS_NO_MEMORY;
    if (!call)
        return STATUS_INTERNAL_ERROR;

    p_dbus_pending_call_set_notify( call, bluez_register_auth_agent_callback, NULL, NULL );
    p_dbus_pending_call_unref( call );
    return STATUS_SUCCESS;
}

#define DBUS_OBJECTMANAGER_METHOD_GETMANAGEDOBJECTS_RETURN_SIGNATURE                               \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING                                                           \
    DBUS_TYPE_OBJECT_PATH_AS_STRING                                                                \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING                                                           \
    DBUS_TYPE_STRING_AS_STRING                                                                     \
    DBUS_TYPE_ARRAY_AS_STRING                                                                      \
    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING                                                           \
    DBUS_TYPE_STRING_AS_STRING                                                                     \
    DBUS_TYPE_VARIANT_AS_STRING                                                                    \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING                                                             \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING                                                             \
    DBUS_DICT_ENTRY_END_CHAR_AS_STRING


static void parse_mac_address( const char *addr_str, BYTE dest[6] )
{
    int addr[6], i;

    sscanf( addr_str, "%x:%x:%x:%x:%x:%x", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4],
            &addr[5] );
    for (i = 0 ; i < 6; i++)
        dest[i] = addr[i];
}

static BOOL parse_uuid( GUID *guid, const char *str )
{
    int ret;
    if (strlen( str ) != 36)
        return FALSE;
    ret = sscanf( str, "%08x-%hx-%hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", &guid->Data1, &guid->Data2, &guid->Data3,
                  &guid->Data4[0], &guid->Data4[1], &guid->Data4[2], &guid->Data4[3], &guid->Data4[4], &guid->Data4[5],
                  &guid->Data4[6], &guid->Data4[7] );
    if (ret != 11)
        return FALSE;
    return TRUE;
}

static void bluez_dbus_wait_for_reply_callback( DBusPendingCall *pending_call, void *wait )
{
    sem_post( wait );
}

static NTSTATUS bluez_dbus_pending_call_wait( DBusPendingCall *pending_call, DBusMessage **reply, DBusError *error )
{
    sem_t wait;

    sem_init( &wait, 0, 0 );
    if (!p_dbus_pending_call_set_notify( pending_call, bluez_dbus_wait_for_reply_callback, &wait, NULL ))
    {
        sem_destroy( &wait );
        p_dbus_pending_call_unref( pending_call );
        return STATUS_NO_MEMORY;
    }
    for (;;)
    {
        int ret = sem_wait( &wait );
        if (!ret)
        {
            *reply = p_dbus_pending_call_steal_reply( pending_call );
            if (p_dbus_set_error_from_message( error, *reply ))
            {
                p_dbus_message_unref( *reply );
                *reply = NULL;
            }
            p_dbus_pending_call_unref( pending_call );
            sem_destroy( &wait );
            return STATUS_SUCCESS;
        }
        if (errno == EINTR)
            continue;

        ERR( "Failed to wait for DBus method reply: %s\n", debugstr_a( strerror( errno ) ) );
        sem_destroy( &wait );
        p_dbus_pending_call_cancel( pending_call );
        p_dbus_pending_call_unref( pending_call );
        return STATUS_INTERNAL_ERROR;
    }
}

/* Like dbus_connection_send_with_reply_and_block, but it does not acquire a lock on the connection, instead relying on
 * the main loop in bluez_dbus_loop. This is faster than send_with_reply_and_block.
 * This takes ownership of the request, so there is no need to unref it. */
static NTSTATUS bluez_dbus_send_and_wait_for_reply( DBusConnection *connection, DBusMessage *request, DBusMessage **reply,
                                                    DBusError *error )
{
    DBusPendingCall *pending_call;
    dbus_bool_t success;

    success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout );
    p_dbus_message_unref( request );

    if (!success)
        return STATUS_NO_MEMORY;
    return bluez_dbus_pending_call_wait( pending_call, reply, error );
}

static NTSTATUS bluez_adapter_set_discovery_filter( void *connection, const char *adapter_path,
                                                    const char *transport_str )
{
    DBusMessage *request, *reply;
    DBusMessageIter iter, dict_iter;
    DBusError error;
    NTSTATUS status;

    TRACE( "(%p, %s, %s)\n", connection, debugstr_a( adapter_path ), debugstr_a( transport_str ) );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
                                              "SetDiscoveryFilter" );
    if (!request) return STATUS_NO_MEMORY;

    p_dbus_message_iter_init_append( request, &iter );
    if (!p_dbus_message_iter_open_container( &iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!bluez_variant_dict_add_entry( &dict_iter, "Transport", DBUS_TYPE_STRING,
                                       DBUS_TYPE_STRING_AS_STRING, &transport_str ))
    {
        p_dbus_message_iter_abandon_container( &iter, &dict_iter );
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!p_dbus_message_iter_close_container( &iter, &dict_iter ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    p_dbus_error_init ( &error );

    TRACE( "Setting discovery filter on %s to use transport %s\n", debugstr_a( adapter_path ),
           debugstr_a( transport_str ) );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        WARN( "Failed to set discovery filter: %s\n", dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_message_unref( reply );
    p_dbus_error_free( &error );

    return STATUS_SUCCESS;

}

NTSTATUS bluez_adapter_start_discovery( void *connection, const char *adapter_path )
{
    DBusMessage *request, *reply;
    DBusError error;
    NTSTATUS status;

    TRACE( "(%p, %s)\n", connection, debugstr_a( adapter_path ) );

    status = bluez_adapter_set_discovery_filter( connection, adapter_path, "bredr" );
    if (status != STATUS_SUCCESS) return status;

    request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
                                              "StartDiscovery" );
    if (!request) return STATUS_NO_MEMORY;

    TRACE( "Starting discovery on %s\n", debugstr_a( adapter_path ) );
    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        ERR( "Failed to start discovery on adapter %s: %s\n", debugstr_a( adapter_path ), dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
    return STATUS_SUCCESS;
}

NTSTATUS bluez_adapter_stop_discovery( void *connection, const char *adapter_path )
{
    DBusMessage *request, *reply;
    DBusError error;
    NTSTATUS status;

    TRACE( "(%p, %s)\n", connection, debugstr_a( adapter_path ) );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
                                              "StopDiscovery" );
    if (!request) return STATUS_NO_MEMORY;

    TRACE( "Stopping discovery on %s\n", debugstr_a( adapter_path ) );
    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        ERR( "Failed to stop discovery on adapter %s: %s\n", debugstr_a( adapter_path ),
             dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
    return STATUS_SUCCESS;
}

NTSTATUS bluez_adapter_remove_device( void *connection, const char *adapter_path, const char *device_path )
{
    DBusMessage *request, *reply = NULL;
    DBusError error;
    NTSTATUS status;

    TRACE( "(%p, %s, %s)\n", connection, debugstr_a( adapter_path ), debugstr_a( device_path ) );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER, "RemoveDevice" );
    if (!request)
        return STATUS_NO_MEMORY;

    if (!p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_INVALID ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        ERR( "Failed to remove device %s on adapter %s: %s\n", debugstr_a( device_path ),
             debugstr_a( adapter_path ), dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_message_unref( reply );
    p_dbus_error_free( &error );

    return STATUS_SUCCESS;
}

NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params )
{
    DBusMessage *request, *reply;
    DBusMessageIter iter, sub_iter;
    DBusError error;
    DBusBasicValue val;
    int val_type;
    static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER;
    const char *prop_name;
    NTSTATUS status;

    TRACE( "(%p, %p)\n", connection, params );

    switch (params->prop_flag)
    {
    case LOCAL_RADIO_CONNECTABLE:
        prop_name = "Connectable";
        val.bool_val = params->prop->boolean;
        val_type = DBUS_TYPE_BOOLEAN;
        break;
    case LOCAL_RADIO_DISCOVERABLE:
        prop_name = "Discoverable";
        val.bool_val = params->prop->boolean;
        val_type = DBUS_TYPE_BOOLEAN;
        break;
    default:
        return STATUS_INVALID_PARAMETER;
    }

    TRACE( "Setting property %s for adapter %s\n", debugstr_a( prop_name ), debugstr_a( params->adapter->str ) );
    request = p_dbus_message_new_method_call( BLUEZ_DEST, params->adapter->str,
                                              DBUS_INTERFACE_PROPERTIES, "Set" );
    if (!request) return STATUS_NO_MEMORY;

    p_dbus_message_iter_init_append( request, &iter );
    if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &adapter_iface ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &prop_name ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!p_dbus_message_iter_open_container( &iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &sub_iter ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!p_dbus_message_iter_append_basic( &sub_iter, val_type, &val ))
    {
        p_dbus_message_iter_abandon_container( &iter, &sub_iter );
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    if (!p_dbus_message_iter_close_container( &iter, &sub_iter ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        ERR( "Failed to set property %s for adapter %s: %s\n", debugstr_a( prop_name ),
             debugstr_a( params->adapter->str ), dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );

    return STATUS_SUCCESS;
}

static void bluez_radio_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant,
                                              struct winebluetooth_radio_properties *props,
                                              winebluetooth_radio_props_mask_t *props_mask,
                                              winebluetooth_radio_props_mask_t wanted_props_mask )
{
    TRACE_(dbus)( "(%s, %s, %p, %p, %#x)\n", debugstr_a( prop_name ), dbgstr_dbus_iter( variant ), props, props_mask,
                  wanted_props_mask );

    if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_NAME &&
        !strcmp( prop_name, "Name" ) &&
        p_dbus_message_iter_get_arg_type( variant ))
    {
        const char *name_str;
        SIZE_T len;
        p_dbus_message_iter_get_basic( variant, &name_str );
        len = strlen( name_str );
        memcpy( props->name, name_str, min( len + 1, ARRAYSIZE( props->name )));
        props->name[ARRAYSIZE( props->name ) - 1] = '\0';
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_NAME;
    }
    if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS &&
        !strcmp( prop_name, "Address" ) &&
        p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
    {
        const char *addr_str;
        p_dbus_message_iter_get_basic( variant, &addr_str );
        parse_mac_address( addr_str, props->address.rgBytes );
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CLASS &&
             !strcmp( prop_name, "Class" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT32)
    {
        dbus_uint32_t class;
        p_dbus_message_iter_get_basic( variant, &class );
        props->class = class;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CLASS;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER &&
             !strcmp( prop_name, "Manufacturer" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT16)
    {
        dbus_uint16_t manufacturer;
        p_dbus_message_iter_get_basic( variant, &manufacturer );
        props->manufacturer = manufacturer;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE &&
             !strcmp( prop_name, "Connectable" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t connectable;
        p_dbus_message_iter_get_basic( variant, &connectable );
        props->connectable = connectable != 0;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE &&
             !strcmp( prop_name, "Discoverable" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t discoverable;
        p_dbus_message_iter_get_basic( variant, &discoverable );
        props->discoverable = discoverable != 0;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING &&
             !strcmp( prop_name, "Discovering") &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t discovering;
        p_dbus_message_iter_get_basic( variant, &discovering );
        props->discovering = discovering != 0;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE &&
             !strcmp( prop_name, "Pairable" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t pairable;
        p_dbus_message_iter_get_basic( variant, &pairable );
        props->pairable = pairable != 0;
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_VERSION &&
             !strcmp( prop_name, "Version" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BYTE)
    {
        p_dbus_message_iter_get_basic( variant, &props->version );
        *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_VERSION;
    }
}

static void bluez_device_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant,
                                               struct winebluetooth_device_properties *props,
                                               winebluetooth_device_props_mask_t *props_mask,
                                               winebluetooth_device_props_mask_t wanted_props_mask )
{
    TRACE_( dbus )( "(%s, %s, %p, %p, %#x)\n", debugstr_a( prop_name ), dbgstr_dbus_iter( variant ), props, props_mask,
                    wanted_props_mask );


    if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_NAME &&
        !strcmp( prop_name, "Name" ) &&
        p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
    {
        const char *name_str;
        SIZE_T len;

        p_dbus_message_iter_get_basic( variant, &name_str );
        len = strlen( name_str );
        memcpy( props->name, name_str, min( len + 1, ARRAYSIZE( props->name ) ) );
        props->name[ARRAYSIZE( props->name ) - 1] = '\0';
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_NAME;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS &&
        !strcmp( prop_name, "Address" ) &&
        p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
    {
        const char *addr_str;

        p_dbus_message_iter_get_basic( variant, &addr_str );
        parse_mac_address( addr_str, props->address.rgBytes );
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED &&
             !strcmp( prop_name, "Connected" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t connected;

        p_dbus_message_iter_get_basic( variant, &connected );
        props->connected = !!connected;
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED &&
             !strcmp( prop_name, "Paired" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t paired;

        p_dbus_message_iter_get_basic( variant, &paired );
        props->paired = !!paired;
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING &&
             !strcmp( prop_name, "LegacyPairing" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t legacy;

        p_dbus_message_iter_get_basic( variant, &legacy );
        props->legacy_pairing = !!legacy;
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED &&
             !strcmp( prop_name, "Trusted" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t trusted;

        p_dbus_message_iter_get_basic( variant, &trusted );
        props->trusted = !!trusted;
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED;
    }
    else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CLASS &&
             !strcmp( prop_name, "Class" ) &&
             p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT32)
    {
        p_dbus_message_iter_get_basic( variant, &props->class );
        *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_CLASS;
    }
}

static void bluez_gatt_service_props_from_dict_entry( const char *prop_name, DBusMessageIter *variant,
                                                      struct winebluetooth_watcher_event_gatt_service_added *service )
{
    TRACE_( dbus )( "(%s, %s, %p)\n", debugstr_a( prop_name ), dbgstr_dbus_iter( variant ), service );

    if (!strcmp( prop_name, "Device" )
        && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_OBJECT_PATH )
    {
        const char *device_path;
        struct unix_name *device_name;

        p_dbus_message_iter_get_basic( variant, &device_path );
        device_name = unix_name_get_or_create( device_path );
        service->device.handle = (UINT_PTR)device_name;
    }
    else if (!strcmp( prop_name, "Handle" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT16)
        p_dbus_message_iter_get_basic( variant, &service->attr_handle );
    else if (!strcmp( prop_name, "Primary" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
    {
        dbus_bool_t primary;
        p_dbus_message_iter_get_basic( variant, &primary );
        service->is_primary = !!primary;
    }
    else if (!strcmp( prop_name, "UUID" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
    {
        const char *uuid_str;
        p_dbus_message_iter_get_basic( variant, &uuid_str );
        if (!parse_uuid( &service->uuid, uuid_str ))
            ERR( "Failed to parse UUID %s\n", debugstr_a( uuid_str ) );
    }
}

struct named_flag
{
    const char *name;
    BOOLEAN *flag;
};

static int named_flag_cmp( const void *flag1, const void *flag2 )
{
    const struct named_flag *f1 = flag1, *f2 = flag2;
    return strcmp( f1->name, f2->name );
}

static BOOL bluez_gatt_characteristic_value_new_from_iter( DBusMessage *message, DBusMessageIter *array_iter,
                                                           struct winebluetooth_gatt_characteristic_value *value );
static BOOL
bluez_gatt_characteristic_props_from_dict_entry( DBusMessage *msg, const char *prop_name, DBusMessageIter *variant,
                                                 struct winebluetooth_watcher_event_gatt_characteristic_added *chrc )
{
    TRACE_( dbus )( "(%s, %s, %p)\n", debugstr_a( prop_name ), dbgstr_dbus_iter( variant ), chrc );

    if (!strcmp( prop_name, "Flags" )
        && p_dbus_message_iter_get_arg_type ( variant ) == DBUS_TYPE_ARRAY
        && p_dbus_message_iter_get_element_type ( variant ) == DBUS_TYPE_STRING)
    {
        /* These should always be sorted by the flag name. */
        const struct named_flag flags[] = {
            { "authenticate-signed-writes", &chrc->props.IsSignedWritable },
            { "broadcast", &chrc->props.IsBroadcastable },
            { "extended-properties", &chrc->props.HasExtendedProperties },
            { "indicate", &chrc->props.IsIndicatable },
            { "notify", &chrc->props.IsNotifiable },
            { "read", &chrc->props.IsReadable },
            { "write", &chrc->props.IsWritable },
            { "write-without-response", &chrc->props.IsWritableWithoutResponse },
        };
        DBusMessageIter flags_iter;

        p_dbus_message_iter_recurse( variant, &flags_iter );
        while (p_dbus_message_iter_get_arg_type( &flags_iter ) != DBUS_TYPE_INVALID)
        {
            const struct named_flag *flag;
            struct named_flag name;

            p_dbus_message_iter_get_basic( &flags_iter, &name.name );
            if ((flag = bsearch( &name, flags, ARRAY_SIZE( flags ), sizeof( *flags ), named_flag_cmp )))
                *flag->flag = TRUE;
            else
                FIXME( "Unknown characteristic flag: %s\n", debugstr_a( name.name ) );
            p_dbus_message_iter_next( &flags_iter );
        }
    }
    else if (!strcmp( prop_name, "Service" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_OBJECT_PATH)
    {
        const char *path;

        p_dbus_message_iter_get_basic( variant, &path );
        return !!(chrc->service.handle = (UINT_PTR)unix_name_get_or_create( path ));
    }
    else if (!strcmp( prop_name, "UUID" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
    {
        const char *uuid_str;
        GUID uuid;

        p_dbus_message_iter_get_basic( variant, &uuid_str );
        if (parse_uuid( &uuid, uuid_str ))
            uuid_to_le( &uuid, &chrc->props.CharacteristicUuid );
        else
            ERR( "Failed to parse UUID %s\n", debugstr_a( uuid_str ) );
    }
    else if (!strcmp( prop_name, "Handle" )
             && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT16)
        p_dbus_message_iter_get_basic( variant, &chrc->props.AttributeHandle );
    else if (!strcmp( prop_name, "Value" ) && p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_ARRAY
             && p_dbus_message_iter_get_element_type( variant ) == DBUS_TYPE_BYTE)
        return bluez_gatt_characteristic_value_new_from_iter( msg, variant, &chrc->value );
    return TRUE;
}

static NTSTATUS bluez_adapter_get_props_async( void *connection, const char *radio_object_path,
                                               DBusPendingCall **call )
{
    DBusMessage *request;
    static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER;
    dbus_bool_t success;

    request = p_dbus_message_new_method_call( BLUEZ_DEST, radio_object_path,
                                              DBUS_INTERFACE_PROPERTIES, "GetAll" );
    if (!request) return STATUS_NO_MEMORY;

    p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID );

    success = p_dbus_connection_send_with_reply( connection, request, call, bluez_timeout );
    p_dbus_message_unref( request );
    if (!success)
        return STATUS_NO_MEMORY;
    if (!*call)
        return STATUS_INTERNAL_ERROR;

    return STATUS_SUCCESS;
}

static NTSTATUS bluez_device_get_props_by_path_async( DBusConnection *connection,
                                                      const char *device_path,
                                                      DBusPendingCall **pending_call )
{
    DBusMessage *request;
    static const char *adapter_iface = BLUEZ_INTERFACE_DEVICE;
    dbus_bool_t ret;

    request = p_dbus_message_new_method_call( BLUEZ_DEST, device_path,
                                              DBUS_INTERFACE_PROPERTIES, "GetAll" );
    if (!request) return STATUS_NO_MEMORY;

    p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID );
    ret = p_dbus_connection_send_with_reply( connection, request, pending_call, bluez_timeout );
    p_dbus_message_unref( request );
    if (!ret)
        return STATUS_NO_MEMORY;
    if (!*pending_call)
        return STATUS_INTERNAL_ERROR;

    return STATUS_SUCCESS;
}

struct bluez_gatt_characteristic_value
{
    DBusMessage *message;
    const BYTE *buf; /* Points into message */
};

/* array_iter must point to the start of the byte array, message should be the DBus message that the iterator belongs
 * to. */
static BOOL bluez_gatt_characteristic_value_new_from_iter( DBusMessage *message, DBusMessageIter *array_iter,
                                                           struct winebluetooth_gatt_characteristic_value *value )
{
    struct bluez_gatt_characteristic_value *val;
    DBusMessageIter bytes_iter;
    int size;

    TRACE_( dbus )( "(%s, %s, %p)\n", dbgstr_dbus_message( message ), dbgstr_dbus_iter( array_iter ), value );

    if (!(val = calloc( 1, sizeof( *val ) ))) return FALSE;
    val->message = p_dbus_message_ref( message );
    p_dbus_message_iter_recurse( array_iter, &bytes_iter );
    p_dbus_message_iter_get_fixed_array( &bytes_iter, &val->buf, &size );

    value->size = size;
    value->handle = (UINT_PTR)val;

    return TRUE;
}

void bluez_gatt_characteristic_value_free( void *val )
{
    struct bluez_gatt_characteristic_value *value = val;

    if (value)
    {
        p_dbus_message_unref( value->message );
        free( value );
    }
}

void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *dest )
{
    struct bluez_gatt_characteristic_value *val = (struct bluez_gatt_characteristic_value *)value->handle;

    memcpy( dest, val->buf, value->size );
    bluez_gatt_characteristic_value_free( val );
}

static NTSTATUS bluez_gatt_error_to_status( const DBusError *error )
{
    if (p_dbus_error_has_name( error, "org.bluez.Error.NotPermitted" ))
    {
        if (!strcmp( error->message, "Read not permitted" ))
            return STATUS_BTH_ATT_READ_NOT_PERMITTED;
        else if (!strcmp( error->message, "Write not permitted" ))
            return STATUS_BTH_ATT_WRITE_NOT_PERMITTED;
        else
            return STATUS_BTH_ATT_INSUFFICIENT_AUTHENTICATION;
    }
    else if (p_dbus_error_has_name( error, "org.bluez.Error.NotAuthorized" ))
        return STATUS_BTH_ATT_INSUFFICIENT_AUTHORIZATION;
    else if (p_dbus_error_has_name( error, "org.bluez.Error.NotSupported" ))
        return STATUS_BTH_ATT_REQUEST_NOT_SUPPORTED;
    else if (p_dbus_error_has_name( error, "org.bluez.Error.InvalidArguments" ))
    {
        if (!strcmp( error->message, "Invalid offset" ))
            return STATUS_BTH_ATT_INVALID_OFFSET;
        else if (!strcmp( error->message, "Invalid Length" ))
            return STATUS_BTH_ATT_INVALID_ATTRIBUTE_VALUE_LENGTH;
        return STATUS_BTH_ATT_UNKNOWN_ERROR;
    }
    else if (p_dbus_error_has_name( error, "org.bluez.Error.Failed" ) && !strcmp( error->message, "Not connected" ))
        return STATUS_DEVICE_NOT_CONNECTED;
    return bluez_dbus_error_to_ntstatus( error );
}

struct bluez_async_req_data
{
    IRP *irp;
    struct bluez_watcher_ctx *watcher_ctx;
};

static void bluez_gatt_characteristic_read_callback( DBusPendingCall *pending, void *param );

NTSTATUS bluez_gatt_characteristic_read( void *connection, void *watcher_ctx, struct unix_name *characteristic,
                                         IRP *irp )
{
    DBusMessageIter args_iter, dict_iter = DBUS_MESSAGE_ITER_INIT_CLOSED;
    struct bluez_async_req_data *data;
    DBusPendingCall *pending_call = NULL;
    DBusMessage *request;
    NTSTATUS status;
    dbus_bool_t success;

    TRACE( "(%s, %p)\n", debugstr_a( characteristic->str ), irp );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, characteristic->str, BLUEZ_INTERFACE_GATT_CHARACTERISTICS,
                                              "ReadValue" );
    if (!request)
        return STATUS_NO_MEMORY;

    p_dbus_message_iter_init_append( request, &args_iter );
    if (!p_dbus_message_iter_open_container( &args_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter ))
    {
        status = STATUS_NO_MEMORY;
        goto failed;
    }
    if (!p_dbus_message_iter_close_container( &args_iter, &dict_iter ))
    {
        status = STATUS_NO_MEMORY;
        goto failed;
    }
    success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout );
    if (!success)
    {
        status = STATUS_NO_MEMORY;
        goto failed;
    }
    if (!pending_call)
    {
        status = STATUS_INTERNAL_ERROR;
        goto failed;
    }
    if (!(data = malloc( sizeof( *data ) )))
    {
        status = STATUS_NO_MEMORY;
        goto failed;
    }
    data->irp = irp;
    data->watcher_ctx = watcher_ctx;
    if (!p_dbus_pending_call_set_notify( pending_call, bluez_gatt_characteristic_read_callback, data, free ))
    {
        free( data );
        p_dbus_pending_call_cancel( pending_call );
        p_dbus_pending_call_unref( pending_call );
        status = STATUS_NO_MEMORY;
        goto failed;
    }
    p_dbus_pending_call_unref( pending_call );
    p_dbus_message_unref( request );
    return STATUS_PENDING;
failed:
    p_dbus_message_iter_abandon_container_if_open( &args_iter, &dict_iter );
    p_dbus_message_unref( request );
    return status;
}

struct bluez_watcher_ctx
{
    DBusConnection *connection;
    char *bluez_dbus_unique_name;

    /* struct bluez_watcher_event */
    struct list event_list;
};

static BOOL bluez_event_list_queue_new_event( struct list *event_list,
                                              enum winebluetooth_watcher_event_type event_type,
                                              union winebluetooth_watcher_event_data event );

static void bluez_gatt_characteristic_read_callback( DBusPendingCall *pending, void *param )
{
    struct winebluetooth_watcher_event_gatt_characteristic_value_read read = {0};
    union winebluetooth_watcher_event_data event;
    struct bluez_async_req_data *data = param;
    DBusMessage *reply;
    DBusError error;

    read.irp = data->irp;
    reply = p_dbus_pending_call_steal_reply( pending );
    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
        read.result = bluez_gatt_error_to_status( &error );
    else
    {
        DBusMessageIter iter;

        p_dbus_message_iter_init( reply, &iter );
        if (!bluez_gatt_characteristic_value_new_from_iter( reply, &iter, &read.value ))
            read.result = STATUS_NO_MEMORY;
    }

    event.gatt_characteristic_value_read = read;
    if (!bluez_event_list_queue_new_event( &data->watcher_ctx->event_list,
                                           BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_READ, event ))
        bluez_gatt_characteristic_value_free( (struct bluez_gatt_characteristic_value *)read.value.handle );
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
}

/* The status of a pairing session initiated by BlueZ. */
enum bluez_pairing_session_status
{
    /* No pairing session is active. */
    BLUEZ_PAIRING_SESSION_NONE,
    /* We have received an authentication request from BlueZ, which should be queued to the PE driver. */
    BLUEZ_PAIRING_SESSION_INCOMING,
    /* The authentication request has been relayed to the driver, and we're waiting for a response from userspace. */
    BLUEZ_PAIRING_SESSION_PENDING_REPLY,
    /* The authentication request has been cancelled by BlueZ. */
    BLUEZ_PAIRING_SESSION_CANCELLED,
};

struct bluez_auth_agent_ctx
{
    LONG refcnt;

    pthread_mutex_t lock; /* Guards all fields below. */
    enum bluez_pairing_session_status status;
    struct unix_name *device;
    BLUETOOTH_AUTHENTICATION_METHOD method;
    UINT32 passkey;
    /* The auth request we received from BlueZ. */
    DBusMessage *auth_request;
    DBusPreallocatedSend *preallocate_send;
    DBusConnection *connection;
};

void *bluez_dbus_init( void )
{
    DBusError error;
    DBusConnection *connection;

    if (!load_dbus_functions()) return NULL;

    p_dbus_threads_init_default();
    p_dbus_error_init ( &error );

    connection = p_dbus_bus_get_private ( DBUS_BUS_SYSTEM, &error );
    if (!connection)
    {
        WARN( "Failed to get system dbus connection: %s\n", dbgstr_dbus_error( &error ) );
        p_dbus_error_free( &error );
        return NULL;
    }

    return connection;
}

/* Return the unique connection name for org.bluez. We use this to ensure that:
 * - BlueZ is actually available on this sytem before we make any calls to it.
 * - Only BlueZ can make method calls to us.
 */
static NTSTATUS bluez_dbus_get_unique_name_async( DBusConnection *connection, DBusPendingCall **call )
{
    const char *bluez_name = BLUEZ_DEST;
    DBusMessage *request;
    dbus_bool_t success;

    request = p_dbus_message_new_method_call( "org.freedesktop.DBus", "/org/freedesktop/DBus", DBUS_INTERFACE_DBUS,
                                              "GetNameOwner" );
    if (!request) return STATUS_NO_MEMORY;

    if (!p_dbus_message_append_args( request, DBUS_TYPE_STRING, &bluez_name, DBUS_TYPE_INVALID ))
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    success = p_dbus_connection_send_with_reply( connection, request, call, bluez_timeout);
    p_dbus_message_unref( request );
    return success ? (*call ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR) : STATUS_NO_MEMORY;
}

static NTSTATUS bluez_enumerate_objects( DBusMessage *reply, struct list *event_list );

static void bluez_get_managed_objects_callback( DBusPendingCall *call, void *data )
{
    DBusMessage *reply = p_dbus_pending_call_steal_reply( call );
    struct bluez_watcher_ctx *ctx = data;
    DBusError error;
    NTSTATUS status;

    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
        ERR( "Error getting object list from BlueZ: %s\n", dbgstr_dbus_error( &error ) );
    else if ((status = bluez_enumerate_objects( reply, &ctx->event_list )))
        ERR( "Error enumerating BlueZ objects: %x\n", status );
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
}

/* Called once the BlueZ service has been activated by the system. */
static void bluez_on_service_available( struct bluez_watcher_ctx *ctx )
{
    DBusPendingCall *call;
    NTSTATUS status;

    TRACE_(dbus)( "org.bluez available on %s, initializing bluetooth.\n", debugstr_a( ctx->bluez_dbus_unique_name ) );

    if ((status = bluez_register_auth_agent_async( ctx->connection )))
        ERR_(dbus)( "Failed to create async RegisterAgent call: %#x\n", status );
    if ((status = bluez_get_objects_async( ctx->connection, &call )))
        ERR_(dbus)( "Failed to create async GetManagedObjects call: %#x\n", status );
    else
    {
        if (!p_dbus_pending_call_set_notify( call, bluez_get_managed_objects_callback, ctx, NULL ))
            p_dbus_pending_call_cancel( call );
        p_dbus_pending_call_unref( call );
    }
}

static void bluez_dbus_get_name_owner_callback( DBusPendingCall *call, void *data )
{
    DBusMessage *reply = p_dbus_pending_call_steal_reply( call );
    struct bluez_watcher_ctx *ctx = data;
    const char *name;
    DBusError error;

    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
        WARN_(dbus)( "Failed to get unique name for org.bluez: %s\n", dbgstr_dbus_error( &error ) );
    else if (!p_dbus_message_get_args( reply, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID ))
        ERR_(dbus)( "Failed to read string from GetNameOwner reply: %s\n", dbgstr_dbus_error(  &error ) );
    /* If bluez_signal_handler has already set a unique_name, nothing needs to be done. This can happen if BlueZ
     * (re)starts right after we set up the signal handler in bluez_watcher_init. */
    else if (!ctx->bluez_dbus_unique_name && ((ctx->bluez_dbus_unique_name = strdup( name ))))
        bluez_on_service_available( ctx );
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );
}

void bluez_dbus_close( void *connection )
{
    TRACE_(dbus)( "(%s)\n", dbgstr_dbus_connection( connection ) );

    p_dbus_connection_flush( connection );
    p_dbus_connection_close( connection );
}

void bluez_dbus_free( void *connection )
{
    TRACE_(dbus)( "(%s)\n", dbgstr_dbus_connection( connection ) );

    p_dbus_connection_unref( connection );
}

static struct bluez_auth_agent_ctx *bluez_auth_agent_ctx_incref( struct bluez_auth_agent_ctx *ctx )
{
    InterlockedIncrement( &ctx->refcnt );
    return ctx;
}

static void bluez_auth_agent_ctx_decref( struct bluez_auth_agent_ctx *ctx )
{
    if (InterlockedDecrement( &ctx->refcnt ))
        return;

    if (ctx->status != BLUEZ_PAIRING_SESSION_NONE)
    {
        unix_name_free( ctx->device );
        if (ctx->status != BLUEZ_PAIRING_SESSION_CANCELLED)
        {
            p_dbus_connection_free_preallocated_send( ctx->connection, ctx->preallocate_send );
            p_dbus_message_unref( ctx->auth_request );
            p_dbus_connection_unref( ctx->connection );
        }
    }
    pthread_mutex_destroy( &ctx->lock );
    free( ctx );
}

static DBusHandlerResult bluez_auth_agent_vtable_message_handler( DBusConnection *connection, DBusMessage *message,
                                                                  void *data )
{
    struct bluez_auth_agent_ctx *ctx = data;
    DBusPreallocatedSend *prealloc_send;
    DBusMessage *reply;

    TRACE_(dbus)( "(%s, %s, %p)\n", dbgstr_dbus_connection( connection ), dbgstr_dbus_message( message ),
                  data );

    prealloc_send = p_dbus_connection_preallocate_send( connection );
    if (!prealloc_send)
        return DBUS_HANDLER_RESULT_NEED_MEMORY;

    if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "RequestConfirmation" ))
    {
        struct unix_name *device;
        const char *device_path;
        dbus_uint32_t passkey;
        DBusError error;

        p_dbus_error_init( &error );
        if (!p_dbus_message_get_args( message, &error, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_UINT32, &passkey,
                                      DBUS_TYPE_INVALID ))
        {
            ERR( "Failed to get message args: %s\n", dbgstr_dbus_error( &error ) );
            p_dbus_error_free( &error );
            p_dbus_connection_free_preallocated_send( connection, prealloc_send );
            return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
        }
        p_dbus_error_free( &error );
        device = unix_name_get_or_create( device_path );
        if (!device)
        {
            ERR( "Failed to allocate memory for device path %s\n", device_path );
            p_dbus_connection_free_preallocated_send( connection, prealloc_send );
            return DBUS_HANDLER_RESULT_NEED_MEMORY;
        }

        TRACE( "Received a numeric confirmation request for device %s\n", debugstr_a( device_path ) );
        pthread_mutex_lock( &ctx->lock );
        ctx->status = BLUEZ_PAIRING_SESSION_INCOMING;
        ctx->device = device;
        ctx->method = BLUETOOTH_AUTHENTICATION_METHOD_NUMERIC_COMPARISON;
        ctx->passkey = passkey;
        ctx->auth_request = p_dbus_message_ref( message );
        ctx->preallocate_send = prealloc_send;
        ctx->connection = p_dbus_connection_ref( connection );
        pthread_mutex_unlock( &ctx->lock );

        return DBUS_HANDLER_RESULT_HANDLED;
    }
    if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "AuthorizeService" ))
        /* Services are handled by BlueZ and other services, so we can just allow them here, as
         * this method only gets called for authenticated devices. */
        reply = p_dbus_message_new_method_return( message );
    else if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "Cancel" ))
    {
        pthread_mutex_lock( &ctx->lock );
        if (ctx->status == BLUEZ_PAIRING_SESSION_INCOMING || ctx->status == BLUEZ_PAIRING_SESSION_PENDING_REPLY)
        {
            TRACE( "Cancelling authentication request from device %s\n", debugstr_a( ctx->device->str ) );
            ctx->status = BLUEZ_PAIRING_SESSION_CANCELLED;
            unix_name_free( ctx->device );
            p_dbus_message_unref( ctx->auth_request );
            p_dbus_connection_free_preallocated_send( ctx->connection, ctx->preallocate_send );
            p_dbus_connection_unref( ctx->connection );
        }
        pthread_mutex_unlock( &ctx->lock );
        reply = p_dbus_message_new_method_return( message );
    }
    else
    {
        FIXME( "Unsupported method call: %s\n", debugstr_a( p_dbus_message_get_member( message ) ) );
        reply = p_dbus_message_new_error( message, "org.bluez.Error.Rejected", "" );
    }

    if (!reply)
    {
        p_dbus_connection_free_preallocated_send( connection, prealloc_send );
        return DBUS_HANDLER_RESULT_NEED_MEMORY;
    }
    p_dbus_connection_send_preallocated( connection, prealloc_send, reply, NULL );
    p_dbus_message_unref( reply );
    return DBUS_HANDLER_RESULT_HANDLED;
}

const static struct DBusObjectPathVTable bluez_auth_agent_object_vtable = {
    .message_function = bluez_auth_agent_vtable_message_handler };

NTSTATUS bluez_auth_agent_start( void *connection, void **auth_agent_ctx )
{
    struct bluez_auth_agent_ctx *ctx;
    NTSTATUS status = STATUS_SUCCESS;
    dbus_bool_t success;
    DBusError error;

    TRACE( "(%s, %p)\n", dbgstr_dbus_connection( connection ), auth_agent_ctx );

    ctx = malloc( sizeof( *ctx ) );
    if (!ctx)
        return STATUS_NO_MEMORY;

    pthread_mutex_init( &ctx->lock, NULL );
    ctx->status = BLUEZ_PAIRING_SESSION_NONE;
    ctx->refcnt = 1;

    p_dbus_error_init( &error );
    TRACE_(dbus)( "Registering an org.bluez.Agent1 object at %s\n", WINE_BLUEZ_AUTH_AGENT_PATH );
    /* No need to increase the reference count on the auth agent context here, as the vtable handlers only get called
     * inside bluez_dbus_loop, where the reference count gets incremented at start. */
    success = p_dbus_connection_try_register_object_path( connection, WINE_BLUEZ_AUTH_AGENT_PATH,
                                                          &bluez_auth_agent_object_vtable, ctx, &error );
    if (!success)
    {
        ERR_(dbus)( "Failed to register object: %s\n", dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        bluez_auth_agent_ctx_decref( ctx );
    }
    else
        *auth_agent_ctx = ctx;
    p_dbus_error_free( &error );
    return status;
}

NTSTATUS bluez_auth_agent_stop( void *connection, void *auth_agent_ctx )
{
    static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
    struct bluez_auth_agent_ctx *ctx = auth_agent_ctx;
    DBusMessage *request;
    dbus_bool_t success;

    TRACE( "(%s, %p)\n", dbgstr_dbus_connection( connection ), auth_agent_ctx );

    bluez_auth_agent_ctx_decref( ctx );
    request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER,
                                              "UnregisterAgent" );
    if (!request)
        return STATUS_NO_MEMORY;

    success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path,
                                          DBUS_TYPE_INVALID );
    if (!success)
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    success = p_dbus_connection_send( connection, request, NULL );
    p_dbus_message_unref( request );
    if (!success)
        return STATUS_NO_MEMORY;

    success = p_dbus_connection_unregister_object_path( connection, WINE_BLUEZ_AUTH_AGENT_PATH );
    return success ? STATUS_SUCCESS : STATUS_NO_MEMORY;
}

NTSTATUS bluez_auth_agent_request_default( void *connection )
{
    static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
    DBusMessage *request, *reply;
    dbus_bool_t success;
    NTSTATUS status;
    DBusError error;

    TRACE( "(%p)\n", connection );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER,
                                              "RequestDefaultAgent" );
    if (!request)
        return STATUS_NO_MEMORY;

    success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path,
                                          DBUS_TYPE_INVALID );
    if (!success)
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }

    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_message_unref( request );
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        status = bluez_dbus_error_to_ntstatus( &error );
        ERR( "RequestDefaultAgent failed: %s\n", dbgstr_dbus_error( &error ) );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_error_free( &error );
    p_dbus_message_unref( reply );

    return STATUS_SUCCESS;
}

NTSTATUS bluez_auth_agent_send_response( void *auth_agent, struct unix_name *device,
                                         BLUETOOTH_AUTHENTICATION_METHOD method, UINT32 numeric_or_passkey,
                                         BOOL negative, BOOL *authenticated )
{
    struct bluez_auth_agent_ctx *ctx = auth_agent;
    NTSTATUS ret;

    TRACE( "auth_agent=%p device=%s negative=%d\n", auth_agent, debugstr_a( device->str ), negative );

    pthread_mutex_lock( &ctx->lock );
    switch (ctx->status)
    {
    case BLUEZ_PAIRING_SESSION_PENDING_REPLY:
    {
        DBusMessage *reply;
        if (device != ctx->device)
        {
            ret = STATUS_DEVICE_NOT_CONNECTED;
            goto done;
        }
        if (numeric_or_passkey != ctx->passkey || method != ctx->method)
            negative = TRUE;

        reply = negative ? p_dbus_message_new_error( ctx->auth_request, "org.bluez.Rejected", "" )
                         : p_dbus_message_new_method_return( ctx->auth_request );
        if (!reply)
        {
            ret = STATUS_NO_MEMORY;
            goto done;
        }
        p_dbus_connection_send_preallocated( ctx->connection, ctx->preallocate_send, reply, NULL );

        unix_name_free( ctx->device );
        p_dbus_message_unref( ctx->auth_request );
        p_dbus_connection_unref( ctx->connection );
        ctx->status = BLUEZ_PAIRING_SESSION_NONE;
        *authenticated = !negative;
        ret = STATUS_SUCCESS;
        break;
    }
    case BLUEZ_PAIRING_SESSION_CANCELLED:
        ret = STATUS_CANCELLED;
        break;
    default:
        ret = STATUS_DEVICE_NOT_READY;
        break;
    }
done:
    pthread_mutex_unlock( &ctx->lock );
    return ret;
}

NTSTATUS bluez_device_disconnect( void *connection, const char *device_path )
{
    DBusMessage *request, *reply = NULL;
    NTSTATUS status;
    DBusError error;

    TRACE( "(%p, %s)\n", connection, debugstr_a( device_path ) );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, device_path, BLUEZ_INTERFACE_DEVICE, "Disconnect" );
    if (!request)
        return STATUS_NO_MEMORY;

    p_dbus_error_init( &error );
    status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
    if (status)
    {
        p_dbus_error_free( &error );
        return status;
    }
    if (!reply)
    {
        ERR( "Failed to disconnect device %s: %s\n", debugstr_a( device_path ), dbgstr_dbus_error( &error ) );
        status = bluez_dbus_error_to_ntstatus( &error );
        p_dbus_error_free( &error );
        return status;
    }
    p_dbus_message_unref( reply );
    p_dbus_error_free( &error );

    return STATUS_SUCCESS;
}

static void bluez_device_pair_callback( DBusPendingCall *pending, void *param )
{
    struct bluez_async_req_data *data = param;
    DBusMessage *reply;
    DBusError error;
    union winebluetooth_watcher_event_data event = {0};

    event.pairing_finished.irp = data->irp;
    reply = p_dbus_pending_call_steal_reply( pending );
    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
    {
        event.pairing_finished.result = bluez_dbus_error_to_ntstatus( &error );
        ERR( "Failed to pair: %s\n", dbgstr_dbus_error( &error ) );
    }
    p_dbus_error_free( &error );

    bluez_event_list_queue_new_event( &data->watcher_ctx->event_list,
                                      BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED, event );
    p_dbus_message_unref( reply );
}

NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct unix_name *device, IRP *irp )
{
    DBusMessage *request;
    DBusPendingCall *pending_call = NULL;
    struct bluez_async_req_data *data;
    dbus_bool_t success;

    TRACE( "(%p, %p, %s, %p)\n", connection, watcher_ctx, debugstr_a( device->str ), irp );

    request = p_dbus_message_new_method_call( BLUEZ_DEST, device->str, BLUEZ_INTERFACE_DEVICE, "Pair" );
    if (!request)
        return STATUS_NO_MEMORY;

    data = malloc( sizeof( *data ) );
    if (!data)
    {
        p_dbus_message_unref( request );
        return STATUS_NO_MEMORY;
    }
    data->irp = irp;
    data->watcher_ctx = watcher_ctx;
    success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout );
    p_dbus_message_unref( request );
    if (!success)
    {
        free( data );
        return STATUS_NO_MEMORY;
    }
    if (!pending_call)
    {
        free( data );
        return STATUS_INTERNAL_ERROR;
    }
    if (!p_dbus_pending_call_set_notify( pending_call, bluez_device_pair_callback, data, free ))
    {
        free( data );
        p_dbus_pending_call_cancel( pending_call );
        p_dbus_pending_call_unref( pending_call );
        return STATUS_NO_MEMORY;
    }

    p_dbus_pending_call_unref( pending_call );
    return STATUS_PENDING;
}

struct bluez_watcher_event
{
    struct list entry;
    enum winebluetooth_watcher_event_type event_type;
    union winebluetooth_watcher_event_data event;

    /* Some DBus signals, like PropertiesChanged in org.freedesktop.DBus.Properties, require us to
     * perform an additional call to get the complete state of the object (in this instance, call
     * Get/GetAll to get the values of invalidated properties). The event is queued out only once
     * this call completes. */
    DBusPendingCall *pending_call;
};

static BOOL bluez_event_list_queue_new_event_with_call(
    struct list *event_list, enum winebluetooth_watcher_event_type event_type,
    union winebluetooth_watcher_event_data event, DBusPendingCall *call,
    DBusPendingCallNotifyFunction callback )
{
    struct bluez_watcher_event *event_entry;

    if (!(event_entry = calloc(1, sizeof( *event_entry ) )))
    {
        ERR( "Could not allocate memory for DBus event.\n" );
        return FALSE;
    }

    event_entry->event_type = event_type;
    event_entry->event = event;
    event_entry->pending_call = call;
    if (call && callback && !p_dbus_pending_call_set_notify( call, callback, &event_entry->event, NULL ))
    {
        free( event_entry );
        return FALSE;
    }
    list_add_tail( event_list, &event_entry->entry );

    return TRUE;
}

static BOOL bluez_event_list_queue_new_event( struct list *event_list,
                                              enum winebluetooth_watcher_event_type event_type,
                                              union winebluetooth_watcher_event_data event )
{
    return bluez_event_list_queue_new_event_with_call( event_list, event_type, event, NULL, NULL );
}

static void bluez_filter_radio_props_changed_callback( DBusPendingCall *call, void *user_data )
{
    union winebluetooth_watcher_event_data *event = user_data;
    struct winebluetooth_watcher_event_radio_props_changed *changed = &event->radio_props_changed;
    const struct unix_name *radio = (struct unix_name *)event->radio_props_changed.radio.handle;
    DBusMessage *reply;
    DBusMessageIter dict, prop_iter, variant;
    const char *prop_name;
    DBusError error;

    TRACE( "call %p, radio %s\n", call, debugstr_a( radio->str ) );

    reply = p_dbus_pending_call_steal_reply( call );
    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
    {
        ERR( "Failed to get adapter properties for %s: %s\n", debugstr_a( radio->str ), dbgstr_dbus_error( &error ) );
        p_dbus_error_free( &error );
        p_dbus_message_unref( reply );
        return;
    }
    p_dbus_error_free( &error );

    p_dbus_message_iter_init( reply, &dict );
    p_dbus_message_iter_recurse( &dict, &prop_iter );
    while((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
    {
        bluez_radio_prop_from_dict_entry( prop_name, &variant, &changed->props,
                                          &changed->changed_props_mask, changed->invalid_props_mask );
    }
    changed->invalid_props_mask &= ~changed->changed_props_mask;
    p_dbus_message_unref( reply );
}

static void bluez_filter_device_props_changed_callback( DBusPendingCall *call, void *user_data )
{
    union winebluetooth_watcher_event_data *event = user_data;
    struct winebluetooth_watcher_event_device_props_changed *changed = &event->device_props_changed;
    const struct unix_name *device = (struct unix_name *)event->device_props_changed.device.handle;
    DBusMessage *reply;
    DBusMessageIter dict, prop_iter, variant;
    const char *prop_name;
    DBusError error;

    TRACE( "call %p, device %s\n", call, debugstr_a( device->str ) );

    reply = p_dbus_pending_call_steal_reply( call );
    p_dbus_error_init( &error );
    if (p_dbus_set_error_from_message( &error, reply ))
    {
        ERR( "Failed to get device properties for %s: %s\n", debugstr_a( device->str ), dbgstr_dbus_error( &error ) );
        p_dbus_error_free( &error );
        p_dbus_message_unref( reply );
        return;
    }
    p_dbus_error_free( &error );

    p_dbus_message_iter_init( reply, &dict );
    p_dbus_message_iter_recurse( &dict, &prop_iter );
    while((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
    {
        bluez_device_prop_from_dict_entry( prop_name, &variant, &changed->props,
                                           &changed->changed_props_mask,
                                           changed->invalid_props_mask );
    }
    changed->invalid_props_mask &= ~changed->changed_props_mask;
    p_dbus_message_unref( reply );
}

struct bluez_object_property_masks
{
    const char *prop_name;
    UINT16 mask;
};

static UINT16 bluez_dbus_get_invalidated_properties_from_iter(
    DBusMessageIter *invalid_prop_iter, const struct bluez_object_property_masks *prop_masks,
    SIZE_T len )
{
    UINT16 mask = 0;

    while (p_dbus_message_iter_has_next( invalid_prop_iter ))
    {
        const char *prop_name;
        SIZE_T i;

        assert( p_dbus_message_iter_get_arg_type( invalid_prop_iter ) == DBUS_TYPE_STRING );
        p_dbus_message_iter_get_basic( invalid_prop_iter, &prop_name );
        for (i = 0; i < len; i++)
        {
            if (strcmp( prop_masks[i].prop_name, prop_name ) == 0)
                mask |= prop_masks[i].mask;
        }

        p_dbus_message_iter_next( invalid_prop_iter );
    }

    return mask;
}

static void winebluetooth_watcher_event_free( enum winebluetooth_watcher_event_type event_type,
                                              union winebluetooth_watcher_event_data *event )
{
    switch (event_type)
    {
    case BLUETOOTH_WATCHER_EVENT_TYPE_SERVICE_DOWN:
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED:
        if (event->radio_added.radio.handle) unix_name_free( (struct unix_name *)event->radio_added.radio.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED:
        if (event->radio_removed.handle) unix_name_free( (struct unix_name *)event->radio_removed.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED:
        if (event->radio_props_changed.radio.handle)
            unix_name_free( (struct unix_name *)event->radio_props_changed.radio.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED:
        if (event->device_added.radio.handle) unix_name_free( (struct unix_name *)event->device_added.radio.handle );
        if (event->device_added.device.handle) unix_name_free( (struct unix_name *)event->device_added.device.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED:
        if (event->device_removed.device.handle)
            unix_name_free( (struct unix_name *)event->device_removed.device.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED:
        if (event->device_props_changed.device.handle)
            unix_name_free( (struct unix_name *)event->device_props_changed.device.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED:
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED:
        if (event->gatt_service_added.device.handle)
            unix_name_free( (struct unix_name *)event->gatt_service_added.device.handle );
        if (event->gatt_service_added.service.handle)
            unix_name_free( (struct unix_name *)event->gatt_service_added.service.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED:
        if (event->gatt_service_removed.handle)
            unix_name_free( (struct unix_name *)event->gatt_service_removed.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED:
        if (event->gatt_characteristic_added.characteristic.handle)
            unix_name_free( (struct unix_name *)event->gatt_characteristic_added.characteristic.handle );
        if (event->gatt_characteristic_added.service.handle)
            unix_name_free( (struct unix_name *)event->gatt_characteristic_added.service.handle );
        bluez_gatt_characteristic_value_free(
            (struct bluez_gatt_characteristic_value *)event->gatt_characteristic_added.value.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED:
        if (event->gatt_characterisic_removed.handle)
            unix_name_free( (struct unix_name *)event->gatt_characterisic_removed.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED:
        if (event->gatt_characteristic_value_changed.characteristic.handle)
            unix_name_free( (struct unix_name *)event->gatt_characteristic_value_changed.characteristic.handle );
        bluez_gatt_characteristic_value_free(
            (struct bluez_gatt_characteristic_value *)event->gatt_characteristic_added.value.handle );
        break;
    case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_READ:
        bluez_gatt_characteristic_value_free(
            (struct bluez_gatt_characteristic_value *)event->gatt_characteristic_value_read.value.handle );
        break;
    }
}

/* Examine a new BlueZ DBus object available at path and queue a BLUETOOTH_WATCHER_* event if it is an object we
 * are interested in.
 * ifaces_iter should point to the start of the interfaces + properties dict.
 * init_entry should be TRUE if this object has been discovered through initial device discovery in
 * bluez_enumerate_objects.
 */
static BOOL bluez_handle_new_object( DBusMessage *msg, const char *path, DBusMessageIter *ifaces_iter, BOOL init_entry,
                                     struct list *event_list, BOOL *ret_oom )
{
    enum winebluetooth_watcher_event_type event_type = 0;
    union winebluetooth_watcher_event_data event = {0};
    BOOL new_object = FALSE, oom = FALSE;
    DBusMessageIter props_iter;
    const char *iface;

    TRACE_( dbus )( "(%s, %s, %d, %p, %p)\n", debugstr_a( path ), dbgstr_dbus_iter( ifaces_iter ), init_entry,
                    event_list, ret_oom );

    while ((iface = bluez_next_dict_entry ( ifaces_iter, &props_iter )))
    {
        DBusMessageIter variant;
        const char *prop_name;

        if (!strcmp( iface, BLUEZ_INTERFACE_ADAPTER ))
        {
            if (!(event.radio_added.radio.handle = (UINT_PTR)unix_name_get_or_create( path )))
            {
                oom = TRUE;
                break;
            }
            while ((prop_name = bluez_next_dict_entry( &props_iter, &variant )))
            {
                bluez_radio_prop_from_dict_entry( prop_name, &variant, &event.radio_added.props,
                                                  &event.radio_added.props_mask,
                                                  WINEBLUETOOTH_RADIO_ALL_PROPERTIES );
            }
            event_type = BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED;
            new_object = TRUE;
            TRACE( "New BlueZ org.bluez.Adapter1 object %s: %#" PRIxPTR "\n", debugstr_a( path ),
                   event.radio_added.radio.handle );
        }
        else if (!strcmp( iface, BLUEZ_INTERFACE_DEVICE ))
        {
            if (!(event.device_added.device.handle = (UINT_PTR)unix_name_get_or_create( path )))
            {
                oom = TRUE;
                break;
            }
            event.device_added.init_entry = init_entry;

            while ((prop_name = bluez_next_dict_entry( &props_iter, &variant )))
            {
                if (!strcmp( prop_name, "Adapter" ) &&
                    p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH)
                {
                    const char *path;
                    p_dbus_message_iter_get_basic( &variant, &path );
                    if (!(event.device_added.radio.handle = (UINT_PTR)unix_name_get_or_create( path )))
                    {
                        oom = TRUE;
                        goto done;
                    }
                }
                else
                    bluez_device_prop_from_dict_entry( prop_name, &variant, &event.device_added.props,
                                                       &event.device_added.known_props_mask,
                                                       WINEBLUETOOTH_DEVICE_ALL_PROPERTIES );
            }
            if (!event.device_added.radio.handle)
            {
                unix_name_free( (struct unix_name *)event.device_added.device.handle );
                ERR( "Could not find the associated adapter for device %s\n", debugstr_a( path ) );
            }
            else
            {
                TRACE( "New org.bluez.Device1 object %s: %#" PRIxPTR "\n", debugstr_a( path ),
                       event.device_added.device.handle );
                event_type = BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED;
                new_object = TRUE;
            }
        }
        else if (!strcmp( iface ,BLUEZ_INTERFACE_GATT_SERVICE ))
        {
            if (!(event.gatt_service_added.service.handle = (UINT_PTR)unix_name_get_or_create( path )))
            {
                oom = TRUE;
                break;
            }

            while ((prop_name = bluez_next_dict_entry( &props_iter, &variant )))
                bluez_gatt_service_props_from_dict_entry( prop_name, &variant, &event.gatt_service_added );
            if (!event.gatt_service_added.device.handle)
            {
                unix_name_free( (struct unix_name *)event.gatt_service_added.service.handle );
                ERR( "Could not find the associated device for the GATT service %s\n", debugstr_a( path ) );
            }
            else
            {
                TRACE( "New org.bluez.GattService1 object %s: %#" PRIxPTR "\n", debugstr_a( path ),
                       event.gatt_service_added.service.handle );
                event_type = BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED;
                new_object = TRUE;
            }
        }
        else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_CHARACTERISTICS ))
        {
            if (!(event.gatt_characteristic_added.characteristic.handle = (UINT_PTR)unix_name_get_or_create( path )))
            {
                oom = TRUE;
                break;
            }

            while ((prop_name = bluez_next_dict_entry( &props_iter, &variant )))
            {
                if (!bluez_gatt_characteristic_props_from_dict_entry( msg, prop_name, &variant,
                                                                      &event.gatt_characteristic_added ))
                {
                    oom = TRUE;
                    goto done;
                }
            }
            if (!event.gatt_characteristic_added.service.handle)
            {
                unix_name_free( (struct unix_name *)event.gatt_characteristic_added.characteristic.handle );
                bluez_gatt_characteristic_value_free(
                    (struct bluez_gatt_characteristic_value *)event.gatt_characteristic_added.value.handle );
                ERR( "Could not find the associated service for the GATT charcteristic %s\n", debugstr_a( path ) );
            }
            else
            {
                TRACE( "New org.bluez.GattCharacteristic1 object %s: %#" PRIxPTR "\n", debugstr_a( path ),
                       event.gatt_characteristic_added.characteristic.handle );
                event_type = BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED;
                new_object = TRUE;
            }
        }

        if (new_object) break;
    }

done:
    if (oom || (new_object && !bluez_event_list_queue_new_event( event_list, event_type, event )))
    {
        winebluetooth_watcher_event_free( event_type, &event );
        if (ret_oom) *ret_oom = TRUE;
        return FALSE;
    }
    if (ret_oom) *ret_oom = FALSE;
    return new_object;
}

static void bluez_signal_handler( DBusConnection *conn, DBusMessage *msg, const char *signal_iface,
                                  const char *signal_name, const char *signal_sig, struct bluez_watcher_ctx *ctx )
{
    struct list *event_list = &ctx->event_list;

    if (!strcmp( signal_iface, DBUS_INTERFACE_DBUS ) &&
        !strcmp( signal_name, DBUS_DBUS_SIGNAL_NAMEOWNERCHANGED ) &&
        !strcmp( signal_sig, DBUS_NAMEOWNERCHANGED_SIGNATURE ))
    {
        const static union winebluetooth_watcher_event_data empty_event;
        const char *name, *old_owner, *new_owner;
        DBusError error;

        p_dbus_error_init( &error );
        if (!p_dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner,
                                      DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID ))
        {
            ERR( "Failed to read NameOwnerChanged arguments: %s\n", dbgstr_dbus_error( &error ) );
            p_dbus_error_free( &error );
            return;
        }
        p_dbus_error_free( &error );
        if (strcmp( name, BLUEZ_DEST )) return;
        if (new_owner[0] && (!ctx->bluez_dbus_unique_name || strcmp( ctx->bluez_dbus_unique_name, new_owner )))
        {
            free( ctx->bluez_dbus_unique_name );
            if ((ctx->bluez_dbus_unique_name = strdup( new_owner )))
                bluez_on_service_available( ctx );
        }
        else if (ctx->bluez_dbus_unique_name)
        {
            TRACE_(dbus)( "org.bluez down, removing all entries.\n" );

            free( ctx->bluez_dbus_unique_name );
            ctx->bluez_dbus_unique_name = NULL;
            /* When BlueZ shuts down gracefully, we will already have received InterfaceRemoved signals for
             * all previously added Bluetooth entries. This event is useful for when BlueZ crashes, in which
             * case we would have to remove the entries ourselves. */
            bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_SERVICE_DOWN, empty_event );
        }
    }
    if (!strcmp( signal_iface, DBUS_INTERFACE_OBJECTMANAGER ) &&
        !strcmp( signal_name, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED ) &&
        !strcmp( signal_sig, DBUS_INTERFACES_ADDED_SIGNATURE ))
    {
        DBusMessageIter iter, ifaces_iter;
        const char *object_path;

        p_dbus_message_iter_init( msg, &iter );
        p_dbus_message_iter_get_basic( &iter, &object_path );
        p_dbus_message_iter_next( &iter );
        p_dbus_message_iter_recurse( &iter, &ifaces_iter );

        bluez_handle_new_object( msg, object_path, &ifaces_iter, FALSE, event_list, NULL );
    }
    else if (!strcmp( signal_iface, DBUS_INTERFACE_OBJECTMANAGER ) &&
             !strcmp( signal_name, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED ) &&
             !strcmp( signal_sig, DBUS_INTERFACES_REMOVED_SIGNATURE ))
    {
        const char *object_path;
        char **interfaces;
        int n_interfaces, i;
        DBusError error;
        dbus_bool_t success;

        p_dbus_error_init( &error );
        success = p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &object_path,
                                           DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &interfaces,
                                           &n_interfaces, DBUS_TYPE_INVALID );
        if (!success)
        {
            ERR( "error getting arguments from message: %s\n", dbgstr_dbus_error( &error ));
            p_dbus_error_free( &error );
            return;
        }

        p_dbus_error_free( &error );
        for (i = 0; i < n_interfaces; i++)
        {
            if (!strcmp( interfaces[i], BLUEZ_INTERFACE_ADAPTER ))
            {
                winebluetooth_radio_t radio;
                struct unix_name *radio_name;
                union winebluetooth_watcher_event_data event;

                radio_name = unix_name_get_or_create( object_path );
                if (!radio_name)
                {
                    ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ));
                    continue;
                }
                radio.handle = (UINT_PTR)radio_name;
                event.radio_removed = radio;
                if (!bluez_event_list_queue_new_event(
                        event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, event ))
                    unix_name_free( radio_name );
            }
            else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_DEVICE ))
            {
                struct unix_name *device;
                union winebluetooth_watcher_event_data event;

                device = unix_name_get_or_create( object_path );
                if (!device)
                {
                    ERR( "Failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) );
                    continue;
                }
                event.device_removed.device.handle = (UINT_PTR)device;
                if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED,
                                                       event ))
                    unix_name_free( device );
            }
            else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_GATT_SERVICE ))
            {
                struct unix_name *service;
                union winebluetooth_watcher_event_data event;

                service = unix_name_get_or_create( object_path );
                if (!service)
                {
                    ERR( "Failed to allocate memory for GATT service path %s\n", debugstr_a( object_path ) );
                    continue;
                }
                event.gatt_service_removed.handle = (UINT_PTR)service;
                if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED,
                                                       event ))
                    unix_name_free( service );
            }
            else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_GATT_CHARACTERISTICS ))
            {
                struct unix_name *chrc;
                union winebluetooth_watcher_event_data event;

                chrc = unix_name_get_or_create( object_path );
                if (!chrc)
                {
                    ERR( "Failed to allocate memory for GATT characteristic path %s\n", debugstr_a( object_path ) );
                    continue;
                }
                event.gatt_characterisic_removed.handle = (UINT_PTR)chrc;
                if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED,
                                                       event ))
                    unix_name_free( chrc );
            }
        }
        p_dbus_free_string_array( interfaces );
    }
    else if (!strcmp( signal_iface, DBUS_INTERFACE_PROPERTIES ) &&
             !strcmp( signal_name, DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED ) &&
             !strcmp( signal_sig, DBUS_PROPERTIES_CHANGED_SIGNATURE ))
    {
        DBusMessageIter iter;
        const char *iface;

        p_dbus_message_iter_init( msg, &iter );
        p_dbus_message_iter_get_basic( &iter, &iface );

        if (!strcmp( iface, BLUEZ_INTERFACE_ADAPTER ))
        {
            struct winebluetooth_watcher_event_radio_props_changed props_changed = {0};
            struct unix_name *radio;
            DBusMessageIter changed_props_iter, invalid_props_iter, variant;
            const char *prop_name;
            const static struct bluez_object_property_masks radio_prop_masks[] = {
                { "Name", WINEBLUETOOTH_RADIO_PROPERTY_NAME },
                { "Address", WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS },
                { "Discoverable", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE },
                { "Connectable", WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE },
                { "Class", WINEBLUETOOTH_RADIO_PROPERTY_CLASS },
                { "Manufacturer", WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER },
                { "Version", WINEBLUETOOTH_RADIO_PROPERTY_VERSION },
                { "Discovering", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING },
                { "Pairable", WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE },
            };
            const char *object_path;

            p_dbus_message_iter_next( &iter );
            p_dbus_message_iter_recurse( &iter, &changed_props_iter );
            while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant )))
            {
                bluez_radio_prop_from_dict_entry( prop_name, &variant, &props_changed.props,
                                                  &props_changed.changed_props_mask,
                                                  WINEBLUETOOTH_RADIO_ALL_PROPERTIES );
            }

            p_dbus_message_iter_next( &iter );
            p_dbus_message_iter_recurse( &iter, &invalid_props_iter );
            props_changed.invalid_props_mask = bluez_dbus_get_invalidated_properties_from_iter(
                &invalid_props_iter, radio_prop_masks, ARRAY_SIZE( radio_prop_masks ) );
            if (!props_changed.changed_props_mask && !props_changed.invalid_props_mask)
                /* No properties that are of any interest to us have changed or been invalidated,
                 * no need to generate an event. */
                return;

            object_path = p_dbus_message_get_path( msg );
            radio = unix_name_get_or_create( object_path );
            if (!radio)
            {
                ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) );
                return;
            }
            props_changed.radio.handle = (UINT_PTR)radio;
            TRACE( "Properties changed for radio %s, changed %#x, invalid %#x\n",
                   debugstr_a( radio->str ), props_changed.changed_props_mask,
                  props_changed.invalid_props_mask );
            if (props_changed.invalid_props_mask != 0)
            {
                DBusPendingCall *pending_call = NULL;
                union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed };
                NTSTATUS status = bluez_adapter_get_props_async( conn, radio->str, &pending_call );

                if (status != STATUS_SUCCESS)
                {
                    ERR( "Failed to create async call to get adapter properties: %#x\n",
                         status );
                    unix_name_free( radio );
                    return;
                }

                if (!bluez_event_list_queue_new_event_with_call( event_list,
                                                                BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED,
                                                                event, pending_call,
                                                                bluez_filter_radio_props_changed_callback ))
                {
                    unix_name_free( radio );
                    p_dbus_pending_call_cancel( pending_call );
                    p_dbus_pending_call_unref( pending_call );
                    return;
                }
            }
            else
            {
                union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed };
                if (!bluez_event_list_queue_new_event( event_list,
                                                      BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED,
                                                      event ))
                {
                    unix_name_free( radio );
                    return;
                }
            }
        }
        else if (strcmp( iface, BLUEZ_INTERFACE_DEVICE ) == 0)
        {
            struct winebluetooth_watcher_event_device_props_changed props_changed = {0};
            struct unix_name *device;
            const char *prop_name, *object_path;
            union winebluetooth_watcher_event_data event;
            DBusMessageIter changed_props_iter, invalid_props_iter, variant;
            const static struct bluez_object_property_masks device_prop_masks[] = {
                { "Name", WINEBLUETOOTH_DEVICE_PROPERTY_NAME },
                { "Address", WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS },
                { "Connected", WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED },
                { "Paired", WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED },
                { "LegacyPairing", WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING },
                { "Trusted", WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED },
                { "Class", WINEBLUETOOTH_DEVICE_PROPERTY_CLASS }
            };

            p_dbus_message_iter_next( &iter );
            p_dbus_message_iter_recurse( &iter, &changed_props_iter );
            while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant )))
            {
                bluez_device_prop_from_dict_entry( prop_name, &variant, &props_changed.props,
                                                   &props_changed.changed_props_mask,
                                                   WINEBLUETOOTH_DEVICE_ALL_PROPERTIES );
            }
            p_dbus_message_iter_next( &iter );
            p_dbus_message_iter_recurse( &iter, &invalid_props_iter );
            props_changed.invalid_props_mask = bluez_dbus_get_invalidated_properties_from_iter(
                &invalid_props_iter, device_prop_masks, ARRAY_SIZE( device_prop_masks ) );
            /* No properties that we're interested in have changed or been invalidated. */
            if (!props_changed.changed_props_mask && !props_changed.invalid_props_mask)
                return;

            object_path = p_dbus_message_get_path( msg );
            TRACE( "Properties changed for device %s, changed %#x, invalidated %#x\n", debugstr_a( object_path ),
                   props_changed.changed_props_mask, props_changed.invalid_props_mask );

            device = unix_name_get_or_create( object_path );
            if (!device)
            {
                ERR( "Failed to allocate memory for device path %s\n", object_path );
                return;
            }
            props_changed.device.handle = (UINT_PTR)device;
            event.device_props_changed = props_changed;

            if (props_changed.invalid_props_mask != 0)
            {
                DBusPendingCall *pending_call = NULL;
                NTSTATUS status;

                status = bluez_device_get_props_by_path_async( conn, device->str, &pending_call );
                if (status)
                {
                    ERR( "Failed to create async call to get device properties: %#x\n", status );
                    unix_name_free( device );
                    return;
                }

                if (!bluez_event_list_queue_new_event_with_call( event_list,
                                                                 BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED,
                                                                 event, pending_call,
                                                                 bluez_filter_device_props_changed_callback ))
                {
                    unix_name_free( device );
                    p_dbus_pending_call_cancel( pending_call );
                    p_dbus_pending_call_unref( pending_call );
                    return;
                }
            }
            else if (!bluez_event_list_queue_new_event( event_list,
                                                        BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED,
                                                        event ))
            {

                unix_name_free( device );
                return;
            }
        }
        else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_CHARACTERISTICS ))
        {
            struct winebluetooth_watcher_event_gatt_characteristic_value_changed changed_event = {0};
            union winebluetooth_watcher_event_data event;
            DBusMessageIter changed_props_iter, variant;
            const char *prop_name, *object_path;
            struct unix_name *chrc_name;
            BOOL val_changed = FALSE;

            p_dbus_message_iter_next( &iter );
            p_dbus_message_iter_recurse( &iter, &changed_props_iter );
            while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant )))
            {
                if (!strcmp( prop_name, "Value" )
                    && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_ARRAY
                    && p_dbus_message_iter_get_element_type( &variant ) == DBUS_TYPE_BYTE)
                {
                    val_changed = bluez_gatt_characteristic_value_new_from_iter( msg, &variant, &changed_event.value );
                    break;
                }
            }
            if (!val_changed)
                return;

            object_path = p_dbus_message_get_path( msg );
            TRACE( "Value changed for GATT characteristic %s\n", debugstr_a( object_path ) );
            if (!(chrc_name = unix_name_get_or_create( object_path )))
            {
                ERR( "Failed to allocate memory for GATT characteristic path %s\n", debugstr_a( object_path ) );
                bluez_gatt_characteristic_value_free(
                    (struct bluez_gatt_characteristic_value *)changed_event.value.handle );
                return;
            }

            changed_event.characteristic.handle = (UINT_PTR)chrc_name;
            event.gatt_characteristic_value_changed = changed_event;
            if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED,
                                                   event ))
            {
                unix_name_free( chrc_name );
                bluez_gatt_characteristic_value_free(
                    (struct bluez_gatt_characteristic_value *)changed_event.value.handle );
                return;
            }
        }
    }
}

static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *data )
{
    struct bluez_watcher_ctx *ctx = data;
    int type;

    if (TRACE_ON( dbus ))
        TRACE_( dbus )( "(%s, %s, %p)\n", dbgstr_dbus_connection( conn ), dbgstr_dbus_message( msg ), data );

    type = p_dbus_message_get_type( msg );
    if (type == DBUS_MESSAGE_TYPE_SIGNAL)
        bluez_signal_handler( conn, msg, p_dbus_message_get_interface( msg ), p_dbus_message_get_member( msg ),
                              p_dbus_message_get_signature( msg ), ctx );
    else if (type == DBUS_MESSAGE_TYPE_METHOD_CALL)
    {
        DBusMessage *reply;
        const char *sender;
        dbus_bool_t success;

        /* Only allow incoming method calls from org.bluez. */
        if (!(sender = p_dbus_message_get_sender( msg )) || !ctx->bluez_dbus_unique_name ||
            strcmp( sender, ctx->bluez_dbus_unique_name ))
        {
            if (!(reply = p_dbus_message_new_error( msg, DBUS_ERROR_ACCESS_DENIED, "Access Denied" )))
                return DBUS_HANDLER_RESULT_NEED_MEMORY;

            ERR_(dbus)("Denying method call from unknown sender: %s\n", debugstr_a( sender ) );
            success = p_dbus_connection_send( conn, reply, NULL );
            p_dbus_message_unref( reply );
            return success ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NEED_MEMORY;
        }
    }

    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static const char BLUEZ_MATCH_OBJECTMANAGER[] = "type='signal',"
                                                "interface='org.freedesktop.DBus.ObjectManager',"
                                                "sender='"BLUEZ_DEST"',"
                                                "path='/'";
static const char BLUEZ_MATCH_PROPERTIES[] = "type='signal',"
                                             "interface='"DBUS_INTERFACE_PROPERTIES"',"
                                             "member='PropertiesChanged',"
                                             "sender='"BLUEZ_DEST"',";
static const char DBUS_MATCH_NAMEOWNERCHANGED[] = "type='signal',"
                                                  "interface='"DBUS_INTERFACE_DBUS"',"
                                                  "member='NameOwnerChanged',"
                                                  "arg0='"BLUEZ_DEST"',"
                                                  "path='/org/freedesktop/DBus',"
                                                  "sender='org.freedesktop.DBus'";

static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER, BLUEZ_MATCH_PROPERTIES, DBUS_MATCH_NAMEOWNERCHANGED };

/* Free up the watcher alongside any remaining events and initial devices and other associated resources. */
static void bluez_watcher_free( struct bluez_watcher_ctx *watcher )
{
    struct bluez_watcher_event *event1, *event2;

    LIST_FOR_EACH_ENTRY_SAFE( event1, event2, &watcher->event_list, struct bluez_watcher_event, entry )
    {
        list_remove( &event1->entry );
        winebluetooth_watcher_event_free( event1->event_type, &event1->event );
        if (event1->pending_call)
        {
            p_dbus_pending_call_cancel( event1->pending_call );
            p_dbus_pending_call_unref( event1->pending_call );
        }
        free( event1 );
    }

    p_dbus_connection_unref( watcher->connection );
    free( watcher->bluez_dbus_unique_name );
    free( watcher );
}

NTSTATUS bluez_watcher_init( void *connection, void **ctx )
{
    struct bluez_watcher_ctx *watcher_ctx;
    DBusPendingCall *call;
    NTSTATUS status;
    DBusError err;
    SIZE_T i;

    if (!(watcher_ctx = calloc( 1, sizeof( struct bluez_watcher_ctx ) ))) return STATUS_NO_MEMORY;
    watcher_ctx->connection = p_dbus_connection_ref( connection );
    list_init( &watcher_ctx->event_list );

    /* The bluez_dbus_loop thread will free up the watcher when the disconnect message is processed (i.e,
     * dbus_connection_read_write_dispatch returns false). Using a free-function with dbus_connection_add_filter
     * is racy as the filter is removed from a different thread. */
    if (!p_dbus_connection_add_filter( connection, bluez_filter, watcher_ctx, NULL ))
    {
        ERR_(dbus)( "Could not add DBus filter\n" );
        status = STATUS_NO_MEMORY;
        goto done;
    }
    p_dbus_error_init( &err );
    for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++)
    {
        const char *rule = BLUEZ_MATCH_RULES[i];

        TRACE_(dbus)( "Adding DBus match rule %s\n", debugstr_a( rule ) );
        p_dbus_bus_add_match( connection, rule, &err );
        if (p_dbus_error_is_set( &err ))
        {
            ERR_(dbus)( "Could not add DBus match %s: %s\n", debugstr_a( rule ), dbgstr_dbus_error( &err ) );
            status = bluez_dbus_error_to_ntstatus( &err );
            goto done;
        }
    }
    /* Get the unique name after setting up a signal handler, this avoids a race condition if BlueZ (re)starts (and thus
     * gets a new unique name) between the call to GetNameOwner and dbus_connection_add_filter. */
    if ((status = bluez_dbus_get_unique_name_async( connection, &call ))) goto done;
    if (!p_dbus_pending_call_set_notify( call, bluez_dbus_get_name_owner_callback, watcher_ctx, NULL ))
    {
        status = STATUS_NO_MEMORY;
        p_dbus_pending_call_cancel( call );
    }
    p_dbus_pending_call_unref( call );
done:
    p_dbus_error_free( &err );
    if (status)
    {
        p_dbus_connection_unref( watcher_ctx->connection );
        free( watcher_ctx->bluez_dbus_unique_name );
        free( watcher_ctx );
    }
    else
    {
        *ctx = watcher_ctx;
        TRACE( "ctx=%p\n", ctx );
    }
    return status;
}

void bluez_watcher_close( void *connection, void *ctx )
{
    SIZE_T i;
    for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++)
    {
        DBusError error;

        p_dbus_error_init( &error );
        p_dbus_bus_remove_match( connection, BLUEZ_MATCH_RULES[i], &error );
        if (p_dbus_error_is_set( &error ))
            ERR( "Could not remove DBus match %s: %s\n", debugstr_a( BLUEZ_MATCH_RULES[i] ), dbgstr_dbus_error( &error ) );
        p_dbus_error_free( &error );
    }
    p_dbus_connection_remove_filter( connection, bluez_filter, ctx );
}

/* Enuerate through the list of BlueZ objects contained in reply (from GetManagedObjects) and queue the associated
 * BLUETOOTH_WATCHER_ event for objects we're interested in onto event_list. */
static NTSTATUS bluez_enumerate_objects( DBusMessage *reply, struct list *event_list )
{
    DBusMessageIter dict, paths_iter, iface_iter;
    const char *path;

    if (!p_dbus_message_has_signature( reply,
                                       DBUS_OBJECTMANAGER_METHOD_GETMANAGEDOBJECTS_RETURN_SIGNATURE ))
    {
        ERR( "Unexpected signature in GetManagedObjects reply: %s\n",
             debugstr_a( p_dbus_message_get_signature( reply ) ) );
        return STATUS_INTERNAL_ERROR;
    }

    p_dbus_message_iter_init( reply, &dict );
    p_dbus_message_iter_recurse( &dict, &paths_iter );
    while((path = bluez_next_dict_entry( &paths_iter, &iface_iter )))
    {
        BOOL oom;
        if (!bluez_handle_new_object( reply, path, &iface_iter, TRUE, event_list, &oom ) && oom)
            return STATUS_NO_MEMORY;
    }
    return STATUS_SUCCESS;
}

static BOOL bluez_watcher_event_queue_pop( struct bluez_watcher_ctx *ctx, struct winebluetooth_watcher_event *event )
{
    struct bluez_watcher_event *head;

    if (list_empty( &ctx->event_list )) return FALSE;

    head = LIST_ENTRY( list_head( &ctx->event_list ), struct bluez_watcher_event, entry );
    if (head->pending_call && !p_dbus_pending_call_get_completed( head->pending_call )) return FALSE;

    event->event_type = head->event_type;
    event->event_data = head->event;
    list_remove( &head->entry );
    if (head->pending_call) p_dbus_pending_call_unref( head->pending_call );
    free( head );
    return TRUE;
}

static BOOL bluez_auth_agent_ctx_have_event( struct bluez_auth_agent_ctx *ctx,
                                             struct winebluetooth_auth_event *event )
{
    BOOL have_event = FALSE;

    pthread_mutex_lock( &ctx->lock );
    if (ctx->status == BLUEZ_PAIRING_SESSION_INCOMING)
    {
        event->device.handle = (UINT_PTR)unix_name_dup( ctx->device );
        event->method = ctx->method;
        event->numeric_value_or_passkey = ctx->passkey;

        ctx->status = BLUEZ_PAIRING_SESSION_PENDING_REPLY;
        have_event = TRUE;
    }
    pthread_mutex_unlock( &ctx->lock );

    return have_event;
}

NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent,
                          struct winebluetooth_event *result )
{
    DBusConnection *connection;
    struct bluez_watcher_ctx *watcher_ctx = watcher;
    auth_agent = bluez_auth_agent_ctx_incref( auth_agent );

    TRACE( "(%p, %p, %p)\n", c, watcher, result );
    connection = p_dbus_connection_ref( c );

    while (TRUE)
    {
        if (bluez_watcher_event_queue_pop( watcher_ctx, &result->data.watcher_event ))
        {
            result->status = WINEBLUETOOTH_EVENT_WATCHER_EVENT;
            p_dbus_connection_unref( connection );
            bluez_auth_agent_ctx_decref( auth_agent );
            return STATUS_PENDING;
        }
        else if (bluez_auth_agent_ctx_have_event( auth_agent, &result->data.auth_event ))
        {
            result->status = WINEBLUETOOTH_EVENT_AUTH_EVENT;
            p_dbus_connection_unref( connection );
            bluez_auth_agent_ctx_decref( auth_agent );
            return STATUS_PENDING;
        }
        else if (!p_dbus_connection_read_write_dispatch( connection, 100 ))
        {
            bluez_watcher_free( watcher_ctx );
            bluez_auth_agent_ctx_decref( auth_agent );
            p_dbus_connection_unref( connection );
            TRACE( "Disconnected from DBus\n" );
            return STATUS_SUCCESS;
        }
    }
}
#else

void *bluez_dbus_init( void ) { return NULL; }
void bluez_dbus_close( void *connection ) {}
void bluez_dbus_free( void *connection ) {}
NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { return STATUS_NOT_SUPPORTED; }
void bluez_watcher_close( void *connection, void *ctx ) {}
NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent, struct winebluetooth_event *result )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_adapter_start_discovery( void *connection, const char *adapter_path )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_adapter_stop_discovery( void *connection, const char *adapter_path )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_adapter_remove_device( void *connection, const char *adapter_path, const char *device_path )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_auth_agent_start( void *connection, void **ctx ) { return STATUS_NOT_SUPPORTED; }
NTSTATUS bluez_auth_agent_stop( void *connection, void *ctx ) { return STATUS_NOT_SUPPORTED; }
NTSTATUS bluez_auth_agent_request_default( void *connection ) { return STATUS_NOT_SUPPORTED; }
NTSTATUS bluez_auth_agent_send_response( void *auth_agent, struct unix_name *device,
                                         BLUETOOTH_AUTHENTICATION_METHOD method, UINT32 numeric_or_passkey,
                                         BOOL negative, BOOL *authenticated )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_device_disconnect( void *connection, const char *device_path )
{
    return STATUS_NOT_SUPPORTED;
}
NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct unix_name *device, IRP *irp )
{
    return STATUS_NOT_SUPPORTED;
}
void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *buf ) {}
void bluez_gatt_characteristic_value_free( void *val ) {}
NTSTATUS bluez_gatt_characteristic_read( void *connection, void *watcher, struct unix_name *characteristic, IRP *irp )
{
    return STATUS_NOT_SUPPORTED;
}
#endif /* SONAME_LIBDBUS_1 */
