How to Retrieve Advertising Payload from Ibeacon/Ble

Read advertisement packet in Android

This is what I was looking for:

The BLE scan API BluetoothAdapter.startLeScan(ScanCallback) requires a call back function for the scan results. the method needs to look like the following:

    private BluetoothAdapter.LeScanCallback ScanCallback =
new BluetoothAdapter.LeScanCallback()onLeScan(final BluetoothDevice device,
int rssi,
final byte[] scanRecord)
{...}

And the scanRecord variable is a byte array which contains the Advertisement packet payload.

Per the BLE specification the structure of the payload is very simple as follows:

The packets can be up to 47 bytes in length and consist of:

  • 1 byte preamble
  • 4 byte access address
  • 2-39 bytes advertising channelPDU
  • 3 bytes CRC

For advertisement communication channels, the access address is always 0x8E89BED6.

The PDU in turn has its own header (2 bytes: size of the payload and its type – whether the device supports connections, etc.) and the actual payload (up to 37 bytes).

Finally, the first 6 bytes of the payload are the MAC address of the device, and the actual information can have up to 31 bytes.

the format of the actual information is as follows:

first byte is length of the data and second byte is type followed by the data.

This is a clever way to allow any application to skip entire data records if they don't care about the contents.

Here is the sample code to determine the contents of the Advertisement packet:

parseAdvertisementPacket(final byte[] scanRecord) {

byte[] advertisedData = Arrays.copyOf(scanRecord, scanRecord.length);

int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;

int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (len > 1) {
int uuid16 = advertisedData[offset++] & 0xFF;
uuid16 |= (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format(
"%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData,
offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
Log.e("BlueToothDeviceFilter.parseUUID", e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
case 0xFF: // Manufacturer Specific Data
Log.d(TAG, "Manufacturer Specific Data size:" + len +" bytes" );
while (len > 1) {
if(i < 32) {
MfgData[i++] = advertisedData[offset++];
}
len -= 1;
}
Log.d(TAG, "Manufacturer Specific Data saved." + MfgData.toString());
break;
default:
offset += (len - 1);
break;
}
}

thanks to

how-ibeacons-work

bluetooth org specs

mass for putting me on the right direction!

Can an iBeacon have a data payload

A few possibilities:

  • You can tack on one extra data byte to the end of the iBeacon transmission before it reaches its max advertisement length. This byte cannot be read by iOS devices, though, because Apple blocks reading raw data of iBeacon adverts. It would work on Android/Mac/Linux.

  • You can interleave a second advertisenent with mostly data fields and line the two up with a common identifier like the minor. The more bytes you allocate to lining up the advertisements, the fewer you have to use for data. You can't use the mac tobline them up, because that is unreadable in iOS for the iBeacon transmission.

  • You can make the beacon connectable via GATT, and read data fields with GATT attributes. The beacon will stop advertising, though, when connected. This limits throughput and reliability.

All of these options require you to build a custom BLE beacon that does multiple advertisements. It is not a trivial undertaking.

Get advertisement data for BLE in iOS

Unfortunately, iOS does not allow you to access the raw advertisement data. I wrote a blog post demonstrating this. While the post is specifically about iBeacons, it applies to any BLE advertisement.

EDIT: To clarify, you can read the raw manufacturer data bytes or service data bytes of non-iBeacon advertisements. It is only the iBeacon advertisements that have their manufacturer data bytes hidden by CoreLocation. See here: Obtaining Bluetooth LE scan response data with iOS

The equivalent MacOS CoreLocation methods do allow this, so it is probably an intentional security or power saving restriction on iOS.

Attaching advertising payload for iOS without pairing

Unfortunately, you cannot use CoreBluetooth APIs to attach data to advertisements. On iOS the CBAdvertisementDataServiceDataKey is read-only. While Bluetooth LE allows attaching service data , Apple effectively disallows 3rd party apps from doing this.

You do have a few options:

  1. Encode your data inside a 128-bit service UUID and advertise that. You will need to reserver a byte or two in the UUID to know that it is "your" advertisement, and therefore OK to decode the data from the other byes. This full UUID will only be advertised when your app is in the foreground visible on the screen. Let it go to the background or the screen turn off, and it will no longer advertise in that form. Similarly, receiving iOS devices must also be in the foreground with the screen on. This is because iOS disallows getting background scan results without specifying the matching service UUID up front. And because you are dynamically manipulating some of those bytes, you don't know what it will be.

  2. Do a similar kind of encoding with the 4 byte major and minor fields inside the iBeacon BLE advertisement using CoreLocation. Again, this allows you to transmit only when the app is in the foreground. Receiving, however, can happen to a limited degree in the background (for 5-10 seconds after one of your beacons first appears when combining monitoring and ranging APIs.) The big disadvantage is you only have four bytes to work with.

  3. Advertise data by manipulating the 128-bit background BLE Overflow Area Advertisement. This technique is more advanced, but advertising works in the background. Receiving works in the foreground, and partly in the background -- you can receive if the screen is at least turned on. You can read more about this technique and access free sample code in my blog post herehttp://www.davidgyoungtech.com/2020/05/07/hacking-the-overflow-area.

BLE obtain uuid encoded in advertising packet

If you want to get UUID / any other data e.g. Manufacturer Data out of scanRec[] bytes after BLE Scan, you first need to understand the data format of those Advertisement Data packet.

Came from Bluetooth.org:
Advertising or Scan Response Data format

Too much theory, want to see some code snippet? This function below would straight forward print parsed raw data bytes. Now, you need to know each type code to know what data packet refers to what information. e.g. Type : 0x09, refers to BLE Device Name, Type : 0x07, refers to UUID.

public void printScanRecord (byte[] scanRecord) {

// Simply print all raw bytes
try {
String decodedRecord = new String(scanRecord,"UTF-8");
Log.d("DEBUG","decoded String : " + ByteArrayToString(scanRecord));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

// Parse data bytes into individual records
List<AdRecord> records = AdRecord.parseScanRecord(scanRecord);

// Print individual records
if (records.size() == 0) {
Log.i("DEBUG", "Scan Record Empty");
} else {
Log.i("DEBUG", "Scan Record: " + TextUtils.join(",", records));
}

}

public static String ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.length * 2);
for (byte b : ba)
hex.append(b + " ");

return hex.toString();
}

public static class AdRecord {

public AdRecord(int length, int type, byte[] data) {
String decodedRecord = "";
try {
decodedRecord = new String(data,"UTF-8");

} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}

Log.d("DEBUG", "Length: " + length + " Type : " + type + " Data : " + ByteArrayToString(data));
}

// ...

public static List<AdRecord> parseScanRecord(byte[] scanRecord) {
List<AdRecord> records = new ArrayList<AdRecord>();

int index = 0;
while (index < scanRecord.length) {
int length = scanRecord[index++];
//Done once we run out of records
if (length == 0) break;

int type = scanRecord[index];
//Done if our record isn't a valid type
if (type == 0) break;

byte[] data = Arrays.copyOfRange(scanRecord, index+1, index+length);

records.add(new AdRecord(length, type, data));
//Advance
index += length;
}

return records;
}

// ...
}

After this parsing, those data bytes would make more sense, and you can figure out next level of decoding.

Can I read an iPhone beacon with Windows.Devices.Bluetooth.Advertisement.BluetoothLEManufacturerData

The most important thing you need to do to detect Beacons on Windows 10 is to use the new BluetoothLeAdvertisementWatcher class.

The code in the question seems focussed on setting up a filter to look for only specific Bluetooth LE advertisements matching a company code and perhaps a UUID contained in the advertisement. While this is one approach, it isn't strictly necessary -- you can simply look for all Bluetooth LE advertisements, then decode them to see if they are beacon advertisements.

I've pasted some code below that shows what I think you want to do. Major caveat: I have not tested this code myself, as I don't have a Windows 10 development environment. If you try it yourself and make corrections, please let me know and I will update my answer.

private BluetoothLEAdvertisementWatcher bluetoothLEAdvertisementWatcher;

public LookForBeacons() {
bluetoothLEAdvertisementWatcher = new BluetoothLEAdvertisementWatcher();
bluetoothLEAdvertisementWatcher.Received += OnAdvertisementReceived;
bluetoothLEAdvertisementWatcher.Start();
}

private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) {

var manufacturerSections = eventArgs.Advertisement.ManufacturerData;
if (manufacturerSections.Count > 0) {
var manufacturerData = manufacturerSections[0];
var data = new byte[manufacturerData.Data.Length];
using (var reader = DataReader.FromBuffer(manufacturerData.Data)) {
reader.ReadBytes(data);

// If we arrive here we have detected a Bluetooth LE advertisement
// Add code here to decode the the bytes in data and read the beacon identifiers

}
}
}

The next obvious question is how do you decode the bytes of the advertisement? It's pretty easy to search the web and find out the byte sequence of various beacon types, even proprietary ones. For the sake of keeping this answer brief and out of the intellectual property thicket, I'll simply describe how to decode the bytes of an open-source AltBeacon advertisement:

18 01 be ac 2f 23 44 54 cf 6d 4a 0f ad f2 f4 91 1b a9 ff a6 00 01 00 02 c5 00

This is decoded as:

  • The first two bytes are the company code (0x0118 = Radius Networks)
  • The next two bytes are the beacon type code (0xacbe = AltBeacon)
  • The next 16 bytes are the first identifier 2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6
  • The next 2 bytes are the second identifier 0x0001
  • The next 2 bytes are the third identifier 0x0002
  • The following byte is the power calibration value 0xC5 -> -59 dBm

iBeacon advertising packet transmission time

This is a useful question in terms of getting an ideal lower bound on power usage by the device (excluding any compute power used by the device).

The BLE packet has a preamble of 1 byte, access address of 4 bytes, header of 2 bytes, MAC address of 6 bytes, data of up to 31 bytes, then a CRC of 3 bytes. That's a total of 46 bytes or 368 bits.

BLE has a supposed data rate of 1Mbit. According to this article, that excludes framing / error checking / connecting (although an advertising packet probably won't spend a lot of time connecting). So assuming the best case of 1Mbit=1024*1024, we can send 2849 advertising packets per second. That means each one is about 0.35 ms - in an ideal world. If the article is right, and the effective data rate is as much as 4x slower, it could be as long as 1.4 ms.

Android: iBeacon - read its advertisement (e.g txPower)

The transmit (TX) power of the OnyxBeacon, and as of any other BLE device that is iBeacon compatible, can be obtained from the advertising packet. This does not require connecting to the device. Also, the TX power value will vary depending on the power level, as the TX power value is calibrated for each power level of the device. The TX power is the last byte of the advertising payload. More information on the structure of the iBeacon packet can be found here: http://www.havlena.net/en/location-technologies/ibeacons-how-do-they-technically-work/

Here is a sample of how to parse an iBeacon packet and obtain the TX power:

BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, final int rssi, final byte[] scanData) {
if (scanData[7] == 0x02 && scanData[8] == 0x15) { // iBeacon indicator
System.out.println("iBeacon Packet: %s", bytesToHexString(scanData));
UUID uuid = getGuidFromByteArray(Arrays.copyOfRange(scanData, 9, 25));
int major = (scanData[25] & 0xff) * 0x100 + (scanData[26] & 0xff);
int minor = (scanData[27] & 0xff) * 0x100 + (scanData[28] & 0xff);
byte txpw = scanData[29];
System.out.println("iBeacon Major = " + major + " | Minor = " + minor + " TxPw " + (int)txpw + " | UUID = " + uuid.toString());
}
}
};

public static String bytesToHexString(byte[] bytes) {
StringBuilder buffer = new StringBuilder();
for(int i=0; i<bytes.length; i++) {
buffer.append(String.format("%02x", bytes[i]);
}
return buffer.toString();
}
public static UUID getGuidFromByteArray(byte[] bytes)
{
ByteBuffer bb = ByteBuffer.wrap(bytes);
UUID uuid = new UUID(bb.getLong(), bb.getLong());
return uuid;
}


Related Topics



Leave a reply



Submit