[PATCH 0/5] MR10208: winebth.sys part 15: Add support for reading/caching GATT characteristic value
Characteristic values represent data that can either 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. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208
From: Vibhav Pant <vibhavp@gmail.com> --- dlls/winebth.sys/dbus.c | 86 ++++++++++++++++++++++++++++++-- dlls/winebth.sys/unixlib.c | 17 +++++++ dlls/winebth.sys/unixlib.h | 13 +++++ dlls/winebth.sys/unixlib_priv.h | 2 + dlls/winebth.sys/winebluetooth.c | 38 +++++++++++++- dlls/winebth.sys/winebth.c | 16 ++++++ dlls/winebth.sys/winebth_priv.h | 21 ++++++++ include/bthledef.h | 6 +++ 8 files changed, 193 insertions(+), 6 deletions(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 3dcd108c393..881621e8ca9 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,56 @@ 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 */ +}; + +/* bytes_iter must be _inside_ the byte array (see the documentation for dbus_message_iter_get_fixed_array). + * message should point to the DBus message containing the byte array value. */ +static BOOL bluez_gatt_characteristic_value_new_from_iter( DBusMessage *message, DBusMessageIter *bytes_iter, + struct winebluetooth_gatt_characteristic_value *value ) +{ + const BYTE *buf; + int size; + + TRACE_( dbus )( "(%s, %s, %p)\n", dbgstr_dbus_message( message ), dbgstr_dbus_iter( bytes_iter ), value ); + + p_dbus_message_iter_get_fixed_array( bytes_iter, &buf, &size ); + value->size = size; + if (size > sizeof( value->buf )) + { + struct bluez_gatt_characteristic_value *dbus_val; + + if (!(dbus_val = calloc( 1, sizeof( *dbus_val ) ))) + return FALSE; + dbus_val->buf = buf; + dbus_val->message = p_dbus_message_ref( message ); + value->handle = (UINT_PTR)dbus_val; + } + else if (size) + memcpy( value->buf, buf, size ); + 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 *dbus_val = (struct bluez_gatt_characteristic_value *)value->handle; + + assert( !winebluetooth_gatt_characteristic_value_is_inline( value ) ); + memcpy( dest, value->buf, value->size ); + bluez_gatt_characteristic_value_free( dbus_val ); +} + struct bluez_watcher_ctx { void *init_device_list_call; @@ -2185,6 +2234,9 @@ 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 ); + if (!winebluetooth_gatt_characteristic_value_is_inline( &event1->event.gatt_characteristic_added.value )) + 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 ); @@ -2439,10 +2491,31 @@ 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) + { + DBusMessageIter bytes_iter; + p_dbus_message_iter_recurse( &variant, &bytes_iter ); + if (!bluez_gatt_characteristic_value_new_from_iter( reply, &bytes_iter, + &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) { + if (!winebluetooth_gatt_characteristic_value_is_inline( &init_entry->object.characteristic.value )) + 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 ) ); @@ -2661,5 +2734,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 0045db54bcf..c11000859bd 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -276,6 +276,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( params->val ); + return STATUS_SUCCESS; +} static NTSTATUS bluetooth_get_event( void *args ) { @@ -309,6 +324,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..2b2ecd919de 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 +{ + struct winebluetooth_gatt_characteristic_value *val; +}; + 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 3d9fc34a585..d920e7fecbf 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -62,5 +62,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 3da8d76a8fb..4a89d871564 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 @@ -225,6 +224,41 @@ 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 { %p } }", val->size, (void *)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 ); + + if (!winebluetooth_gatt_characteristic_value_is_inline( val )) + { + args.val = val; + args.buf = dest; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_value_move, &args ); + } + else if (val->size) + memcpy( dest, val->buf, val->size ); +} + +void winebluetooth_gatt_characteristic_value_free( struct winebluetooth_gatt_characteristic_value *val ) +{ + struct bluetooth_gatt_characteristic_value_free_params args = {0}; + + TRACE( "(%s)\n", debugstr_winebluetooth_gatt_characteristic_value( val ) ); + + if (!winebluetooth_gatt_characteristic_value_is_inline( val )) + { + args.val = val; + 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 56e6296bf60..84ed2b91256 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -131,6 +131,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 @@ -1334,6 +1335,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 ), @@ -1353,6 +1366,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 ); } @@ -1391,6 +1405,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..5b13f78aa59 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -262,6 +262,26 @@ static inline BOOL winebluetooth_gatt_characteristic_equal( winebluetooth_gatt_c return c1.handle == c2.handle; } +#define WINEBLUETOOTH_CHARACTERISTIC_INLINE_SIZE 256 + +struct winebluetooth_gatt_characteristic_value +{ + UINT32 size; + /* If size <= WINEBLUETOOTH_CHARACTERISTIC_INLINE_SIZE, then the value is stored in buf. + * Otherwise, the Unix handle gets used to perform a copy through the unixlib. */ + union { + BYTE buf[WINEBLUETOOTH_CHARACTERISTIC_INLINE_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 ); +static inline BOOL winebluetooth_gatt_characteristic_value_is_inline( const struct winebluetooth_gatt_characteristic_value *val ) +{ + return val->size <= ARRAY_SIZE( val->buf ); +} + enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, @@ -346,6 +366,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 | 55 +++++++++++++++++++++++++++ dlls/winebth.sys/winebth.c | 67 ++++++++++++++++++++++++++++++++- dlls/winebth.sys/winebth_priv.h | 8 ++++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 881621e8ca9..efc264e41ef 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -2148,6 +2148,55 @@ 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) + { + DBusMessageIter bytes_iter; + + p_dbus_message_iter_recurse( &variant, &bytes_iter ); + if (!bluez_gatt_characteristic_value_new_from_iter( msg, &bytes_iter, &changed_event.value )) + return; + val_changed = TRUE; + } + } + 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 ) ); + if (!winebluetooth_gatt_characteristic_value_is_inline( &changed_event.value )) + 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 ); + if (!winebluetooth_gatt_characteristic_value_is_inline( &changed_event.value )) + bluez_gatt_characteristic_value_free( (struct bluez_gatt_characteristic_value *)changed_event.value.handle ); + return; + } + } } } @@ -2241,6 +2290,12 @@ 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 ); + if (!winebluetooth_gatt_characteristic_value_is_inline( &event1->event.gatt_characteristic_added.value )) + 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 84ed2b91256..6fcc79a0753 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 @@ -1420,6 +1420,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; @@ -1471,6 +1533,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 5b13f78aa59..3e32ae9470b 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -295,6 +295,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 @@ -369,6 +370,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; @@ -382,6 +389,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 | 160 ++++++++++++++++++++++++++++--- dlls/winebth.sys/dbus.h | 1 + dlls/winebth.sys/unixlib.c | 17 ++++ dlls/winebth.sys/unixlib.h | 14 +++ dlls/winebth.sys/unixlib_priv.h | 2 + dlls/winebth.sys/winebluetooth.c | 20 ++++ dlls/winebth.sys/winebth.c | 118 ++++++++++++++++++++++- dlls/winebth.sys/winebth_priv.h | 11 +++ include/wine/winebth.h | 12 +++ 9 files changed, 340 insertions(+), 15 deletions(-) diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index efc264e41ef..4bc9c2b0340 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -1033,6 +1033,104 @@ void bluez_gatt_characteristic_value_move( struct winebluetooth_gatt_characteris bluez_gatt_characteristic_value_free( dbus_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" )) + return STATUS_BTH_ATT_UNKNOWN_ERROR; + 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 ); + free( data ); + return status; +} + struct bluez_watcher_ctx { void *init_device_list_call; @@ -1050,6 +1148,44 @@ 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, bytes_iter; + + p_dbus_message_iter_init( reply, &iter ); + p_dbus_message_iter_recurse( &iter, &bytes_iter ); + if (!bluez_gatt_characteristic_value_new_from_iter( reply, &bytes_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 )) + { + if (!winebluetooth_gatt_characteristic_value_is_inline( &read.value )) + 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 { @@ -1471,18 +1607,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}; @@ -1506,7 +1633,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 ); @@ -2296,6 +2423,11 @@ 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: + if (!winebluetooth_gatt_characteristic_value_is_inline( &event1->event.gatt_characteristic_value_read.value )) + bluez_gatt_characteristic_value_free( (struct bluez_gatt_characteristic_value *) + event1->event.gatt_characteristic_value_read.value.handle ); + break; } free( event1 ); } @@ -2552,6 +2684,7 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis && p_dbus_message_iter_get_element_type( &variant ) == DBUS_TYPE_BYTE) { DBusMessageIter bytes_iter; + p_dbus_message_iter_recurse( &variant, &bytes_iter ); if (!bluez_gatt_characteristic_value_new_from_iter( reply, &bytes_iter, &init_entry->object.characteristic.value )) @@ -2794,5 +2927,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 1232c87caf9..5b786524091 100644 --- a/dlls/winebth.sys/dbus.h +++ b/dlls/winebth.sys/dbus.h @@ -83,6 +83,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 c11000859bd..07aa6f8388d 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -269,6 +269,13 @@ static NTSTATUS bluetooth_gatt_service_free( void *args ) return STATUS_SUCCESS; } +static NTSTATUS bluetooth_gatt_characteristic_dup( void *args ) +{ + struct bluetooth_gatt_characteristic_dup_params *params = args; + unix_name_dup( params->characteristic ); + return STATUS_SUCCESS; +} + static NTSTATUS bluetooth_gatt_characteristic_free( void *args ) { struct bluetooth_gatt_characteristic_free_params *params = args; @@ -276,6 +283,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,7 +337,10 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_gatt_service_free, + bluetooth_gatt_characteristic_dup, 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 2b2ecd919de..d76593609dc 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; @@ -165,7 +176,10 @@ enum bluetoothapis_funcs unix_bluetooth_gatt_service_free, + unix_bluetooth_gatt_characteristic_dup, 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 d920e7fecbf..085da8c90c7 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -64,5 +64,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 4a89d871564..973a2053452 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -214,6 +214,16 @@ void winebluetooth_gatt_service_free( winebluetooth_gatt_service_t service ) UNIX_BLUETOOTH_CALL( bluetooth_gatt_service_free, &args ); } +void winebluetooth_gatt_characteristic_dup( winebluetooth_gatt_characteristic_t characteristic ) +{ + struct bluetooth_gatt_characteristic_dup_params args = {0}; + + TRACE( "(%p)\n", (void *)characteristic.handle ); + + args.characteristic = characteristic.handle; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_characteristic_dup, &args ); +} + void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t characteristic ) { struct bluetooth_gatt_characteristic_free_params args = {0}; @@ -224,6 +234,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 6fcc79a0753..60c7733cfd3 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -123,6 +123,8 @@ struct bluetooth_gatt_service CRITICAL_SECTION chars_cs; struct list characteristics; /* Guarded by chars_cs */ + + LIST_ENTRY irp_list; /* Guarded by device_list_cs */ }; struct bluetooth_gatt_characteristic @@ -196,6 +198,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] ); @@ -234,6 +250,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; @@ -253,12 +270,75 @@ 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; + } + + irp->IoStatus.Information = sizeof( *params ); + 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) + { + winebluetooth_gatt_characteristic_t chrc2 = chrc->characteristic ; + + /* Avoids a deadlock if the event loop holds device_list_cs and is waiting on chars_cs. */ + winebluetooth_gatt_characteristic_dup( chrc2 ); + LeaveCriticalSection( &ext->chars_cs ); + + EnterCriticalSection( &device_list_cs ); + status = winebluetooth_gatt_characteristic_read_async( chrc2, irp ); + if (status == STATUS_PENDING) + { + IoMarkIrpPending( irp ); + InsertTailList( &ext->irp_list, &irp->Tail.Overlay.ListEntry ); + } + LeaveCriticalSection( &device_list_cs ); + + winebluetooth_gatt_characteristic_free( chrc2 ); + } + else + { + ULONG needed = offsetof( struct winebth_gatt_service_read_characterisitic_value_params, buf[params->size] ); + + params->size = chrc->value->DataSize; + status = STATUS_SUCCESS; + if (outsize >= needed) + { + memcpy( params->buf, chrc->value->Data, params->size ); + irp->IoStatus.Information = + offsetof( struct winebth_gatt_service_read_characterisitic_value_params, buf[params->size] ); + } + 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; } @@ -1241,6 +1321,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 ); @@ -1482,6 +1563,33 @@ 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 ) +{ + NTSTATUS status = read.result; + + if (!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; + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( read.irp ); + 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 + { + read.irp->IoStatus.Information = sizeof( *params ); + winebluetooth_gatt_characteristic_value_free( &read.value ); + } + } + complete_irp( read.irp, status ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -1536,6 +1644,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 ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 3e32ae9470b..cd33d3dd72b 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -255,6 +255,7 @@ static inline BOOL winebluetooth_gatt_service_equal( winebluetooth_gatt_service_ return s1.handle == s2.handle; } +void winebluetooth_gatt_characteristic_dup( winebluetooth_gatt_characteristic_t characteristic ); void winebluetooth_gatt_characteristic_free( winebluetooth_gatt_characteristic_t characteristic ); static inline BOOL winebluetooth_gatt_characteristic_equal( winebluetooth_gatt_characteristic_t c1, winebluetooth_gatt_characteristic_t c2) @@ -281,6 +282,7 @@ static inline BOOL winebluetooth_gatt_characteristic_value_is_inline( const stru { return val->size <= ARRAY_SIZE( val->buf ); } +NTSTATUS winebluetooth_gatt_characteristic_read_async( winebluetooth_gatt_characteristic_t chrc, IRP *irp ); enum winebluetooth_watcher_event_type { @@ -296,6 +298,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 @@ -376,6 +379,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; @@ -390,6 +400,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
Do we really need the inline case? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208#note_130771
On Sun Mar 1 11:53:43 2026 +0000, Elizabeth Figura wrote:
Do we really need the inline case? Characteristics is how most IO in BLE is done, so I wanted to minimize the roundtrips between PE and Unix as much as possible (given that the wineserver IPC already going to make this slow). Plus, from my understanding, most GATT services tend to limit the size of the values to the default MTU (23 or so bytes). This way, most value reads should be happening inline anyway.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10208#note_130856
participants (3)
-
Elizabeth Figura (@zfigura) -
Vibhav Pant -
Vibhav Pant (@vibhavp)