# protocol-sdk
**Repository Path**: hslei/protocol-sdk
## Basic Information
- **Project Name**: protocol-sdk
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-08-19
- **Last Updated**: 2026-08-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# leobit-protocol-sdk
通用协议解析 / 组帧 SDK:以 **YAML 协议定义** 驱动,完成 **字节帧 ↔ 结构化 Map** 的双向转换,消除代码中的偏移量 / 长度魔法值。
- **纯 Java 8**,无 Spring / MyBatis / Netty 依赖,后端服务与终端模拟器客户端均可使用
- **独立 SDK**:不继承任何父 POM、不依赖任何业务模块,其他模块通过 Maven 依赖它
- 零魔法值:帧结构完全由 YAML 描述,新增协议帧无需改动代码
## 目录
- [模块结构](#模块结构)
- [核心功能](#核心功能)
- [快速开始](#快速开始)
- [YAML 协议定义](#yaml-协议定义)
- [字段类型](#字段类型)
- [表达式语法](#表达式语法)
- [错误码](#错误码)
- [构建与测试](#构建与测试)
## 模块结构
| 包 | 职责 | 核心类 |
|---|---|---|
| `com.leobit.protocol.model` | 协议定义与结果模型 | `ProtocolDefinition`、`FrameDefinition`、`FieldDefinition`、`FieldType`、`ProtocolFrame` |
| `com.leobit.protocol.engine` | 编解码引擎与表达式求值 | `GenericFrameEngine`、`ExpressionEvaluator` |
| `com.leobit.protocol.loader` | YAML 协议定义加载 | `YamlProtocolLoader` |
| `com.leobit.protocol.util` | 字节缓冲区工具 | `ByteBufferUtils` |
| `com.leobit.protocol.exception` | 统一异常体系 | `ProtocolEngineException` |
### model —— 协议定义模型
- **`ProtocolDefinition`**:协议元信息(name / version / active)、外层帧头 `outerHeader`、消息体帧索引(按 type+code)、嵌套子帧索引(按 id)、SN/TYPE/CODE/LEN 字段名约定及 `lenIncludesHeader` 语义。
- **`FrameDefinition`**:单帧结构,`typeCode` + `codeCode` 唯一定位一帧(`frameKey()`),包含有序字段列表。
- **`FieldDefinition`**:字段定义,支持类型、长度、字节序、缩放、条件、默认值、嵌套引用、校验算法等完整描述。
- **`ProtocolFrame`**:解析结果对象(不可变),暴露 `sn / type / code / body`(body 为只读 Map,支持泛型取值 `get("field")`)。
### engine —— 编解码引擎
- **`GenericFrameEngine`**:核心引擎,绑定一个 `ProtocolDefinition`,提供 `decode` / `encodeFrame` / `decodeContent` / `encodeContent` 等双向转换 API。
- **`ExpressionEvaluator`**:轻量级表达式求值器,支持字段引用 `${fieldName}`、算术、比较、逻辑运算,用于 `lengthExpr` / `conditionExpr`。
### loader —— 协议定义加载
- **`YamlProtocolLoader`**:将 YAML 协议描述装配为 `ProtocolDefinition`,支持 classpath / 文件 / 流 / Reader 四种加载方式;`nestedFrameId` 与 `discriminatorMap` 可写帧名或数字 id(两阶段解析,自动解析为 id)。
### util / exception
- **`ByteBufferUtils`**:大小端、有符号 / 无符号定长整数读写。
- **`ProtocolEngineException`**:运行时异常,携带枚举 `ErrorCode`(见[错误码](#错误码)),所有异常通过统一错误码区分失败类别。
## 核心功能
- **字节帧 ↔ Map 双向映射**:依据 YAML 定义自动解析/组帧,无需手写偏移量。
- **13 种字段类型**:UINT8~UINT64、INT8~INT64、TEXT、BYTES、BITMASK、LENGTH_REF、CHECKSUM、NESTED。
- **嵌套子帧 + 判别路由**:解决"同一字段的字节内容按判别值解析成不同结构"的场景(如 `appType=0x00` 是运动包、`0x80` 是设备包)——NESTED 字段支持 `nestedFrameId` 直连,或 `discriminatorField` + `discriminatorMap` 按判别值路由(支持多级嵌套),无需在代码中手写 if-else 偏移量逻辑。子帧没有 type/code,加载时分配自增 id 建立 `nestedFrames` 索引,运行期按 id O(1) 查找。
- **变长字段**:解决"字段长度依赖其他字段或剩余字节"的场景——`lengthExpr` 表达式驱动(含 `remaining` 特殊字面量),也可由 `lengthBytes` 定长。
- **条件字段**:解决"字段只在特定条件下存在"的场景——`conditionExpr` 为假时该字段跳过解析/编码。
- **物理量换算**:`scale` / `offset` 实现 raw ↔ 物理值自动换算(解码 `raw * scale + offset`,编码反向取整)。
- **校验和**:`SUM8` / `CRC16` 自动计算与校验,校验失败抛 `INVALID_FRAME`。
- **位提取**:`BITMASK` + `bitRange`(如 `"4-7"`)提取指定位段。
- **自动长度**:编码时自动计算 LEN 字段(含或不含帧头由 `lenIncludesHeader` 决定),内容超限抛 `LENGTH_EXCEEDED`。
- **嵌套字段重编码**:`encodeNestedField(type, code, fieldName, values, outerContext)` 按判别字段路由子帧,把结构化 NESTED 字段值重编码为字节(如报文日志 dataHex),路由失败返回 `null`。
- **大小端**:字段级字节序配置,默认大端。
## 快速开始
### 1. 引入依赖
```xml
com.leobit
leobit-protocol-sdk
1.0.0
```
> snakeyaml 为 `optional` 依赖:仅使用 YAML 加载时需要额外引入;Lombok 为编译期注解(`provided`),不会传递。
### 2. 代码示例
```java
// 加载协议定义(classpath 下的 YAML)
ProtocolDefinition protocol = new YamlProtocolLoader()
.loadFromClasspath("protocols/zdmock-protocol.yaml");
// 创建引擎(绑定协议)
GenericFrameEngine engine = new GenericFrameEngine(protocol);
// ===== 解码:byte[] → ProtocolFrame =====
ProtocolFrame frame = engine.decode(recvBytes);
int sn = frame.getSn();
Map body = frame.getBody(); // 只读 Map
String signalLevel = frame.get("signalLevel"); // 泛型取值
// ===== 编码:Map → byte[] =====
Map values = new HashMap<>();
values.put("powerOn", 1);
values.put("targetSpeed", 300); // 物理值,引擎自动换算 raw
byte[] out = engine.encodeFrame(sn, 0x01, 0x20, values);
```
## YAML 协议定义
```yaml
protocol:
name: zdmock
version: v1
active: true
snFieldName: sn
typeFieldName: type
codeFieldName: code
lenFieldName: len
lenIncludesHeader: true # LEN 是否包含帧头字节数
outerHeader: # 外层帧头(定长字段)
frameName: outer.header
fields:
- {fieldName: sn, fieldType: UINT8}
- {fieldName: type, fieldType: UINT8}
- {fieldName: code, fieldType: UINT8}
- {fieldName: len, fieldType: UINT16, byteOrder: LITTLE}
frames: # 消息体帧(按 type/code 索引)
- frameName: ctrl.param.set
typeCode: 0x01
codeCode: 0x20
direction: C2S
fields:
- {fieldName: powerOn, fieldType: UINT8, defaultValue: "1"}
- {fieldName: targetSpeed, fieldType: INT16, scale: 0.1, signed: true}
- {fieldName: data, fieldType: NESTED, discriminatorField: appType,
discriminatorMap: "0x00=appdata.motion,0x80=appdata.equip"}
nestedFrames: # 嵌套子帧(按 id 索引)
- frameName: appdata.equip
fields:
- {fieldName: payload, fieldType: NESTED,
discriminatorMap: "1=equip.position,2=equip.fuel"}
```
### 字段常用属性
| 属性 | 说明 | 示例 |
|---|---|---|
| `fieldName` | 字段名(解码后的 Map 键) | `targetSpeed` |
| `fieldType` | 字段类型(见[字段类型](#字段类型)) | `UINT16` |
| `lengthBytes` | 定长字段字节数 | `2` |
| `lengthExpr` | 变长字段长度表达式 | `"${len}-5"`、`remaining` |
| `byteOrder` | `BIG` / `LITTLE`(默认 BIG) | `LITTLE` |
| `signed` | 有符号(数值类型,默认 false) | `true` |
| `scale` / `offset` | 物理量换算:物理值 = raw × scale + offset | `scale: 0.1` |
| `defaultValue` | 编码时缺省值 | `"1"`、`"0x10"` |
| `conditionExpr` | 条件字段,为假时跳过 | `"${mode} == 2"` |
| `nestedFrameId` | NESTED 字段直连子帧(数字 id 或帧名) | `appdata.motion` |
| `discriminatorField` / `discriminatorMap` | 按判别值路由子帧 | 见上例 |
| `checksumAlgorithm` | `NONE` / `SUM8` / `CRC16`(默认 NONE) | `CRC16` |
| `bitRange` | BITMASK 位段,如 `4-7` | `"4-7"` |
| `sortOrder` | 字段处理顺序 | `10` |
## 字段类型
| 类型 | 说明 |
|---|---|
| `UINT8` ~ `UINT64` | 无符号整数(1~8 字节) |
| `INT8` ~ `INT64` | 有符号整数(配合 `signed: true`) |
| `TEXT` | 文本,按长度读取后 UTF-8 解码 |
| `BYTES` | 原始字节数组 |
| `BITMASK` | 位掩码,配合 `bitRange` 提取位段 |
| `LENGTH_REF` | 长度引用字段(供其他字段 `lengthExpr` 引用) |
| `CHECKSUM` | 校验和 / CRC,自动计算与校验 |
| `NESTED` | 嵌套子协议帧(支持判别路由与多级嵌套) |
## 表达式语法
表达式求值器(`ExpressionEvaluator`)是**变长字段与条件字段的引擎**:YAML 中声明的 `lengthExpr` / `conditionExpr` 在编解码时由它实时求值,使帧结构定义完全数据化——协议调整只改 YAML、不改代码。典型场景:
- 帧体长度 = LEN 字段 − 帧头 5 字节:`lengthExpr: "${len} - 5"`
- 字段长度 = 剩余全部字节:`lengthExpr: remaining`
- 仅当类型为运动包时解析该字段:`conditionExpr: "${appType} == 0x00"`
支持的语法(求值失败抛 `EXPRESSION_ERROR`):
- **字段引用**:`${fieldName}`(引用同帧已解析字段或外层帧头字段)
- **数字常量**:十进制、十六进制(`0x...`)
- **特殊字面量**:`remaining`(剩余字节数,用于 `lengthExpr`)
- **算术**:`+ - * / %`
- **比较**:`== != < > <= >=`
- **逻辑**:`&& || !`
- **括号**:`( )`
示例:`"${len} - 5"`、`"${mode} == 2 && ${count} > 0"`、`"remaining - 4"`。
## 错误码
`ProtocolEngineException.ErrorCode`:
| 错误码 | 含义 |
|---|---|
| `CONFIG_ERROR` | 协议定义配置错误(YAML 缺失、未知子帧名等) |
| `INVALID_FRAME` | 帧数据非法(长度不足、校验和不匹配等) |
| `UNKNOWN_FRAME` | 未找到 type+code 对应的帧定义 |
| `INVALID_FIELD` | 字段值非法 |
| `LENGTH_EXCEEDED` | 编码内容超出 LEN 上限(65535 - 帧头) |
| `EXPRESSION_ERROR` | 表达式语法或求值错误 |
| `UNSUPPORTED_TYPE` | 不支持的字段类型 |
## 构建与测试
```bash
mvn clean test # 需要 JDK 1.8+(编译目标锁定 Java 8,字节码 major version 52)
mvn install # 安装到本地仓库,供其他模块依赖
```
- 编译目标在 `maven-compiler-plugin` 中**显式写死 source/target 1.8**,不受全局 Maven settings 的 profile 属性覆盖影响
- 单元测试:26 个用例覆盖表达式求值、YAML 加载、帧编解码(含嵌套路由、校验和、条件/变长字段)
## 依赖关系
```
┌─────────────────────┐
│ hbotsdk / hsl-iot / netty server·simulator ... (消费方)
└──────────┬──────────┘
│ 依赖
┌──────────▼──────────┐
│ leobit-protocol-sdk ← 本 SDK(独立、自包含)
└─────────────────────┘
```
SDK 对外部依赖为零传递:snakeyaml(`optional`,仅 YAML 加载需要)、Lombok(`provided`,编译期)、JUnit(`test`)。其他模块按需引入后即可使用,SDK 自身的演进不影响任何消费方。