Bluez tutorial BLE link layer PDU

 

Chapter 8 — bluez tutorial BLE Link Layer PDU
Part 5: PDU Types — Advertising Channel PDU, Data Channel PDU & SN/NESN Acknowledgment
Adv PDU Types
4 variants
Scan PDUs
SCAN_REQ + RSP
Data Header
LLID, SN, NESN, MD
MIC
4 octets (encrypted)

Keywords:

BLE PDU format explained BLE advertising channel PDU ADV_IND ADV_DIRECT_IND BLE SCAN_REQ SCAN_RSP BLE CONNECT_REQ PDU BLE data channel PDU LLID BLE SN NESN flow control BLE MIC encrypted BLE MD more data bit BLE TxAdd RxAdd address type

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

The 2-Octet Header — Every Field Decoded

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.

Advertising Channel PDU — Header & Payload Layout

HEADER
2 octets (16 bits)
PAYLOAD
6 to 37 octets (as set by the Length field in the header)

Header bit layout — LSB on the left
PDU Type
4 bits
[3:0]
RFU
2 bits
[5:4]
TxAdd
1 bit
[6]
RxAdd
1 bit
[7]
Length
6 bits
[13:8]
RFU
2 bits
[15:14]
RFU = Reserved for Future Use — always set to 0, ignored on receive

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

Category 1 — Advertising PDUs (Table 8.1)

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:

Four Advertising PDU Types — When to Use Each

ADV_IND
Connectable Undirected
The most common advertising type. The device broadcasts to anyone within range and is willing to accept a connection from any scanner. A BLE heart rate monitor sending ADV_IND is saying “I exist, I am a heart rate monitor, and any phone can connect to me.” Received by both Scanners and Initiators.

ADV_DIRECT_IND
Connectable Directed
A reconnection-optimised advertising type. The payload contains both the Advertiser’s address and the specific address of the device it wants to reconnect with. Only the named target will respond. Used by bonded devices trying to re-establish a previously paired connection quickly. Devices that see this packet but are not the named target ignore it entirely.

ADV_NONCONN_IND
Non-Connectable Directed
A pure broadcast — the device is not connectable at all. Nobody can send a SCAN_REQ or CONNECT_REQ in response to this PDU. Used for one-way beacons that broadcast sensor data or proximity information without ever forming a connection. Ideal for TX-only devices: the device never needs a receiver at all since it will never process incoming packets.

ADV_SCAN_IND
Scannable Undirected
Scannable but not connectable. A Scanner may send a SCAN_REQ to get more data from this Advertiser, but cannot send a CONNECT_REQ. The device provides additional information on request but refuses connections. Useful for broadcast devices that want to offer supplementary data without accepting a connection — for example a retail display sending product details.
Advertising PDU Types — Quick Comparison
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(&params, 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, &params);
Category 2 — Scanning PDUs (Table 8.2)

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.

SCAN_REQ

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.

Payload: ScanA (scanner addr 6 bytes) + AdvA (advertiser addr 6 bytes)
SCAN_RSP

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.

Payload: AdvA (6 bytes) + ScanRspData (0–31 bytes)
Scanning PDU Exchange — Same Channel, Same Advertising Event

All three packets below are on the SAME advertising channel (e.g. channel 37)

Advertiser
ADV_IND (step 1 — initial broadcast)
Scanner
Advertiser
SCAN_REQ (step 2 — scanner asks for more)
Scanner
Advertiser
SCAN_RSP (step 3 — supplemental data)
Scanner
Advertising event closed. Advertiser moves to next channel for the next 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.

Category 3 — Initiating PDU (Table 8.3)

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.

CONNECT_REQ

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

The Data Channel PDU Header — Every Bit Has a Purpose

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.

Data Channel PDU — Header, Payload, and MIC Layout

HEADER
2 octets
PAYLOAD
0–247 bytes (Length field includes MIC if present)
MIC
4 octets
(only if encrypted)

Header bit layout — LSB on the left
LLID
2 bits
[1:0]
NESN
1 bit
[2]
SN
1 bit
[3]
MD
1 bit
[4]
RFU
3 bits
[7:5]
Length
8 bits
[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

A Single Bit Acknowledgment System That Just Works

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.

Step-by-Step SN/NESN Walk-Through

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.

SN/NESN Mechanism — Full Packet Exchange Trace

Master
Slave

Both devices enter Connection state — tSN=0, nESN=0 for both

Sends:
SN=0, NESN=0
tSN=0, nESN=0
──────────────→
Packet 1 (good)
Receives SN=0
SN=tSN ✓ new packet
Increment nESN→1

Receives NESN=1
NESN≠nESN ✓ acked!
Increment tSN→1
Increment nESN→1
←──────────────
Reply SN=0, NESN=1 (acks Master pkt)
Sends:
SN=0, NESN=1
NESN=1 = ack to Master

Sends:
SN=1, NESN=1
New packet with SN=tSN=1
──────────────→
⚠ Packet has an error!
CRC error detected
Do NOT increment nESN
nESN stays at 1

Receives NESN=1
NESN = nESN!
Previous pkt NOT acked
Must retransmit SN=1
←──────────────
Reply SN=0, NESN=1 (same NESN=no ack)
Sends:
SN=0, NESN=1
NESN=1 unchanged = no ack

Retransmits:
SN=1, NESN=1
Same SN=1 (retry)
──────────────→
✓ Received correctly this time
Receives SN=1=tSN
New packet ✓
Increment nESN→0
(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;
}

Section 8.9 Complete — PDU Format Summary

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)

 

Leave a Reply

Your email address will not be published. Required fields are marked *