4 division
This commit is contained in:
@@ -66,85 +66,33 @@
|
||||
| `discovery_clear` | 无 | `0` | - | 清空发现池 |
|
||||
| `list_rules` | 无 | `0` | - | 返回规则计数状态 |
|
||||
| `get_status` | 无 | `0` | - | 返回规则计数 + discovery/mapping 状态 |
|
||||
| `get_rules_status` | 无 | `0` | - | 规则 + 映射计数与 `mappingCatalogVersion` |
|
||||
| `get_discovery_status` | 无 | `0` | - | discovery 状态 JSON |
|
||||
| `list_discovery_samples` | `offset?`, `limit?`, `endpointHash?`, `protocol?` | `0` | - | 返回 `samples` 数组 |
|
||||
| `get_pipeline_status` | 无 | `0` | `5003` | PipelineEngine `status()`;不可用时 `pipeline_unavailable` |
|
||||
| `get_pipeline_counters` | 无 | `0` | `5003` | 计数 JSON |
|
||||
| `get_pipeline_config` | 无 | `0` | `5003` | 运行时配置 JSON |
|
||||
| `clear_pipeline_counters` | 无 | `0` | `5003` | 清零后返回计数 JSON |
|
||||
| `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` |
|
||||
| `get_plugins` | 无 | `0` | - | 合并 protocol/framer/mapper 插件列表 |
|
||||
| `get_plugins_protocols` | 无 | `0` | - | `protocols` |
|
||||
| `get_plugins_mappers` | 无 | `0` | - | `mappers` |
|
||||
| `list_metadata` | 无 | `0` | `5004` | `metadatas` + `catalogVersion` |
|
||||
| `get_metadata` | `metadataId` | `0` | `4046`/`5004` | 单条 metadata;`metadata_not_found` |
|
||||
| `upsert_metadata` | `payload`(须含 `metadataId`) | `0` | `4005`/`5004` | 写回字典文件,返回新 `catalogVersion` |
|
||||
| `delete_metadata` | `metadataId` | `0` | `4046`/`5004` | 删除并递增 `catalogVersion` |
|
||||
| `list_profiles` | 无 | `0` | - | `profiles` + `catalogVersion` |
|
||||
| `list_profiles_for_device` | `deviceId` | `0` | - | 按设备过滤的 `profiles` |
|
||||
| 其他未知 action | - | - | `4040` | 未知动作 |
|
||||
|
||||
## 5) HTTP REST 路由表(RestApiRoutes)
|
||||
## 5) 控制面入口(无 HTTP)
|
||||
|
||||
### 5.1 通用路由
|
||||
`softbus_daemon` 不再内置 HTTP 服务。浏览器侧请使用 **`softbus_orchestrator_py`**(`python -m softbus_orchestrator`):提供治理 REST、`/v1/command` 与 `/api/v1/*` 到 D-Bus 的 HTTP 兼容层,以及 DAG ingress。运维能力在进程内仍通过 **`executeCommand`** 转发到 `com.softbus.Daemon`。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/health` | 健康检查,返回 `{"ok": true}` |
|
||||
| `GET` | `/v1/status` | 透传 `get_status` 的状态响应 |
|
||||
| `POST` | `/v1/command` | 统一命令入口(请求体为 executeCommand JSON) |
|
||||
|
||||
### 5.2 设备树 / 设备路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/api/v1/device-tree` | 无 | 返回当前设备树快照 |
|
||||
| `GET` | `/api/v1/devices` | 无 | 返回当前运行态设备列表 |
|
||||
| `GET` | `/api/v1/devices/{id}` | path: `id(int)` | 返回指定运行态设备详情 |
|
||||
| `PATCH` | `/api/v1/device-tree/nodes/{id}` | body: 任意可变节点字段子集 | 部分更新设备树节点;当前至少保护 `id`、`params.stableKey`、`params.online`、`params.lastSeenTs` |
|
||||
|
||||
### 5.3 规则管理路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/api/v1/rules/status` | 无 | 返回动态规则计数状态 |
|
||||
| `POST` | `/api/v1/rules/filter` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `condition?` | 新增过滤规则 |
|
||||
| `DELETE` | `/api/v1/rules/filter/{id}` | path: `id` | 删除过滤规则 |
|
||||
| `POST` | `/api/v1/rules/transform` | body: `id`, `endpointHash?`, `deviceId?`, `stableKey?`, `scale?`, `offset?`, `unit?` | 新增转换规则 |
|
||||
| `DELETE` | `/api/v1/rules/transform/{id}` | path: `id` | 删除转换规则 |
|
||||
|
||||
- 说明:
|
||||
- `transform` 规则已经在 `PipelineEngine::applyTransformAndEnrich(...)` 中生效。
|
||||
- `filter` 路由当前主要提供“注册表管理”能力;动态 filter 规则尚未真正接入 `PipelineEngine::stage3Filter(...)` 的执行链路。
|
||||
|
||||
### 5.4 Pipeline 路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/api/v1/pipeline/status` | 无 | 返回 pipeline 运行状态、session 数量、队列容量、插件摘要与 counters |
|
||||
| `GET` | `/api/v1/pipeline/counters` | 无 | 返回 `in/out/drop/error/mergeDrop` 计数器快照 |
|
||||
| `GET` | `/api/v1/pipeline/config` | 无 | 返回当前运行时配置快照 |
|
||||
|
||||
- 说明:`/v1/status` 仍然是 control-plane 摘要(规则 + discovery + mapping),不等同于 `/api/v1/pipeline/status`。
|
||||
|
||||
### 5.5 插件 / 协议能力路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/api/v1/plugins` | 无 | 返回 parser / framer / mapper 三类逻辑插件的统一列表 |
|
||||
| `GET` | `/api/v1/plugins/protocols` | 无 | 返回按协议聚合后的能力快照(parser/framer/mapper pluginId) |
|
||||
| `GET` | `/api/v1/plugins/mappers` | 无 | 返回 mapper 插件列表 |
|
||||
|
||||
### 5.6 Metadata / Profile 路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `GET` | `/api/v1/metadata` | 无 | 返回 `data`(字典数组)与可选 `catalogVersion`(根级字段) |
|
||||
| `GET` | `/api/v1/metadata/{metadataId}` | path: `metadataId` | 返回指定 metadata 详情(含可选 `catalogVersion`) |
|
||||
| `GET` | `/api/v1/profiles` | 无 | 返回 `data` 与可选 `catalogVersion` |
|
||||
| `GET` | `/api/v1/profiles/{deviceId}` | path: `deviceId` | 返回指定 `deviceId` 关联的全部 profile(含可选 `catalogVersion`) |
|
||||
|
||||
### 5.7 Discovery 路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/api/v1/discovery/start` | body: `ttlMs?` | 开启嗅探;可设置自动关闭 TTL |
|
||||
| `POST` | `/api/v1/discovery/stop` | 无 | 关闭嗅探 |
|
||||
| `GET` | `/api/v1/discovery/status` | 无 | 获取发现池运行状态 |
|
||||
| `POST` | `/api/v1/discovery/clear` | 无 | 清空发现池样本 |
|
||||
| `GET` | `/api/v1/discovery/samples` | query: `offset?`, `limit?`, `endpointHash?`, `protocol?` | 拉取样本(按更新时间倒序,且过滤已映射 key) |
|
||||
|
||||
### 5.8 Mapping 路由
|
||||
|
||||
| 方法 | 路径 | 请求参数 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `POST` | `/api/v1/mapping/bind` | body: `compositeKey`, `domPath`, `scale?`, `offset?`, `unit?`, `metadataId?`, `pointId?`, `catalogVersion?`, `updatedBy?` | 绑定或更新映射规则并写入 `runtime_mappings.json` |
|
||||
| `POST` | `/api/v1/mapping/unbind` | body: `compositeKey` | 删除映射规则并持久化 |
|
||||
| `GET` | `/api/v1/mapping/list` | 无 | 返回 `items` 与 `mappingCatalogVersion` |
|
||||
治理 **`graph_catalog.json`**:`POST /api/v1/graphs` 按 **`graphId` 幂等 upsert**(合并字段,未传 `versions` 时保留已有版本);**拓扑与 `nodes`/`edges`** 应通过 **`POST /api/v1/graphs/{graph_id}/versions`** 追加版本体;若 `graph_id` 尚不存在,该接口会**自动创建**图壳再写入版本。**Pipeline 与 DOM 的可视化绑定** 若需统一图模型,需在产品与 daemon 侧另行定义配置 schema(当前无单一 REST)。
|
||||
|
||||
## 6) 通用错误码约定(当前实现)
|
||||
|
||||
@@ -161,14 +109,17 @@
|
||||
| `4042` | `transform_rule_not_found` | 删除转换规则时未找到 |
|
||||
| `4043` | `mapping_rule_not_found` | 删除映射规则时未找到 |
|
||||
| `5002` | `persist_mappings_failed` | 映射规则内存更新成功但写入 `runtime_mappings.json` 失败 |
|
||||
| `5003` | `pipeline_unavailable` / `device_tree_unavailable` | Pipeline 或设备树模型未就绪 |
|
||||
| `5004` | `metadata_*` | 元数据字典读写/解析/保存失败 |
|
||||
| `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` 未找到 |
|
||||
|
||||
## 7) 维护建议
|
||||
|
||||
- 新增 action 时,同时更新:
|
||||
- `CommandDispatcher::dispatch(...)`
|
||||
- 本文档第 4 节 action 表
|
||||
- 新增 REST 路由时,同时更新:
|
||||
- `RestApiRoutes.cpp`
|
||||
- 本文档第 5 节路由表
|
||||
- 若扩展返回结构,保持 `code/message/data` 三元组不变,避免客户端兼容性问题。
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#include "api/http/RestApiCommandHttp.h"
|
||||
|
||||
#include <QHttpServerResponse>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#include "api/ipc_dbus/CommandDispatcher.h"
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
QHttpServerResponse makeCommandHttpResponse(const QString& id, const softbus::api::ipc::DispatchResult& r)
|
||||
{
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("id"), id);
|
||||
o.insert(QStringLiteral("code"), r.code);
|
||||
o.insert(QStringLiteral("message"), r.message);
|
||||
o.insert(QStringLiteral("data"), r.data);
|
||||
const auto httpStatus =
|
||||
r.code == 0 ? QHttpServerResponse::StatusCode::Ok : QHttpServerResponse::StatusCode::BadRequest;
|
||||
return QHttpServerResponse(o, httpStatus);
|
||||
}
|
||||
|
||||
QHttpServerResponse handleExecuteCommandHttpBody(const QByteArray& body, softbus::api::ipc::CommandDispatcher& dispatcher)
|
||||
{
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(body, &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return makeCommandHttpResponse(QString(), {4000, QStringLiteral("invalid_json"), {}});
|
||||
}
|
||||
const QJsonObject req = doc.object();
|
||||
const QString id = req.value(QStringLiteral("id")).toString();
|
||||
const QString action = req.value(QStringLiteral("action")).toString();
|
||||
const QJsonObject params = req.value(QStringLiteral("params")).toObject();
|
||||
if (action.isEmpty()) {
|
||||
return makeCommandHttpResponse(id, {4003, QStringLiteral("invalid_action"), {}});
|
||||
}
|
||||
const auto result = dispatcher.dispatch(action, params);
|
||||
return makeCommandHttpResponse(id, result);
|
||||
}
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::api::ipc
|
||||
{
|
||||
struct DispatchResult;
|
||||
class CommandDispatcher;
|
||||
} // namespace softbus::api::ipc
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
/// Same envelope as D-Bus `executeCommand` response body (id/code/message/data).
|
||||
QHttpServerResponse makeCommandHttpResponse(const QString& id, const softbus::api::ipc::DispatchResult& r);
|
||||
|
||||
/// POST body same shape as D-Bus request: { "id", "action", "params" }.
|
||||
QHttpServerResponse handleExecuteCommandHttpBody(const QByteArray& body, softbus::api::ipc::CommandDispatcher& dispatcher);
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "api/http/RestApiHttpConfig.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
bool loadRestApiHttpListenOptions(const QString& configPath, RestApiHttpListenOptions* out, QString* err)
|
||||
{
|
||||
if (!out) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("null_output");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
*out = {};
|
||||
|
||||
if (configPath.isEmpty()) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("config_path_empty");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile f(configPath);
|
||||
if (!f.exists()) {
|
||||
return true;
|
||||
}
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("config_open_failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const QByteArray raw = f.readAll();
|
||||
f.close();
|
||||
|
||||
QJsonParseError pe;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(raw, &pe);
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("config_json_invalid");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const QJsonObject root = doc.object();
|
||||
const QJsonObject api = root.value(QStringLiteral("api")).toObject();
|
||||
const QJsonObject httpCfg = api.value(QStringLiteral("http")).toObject();
|
||||
if (httpCfg.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
out->sectionPresent = true;
|
||||
out->enabled = httpCfg.value(QStringLiteral("enabled")).toBool(false);
|
||||
out->bindHost = httpCfg.value(QStringLiteral("bind")).toString(QStringLiteral("127.0.0.1"));
|
||||
if (out->bindHost.isEmpty()) {
|
||||
out->bindHost = QStringLiteral("127.0.0.1");
|
||||
}
|
||||
out->port = httpCfg.value(QStringLiteral("port")).toInt(18080);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
/// Parsed `api.http` block from daemon_config.json.
|
||||
struct RestApiHttpListenOptions
|
||||
{
|
||||
bool sectionPresent{false};
|
||||
bool enabled{false};
|
||||
QString bindHost;
|
||||
int port{18080};
|
||||
};
|
||||
|
||||
/// Reads and validates `api.http`. On success returns true; on missing file returns true with sectionPresent=false.
|
||||
/// Hard errors (open/parse) set err and return false.
|
||||
bool loadRestApiHttpListenOptions(const QString& configPath, RestApiHttpListenOptions* out, QString* err = nullptr);
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,810 +0,0 @@
|
||||
#include "api/http/RestApiRoutes.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpHeaders>
|
||||
#include <QHttpServerRequest>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QHttpServerResponder>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonValue>
|
||||
#include <QSaveFile>
|
||||
#include <QUrlQuery>
|
||||
#include <utility>
|
||||
|
||||
#include "api/http/RestApiCommandHttp.h"
|
||||
#include "api/ipc_dbus/CommandDispatcher.h"
|
||||
#include "core/metadata/MetadataRegistry.h"
|
||||
#include "core/metadata/ProfileRegistry.h"
|
||||
#include "core/plugin_system/PluginManager.h"
|
||||
#include "device_bus/DeviceBus.h"
|
||||
#include "message_bus/pipeline/discovery/DiscoveryPool.h"
|
||||
#include "message_bus/pipeline/mapping/MappingRegistry.h"
|
||||
#include "message_bus/pipeline/PipelineEngine.h"
|
||||
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QHttpServerResponse withCorsHeaders(QHttpServerResponse response)
|
||||
{
|
||||
QHttpHeaders headers = response.headers();
|
||||
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowOrigin, QByteArrayView("*"));
|
||||
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowMethods,
|
||||
QByteArrayView("GET, POST, PATCH, DELETE, OPTIONS"));
|
||||
headers.append(QHttpHeaders::WellKnownHeader::AccessControlAllowHeaders,
|
||||
QByteArrayView("Content-Type, Authorization, X-Requested-With"));
|
||||
headers.append(QHttpHeaders::WellKnownHeader::AccessControlMaxAge, QByteArrayView("86400"));
|
||||
response.setHeaders(std::move(headers));
|
||||
return response;
|
||||
}
|
||||
|
||||
QHttpServerResponse corsPreflightResponse()
|
||||
{
|
||||
return withCorsHeaders(QHttpServerResponse(QHttpServerResponse::StatusCode::NoContent));
|
||||
}
|
||||
|
||||
void registerCorsPreflightRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/mapping/bind"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[]() { return corsPreflightResponse(); });
|
||||
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Options, routeContext,
|
||||
[](const QString&) { return corsPreflightResponse(); });
|
||||
}
|
||||
|
||||
constexpr auto kProtectedNodeKeys = std::array{
|
||||
"id",
|
||||
"stableKey",
|
||||
"online",
|
||||
"lastSeenTs",
|
||||
};
|
||||
|
||||
softbus::core::models::ProtocolType protocolFromString(const QString& s)
|
||||
{
|
||||
const QString x = s.trimmed().toUpper();
|
||||
if (x == QStringLiteral("MODBUS_RTU")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_RTU;
|
||||
}
|
||||
if (x == QStringLiteral("MODBUS_ASCII")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_ASCII;
|
||||
}
|
||||
if (x == QStringLiteral("MODBUS_TCP")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_TCP;
|
||||
}
|
||||
if (x == QStringLiteral("CAN_RAW")) {
|
||||
return softbus::core::models::ProtocolType::CAN_RAW;
|
||||
}
|
||||
if (x == QStringLiteral("CAN_OPEN")) {
|
||||
return softbus::core::models::ProtocolType::CAN_OPEN;
|
||||
}
|
||||
if (x == QStringLiteral("UDP_CUSTOM")) {
|
||||
return softbus::core::models::ProtocolType::UDP_CUSTOM;
|
||||
}
|
||||
return softbus::core::models::ProtocolType::UNKNOWN;
|
||||
}
|
||||
|
||||
QJsonObject parseJsonBody(const QHttpServerRequest& request, bool* ok = nullptr)
|
||||
{
|
||||
QJsonParseError err;
|
||||
const auto doc = QJsonDocument::fromJson(request.body(), &err);
|
||||
const bool parsed = err.error == QJsonParseError::NoError && doc.isObject();
|
||||
if (ok) {
|
||||
*ok = parsed;
|
||||
}
|
||||
if (!parsed) {
|
||||
return {};
|
||||
}
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QHttpServerResponse jsonResponse(const QJsonObject& obj,
|
||||
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
|
||||
{
|
||||
return withCorsHeaders(QHttpServerResponse(obj, code));
|
||||
}
|
||||
|
||||
QHttpServerResponse okDataResponse(const QJsonValue& data,
|
||||
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
|
||||
{
|
||||
return jsonResponse(QJsonObject{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}}, code);
|
||||
}
|
||||
|
||||
QHttpServerResponse okDataResponseWithCatalog(const QJsonValue& data,
|
||||
const QString& catalogVersion,
|
||||
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::Ok)
|
||||
{
|
||||
QJsonObject o{{QStringLiteral("ok"), true}, {QStringLiteral("data"), data}};
|
||||
if (!catalogVersion.isEmpty()) {
|
||||
o.insert(QStringLiteral("catalogVersion"), catalogVersion);
|
||||
}
|
||||
return jsonResponse(o, code);
|
||||
}
|
||||
|
||||
QHttpServerResponse errorResponse(const QString& error,
|
||||
QHttpServerResponse::StatusCode code = QHttpServerResponse::StatusCode::BadRequest)
|
||||
{
|
||||
return jsonResponse(QJsonObject{{QStringLiteral("ok"), false}, {QStringLiteral("error"), error}}, code);
|
||||
}
|
||||
|
||||
QJsonObject deviceTreeNodeToJson(const DeviceTreeNode& node)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("id"), node.id);
|
||||
obj.insert(QStringLiteral("name"), node.name);
|
||||
obj.insert(QStringLiteral("type"), node.type);
|
||||
obj.insert(QStringLiteral("endpoint"), node.endpoint);
|
||||
obj.insert(QStringLiteral("driver"), node.driver);
|
||||
obj.insert(QStringLiteral("capabilitiesRef"), node.capabilitiesRef);
|
||||
obj.insert(QStringLiteral("params"), node.params);
|
||||
obj.insert(QStringLiteral("enabled"), node.enabled);
|
||||
QJsonArray children;
|
||||
for (const auto& child : node.childrenIds) {
|
||||
children.push_back(child);
|
||||
}
|
||||
obj.insert(QStringLiteral("children"), children);
|
||||
return obj;
|
||||
}
|
||||
|
||||
QJsonArray devicesToJson(const std::vector<softbus::devices::DeviceInfo>& devices)
|
||||
{
|
||||
QJsonArray out;
|
||||
for (const auto& device : devices) {
|
||||
out.push_back(device.toJsonObject());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QString resolveConfigBaseDir()
|
||||
{
|
||||
const QString daemonConfigPath = softbus::device_bus::DeviceBus::instance().configPath();
|
||||
if (!daemonConfigPath.isEmpty()) {
|
||||
return QFileInfo(daemonConfigPath).dir().absolutePath() + QChar('/');
|
||||
}
|
||||
QString configBase = qEnvironmentVariable("SOFTBUS_CONFIG_DIR");
|
||||
if (!configBase.isEmpty()) {
|
||||
return configBase.endsWith(QChar('/')) ? configBase : configBase + QChar('/');
|
||||
}
|
||||
return QDir::homePath() + QStringLiteral("/softbus/config/");
|
||||
}
|
||||
|
||||
QString resolveConfigFile(const QString& fileName)
|
||||
{
|
||||
return resolveConfigBaseDir() + fileName;
|
||||
}
|
||||
|
||||
bool ensureConfigFileInitialized(const QString& targetPath,
|
||||
const QString& fallbackPath,
|
||||
const QByteArray& defaultContent)
|
||||
{
|
||||
if (QFile::exists(targetPath)) {
|
||||
return true;
|
||||
}
|
||||
const QFileInfo fi(targetPath);
|
||||
if (!QDir().mkpath(fi.dir().absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
QByteArray initBytes = defaultContent;
|
||||
QFile fallback(fallbackPath);
|
||||
if (!fallbackPath.isEmpty() && fallback.exists() && fallback.open(QIODevice::ReadOnly)) {
|
||||
initBytes = fallback.readAll();
|
||||
}
|
||||
QSaveFile initFile(targetPath);
|
||||
if (!initFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
if (initFile.write(initBytes) != initBytes.size()) {
|
||||
initFile.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
return initFile.commit();
|
||||
}
|
||||
|
||||
void ensureRestApiConfigFilesInitialized()
|
||||
{
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() {
|
||||
const QByteArray defaultRules = QJsonDocument(QJsonObject{
|
||||
{QStringLiteral("filter"), QJsonArray{}},
|
||||
{QStringLiteral("transform"), QJsonArray{}},
|
||||
}).toJson(QJsonDocument::Indented);
|
||||
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_rules.json")),
|
||||
QStringLiteral(),
|
||||
defaultRules);
|
||||
|
||||
const QByteArray defaultMappings = QStringLiteral(
|
||||
"{\n \"catalogVersion\": 1,\n \"items\": []\n}\n")
|
||||
.toUtf8();
|
||||
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("runtime_mappings.json")),
|
||||
QStringLiteral(),
|
||||
defaultMappings);
|
||||
|
||||
const QByteArray defaultProfiles = QStringLiteral(
|
||||
"{\n \"catalogVersion\": \"\",\n \"profiles\": []\n}\n")
|
||||
.toUtf8();
|
||||
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("device_profiles.json")),
|
||||
QStringLiteral("config/device_profiles.json"),
|
||||
defaultProfiles);
|
||||
|
||||
const QByteArray defaultMetadata = QStringLiteral(
|
||||
"{\n \"catalogVersion\": \"\",\n \"metadatas\": []\n}\n")
|
||||
.toUtf8();
|
||||
(void)ensureConfigFileInitialized(resolveConfigFile(QStringLiteral("metadata_dictionary.json")),
|
||||
QStringLiteral("config/metadata_dictionary.json"),
|
||||
defaultMetadata);
|
||||
});
|
||||
}
|
||||
|
||||
bool ensureMetadataLoaded(softbus::core::metadata::MetadataRegistry& registry, QString* error)
|
||||
{
|
||||
static std::mutex mtx;
|
||||
static bool attempted = false;
|
||||
static bool loaded = false;
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
if (!attempted) {
|
||||
ensureRestApiConfigFilesInitialized();
|
||||
attempted = true;
|
||||
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("metadata_dictionary.json")));
|
||||
}
|
||||
if (!loaded && error) {
|
||||
*error = registry.lastError();
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
softbus::core::metadata::MetadataRegistry& metadataRegistry()
|
||||
{
|
||||
static softbus::core::metadata::MetadataRegistry registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
bool ensureProfileLoaded(QString* error)
|
||||
{
|
||||
static std::mutex mtx;
|
||||
static bool attempted = false;
|
||||
static bool loaded = false;
|
||||
std::lock_guard<std::mutex> lk(mtx);
|
||||
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
|
||||
if (!attempted) {
|
||||
ensureRestApiConfigFilesInitialized();
|
||||
attempted = true;
|
||||
loaded = registry.loadFromFile(resolveConfigFile(QStringLiteral("device_profiles.json")));
|
||||
}
|
||||
if (!loaded && error) {
|
||||
*error = registry.lastError();
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool isProtectedNodeKey(const QString& key)
|
||||
{
|
||||
for (const auto* value : kProtectedNodeKeys) {
|
||||
if (key == QLatin1String(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool applyNodePatch(DeviceTreeNode& node, const QJsonObject& patch)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (patch.contains(QStringLiteral("name")) && patch.value(QStringLiteral("name")).isString()) {
|
||||
node.name = patch.value(QStringLiteral("name")).toString();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("type")) && patch.value(QStringLiteral("type")).isString()) {
|
||||
node.type = patch.value(QStringLiteral("type")).toString();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("endpoint")) && patch.value(QStringLiteral("endpoint")).isString()) {
|
||||
node.endpoint = patch.value(QStringLiteral("endpoint")).toString();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("driver")) && patch.value(QStringLiteral("driver")).isString()) {
|
||||
node.driver = patch.value(QStringLiteral("driver")).toString();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("capabilitiesRef")) && patch.value(QStringLiteral("capabilitiesRef")).isString()) {
|
||||
node.capabilitiesRef = patch.value(QStringLiteral("capabilitiesRef")).toString();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("enabled")) && patch.value(QStringLiteral("enabled")).isBool()) {
|
||||
node.enabled = patch.value(QStringLiteral("enabled")).toBool();
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("children")) && patch.value(QStringLiteral("children")).isArray()) {
|
||||
QVector<QString> children;
|
||||
for (const auto& child : patch.value(QStringLiteral("children")).toArray()) {
|
||||
if (child.isString()) {
|
||||
children.push_back(child.toString());
|
||||
}
|
||||
}
|
||||
node.childrenIds = children;
|
||||
changed = true;
|
||||
}
|
||||
if (patch.contains(QStringLiteral("params")) && patch.value(QStringLiteral("params")).isObject()) {
|
||||
const auto paramsPatch = patch.value(QStringLiteral("params")).toObject();
|
||||
for (auto it = paramsPatch.begin(); it != paramsPatch.end(); ++it) {
|
||||
if (isProtectedNodeKey(it.key())) {
|
||||
continue;
|
||||
}
|
||||
node.params.insert(it.key(), it.value());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void registerDiscoveryRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/discovery/start"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[](const QHttpServerRequest& request) {
|
||||
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
|
||||
const auto payload = parseJsonBody(request);
|
||||
const std::int64_t ttlMs = payload.value(QStringLiteral("ttlMs")).toInteger(0);
|
||||
pool.startSniffing(ttlMs);
|
||||
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/discovery/stop"), QHttpServerRequest::Method::Post, routeContext, []() {
|
||||
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
|
||||
pool.stopSniffing();
|
||||
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/discovery/status"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
|
||||
pool.tick();
|
||||
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/discovery/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
|
||||
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
|
||||
pool.clear();
|
||||
return withCorsHeaders(QHttpServerResponse(pool.status(), QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/discovery/samples"), QHttpServerRequest::Method::Get, routeContext,
|
||||
[](const QHttpServerRequest& request) {
|
||||
auto& pool = softbus::message_bus::pipeline::DiscoveryPool::instance();
|
||||
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
|
||||
pool.tick();
|
||||
const QUrlQuery query(request.url().query());
|
||||
const std::size_t offset =
|
||||
static_cast<std::size_t>(std::max(0, query.queryItemValue(QStringLiteral("offset")).toInt(0)));
|
||||
bool limitOk = false;
|
||||
const int limitCandidate = query.queryItemValue(QStringLiteral("limit")).toInt(&limitOk, 10);
|
||||
const int limitRaw = limitOk ? limitCandidate : 200;
|
||||
const std::size_t limit = static_cast<std::size_t>(std::clamp(limitRaw, 1, 500));
|
||||
const std::uint32_t endpointHash = static_cast<std::uint32_t>(
|
||||
query.queryItemValue(QStringLiteral("endpointHash")).toUInt(nullptr, 10));
|
||||
const auto protocol = protocolFromString(query.queryItemValue(QStringLiteral("protocol")));
|
||||
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("items"),
|
||||
pool.listSamples(offset, limit, endpointHash, protocol, mappingReg.snapshotMappedKeys()));
|
||||
o.insert(QStringLiteral("offset"), static_cast<qint64>(offset));
|
||||
o.insert(QStringLiteral("limit"), static_cast<qint64>(limit));
|
||||
o.insert(QStringLiteral("status"), pool.status());
|
||||
return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
}
|
||||
|
||||
void registerMappingRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/mapping/bind"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[](const QHttpServerRequest& request) {
|
||||
const auto payload = parseJsonBody(request);
|
||||
softbus::message_bus::pipeline::MappingRule rule;
|
||||
rule.compositeKey = payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
|
||||
rule.domPath = payload.value(QStringLiteral("domPath")).toString();
|
||||
rule.scale = payload.value(QStringLiteral("scale")).toDouble(1.0);
|
||||
rule.offset = payload.value(QStringLiteral("offset")).toDouble(0.0);
|
||||
rule.unit = payload.value(QStringLiteral("unit")).toString();
|
||||
rule.metadataId = payload.value(QStringLiteral("metadataId")).toString();
|
||||
rule.pointId = payload.value(QStringLiteral("pointId")).toString();
|
||||
rule.catalogVersion = payload.value(QStringLiteral("catalogVersion")).toString();
|
||||
rule.updatedBy = payload.value(QStringLiteral("updatedBy")).toString(QStringLiteral("http"));
|
||||
rule.updatedAtMs = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
|
||||
if (!mappingReg.upsert(rule)) {
|
||||
return withCorsHeaders(QHttpServerResponse(
|
||||
QJsonObject{{QStringLiteral("error"), QStringLiteral("invalid_mapping_rule")}},
|
||||
QHttpServerResponse::StatusCode::BadRequest));
|
||||
}
|
||||
ensureRestApiConfigFilesInitialized();
|
||||
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
|
||||
(void)mappingReg.savePersistedFile(mapPath);
|
||||
return withCorsHeaders(QHttpServerResponse(
|
||||
QJsonObject{{QStringLiteral("ok"), true},
|
||||
{QStringLiteral("compositeKey"), QString::number(rule.compositeKey)},
|
||||
{QStringLiteral("mappingCatalogVersion"),
|
||||
QString::number(mappingReg.mappingCatalogVersion())}},
|
||||
QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/mapping/unbind"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[](const QHttpServerRequest& request) {
|
||||
const auto payload = parseJsonBody(request);
|
||||
const std::uint64_t compositeKey =
|
||||
payload.value(QStringLiteral("compositeKey")).toString().toULongLong();
|
||||
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
|
||||
const bool ok = mappingReg.remove(compositeKey);
|
||||
if (ok) {
|
||||
ensureRestApiConfigFilesInitialized();
|
||||
const QString mapPath = resolveConfigFile(QStringLiteral("runtime_mappings.json"));
|
||||
(void)mappingReg.savePersistedFile(mapPath);
|
||||
}
|
||||
return withCorsHeaders(QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), ok},
|
||||
{QStringLiteral("compositeKey"),
|
||||
QString::number(compositeKey)}},
|
||||
ok ? QHttpServerResponse::StatusCode::Ok
|
||||
: QHttpServerResponse::StatusCode::NotFound));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/mapping/list"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto& mappingReg = softbus::message_bus::pipeline::MappingRegistry::instance();
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("items"), mappingReg.list());
|
||||
o.insert(QStringLiteral("mappingCatalogVersion"), mappingReg.mappingCatalogVersion());
|
||||
return withCorsHeaders(QHttpServerResponse(o, QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
}
|
||||
|
||||
void registerDeviceRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/device-tree"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
|
||||
if (!model) {
|
||||
return errorResponse(QStringLiteral("device_tree_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(model->toDeviceTreeJson());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/devices"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
return okDataResponse(devicesToJson(softbus::device_bus::DeviceBus::instance().listDevices()));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/devices/<arg>"), QHttpServerRequest::Method::Get, routeContext,
|
||||
[](int id) {
|
||||
const auto device = softbus::device_bus::DeviceBus::instance().findDeviceById(id);
|
||||
if (!device.has_value()) {
|
||||
return errorResponse(QStringLiteral("device_not_found"),
|
||||
QHttpServerResponse::StatusCode::NotFound);
|
||||
}
|
||||
return okDataResponse(device->toJsonObject());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/device-tree/nodes/<arg>"), QHttpServerRequest::Method::Patch, routeContext,
|
||||
[](const QString& id, const QHttpServerRequest& request) {
|
||||
bool bodyOk = false;
|
||||
const auto patch = parseJsonBody(request, &bodyOk);
|
||||
if (!bodyOk) {
|
||||
return errorResponse(QStringLiteral("invalid_json"),
|
||||
QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
|
||||
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
|
||||
if (!model) {
|
||||
return errorResponse(QStringLiteral("device_tree_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
const int index = model->findIndexById(id);
|
||||
if (index < 0) {
|
||||
return errorResponse(QStringLiteral("device_tree_node_not_found"),
|
||||
QHttpServerResponse::StatusCode::NotFound);
|
||||
}
|
||||
|
||||
auto& node = model->nodesMutable()[index];
|
||||
const bool changed = applyNodePatch(node, patch);
|
||||
if (!changed) {
|
||||
return errorResponse(QStringLiteral("no_mutable_fields_provided"),
|
||||
QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
|
||||
model->rebuildIndex();
|
||||
auto& bus = softbus::device_bus::DeviceBus::instance();
|
||||
bus.markDeviceTreeDirty();
|
||||
(void)bus.reconcileFromTreeModel();
|
||||
return okDataResponse(deviceTreeNodeToJson(model->nodes().at(index)));
|
||||
});
|
||||
}
|
||||
|
||||
void registerRuleRoutes(QHttpServer* server,
|
||||
QObject* routeContext,
|
||||
softbus::api::ipc::CommandDispatcher* dispatcher)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/rules/status"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
return okDataResponse(softbus::message_bus::pipeline::DynamicRuleRegistry::instance().status());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/rules/filter"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[dispatcher](const QHttpServerRequest& request) {
|
||||
bool bodyOk = false;
|
||||
const auto payload = parseJsonBody(request, &bodyOk);
|
||||
if (!bodyOk) {
|
||||
return errorResponse(QStringLiteral("invalid_json"),
|
||||
QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
const auto result = dispatcher->dispatch(QStringLiteral("add_filter"), payload);
|
||||
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/rules/filter/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
|
||||
[dispatcher](const QString& id) {
|
||||
const auto result = dispatcher->dispatch(QStringLiteral("remove_filter"),
|
||||
QJsonObject{{QStringLiteral("id"), id}});
|
||||
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/rules/transform"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[dispatcher](const QHttpServerRequest& request) {
|
||||
bool bodyOk = false;
|
||||
const auto payload = parseJsonBody(request, &bodyOk);
|
||||
if (!bodyOk) {
|
||||
return errorResponse(QStringLiteral("invalid_json"),
|
||||
QHttpServerResponse::StatusCode::BadRequest);
|
||||
}
|
||||
const auto result = dispatcher->dispatch(QStringLiteral("add_transform_rule"), payload);
|
||||
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/rules/transform/<arg>"), QHttpServerRequest::Method::Delete, routeContext,
|
||||
[dispatcher](const QString& id) {
|
||||
const auto result = dispatcher->dispatch(QStringLiteral("remove_transform_rule"),
|
||||
QJsonObject{{QStringLiteral("id"), id}});
|
||||
return withCorsHeaders(makeCommandHttpResponse(QString(), result));
|
||||
});
|
||||
}
|
||||
|
||||
void registerPipelineRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/pipeline/status"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!pipeline) {
|
||||
return errorResponse(QStringLiteral("pipeline_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(pipeline->status());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/pipeline/counters"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!pipeline) {
|
||||
return errorResponse(QStringLiteral("pipeline_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(pipeline->countersJson());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/pipeline/counters/clear"), QHttpServerRequest::Method::Post, routeContext, []() {
|
||||
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!pipeline) {
|
||||
return errorResponse(QStringLiteral("pipeline_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
pipeline->resetCounters();
|
||||
return okDataResponse(pipeline->countersJson());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/pipeline/config"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto* pipeline = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!pipeline) {
|
||||
return errorResponse(QStringLiteral("pipeline_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(pipeline->runtimeConfigJson());
|
||||
});
|
||||
}
|
||||
|
||||
void registerPluginRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/plugins"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pluginManager) {
|
||||
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
QJsonArray items;
|
||||
for (const auto& entry : pluginManager->protocolPluginsSnapshot()) {
|
||||
items.push_back(entry);
|
||||
}
|
||||
for (const auto& entry : pluginManager->protocolFramerPluginsSnapshot()) {
|
||||
items.push_back(entry);
|
||||
}
|
||||
for (const auto& entry : pluginManager->protocolMapperPluginsSnapshot()) {
|
||||
items.push_back(entry);
|
||||
}
|
||||
return okDataResponse(items);
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/plugins/protocols"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pluginManager) {
|
||||
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(pluginManager->protocolCapabilitiesSnapshot());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/plugins/mappers"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto pluginManager = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pluginManager) {
|
||||
return errorResponse(QStringLiteral("plugin_manager_unavailable"),
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponse(pluginManager->protocolMapperPluginsSnapshot());
|
||||
});
|
||||
}
|
||||
|
||||
void registerMetadataRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/metadata"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
auto& registry = metadataRegistry();
|
||||
QString error;
|
||||
if (!ensureMetadataLoaded(registry, &error)) {
|
||||
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/metadata/<arg>"), QHttpServerRequest::Method::Get, routeContext,
|
||||
[](const QString& metadataId) {
|
||||
auto& registry = metadataRegistry();
|
||||
QString error;
|
||||
if (!ensureMetadataLoaded(registry, &error)) {
|
||||
return errorResponse(error.isEmpty() ? QStringLiteral("metadata_unavailable") : error,
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
const auto items = registry.list();
|
||||
for (const auto& item : items) {
|
||||
const auto obj = item.toObject();
|
||||
if (obj.value(QStringLiteral("metadataId")).toString() == metadataId) {
|
||||
return okDataResponseWithCatalog(obj, registry.catalogVersion());
|
||||
}
|
||||
}
|
||||
return errorResponse(QStringLiteral("metadata_not_found"),
|
||||
QHttpServerResponse::StatusCode::NotFound);
|
||||
});
|
||||
}
|
||||
|
||||
void registerProfileRoutes(QHttpServer* server, QObject* routeContext)
|
||||
{
|
||||
server->route(QStringLiteral("/api/v1/profiles"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
QString error;
|
||||
if (!ensureProfileLoaded(&error)) {
|
||||
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
|
||||
return okDataResponseWithCatalog(registry.list(), registry.catalogVersion());
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/api/v1/profiles/<arg>"), QHttpServerRequest::Method::Get, routeContext,
|
||||
[](const QString& deviceId) {
|
||||
QString error;
|
||||
if (!ensureProfileLoaded(&error)) {
|
||||
return errorResponse(error.isEmpty() ? QStringLiteral("profiles_unavailable") : error,
|
||||
QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
auto& registry = softbus::core::metadata::ProfileRegistry::instance();
|
||||
return okDataResponseWithCatalog(registry.listByDeviceId(deviceId), registry.catalogVersion());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void registerSoftbusRestApiRoutes(QHttpServer* server,
|
||||
QObject* routeContext,
|
||||
softbus::api::ipc::CommandDispatcher* dispatcher)
|
||||
{
|
||||
if (!server || !routeContext || !dispatcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
server->route(QStringLiteral("/health"), QHttpServerRequest::Method::Get, routeContext, []() {
|
||||
return withCorsHeaders(
|
||||
QHttpServerResponse(QJsonObject{{QStringLiteral("ok"), true}}, QHttpServerResponse::StatusCode::Ok));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/v1/status"), QHttpServerRequest::Method::Get, routeContext, [dispatcher]() {
|
||||
return withCorsHeaders(makeCommandHttpResponse(QString(), dispatcher->dispatch(QStringLiteral("get_status"), {})));
|
||||
});
|
||||
|
||||
server->route(QStringLiteral("/v1/command"), QHttpServerRequest::Method::Post, routeContext,
|
||||
[dispatcher](const QHttpServerRequest& request) {
|
||||
return withCorsHeaders(handleExecuteCommandHttpBody(request.body(), *dispatcher));
|
||||
});
|
||||
|
||||
registerCorsPreflightRoutes(server, routeContext);
|
||||
registerDiscoveryRoutes(server, routeContext);
|
||||
registerMappingRoutes(server, routeContext);
|
||||
registerDeviceRoutes(server, routeContext);
|
||||
registerRuleRoutes(server, routeContext, dispatcher);
|
||||
registerPipelineRoutes(server, routeContext);
|
||||
registerPluginRoutes(server, routeContext);
|
||||
registerMetadataRoutes(server, routeContext);
|
||||
registerProfileRoutes(server, routeContext);
|
||||
|
||||
server->setMissingHandler(routeContext, [](const QHttpServerRequest&, QHttpServerResponder& responder) {
|
||||
QHttpServerResponse resp(QJsonObject{{QStringLiteral("error"), QStringLiteral("not_found")}},
|
||||
QHttpServerResponse::StatusCode::NotFound);
|
||||
responder.sendResponse(withCorsHeaders(std::move(resp)));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
class QObject;
|
||||
class QHttpServer;
|
||||
|
||||
namespace softbus::api::ipc
|
||||
{
|
||||
class CommandDispatcher;
|
||||
} // namespace softbus::api::ipc
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
/// Registers REST routes on `server`. `routeContext` is the QObject passed to QHttpServer::route (lifetime).
|
||||
void registerSoftbusRestApiRoutes(QHttpServer* server,
|
||||
QObject* routeContext,
|
||||
softbus::api::ipc::CommandDispatcher* dispatcher);
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,115 +0,0 @@
|
||||
#include "api/http/RestApiServer.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QHttpServer>
|
||||
#include <QTcpServer>
|
||||
|
||||
#include "api/http/RestApiHttpConfig.h"
|
||||
#include "api/http/RestApiRoutes.h"
|
||||
#include "api/ipc_dbus/CommandDispatcher.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
struct RestApiServer::Impl
|
||||
{
|
||||
std::unique_ptr<softbus::api::ipc::CommandDispatcher> dispatcher;
|
||||
std::unique_ptr<QHttpServer> http;
|
||||
std::unique_ptr<QTcpServer> tcp;
|
||||
};
|
||||
|
||||
RestApiServer::RestApiServer(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_impl(std::make_unique<Impl>())
|
||||
{
|
||||
m_impl->dispatcher = std::make_unique<softbus::api::ipc::CommandDispatcher>();
|
||||
}
|
||||
|
||||
RestApiServer::~RestApiServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void RestApiServer::stop()
|
||||
{
|
||||
if (!m_impl || !m_impl->http) {
|
||||
return;
|
||||
}
|
||||
m_impl->http->disconnect();
|
||||
m_impl->http.reset();
|
||||
m_impl->tcp.reset();
|
||||
}
|
||||
|
||||
bool RestApiServer::tryStartFromConfigFile(const QString& configPath, QString* err)
|
||||
{
|
||||
if (!m_impl || !m_impl->dispatcher) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("internal_error");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RestApiHttpListenOptions opts;
|
||||
if (!loadRestApiHttpListenOptions(configPath, &opts, err)) {
|
||||
return false;
|
||||
}
|
||||
if (!opts.sectionPresent) {
|
||||
LOG_INFO() << "RestApiServer: no api.http section, HTTP disabled";
|
||||
return true;
|
||||
}
|
||||
if (!opts.enabled) {
|
||||
LOG_INFO() << "RestApiServer: api.http.enabled is false or missing, HTTP disabled";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opts.port <= 0 || opts.port > 65535) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("invalid_port");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
m_impl->http = std::make_unique<QHttpServer>(this);
|
||||
m_impl->tcp = std::make_unique<QTcpServer>();
|
||||
|
||||
QHttpServer* srv = m_impl->http.get();
|
||||
registerSoftbusRestApiRoutes(srv, this, m_impl->dispatcher.get());
|
||||
|
||||
const QHostAddress addr(opts.bindHost);
|
||||
if (addr.isNull()) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("invalid_bind_address");
|
||||
}
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_impl->tcp->listen(addr, static_cast<quint16>(opts.port))) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("tcp_listen_failed");
|
||||
}
|
||||
LOG_ERROR() << "RestApiServer: listen failed bind=" << opts.bindHost << "port=" << opts.port
|
||||
<< "err=" << m_impl->tcp->errorString();
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!srv->bind(m_impl->tcp.get())) {
|
||||
if (err) {
|
||||
*err = QStringLiteral("http_bind_failed");
|
||||
}
|
||||
LOG_ERROR() << "RestApiServer: QHttpServer::bind failed";
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)m_impl->tcp.release();
|
||||
|
||||
LOG_INFO() << "RestApiServer: listening http://" << opts.bindHost << ":" << opts.port;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
namespace softbus::api::http
|
||||
{
|
||||
|
||||
class RestApiServer final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RestApiServer(QObject* parent = nullptr);
|
||||
~RestApiServer() override;
|
||||
|
||||
/// Reads `api.http` from JSON config. If disabled or missing, returns true and does nothing.
|
||||
/// If enabled but listen/bind fails, returns false and sets err.
|
||||
bool tryStartFromConfigFile(const QString& configPath, QString* err = nullptr);
|
||||
|
||||
void stop();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace softbus::api::http
|
||||
@@ -8,12 +8,20 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "core/metadata/MetadataRegistry.h"
|
||||
#include "core/metadata/ProfileRegistry.h"
|
||||
#include "core/models/RawBusMessage.h"
|
||||
#include "core/plugin_system/PluginManager.h"
|
||||
#include "device_bus/DeviceBus.h"
|
||||
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
|
||||
#include "device_bus/tree/DeviceTreeModel.h"
|
||||
#include "message_bus/pipeline/PipelineEngine.h"
|
||||
#include "message_bus/pipeline/discovery/DiscoveryPool.h"
|
||||
#include "message_bus/pipeline/mapping/MappingRegistry.h"
|
||||
#include "message_bus/pipeline/rules/DynamicRuleRegistry.h"
|
||||
#include "utils/logs/logging.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace softbus::api::ipc
|
||||
{
|
||||
|
||||
@@ -36,6 +44,36 @@ QString runtimeMappingsConfigPath()
|
||||
return fi.dir().filePath(QStringLiteral("runtime_mappings.json"));
|
||||
}
|
||||
|
||||
QString metadataDictionaryPath()
|
||||
{
|
||||
const QFileInfo fi(rulesConfigPath());
|
||||
return fi.dir().filePath(QStringLiteral("metadata_dictionary.json"));
|
||||
}
|
||||
|
||||
softbus::core::models::ProtocolType parseProtocolFilter(const QString& s)
|
||||
{
|
||||
const QString u = s.trimmed().toUpper();
|
||||
if (u == QLatin1String("MODBUS_RTU")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_RTU;
|
||||
}
|
||||
if (u == QLatin1String("MODBUS_ASCII")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_ASCII;
|
||||
}
|
||||
if (u == QLatin1String("MODBUS_TCP")) {
|
||||
return softbus::core::models::ProtocolType::MODBUS_TCP;
|
||||
}
|
||||
if (u == QLatin1String("CAN_RAW")) {
|
||||
return softbus::core::models::ProtocolType::CAN_RAW;
|
||||
}
|
||||
if (u == QLatin1String("CAN_OPEN")) {
|
||||
return softbus::core::models::ProtocolType::CAN_OPEN;
|
||||
}
|
||||
if (u == QLatin1String("UDP_CUSTOM")) {
|
||||
return softbus::core::models::ProtocolType::UDP_CUSTOM;
|
||||
}
|
||||
return softbus::core::models::ProtocolType::UNKNOWN;
|
||||
}
|
||||
|
||||
bool persistMappingsToDedicatedConfig(const softbus::message_bus::pipeline::MappingRegistry& reg)
|
||||
{
|
||||
const QString path = runtimeMappingsConfigPath();
|
||||
@@ -254,7 +292,10 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
|
||||
QJsonObject{{QStringLiteral("compositeKey"), keyText}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_mapping_rules")) {
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("items"), mappingReg.list()}}};
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("items"), mappingReg.list()},
|
||||
{QStringLiteral("mappingCatalogVersion"), mappingReg.mappingCatalogVersion()}}};
|
||||
}
|
||||
if (action == QStringLiteral("discovery_start")) {
|
||||
const auto ttlMs = params.value(QStringLiteral("ttlMs")).toInteger(0);
|
||||
@@ -269,6 +310,307 @@ DispatchResult CommandDispatcher::dispatch(const QString& action, const QJsonObj
|
||||
discovery.clear();
|
||||
return {0, QStringLiteral("ok"), discovery.status()};
|
||||
}
|
||||
if (action == QStringLiteral("get_rules_status")) {
|
||||
QJsonObject o = reg.status();
|
||||
const QJsonObject ms = mappingReg.status();
|
||||
o.insert(QStringLiteral("mappingRuleCount"), ms.value(QStringLiteral("mappingRuleCount")));
|
||||
o.insert(QStringLiteral("mappingCatalogVersion"), ms.value(QStringLiteral("mappingCatalogVersion")));
|
||||
return {0, QStringLiteral("ok"), o};
|
||||
}
|
||||
if (action == QStringLiteral("get_discovery_status")) {
|
||||
return {0, QStringLiteral("ok"), discovery.status()};
|
||||
}
|
||||
if (action == QStringLiteral("list_discovery_samples")) {
|
||||
const std::size_t offset = static_cast<std::size_t>(std::max<qint64>(0, params.value(QStringLiteral("offset")).toInteger(0)));
|
||||
const std::size_t limit = static_cast<std::size_t>(std::max<qint64>(1, params.value(QStringLiteral("limit")).toInteger(200)));
|
||||
const std::uint32_t endpointFilter = static_cast<std::uint32_t>(params.value(QStringLiteral("endpointHash")).toInteger(0));
|
||||
const QString protoText = params.value(QStringLiteral("protocol")).toString();
|
||||
const auto proto = parseProtocolFilter(protoText);
|
||||
const auto mappedKeys = mappingReg.snapshotMappedKeys();
|
||||
const QJsonArray arr = discovery.listSamples(offset, limit, endpointFilter, proto, mappedKeys);
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("samples"), arr}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_pipeline_status")) {
|
||||
auto* eng = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!eng) {
|
||||
return {5003, QStringLiteral("pipeline_unavailable"), {}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), eng->status()};
|
||||
}
|
||||
if (action == QStringLiteral("get_pipeline_counters")) {
|
||||
auto* eng = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!eng) {
|
||||
return {5003, QStringLiteral("pipeline_unavailable"), {}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), eng->countersJson()};
|
||||
}
|
||||
if (action == QStringLiteral("get_pipeline_config")) {
|
||||
auto* eng = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!eng) {
|
||||
return {5003, QStringLiteral("pipeline_unavailable"), {}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), eng->runtimeConfigJson()};
|
||||
}
|
||||
if (action == QStringLiteral("clear_pipeline_counters")) {
|
||||
auto* eng = softbus::device_bus::DeviceBus::instance().pipelineEngine();
|
||||
if (!eng) {
|
||||
return {5003, QStringLiteral("pipeline_unavailable"), {}};
|
||||
}
|
||||
eng->resetCounters();
|
||||
return {0, QStringLiteral("ok"), eng->countersJson()};
|
||||
}
|
||||
if (action == QStringLiteral("get_device_tree")) {
|
||||
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
|
||||
if (!model) {
|
||||
return {5003, QStringLiteral("device_tree_unavailable"), {}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("tree"), model->toDeviceTreeJson()}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_devices")) {
|
||||
const auto devices = softbus::device_bus::DeviceBus::instance().listDevices();
|
||||
QJsonArray arr;
|
||||
for (const auto& d : devices) {
|
||||
arr.push_back(d.toJsonObject());
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("devices"), arr}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_device")) {
|
||||
const int id = params.value(QStringLiteral("deviceId")).toInt(-1);
|
||||
const auto opt = softbus::device_bus::DeviceBus::instance().findDeviceById(id);
|
||||
if (!opt.has_value()) {
|
||||
return {4044, QStringLiteral("device_not_found"), {}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), opt->toJsonObject()};
|
||||
}
|
||||
if (action == QStringLiteral("patch_device_tree_node")) {
|
||||
const QString id = params.value(QStringLiteral("id")).toString();
|
||||
const QJsonObject patch = params.value(QStringLiteral("patch")).toObject();
|
||||
auto model = softbus::device_bus::DeviceBus::instance().deviceTreeModel();
|
||||
if (!model) {
|
||||
return {5003, QStringLiteral("device_tree_unavailable"), {}};
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
model->rebuildIndex();
|
||||
softbus::device_bus::DeviceBus::instance().markDeviceTreeDirty();
|
||||
(void)softbus::device_bus::DeviceBus::instance().reconcileFromTreeModel();
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("id"), id}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_plugins")) {
|
||||
const auto pm = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pm) {
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("plugins"), QJsonArray{}}}};
|
||||
}
|
||||
QJsonArray all;
|
||||
for (const auto& v : pm->protocolPluginsSnapshot()) {
|
||||
all.push_back(v);
|
||||
}
|
||||
for (const auto& v : pm->protocolFramerPluginsSnapshot()) {
|
||||
all.push_back(v);
|
||||
}
|
||||
for (const auto& v : pm->protocolMapperPluginsSnapshot()) {
|
||||
all.push_back(v);
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("plugins"), all}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_plugins_protocols")) {
|
||||
const auto pm = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pm) {
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("protocols"), QJsonArray{}}}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("protocols"), pm->protocolCapabilitiesSnapshot()}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_plugins_mappers")) {
|
||||
const auto pm = softbus::device_bus::DeviceBus::instance().pluginManager();
|
||||
if (!pm) {
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("mappers"), QJsonArray{}}}};
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("mappers"), pm->protocolMapperPluginsSnapshot()}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_metadata")) {
|
||||
softbus::core::metadata::MetadataRegistry mr;
|
||||
const QString p = metadataDictionaryPath();
|
||||
if (!mr.loadFromFile(p)) {
|
||||
return {5004, mr.lastError(), {}};
|
||||
}
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("metadatas"), mr.list()},
|
||||
{QStringLiteral("catalogVersion"), mr.catalogVersion()}}};
|
||||
}
|
||||
if (action == QStringLiteral("get_metadata")) {
|
||||
const QString mid = params.value(QStringLiteral("metadataId")).toString();
|
||||
QFile f(metadataDictionaryPath());
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return {5004, QStringLiteral("metadata_open_failed"), {}};
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
|
||||
f.close();
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return {5004, QStringLiteral("metadata_json_invalid"), {}};
|
||||
}
|
||||
const QJsonObject root = doc.object();
|
||||
const QJsonArray items = root.value(QStringLiteral("metadatas")).toArray();
|
||||
for (const auto& it : items) {
|
||||
const QJsonObject o = it.toObject();
|
||||
if (o.value(QStringLiteral("metadataId")).toString() == mid) {
|
||||
return {0, QStringLiteral("ok"), o};
|
||||
}
|
||||
}
|
||||
return {4046, QStringLiteral("metadata_not_found"), {}};
|
||||
}
|
||||
if (action == QStringLiteral("upsert_metadata")) {
|
||||
const QJsonObject payload = params.value(QStringLiteral("payload")).toObject();
|
||||
const QString mid = payload.value(QStringLiteral("metadataId")).toString();
|
||||
if (mid.isEmpty()) {
|
||||
return {4005, QStringLiteral("invalid_metadata"), {}};
|
||||
}
|
||||
const QString path = metadataDictionaryPath();
|
||||
QJsonObject root;
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return {5004, QStringLiteral("metadata_open_failed"), {}};
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
|
||||
f.close();
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return {5004, QStringLiteral("metadata_json_invalid"), {}};
|
||||
}
|
||||
root = doc.object();
|
||||
}
|
||||
QJsonArray items = root.value(QStringLiteral("metadatas")).toArray();
|
||||
bool updated = false;
|
||||
for (int i = 0; i < items.size(); ++i) {
|
||||
QJsonObject o = items.at(i).toObject();
|
||||
if (o.value(QStringLiteral("metadataId")).toString() != mid) {
|
||||
continue;
|
||||
}
|
||||
for (auto it = payload.constBegin(); it != payload.constEnd(); ++it) {
|
||||
o.insert(it.key(), it.value());
|
||||
}
|
||||
o.insert(QStringLiteral("metadataId"), mid);
|
||||
items[i] = o;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
if (!updated) {
|
||||
QJsonObject o = payload;
|
||||
o.insert(QStringLiteral("metadataId"), mid);
|
||||
items.push_back(o);
|
||||
}
|
||||
root.insert(QStringLiteral("metadatas"), items);
|
||||
const QString ver = QString::number(QDateTime::currentMSecsSinceEpoch());
|
||||
root.insert(QStringLiteral("catalogVersion"), ver);
|
||||
{
|
||||
QFileInfo fi(path);
|
||||
(void)QDir().mkpath(fi.dir().absolutePath());
|
||||
QSaveFile out(path);
|
||||
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return {5004, QStringLiteral("metadata_save_failed"), {}};
|
||||
}
|
||||
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented);
|
||||
if (out.write(bytes) != bytes.size() || !out.commit()) {
|
||||
return {5004, QStringLiteral("metadata_save_commit_failed"), {}};
|
||||
}
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("metadataId"), mid}, {QStringLiteral("catalogVersion"), ver}}};
|
||||
}
|
||||
if (action == QStringLiteral("delete_metadata")) {
|
||||
const QString mid = params.value(QStringLiteral("metadataId")).toString();
|
||||
const QString path = metadataDictionaryPath();
|
||||
QJsonObject root;
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return {5004, QStringLiteral("metadata_open_failed"), {}};
|
||||
}
|
||||
QJsonParseError pe;
|
||||
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
|
||||
f.close();
|
||||
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return {5004, QStringLiteral("metadata_json_invalid"), {}};
|
||||
}
|
||||
root = doc.object();
|
||||
}
|
||||
QJsonArray items = root.value(QStringLiteral("metadatas")).toArray();
|
||||
QJsonArray next;
|
||||
bool removed = false;
|
||||
for (const auto& it : items) {
|
||||
const QJsonObject o = it.toObject();
|
||||
if (o.value(QStringLiteral("metadataId")).toString() == mid) {
|
||||
removed = true;
|
||||
continue;
|
||||
}
|
||||
next.push_back(o);
|
||||
}
|
||||
if (!removed) {
|
||||
return {4046, QStringLiteral("metadata_not_found"), {}};
|
||||
}
|
||||
root.insert(QStringLiteral("metadatas"), next);
|
||||
const QString ver = QString::number(QDateTime::currentMSecsSinceEpoch());
|
||||
root.insert(QStringLiteral("catalogVersion"), ver);
|
||||
{
|
||||
QFileInfo fi(path);
|
||||
(void)QDir().mkpath(fi.dir().absolutePath());
|
||||
QSaveFile out(path);
|
||||
if (!out.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return {5004, QStringLiteral("metadata_save_failed"), {}};
|
||||
}
|
||||
const QByteArray bytes = QJsonDocument(root).toJson(QJsonDocument::Indented);
|
||||
if (out.write(bytes) != bytes.size() || !out.commit()) {
|
||||
return {5004, QStringLiteral("metadata_save_commit_failed"), {}};
|
||||
}
|
||||
}
|
||||
return {0, QStringLiteral("ok"), QJsonObject{{QStringLiteral("deleted"), mid}, {QStringLiteral("catalogVersion"), ver}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_profiles")) {
|
||||
auto& pr = softbus::core::metadata::ProfileRegistry::instance();
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("profiles"), pr.list()},
|
||||
{QStringLiteral("catalogVersion"), pr.catalogVersion()}}};
|
||||
}
|
||||
if (action == QStringLiteral("list_profiles_for_device")) {
|
||||
const QString deviceId = params.value(QStringLiteral("deviceId")).toString();
|
||||
auto& pr = softbus::core::metadata::ProfileRegistry::instance();
|
||||
return {0,
|
||||
QStringLiteral("ok"),
|
||||
QJsonObject{{QStringLiteral("profiles"), pr.listByDeviceId(deviceId)},
|
||||
{QStringLiteral("catalogVersion"), pr.catalogVersion()}}};
|
||||
}
|
||||
return {4040, QStringLiteral("unknown_action"), {}};
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,12 @@ QString IpcDBus::executeCommand(const QString& requestJson)
|
||||
o.insert(QStringLiteral("code"), code);
|
||||
o.insert(QStringLiteral("message"), message);
|
||||
o.insert(QStringLiteral("data"), data);
|
||||
if (data.contains(QStringLiteral("catalogVersion"))) {
|
||||
o.insert(QStringLiteral("catalogVersion"), data.value(QStringLiteral("catalogVersion")));
|
||||
}
|
||||
if (data.contains(QStringLiteral("mappingCatalogVersion"))) {
|
||||
o.insert(QStringLiteral("mappingCatalogVersion"), data.value(QStringLiteral("mappingCatalogVersion")));
|
||||
}
|
||||
return QString::fromUtf8(QJsonDocument(o).toJson(QJsonDocument::Compact));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user