gatt: Add ATT long-write (Prepare/Execute Write) support#961
Conversation
| # the part value is limited to ATT_MTU - 5 bytes. | ||
| max_part_size = self.mtu - 5 | ||
| offset = 0 | ||
| while offset < len(value): |
There was a problem hiding this comment.
| while offset < len(value): | |
| for offset in range(0, len(value), max_part_size): |
| for handle, value in values.items(): | ||
| attribute = self.get_attribute(handle) | ||
| assert attribute is not None | ||
| await attribute.write_value(bearer, bytes(value)) |
There was a problem hiding this comment.
This is debatable here, and also why I didn't implement long write. On most platforms, including Android, iOS, and BlueZ, write request callbacks have arguments for offset, and every prepared write request is handled by handler applications. Whether to execute long reliable write simultaneously is actually decided by handlers. Thus, the design here will be different from most platforms.
Also, Core spec doesn't regulate the detail about prepared write. For example, sending (offset=0, length=3), (offset=4,length=1) (byte 3 is missing) is valid. Of course, apps can reject them, but here the behavior just fixed by the server class.
There was a problem hiding this comment.
However, handling like these platforms is also challenging for us, because we didn't introduce the offset parameter at first. The only way to achieve that is to introduce V3 handler, but it worths a hesitation.
There was a problem hiding this comment.
Supporting the most flexible case, where the low level offset-based reads and writes are exposed to the handlers would indeed require some API extension ("v3 handler"), that's probably not needed for most use cases, where what you want it maximum simplicity, where the application only deals with complete payloads (including the ability to encode/decode automatically into specific data types, which is very convenient). So implementing the simple case first, where re-assembly is done in the library, seems like a good option, even if we have to enforce some fixed policy (like "no gaps allowed", for example).
Add support for the Write Long Characteristic Values procedure, i.e. ATT Prepare Write plus Execute Write. This is the GATT procedure in BLE spec Vol 3 Part G 4.9.4, built on the ATT Queued Writes procedure in Vol 3 Part F 3.4.6. It is the write counterpart to the long-read support that already exists, where read_value falls back to Read Blob Requests for values larger than the MTU.
Previously the GATT server had no handlers for these PDUs, so it replied REQUEST_NOT_SUPPORTED to any Prepare Write Request, meaning a value larger than ATT_MTU minus 3 could not be written. The PDU classes already existed in att.py; only the procedure handling was missing.
Server: queues prepared writes per bearer and reassembles them on Execute with flags 0x01, committing the full value through the normal Attribute.write_value path. Flags 0x00 cancels the queue. A gap in the prepared offsets is rejected with INVALID_OFFSET, mirroring on_att_read_blob_request. The queue is cleaned up on disconnection.
Client: write_value with with_response now switches automatically to Prepare and Execute when the value exceeds ATT_MTU minus 3, chunking into parts of ATT_MTU minus 5 bytes, mirroring how read_value handles long reads. Short writes are unchanged.
Tests: client-side auto long-write round-trip, manual Prepare and Execute round-trip, cancel leaves the value unwritten, and non-contiguous offsets are rejected.