edge split
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -31,6 +31,13 @@ set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
# Qt6:daemon 使用 Core/DBus/Network/SerialPort/Sql(orchestrator 已迁至 Python)
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core SerialPort Network Sql DBus)
|
||||
find_package(Qt6 QUIET COMPONENTS SerialBus)
|
||||
if(TARGET Qt6::SerialBus)
|
||||
set(SOFTBUS_DAEMON_HAVE_CAN 1)
|
||||
else()
|
||||
set(SOFTBUS_DAEMON_HAVE_CAN 0)
|
||||
message(STATUS "Qt6 SerialBus not found: building softbus_daemon without local SocketCAN devices")
|
||||
endif()
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
|
||||
@@ -182,6 +189,10 @@ qt_add_executable(softbus_daemon
|
||||
src/message_bus/ingress/IIngressPort.h
|
||||
src/message_bus/ingress/DriverIngressAdapter.h
|
||||
src/message_bus/ingress/DriverIngressAdapter.cpp
|
||||
src/message_bus/ingress/EdgeUplinkWire.h
|
||||
src/message_bus/ingress/EdgeUplinkWire.cpp
|
||||
src/message_bus/ingress/EdgeTcpIngressService.h
|
||||
src/message_bus/ingress/EdgeTcpIngressService.cpp
|
||||
src/message_bus/egress/IEgressPort.h
|
||||
src/message_bus/egress/EgressRouter.h
|
||||
src/message_bus/egress/EgressRouter.cpp
|
||||
@@ -201,6 +212,29 @@ target_link_libraries(softbus_daemon
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
if(SOFTBUS_DAEMON_HAVE_CAN)
|
||||
target_sources(softbus_daemon PRIVATE
|
||||
src/device_bus/manager/CanDeviceManager.cpp
|
||||
src/device_bus/manager/CanDeviceManager.h
|
||||
src/devices/physical/CanDevice.cpp
|
||||
src/devices/physical/CanDevice.h
|
||||
)
|
||||
target_link_libraries(softbus_daemon PRIVATE Qt6::SerialBus)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(softbus_daemon PRIVATE SOFTBUS_DAEMON_HAVE_CAN=${SOFTBUS_DAEMON_HAVE_CAN})
|
||||
|
||||
qt_add_executable(edge_uplink_wire_test
|
||||
tests/edge_uplink_wire_test.cpp
|
||||
src/message_bus/ingress/EdgeUplinkWire.cpp
|
||||
)
|
||||
target_include_directories(edge_uplink_wire_test
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
)
|
||||
target_link_libraries(edge_uplink_wire_test PRIVATE Qt6::Core)
|
||||
|
||||
# Export daemon symbols so runtime-loaded protocol plugins can resolve
|
||||
# shared singleton APIs (e.g. DiscoveryPool/MappingRegistry) from host process.
|
||||
# if(UNIX AND NOT APPLE)
|
||||
|
||||
BIN
bin/edge_uplink_wire_test
Normal file
BIN
bin/edge_uplink_wire_test
Normal file
Binary file not shown.
Binary file not shown.
19
docs/EDGE_LOCAL_SETUP.md
Normal file
19
docs/EDGE_LOCAL_SETUP.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 边缘上行与核心本地联调
|
||||
|
||||
## 前提
|
||||
|
||||
- 已编译 `softbus_daemon`(含 `EdgeTcpIngressService`)。
|
||||
- 在 `~/softbus/config/daemon_config.json` 中启用 **`edgeIngress.enabled`**,并设置 **`port`**(默认 `18765`)。若设置 **`authToken`**,边缘首帧 HELLO 必须携带相同 token。
|
||||
- 设备树中需存在可被边缘修改的节点:满足 `params.edgeId` 等于边缘 `edgeId`,或节点 `id` 为 `edges/<edgeId>` / `edges/<edgeId>/...` 前缀(见 `DeviceTreeModel::applyEdgePropose`)。
|
||||
|
||||
## 快速验证
|
||||
|
||||
1. 启动核心:`softbus_daemon`(`CoreRouter` 角色,默认)。
|
||||
2. 运行单元测试(线格式):`bin/edge_uplink_wire_test`。
|
||||
3. 使用 `edge_agent`(独立仓库)连接核心、发送 HELLO 与可选 `DATA_RAWBUS` / `TREE_PROPOSE`(见该仓库 README)。
|
||||
|
||||
## D-Bus 辅助命令
|
||||
|
||||
- `list_edge_sessions`:查看当前边缘 TCP 会话摘要。
|
||||
- `get_edge_tree_revision`:当前内存设备树 `treeRevision`。
|
||||
- `push_edge_tree`:向指定 `edgeId` 的已连接边缘 TCP 会话发送 **TREE_FULL**(子树 JSON,规则同 `DeviceTreeModel::exportSubtreeForEdge`)。HELLO 成功后核心也会自动推送一次。**`patch_device_tree_node`** 若修改的节点属于某边缘作用域(`params.edgeId` 或 `edges/<edgeId>/...`),响应里会带 **`edgeTreeDownlink`** 并尝试对该 edge 自动下行(无会话时 `ok=false` 等见各元素 `error` 字段)。
|
||||
146
docs/EDGE_UPLINK_PROTOCOL_v1.md
Normal file
146
docs/EDGE_UPLINK_PROTOCOL_v1.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 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:<linux_ifname>`**,例如 `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/<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`(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` 一致;本协议为 **额外入口**,不得绕过校验逻辑。
|
||||
7
main.cpp
7
main.cpp
@@ -38,7 +38,9 @@ int main(int argc, char *argv[])
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption roleOption(QStringLiteral("role"),
|
||||
QStringLiteral("Daemon role (CoreRouter, CollectorSerial, CollectorCan, Collector, SqlServer)"),
|
||||
QStringLiteral("Daemon role. CoreRouter: full daemon + D-Bus. "
|
||||
"Collector* roles are legacy/in-process collectors; "
|
||||
"prefer standalone softbus_edge_agent repo for Ethernet edge."),
|
||||
QStringLiteral("role"),
|
||||
QStringLiteral("CoreRouter"));
|
||||
parser.addOption(roleOption);
|
||||
@@ -65,7 +67,8 @@ int main(int argc, char *argv[])
|
||||
role = CoreService::Role::SqlServer;
|
||||
} else {
|
||||
LOG_ERROR() << "Invalid role:" << roleString
|
||||
<< "valid roles are: CoreRouter, CollectorSerial, CollectorCan, Collector, SqlServer";
|
||||
<< "valid roles are: CoreRouter, CollectorSerial, CollectorCan, Collector, SqlServer "
|
||||
"(Ethernet edge uses separate softbus_edge_agent; see docs/EDGE_UPLINK_PROTOCOL_v1.md)";
|
||||
return 1;
|
||||
}
|
||||
// 开启coreserive的初始化工作|线程
|
||||
|
||||
@@ -76,7 +76,11 @@
|
||||
| `get_device_tree` | 无 | `0` | `5003` | 返回 `tree` |
|
||||
| `list_devices` | 无 | `0` | - | 返回 `devices` |
|
||||
| `get_device` | `deviceId` | `0` | `4044` | 单设备 JSON;未找到 `device_not_found` |
|
||||
| `patch_device_tree_node` | `id`, `patch`(字段同设备树节点) | `0` | `4045`/`5003` | 更新节点并协调;`node_not_found` |
|
||||
| `patch_device_tree_node` | `id`, `patch`(字段同设备树节点) | `0` | `4045`/`5003` | 更新节点并协调;成功时可选带 `edgeTreeDownlink`(对 `params.edgeId` / `edges/<id>/` 作用域自动 `TREE_FULL`) |
|
||||
| `list_edge_sessions` | 无 | `0` | - | 返回边缘 TCP 会话 `sessions`(`edgeId`/`helloOk`/`peer`) |
|
||||
| `get_edge_tree_revision` | 无 | `0` | - | 返回 `treeRevision`(与边缘 `CTRL_TREE_PROPOSE` 乐观锁对齐) |
|
||||
| `push_edge_tree` | `edgeId` | `0` | `4000`/`4047`/`5003`/`5005` | 向已 HELLO 的会话下行 `TREE_FULL`;成功返回 `sent`、`treeRevision`;无在线会话 `no_active_session`;ingress 未启用 `edge_ingress_disabled` |
|
||||
| (edge TCP)`TREE_PROPOSE` | JSON:`edgeId`、`baseTreeRevision`、`entries` | - | `CONFLICT`/`FORBIDDEN_SCOPE` 等 | 与 D-Bus 共用 `DeviceTreeModel::applyEdgePropose`;`entries` 支持 `op` 省略/`patch` 与 **`op: upsert`**(边缘子树内新增或整节点合并),见 `docs/EDGE_UPLINK_PROTOCOL_v1.md` §6 |
|
||||
| `get_plugins` | 无 | `0` | - | 合并 protocol/framer/mapper 插件列表 |
|
||||
| `get_plugins_protocols` | 无 | `0` | - | `protocols` |
|
||||
| `get_plugins_mappers` | 无 | `0` | - | `mappers` |
|
||||
@@ -111,10 +115,12 @@
|
||||
| `5002` | `persist_mappings_failed` | 映射规则内存更新成功但写入 `runtime_mappings.json` 失败 |
|
||||
| `5003` | `pipeline_unavailable` / `device_tree_unavailable` | Pipeline 或设备树模型未就绪 |
|
||||
| `5004` | `metadata_*` | 元数据字典读写/解析/保存失败 |
|
||||
| `5005` | `edge_ingress_disabled` / `edge_ingress_unavailable` | 边缘 TCP ingress 未启用或未初始化 |
|
||||
| `4005` | `invalid_metadata` | `upsert_metadata` 缺少 `metadataId` |
|
||||
| `4044` | `device_not_found` | `get_device` 未找到 |
|
||||
| `4045` | `node_not_found` | `patch_device_tree_node` 未找到节点 |
|
||||
| `4046` | `metadata_not_found` | `get_metadata` / `delete_metadata` 未找到 |
|
||||
| `4047` | `no_active_session` | `push_edge_tree` 时该 `edgeId` 无已 HELLO 的 TCP 会话 |
|
||||
|
||||
## 7) 维护建议
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMutexLocker>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "core/metadata/MetadataRegistry.h"
|
||||
@@ -366,6 +367,45 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("tree"), model->toDeviceTreeJson()}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_edge_sessions")) {
|
||||
const QJsonArray arr = softbus::device_bus::DeviceBus::instance().listEdgeIngressSessions();
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("sessions"), arr}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_edge_tree_revision")) {
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("treeRevision"),
|
||||
softbus::device_bus::DeviceBus::instance().deviceTreeRevision()}}};
|
||||
}
|
||||
if (action == QStringLiteral("push_edge_tree")) {
|
||||
const QString edgeId = params.value(QStringLiteral("edgeId")).toString();
|
||||
if (edgeId.isEmpty()) {
|
||||
return {4000, QStringLiteral("missing_edgeId"), {}};
|
||||
}
|
||||
int sent = 0;
|
||||
QString err;
|
||||
const bool ok = softbus::device_bus::DeviceBus::instance().pushEdgeTreeFullDownlink(edgeId, &sent, &err);
|
||||
if (!ok) {
|
||||
int code = 5003;
|
||||
if (err == QStringLiteral("no_active_session")) {
|
||||
code = 4047;
|
||||
} else if (err == QStringLiteral("missing_edgeId")) {
|
||||
code = 4000;
|
||||
} else if (err == QStringLiteral("edge_ingress_disabled") || err == QStringLiteral("edge_ingress_unavailable")) {
|
||||
code = 5005;
|
||||
}
|
||||
return {code, err, QJsonObject{{QStringLiteral("edgeId"), edgeId}}};
|
||||
}
|
||||
qint64 rev = 0;
|
||||
if (auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel()) {
|
||||
rev = model->treeRevision();
|
||||
}
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("edgeId"), edgeId},
|
||||
{QStringLiteral("sent"), sent},
|
||||
{QStringLiteral("treeRevision"), rev}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_devices")) {
|
||||
const auto devices = softbus::device_bus::DeviceBus::instance().listDevices();
|
||||
QJsonArray arr;
|
||||
@@ -389,42 +429,39 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
|
||||
if (!model) {
|
||||
return {5003, QStringLiteral("device_tree_unavailable"), {}};
|
||||
}
|
||||
QMutexLocker locker(&model->writeMutex());
|
||||
const int idx = model->findIndexById(id);
|
||||
if (idx < 0) {
|
||||
return {4045, QStringLiteral("node_not_found"), {}};
|
||||
}
|
||||
DeviceTreeNode& node = model->nodesMutable()[idx];
|
||||
for (auto it = patch.constBegin(); it != patch.constEnd(); ++it) {
|
||||
const QString k = it.key();
|
||||
if (k == QStringLiteral("params") && it.value().isObject()) {
|
||||
QJsonObject p = node.params;
|
||||
const QJsonObject patchParams = it.value().toObject();
|
||||
for (auto pit = patchParams.constBegin(); pit != patchParams.constEnd(); ++pit) {
|
||||
const QString pk = pit.key();
|
||||
if (pk == QStringLiteral("stableKey") || pk == QStringLiteral("online") || pk == QStringLiteral("lastSeenTs")) {
|
||||
continue;
|
||||
}
|
||||
p.insert(pk, pit.value());
|
||||
}
|
||||
node.params = p;
|
||||
} else if (k == QStringLiteral("name")) {
|
||||
node.name = it.value().toString();
|
||||
} else if (k == QStringLiteral("type")) {
|
||||
node.type = it.value().toString();
|
||||
} else if (k == QStringLiteral("endpoint")) {
|
||||
node.endpoint = it.value().toString();
|
||||
} else if (k == QStringLiteral("driver")) {
|
||||
node.driver = it.value().toString();
|
||||
} else if (k == QStringLiteral("capabilitiesRef")) {
|
||||
node.capabilitiesRef = it.value().toString();
|
||||
} else if (k == QStringLiteral("enabled")) {
|
||||
node.enabled = it.value().toBool(node.enabled);
|
||||
}
|
||||
if (!model->applyPatchToNodeById(id, patch)) {
|
||||
return {4045, QStringLiteral("node_not_found"), {}};
|
||||
}
|
||||
const DeviceTreeNode& patchedNode = model->nodes().at(idx);
|
||||
const QVector<QString> edgePushTargets = model->downlinkEdgeIdsForNode(patchedNode);
|
||||
model->rebuildIndex();
|
||||
model->bumpTreeRevision();
|
||||
locker.unlock();
|
||||
softbus::device_bus::DeviceBus::instance().markDeviceTreeDirty();
|
||||
(void)softbus::device_bus::DeviceBus::instance().reconcileFromTreeModel();
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), id}}};
|
||||
QJsonObject data{{QStringLiteral("id"), id}};
|
||||
if (!edgePushTargets.isEmpty()) {
|
||||
QJsonArray downlinkArr;
|
||||
for (const QString& eid : edgePushTargets) {
|
||||
int sent = 0;
|
||||
QString err;
|
||||
const bool ok = softbus::device_bus::DeviceBus::instance().pushEdgeTreeFullDownlink(eid, &sent, &err);
|
||||
QJsonObject one{{QStringLiteral("edgeId"), eid},
|
||||
{QStringLiteral("ok"), ok},
|
||||
{QStringLiteral("sent"), sent}};
|
||||
if (!ok && !err.isEmpty()) {
|
||||
one.insert(QStringLiteral("error"), err);
|
||||
}
|
||||
downlinkArr.append(one);
|
||||
}
|
||||
data.insert(QStringLiteral("edgeTreeDownlink"), downlinkArr);
|
||||
}
|
||||
return {0, QStringLiteral("ok"), data};
|
||||
}
|
||||
if (action == QStringLiteral("get_plugins")) {
|
||||
const auto pm = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
|
||||
@@ -68,6 +68,13 @@ bool CoreService::initialize()
|
||||
{QStringLiteral("ipcSocketName"), QStringLiteral("softbus_orchestrator_bus")},
|
||||
{QStringLiteral("ipcOutboxPath"), QDir::homePath() + QStringLiteral("/softbus/run/dom_outbox.ndjson")}
|
||||
}},
|
||||
{QStringLiteral("edgeIngress"), QJsonObject{
|
||||
{QStringLiteral("enabled"), false},
|
||||
{QStringLiteral("listenAddress"), QStringLiteral("0.0.0.0")},
|
||||
{QStringLiteral("port"), 18765},
|
||||
{QStringLiteral("maxPayloadBytes"), 4 * 1024 * 1024},
|
||||
{QStringLiteral("authToken"), QString()}
|
||||
}},
|
||||
{QStringLiteral("plugins"), QJsonObject{}},
|
||||
{QStringLiteral("device_tree"), QJsonArray{}}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace softbus::core::models
|
||||
|
||||
enum class DataDomain : std::uint8_t
|
||||
{
|
||||
PROCESS_PARAM = 0, // 过程参数
|
||||
PROCESS_PARAM = 0, // 过程参 数
|
||||
CONTROL_CMD = 1, // 控制指令
|
||||
MONITOR_DATA = 2, // 监控数据
|
||||
MANAGE_DATA = 3, // 管理数据
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QSaveFile>
|
||||
|
||||
@@ -196,4 +197,31 @@ void DeviceBus::markDeviceTreeDirty() {
|
||||
d->deviceTreeDirty = true;
|
||||
}
|
||||
|
||||
QJsonArray DeviceBus::listEdgeIngressSessions() const {
|
||||
if (!d || !d->manager) {
|
||||
return {};
|
||||
}
|
||||
return d->manager->listEdgeIngressSessions();
|
||||
}
|
||||
|
||||
bool DeviceBus::pushEdgeTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut) const {
|
||||
if (!d || !d->manager) {
|
||||
if (sentCountOut) {
|
||||
*sentCountOut = 0;
|
||||
}
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("device_bus_unavailable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return d->manager->pushEdgeTreeFullDownlink(edgeId, sentCountOut, errorCodeOut);
|
||||
}
|
||||
|
||||
qint64 DeviceBus::deviceTreeRevision() const {
|
||||
if (!d || !d->treeModel) {
|
||||
return 0;
|
||||
}
|
||||
return d->treeModel->treeRevision();
|
||||
}
|
||||
|
||||
} // namespace softbus::device_bus
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -44,6 +45,12 @@ public:
|
||||
// 注册/更新设备树后调用,确保 shutdown 会落盘
|
||||
void markDeviceTreeDirty();
|
||||
|
||||
QJsonArray listEdgeIngressSessions() const;
|
||||
qint64 deviceTreeRevision() const;
|
||||
|
||||
/** 向已连接的 edge 会话下行 TREE_FULL;成功表示至少一路送达。 */
|
||||
bool pushEdgeTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut) const;
|
||||
|
||||
private:
|
||||
DeviceBus();
|
||||
~DeviceBus();
|
||||
|
||||
110
src/device_bus/manager/CanDeviceManager.cpp
Normal file
110
src/device_bus/manager/CanDeviceManager.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "device_bus/manager/CanDeviceManager.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QCanBus>
|
||||
#include <QCanBusDevice>
|
||||
|
||||
#include "device_bus/registry/IDeviceRegistry.h"
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static bool setIfChanged(QJsonObject& params, const QString& key, const QJsonValue& value)
|
||||
{
|
||||
const auto it = params.find(key);
|
||||
if (it == params.end() || it.value() != value) {
|
||||
params.insert(key, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool persistCanBitrate(DeviceTreeModel* treeModel,
|
||||
const QString& endpoint,
|
||||
int bitrate,
|
||||
const std::function<void()>& dirtyCb)
|
||||
{
|
||||
if (!treeModel || endpoint.isEmpty() || bitrate <= 0) {
|
||||
return false;
|
||||
}
|
||||
const int idx = treeModel->findIndexByEndpoint(endpoint);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
auto& nodes = treeModel->nodesMutable();
|
||||
if (idx >= static_cast<int>(nodes.size())) {
|
||||
return false;
|
||||
}
|
||||
auto& p = nodes[idx].params;
|
||||
bool changed = setIfChanged(p, QStringLiteral("bitrate"), bitrate);
|
||||
if (changed && dirtyCb) {
|
||||
dirtyCb();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool initializeCanDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const CanInitDeps& deps)
|
||||
{
|
||||
if (!deps.canDevicesByEndpoint || !deps.ingressAdapter || !deps.memoryPool) {
|
||||
return false;
|
||||
}
|
||||
if (deps.canDevicesByEndpoint->contains(endpoint)) {
|
||||
LOG_WARNING() << "DeviceBusManager: CAN device already exists for" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
int bitrate = params.value(QStringLiteral("bitrate")).toInt(500'000);
|
||||
if (bitrate <= 0) {
|
||||
bitrate = 500'000;
|
||||
}
|
||||
persistCanBitrate(deps.treeModel, endpoint, bitrate, deps.dirtyCb);
|
||||
|
||||
QString iface = endpoint.trimmed();
|
||||
if (iface.startsWith(QStringLiteral("can:"), Qt::CaseInsensitive)) {
|
||||
iface = iface.mid(4);
|
||||
}
|
||||
QString err;
|
||||
std::unique_ptr<QCanBusDevice> bus(QCanBus::instance()->createDevice(QStringLiteral("socketcan"), iface, &err));
|
||||
if (!bus) {
|
||||
LOG_ERROR() << "CanDeviceManager: createDevice failed" << endpoint << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
softbus::devices::DeviceInfo info;
|
||||
info.id = -1;
|
||||
info.endpoint = endpoint;
|
||||
info.type = QStringLiteral("can");
|
||||
info.kind = softbus::devices::DeviceKind::Can;
|
||||
info.protocol = params.value(QStringLiteral("protocol")).toString(QStringLiteral("can_raw"));
|
||||
|
||||
softbus::devices::physical::CanDevice::Config cfg;
|
||||
cfg.endpoint = endpoint;
|
||||
cfg.bitrate = bitrate;
|
||||
|
||||
QSharedPointer<softbus::devices::physical::CanDevice> device(
|
||||
new softbus::devices::physical::CanDevice(std::move(bus), cfg, info));
|
||||
device->bindIngress(deps.ingressAdapter, deps.memoryPool);
|
||||
|
||||
if (deps.registry) {
|
||||
deps.registry->registerDevice(info);
|
||||
}
|
||||
|
||||
if (!device->start()) {
|
||||
LOG_ERROR() << "CanDeviceManager: connectDevice failed" << endpoint;
|
||||
return false;
|
||||
}
|
||||
|
||||
deps.canDevicesByEndpoint->insert(endpoint, device);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
34
src/device_bus/manager/CanDeviceManager.h
Normal file
34
src/device_bus/manager/CanDeviceManager.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "devices/physical/CanDevice.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
|
||||
namespace softbus::device_bus::registry { class IDeviceRegistry; }
|
||||
|
||||
class DeviceTreeModel;
|
||||
|
||||
namespace softbus::device_bus::manager
|
||||
{
|
||||
|
||||
struct CanInitDeps
|
||||
{
|
||||
DeviceTreeModel* treeModel{nullptr};
|
||||
std::function<void()> dirtyCb;
|
||||
softbus::device_bus::registry::IDeviceRegistry* registry{nullptr};
|
||||
softbus::message_bus::ingress::IIngressPort* ingressAdapter{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> memoryPool;
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::CanDevice>>* canDevicesByEndpoint{nullptr};
|
||||
};
|
||||
|
||||
bool initializeCanDeviceWithDeps(const QString& endpoint, const QJsonObject& params, const CanInitDeps& deps);
|
||||
|
||||
} // namespace softbus::device_bus::manager
|
||||
@@ -15,12 +15,16 @@
|
||||
#include "device_bus/DeviceBus.h"
|
||||
#include "message_bus/egress/EgressRouter.h"
|
||||
#include "message_bus/ingress/DriverIngressAdapter.h"
|
||||
#include "message_bus/ingress/EdgeTcpIngressService.h"
|
||||
#include "message_bus/pipeline/PipelineEngine.h"
|
||||
#include "core/metadata/ProfileRegistry.h"
|
||||
#include "message_bus/pipeline/mapping/MappingRegistry.h"
|
||||
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
|
||||
#include "core/plugin_system/PluginManager.h"
|
||||
#include "device_bus/manager/SerialDeviceManager.h"
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
#include "device_bus/manager/CanDeviceManager.h"
|
||||
#endif
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
@@ -343,6 +347,28 @@ bool DeviceBusManager::initialize(const QJsonObject& rootConfig)
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonObject edgeIngressObj = rootConfig.value(QStringLiteral("edgeIngress")).toObject();
|
||||
softbus::message_bus::ingress::EdgeTcpIngressService::Config edgeCfg;
|
||||
edgeCfg.enabled = edgeIngressObj.value(QStringLiteral("enabled")).toBool(false);
|
||||
edgeCfg.listenAddress = edgeIngressObj.value(QStringLiteral("listenAddress")).toString(QStringLiteral("0.0.0.0"));
|
||||
edgeCfg.port = edgeIngressObj.value(QStringLiteral("port")).toInt(18765);
|
||||
edgeCfg.maxPayloadBytes = edgeIngressObj.value(QStringLiteral("maxPayloadBytes")).toInt(4 * 1024 * 1024);
|
||||
edgeCfg.authToken = edgeIngressObj.value(QStringLiteral("authToken")).toString();
|
||||
m_edgeTcpIngress = std::make_unique<softbus::message_bus::ingress::EdgeTcpIngressService>();
|
||||
softbus::message_bus::ingress::EdgeTcpIngressService::Deps edgeDeps;
|
||||
edgeDeps.ingress = m_ingressAdapter.get();
|
||||
edgeDeps.pool = m_memoryPool;
|
||||
edgeDeps.tree = m_treeModel;
|
||||
edgeDeps.markDeviceTreeDirty = [this]() {
|
||||
if (m_dirtyCb) {
|
||||
m_dirtyCb();
|
||||
}
|
||||
};
|
||||
edgeDeps.reconcileFromTreeModel = [this]() { reconcileFromTreeModel(); };
|
||||
if (!m_edgeTcpIngress->start(edgeCfg, edgeDeps)) {
|
||||
LOG_WARNING() << "DeviceBusManager: EdgeTcpIngressService start failed";
|
||||
}
|
||||
|
||||
const auto nodes = m_treeModel->nodes();
|
||||
for (const auto& n : nodes) {
|
||||
if (!n.enabled) continue;
|
||||
@@ -355,6 +381,11 @@ bool DeviceBusManager::initialize(const QJsonObject& rootConfig)
|
||||
if (kind == softbus::devices::DeviceKind::Serial) {
|
||||
initializeSerialDevice(n.endpoint, n.params);
|
||||
m_deviceEndpointsMap.insert(n.endpoint, kind);
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
} else if (kind == softbus::devices::DeviceKind::Can) {
|
||||
initializeCanDevice(n.endpoint, n.params);
|
||||
m_deviceEndpointsMap.insert(n.endpoint, kind);
|
||||
#endif
|
||||
} else {
|
||||
LOG_WARNING() << "DeviceBusManager: skip unsupported kind for endpoint=" << n.endpoint
|
||||
<< "type=" << n.type;
|
||||
@@ -371,6 +402,9 @@ bool DeviceBusManager::reconcileFromTreeModel()
|
||||
}
|
||||
|
||||
QSet<QString> desiredSerialEndpoints;
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
QSet<QString> desiredCanEndpoints;
|
||||
#endif
|
||||
const auto nodes = m_treeModel->nodes();
|
||||
for (const auto& n : nodes) {
|
||||
if (!n.enabled) continue;
|
||||
@@ -387,6 +421,16 @@ bool DeviceBusManager::reconcileFromTreeModel()
|
||||
initializeSerialDevice(n.endpoint, n.params);
|
||||
}
|
||||
m_deviceEndpointsMap.insert(n.endpoint, kind);
|
||||
} else if (kind == softbus::devices::DeviceKind::Can) {
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
desiredCanEndpoints.insert(n.endpoint);
|
||||
if (!m_canDevicesByEndpointSharedPtr.contains(n.endpoint)) {
|
||||
initializeCanDevice(n.endpoint, n.params);
|
||||
}
|
||||
m_deviceEndpointsMap.insert(n.endpoint, kind);
|
||||
#else
|
||||
(void)n;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,22 +442,42 @@ bool DeviceBusManager::reconcileFromTreeModel()
|
||||
m_deviceEndpointsMap.remove(endpoint);
|
||||
}
|
||||
}
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
const auto currentCan = m_canDevicesByEndpointSharedPtr.keys();
|
||||
for (const auto& endpoint : currentCan) {
|
||||
if (!desiredCanEndpoints.contains(endpoint)) {
|
||||
shutdownCanDevice(endpoint);
|
||||
m_deviceEndpointsMap.remove(endpoint);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceBusManager::shutdown()
|
||||
{
|
||||
if (m_edgeTcpIngress) {
|
||||
m_edgeTcpIngress->stop();
|
||||
m_edgeTcpIngress.reset();
|
||||
}
|
||||
|
||||
m_egressRunning.store(false);
|
||||
if (m_egressThread.joinable()) {
|
||||
m_egressThread.join();
|
||||
}
|
||||
|
||||
// 关闭并销毁所有串口设备
|
||||
// 关闭并销毁所有串口与 CAN 设备
|
||||
const auto keys = m_serialDevicesByEndpointSharedPtr.keys();
|
||||
for (const auto& endpoint : keys) {
|
||||
shutdownSerialDevice(endpoint);
|
||||
}
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
const auto canKeys = m_canDevicesByEndpointSharedPtr.keys();
|
||||
for (const auto& endpoint : canKeys) {
|
||||
shutdownCanDevice(endpoint);
|
||||
}
|
||||
#endif
|
||||
if (m_pipelineEngine) {
|
||||
m_pipelineEngine->stop();
|
||||
m_pipelineEngine->drain();
|
||||
@@ -446,6 +510,28 @@ std::shared_ptr<softbus::core::plugin_system::PluginManager> DeviceBusManager::p
|
||||
return m_pluginManager;
|
||||
}
|
||||
|
||||
QJsonArray DeviceBusManager::listEdgeIngressSessions() const
|
||||
{
|
||||
if (!m_edgeTcpIngress) {
|
||||
return {};
|
||||
}
|
||||
return m_edgeTcpIngress->sessionsSnapshot();
|
||||
}
|
||||
|
||||
bool DeviceBusManager::pushEdgeTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut)
|
||||
{
|
||||
if (!m_edgeTcpIngress) {
|
||||
if (sentCountOut) {
|
||||
*sentCountOut = 0;
|
||||
}
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("edge_ingress_unavailable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return m_edgeTcpIngress->pushTreeFullDownlink(edgeId, sentCountOut, errorCodeOut);
|
||||
}
|
||||
|
||||
void DeviceBusManager::initializeSerialDevice(const QString& endpoint, const QJsonObject& params)
|
||||
{
|
||||
softbus::device_bus::manager::SerialInitDeps deps;
|
||||
@@ -481,6 +567,37 @@ void DeviceBusManager::shutdownSerialDevice(const QString& endpoint)
|
||||
m_serialDevicesByEndpointSharedPtr.erase(it);
|
||||
}
|
||||
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
void DeviceBusManager::initializeCanDevice(const QString& endpoint, const QJsonObject& params)
|
||||
{
|
||||
softbus::device_bus::manager::CanInitDeps deps;
|
||||
deps.treeModel = m_treeModel.get();
|
||||
deps.dirtyCb = m_dirtyCb;
|
||||
deps.registry = m_registry;
|
||||
deps.ingressAdapter = m_ingressAdapter.get();
|
||||
deps.memoryPool = m_memoryPool;
|
||||
deps.canDevicesByEndpoint = &m_canDevicesByEndpointSharedPtr;
|
||||
(void)softbus::device_bus::manager::initializeCanDeviceWithDeps(endpoint, params, deps);
|
||||
}
|
||||
|
||||
void DeviceBusManager::shutdownCanDevice(const QString& endpoint)
|
||||
{
|
||||
auto it = m_canDevicesByEndpointSharedPtr.find(endpoint);
|
||||
if (it == m_canDevicesByEndpointSharedPtr.end()) {
|
||||
return;
|
||||
}
|
||||
auto device = it.value();
|
||||
if (device) {
|
||||
auto info = device->deviceInfo();
|
||||
if (m_registry && info.id >= 0) {
|
||||
m_registry->unregisterDevice(info.id);
|
||||
}
|
||||
device->stop();
|
||||
}
|
||||
m_canDevicesByEndpointSharedPtr.erase(it);
|
||||
}
|
||||
#endif
|
||||
|
||||
void DeviceBusManager::processEgressLoop()
|
||||
{
|
||||
while (m_egressRunning.load()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QList>
|
||||
@@ -18,8 +19,12 @@
|
||||
#include "device_bus/registry/IDeviceRegistry.h"
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "devices/physical/SerialDevice.h"
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
#include "devices/physical/CanDevice.h"
|
||||
#endif
|
||||
#include "message_bus/egress/EgressRouter.h"
|
||||
#include "message_bus/ingress/DriverIngressAdapter.h"
|
||||
#include "message_bus/ingress/EdgeTcpIngressService.h"
|
||||
#include "message_bus/pipeline/PipelineEngine.h"
|
||||
|
||||
class DeviceBusManager
|
||||
@@ -39,9 +44,16 @@ public:
|
||||
softbus::message_bus::pipeline::PipelineEngine* pipelineEngine() const;
|
||||
std::shared_ptr<softbus::core::plugin_system::PluginManager> pluginManager() const;
|
||||
|
||||
QJsonArray listEdgeIngressSessions() const;
|
||||
bool pushEdgeTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut);
|
||||
|
||||
private:
|
||||
void initializeSerialDevice(const QString& endpoint, const QJsonObject& params);
|
||||
void shutdownSerialDevice(const QString& endpoint);
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
void initializeCanDevice(const QString& endpoint, const QJsonObject& params);
|
||||
void shutdownCanDevice(const QString& endpoint);
|
||||
#endif
|
||||
void processEgressLoop();
|
||||
// void initializeCanDriver();
|
||||
// void shutdownCanDriver();
|
||||
@@ -58,6 +70,9 @@ private:
|
||||
QMap<QString, softbus::devices::DeviceKind> m_deviceEndpointsMap;
|
||||
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::SerialDevice>> m_serialDevicesByEndpointSharedPtr;
|
||||
#if SOFTBUS_DAEMON_HAVE_CAN
|
||||
QHash<QString, QSharedPointer<softbus::devices::physical::CanDevice>> m_canDevicesByEndpointSharedPtr;
|
||||
#endif
|
||||
|
||||
softbus::device_bus::registry::IDeviceRegistry* m_registry = nullptr;
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_memoryPool;
|
||||
@@ -65,6 +80,7 @@ private:
|
||||
std::unique_ptr<softbus::core::plugin_system::PluginHost> m_pluginHost;
|
||||
std::unique_ptr<softbus::message_bus::pipeline::PipelineEngine> m_pipelineEngine;
|
||||
std::unique_ptr<softbus::message_bus::ingress::DriverIngressAdapter> m_ingressAdapter;
|
||||
std::unique_ptr<softbus::message_bus::ingress::EdgeTcpIngressService> m_edgeTcpIngress;
|
||||
std::unique_ptr<softbus::message_bus::egress::EgressRouter> m_egressRouter;
|
||||
std::atomic<bool> m_egressRunning{false};
|
||||
std::thread m_egressThread;
|
||||
|
||||
@@ -1,10 +1,134 @@
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void applyJsonPatchToDeviceTreeNode(DeviceTreeNode& node, const QJsonObject& patch)
|
||||
{
|
||||
for (auto it = patch.constBegin(); it != patch.constEnd(); ++it) {
|
||||
const QString k = it.key();
|
||||
if (k == QStringLiteral("params") && it.value().isObject()) {
|
||||
QJsonObject p = node.params;
|
||||
const QJsonObject patchParams = it.value().toObject();
|
||||
for (auto pit = patchParams.constBegin(); pit != patchParams.constEnd(); ++pit) {
|
||||
const QString pk = pit.key();
|
||||
if (pk == QStringLiteral("stableKey") || pk == QStringLiteral("online") || pk == QStringLiteral("lastSeenTs")) {
|
||||
continue;
|
||||
}
|
||||
p.insert(pk, pit.value());
|
||||
}
|
||||
node.params = p;
|
||||
} else if (k == QStringLiteral("name")) {
|
||||
node.name = it.value().toString();
|
||||
} else if (k == QStringLiteral("type")) {
|
||||
node.type = it.value().toString();
|
||||
} else if (k == QStringLiteral("endpoint")) {
|
||||
node.endpoint = it.value().toString();
|
||||
} else if (k == QStringLiteral("driver")) {
|
||||
node.driver = it.value().toString();
|
||||
} else if (k == QStringLiteral("capabilitiesRef")) {
|
||||
node.capabilitiesRef = it.value().toString();
|
||||
} else if (k == QStringLiteral("enabled")) {
|
||||
node.enabled = it.value().toBool(node.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool idAllowedForEdgeProposeNew(const QString& id, const QString& edgeId)
|
||||
{
|
||||
if (id.isEmpty() || edgeId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const QString rootId = QStringLiteral("edges/%1").arg(edgeId);
|
||||
const QString prefix = rootId + QLatin1Char('/');
|
||||
return (id == rootId) || id.startsWith(prefix);
|
||||
}
|
||||
|
||||
bool childrenIdsAllowedForEdge(const QVector<QString>& children, const QString& edgeId)
|
||||
{
|
||||
const QString rootId = QStringLiteral("edges/%1").arg(edgeId);
|
||||
const QString prefix = rootId + QLatin1Char('/');
|
||||
for (const QString& c : children) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!(c == rootId || c.startsWith(prefix))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DeviceTreeNode deviceTreeNodeFromJsonObject(const QJsonObject& obj)
|
||||
{
|
||||
DeviceTreeNode node;
|
||||
node.id = obj.value(QStringLiteral("id")).toString();
|
||||
node.name = obj.value(QStringLiteral("name")).toString();
|
||||
node.type = obj.value(QStringLiteral("type")).toString();
|
||||
node.endpoint = obj.value(QStringLiteral("endpoint")).toString();
|
||||
node.driver = obj.value(QStringLiteral("driver")).toString();
|
||||
node.capabilitiesRef = obj.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
node.params = obj.value(QStringLiteral("params")).toObject();
|
||||
node.enabled = obj.value(QStringLiteral("enabled")).toBool(true);
|
||||
const QJsonValue ch = obj.value(QStringLiteral("children"));
|
||||
if (ch.isArray()) {
|
||||
for (const QJsonValue& cv : ch.toArray()) {
|
||||
if (cv.isString()) {
|
||||
node.childrenIds.push_back(cv.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void mergeUpsertJsonIntoNode(DeviceTreeNode& node, const QJsonObject& src)
|
||||
{
|
||||
if (src.contains(QStringLiteral("name"))) {
|
||||
node.name = src.value(QStringLiteral("name")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("type"))) {
|
||||
node.type = src.value(QStringLiteral("type")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("endpoint"))) {
|
||||
node.endpoint = src.value(QStringLiteral("endpoint")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("driver"))) {
|
||||
node.driver = src.value(QStringLiteral("driver")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("capabilitiesRef"))) {
|
||||
node.capabilitiesRef = src.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
}
|
||||
if (src.contains(QStringLiteral("enabled"))) {
|
||||
node.enabled = src.value(QStringLiteral("enabled")).toBool(node.enabled);
|
||||
}
|
||||
if (src.contains(QStringLiteral("params")) && src.value(QStringLiteral("params")).isObject()) {
|
||||
QJsonObject p = node.params;
|
||||
const QJsonObject patchParams = src.value(QStringLiteral("params")).toObject();
|
||||
for (auto pit = patchParams.constBegin(); pit != patchParams.constEnd(); ++pit) {
|
||||
p.insert(pit.key(), pit.value());
|
||||
}
|
||||
node.params = p;
|
||||
}
|
||||
if (src.contains(QStringLiteral("children")) && src.value(QStringLiteral("children")).isArray()) {
|
||||
node.childrenIds.clear();
|
||||
for (const QJsonValue& cv : src.value(QStringLiteral("children")).toArray()) {
|
||||
if (cv.isString()) {
|
||||
node.childrenIds.push_back(cv.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DeviceTreeModel::loadFromDaemonConfig(const QJsonObject& root, QString* errorMessage)
|
||||
{
|
||||
m_nodes.clear();
|
||||
@@ -67,6 +191,9 @@ bool DeviceTreeModel::loadFromDaemonConfig(const QJsonObject& root, QString* err
|
||||
}
|
||||
|
||||
LOG_INFO() << "DeviceTreeModel loaded" << m_nodes.size() << "nodes from daemon_config";
|
||||
m_treeRevision.store(std::max(qint64(1),
|
||||
root.value(QStringLiteral("device_tree_revision")).toVariant().toLongLong()),
|
||||
std::memory_order_relaxed);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,30 +208,51 @@ void DeviceTreeModel::rebuildIndex()
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QJsonObject deviceNodeToJsonObject(const DeviceTreeNode& n)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("id"), n.id);
|
||||
if (!n.name.isEmpty()) {
|
||||
obj.insert(QStringLiteral("name"), n.name);
|
||||
}
|
||||
if (!n.type.isEmpty()) {
|
||||
obj.insert(QStringLiteral("type"), n.type);
|
||||
}
|
||||
if (!n.endpoint.isEmpty()) {
|
||||
obj.insert(QStringLiteral("endpoint"), n.endpoint);
|
||||
}
|
||||
if (!n.driver.isEmpty()) {
|
||||
obj.insert(QStringLiteral("driver"), n.driver);
|
||||
}
|
||||
if (!n.capabilitiesRef.isEmpty()) {
|
||||
obj.insert(QStringLiteral("capabilitiesRef"), n.capabilitiesRef);
|
||||
}
|
||||
if (!n.params.isEmpty()) {
|
||||
obj.insert(QStringLiteral("params"), n.params);
|
||||
}
|
||||
obj.insert(QStringLiteral("enabled"), n.enabled);
|
||||
|
||||
if (!n.childrenIds.isEmpty()) {
|
||||
QJsonArray children;
|
||||
for (const auto& c : n.childrenIds) {
|
||||
children.append(c);
|
||||
}
|
||||
obj.insert(QStringLiteral("children"), children);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QJsonArray DeviceTreeModel::toDeviceTreeJson() const
|
||||
{
|
||||
QJsonArray arr;
|
||||
|
||||
for (const auto& n : m_nodes) {
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("id"), n.id);
|
||||
if (!n.name.isEmpty()) obj.insert(QStringLiteral("name"), n.name);
|
||||
if (!n.type.isEmpty()) obj.insert(QStringLiteral("type"), n.type);
|
||||
if (!n.endpoint.isEmpty()) obj.insert(QStringLiteral("endpoint"), n.endpoint);
|
||||
if (!n.driver.isEmpty()) obj.insert(QStringLiteral("driver"), n.driver);
|
||||
if (!n.capabilitiesRef.isEmpty()) obj.insert(QStringLiteral("capabilitiesRef"), n.capabilitiesRef);
|
||||
if (!n.params.isEmpty()) obj.insert(QStringLiteral("params"), n.params);
|
||||
obj.insert(QStringLiteral("enabled"), n.enabled);
|
||||
|
||||
if (!n.childrenIds.isEmpty()) {
|
||||
QJsonArray children;
|
||||
for (const auto& c : n.childrenIds) {
|
||||
children.append(c);
|
||||
}
|
||||
obj.insert(QStringLiteral("children"), children);
|
||||
}
|
||||
|
||||
arr.append(obj);
|
||||
arr.append(deviceNodeToJsonObject(n));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
@@ -225,3 +373,247 @@ bool DeviceTreeModel::markOfflineByStableKey(const QString& stableKey, const QSt
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceTreeModel::bumpTreeRevision()
|
||||
{
|
||||
m_treeRevision.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::applyPatchToNodeById(const QString& id, const QJsonObject& patch)
|
||||
{
|
||||
const int idx = findIndexById(id);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
applyJsonPatchToDeviceTreeNode(m_nodes[idx], patch);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceTreeModel::nodeBelongsToEdge(const DeviceTreeNode& node, const QString& edgeId)
|
||||
{
|
||||
if (edgeId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (node.params.value(QStringLiteral("edgeId")).toString() == edgeId) {
|
||||
return true;
|
||||
}
|
||||
const QString prefix = QStringLiteral("edges/%1/").arg(edgeId);
|
||||
if (node.id == QStringLiteral("edges/%1").arg(edgeId) || node.id.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray DeviceTreeModel::exportSubtreeForEdge(const QString& edgeId, qint64* outRevisionAtExport) const
|
||||
{
|
||||
QJsonArray arr;
|
||||
if (edgeId.isEmpty()) {
|
||||
return arr;
|
||||
}
|
||||
QMutexLocker locker(&m_writeMutex);
|
||||
if (outRevisionAtExport) {
|
||||
*outRevisionAtExport = m_treeRevision.load(std::memory_order_relaxed);
|
||||
}
|
||||
for (const auto& n : m_nodes) {
|
||||
if (nodeBelongsToEdge(n, edgeId)) {
|
||||
arr.append(deviceNodeToJsonObject(n));
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
QVector<QString> DeviceTreeModel::downlinkEdgeIdsForNode(const DeviceTreeNode& node) const
|
||||
{
|
||||
QVector<QString> out;
|
||||
auto pushUnique = [&out](const QString& s) {
|
||||
const QString t = s.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (const QString& x : out) {
|
||||
if (x == t) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
out.append(t);
|
||||
};
|
||||
pushUnique(node.params.value(QStringLiteral("edgeId")).toString());
|
||||
static const QString pfx = QStringLiteral("edges/");
|
||||
if (node.id.startsWith(pfx)) {
|
||||
const QString rest = node.id.mid(pfx.size());
|
||||
const int slash = rest.indexOf(QLatin1Char('/'));
|
||||
pushUnique(slash < 0 ? rest : rest.left(slash));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
DeviceTreeModel::EdgeProposeResult DeviceTreeModel::applyEdgePropose(const QString& edgeId,
|
||||
qint64 baseRevision,
|
||||
const QJsonArray& entries)
|
||||
{
|
||||
EdgeProposeResult r;
|
||||
QMutexLocker locker(&m_writeMutex);
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
if (entries.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("entries must be non-empty");
|
||||
return r;
|
||||
}
|
||||
if (baseRevision != m_treeRevision.load(std::memory_order_relaxed)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("CONFLICT");
|
||||
r.message = QStringLiteral("baseTreeRevision mismatch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
|
||||
for (const QJsonValue& ev : entries) {
|
||||
if (!ev.isObject()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("entries must be objects");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
const QJsonObject eo = ev.toObject();
|
||||
const QString id = eo.value(QStringLiteral("id")).toString();
|
||||
const QString op = eo.value(QStringLiteral("op")).toString();
|
||||
|
||||
if (op.isEmpty() || op == QStringLiteral("patch")) {
|
||||
const QJsonObject patch = eo.value(QStringLiteral("patch")).toObject();
|
||||
if (id.isEmpty() || patch.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("each patch entry needs id and patch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
const DeviceTreeNode* node = findById(id);
|
||||
if (!node) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("NODE_NOT_FOUND");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!nodeBelongsToEdge(*node, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!applyPatchToNodeById(id, patch)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("NODE_NOT_FOUND");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
} else if (op == QStringLiteral("upsert")) {
|
||||
const QJsonObject nodeJo = eo.value(QStringLiteral("node")).toObject();
|
||||
if (id.isEmpty() || nodeJo.isEmpty()) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("upsert entry needs id and non-empty node");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (nodeJo.contains(QStringLiteral("id"))) {
|
||||
const QString nid = nodeJo.value(QStringLiteral("id")).toString();
|
||||
if (!nid.isEmpty() && nid != id) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("node.id must match entry id");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
const int idx = findIndexById(id);
|
||||
if (idx < 0) {
|
||||
if (!idAllowedForEdgeProposeNew(id, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
DeviceTreeNode n = deviceTreeNodeFromJsonObject(nodeJo);
|
||||
if (n.id.isEmpty()) {
|
||||
n.id = id;
|
||||
}
|
||||
if (n.id != id) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("upsert node id mismatch");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!n.params.contains(QStringLiteral("edgeId"))) {
|
||||
QJsonObject p = n.params;
|
||||
p.insert(QStringLiteral("edgeId"), edgeId);
|
||||
n.params = p;
|
||||
}
|
||||
if (!nodeBelongsToEdge(n, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!childrenIdsAllowedForEdge(n.childrenIds, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = QStringLiteral("children outside edge scope");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
m_nodes.push_back(n);
|
||||
} else {
|
||||
DeviceTreeNode& existing = m_nodes[idx];
|
||||
if (!nodeBelongsToEdge(existing, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
mergeUpsertJsonIntoNode(existing, nodeJo);
|
||||
existing.id = id;
|
||||
if (!existing.params.contains(QStringLiteral("edgeId"))) {
|
||||
existing.params.insert(QStringLiteral("edgeId"), edgeId);
|
||||
}
|
||||
if (!nodeBelongsToEdge(existing, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = id;
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
if (!childrenIdsAllowedForEdge(existing.childrenIds, edgeId)) {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("FORBIDDEN_SCOPE");
|
||||
r.message = QStringLiteral("children outside edge scope");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.ok = false;
|
||||
r.code = QStringLiteral("INVALID_JSON");
|
||||
r.message = QStringLiteral("unknown op");
|
||||
r.currentRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
return r;
|
||||
}
|
||||
rebuildIndex();
|
||||
}
|
||||
|
||||
rebuildIndex();
|
||||
bumpTreeRevision();
|
||||
r.ok = true;
|
||||
r.code = QStringLiteral("OK");
|
||||
r.newRevision = m_treeRevision.load(std::memory_order_relaxed);
|
||||
r.currentRevision = r.newRevision;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <atomic>
|
||||
#include <QMutex>
|
||||
|
||||
// 轻量级设备树模型:从 daemon_config.json 的 device_tree 字段加载
|
||||
|
||||
@@ -53,8 +55,35 @@ public:
|
||||
// 当外部直接修改 nodesMutable() 后,需要重建索引
|
||||
void rebuildIndex();
|
||||
|
||||
// 与 D-Bus patch / 边缘 CTRL_TREE_PROPOSE 共用的写锁与 revision(见 docs/EDGE_UPLINK_PROTOCOL_v1.md)
|
||||
QMutex& writeMutex() { return m_writeMutex; }
|
||||
qint64 treeRevision() const { return m_treeRevision.load(std::memory_order_relaxed); }
|
||||
void bumpTreeRevision();
|
||||
bool applyPatchToNodeById(const QString& id, const QJsonObject& patch);
|
||||
|
||||
struct EdgeProposeResult
|
||||
{
|
||||
bool ok{false};
|
||||
QString code; // OK, CONFLICT, FORBIDDEN_SCOPE, NODE_NOT_FOUND, INVALID_JSON
|
||||
QString message;
|
||||
qint64 newRevision{0};
|
||||
qint64 currentRevision{0};
|
||||
};
|
||||
EdgeProposeResult applyEdgePropose(const QString& edgeId, qint64 baseRevision, const QJsonArray& entries);
|
||||
|
||||
/** 导出属于该 edge 的节点子集(与 applyEdgePropose 作用域规则一致),用于下行 TREE_FULL。
|
||||
* 若传入 outRevisionAtExport,则写入导出时刻的 treeRevision(与 nodes 同一快照)。 */
|
||||
QJsonArray exportSubtreeForEdge(const QString& edgeId, qint64* outRevisionAtExport = nullptr) const;
|
||||
|
||||
/** 节点若处于边缘作用域(params.edgeId 或 id 前缀 edges/<edgeId>/...),返回应尝试下行 TREE_FULL 的 edgeId(去重)。 */
|
||||
QVector<QString> downlinkEdgeIdsForNode(const DeviceTreeNode& node) const;
|
||||
|
||||
private:
|
||||
static bool nodeBelongsToEdge(const DeviceTreeNode& node, const QString& edgeId);
|
||||
|
||||
QVector<DeviceTreeNode> m_nodes;
|
||||
QHash<QString, int> m_indexById;
|
||||
mutable QMutex m_writeMutex;
|
||||
std::atomic<qint64> m_treeRevision{1};
|
||||
};
|
||||
|
||||
|
||||
147
src/devices/physical/CanDevice.cpp
Normal file
147
src/devices/physical/CanDevice.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "devices/physical/CanDevice.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QCanBusDevice>
|
||||
#include <QCanBusFrame>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "core/models/MessageRoutingUtils.h"
|
||||
#include "core/models/RawBusMessage.h"
|
||||
#include "message_bus/ingress/IIngressPort.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
static QString socketCanInterfaceName(const QString& endpoint)
|
||||
{
|
||||
QString ep = endpoint.trimmed();
|
||||
if (ep.startsWith(QStringLiteral("can:"), Qt::CaseInsensitive)) {
|
||||
return ep.mid(4);
|
||||
}
|
||||
return ep;
|
||||
}
|
||||
|
||||
CanDevice::CanDevice(std::unique_ptr<QCanBusDevice> bus, const Config& config, const softbus::devices::DeviceInfo& info)
|
||||
: m_bus(std::move(bus))
|
||||
, m_config(config)
|
||||
, m_info(info)
|
||||
{
|
||||
}
|
||||
|
||||
CanDevice::~CanDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool CanDevice::start()
|
||||
{
|
||||
if (!m_bus) {
|
||||
LOG_ERROR() << "CanDevice: null bus";
|
||||
return false;
|
||||
}
|
||||
QObject::connect(m_bus.get(), &QCanBusDevice::framesReceived, m_bus.get(), [this]() { onFramesAvailable(); });
|
||||
QObject::connect(m_bus.get(), &QCanBusDevice::errorOccurred, m_bus.get(), [this](QCanBusDevice::CanBusError) {
|
||||
onError();
|
||||
});
|
||||
|
||||
if (m_config.bitrate > 0) {
|
||||
m_bus->setConfigurationParameter(QCanBusDevice::BitRateKey, m_config.bitrate);
|
||||
}
|
||||
LOG_INFO() << "CanDevice: connectDevice" << m_config.endpoint << "iface" << socketCanInterfaceName(m_config.endpoint);
|
||||
return m_bus->connectDevice();
|
||||
}
|
||||
|
||||
void CanDevice::stop()
|
||||
{
|
||||
if (!m_bus) {
|
||||
return;
|
||||
}
|
||||
QObject::disconnect(m_bus.get(), nullptr, m_bus.get(), nullptr);
|
||||
if (m_bus->state() == QCanBusDevice::ConnectedState) {
|
||||
m_bus->disconnectDevice();
|
||||
}
|
||||
m_bus.reset();
|
||||
}
|
||||
|
||||
softbus::devices::DeviceInfo CanDevice::deviceInfo() const
|
||||
{
|
||||
return m_info;
|
||||
}
|
||||
|
||||
QString CanDevice::endpoint() const
|
||||
{
|
||||
return m_config.endpoint;
|
||||
}
|
||||
|
||||
softbus::devices::DeviceKind CanDevice::kind() const
|
||||
{
|
||||
return softbus::devices::DeviceKind::Can;
|
||||
}
|
||||
|
||||
void CanDevice::bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool)
|
||||
{
|
||||
m_ingress = ingress;
|
||||
m_pool = std::move(pool);
|
||||
}
|
||||
|
||||
void CanDevice::onFramesAvailable()
|
||||
{
|
||||
if (!m_ingress || !m_pool || !m_bus) {
|
||||
return;
|
||||
}
|
||||
while (m_bus->framesAvailable()) {
|
||||
const QCanBusFrame frame = m_bus->readFrame();
|
||||
if (frame.frameType() != QCanBusFrame::DataFrame && frame.frameType() != QCanBusFrame::RemoteRequestFrame) {
|
||||
continue;
|
||||
}
|
||||
const QByteArray busPayload = frame.payload();
|
||||
auto block = m_pool->allocate(static_cast<std::size_t>(busPayload.size()));
|
||||
if (!block || !block->data || block->capacity < static_cast<std::size_t>(busPayload.size())) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("memory pool exhausted"));
|
||||
return;
|
||||
}
|
||||
if (!busPayload.isEmpty()) {
|
||||
std::memcpy(block->data, busPayload.constData(), static_cast<std::size_t>(busPayload.size()));
|
||||
}
|
||||
block->size = static_cast<std::size_t>(busPayload.size());
|
||||
|
||||
softbus::core::models::RawBusMessage ctx;
|
||||
ctx.direction = softbus::core::models::BusDirection::Upstream;
|
||||
ctx.endpointHash = softbus::core::models::makeEndpointHash(m_config.endpoint);
|
||||
ctx.protocol = softbus::core::models::protocolTypeFromHint(m_info.protocol);
|
||||
if (ctx.protocol == softbus::core::models::ProtocolType::UNKNOWN) {
|
||||
ctx.protocol = softbus::core::models::ProtocolType::CAN_RAW;
|
||||
}
|
||||
ctx.header.can.cobId = static_cast<std::uint32_t>(frame.frameId() & 0x1FFFFFFFU);
|
||||
ctx.header.can.isExtended = frame.hasExtendedFrameFormat();
|
||||
ctx.header.can.isRtr = (frame.frameType() == QCanBusFrame::RemoteRequestFrame);
|
||||
ctx.logicalAddress = ctx.header.can.cobId & 0xFFU;
|
||||
ctx.routingKey = softbus::core::models::makeRoutingKey(ctx.endpointHash, m_info.id);
|
||||
ctx.timestampMs = QDateTime::currentMSecsSinceEpoch();
|
||||
ctx.deviceId = m_info.id;
|
||||
ctx.payload.block = std::move(block);
|
||||
ctx.payload.length = static_cast<std::size_t>(busPayload.size());
|
||||
ctx.traceId = QStringLiteral("can:%1:%2:%3")
|
||||
.arg(m_config.endpoint)
|
||||
.arg(ctx.header.can.cobId)
|
||||
.arg(ctx.timestampMs);
|
||||
|
||||
if (!m_ingress->push(std::move(ctx))) {
|
||||
m_ingress->reportError(m_config.endpoint, QStringLiteral("ingress queue full"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanDevice::onError()
|
||||
{
|
||||
if (!m_ingress || !m_bus) {
|
||||
return;
|
||||
}
|
||||
m_ingress->reportError(m_config.endpoint, m_bus->errorString());
|
||||
}
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
52
src/devices/physical/CanDevice.h
Normal file
52
src/devices/physical/CanDevice.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "devices/DeviceTypes.h"
|
||||
#include "devices/base/IPhysicalDevice.h"
|
||||
|
||||
namespace softbus::message_bus::ingress { class IIngressPort; }
|
||||
namespace softbus::core::memory { class MemoryPool; }
|
||||
|
||||
class QCanBusDevice;
|
||||
|
||||
namespace softbus::devices::physical
|
||||
{
|
||||
|
||||
class CanDevice : public base::IPhysicalDevice
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
QString endpoint;
|
||||
int bitrate{500000};
|
||||
};
|
||||
|
||||
CanDevice(std::unique_ptr<QCanBusDevice> bus, const Config& config, const softbus::devices::DeviceInfo& info);
|
||||
~CanDevice() override;
|
||||
|
||||
bool start() override;
|
||||
void stop() override;
|
||||
|
||||
softbus::devices::DeviceInfo deviceInfo() const override;
|
||||
|
||||
QString endpoint() const;
|
||||
softbus::devices::DeviceKind kind() const;
|
||||
void bindIngress(softbus::message_bus::ingress::IIngressPort* ingress,
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool);
|
||||
|
||||
private:
|
||||
void onFramesAvailable();
|
||||
void onError();
|
||||
|
||||
private:
|
||||
std::unique_ptr<QCanBusDevice> m_bus;
|
||||
Config m_config;
|
||||
softbus::devices::DeviceInfo m_info;
|
||||
softbus::message_bus::ingress::IIngressPort* m_ingress{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> m_pool;
|
||||
};
|
||||
|
||||
} // namespace softbus::devices::physical
|
||||
368
src/message_bus/ingress/EdgeTcpIngressService.cpp
Normal file
368
src/message_bus/ingress/EdgeTcpIngressService.cpp
Normal file
@@ -0,0 +1,368 @@
|
||||
#include "message_bus/ingress/EdgeTcpIngressService.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QAbstractSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
using softbus::message_bus::ingress::edge_uplink::FrameKind;
|
||||
using softbus::message_bus::ingress::edge_uplink::decodeDataRawBusPayload;
|
||||
using softbus::message_bus::ingress::edge_uplink::encodeOuterFrame;
|
||||
using softbus::message_bus::ingress::edge_uplink::tryPopOneOuterFrame;
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
EdgeTcpIngressService::EdgeTcpIngressService(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
EdgeTcpIngressService::~EdgeTcpIngressService()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool EdgeTcpIngressService::start(const Config& cfg, const Deps& deps)
|
||||
{
|
||||
m_cfg = cfg;
|
||||
m_deps = deps;
|
||||
if (!m_cfg.enabled) {
|
||||
LOG_INFO() << "EdgeTcpIngressService: disabled in config (edgeIngress.enabled=false); no TCP listen. "
|
||||
"Set edgeIngress.enabled=true in ~/softbus/config/daemon_config.json to accept edge_agent.";
|
||||
return true;
|
||||
}
|
||||
if (!m_deps.ingress || !m_deps.pool || !m_deps.tree || !m_deps.markDeviceTreeDirty || !m_deps.reconcileFromTreeModel) {
|
||||
LOG_ERROR() << "EdgeTcpIngressService: invalid deps";
|
||||
return false;
|
||||
}
|
||||
if (m_server) {
|
||||
return true;
|
||||
}
|
||||
m_server = new QTcpServer(this);
|
||||
connect(m_server, &QTcpServer::newConnection, this, &EdgeTcpIngressService::onNewConnection);
|
||||
const QHostAddress addr(m_cfg.listenAddress);
|
||||
if (!m_server->listen(addr, static_cast<quint16>(m_cfg.port))) {
|
||||
LOG_ERROR() << "EdgeTcpIngressService: listen failed" << m_cfg.listenAddress << m_cfg.port
|
||||
<< m_server->errorString();
|
||||
delete m_server;
|
||||
m_server = nullptr;
|
||||
return false;
|
||||
}
|
||||
LOG_INFO() << "EdgeTcpIngressService: listening" << m_cfg.listenAddress << "port" << m_cfg.port;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::stop()
|
||||
{
|
||||
if (m_server) {
|
||||
m_server->close();
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it->first) {
|
||||
it->first->disconnect(this);
|
||||
it->first->abort();
|
||||
}
|
||||
}
|
||||
m_clients.clear();
|
||||
m_server->deleteLater();
|
||||
m_server = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonArray EdgeTcpIngressService::sessionsSnapshot() const
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const auto& kv : m_clients) {
|
||||
if (!kv.second) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("edgeId"), kv.second->edgeId);
|
||||
o.insert(QStringLiteral("helloOk"), kv.second->helloOk);
|
||||
o.insert(QStringLiteral("peer"),
|
||||
kv.first ? QStringLiteral("%1:%2").arg(kv.first->peerAddress().toString()).arg(kv.first->peerPort())
|
||||
: QString());
|
||||
arr.append(o);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::sendTreeFullToSocket(QTcpSocket* sock, const QString& edgeId)
|
||||
{
|
||||
if (!sock || edgeId.isEmpty() || !m_deps.tree) {
|
||||
return;
|
||||
}
|
||||
qint64 rev = 0;
|
||||
const QJsonArray subtree = m_deps.tree->exportSubtreeForEdge(edgeId, &rev);
|
||||
QJsonObject envelope;
|
||||
envelope.insert(QStringLiteral("kind"), QStringLiteral("TREE_FULL"));
|
||||
envelope.insert(QStringLiteral("edgeId"), edgeId);
|
||||
envelope.insert(QStringLiteral("treeRevision"), rev);
|
||||
envelope.insert(QStringLiteral("tree"), subtree);
|
||||
const QByteArray pl = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::TreeFull, pl);
|
||||
(void)sock->write(frame);
|
||||
}
|
||||
|
||||
bool EdgeTcpIngressService::pushTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut)
|
||||
{
|
||||
if (sentCountOut) {
|
||||
*sentCountOut = 0;
|
||||
}
|
||||
if (errorCodeOut) {
|
||||
errorCodeOut->clear();
|
||||
}
|
||||
if (!m_cfg.enabled) {
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("edge_ingress_disabled");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!m_deps.tree) {
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("device_tree_unavailable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (edgeId.isEmpty()) {
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("missing_edgeId");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int sent = 0;
|
||||
for (auto& kv : m_clients) {
|
||||
if (!kv.first || !kv.second || !kv.second->helloOk || kv.second->edgeId != edgeId) {
|
||||
continue;
|
||||
}
|
||||
if (kv.first->state() != QAbstractSocket::ConnectedState) {
|
||||
continue;
|
||||
}
|
||||
sendTreeFullToSocket(kv.first, edgeId);
|
||||
++sent;
|
||||
}
|
||||
if (sentCountOut) {
|
||||
*sentCountOut = sent;
|
||||
}
|
||||
if (sent == 0) {
|
||||
if (errorCodeOut) {
|
||||
*errorCodeOut = QStringLiteral("no_active_session");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::onNewConnection()
|
||||
{
|
||||
if (!m_server) {
|
||||
return;
|
||||
}
|
||||
while (QTcpSocket* sock = m_server->nextPendingConnection()) {
|
||||
auto ctx = std::make_unique<ClientCtx>();
|
||||
m_clients.emplace(sock, std::move(ctx));
|
||||
connect(sock, &QTcpSocket::readyRead, this, &EdgeTcpIngressService::onClientReadyRead);
|
||||
connect(sock, &QTcpSocket::disconnected, this, &EdgeTcpIngressService::onClientDisconnected);
|
||||
LOG_INFO() << "EdgeTcpIngressService: client connected" << sock->peerAddress().toString() << sock->peerPort();
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::onClientDisconnected()
|
||||
{
|
||||
auto* sock = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!sock) {
|
||||
return;
|
||||
}
|
||||
m_clients.erase(sock);
|
||||
sock->deleteLater();
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::onClientReadyRead()
|
||||
{
|
||||
auto* sock = qobject_cast<QTcpSocket*>(sender());
|
||||
auto it = m_clients.find(sock);
|
||||
if (it == m_clients.end() || !it->second) {
|
||||
return;
|
||||
}
|
||||
ClientCtx& ctx = *it->second;
|
||||
ctx.inbuf.append(sock->readAll());
|
||||
tryConsumeIncoming(sock, ctx);
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::tryConsumeIncoming(QTcpSocket* sock, ClientCtx& ctx)
|
||||
{
|
||||
while (true) {
|
||||
const auto popped = tryPopOneOuterFrame(ctx.inbuf, m_cfg.maxPayloadBytes);
|
||||
if (!popped.has_value()) {
|
||||
break;
|
||||
}
|
||||
const std::uint16_t kindU16 = static_cast<std::uint16_t>(popped->kind);
|
||||
handleFrame(sock, ctx, kindU16, popped->payload);
|
||||
}
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::sendTreeResult(QTcpSocket* sock, const QJsonObject& body)
|
||||
{
|
||||
if (!sock) {
|
||||
return;
|
||||
}
|
||||
const QByteArray pl = QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::TreeResult, pl);
|
||||
(void)sock->write(frame);
|
||||
}
|
||||
|
||||
void EdgeTcpIngressService::handleFrame(QTcpSocket* sock, ClientCtx& ctx, std::uint16_t kindRaw, const QByteArray& payload)
|
||||
{
|
||||
const auto kind = static_cast<FrameKind>(kindRaw);
|
||||
|
||||
if (kind == FrameKind::Hello) {
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(payload, &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("INVALID_JSON")},
|
||||
{QStringLiteral("message"), pe.errorString()}});
|
||||
sock->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
const QJsonObject o = doc.object();
|
||||
ctx.edgeId = o.value(QStringLiteral("edgeId")).toString();
|
||||
if (ctx.edgeId.isEmpty()) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("INVALID_JSON")},
|
||||
{QStringLiteral("message"), QStringLiteral("missing edgeId")}});
|
||||
sock->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
if (!m_cfg.authToken.isEmpty()) {
|
||||
if (o.value(QStringLiteral("authToken")).toString() != m_cfg.authToken) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("VALIDATION_ERROR")},
|
||||
{QStringLiteral("message"), QStringLiteral("bad authToken")}});
|
||||
sock->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ctx.helloOk = true;
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), true},
|
||||
{QStringLiteral("code"), QStringLiteral("OK")},
|
||||
{QStringLiteral("message"), QStringLiteral("hello")},
|
||||
{QStringLiteral("currentTreeRevision"), m_deps.tree->treeRevision()}});
|
||||
sendTreeFullToSocket(sock, ctx.edgeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.helloOk) {
|
||||
if (kind == FrameKind::DataRawBus && m_cfg.authToken.isEmpty()) {
|
||||
// allow anonymous raw uplink when no authToken configured
|
||||
} else if (kind == FrameKind::TreePropose) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("VALIDATION_ERROR")},
|
||||
{QStringLiteral("message"), QStringLiteral("HELLO required")}});
|
||||
return;
|
||||
} else {
|
||||
LOG_WARNING() << "EdgeTcpIngressService: HELLO required before this frame kind" << kindRaw;
|
||||
sock->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == FrameKind::DataRawBus) {
|
||||
QString err;
|
||||
softbus::core::models::RawBusMessage ctxMsg;
|
||||
QByteArray busPayload;
|
||||
if (!decodeDataRawBusPayload(payload, ctxMsg, busPayload, &err)) {
|
||||
LOG_WARNING() << "EdgeTcpIngressService: decode DataRawBus failed" << err;
|
||||
return;
|
||||
}
|
||||
if (busPayload.isEmpty()) {
|
||||
const auto p = ctxMsg.protocol;
|
||||
if (p != softbus::core::models::ProtocolType::CAN_RAW
|
||||
&& p != softbus::core::models::ProtocolType::CAN_OPEN) {
|
||||
LOG_WARNING() << "EdgeTcpIngressService: empty bus payload";
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto block = m_deps.pool->allocate(static_cast<std::size_t>(busPayload.size()));
|
||||
if (!block || !block->data || block->capacity < static_cast<std::size_t>(busPayload.size())) {
|
||||
LOG_WARNING() << "EdgeTcpIngressService: memory pool exhausted";
|
||||
return;
|
||||
}
|
||||
std::memcpy(block->data, busPayload.constData(), static_cast<std::size_t>(busPayload.size()));
|
||||
block->size = static_cast<std::size_t>(busPayload.size());
|
||||
ctxMsg.payload.block = std::move(block);
|
||||
ctxMsg.payload.length = static_cast<std::size_t>(busPayload.size());
|
||||
if (!m_deps.ingress->push(std::move(ctxMsg))) {
|
||||
m_deps.ingress->reportError(QStringLiteral("edge_tcp"), QStringLiteral("ingress queue full"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == FrameKind::TreePropose) {
|
||||
if (!ctx.helloOk) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("VALIDATION_ERROR")},
|
||||
{QStringLiteral("message"), QStringLiteral("HELLO required")}});
|
||||
return;
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(payload, &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("INVALID_JSON")},
|
||||
{QStringLiteral("message"), pe.errorString()}});
|
||||
return;
|
||||
}
|
||||
const QJsonObject o = doc.object();
|
||||
const QString edgeId = o.value(QStringLiteral("edgeId")).toString();
|
||||
if (edgeId != ctx.edgeId) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), QStringLiteral("FORBIDDEN_SCOPE")},
|
||||
{QStringLiteral("message"), QStringLiteral("edgeId mismatch")}});
|
||||
return;
|
||||
}
|
||||
const qint64 baseRev = o.value(QStringLiteral("baseTreeRevision")).toVariant().toLongLong();
|
||||
const QJsonArray entries = o.value(QStringLiteral("entries")).toArray();
|
||||
const auto res = m_deps.tree->applyEdgePropose(edgeId, baseRev, entries);
|
||||
if (!res.ok) {
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), false},
|
||||
{QStringLiteral("code"), res.code},
|
||||
{QStringLiteral("message"), res.message},
|
||||
{QStringLiteral("currentTreeRevision"), res.currentRevision}});
|
||||
return;
|
||||
}
|
||||
m_deps.markDeviceTreeDirty();
|
||||
m_deps.reconcileFromTreeModel();
|
||||
sendTreeResult(sock,
|
||||
QJsonObject{{QStringLiteral("ok"), true},
|
||||
{QStringLiteral("code"), QStringLiteral("OK")},
|
||||
{QStringLiteral("newTreeRevision"), res.newRevision},
|
||||
{QStringLiteral("currentTreeRevision"), res.currentRevision}});
|
||||
int sentFull = 0;
|
||||
(void)pushTreeFullDownlink(ctx.edgeId, &sentFull, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == FrameKind::Ack || kind == FrameKind::Report) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WARNING() << "EdgeTcpIngressService: unhandled frame kind" << kindRaw;
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
80
src/message_bus/ingress/EdgeTcpIngressService.h
Normal file
80
src/message_bus/ingress/EdgeTcpIngressService.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "core/memory/MemoryPool.h"
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "message_bus/ingress/DriverIngressAdapter.h"
|
||||
|
||||
namespace softbus::message_bus::ingress
|
||||
{
|
||||
|
||||
class EdgeTcpIngressService final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
bool enabled{false};
|
||||
QString listenAddress{QStringLiteral("0.0.0.0")};
|
||||
int port{18765};
|
||||
int maxPayloadBytes{4 * 1024 * 1024};
|
||||
QString authToken; // empty: first frame need not be HELLO auth (still recommend HELLO for edgeId)
|
||||
};
|
||||
|
||||
struct Deps
|
||||
{
|
||||
DriverIngressAdapter* ingress{nullptr};
|
||||
std::shared_ptr<softbus::core::memory::MemoryPool> pool;
|
||||
std::shared_ptr<DeviceTreeModel> tree;
|
||||
std::function<void()> markDeviceTreeDirty;
|
||||
std::function<void()> reconcileFromTreeModel;
|
||||
};
|
||||
|
||||
explicit EdgeTcpIngressService(QObject* parent = nullptr);
|
||||
~EdgeTcpIngressService() override;
|
||||
|
||||
bool start(const Config& cfg, const Deps& deps);
|
||||
void stop();
|
||||
|
||||
QJsonArray sessionsSnapshot() const;
|
||||
|
||||
/** 向所有已 HELLO 且 edgeId 匹配的会话发送 TREE_FULL(SBUP 外层 kind=TreeFull)。至少送达一路则返回 true。 */
|
||||
bool pushTreeFullDownlink(const QString& edgeId, int* sentCountOut, QString* errorCodeOut);
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onClientReadyRead();
|
||||
void onClientDisconnected();
|
||||
|
||||
private:
|
||||
struct ClientCtx
|
||||
{
|
||||
QByteArray inbuf;
|
||||
QString edgeId;
|
||||
bool helloOk{false};
|
||||
};
|
||||
|
||||
void handleFrame(QTcpSocket* sock, ClientCtx& ctx, std::uint16_t kindRaw, const QByteArray& payload);
|
||||
void sendTreeResult(QTcpSocket* sock, const QJsonObject& body);
|
||||
void sendTreeFullToSocket(QTcpSocket* sock, const QString& edgeId);
|
||||
void tryConsumeIncoming(QTcpSocket* sock, ClientCtx& ctx);
|
||||
|
||||
Config m_cfg;
|
||||
Deps m_deps;
|
||||
QTcpServer* m_server{nullptr};
|
||||
std::unordered_map<QTcpSocket*, std::unique_ptr<ClientCtx>> m_clients;
|
||||
};
|
||||
|
||||
} // namespace softbus::message_bus::ingress
|
||||
140
src/message_bus/ingress/EdgeUplinkWire.cpp
Normal file
140
src/message_bus/ingress/EdgeUplinkWire.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::message_bus::ingress::edge_uplink
|
||||
{
|
||||
|
||||
static std::uint16_t readU16BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint16>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::uint32_t readU32BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint32>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::int32_t readI32BE(const unsigned char* p)
|
||||
{
|
||||
return static_cast<std::int32_t>(readU32BE(p));
|
||||
}
|
||||
|
||||
static std::int64_t readI64BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<qint64>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
static std::uint64_t readU64BE(const unsigned char* p)
|
||||
{
|
||||
return qFromBigEndian<quint64>(reinterpret_cast<const quint8*>(p));
|
||||
}
|
||||
|
||||
QByteArray encodeOuterFrame(FrameKind kind, const QByteArray& payload)
|
||||
{
|
||||
QByteArray out;
|
||||
out.resize(static_cast<int>(kOuterHeaderBytes + payload.size()));
|
||||
unsigned char* d = reinterpret_cast<unsigned char*>(out.data());
|
||||
d[0] = static_cast<unsigned char>(kMagic0);
|
||||
d[1] = static_cast<unsigned char>(kMagic1);
|
||||
d[2] = static_cast<unsigned char>(kMagic2);
|
||||
d[3] = static_cast<unsigned char>(kMagic3);
|
||||
qToBigEndian(kWireVersion, d + 4);
|
||||
qToBigEndian(static_cast<std::uint16_t>(kind), d + 6);
|
||||
qToBigEndian(static_cast<std::uint32_t>(0), d + 8);
|
||||
qToBigEndian(static_cast<std::uint32_t>(payload.size()), d + 12);
|
||||
if (!payload.isEmpty()) {
|
||||
std::memcpy(d + kOuterHeaderBytes, payload.constData(), static_cast<std::size_t>(payload.size()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<OuterFramePop> tryPopOneOuterFrame(QByteArray& buffer, int maxPayloadBytes)
|
||||
{
|
||||
if (buffer.size() < static_cast<int>(kOuterHeaderBytes)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const unsigned char* d = reinterpret_cast<const unsigned char*>(buffer.constData());
|
||||
if (d[0] != static_cast<unsigned char>(kMagic0) || d[1] != static_cast<unsigned char>(kMagic1)
|
||||
|| d[2] != static_cast<unsigned char>(kMagic2) || d[3] != static_cast<unsigned char>(kMagic3)) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: bad magic, dropping byte";
|
||||
buffer.remove(0, 1);
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::uint16_t ver = readU16BE(d + 4);
|
||||
if (ver != kWireVersion) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: unsupported wire_version" << ver;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::uint32_t payloadLen = readU32BE(d + 12);
|
||||
if (maxPayloadBytes > 0 && payloadLen > static_cast<std::uint32_t>(maxPayloadBytes)) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: payload_len exceeds max" << payloadLen << maxPayloadBytes;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
const int total = static_cast<int>(kOuterHeaderBytes) + static_cast<int>(payloadLen);
|
||||
if (payloadLen > 64u * 1024u * 1024u) {
|
||||
LOG_WARNING() << "EdgeUplinkWire: absurd payload_len" << payloadLen;
|
||||
buffer.clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
if (buffer.size() < total) {
|
||||
return std::nullopt;
|
||||
}
|
||||
OuterFramePop fr;
|
||||
fr.kind = static_cast<FrameKind>(readU16BE(d + 6));
|
||||
fr.payload = QByteArray(buffer.constData() + static_cast<int>(kOuterHeaderBytes), static_cast<int>(payloadLen));
|
||||
fr.consumedBytes = total;
|
||||
buffer.remove(0, total);
|
||||
return fr;
|
||||
}
|
||||
|
||||
bool decodeDataRawBusPayload(const QByteArray& payload,
|
||||
softbus::core::models::RawBusMessage& ctx,
|
||||
QByteArray& busPayloadOut,
|
||||
QString* errorMessage)
|
||||
{
|
||||
if (payload.size() < static_cast<int>(kDataRawBusFixedBytes)) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("data_rawbus_too_short");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(payload.constData());
|
||||
const std::uint16_t traceLen = readU16BE(p + 0);
|
||||
if (traceLen > static_cast<std::uint16_t>(kMaxTraceLen)) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("trace_too_long");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const int need = static_cast<int>(kDataRawBusFixedBytes) + static_cast<int>(traceLen);
|
||||
if (payload.size() < need) {
|
||||
if (errorMessage) {
|
||||
*errorMessage = QStringLiteral("data_rawbus_truncated_trace");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
ctx = {};
|
||||
ctx.deviceId = readI32BE(p + 2);
|
||||
ctx.endpointHash = readU32BE(p + 6);
|
||||
ctx.protocol = static_cast<softbus::core::models::ProtocolType>(readU16BE(p + 10));
|
||||
ctx.logicalAddress = readU32BE(p + 14);
|
||||
ctx.routingKey = readU64BE(p + 18);
|
||||
std::memcpy(&ctx.header, p + 26, 8);
|
||||
ctx.timestampMs = readI64BE(p + 34);
|
||||
ctx.direction = static_cast<softbus::core::models::BusDirection>(static_cast<signed char>(p[42]));
|
||||
if (traceLen > 0) {
|
||||
ctx.traceId = QString::fromUtf8(reinterpret_cast<const char*>(p + 48), static_cast<int>(traceLen));
|
||||
}
|
||||
busPayloadOut = payload.mid(need);
|
||||
ctx.payload = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::message_bus::ingress::edge_uplink
|
||||
53
src/message_bus/ingress/EdgeUplinkWire.h
Normal file
53
src/message_bus/ingress/EdgeUplinkWire.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "core/models/RawBusMessage.h"
|
||||
|
||||
namespace softbus::message_bus::ingress::edge_uplink
|
||||
{
|
||||
|
||||
inline constexpr char kMagic0 = 'S';
|
||||
inline constexpr char kMagic1 = 'B';
|
||||
inline constexpr char kMagic2 = 'U';
|
||||
inline constexpr char kMagic3 = 'P';
|
||||
inline constexpr std::uint16_t kWireVersion = 1;
|
||||
|
||||
enum class FrameKind : std::uint16_t
|
||||
{
|
||||
Hello = 1,
|
||||
Ack = 2,
|
||||
DataRawBus = 3,
|
||||
TreeFull = 4,
|
||||
TreePatch = 5,
|
||||
TreePropose = 6,
|
||||
TreeResult = 7,
|
||||
Report = 8,
|
||||
};
|
||||
|
||||
inline constexpr std::size_t kOuterHeaderBytes = 16;
|
||||
inline constexpr std::size_t kDataRawBusFixedBytes = 48;
|
||||
inline constexpr int kMaxTraceLen = 512;
|
||||
|
||||
struct OuterFramePop
|
||||
{
|
||||
FrameKind kind{FrameKind::Hello};
|
||||
QByteArray payload;
|
||||
int consumedBytes{0};
|
||||
};
|
||||
|
||||
std::optional<OuterFramePop> tryPopOneOuterFrame(QByteArray& buffer, int maxPayloadBytes = 4 * 1024 * 1024);
|
||||
|
||||
QByteArray encodeOuterFrame(FrameKind kind, const QByteArray& payload);
|
||||
|
||||
// 填充除 payload.block 外的标量与 trace;busPayload 为总线原始字节,由调用方 allocate 后写入 ctx.payload
|
||||
bool decodeDataRawBusPayload(const QByteArray& payload,
|
||||
softbus::core::models::RawBusMessage& ctx,
|
||||
QByteArray& busPayloadOut,
|
||||
QString* errorMessage);
|
||||
|
||||
} // namespace softbus::message_bus::ingress::edge_uplink
|
||||
57
tests/edge_uplink_wire_test.cpp
Normal file
57
tests/edge_uplink_wire_test.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include <QtEndian>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "message_bus/ingress/EdgeUplinkWire.h"
|
||||
|
||||
using softbus::message_bus::ingress::edge_uplink::FrameKind;
|
||||
using softbus::message_bus::ingress::edge_uplink::encodeOuterFrame;
|
||||
using softbus::message_bus::ingress::edge_uplink::tryPopOneOuterFrame;
|
||||
|
||||
int main()
|
||||
{
|
||||
const QByteArray inner = QByteArray("hello");
|
||||
const QByteArray frame = encodeOuterFrame(FrameKind::Hello, inner);
|
||||
assert(frame.size() == 16 + inner.size());
|
||||
assert(frame.at(0) == 'S' && frame.at(1) == 'B' && frame.at(2) == 'U' && frame.at(3) == 'P');
|
||||
|
||||
QByteArray buf = frame;
|
||||
const auto popped = tryPopOneOuterFrame(buf, 1024 * 1024);
|
||||
assert(popped.has_value());
|
||||
assert(popped->kind == FrameKind::Hello);
|
||||
assert(popped->payload == inner);
|
||||
assert(buf.isEmpty());
|
||||
|
||||
// DATA_RAWBUS round-trip meta
|
||||
QByteArray raw;
|
||||
raw.resize(48 + 5 + 3);
|
||||
unsigned char* w = reinterpret_cast<unsigned char*>(raw.data());
|
||||
qToBigEndian(static_cast<quint16>(5), w + 0); // trace len
|
||||
qToBigEndian(static_cast<qint32>(7), reinterpret_cast<uchar*>(w + 2));
|
||||
qToBigEndian(static_cast<quint32>(0xAABBCCDD), w + 6);
|
||||
qToBigEndian(static_cast<quint16>(0), w + 10);
|
||||
qToBigEndian(static_cast<quint16>(0), w + 12);
|
||||
qToBigEndian(static_cast<quint32>(1), w + 14);
|
||||
qToBigEndian(static_cast<quint64>(2), w + 18);
|
||||
qToBigEndian(static_cast<quint64>(0), w + 26);
|
||||
qToBigEndian(static_cast<qint64>(123456789LL), reinterpret_cast<uchar*>(w + 34));
|
||||
w[42] = static_cast<unsigned char>(1); // direction
|
||||
std::memset(w + 43, 0, 5);
|
||||
std::memcpy(w + 48, "trace", 5);
|
||||
std::memcpy(w + 53, "AB\x00", 3);
|
||||
|
||||
softbus::core::models::RawBusMessage msg;
|
||||
QByteArray busOut;
|
||||
QString err;
|
||||
const bool ok = softbus::message_bus::ingress::edge_uplink::decodeDataRawBusPayload(raw, msg, busOut, &err);
|
||||
assert(ok);
|
||||
assert(msg.deviceId == 7);
|
||||
assert(msg.endpointHash == 0xAABBCCDDu);
|
||||
assert(msg.traceId == QStringLiteral("trace"));
|
||||
assert(busOut.size() == 3);
|
||||
assert(busOut.at(0) == 'A');
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user