Link Manager Protocol & Host Controller Interface

Bluetooth Lower Layers — Part 3
Link Manager Protocol & Host Controller Interface — explained simply, with code
LMP
Link Manager
HCI
Host ↔ Controller
4
Security Layers
4
HCI Packet Types

What is Link Manager Protocol & Host Controller Interface?

hello students welcome to this free bluetooth development course in c, in this lecture we will discuss about Link Manager Protocol & Host Controller Interface In the previous part we covered the Baseband — the raw radio scheduling layer. The Link Manager (LM) sits right above it and acts as the “operations manager” for a Bluetooth connection. It decides how to set up a link, how to keep it alive, how to tear it down, and how to secure it — all by exchanging special control messages called LMP PDUs.

Think of it this way: the Baseband is the road, and the Link Manager is the traffic controller standing in the middle — deciding who goes, how fast, and whether vehicles need an identity check before entering.

Key Terms in This Post

LMP PDU Connection Establishment Link Supervision AFH Pairing Authentication Encryption Secure Simple Pairing HCI HCI Command HCI Event OGF / OCF BlueZ btmon

Where Does LMP Sit in the Stack?

Classic Bluetooth Protocol Stack
Application / Profiles (A2DP, HFP…)
L2CAP & RFCOMM
HCI (Host Controller Interface)
▼ ▲ (command / event packets)
🔴 Link Manager (LM) — WE ARE HERE
Baseband
Radio (2.4 GHz)

The Link Manager lives inside the controller. It talks to the Baseband below using hardware registers and talks to the Host above through the HCI layer. Two Link Managers — one on each device — communicate peer-to-peer by exchanging LMP PDUs over the air.

3.4.1 — Connection Control

Connection control covers everything that happens from the moment you say “connect” to the moment you say “disconnect”. Let’s walk through each piece.

Connection Establishment

When device A wants to connect to device B, the Link Manager on A sends an LMP_host_connection_req PDU. Device B’s LM accepts it. From this point the initiating device becomes the Master and the other becomes the Slave.

If you want it the other way around (your device is the initiator but you still want to be the Slave), the LM can do a role switch right during setup.

LMP Connection Flow

Device A (Master)
host_connection_req ──▶
features_req ──▶
◀── features_res
◀── setup_complete
✅ Connected

Device B (Slave)
◀── receives req
◀── features_req
features_res ──▶
setup_complete ──▶
✅ Connected

You can observe this in real-time using btmon:

## Start btmon before pairing/connecting
$ sudo btmon

## Sample output — LMP connection packets visible in HCI trace
< HCI Command: Create Connection (0x01|0x0005) plen 13
        Address: AA:BB:CC:DD:EE:FF (Unknown)
        Packet type: 0xcc18
        Page scan repetition mode: R1 (0x01)
        Page scan mode: Mandatory (0x00)
        Clock offset: 0x0000
        Role switch: Allow slave (0x01)

> HCI Event: Command Status (0x0f) plen 4
        Create Connection (0x01|0x0005) ncmd 1
        Status: Success (0x00)

> HCI Event: Connection Complete (0x03) plen 11
        Status: Success (0x00)
        Handle: 0x0001
        Address: AA:BB:CC:DD:EE:FF (Unknown)
        Link type: ACL (0x01)
        Encryption: Disabled (0x00)

Link Supervision — The Heartbeat Timer

Once connected, both devices run a supervision timer. The Baseband keeps sending POLL/NULL packets as heartbeats. If no heartbeat is received for the negotiated timeout period, the Link Manager declares the link dead and reports a link supervision timeout to the Host.

Link Supervision Timer Flow
Link
Established
POLL / NULL
heartbeats
No heartbeat
in T_supervison?
Timeout ⚠️
Report to Host
Default timeout is negotiated at connection setup (can be 0.625ms × N, up to 40.9s)
/* Reading link supervision timeout via BlueZ / HCI socket */
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

void read_link_supervision_timeout(int hci_sock, uint16_t handle)
{
    struct hci_request req;
    read_link_supervision_timeout_rp rp;
    read_link_supervision_timeout_cp cp;

    memset(&cp, 0, sizeof(cp));
    cp.handle = htobs(handle);

    memset(&req, 0, sizeof(req));
    req.ogf    = OGF_HOST_CTL;
    req.ocf    = OCF_READ_LINK_SUPERVISION_TIMEOUT;
    req.cparam = &cp;
    req.clen   = sizeof(cp);
    req.rparam = &rp;
    req.rlen   = sizeof(rp);

    if (hci_send_req(hci_sock, &req, 1000) < 0) {
        perror("hci_send_req");
        return;
    }

    /* timeout is in units of 0.625 ms */
    printf("Link supervision timeout: %u slots = %.1f ms\n",
           btohs(rp.timeout),
           btohs(rp.timeout) * 0.625);
}

Adaptive Frequency Hopping (AFH)

Bluetooth hops across 79 channels. If Wi-Fi is active nearby it pollutes several of those channels. AFH lets the Master tell every Slave: “skip these bad channels”. The channel map is a 79-bit bitmap — each bit says whether that channel is good or bad.

AFH Channel Map (simplified — 79 channels)
Good channel Bad (Wi-Fi interference)
/*
 * Send SET_AFH (LMP_set_AFH) via HCI to enable AFH on a connection.
 * In BlueZ this is handled internally, but the HCI command looks like:
 */

/* HCI_Write_AFH_Channel_Assessment_Mode */
uint8_t mode = 0x01; /* enable automatic channel assessment */

/* 10-byte channel map: 79 bits, bit N = channel N quality */
/* 0xFF = good, 0x00 = bad/unused */
uint8_t channel_map[10] = {
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  /* channels 0..39  — all good */
    0xFF, 0xFF, 0xFF, 0xFF, 0x7F   /* channels 40..78 */
    /* To mark channels 3-7 bad: clear those bits in byte 0 */
};

/* BlueZ command line: check AFH status */
// $ hciconfig hci0 afh
// $ btmgmt info

3.4.2 — Security in the Link Manager

Security in Classic Bluetooth operates at the Link Manager layer. It has four stages — and they build on each other. You can’t do encryption without first doing authentication, and you can’t do authentication without a shared key from pairing.

Security Dependency Chain
🔒 Secure Simple Pairing (v2.1+)
Better UX + ECDH key agreement
builds on ▼
🔐 Encryption
Requires authentication first
builds on ▼
🆔 Authentication
Requires a shared link key
builds on ▼
🤝 Pairing
Creates the shared link key

3.4.2.1 — Pairing (Legacy)

Pairing is a one-time ceremony. The user enters a PIN on both devices (like “1234”). The Link Managers on both sides take that PIN, mix it with a random number and each device’s BD_ADDR, and derive a Link Key. This key is stored persistently — so the next time these same two devices meet, no PIN is needed.

Legacy Pairing — Link Key Derivation
PIN (user input)
Random Number (IN_RAND)
BD_ADDR
E22
Algorithm
🔑 Link Key
(128-bit, stored)
/* Monitoring pairing in BlueZ using btmon */

$ sudo btmon

/* You will see something like: */
> HCI Event: Link Key Request (0x17) plen 6
        Address: AA:BB:CC:DD:EE:FF

/* If key not found, LM initiates pairing: */
< HCI Command: PIN Code Request Reply (0x01|0x000d) plen 23
        Address: AA:BB:CC:DD:EE:FF
        PIN length: 4
        PIN code: 31 32 33 34  "1234"

> HCI Event: Link Key Notification (0x18) plen 23
        Address: AA:BB:CC:DD:EE:FF
        Link key: a1 b2 c3 d4 e5 f6 ...
        Key type: Combination key (0x00)

3.4.2.2 — Authentication (Challenge-Response)

Every time two previously paired devices reconnect, the LM runs a challenge-response check. Device A (the verifier) sends a random number. Device B computes a response using the random number, its own BD_ADDR, and the stored Link Key. Device A does the same calculation independently. If both answers match — identity confirmed.

Challenge-Response Authentication

Device A (Verifier)
Generate AU_RAND
Send AU_RAND ──▶
Compute E1(Key, BD_ADDR_B, AU_RAND)
◀── Receive SRES
Compare — match? ✅

Device B (Claimant)
(wait)
◀── Receive AU_RAND
Compute E1(Key, BD_ADDR_B, AU_RAND)
Send SRES ──▶
Done — wait for result

3.4.2.3 — Encryption

Encryption is optional, but if both sides agree to use it, every byte on the air gets encrypted using a session key derived from the Link Key. The encryption key size can range from 8 to 128 bits — negotiated by the two LMs during connection.

Encryption Enable Sequence
Auth ✅
(prerequisite)
Negotiate
key size
Derive
Encryption Key
🔒 All data
encrypted
/* Enable encryption via HCI — BlueZ example */

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

int enable_encryption(int dd, uint16_t handle)
{
    struct hci_request        rq;
    set_conn_encrypt_cp       cp;
    set_conn_encrypt_rp       rp;

    cp.handle  = htobs(handle);
    cp.encrypt = 0x01;  /* 0x01 = enable encryption */

    memset(&rq, 0, sizeof(rq));
    rq.ogf    = OGF_LINK_CTL;
    rq.ocf    = OCF_SET_CONN_ENCRYPT;
    rq.cparam = &cp;
    rq.clen   = SET_CONN_ENCRYPT_CP_SIZE;
    rq.rparam = &rp;
    rq.rlen   = SET_CONN_ENCRYPT_RP_SIZE;
    rq.event  = EVT_ENCRYPT_CHANGE;

    if (hci_send_req(dd, &rq, 2000) < 0) {
        perror("hci_send_req");
        return -1;
    }

    if (rp.status) {
        fprintf(stderr, "Encryption failed: %s\n",
                strerror(EIO));
        return -1;
    }

    printf("Encryption enabled on handle 0x%04x\n", handle);
    return 0;
}

3.4.2.4 — Secure Simple Pairing (SSP)

Legacy pairing had two problems: the PIN was often “0000” or “1234” (weak), and the user experience was clunky. Secure Simple Pairing, introduced in Bluetooth 2.1 + EDR, fixes both.

SSP uses Elliptic Curve Diffie-Hellman (ECDH) key exchange. The devices generate public/private key pairs and agree on a shared secret without ever transmitting the secret itself. This protects against passive eavesdropping even if someone records the entire pairing exchange.

SSP — Four Association Models
🔢
Numeric Comparison
Both devices show a 6-digit number. User confirms they match.
Use: display on both sides
⌨️
Passkey Entry
One device displays a code, user types it on the other.
Use: keyboard + display combo
🏷️
Out of Band (OOB)
Keys exchanged over a second channel, e.g. NFC tap.
Use: NFC-capable devices
🤝
Just Works
No user interaction at all. Convenient but no MITM protection.
Use: headsets, IoT devices
/* SSP ECDH key exchange — conceptual pseudocode */

/* Each device generates an EC key pair */
ec_key_t my_key    = ec_generate_keypair(P192_CURVE);
uint8_t  my_pub[48]; /* 24-byte X + 24-byte Y coordinates */
ec_get_public_key(&my_key, my_pub);

/* Exchange public keys over the air (LMP_public_key_exchange_req/res) */
send_lmp_pdu(LMP_IO_CAPABILITY_REQ, io_cap, oob_present, auth_req);
recv_lmp_pdu(LMP_IO_CAPABILITY_RES, &peer_io_cap);

/* Both sides derive the same Diffie-Hellman key */
uint8_t dh_key[24];
ec_compute_dhkey(&my_key, peer_pub, dh_key);
/* dh_key is now identical on both devices without being transmitted */

/* Numeric comparison: derive 6-digit code from dh_key + nonces */
uint32_t code = ssp_numeric_code(dh_key, na, nb);
printf("Confirm code on both devices: %06u\n", code);

/* BlueZ command to see SSP pairing events in btmon */
// $ sudo btmon 2>&1 | grep -i "simple pairing\|io cap\|public key"

3.5 — Host Controller Interface (HCI)

The HCI is the boundary line between software (Host) and hardware (Controller). Everything above HCI — L2CAP, RFCOMM, profiles, your application — runs on the main processor. Everything below — Link Manager, Baseband, Radio — runs inside the Bluetooth chip.

This separation is what lets you plug any Bluetooth USB dongle into any PC and have it just work. The Host speaks a standardised HCI language; the Controller understands that same language regardless of who made it.

HCI — The Host / Controller Boundary

HOST (runs on main CPU)
Your App Profiles RFCOMM L2CAP HCI Driver

⚡ HCI (USB / UART / SDIO)
← HCI EventsHCI Commands →← ← ACL Data →→

CONTROLLER (inside the Bluetooth chip)
HCI Layer Link Manager Baseband Radio

💡 Note: HCI is optional in single-chip designs where Host and Controller firmware run on the same processor. In that case there is no physical HCI transport — function calls replace packets. But the HCI command/event API is often still used internally for easier porting.

3.5.1 — HCI Packet Types

All communication over HCI happens through four distinct packet types. Each packet starts with a 1-byte indicator that tells the transport layer which type it is.

📤
HCI Command Packet
Sent Host → Controller.
Tells the chip what to do:
scan, connect, reset, etc.
Indicator: 0x01
📨
HCI Event Packet
Sent Controller → Host.
Asynchronous notifications:
connection complete, error, etc.
Indicator: 0x04
📦
HCI ACL Data Packet
Bidirectional. Carries
asynchronous user data:
L2CAP frames, file transfers, etc.
Indicator: 0x02
🎙️
HCI SCO Data Packet
Bidirectional. Carries
synchronous audio:
voice calls (SCO/eSCO links).
Indicator: 0x03

HCI Command Packet Structure

Every HCI Command carries a 2-byte OpCode which is split into two sub-fields: the OGF (6 bits, group) and the OCF (10 bits, specific command within that group).

HCI OpCode — 16 bits
OGF
bits 15..10
(6 bits)
OCF
bits 9..0
(10 bits)

OGF Groups:
0x01 — Link Control 0x02 — Link Policy 0x03 — Host Control 0x04 — Info Params 0x08 — LE Commands

For example, HCI_Reset has OGF = 0x03 (Host Control) and OCF = 0x0003. Combining them: OpCode = (0x03 << 10) | 0x003 = 0x0C03.

/*
 * HCI Command packet layout in memory:
 *
 * Byte 0:    Packet indicator (0x01 for Command)
 * Bytes 1-2: OpCode (OCF in lower 10 bits, OGF in upper 6 bits)
 * Byte 3:    Parameter Total Length
 * Bytes 4+:  Parameters
 *
 * Example: HCI_Reset (no parameters)
 *   Indicator : 0x01
 *   OpCode    : 0x03 0x0C   (little-endian: OCF=0x003, OGF=0x03)
 *   Param Len : 0x00
 */

/* Send HCI Reset using raw socket */
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

int hci_reset(int dev_id)
{
    int dd = hci_open_dev(dev_id);
    if (dd < 0) {
        perror("hci_open_dev");
        return -1;
    }

    /* hci_send_cmd wraps the packet framing for us */
    /* OGF = 0x03 (Host_CTL), OCF = 0x0003 (Reset) */
    if (hci_send_cmd(dd, OGF_HOST_CTL, OCF_RESET, 0, NULL) < 0) {
        perror("hci_send_cmd");
        hci_close_dev(dd);
        return -1;
    }

    /* Wait for Command Complete event */
    struct hci_filter nf;
    hci_filter_clear(&nf);
    hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
    hci_filter_set_event(EVT_CMD_COMPLETE, &nf);
    setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));

    unsigned char buf[HCI_MAX_EVENT_SIZE];
    int len = read(dd, buf, sizeof(buf));
    if (len < 0) {
        perror("read");
        hci_close_dev(dd);
        return -1;
    }

    printf("HCI Reset complete. Status: 0x%02x\n", buf[6]);
    hci_close_dev(dd);
    return 0;
}

/* Quick command-line way */
// $ hciconfig hci0 reset
// $ btmgmt power off && btmgmt power on

Controller Types Supported Over HCI

BR/EDR Controller
Classic Bluetooth only. No BLE. Common in older audio devices.
BR/EDR/LE Controller
Dual-mode. Most modern phones and laptops use this. Supports both Classic and BLE simultaneously.
LE-Only Controller
BLE only. Used in IoT sensors, fitness trackers, beacons. Smaller, cheaper, lower power.
AMP Controller
Bluetooth 3.0 + HS. Used 802.11 Wi-Fi radio for high-speed data. Rare in practice, mostly deprecated.

Putting It All Together — Live Debug with btmon

The best way to see LMP and HCI in action is to run btmon while connecting a Bluetooth device. Here is what a full connect + authenticate + encrypt sequence looks like in btmon output:

$ sudo btmon

# ── STEP 1: Host sends HCI Create Connection command ──────────────────
< HCI Command: Create Connection (0x01|0x0005)
        Address: 11:22:33:44:55:66

# ── STEP 2: Controller acknowledges ────────────────────────────────────
> HCI Event: Command Status (0x0f)
        Create Connection (0x01|0x0005) ncmd 1
        Status: Success (0x00)

# ── STEP 3: Baseband completes paging, LMP exchange done ───────────────
> HCI Event: Connection Complete (0x03)
        Status: Success (0x00)
        Handle: 0x0001
        Address: 11:22:33:44:55:66
        Link type: ACL (0x01)
        Encryption: Disabled (0x00)     <-- not yet encrypted

# ── STEP 4: LM requests authentication ─────────────────────────────────
> HCI Event: Link Key Request (0x17)
        Address: 11:22:33:44:55:66

< HCI Command: Link Key Request Reply (0x01|0x000b)
        Address: 11:22:33:44:55:66
        Link key: a1 b2 c3 d4 e5 f6 a7 b8 c9 d0 e1 f2 a3 b4 c5 d6

# ── STEP 5: Auth done, now enable encryption ───────────────────────────
< HCI Command: Set Connection Encryption (0x01|0x0013)
        Handle: 0x0001
        Encryption: Enabled (0x01)

> HCI Event: Encryption Change (0x08)
        Status: Success (0x00)
        Handle: 0x0001
        Encryption: Enabled with E0 (0x01)   <-- link is now encrypted

# ── Everything above this line was LMP under the hood ──────────────────
# ── From here on all ACL data is ciphertext ────────────────────────────
/* Useful btmon filters for LMP/HCI study */

# Show only connection events
$ sudo btmon 2>&1 | grep -i "connection\|disconnect\|handle"

# Show only security events
$ sudo btmon 2>&1 | grep -i "encrypt\|link key\|pin\|pairing\|auth"

# Dump to file for later analysis
$ sudo btmon -w /tmp/capture.btsnoop

# Open saved btsnoop in Wireshark
$ wireshark /tmp/capture.btsnoop
# Filter: bthci_cmd || bthci_evt || btl2cap

Quick Reference — LMP PDUs and HCI Commands
LMP PDU / HCI Cmd Direction Purpose
LMP_host_connection_req Master → Slave (over air) Initiate ACL connection
LMP_detach Either → Either Gracefully disconnect
LMP_au_rand Verifier → Claimant Authentication challenge
LMP_sres Claimant → Verifier Authentication response
LMP_encryption_mode_req Either Request encryption enable/disable
LMP_set_AFH Master → Slave Set AFH channel map
HCI_Create_Connection Host → Controller Trigger connection to remote device
HCI_Disconnect Host → Controller Disconnect a specific handle
HCI_Set_Connection_Encrypt Host → Controller Enable or disable encryption
EVT_Connection_Complete Controller → Host Notify connection is up
EVT_Encryption_Change Controller → Host Notify encryption state changed
EVT_Link_Key_Notification Controller → Host New link key generated, store it

Summary

The Link Manager is the brains of a Classic Bluetooth connection. It handles everything from dialling up the initial link, keeping it alive with supervision timers, adapting the frequency hopping pattern to avoid interference, and locking the link down with pairing, authentication, and encryption.

The HCI is the clean interface that lets a Host talk to a Controller regardless of the chip vendor. Commands flow down, events flow back up, and ACL/SCO data flows in both directions — all over the same physical channel (USB, UART, or SDIO on embedded boards).

In the next part we will move up the stack into L2CAP — the layer that multiplexes many logical channels over a single ACL link and handles segmentation and reassembly of large payloads.

✅ LMP Connection Control ✅ Link Supervision ✅ AFH ✅ Pairing & Link Key ✅ Authentication ✅ Encryption ✅ Secure Simple Pairing ✅ HCI Architecture ✅ HCI Packet Types ✅ OGF / OCF OpCode i hope you have learnt about Link Manager Protocol & Host Controller Interface

Next Up: L2CAP — The Multiplexing Layer

We move up one more layer and look at how L2CAP channels, PSMs, MTU negotiation, and segmentation work in Classic Bluetooth.

Leave a Reply

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