# SoftBus 边缘上行协议 v1(EDGE_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` 边缘仓库应维护与本文档 **字节级一致** 的副本(如 `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(预留) | ## 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 BE,UTF-8 字节数,最大 512 | | 2 | 4 | device_id | i32 BE | | 6 | 4 | endpoint_hash | u32 BE | | 10 | 2 | protocol | u16 BE,与 `ProtocolType` 数值一致 | | 12 | 2 | reserved0 | u16 BE,0 | | 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:`**,例如 `can:vcan0`。设备树节点 `type` 为 `can` 或仅依赖 `endpoint` 前缀推断时,边缘/核心 `EdgeDeviceBus` / `DeviceBusManager` 使用 Qt **SocketCAN** 插件(`socketcan`)打开对应接口;`makeEndpointHash(endpoint)` 与串口一致为 `qHash(endpoint)`。 - **bus_payload**:单帧 **数据场** 原始字节(0–8 字节)。**DLC=0 的数据帧合法**;核心 `EdgeTcpIngressService` 对 `protocol == CAN_RAW` 或 `CAN_OPEN` 时允许空 `bus_payload`,仍携带 `protocol_header` 中的 CAN id 元数据。 - **protocol(u16)**:与 `ProtocolType` 枚举数值一致;CAN 上行应使用 **`CAN_RAW`**(设备树 `params.protocol` 常用字符串 `can_raw`,与 `protocolTypeFromHint` 一致)。 - **protocol_header(8 字节)**:与 `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/` 或 `edges//...` 前缀(与 §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/` 或其前缀下的子 id),否则 `FORBIDDEN_SCOPE`。 ### 6.2 作用域(MVP) 仅允许修改满足以下 **任一** 条件的节点: - `node.params.edgeId`(string)等于会话 `edgeId`;或 - `node.id` 等于 `edges/` 或以 `edges//` 为前缀。 否则整批 **拒绝**(`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`(integer,CONFLICT 时建议带):核心当前 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` 一致;本协议为 **额外入口**,不得绕过校验逻辑。