5 Link Types
LC, ACL-C/U, SCO-S, eSCO-S
BR + EDR
Packet formats
BlueZ Snippets
Real code examples
3 Scenarios
File · Voice · Music
What This Article Covers
In the previous article we covered Physical Channels, Physical Links, and the three Logical Transports — ACL, SCO, and eSCO. Now we go one layer higher and look at Logical Links, which are the actual named channels that carry specific traffic types like LMP control signals or L2CAP user data.
After that, we open a real Bluetooth packet and see what is inside — what the access code does, what every header field means, and how BR (Basic Rate) and EDR (Enhanced Data Rate) packets differ in structure. Finally, we look at the different packet types that exist and what each one is used for. Code snippets from BlueZ are included wherever they make the concept clearer.
Keywords covered in this article
📋 Table of Contents
- 1. Logical Links — The Named Channels on Top of Transports
- 2. The Five Logical Link Types
- 3. Real-World Scenarios: File, Voice, and Music
- 4. BlueZ — Opening an ACL Socket
- 5. Inside a Bluetooth Packet — BR and EDR Format
- 6. Access Code — The Packet’s Front Door
- 7. Packet Header Fields — One by One
- 8. Payload — Where the Real Data Lives
- 9. How EDR Packets Differ from BR
- 10. Packet Types — The TYPE Field
- 11. Reading a Packet Type Name (3DH5, 2EV3)
- 12. Link Control Packet Types in Detail
- 13. Summary and Key Takeaways
In the previous article we saw that a Logical Transport (ACL, SCO, eSCO) is a pipe between a Master and a Slave. A Logical Link is a named sub-channel that rides inside that pipe and is dedicated to carrying a specific kind of traffic.
The simplest way to understand this distinction is with an analogy. Imagine an ACL transport as a highway. On that highway you have different lanes — one lane carries trucks (control messages between link managers), another carries cars (user data from your app). Each lane is a Logical Link. They all share the same physical highway, but they carry different types of cargo and have different priority rules.
LOGICAL LINKS ON TOP OF LOGICAL TRANSPORTS
============================================================
ACL Logical Transport
┌─────────────────────────────────────┐
│ ┌─────────┐ ┌─────────────────┐ │
│ │ ACL-C │ │ ACL-U │ │
│ │ Control │ │ User Data │ │
│ │ (LMP) │ │ (L2CAP layer) │ │
│ └─────────┘ └─────────────────┘ │
└─────────────────────────────────────┘
(LC link rides inside packet headers, not payload)
SCO Logical Transport
┌─────────────────────────────────────┐
│ ┌─────────────────────────────┐ │
│ │ SCO-S │ │
│ │ Synchronous Voice Stream │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
eSCO Logical Transport
┌─────────────────────────────────────┐
│ ┌─────────────────────────────┐ │
│ │ eSCO-S │ │
│ │ Enhanced Sync Voice/WBS │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
============================================================
| Link Type | Full Name | What it Carries | Where it Lives |
|---|---|---|---|
| LC | Link Control | ARQ, flow control, payload type info — very low-level bookkeeping | Inside the packet header of every packet |
| ACL-C | ACL Control | LMP (Link Manager Protocol) messages between the Master and Slave link managers | Packet payload, on ACL transport |
| ACL-U | ACL User | L2CAP data — this is where all your application data flows (A2DP, HFP, FTP, etc.) | Packet payload, on ACL transport |
| SCO-S | SCO Synchronous | Synchronous voice stream (narrow-band, 64 kbps) | SCO logical transport |
| eSCO-S | eSCO Synchronous | Higher-quality voice, Wide Band Speech | eSCO logical transport |
LC Link — the Invisible Link
The LC link is special because it does not get a slot in the packet payload. Instead, it is embedded directly in the packet header of every single Bluetooth packet that is transmitted. Fields like the acknowledgement bit (ARQN), the flow control bit (FLOW), and the sequence number (SEQN) are all LC link fields. This makes the LC link an ever-present background channel — you cannot send any Bluetooth packet without also sending LC information.
ACL-C vs ACL-U — Priority Matters
Both ACL-C and ACL-U share the same ACL logical transport, but they are not treated equally. ACL-C has higher priority than ACL-U. This makes sense — you never want your application’s file download to block a critical LMP control message that manages the connection itself.
Let’s walk through three everyday Bluetooth use cases and see exactly which links are involved in each one.
When you transfer a file between your phone and a laptop over Bluetooth, the whole transfer happens over a single ACL link. Specifically, the file data flows on the ACL-U logical link (carried by L2CAP), while the FTP profile commands (open file, read chunk, close) also travel on ACL but via higher protocol layers.
[Phone] ──────── ACL Link ──────────► [Laptop]
│
ACL-U
(L2CAP data)
│
FTP profile on top
File chunks transferred
in bursts, retransmitted
if lost
When a call arrives and the user accepts it on their Bluetooth headset, two links are active at the same time on the same physical connection:
- ACL link — used first to set up the connection and to carry HFP (Hands-Free Profile) commands. Things like “call accepted”, “volume changed”, “call ended” all travel here.
- SCO or eSCO link — created once the user is ready to talk. The actual voice audio flows here. SCO for narrow-band (older headsets), eSCO for wideband/HD voice (modern ones).
[Phone] ──── Physical Link ────► [Headset]
│ │
[ACL] [SCO / eSCO]
│ │
HFP commands Voice audio
(call accept, (64 kbps or
volume, mute) WBS 16kHz)
Music streaming might seem like it should use SCO or eSCO since it’s audio — but it doesn’t. Here’s why: SCO/eSCO were designed for voice sampled at 8–16 kHz. Music is sampled at up to 48 kHz, which needs far more bandwidth than SCO can provide.
So music travels over the ACL link. But raw music at 48 kHz would be too large even for ACL, so the phone first compresses the audio using the SBC codec (Sub-Band Codec — the default codec specified by the Bluetooth A2DP profile). The headset decompresses and plays it back.
[Phone] [Stereo Headset]
│ │
│ MP3 / AAC → SBC encode │
│ │
│ ────── ACL-U link (L2CAP + A2DP) ────►│
│ │
│ SBC compressed stream │
│ │
│ SBC decode → speaker
Sampling rates:
────────────────────────────────────────────────────────────
Voice (SCO) : 8,000 Hz → only 64 kbps, fine for calls
WBS (eSCO) : 16,000 Hz → richer but still voice-only
Music (ACL) : up to 48,000 Hz → SBC codec needed
============================================================
In BlueZ (the Linux Bluetooth stack), when user-space code opens an L2CAP socket to a remote device, it is actually using the ACL-U logical link underneath. The kernel sets up the ACL transport automatically. Here is what that looks like at the application level:
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/l2cap.h> int main(void) { /* AF_BLUETOOTH + BTPROTO_L2CAP creates a socket that rides on the ACL-U logical link. Every L2CAP PSM connection you open to a remote device goes through the ACL transport. */ int sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sock < 0) { perror("socket"); return 1; } struct sockaddr_l2 addr = {0}; addr.l2_family = AF_BLUETOOTH; addr.l2_psm = htobs(0x1001); /* PSM for your custom service */ /* Remote device address — replace with your target */ str2ba("AA:BB:CC:DD:EE:FF", &addr.l2_bdaddr); /* connect() triggers an ACL connection at the HCI level. BlueZ's kernel module (net/bluetooth/l2cap_core.c) will: 1. Check if an ACL link to this BD_ADDR already exists. 2. If not, issue HCI_Create_Connection to the controller. 3. Once ACL is up, open the L2CAP channel on ACL-U. */ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("connect"); return 1; } printf("ACL-U link established via L2CAP socket\n"); /* From here: send() / recv() carry your user data on ACL-U */ return 0; }
If you want to watch the actual ACL link come up at the HCI level, you can use btmon while running the above code:
For SCO (voice) connections, BlueZ uses BTPROTO_SCO instead. The kernel internally issues HCI_Add_SCO_Connection or HCI_Setup_Synchronous_Connection to the controller, which creates the SCO or eSCO logical transport and the SCO-S / eSCO-S link on top.
Every packet sent over a Bluetooth physical channel has the same three-part structure at the top level: an Access Code, a Header, and a Payload. Only the Access Code is mandatory. The Header and Payload are optional depending on the packet type.
STANDARD BR (BASIC RATE) PACKET FORMAT
============================================================
┌──────────────┬──────────────┬──────────────────────┐
│ ACCESS CODE │ HEADER │ PAYLOAD │
│ (72 bits) │ (54 bits) │ (0 – 2745 bits) │
└──────────────┴──────────────┴──────────────────────┘
^ Mandatory ^ Optional ^ Optional
STANDARD EDR (ENHANCED DATA RATE) PACKET FORMAT
============================================================
┌──────────────┬──────────────┬───────┬──────┬──────────────┬─────────┐
│ ACCESS CODE │ HEADER │ GUARD │ SYNC │ PAYLOAD │ TRAILER │
│ (72 bits) │ (54 bits) │ │ │ │ │
└──────────────┴──────────────┴───────┴──────┴──────────────┴─────────┘
^ Extra fields needed because EDR switches modulation for payload
============================================================
The modulation scheme changes between the header and payload in an EDR packet. The GUARD and SYNC fields signal this transition to the radio hardware so it can switch cleanly. The TRAILER marks the end of the high-speed payload section. We will cover this more in the EDR section below.
The Access Code is the very first thing in every Bluetooth packet and it is always present. Before the receiver even looks at the header or payload, it checks the Access Code. If the code does not match what the receiver expects, the entire packet is silently discarded.
It serves three purposes at once: synchronisation (helps the receiver lock its clock to the incoming signal), DC offset compensation (corrects for any DC bias in the radio hardware), and identification (tells the receiver whether this packet belongs to its piconet or not).
WHICH ACCESS CODE DOES EACH PHASE USE?
============================================================
Phase Access Code Used Purpose
───────────── ─────────────────── ──────────────────────
Discovery IAC "Is anyone out there?"
Paging DAC (target BD_ADDR) "Calling device XYZ..."
Connected CAC (piconet key) "This belongs to our net"
Receiver logic on every packet:
┌──────────────────────────────────────────────┐
│ Read Access Code │
│ ↓ │
│ Does it match expected code for my mode? │
│ │
│ YES → process header + payload │
│ NO → silently discard, look for next │
└──────────────────────────────────────────────┘
============================================================
The 54-bit packet header carries six fields. Together they tell the receiver who should process this packet, what type it is, whether to pause or resume sending, whether the previous packet was received correctly, and whether this header itself is intact.
BLUETOOTH PACKET HEADER (54 bits total, includes 1/3 rate FEC)
============================================================
┌──────────┬──────┬──────┬──────┬──────┬─────┐
│ LT_ADDR │ TYPE │ FLOW │ ARQN │ SEQN │ HEC │
│ 3 bits │4 bits│1 bit │1 bit │1 bit │8bits│
└──────────┴──────┴──────┴──────┴──────┴─────┘
The full 54 bits = 18 raw bits encoded with 1/3 rate FEC
(every bit sent 3 times for robust header delivery)
============================================================
This identifies which Slave in the piconet this packet is addressed to (when sent by the Master) or which Slave is sending (when the Slave responds). With 3 bits you can address 0–7. Address 0 is reserved for broadcasts, leaving addresses 1–7 for up to seven Slaves — which is exactly the piconet limit.
Specifies the packet type (DM1, DH1, DH3, HV1, EV3, etc.). The receiver reads this to know how many slots the packet occupies, how to decode the payload, and whether to expect CRC or FEC. We’ll cover all packet types in the next sections.
Flow control. When the receiver’s buffer is full and it cannot accept more data, it sets FLOW = 0 in its next outgoing packet. The sender sees this and pauses. Once the receiver drains its buffer, it sets FLOW = 1 again and the sender resumes. This prevents buffer overflows at the application level.
After receiving a packet and checking its CRC, the receiver sends back ARQN = 1 (ACK, all good) or ARQN = 0 (NAK, please resend). The sender uses this to decide whether to move to the next packet or retransmit the current one. This is the heart of the ARQ (Automatic Repeat reQuest) mechanism in Bluetooth.
A single alternating bit (0 → 1 → 0 → 1…) that helps the receiver detect duplicate packets. If the sender retransmits a packet (because it did not get an ACK), the sequence number stays the same. When the receiver sees the same SEQN twice in a row, it knows it’s a duplicate and discards the second copy.
An 8-bit checksum covering the header fields. If the receiver calculates a different HEC than the one in the packet, the entire packet is thrown away immediately — the payload is not even looked at. This protects against corrupted headers causing misinterpretation of the packet type or address.
FEC means Forward Error Correction — every bit in the header is sent three times so the receiver can recover the original bit even if one copy is corrupted. The header is small (18 bits raw) so the cost is low. This extra protection is worth it because a misread TYPE or LT_ADDR would cause the entire packet to be misinterpreted.
The payload section holds the actual data being transferred, followed by a CRC for integrity checking. The payload’s internal structure depends on the logical transport it belongs to:
PAYLOAD STRUCTURE BY PACKET TYPE
============================================================
ACL Packet Payload:
┌──────────────────────────────────┬───────┐
│ Asynchronous Data Field │ CRC │
│ (L2CAP frames, LMP messages) │ │
└──────────────────────────────────┴───────┘
SCO/eSCO Packet Payload:
┌──────────────────────────────────┐
│ Synchronous Data Field │
│ (PCM voice samples — no CRC) │
└──────────────────────────────────┘
Note: SCO payloads have no CRC because dropped voice
bytes are not retransmitted.
DV Packet Payload (special — carries both):
┌────────────────┬──────────────────────┬───────┐
│ Sync Data │ Async Data │ CRC │
│ (Voice) │ (ACL-C LMP data) │ │
└────────────────┴──────────────────────┴───────┘
DV = Data + Voice in a single packet
============================================================
The DV (Data-Voice) packet is an interesting special case. It carries voice in the synchronous field and control data (LMP) in the asynchronous field — both inside a single packet. Only the async part has a CRC because voice data doesn’t need error correction.
BR (Basic Rate) packets use GFSK (Gaussian Frequency Shift Keying) modulation throughout — for both the header and payload. At 1 Mbps it gives decent throughput.
EDR (Enhanced Data Rate) keeps GFSK for the access code and header (for backward compatibility with every Bluetooth receiver), but then switches to a more efficient modulation for the payload — either π/4-DQPSK (2 Mbps) or 8DPSK (3 Mbps). This is why EDR packets need three extra fields that BR packets don’t have:
EDR MODULATION SWITCH
============================================================
[ACCESS CODE] [HEADER] [GUARD][SYNC][PAYLOAD][TRAILER]
↑ ↑ ↑ ↑ ↑ ↑
GFSK GFSK Transition EDR mod Return to
(1 Mbps) (1 Mbps) ramp-down π/4-DQPSK GFSK / end
or 8DPSK
GUARD : ramp-down period, radio transitions out of GFSK
SYNC : synchronisation sequence, receiver locks to EDR
TRAILER: marks end of EDR payload, radio transitions back
Result: header stays compatible with all BT receivers.
Only EDR-capable receivers decode the payload.
============================================================
Backward compatibility. Any Bluetooth device (even pre-EDR) can read the header and understand the packet type and addressing. If the device does not support EDR, it simply ignores the payload. This is a deliberate design decision so EDR-capable devices can coexist with older ones.
The 4-bit TYPE field in the header can represent 16 values (0–15). Different sets of values are interpreted differently depending on the logical transport. Broadly, packet types fall into three categories:
Bluetooth packet names look cryptic at first but each character has a specific meaning. Let’s decode two examples:
READING ACL PACKET NAME — 3DH5
============================================================
3 D H 5
│ │ │ │
│ │ │ └── Occupies 5 time slots
│ │ │
│ │ └────── High Rate packet (H = High Rate)
│ │
│ └────────── D = ACL Data Packet
│
└────────────── EDR prefix: 3 = 8DPSK (3 Mbps)
(No prefix = Basic Rate 1 Mbps)
So "3DH5" = EDR, ACL data, high-rate, 5-slot packet
─────────────────────────────────────────────────
READING SYNC PACKET NAME — 2EV3
============================================================
2 E V 3
│ │ │ │
│ │ │ └── Occupies 3 time slots
│ │ │
│ │ └────── V = Voice (synchronous)
│ │
│ └────────── EV = Enhanced Voice → eSCO packet
│
└────────────── EDR prefix: 2 = π/4-DQPSK (2 Mbps)
(HV or DV prefix = SCO packet)
So "2EV3" = EDR, eSCO, 3-slot voice packet
============================================================
For BR packets there is no numeric prefix — so DH3 is a 1 Mbps ACL high-rate 3-slot packet, and HV1 is a 1 Mbps SCO voice 1-slot packet.
| Name | Transport | Rate | Slots | Type |
|---|---|---|---|---|
| DM1 | ACL | BR (1 Mbps) | 1 | Medium-rate data |
| DH5 | ACL | BR (1 Mbps) | 5 | High-rate data |
| 3DH5 | ACL | EDR (3 Mbps) | 5 | High-rate data |
| HV1 | SCO | BR (1 Mbps) | 1 | Voice (no FEC) |
| EV3 | eSCO | BR (1 Mbps) | 3 | Enhanced voice |
| 2EV3 | eSCO | EDR (2 Mbps) | 3 | Enhanced voice |
These five packet types are used to manage the piconet itself — before, during, and after connection setup. None of them carry user data (except DM1 which can also carry control data).
Used before any connection exists. It contains only the Device Access Code (DAC) or Inquiry Access Code (IAC). There is no header and no payload — it is literally just the access code by itself. This makes it the most lightweight packet in the entire Bluetooth spec. A paging device broadcasts ID packets repeatedly until the target responds.
ID Packet:
┌──────────────┐
│ ACCESS CODE │ <-- No Header, No Payload
│ (DAC / IAC) │ Most robust, minimal size
└──────────────┘
Once connected, a Slave must respond to every Master transmission in its allotted slot — even if it has nothing to send. The NULL packet is the Slave’s way of saying “I received your packet, I have nothing to send back.” It carries no payload, but the ARQN field in the header is set to 1 (positive ACK) or 0 (negative ACK / please resend).
NULL is also used by the Slave to tell the Master that its receive buffer is full (FLOW = 0). The Master will then pause transmission until the Slave clears its buffer and sends FLOW = 1 in a subsequent NULL.
NULL Packet use-cases:
┌──────────────────────────────────────────────────┐
│ Master sends data → Slave has nothing to send │
│ Slave replies with NULL (ARQN=1 = got it fine) │
│ │
│ Slave buffer fills up → FLOW=0 in NULL │
│ Master pauses → Slave clears buffer │
│ Slave sends FLOW=1 → Master resumes │
└──────────────────────────────────────────────────┘
The POLL packet is the Master’s equivalent of NULL, but with one key difference: a Slave receiving a POLL must reply. It cannot ignore it. The Master uses POLL to check two things: is the Slave still there (alive check), and does the Slave have any data to send?
If the Slave has nothing to send, it responds with a NULL packet. If it has data, it responds with a data packet. Unlike NULL, the Master does not need to acknowledge the POLL — it simply sends it and waits for the reply.
POLL / NULL interaction:
Master Slave
│ │
│──── POLL ──────────────────► │ "Are you there? Any data?"
│ │
│◄─── NULL ─────────────────── │ "Yes I'm here, nothing to send"
│ │
│ OR │
│ │
│◄─── [Data Packet] ────────── │ "Yes I'm here, here's my data"
│ │
Note: NULL does not require an ACK from Master.
POLL always requires a response from Slave.
The FHS (Frequency Hop Synchronization) packet carries the Master’s real-time clock value and BD_ADDR. This information is what allows a Slave to derive the Master’s frequency-hopping sequence and synchronise to it. An FHS packet is sent in two situations:
- During the initial connection setup — the Master sends its FHS so the Slave can join the piconet and sync clocks.
- During a Role Switch — when the two devices swap Master/Slave roles, the new Master sends an FHS so the former Master (now Slave) can resync to the new piconet parameters.
DM1 is the odd one out in this group because it can carry actual data in its payload. It occupies a single slot and uses medium data rate. It is classified as both a Link Control packet type (used for LMP messages on ACL-C) and an ACL data packet type (used for small L2CAP payloads on ACL-U).
In practice, DM1 is heavily used for LMP control messages — things like encryption requests, channel map updates, and feature exchange messages — because these are small and need to be sent reliably.
You can watch NULL, POLL and FHS events at the HCI level using
sudo btmon while connecting devices. The HCI event stream will show connection request, connection complete (FHS exchanged), and then data events once ACL is up.In the next article we go up the stack into L2CAP — how it multiplexes multiple protocols over the single ACL-U link, what channels and PSMs are, and how flow control and segmentation work. We will also look at how BlueZ exposes L2CAP to user-space applications.
Keep Climbing the Bluetooth Stack 🚀
This is part of the EmbeddedPathashala Bluetooth Lower Layers series. Each article builds directly on the previous one — no gaps, no hand-waving.
