-- v2: bluetoothapis/tests: Implement tests for BluetoothGATTGetCharacteristics. bluetoothapis: Implement BluetoothGATTGetCharacteristics. winebth.sys: Remove GATT characteristic entries when they are removed from the Unix Bluetooth service. winebth.sys: Implement IOCTL_WINEBTH_LE_DEVICE_GET_GATT_CHARACTERISTICS.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 19 ++++++++++++++ dlls/winebth.sys/winebth.c | 45 +++++++++++++++++++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 6 +++++ 3 files changed, 70 insertions(+)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 08d63ceb307..5f736583f79 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -1677,6 +1677,22 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v 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 ); + } } p_dbus_free_string_array( interfaces ); } @@ -1918,6 +1934,9 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) unix_name_free( (struct unix_name *)event1->event.gatt_service_added.device.handle ); unix_name_free( (struct unix_name *)event1->event.gatt_service_added.service.handle ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED: + unix_name_free( (struct unix_name *)event1->event.gatt_service_removed.handle ); + break; } free( event1 ); } diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index bc880019740..637a080e470 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -1080,6 +1080,48 @@ static void bluetooth_device_add_gatt_service( struct winebluetooth_watcher_even winebluetooth_gatt_service_free( event.service ); }
+static void bluetooth_gatt_service_remove( winebluetooth_gatt_service_t service ) +{ + struct bluetooth_radio *radio; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( radio, &device_list, struct bluetooth_radio, entry ) + { + struct bluetooth_remote_device *device; + + EnterCriticalSection( &radio->remote_devices_cs ); + LIST_FOR_EACH_ENTRY( device, &radio->remote_devices, struct bluetooth_remote_device, entry ) + { + struct bluetooth_gatt_service *svc; + + EnterCriticalSection( &device->props_cs ); + if (!device->le) + { + LeaveCriticalSection( &device->props_cs ); + continue; + } + LIST_FOR_EACH_ENTRY( svc, &device->gatt_services, struct bluetooth_gatt_service, entry ) + { + if (winebluetooth_gatt_service_equal( svc->service, service )) + { + list_remove( &svc->entry ); + LeaveCriticalSection( &device->props_cs ); + LeaveCriticalSection( &radio->remote_devices_cs ); + LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_service_free( svc->service ); + free( svc ); + winebluetooth_gatt_service_free( service ); + return; + } + } + LeaveCriticalSection( &device->props_cs ); + } + LeaveCriticalSection( &radio->remote_devices_cs ); + } + LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_service_free( service ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1122,6 +1164,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED: bluetooth_device_add_gatt_service( event->event_data.gatt_service_added ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED: + bluetooth_gatt_service_remove( event->event_data.gatt_service_removed ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index afacccdfb2c..6ba90f36615 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -222,6 +222,10 @@ NTSTATUS winebluetooth_auth_send_response( winebluetooth_device_t device, BLUETO NTSTATUS winebluetooth_device_start_pairing( winebluetooth_device_t device, IRP *irp );
void winebluetooth_gatt_service_free( winebluetooth_gatt_service_t service ); +static inline BOOL winebluetooth_gatt_service_equal( winebluetooth_gatt_service_t s1, winebluetooth_gatt_service_t s2) +{ + return s1.handle == s2.handle; +}
enum winebluetooth_watcher_event_type { @@ -233,6 +237,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED, BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED, + BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED, };
struct winebluetooth_watcher_event_radio_added @@ -300,6 +305,7 @@ union winebluetooth_watcher_event_data struct winebluetooth_watcher_event_device_props_changed device_props_changed; struct winebluetooth_watcher_event_pairing_finished pairing_finished; struct winebluetooth_watcher_event_gatt_service_added gatt_service_added; + winebluetooth_gatt_service_t gatt_service_removed; };
struct winebluetooth_watcher_event
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 124 +++++++++++++++++++++++++++---- dlls/winebth.sys/unixlib.c | 10 +++ dlls/winebth.sys/unixlib.h | 7 ++ dlls/winebth.sys/winebluetooth.c | 10 +++ dlls/winebth.sys/winebth.c | 81 ++++++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 27 +++++++ include/bthledef.h | 16 ++++ 7 files changed, 261 insertions(+), 14 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 5f736583f79..6e87b42cda1 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -121,6 +121,7 @@ const int bluez_timeout = -1; #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; @@ -857,6 +858,8 @@ struct bluez_watcher_ctx struct list initial_device_list; /* struct bluez_init_entry */ struct list initial_gatt_service_list; + /* struct bluez_init_entry */ + struct list initial_gatt_chars_list;
/* struct bluez_watcher_event */ struct list event_list; @@ -868,6 +871,7 @@ struct bluez_init_entry struct winebluetooth_watcher_event_radio_added radio; struct winebluetooth_watcher_event_device_added device; struct winebluetooth_watcher_event_gatt_service_added service; + struct winebluetooth_watcher_event_gatt_characteristic_added characteristic; } object; struct list entry; }; @@ -1473,26 +1477,25 @@ struct bluez_object_property_masks 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 ) +static UINT16 bluez_dbus_mask_from_string_array( DBusMessageIter *arr_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 )) + while (p_dbus_message_iter_get_arg_type( arr_iter ) != DBUS_TYPE_INVALID) { 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 ); + assert( p_dbus_message_iter_get_arg_type( arr_iter ) == DBUS_TYPE_STRING ); + p_dbus_message_iter_get_basic( arr_iter, &prop_name ); for (i = 0; i < len; i++) { - if (strcmp( prop_masks[i].prop_name, prop_name ) == 0) + if (!strcmp( prop_masks[i].prop_name, prop_name )) mask |= prop_masks[i].mask; }
- p_dbus_message_iter_next( invalid_prop_iter ); + p_dbus_message_iter_next( arr_iter ); }
return mask; @@ -1735,7 +1738,7 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v
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( + props_changed.invalid_props_mask = bluez_dbus_mask_from_string_array( &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, @@ -1817,7 +1820,7 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v } 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( + props_changed.invalid_props_mask = bluez_dbus_mask_from_string_array( &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) @@ -1937,6 +1940,10 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED: unix_name_free( (struct unix_name *)event1->event.gatt_service_removed.handle ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED: + unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.characteristic.handle ); + unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.service.handle ); + break; } free( event1 ); } @@ -1965,6 +1972,7 @@ NTSTATUS bluez_watcher_init( void *connection, void **ctx ) list_init( &watcher_ctx->initial_radio_list ); list_init( &watcher_ctx->initial_device_list ); list_init( &watcher_ctx->initial_gatt_service_list ); + list_init( &watcher_ctx->initial_gatt_chars_list ); list_init( &watcher_ctx->event_list );
/* The bluez_dbus_loop thread will free up the watcher when the disconnect message is processed (i.e, @@ -2020,7 +2028,8 @@ void bluez_watcher_close( void *connection, void *ctx ) }
static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct list *adapter_list, - struct list *device_list, struct list *gatt_service_list ) + struct list *device_list, struct list *gatt_service_list, + struct list *gatt_chars_list ) { DBusMessageIter dict, paths_iter, iface_iter, prop_iter; const char *path; @@ -2199,11 +2208,87 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis TRACE( "Found BlueZ org.bluez.GattService1 object %s %p\n", debugstr_a( path ), service_name ); break; } + else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_CHARACTERISTICS )) + { + const static struct bluez_object_property_masks flag_masks[] = { + { "broadcast", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_BROADCAST }, + { "read", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_READ }, + { "write", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE }, + { "write-without-response", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_WITHOUT_RESPONSE }, + { "notify", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_NOTIFY }, + { "indicate", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_INDICATE }, + { "authenticate-signed-writes", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_SIGNED }, + { "extended-properties", WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_EXTENDED_PROPS }, + }; + struct unix_name *service_name = NULL, *char_name; + struct bluez_init_entry *init_entry; + DBusMessageIter variant; + const char *prop_name; + + init_entry = calloc( 1, sizeof( *init_entry ) ); + if (!init_entry) + { + status = STATUS_NO_MEMORY; + goto done; + } + char_name = unix_name_get_or_create( path ); + if (!char_name) + { + ERR("Failed to allocate memory for characteristic path %s\n", debugstr_a( path )); + free( init_entry ); + goto done; + } + + init_entry->object.characteristic.characteristic.handle = (UINT_PTR)char_name; + while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + 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) + { + DBusMessageIter flags; + p_dbus_message_iter_recurse( &variant, &flags ); + init_entry->object.characteristic.flags = + bluez_dbus_mask_from_string_array( &flags, flag_masks, ARRAY_SIZE( flag_masks ) ); + } + 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 ); + service_name = 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; + p_dbus_message_iter_get_basic( &variant, &uuid_str ); + if (!parse_uuid( &init_entry->object.characteristic.uuid, uuid_str )) + ERR( "Failed to parse UUID %s for GATT characteristic %s\n", debugstr_a( uuid_str ), path ); + } + else if (!strcmp( prop_name, "Handle" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_UINT16) + p_dbus_message_iter_get_basic( &variant, &init_entry->object.characteristic.handle ); + } + if (!service_name) + { + unix_name_free( char_name ); + free( init_entry ); + ERR( "Could not find the associated service for the GATT charcteristic %s\n", debugstr_a( path ) ); + break; + } + init_entry->object.characteristic.service.handle = (UINT_PTR)service_name; + + list_add_tail( gatt_chars_list, &init_entry->entry ); + TRACE( "Found Bluez org.bluez.GattCharacteristic1 object %s %p\n", debugstr_a( path ), char_name ); + break; + } } }
- TRACE( "Initial device list: radios: %d, devices: %d, GATT services: %d\n", list_count( adapter_list ), - list_count( device_list ), list_count( gatt_service_list ) ); + TRACE( "Initial device list: radios: %d, devices: %d, GATT services: %d, characteristics: %d \n", + list_count( adapter_list ), list_count( device_list ), list_count( gatt_service_list ), + list_count( gatt_chars_list ) ); done: return status; } @@ -2243,6 +2328,16 @@ static BOOL bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx, stru free( service ); return TRUE; } + if (!list_empty( &ctx->initial_gatt_chars_list )) + { + struct bluez_init_entry *characteristic; + characteristic = LIST_ENTRY( list_head( &ctx->initial_gatt_chars_list ), struct bluez_init_entry, entry ); + event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED; + event->event_data.gatt_characteristic_added = characteristic->object.characteristic; + list_remove( &characteristic->entry ); + free( characteristic ); + return TRUE; + } if (!list_empty( &ctx->event_list )) { struct bluez_watcher_event *watcher_event = @@ -2340,7 +2435,8 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent, } status = bluez_build_initial_device_lists( reply, &watcher_ctx->initial_radio_list, &watcher_ctx->initial_device_list, - &watcher_ctx->initial_gatt_service_list ); + &watcher_ctx->initial_gatt_service_list, + &watcher_ctx->initial_gatt_chars_list ); p_dbus_message_unref( reply ); if (status != STATUS_SUCCESS) { diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 5e020464ca4..763f2f32346 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -255,6 +255,14 @@ static NTSTATUS bluetooth_gatt_service_free( void *args ) return STATUS_SUCCESS; }
+static NTSTATUS bluetooth_gatt_characteristic_free( void *args ) +{ + struct bluetooth_gatt_characteristic_free_params *params = args; + unix_name_free( params->characteristic ); + return STATUS_SUCCESS; +} + + static NTSTATUS bluetooth_get_event( void *args ) { struct bluetooth_get_event_params *params = args; @@ -284,6 +292,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = {
bluetooth_gatt_service_free,
+ bluetooth_gatt_characteristic_free, + bluetooth_get_event, };
diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index 42783ccf400..4629a7191d4 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -59,6 +59,11 @@ struct bluetooth_gatt_service_free_params unix_name_t service; };
+struct bluetooth_gatt_characteristic_free_params +{ + unix_name_t characteristic; +}; + struct bluetooth_device_disconnect_params { unix_name_t device; @@ -137,6 +142,8 @@ enum bluetoothapis_funcs
unix_bluetooth_gatt_service_free,
+ unix_bluetooth_gatt_characteristic_free, + unix_bluetooth_get_event,
unix_funcs_count diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index add7020a0b3..7762b0f3fd1 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -198,6 +198,16 @@ void winebluetooth_gatt_service_free( winebluetooth_gatt_service_t service ) UNIX_BLUETOOTH_CALL( bluetooth_gatt_service_free, &args ); }
+void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t characteristic ) +{ + struct bluetooth_gatt_characteristic_free_params args = {0}; + + TRACE( "(%p)\n", (void *)characteristic.handle ); + + args.characteristic = characteristic.handle; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_free, &args ); +} + NTSTATUS winebluetooth_get_event( struct winebluetooth_event *result ) { struct bluetooth_get_event_params params = {0}; diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 637a080e470..3b6bb293ac3 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -115,6 +115,16 @@ struct bluetooth_gatt_service GUID uuid; unsigned int primary : 1; UINT16 handle; + + struct list characteristics; +}; + +struct bluetooth_gatt_characteristic +{ + struct list entry; + + winebluetooth_gatt_characteristic_t characteristic; + BTH_LE_GATT_CHARACTERISTIC props; };
enum bluetooth_pdo_ext_type @@ -1062,6 +1072,7 @@ static void bluetooth_device_add_gatt_service( struct winebluetooth_watcher_even service->primary = !!event.is_primary; service->handle = event.attr_handle; bluetooth_device_enable_le_iface( device ); + list_init( &service->characteristics );
EnterCriticalSection( &device->props_cs ); list_add_tail( &device->gatt_services, &service->entry ); @@ -1122,6 +1133,73 @@ static void bluetooth_gatt_service_remove( winebluetooth_gatt_service_t service winebluetooth_gatt_service_free( service ); }
+static void +bluetooth_gatt_service_add_characteristic( struct winebluetooth_watcher_event_gatt_characteristic_added characteristic ) +{ + struct bluetooth_radio *radio; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( radio, &device_list, struct bluetooth_radio, entry ) + { + struct bluetooth_remote_device *device; + + EnterCriticalSection( &radio->remote_devices_cs ); + LIST_FOR_EACH_ENTRY( device, &radio->remote_devices, struct bluetooth_remote_device, entry ) + { + struct bluetooth_gatt_service *svc; + + EnterCriticalSection( &device->props_cs ); + if (!device->le) + { + LeaveCriticalSection( &device->props_cs ); + continue; + } + LIST_FOR_EACH_ENTRY( svc, &device->gatt_services, struct bluetooth_gatt_service, entry ) + { + if (winebluetooth_gatt_service_equal( svc->service, characteristic.service )) + { + struct bluetooth_gatt_characteristic *entry; + UINT32 flags = characteristic.flags; + + if (!(entry = calloc( 1, sizeof( *entry ) ))) + { + LeaveCriticalSection( &device->props_cs ); + LeaveCriticalSection( &radio->remote_devices_cs ); + goto failed; + } + + TRACE( "Adding GATT characteristic %s under service %s for device %p\n", + debugstr_guid( &characteristic.uuid ), debugstr_guid( &svc->uuid ), (void *)device->device.handle ); + + entry->characteristic = characteristic.characteristic; + uuid_to_le( &characteristic.uuid, &entry->props.CharacteristicUuid ); + entry->props.AttributeHandle = characteristic.handle; + entry->props.IsBroadcastable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_BROADCAST); + entry->props.IsReadable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_READ); + entry->props.IsWritable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE); + entry->props.IsNotifiable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_NOTIFY); + entry->props.IsIndicatable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_INDICATE); + entry->props.IsSignedWritable = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_SIGNED); + entry->props.HasExtendedProperties = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_EXTENDED_PROPS); + entry->props.IsWritableWithoutResponse = !!(flags & WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_WITHOUT_RESPONSE); + list_add_tail( &svc->characteristics, &entry->entry ); + LeaveCriticalSection( &device->props_cs ); + LeaveCriticalSection( &radio->remote_devices_cs ); + LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_service_free( characteristic.service ); + return; + } + } + LeaveCriticalSection( &device->props_cs ); + } + LeaveCriticalSection( &radio->remote_devices_cs ); + } +failed: + LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_characteristic_free( characteristic.characteristic ); + winebluetooth_gatt_service_free( characteristic.service ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1167,6 +1245,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED: bluetooth_gatt_service_remove( event->event_data.gatt_service_removed ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED: + bluetooth_gatt_service_add_characteristic( event->event_data.gatt_characteristic_added ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 6ba90f36615..6444678ed18 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -192,6 +192,11 @@ typedef struct UINT_PTR handle; } winebluetooth_gatt_service_t;
+typedef struct +{ + UINT_PTR handle; +} winebluetooth_gatt_characteristic_t; + NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, SIZE_T *size ); void winebluetooth_radio_free( winebluetooth_radio_t radio ); @@ -227,6 +232,8 @@ static inline BOOL winebluetooth_gatt_service_equal( winebluetooth_gatt_service_ return s1.handle == s2.handle; }
+void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t characteristic ); + enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, @@ -238,6 +245,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED, + BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED, };
struct winebluetooth_watcher_event_radio_added @@ -295,6 +303,24 @@ struct winebluetooth_watcher_event_gatt_service_added GUID uuid; };
+#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_BROADCAST 1 +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_READ (1 << 1) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE (1 << 2) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_NOTIFY (1 << 3) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_INDICATE (1 << 4) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_SIGNED (1 << 5) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_EXTENDED_PROPS (1 << 6) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_WITHOUT_RESPONSE (1 << 7) + +struct winebluetooth_watcher_event_gatt_characteristic_added +{ + winebluetooth_gatt_characteristic_t characteristic; + winebluetooth_gatt_service_t service; + GUID uuid; + UINT32 flags; + UINT16 handle; +}; + union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; @@ -306,6 +332,7 @@ union winebluetooth_watcher_event_data struct winebluetooth_watcher_event_pairing_finished pairing_finished; struct winebluetooth_watcher_event_gatt_service_added gatt_service_added; winebluetooth_gatt_service_t gatt_service_removed; + struct winebluetooth_watcher_event_gatt_characteristic_added gatt_characteristic_added; };
struct winebluetooth_watcher_event diff --git a/include/bthledef.h b/include/bthledef.h index d3d0393be99..25bb2055db2 100644 --- a/include/bthledef.h +++ b/include/bthledef.h @@ -34,6 +34,22 @@ typedef struct _BTH_LE_GATT_SERVICE USHORT AttributeHandle; } BTH_LE_GATT_SERVICE, *PBTH_LE_GATT_SERVICE;
+typedef struct _BTH_LE_GATT_CHARACTERISTIC +{ + USHORT ServiceHandle; + BTH_LE_UUID CharacteristicUuid; + USHORT AttributeHandle; + USHORT CharacteristicValueHandle; + BOOLEAN IsBroadcastable; + BOOLEAN IsReadable; + BOOLEAN IsWritable; + BOOLEAN IsWritableWithoutResponse; + BOOLEAN IsSignedWritable; + BOOLEAN IsNotifiable; + BOOLEAN IsIndicatable; + BOOLEAN HasExtendedProperties; +} BTH_LE_GATT_CHARACTERISTIC, *PBTH_LE_GATT_CHARACTERISTIC; + DEFINE_GUID( GUID_BLUETOOTHLE_DEVICE_INTERFACE, 0x781aee18, 0x7733, 0x4ce4, 0xad, 0xd0, 0x91, 0xf4, 0x1c, 0x67, 0xb5, 0x92 ); DEFINE_GUID( BTH_LE_ATT_BLUETOOTH_BASE_GUID, 0, 0, 0x1000, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb ); #endif
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/winebth.c | 85 +++++++++++++++++++++++++++++++++++++- include/wine/winebth.h | 9 ++++ 2 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 3b6bb293ac3..685e70aedc1 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -116,7 +116,8 @@ struct bluetooth_gatt_service unsigned int primary : 1; UINT16 handle;
- struct list characteristics; + CRITICAL_SECTION chars_cs; + struct list characteristics; /* Guarded by chars_cs */ };
struct bluetooth_gatt_characteristic @@ -180,10 +181,34 @@ static void uuid_to_le( const GUID *uuid, BTH_LE_UUID *le_uuid ) } }
+static void le_to_uuid( const BTH_LE_UUID *le_uuid, GUID *uuid ) +{ + if (le_uuid->IsShortUuid) + { + *uuid = BTH_LE_ATT_BLUETOOTH_BASE_GUID; + uuid->Data1 = le_uuid->Value.ShortUuid; + } + else + *uuid = le_uuid->Value.LongUuid; +} + +/* Caller should hold props_cs */ +static struct bluetooth_gatt_service *find_gatt_service( struct list *services, const GUID *uuid, UINT16 handle ) +{ + struct bluetooth_gatt_service *service; + LIST_FOR_EACH_ENTRY( service, services, struct bluetooth_gatt_service, entry ) + { + if (IsEqualGUID( &service->uuid, uuid ) && service->handle == handle) + return service; + } + return NULL; +} + static NTSTATUS bluetooth_remote_device_dispatch( DEVICE_OBJECT *device, struct bluetooth_remote_device *ext, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; + ULONG insize = stack->Parameters.DeviceIoControl.InputBufferLength; ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; NTSTATUS status = irp->IoStatus.Status;
@@ -232,6 +257,53 @@ static NTSTATUS bluetooth_remote_device_dispatch( DEVICE_OBJECT *device, struct status = STATUS_MORE_ENTRIES; break; } + case IOCTL_WINEBTH_LE_DEVICE_GET_GATT_CHARACTERISTICS: + { + const SIZE_T min_size = offsetof( struct winebth_le_device_get_gatt_characteristics_params, characteristics[0] ); + struct winebth_le_device_get_gatt_characteristics_params *chars = irp->AssociatedIrp.SystemBuffer; + struct bluetooth_gatt_characteristic *chrc; + struct bluetooth_gatt_service *service; + SIZE_T rem; + GUID uuid; + + if (!chars || insize < sizeof ( chars->service ) || outsize < sizeof( *chars )) + { + status = STATUS_INVALID_USER_BUFFER; + break; + } + + rem = (outsize - min_size)/sizeof( *chars->characteristics ); + status = STATUS_SUCCESS; + chars->count = 0; + le_to_uuid( &chars->service.ServiceUuid, &uuid ); + + EnterCriticalSection( &ext->props_cs ); + service = find_gatt_service( &ext->gatt_services, &uuid, chars->service.AttributeHandle ); + if (!service) + { + status = STATUS_INVALID_PARAMETER; + LeaveCriticalSection( &ext->props_cs ); + break; + } + + EnterCriticalSection( &service->chars_cs ); + LIST_FOR_EACH_ENTRY( chrc, &service->characteristics, struct bluetooth_gatt_characteristic, entry ) + { + chars->count++; + if (rem) + { + chars->characteristics[chars->count - 1] = chrc->props; + rem--; + } + } + LeaveCriticalSection( &service->chars_cs ); + LeaveCriticalSection( &ext->props_cs ); + + irp->IoStatus.Information = offsetof( struct winebth_le_device_get_gatt_characteristics_params, characteristics[chars->count] ); + if (chars->count > rem) + status = STATUS_MORE_ENTRIES; + break; + } default: FIXME( "Unimplemented IOCTL code: %#lx\n", code ); } @@ -1073,6 +1145,8 @@ static void bluetooth_device_add_gatt_service( struct winebluetooth_watcher_even service->handle = event.attr_handle; bluetooth_device_enable_le_iface( device ); list_init( &service->characteristics ); + InitializeCriticalSectionEx( &service->chars_cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO ); + service->chars_cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": bluetooth_gatt_service.chars_le");
EnterCriticalSection( &device->props_cs ); list_add_tail( &device->gatt_services, &service->entry ); @@ -1115,11 +1189,20 @@ static void bluetooth_gatt_service_remove( winebluetooth_gatt_service_t service { if (winebluetooth_gatt_service_equal( svc->service, service )) { + struct bluetooth_gatt_characteristic *cur, *next; + list_remove( &svc->entry ); LeaveCriticalSection( &device->props_cs ); LeaveCriticalSection( &radio->remote_devices_cs ); LeaveCriticalSection( &device_list_cs ); winebluetooth_gatt_service_free( svc->service ); + svc->chars_cs.DebugInfo->Spare[0] = 0; + DeleteCriticalSection( &svc->chars_cs ); + LIST_FOR_EACH_ENTRY_SAFE( cur, next, &svc->characteristics, struct bluetooth_gatt_characteristic, entry ) + { + winebluetooth_gatt_characteristic_free( cur->characteristic ); + free( cur ); + } free( svc ); winebluetooth_gatt_service_free( service ); return; diff --git a/include/wine/winebth.h b/include/wine/winebth.h index 6a2893d6474..572be91bb45 100644 --- a/include/wine/winebth.h +++ b/include/wine/winebth.h @@ -38,6 +38,8 @@
/* Get all primary GATT services for the LE device. */ #define IOCTL_WINEBTH_LE_DEVICE_GET_GATT_SERVICES CTL_CODE(FILE_DEVICE_BLUETOOTH, 0xc0, METHOD_BUFFERED, FILE_ANY_ACCESS) +/* Get all characteristics for a GATT service */ +#define IOCTL_WINEBTH_LE_DEVICE_GET_GATT_CHARACTERISTICS CTL_CODE(FILE_DEVICE_BLUETOOTH, 0xc1, METHOD_BUFFERED, FILE_ANY_ACCESS)
DEFINE_GUID( GUID_WINEBTH_AUTHENTICATION_REQUEST, 0xca67235f, 0xf621, 0x4c27, 0x85, 0x65, 0xa4, 0xd5, 0x5e, 0xa1, 0x26, 0xe8 ); @@ -84,6 +86,13 @@ struct winebth_le_device_get_gatt_services_params BTH_LE_GATT_SERVICE services[0]; };
+struct winebth_le_device_get_gatt_characteristics_params +{ + BTH_LE_GATT_SERVICE service; + ULONG count; + BTH_LE_GATT_CHARACTERISTIC characteristics[0]; +}; + #pragma pack(pop)
#endif /* __WINEBTH_H__ */
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 19 ++++++++++++ dlls/winebth.sys/winebth.c | 53 +++++++++++++++++++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 7 +++++ 3 files changed, 79 insertions(+)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 6e87b42cda1..bc9b6b56f64 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -1696,6 +1696,22 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v 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 ); } @@ -1944,6 +1960,9 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.characteristic.handle ); unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.service.handle ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED: + unix_name_free( (struct unix_name *)event1->event.gatt_characterisic_removed.handle ); + break; } free( event1 ); } diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 685e70aedc1..11bbe5692a3 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -1283,6 +1283,56 @@ failed: winebluetooth_gatt_service_free( characteristic.service ); }
+static void bluetooth_gatt_characteristic_remove( winebluetooth_gatt_characteristic_t handle ) +{ + struct bluetooth_radio *radio; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( radio, &device_list, struct bluetooth_radio, entry ) + { + struct bluetooth_remote_device *device; + + EnterCriticalSection( &radio->remote_devices_cs ); + LIST_FOR_EACH_ENTRY( device, &radio->remote_devices, struct bluetooth_remote_device, entry ) + { + struct bluetooth_gatt_service *svc; + + EnterCriticalSection( &device->props_cs ); + if (!device->le) + { + LeaveCriticalSection( &device->props_cs ); + continue; + } + LIST_FOR_EACH_ENTRY( svc, &device->gatt_services, struct bluetooth_gatt_service, entry ) + { + struct bluetooth_gatt_characteristic *chrc; + EnterCriticalSection( &svc->chars_cs ); + LIST_FOR_EACH_ENTRY( chrc, &svc->characteristics, struct bluetooth_gatt_characteristic, entry ) + { + if (winebluetooth_gatt_characteristic_equal( chrc->characteristic, handle )) + { + list_remove( &chrc->entry ); + LeaveCriticalSection( &svc->chars_cs ); + LeaveCriticalSection( &device->props_cs ); + LeaveCriticalSection( &radio->remote_devices_cs ); + LeaveCriticalSection( &device_list_cs ); + + winebluetooth_gatt_characteristic_free( chrc->characteristic ); + winebluetooth_gatt_characteristic_free( handle ); + free( chrc ); + return; + } + } + LeaveCriticalSection( &svc->chars_cs ); + } + LeaveCriticalSection( &device->props_cs ); + } + LeaveCriticalSection( &radio->remote_devices_cs ); + } + LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_characteristic_free( handle ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1331,6 +1381,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED: bluetooth_gatt_service_add_characteristic( event->event_data.gatt_characteristic_added ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED: + bluetooth_gatt_characteristic_remove( event->event_data.gatt_characterisic_removed ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 6444678ed18..3b009c6da5d 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -233,6 +233,11 @@ static inline BOOL winebluetooth_gatt_service_equal( winebluetooth_gatt_service_ }
void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t characteristic ); +static inline BOOL winebluetooth_gatt_characteristic_equal( winebluetooth_gatt_characteristic_t c1, + winebluetooth_gatt_characteristic_t c2) +{ + return c1.handle == c2.handle; +}
enum winebluetooth_watcher_event_type { @@ -246,6 +251,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED, + BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED, };
struct winebluetooth_watcher_event_radio_added @@ -333,6 +339,7 @@ union winebluetooth_watcher_event_data struct winebluetooth_watcher_event_gatt_service_added gatt_service_added; winebluetooth_gatt_service_t gatt_service_removed; struct winebluetooth_watcher_event_gatt_characteristic_added gatt_characteristic_added; + winebluetooth_gatt_characteristic_t gatt_characterisic_removed; };
struct winebluetooth_watcher_event
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/bluetoothapis.spec | 2 +- dlls/bluetoothapis/gatt.c | 80 ++++++++++++++++++++++++++- include/bluetoothleapis.h | 1 + 3 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/dlls/bluetoothapis/bluetoothapis.spec b/dlls/bluetoothapis/bluetoothapis.spec index 2cc00f46c9d..9d171a18013 100644 --- a/dlls/bluetoothapis/bluetoothapis.spec +++ b/dlls/bluetoothapis/bluetoothapis.spec @@ -35,7 +35,7 @@ @ stub BluetoothGATTBeginReliableWrite @ stub BluetoothGATTEndReliableWrite @ stub BluetoothGATTGetCharacteristicValue -@ stub BluetoothGATTGetCharacteristics +@ stdcall BluetoothGATTGetCharacteristics(ptr ptr long ptr ptr long) @ stub BluetoothGATTGetDescriptorValue @ stub BluetoothGATTGetDescriptors @ stub BluetoothGATTGetIncludedServices diff --git a/dlls/bluetoothapis/gatt.c b/dlls/bluetoothapis/gatt.c index 12b3ad00f78..9441a0bb3e3 100644 --- a/dlls/bluetoothapis/gatt.c +++ b/dlls/bluetoothapis/gatt.c @@ -38,6 +38,22 @@
WINE_DEFAULT_DEBUG_CHANNEL( bluetoothapis );
+static const char *debugstr_BTH_LE_UUID( const BTH_LE_UUID *uuid ) +{ + if (!uuid) + return wine_dbg_sprintf( "(null)" ); + if (uuid->IsShortUuid) + return wine_dbg_sprintf("{ IsShortUuid=1 {%#x} }", uuid->Value.ShortUuid ); + return wine_dbg_sprintf( "{ IsShortUuid=0 %s }", debugstr_guid( &uuid->Value.LongUuid ) ); +} + +static const char *debugstr_BTH_LE_GATT_SERVICE( const BTH_LE_GATT_SERVICE *svc ) +{ + if (!svc) + return wine_dbg_sprintf( "(null)" ); + return wine_dbg_sprintf( "{ %s %#x }", debugstr_BTH_LE_UUID( &svc->ServiceUuid ), svc->AttributeHandle ); +} + HRESULT WINAPI BluetoothGATTGetServices( HANDLE le_device, USHORT count, BTH_LE_GATT_SERVICE *buf, USHORT *actual, ULONG flags ) { @@ -88,10 +104,72 @@ HRESULT WINAPI BluetoothGATTGetServices( HANDLE le_device, USHORT count, BTH_LE_ return HRESULT_FROM_WIN32( ERROR_MORE_DATA ); }
- memcpy( buf, services->services, min( services_count, count ) ); + memcpy( buf, services->services, min( services_count, count ) * sizeof( *buf ) ); free( services ); if (count < services_count) return HRESULT_FROM_WIN32( ERROR_INVALID_USER_BUFFER );
return S_OK; } + +HRESULT WINAPI BluetoothGATTGetCharacteristics( HANDLE device, BTH_LE_GATT_SERVICE *service, USHORT count, + BTH_LE_GATT_CHARACTERISTIC *buf, USHORT *actual, ULONG flags ) +{ + struct winebth_le_device_get_gatt_characteristics_params *chars; + SIZE_T chars_count = 1; + + TRACE( "(%p, %s, %u, %p, %p, %#lx)\n", device, debugstr_BTH_LE_GATT_SERVICE( service ), count, buf, actual, flags ); + + if (!actual) + return E_POINTER; + + if ((buf && !count) || !actual) + return E_INVALIDARG; + + if (!service) + return E_INVALIDARG; + + for (;;) + { + DWORD size, bytes; + + size = offsetof( struct winebth_le_device_get_gatt_characteristics_params, characteristics[chars_count] ); + chars = calloc( 1, size ); + if (!chars) + return HRESULT_FROM_WIN32( ERROR_NO_SYSTEM_RESOURCES ); + chars->service = *service; + if (!DeviceIoControl( device, IOCTL_WINEBTH_LE_DEVICE_GET_GATT_CHARACTERISTICS, chars, size, chars, + size, &bytes, NULL ) && GetLastError() != ERROR_MORE_DATA) + { + free( chars ); + return HRESULT_FROM_WIN32( GetLastError() ); + } + if (!chars->count) + { + *actual = 0; + free( chars ); + return S_OK; + } + if (chars_count != chars->count) + { + chars_count = chars->count; + free( chars ); + continue; + } + break; + } + + *actual = chars_count; + if (!buf) + { + free( chars ); + return HRESULT_FROM_WIN32( ERROR_MORE_DATA ); + } + + memcpy( buf, chars->characteristics, min( chars_count, count ) * sizeof( *buf ) ); + free( chars ); + if (count < chars_count) + return HRESULT_FROM_WIN32( ERROR_INVALID_USER_BUFFER ); + + return S_OK; +} diff --git a/include/bluetoothleapis.h b/include/bluetoothleapis.h index 64cdd366f7d..e790eb32aa4 100644 --- a/include/bluetoothleapis.h +++ b/include/bluetoothleapis.h @@ -25,6 +25,7 @@ extern "C" { #endif
HRESULT WINAPI BluetoothGATTGetServices( HANDLE, USHORT, BTH_LE_GATT_SERVICE *, USHORT *, ULONG ); +HRESULT WINAPI BluetoothGATTGetCharacteristics( HANDLE, BTH_LE_GATT_SERVICE *, USHORT, BTH_LE_GATT_CHARACTERISTIC *, USHORT *, ULONG );
#ifdef __cplusplus }
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/tests/gatt.c | 85 ++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-)
diff --git a/dlls/bluetoothapis/tests/gatt.c b/dlls/bluetoothapis/tests/gatt.c index 0afe1438141..66b42b9fa2a 100644 --- a/dlls/bluetoothapis/tests/gatt.c +++ b/dlls/bluetoothapis/tests/gatt.c @@ -102,6 +102,49 @@ static const char *debugstr_BTH_LE_GATT_SERVICE( const BTH_LE_GATT_SERVICE *svc return wine_dbg_sprintf( "{ %s %#x }", debugstr_BTH_LE_UUID( &svc->ServiceUuid ), svc->AttributeHandle ); }
+static const char *debugstr_BTH_LE_GATT_CHARACTERISTIC( const BTH_LE_GATT_CHARACTERISTIC *chrc ) +{ + if (!chrc) + return wine_dbg_sprintf( "(null)" ); + return wine_dbg_sprintf( "{ %#x %s %#x %#x %d %d %d %d %d %d %d %d}", chrc->ServiceHandle, + debugstr_BTH_LE_UUID( &chrc->CharacteristicUuid ), chrc->AttributeHandle, + chrc->CharacteristicValueHandle, chrc->IsBroadcastable, chrc->IsReadable, chrc->IsWritable, + chrc->IsWritableWithoutResponse, chrc->IsSignedWritable, chrc->IsNotifiable, + chrc->IsIndicatable, chrc->HasExtendedProperties ); +} + +static void test_service_BluetoothGATTGetCharacteristics( HANDLE device, BTH_LE_GATT_SERVICE *service ) +{ + HRESULT ret; + USHORT actual = 0, actual2 = 0, i; + BTH_LE_GATT_CHARACTERISTIC *buf = NULL, dummy; + + ret = BluetoothGATTGetCharacteristics( device, service, 0, &dummy, &actual, 0 ); + ok( ret == E_INVALIDARG, "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( device, service, 0, NULL, &actual, 0 ); + ok( ret == HRESULT_FROM_WIN32( ERROR_MORE_DATA ), "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( device, service, actual, NULL, &actual2, 0 ); + ok( ret == HRESULT_FROM_WIN32( ERROR_MORE_DATA ), "got ret %#lx\n", ret ); + ok( actual == actual2, "%u != %u\n", actual, actual2 ); + + buf = calloc( actual, sizeof( *buf ) ); + ret = BluetoothGATTGetCharacteristics( device, service, actual, buf, &actual, 0 ); + ok( ret == S_OK, "BluetoothGATTGetCharacteristics failed: %#lx\n", ret ); + if (FAILED( ret )) + { + skip( "BluetoothGATTGetCharacteristics failed.\n" ); + free( buf ); + return; + } + + for (i = 0; i < actual; i++) + trace( "characteristic %u: %s\n", i, debugstr_BTH_LE_GATT_CHARACTERISTIC( &buf[i] ) ); + + free( buf ); +} + static void test_device_BluetoothGATTGetServices( HANDLE device, void *param ) { HRESULT ret; @@ -141,7 +184,12 @@ static void test_device_BluetoothGATTGetServices( HANDLE device, void *param ) }
for (i = 0; i < actual; i++) - trace( "service %u: %s\n", i, debugstr_BTH_LE_GATT_SERVICE( &buf[i] ) ); + { + winetest_push_context( "service %u", i ); + trace( "%s\n", debugstr_BTH_LE_GATT_SERVICE( &buf[i] ) ); + test_service_BluetoothGATTGetCharacteristics( device, &buf[i] ); + winetest_pop_context(); + }
free( buf ); } @@ -156,7 +204,42 @@ static void test_BluetoothGATTGetServices( void ) test_for_all_le_devices( __LINE__, test_device_BluetoothGATTGetServices, NULL ); }
+static void test_device_BluetoothGATTGetCharacteristics( HANDLE device, void *data ) +{ + HRESULT ret; + USHORT actual = 0; + BTH_LE_GATT_SERVICE svc = {.ServiceUuid = {TRUE, {.ShortUuid = 0xdead}}, 0xbeef}; + + ret = BluetoothGATTGetCharacteristics( device, NULL, 0, NULL, NULL, 0 ); + ok( ret == E_POINTER, "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( device, &svc, 0, NULL, NULL, 0 ); + ok( ret == E_POINTER, "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( device, &svc, 0, NULL, &actual, 0 ); + ok( ret == E_INVALIDARG, "got ret %#lx\n", ret ); +} + +static void test_BluetoothGATTGetCharacteristic( void ) +{ + HRESULT ret; + USHORT actual = 0; + BTH_LE_GATT_SERVICE svc; + + ret = BluetoothGATTGetCharacteristics( NULL, NULL, 0, NULL, NULL, 0 ); + ok( ret == E_POINTER, "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( NULL, &svc, 0, NULL, &actual, 0 ); + ok( ret == E_HANDLE, "got ret %#lx\n", ret ); + + ret = BluetoothGATTGetCharacteristics( INVALID_HANDLE_VALUE, &svc, 0, NULL, &actual, 0 ); + ok( ret == E_HANDLE, "got ret %#lx\n", ret ); + + test_for_all_le_devices( __LINE__, test_device_BluetoothGATTGetCharacteristics, NULL ); +} + START_TEST( gatt ) { test_BluetoothGATTGetServices(); + test_BluetoothGATTGetCharacteristic(); }
2/6:
``` +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_BROADCAST 1 +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_READ (1 << 1) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE (1 << 2) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_NOTIFY (1 << 3) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_INDICATE (1 << 4) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_SIGNED (1 << 5) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_EXTENDED_PROPS (1 << 6) +#define WINEBLUETOOTH_GATT_CHARACTERISTIC_FLAGS_WRITE_WITHOUT_RESPONSE (1 << 7) + +struct winebluetooth_watcher_event_gatt_characteristic_added +{ + winebluetooth_gatt_characteristic_t characteristic; + winebluetooth_gatt_service_t service; + GUID uuid; + UINT32 flags; + UINT16 handle; +}; + ```
This looks like a rewrite of BTH_LE_GATT_CHARACTERISTIC. Why not use that struct directly?
3/6:
``` + CRITICAL_SECTION chars_cs; + struct list characteristics; /* Guarded by chars_cs */ ```
In this patch series this is only ever taken under props_cs. Is there a point to having this separate CS? Even if there will be a case where it can be taken by itself, will it really make a measurable performance difference?
(The same can be asked of the four other mutexes already used in this file. Those at least are already used by themselves, but do we really need all of them?)
``` + irp->IoStatus.Information = offsetof( struct winebth_le_device_get_gatt_characteristics_params, characteristics[chars->count] ); ```
You don't actually use this later, but instead check ->count directly in the caller. Since this is a private ioctl you might as well get rid of everything you're not using.
5/6:
``` +static const char *debugstr_BTH_LE_UUID( const BTH_LE_UUID *uuid ) +{ + if (!uuid) + return wine_dbg_sprintf( "(null)" ); + if (uuid->IsShortUuid) + return wine_dbg_sprintf("{ IsShortUuid=1 {%#x} }", uuid->Value.ShortUuid ); + return wine_dbg_sprintf( "{ IsShortUuid=0 %s }", debugstr_guid( &uuid->Value.LongUuid ) ); +} + ```
You're never passing a NULL value to this function. Same with debugstr_BTH_LE_GATT_CHARACTERISTIC() in 6/6.
``` + if (!actual) + return E_POINTER; + + if ((buf && !count) || !actual) + return E_INVALIDARG; ```
That doesn't look right. Also, the spacing is a bit off.
``` + for (;;) ```
Why the loop? The API function only asks for the first N and the total count; you can just allocate a buffer large enough to hold the first N and then return it, can't you?
Do we want a FIXME for the flags?
6/6:
``` + ok( ret == S_OK, "BluetoothGATTGetCharacteristics failed: %#lx\n", ret ); + if (FAILED( ret )) + { + skip( "BluetoothGATTGetCharacteristics failed.\n" ); + free( buf ); + return; + } ```
I always feel that this sort of thing is unnecessary. If a test failed, it needs to be fixed, no matter if there's one failure, many failures, or a crash.
``` + if (!actual) + return E_POINTER; + + if ((buf && !count) || !actual) + return E_INVALIDARG; ```
That doesn't look right.
Same for BluetoothGATTGetServices().
This looks like a rewrite of BTH_LE_GATT_CHARACTERISTIC. Why not use that struct directly?
There are a [bunch of additional flags ](https://github.com/bluez/bluez/blob/master/doc/org.bluez.GattCharacteristic.... `org.bluez.GattCharacteristic` objects that `BTH_LE_GATT_CHARACTERISTIC` does not support. The driver doesn't use them ATM, but they are utilized in the WinRT APIs, so I'll likely add them in the future.
In this patch series this is only ever taken under props_cs. Is there a point to having this separate CS? Even if there will be a case where it can be taken by itself, will it really make a measurable performance difference?
(The same can be asked of the four other mutexes already used in this file. Those at least are already used by themselves, but do we really need all of them?)
So, `chars_cs` does not get used by itself in this series, but is meant for future MRs, where the driver creates and exposes PDOs for individual GATT services on every LE device (which is needed for [`BluetoothGATTGetCharacteristicValue`](https://learn.microsoft.com/en-us/windows/win32/api/bluetoothleapis/nf-bluet...) as it operates on HANDLEs to _services_, not devices).
You don't actually use this later, but instead check ->count directly in the caller. Since this is a private ioctl you might as well get rid of everything you're not using.
Doesn't IoStatus.Information need to be set so that ntoskrnl knows how many bytes to transfer in the reply?
That doesn't look right. Also, the spacing is a bit off.
Oops, thanks.
Why the loop? The API function only asks for the first N and the total count; you can just allocate a buffer large enough to hold the first N and then return it, can't you?
It's the same idea as `IOCTL_BTH_GET_DEVICE_INFO` in bluetoothapis/main.c. This allows for the number of GATT services/characteristics to change between two successive IOCTL calls, which might happens if the system is discovering a LE device that hasn't finished exposing all of its available services/characteristics.
Do we want a FIXME for the flags?
For both BluetoothGetGATTServices and BluetoothGATTGetCharacteristics, the MSDN documentation lists `BLUETOOTH_GATT_FLAG_NONE` as the only flag option, which is 0.
I always feel that this sort of thing is unnecessary. If a test failed, it needs to be fixed, no matter if there's one failure, many failures, or a crash.
Fair, removed this is the latest revision, thanks.