BLE ATT layer ATT Write, Commands, Notifications & Indications

 

ATT Write, Commands, Notifications & Indications

Chapter 12 Part 3 of 4 · Write Request · Prepare/Execute Write · Write Command · Signed Write · Notification · Indication · Confirmation

Prepare+ExecuteLong value writes
0x00 / 0x01Cancel / Commit flag
12-byteAuth signature
IndicationReliable push
SEO Keywords:

BLE ATT Write Request Response acknowledged BLE Prepare Write Request Value Offset queue BLE Execute Write Request Flags cancel commit BLE Write Command no response control point BLE Signed Write Command 12-octet CSRK signature BLE Handle Value Notification unreliable push BLE Handle Value Indication reliable Confirmation

12.4.1.17 — Write Request & Response

A Confirmed Write — Server Must Acknowledge

The most common write operation. The client sends a new value for an attribute and waits for the server to acknowledge that it was written successfully. If anything goes wrong — wrong permissions, attribute doesn’t exist, value out of range — the server sends an Error Response instead of a Write Response.

Figure 12.13 — Write Request and Response
ClientServer
Write Request (Attribute Handle, Attribute Value)
Write Response (no parameters — just an ACK)
Or: Error Response (if permissions insufficient or handle invalid)
/* Write with acknowledgement using gatttool                  */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
         --handle=0x0011 --value=01

/* Python with BlueZ D-Bus:                                  */
char_iface.WriteValue([0x01], {})     # acknowledged write   */

/* C with BlueZ socket API — Write Request opcode = 0x12:    */
uint8_t pdu[] = {
    0x12,               /* Write Request opcode              */
    0x11, 0x00,         /* Attribute Handle (little-endian)  */
    0x01                /* Value to write                    */
};
send(att_sock, pdu, sizeof(pdu), 0);
/* Receive Write Response (opcode 0x13) to confirm           */

12.4.1.19 — Prepare Write Request & Execute Write

Writing Long Values That Don’t Fit One PDU

When an attribute value is too large for a single Write Request (limited to ATT_MTU-3 bytes), the client uses Prepare Write + Execute Write. The client sends the value in chunks with Prepare Write Requests, the server queues each chunk, and finally the client sends an Execute Write Request to commit everything atomically.

This two-phase commit guarantees the attribute either gets fully updated or not at all. The server never applies partial writes — the attribute’s value only changes once Execute Write with Flags=0x01 arrives.

Figure 12.14 — Prepare Write and Execute Write Sequence
ClientServer
Prepare Write Request (Handle, Offset=0, Chunk 1)
Prepare Write Response (echoes back Handle, Offset, Chunk 1) ← queued ✓
Prepare Write Request (Handle, Offset=N, Chunk 2)
Prepare Write Response (Handle, Offset=N, Chunk 2) ← queued ✓
… repeat for each chunk …
Execute Write Request (Flags = 0x01 — commit all queued writes)
Execute Write Response — all queued data written to attribute
Flags = 0x00 — Cancel

Discard all queued Prepare Write data without writing anything. Used when the client detects an error mid-sequence and wants to abort cleanly.

Flags = 0x01 — Commit

Write all queued data to the attribute in the order it was queued. The attribute’s new value becomes live immediately after this response.

Multi-client isolation: When several clients are simultaneously connected, each client’s Prepare Write queue is completely separate. Client A’s Execute Write does not affect Client B’s pending writes at all.

What the echo response does: The server’s Prepare Write Response echoes back the exact Handle, Offset, and data that was received. This lets the client verify the server received the data correctly before sending the next chunk. If there is a mismatch, the client can cancel and retry.

12.4.2.1 — Write Command

Fire and Forget — No Acknowledgement Ever

The Write Command is a one-way write with no acknowledgement. The server never sends a Write Response, and critically, it also does not send an Error Response even if the write fails. The client gets no feedback whatsoever. This makes Write Command an unreliable operation — but that is intentional for certain use cases.

Typical use: Control point attributes — attributes whose purpose is to trigger an action rather than store a value. Examples include starting/stopping device discovery, triggering a LED blink, or sending a command to reset a measurement counter. For these, the latency reduction from skipping the acknowledgement matters more than guaranteed delivery.

Figure 12.15 — Write Command (No Response)
ClientServer
Write Command (Attribute Handle, Attribute Value) — CMD flag = 1 in opcode
No response ever sent by server — client receives nothing back
/* Write command (no response) with gatttool                  */
gatttool -b AA:BB:CC:DD:EE:FF --char-write \
         --handle=0x0025 --value=01

/* --char-write sends Write Command (opcode 0x52, CMD=1)     */
/* --char-write-req sends Write Request (opcode 0x12, ACK)   */

/* C example — Write Command opcode = 0x52:                  */
uint8_t pdu[] = {
    0x52,               /* Write Command opcode (CMD bit=1)  */
    0x25, 0x00,         /* Attribute Handle (little-endian)  */
    0x01                /* Value                             */
};
send(att_sock, pdu, sizeof(pdu), 0);
/* No recv() needed — no response coming                     */

12.4.2.2 — Signed Write Command

A Write Command with a 12-byte authentication signature appended. The signature is computed using the CSRK (Connection Signature Resolving Key) that was distributed during SM Phase 3 pairing. The server verifies the signature against its stored CSRK for this client before processing the write.

This is the BLE mechanism for data integrity without link encryption — useful for scenarios where encrypting the entire link is not desirable (e.g., high-frequency sensor data where encryption latency matters) but you still need to prove the data came from a legitimate source and was not tampered with.

Figure 12.16 — Signed Write Command with Authentication Signature
ClientServer
Signed Write Command (Handle, Value, 12-byte Auth Signature)
Server verifies signature with CSRK — silently discards if invalid (no Error Response)

The signature covers the opcode, handle, value, and a counter that increments with every signed write. The counter prevents replay attacks — replaying an old signed PDU will fail verification because the expected counter has advanced.

12.4.3.1 — Handle Value Notification

Server Pushes Data — Client Does Not Confirm

A Notification is the server pushing data to the client unprompted. The client does not send any response. This makes Notifications the lowest-latency way to deliver data — there is no round-trip wait. It is also unreliable: if the client is busy, the notification may be dropped without the server knowing.

This is perfect for streaming data where individual missed readings are acceptable. A heart rate monitor, for example, sends a new heart rate measurement via Notification every second. If one notification is missed, the next one arrives a second later and the gap goes unnoticed. The client configures which notifications it wants to receive by writing to the Client Characteristic Configuration Descriptor (CCCD).

Figure 12.17 — Handle Value Notification (No Client Response)
ClientServer
Handle Value Notification (Attribute Handle, Attribute Value)
Client processes or discards — no message sent back
/* Subscribe to notifications with bluetoothctl               */
[AA:BB:CC:DD:EE:FF]# select-attribute /org/bluez/hci0/dev_.../char0010
[AA:BB:CC:DD:EE:FF]# notify on
/* This writes 0x0001 to the CCCD on handle 0x0011           */
/* Server now sends notifications whenever value changes     */

/* With gatttool:                                             */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
         --handle=0x0011 --value=0100
/* 0x0100 little-endian = CCCD value enabling notifications  */

/* Listen for incoming notifications:                        */
gatttool -b AA:BB:CC:DD:EE:FF --listen

/* Python BlueZ D-Bus notification callback:                 */
def properties_changed(interface, changed, invalidated, path):
    if 'Value' in changed:
        print("Notification:", bytes(changed['Value']))

bus.add_signal_receiver(properties_changed,
    dbus_interface='org.freedesktop.DBus.Properties',
    signal_name='PropertiesChanged',
    path_keyword='path')                                     

12.4.4 — Handle Value Indication & Confirmation

Server Pushes Data — Client Must Confirm Receipt

An Indication is identical to a Notification in payload but requires the client to send back a Handle Value Confirmation. Only after that confirmation arrives does the server proceed to send the next Indication. This guarantees delivery at the ATT level — the server knows the data reached the client.

The tradeoff is latency. Every indication requires a round trip before the next one can be sent. Notifications are lower latency but unreliable. Indications are reliable but slower. Use Indications for critical data where missing a value is not acceptable — like a blood pressure measurement that should always be delivered intact.

Figure 12.18 — Handle Value Indication and Confirmation
ClientServer
Handle Value Indication (Attribute Handle, Attribute Value)
Handle Value Confirmation (no parameters — just an ACK)
Server can now send next Indication — sequential, one at a time
Notification vs Indication

Property Notification Indication
Reliable? No Yes
Client response? None Confirmation
Throughput? Higher Lower (RTT per packet)
CCCD value? 0x0001 0x0002
Example use Heart rate, steps Blood pressure, glucose
/* Enable indications on a characteristic (CCCD = 0x0002):   */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
         --handle=0x0012 --value=0200
/* 0x0200 little-endian = CCCD value enabling indications    */

/* BlueZ handles the Confirmation automatically              */
/* The stack sends Handle Value Confirmation (opcode 0x1E)   */
/* transparently when an Indication PDU (opcode 0x1D) is RX  */

/* If you need to detect whether characteristic supports     */
/* Notification vs Indication — check the CCCD descriptor    */
/* and the Properties byte in the Characteristic Declaration */

/* Properties byte: bit 4 = Notify, bit 5 = Indicate         */
/* Value 0x10 = Notify only, 0x20 = Indicate only            */
/* Value 0x30 = both supported                               */

Chapter 12 ATT Series

Next — ATT Practical Examples & Chapter Summary

Part 4 covers the real sniffer captures of Exchange MTU and service discovery with the iterative Read By Group Type pattern, plus the complete Chapter 12 summary.

Next: Part 4 Practical Examples → ← Part 2

Leave a Reply

Your email address will not be published. Required fields are marked *