一个轻量级、可移植的 C99 协议解析库,面向嵌入式系统设计。通过规则驱动的字段描述符定义协议结构,无需手写解析代码,支持定长/不定长协议的解析与逆向构造。
核心特性:
- 规则驱动:通过描述符宏定义协议结构,编译期自动生成元数据
- 内存安全:默认拷贝模式将 DMA/易失缓冲区数据复制到堆内存,防止数据覆盖
- 零拷贝优化:可选零拷贝模式,直接传递源缓冲区指针
- 变长支持:支持 TLV/LTV 等不定长协议格式
- 数据包构造:支持定长/不定长消息逆向构造,乱序字段填充,惰性内存分配
- CRC 校验:内置 CRC-32,支持自定义校验算法
- 重入安全:基于实例的设计,支持多线程并发解析
- 跨平台:支持 GCC、Clang、MSVC,优化 RISC-V32/ARM32 内存布局
- 零依赖:纯 C99 实现,仅需标准库
typedef struct {
uint8_t type;
uint8_t length;
uint8_t data[]; // 柔性数组(不定长数据)
} my_protocol_t;const protocol_field_descriptor_t fields[] = {
FIELD_DESC_FIXED(my_protocol_t, type, FIELD_TYPE_UINT8, NULL),
FIELD_DESC_FIXED(my_protocol_t, length, FIELD_TYPE_UINT8, &len_calls),
FIELD_DESC_VAR (my_protocol_t, data, FIELD_TYPE_UINT8, FIELD_LEN_SYMBOL, NULL),
};
const protocol_message_descriptor_t msg_desc = {
.name = "my_protocol",
.fields = fields,
.num_fields = FIELD_ARR_SIZE(fields),
.total_size = -1, // 变长消息
};// 初始化解析器实例
app_parser_instance_t parser;
app_parser_init(&parser, &memCalls);
// 解析消息
parsing_user_data_t user_data = {NULL, 0};
protocol_err_t ret = app_parse_message_ex(&parser, &user_data, &msg_desc, raw_data);
app_parser_deinit(&parser);解析器的核心逻辑由可读的字段描述符驱动,而非硬编码。通过 FIELD_DESC_FIXED / FIELD_DESC_VAR 等宏在编译期自动生成元数据(偏移量、元素大小、元素个数),运行时只需顺序遍历描述符数组即可完成解析。新增协议只需定义描述符数组,无需修改解析引擎。
// 字段描述符(单个字段的元数据)
typedef struct {
const char *name; // 字段名称
const protocol_field_calls_t *calls; // 回调函数配置
uint16_t offset; // 在结构体中的偏移量
int16_t itemSize; // 元素大小
int16_t itemCount; // >0: 定长元素个数; <0: 变长模式
uint8_t type; // 字段类型枚举
uint8_t flags; // 标志位
} protocol_field_descriptor_t;
// 消息描述符(整个协议的模板)
typedef struct {
const char *name;
const protocol_field_descriptor_t *fields;
field_callback_t on_message_start_callback;
field_callback_t on_message_end_callback;
uint16_t num_fields;
int32_t total_size; // >0: 定长; -1: 变长
} protocol_message_descriptor_t;嵌入式平台优化:
- 移除位域,改用完整
uint8_t,避免 RISC-V32/ARM32 编译器兼容性问题 - 字段顺序优化(指针 → 整数 → 小类型),减少 padding
- 跨平台 packed 宏 + 编译时断言验证结构体大小
| 结构体 | 32-bit | 64-bit |
|---|---|---|
protocol_field_descriptor_t |
16 字节 | 24 字节 |
protocol_message_descriptor_t |
22 字节 | 38 字节 |
拷贝模式(默认):为每个带回调的字段分配堆内存并拷贝数据,防止 DMA 缓冲区被后续数据覆盖。使用双向链表管理临时数据节点,解析完成后自动释放。
注意:拷贝模式逐字段分配堆内存,字段数较多的协议可能产生内存碎片。性能敏感场景建议使用零拷贝模式。
零拷贝模式:通过 FIELD_FLAG_ZERO_COPY 标志启用,直接传递源缓冲区指针给回调函数,无内存分配。用户需确保回调期间原始缓冲区有效且不被修改。
PacketConstructor 提供协议数据包的逆向构造能力,与解析器共用字段描述符:
- 模式驱动:
constructor_mode_t枚举 + union 自动切换定长/不定长逻辑 - 惰性分配:init 仅分配定长缓冲区,变长管理数组在首次
set_field时分配 - 乱序支持:字段可按任意顺序填充,位图跟踪完成状态
- 多实例:每个构造器实例独立,可同时构造多种消息
// 初始化/反初始化解析器实例
protocol_err_t app_parser_init(app_parser_instance_t *parser, const parsing_memCall_t *memCalls);
protocol_err_t app_parser_deinit(app_parser_instance_t *parser);
// 解析消息(重入安全,推荐使用)
protocol_err_t app_parse_message_ex(app_parser_instance_t *parser,
parsing_user_data_t *user,
const protocol_message_descriptor_t *msg_desc,
const uint8_t *raw_data);
// 旧版 API(使用全局变量,非线程安全,仅用于快速理解核心理念)
protocol_err_t app_memCall_init(const parsing_memCall_t *memCalls);
protocol_err_t app_parse_message(parsing_user_data_t *user,
const protocol_message_descriptor_t *msg_desc,
const uint8_t *raw_data);
// CRC 配置
void app_parser_set_crc_config(app_parser_instance_t *parser, const app_crc_config_t *crc_config);
void app_parser_enable_crc(app_parser_instance_t *parser, bool enable);新旧 API 选择:旧版 API 仅用于快速理解项目核心理念,生产环境统一使用新版 API。旧版 API 使用全局变量,不支持 CRC 校验,不支持多线程并发。
protocol_err_t app_constructor_init(app_packet_constructor_t *ctor,
const protocol_message_descriptor_t *msg_desc,
const parsing_memCall_t *memCalls);
protocol_err_t app_constructor_set_field(app_packet_constructor_t *ctor,
const char *field_name,
const void *data, size_t data_size);
protocol_err_t app_constructor_set_field_by_index(app_packet_constructor_t *ctor,
uint16_t field_index,
const void *data, size_t data_size);
bool app_constructor_is_complete(const app_packet_constructor_t *ctor);
uint32_t app_constructor_get_missing_mask(const app_packet_constructor_t *ctor);
protocol_err_t app_constructor_finalize(app_packet_constructor_t *ctor,
uint8_t **out_buffer, size_t *out_size);
void app_constructor_deinit(app_packet_constructor_t *ctor);| 宏 | 用途 |
|---|---|
FIELD_DESC_FIXED(type, member, f_type, calls) |
定长字段 |
FIELD_DESC_FIXED_WITH_FLAGS(type, member, f_type, flags, calls) |
定长字段(带标志位) |
FIELD_DESC_VAR(type, member, f_type, mode, calls) |
变长字段 |
FIELD_DESC_VAR_WITH_FLAGS(type, member, f_type, mode, flags, calls) |
变长字段(带标志位) |
| 模式 | 值 | 说明 | 状态 |
|---|---|---|---|
FIELD_LEN_SYMBOL |
-2 | 关联前一个字段的长度值 | ✅ 已实现 |
FIELD_END_SYMBOL |
-1 | 遇结束符终止(如 \0) |
|
FIELD_ALL_REMAINING |
-3 | 剩余所有数据 |
| 标志 | 说明 | 状态 |
|---|---|---|
FIELD_FLAG_NONE |
默认拷贝模式 | ✅ 已实现 |
FIELD_FLAG_ZERO_COPY |
零拷贝模式 | ✅ 已实现 |
FIELD_FLAG_BIG_ENDIAN |
强制大端序 | |
FIELD_FLAG_LITTLE_ENDIAN |
强制小端序 |
| 错误码 | 值 | 说明 |
|---|---|---|
PROTOCOL_OK |
0 | 成功 |
PROTOCOL_ERR_FAIL |
1 | 通用失败 |
PROTOCOL_ERR_ARG |
2 | 参数错误 |
PROTOCOL_ERR_MEM |
3 | 内存分配失败 |
PROTOCOL_ERR_PARSE |
4 | 解析错误 |
PROTOCOL_ERR_PASSMSG |
5 | 用户回调要求跳过 |
PROTOCOL_ERR_CALLS_INIT |
6 | 内存回调未初始化 |
PROTOCOL_ERR_FUNCS |
7 | 内存函数测试失败 |
PROTOCOL_ERR_CRC |
8 | CRC 校验失败 |
typedef struct {
uint16_t header;
uint32_t timestamp;
uint8_t status;
uint8_t data[16];
} fixed_protocol_t;
const protocol_field_descriptor_t fields[] = {
FIELD_DESC_FIXED(fixed_protocol_t, header, FIELD_TYPE_UINT16, NULL),
FIELD_DESC_FIXED(fixed_protocol_t, timestamp, FIELD_TYPE_UINT32, NULL),
FIELD_DESC_FIXED(fixed_protocol_t, status, FIELD_TYPE_UINT8, NULL),
FIELD_DESC_FIXED(fixed_protocol_t, data, FIELD_TYPE_UINT8, NULL),
};
const protocol_message_descriptor_t msg_desc = {
.name = "fixed_protocol",
.fields = fields,
.num_fields = FIELD_ARR_SIZE(fields),
.total_size = sizeof(fixed_protocol_t),
};
// 解析
app_parser_instance_t parser;
app_parser_init(&parser, &memCalls);
parsing_user_data_t user = {NULL, 0};
app_parse_message_ex(&parser, &user, &msg_desc, raw_data);
app_parser_deinit(&parser);typedef struct {
uint8_t type;
uint8_t length;
uint8_t data[];
} tlv_protocol_t;
// 长度字段回调:将长度值存入 user->uDataSize
static protocol_err_t tlv_len_callback(parsing_user_data_t *_user,
const parsing_raw_data_t *_raw) {
_user->uDataSize = *(uint8_t *)_raw->rawStream;
return PROTOCOL_OK;
}
const protocol_field_descriptor_t tlv_fields[] = {
FIELD_DESC_FIXED(tlv_protocol_t, type, FIELD_TYPE_UINT8, NULL),
FIELD_DESC_FIXED(tlv_protocol_t, length, FIELD_TYPE_UINT8, &len_calls),
FIELD_DESC_VAR (tlv_protocol_t, data, FIELD_TYPE_UINT8, FIELD_LEN_SYMBOL, NULL),
};
const protocol_message_descriptor_t tlv_msg_desc = {
.name = "tlv_protocol",
.fields = tlv_fields,
.num_fields = FIELD_ARR_SIZE(tlv_fields),
.total_size = -1, // 变长消息
};// 定长消息构造(支持乱序填充)
app_packet_constructor_t ctor;
app_constructor_init(&ctor, &msg_desc, &memCalls);
uint16_t hdr = 0x1234;
app_constructor_set_field(&ctor, "header", &hdr, sizeof(hdr));
uint8_t payload[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
app_constructor_set_field_by_index(&ctor, 3, payload, sizeof(payload));
// ... 填充其余字段 ...
if (app_constructor_is_complete(&ctor)) {
uint8_t *packet = NULL;
size_t size = 0;
app_constructor_finalize(&ctor, &packet, &size);
// 使用 packet ...
memCalls.free(packet);
}
app_constructor_deinit(&ctor);// LTV 不定长消息构造
// test_ltv_build_t: len(定长) + data[](变长, FIELD_LEN_SYMBOL)
app_packet_constructor_t ctor_ltv;
app_constructor_init(&ctor_ltv, <v_build_desc, &memCalls);
uint8_t len_val = 10;
app_constructor_set_field(&ctor_ltv, "len", &len_val, sizeof(len_val));
uint8_t data[10] = {0x01, 0x08, 0x02, 0x04, 0x08, 0x01, 0x01, 0x02, 0x04, 0x08};
app_constructor_set_field(&ctor_ltv, "data", data, sizeof(data));
uint8_t *packet = NULL;
size_t size = 0;
app_constructor_finalize(&ctor_ltv, &packet, &size);
// packet = [len=10][typeNum+payload], size = 1+10 = 11
memCalls.free(packet);
app_constructor_deinit(&ctor_ltv);注意:
FIELD_LEN_SYMBOL变长字段需先设置长度字段,再设置数据字段。data_size不能超过长度字段的值。
调用者必须确保传入的原始数据缓冲区足够大,能够容纳完整的报文。
解析器根据 msg_desc->total_size 或字段定义访问缓冲区,不接收缓冲区长度参数。缓冲区不完整会导致越界访问和未定义行为。对于变长报文,建议在调用解析前先验证缓冲区长度。
解析器不进行字节序转换。 数据的字节序由通讯双方预先协定,如需转换请在回调中自行处理。
FIELD_FLAG_BIG_ENDIAN / FIELD_FLAG_LITTLE_ENDIAN 为预留标志,当前版本不实现自动转换功能,后续如有需要可基于该标志扩展。
通过 parsing_memCall_t 注入 malloc/calloc/realloc/free 函数指针,用户可集成自定义内存池。
- 拷贝模式(默认):逐字段分配堆内存并拷贝,解析完成后自动释放
- 零拷贝模式:无内存分配,直接传递源指针
app_crc_config_t crc_cfg = {
.calc_crc = NULL, // NULL 使用内置 CRC-32
.crc_offset = 0, // CRC 字段偏移
.crc_size = 4, // CRC 字段大小(1/2/4 字节)
};
app_parser_set_crc_config(&parser, &crc_cfg);
app_parser_enable_crc(&parser, true);限制:内置 CRC 校验仅支持定长消息(
total_size > 0)。变长消息在解析时无法获知实际数据长度,需在on_message_end_callback回调中自行校验。
以下功能已定义接口但当前版本未完整实现,后续版本可能扩展:
| 功能 | 位置 | 说明 |
|---|---|---|
FIELD_END_SYMBOL |
itemCountMode_t |
结束符模式,当前 streamSize 返回 0 |
FIELD_ALL_REMAINING |
itemCountMode_t |
剩余全部模式,当前 streamSize 返回 0 |
FIELD_FLAG_BIG/LITTLE_ENDIAN |
field_flag_t |
字节序标志,当前需在回调中手动处理 |
on_serialize_callback |
protocol_field_calls_t |
序列化回调,当前未使用 |
APP_MAX_FIELDS / APP_MAX_TEMPLATES |
宏定义 | 预留限制值,当前未引用 |
typedef protocol_err_t (*field_callback_t)(parsing_user_data_t *_user,
const parsing_raw_data_t *_raw);_user->uData/_user->uDataSize:用户上下文,可在字段间传递信息(如长度值)_raw->rawStream/_raw->streamSize:字段数据指针和大小- 返回
PROTOCOL_OK继续解析;返回PROTOCOL_ERR_PASSMSG跳过剩余字段
- 所有 printf 格式符使用
%u+(unsigned int)强转,替代%zu,兼容简化 C 标准库 - 结构体使用 packed 属性,编译时断言验证跨平台大小一致性
- 不依赖 C11 特性(
_Static_assert仅在编译器支持时启用)
- C 编译器(支持 C99):GCC / Clang / MSVC
- CMake 3.10+
mkdir build && cd build
cmake ..
cmake --build .gcc -std=c99 -I include -I thirdparty/c-linked-list-main/src/linkedlist \
sources/AnyProtocolParser.c sources/PacketConstructor.c sources/main.c \
-o parser_test.\build.batgcc -DAPP_PARSER_ENABLE_DEBUG_LOG ...运行测试:
./build/bin/outputFile.exe测试覆盖:
- 嵌入式平台内存对齐:结构体大小、字段偏移、跨平台一致性(6 项)
- 定长消息解析:基本字段类型(UINT8/16/32/64、指针、数组)
- TLV/LTV 不定长消息:Type-Length-Value / Length-Type-Value 格式
- 零拷贝模式:
FIELD_FLAG_ZERO_COPY标志 - 重入安全 API:多实例并发解析
- CRC 校验:成功/失败场景
- 定长数据包构造:乱序填充、完整性检查、往返验证
- LTV 不定长数据包构造:错误处理、定长+变长合并、往返验证
AnyProtocolParser/
├── include/
│ ├── AnyProtocolParser.h # 核心头文件(API、宏、数据结构)
│ ├── PacketConstructor.h # 数据包构造器头文件
│ └── DBG_macro.h # 调试宏
├── sources/
│ ├── AnyProtocolParser.c # 解析器实现
│ ├── PacketConstructor.c # 构造器实现
│ ├── main.c # 测试用例
│ └── test_alignment.c # 嵌入式平台对齐测试
├── thirdparty/
│ └── c-linked-list-main/ # 双向链表库(MIT)
├── CMakeLists.txt
├── build.bat
└── readme.md
- 多协议网关或路由器固件
- 协议版本迭代频繁的嵌入式设备
- 从 DMA 缓冲区接收数据,需确保数据安全的场景
- 需要逆向构造协议数据包的场景
- 工业控制、IoT 设备的自定义通信协议
v2.2 (2026-07-07) - 文档重构与缺陷修复:
- 修复新版 API 错误码被静默吞掉的 Bug
- 重构 readme 文档,修正不准确的描述(FSM、四层架构等)
- 明确标注预留功能状态(FIELD_END_SYMBOL、字节序标志等)
- 补充 CRC 校验对变长消息的限制说明
- 明确新旧 API 定位:旧版仅用于理解核心理念,生产环境使用新版
v2.2 (2026-07-06) - 数据包构造器:
- 新增
app_packet_constructor_t,支持定长/不定长消息逆向构造 - 模式驱动设计(
constructor_mode_t+ union),惰性内存分配 - 乱序字段填充,位图跟踪完成状态,多实例独立
v2.1 - 嵌入式平台优化:
- 移除位域,改用
uint8_t,优化字段顺序,跨平台 packed 宏 - 编译时断言验证结构体大小,新增对齐测试套件
v2.0 - 核心功能:
- 零拷贝模式、CRC 校验、重入安全 API、带标志位的描述符宏
MIT License