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)