ATT Read Methods — All Request/Response Operations
Chapter 12 Part 2 of 4 · Exchange MTU · Find Info · Find By Type · Read By Type · Read · Read Blob · Read Multiple · Read By Group Type
12.4.1.1 — Exchange MTU Request & Response
Negotiating a Bigger Packet Size
The default ATT_MTU for BLE is 23 octets. This means by default, the largest ATT payload in a single PDU is 23 bytes. For many sensors (temperature, heart rate) this is plenty. But for applications that transfer firmware updates, audio data, or larger records, 23 bytes per round-trip is very slow.
The Exchange MTU procedure lets the client tell the server what receive size it supports, and learn what size the server supports. Both sides then use the smaller of the two values for the rest of the connection.
The Exchange MTU Request is sent only once per connection and only by the client. The server cannot initiate this exchange. If the server’s MTU is larger than the default, it waits for the client to ask.
/* Set larger MTU with BlueZ gatttool */
gatttool -b AA:BB:CC:DD:EE:FF --mtu=512 -I
/* Or with bluetoothctl (MTU negotiated automatically) */
/* BlueZ will use the maximum supported by the adapter */
/* Check negotiated MTU in kernel log: */
/* dmesg | grep -i "mtu" */
/* [ 123.456] Bluetooth: hci0: ATT MTU negotiated: 247 */
/* Programmatic MTU request with BlueZ D-Bus: */
/* org.bluez.GattCharacteristic1 uses the negotiated MTU */
/* automatically — no manual ATT MTU request needed */
12.4.1.3 — Find Information Request & Response
Getting Handle-Type Pairs for a Range of Attributes
This is how a client learns the type (UUID) of every attribute in a handle range without reading their values. It is most useful during service discovery when a client needs to identify what kind of attribute lives at each handle before deciding whether to read it.
Response details: The Format field tells the client whether the UUIDs in the response are 16-bit or 128-bit (both cannot be mixed in one response). The Attribute Data List contains handle-UUID pairs in ascending handle order. If the range is long, the response may span multiple PDUs — but individual pairs are never split across PDUs.
12.4.1.5 — Find By Type Value Request & Response
Locating Attributes Matching Both Type AND Value
More selective than Find Information. The client specifies a 16-bit UUID for the type and a specific value to match. The server returns only the handles of attributes that match both. This is the operation GATT uses internally to discover services by UUID — for example, find all attributes where Type = Primary Service AND Value = Heart Rate Service UUID.
UUID restriction: The Attribute Type in this request can only be a 16-bit UUID. 128-bit UUIDs are not supported in this particular operation.
12.4.1.7 — Read By Type Request & Response
Reading All Attributes of a Given Type in One Pass
Instead of discovering handles then reading each one individually, Read By Type lets a client get the handle and value of every attribute with a specified UUID in one round trip. GATT uses this to discover all characteristics within a service — the client asks “give me all attributes with Type = Characteristic and I get back every characteristic declaration with its properties and value handle.”
Length field: The response includes a Length field indicating the byte size of each handle-value pair. All pairs in a single response have the same length. Pairs are returned in ascending handle order and are never split across PDUs. UUID can be 16-bit or 128-bit.
12.4.1.9 — Read Request & Response
Reading a Single Attribute by Handle
The most basic ATT operation. The client already knows the handle (from a previous discovery) and wants the current value. The server responds with the value. If the value is larger than ATT_MTU minus 1 bytes, only the first ATT_MTU-1 bytes are returned. The remainder must be fetched with Read Blob.
/* Read a characteristic value with gatttool */
gatttool -b AA:BB:CC:DD:EE:FF --char-read --handle=0x002A
/* With bluetoothctl: */
[AA:BB:CC:DD:EE:FF]# select-attribute /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0010/char0011
[AA:BB:CC:DD:EE:FF]# read
/* With BlueZ D-Bus / Python: */
import dbus
bus = dbus.SystemBus()
char = bus.get_object('org.bluez', '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0010/char0011')
char_iface = dbus.Interface(char, 'org.bluez.GattCharacteristic1')
value = char_iface.ReadValue({})
print(bytes(value))
12.4.1.11 — Read Blob Request & Response
Reading Long Attribute Values One Chunk at a Time
When a value is larger than ATT_MTU-1 bytes, the Read Request only returns the first chunk. Read Blob continues from a specified offset. The client keeps incrementing the offset and sending Read Blob Requests until the response comes back shorter than ATT_MTU-1 bytes — that signals the end of the value.
Value Offset is zero-based. Offset=0 means start from the first byte. This is the same information the server used in the Read Request (offset 0 is implicit there). The client must reassemble the chunks in order.
12.4.1.13 — Read Multiple Request & Response
Reading Several Attributes in One Round Trip
Instead of a separate Read Request for each attribute, Read Multiple sends a set of two or more handles in one PDU. The server concatenates the values in the same order as the handles provided and returns them all in one Response. This is efficient when polling multiple fixed-length values simultaneously — for example, reading temperature, humidity, and pressure in a single ATT transaction.
Important limitation: Values are concatenated without any delimiters or length markers. For this to work correctly, all requested attribute values must be of known, fixed length so the client can split the concatenated blob correctly. Variable-length values cannot be reliably decoded from a concatenated response.
12.4.1.15 — Read By Group Type Request & Response
Discovering All Service Boundaries at Once
This is the backbone of service discovery in BLE. Read By Group Type lets a client ask “give me all the Primary Service declarations and their handle ranges”. The server returns each service’s start handle, end handle (the last attribute in that service), and service UUID. The client iterates through the full handle space until it gets an Error Response meaning there are no more services.
The response’s Attribute Data List contains tuples of (Start Handle, End Group Handle, Service UUID). The End Group Handle is the handle of the last attribute inside that service definition. When the client sees the last handle in the response, it sends another request starting from that handle + 1, and repeats until it gets an Error Response.
/* Discovering all primary services with gatttool */
gatttool -b AA:BB:CC:DD:EE:FF --primary
/* Output example: */
/* attr handle: 0x0001, end grp handle: 0x0007 uuid: 1800 */
/* attr handle: 0x0016, end grp handle: 0x0019 uuid: 1801 */
/* attr handle: 0x0080, end grp handle: 0x0082 uuid: 1803 */
/* attr handle: 0x0083, end grp handle: 0x0085 uuid: 1802 */
/* gatttool fires multiple Read By Group Type Requests */
/* automatically until Error Response received */
/* Finding a specific service by UUID: */
gatttool -b AA:BB:CC:DD:EE:FF --primary --uuid=0x180D
/* Returns only the Heart Rate Service entry */
Next — Write Methods, Commands, Notifications & Indications
Part 3 covers Write Request/Response, Prepare Write for long values, Execute Write, Write Command, Signed Write, Handle Value Notification, and Handle Value Indication/Confirmation.
