OpenHarmony--Coap协议封包源码分析


前言

  之前在服务发布总体流程中说过OpenHarmony是服务发布功能是基于COAP轻量级协议的。在本篇中,将简单介绍COAP协议,并且分析OpenHarmony中对Coap协议的封包(拆包就是反操作)源码。

Coap协议[1][2]

  CoAP是受限制的应用协议(Constrained Application Protocol)的代名词。在当前由PC机组成的世界,信息交换是通过TCP和应用层协议HTTP实现的。但是对于小型设备而言,实现TCP和HTTP协议开销太大。为了让小设备可以接入互联网,CoAP协议被设计出来。 CoAP是一种应用层协议,它运行于 UDP协议之上而不是像HTTP那样运行于TCP之上。CoAP协议非常小巧,最小的数据包仅为4字节。

coap消息类型

  CoAP采用与HTTP协议相同的请求响应工作模式。CoAP协议共有4中不同的消息类型。

  CON——需要被确认的请求,如果CON请求被发送,那么对方必须做出响应。   NON——不需要被确认的请求,如果NON请求被发送,那么对方不必做出回应。   ACK——应答消息,接受到CON消息的响应。   RST——复位消息,当接收者接受到的消息包含一个错误,接受者解析消息或者不再关心发送者发送的内容,那么复位消息将会被发送。

coap消息结构

Ver : 版本号;            T : 消息类型               Code: Code在CoAP请求报文和响应报文中具有不同的表现形式              Message ID:报文编号,用于重复消息检测、匹配消息类型等

Token : 随机数构成,使用Token之后可以有效防止响应欺骗 (开始一直不懂Token作用,后面看OpenHarmony Message ID是顺序分配的,为了安全)

Option:可选项项,0个或者多个options。主要用于描述请求或者响应对应的各个属性。

coap option 结构

  Option Delta:Option在message中的实例必须按照编号大小顺序存放

  Option length:取值0-12表示option占用的字节数;取值13时表示需要占用扩展中的一个字节,且表示option length减去269的部分;取值15时,保留

  Option value:表示Option具体内容项目

OpenHarmony Coap 封包过程

基本数据结构与定义

 1 //一个消息报文最多option数量
 2 #define COAP_MAX_OPTION 16
 3 
 4 //传输使用协议 默认值UDP
 5 enum COAP_ProtocolTypeEnum {
 6     COAP_UDP = 0,
 7     COAP_TCP
 8 };
 9 
10 //方法类型
11 enum COAP_MethodTypeEnum {
12     COAP_METHOD_GET = 1,
13     COAP_METHOD_POST = 2,
14     COAP_METHOD_PUT = 3,
15     COAP_METHOD_DELETE = 4
16 };
17 
18 enum COAP_MsgTypeEnum {
19     COAP_TYPE_CON = 0, //CON 需要确认的请求
20     COAP_TYPE_NONCON = 1, //NON 不需要确认的请求
21     COAP_TYPE_ACK = 2,  //应答消息
22     COAP_TYPE_RESET = 3 //复位消息
23 };
24 
25 typedef struct {
26     unsigned int ver : 2;   //版本
27     unsigned int type : 2;  //msg类型 (CON,NON,ACK,RST)
28     unsigned int tokenLen : 4;  //token 长度
29     unsigned int code : 8;  //列代码  3bit --分类信息(请求,成功响应,客户端错误响应,服务端错误响应)  5bit--详细信息
30     union {
31         unsigned short msgLen;  
32         unsigned short msgId;
33     } varSection;
34 } COAP_Header;
35 
36 typedef struct {
37     unsigned short num;   //编号
38     const unsigned char *optionBuf;  //option 参数和数据
39     unsigned int len; // optionbuf长度
40 } COAP_Option;  //用于描述请求或响应对应的各个属性
41 
42 
43 typedef struct {
44     enum COAP_ProtocolTypeEnum protocol;  //协议
45     unsigned int len;
46     COAP_Header header;   //
47     COAP_Buffer token;  //请求ID ,关联响应与请求
48     unsigned char optionsNum;  // 消息报文中option的数量
49     COAP_Option options[COAP_MAX_OPTION]; // 存储option
50     COAP_Buffer payload;  //实际携带数据内容, 若有 加上标志OxFF
51 } COAP_Packet;   //coap 数据包结构

Coap 封包

 组包:创建头部和数据包主体 

 1 /*
 2     函数功能 : 组包
 3     函数参数: 
 4             1.pkt : COAP包
 5             2.param : 包参数
 6             3.token : token
 7             4.payload: 数据
 8             5.buf : pkt对应的读写缓冲区
 9     详细:
10             1.创建头
11             2.创建主体
12     函数返回: 成功返回0,否则返回非零
13 */
14 int COAP_SoftBusEncode(COAP_Packet *pkt, const COAP_PacketParam *param, const COAP_Buffer *token,
15     const COAP_Buffer *payload, COAP_ReadWriteBuffer *buf)
16 {
17     ......
18     
19     ret = COAP_CreateHeader(pkt, param, buf); // 创建头
20     
21     ......
22     ret = COAP_CreateBody(pkt, param, NULL, payload, buf);//数据负载
23     .......
24 }

构造消息头

 1 /*
 2     函数功能: 通过ptkParam 构造消息头
 3     函数参数: 
 4             1. pkt: COAP 包
 5             2. pktParam: 包参数
 6             3. buf:pkt对应读写缓冲区
 7     详细:  
 8             1.先检查包参数中的协议,消息类型
 9             2.用pktParam中的各种属性设置COAP数据包中Header属性
10             3.如果协议为UDP,那么将数据包中Header属性中信息保存在读写缓冲区中
11     函数返回值:成功返回0, 否则返回非零
12 */
13 static int COAP_CreateHeader(COAP_Packet *pkt, const COAP_PacketParam *pktParam, COAP_ReadWriteBuffer *buf)

pkt中的各个成员的创建过程:

1. 添加Token

 1 /*
 2     函数功能: 添加Token
 3     函数参数:
 4             1.pkt : COAP包
 5             2.token : 待添加的token数据
 6             3.buf: pkt对应的读写缓冲区
 7     详细:  
 8             1.检查各项属性是否合法
 9             2.更新pkt相关的token属性值
10             3.将数据拷贝到缓冲区
11     函数返回值: 成功返回0,其他返回非零
12 */
13 static int COAP_AddToken(COAP_Packet *pkt, const COAP_Buffer *token, COAP_ReadWriteBuffer *buf)
14 {
15     ......
16             //将token数据拷贝到pkt对应的缓冲区
17         if (memcpy_s(&buf->readWriteBuf[buf->len], pkt->header.tokenLen, token->buffer, token->len) != EOK) {
18             return DISCOVERY_ERR_INVALID_ARGUMENT;
19         }
20      .......          
21 }

2.添加option

  先判断是否还可以添加option(option num <=16),如果可以添加,那么将新的option添加到最后。

 1 /*
 2     函数功能:主要就是判断是否可以添加一个新的option
 3     函数参数:
 4             1.pkt : coap数据包
 5             2.option : 要检查的option
 6             3.buf : pkt对应的缓冲区
 7     详细:
 8             1.首先检查各属性是否合法
 9             2.获取当前runningDelta(pkt最后一个option的num)
10             3.获取option的长度
11     函数返回: 没有越界返回0, 否则返回非零
12 */
13 static int COAP_CheckOption(const COAP_Packet *pkt, const COAP_Option *option, const COAP_ReadWriteBuffer *buf)
 1 /*
 2     函数功能: 添加option
 3     函数参数: 
 4             1. pkt : coap包
 5             2. option: 待添加的option
 6             3.buf:pkt对应的读写缓冲区
 7     详细:
 8             1.检查是否还可以添加一个新option
 9             2.获取待添加的 option delta, option length 字段中的值
10             3.添加option
11             4.更新pkt中关于option的属性值
12     函数返回值: 成功返回0, 否则返回非零
13 */
14 static int COAP_AddOption(COAP_Packet *pkt, const COAP_Option *option, COAP_ReadWriteBuffer *buf)
15 {
16     ......
17     
18     if (COAP_CheckOption(pkt, option, buf) != DISCOVERY_ERR_SUCCESS) {  //检查是否还可添加
19         return DISCOVERY_ERR_INVALID_ARGUMENT;
20     }
21     
22     ......
23       prevOptionNum = pkt->options[pkt->optionsNum - 1].num; // 当前最后一个option的num
24     optionDelta = option->num - prevOptionNum;  // 获取待添加option的 delta值
25     COAP_GetOptionParam(optionDelta, &delta); // 获取待添加option的option delta字段中的值  
26     COAP_GetOptionParam(option->len, &len);  // 获取待添加option的option length字段的值
27     buf->readWriteBuf[buf->len++] = (char)(((delta << COAP_SHIFT_BIT4) | len) & 0xFF); // 添加option 的 option delta option length 
28     //如果option delta值为13/14,添加 option delta(extended)
29     if (delta == COAP_EXTEND_DELTA_VALUE_UINT8) {  //新option delta字段的值 == 13 
30         buf->readWriteBuf[buf->len++] = (char)(optionDelta - COAP_LOW_DELTA_NUM); //添加 option delta(extended)字段中的值  
31     } else if (delta == COAP_EXTEND_DELTA_VALUE_UINT16) {  //新option delta字段的值 == 14
32         //添加 option delta(extended)字段中的值 (需占两个字节)
33         buf->readWriteBuf[buf->len++] = (char)((optionDelta - (COAP_LOW_DELTA_NUM + COAP_MID_DELTA_NUM))
34                                                 >> COAP_SHIFT_BIT8);
35         buf->readWriteBuf[buf->len++] = (char)((optionDelta - (COAP_LOW_DELTA_NUM + COAP_MID_DELTA_NUM)) & 0xFF);
36     }
37     //如果option length值为13/14,添加 option length(extended)
38     if (len == COAP_EXTEND_DELTA_VALUE_UINT8) {
39         buf->readWriteBuf[buf->len++] = (char)(option->len - COAP_LOW_DELTA_NUM);
40     } else if (len == COAP_EXTEND_DELTA_VALUE_UINT16) {
41         buf->readWriteBuf[buf->len++] = (char)((option->len - (COAP_LOW_DELTA_NUM + COAP_MID_DELTA_NUM))
42                                                 >> COAP_SHIFT_BIT8);
43         buf->readWriteBuf[buf->len++] = (char)((option->len - (COAP_LOW_DELTA_NUM + COAP_MID_DELTA_NUM)) & 0xFF);
44     }
45     //如果option具有负载数据,向buf添加其数据
46     if (option->len != 0) {
47         if (memcpy_s(&buf->readWriteBuf[buf->len], buf->size - buf->len, option->optionBuf, option->len) != EOK) {
48             return DISCOVERY_ERR_OPT_INVALID_BIG;
49         }
50     }
51     //更新pkt中关于option的属性值
52     pkt->options[pkt->optionsNum].optionBuf = (const unsigned char *)&buf->readWriteBuf[buf->len];
53     pkt->options[pkt->optionsNum].num = option->num;
54     pkt->options[pkt->optionsNum].len = option->len;
55     //更新添加之后pkt,buf以存储长度
56     buf->len += option->len;
57     pkt->len = buf->len;
58     //option数量+1
59     pkt->optionsNum++;
60     return DISCOVERY_ERR_SUCCESS;
61 }

coap Message ID分配(因为msg id是顺序增加的,所以这是非常好猜测的,所以需要Token进一步保证安全)

 1 //初始化g_msgId,用作为COAP报文中Message ID
 2 void COAP_SoftBusInitMsgId(void)
 3 {
 4     g_msgId = (unsigned short)(RAND_DIVISOR);
 5 }
 6 //g_msgId+1(循环使用),作为下一个包的Message ID 
 7 unsigned short COAP_SoftBusMsgId(void)
 8 {
 9     if (++g_msgId == 0) {
10         g_msgId++;
11     }
12     return g_msgId;
13 }

添加数据负载

 1 /*
 2     函数功能:coap数据包添加数据
 3     函数参数:
 4             1.pkt: COAP报文结构
 5             2.payload: 数据缓冲区
 6             3.buf: pkt对应读写缓冲区
 7     详细:
 8             1. 检查各种参数
 9             2. 用COAP数据缓冲区的长度设置COAP数据包中payload属性中的len
10             3. 如果是携带数据,那么就在加上payload标识符0XFF(分隔符),pkt中payload.buffer指向实际存放实际的地址
11             4. 将数据写入到读写缓冲区,buf->len 加上数据长度
12     函数返回值:成功返回0, 其他返回非零
13 */
14 static int COAP_AddData(COAP_Packet *pkt, const COAP_Buffer *payload, COAP_ReadWriteBuffer *buf)

[1] : 【IoT美学】如何快速了解CoAP协议?一篇就够了-云社区-华为云 (huaweicloud.com)

[2]:  RFC 7252: The Constrained Application Protocol (CoAP) (rfc-editor.org)