🎉 25% off Pre-Sale! Bluetooth LE course with real hardware included - no SDK required
Bluetooth Low Energy · · 11 min read

A Deep Dive into BLE Packets and Events

Understanding BLE packets is imperative for any developer. Learn by analyzing real sniffer captures of advertisements, connection requests, and post-connection operations like version exchange, MTU negotiation, and attribute discovery.

A Deep Dive into Bluetooth LE Packets and Events

In BLE, there are many events and operations that can be exchanged between a Peripheral and a Central. Understanding these events is imperative for any BLE developer, and there are two aspects to achieving this:

I believe these two methods go hand in hand in helping achieve a full understanding of BLE.

We cover the first aspect in my Intro to Bluetooth Low Energy book (which you can download for free here or purchase in paperback format).

In this post, we will go through a number of events and better understand them by analyzing captures from a Bluetooth sniffer (Ellisys Bluetooth Tracker).

Here’s a list of events/operations we’ll be looking at:

Advertisements

In this section, we’ll take a look at a couple of different advertisement packet types including:

For each of these types, we’ll look at:

Before we get into specific examples, let’s first look at the format of an Uncoded (1M PHY, 2M PHY) Advertisement packet (figure taken from the Bluetooth 5.1 specification document):

Uncoded PHY advertisement packet format
Source: Core Bluetooth Specification version 5.2

You’ll notice that there are two main parts in the PDU: Header and Payload.

The Header is standard for all advertisement packets, and the Payload depends on the advertisement type (PDU).

Nonconnectable Scannable Undirected Advertisements

Let’s take a look at a nonconnectable scannable advertisement from a BLE sniffer capture:

Nonconnectable scannable advertisement packet in sniffer capture

Here are the details as shown by the sniffer software. The first screen capture shows the details of the Link Layer information:

Link Layer details of nonconnectable scannable advertisement

A few notes about the above fields:

The remaining details show the contents of the Link Layer packet itself. I’ve gone ahead and highlighted the most important fields:

Advertisement packet contents with highlighted fields

A few notes about the fields in the capture:

Advertisement type (PDU): ADV_SCAN_IND. This refers to the nonconnectable scannable undirected advertisement type. The value in Hex is shown to the right: 0x6 and is defined in the official specification:

ADV_SCAN_IND PDU type definition from Bluetooth specification

Payload length is 28 (bytes).
Appearance value is Unknown (0x0000).
Advertising Flags:
LE Limited Discoverable Mode: No
LE General Discoverable Mode: Yes
Simultaneous LE and BR/EDR (Controller): No
Simultaneous LE and BR/EDR (Host): No
Device name (Local Name): “Nordic_Blinky
Raw Data:

Raw advertisement packet data in hex

Let’s take a look at how the raw data maps to the values listed in the Details screenshot.

Advertising physical channel PDU header:

Advertisement PDU header raw data breakdown

Payload
The payload for an ADV_SCAN_IND packet is defined in the specification as:

ADV_SCAN_IND payload format from specification

So, we have the AdvA (Bluetooth Address of the advertising device) and the advertising data:

Bluetooth Address: CD:54:DD:CD:90:6A

Bluetooth address bytes in raw packet data

AdvData (Advertisement Data):
The advertising data takes the following format (from the Bluetooth specification):

Advertisement data structure format from specification

This follows the well-known LTV (length-type-value) format. In our example, we have the following fields:

Advertisement data fields breakdown

Now, let’s look at what each of these fields represents.

To do this, we need to refer to:

From these references, we can now analyze the fields:

03 19 00 00:
03 is the Length.
This field has type = 0x19 which represents «Appearance».
It has a value of 0x00 0x00 which maps to “Unknown”.

02 01 06:
02 is the Length.
This field has type = 0x01 which represents «Flags».
Flags are defined as following (from the CSS document):

Advertising Flags bit definitions from specification

In our example, the Flags field has a value of 0x06 (or 00000110 binary) which means:

Bit 1 is set –> LE General Discoverable Mode is enabled
Bit 2 is set –> Br/EDR Not Support is enabled

0E 09 4E 6F 72 64 69 63 5F 42 6C 69 6E 6B 79:
This field has type = 0x09 which represents «Complete Local Name» or what’s known as the Device Name. It has a value of 4E 6F 72 64 69 63 5F 42 6C 69 6E 6B 79 (in Hex), which maps to “Nordic_Blinky” in ASCII.

The last bytes are the CRC of the Payload:

CRC bytes in advertisement packet

Connectable Scannable Undirected Advertisements

Now, let’s look at another type of advertisement packet: a connectable scannable undirected advertisement. We won’t go into the details as we did for the previous advertisement type example.

Connectable scannable undirected advertisement sniffer capture

In this example, we notice a lot of similarities to the previous example. All we changed is the type of advertisement.

Here are the details of the selected packet:

Connectable advertisement Link Layer details
Connectable advertisement packet details

Connection Requests

One of the most important packets in BLE is the Connection Request packet. It’s the packet that the Central sends to an advertising Peripheral (sending a connectable advertising packet) to initiate a connection.

This packet type is very important because of the crucial information it contains (which the Central needs to convey to the Peripheral).

Let’s take a look at the contents of a connection request packet (taken from the official Bluetooth specification document):

Connection request packet format from Bluetooth specification

The important information is contained in the LLData part of the packet:

Sample Connection Request Packet (Sniffer Capture)

Here’s an example of a connection request packet as captured by the Ellisys Bluetooth Tracker sniffer:

Connection request packet sniffer capture

And here are the details of the packet:

Connection request packet details part 1
Connection request packet details part 2

Let’s look at the raw data and see how it maps to these details:

Connection request raw packet data

Now, let’s look at the LLData fields:

LLData fields in CONNECT_IND packet

Keep in mind that the data displayed here is LSB to MSB (as it is received at the receiver, from left to right).

That’s it. Now you know about every single bit in a connection request packet!

Post-Connection Requests and Operations

Once two BLE devices are connected, they go through a few operations and data packet exchanges that are necessary before any user-initiated data is exchanged between the two.

Let's look at the following operations:

Version Exchange

This packet gets sent from each of the devices to inform the peer of the Bluetooth version used and the manufacturer of the chipset.

Let’s take a look at the packets being exchanged in the following example (iPhone Xs MAX <–> Nordic nRF52840 DK):

Version exchange packets between iPhone and Nordic DK

As you can see, the packet is sent from each of the devices. Each contains four fields:

Following is what is sent by each of the devices (first being the iPhone, and second being the Nordic DK):

iPhone version exchange details
Nordic DK version exchange details

Feature Exchange

Since not all features within a Bluetooth specification version are mandatory (some are optional), the devices need to communicate to each other what specific features are indeed supported.

This is done via the Feature Exchange packet.

One of the devices sends the LL_FEATURE_REQ listing all the features it supports, and the other device responds back with an LL_FEATURE_RSP listing the features it supports.

As of version 5.1 of the Bluetooth specification, there are 28 defined bits that indicate whether a feature is supported or not.

Let’s take a look at the list of features supported by each of the devices in our example.

iPhone:

iPhone supported Bluetooth LE features list

Nordic Dev Kit:

Nordic DK supported Bluetooth LE features list

Exchange MTU

First, let’s define what MTU is:

MTU stands for Maximum Transfer Unit and it defines the maximum amount of data that can be handled by the transmitter and receiver and which they can hold in their buffers.

There is no limit per the spec on how high the MTU value can be, but the specific stack in use may have its own limitations.

The effective MTU gets determined by the minimum value of ATT MTU that the client and server support.

For example, if a client supports an ATT MTU of 100 bytes and the server responds that it supports an ATT MTU of 150 bytes, then the client will decide that the ATT MTU to be used for the connection from thereon is 100 bytes.

Here’s a look at the Exchange MTU packet in our example. The client (iPhone) sends an Exchange MTU Request packet and the server (Nordic DK) sends back an Exchange MTU Response packet.

Exchange MTU packet sent by the iPhone, with an MTU value of 293 bytes:

Exchange MTU request from iPhone with value 293

Exchange MTU packet sent by the Nordic Dev Kit, with an MTU value of 23:

Exchange MTU response from Nordic DK with value 23

Attribute Discovery

The Attribute discovery process happens whenever two devices connect to each the first time.

After that, the client may cache all attributes (including services, characteristics, descriptors, etc.) to avoid performing the discovery process the next time the two devices connect.

If a client does not cache the attributes, then it must perform the discovery process each time it connects to the server.

The attribute list will contain a key-value pair for each of the attributes with an Attribute Handle being the key and the Attribute Type (UUID) being the value.

If the server would like to support changes in the attribute handles (e.g. due to a firmware update or factory reset), then it must include the Service Changed characteristic to notify the client that an attribute handle has changed, which in turn triggers a service discovery.

Here’s a look at what the discovery process looks like:

GATT attribute discovery process in sniffer capture

As you can see, this usually requires multiple packets especially if there are many defined services and characteristics on the server-side.

The client keeps requesting to read the Attributes until it receives “Attribute Not Found” error message which indicates the end of the discovery process:

Attribute Not Found response ending discovery process

Reading the Device Name

Another request that usually gets sent by the client (especially in applications that have a UI element) is reading the Device Name.

This is a simple Read Request GATT operation:

Read Device Name GATT operation in sniffer capture

Conclusion

We covered a lot in this post! Let's do a quick recap of what we went through:

I've found that being comfortable reading packets at the bit level makes a huge difference when you're trying to track down issues in your Bluetooth LE applications. If something isn't working as expected, a sniffer capture will almost always point you in the right direction.

There are, of course, many more Bluetooth LE packets and events beyond what we covered here, including GATT operations (Read, Write, Notifications, Indications) and miscellaneous events like Connection Parameters Update, PHY Update, and Data Length Extension (DLE).

💡
Insider Tip: Want to go even deeper into Bluetooth LE packets and events? Check out the Bluetooth Developer Academy, where I cover additional packets with downloadable Ellisys capture files you can analyze on your own computer!

Read next