This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Identifying unprovisioned mesh device

I'm struggling with how to properly identify an unprovisioned mesh device. Basically when i scan for unprovisioned devices from my iOS app, I'd like for only our devices to show up, not any random mesh device. I'm still struggling to understand all the terminology, but if I understand everything correctly, I should be checking the `p_device_uri` set in `mesh_provisionee_prov_start` against the advertised data, or something. According to the mesh spec, it seems like this data is hashed using the salt function `s1`, which I think corresponds to `OpenSSLHelper.calculateSalt` in the iOS SDK. However, what I'm not understanding is which data to actually check against and how. So far, when I discover unprovisioned proxy nodes, I can do this:

func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any],
                        rssi RSSI: NSNumber) {

  if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as! [CBUUID: NSData]? {
    let provisioningData = serviceData[MeshServiceProvisioningUUID]
    print("service data: \(provisioningData)")
  }

}

`provisioningData` contains some bytes of data, for me it returned `E754E3DAAFFDEE419AC6AD32896107A80000`, Which is 18bytes. The mesh spec seems to define the beacon format as follows:

But this comes out to 23 bytes, so it can't really match the format of the data I received.

It seems that the nRF Connect application is able to decipher at least some of this data, since it shows the device UUID in its scan view, but since the source of this app is not available I can't check against it.

Any help would be appreciated!

Parents
  • Hi Jonas, 

    Yes, you are correct. The Device UUID will be unique per device, so placing a hash of a common identifier in the unprovisioned beacon packet may be the way to go. Have you added the URI data to the example? The pointer to the URI data is passed to mesh_provisionee_prov_start() in the start() function in main(). By defualt this is set to NULL. 

    static void start(void)
    {
        rtt_input_enable(app_rtt_input_handler, RTT_INPUT_POLL_PERIOD_MS);
    
        if (!m_device_provisioned)
        {
            static const uint8_t static_auth_data[NRF_MESH_KEY_SIZE] = STATIC_AUTH_DATA;
            mesh_provisionee_start_params_t prov_start_params =
            {
                .p_static_data    = static_auth_data,
                .prov_complete_cb = provisioning_complete_cb,
                .prov_device_identification_start_cb = device_identification_start_cb,
                .prov_device_identification_stop_cb = NULL,
                .prov_abort_cb = provisioning_aborted_cb,
                .p_device_uri = NULL
            };
            ERROR_CHECK(mesh_provisionee_prov_start(&prov_start_params));
        }
    
        const uint8_t *p_uuid = nrf_mesh_configure_device_uuid_get();
        UNUSED_VARIABLE(p_uuid);
        __LOG_XB(LOG_SRC_APP, LOG_LEVEL_INFO, "Device UUID ", p_uuid, NRF_MESH_UUID_SIZE);
    
        ERROR_CHECK(mesh_stack_start());
    
        hal_led_mask_set(LEDS_MASK, LED_MASK_STATE_OFF);
        hal_led_blink_ms(LEDS_MASK, LED_BLINK_INTERVAL_MS, LED_BLINK_CNT_START);
    }

    Best regards
    Bjørn

  • Hi Bjørn,

    Thanks for the reply, yes I've added a device URI (though I'm not quite sure if its content matters, or is supposed to have any specific format, I've just picked something random). So I think this is supposed to be included in the device beacon.

    What I don't know is how to extract and compare this information in iOS. In other words, when I have discovered a device via the CentralManager in iOS, I will need to do something to compare the data that I receive against the URI that I've set when I call `mesh_provisionee_prov_start`, but how do I do this? Since I just receive a random jumble of bytes, part of which *may* be the hashed URI, it's really hard to tell.

    As I wrote in the original post, the nRF Connect app does extract this information, but its source code is not open source, so I can't check how it does this, and I've been unable to find any documentation. But maybe you could check the source code for nRF Connect on how it does this. I've posted a screenshot from nRF where it shows Device UUID and OOB information below.

    Thank you for the help,

    /Jonas

    nRF Connect showing Device UUID etc

  • I am no iOS or Swift expert, but I would assume that you will get the content of the advertisment packet as an array of bytes. The advertisment packet will consist of several AD structures

    Each of these ad structures start with a byte indicating the length of the structure. The second byte is the type, which will be 0x16 for the Mesh beacon structure. So once youve foudn the correct structure you can fin the URI Hash which will be the the four last octets in that structure. 

Reply
  • I am no iOS or Swift expert, but I would assume that you will get the content of the advertisment packet as an array of bytes. The advertisment packet will consist of several AD structures

    Each of these ad structures start with a byte indicating the length of the structure. The second byte is the type, which will be 0x16 for the Mesh beacon structure. So once youve foudn the correct structure you can fin the URI Hash which will be the the four last octets in that structure. 

Children
  • I've finally understood why and how this works. What I'm receiving is in fact not the GAP advertising data, but rather the GATT scan response. As per the mesh spec (Table 7.3) the service data in the GATT information only contains the Device UUID (first 16 bytes) and the OOB information (last 2 bytes), which matches exactly what I receive. I've been unable to find any reference to reading GAP data in iOS, I wonder if it's even supported at all. Looking at the CoreBluetooth API, it seems there is no other way of searching for devices.

    So by looking into GATT, I found out that the application can override `mesh_adv_data_set` and that this allows me to hook into the advertised data. I've added some manufacture data in the scan response which allows me to identify the device.

    Something like this:

    typedef struct __attribute((packed))
    {
        uint16_t product_id;
        uint16_t app_version;
    } lc_mesh_manuf_data_t;
    
    void mesh_adv_data_set(uint16_t service_uuid, const uint8_t * p_service_data, uint8_t length)
    {
        // [...]
    
        /* The application may freely set anything into the scan response data. */
        ble_advdata_t srdata;
        memset(&srdata, 0, sizeof(srdata));
        srdata.name_type = BLE_ADVDATA_FULL_NAME;
    
        ble_advdata_manuf_data_t manuf_data;
    
        lc_mesh_manuf_data_t manuf_data_data;
    
        manuf_data_data.product_id = APP_PRODUCT_ID;
        manuf_data_data.app_version = APP_VERSION;
    
        manuf_data.company_identifier = APP_COMPANY_ID;
        manuf_data.data.p_data = (uint8_t *) &manuf_data_data;
        manuf_data.data.size = sizeof(manuf_data_data);
    
        srdata.p_manuf_specific_data = &manuf_data;
    
        // [...]
    }

    This seems to work quite well. And I can read out this information in iOS like this:

    extension MeshNetworkScanner: CBCentralManagerDelegate {
        func centralManagerDidUpdateState(_ central: CBCentralManager) {
            // FIXME: handle central manager being unavailable
        }
    
        func centralManager(_ central: CBCentralManager,
                            didDiscover peripheral: CBPeripheral,
                            advertisementData: [String: Any],
                            rssi RSSI: NSNumber) {
            
            if let serviceData = advertisementData[CBAdvertisementDataManufacturerDataKey] as! Data? {
                if serviceData.count >= 6 {
                    let manufacturerId = UInt16(littleEndian: serviceData[0...1].withUnsafeBytes { $0.pointee })
                    let productId = UInt16(littleEndian: serviceData[2...3].withUnsafeBytes { $0.pointee })
                    
                    // we'll check that this is an actual LightCore device
                    if manufacturerId == MeshNetwork.companyId && productId == MeshNetwork.productId {
                        let newNode = UnprovisionedMeshNode(withPeripheral: peripheral, andAdvertisementDictionary: advertisementData, RSSI: RSSI)
                        delegate?.nodeDiscovered(newNode)
                    }
                }
            }
        }
    }
    

  • Hi Nicklas, 

    Advertising it exclusivly related to the Generic Access Profile, hence the sd_ble_gap_-  prefix to all SoftDevice functions related to advertisment.  

    A node, i.e. an unprovisioned device, can be provisioned through the Advertisment bearer or the GATT bearer using the Mesh Provisioning Service. I assumed that you would be able to receive both the PB-ADV and PB-GATT advertisement data with iOS devices as the nRF device will interleave these, see quote below:

    If PB-ADV is supported, an unprovisioned Provisioning Server that supports PB-GATT has to interleave the connectable advertising for PB-GATT with the Unprovisioned Device beacon (see Section 3.9.2).

    However, it could be that iOS devices filter out advertisment packets with the ADV type used by the Unprovisioned Device beacon. So adding a custom ID to the scan response data for the PB-GATT advertisment data is the way to go. 

    Best regards

    Bjørn 

Related