1. 概述
CoAP(Constrained Application Protocol)是为类似智能家居、楼宇自动化等 M2M 应用设计,专门用于受限节点之间的通信网络传输协议。
与 HTTP 相类似,CoAP 为应用端点提供了一套请求/响应的交互模型,旨在满足受限环境特定需求(如多播、低开销等)的条件下,轻松达成与 HTTP 交互,集成入 WEB 网络中的目的。
2. 消息格式
2.1 UDP 消息格式
CoAP 默认使用 UDP(DTLS) 传输。
CoAP 消息使用二进制编码,使用固定 4 字节长度的消息头,接一个 0~8 字节长度的变长 Token,若干 Type-Length-Value (TLV)格式的 Options 或序列 0。最后的载荷部分可选。若载荷存在,以一个字节长度的 0xFF (载荷标识)表明 Options 的结束与 载荷的开始。如下表所示:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 1: Message Format as Defined in RFC 7252
- Verison:RFC7252 定义了版本 “01” 的消息格式。携带未知版本号的消息将被忽略。
- Type:消息类型 Confirmable (0),Non-confirmable (1),Acknowledgement (2),Reset (3)。
- TKL:Token 字段的实际长度。
- Code:高 3bit 表明消息类别,低 5bit 表明细节。目前有效的类别有请求(0),成功的响应(2),客户端错误的响应(4),服务端错误的响应(5)。“0.00”特定的表明空消息。更多编码参见附录A和附录B。
- Message ID:可用于检测消息重传,匹配请求响应对。
- Token:独立于 Message ID,也用于匹配请求和响应。
2.2 TCP 消息格式
由于某些网络对 UDP 的使用限制,CoAP 又添加了对可靠传输网络(如 TCP、TLS等)的支持。消息以固定初始一个字节开始,格式如下表:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Len | TKL | Extended Length (if any, as chosen by Len) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Code | Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 2: CoAP Frame for Reliable Transports
- Length:从选项开始的消息长度。0~12 直接表明消息长度;13 表示初始字节后有一个字节,值为消息长度减去 13;14 表示初始字节后有两个字节,值为消息长度减去 269; 15 表示初始字节后有四个字节,值为消息长度减去 65805。
- 其它字段与 2.1 含义相同。
code 在 7.00-7.31 范围内的消息为信号消息,仅在可靠传输网络下使用,允许端点之间:
- 获取相关特征,如消息最大值(默认1152);
- 以有序的方式关闭连接;
- 对严重错误,在结束连接时提供诊断信息。
2.3 选项
CoAP 定义了一系列选项,每个选项以固定编码指代。选项消息并不直接给出固定编码,而是用与前一个选项的编码差值来表明。对于同一选项编码的多个选项字段,编码差值可为 0。选项消息以固定初始一个字节开始,格式如下表:
0 1 2 3 4 5 6 7
+---------------+---------------+
| | |
| Option Delta | Option Length | 1 byte
| | |
+---------------+---------------+
\ \
/ Option Delta / 0-2 bytes
\ (extended) \
+-------------------------------+
\ \
/ Option Length / 0-2 bytes
\ (extended) \
+-------------------------------+
\ \
/ /
\ \
/ Option Value / 0 or more bytes
\ \
/ /
\ \
+-------------------------------+
Figure 3: Option Format
- Option Delta: 0~12 是编码差值;13 表示初始字节后,再跟随一个字节,值为实际编码差值减去 13;14 表示初始字节后,再跟随两个字节,值为实际编码差值减去 269;15 表示复用为载荷标识(Payload Marker)。
- Option Length:0~12 是 Option Value 字段的实际长度;13 表示 Option Value 字段前有一个字节,值为实际长度减去 13;14 表示 Option Value 字段前有两个字节,值为实际长度减去 269; 15 留用。
- Option Value:选项字段的实际值。可以为空(实际长度为0),opaque(不透明的字节序列),数字,字符串四种类型的值。
在信号消息中,还定义了一些专有选项,不在各类消息中通用,其编码参考[附录F](#appendix6)。
3. 简单通信模型
CoAP 定义了四种消息类型:Confirmable(CON),Non-confirmable(NON),Acknowledgement(ACK) 和 Reset(RST)。发送方发送一条 CON 类型的消息,接收方返回一个相同 MessageID 的 ACK 以作确认。此过程中可使用重传机制保证可靠性。若接收方不支持处理 CON 类型,则返回 RST 替代。若传输过程不需要考虑可靠性,发送发可直接发送 NON 类型消息。若接收方不支持处理 NON 类型,可以返回 RST 说明情况。
CoAP Client CoAP Server CoAP Client CoAP Server
| | | |
| CON [0xbc90] | | (-------) [------] |
| GET /temperature | | GET /temperature |
| (Token 0x71) | | (Token 0x71) |
+------------------->| +------------------->|
| | | |
| ACK [0xbc90] | | (-------) [------] |
| 2.05 Content | | 2.05 Content |
| (Token 0x71) | | (Token 0x71) |
| "22.5 C" | | "22.5 C" |
|<-------------------+ |<-------------------+
| | | |
CoAP over UDP CoAP over reliable
transports
Figure 4: Comparison between CoAP over Unreliable Transports and
CoAP over Reliable Transports
请求消息全部承载于 CON 和 NON 消息中,响应消息则还可以通过 ACK 类消息捎带。
Client Server Client Server
| | | |
| CON [0xbc90] | | CON [0xbc91] |
| GET /temperature | | GET /temperature |
| (Token 0x71) | | (Token 0x72) |
+----------------->| +----------------->|
| | | |
| ACK [0xbc90] | | ACK [0xbc91] |
| 2.05 Content | | 4.04 Not Found |
| (Token 0x71) | | (Token 0x72) |
| "22.5 C" | | "Not found" |
|<-----------------+ |<-----------------+
| | | |
Figure 5: Two GET Requests with Piggybacked Responses
若服务端不能立即响应客户端的请求,可以简单返回一个空的 ACK 消息避免客户端不停重传请求,然后再合适的时机将响应信息传递给客户端。
Client Server
| |
| CON [0x7a10] |
| GET /temperature |
| (Token 0x73) |
+----------------->|
| |
| ACK [0x7a10] |
|<-----------------+
| |
... Time Passes ...
| |
| CON [0x23bb] |
| 2.05 Content |
| (Token 0x73) |
| "22.5 C" |
|<-----------------+
| |
| ACK [0x23bb] |
+----------------->|
| |
Figure 6: A GET Request with a Separate Response
若请求附加在 NON 类型的消息中,则响应报文一般会使用一个新 MessageID 的 NON 消息。但是,使用 CON 响应 NON 请求,或使用 NON 响应 CON 请求都是协议允许的,端点实现需具备应对该情形的功能。
Client Server
| |
| NON [0x7a11] |
| GET /temperature |
| (Token 0x74) |
+----------------->|
| |
| NON [0x23bc] |
| 2.05 Content |
| (Token 0x74) |
| "22.5 C" |
|<-----------------+
| |
Figure 7: A Request and a Response Carried in Non-confirmable Messages
小结上述四种消息类型的应用:
+----------+-----+-----+-----+-----+
| | CON | NON | ACK | RST |
+----------+-----+-----+-----+-----+
| Request | X | X | - | - |
| Response | X | X | X | - |
| Empty | * | - | X | X |
+----------+-----+-----+-----+-----+
注:”*” 表示仅在引出一个空 RST 消息的场景下使用(”CoAP ping”)。
4. CoAP URIs
4.1 Scheme
URI Scheme 如下,coap 端口默认为 TCP/UDP 5683、WebSockets 80, coaps 端口默认为 TCP/UDP 5684、WebSockets 443:
coap-URI = "coap:" "//" host [ ":" port ] path-abempty [ "?" query ]
coaps-URI = "coaps:" "//" host [ ":" port ] path-abempty [ "?" query ]
coap-tcp-URI = "coap+tcp:" "//" host [ ":" port ] path-abempty [ "?" query ]
coaps-tcp-URI = "coaps+tcp:" "//" host [ ":" port ] path-abempty [ "?" query ]
coap-ws-URI = "coap+ws:" "//" host [ ":" port ] path-abempty [ "?" query ]
coaps-ws-URI = "coaps+ws:" "//" host [ ":" port ] path-abempty [ "?" query ]
scheme 和 host 是大小写不敏感的,通常都为小写;其它部分大小写敏感。除部分保留字外,字符与其百分号编码等价,视为同一 URI(通常不使用百分号编码)。如下:
coap://example.com:5683/~sensors/temp.xml
coap://EXAMPLE.com/%7Esensors/temp.xml
coap://EXAMPLE.com:/%7esensors/temp.xml
4.2 分解 URI 为 options
遵循如下步骤,从 URI 中分解出 Uri-Host,Uri-Port,Uri-Path 和 Uri-Query:
- 若 URI 不满足绝对路径格式,直接失败。
- 使用 RFC3986 中定义的方法解析 url。
- 若解得的 “scheme” 不是 4.1 中提到的,直接失败。
- 若解得的 url 包含 “fragment”,直接失败。
- 若解得的 url 的 “host” 不是 IP 地址格式,则将其转为小写字符,解码百分号编码字符,得到 Uri-Host 选项的值。
- 若解得的 url 包含 “port”,且不是请求消息的目标 TCP/UDP 端口,则添加 Uri-Port 选项并置为 “port” 值。
- 若解得的 url 的 “path” 为空或单独一个 “/”,跳过此步骤;否则为 “path” 的每个部分添加一个 Uri-Path 选项(选项值不包含分隔符)。同时,百分号编码需要转换为对应的字符。
- 若解得的 url 包含 “query”,为每个参数添加一个 Uri-Query 选项(选项值不包含问号和分隔符)。同时,百分号编码需要转换为对应的字符。
4.3 组合 options 为 URI
遵循如下步骤,从 coap 选项中构建一个完整 URI:
- 根据实际情况,“url” 以 4.1 中合适的 scheme 开始。
- 若请求包含 Uri-Host 选项,则 “host” 值为该选项的值。同时,任何非 ASCII 编码的字符需要进行百分号编码。若请求不包含 Uri-Host 选项,则 “host” 值为请求消息的目的 IP 地址。
- 将 “host” 添加到 “url” 中。
- 若请求包含 Uri-Port 选项,则 “port” 值为该选项的值。若请求不包含 Uri-Port 选项,则 “port” 值为请求消息的 TCP/UDP 目的端口。
- 若 “port” 不是 scheme 的默认端口,则将该端口号添加到 “url” 中,以一个 U+003A 分号(:) 连接。
- 初始化 “resource name” 为空字符串。对于每一个 Uri-Path 选项,将其值添加到 “resource name” 后,以一个 U+002F 斜杠(/) 连接。同时,不是非保留字集合、“sub-delims”集合、U+003A(:)、U+0040(@) 的字符,需要进行百分号编码。
- 若 “resource name” 依然为空,则将其置为单字符 U+002F(/)。
- 对于请求消息中的每个 Uri-Query 选项,将其值添加到 “resource name” 后,以一个 U+003F(?)(首个)或 U+0026(&)(后续)连接。同时,不是非保留字集合、“sub-delims”集合(U+0026(&) 除外)、U+003A(:)、U+0040(@)、U+002F(/)、U+003F(?) 的字符,需要进行百分号编码。
- 将 “resource name” 添加到 “url”。
- 返回 “url”。
5. 组播
CoAP 支持使用 NON 类消息向一个组播 IP 地址发送请求,达到组播的目的。因此,服务端需要具备识别一个请求是否来自组播的能力,并避免向组播来源发送 RST 类消息。而发送方也要尽量避免使用与组播相同的 Message ID。
服务端一般不对收到的组播请求作出回应。作出回应时,也会等待一个 Leisure 周期,不会立即响应。
6. 块传输
6.1 块选项格式
出于很多原因,限制数据包的大小是有益的:
- UDP 包不得超过 64KB
- 避免 IP 分片(IPv6 MTU 为 1280)
- 避免适配层分片(如 6LoWPAN 限制 60~80B)
当一个资源超过单一 CoAP 数据包的大小时,可以添加 Block 选项启用块传输。鉴于这种传输可能发生在两个方向,用数字 1(Block1,Size1) 和 2(Block2,Size2) 分别指代请求和响应相关的传输。
有三个信息包含在 Block 选项的值中:
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
| NUM |M| SZX |
+-+-+-+-+-+-+-+-+
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NUM |M| SZX |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NUM |M| SZX |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 8: Block Option Value
-
块大小(SZX)
3bit,实际块大小为 2^(SZX + 4),有效取值 0~7。其中取值 7 代表 BERT 选项,实际块大小与取值 6 相同(即 2^10 字节)。
-
是否存在更多块(M)
1bit,0 代表本块是传输的最后一块。
-
块的相对序号(NUM)
从 0 开始的块序号。本块实际传输从字节序号 “NUM « (SZX + 4)” 开始。
6.2 Block2 应用
当响应携带 M 标记的 Block2 选项时,请求方可以通过发送更多和初始请求相同的请求消息来获取后续资源。这些后续请求需要添加 Block2 选项,M 标记置零。
一旦请求方使用了 Block2 选项并且第一个响应被处理,此次块传输过程中的 SZX 不再调整。
示例:
CLIENT SERVER
| |
| CON [MID=1234], GET, /status ------> |
| |
| <------ ACK [MID=1234], 2.05 Content, 2:0/1/128 |
| |
| CON [MID=1235], GET, /status, 2:2/0/64 ------> |
| |
| <------ ACK [MID=1235], 2.05 Content, 2:2/1/64 |
| |
| CON [MID=1236], GET, /status, 2:3/0/64 ------> |
| |
| <------ ACK [MID=1236], 2.05 Content, 2:3/1/64 |
| |
| CON [MID=1237], GET, /status, 2:4/0/64 ------> |
| |
| <------ ACK [MID=1237], 2.05 Content, 2:4/1/64 |
| |
| CON [MID=1238], GET, /status, 2:5/0/64 ------> |
| |
| <------ ACK [MID=1238], 2.05 Content, 2:5/0/64 |
Figure 9: Block-Wise GET with Late Negotiation
6.2 Block1 应用
带载荷的请求消息(PUT、POST等)中,Block1 选项指代请求消息中的载荷。作为此类请求的回应,Block1 中设置请求消息中载荷的偏好值。从第二个请求消息开始,发送端优先使用响应指定的 SZX,或设置更小的值。
示例:
CLIENT SERVER
| |
| CON [MID=1234], PUT, /options, 1:0/1/128 ------> |
| |
| <------ ACK [MID=1234], 2.31 Continue, 1:0/1/32 |
| |
| CON [MID=1235], PUT, /options, 1:4/1/32 ------> |
| |
| <------ ACK [MID=1235], 2.31 Continue, 1:4/1/32 |
| |
| CON [MID=1236], PUT, /options, 1:5/1/32 ------> |
| |
| <------ ACK [MID=1235], 2.31 Continue, 1:5/1/32 |
| |
| CON [MID=1237], PUT, /options, 1:6/0/32 ------> |
| |
| <------ ACK [MID=1236], 2.04 Changed, 1:6/0/32 |
Figure 10: Simple Atomic Block-Wise PUT with Negotiation
6.3 BERT 应用
BERT 扩展了块传输协议,使其可以在可靠传输网络传输更大的消息。
收到携带 BERT 选项请求的接收方可以用不同的 SZX 值作出回应,且每个响应携带多块数据。除最后一块数据外,每块数据的大小为 1024。
示例:
CoAP Client CoAP Server
| |
| GET, /status ------> |
| |
| <------ 2.05 Content, 2:0/1/BERT(3072) |
| |
| GET, /status, 2:3/0/BERT ------> |
| |
| <------ 2.05 Content, 2:3/1/BERT(5120) |
| |
| GET, /status, 2:8/0/BERT ------> |
| |
| <------ 2.05 Content, 2:8/0/BERT(4711) |
Figure 11: GET with BERT Blocks
7. 参考
- https://tools.ietf.org/html/rfc7252
- https://tools.ietf.org/html/rfc7959
- https://tools.ietf.org/html/rfc8323
8. 附录
附录A CoAP 事务码
+------+--------+-----------+
| Code | Name | Reference |
+------+--------+-----------+
| 0.01 | GET | [RFC7252] |
| 0.02 | POST | [RFC7252] |
| 0.03 | PUT | [RFC7252] |
| 0.04 | DELETE | [RFC7252] |
+------+--------+-----------+
附录B CoAP 响应码
3.00-3.31 保留,其它还未指定。
+------+------------------------------+-----------+
| Code | Description | Reference |
+------+------------------------------+-----------+
| 2.01 | Created | [RFC7252] |
| 2.02 | Deleted | [RFC7252] |
| 2.03 | Valid | [RFC7252] |
| 2.04 | Changed | [RFC7252] |
| 2.05 | Content | [RFC7252] |
| 2.31 | Continue | [RFC7959] |
| 4.00 | Bad Request | [RFC7252] |
| 4.01 | Unauthorized | [RFC7252] |
| 4.02 | Bad Option | [RFC7252] |
| 4.03 | Forbidden | [RFC7252] |
| 4.04 | Not Found | [RFC7252] |
| 4.05 | Method Not Allowed | [RFC7252] |
| 4.06 | Not Acceptable | [RFC7252] |
| 4.08 | Request Entity Incomplete | [RFC7959] |
| 4.12 | Precondition Failed | [RFC7252] |
| 4.13 | Request Entity Too Large | [RFC7252] |
| 4.15 | Unsupported Content-Format | [RFC7252] |
| 5.00 | Internal Server Error | [RFC7252] |
| 5.01 | Not Implemented | [RFC7252] |
| 5.02 | Bad Gateway | [RFC7252] |
| 5.03 | Service Unavailable | [RFC7252] |
| 5.04 | Gateway Timeout | [RFC7252] |
| 5.05 | Proxying Not Supported | [RFC7252] |
| 7.01 | CSM | [RFC8323] |
| 7.02 | Ping | [RFC8323] |
| 7.03 | Pong | [RFC8323] |
| 7.04 | Release | [RFC8323] |
| 7.05 | Abort | [RFC8323] |
+------+------------------------------+-----------+
附录C CoAP 选项编码
选项编码可划分为四段:
+-------------+---------------------------------------+
| Range | Registration Procedures |
+-------------+---------------------------------------+
| 0-255 | IETF Review or IESG Approval |
| 256-2047 | Specification Required |
| 2048-64999 | Expert Review |
| 65000-65535 | Experimental use (no operational use) |
+-------------+---------------------------------------+
第一段中定义了如下信息字段:
+--------+------------------+-----------+
| Number | Name | Reference |
+--------+------------------+-----------+
| 0 | (Reserved) | [RFC7252] |
| 1 | If-Match | [RFC7252] |
| 3 | Uri-Host | [RFC7252] |
| 4 | ETag | [RFC7252] |
| 5 | If-None-Match | [RFC7252] |
| 7 | Uri-Port | [RFC7252] |
| 8 | Location-Path | [RFC7252] |
| 11 | Uri-Path | [RFC7252] |
| 12 | Content-Format | [RFC7252] |
| 14 | Max-Age | [RFC7252] |
| 15 | Uri-Query | [RFC7252] |
| 17 | Accept | [RFC7252] |
| 20 | Location-Query | [RFC7252] |
| 23 | Block2 | [RFC7959] |
| 27 | Block1 | [RFC7959] |
| 28 | Size2 | [RFC7959] |
| 35 | Proxy-Uri | [RFC7252] |
| 39 | Proxy-Scheme | [RFC7252] |
| 60 | Size1 | [RFC7252] |
| 128 | (Reserved) | [RFC7252] |
| 132 | (Reserved) | [RFC7252] |
| 136 | (Reserved) | [RFC7252] |
| 140 | (Reserved) | [RFC7252] |
+--------+------------------+-----------+
附录D URI 保留字
保留字的目的是在 URI 内提供一套分隔符的集合。
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
对应的,非保留字包含如下:
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
附录E CoAP 传输参数
部分传输参数的默认值如下:
+-------------------+---------------+
| name | default value |
+-------------------+---------------+
| ACK_TIMEOUT | 2 seconds |
| ACK_RANDOM_FACTOR | 1.5 |
| MAX_RETRANSMIT | 4 |
| NSTART | 1 |
| DEFAULT_LEISURE | 5 seconds |
| PROBING_RATE | 1 byte/second |
+-------------------+---------------+
附录F CoAP Signaling Options
+------------+--------+---------------------+-----------+
| Applies to | Number | Name | Reference |
+------------+--------+---------------------+-----------+
| 7.01 | 2 | Max-Message-Size | RFC 8323 |
| 7.01 | 4 | Block-Wise-Transfer | RFC 8323 |
| 7.02, 7.03 | 2 | Custody | RFC 8323 |
| 7.04 | 2 | Alternative-Address | RFC 8323 |
| 7.04 | 4 | Hold-Off | RFC 8323 |
| 7.05 | 2 | Bad-CSM-Option | RFC 8323 |
+------------+--------+---------------------+-----------+