基于CAPL的DDS子消息解析- Data
1往期回顾
通过《DDS—RTPS一致性测试案例分析》一文,我们了解到 Data 子消息在 Data Distribution Service(DDS) 通信中扮演着至关重要的角色。它不仅负责 DDS 实体的 Simple Participant Discovery Protocol (SPDP) 发现流程,还参与了 Simple Endpoint Discovery Protocol (SEDP) 中的 Topic 和 QoS 确认流程。此外,Data 子消息还承担着发布 Topic 信息、声明存活性以及通知对端自身下线等功能,堪称 DDS 协议中“最忙碌”的子消息。为了更深入地理解 Data 子消息的工作原理,并能够通过 CAPL 仿真和解析该子消息,我们需要揭开其神秘的面纱,详细分析其结构和功能。这不仅有助于我们更好地掌握 DDS 协议的通信机制,还能为 CAPL 脚本开发提供清晰的思路和实用的方法。接下来,我们将从 Data 子消息的结构、字段作用以及实际应用场景入手,逐步解析其核心功能,并通过 CAPL 仿真实例展示如何解析和生成 Data 子消息,为开发高效的 DDS 通信脚本奠定基础。
2 Data详解
Data子消息是由The Real-time Publish-Subscribe Protocol(RTPS) DDS Interoperability Wire Protocol Specification定义的。RTPS 是 DDS 协议的核心实现,负责定义数据分发的具体通信机制。根据 RTPS 协议,Data 子消息的报文结构如下所示:
图1 Data定义
2.1 DATA
表示SubmessageId,为固定值,是0x15。RTPS中定义的消息类型为SubmessageFlag,占1字节,因此在CAPL中可以用byte类型表示,如“byte submessageId;”。
2.2 Flags
消息类型为SubmessageFlag ,占1字节, Flags只使用了高5位。由低到高分别简写为N、K、D、Q和E,其中N = 1表示serializedPayload采用非标准格式,否则采用DDS规定的标准格式。标准格式指的是PL_CDR_BE、PL_CDR_LE等序列化格式,RTPS V2.5的10章节有介绍,而这些序列化细则主要是在DDS-XTypes协议中定义;Q = 1表示消息中包含inlineQos参数,否则不包含。当Q = 0时,解析Data时要跳过inlineQos参数;E = 1表示为数据格式为Little_Endian,否则为Big_Endian;K和D是组合使用的,如下:
•D=0和K=0表示没有serializedPayload。
•D=1和K=0表示serializedPayload包含序列化的数据。
•D=0和K=1表示serializedPayload包含序列化的Key。
•D=1和K=1在这个版本的协议中是一个无效的组合。
Flags在CAPL中可以用byte类型表示,如“byte flags;”,但是实际操作中会频繁的用到D、K、E等标志位,所以可以采用struct的数据类型,如下所示:
struct DataFlags_t {
byte value; //原始Flags值
byte E;
byte Q;
byte D;
byte K;
byte N;
};
这样在脚本中就可以直观地使用标志位,如Data.flags.E、Data.flags.Q等。
2.3 OctetsToNextHeader、extraFlags和OctetsToInlineQos
OctetsToNextHeader字段表示当前子消息的长度,其值为到下一个子消息头起始位置的字节数。RTPS中定义的消息类型为unsigned short,占2字节,在CAPL中可以用word类型表示,如“word submessageLength;”。
extraFlags未使用,该版本应该将参数设置为零。RTPS中定义的消息类型为unsigned short,占2字节,在CAPL中可以用word类型表示,如“word extraFlags;”。
OctetsToInlineQos表示紧跟该字段的第一个字节开始直到inlineQos的第一个字节的字节数。如果inlineQos不存在,则表示到serializedPayload的第一个字节的字节数。RTPS中定义的消息类型为unsigned short,占2 字节,在CAPL中可以用word类型表示,如“word inlineQosFlag”。
2.4 readerId和writerId
readerId表示消息接收实体Id,writerId表示消息发送实体Id。应该重点是关注这两个参数,进而判断该消息的实际用途(详见《DDS—RTPS一致性测试案例分析》)。RTPS中定义的数据类型为EntityId_t,其定义如下所示:
typedef octet OctetArray3[3];
struct EntityId_t {
OctetArray3 entityKey;
octet entityKind;
};
其中entityKey表示Entity类型,比如是Participant、Reader、Writer、Reader Group、Writer Group,以及该Entity是built-in Entity、user-defined Entity 或者vendor-specific Entity。
entityKind表示Entity是built-in Entity、user-defined Entity 或者vendor-specific Entity,比如entityKind = 0x02表示User-defined 的Writer (with Key), entityKind = 0x07表示User-defined Entity的Reader(with Key),具体规则详见RTPS V2.5协议的9.3.1.2。
例如,协议规定ENTITYID_SPDP_BUILTIN_PARTICIPANT_ANNOUNCER = {{00,01,00},c2},所以当Data的writerId = {{00,01,00},c2}时,我们就可以知道这是一个SPDP消息。
虽然EntityId_t是一个结构体,但是RTPS协议对EntityId都整体定义好的,所以在CAPL可以简化为dword类型,占4字节,如“dword writerId;”,因此writerId = {{00,01,00},c2}可以等效表示为writerId = 0x000100c2。
2.5 writerSN
表示 Writer 发送 Data 消息的相对顺序。每次发送新的 Data 消息时,writerSN 会递增,从而为每条消息分配一个连续的序号。例如,第一条发送的 Data 消息的 writerSN = 1,之后每发送一条新消息,writerSN 增加 1。如果某条消息丢失,Reader 可以通过 writerSN 检测到丢失的消息,并请求重传缺失writerSN的Data消息,这也是DDS消息的可靠性不依赖于传输协议的机制。
需要注意的是,writerSN 的更新是由每个 Entity 独立管理的,而不是全局统一的。每个 Entity 维护自己的 writerSN 序列。此外,只有 Data 消息的内容发生实质更新时,writerSN 才会递增。例如:
- SPDP 消息:由built-in Entity的 SPDP BuiltinParticipantWriter 发送。如果在程序执行周期内,DomainId、单播 IP 或广播 IP 等配置没有发生变化,SPDP 报文内容不会改变,因此 writerSN 并不会增加。
- Topic消息:由User-defined Entity的 Writer发送。每次发布Topic 时,其内容通常会发生变化,因此 writerSN 会递增。
RTPS中定义的消息类型为SequenceNumber_t,数据结构如下:
struct SequenceNumber_t {
long high;
unsigned long low;
};
其中high表示有符号的高4字节,low表示无符号的低4字节,所以是一个有符号数。因此,在CAPL中也用struct类型表示,如下所示:
struct SequenceNumber_t {
long high;
dword low;
};
2.6 inlineQos
仅当Q = 1才会有效,包含可能影响消息解释的QoS。消息格式定义如下所示:
struct Parameter {
ParameterId_t parameterId;
short length;
octet value[length]; // Pseudo-IDL: array of non-const length
};
parameterId标识独一无二的参数,由协议规定,length表示value的长度。具体规则详见RTPS V2.5协议的9.6.4,这只介绍一个常用的inlineQos来帮助大家熟悉报文分析方法。例如,inlineQos的原始报文为71 00 04 00 00 00 00 03,采用Little_Endian。
这段报文解析为parameterId = 0x0071,length = 0x0004,value[4] = {0,0,0,3}。根据RTPS协议,0x71表示PID_STATUS_INFO,该消息的value表示一个32位的标志,只用到了高3位,由低到高分别是F、U、D,其中:
D = 1表示 Writer 处理了子消息中 Key 对应的数据对象实例,该实例的生命周期结束了。
U = 1表示 Writer 注销了子消息中 Key 对应的数据对象实例,不再管理该实例。
F = 1表示Writer为子消息中 Key 对应的数据对象实例写入了一个样本,但样本未通过 Reader 内容过滤器,也就是没有提交给Reader。
所以,上面的inlineQoS表示DataWriter已经注销了。
在CAPL中可以用struct类型表示,由于CAPL不支持动态创建数组,因此需要预定义较大的数组来保存数据,如下所示:
struct ParameterList_t{
word parameterId;
word length;
byte value[512]; // value[length]
};
2.7 serializedPayload
仅当D = 1或者K = 1才显示,如果D = 1则表示为序列化的数据,如果K = 1则表示为序列化后的Key值,标识数据对象的实例,数据结构定义如下:
typedef octet RepresentationIdentifier[2];
typedef octet RepresentationOptions[2];
struct SerializedPayloadHeader {
RepresentationIdentifier representation_identifier;
RepresentationOptions representation_options;
}; //SerializedPayload
representation_identifier标识所使用的数据表示方式,如果协议规定PL_CDR_BE = {0x00, 0x02},PL_CDR_LE = {0x00, 0x03},详情请看RTPS V2.5的10.3 -10.6章节。representation_options默认为全0,应该忽略。所以如果脚本需要解析payload需要关注representation_identifier的参数。
在CAPL中可以用struct类型表示,其中buffer表示序列化的payload,并且要额外的记录数据长度,如下所示:
struct serializedData_t {
word kind;
word option;
byte buffer[DATA_SERIALIZED_DATA_LENGTH];
dword len;
};
3 CAPL解析
通过上文的介绍,我们已经知道了Data子消息的构成,因此解析Data子消息前首先要基于CAPL建立其对应的数据结构,如下所示:
图2 Data_t定义
解析时,首先将RTPS消息分割成报头和子消息,根据SubmessageId判断子消息类型,然后进行反序列化完成数据解析,当这个子消息解析完成后,将继续接下一个,直到所有子消息被解析完成,整个流程如下所示:
图3 子消息解析流程和示例代码
解析过程中,需要对“特殊”Data进行解析,比如SPDP报文的解析核心是对serializedPayload解析,由于篇幅有限只为大家展现下某车企的SPDP报文解析结果,如下所示:
图4 SPDP报文解析结果
文中提到的 CAPL 脚本不仅能够解析 Data 子消息,覆盖了 DDS 协议中的全部子消息类型,并支持对报文格式、协议一致性以及 QoS 策略等方面的全面测试。
4结语
通过本文,我们详细介绍了 Data 子消息的结构、字段作用以及实际应用场景,并为大家提供了通过 CAPL 脚本解析报文的思路和方法,希望能够为您的开发工作提供实用参考。由于水平有限,对协议难免有所偏差,如果您对本文内容感兴趣或者有其他疑问,对 CAPL 脚本的实现细节或 DDS 协议测试有进一步需求,欢迎将需求发送至邮箱market@dotrustech.com,我们将竭诚为您提供支持。