Make everything connectable.


概念

1. 设计初衷

  据wikipedia词条,CoAP协议是一种专用于constrained devices之间相互通信的物联网协议。其RFC文档编号为7252,缺省端口号为5683CoAP协议的设计初衷就是为了方便资源受限型的设备(如手机、网络摄像头等等)能够接入互联网,因为这种类型的设备无法直接使用已有的HTTP协议。

2. 协议特性

  据CoAP的RFC文档,该协议的特性如下:

  • web协议满足受限环境中的M2M需求;
  • 默认基于UDP协议(可选的可靠性),支持单播及多播请求;
  • 异步消息交换;
  • 低头部(报文头)开销及解析复杂度;
  • URI及Content-Type支持;
  • 提供简单的代理及缓存机制;
  • 无状态的HTTP映射,允许通过HTTP以统一的方式提供对CoAP资源的访问或者通过CoAP交替实现HTTP的简单接口来构建代理;
  • 提供可选的安全性,由DTLS保障;
  • 支持观察模式及块传输方式;

  值得强调的一点是,同一个CoAP设备即扮演客户端又承担服务器的角色。

3. 协议概览

  CoAP协议结构概览如下图所示(摘自wikipedia):

  CoAP协议的实际报文则如下(wireshark解析结果):

3.1 协议结构

  就报文功能结构而言,CoAP协议由几个部分组成,即:报头(Header) + 令牌(Token) + 可选项(Options) + 定界符(Marker) + 负载(Payload),如下所示:

1
2
3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Header | Token(optional) | Options(optional) | Marker + Payload(optional) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  就报文长度特点而言,CoAP协议仍然采用定长+变长的方式。定长是为了方便解码器预判和识别,变长则是为了协议的可扩展性及功能保障。

  对于CoAP报文而言,可选项和负载都非必须出现的,视具体情况而定。

3.1.1 Header

  协议报头是定长的,总共4个字节,其组成如下:

1
2
3
4
5
 0               1               2               3          
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  从前往后依次为:版本号(2bit)、消息类型(2bit)、Token长度(4bit)、Code(8bit)、消息ID(8bit)。

  TKL为0的时候即为最小头部(4字节),由此可见CoAP报文的结构是相当简洁紧凑的,这就是我们上面提到的协议特性的第四点:低开销和解析复杂度。

3.1.2 Token

  协议中Token的长度由报头的TKL字段的值决定,取值范围是$[0, 8]$字节,属于变长字段。由于该值可以为0,因此我们把Token也视作是可选(optional)的。如果有的话,它紧跟在报头后面,如下所示:

1
2
3
4
5
6
 0                                                             3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

3.1.2 Options

  Options部分又可以拆分为五个部分,即:定长部分(前1个字节) + 变长的Delta/Length/Value。Options的整体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  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
\ \
/ /
\ \
+-------------------------------+

  其中,Option Delta(extended)的长度由第一个字节中的Option Delta的值决定,Option Length(extended)的长度则由第一个字节中的Option Length的值决定。Option Value的长度则由前面的Option DeltaOption Legnth共同决定,这部分较复杂,会在后续协议详解中详细阐释。

3.1.3 Marker && Payload

  Payload部分则完全是变长的了,除了前面的HeaderOptions,剩下的部分均被视作CoAPPayload。为了正确识别Payload的起始位置,RFC规定在Payload前面必须要有一个字节的定界符(Marker)0xFF,如果没有则接收方会直接按错误消息处理(丢弃),如下所示:

1
2
3
4
 0 1 2 3 4 5 6 7 0 1 2 ···  
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

3.2 消息模型

  同样的,鉴于CoAP协议的使用场景,其消息模型势必也要设计的尽量简洁,这样才能减少不必要的开销。这就是为什么CoAP协议默认是基于UDP的原因,因为UDP协议是无连接的尽量交付方式。

  同时,为了提供可靠性支持,CoAP协议的消息中引入了确认机制,确保消息可靠的传输到彼端。因此,CoAP请求方向一共有2种类型的消息:Confirmable(简写为CON)和Non-confirmable(简写为NON)。前者要求接收方必须响应,后者则没有要求。

  针对客户端发送来的请求消息,服务器端收到消息处理后也有2种类型的响应:Acknowledgement(简写为ACK)和Reset(简写为RST)。前者对客户端发送来的消息进行确认(对应CON消息),后者表示客户端发送的消息存在错误(不管是CON消息还是NON消息,只要有错误服务器均会响应)。

3.2.1 消息类型

  综上所述,CoAP协议中一共有四种类型的消息(对应报头的Type字段):

  • 需要确认的消息Confirmable
  • 无需确认的消息Non-confirmable
  • 确认消息Acknownledgement
  • 重置消息Reset

3.2.2 传输模型

  因此,CoAP中的消息传输模型如下:

1
2
3
4
5
6
7
8
9
Client              Server               Client              Server
| | | |
| CON [0x7d34] | | NON [0x01a0] |
+----------------->| vs +----------------->|
| | | |
| ACK [0x7d34] |
|<-----------------+
| |
可靠消息传输模式 非可靠消息传输模式

  对于CON类型的消息,接收者收到后,会返回ACK确认,一旦发送后,接收者无需去关心发送者是否收到ACK消息,因为如果发送者没有收到ACK确认,会自己重发一次请求。这种处理机制大大提高了处理效率。

深入

4. 协议详解

4.1 Header

  报头各字段定义如下:

1
2
3
4
5
 0               1               2               3          
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

4.1.1 Version(Ver)

  版本号,表示CoAP协议的版本号,2-bit无符号整数。将来可能会修改成其它值,但目前该字段必须置为1(二进制01),若为其它值则报文会直接被忽略而没有任何提示。

4.1.2 Type(T)

  消息类型,表示当前消息所属的种类,2-bit无符号整数。目前共有四种,其中请求方向两种:CONNON,响应方向两种:ACKRST
  由于CoAP协议是基于UDP协议的,而UDP本身并不能提供类似TCP的可靠连接,所以为了确保传输的可靠性,CoAP协议自己实现了一套轻量级的可靠机制,其特点如下:

  • 对于CON消息,采用简单的指数回退算法的stop-and-wait重传机制;
  • 对于CONNON消息,均提供了重复消息检测机制。

  四种消息类型的详细信息如下表所示:

消息类型Type字段值
消息方向中文名称英文全称英文简称十进制二进制
请求消息需确认消息ConfirmableCON0..00 ….
无需确认消息None-confirmableNON1..01 ….
响应消息确认消息AcknowledgementACK2..10 ….
重置消息ResetRST3..11 ….

  对于请求方向的CONNON两种消息,因其用途不同,因此对这两种类型的消息有不同的要求。

4.1.2.1 CON消息

  对于CON类型的消息,它是实现可靠连接的基础,其具体要求列出如下:

  • 必须包含请求/响应(用来触发RST消息的情况除外,这种情况可以为空);
  • 接收者必须返回ACK类型的消息进行确认(无法解析的需要发送拒绝消息);
  • 无法解析的CON消息(包括CON消息体为空、使用了保留的class类型1,6,7、或者消息格式错误),接收方必须发送RST消息明确拒绝并忽略它;

  • ACK消息必须包含与CON消息匹配的Message ID,消息内容可以为空,也可以包含有响应的响应;

  • RST消息必须包含与CON消息匹配的Message ID,并且其消息内容必须为空
  • 未收到ACKRSTCON消息,遵循时间间隔指数递增的方式进行重传,直到收到这两种消息或超过重试次数(由超时时常和重传计数器两项共同决定)为止;
4.1.2.2 NON消息

  对于NON类型的消息,没有可靠性要求,因此协议对其规定相比于CON消息要少的多,具体列出如下:

  • 消息体不能为空,必须包含请求/响应;
  • 接收者不能对其进行确认(ACK);
  • 对于无法解析的NON消息(包括CON消息体为空、使用了保留的class类型1,6,7、或者消息格式错误),接收者需通过RST消息拒绝接受并忽略该消息;
  • NON消息的发送者没有义务判断消息是否送达;
4.1.2.3 其它要求
  • 对于ACKRST消息,不能通过发送ACKRST消息来明确拒绝,只能采用忽略的方式委婉拒绝;
  • 新建的CON消息,其初始超时时间为[ACK_TIMEOUT, ACK_TIMEOUT * ACK_RANDOM_FACTOR]之间的一个随机值,并且其重传计数器值为0;
  • 超时触发后,若重传计数小于MAX_RETRANSMIT,消息立即重传,重传计数器值自增,超时时间设置为当前的两倍;
  • 超时出发后,若重传计数达到MAX_RETRANSMIT或收到RST,则传输失败,重传取消;
  • 某些情况下,CON消息的发送方可能会放弃ACK确认。比如,发送方的应用取消了请求、发送方通过其它途径获知消息已被接收、ICMP错误等等。
4.1.2.4 组合表

  综上,整理出下表所示的请求/响应与上述4种消息的组合使用情况表,如下所示:

请求/响应与消息类型组合情况表

CON NON ACK RST
Request × ×
Response ×
Empty * ×

  表中符号说明:

  • √  可以组合使用;
  • × 不能组合使用;
  • *  仅用于触发RST消息(CoAP ping)。

4.1.3 Token Length(TKL)

  Token长度,表示后面的token字段的长度,4-bit无符号整数。该字段目前取值范围为$[0, 9]$,表示目前支持的最大token长度为8字节,$[9, 15]$区间的值目前为保留值,不应出现在报文中,凡是token字段出现这些值的报文均应被当作错误报文处理

4.1.4 Code

  请求/响应码,用来表示该消息属于请求还是响应,以及属于什么类型的请求/响应,8-bit无符号整数。该字段又包含了classdetail`两个子字段,如下所示:

1
2
3
4
5
 0               
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|class| detail |
+-+-+-+-+-+-+-+-+

  由上可知,class占了3-bit,共$2^3=8$种可选值,detail各占了5-bit,均存在$2^5=32$种可选值。class目前有效的取值为0、2、4、5、7,其余的均为保留值(非法值)。不同的class所对应的detail的有效取值是不同的,我们将所有有效的classdetail的取值整理如下表所示:

class和detail取值组合情况表

class
0
(Method)
2
(Success)
4
(Client Error)
5
(Server Error)
7
(Signaling Codes)
detail0EMPTYBad RequestInternal Server ErrorUnassigned
1GETCreatedUnauthorizedNot ImplementedCSM
2POSTDeletedBad OptionBad GatewayPing
3PUTValidForbiddenService UnavailablePong
4DELETEChangedNot FoundGateway TimeoutRelease
5FETCHContentMethod Not AllowedProxying Not SupportedAbort
6PATCHNot Acceptable
7iPATCH
8Request Entity Incomplete
9Conflict
12Precondition Failed
13Request Entity Too Large
15Unsupported Content-Format
31Continue
4.1.4.1 class

  class的所有取值中只有0表示请求,更准确的说,只有GET(0.01)POST(0.02)PUT(0.03)DELETE(0.04)这几个method用来表示CoAp中的请求数据。而响应码则有三类,分别是2、4、5。
  因此,更准确的说,一个报文是请求还是响应,取决于Code字段的前3-bit的值。

4.1.4.2 detail

  detail的取值并没有固定含义,需要与前面的class搭配,detail可以理解为class的子类,是对某种类型的class的进一步细分。

4.1.5 Message ID

  消息的ID(唯一标识),16-bit无符号整数,网络字节序Message ID用来检测重复消息以及用来匹配CON/NONACK/RST消息对。

  这里需要强调的一点是:Message ID是用来匹配CoAP中的四大类型的消息的,而消息中承载的请求/响应则是通过下面要讲的token来匹配的

4.1.5.1 重复性检测

  由于各种原因,接收者可能会多次向接收者发送相同的CON消息(因为CON需要回应,而NON不需要),Message ID能有效的检测处这些重复的消息。

  协议规定,对于重复的CON消息,每一条接收者都要逐一回应,但对于该消息中表示的请求/响应命令,只能执行一次。但如果消息中的请求是幂等性的或者可以按照幂等性的方式进行处理,这种约束可以放宽,比如:

  • 对于幂等性的请求,为了保证只处理一次,接收者需要维护相应的状态信息,如果维护这些状态的开销远大于多次处理的开销,那么接收者可以对请求命令多次执行;
  • 对于某些非幂等性的请求,如果应用层的语义允许折衷,某些受限服务器可能也会放宽对重复消息的约束要求(情况与上条类似);

  对于重复的NON消息,接收者可根据语义适当放宽上述一般性的原则并静默忽略这些重复消息,同时对于该消息中表示的请求/响应命令,只能执行一次

4.1.5.2 消息配对

  此外,前面已经提到过,对于CON消息,接收者必须做出响应,即使用ACK表示已接收或者RST表示拒收。而对于NON消息则无需进行确认,但如果要拒收也必须要发送RSTMessage ID就是用来匹配这些成对的消息的,使得发送者能够准确的知道接收者是在对哪条消息做出回应。

  对于消息配对,还需要注意以下关键点:

  • Message IDCON/NON消息的发送者生成,随附在CoAP报文报头中;
  • 接收者响应的消息中必须包含Message ID,并且必须保持与要回应的消息的一致;
  • 在消息交换生命周期(EXCHANGE_LIFETIME,是指CON消息发出开始,到接收到响应的这个时间间隔)内,Message ID不可重用;
  • RFC文档并没有规定如何生成这个Message ID,但是为了避免off-path attack,建议保存这个ID的变量用随机值来初始化;
  • Message ID中还必须编码发送者的身份信息,因为接收者同一时间可能会收到来自不同发送者的具有相同ID的信息,如果发送者不把自己的信息编码进去,接收者无法区分发送者。

4.2 Token

  Token是一个单一结构的字段,没有子字段,如下所示:

1
2
3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  上面已经提到过,Token是用来匹配消息中承载的请求~响应对的。它是为了方便客户端区分发送的众多请求的标志,因此也可以称作请求ID。对于Token,协议做了如下明确规定:

  • 每一条消息中都应该包含Token(长度为0的时候视作包含了一个空串);
  • 每一个请求中包含的由客户端生成的Token服务器在响应中都必须携带相同的Token(不能修改);
  • Token的生成应保证在同一对源~目的终端中是唯一的,不同的源-目的终端对(比如不同的源端口号)中则可以使用相同的Token
  • 对于未使用TLS的客户端,应该使用一个非显著可得的、随机的Token以确保响应不会被欺骗;
  • 直接接入常规互联网的客户端应该确保Token至少具有32-bit的随机性;
  • 服务器收到的未被生成的Token应被透明化处理,不对该数值做任何假设;
  • 当且仅当客户端与服务器之间没有其它Token在用或者客户端的请求都是piggybacked response(即刻响应,几乎没有时延)时,Token才可以为空;

4.2.1 请求/响应配对

  请求~响应对的精确匹配原则如下:

  • 响应的源端必须和请求的目的端严格一致;
  • 对于piggybacked responseCON消息中的Message ID必须与其ACK响应中的Message ID严格一致,并且请求/响应中的Token必须严格一致。
  • 对于单独的响应,则只要求响应与其原始请求中的Token一致即可;
  • 若客户端收到的响应并非所预期,则应该直接拒绝。

4.3 Options

  对于CoAP的报文来说,Options这个字段是可选的,可能有也可能没有。这个字段由2个定长的字段和3个变长的字段组成,如下所示:

1
2
3
4
5
6
7
8
9
10
  0   1   2   3   4   5   6   7
+---------------+---------------+
| Option Delta | Option Length | 1 byte
+---------------+---------------+
| Option Delta(extended) | 0-2 bytes
+-------------------------------+
| Option Length(extended) | 0-2 bytes
+-------------------------------+
| Option Value | 0 or more bytes
+-------------------------------+

  其中Option DeltaOption Length为定长字段,分别占用了4-bit,无符号整数。

  Option Delta(extended)Option Length(extended)为变长字段,是前面Option DeltaOption Length的补充,其长度取决于前面Option DeltaOption Length的值,在$[0, 2]$字节范围内选取,无符号整数。

  Option ValueOption的负载(真正内容),变长字段,其长度取决于前面的Option LengthOption Length(extended),其格式类型则取决于前面的Option DeltaOption Delta(extended)

4.3.1 Option Delta

  Option DeltaOption中相当重要的字段,它决定了Option的负载(Value)该如何被解析以及数据的格式类型,同时还编码了一些额外的属性信息,它还决定了该Option在单个消息中是否能重复出现。

4.3.1.1 Option Number

  需要注意的是,Option Delta这个字段的值单独来看是没有意义的,需要与Option Delta(extended)字段(如果存在的话)的值结合,同时需要与之前出现过的所有OptionOption DeltaOption Delta(extended)的值做累和形成Option Number,用这个累和值用来判断Option Value表示的是什么数据。

  因此Option Number是为了方便确定当前Option所属的类型而虚拟出来的概念,并非实际的字段。

  假定要计算当前第$n$个Option时的Option Numbers值,则有:
$$
ONs = \sum_{i=1}^{n}ON_i = \sum_{i=1}^{n}(OD_i + OD^{ext}_{i})
\tag{4 - 3 - 2}
$$

  式中:
   $ONs$—— 表示当前的Option Numbers的值;
   $ON_i$—— 表示第$i$个OptionOption Number的值;
   $OD_i$—— 表示第$i$个OptionOption Delta字段的值;
   $OD^{ext}_i$—— 表示第$i$个OptionOption Delta(extended)字段的值;

  文字比较抽象,下面辅以图文说明:

图4-1  Wireshark解码出的Options示意图

  对应的Option Number的详细计算步骤如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Option Number(初始值)= 0
1# Option:
Option Delta= 11
Option Delta(extended)= 0(无)
Option Number= Option Delta + Option Delta(extended) + Option Number
= 11 + 0 + 0
= 11(Uri-Path)
2# Option:
Option Delta = 1
Option Delta(extended)= 0(无)
Option Number= 1 + 0 + 11(Uri-Path)
= 12(Content-Format)

3# Option:
Option Delta = 13
Option Delta(extended)= 35
Option Number= 13 + 35 + 12(Content-Format)
= 60(Size1)

  Option Number的另一个用处是用来编码其它附加信息,准确点说是三类附加信息,其对应编码的结构如下所示:

1
2
3
4
5
  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| | NoCacheKey| U | C |
+---+---+---+---+---+---+---+---+
Last Byte

  第一类信息:当前Option是属于紧急(Critical)还是可选(Elective)的,用Option Number对应二进制最后一个字节的最后一个bit编码(上图中字母C位置),若最后一位为1则是Critical,若为0则是Elective。即当Option Number为奇数时为Critical,偶数时则是Elective

  第二类信息:当前Option是否可以安全的进行转发。用Option Number对应二进制最后一个字节的倒数第二个bit编码(上图中字母U位置),若为1则是Unsafe,若为0则是Safe

  第三类信息:当前Option是否为Cache-Key。用Option Number对应二进制最后一个字节的倒数第3~5(共3)个bit编码,当且仅当OptionSafe(即第6个bit为0)且这3个bit均置为1的时候,表示NoCacheKey,除此之外均为Cache-Key

  上述编码信息提取的代码如下:

1
2
3
bool is_critical = option_number & 1
bool is_safe = option_number & 2
bool is_cache_key = (option_number & 0x1E) == 0x1C

4.3.2 Option Delta(extended)

  由于Option Delta只占用了4-bit,其取值范围有限($[0, 16]$),需要进行扩展,因此就有了这个字段。该字段的长度取决于Option Delta的值,如下表所示:

Option Delta(extended)Option Delta值计算方式
长度(字节)取值范围
Option Delta字段值0~1200Option Delta
1310~255Option Delta(extended) + 13
1420~65535Option Delta(extended) + 269
15未定义未定义保留值,暂不使用。
此时Delta Option所属字节必须为0xFF。

4.3.3 Option Length

  这个字段作用单一,仅仅用来表示OptionOption Value的实际长度,4-bit,无符号整数。与Option Delta类似,它也需要结合Option Length(extended)字段的值才能计算出最终Option Value的长度。

4.3.4 Option Length(extended)

  同样的,由于Option Length只占用了4-bit,其取值范围也是$[0, 16]$个字节,因此需要进行扩展,以便Option能承载长的数据。该字段的长度也取决于Option Length的值,如下表所示:

Option Length(extended)Option Length值计算方式
长度(字节)取值范围
Option Length字段值0~1200Option Length
1310~255Option Length(extended) + 13
1420~65535Option Length(extended) + 269
15未定义未定义保留值,暂不使用。
此时Delta Length所属字节必须为0xFF。

4.3.5 Optioin Value

  这个字段是Option的负载部分,它具体表示什么含义以及是什么数据类型需要根据累和求出的Option Numer来判断。RFC文档中目前定义了15种含义,4种数据格式。结合前面提到的Option三大属性有如下组合表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+-----+---+---+---+---+----------------+--------+--------+------------------+
| No. | C | U | N | R | Name | Format | Length | Default |
+-----+---+---+---+---+----------------+--------+--------+------------------+
| 1 | √ | | | √ | If-Match | opaque | 0-8 | (none) |
| 3 | √ | √ | - | | Uri-Host | string | 1-255 | (dst IP(request) |
| 4 | | | | √ | ETag | opaque | 1-8 | (none) |
| 5 | √ | | | | If-None-Match | empty | 0 | (none) |
| 7 | √ | √ | - | | Uri-Port | uint | 0-2 | (dst UDP port) |
| 8 | | | | √ | Location-Path | string | 0-255 | (none) |
| 11 | √ | √ | - | √ | Uri-Path | string | 0-255 | (none) |
| 12 | | | | | Content-Format | uint | 0-2 | (none) |
| 14 | | √ | - | | Max-Age | uint | 0-4 | 60 |
| 15 | √ | √ | - | √ | Uri-Query | string | 0-255 | (none) |
| 17 | √ | | | | Accept | uint | 0-2 | (none) |
| 20 | | | | √ | Location-Query | string | 0-255 | (none) |
| 35 | √ | √ | - | | Proxy-Uri | string | 1-1034 | (none) |
| 39 | √ | √ | - | | Proxy-Scheme | string | 1-255 | (none) |
| 60 | | | √ | | Size1 | uint | 0-4 | (none) |
+-----+---+---+---+---+----------------+--------+--------+------------------+

  表中:

   No.——Option Number的累和值;
   C——Option是否Critical
   U——Option是否Unsafe
   N——Option是否NoCacheKey
   R——Option在同条消息种是否可重复出现;
   Name——Option Value的含义;
   Format——Option Value的数据类型;
   Length——Option Value的有效长度范围;
   Default——Option Value的缺省值;

  说明:
   1. 因为NoCacheKey只有在Safe的情况下才有意义,因此所有Unsafe的这部分用短横线填充,表示无意义;
   2. 上述表格中仅定义了部分Option Number,对于未定义的部分,如何处理需要根据Option Delta类型来判断,见下文。

  对于上述表格中未出现的其它Option Number,如何处理需要根据OptionCritical还是Elective类型,处理准则如下:

  • 未知的Elective类型的Option,接收方必须静默忽略,不做解析;
  • CON请求消息中未知的CriticalOption,接收方必须返回4.02(Bad Option)响应,且该响应中必须包含无法识别的Option(s)描述信息;
  • CON响应消息或ACKpiggybacked消息中未知的CriticalOption,必须被拒绝;
  • NON消息中未知的CriticalOption,必须被拒绝;
  • 上述所有准则仅适用于非代理端,至于代理端的,以后有时间再补充。

4.4 Marker && Payload

  至此,CoAP报文只剩下最后一个部分:负载。这个部分是可选的,可能出现也可能不出现。为了准确的知道负载的起始位置,就在负载前设置一个字节的Marker标记。CoAP负载的长度为Marker后的第一个字节开始,到整个UDP报文负载的结尾,结构如下所示:

1
2
3
4
5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| UDP Header | UDP Payload |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| UDP Header | CoAP Header + Token + Options |1 1 1 1 1 1 1 1| Payload (if any) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

4.4.1 Marker

  定长字段,只占1个字节,内容也是固定值0xFFMarker字段当且仅当负载长度不为0才会出现,否则消息应该被当作错误消息处理。

4.4.2 Payload

  RFC文档中只是对CoAP报文中消息大小的上限做了规定,并没有定义其下限。如果消息大小比IP报文大小大,就可能会生成一些无法预知的IP分片。

  由上面的描述可知,CoAP报文的负载只是通过定界符Marker来标记起始位置,通过UDP报文负载的末尾来标记其结束位置。报文中并没有能反映这个具体长度的字段。如果CoAP的报文被IP分片或分布在多个UDP报文的负载中,那么将导致CoAP报文无法正常解析。

  因此,一个被合理封装的CoAP消息应该能轻松的放进单个IP报文中以避免被分片,同时也能容纳在一个UDP报文中。

  如果到某个目的地址的传输路径上的MTU值未知,RFC文档推荐将这个值假定为1280。如果无法获知UDP的头部长度,RFC推荐将CoAP报文的长度上限设置为1152字节,CoAP的负载大小上限设置为1024字节。

  RFC同时指出,CoAP协议对于报文大小的选择在IPv6上以及大部分IPV4网络中都能工作的很好,但在IPv4网络中很难完全保证没有IP分片,因为某些非常规IPv4网络可能会将其MTU限制的非常非常低,比如68字节。这种情况只给UDP的负载留下了40个字节的空间。

  CoAP报文的大小对于具体实现来说至关重要,大多数实现都会使用缓冲区来缓存报文。当某些受限设备可用资源过于捉襟见肘而无法分配上述大小的缓冲区时,可能会遵循如下策略原则放弃使用DTLS

  • 当报文被存入过小的缓冲区时,可根据自己需求决定是否丢弃剩下尚未接收的报文转而处理已接收部分,这样能确保报头以及Option能被解析,此时服务器应该中断正在处理的请求并返回4.13响应。

4.5 后记

  RFC阅读起来耗时耗力,以上内容虽然没有囊括协议的所有细节,但已经足以对CoAP报文有较为全面的理解并编写解析代码,就暂时写到这里,后续有时间再来补充剩余部分。

4.6 参考文献

  1. CoAP RFC
  2. 物联网协议之CoAP协议开发学习笔记之协议详解
  3. 物联网协议之CoAP协议开发学习笔记
  4. CoAP协议简介