ATT Practical Examples & Chapter 12 Summary
Chapter 12 Part 4 of 4 · Exchange MTU Capture · Service Discovery Walk-Through · Three-Pass Iteration · Error Response · Summary
12.5.1 — Exchange MTU Practical Capture
Figure 12.19 — What a Real MTU Negotiation Looks Like
The sniffer capture shows two consecutive frames — Frame #815 (client sends the request) and Frame #817 (server responds). The outcome of this exchange sets the ATT payload size for every subsequent PDU in this connection.
| Frame | Role | PDU | Key Value |
|---|---|---|---|
| 815 | Master (Client) | Exchange MTU Request | Client Rx MTU = 525 octets |
| 817 | Slave (Server) | Exchange MTU Response | Server Rx MTU = 23 octets |
Both sides will use exactly 23 octets for every ATT PDU for the rest of this connection.
Neither side can negotiate a larger MTU later — the exchange happens only once per connection.
The client (a phone or PC) supports a much larger MTU — it offered 525 bytes. The server (a simple sensor) only supports 23 bytes. Because the minimum of the two values wins, the session runs at 23 bytes. The client’s larger buffer capability goes unused, but the protocol remains correct.
This is a common scenario in BLE IoT deployments. Constrained sensors often cannot allocate large receive buffers, so they advertise the minimum supported MTU in their response.
12.5.2 — Reading Primary Services — Three-Pass Example
The Iterative Service Discovery Pattern
A client cannot fetch all services in one request if there are many of them — the response is limited to ATT_MTU size. Instead, the client uses an iterative pattern: request a range, note the last handle returned, then send a new request starting just above that handle. This repeats until the server responds with an Error Response meaning there are no more services in the requested range.
The complete service map discovered:
| # | Service | UUID | Start Handle | End Handle |
|---|---|---|---|---|
| 1 | Generic Access Profile | 0x1800 | 0x0001 | 0x0007 |
| 2 | Generic Attribute Profile | 0x1801 | 0x0016 | 0x0019 |
| 3 | Link Loss Alert | 0x1803 | 0x0080 | 0x0082 |
| 4 | Immediate Alert | 0x1802 | 0x0083 | 0x0085 |
| 5 | Tx Power | 0x1804 | 0x0086 | 0x0088 |
/* gatttool -- primary does this exact iteration automatically */
gatttool -b AA:BB:CC:DD:EE:FF --primary
/* To see the raw ATT frames: run btmon in parallel */
sudo btmon &
gatttool -b AA:BB:CC:DD:EE:FF --primary
/* btmon output shows Read By Group Type Requests: */
/* ATT: Read By Group Type Request (0x10) len 6 */
/* Handle range: 0x0001-0xffff */
/* Attribute group type: Primary Service (0x2800) */
/* ATT: Read By Group Type Response (0x11) len 20 */
/* Attribute data length: 6 */
/* Attribute group list: 3 entries */
/* handle: 0x0001, end: 0x0007, uuid: 0x1800 */
/* handle: 0x0016, end: 0x0019, uuid: 0x1801 */
/* handle: 0x0080, end: 0x0082, uuid: 0x1803 */
/* (continues with next request at 0x0083...) */
12.6 — Chapter 12 Summary
ATT — A Simple, Focused Data Exchange Protocol
ATT’s design philosophy is minimalism. It provides exactly what is needed to expose, discover, and transfer data between BLE devices — and nothing more. The complexity of organising that data is left to GATT in the next layer up.
- Client-server model — clear separation
- Sequential transactions — simple flow control
- Fixed CID 0x0004 — no setup needed
- Default 23-byte MTU, negotiable upward
- 30-second transaction timeout
- Read (single, blob, multiple, by type, by group)
- Write (acknowledged request, fire-and-forget command)
- Long writes (prepare + execute two-phase)
- Server push (notification, indication)
- Data signing (signed write command)
- 10–20 bytes typical per LE transaction
- Fits in one LE ACL data packet
- Long values: use Read Blob or Prepare Write
- Service discovery: iterative Group Type reads
- Completion always signalled by Error Response
Next — Chapter 13: Generic Attribute Profile (GATT)
GATT builds the Profile → Service → Characteristic hierarchy on top of ATT. Mandatory for all BLE devices.
