[PATCH v4 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.~~ -- v4: 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 | 73 ++++++++++++++++++++++++++++++-- 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, 161 insertions(+), 6 deletions(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index a2b1e1020fb..cc3da715a3c 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,7 @@ 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 ) {} +void bluez_gatt_characteristic_value_free( void *val ) {} #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 cc3da715a3c..fec27989677 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 fec27989677..25fc4e2eae7 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 ); } @@ -2847,5 +2974,8 @@ NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct } void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteristic_value *value, BYTE *buf ) {} void bluez_gatt_characteristic_value_free( void *val ) {} - +NTSTATUS bluez_gatt_characteristic_read( void *connection, void *watcher, struct unix_name *characteristic, IRP *irp ) +{ + return STATUS_NOT_SUPPORTED; +} #endif /* SONAME_LIBDBUS_1 */ 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
This merge request was approved by Elizabeth Figura. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
participants (2)
-
Elizabeth Figura (@zfigura) -
Vibhav Pant -
Vibhav Pant (@vibhavp)