What You Will Learn
Hello students welcome to free bluetooth development course, in this lecture i will explain you about Hci connection handle In the previous sections we covered HCI commands, events, and flow control. Now we go one level deeper — understanding how the HCI layer identifies individual connections using a Connection Handle, and how packets actually travel between the Host and the Controller over a physical transport like UART.
This is essential knowledge for anyone doing Bluetooth programming in C, debugging HCI logs, or building a BlueZ-based application.
3.5.5 — Connection Handle
When the HCI layer establishes a new logical link between the Host and a remote device, it needs a way to refer to that specific connection for all future operations. It does this by assigning a unique Connection Handle.
Think of it like a file descriptor in Linux — once a connection is open, every operation (sending data, polling status, disconnecting) uses this handle instead of the full device address.
Connection_Complete_Event once the connection is successfully established. After that, the Host must use this handle for all subsequent operations on that connection. HOST CONTROLLER
---- ----------
Send HCI_Create_Connection (BD_ADDR)
─────────────────────────────────────────────►
(starts connection procedure)
◄────────────────────────────────────────────────
Command_Status_Event
(cmd accepted, processing started)
◄────────────────────────────────────────────────
Connection_Complete_Event
{
Status = 0x00 (success)
Connection_Handle = 0x0040 ← assigned here
BD_ADDR = AA:BB:CC:DD:EE:FF
Link_Type = ACL
Encryption = 0x00
}
Host stores handle 0x0040
─────────────────────────────────────────────► (send ACL data on handle 0x0040)
─────────────────────────────────────────────► (disconnect on handle 0x0040)
Every one of the above commands takes the Connection_Handle as a parameter so the controller knows which active connection the command targets.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
/* -------------------------------------------------------
* The connection handle is a 12-bit value (bits 11:0).
* Bits 15:14 carry the Packet Boundary Flag and
* Broadcast Flag in ACL data packets.
* ------------------------------------------------------- */
#define CONN_HANDLE_MASK 0x0FFF
typedef struct {
uint16_t handle; /* 12-bit connection handle from controller */
bdaddr_t remote_addr; /* BD_ADDR of the remote device */
uint8_t link_type; /* 0x01 = ACL, 0x00 = SCO */
} hci_connection_t;
/* Called when Connection_Complete_Event (0x03) is received */
void on_connection_complete(const uint8_t *evt_buf, hci_connection_t *conn)
{
/*
* Connection Complete Event parameter layout:
* Byte 0 : Status
* Byte 1-2 : Connection_Handle (little-endian, 12 bits used)
* Byte 3-8 : BD_ADDR
* Byte 9 : Link_Type
* Byte 10 : Encryption_Mode
*/
uint8_t status = evt_buf[0];
uint16_t raw_handle;
if (status != 0x00) {
printf("[HCI] Connection failed, status=0x%02X\n", status);
return;
}
/* Extract 16-bit value; mask to 12 bits */
memcpy(&raw_handle, &evt_buf[1], sizeof(raw_handle));
conn->handle = raw_handle & CONN_HANDLE_MASK;
conn->link_type = evt_buf[9];
/* BD_ADDR is stored LSB first in HCI (little-endian) */
memcpy(&conn->remote_addr, &evt_buf[3], 6);
printf("[HCI] Connection established!\n");
printf(" Handle : 0x%04X\n", conn->handle);
printf(" Link Type : %s\n",
conn->link_type == 0x01 ? "ACL" : "SCO");
}
/* Send HCI_Disconnect using the stored connection handle */
int hci_send_disconnect(int dev_fd, uint16_t conn_handle, uint8_t reason)
{
/*
* HCI_Disconnect command (OGF=0x01, OCF=0x0006)
* Parameter 1: Connection_Handle (2 bytes, 12-bit value)
* Parameter 2: Reason (1 byte)
*/
struct hci_request rq;
disconnect_cp cp;
evt_disconn_complete rp;
memset(&cp, 0, sizeof(cp));
cp.handle = htobs(conn_handle); /* host to BT short (little-endian) */
cp.reason = reason; /* 0x13 = Remote User Terminated */
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LINK_CTL;
rq.ocf = OCF_DISCONNECT;
rq.cparam = &cp;
rq.clen = DISCONNECT_CP_SIZE;
rq.rparam = &rp;
rq.rlen = EVT_DISCONN_COMPLETE_SIZE;
if (hci_send_req(dev_fd, &rq, 5000) < 0) {
perror("hci_send_req (disconnect)");
return -1;
}
printf("[HCI] Disconnect sent for handle 0x%04X\n", conn_handle);
return 0;
}
int main(void)
{
int dev_id = hci_get_route(NULL);
int dev_fd = hci_open_dev(dev_id);
hci_connection_t conn;
if (dev_fd < 0) {
perror("hci_open_dev");
return 1;
}
printf("[HCI] Device fd=%d opened\n", dev_fd);
/*
* In a real program you would:
* 1. Send HCI_Create_Connection
* 2. Wait for Connection_Complete_Event in an event loop
* 3. Call on_connection_complete() to store the handle
* 4. Use conn.handle for all further operations
*/
/* Simulating a received Connection_Complete_Event buffer */
uint8_t mock_evt[] = {
0x00, /* Status = Success */
0x40, 0x00, /* Connection_Handle = 0x0040 (little-endian) */
0xFF,0xEE,0xDD,
0xCC,0xBB,0xAA, /* BD_ADDR = AA:BB:CC:DD:EE:FF (reversed) */
0x01, /* Link_Type = ACL */
0x00 /* Encryption = disabled */
};
on_connection_complete(mock_evt, &conn);
/* Now use the handle to disconnect */
hci_send_disconnect(dev_fd, conn.handle, 0x13);
hci_close_dev(dev_fd);
return 0;
}
0x0FFF when extracting the handle from a raw HCI buffer.
3.5.6 — HCI Transport Layer
The HCI interface does not dictate a single physical connection. The Bluetooth specification defines four standard transport layers so that Host and Controller can be connected in different hardware configurations.
| # | Transport Layer | Common Use Case | Type |
|---|---|---|---|
| 1 | UART (H4) | Smartphones, Tablets, SBCs (same PCB) | Most Common |
| 2 | USB | External BT USB dongles on PCs/Laptops | Common |
| 3 | Secure Digital (SD) | SD card form-factor BT modules | Rare |
| 4 | Three-Wire UART (H5) | Reliable UART with ACK/NACK (longer cables) | Specialized |
The UART Transport Layer (also called H4) is by far the most widely used. It is used when the Host CPU and the Bluetooth Controller sit on the same PCB — for example in a Raspberry Pi, an Android smartphone, or a custom embedded board.
Because the two chips are physically close to each other, the UART line is assumed to be error-free. There is no built-in retransmission mechanism — that is why a separate Three-Wire UART (H5) transport was defined for noisy or longer connections.
Hardware_Error_Event to the Host. The Host must then terminate all pending Bluetooth activity and send an HCI_Reset command to the Controller to restore communication.Every HCI packet sent over UART is prefixed with a 1-byte Packet Indicator. This is necessary because Commands, Events, and Data packets all share the same UART wire — without an indicator, the receiver would not know what type of packet is arriving.
| Packet Indicator | HCI Packet Type | Direction |
|---|---|---|
0x01 |
HCI Command Packet | Host → Controller |
0x02 |
HCI ACL Data Packet | Host ↔ Controller (bidirectional) |
0x03 |
HCI Synchronous (SCO/eSCO) Packet | Host ↔ Controller (voice data) |
0x04 |
HCI Event Packet | Controller → Host |
HCI UART TRANSPORT LAYER
═══════════════════════════════════════════════════════════
HOST CONTROLLER
┌─────────────────┐ ┌─────────────────────┐
│ │ │ │
│ │──── 0x01 ────►│ HCI Command Pkt │
│ │ │ │
│ │◄─── 0x04 ────│ HCI Event Pkt │
│ │ │ │
│ │──── 0x02 ────►│ HCI ACL Data Pkt │
│ │◄─── 0x02 ────│ HCI ACL Data Pkt │
│ │ │ │
│ │──── 0x03 ────►│ HCI Sync Data Pkt │
│ │◄─── 0x03 ────│ HCI Sync Data Pkt │
│ │ │ │
└─────────────────┘ └─────────────────────┘
│ │
TX ────┤ UART RX/TX Lines ├─────────────────┤ RX
RX ────┘ └────
In many smartphones, HCI Synchronous Data packets (SCO/eSCO) carrying voice are not sent over the UART transport at all. Instead, the Application Processor or Modem has a direct PCM / I²S interface to the Bluetooth chip.
This is more efficient for real-time audio — the voice stream bypasses the HCI UART path entirely, reducing latency and CPU overhead.
TYPICAL HCI UART CONNECTIONS IN A SMARTPHONE
═══════════════════════════════════════════════
┌───────────────────────┐ ┌────────────────────────┐
│ Application │ │ │
│ Processor │ │ Bluetooth Chip │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌──────────────────┐ │
│ │ Bluetooth Host │ │◄──UART─►│ │ HCI Transport │ │
│ │ Stack (BlueZ) │ │ │ │ (Cmds+Events+ACL)│ │
│ └─────────────────┘ │ │ └──────────────────┘ │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌──────────────────┐ │
│ │ Cellular Modem │ │◄─PCM/──►│ │ PCM / I2S │ │
│ │ (Voice codec) │ │ I2S │ │ Voice Interface │ │
│ └─────────────────┘ │ │ └──────────────────┘ │
└───────────────────────┘ └────────────────────────┘
HCI UART → Commands, Events, ACL Data
PCM/I2S → Voice (SCO/eSCO) packets directly
3.5.6.1 — Decoding HCI Packets
Understanding how to manually decode raw HCI bytes is a core skill for Bluetooth debugging. You will use this every time you run btmon, read a Wireshark capture, or parse a UART logic analyser trace.
Let’s walk through the most common exchange — the HCI_Reset command and its Command Complete Event.
┌──────────────────────────────────────────────────────────────────┐ │ BYTES ON THE UART WIRE (Host → Controller) │ │ │ │ 01 03 0C 00 │ │ ── ─────── ── │ │ │ │ └── Parameter Total Length = 0 (no params) │ │ │ └───────── Opcode = 0x0C03 │ │ │ OGF = 0x03 (HCI Control & Baseband) │ │ │ OCF = 0x0003 (Reset) │ │ └───────────────── Packet Indicator = 0x01 (HCI Command) │ └──────────────────────────────────────────────────────────────────┘ Opcode encoding: ┌──────────────────────────────────────────────────────────────────┐ │ Bits [15:10] = OGF (Opcode Group Field) = 0x03 │ │ Bits [ 9: 0] = OCF (Opcode Command Field) = 0x003 │ │ │ │ Raw value = (OGF << 10) | OCF │ │ = (0x03 << 10) | 0x003 │ │ = 0x0C03 → stored little-endian as: 03 0C │ └──────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ BYTES ON THE UART WIRE (Controller → Host) │ │ │ │ 04 0E 04 02 03 0C 00 │ │ ── ── ── ── ─────── ── │ │ │ │ │ │ │ └── Return Parameter(s) │ │ │ │ │ │ └───────── Command_Opcode = 0x0C03 │ │ │ │ │ └───────────────── Num_HCI_Command_Packets = 2 │ │ │ │ └────────────────────── Param Total Length = 4 bytes │ │ │ └─────────────────────────── Event Code = 0x0E │ │ │ (Command Complete Event) │ │ └──────────────────────────────── Packet Indicator = 0x04 │ │ (HCI Event Packet) │ └──────────────────────────────────────────────────────────────────┘
| Byte(s) | Hex Value | Field Name | Meaning |
|---|---|---|---|
| 0 | 0x01 |
Packet Indicator | This is an HCI Command packet |
| 1–2 | 0x03 0x0C |
Opcode (little-endian) | OGF=0x03, OCF=0x003 → HCI_Reset |
| 3 | 0x00 |
Parameter Total Length | No parameters follow |
| ↓ Controller Response (Event) | |||
| 0 | 0x04 |
Packet Indicator | This is an HCI Event packet |
| 1 | 0x0E |
Event Code | Command Complete Event |
| 2 | 0x04 |
Param Total Length | 4 bytes of parameters follow |
| 3 | 0x02 |
Num_HCI_Command_Packets | Host may now send 2 more commands |
| 4–5 | 0x03 0x0C |
Command_Opcode | Echoes back 0x0C03 (HCI_Reset) |
| 6 | 0x00 |
Return_Parameter | Status = 0x00 (Success) |
#include <stdio.h>
#include <stdint.h>
#include <string.h>
/* -------------------------------------------------------
* Packet Indicators (H4 UART)
* ------------------------------------------------------- */
#define HCI_COMMAND_PKT 0x01
#define HCI_ACLDATA_PKT 0x02
#define HCI_SCODATA_PKT 0x03
#define HCI_EVENT_PKT 0x04
/* Event codes */
#define EVT_CMD_COMPLETE 0x0E
#define EVT_CMD_STATUS 0x0F
/* OGF values */
#define OGF_LINK_CTL 0x01
#define OGF_HOST_CTL 0x03 /* HCI Control & Baseband */
#define OGF_INFO_PARAM 0x04
/* Opcode helper: pack OGF and OCF into 16-bit opcode */
#define HCI_OPCODE(ogf, ocf) (((ogf) << 10) | (ocf))
/* Common opcodes */
#define HCI_RESET_OPCODE HCI_OPCODE(OGF_HOST_CTL, 0x0003) /* = 0x0C03 */
#define HCI_READ_BD_ADDR HCI_OPCODE(OGF_INFO_PARAM, 0x0009) /* = 0x1009 */
/* -------------------------------------------------------
* Build an HCI_Reset command into a buffer.
* Returns number of bytes written.
* ------------------------------------------------------- */
int build_hci_reset(uint8_t *buf)
{
/*
* HCI Command packet layout:
* [0] Packet Indicator (1 byte)
* [1-2] Opcode (2 bytes, little-endian)
* [3] Parameter Length (1 byte)
* [4..N] Parameters (0 bytes for HCI_Reset)
*/
buf[0] = HCI_COMMAND_PKT;
buf[1] = HCI_RESET_OPCODE & 0xFF; /* low byte */
buf[2] = (HCI_RESET_OPCODE >> 8) & 0xFF; /* high byte */
buf[3] = 0x00; /* no params */
return 4;
}
/* -------------------------------------------------------
* Parse a raw UART byte stream and print decoded fields.
* ------------------------------------------------------- */
void decode_hci_packet(const uint8_t *buf, int len)
{
if (len < 1) {
printf("[DECODE] Empty buffer\n");
return;
}
uint8_t pkt_indicator = buf[0];
printf("[DECODE] ─────────────────────────────────────\n");
switch (pkt_indicator) {
case HCI_COMMAND_PKT: {
printf("[DECODE] Packet Type : HCI Command (0x01)\n");
if (len < 4) { printf("[DECODE] Truncated!\n"); return; }
uint16_t opcode = (uint16_t)(buf[1] | (buf[2] << 8));
uint8_t ogf = (opcode >> 10) & 0x3F;
uint16_t ocf = opcode & 0x03FF;
uint8_t plen = buf[3];
printf("[DECODE] Opcode : 0x%04X (OGF=0x%02X, OCF=0x%03X)\n",
opcode, ogf, ocf);
printf("[DECODE] Param Length : %d bytes\n", plen);
if (opcode == HCI_RESET_OPCODE)
printf("[DECODE] Command Name : HCI_Reset\n");
break;
}
case HCI_EVENT_PKT: {
printf("[DECODE] Packet Type : HCI Event (0x04)\n");
if (len < 3) { printf("[DECODE] Truncated!\n"); return; }
uint8_t event_code = buf[1];
uint8_t plen = buf[2];
printf("[DECODE] Event Code : 0x%02X\n", event_code);
printf("[DECODE] Param Length : %d bytes\n", plen);
if (event_code == EVT_CMD_COMPLETE && len >= 7) {
uint8_t num_pkts = buf[3];
uint16_t cmd_op = (uint16_t)(buf[4] | (buf[5] << 8));
uint8_t status = buf[6];
printf("[DECODE] Event Name : Command_Complete\n");
printf("[DECODE] Num_HCI_Pkts : %d (host may send %d more cmds)\n",
num_pkts, num_pkts);
printf("[DECODE] Command_Op : 0x%04X\n", cmd_op);
printf("[DECODE] Return Status : 0x%02X (%s)\n",
status, status == 0 ? "Success" : "Error");
}
break;
}
case HCI_ACLDATA_PKT: {
printf("[DECODE] Packet Type : HCI ACL Data (0x02)\n");
if (len < 5) { printf("[DECODE] Truncated!\n"); return; }
uint16_t handle_flags = (uint16_t)(buf[1] | (buf[2] << 8));
uint16_t conn_handle = handle_flags & 0x0FFF;
uint8_t pb_flag = (handle_flags >> 12) & 0x03;
uint8_t bc_flag = (handle_flags >> 14) & 0x03;
uint16_t data_len = (uint16_t)(buf[3] | (buf[4] << 8));
printf("[DECODE] Conn Handle : 0x%04X\n", conn_handle);
printf("[DECODE] PB Flag : %d (0=1st non-frag, 1=cont, 2=1st frag)\n",
pb_flag);
printf("[DECODE] BC Flag : %d (0=point-to-point)\n", bc_flag);
printf("[DECODE] Data Length : %d bytes\n", data_len);
break;
}
case HCI_SCODATA_PKT:
printf("[DECODE] Packet Type : HCI SCO/Voice Data (0x03)\n");
break;
default:
printf("[DECODE] Unknown Packet Indicator: 0x%02X\n", pkt_indicator);
break;
}
printf("[DECODE] ─────────────────────────────────────\n\n");
}
/* -------------------------------------------------------
* Main — demonstrate building and decoding HCI packets
* ------------------------------------------------------- */
int main(void)
{
uint8_t tx_buf[16];
int tx_len;
/* 1. Build HCI_Reset command */
printf("=== Building HCI_Reset Command ===\n");
tx_len = build_hci_reset(tx_buf);
printf("Raw bytes: ");
for (int i = 0; i < tx_len; i++) printf("%02X ", tx_buf[i]);
printf("\n\n");
/* 2. Decode the command bytes */
printf("=== Decoding HCI_Reset Command ===\n");
decode_hci_packet(tx_buf, tx_len);
/* 3. Simulate receiving Command_Complete_Event */
printf("=== Decoding Command_Complete_Event ===\n");
uint8_t evt_buf[] = { 0x04, 0x0E, 0x04, 0x02, 0x03, 0x0C, 0x00 };
decode_hci_packet(evt_buf, sizeof(evt_buf));
/* 4. Simulate receiving an ACL Data packet on handle 0x0040 */
printf("=== Decoding ACL Data Packet ===\n");
/*
* ACL Header:
* [0] Packet Indicator = 0x02
* [1-2] Handle + Flags = 0x40 | (PB=0x02 << 12) = 0x2040
* little-endian: 40 20
* [3-4] Data Length = 0x0005 → LE: 05 00
* [5-9] Payload = 5 bytes of L2CAP data
*/
uint8_t acl_buf[] = { 0x02, 0x40, 0x20, 0x05, 0x00,
0x01, 0x00, 0x04, 0x00, 0x00 };
decode_hci_packet(acl_buf, sizeof(acl_buf));
return 0;
}
# Compile (BlueZ development headers required for connection handle example) gcc -o hci_uart_decode hci_uart_decode.c ./hci_uart_decode # Expected output: # === Building HCI_Reset Command === # Raw bytes: 01 03 0C 00 # # === Decoding HCI_Reset Command === # [DECODE] Packet Type : HCI Command (0x01) # [DECODE] Opcode : 0x0C03 (OGF=0x03, OCF=0x003) # [DECODE] Param Length : 0 bytes # [DECODE] Command Name : HCI_Reset # # === Decoding Command_Complete_Event === # [DECODE] Packet Type : HCI Event (0x04) # [DECODE] Event Code : 0x0E # [DECODE] Param Length : 4 bytes # [DECODE] Event Name : Command_Complete # [DECODE] Num_HCI_Pkts : 2 # [DECODE] Command_Op : 0x0C03 # [DECODE] Return Status : 0x00 (Success) # # For the connection handle example (requires BlueZ headers): gcc -o hci_conn hci_connection_handle.c -lbluetooth
Quick Reference Summary
HCI CONCEPTS — QUICK REFERENCE ════════════════════════════════════════════════════════════════ Connection Handle ───────────────── • Assigned by Controller in Connection_Complete_Event • 12-bit value (bits 11:0 of a 16-bit field) • Used for: ACL send, Disconnect, RSSI query, Encryption setup • Mask to extract: handle = raw_value & 0x0FFF HCI Transport Layers ───────────────────── • UART (H4) — Same PCB, no error correction, most common • USB — External dongles • SD — SD card modules • UART (H5) — Three-wire UART with reliability (longer links) UART Packet Indicators ─────────────────────── • 0x01 → HCI Command (Host → Controller) • 0x02 → HCI ACL Data (bidirectional) • 0x03 → HCI SCO/eSCO Data (voice, bidirectional) • 0x04 → HCI Event (Controller → Host) HCI Command Opcode Structure ───────────────────────────── • 16-bit value, little-endian on wire • Bits [15:10] = OGF (Opcode Group Field) • Bits [ 9: 0] = OCF (Opcode Command Field) • HCI_OPCODE(ogf, ocf) = (ogf << 10) | ocf Common Opcodes ────────────── • 0x0C03 HCI_Reset • 0x0406 HCI_Disconnect • 0x0409 HCI_Read_RSSI • 0x1009 HCI_Read_BD_ADDR • 0x2006 LE_Set_Advertising_Parameters • 0x200B LE_Set_Scan_Parameters
Continue Learning Bluetooth
Explore the complete HCI series, BLE protocol stack, and BlueZ internals at EmbeddedPathashala — free for all embedded developers. Hope you have learnt about HCI Connection Handle.
