Files
softbus_daemon/docs/EDGE_UPLINK_PROTOCOL_v1.md

180 lines
10 KiB
Markdown
Raw Normal View History

2026-05-13 16:45:31 +08:00
# SoftBus 边缘上行协议 v1EDGE_UPLINK_PROTOCOL_v1
本文档定义 **边缘 `edge_agent` → 核心 `softbus_daemon`** 的 TCP 二进制帧格式,支持:
- **数据面**`DATA_RAWBUS`(载荷语义对齐 [`RawBusMessage`](file:///home/dt/myCode/bus/soft_bus/softbus_daemon/src/core/models/RawBusMessage.h)
- **控制面**:会话、`CTRL_TREE_*`**反向同步** `CTRL_TREE_PROPOSE` / `CTRL_TREE_RESULT`
2026-05-19 16:42:01 +08:00
- **调试面**`DEBUG_TUNNEL`(双向 UTF-8 JSON见 §10须已 HELLO
2026-05-13 16:45:31 +08:00
边缘仓库应维护与本文档 **字节级一致** 的副本(如 `docs/UPLINK_PROTOCOL_v1.md`)。
## 1. 传输
- **传输层**TCP由核心监听`edgeIngress.listenAddress` / `port`),边缘主动连接。
- **字节序**:多字节整数均为 **大端(网络序)**
- **帧大小上限**:由核心配置 `maxFrameBytes`(默认 4MiB强制超限则断开连接。
## 2. 外层帧(所有 `frame_kind` 共用)
| 偏移 | 长度 | 字段 | 说明 |
|------|------|------|------|
| 0 | 4 | magic | 固定 ASCII `SBUP`0x53 0x42 0x55 0x50 |
| 4 | 2 | wire_version | 当前为 **1** |
| 6 | 2 | frame_kind | 见 §3 |
| 8 | 4 | reserved | 填 0 |
| 12 | 4 | payload_len | 后续 payload 字节数 |
| 16 | payload_len | payload | 依 `frame_kind` 解析 |
## 3. frame_kind 枚举u16
| 值 | 名称 | 方向 | 说明 |
|---:|------|------|------|
| 1 | HELLO | 边缘→核心 | UTF-8 JSON见 §4 |
| 2 | ACK | 边缘→核心 | UTF-8 JSON可选应用下行树后确认 |
| 3 | DATA_RAWBUS | 边缘→核心 | 二进制,见 §5 |
| 4 | TREE_FULL | 核心→边缘 | UTF-8 JSON见 §4.1 |
| 5 | TREE_PATCH | 核心→边缘 | UTF-8 JSON预留下行 |
| 6 | TREE_PROPOSE | 边缘→核心 | UTF-8 JSON见 §6 |
| 7 | TREE_RESULT | 核心→边缘 | UTF-8 JSON见 §7 |
| 8 | REPORT | 边缘→核心 | UTF-8 JSON预留 |
2026-05-19 16:42:01 +08:00
| 9 | DEBUG_TUNNEL | **双向** | UTF-8 JSON见 §10**仅**在会话已 HELLO 后允许 |
2026-05-13 16:45:31 +08:00
## 4. HELLO边缘→核心
若核心配置了 `authToken`(非空),则 **首帧必须是 HELLO**,否则核心断开。
JSON 字段:
- `edgeId`string必填与会话绑定后续 `TREE_PROPOSE` 须与此一致。
- `authToken`string若核心配置了 token 则必填且相等)。
- `currentTreeRevision`integer可选边缘当前已应用的 `treeRevision`,供核心决定是否下行全量。
核心响应:可紧跟发送 `TREE_RESULT`**HELLO 成功后**核心会再发送一帧 **`TREE_FULL`**(与 `push_edge_tree` 下行格式相同,见 §4.1)。
## 4.1 TREE_FULL核心→边缘
UTF-8 JSON置于外层 `frame_kind=4` 的 payload 中:
- `kind`:固定字符串 `"TREE_FULL"`(便于与纯数组载荷区分)。
- `edgeId`string与会话绑定边缘应校验与本地 `edgeId` 一致。
- `treeRevision`integer导出子树时刻的**全局** `treeRevision`(与 `TREE_PROPOSE``baseTreeRevision` 语义一致)。
- `tree`array节点对象数组字段形状与核心 `get_device_tree` 中单节点一致(`id`/`name`/`type`/`endpoint`/`driver`/`capabilitiesRef`/`params`/`enabled`/`children`);仅包含 **属于该 edge 作用域** 的节点(规则同 §6.2)。
`tree` 表示核心当前未为该 edge 分配任何节点(仍合法)。
## 5. DATA_RAWBUS 的 payload二进制
**外层 payload** 内为定长头 + 变长 `traceId` + 变长 **bus payload**
| 偏移 | 长度 | 字段 | 类型 |
|------|------|------|------|
| 0 | 2 | trace_len | u16 BEUTF-8 字节数,最大 512 |
| 2 | 4 | device_id | i32 BE |
| 6 | 4 | endpoint_hash | u32 BE |
| 10 | 2 | protocol | u16 BE`ProtocolType` 数值一致 |
| 12 | 2 | reserved0 | u16 BE0 |
| 14 | 4 | logical_address | u32 BE |
| 18 | 8 | routing_key | u64 BE |
| 26 | 8 | protocol_header | 8 字节原始拷贝至 `ProtocolHeader` union |
| 34 | 8 | timestamp_ms | i64 BE |
| 42 | 1 | direction | i8`BusDirection` 枚举数值 |
| 43 | 5 | reserved1 | 填 0 |
| 48 | trace_len | trace_utf8 | UTF-8 |
| 48+trace_len | 剩余 | bus_payload | 写入 `PayloadRef` |
总长度:`48 + trace_len + bus_payload_len` 须等于外层 `payload_len`
### 5.1 SocketCAN / `CAN_RAW`(边缘与核心本机可选)
- **endpoint 字符串**:建议使用 **`can:<linux_ifname>`**,例如 `can:vcan0`。设备树节点 `type``can` 或仅依赖 `endpoint` 前缀推断时,边缘/核心 `EdgeDeviceBus` / `DeviceBusManager` 使用 Qt **SocketCAN** 插件(`socketcan`)打开对应接口;`makeEndpointHash(endpoint)` 与串口一致为 `qHash(endpoint)`
- **bus_payload**:单帧 **数据场** 原始字节08 字节)。**DLC=0 的数据帧合法**;核心 `EdgeTcpIngressService``protocol == CAN_RAW``CAN_OPEN` 时允许空 `bus_payload`,仍携带 `protocol_header` 中的 CAN id 元数据。
- **protocolu16**:与 `ProtocolType` 枚举数值一致CAN 上行应使用 **`CAN_RAW`**(设备树 `params.protocol` 常用字符串 `can_raw`,与 `protocolTypeFromHint` 一致)。
- **protocol_header8 字节)**:与 `ProtocolHeader::can` 一致:`cobId`29 位有效)、`isExtended``isRtr`;编码侧将 `RawBusMessage::header` 按 8 字节写入再参与 `DATA_RAWBUS` 定长头(与现有 Modbus 头填法相同,见实现 `encodeDataRawBusPayload`)。
## 6. TREE_PROPOSE边缘→核心反向同步
UTF-8 JSON。
### 6.1 固定字段
- `edgeId`string须与 HELLO 中一致。
- `baseTreeRevision`integer**全局树 revision**(与核心 `DeviceTreeModel::treeRevision()` 一致);不一致则 **CONFLICT**
- `entries`array必填每项为下列之一。
#### 6.1.1 `patch`(默认,与历史行为一致)
- `op` 省略或 `"patch"`
- `id`string必填须已存在于设备树。
- `patch`object必填字段规则与 D-Bus `patch_device_tree_node``patch` 相同(`name`/`type`/`endpoint`/`driver`/`capabilitiesRef`/`enabled`/`params` 对象合并)。
#### 6.1.2 `upsert`(边缘注册新节点或整节点合并)
- `op``"upsert"`
- `id`string必填目标节点 id。新建时 **必须**`edges/<edgeId>``edges/<edgeId>/...` 前缀(与 §6.2 作用域一致);已存在节点则按作用域校验后 **合并** `node` 字段。
- `node`object必填`get_device_tree` 单节点形状一致:`id`/`name`/`type`/`endpoint`/`driver`/`capabilitiesRef`/`params`/`enabled`/`children``children` 为子节点 id 字符串数组)。新建时若省略 `node.id` 则使用条目 `id`;若 `node.id` 与条目 `id` 均非空则须相等。
- **新建**:核心在 `params` 中未带 `edgeId` 时自动写入会话 `edgeId`;合并后节点须满足 §6.2。
- **`children`**:仅允许引用同一边缘子树内的 id`edges/<edgeId>` 或其前缀下的子 id否则 `FORBIDDEN_SCOPE`
### 6.2 作用域MVP
仅允许修改满足以下 **任一** 条件的节点:
- `node.params.edgeId`string等于会话 `edgeId`;或
- `node.id` 等于 `edges/<edgeId>` 或以 `edges/<edgeId>/` 为前缀。
否则整批 **拒绝**`FORBIDDEN_SCOPE`)。
### 6.3 并发
与 D-Bus `patch_device_tree_node` 共用 **`DeviceTreeModel` 写锁**,保证 revision 单调、无丢失更新。成功应用后,核心还会向该会话 **`edgeId` 的所有已 HELLO 连接** 再发一帧 **`TREE_FULL`**(与 `push_edge_tree` 载荷一致),便于边缘落盘与 revision 对齐。
## 7. TREE_RESULT核心→边缘
UTF-8 JSON
- `ok`bool
- `code`string`OK` | `CONFLICT` | `FORBIDDEN_SCOPE` | `INVALID_JSON` | `NODE_NOT_FOUND` | `VALIDATION_ERROR`
- `message`string可选
- `newTreeRevision`integer成功时应用后的 revision
- `currentTreeRevision`integerCONFLICT 时建议带):核心当前 revision
## 8. treeRevision 语义MVP
- 核心在进程内维护单调递增的 **`treeRevision`**(从 1 起,每次成功变更设备树内存模型后 +1
- 守护进程重启后 revision **重置**MVP边缘应在 HELLO 后若收到 `CONFLICT` 或 revision 不明,依赖核心自动下发的 **`TREE_FULL`**、`push_edge_tree` 运维命令,或重新下发配置。
## 9. 与 orchestrator / D-Bus
核心侧设备树持久化与 `reconcile` 行为与现有 `patch_device_tree_node` 一致;本协议为 **额外入口**,不得绕过校验逻辑。
2026-05-19 16:42:01 +08:00
## 10. DEBUG_TUNNEL`frame_kind = 9`,双向)
用于主机与边缘之间的 **调试指令与监控** 类 JSON不替代 `DATA_RAWBUS` / `TREE_*` 控制面。
### 10.1 会话要求
- **两端**均须在 **HELLO 成功之后** 才能发送本帧;核心对未 HELLO 连接上的 `DEBUG_TUNNEL` 会断开(与 `TREE_FULL` 等下行一致)。
- 载荷为 **UTF-8 JSON 对象**(建议总大小远小于 `maxFrameBytes`)。
### 10.2 推荐信封(可选)
为便于对账与过滤,建议载荷使用统一形状(实现不强制解析,可原样记录日志):
- `v`integer信封版本当前 **1**
- `from`string`"host"``"edge"`
- `tsMs`integer发送端单调时钟毫秒`QDateTime::currentMSecsSinceEpoch()`)。
- `body`object实际调试负载指令、采样快照、订阅键等由产品约定
### 10.3 主机 → 边缘
- 核心通过 `executeCommand` 的 action **`edge_debug_send`** 向指定 `edgeId` 的已 HELLO 会话 **编码一帧** `DEBUG_TUNNEL` 并写入 TCP。
- `edge_debug_send``params`**必填** `edgeId`;调试 JSON 可放在 **`debug` 对象**中,或省略 `debug` 时除 `edgeId` 外的其余 `params` 字段合并为 `body`;核心会再包一层 `v`/`from:"host"`/`tsMs`/`body` 后下行(见 `CommandDispatcher`)。
### 10.4 边缘 → 主机
- 边缘在已连接且 HELLO 后,将 JSON 对象编码为 `DEBUG_TUNNEL` 上行;核心在 `EdgeTcpIngressService` 中解析并 **打日志**(可扩展为内部总线/订阅)。
### 10.5 与边缘 Ops HTTP 的关系
- 边缘进程内 **`OpsUiHttp`** 提供本机 **`GET /debug_host`**(最近从主机收到的调试 JSON 环形缓冲)与 **`POST /debug_edge`**(将 body JSON 经 `DEBUG_TUNNEL` 发往核心),便于在现场用 `curl` 与核心侧 `edge_debug_send`**闭环联调**