4 variants
SCAN_REQ + RSP
LLID, SN, NESN, MD
4 octets (encrypted)
Keywords:
8.9.4 — Two Types of PDU
After the Access Address in every BLE packet comes the PDU (Protocol Data Unit) — this is where the actual content of the packet lives. The PDU carries different information depending on whether the packet is travelling on an advertising channel or a data channel. BLE defines exactly two PDU types to keep things simple:
Advertising Channel PDU
Carried on the three advertising channels (37, 38, 39). Used for device discovery, service announcement, connection setup and broadcast.
Data Channel PDU
Carried on the 37 data channels during an active connection. Used for GATT data transfer and link layer control procedures.
8.9.4.1 — Advertising Channel PDU Format
Every advertising channel PDU begins with a 2-byte (16-bit) header followed by a variable-length payload. The header packs several fields into those 16 bits. Understanding each field is essential for reading sniffer captures and for writing BLE advertising code.
[3:0]
[5:4]
[6]
[7]
[13:8]
[15:14]
PDU Type (4 bits)
Identifies what kind of advertising channel PDU this is. The four bits encode one of the advertising, scanning, or initiating PDU types. The receiver uses this field first to know how to interpret the payload. For example, 0b0000 = ADV_IND, 0b0011 = SCAN_REQ, 0b0101 = CONNECT_REQ.
TxAdd (1 bit) — Transmitter Address Type
Tells the receiver what kind of address the sender is using in the payload. 0 = public address (permanent, IEEE-assigned). 1 = random address (privacy-preserving, rotates over time). Not all PDU types use this field — it is only meaningful for the PDU types that include a sender address in the payload.
RxAdd (1 bit) — Receiver Address Type
The mirror of TxAdd — tells the receiver what kind of address the intended recipient is using. 0 = public address. 1 = random address. Again, only valid for PDU types that carry a target address (like ADV_DIRECT_IND which is directed at a specific device).
Length (6 bits)
The size of the payload field in bytes. Since this is 6 bits, the maximum value is 63. However, BLE further constrains valid payload sizes to 6–37 bytes depending on the PDU type. The receiver uses this to know exactly how many bytes to read as the payload — it does not have to guess where the packet ends.
/* BlueZ advertising PDU header structure (from net/bluetooth/hci.h) */
struct le_adv_pdu_hdr {
uint8_t pdu_type:4; /* PDU type: ADV_IND=0x0, ADV_DIRECT=0x1, */
/* ADV_NONCONN=0x2, SCAN_REQ=0x3, */
/* SCAN_RSP=0x4, CONNECT_REQ=0x5, */
/* ADV_SCAN=0x6 */
uint8_t rfu1:2; /* reserved, ignore on rx, set 0 on tx */
uint8_t tx_add:1; /* 0 = public address, 1 = random address */
uint8_t rx_add:1; /* 0 = public address, 1 = random address */
uint8_t length:6; /* payload length in octets (valid: 6–37) */
uint8_t rfu2:2; /* reserved */
} __attribute__((packed));
/* Reading TxAdd/RxAdd when parsing a received advertising report */
void parse_adv_header(const struct le_adv_pdu_hdr *hdr)
{
printf("PDU type : 0x%01X\n", hdr->pdu_type);
printf("TX address : %s\n",
hdr->tx_add ? "random" : "public");
printf("RX address : %s\n",
hdr->rx_add ? "random" : "public");
printf("Payload len: %d bytes\n", hdr->length);
}
Three Categories of Advertising Channel PDUs
Advertising PDUs are sent by a device in the Advertising state and received by devices in either the Scanning or Initiating state. There are four variants, each suited to a different advertising scenario:
| PDU Name | Connectable? | Scannable? | Directed? | Typical Use |
|---|---|---|---|---|
| ADV_IND | ✓ Yes | ✓ Yes | ✗ No | General sensor advertising to anyone |
| ADV_DIRECT_IND | ✓ Yes | ✗ No | ✓ Yes | Fast reconnect to known paired device |
| ADV_NONCONN_IND | ✗ No | ✗ No | ✗ No | TX-only beacon, pure one-way broadcast |
| ADV_SCAN_IND | ✗ No | ✓ Yes | ✗ No | Provide extra data on request, no connections |
/* Setting advertising type in BlueZ HCI */
/* le_set_advertising_parameters_cp.advtype field values */
#define LE_ADV_IND 0x00 /* connectable undirected */
#define LE_ADV_DIRECT_IND 0x01 /* connectable directed */
#define LE_ADV_NONCONN_IND 0x02 /* non-connectable */
#define LE_ADV_SCAN_IND 0x06 /* scannable undirected */
/* Example: set up non-connectable advertising (beacon mode) */
le_set_advertising_parameters_cp params;
memset(¶ms, 0, sizeof(params));
params.min_interval = htobs(0x0320); /* 500ms */
params.max_interval = htobs(0x0320);
params.advtype = LE_ADV_NONCONN_IND; /* beacon, no connections */
params.chan_map = 0x07; /* all 3 adv channels */
hci_send_cmd(sock, OGF_LE_CTL,
OCF_LE_SET_ADVERTISING_PARAMETERS,
LE_SET_ADVERTISING_PARAMETERS_CP_SIZE, ¶ms);
Scanning PDUs are exchanged between a Scanner and an Advertiser when the Scanner wants more information than what the initial advertising packet contained. This is a two-packet handshake that happens within the same advertising event on the same channel.
Sender: Scanner → Receiver: Advertiser
The Scanner sends this after receiving an ADV_IND or ADV_SCAN_IND to ask the Advertiser for supplemental data. The payload contains both the Scanner’s address and the Advertiser’s address. The Advertiser checks that the request is addressed to itself before responding.
Sender: Advertiser → Receiver: Scanner
The Advertiser’s reply to a SCAN_REQ. Contains up to 31 bytes of additional advertising data — for example, the full device name if it was too long for the ADV_IND, or additional services and manufacturer-specific data. Sent on the same channel as the original ADV_IND.
Referring back to the sniffer capture from the previous section: Frame #427 was ADV_IND from the Advertiser, Frame #428 was SCAN_REQ from the Scanner, and Frame #429 was SCAN_RSP from the Advertiser — all three on channel 37, all forming a single advertising event.
There is exactly one initiating PDU: CONNECT_REQ. It is sent by the Initiator in response to an ADV_IND or ADV_DIRECT_IND from an Advertiser that the Initiator wants to connect to.
Sender: Initiator state → Receiver: Advertising state
The CONNECT_REQ is the most information-rich of all advertising channel PDUs. Its payload contains the complete set of parameters needed to establish the connection: both device addresses, the randomly generated Access Address for this connection, CRC initialisation value, transmit window offset and size, connection interval, slave latency, supervision timeout, channel map, and hop increment. After receiving this PDU, both devices transition to the Connection state and the link layer state machine starts using the data channels.
/* Monitoring for CONNECT_REQ with btmon to see connection params */
/* Run: sudo btmon | grep -A 30 "LE Enhanced Connection" */
/* When a connection is made, btmon shows: */
/* > HCI Event: LE Meta Event (0x3e) */
/* LE Connection Complete (0x01) */
/* Status: Success (0x00) */
/* Handle: 0x0040 */
/* Role: Master (0x00) ← we sent CONNECT_REQ = we are Master */
/* Address type: Public (0x00) */
/* Address: AA:BB:CC:DD:EE:FF (Polar H7) */
/* Connection interval: 50.00 ms (0x0028) */
/* Connection latency: 0 (0x0000) */
/* Supervision timeout: 2000 ms (0x00c8) */
/* Master clock accuracy: 500 ppm */
8.9.4.2 — Data Channel PDU Format
Once a BLE connection is established, all data flows through data channel PDUs. The header is again 2 bytes, but with a completely different set of fields from the advertising channel PDU header. Each bit serves a specific function in managing the flow of packets between Master and Slave.
(only if encrypted)
[1:0]
[2]
[3]
[4]
[7:5]
[15:8]
LLID — Link Layer ID (2 bits)
Identifies the type of the PDU: whether it is carrying L2CAP data upward to the host or is a Link Layer control PDU that the Link Layer itself must process. Two values matter: 0b10 = LL data PDU (carries L2CAP frames); 0b11 = LL control PDU (carries Link Layer control opcodes for connection management).
MD — More Data (1 bit)
A single-bit flag that tells the other device whether the sender has more data queued up after this packet. If MD=1, the sender wants to keep the connection event open to send more packets. If MD=0, the sender has no more data and is happy for the connection event to close. Both Master and Slave can set this bit. If either sets MD=1, the connection event continues. The event only closes when both have MD=0 simultaneously.
Length (8 bits)
The total size of the payload field in bytes, including the MIC field if present. This is 8 bits (vs the 6-bit length in advertising PDUs), allowing data payloads up to 251 bytes in BLE 4.2+. The receiver reads exactly this many bytes as the PDU body.
MIC — Message Integrity Check (4 octets, optional)
Included only when the connection is encrypted and the payload is non-empty. The MIC is a 4-byte authentication tag computed using AES-128 over the PDU contents. The receiver recomputes the MIC and compares it — if they differ, the PDU was tampered with or corrupted, and it is discarded. This provides integrity protection on top of the CRC’s error detection.
SN and NESN — Acknowledgment & Flow Control
The SN (Sequence Number) and NESN (Next Expected Sequence Number) fields together implement a lightweight but robust stop-and-wait acknowledgment protocol. Using just two bits of state, BLE achieves reliable in-order packet delivery with automatic retransmission of lost or corrupted packets.
Each device maintains two 1-bit counters in its Link Layer:
tSN
transmitSeqNum
My current send sequence number. Starts at 0. I put this value in the SN field of every packet I send. I increment it when the peer acknowledges my last packet.
nESN
nextExpectedSeqNum
The SN value I expect in the peer’s next new packet. Starts at 0. I put this value in the NESN field of packets I send. I increment it when I successfully receive a new packet from the peer.
Both Master and Slave start in the Connection state with tSN=0 and nESN=0. The diagrams below trace exactly what happens, including both the success path and the retransmission path when a packet has an error.
Increment nESN→1
(wraps: 1+1 mod 2 = 0)
The two key rules summarised:
Receiving a packet correctly:
Increment nESN (toggles between 0 and 1). The next packet I send will carry the new nESN value in its NESN field, which signals to the sender that I received its last packet.
Receiving a packet with an error:
Do NOT change nESN. The next packet I send will carry the same nESN as before. When the sender sees its own nESN value returned unchanged, it knows the packet was not received and retransmits the same packet with the same SN.
Why only 1-bit sequence numbers? This is a stop-and-wait protocol — only one unacknowledged packet is in flight at any time. With only one outstanding packet, you only need to distinguish between two states: “same packet as before” (not acknowledged, retry it) or “next packet” (previous one was acknowledged, send new one). One bit is exactly enough to represent these two states.
/* BLE SN/NESN logic — simplified from BlueZ LL implementation */
/* Based on Bluetooth Core Spec 4.0, Section 4.5.9 */
struct ll_conn_tx_state {
uint8_t tSN; /* transmitSeqNum — 1-bit, so only 0 or 1 */
uint8_t nESN; /* nextExpectedSeqNum — 1-bit, only 0 or 1 */
};
/*
* Called when we receive a data channel PDU from the peer.
* Returns 1 if this is a new packet (not a duplicate).
*/
int process_received_pdu(struct ll_conn_tx_state *state,
uint8_t rx_sn, /* SN from received header */
uint8_t rx_nesn,/* NESN from received header */
int crc_ok) /* 1 if CRC matched */
{
/* Step 1: check if peer acknowledged our last transmission */
if (rx_nesn != state->nESN) {
/*
* NESN changed → our last packet was acknowledged.
* Advance our transmit sequence number and allow
* sending the next new packet.
*/
state->tSN = (state->tSN + 1) & 0x01; /* toggle 0↔1 */
}
/* else: NESN unchanged → our last packet needs retransmission */
/* Step 2: if CRC is good, check if this is a new inbound packet */
if (crc_ok && (rx_sn == state->nESN)) {
/*
* SN matches what we expected → this is a new packet.
* Acknowledge it by incrementing our nESN.
*/
state->nESN = (state->nESN + 1) & 0x01; /* toggle 0↔1 */
return 1; /* caller should pass payload to L2CAP */
}
/*
* Either CRC failed or SN != nESN (duplicate / out-of-order).
* Drop this PDU. nESN unchanged = implicit retransmit request.
*/
return 0;
}
Adv PDU Header
- PDU Type: 4 bits
- TxAdd/RxAdd: address type flags
- Length: 6 bits (payload size)
- Payload: 6–37 bytes
Advertising PDUs
- ADV_IND — general broadcast
- ADV_DIRECT_IND — reconnect
- ADV_NONCONN_IND — beacon
- ADV_SCAN_IND — info on request
Scan/Init PDUs
- SCAN_REQ — ask for more data
- SCAN_RSP — supply extra data
- CONNECT_REQ — start connection
- All on same channel as ADV
Data PDU & Flow Control
- LLID: data vs control PDU
- MD: keep connection event open
- SN/NESN: 1-bit stop-and-wait ACK
- MIC: 4-byte auth (encrypted only)
