Question

A Bluetooth low energy device is uniquely identified by it's address (in the Android API they call this the MAC address and denote it as colon separated hex values e.g. 11:aa:22:bb:33:cc).

But to uniquely identify a BLE address you need to know if it's a public or a private address. In essence, 49 bits are necessary to identify an address, not 48.

Random addresses can be either static random, non-resolvable private or resolvable private and these types are separated by a bit pattern in the two most significant bytes (11, 00 and 10 respectively).

But I don't see anywhere that you can separate public and random addresses just by looking at the 48 bits in the address.

So how does this work in the Android API? How do they know what device to connect to when they don't know if the address you've specified are public or random?

The API in question is for instance the getRemoteDevice function. It says:

Valid Bluetooth hardware addresses must be upper case, in a format such as
"00:11:22:33:AA:BB". The helper checkBluetoothAddress(String) is available
to validate a Bluetooth address.

A BluetoothDevice will always be returned for a valid hardware address,
even if this adapter has never seen that device.

So you give the function 48 bits of data and there is no way to tell it if the address is public or private. This means the device is not uniquely identified.

Was it helpful?

Solution

Since nobody else seems to have an answer to offer I started testing on my own.

I tried making an app that creates a device from a string representation of an address and tried setting up my device with the 48 bit address alternating the public or private bit to see what the Android stack does.

private final BluetoothGattCallback leGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.i("Fisken", "Gatt connected " + gatt.getDevice().getAddress() + " status " + status);
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.w("Fisken", "Disconnect and close");
                gatt.disconnect();
                gatt.close();
            }
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.i("Fisken", "Gatt disconnected " + gatt.getDevice().getAddress() + " status " + status);
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.w("Fisken", "Disconnect and close");
                gatt.disconnect();
            }
            gatt.close();
        }
    }
};

BluetoothAdapter mBluetoothAdapter = ((BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
BluetoothDevice d = mBluetoothAdapter.getRemoteDevice("FF:55:44:33:22:11");
d.connectGatt(this, false, leGattCallback);

With this code, if I start my BLE peripheral with a random address everything works as expected. However, if I try running it with the same address with the public bit set, logcat says "Gatt connected", but that's just not true. And I'm never able to disconnect.

Update: I did some more testing to figure this out. The onConnectionStateChange event I get is just the connection attempt timing out. The status is set to either 133 (if I get STATE_CONNECTED) or 257 (if I get a STATE_DISCONNECTED) and I've seen both. In either case I should (and now do in the sample code) cancel the connection attempt and close the client.

I've also found out that if I do a scan first, so that the device I'm trying to connect to have been seen recently and then do a connect based solely on the device mac address then I am able to connect to both random and public addresses without any trouble.

So this seems to be a bug/and or missing feature in the Android API. It does not allow you to connect to a public address without first having scanned for it. It does however work for random addresses.

OTHER TIPS

It is possible to guess if the address is public or random, though it will not work in every case.

As you say above, in case of a random address, both MSB are either 00xx, 01xx or 11xx... so if it is 10xx, then it is a public address (from a company whose OUI starts with 8,9, A or B)

Also, the number of registered OUI is very limited compared to what is existing, so by searching the potential OUI in the IEEE database, a matching result will probably mean a public adress.

Registered OUI count: ~20500, so 0.12% out of 2^24 bits and 0.48% out of 2^22 bits.

Without the IEEE database, it is possible to rely on the fact that the first LSB of a OUI is always 0, and the second LSB is almost always 0 (actually, it should be always 0 as these addresses are universally administered).

Also, other statiscal analysis can be used: for instance, 60% of the OUI start with 00. On the other hand, a non resolvable private address, has only a probability of 1.66% to start with 00 (with uniform random generator).

I think your original 'need 49 bits to distinguish between public and random addresses' is correct. I cannot find anything in the encoding of an IEEE public address which restricts the MSB to be '10' which, if true, would solve the problem.

So the only thing one can use is the 'random address' bit setting in the advertisements of the peripheral or the equivalent bit setting in the connection initiation packet of the central. If these bits are not set, then the address the said endpoint exposes is public.

I will add: From core spec Vol 6 Part B section 1.3 Device addresses: Calling MS = most significant

Static random address:          two MB bits of MS byte are 1 1 such that the MS byte is 11xxxxxx & with 0xC0
Non-resolvable private address: two MB bits of MS byte are 0 0 such that the MS byte is 00xxxxxx & with 0x00
Resolvable private address:     two MB bits of MS byte are 0 1 such that the MS byte is 01xxxxxx & with 0x40

There is no way to distinguish a public address from one of the above types of addresses without also having the address type flag. Thus the need for the '49th' bit (the extra flag). The address alone does not do it!

Whether the advertising address is public or private is set in the header of the advertising message. Setting the address type in the application layer to public means that the BLE link layer will transmit the actual "MAC"address. Setting the address type to static/private resolvable, indicates that the BLE link layer will scramble the address with an identity resolving key (IRK).

You can differentiate between public and private addresses by looking at the 2 most significant bits. An address is 48 bits long, not 49. See Core Bluetooth Specification v4.2, Vol 6, Part B, Section 1.3.

This seems too obvious for everyone to have missed but from what I have seen the PDU Hdr bytes contain a TxAdd bit that indicates whether or not the MAC is public or private...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top