0x0004
0x0005
0x0006
Zero needed
Continuing Section 10.4 — MTU Comparison
The previous file introduced the MTU concept and the 23-octet minimum for LE. This section completes the MTU comparison with BR/EDR and explains the practical benefits of keeping the LE MTU small. It then moves into Section 10.5 — the three key features L2CAP provides to higher layers.
10.4 — MTU (Completed)
The minimum MTU for LE (23 octets) is much smaller than BR/EDR’s minimum (48 octets without extended flow spec, 672 with). This was not an oversight — it was a deliberate design decision with two direct benefits:
The maximum SDU size directly determines how large the receive and transmit buffers need to be in the controller and host. A 23-byte maximum requires far less RAM than a 672-byte maximum. For small BLE sensors running on tiny microcontrollers with 4–8 KB of total RAM, this difference is critical.
Smaller packets mean shorter radio-on time per packet. When a heart rate monitor only needs to send 4 bytes of data, packing it into a 23-byte PDU and transmitting that costs far less power than the minimum 48-byte BR/EDR packet would. Every microsecond the radio is on costs battery.
What happens at the L2CAP level if the sender tries to exceed the receiver’s MTU? The receiver returns a Command_Reject PDU. The Command_Reject carries a reason code — in this case “Signaling MTU exceeded.” The sender must reduce its packet size or close the channel.
/* Checking MTU on a BLE connection with BlueZ */
/* The effective L2CAP MTU for ATT is negotiated via ATT */
/* Exchange MTU Request (opcode 0x02) / Response (0x03) */
/* Using gatttool to negotiate a larger MTU: */
/* gatttool --device=AA:BB:CC:DD:EE:FF --mtu=247 -I */
/* Reading current ATT MTU from BlueZ kernel trace: */
/* sudo cat /sys/kernel/debug/bluetooth/hci0/l2cap */
/* or via btmon: shows ATT MTU exchange in connection setup */
/* Default after BLE connection established: 23 bytes */
/* After Exchange MTU: up to min(client_mtu, server_mtu) */
10.5 — L2CAP Features
Like BR/EDR, LE L2CAP provides the abstraction of channels to the layers above it. To ATT and the Security Manager, L2CAP looks like a simple delivery service with named channels. The three features that make this work are:
10.5.1 — Fixed Channel Identifiers
A Channel Identifier (CID) is a number that tells L2CAP which higher-layer entity a packet belongs to. It is the “address” at the L2CAP layer — like a port number in TCP/IP. All data for ATT is labelled with CID 0x0004; all data for the Security Manager is labelled with CID 0x0006. The receiving L2CAP reads the CID from the packet header and knows exactly which protocol to deliver it to.
BR/EDR vs LE — the big difference: In BR/EDR, most CIDs are dynamically allocated. If SDP wants to create a connection, L2CAP goes through a CONNECT → CONFIGURE handshake to establish the channel and get a CID assigned. This takes several PDU round trips before any data can flow.
In LE, all channels use fixed CIDs. The CIDs for ATT and SMP are defined in the specification and never change. No negotiation, no handshake, no CONNECT or CONFIGURE PDUs needed. As soon as the Link Layer connection is established, ATT can immediately send data on CID 0x0004 and SMP can immediately send data on CID 0x0006.
| CID | Description |
|---|---|
| 0x0004 | Attribute Protocol (ATT)
All GATT reads, writes, notifications, and indications travel over this channel. Available immediately after connection establishment — no setup needed. |
| 0x0005 | LE L2CAP Signaling Channel
L2CAP control messages — connection parameter update requests, credit-based connection requests (4.1), and command reject PDUs. One command per PDU (simpler than BR/EDR). |
| 0x0006 | Security Manager Protocol (SMP)
Pairing, key exchange, and authentication messages. Also available immediately after connection with no prior setup. |
In BR/EDR, if an application needs to use SDP (Service Discovery), L2CAP must first exchange CONNECT and CONFIGURE signals to establish the channel. These are multiple round trips over the air before any real data can flow. In BLE, the moment the Link Layer reports a connection established, ATT and SMP can start sending immediately. This is one of the reasons BLE feels so much faster to connect than classic Bluetooth.
| CID Range | Description |
|---|---|
| 0x0020 – 0x003E | As per SIG assigned numbers page — fixed assignments for specific protocols |
| 0x0040 – 0x007F | Dynamically allocated during credit-based connection mechanisms (the LE credit-based flow control introduced in 4.1) |
BLE 4.1 added these additional CID ranges to support connection-oriented channels — channels that are dynamically created for specific use cases (like the Internet Protocol Support Profile, IPSP). These use the credit-based flow control mode introduced in 4.1. The dynamically allocated range 0x0040–0x007F is assigned when the channel is created and released when it is closed.
/* L2CAP channel CIDs in BlueZ kernel source */
/* From include/net/bluetooth/l2cap.h */
#define L2CAP_CID_ATT 0x0004 /* Attribute Protocol */
#define L2CAP_CID_LE_SIGNALING 0x0005 /* LE Signaling */
#define L2CAP_CID_SMP 0x0006 /* Security Manager */
/* These are available as soon as LE connection is created */
/* No LE_Connect or LE_Config PDUs needed — just send data */
/* Checking L2CAP channels on an active connection: */
/* sudo cat /sys/kernel/debug/bluetooth/hci0/l2cap */
/* or: */
/* sudo bt-monitor | grep L2CAP */
10.5.2 — Fragmentation and Defragmentation
Higher layer protocols like ATT may need to send packets larger than what the link layer can carry in a single PDU. The link layer has a maximum payload size determined by the controller’s ACL buffer size (27 bytes in BLE 4.0, up to 251 bytes in BLE 4.2). L2CAP handles the mismatch transparently.
When ATT hands L2CAP an SDU that is larger than the controller’s ACL buffer size, L2CAP splits it into multiple PDU fragments. Each fragment fits in one HCI ACL data packet. The first fragment’s L2CAP header contains the full SDU length so the receiver knows how much to expect.
As fragments arrive, L2CAP collects them using the Length field in the first PDU to know how many bytes to expect. When all fragments have arrived, L2CAP reassembles them into the original SDU and delivers it to the upper layer (ATT or SMP) as one complete packet.
The PB (Packet Boundary) bits in the HCI ACL packet header tell the controller and the L2CAP layer whether a fragment is the start of a new SDU (PB=10) or a continuation of the previous one (PB=01). L2CAP uses the Length field in the first fragment to know when it has received all pieces.
10.5.3 — Channel Multiplexing and Demultiplexing
There is only one LE-U logical link between two connected BLE devices. But there are three L2CAP channels that all need to share it: ATT (CID 0x0004), SMP (CID 0x0006), and L2CAP’s own signaling channel (CID 0x0005). L2CAP handles this by labelling every packet with its CID and sorting incoming packets by CID.
Reads CID on RX → delivers to right protocol
How multiplexing works: When ATT sends data to L2CAP, L2CAP adds the L2CAP header (with CID=0x0004) and puts the packet into the LE-U link’s transmission queue. When SMP sends something, L2CAP adds CID=0x0006. When its own signaling needs to be sent, it uses CID=0x0005. All these packets flow through the same physical data channel using the connection’s frequency hopping pattern.
How demultiplexing works: When a packet arrives, L2CAP reads the CID from the packet header. If CID=0x0004 it passes the payload to ATT. If CID=0x0006 it passes it to SMP. If CID=0x0005 it processes it internally as a signaling command. The upper layers never see raw PDUs — they only receive the extracted payload (the SDU).
/* L2CAP multiplexing is transparent — upper layers just */
/* use sockets with a specific CID/protocol type */
/* In BlueZ, connecting to the ATT channel (CID 0x0004): */
struct sockaddr_l2 addr = {
.l2_family = AF_BLUETOOTH,
.l2_psm = 0, /* not used for fixed CIDs */
.l2_cid = htobs(L2CAP_CID_ATT), /* 0x0004 */
.l2_bdaddr_type = BDADDR_LE_PUBLIC,
};
/* Alternatively: BlueZ bt_io library handles this */
/* automatically when you open a GATT connection */
/* The L2CAP layer is completely transparent to GATT code */
Next — Data Packets, B-Frame & L2CAP Signaling
You now understand all three L2CAP features. PDF 2123 covers the actual data packet format (B-Frame), the five modes of operation, L2CAP parameters, and the signaling channel with its commands.
Next: Data Packets, Signaling & Commands → ← PDF 1618: L2CAP Intro
