
RIOT unicoap PDU 解析与序列化实战RFC 7252 报文编解码完全指南【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOTunicoap是 RIOT 的统合 CoAPConstrained Application Protocol套件通过驱动模块支持 UDP、DTLS 等多种传输。本文聚焦其中最关键的一环PDU协议数据单元的解析与序列化即如何把网络上收到的 CoAP 字节流还原成unicoap_message_t消息对象以及如何把消息对象重新编码成可发送的字节流。读完本文你将掌握unicoap_pdu_parse_*系列解析器、unicoap_pdu_build_*/unicoap_pdu_buildv_*系列序列化器的完整用法并理解其驱动解析头部、公共实现解析选项的两步式架构设计。说明unicoap仍在开发中见 introduction.md本文以当前仓库源码为准介绍 PDU 编解码相关已实现的能力。背景为什么 PDU 编解码需要单独讨论CoAP 与 HTTP 不同它在设计上针对低带宽、小内存的物联网节点做了大量精简。而 CoAP 报文PDU的格式随传输层不同而变化CoAP over UDP / DTLS 使用RFC 7252定义的 PDU 格式4 字节定长头 可选 TokenCoAP over TCP / TLS 使用 RFC 8323 定义的另一种 PDU 头CoAP over GATTBLE草案则使用完全自定义的紧凑格式。因此unicoap把 PDU 编解码拆成两层驱动层负责与传输相关的头部公共unicoap实现负责所有 CoAP 组合共享的 Options 与 Payload 部分。这也解释了 API 命名中的rfc7252后缀——它标识的是具体传输组合对应的 PDU 格式。当前仓库中已实现的 RFC 7252 PDU 编解码位于 sys/net/application_layer/unicoap/drivers/rfc7252/common/pdu/pdu.c。解析 PDUParsing从字节流到消息对象一步分配使用unicoap_parser_result_t如果消息是从网络上收到的、需要由应用自行解析推荐使用net_unicoap_message分组中的解析器配合unicoap_parser_result_t结果结构体。该结构体在 sys/include/net/unicoap/message.h 中定义一次性包含三样东西typedef struct { unicoap_message_t message; /* 解析出的消息 */ unicoap_options_t options; /* 解析出的选项 */ unicoap_message_properties_t properties; /* 消息属性Token、RFC 7252 的 ID 与类型等 */ } unicoap_parser_result_t;它的价值在于免去了手动分配消息结构、选项结构并手工把它们关联起来的样板代码。其中unicoap_pdu_parse_rfc7252_result是一个 static inline 包装函数内部会自动执行parsed-message.options parsed-options;后再调用真正的解析器你只需零初始化整个结果结构体即可。解析 RFC 7252 PDU 的完整示例下面是官方文档给出的最小可运行片段同样的代码也出现在 examples/networking/coap/unicoap_message/main.c 的_example_parse_pdu中/* Parse an RFC 7252 PDU */ uint8_t pdu[] { 0x40, 0x02, 0xfe, 0xb1, 0xb9, 0x61, 0x63, 0x74, 0x75, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x04, 0x6c, 0x65, 0x64, 0x73, 0x11, 0x32, 0x37, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3d, 0x67, 0x21, 0x32, 0xff, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x6f, 0x6e }; unicoap_parser_result_t parsed { 0 }; /* Zero-initialize everything */ /* Parse message */ ssize_t res unicoap_pdu_parse_rfc7252_result(pdu, sizeof(pdu), parsed); /* Handle errors */ if (res 0) { /* eat error */ } /* parsed.message contains the parsed message */注意unicoap_pdu_parse_rfc7252_result的原型为(uint8_t* pdu, size_t size, unicoap_parser_result_t* parsed)返回0表示成功返回负 errno 表示失败。示例中把const数组强转为uint8_t*是因为解析器可能暴露指向 PDU 缓冲区的指针用于选项查找是否可变取决于你后续是否要修改选项详见 message.h 中该函数的注释。解析完成后传输相关的头部信息通过parsed.properties访问printf(CoAP message has token%i bytes\n, parsed.properties.token_length); printf(CoAP over UDP/DTLS has id%i type%s\n, parsed.properties.rfc7252.id, unicoap_string_from_rfc7252_type(parsed.properties.rfc7252.type));其中unicoap_string_from_rfc7252_type会把 RFC 7252 消息类型CON/NON/ACK/RST转成可打印字符串实现在 pdu.c 中。解析器的错误返回RFC 7252 解析器可能返回以下负错误码错误码含义触发场景-EBADMSG报文格式错误PDU 长度小于 4 字节头部、CoAP 版本不为 1、空消息Code0.00却带有多余字节、Token 长度非法或超出缓冲区-EBADOPT选项格式错误选项 delta/length 编码非法由公共选项解析返回-ENOBUFS缓冲区不足选项存储缓冲区过小源码级原理两级解析与头部校验unicoap_pdu_parse_rfc7252_result→unicoap_pdu_parse_rfc7252的解析过程分两步驱动解析头部unicoap_pdu_parse_rfc7252在 pdu.c 中实现先做一系列校验再读取头部字段公共实现解析 Options 与 Payload头部解析完成后调用unicoap_pdu_parse_options_and_payload(cursor, end, message)实现在 options.c这部分被所有 CoAP 组合共享。RFC 7252 的 4 字节定长头部在源码中以打包结构体表示见 pdu.c 中的unicoap_header_rfc7252_t0 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) ... --------------------------------头部各字段的含义与取值Ver2 bitCoAP 版本号当前固定为1UNICOAP_COAP_VERSION_1解析器会校验非法版本返回-EBADMSGT2 bitRFC 7252 消息类型取值为UNICOAP_TYPE_CON0、UNICOAP_TYPE_NON1、UNICOAP_TYPE_ACK2、UNICOAP_TYPE_RST3TKL4 bitToken 长度08 字节。长度 915 被 RFC 7252 Section 3 保留禁止发送收到必须按格式错误处理——源码中通过CONFIG_UNICOAP_EXTERNAL_TOKEN_LENGTH_MAX检查并返回-EBADMSGCode1 字节CoAP 代码如0.02表示 POST 请求、2.05表示内容响应Message ID2 字节网络字节序用于匹配 CON/ACK 消息源码用ntohs/htons做字节序转换。解析 Token 后解析器把cursor指向 Options 起始位置交给公共的unicoap_pdu_parse_options_and_payload。后者逐个读取选项支持 delta 扩展编码并在遇到0xFF载荷分隔符时返回-EPAYLD内部信号以切分出 Payload 区见 options.c 的_read_option_in_range调用逻辑。实战解码文档示例 PDU文档中的示例 PDU 实际上就是一个完整的 CoAP 请求逐字节解码如下字节十六进制含义0x40Ver1、TCON、TKL00x02Code0.02POST 请求0xfe 0xb1Message ID 0xFEB1 652010xb9 9 字节Option 11Uri-Pathactuators0x04 4 字节Option 11Uri-Pathleds0x11 0x32Option 12Content-Format50application/json0x27 7 字节Option 15Uri-Querycolorg0x21 0x32Option 17Accept50application/json0xffPayload 分隔符modeonPayload即向/actuators/leds?colorg发送一个POST请求Content-Format 与 Accept 均为 JSON载荷为modeon。这与 main.c 中_example_parse_pdu的注释完全吻合。解析后的消息可以通过unicoap_message_code_is_request、unicoap_message_code_is_response判断类别通过message-method、message-status、message-signal访问类型化视图并用unicoap_options_*系列访问器读取选项详见 message-example.md。序列化Serializing从消息对象到字节流序列化前需要先决定输出形式向量化数据vectored data使用unicoap_pdu_buildv_*系列函数适用于使用sendv类向量发送接口的传输驱动。向量化数据用iolist_tRIOT 的 iolist 机制表示可以避免把头部、选项、载荷拼接进同一块连续内存连续数据contiguous data使用unicoap_pdu_build_*系列函数直接写入一整块连续缓冲区。两者都要根据目标传输选择对应的 PDU 格式函数如 RFC 7252 对应unicoap_pdu_build_rfc7252/unicoap_pdu_buildv_rfc7252。构建连续 PDUunicoap_pdu_build_rfc7252/* Build an RFC 7252 PDU */ uint8_t pdu[MY_CAPACITY] { 0 }; ssize_t size unicoap_pdu_build_rfc7252(pdu, sizeof(pdu), message, properties); /* Handle errors */ if (size 0) { /* eat error */ }成功时返回 PDU 总字节数失败时返回负错误码最常见的是-ENOBUFS缓冲区太小。函数实现见 message.h它是一个 static inline先调用unicoap_pdu_build_header_rfc7252写入头部再用剩余容量调用公共的unicoap_pdu_build_options_and_payload实现在 utils.c写入选项、0xFF分隔符与载荷最后把两者字节数相加。序列化前需要准备好unicoap_message_t message与unicoap_message_properties_t properties。消息对象可用初始化函数或初始化器创建例如unicoap_request_init_string_with_options(message, UNICOAP_METHOD_POST, Hello, World!, options)属性结构体则包含 Token 与传输相关的头部字段unicoap_message_properties_t properties { .token NULL, .token_length 0, .rfc7252 { .id 0xABCD, .type UNICOAP_TYPE_NON } };在极简场景如单请求在途的受限节点下可以不使用 Token。properties.rfc7252.id会作为 Message ID 写入头部properties.rfc7252.type决定消息类型。若选择向量化发送则调用unicoap_pdu_buildv_rfc7252它会把报文组织为最多UNICOAP_PDU_IOLIST_COUNT值为 4个 iolist 条目分别承载头部、选项、载荷分隔符与载荷可直接交给驱动的向量发送接口。RFC 7252 驱动UDP 与 DTLS都支持向量化发送因此其内部发送路径见 messaging.c使用的是带v后缀的版本。纯编解码模块unicoap_driver_rfc7252_pdu如果只想实验 PDU 编解码、编写不需要网络 I/O 的单元测试可以只引入 PDU 模块而不挂任何网络后端。在应用Makefile中添加见 RFC 7252 Framing 模块文档USEMODULE unicoap_driver_rfc7252_pdu这样即可在无 UDP/DTLS 驱动的情况下使用unicoap_pdu_parse_rfc7252/unicoap_pdu_build_rfc7252等 API。若要启用完整网络功能则按 introduction.md 的 Quick Start 添加USEMODULE unicoap USEMODULE unicoap_driver_udp实现两步式架构与驱动扩展点官方文档明确指出unicoap的解析与序列化都是分两步完成的消息头部直到 Options 之前由驱动解析/序列化——因为头部格式随传输RFC 7252、RFC 8323、GATT 草案而变化Options 由公共unicoap实现解析/序列化——因为选项编码规则在所有 CoAP 组合中是共享的。这一设计在代码中的对应关系非常清晰pdu.c 负责 RFC 7252 头部编解码解析末尾调用unicoap_pdu_parse_options_and_payload定义于 options.c序列化则依赖unicoap_pdu_build_options_and_payload定义于 utils.c公共函数声明与unicoap_parser_result_t、unicoap_pdu_build_rfc7252、unicoap_pdu_buildv_rfc7252等 API 集中在 message.h 的net_unicoap_pdu分组中在 message.h 中可以看到/* MARK: unicoap_driver_extension_point */标记它标注了新增传输驱动/PDU 格式时需要扩展的代码区域。这种分层带来的好处是新增一种 CoAP 组合时只需实现头部编解码 传输而选项查找表、选项迭代器、Content-Format 等公共逻辑完全复用。驱动模块之间的依赖关系如 UDP 与 DTLS 驱动共享 RFC 7252 公共 messaging 模块定义在 sys/net/application_layer/unicoap/Makefile.dep 中。更完整的架构说明exchange/messaging/transport 三层模型与 CoAP 组合图谱参见 internals.md。常见错误与调试建议-ENOBUFS序列化时 PDU 缓冲区或选项缓冲区太小。可以先估算头部RFC 7252 为 4 字节 Token 长度与选项编码后的总长度再分配缓冲区也可以在失败后依据返回的负值区分是哪个缓冲区不足。-EBADMSG解析时收到的 PDU 不合法长度不足、版本错误、Token 长度 915、空消息带载荷等。-EBADOPT选项编码损坏说明对端实现异常或数据被破坏。缓冲区生命周期解析器不会复制 PDU 数据message.payload与选项first类访问器返回的是指向原始 PDU 缓冲区的视图非空终止使用时必须显式传入长度如printf(%.*s\n, (int)res, query);。解析后 PDU 缓冲区被释放则这些指针失效。调试日志启用CONFIG_UNICOAP_DEBUG_LOGGING后RFC 7252 PDU 编解码模块会输出带.pdu.rfc7252前缀的调试信息见 pdu.c 中的PDU_7252_DEBUG宏可用于定位解析失败的具体原因。参考示例与延伸阅读完整的解析 序列化演示程序examples/networking/coap/unicoap_message/main.c包含 PDU 构造、消息创建、选项读写与序列化的全流程选项的读取、添加与迭代 API 详解message-example.md消息容器的初始化与类型化视图message.md如何用这些 API 编写一个 CoAP 服务器server-tutorial.mdunicoap的分层设计与驱动机制internals.md。提示unicoap是 RIOT 中正在演进的统合 CoAP 框架官方文档明确其目标是逐步取代net_gcoap、net_nanocoap与net_nanosock。当前功能仍在完善中使用前请以仓库中实际实现的 API 与文档标注为准。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考