2

I have written an application in C using Bluez that implements a peripheral (a GATT server). I managed to advertise and make my peripheral work. Reading a characteristic works fine, notifying works fine and even a Write Without Reponse works. However, what doesn't work is the WRITE WITH RESPONSE. When I do that from an Android app (nrfConnect), I receive a ERR UNLIKELY (code 14). Looks like the 'response' that belongs to a write with response is not sent.

I thought I was doing the right thing by doing:

g_dbus_method_invocation_return_value(invocation, g_variant_new ("()"));

...but apparently it is not what Bluez is expecting. What should I do to make Bluez send the 'response' that belongs to a 'Write with response'?

Here is my code:

static void bluez_characteristic_method_call(GDBusConnection *conn,
                                             const gchar *sender,
                                             const gchar *path,
                                             const gchar *interface,
                                             const gchar *method,
                                             GVariant *params,
                                             GDBusMethodInvocation *invocation,
                                             void *userdata) {

    log_debug(TAG, "local characteristic method called: %s", method);
    LocalCharacteristic *characteristic = (LocalCharacteristic *) userdata;
    g_assert(characteristic != NULL);

    Application *application = characteristic->application;
    g_assert(application != NULL);

    if (g_str_equal(method, "ReadValue")) {
        g_assert(g_str_equal(g_variant_get_type_string(params), "(a{sv})"));

        GVariant *result = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
                                                     characteristic->value->data,
                                                     characteristic->value->len,
                                                     sizeof(guint8));
        g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&result, 1));
        return;
    } else if (g_str_equal(method, "WriteValue")) {
        g_assert(g_str_equal(g_variant_get_type_string(params), "(aya{sv})"));
        GVariant *valueVariant, *optionsVariant;
        g_variant_get(params, "(@ay@a{sv})", &valueVariant, &optionsVariant);

 
        // Copy byte array and store it
        size_t data_length = 0;
        guint8 *data = (guint8 *) g_variant_get_fixed_array(valueVariant, &data_length, sizeof(guint8));
        GByteArray *byteArray = g_byte_array_sized_new(data_length);
        g_byte_array_append(byteArray, data, data_length);
        binc_characteristic_set_value(characteristic, byteArray);

        // Send properties changed signal with new value
        binc_application_notify(application, characteristic->service_uuid, characteristic->uuid, byteArray);

        // Send a response for a Write With Response
        g_dbus_method_invocation_return_value(invocation, g_variant_new ("()"));
    } else if...

    ....
 }

1 Answers1

3

I'm actually running into the same problem but using python dasbus: https://github.com/rhinstaller/dasbus/issues/88. After staring at dbus-monitor, btmon, and other tools, I tried downgrading bluez from 5.62 to 5.50. I'm now getting write responses. I'll be doing a bisect shortly and opening an issue with bluez but I think you should try an older version of bluez.

EDIT: bisected. bluez 5.61 should be okay to use.

838c0dc7641e1c991c0f3027bf94bee4606012f8 is the first bad commit
commit 838c0dc7641e1c991c0f3027bf94bee4606012f8
Author: Bernie Conrad <bernie@allthenticate.net>
Date:   Tue Sep 28 16:00:15 2021 -0700

    gatt: Fix not cleaning up when disconnected

    There is a current use after free possible on a gatt server if a client
    disconnects while a WriteValue call is being processed with dbus.

    This patch includes the addition of a pending disconnect callback to handle
    cleanup better if a disconnect occurs during a write, an acquire write
    or read operation using bt_att_register_disconnect with the cb.

 src/gatt-database.c | 128 ++++++++++++++++++++++++++++++----------------------
 1 file changed, 74 insertions(+), 54 deletions(-)

Copy/paste of some of the debugging here:

btmon, bluez 5.62:

> ACL Data RX: Handle 64 flags 0x02 dlen 12                          #146 [hci0] 20:53:37.594026
      ATT: Write Request (0x12) len 7
        Handle: 0x01f0
          Data: 00014e2300
> HCI Event: Number of Completed Packets (0x13) plen 5               #147 [hci0] 20:53:37.594694
        Num handles: 1
        Handle: 64
        Count: 2
< ACL Data TX: Handle 64 flags 0x00 dlen 9                           #148 [hci0] 20:53:42.599869
      ATT: Error Response (0x01) len 4
        Write Request (0x12)
        Handle: 0x01f0
        Error: Unlikely Error (0x0e)

btmon, bluez 5.50:

> ACL Data RX: Handle 64 flags 0x02 dlen 12                              #190 [hci0] 10:37:38.043505
      ATT: Write Request (0x12) len 7
        Handle: 0x005c
          Data: 0003cb2400
< ACL Data TX: Handle 64 flags 0x00 dlen 5                               #191 [hci0] 10:37:38.044305
      ATT: Write Response (0x13) len 0

Code:

@dbus_interface("org.bluez.GattCharacteristic1")
class BLECharacteristicGatt(InterfaceTemplate):

    def ReadValue(self, options: Dict[Str, Variant]) -> List[Byte]:
        print(f"UUID {self.implementation._service._uuid} / {self.implementation._uuid} read value")
        return list(self.implementation.value)

    def WriteValue(self, value: List[Byte], options: Dict[Str, Variant]) -> None:
        try:
            print(f"UUID {self.implementation._service._uuid} / {self.implementation._uuid} write value")
            self.implementation.value = value
        except Exception as e:
            print(e)

    @property
    def UUID(self) -> Str:
        return self.implementation._uuid

    @property
    def Service(self) -> ObjPath:
        return self.implementation._service._path

    @property
    def Flags(self) -> List[Str]:
        return self.implementation._flags

bluetoothd debug, bluez 5.62:

Mar 09 07:39:38 redacted bluetoothd[4136041]: src/device.c:gatt_debug() (chan 0x555620d5a4e0) ATT PDU received: 0x12
Mar 09 07:39:38 redacted bluetoothd[4136041]: src/device.c:gatt_debug() Write Req - handle: 0x0022
Mar 09 07:39:43 redacted bluetoothd[4136041]: src/device.c:gatt_debug() Write Complete: err -110
Mar 09 07:39:43 redacted bluetoothd[4136041]: src/device.c:gatt_debug() (chan 0x555620d5a4e0) ATT PDU received: 0x12
Mar 09 07:39:43 redacted bluetoothd[4136041]: src/device.c:gatt_debug() Write Req - handle: 0x0025
Mar 09 07:39:48 redacted bluetoothd[4136041]: src/device.c:gatt_debug() Write Complete: err -110
Josh
  • 61
  • 3
  • Yes, that looks like the same problem. I am on Bluez 5.60 so it seems the problem was introduced between 5.50 and 5.60. I will also run a test on 5.50 to see if my code still works there. – Martijn van Welie Mar 10 '22 at 06:09
  • git bisect says... 838c0dc7641e1c991c0f3027bf94bee4606012f8 is the first bad commit. – Josh Mar 11 '22 at 16:11
  • I tried to reproduce the error using the BlueZero python package and I also got the ERR UNLIKELY. Seems like it is really a Bluez bug. The commit you found might be causing it....should open a bug on the Bluez repo. – Martijn van Welie Mar 12 '22 at 10:28
  • Did some more tests. My code works fine on Bluez 5.50 (Pi image, Buster) but it doesn't work anymore on 5.55 (Pi image, Bullseye). Seems like the issue was introduced between 5.50 and 5.55. – Martijn van Welie Mar 13 '22 at 19:37
  • I opened an issue in the Bluez repo: https://github.com/bluez/bluez/issues/317 – Martijn van Welie Mar 13 '22 at 19:49
  • Looks like this bug was fixed. I tried latest code from github and the issue is gone there. So that will become 5.64. I guess it was fixed somewhere between 5.62 and 5.64 – Martijn van Welie Mar 15 '22 at 20:18
  • You're right, looks like https://github.com/bluez/bluez/commit/259407032af93cc861b3648780f7478921172572 fixed it which should be in 5.63+ – Josh Mar 17 '22 at 16:45