What GATT is for
After two Bluetooth Low Energy devices connect, they need a shared way to say what data exists and how to reach it. The Generic Attribute Profile (GATT) provides it. The Core Specification describes GATT as the structure in which profile data is exchanged, built on top of the Attribute Protocol (ATT). Android’s BLE overview puts it more plainly: GATT is a general specification for sending and receiving short pieces of data, known as attributes, over a BLE link.
GATT has two roles. The server holds the data and answers requests. The client sends requests and receives responses, notifications and indications. The specification notes that these roles are not fixed to a device: a device takes a role when it starts a procedure, and it can act as client and server at the same time. In the common case, a sensor is the server and the phone reading it is the client.
The hierarchy: profiles, services, characteristics
GATT organises data in layers.
- Profile. The top level. A profile is one or more services that together fulfil a use case, such as a heart rate monitor.
- Service. A collection of data and behaviours for one function or feature of the device. A service can be primary, exposing functionality of the device, or secondary, meant only to be included by another service.
- Characteristic. A single value used in a service, along with properties that say how the value can be accessed. Android’s overview compares a characteristic to a type, like a class.
- Descriptor. Extra information about a characteristic value, such as a human-readable description, a valid range or a unit of measure. A characteristic can have none or several.
Apple’s Core Bluetooth guide gives a concrete example. A heart rate monitor has a heart rate service. Inside it, one characteristic holds the body location of the sensor and another carries the heart rate measurements.
How everything is identified
Every service, characteristic and descriptor has a type, given as a UUID. The Bluetooth SIG registers short 16-bit UUIDs for standard ones in its Assigned Numbers. The Heart Rate service is 0x180D, the Heart Rate Measurement characteristic is 0x2A37, and the Client Characteristic Configuration descriptor is 0x2902.
These short forms stand in for full 128-bit UUIDs. Apple’s guide explains that 180D is shortened from 0000180D-0000-1000-8000-00805F9B34FB, built on the Bluetooth base UUID. A custom service, one the SIG has not defined, uses a 128-bit UUID that the developer generates. This is how two copies of an app recognise each other: both are built with the same custom service and characteristic UUIDs, and one device can look for that service in advertising packets before it connects.
Underneath, the Attribute Protocol stores each of these items as an attribute with a type, a 16-bit handle that the server assigns, and permissions. Clients discover the handles, then use them in read and write requests. Apps rarely see handles directly; platform APIs work with service and characteristic objects.
Reading, writing and subscribing
A characteristic’s properties decide what a client may do with it:
- Read. The client asks for the current value.
- Write. The client sends a new value. A write request gets a response from the server. A write command, called write without response on iOS and Android, does not, so the client gets no confirmation.
- Notify and indicate. The server pushes the value whenever it changes. To turn this on, the client writes to the characteristic’s Client Characteristic Configuration descriptor. Android’s guide shows this step: enable notifications locally, then write the descriptor on the remote device.
On the server side, Core Bluetooth sends updates with updateValue(_:for:onSubscribedCentrals:), and Android uses BluetoothGattServer.
A typical session after connecting runs in this order: discover services, discover the characteristics in the service you need, subscribe to the ones that change, and read or write the rest.
Size limits
Values are small. The Attribute Protocol limits an attribute value to 512 octets, and a single packet carries less than that, depending on the ATT MTU the two devices agree. Longer values need special read and write procedures, and anything bigger than one attribute, such as a chat message history or a file, has to be split by the application into pieces and put back together on the other side.
Designing a service for your own app
For a custom link between two devices, such as two phones, keep the GATT design small:
- One custom service UUID that both apps share.
- One characteristic the client writes to, for data going to the server.
- One characteristic the server notifies on, for data coming back.
- A framing format of your own inside those values, so that messages larger than one write can be split and reassembled.
Then decide which side runs the server. Either phone can, and in apps where either user may start a conversation, each phone often runs both a GATT server and a client. That choice is tied to the central and peripheral roles, which decide who connects to whom.