Block-Wise Transfers in the Constrained Application Protocol (CoAP)
updated by
- rfc-8323 — CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets
updates
- rfc-7252 — The Constrained Application Protocol (CoAP)
Extracted elements (29)
Block sizes are restricted to powers of two between 16 and 1024 bytes (SZX 0–6) to keep implementation as simple as possible and reduce code size on constrained nodes. The value 7 (2048 bytes) is reserved rather than permitted, keeping the range small.
Block-wise transfers are strictly layered on top of the CoAP base protocol (RFC 7252) and do not remove or bypass its congestion control (NSTART, PROBING_RATE). Non-confirmable requests within block-wise transfers outside of multicast use are discouraged because they escalate the overall non-delivery probability.
The overriding design objective is to avoid conversation state on the server for block-wise GET. Each block transfer is an independent CoAP exchange; the server can be truly stateless. Atomicity for POST/PUT necessarily requires some state, but stateless per-block handling is also permitted where intermediate inconsistency is acceptable.
Block size negotiation is bilateral: a client may propose a size in the initial request; the server may respond with a smaller size. For Block2, subsequent requests MUST use the server's chosen size. For Block1, the client SHOULD heed the server's preference and may need to renumber blocks if the size decreases mid-transfer.
For multicast GET, a client or server may use Block2 with NUM=0 to limit response size. Retrieval of further blocks always uses unicast exchanges, and the client SHOULD heed block size preferences indicated by the server in the multicast response.
When combining Block-Wise transfers with the Observe option, the server only sends the first block in a 2.05 notification. The client retrieves subsequent blocks via additional GET requests with Block2 NUM > 0, and MUST compare ETags across blocks to ensure consistency.
Block2 transfers that retrieve the response body for a request that used Block1 MUST be performed in sequential order.
Either Block option (Block1 or Block2) MUST be processed when present in a CoAP message, or the message must be rejected; the option is therefore Critical. Either Block option MUST NOT occur more than once in a single message.
For an atomic Block1 transfer, if not all previous blocks are available at the server when the final block (M=0) is received, the transfer fails and error code 4.08 (Request Entity Incomplete) MUST be returned. Clients SHOULD always start with block zero and send blocks in order.
If-None-Match only works correctly on Block1 requests with NUM=0 and MUST NOT be used on Block1 requests with NUM != 0.
In a Block2 request (control usage), the client MUST set the M bit to zero and the server MUST ignore it on reception. The server MUST use the block size indicated by the client or a smaller size.
In descriptive usage, the block size implied by SZX MUST match the size of the payload in bytes when the M bit is set. If the server returns a smaller block size than the client requested, the client MUST use the smaller size in subsequent requests.
The Content-Format Option sent with requests or responses MUST reflect the Content-Format of the entire body. If blocks of a request arrive at a server with mismatching Content-Format Options, the server MUST NOT assemble them into a single request.
The Size2 option value MUST be set to 0 when used in a request as a size request (asking the server to provide a size estimate). A client MUST be prepared for the server to ignore the size estimate request, as the options are elective.
The SZX value 7 (which would imply a 2048-byte block) is reserved: it MUST NOT be sent, and MUST lead to a 4.00 Bad Request response code upon reception in a request.
When reassembling a response from blocks of a dynamically changing resource, if an ETag Option is available, the client MUST compare ETag Options across blocks. Mismatched ETags indicate the representation changed mid-transfer.
Response code 2.31 (Continue) indicates that a non-final block of a Block1 request body was received successfully and the server encourages sending further blocks, but a final outcome cannot yet be determined. No payload is returned with this response code.
Response code 4.08 (Request Entity Incomplete) indicates the server has not received the blocks it needs: the client has not sent all blocks, not sent them in the required order, or sent them so long ago that the server discarded them.
Response code 4.13 (Request Entity Too Large) may be returned at any time during a Block1 transfer when the server lacks resources for atomic assembly. A 4.13 response to a request without Block1 is a hint to try Block1; a 4.13 with a smaller SZX in its Block1 Option is a hint to try that smaller block size.
The Block2 option (number 23) and Block1 option (number 27) are Critical, Unsafe, variable-length (0–3 byte) unsigned integer CoAP options. Block1 pertains to the request payload; Block2 pertains to the response payload. Both may appear in both request and response messages.
The Size2 option (number 28) and Size1 option (number 60) are elective, No-Cache-Key, variable-length (0–4 byte) unsigned integer options. Size2 indicates total response body size; Size1 indicates total request body size or (in 4.13 responses) the maximum acceptable request entity size.
This document registers Block2 (23), Block1 (27), and Size2 (28) in the 'CoAP Option Numbers' registry defined by RFC 7252. (Size1 at number 60 was previously defined by RFC 7252 and is further specified here.)
This document registers response codes 2.31 (Continue) and 4.08 (Request Entity Incomplete) in the 'CoAP Response Codes' registry defined by RFC 7252.
A stateless server is susceptible to resource exhaustion if an attacker sends a Block1 request with a high block number, causing the server to allocate a large sparse representation. Servers SHOULD avoid resource exhaustion from state created by untrusted sources and SHOULD prefer stateless approaches to minimize attack surface.
All blocks of a resource protected by a security association MUST be subject to the same security checks; it MUST NOT be possible for unprotected exchanges to influence blocks of an otherwise protected resource. Where object security is used, PUT/POST should be implemented atomically.
Partial block transfers can bypass Intrusion Detection Systems (IDSs); it is RECOMMENDED that IDSs analyzing CoAP traffic implement the Block options to reassemble entire representations. Techniques analogous to IP fragmentation attacks (interleaving blocks from different paths) may still defeat IDS reassembly.
To mitigate amplification attacks, a CoAP server can limit the size of individual blocks returned (e.g., a 10-byte request returning a 64-byte block instead of a 1016-byte full representation), significantly reducing the amplification factor provided to attackers.
For a Block1 atomic PUT/POST: the server accumulates blocks, responding 2.31 Continue (M=1 in Block1 response) for non-final blocks. On the final block (M=0 in request), the server either commits atomically and returns a success code, or returns 4.08 if prior blocks are missing, or 4.13 if it lacks resources.
A Block option value is a 0–3 byte unsigned integer encoding three fields: SZX (3 LSBs, bits 0–2) encodes block size as 2^(SZX+4) bytes (16–1024); M bit (bit 3) indicates whether more blocks follow; NUM (remaining upper bits) is the zero-based block sequence number. The byte offset of the block is NUM << (SZX+4).