[PATCH v3 0/5] MR10208: winebth.sys part 15: Add support for reading/caching GATT characteristic values
Characteristic values represent data that can be read from or written to. The `winebluetooth_gatt_characteristic_value` struct gets used to send the buffer from Unix to PE land, and has the following structure: ```c #define WINEBLUETOOTH_CHARACTERISTIC_INLINE_SIZE 256 struct winebluetooth_gatt_characteristic_value { UINT32 size; union { BYTE buf[WINEBLUETOOTH_CHARACTERISTIC_INLINE_SIZE]; UINT_PTR handle; }; }; ``` For values <= 256 bytes, the value gets stored inline in `buf`, and copying is trivial. Otherwise, `handle` stores the DBus message containing the value and the `bluetooth_gatt_characteristic_value_move` unixlib function performs a copy and frees the associated DBus message. -- v3: bluetoothapis/tests: Add tests for BluetoothGATTGetCharacteristicValue. bluetoothapis: Implement BluetoothGATTGetCharacteristicValue. winebth.sys: Implement IOCTL_WINEBTH_GATT_SERVICE_READ_CHARACTERISITIC_VALUE. winebth.sys: Update the cached GATT characteristic value on receiving a PropertiesChanged signal for a GATT characteristic value from BlueZ. winebth.sys: Store GATT characteristic value cached by the Unix Bluetooth service on the PE driver. https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/winebth.sys/dbus.c | 76 ++++++++++++++++++++++++++++++-- dlls/winebth.sys/unixlib.c | 17 +++++++ dlls/winebth.sys/unixlib.h | 13 ++++++ dlls/winebth.sys/unixlib_priv.h | 2 + dlls/winebth.sys/winebluetooth.c | 30 ++++++++++++- dlls/winebth.sys/winebth.c | 16 +++++++ dlls/winebth.sys/winebth_priv.h | 10 +++++ include/bthledef.h | 6 +++ 8 files changed, 164 insertions(+), 6 deletions(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index a2b1e1020fb..6001e2a75b1 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -1,8 +1,7 @@ /* * Support for communicating with BlueZ over DBus. * - * Copyright 2024 Vibhav Pant - * Copyright 2025 Vibhav Pant + * 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 @@ -984,6 +983,50 @@ static NTSTATUS bluez_device_get_props_by_path_async( DBusConnection *connection 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; + + 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 ); +} + struct bluez_watcher_ctx { char *bluez_dbus_unique_name; @@ -2247,6 +2290,8 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) 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 ); + bluez_gatt_characteristic_value_free( + (struct bluez_gatt_characteristic_value *)event1->event.gatt_characteristic_added.value.handle ); break; case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED: unix_name_free( (struct unix_name *)event1->event.gatt_characterisic_removed.handle ); @@ -2510,10 +2555,28 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis init_entry->object.characteristic.characteristic.handle = (UINT_PTR)char_name; while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) - bluez_gatt_characteristic_props_from_dict_entry( prop_name, &variant, - &init_entry->object.characteristic ); + { + 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) + { + if (!bluez_gatt_characteristic_value_new_from_iter( reply, &variant, + &init_entry->object.characteristic.value )) + { + unix_name_free( char_name ); + free( init_entry ); + status = STATUS_NO_MEMORY; + goto done; + } + } + else + bluez_gatt_characteristic_props_from_dict_entry( prop_name, &variant, + &init_entry->object.characteristic ); + } if (!init_entry->object.characteristic.service.handle) { + bluez_gatt_characteristic_value_free( + (struct bluez_gatt_characteristic_value *)init_entry->object.characteristic.value.handle ); unix_name_free( char_name ); free( init_entry ); ERR( "Could not find the associated service for the GATT charcteristic %s\n", debugstr_a( path ) ); @@ -2732,5 +2795,10 @@ NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct { return STATUS_NOT_SUPPORTED; } +void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *buf ) +{ + return STATUS_NOT_SUPPORTED; +} +void bluez_gatt_characteristic_value_free( void *val ) { return STATUS_NOT_SUPPORTED; } #endif /* SONAME_LIBDBUS_1 */ diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 530fe432413..4eee0bfa132 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -275,6 +275,21 @@ static NTSTATUS bluetooth_gatt_characteristic_free( void *args ) return STATUS_SUCCESS; } +static NTSTATUS bluetooth_gatt_characteristic_value_move( void *args ) +{ + struct bluetooth_gatt_characteristic_value_move_params *params = args; + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + bluez_gatt_characteristic_value_move( params->val, params->buf ); + return STATUS_SUCCESS; +} + +static NTSTATUS bluetooth_gatt_characteristic_value_free( void *args ) +{ + struct bluetooth_gatt_characteristic_value_free_params *params = args; + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + bluez_gatt_characteristic_value_free( (void *)params->handle ); + return STATUS_SUCCESS; +} static NTSTATUS bluetooth_get_event( void *args ) { @@ -308,6 +323,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_gatt_service_free, bluetooth_gatt_characteristic_free, + bluetooth_gatt_characteristic_value_move, + bluetooth_gatt_characteristic_value_free, bluetooth_get_event, }; diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index 765003a5fc3..beaa6ae2825 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -74,6 +74,17 @@ struct bluetooth_gatt_characteristic_free_params unix_name_t characteristic; }; +struct bluetooth_gatt_characteristic_value_move_params +{ + struct winebluetooth_gatt_characteristic_value *val; + BYTE *buf; +}; + +struct bluetooth_gatt_characteristic_value_free_params +{ + UINT_PTR handle; +}; + struct bluetooth_device_disconnect_params { unix_name_t device; @@ -155,6 +166,8 @@ enum bluetoothapis_funcs unix_bluetooth_gatt_service_free, unix_bluetooth_gatt_characteristic_free, + unix_bluetooth_gatt_characteristic_value_move, + unix_bluetooth_gatt_characteristic_value_free, unix_bluetooth_get_event, diff --git a/dlls/winebth.sys/unixlib_priv.h b/dlls/winebth.sys/unixlib_priv.h index 4348aa6f5df..93e9ea35aa5 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -61,5 +61,7 @@ extern NTSTATUS bluez_auth_agent_send_response( void *auth_agent, struct unix_na extern NTSTATUS bluez_device_disconnect( void *connection, const char *device_path ); extern NTSTATUS bluez_device_start_pairing( void *dbus_connection, void *watcher_ctx, struct unix_name *device, IRP *irp ); extern NTSTATUS bluez_watcher_init( void *connection, void **ctx ); +extern void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *buf ); +extern void bluez_gatt_characteristic_value_free( void *val ); extern void bluez_watcher_close( void *connection, void *ctx ); #endif /* __WINE_WINEBTH_UNIXLIB_PRIV_H */ diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index f91d2b9a440..98c716f98be 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -1,8 +1,7 @@ /* * Wine bluetooth APIs * - * Copyright 2024 Vibhav Pant - * Copyright 2025 Vibhav Pant + * 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 @@ -224,6 +223,33 @@ void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_free, &args ); } +static const char * +debugstr_winebluetooth_gatt_characteristic_value( const struct winebluetooth_gatt_characteristic_value *val ) +{ + return val ? wine_dbg_sprintf( "{ %I32u { %#Ix } }", val->size, val->handle ) : "(null)"; +} + +void winebluetooth_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *val, BYTE *dest ) +{ + struct bluetooth_gatt_characteristic_value_move_params args = {0}; + + TRACE( "(%s, %p)\n", debugstr_winebluetooth_gatt_characteristic_value( val ), dest ); + + args.val = val; + args.buf = dest; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_value_move, &args ); +} + +void winebluetooth_gatt_characteristic_value_free( struct winebluetooth_gatt_characteristic_value *val ) +{ + struct bluetooth_gatt_characteristic_value_free_params args = {0}; + + TRACE( "(%#Ix)\n", val->handle ); + + args.handle = val->handle; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_value_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 510d7fb72b7..ac33b16de71 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -130,6 +130,7 @@ struct bluetooth_gatt_characteristic winebluetooth_gatt_characteristic_t characteristic; BTH_LE_GATT_CHARACTERISTIC props; + BTH_LE_GATT_CHARACTERISTIC_VALUE *value; }; enum bluetooth_pdo_ext_type @@ -1333,6 +1334,18 @@ bluetooth_gatt_service_add_characteristic( struct winebluetooth_watcher_event_ga LeaveCriticalSection( &device->props_cs ); goto failed; } + if (characteristic.value.size) + { + entry->value = calloc( 1, offsetof( BTH_LE_GATT_CHARACTERISTIC_VALUE, Data[characteristic.value.size] ) ); + if (!entry->value) + { + LeaveCriticalSection( &device->props_cs ); + free( entry ); + goto failed; + } + entry->value->DataSize = characteristic.value.size; + winebluetooth_gatt_characteristic_value_move( &characteristic.value, entry->value->Data ); + } TRACE( "Adding GATT characteristic %#x under service %s for device %p\n", characteristic.props.AttributeHandle, debugstr_guid( &svc->uuid ), @@ -1352,6 +1365,7 @@ bluetooth_gatt_service_add_characteristic( struct winebluetooth_watcher_event_ga } failed: LeaveCriticalSection( &device_list_cs ); + winebluetooth_gatt_characteristic_value_free( &characteristic.value ); winebluetooth_gatt_characteristic_free( characteristic.characteristic ); winebluetooth_gatt_service_free( characteristic.service ); } @@ -1390,6 +1404,8 @@ static void bluetooth_gatt_characteristic_remove( winebluetooth_gatt_characteris winebluetooth_gatt_characteristic_free( chrc->characteristic ); winebluetooth_gatt_characteristic_free( handle ); + if (chrc->value) + free( chrc->value ); free( chrc ); return; } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 765fe1d55f7..ff489097081 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -262,6 +262,15 @@ static inline BOOL winebluetooth_gatt_characteristic_equal( winebluetooth_gatt_c return c1.handle == c2.handle; } +struct winebluetooth_gatt_characteristic_value +{ + UINT32 size; + UINT_PTR handle; +}; + +void winebluetooth_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *val, BYTE *dest ); +void winebluetooth_gatt_characteristic_value_free( struct winebluetooth_gatt_characteristic_value *val ); + enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, @@ -346,6 +355,7 @@ struct winebluetooth_watcher_event_gatt_characteristic_added winebluetooth_gatt_characteristic_t characteristic; winebluetooth_gatt_service_t service; BTH_LE_GATT_CHARACTERISTIC props; + struct winebluetooth_gatt_characteristic_value value; }; union winebluetooth_watcher_event_data diff --git a/include/bthledef.h b/include/bthledef.h index 32585c06f8e..3cc9e01410a 100644 --- a/include/bthledef.h +++ b/include/bthledef.h @@ -54,6 +54,12 @@ typedef struct _BTH_LE_GATT_CHARACTERISTIC BOOLEAN HasExtendedProperties; } BTH_LE_GATT_CHARACTERISTIC, *PBTH_LE_GATT_CHARACTERISTIC; +typedef struct _BTH_LE_GATT_CHARACTERISTIC_VALUE +{ + ULONG DataSize; + UCHAR Data[1]; +} BTH_LE_GATT_CHARACTERISTIC_VALUE, *PBTH_LE_GATT_CHARACTERISTIC_VALUE; + DEFINE_GUID( GUID_BLUETOOTHLE_DEVICE_INTERFACE, 0x781aee18, 0x7733, 0x4ce4, 0xad, 0xd0, 0x91, 0xf4, 0x1c, 0x67, 0xb5, 0x92 ); DEFINE_GUID( GUID_BLUETOOTH_GATT_SERVICE_DEVICE_INTERFACE, 0x6e3bb679, 0x4372, 0x40c8, 0x9e, 0xaa, 0x45, 0x09, 0xdf, 0x26, 0x0c, 0xd8 ); DEFINE_GUID( BTH_LE_ATT_BLUETOOTH_BASE_GUID, 0, 0, 0x1000, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/winebth.sys/dbus.c | 50 ++++++++++++++++++++++++ dlls/winebth.sys/winebth.c | 67 ++++++++++++++++++++++++++++++++- dlls/winebth.sys/winebth_priv.h | 8 ++++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 6001e2a75b1..1f52587a4e8 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -2187,6 +2187,51 @@ static void bluez_signal_handler( DBusConnection *conn, DBusMessage *msg, const 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; + } + } } } @@ -2296,6 +2341,11 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED: unix_name_free( (struct unix_name *)event1->event.gatt_characterisic_removed.handle ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED: + unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_value_changed.characteristic.handle ); + bluez_gatt_characteristic_value_free( + (struct bluez_gatt_characteristic_value *)event1->event.gatt_characteristic_added.value.handle ); + break; } free( event1 ); } diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index ac33b16de71..58fe13e4aea 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -1,7 +1,7 @@ /* * Bluetooth bus driver * - * Copyright 2024-2025 Vibhav Pant + * 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 @@ -1419,6 +1419,68 @@ static void bluetooth_gatt_characteristic_remove( winebluetooth_gatt_characteris winebluetooth_gatt_characteristic_free( handle ); } +static void bluetooth_gatt_characteristic_value_update( struct winebluetooth_watcher_event_gatt_characteristic_value_changed event ) +{ + struct bluetooth_radio *radio; + BOOL free_chrc_val = TRUE; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( radio, &device_list, struct bluetooth_radio, entry ) + { + struct bluetooth_remote_device *device; + + 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, event.characteristic )) + { + if (!chrc->value || chrc->value->DataSize < event.value.size) + { + void *tmp; + + tmp = realloc( chrc->value, offsetof( BTH_LE_GATT_CHARACTERISTIC_VALUE, Data[event.value.size] ) ); + if (!tmp) + { + LeaveCriticalSection( &svc->chars_cs ); + LeaveCriticalSection( &device->props_cs ); + goto done; + } + chrc->value = tmp; + } + chrc->value->DataSize = event.value.size; + winebluetooth_gatt_characteristic_value_move( &event.value, chrc->value->Data ); + free_chrc_val = FALSE; + LeaveCriticalSection( &svc->chars_cs ); + LeaveCriticalSection( &device->props_cs ); + goto done; + } + } + LeaveCriticalSection( &svc->chars_cs ); + } + LeaveCriticalSection( &device->props_cs ); + } + } +done: + LeaveCriticalSection( &device_list_cs ); + if (free_chrc_val) + winebluetooth_gatt_characteristic_value_free( &event.value ); + winebluetooth_gatt_characteristic_free( event.characteristic ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1470,6 +1532,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED: bluetooth_gatt_characteristic_remove( event->event_data.gatt_characterisic_removed ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED: + bluetooth_gatt_characteristic_value_update( event->event_data.gatt_characteristic_value_changed ); + 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 ff489097081..2e29b74c87e 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -284,6 +284,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED, + BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED, }; struct winebluetooth_watcher_event_radio_added @@ -358,6 +359,12 @@ struct winebluetooth_watcher_event_gatt_characteristic_added struct winebluetooth_gatt_characteristic_value value; }; +struct winebluetooth_watcher_event_gatt_characteristic_value_changed +{ + winebluetooth_gatt_characteristic_t characteristic; + struct winebluetooth_gatt_characteristic_value value; +}; + union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; @@ -371,6 +378,7 @@ union winebluetooth_watcher_event_data 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_gatt_characteristic_value_changed gatt_characteristic_value_changed; }; struct winebluetooth_watcher_event -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/winebth.sys/dbus.c | 154 ++++++++++++++++++++++++++++--- dlls/winebth.sys/dbus.h | 1 + dlls/winebth.sys/unixlib.c | 9 ++ dlls/winebth.sys/unixlib.h | 13 +++ dlls/winebth.sys/unixlib_priv.h | 2 + dlls/winebth.sys/winebluetooth.c | 10 ++ dlls/winebth.sys/winebth.c | 134 +++++++++++++++++++++++++-- dlls/winebth.sys/winebth_priv.h | 10 ++ include/wine/winebth.h | 12 +++ 9 files changed, 323 insertions(+), 22 deletions(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 1f52587a4e8..dc6e7c74bf8 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -1027,6 +1027,104 @@ void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteris 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 = NULL; + 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; + if (!(data = malloc( sizeof( *data ) ))) + { + status = STATUS_NO_MEMORY; + goto failed; + } + + data->irp = irp; + data->watcher_ctx = watcher_ctx; + 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 (!p_dbus_pending_call_set_notify( pending_call, bluez_gatt_characteristic_read_callback, data, free )) + { + 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 { char *bluez_dbus_unique_name; @@ -1045,6 +1143,40 @@ struct bluez_watcher_ctx 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 ); +} + struct bluez_init_entry { union { @@ -1510,18 +1642,9 @@ NTSTATUS bluez_device_disconnect( void *connection, const char *device_path ) return STATUS_SUCCESS; } -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 ); -struct bluez_device_pair_data -{ - IRP *irp; - struct bluez_watcher_ctx *watcher_ctx; -}; - static void bluez_device_pair_callback( DBusPendingCall *pending, void *param ) { - struct bluez_device_pair_data *data = param; + struct bluez_async_req_data *data = param; DBusMessage *reply; DBusError error; union winebluetooth_watcher_event_data event = {0}; @@ -1545,7 +1668,7 @@ NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct { DBusMessage *request; DBusPendingCall *pending_call = NULL; - struct bluez_device_pair_data *data; + struct bluez_async_req_data *data; dbus_bool_t success; TRACE( "(%p, %p, %s, %p)\n", connection, watcher_ctx, debugstr_a( device->str ), irp ); @@ -2346,6 +2469,10 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) bluez_gatt_characteristic_value_free( (struct bluez_gatt_characteristic_value *)event1->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 *)event1->event.gatt_characteristic_value_read.value.handle ); + break; } free( event1 ); } @@ -2850,5 +2977,8 @@ void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteris return STATUS_NOT_SUPPORTED; } void bluez_gatt_characteristic_value_free( void *val ) { return STATUS_NOT_SUPPORTED; } - +void bluez_gatt_characteristic_read( void *connection, void *watcher, struct unix_name *characteristic, IRP *irp ) +{ + return STATUS_NOT_SUPPORTED; +} #endif /* SONAME_LIBDBUS_1 */ diff --git a/dlls/winebth.sys/dbus.h b/dlls/winebth.sys/dbus.h index 4cbf37883df..511e8caf25b 100644 --- a/dlls/winebth.sys/dbus.h +++ b/dlls/winebth.sys/dbus.h @@ -84,6 +84,7 @@ DO_FUNC(dbus_message_is_signal); \ DO_FUNC(dbus_message_iter_append_basic); \ DO_FUNC(dbus_message_iter_abandon_container); \ + DO_FUNC(dbus_message_iter_abandon_container_if_open); \ DO_FUNC(dbus_message_iter_close_container); \ DO_FUNC(dbus_message_iter_get_arg_type); \ DO_FUNC(dbus_message_iter_get_element_type); \ diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 4eee0bfa132..cda33695bbb 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -275,6 +275,13 @@ static NTSTATUS bluetooth_gatt_characteristic_free( void *args ) return STATUS_SUCCESS; } +static NTSTATUS bluetooth_gatt_characteristic_read( void *args ) +{ + struct bluetooth_gatt_characteristic_read_params *params = args; + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + return bluez_gatt_characteristic_read( dbus_connection, bluetooth_watcher, params->chrc, params->irp ); +} + static NTSTATUS bluetooth_gatt_characteristic_value_move( void *args ) { struct bluetooth_gatt_characteristic_value_move_params *params = args; @@ -323,6 +330,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_gatt_service_free, bluetooth_gatt_characteristic_free, + bluetooth_gatt_characteristic_read, + bluetooth_gatt_characteristic_value_move, bluetooth_gatt_characteristic_value_free, diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index beaa6ae2825..909a4757779 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -69,6 +69,11 @@ struct bluetooth_gatt_service_free_params unix_name_t service; }; +struct bluetooth_gatt_characteristic_dup_params +{ + unix_name_t characteristic; +}; + struct bluetooth_gatt_characteristic_free_params { unix_name_t characteristic; @@ -137,6 +142,12 @@ struct bluetooth_device_start_pairing_params IRP *irp; }; +struct bluetooth_gatt_characteristic_read_params +{ + unix_name_t chrc; + IRP *irp; +}; + struct bluetooth_get_event_params { struct winebluetooth_event result; @@ -166,6 +177,8 @@ enum bluetoothapis_funcs unix_bluetooth_gatt_service_free, unix_bluetooth_gatt_characteristic_free, + unix_bluetooth_gatt_characteristic_read, + unix_bluetooth_gatt_characteristic_value_move, unix_bluetooth_gatt_characteristic_value_free, diff --git a/dlls/winebth.sys/unixlib_priv.h b/dlls/winebth.sys/unixlib_priv.h index 93e9ea35aa5..b0c2059d526 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -63,5 +63,7 @@ extern NTSTATUS bluez_device_start_pairing( void *dbus_connection, void *watcher extern NTSTATUS bluez_watcher_init( void *connection, void **ctx ); extern void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *buf ); extern void bluez_gatt_characteristic_value_free( void *val ); +extern NTSTATUS bluez_gatt_characteristic_read( void *connection, void *watcher_ctx, struct unix_name *characteristic, + IRP *irp ); extern void bluez_watcher_close( void *connection, void *ctx ); #endif /* __WINE_WINEBTH_UNIXLIB_PRIV_H */ diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index 98c716f98be..5bab7230fa3 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -223,6 +223,16 @@ void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_free, &args ); } +NTSTATUS winebluetooth_gatt_characteristic_read_async( winebluetooth_gatt_characteristic_t chrc, IRP *irp ) +{ + struct bluetooth_gatt_characteristic_read_params params = {0}; + + TRACE( "(%p, %p)\n", (void *)chrc.handle, irp ); + params.chrc = chrc.handle; + params.irp = irp; + return UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_read, ¶ms ); +} + static const char * debugstr_winebluetooth_gatt_characteristic_value( const struct winebluetooth_gatt_characteristic_value *val ) { diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 58fe13e4aea..de26da7e2be 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -122,6 +122,8 @@ struct bluetooth_gatt_service CRITICAL_SECTION chars_cs; struct list characteristics; /* Guarded by chars_cs */ + + LIST_ENTRY irp_list; /* Guarded by chars_cs */ }; struct bluetooth_gatt_characteristic @@ -195,6 +197,20 @@ static struct bluetooth_gatt_service *find_gatt_service( struct list *services, return NULL; } +/* Called should hold chars_cs */ +static struct bluetooth_gatt_characteristic *find_gatt_characteristic( struct list *chars, const BTH_LE_UUID *uuid, + UINT16 handle ) +{ + struct bluetooth_gatt_characteristic *chrc; + + LIST_FOR_EACH_ENTRY( chrc, chars, struct bluetooth_gatt_characteristic, entry ) + { + if (IsBthLEUuidMatch( chrc->props.CharacteristicUuid, *uuid ) && chrc->props.AttributeHandle == handle) + return chrc; + } + return NULL; +} + static NTSTATUS bluetooth_gatt_service_get_characteristics( struct bluetooth_gatt_service *service, IRP *irp ) { const SIZE_T min_size = offsetof( struct winebth_le_device_get_gatt_characteristics_params, characteristics[0] ); @@ -233,6 +249,7 @@ static NTSTATUS bluetooth_gatt_service_get_characteristics( struct bluetooth_gat static NTSTATUS bluetooth_gatt_service_dispatch( DEVICE_OBJECT *device, struct bluetooth_gatt_service *ext, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; NTSTATUS status = irp->IoStatus.Status; @@ -252,12 +269,68 @@ static NTSTATUS bluetooth_gatt_service_dispatch( DEVICE_OBJECT *device, struct b status = bluetooth_gatt_service_get_characteristics( ext, irp ); break; } + case IOCTL_WINEBTH_GATT_SERVICE_READ_CHARACTERISITIC_VALUE: + { + struct winebth_gatt_service_read_characterisitic_value_params *params = irp->AssociatedIrp.SystemBuffer; + struct bluetooth_gatt_characteristic *chrc; + + if (!params || outsize < sizeof( *params )) + { + status = STATUS_INVALID_USER_BUFFER; + break; + } + + EnterCriticalSection( &ext->chars_cs ); + chrc = find_gatt_characteristic( &ext->characteristics, ¶ms->uuid, params->handle ); + if (!chrc) + { + status = STATUS_NOT_FOUND; + LeaveCriticalSection( &ext->chars_cs ); + break; + } + if (!chrc->props.IsReadable) + { + status = STATUS_PRIVILEGE_NOT_HELD; + LeaveCriticalSection( &ext->chars_cs ); + break; + } + if (params->from_device || !chrc->value) + { + status = winebluetooth_gatt_characteristic_read_async( chrc->characteristic, irp ); + if (status == STATUS_PENDING) + { + IoMarkIrpPending( irp ); + InsertTailList( &ext->irp_list, &irp->Tail.Overlay.ListEntry ); + } + } + else + { + ULONG needed = offsetof( struct winebth_gatt_service_read_characterisitic_value_params, buf[chrc->value->DataSize] ); + + params->size = chrc->value->DataSize; + if (outsize >= needed) + { + status = STATUS_SUCCESS; + memcpy( params->buf, chrc->value->Data, params->size ); + irp->IoStatus.Information = needed; + } + else + { + status = STATUS_MORE_ENTRIES; + irp->IoStatus.Information = sizeof( *params ); + } + } + LeaveCriticalSection( &ext->chars_cs ); + break; + } default: FIXME( "Unimplemented IOCTL code: %#lx\n", code ); } - - irp->IoStatus.Status = status; - IoCompleteRequest( irp, IO_NO_INCREMENT ); + if (status != STATUS_PENDING) + { + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + } return status; } @@ -1172,9 +1245,7 @@ static void bluetooth_radio_report_auth_event( struct winebluetooth_auth_event e static void complete_irp( IRP *irp, NTSTATUS result ) { - EnterCriticalSection( &device_list_cs ); RemoveEntryList( &irp->Tail.Overlay.ListEntry ); - LeaveCriticalSection( &device_list_cs ); irp->IoStatus.Status = result; IoCompleteRequest( irp, IO_NO_INCREMENT ); @@ -1240,6 +1311,7 @@ static void bluetooth_device_add_gatt_service( struct winebluetooth_watcher_even ext->gatt_service.primary = !!event.is_primary; ext->gatt_service.handle = event.attr_handle; ext->gatt_service.remote_device = device; + InitializeListHead( &ext->gatt_service.irp_list ); list_init( &ext->gatt_service.characteristics ); InitializeCriticalSectionEx( &ext->gatt_service.chars_cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO ); @@ -1481,6 +1553,40 @@ done: winebluetooth_gatt_characteristic_free( event.characteristic ); } +static void bluetooth_gatt_characteristic_value_read_complete_irp( + struct winebluetooth_watcher_event_gatt_characteristic_value_read read ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( read.irp ); + struct bluetooth_pdo_ext *ext = stack->DeviceObject->DeviceExtension; + NTSTATUS status; + + assert( ext->type == BLUETOOTH_PDO_EXT_GATT_SERVICE ); + + if (!(status = read.result)) + { + ULONG needed = offsetof( struct winebth_gatt_service_read_characterisitic_value_params, buf[read.value.size] ); + struct winebth_gatt_service_read_characterisitic_value_params *params = read.irp->AssociatedIrp.SystemBuffer; + ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; + + params->size = read.value.size; + if (outsize >= needed) + { + read.irp->IoStatus.Information = needed; + winebluetooth_gatt_characteristic_value_move( &read.value, params->buf ); + } + else + { + status = STATUS_MORE_ENTRIES; + read.irp->IoStatus.Information = sizeof( *params ); + winebluetooth_gatt_characteristic_value_free( &read.value ); + } + } + + EnterCriticalSection( &ext->gatt_service.chars_cs ); + complete_irp( read.irp, status ); + LeaveCriticalSection( &ext->gatt_service.chars_cs ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1517,8 +1623,10 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) bluetooth_radio_update_device_props( event->event_data.device_props_changed); break; case BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED: + EnterCriticalSection( &device_list_cs ); complete_irp( event->event_data.pairing_finished.irp, event->event_data.pairing_finished.result ); + LeaveCriticalSection( &device_list_cs ); break; case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED: bluetooth_device_add_gatt_service( event->event_data.gatt_service_added ); @@ -1535,6 +1643,10 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED: bluetooth_gatt_characteristic_value_update( event->event_data.gatt_characteristic_value_changed ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_READ: + bluetooth_gatt_characteristic_value_read_complete_irp( + event->event_data.gatt_characteristic_value_read ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } @@ -1778,13 +1890,12 @@ static void bluetooth_radio_set_properties( DEVICE_OBJECT *obj, sizeof( props->version ), &props->version ); } -/* Caller should hold device_list_cs. */ -static void remove_pending_irps( struct bluetooth_radio *radio ) +static void remove_pending_irps( LIST_ENTRY *irp_list ) { LIST_ENTRY *entry; IRP *irp; - while ((entry = RemoveHeadList( &radio->irp_list )) != &radio->irp_list) + while ((entry = RemoveHeadList( irp_list )) != irp_list) { irp = CONTAINING_RECORD( entry, IRP, Tail.Overlay.ListEntry ); irp->IoStatus.Status = STATUS_DELETE_PENDING; @@ -1838,7 +1949,9 @@ static NTSTATUS WINAPI gatt_service_pdo_pnp( DEVICE_OBJECT *device_obj, struct b case IRP_MN_REMOVE_DEVICE: { struct bluetooth_gatt_characteristic *chrc, *next; + assert( ext->removed ); + remove_pending_irps( &ext->irp_list ); if (ext->service_symlink_name.Buffer) { IoSetDeviceInterfaceState( &ext->service_symlink_name, FALSE ); @@ -1857,6 +1970,7 @@ static NTSTATUS WINAPI gatt_service_pdo_pnp( DEVICE_OBJECT *device_obj, struct b } case IRP_MN_SURPRISE_REMOVAL: { + remove_pending_irps( &ext->irp_list ); EnterCriticalSection( &ext->remote_device->props_cs ); if (!ext->removed) { @@ -2067,7 +2181,7 @@ static NTSTATUS WINAPI radio_pdo_pnp( DEVICE_OBJECT *device_obj, struct bluetoot case IRP_MN_REMOVE_DEVICE: assert( device->removed ); EnterCriticalSection( &device_list_cs ); - remove_pending_irps( device ); + remove_pending_irps( &device->irp_list ); LeaveCriticalSection( &device_list_cs ); if (device->bthport_symlink_name.Buffer) @@ -2087,7 +2201,7 @@ static NTSTATUS WINAPI radio_pdo_pnp( DEVICE_OBJECT *device_obj, struct bluetoot break; case IRP_MN_SURPRISE_REMOVAL: EnterCriticalSection( &device_list_cs ); - remove_pending_irps( device ); + remove_pending_irps( &device->irp_list ); if (!device->removed) { device->removed = TRUE; diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 2e29b74c87e..85ad4e28f64 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -270,6 +270,7 @@ struct winebluetooth_gatt_characteristic_value void winebluetooth_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *val, BYTE *dest ); void winebluetooth_gatt_characteristic_value_free( struct winebluetooth_gatt_characteristic_value *val ); +NTSTATUS winebluetooth_gatt_characteristic_read_async( winebluetooth_gatt_characteristic_t chrc, IRP *irp ); enum winebluetooth_watcher_event_type { @@ -285,6 +286,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_CHANGED, + BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_VALUE_READ, }; struct winebluetooth_watcher_event_radio_added @@ -365,6 +367,13 @@ struct winebluetooth_watcher_event_gatt_characteristic_value_changed struct winebluetooth_gatt_characteristic_value value; }; +struct winebluetooth_watcher_event_gatt_characteristic_value_read +{ + IRP *irp; + struct winebluetooth_gatt_characteristic_value value; + NTSTATUS result; +}; + union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; @@ -379,6 +388,7 @@ union winebluetooth_watcher_event_data struct winebluetooth_watcher_event_gatt_characteristic_added gatt_characteristic_added; winebluetooth_gatt_characteristic_t gatt_characterisic_removed; struct winebluetooth_watcher_event_gatt_characteristic_value_changed gatt_characteristic_value_changed; + struct winebluetooth_watcher_event_gatt_characteristic_value_read gatt_characteristic_value_read; }; struct winebluetooth_watcher_event diff --git a/include/wine/winebth.h b/include/wine/winebth.h index 572be91bb45..c41f0362f55 100644 --- a/include/wine/winebth.h +++ b/include/wine/winebth.h @@ -41,6 +41,9 @@ /* 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) +/* Read the associated value for a GATT characteristic */ +#define IOCTL_WINEBTH_GATT_SERVICE_READ_CHARACTERISITIC_VALUE CTL_CODE(FILE_DEVICE_BLUETOOTH, 0xd0, METHOD_BUFFERED, FILE_ANY_ACCESS) + DEFINE_GUID( GUID_WINEBTH_AUTHENTICATION_REQUEST, 0xca67235f, 0xf621, 0x4c27, 0x85, 0x65, 0xa4, 0xd5, 0x5e, 0xa1, 0x26, 0xe8 ); @@ -93,6 +96,15 @@ struct winebth_le_device_get_gatt_characteristics_params BTH_LE_GATT_CHARACTERISTIC characteristics[0]; }; +struct winebth_gatt_service_read_characterisitic_value_params +{ + BTH_LE_UUID uuid; + UINT16 handle; + unsigned int from_device : 1; + ULONG size; + BYTE buf[1]; +}; + #pragma pack(pop) #endif /* __WINEBTH_H__ */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/bluetoothapis/bluetoothapis.spec | 2 +- dlls/bluetoothapis/gatt.c | 72 ++++++++++++++++++++++++++- include/bluetoothleapis.h | 1 + include/bthledef.h | 4 ++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/dlls/bluetoothapis/bluetoothapis.spec b/dlls/bluetoothapis/bluetoothapis.spec index 9d171a18013..6a4af0ad34e 100644 --- a/dlls/bluetoothapis/bluetoothapis.spec +++ b/dlls/bluetoothapis/bluetoothapis.spec @@ -34,7 +34,7 @@ @ stub BluetoothGATTAbortReliableWrite @ stub BluetoothGATTBeginReliableWrite @ stub BluetoothGATTEndReliableWrite -@ stub BluetoothGATTGetCharacteristicValue +@ stdcall BluetoothGATTGetCharacteristicValue(ptr ptr long ptr ptr long) @ stdcall BluetoothGATTGetCharacteristics(ptr ptr long ptr ptr long) @ stub BluetoothGATTGetDescriptorValue @ stub BluetoothGATTGetDescriptors diff --git a/dlls/bluetoothapis/gatt.c b/dlls/bluetoothapis/gatt.c index 38cd8a1694e..225d95a5749 100644 --- a/dlls/bluetoothapis/gatt.c +++ b/dlls/bluetoothapis/gatt.c @@ -1,7 +1,7 @@ /* * BLE Generic Attribute Profile (GATT) APIs * - * Copyright 2025 Vibhav Pant + * Copyright 2025-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 @@ -144,3 +144,73 @@ HRESULT WINAPI BluetoothGATTGetCharacteristics( HANDLE device, BTH_LE_GATT_SERVI return HRESULT_FROM_WIN32( ERROR_INVALID_USER_BUFFER ); return S_OK; } + +HRESULT WINAPI BluetoothGATTGetCharacteristicValue( HANDLE device, BTH_LE_GATT_CHARACTERISTIC *chrc, ULONG size, + BTH_LE_GATT_CHARACTERISTIC_VALUE *val, USHORT *actual, ULONG flags ) +{ + struct winebth_gatt_service_read_characterisitic_value_params *params; + DWORD bytes, outsize, err; + OVERLAPPED ovl = {0}; + HRESULT ret; + + TRACE( "(%p, %p, %lu, %p, %p, %#lx)\n", device, chrc, size, val, actual, flags ); + + if (!device) + return E_HANDLE; + if (!chrc || !(size || val || actual) || (size && size < sizeof( *val ))) + return E_INVALIDARG; + if (size && !val) + return E_POINTER; + outsize = size ? offsetof( struct winebth_gatt_service_read_characterisitic_value_params, + buf[size - offsetof( BTH_LE_GATT_CHARACTERISTIC_VALUE, Data )] ) + : sizeof( *params ); + + if (!(params = calloc( 1, outsize ))) + return HRESULT_FROM_WIN32( ERROR_NO_SYSTEM_RESOURCES ); + + /* FIXME: Figure out what native does when both flags are set. */ + if (flags & BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE) + params->from_device = 1; + if (flags & BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_CACHE) + params->from_device = 0; + + params->uuid = chrc->CharacteristicUuid; + params->handle = chrc->AttributeHandle; + ovl.hEvent = CreateEventW( NULL, TRUE, FALSE, NULL ); + err = ERROR_SUCCESS; + if (!DeviceIoControl( device, IOCTL_WINEBTH_GATT_SERVICE_READ_CHARACTERISITIC_VALUE, params, sizeof( *params ), + params, outsize, &bytes, &ovl )) + { + err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + err = ERROR_SUCCESS; + if (!GetOverlappedResult( device, &ovl, &bytes, TRUE )) + err = GetLastError(); + } + } + CloseHandle( ovl.hEvent ); + + if (err) + { + free( params ); + return HRESULT_FROM_WIN32( err == ERROR_PRIVILEGE_NOT_HELD ? ERROR_INVALID_ACCESS : err ); + } + + ret = S_OK; + *actual = max( offsetof( BTH_LE_GATT_CHARACTERISTIC_VALUE, Data[params->size] ), sizeof( *val ) ); + if (val) + { + if (size >= params->size) + { + val->DataSize = params->size; + memcpy( val->Data, params->buf, params->size ); + } + else + ret = HRESULT_FROM_WIN32( ERROR_INVALID_USER_BUFFER ); + } + else + ret = HRESULT_FROM_WIN32( ERROR_MORE_DATA ); + free( params ); + return ret; +} diff --git a/include/bluetoothleapis.h b/include/bluetoothleapis.h index e790eb32aa4..510a35aad2a 100644 --- a/include/bluetoothleapis.h +++ b/include/bluetoothleapis.h @@ -26,6 +26,7 @@ extern "C" { 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 ); +HRESULT WINAPI BluetoothGATTGetCharacteristicValue( HANDLE, BTH_LE_GATT_CHARACTERISTIC *, ULONG, BTH_LE_GATT_CHARACTERISTIC_VALUE *, USHORT *, ULONG ); #ifdef __cplusplus } diff --git a/include/bthledef.h b/include/bthledef.h index 3cc9e01410a..aa2121cb10d 100644 --- a/include/bthledef.h +++ b/include/bthledef.h @@ -22,6 +22,10 @@ extern "C" { #endif +#define BLUETOOTH_GATT_FLAG_NONE 0 +#define BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE 0x0004 +#define BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_CACHE 0x0008 + typedef struct _BTH_LE_UUID { BOOLEAN IsShortUuid; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/bluetoothapis/tests/gatt.c | 84 ++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/dlls/bluetoothapis/tests/gatt.c b/dlls/bluetoothapis/tests/gatt.c index d442ab21f67..58c0f426383 100644 --- a/dlls/bluetoothapis/tests/gatt.c +++ b/dlls/bluetoothapis/tests/gatt.c @@ -1,7 +1,7 @@ /* * Tests for Bluetooth GATT methods * - * Copyright 2025 Vibhav Pant + * Copyright 2025-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 @@ -118,6 +118,80 @@ static const char *debugstr_BTH_LE_GATT_CHARACTERISTIC( const BTH_LE_GATT_CHARAC chrc->IsIndicatable, chrc->HasExtendedProperties ); } +static BTH_LE_GATT_CHARACTERISTIC_VALUE *get_characteristic_value( int line, HANDLE service, BTH_LE_GATT_CHARACTERISTIC *chrc, ULONG flags ) +{ + BTH_LE_GATT_CHARACTERISTIC_VALUE *val; + USHORT actual; + HRESULT ret; + ULONG size; + + ret = BluetoothGATTGetCharacteristicValue( service, chrc, 0, NULL, &actual, flags ); + ok_( __FILE__, line )( HRESULT_CODE( ret ) == ERROR_MORE_DATA, "%lu != %d\n", HRESULT_CODE( ret ), ERROR_MORE_DATA ); + if (FAILED( ret ) && HRESULT_CODE( ret ) != ERROR_MORE_DATA) + return NULL; + + size = actual; + val = calloc( 1, size ); + ret = BluetoothGATTGetCharacteristicValue( service, chrc, size, val, &actual, flags ); + ok_( __FILE__, line )( ret == S_OK, "BluetoothGATTGetCharacteristicValue failed: %#lx\n", ret ); + if (FAILED( ret )) + { + free( val ); + return NULL; + } + ok_( __FILE__, line )( actual == size, "%u != %lu\n", actual, size ); + return val; +} + +static void test_service_BluetoothGATTGetCharacteristicValue( HANDLE service, BTH_LE_GATT_CHARACTERISTIC *chrc ) +{ + BTH_LE_GATT_CHARACTERISTIC_VALUE *val1, *val2; + char buf[5]; + USHORT actual = 0; + HRESULT ret; + + ret = BluetoothGATTGetCharacteristicValue( NULL, NULL, 0, NULL, NULL, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_HANDLE, "%lu != %d\n", HRESULT_CODE( ret ), ERROR_INVALID_HANDLE ); + ret = BluetoothGATTGetCharacteristicValue( service, NULL, 0, NULL, NULL, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_PARAMETER, "%lu != %d\n", HRESULT_CODE ( ret ), ERROR_INVALID_PARAMETER ); + ret = BluetoothGATTGetCharacteristicValue( service, NULL, 0, NULL, &actual, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_PARAMETER, "%lu != %d\n", HRESULT_CODE ( ret ), ERROR_INVALID_PARAMETER ); + ret = BluetoothGATTGetCharacteristicValue( service, chrc, 0, NULL, NULL, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_PARAMETER, "%lu != %d\n", HRESULT_CODE ( ret ), ERROR_INVALID_PARAMETER ); + ret = BluetoothGATTGetCharacteristicValue( service, chrc, sizeof( buf ), (void *)&buf, &actual, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_PARAMETER, "%lu != %d\n", HRESULT_CODE( ret ), ERROR_INVALID_PARAMETER ); + ret = BluetoothGATTGetCharacteristicValue( service, chrc, 100, NULL, NULL, 0 ); + ok( ret == E_POINTER, "%#lx != %#lx\n", ret, E_POINTER ); + ret = BluetoothGATTGetCharacteristicValue( service, chrc, 100, NULL, &actual, 0 ); + ok( ret == E_POINTER, "%#lx != %#lx\n", ret, E_POINTER ); + + if (!chrc->IsReadable) + { + ret = BluetoothGATTGetCharacteristicValue( service, chrc, 0, NULL, &actual, 0 ); + ok( HRESULT_CODE( ret ) == ERROR_INVALID_ACCESS, "%lu != %d\n", HRESULT_CODE( ret ), ERROR_INVALID_ACCESS ); + return; + } + + val1 = get_characteristic_value( __LINE__, service, chrc, 0 ); + if (val1) + trace( "value: %lu bytes\n", val1->DataSize ); + free( val1 ); + + val1 = get_characteristic_value( __LINE__, service, chrc, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE ); + val2 = get_characteristic_value( __LINE__, service, chrc, BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_CACHE ); + if (val1 && val2) + { + ok( val1->DataSize == val2->DataSize, "%lu != %lu\n", val1->DataSize, val2->DataSize ); + if (val1->DataSize == val2->DataSize) + ok( !memcmp( val1->Data, val2->Data, val1->DataSize ), + "Cached value does not match value previously read from device.\n" ); + } + else + skip( "Couldn't read characteristic value.\n" ); + free( val1 ); + free( val2 ); +} + static void test_service_BluetoothGATTGetCharacteristics( HANDLE service, const BTH_LE_GATT_CHARACTERISTIC *chars, USHORT len ) { HRESULT ret; @@ -204,7 +278,13 @@ static void test_BluetoothGATTGetCharacteristics( HANDLE device, HANDLE service, ok( ret == S_OK, "BluetoothGATTGetCharacteristics failed: %#lx\n", ret ); for (i = 0; i < actual; i++) - trace( "characteristic %u: %s\n", i, debugstr_BTH_LE_GATT_CHARACTERISTIC( &buf[i] ) ); + { + winetest_push_context( "characteristic %u", i ); + trace( "%s\n", debugstr_BTH_LE_GATT_CHARACTERISTIC( &buf[i] ) ); + if (service) + test_service_BluetoothGATTGetCharacteristicValue( service, &buf[i] ); + winetest_pop_context(); + } if (service) test_service_BluetoothGATTGetCharacteristics( service, buf, actual ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
On Mon Mar 2 17:51:24 2026 +0000, Elizabeth Figura wrote:
This, and a lot of things honestly, seem like premature optimization. Even if the cost of a Unix call weren't already small in comparison with the server I/O, or indeed the actual hardware I/O, I think it'd still be a mistake to be adding extra code to optimize before we have any evidence it matters. Same sort of thing about splitting up all the locks, which I've already talked about. It seems unlikely we need more than one; we don't _know_ we need more than one, and the extra complexity just makes things harder and risks deadlocks. Sorry for the delay. I have removed the inline case in the latest revision.
Same sort of thing about splitting up all the locks, which I've already talked about. It seems unlikely we need more than one; we don't _know_ we need more than one, and the extra complexity just makes things harder and risks deadlocks.
The only reason why I used `device_list_cs` was because that's what we use for the pending IRP list for a radio in `struct bluetooth_radio` as well, so I thought it would be necessary to do that here too. I realize that's not really required, so I have replaced it with `chars_cs`, which we already hold here. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208#note_133234
v2: * Rebase against master. * Remove inline storage case in `struct winebluetooth_gatt_characteristic_value`. * Simplify locking code for `IOCTL_WINEBTH_GATT_SERVICE_READ_CHARACTERISITIC_VALUE` by using `chars_cs` to guard `bluetooth_gatt_service.irp_list`. * Use `complete_irp` and `remove_pending_irps` for all IRP lists, not just `bluetooth_radio`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208#note_133236
participants (2)
-
Vibhav Pant -
Vibhav Pant (@vibhavp)