BLE Characteristic Mixup iOS

18 views
Skip to first unread message

Nikolas von Lonski

unread,
Apr 16, 2024, 10:03:18 AMApr 16
to btstack-dev
I'm not sure how to categorize this.

Running the same Swift source code to connect to a BTStack based peripheral and read a given GATT characteristic succeeds on Mac but fails on iOS.
After further testing the issue can be found for iPhone12 but not for 11, 13, 15, and SE.
Other devices offering GATT services that get connected to with the same swift code appear to work fine. (They offer a mix of UUID16, 32 and 128)

To elaborate on the BTStack side:

Given this profile.gatt

```

// PRIMARY_SERVICE, GAP_SERVICE

// CHARACTERISTIC, GAP_DEVICE_NAME, READ, "FRIAMAT_X_SERIALNUMB"


PRIMARY_SERVICE, GATT_SERVICE

CHARACTERISTIC, GATT_DATABASE_HASH, READ,


// Dummy Service

PRIMARY_SERVICE, e80e0b86-3e0b-4d5a-a222-36ebea895000

// Dummy Service, Some Characteristic, with read and write

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea895002, READ | WRITE | DYNAMIC | NOTIFY, "Binding"

// Dummy Service, Characteristic, with read and notify + authentication

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea895004, READ | NOTIFY | DYNAMIC, "Status"

// Dummy Service, Some Characteristic, with read and write

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea895006, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Read"

// Dummy Service, Some Characteristic, with read and write

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea895008, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Write"

// Dummy Service, Some Characteristic, with read and write

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea89500a, NOTIFY | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Operation"

// Dummy Service, Some Characteristic, with read and write

CHARACTERISTIC,  e80e0b86-3e0b-4d5a-a222-36ebea89500c, WRITE | INDICATE | DYNAMIC | ENCRYPTION_KEY_SIZE_16, "Protocol"
```

The read operation for `
e80e0b86-3e0b-4d5a-a222-36ebea895002`
reads on `
e80e0b86-3e0b-4d5a-a222-36ebea895004`.
Subscribing has the same relative behavior.

Hitting other characteristics is a total bust.

The mobile side can be tested using the following repository:
https://github.com/Pluto-Y/Swift-LightBlue?tab=readme-ov-file

NRFConect, and the like all have the same issue.

```

   const uint16_t ADVERTISEMENT_INTERVAL_MIN = 800;

    const uint16_t ADVERTISEMENT_INTERVAL_MAX = 800;


    const uint8_t G_ADV_FLAGS[] = { 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06 };


    const uint8_t CHANNEL_MAP = 0x07; // based on example


    /**

    * @brief Handles packets and events

    *

    * Handles security manager events and ATT server events (Connect/Disconnect/Ready to notify)

    * @param packet_type

    * @param channel

    * @param packet

    * @param size

    */

    void packet_handler(

        uint8_t p_packet_type, uint16_t p_channel, uint8_t *p_packet_ptr, uint16_t p_size)

    {

        UNUSED(p_channel);

        UNUSED(p_size);


        if (p_packet_type != HCI_EVENT_PACKET) {

            return;

        }


        auto &l_context = get_friable_context();


        bd_addr_t l_pairing_partner = { 0 };

        hci_con_handle_t l_con_handle = -1;

        bd_addr_t l_addr = { 0 };

        bd_addr_type_t l_addr_type = BD_ADDR_TYPE_UNKNOWN;

        uint8_t l_status = 0;


        // setup advertisements

        uint16_t l_adv_int_min = ADVERTISEMENT_INTERVAL_MIN;

        uint16_t l_adv_int_max = ADVERTISEMENT_INTERVAL_MAX;

        uint8_t l_adv_type = 0;

        bd_addr_t l_null_addr;


        switch (hci_event_packet_get_type(p_packet_ptr)) {

        case HCI_EVENT_META_GAP:

            switch (hci_event_gap_meta_get_subevent_code(p_packet_ptr)) {

            case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:

                LOG_MSG("Connection complete\n");


                l_con_handle = hci_subevent_le_connection_complete_get_connection_handle(p_packet_ptr);

                l_context.connection.handle = l_con_handle;

                l_context.connection.dirty = true;

                update_state(friable::connection_state::ACTIVE);

                break;


            case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE_V1:

                LOG_MSG("Enhanced Connection complete\n");


                l_con_handle =

                    hci_subevent_le_enhanced_connection_complete_v1_get_connection_handle(p_packet_ptr);

                l_context.connection.handle = l_con_handle;

                l_context.connection.dirty = true;

                update_state(friable::connection_state::ACTIVE);

                break;


            case HCI_SUBEVENT_LE_ENHANCED_CONNECTION_COMPLETE_V2:

                LOG_MSG("Enhanced Connection complete\n");


                l_con_handle =

                    hci_subevent_le_enhanced_connection_complete_v2_get_connection_handle(p_packet_ptr);

                l_context.connection.handle = l_con_handle;

                l_context.connection.dirty = true;

                update_state(friable::connection_state::ACTIVE);

                break;


            case GAP_SUBEVENT_LE_CONNECTION_COMPLETE:

                LOG_MSG("LE Connection complete\n");


                l_con_handle = gap_subevent_le_connection_complete_get_connection_handle(p_packet_ptr);

                l_context.connection.handle = l_con_handle;

                l_context.connection.dirty = true;

                update_state(friable::connection_state::ACTIVE);

                break;


            default:

                LOG_MSG("Unhandled GAP event %d\n", hci_event_gap_meta_get_subevent_code(p_packet_ptr));

                break;

            }

            break;


        case SM_EVENT_NUMERIC_COMPARISON_REQUEST:

            update_state(friable::connection_state::WAIT_CONFIRM);


            if (l_context.callbacks.confirm_pairing == nullptr) {

                LOG_MSG("No confirm_pairing callback set\n");


                update_state(friable::error_e::INVALID_STATE);

                break;

            }

            l_context.callbacks.confirm_pairing(

                sm_event_numeric_comparison_request_get_passkey(p_packet_ptr));

            break;


        case SM_EVENT_PAIRING_STARTED:

            sm_event_pairing_started_get_address(p_packet_ptr, l_pairing_partner);


            LOG_MSG("Pairing started with %s\n", bd_addr_to_str(l_pairing_partner));


            update_state(friable::connection_state::PAIRING);

            break;


        case SM_EVENT_PAIRING_COMPLETE:

            switch (sm_event_pairing_complete_get_status(p_packet_ptr)) {

            case ERROR_CODE_SUCCESS:


                sm_event_pairing_complete_get_address(p_packet_ptr, l_addr);


                LOG_MSG("Pairing complete with %s\n", bd_addr_to_str(l_addr));


                l_con_handle = sm_event_pairing_complete_get_handle(p_packet_ptr);

                memcpy(l_context.connection.address, l_addr, sizeof(bd_addr_t));

                l_context.connection.handle = l_con_handle;

                l_context.connection.dirty = true;


                update_state(friable::connection_state::ACTIVE);

                break;


            case ERROR_CODE_CONNECTION_TIMEOUT:

                LOG_MSG("Pairing failed, timeout\n");

                update_state(friable::error_e::INVALID_STATE);

                break;

            case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:

                LOG_MSG("Pairing failed, disconnected\n");

                update_state(friable::error_e::INVALID_STATE);

                break;

            case ERROR_CODE_AUTHENTICATION_FAILURE:

                LOG_MSG("Pairing failed, authentication failure with reason = %u\n",

                    sm_event_pairing_complete_get_reason(p_packet_ptr));

                update_state(friable::error_e::INVALID_STATE);

                break;

            default:

                LOG_MSG("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(p_packet_ptr));

                update_state(friable::error_e::INVALID_STATE);

                break;

            }

            break;


        case GATT_EVENT_QUERY_COMPLETE:

            l_status = gatt_event_query_complete_get_att_status(p_packet_ptr);


            switch (l_status) {

            case ATT_ERROR_INSUFFICIENT_ENCRYPTION:

                LOG_MSG("GATT Query failed, Insufficient Encryption\n");

                break;


            case ATT_ERROR_INSUFFICIENT_AUTHENTICATION:

                LOG_MSG("GATT Query failed, Insufficient Authentication\n");

                break;


            case ATT_ERROR_BONDING_INFORMATION_MISSING:

                LOG_MSG("GATT Query failed, Bonding Information Missing\n");

                break;


            case ATT_ERROR_SUCCESS:

                LOG_MSG("GATT Query successful\n");

                break;


            default:

                LOG_MSG("GATT Query failed, status 0x%02x\n",

                    gatt_event_query_complete_get_att_status(p_packet_ptr));

                break;

            }

            break;


        case BTSTACK_EVENT_STATE:

            if (btstack_event_state_get_state(p_packet_ptr) != HCI_STATE_WORKING) {

                return;

            }


            memset(l_null_addr, 0, sizeof(bd_addr_t));


            gap_advertisements_set_params(

                l_adv_int_min,

                l_adv_int_max,

                l_adv_type,

                0,

                l_null_addr,

                CHANNEL_MAP,

                0x00);


            assert(sizeof(l_context.advertising_data.data) <= BLE_ADVERTISING_LIMIT); // ble limitation


            gap_advertisements_set_data(

                sizeof(l_context.advertising_data.data),

                l_context.advertising_data.data);


            break;


        case HCI_EVENT_DISCONNECTION_COMPLETE:

            // le_notification_enabled = 0;

            LOG_MSG("HCI_EVENT_DISCONNECTION_COMPLETE\n");


            update_state(friable::connection_state::DISCONNECTED);

            break;


        case ATT_EVENT_CAN_SEND_NOW:

            process_notification(get_friable_context().connection.handle);

            break;


        default:

            break;

        }

    }
```

```
    /**
    * @brief Initialize BTStack as peripheral device
    *
    */
    void sm_peripheral_setup(void)
    {
        l2cap_init();

        // setup SM: Display only
        sm_init();

        // setup ATT server
        att_server_init(profile_data, att_read_callback, att_write_callback);

        // setup GATT Client
        gatt_client_init();

        // inform about BTstack state
        v_sm_event_callback_registration.callback = &packet_handler;
        hci_add_event_handler(&v_sm_event_callback_registration);

        // register for SM events
        v_sm_event_callback_registration.callback = &packet_handler;
        sm_add_event_handler(&v_sm_event_callback_registration);

        // register for ATT
        att_server_register_packet_handler(packet_handler);


        // LE Secure Pairing, Passkey entry initiator enter, responder (us) displays
        sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_YES_NO);
        sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_MITM_PROTECTION);

        att_dump_attributes();
    }
```

```
    void generate_advertising_data(uint8_t *p_buffer_ptr,
        size_t p_buffer_len,
        device_info_t *p_device_info_ptr,
        bd_addr_t p_mac,
        bool p_force_pairing)
    {
        advertising_struct l_adv = {};
        size_t l_offset = 0;

        memcpy(l_adv.basic_flags, BASIC_ADVERTISING_FLAGS, sizeof(BASIC_ADVERTISING_FLAGS));

        l_adv.name[0] = sizeof(l_adv.name) - 1;
        l_adv.name[1] = BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME;
        l_offset += 2;
        //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
        strncpy((char *) l_adv.name + l_offset, DEVICE_BASE_NAME, sizeof(DEVICE_BASE_NAME) - 1);
        l_offset += sizeof(DEVICE_BASE_NAME) - 1;
        l_adv.name[l_offset++] = '_';
        l_adv.name[l_offset++] = p_device_info_ptr->model;
        l_adv.name[l_offset++] = '_';
        //NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
        strncpy((char *) l_adv.name + l_offset,
            (const char *) p_device_info_ptr->serial_number,
            sizeof(device_info_t::serial_number));
        //NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
        l_offset += sizeof(device_info_t::serial_number);

        l_adv.manufacturer_data[0] = sizeof(l_adv.manufacturer_data) - 1;
        l_adv.manufacturer_data[1] = FRIAMAT_ADVERTISING_FLAGS;
        l_offset = 2;
        memcpy(l_adv.manufacturer_data + l_offset, p_mac, sizeof(bd_addr_t));
        l_offset += sizeof(bd_addr_t);
        l_adv.manufacturer_data[l_offset++] = p_force_pairing;

        memcpy(p_buffer_ptr, &l_adv, sizeof(l_adv));

        log_advertising_data(&l_adv);
    }
```


``` trace.txt
Apr 16 15:04:10.266  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Normal - Random - CE:DC:5B:E8:97:E2  -62 dBm - ARTA_\00_  - Manufacturer Specific Data - Channel 38  
Apr 16 15:04:10.266  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Normal - Random - CE:DC:5B:E8:97:E2  -62 dBm - Channel 38  
Apr 16 15:04:10.775  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Normal - Random - CE:DC:5B:E8:97:E2  -60 dBm - ARTA_\00_  - Manufacturer Specific Data - Channel 38  
Apr 16 15:04:10.775  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Normal - Random - CE:DC:5B:E8:97:E2  -60 dBm - Channel 38  
Apr 16 15:04:10.775  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Low Power - Random - CE:DC:5B:E8:97:E2  -64 dBm - ARTA_\00_  - Manufacturer Specific Data - Channel 39  
Apr 16 15:04:10.780  HCI Command      0x0000  CE:DC:5B:E8:97:E2  LE Add Device To Filter Accept List - Random - CE:DC:5B:E8:97:E2  
Apr 16 15:04:11.275  HCI Event        0x0000  CE:DC:5B:E8:97:E2  LE - Ext ADV - 1 Report - Normal - Random - CE:DC:5B:E8:97:E2  -57 dBm - ARTA_\00_  - Manufacturer Specific Data - Channel 38  
Apr 16 15:04:11.277  HCI Event        0x004A  CE:DC:5B:E8:97:E2  LE - Enhanced Connection Complete - Central - Random - CE:DC:5B:E8:97:E2 local - 00:00:00:00:00:00 peer - 00:00:00:00:00:00 - Conn Interval: 22.5 ms  
Apr 16 15:04:11.280  HCI Command      0x0000  CE:DC:5B:E8:97:E2  LE Remove Device From Filter Accept List - Random - CE:DC:5B:E8:97:E2  
Apr 16 15:04:11.290  HCI Command      0x004A  CE:DC:5B:E8:97:E2  Read Remote Version Information - Connection Handle: 0x004A  
Apr 16 15:04:11.320  HCI Event        0x004A  CE:DC:5B:E8:97:E2  Read Remote Version Information Complete  
Apr 16 15:04:11.320  HCI Command      0x004A  CE:DC:5B:E8:97:E2  LE Read Remote Used Features - Connection Handle: 0x004A  
Apr 16 15:04:11.324  HCI Event        0x004A  CE:DC:5B:E8:97:E2  LE - Read Remote Used Features Complete - DPLE Supported  
Apr 16 15:04:11.565  HCI Event        0x004A  CE:DC:5B:E8:97:E2  LE - PHY Update Complete  
Apr 16 15:04:11.610  HCI Event        0x004A  CE:DC:5B:E8:97:E2  LE - Data Length Change  
Apr 16 15:04:11.613  ATT Send         0x004A  CE:DC:5B:E8:97:E2  Exchange MTU Request - MTU: 293  
Apr 16 15:04:11.655  ATT Receive      0x004A  CE:DC:5B:E8:97:E2  Exchange MTU Response - MTU: 293  
Apr 16 15:04:11.656  ATT Send         0x004A  CE:DC:5B:E8:97:E2  Read By Type Request - Start Handle: 0x0004 - End Handle: 0x0006 - UUID: GATT Characteristic Declaration  
Apr 16 15:04:11.702  ATT Receive      0x004A  CE:DC:5B:E8:97:E2  Read By Type Response  
Apr 16 15:04:11.706  ATT Send         0x004A  CE:DC:5B:E8:97:E2  Read By Type Request - Start Handle: 0x0007 - End Handle: 0x0019 - UUID: GATT Include Declaration  
Apr 16 15:04:11.710  HCI Event        0x004A  CE:DC:5B:E8:97:E2  Number Of Completed Packets - Handle: 0x004A - Packets: 0x0002    
Apr 16 15:04:11.745  ATT Receive      0x004A  CE:DC:5B:E8:97:E2  Error Response - Attribute Handle: 0x0007 - Error Code: Attribute Not Found (0x0A)  
Apr 16 15:04:11.750  ATT Send         0x004A  CE:DC:5B:E8:97:E2  Read Request - Handle:0x0009  
Apr 16 15:04:11.792  ATT Receive      0x004A  CE:DC:5B:E8:97:E2  Read Response - Value: 0500 0000  
Apr 16 15:04:11.814  HCI Event        0x004A  CE:DC:5B:E8:97:E2  Number Of Completed Packets - Handle: 0x004A - Packets: 0x0002    
Apr 16 15:04:13.678  ATT Send         0x004A  CE:DC:5B:E8:97:E2  Read By Type Request - Start Handle: 0x0001 - End Handle: 0x0003 - UUID: Device Name  
Apr 16 15:04:13.725  ATT Receive      0x004A  CE:DC:5B:E8:97:E2  Error Response - Attribute Handle: 0x0001 - Error Code: Attribute Not Found (0x0A)  
Apr 16 15:04:13.769  HCI Event        0x004A  CE:DC:5B:E8:97:E2  Number Of Completed Packets - Handle: 0x004A - Packets: 0x0001   
```

Matthias Ringwald

unread,
Apr 17, 2024, 1:10:14 PMApr 17
to btsta...@googlegroups.com
Hi Nikolas

Could you explain what exactly is failing/not working? :)

Where is BTstack running? Could you collect the HCI logs for both setups: macOS and iOS and compare them/post them here?

Best
Matthias
> --
> You received this message because you are subscribed to the Google Groups "btstack-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to btstack-dev...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/btstack-dev/126835b3-3468-46b6-882c-c5d232d8a522n%40googlegroups.com.

Reply all
Reply to author
Forward
0 new messages