Skip to content

Repository files navigation

自适应协议解析器 (Adaptive Protocol Parser)

🇺🇸 English Version

简介

一个轻量级、可移植的 C99 协议解析库,面向嵌入式系统设计。通过规则驱动的字段描述符定义协议结构,无需手写解析代码,支持定长/不定长协议的解析与逆向构造。

核心特性:

  • 规则驱动:通过描述符宏定义协议结构,编译期自动生成元数据
  • 内存安全:默认拷贝模式将 DMA/易失缓冲区数据复制到堆内存,防止数据覆盖
  • 零拷贝优化:可选零拷贝模式,直接传递源缓冲区指针
  • 变长支持:支持 TLV/LTV 等不定长协议格式
  • 数据包构造:支持定长/不定长消息逆向构造,乱序字段填充,惰性内存分配
  • CRC 校验:内置 CRC-32,支持自定义校验算法
  • 重入安全:基于实例的设计,支持多线程并发解析
  • 跨平台:支持 GCC、Clang、MSVC,优化 RISC-V32/ARM32 内存布局
  • 零依赖:纯 C99 实现,仅需标准库

快速开始

1. 定义协议结构

typedef struct {
    uint8_t  type;
    uint8_t  length;
    uint8_t  data[];    // 柔性数组(不定长数据)
} my_protocol_t;

2. 配置字段描述符

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,  // 变长消息
};

3. 解析数据

// 初始化解析器实例
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 时分配
  • 乱序支持:字段可按任意顺序填充,位图跟踪完成状态
  • 多实例:每个构造器实例独立,可同时构造多种消息

API

解析器

// 初始化/反初始化解析器实例
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);

TLV 不定长协议解析

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, &ltv_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 函数指针,用户可集成自定义内存池。

  • 拷贝模式(默认):逐字段分配堆内存并拷贝,解析完成后自动释放
  • 零拷贝模式:无内存分配,直接传递源指针

CRC 校验

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+

CMake 构建

mkdir build && cd build
cmake ..
cmake --build .

GCC 直接编译

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

Windows (MinGW)

.\build.bat

启用调试日志

gcc -DAPP_PARSER_ENABLE_DEBUG_LOG ...

测试

运行测试:

./build/bin/outputFile.exe

测试覆盖:

  1. 嵌入式平台内存对齐:结构体大小、字段偏移、跨平台一致性(6 项)
  2. 定长消息解析:基本字段类型(UINT8/16/32/64、指针、数组)
  3. TLV/LTV 不定长消息:Type-Length-Value / Length-Type-Value 格式
  4. 零拷贝模式FIELD_FLAG_ZERO_COPY 标志
  5. 重入安全 API:多实例并发解析
  6. CRC 校验:成功/失败场景
  7. 定长数据包构造:乱序填充、完整性检查、往返验证
  8. 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

About

解决C语言没有二进制数据流协议解析模板的问题

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages