F´ 中的 Fw::FilePacket:CFDP 风格的文件分包协议与 C++ 实现解析
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/gh_mirrors/fp/fprime
导读
Fw::FilePacket 是 F´(F Prime)飞行软件框架中用于表示“ISF 文件分组包(file packet)”的核心类型,它定义了将任意文件切分成可在遥测/通信链路上传输的数据包的标准二进制格式。本文以 Fw/FilePacket/docs/sdd.md 设计文档为主体,结合 FilePacket.hpp 及各实现文件,完整讲解 START / DATA / END / CANCEL 四种包的字节布局、序列化与反序列化原理、CFDP 校验和算法,并给出基于 Svc/FileDownlink 的真实调用场景,帮助你直接掌握该格式的构造与解析方法。
1 设计背景:与 CFDP PDU 同源的包格式
Fw::FilePacket 的格式设计参考了 CCSDS 文件传输协议(CFDP)第 5 节中定义的协议数据单元(PDU)结构,因此它与航天工程中广泛使用的 CFDP 具有相似的骨架:
- 每个包以包类型 + 序号构成的头部开始;
- 头部之后是随类型而变的载荷(data);
- 整个文件通过一串带序号的包顺序传输,接收方可以据此重组文件并校验完整性。
在 F´ 中,这个类型被Svc::FileDownlink(文件下传组件)用来把本地文件分片发送,也可被对应的上行/地面侧解析。其核心头文件为 Fw/FilePacket/FilePacket.hpp,实现在 Fw/FilePacket 目录下的各.cpp文件中。
2 总体结构:一个包包含什么
从 sdd.md 与 FilePacket.hpp 可以看出,每个文件包包含以下数据:
| 字段 | 长度 | 说明 |
|---|---|---|
| 包类型(Packet type) | 1 字节 | START(0)、DATA(1)、END(2)、CANCEL(3),另有内部占位T_NONE(255) |
| 序号(Sequence index) | 4 字节 | 无符号整数,每个文件从 0 开始,后续每包递增 1 |
| 包数据(Packet data) | 可变 | 具体格式取决于包类型 |
在代码层面,FilePacket被实现为一个联合(union),内部同时包含Header、StartPacket、DataPacket、EndPacket、CancelPacket五种结构,通过m_header.m_type区分当前“视图”,这与 C++ 中“判别式联合(tagged union)”的经典做法一致。
2.1 头部(Header)的序列化
头部总是最先出现在每个包中,其缓冲区大小固定为 5 字节(sizeof(U8) + sizeof(U32)),见 FilePacket.hpp 中HEADERSIZE与 Header.cpp 的bufferSize()。
序列化时先写类型字节(强制转成U8),再写 4 字节序号(Header.cpp);反序列化时依次读回并用static_cast<Type>还原类型(Header.cpp)。这一对toSerialBuffer/fromSerialBuffer是所有包类型共用的基础能力。
3 四种包类型的载荷格式
3.1 START 包(类型 0):声明文件传输开始
START 包的包类型为START,序号恒为0,用于宣告一个新文件传输的开始。其载荷依次为:
| 字段 | 长度 | 说明 |
|---|---|---|
| 文件大小(File size) | 4 字节 | 整个文件的字节数(U32) |
| 源路径长度 | 1 字节 | 源路径(source path)的字节数(U8) |
| 源路径 | 可变 | 源端文件路径 |
| 目的路径长度 | 1 字节 | 目的路径(destination path)的字节数(U8) |
| 目的路径 | 可变 | 目标端文件路径 |
对应实现见 StartPacket.cpp 的initialize:头部以T_START与序号 0 初始化,随后保存文件大小,并分别初始化源、目的两个路径。
路径的序列化在 PathName.cpp 中完成:initialize用StringUtils::string_length(value, MAX_LENGTH)截取路径长度(MAX_LENGTH = 255,见 FilePacket.hpp),写入m_length(1 字节)与指向路径的指针;序列化时先写 1 字节长度、再写入对应字节数的路径内容;反序列化时同样先读长度,再把m_value指向缓冲区中的字符串地址(PathName.cpp)。因此路径长度上限为 255 字节。
3.2 DATA 包(类型 1):传输文件数据分片
DATA 包承载文件的实际内容分片,载荷为:
| 字段 | 长度 | 说明 |
|---|---|---|
| 字节偏移(Byte offset) | 4 字节 | 本包数据在整个文件中的起始字节位置(U32) |
| 数据长度(Data size) | 2 字节 | 本包携带的文件数据字节数(U16) |
| 文件数据(File data) | 可变 | 实际数据内容 |
实现见 DataPacket.cpp 的initialize,其签名接收sequenceIndex、byteOffset、dataSize与指向数据的指针。序列化顺序为:头部 → 4 字节偏移 → 2 字节数据长度 → 原始数据(DataPacket.cpp)。数据指针在反序列化时被直接指向输入缓冲区内的地址(DataPacket.cpp),因此反序列化得到的 DATA 包不拷贝数据,其生命周期与源缓冲区绑定,使用时需注意。
3.3 END 包(类型 2):结束并携带校验和
END 包宣告文件传输结束,其载荷只有一个字段:
| 字段 | 长度 | 说明 |
|---|---|---|
| 32 位哈希值(Checksum) | 4 字节 | 按 CFDP 协议规则由文件数据计算得到的 32 位校验和 |
实现见 EndPacket.cpp:initialize(sequenceIndex, checksum)将传入的CFDP::Checksum对象的值(getValue())保存为m_checksumValue;序列化/反序列化时直接读写该 4 字节值(EndPacket.cpp)。接收方可用它验证整个文件在传输过程中是否发生损坏。
3.4 CANCEL 包(类型 3):取消传输
CANCEL 包用于中止传输,不携带任何数据,仅由头部构成(类型CANCEL+ 序号)。实现见 CancelPacket.cpp,bufferSize()直接返回头部大小;反序列化时要求缓冲区剩余字节数为 0,否则返回FW_DESERIALIZE_SIZE_MISMATCH(CancelPacket.cpp),保证格式的严格性。
4 统一入口:FilePacket 的 toBuffer / fromBuffer
FilePacket对外提供两个统一接口(FilePacket.cpp):
fromBuffer(const Buffer& buffer):将一段原始字节解析为文件包。内部先构造SerialBuffer并fill(),然后读头部,再依据m_type分派给对应的StartPacket::fromSerialBuffer/DataPacket::fromSerialBuffer/EndPacket::fromSerialBuffer/CancelPacket::fromSerialBuffer;若读到T_NONE返回FW_DESERIALIZE_TYPE_MISMATCH,未知类型触发FW_ASSERT(0)(FilePacket.cpp)。toBuffer(Buffer& buffer):把当前包写入字节缓冲区,同样按类型分派。bufferSize():按类型返回所需的缓冲区大小,T_NONE时返回 0。asStartPacket()/asDataPacket()/asEndPacket()/asCancelPacket():以对应类型视图读取包内容,内部均先断言类型匹配(FilePacket.cpp)。fromStartPacket()/fromDataPacket()/fromEndPacket()/fromCancelPacket():从具体包对象构造FilePacket(FilePacket.cpp)。
因此使用方式非常直观:发送方构造具体包 →toBuffer编码;接收方拿到字节 →fromBuffer解码 →asXxxPacket读取字段。
5 END 包背后的 CFDP 校验和算法
END 包携带的 32 位校验和由 CFDP/Checksum/Checksum.hpp 与 CFDP/Checksum/Checksum.cpp 实现。其核心是update(data, offset, length):把文件视为连续的 4 字节“字(word)”序列,逐字累加到m_value(32 位无符号溢出累加),具体分三阶段处理:
- 若
offset % 4 != 0,先按非对齐方式累加第一个不满 4 字节的“字”(addWordUnaligned); - 中间对齐部分以 4 字节为单位直接累加(
addWordAligned); - 末尾不足 4 字节的部分再以非对齐方式收尾。
addByteAtOffset将字节按其在 4 字节字内的位置左移8*(3-offset)位后加到校验和上(Checksum.cpp)。注意:update的offset是相对文件起始的字节偏移——这与 DATA 包中的byteOffset语义一致,发送方应按文件全局偏移逐片更新校验和,接收方才能在 END 包中做整体比对。
Checksum还提供构造(默认 0 / 指定值 / 拷贝)、getValue()、operator==/operator!=,便于在测试中直接比较校验和值(Checksum.hpp)。
6 在 Svc::FileDownlink 中的实际用法
Fw::FilePacket在仓库中最重要的消费者是文件下传组件Svc::FileDownlink。在 Svc/FileDownlink/FileDownlink.cpp 中可以找到两处关键调用:
- 发送数据分片时:构造
Fw::FilePacket::DataPacket并调用filePacket.fromDataPacket(dataPacket)生成可发送的包(FileDownlink.cpp); - 发送起始声明时:构造
StartPacket后调用filePacket.fromStartPacket(startPacket)(FileDownlink.cpp)。
即:业务组件负责构造携带具体字段的包对象,再由FilePacket统一完成编码与类型管理,这正好呼应了第 4 节介绍的“构造 → fromXxxPacket → toBuffer”流程。若需了解完整的分片、调度与校验逻辑,可继续阅读 Svc/FileDownlink/docs/sdd.md 及测试 Svc/FileDownlink/test/ut/FileDownlinkTester.cpp。
7 测试验证:四种包的往返一致性
仓库在 Fw/FilePacket/test/ut/FilePacketMain.cpp 中提供了覆盖四种包类型的 GTest 用例,均遵循“构造 → 序列化 → 反序列化 → 逐字段比较”的往返测试模式:
TEST(FilePacket, Header):以类型T_DATA、序号 10 构造头部,往返后断言一致(FilePacketMain.cpp);TEST(FilePacket, StartPacket):文件大小 10、源路径"source"、目的路径"dest"(FilePacketMain.cpp);TEST(FilePacket, DataPacket):序号 3、字节偏移 42、数据长度 10(FilePacketMain.cpp);TEST(FilePacket, EndPacket):序号 15、校验和值 42(FilePacketMain.cpp);TEST(FilePacket, CancelPacket):序号 10,验证无载荷包可正确往返(FilePacketMain.cpp)。
比较断言由 GTest 辅助命名空间提供:Fw::GTest::FilePackets定义了各包类型的compare函数(见 Fw/FilePacket/GTest/FilePackets.hpp 及其实现,如 GTest/StartPacket.cpp),通过ASSERT_EQ逐一比对头部、文件大小、路径、偏移、数据长度与校验和等字段。若要运行该模块测试,可在构建目录中启用对应的 CMake 目标(模块定义见 Fw/FilePacket/CMakeLists.txt)。
8 小结
Fw::FilePacket是 F´ 内文件传输的统一分组格式,格式骨架取自 CFDP PDU(sdd.md);- 每个包由 5 字节头部(1 字节类型 + 4 字节序号)与类型相关载荷组成,四种类型分别为 START(文件大小 + 双路径)、DATA(偏移 + 长度 + 数据)、END(CFDP 32 位校验和)、CANCEL(无载荷);
- 路径采用“1 字节长度 + 内容”的自描述编码,最长 255 字节;
- 编解码入口统一为
fromBuffer/toBuffer,配合asXxxPacket/fromXxxPacket使用;DATA 包反序列化不拷贝数据,需注意缓冲区生命周期; - END 包校验和由
CFDP::Checksum::update按 4 字节字累加得到,偏移语义与 DATA 包byteOffset一致; - 仓库内通过
Svc::FileDownlink实际使用该类型,并有完整的 GTest 往返测试保证格式的稳定性。
如需进一步掌握文件传输的端到端流程(如何分片、如何驱动发送、如何重组与校验),可继续阅读 Svc/FileDownlink/docs/sdd.md 与 Svc/FileUplink 相关文档。
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/gh_mirrors/fp/fprime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考