first commit

This commit is contained in:
flowerstonezl
2026-06-09 16:50:28 +08:00
commit 70f675a48b
93 changed files with 3802 additions and 0 deletions

105
.gitignore vendored Normal file
View File

@@ -0,0 +1,105 @@
# SoftBus OPC workspace — generated / local files
# ------------------------------------------------------------------------------
# Editor & IDE
# ------------------------------------------------------------------------------
*~
*.autosave
*.swp
.*.swp
.vscode/
.idea/
.cursor/
*.pro.user*
*.qbs.user*
CMakeLists.txt.user*
# ------------------------------------------------------------------------------
# OS
# ------------------------------------------------------------------------------
.DS_Store
Thumbs.db
.directory
# ------------------------------------------------------------------------------
# CMake / build
# ------------------------------------------------------------------------------
/build/
/out/
/cmake-build-*/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
Testing/
compile_commands.json
*.cmake.user
# FetchContent / third-party checkout (under build tree)
/_deps/
# ------------------------------------------------------------------------------
# Compiled objects & libraries
# ------------------------------------------------------------------------------
*.o
*.obj
*.lo
*.a
*.lib
*.so
*.so.*
*.dll
*.dylib
*.exe
*.pdb
*.ilk
*.idb
*.exp
*.manifest
*.res
*.rc
# MinGW / MSVC extras
*.Debug
*.Release
*.i
*.s
# ------------------------------------------------------------------------------
# Qt / moc (if later used)
# ------------------------------------------------------------------------------
*.moc
moc_*.cpp
ui_*.h
qrc_*.cpp
*.qm
*.prl
*.app
/.qmake.cache
/.qmake.stash
# ------------------------------------------------------------------------------
# Python / scripts
# ------------------------------------------------------------------------------
*.pyc
__pycache__/
.cache/
# ------------------------------------------------------------------------------
# Logs & temp
# ------------------------------------------------------------------------------
*.log
*.tmp
*.temp
core
*.core
# ------------------------------------------------------------------------------
# Local secrets (never commit)
# ------------------------------------------------------------------------------
.env
.env.*
*.pem
*.key
credentials.json

28
CMakeLists.txt Normal file
View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.16)
project(softbus_workspace LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(SoftbusPlatform)
include(SoftbusThirdParty)
include(SoftbusPackage)
add_subdirectory(src/softbus_core)
add_subdirectory(src/softbus_transport)
add_subdirectory(src/softbus_api)
add_subdirectory(src/softbus_opcua)
add_subdirectory(src/plugins/protocol_modbus)
add_subdirectory(src/plugins/protocol_canopen)
add_subdirectory(src/softbus_edge)
add_subdirectory(src/softbus_daemon)
message(STATUS "Softbus workspace configured for ${SOFTBUS_PLATFORM_NAME}")

View File

@@ -0,0 +1,23 @@
# ROS-like package registration helper
macro(softbus_package name)
set(SOFTBUS_CURRENT_PACKAGE ${name})
endmacro()
function(softbus_export_include target include_dir)
target_include_directories(${target}
PUBLIC
$<BUILD_INTERFACE:${include_dir}>
$<INSTALL_INTERFACE:include>
)
endfunction()
function(softbus_add_plugin target)
set_target_properties(${target} PROPERTIES
PREFIX ""
OUTPUT_NAME ${target}
)
if(WIN32)
target_compile_definitions(${target} PRIVATE SOFTBUS_PLUGIN_EXPORTS)
endif()
endfunction()

106
cmake/SoftbusPlatform.cmake Normal file
View File

@@ -0,0 +1,106 @@
# Platform detection and conditional dependencies
if(WIN32)
set(SOFTBUS_PLATFORM_NAME "Windows")
add_compile_definitions(SOFTBUS_PLATFORM_WINDOWS NOMINMAX WIN32_LEAN_AND_MEAN)
else()
set(SOFTBUS_PLATFORM_NAME "Linux")
add_compile_definitions(SOFTBUS_PLATFORM_LINUX)
endif()
find_package(Threads REQUIRED)
if(WIN32)
set(SOFTBUS_HAS_DBUS OFF)
set(SOFTBUS_HAS_UDEV OFF)
else()
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(DBUS QUIET dbus-1)
if(DBUS_FOUND)
set(SOFTBUS_HAS_DBUS ON)
else()
set(SOFTBUS_HAS_DBUS OFF)
message(WARNING "libdbus-1 not found; DBus IPC will be disabled on Linux")
endif()
else()
set(SOFTBUS_HAS_DBUS OFF)
endif()
find_library(UDEV_LIB udev)
if(UDEV_LIB)
set(SOFTBUS_HAS_UDEV ON)
else()
set(SOFTBUS_HAS_UDEV OFF)
message(WARNING "libudev not found; udev device monitor will use stub on Linux")
endif()
endif()
# ZeroMQ
option(SOFTBUS_WITH_ZMQ "Enable ZeroMQ transport" OFF)
if(SOFTBUS_WITH_ZMQ)
if(PkgConfig_FOUND)
pkg_check_modules(ZMQ QUIET libzmq)
endif()
if(NOT ZMQ_FOUND)
find_library(ZMQ_LIBRARY NAMES zmq libzmq)
find_path(ZMQ_INCLUDE_DIR zmq.h)
if(ZMQ_LIBRARY AND ZMQ_INCLUDE_DIR)
set(ZMQ_FOUND TRUE)
set(ZMQ_LIBRARIES ${ZMQ_LIBRARY})
set(ZMQ_INCLUDE_DIRS ${ZMQ_INCLUDE_DIR})
endif()
endif()
if(ZMQ_FOUND)
set(SOFTBUS_HAS_ZMQ ON)
message(STATUS "ZeroMQ found: ${ZMQ_LIBRARIES}")
else()
set(SOFTBUS_HAS_ZMQ OFF)
message(WARNING "ZeroMQ not found; set SOFTBUS_WITH_ZMQ=OFF or install libzmq")
endif()
else()
set(SOFTBUS_HAS_ZMQ OFF)
endif()
# open62541 (optional until daemon links it)
option(SOFTBUS_WITH_OPCUA "Enable OPC UA server in daemon" ON)
function(softbus_apply_platform_libs target)
target_link_libraries(${target} PRIVATE Threads::Threads)
if(WIN32)
target_link_libraries(${target} PRIVATE ws2_32 wsock32)
endif()
if(MINGW)
target_compile_options(${target} PRIVATE -fno-stack-protector)
target_link_options(${target} PRIVATE -fno-stack-protector)
endif()
endfunction()
function(softbus_apply_zmq target)
if(SOFTBUS_HAS_ZMQ)
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_ZMQ=1)
target_include_directories(${target} PRIVATE ${ZMQ_INCLUDE_DIRS})
target_link_libraries(${target} PRIVATE ${ZMQ_LIBRARIES})
else()
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_ZMQ=0)
endif()
endfunction()
function(softbus_apply_dbus target)
if(SOFTBUS_HAS_DBUS)
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_DBUS=1)
target_include_directories(${target} PRIVATE ${DBUS_INCLUDE_DIRS})
target_link_libraries(${target} PRIVATE ${DBUS_LIBRARIES})
else()
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_DBUS=0)
endif()
endfunction()
function(softbus_apply_udev target)
if(SOFTBUS_HAS_UDEV)
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_UDEV=1)
target_link_libraries(${target} PRIVATE ${UDEV_LIB})
else()
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_UDEV=0)
endif()
endfunction()

View File

@@ -0,0 +1,79 @@
include(FetchContent)
# nlohmann_json
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(nlohmann_json)
# standalone asio
FetchContent_Declare(
asio
GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git
GIT_TAG asio-1-30-2
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(asio)
if(NOT asio_POPULATED)
FetchContent_Populate(asio)
add_library(asio INTERFACE)
target_include_directories(asio INTERFACE ${asio_SOURCE_DIR}/asio/include)
target_compile_definitions(asio INTERFACE ASIO_STANDALONE ASIO_NO_DEPRECATED)
if(WIN32)
target_compile_definitions(asio INTERFACE _WIN32_WINNT=0x0601)
endif()
endif()
# open62541
if(SOFTBUS_WITH_OPCUA)
set(UA_ENABLE_AMALGAMATION OFF CACHE BOOL "" FORCE)
if(MINGW)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-stack-protector")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-stack-protector")
endif()
set(_softbus_saved_build_shared ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(UA_ENABLE_SUBSCRIPTIONS ON CACHE BOOL "" FORCE)
set(UA_ENABLE_METHODCALLS ON CACHE BOOL "" FORCE)
set(UA_ENABLE_DISCOVERY ON CACHE BOOL "" FORCE)
set(UA_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(UA_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
set(UA_ENABLE_ENCRYPTION OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
open62541
GIT_REPOSITORY https://github.com/open62541/open62541.git
GIT_TAG v1.4.6
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(open62541)
set(BUILD_SHARED_LIBS ${_softbus_saved_build_shared} CACHE BOOL "" FORCE)
if(MINGW AND TARGET open62541)
set_target_properties(open62541 PROPERTIES
INTERFACE_LINK_LIBRARIES "ws2_32;iphlpapi"
)
target_compile_options(open62541 PRIVATE -fno-stack-protector)
target_link_options(open62541 PRIVATE -fno-stack-protector)
endif()
set(SOFTBUS_HAS_OPCUA ON)
else()
set(SOFTBUS_HAS_OPCUA OFF)
endif()
function(softbus_apply_open62541 target)
if(SOFTBUS_HAS_OPCUA)
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_OPCUA=1)
target_link_libraries(${target} PRIVATE open62541)
else()
target_compile_definitions(${target} PRIVATE SOFTBUS_HAS_OPCUA=0)
endif()
endfunction()

View File

@@ -0,0 +1,19 @@
{
"opcua": {
"port": 4840,
"applicationName": "SoftBusDaemon"
},
"uplink": {
"listenHost": "127.0.0.1",
"listenPort": 9000
},
"plugins": {
"modbus": "protocol_modbus",
"canopen": "protocol_canopen"
},
"security": {
"tlsEnabled": false,
"certPath": "",
"keyPath": ""
}
}

18
config/softbus_model.json Normal file
View File

@@ -0,0 +1,18 @@
{
"site": "DemoSite",
"system": "DemoSystem",
"asset": "DemoAsset",
"devices": [
{
"stableDeviceKey": "SimModbusDevice",
"protocol": "modbus",
"points": [
{
"name": "Temperature",
"register": 40001,
"domain": "Process"
}
]
}
]
}

11
config/ua_model.xml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Phase1 placeholder UA model file.
The daemon builds the address space dynamically from config/softbus_model.json.
Phase2 may import a static NodeSet2 XML generated from this template.
-->
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd">
<Models>
<Model ModelUri="urn:softbus:demo" Version="1.0.0"/>
</Models>
</UANodeSet>

View File

@@ -0,0 +1,11 @@
softbus_package(protocol_canopen)
add_library(protocol_canopen SHARED
src/canopen_plugin.cpp
)
softbus_export_include(protocol_canopen "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(protocol_canopen PRIVATE softbus_core)
softbus_add_plugin(protocol_canopen)
softbus_apply_platform_libs(protocol_canopen)

View File

@@ -0,0 +1,13 @@
#pragma once
#include <softbus_core/plugin/IFramer.h>
namespace canopen {
class CanopenFramer : public softbus::core::IFramer {
public:
softbus::core::FrameResult extract(const uint8_t* data, std::size_t size) override;
const char* protocolName() const override { return "canopen"; }
};
} // namespace canopen

View File

@@ -0,0 +1,15 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <cstdint>
#include <string>
namespace canopen {
class CanopenMapper {
public:
bool mapToDom(uint16_t index, uint8_t subIndex, softbus::core::DOMMessage* message);
};
} // namespace canopen

View File

@@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
namespace canopen {
class CanopenNodeManager {
public:
bool registerNode(uint8_t nodeId);
bool setState(uint8_t nodeId, const std::string& state);
std::string state(uint8_t nodeId) const;
private:
std::map<uint8_t, std::string> states_;
};
} // namespace canopen

View File

@@ -0,0 +1,25 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <cstdint>
#include <map>
#include <string>
namespace canopen {
class CanopenObjectDictionary {
public:
bool loadEds(const std::string& path);
bool loadDcf(const std::string& path);
bool hasEntry(uint16_t index, uint8_t subIndex) const;
private:
struct Entry {
std::string name;
std::string dataType;
};
std::map<uint32_t, Entry> entries_;
};
} // namespace canopen

View File

@@ -0,0 +1,14 @@
#pragma once
#include <softbus_core/plugin/IParser.h>
namespace canopen {
class CanopenParser : public softbus::core::IParser {
public:
softbus::core::ParseResult parseFrame(const uint8_t* data, std::size_t size,
const std::string& sourceId) override;
const char* protocolName() const override { return "canopen"; }
};
} // namespace canopen

View File

@@ -0,0 +1,14 @@
#pragma once
#include <canopen/CanopenObjectDictionary.h>
#include <string>
namespace canopen {
class EdsDcfLoader {
public:
bool load(const std::string& path, CanopenObjectDictionary* dictionary);
};
} // namespace canopen

View File

@@ -0,0 +1,57 @@
#include <canopen/CanopenFramer.h>
#include <canopen/CanopenParser.h>
#include <softbus_core/plugin/IParser.h>
#include <canopen/CanopenObjectDictionary.h>
#include <canopen/EdsDcfLoader.h>
#include <canopen/CanopenNodeManager.h>
#include <canopen/CanopenMapper.h>
namespace canopen {
softbus::core::FrameResult CanopenFramer::extract(const uint8_t*, std::size_t)
{
return {};
}
softbus::core::ParseResult CanopenParser::parseFrame(const uint8_t*, std::size_t, const std::string&)
{
return {};
}
bool CanopenObjectDictionary::loadEds(const std::string&) { return false; }
bool CanopenObjectDictionary::loadDcf(const std::string&) { return false; }
bool CanopenObjectDictionary::hasEntry(uint16_t, uint8_t) const { return false; }
bool EdsDcfLoader::load(const std::string&, CanopenObjectDictionary*) { return false; }
bool CanopenNodeManager::registerNode(uint8_t) { return false; }
bool CanopenNodeManager::setState(uint8_t, const std::string&) { return false; }
std::string CanopenNodeManager::state(uint8_t nodeId) const
{
const auto it = states_.find(nodeId);
return it == states_.end() ? "Unknown" : it->second;
}
bool CanopenMapper::mapToDom(uint16_t, uint8_t, softbus::core::DOMMessage*) { return false; }
} // namespace canopen
extern "C" SOFTBUS_PLUGIN_API softbus::core::IFramer* create_framer()
{
return new canopen::CanopenFramer();
}
extern "C" SOFTBUS_PLUGIN_API void destroy_framer(softbus::core::IFramer* framer)
{
delete framer;
}
extern "C" SOFTBUS_PLUGIN_API softbus::core::IParser* create_parser()
{
return new canopen::CanopenParser();
}
extern "C" SOFTBUS_PLUGIN_API void destroy_parser(softbus::core::IParser* parser)
{
delete parser;
}

View File

@@ -0,0 +1,13 @@
softbus_package(protocol_modbus)
add_library(protocol_modbus SHARED
src/ModbusFramer.cpp
src/ModbusParser.cpp
src/modbus_plugin.cpp
)
softbus_export_include(protocol_modbus "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(protocol_modbus PRIVATE softbus_core)
softbus_add_plugin(protocol_modbus)
softbus_apply_platform_libs(protocol_modbus)

View File

@@ -0,0 +1,13 @@
#pragma once
#include <softbus_core/plugin/IFramer.h>
namespace modbus {
class ModbusFramer : public softbus::core::IFramer {
public:
softbus::core::FrameResult extract(const uint8_t* data, std::size_t size) override;
const char* protocolName() const override { return "modbus"; }
};
} // namespace modbus

View File

@@ -0,0 +1,21 @@
#pragma once
#include <softbus_core/plugin/IParser.h>
#include <string>
namespace modbus {
class ModbusParser : public softbus::core::IParser {
public:
explicit ModbusParser(std::string defaultObjectRef = {});
softbus::core::ParseResult parseFrame(const uint8_t* data, std::size_t size,
const std::string& sourceId) override;
const char* protocolName() const override { return "modbus"; }
private:
std::string defaultObjectRef_;
};
} // namespace modbus

View File

@@ -0,0 +1,25 @@
#include <modbus/ModbusFramer.h>
namespace modbus {
softbus::core::FrameResult ModbusFramer::extract(const uint8_t* data, std::size_t size)
{
softbus::core::FrameResult result;
if (size < 7 || data[1] != 0x03) {
return result;
}
const std::size_t byteCount = data[2];
const std::size_t frameSize = 3 + byteCount + 2;
if (size < frameSize) {
return result;
}
result.success = true;
result.frameStartOffset = 0;
result.consumedBytes = frameSize;
result.frame.assign(data, data + frameSize);
return result;
}
} // namespace modbus

View File

@@ -0,0 +1,41 @@
#include <modbus/ModbusParser.h>
namespace modbus {
ModbusParser::ModbusParser(std::string defaultObjectRef)
: defaultObjectRef_(std::move(defaultObjectRef))
{
}
softbus::core::ParseResult ModbusParser::parseFrame(const uint8_t* data, std::size_t size,
const std::string& sourceId)
{
softbus::core::ParseResult result;
if (size < 7 || data[1] != 0x03) {
return result;
}
const std::size_t byteCount = data[2];
if (byteCount < 2 || size < 3 + byteCount + 2) {
return result;
}
const uint16_t registerValue =
(static_cast<uint16_t>(data[3]) << 8) | static_cast<uint16_t>(data[4]);
result.success = true;
result.message.sourceId = sourceId;
result.message.id = sourceId.empty() ? defaultObjectRef_ : sourceId;
result.message.domain = softbus::core::DomDomain::Process;
result.message.quality = softbus::core::QualityCode::Good;
result.message.value = {
{"protocol", "modbus"},
{"function", 0x03},
{"register", 40001},
{"registerValue", registerValue},
{"value", static_cast<double>(registerValue) * 0.1}
};
return result;
}
} // namespace modbus

View File

@@ -0,0 +1,22 @@
#include <modbus/ModbusFramer.h>
#include <modbus/ModbusParser.h>
extern "C" SOFTBUS_PLUGIN_API softbus::core::IFramer* create_framer()
{
return new modbus::ModbusFramer();
}
extern "C" SOFTBUS_PLUGIN_API void destroy_framer(softbus::core::IFramer* framer)
{
delete framer;
}
extern "C" SOFTBUS_PLUGIN_API softbus::core::IParser* create_parser()
{
return new modbus::ModbusParser();
}
extern "C" SOFTBUS_PLUGIN_API void destroy_parser(softbus::core::IParser* parser)
{
delete parser;
}

View File

@@ -0,0 +1,9 @@
softbus_package(softbus_api)
add_library(softbus_api STATIC
src/ExecuteCommand.cpp
)
softbus_export_include(softbus_api "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_api PUBLIC softbus_core nlohmann_json::nlohmann_json)

View File

@@ -0,0 +1,19 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace softbus::api {
struct ExecuteCommand {
std::string traceId;
std::string objectRef;
std::string command;
nlohmann::json params;
static ExecuteCommand fromJson(const nlohmann::json& j);
nlohmann::json toJson() const;
bool validate(std::string* error) const;
};
} // namespace softbus::api

View File

@@ -0,0 +1,51 @@
#include <softbus_api/ExecuteCommand.h>
namespace softbus::api {
ExecuteCommand ExecuteCommand::fromJson(const nlohmann::json& j)
{
ExecuteCommand cmd;
cmd.traceId = j.value("traceId", "");
cmd.objectRef = j.value("objectRef", "");
cmd.command = j.value("command", "");
if (j.contains("params")) {
cmd.params = j["params"];
}
return cmd;
}
nlohmann::json ExecuteCommand::toJson() const
{
return nlohmann::json{
{"type", "executeCommand"},
{"traceId", traceId},
{"objectRef", objectRef},
{"command", command},
{"params", params}
};
}
bool ExecuteCommand::validate(std::string* error) const
{
if (traceId.empty()) {
if (error) {
*error = "traceId is required";
}
return false;
}
if (objectRef.empty()) {
if (error) {
*error = "objectRef is required";
}
return false;
}
if (command.empty()) {
if (error) {
*error = "command is required";
}
return false;
}
return true;
}
} // namespace softbus::api

View File

@@ -0,0 +1,17 @@
softbus_package(softbus_core)
add_library(softbus_core STATIC
src/time/HwClock.cpp
src/logging/Logger.cpp
src/alignment/TimeAligner.cpp
src/plugin/PluginLoader.cpp
src/config/ProfileLoader.cpp
src/platform/PlatformSignal.cpp
src/identity/DeviceIdentity.cpp
src/models/ObjectModel.cpp
)
softbus_export_include(softbus_core "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_core PUBLIC nlohmann_json::nlohmann_json)
softbus_apply_platform_libs(softbus_core)

View File

@@ -0,0 +1,55 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <softbus_core/models/RawPacket.h>
#include <cstdint>
#include <deque>
#include <map>
#include <mutex>
#include <string>
#include <vector>
namespace softbus::core {
enum class AlignStrategy {
Nearest,
Linear
};
struct TimeAlignerConfig {
int64_t windowNs{50'000'000};
AlignStrategy strategy{AlignStrategy::Nearest};
};
struct AlignedSample {
int64_t anchorTimestampNs{0};
std::map<std::string, DOMMessage> channels;
};
class TimeAligner {
public:
explicit TimeAligner(TimeAlignerConfig config = {});
void ingest(const RawPacket& raw);
void ingestDom(const DOMMessage& dom);
std::vector<AlignedSample> drainAligned();
void setConfig(const TimeAlignerConfig& config);
TimeAlignerConfig config() const;
private:
struct ChannelBuffer {
std::deque<DOMMessage> samples;
};
DOMMessage rawToDom(const RawPacket& raw) const;
AlignedSample alignAt(int64_t anchorNs);
mutable std::mutex mutex_;
TimeAlignerConfig config_;
std::map<std::string, ChannelBuffer> channels_;
int64_t lastAnchorNs_{0};
};
} // namespace softbus::core

View File

@@ -0,0 +1,38 @@
#pragma once
#include <cstdint>
#include <string>
namespace softbus::core {
struct OpcUaProfile {
uint16_t port{4840};
std::string applicationName{"SoftBusDaemon"};
};
struct UplinkProfile {
std::string listenHost{"127.0.0.1"};
uint16_t listenPort{9000};
};
struct PluginProfile {
std::string modbus{"protocol_modbus"};
std::string canopen{"protocol_canopen"};
};
struct SecurityProfile {
bool tlsEnabled{false};
std::string certPath;
std::string keyPath;
};
struct DaemonProfile {
OpcUaProfile opcua;
UplinkProfile uplink;
PluginProfile plugins;
SecurityProfile security;
bool loadFromFile(const std::string& path);
};
} // namespace softbus::core

View File

@@ -0,0 +1,19 @@
#pragma once
#include <softbus_core/models/ObjectModel.h>
#include <string>
namespace softbus::core {
class DeviceIdentity {
public:
static std::string generateRuntimeDeviceId();
static std::string generateTraceId();
static bool isValidRuntimeDeviceId(const std::string& id);
static bool isValidStableDeviceKey(const std::string& key);
static ObjectRef buildObjectRef(const ObjectModelTree& tree, const std::string& stableDeviceKey,
const std::string& pointName);
};
} // namespace softbus::core

View File

@@ -0,0 +1,87 @@
#pragma once
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
namespace softbus::core {
enum class LogLevel { Debug, Info, Warn, Error };
class Logger {
public:
static Logger& instance();
void setLevel(LogLevel level);
void log(LogLevel level, const std::string& tag, const std::string& message);
void log(LogLevel level, const std::string& tag, const std::string& traceId,
const std::string& message);
template <typename... Args>
void info(const std::string& tag, Args&&... args)
{
log(LogLevel::Info, tag, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void infoTrace(const std::string& tag, const std::string& traceId, Args&&... args)
{
log(LogLevel::Info, tag, traceId, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void warn(const std::string& tag, Args&&... args)
{
log(LogLevel::Warn, tag, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void warnTrace(const std::string& tag, const std::string& traceId, Args&&... args)
{
log(LogLevel::Warn, tag, traceId, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void error(const std::string& tag, Args&&... args)
{
log(LogLevel::Error, tag, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void errorTrace(const std::string& tag, const std::string& traceId, Args&&... args)
{
log(LogLevel::Error, tag, traceId, concat(std::forward<Args>(args)...));
}
template <typename... Args>
void debug(const std::string& tag, Args&&... args)
{
log(LogLevel::Debug, tag, concat(std::forward<Args>(args)...));
}
private:
Logger() = default;
template <typename T>
static std::string concat(T&& value)
{
std::ostringstream oss;
oss << std::forward<T>(value);
return oss.str();
}
template <typename T, typename... Rest>
static std::string concat(T&& first, Rest&&... rest)
{
std::ostringstream oss;
oss << std::forward<T>(first);
((oss << std::forward<Rest>(rest)), ...);
return oss.str();
}
LogLevel level_{LogLevel::Info};
std::mutex mutex_;
};
} // namespace softbus::core

View File

@@ -0,0 +1,3 @@
#pragma once
#include <softbus_core/queue/LockFreeQueue.h>

View File

@@ -0,0 +1,61 @@
#pragma once
#include <cstdint>
#include <nlohmann/json.hpp>
#include <string>
namespace softbus::core {
enum class DomDomain : uint8_t {
Process = 0,
Control = 1,
Monitor = 2,
Management = 3
};
enum class QualityCode : int {
Good = 0,
Bad = 1,
Timeout = 2,
Overflow = 3
};
struct DOMMessage {
std::string traceId;
std::string id;
std::string sourceId;
int64_t timestampNs{0};
DomDomain domain{DomDomain::Process};
QualityCode quality{QualityCode::Good};
nlohmann::json value;
nlohmann::json toJson() const
{
return nlohmann::json{
{"traceId", traceId},
{"id", id},
{"sourceId", sourceId},
{"timestampNs", timestampNs},
{"domain", static_cast<int>(domain)},
{"quality", static_cast<int>(quality)},
{"value", value}
};
}
static DOMMessage fromJson(const nlohmann::json& j)
{
DOMMessage msg;
msg.traceId = j.value("traceId", "");
msg.id = j.value("id", "");
msg.sourceId = j.value("sourceId", "");
msg.timestampNs = j.value("timestampNs", int64_t{0});
msg.domain = static_cast<DomDomain>(j.value("domain", 0));
msg.quality = static_cast<QualityCode>(j.value("quality", 0));
if (j.contains("value")) {
msg.value = j["value"];
}
return msg;
}
};
} // namespace softbus::core

View File

@@ -0,0 +1,19 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <cstdint>
#include <string>
namespace softbus::core {
struct MetadataDef {
std::string objectRef;
std::string dataType{"double"};
std::string unit;
double scale{1.0};
DomDomain domain{DomDomain::Process};
int registerAddress{0};
};
} // namespace softbus::core

View File

@@ -0,0 +1,50 @@
#pragma once
#include <string>
#include <vector>
namespace softbus::core {
enum class ObjectLayer {
Site = 0,
System = 1,
Asset = 2,
Device = 3,
Point = 4
};
struct ObjectRef {
std::string site;
std::string system;
std::string asset;
std::string device;
std::string point;
std::string toPath() const;
static ObjectRef fromPath(const std::string& path);
bool isValid() const;
};
struct PointDef {
std::string name;
int registerAddress{0};
std::string domain{"Process"};
};
struct DeviceDef {
std::string stableDeviceKey;
std::string protocol;
std::vector<PointDef> points;
};
struct ObjectModelTree {
std::string site;
std::string system;
std::string asset;
std::vector<DeviceDef> devices;
bool loadFromFile(const std::string& path);
bool isValid() const;
};
} // namespace softbus::core

View File

@@ -0,0 +1,9 @@
#pragma once
#include <softbus_core/models/RawPacket.h>
namespace softbus::core {
using RawBusMessage = RawPacket;
} // namespace softbus::core

View File

@@ -0,0 +1,31 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace softbus::core {
struct RawPacket {
std::string traceId;
int64_t timestampNs{0};
std::string runtimeDeviceId;
std::string sourceId;
std::string protocolTag;
std::vector<uint8_t> payload;
RawPacket() = default;
RawPacket(std::string trace, int64_t ts, std::string runtimeId, std::string source,
std::string protocol, std::vector<uint8_t> data)
: traceId(std::move(trace))
, timestampNs(ts)
, runtimeDeviceId(std::move(runtimeId))
, sourceId(std::move(source))
, protocolTag(std::move(protocol))
, payload(std::move(data))
{
}
};
} // namespace softbus::core

View File

@@ -0,0 +1,20 @@
#pragma once
#include <atomic>
#include <functional>
namespace softbus::core {
class PlatformSignal {
public:
using Handler = std::function<void()>;
static void install(Handler handler);
static bool shouldStop();
static void requestStop();
private:
static std::atomic<bool> stopRequested_;
};
} // namespace softbus::core

View File

@@ -0,0 +1,36 @@
#pragma once
#ifdef _WIN32
#ifdef SOFTBUS_PLUGIN_EXPORTS
#define SOFTBUS_PLUGIN_API __declspec(dllexport)
#else
#define SOFTBUS_PLUGIN_API __declspec(dllimport)
#endif
#else
#define SOFTBUS_PLUGIN_API __attribute__((visibility("default")))
#endif
#include <cstddef>
#include <cstdint>
#include <vector>
namespace softbus::core {
struct FrameResult {
bool success{false};
std::size_t consumedBytes{0};
std::size_t frameStartOffset{0};
std::vector<uint8_t> frame;
};
class IFramer {
public:
virtual ~IFramer() = default;
virtual FrameResult extract(const uint8_t* data, std::size_t size) = 0;
virtual const char* protocolName() const = 0;
};
extern "C" SOFTBUS_PLUGIN_API IFramer* create_framer();
extern "C" SOFTBUS_PLUGIN_API void destroy_framer(IFramer* framer);
} // namespace softbus::core

View File

@@ -0,0 +1,23 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <softbus_core/plugin/IFramer.h>
namespace softbus::core {
struct ParseResult {
bool success{false};
DOMMessage message;
};
class IParser {
public:
virtual ~IParser() = default;
virtual ParseResult parseFrame(const uint8_t* data, std::size_t size, const std::string& sourceId) = 0;
virtual const char* protocolName() const = 0;
};
extern "C" SOFTBUS_PLUGIN_API IParser* create_parser();
extern "C" SOFTBUS_PLUGIN_API void destroy_parser(IParser* parser);
} // namespace softbus::core

View File

@@ -0,0 +1,41 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <softbus_core/plugin/IFramer.h>
#include <softbus_core/plugin/IParser.h>
namespace softbus::core {
class PluginLoader {
public:
PluginLoader() = default;
~PluginLoader();
PluginLoader(const PluginLoader&) = delete;
PluginLoader& operator=(const PluginLoader&) = delete;
bool load(const std::string& pluginPath);
void unload();
IFramer* framer() const { return framer_; }
IParser* parser() const { return parser_; }
bool isLoaded() const { return handle_ != nullptr; }
private:
void* handle_{nullptr};
IFramer* framer_{nullptr};
IParser* parser_{nullptr};
using CreateFramerFn = IFramer* (*)();
using DestroyFramerFn = void (*)(IFramer*);
using CreateParserFn = IParser* (*)();
using DestroyParserFn = void (*)(IParser*);
DestroyFramerFn destroyFramer_{nullptr};
DestroyParserFn destroyParser_{nullptr};
};
} // namespace softbus::core

View File

@@ -0,0 +1,87 @@
#pragma once
#include <atomic>
#include <cstddef>
#include <memory>
#include <optional>
namespace softbus::core {
template <typename T>
class LockFreeQueue {
public:
explicit LockFreeQueue(std::size_t capacity)
: capacity_(capacity)
, buffer_(std::make_unique<Slot[]>(capacity))
, head_(0)
, tail_(0)
{
for (std::size_t i = 0; i < capacity_; ++i) {
buffer_[i].sequence.store(i, std::memory_order_relaxed);
}
}
bool push(T item)
{
std::size_t pos = tail_.load(std::memory_order_relaxed);
for (;;) {
Slot& slot = buffer_[pos % capacity_];
const std::size_t seq = slot.sequence.load(std::memory_order_acquire);
const intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos);
if (diff == 0) {
if (tail_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) {
slot.data = std::move(item);
slot.sequence.store(pos + 1, std::memory_order_release);
return true;
}
} else if (diff < 0) {
return false;
} else {
pos = tail_.load(std::memory_order_relaxed);
}
}
}
std::optional<T> pop()
{
std::size_t pos = head_.load(std::memory_order_relaxed);
for (;;) {
Slot& slot = buffer_[pos % capacity_];
const std::size_t seq = slot.sequence.load(std::memory_order_acquire);
const intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(pos + 1);
if (diff == 0) {
if (head_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) {
T item = std::move(slot.data);
slot.sequence.store(pos + capacity_, std::memory_order_release);
return item;
}
} else if (diff < 0) {
return std::nullopt;
} else {
pos = head_.load(std::memory_order_relaxed);
}
}
}
bool empty() const
{
const std::size_t h = head_.load(std::memory_order_acquire);
const std::size_t t = tail_.load(std::memory_order_acquire);
return h >= t;
}
std::size_t capacity() const { return capacity_; }
private:
struct Slot {
std::atomic<std::size_t> sequence{0};
T data{};
};
const std::size_t capacity_;
std::unique_ptr<Slot[]> buffer_;
std::atomic<std::size_t> head_;
std::atomic<std::size_t> tail_;
};
} // namespace softbus::core

View File

@@ -0,0 +1,16 @@
#pragma once
#include <softbus_core/models/RawPacket.h>
#include <softbus_core/models/DOMMessage.h>
#include <softbus_core/models/MetadataDef.h>
#include <softbus_core/models/ObjectModel.h>
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/queue/LockFreeQueue.h>
#include <softbus_core/time/HwClock.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_core/alignment/TimeAligner.h>
#include <softbus_core/plugin/IFramer.h>
#include <softbus_core/plugin/IParser.h>
#include <softbus_core/plugin/PluginLoader.h>
#include <softbus_core/platform/PlatformSignal.h>
#include <softbus_core/config/ProfileLoader.h>

View File

@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
namespace softbus::core {
class HwClock {
public:
static int64_t nowNs();
static double nsToMs(int64_t ns);
};
} // namespace softbus::core

View File

@@ -0,0 +1,107 @@
#include <softbus_core/alignment/TimeAligner.h>
#include <algorithm>
#include <cmath>
#include <limits>
namespace softbus::core {
TimeAligner::TimeAligner(TimeAlignerConfig config)
: config_(config)
{
}
void TimeAligner::setConfig(const TimeAlignerConfig& config)
{
std::lock_guard<std::mutex> lock(mutex_);
config_ = config;
}
TimeAlignerConfig TimeAligner::config() const
{
std::lock_guard<std::mutex> lock(mutex_);
return config_;
}
DOMMessage TimeAligner::rawToDom(const RawPacket& raw) const
{
DOMMessage dom;
dom.traceId = raw.traceId;
dom.id = raw.sourceId;
dom.sourceId = raw.sourceId;
dom.timestampNs = raw.timestampNs;
dom.domain = DomDomain::Process;
dom.quality = QualityCode::Good;
dom.value = {
{"protocol", raw.protocolTag},
{"runtimeDeviceId", raw.runtimeDeviceId},
{"payloadSize", raw.payload.size()},
{"payloadHex", nlohmann::json::array()}
};
for (const auto byte : raw.payload) {
dom.value["payloadHex"].push_back(byte);
}
return dom;
}
void TimeAligner::ingest(const RawPacket& raw)
{
ingestDom(rawToDom(raw));
}
void TimeAligner::ingestDom(const DOMMessage& dom)
{
std::lock_guard<std::mutex> lock(mutex_);
auto& buffer = channels_[dom.sourceId].samples;
buffer.push_back(dom);
while (buffer.size() > 256) {
buffer.pop_front();
}
lastAnchorNs_ = std::max(lastAnchorNs_, dom.timestampNs);
}
AlignedSample TimeAligner::alignAt(int64_t anchorNs)
{
AlignedSample sample;
sample.anchorTimestampNs = anchorNs;
for (auto& [sourceId, channel] : channels_) {
auto& samples = channel.samples;
if (samples.empty()) {
continue;
}
DOMMessage best;
int64_t bestDelta = std::numeric_limits<int64_t>::max();
bool found = false;
for (const auto& candidate : samples) {
const int64_t delta = std::llabs(candidate.timestampNs - anchorNs);
if (delta <= config_.windowNs && delta < bestDelta) {
bestDelta = delta;
best = candidate;
found = true;
}
}
if (found) {
sample.channels[sourceId] = best;
}
}
return sample;
}
std::vector<AlignedSample> TimeAligner::drainAligned()
{
std::lock_guard<std::mutex> lock(mutex_);
if (lastAnchorNs_ == 0 || channels_.empty()) {
return {};
}
std::vector<AlignedSample> result;
result.push_back(alignAt(lastAnchorNs_));
return result;
}
} // namespace softbus::core

View File

@@ -0,0 +1,37 @@
#include <softbus_core/config/ProfileLoader.h>
#include <fstream>
#include <nlohmann/json.hpp>
namespace softbus::core {
bool DaemonProfile::loadFromFile(const std::string& path)
{
std::ifstream ifs(path);
if (!ifs) {
return false;
}
nlohmann::json j;
ifs >> j;
if (j.contains("opcua")) {
opcua.port = static_cast<uint16_t>(j["opcua"].value("port", 4840));
opcua.applicationName = j["opcua"].value("applicationName", "SoftBusDaemon");
}
if (j.contains("uplink")) {
uplink.listenHost = j["uplink"].value("listenHost", "127.0.0.1");
uplink.listenPort = static_cast<uint16_t>(j["uplink"].value("listenPort", 9000));
}
if (j.contains("plugins")) {
plugins.modbus = j["plugins"].value("modbus", "protocol_modbus");
plugins.canopen = j["plugins"].value("canopen", "protocol_canopen");
}
if (j.contains("security")) {
security.tlsEnabled = j["security"].value("tlsEnabled", false);
security.certPath = j["security"].value("certPath", "");
security.keyPath = j["security"].value("keyPath", "");
}
return true;
}
} // namespace softbus::core

View File

@@ -0,0 +1,61 @@
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/time/HwClock.h>
#include <chrono>
#include <iomanip>
#include <random>
#include <sstream>
namespace softbus::core {
namespace {
std::string randomHex(std::size_t bytes)
{
static thread_local std::mt19937_64 rng{
static_cast<std::mt19937_64::result_type>(
std::chrono::steady_clock::now().time_since_epoch().count())};
std::uniform_int_distribution<int> dist(0, 255);
std::ostringstream oss;
for (std::size_t i = 0; i < bytes; ++i) {
oss << std::hex << std::setw(2) << std::setfill('0') << dist(rng);
}
return oss.str();
}
} // namespace
std::string DeviceIdentity::generateRuntimeDeviceId()
{
return "runtime-" + randomHex(8);
}
std::string DeviceIdentity::generateTraceId()
{
return "trace-" + randomHex(8);
}
bool DeviceIdentity::isValidRuntimeDeviceId(const std::string& id)
{
return !id.empty() && id.rfind("runtime-", 0) == 0;
}
bool DeviceIdentity::isValidStableDeviceKey(const std::string& key)
{
return !key.empty() && key.find('/') == std::string::npos;
}
ObjectRef DeviceIdentity::buildObjectRef(const ObjectModelTree& tree, const std::string& stableDeviceKey,
const std::string& pointName)
{
ObjectRef ref;
ref.site = tree.site;
ref.system = tree.system;
ref.asset = tree.asset;
ref.device = stableDeviceKey;
ref.point = pointName;
return ref;
}
} // namespace softbus::core

View File

@@ -0,0 +1,62 @@
#include <softbus_core/logging/Logger.h>
#include <chrono>
#include <iomanip>
#include <sstream>
namespace softbus::core {
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
void Logger::setLevel(LogLevel level)
{
std::lock_guard<std::mutex> lock(mutex_);
level_ = level;
}
void Logger::log(LogLevel level, const std::string& tag, const std::string& message)
{
log(level, tag, "", message);
}
void Logger::log(LogLevel level, const std::string& tag, const std::string& traceId,
const std::string& message)
{
if (static_cast<int>(level) < static_cast<int>(level_)) {
return;
}
const char* levelStr = "INFO";
switch (level) {
case LogLevel::Debug: levelStr = "DEBUG"; break;
case LogLevel::Info: levelStr = "INFO"; break;
case LogLevel::Warn: levelStr = "WARN"; break;
case LogLevel::Error: levelStr = "ERROR"; break;
}
const auto now = std::chrono::system_clock::now();
const auto time = std::chrono::system_clock::to_time_t(now);
std::tm tm{};
#ifdef SOFTBUS_PLATFORM_WINDOWS
localtime_s(&tm, &time);
#else
localtime_r(&time, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S")
<< " [" << levelStr << "] [" << tag << "]";
if (!traceId.empty()) {
oss << "[trace=" << traceId << "]";
}
oss << " " << message;
std::lock_guard<std::mutex> lock(mutex_);
std::cerr << oss.str() << std::endl;
}
} // namespace softbus::core

View File

@@ -0,0 +1,84 @@
#include <softbus_core/models/ObjectModel.h>
#include <fstream>
#include <nlohmann/json.hpp>
#include <sstream>
namespace softbus::core {
std::string ObjectRef::toPath() const
{
std::ostringstream oss;
oss << site << '/' << system << '/' << asset << '/' << device;
if (!point.empty()) {
oss << '/' << point;
}
return oss.str();
}
ObjectRef ObjectRef::fromPath(const std::string& path)
{
ObjectRef ref;
std::istringstream iss(path);
std::string token;
std::string parts[5];
int idx = 0;
while (std::getline(iss, token, '/') && idx < 5) {
parts[idx++] = token;
}
if (idx >= 4) {
ref.site = parts[0];
ref.system = parts[1];
ref.asset = parts[2];
ref.device = parts[3];
if (idx >= 5) {
ref.point = parts[4];
}
}
return ref;
}
bool ObjectRef::isValid() const
{
return !site.empty() && !system.empty() && !asset.empty() && !device.empty();
}
bool ObjectModelTree::loadFromFile(const std::string& path)
{
std::ifstream ifs(path);
if (!ifs) {
return false;
}
nlohmann::json j;
ifs >> j;
site = j.value("site", "");
system = j.value("system", "");
asset = j.value("asset", "");
devices.clear();
if (!j.contains("devices")) {
return isValid();
}
for (const auto& dev : j["devices"]) {
DeviceDef device;
device.stableDeviceKey = dev.value("stableDeviceKey", "");
device.protocol = dev.value("protocol", "");
if (dev.contains("points")) {
for (const auto& pt : dev["points"]) {
PointDef point;
point.name = pt.value("name", "");
point.registerAddress = pt.value("register", 0);
point.domain = pt.value("domain", "Process");
device.points.push_back(std::move(point));
}
}
devices.push_back(std::move(device));
}
return isValid();
}
bool ObjectModelTree::isValid() const
{
return !site.empty() && !system.empty() && !asset.empty();
}
} // namespace softbus::core

View File

@@ -0,0 +1,48 @@
#include <softbus_core/platform/PlatformSignal.h>
#ifdef SOFTBUS_PLATFORM_WINDOWS
#include <windows.h>
#else
#include <csignal>
#endif
namespace softbus::core {
std::atomic<bool> PlatformSignal::stopRequested_{false};
#ifdef SOFTBUS_PLATFORM_WINDOWS
namespace {
BOOL WINAPI consoleHandler(DWORD signal)
{
if (signal == CTRL_C_EVENT || signal == CTRL_BREAK_EVENT || signal == CTRL_CLOSE_EVENT) {
PlatformSignal::requestStop();
return TRUE;
}
return FALSE;
}
}
#endif
void PlatformSignal::install(Handler handler)
{
stopRequested_.store(false);
#ifdef SOFTBUS_PLATFORM_WINDOWS
SetConsoleCtrlHandler(consoleHandler, TRUE);
#else
std::signal(SIGINT, [](int) { requestStop(); });
std::signal(SIGTERM, [](int) { requestStop(); });
#endif
(void)handler;
}
bool PlatformSignal::shouldStop()
{
return stopRequested_.load();
}
void PlatformSignal::requestStop()
{
stopRequested_.store(true);
}
} // namespace softbus::core

View File

@@ -0,0 +1,86 @@
#include <softbus_core/plugin/PluginLoader.h>
#include <softbus_core/logging/Logger.h>
#ifdef SOFTBUS_PLATFORM_WINDOWS
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace softbus::core {
PluginLoader::~PluginLoader()
{
unload();
}
bool PluginLoader::load(const std::string& pluginPath)
{
unload();
#ifdef SOFTBUS_PLATFORM_WINDOWS
handle_ = LoadLibraryA(pluginPath.c_str());
if (!handle_) {
Logger::instance().error("PluginLoader", "Failed to load plugin: ", pluginPath);
return false;
}
auto createFramer = reinterpret_cast<CreateFramerFn>(GetProcAddress(static_cast<HMODULE>(handle_), "create_framer"));
auto destroyFramer = reinterpret_cast<DestroyFramerFn>(GetProcAddress(static_cast<HMODULE>(handle_), "destroy_framer"));
auto createParser = reinterpret_cast<CreateParserFn>(GetProcAddress(static_cast<HMODULE>(handle_), "create_parser"));
auto destroyParser = reinterpret_cast<DestroyParserFn>(GetProcAddress(static_cast<HMODULE>(handle_), "destroy_parser"));
#else
handle_ = dlopen(pluginPath.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle_) {
Logger::instance().error("PluginLoader", "Failed to load plugin: ", pluginPath, " err=", dlerror());
return false;
}
auto createFramer = reinterpret_cast<CreateFramerFn>(dlsym(handle_, "create_framer"));
auto destroyFramer = reinterpret_cast<DestroyFramerFn>(dlsym(handle_, "destroy_framer"));
auto createParser = reinterpret_cast<CreateParserFn>(dlsym(handle_, "create_parser"));
auto destroyParser = reinterpret_cast<DestroyParserFn>(dlsym(handle_, "destroy_parser"));
#endif
if (!createFramer || !destroyFramer || !createParser || !destroyParser) {
Logger::instance().error("PluginLoader", "Plugin missing required exports: ", pluginPath);
unload();
return false;
}
destroyFramer_ = destroyFramer;
destroyParser_ = destroyParser;
framer_ = createFramer();
parser_ = createParser();
if (!framer_ || !parser_) {
unload();
return false;
}
Logger::instance().info("PluginLoader", "Loaded plugin: ", pluginPath);
return true;
}
void PluginLoader::unload()
{
if (framer_ && destroyFramer_) {
destroyFramer_(framer_);
}
if (parser_ && destroyParser_) {
destroyParser_(parser_);
}
framer_ = nullptr;
parser_ = nullptr;
destroyFramer_ = nullptr;
destroyParser_ = nullptr;
if (handle_) {
#ifdef SOFTBUS_PLATFORM_WINDOWS
FreeLibrary(static_cast<HMODULE>(handle_));
#else
dlclose(handle_);
#endif
handle_ = nullptr;
}
}
} // namespace softbus::core

View File

@@ -0,0 +1,37 @@
#include <softbus_core/time/HwClock.h>
#include <chrono>
#ifdef SOFTBUS_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
namespace softbus::core {
int64_t HwClock::nowNs()
{
#ifdef SOFTBUS_PLATFORM_WINDOWS
static LARGE_INTEGER frequency = [] {
LARGE_INTEGER f{};
QueryPerformanceFrequency(&f);
return f;
}();
LARGE_INTEGER counter{};
QueryPerformanceCounter(&counter);
return static_cast<int64_t>((counter.QuadPart * 1'000'000'000LL) / frequency.QuadPart);
#else
timespec ts{};
clock_gettime(CLOCK_REALTIME, &ts);
return static_cast<int64_t>(ts.tv_sec) * 1'000'000'000LL + ts.tv_nsec;
#endif
}
double HwClock::nsToMs(int64_t ns)
{
return static_cast<double>(ns) / 1'000'000.0;
}
} // namespace softbus::core

View File

@@ -0,0 +1,26 @@
softbus_package(softbus_daemon)
add_executable(softbus_daemon
src/main.cpp
src/CoreService.cpp
src/PipelineEngine.cpp
src/MetadataRegistry.cpp
src/ObjectModelRegistry.cpp
src/CommandDispatcher.cpp
src/IpcOptionalDBus.cpp
)
softbus_export_include(softbus_daemon "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_daemon PRIVATE
softbus_core
softbus_transport
softbus_api
softbus_opcua
asio
)
softbus_apply_platform_libs(softbus_daemon)
softbus_apply_open62541(softbus_daemon)
softbus_apply_dbus(softbus_daemon)
add_dependencies(softbus_daemon protocol_modbus)

View File

@@ -0,0 +1,11 @@
#pragma once
namespace softbus::daemon {
class IpcOptionalDBus {
public:
bool initialize();
void shutdown();
};
} // namespace softbus::daemon

View File

@@ -0,0 +1,21 @@
#pragma once
#include <softbus_api/ExecuteCommand.h>
#include <softbus_transport/TcpUplinkServer.h>
#include <functional>
#include <string>
namespace softbus::daemon {
class CommandDispatcher {
public:
explicit CommandDispatcher(softbus::transport::TcpUplinkServer* uplink);
bool dispatch(const softbus::api::ExecuteCommand& command);
private:
softbus::transport::TcpUplinkServer* uplink_{nullptr};
};
} // namespace softbus::daemon

View File

@@ -0,0 +1,23 @@
#pragma once
#include <softbus_core/models/RawPacket.h>
#include <softbus_core/plugin/PluginLoader.h>
#include <softbus_opcua/UaModelBinder.h>
#include <string>
namespace softbus::daemon {
class PipelineEngine {
public:
PipelineEngine(softbus::core::PluginLoader* plugin, softbus::opcua::UaModelBinder* binder);
void process(const softbus::core::RawPacket& packet);
private:
softbus::core::PluginLoader* plugin_{nullptr};
softbus::opcua::UaModelBinder* binder_{nullptr};
};
} // namespace softbus::daemon

View File

@@ -0,0 +1,20 @@
#pragma once
#include <softbus_core/models/MetadataDef.h>
#include <map>
#include <optional>
#include <string>
namespace softbus::daemon {
class MetadataRegistry {
public:
void registerMetadata(const softbus::core::MetadataDef& metadata);
std::optional<softbus::core::MetadataDef> findByObjectRef(const std::string& objectRef) const;
private:
std::map<std::string, softbus::core::MetadataDef> entries_;
};
} // namespace softbus::daemon

View File

@@ -0,0 +1,20 @@
#pragma once
#include <softbus_core/models/ObjectModel.h>
#include <optional>
#include <string>
namespace softbus::daemon {
class ObjectModelRegistry {
public:
bool loadFromFile(const std::string& path);
const softbus::core::ObjectModelTree& tree() const { return tree_; }
std::optional<softbus::core::ObjectRef> findPointByRegister(int registerAddress) const;
private:
softbus::core::ObjectModelTree tree_;
};
} // namespace softbus::daemon

View File

@@ -0,0 +1,32 @@
#include <softbus_daemon/command/CommandDispatcher.h>
#include <softbus_core/logging/Logger.h>
namespace softbus::daemon {
CommandDispatcher::CommandDispatcher(softbus::transport::TcpUplinkServer* uplink)
: uplink_(uplink)
{
}
bool CommandDispatcher::dispatch(const softbus::api::ExecuteCommand& command)
{
std::string error;
if (!command.validate(&error)) {
softbus::core::Logger::instance().errorTrace("CommandDispatcher", command.traceId,
"Invalid executeCommand: ", error);
return false;
}
if (!uplink_) {
return false;
}
const bool ok = uplink_->sendCommand(command);
softbus::core::Logger::instance().infoTrace("CommandDispatcher", command.traceId,
"executeCommand dispatched to Edge objectRef=", command.objectRef,
" command=", command.command, " success=", ok ? "true" : "false");
return ok;
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,171 @@
#include <softbus_daemon/core/CoreService.h>
#include <softbus_daemon/command/CommandDispatcher.h>
#include <softbus_daemon/pipeline/PipelineEngine.h>
#include <softbus_opcua/UaAddressSpaceBuilder.h>
#include <softbus_opcua/UaEventBridge.h>
#include <softbus_opcua/UaMethodBinder.h>
#include <softbus_opcua/UaModelBinder.h>
#include <softbus_opcua/UaServerEngine.h>
#include <softbus_daemon/registry/ObjectModelRegistry.h>
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_core/platform/PlatformSignal.h>
#ifdef SOFTBUS_PLATFORM_WINDOWS
#include <windows.h>
#endif
#include <filesystem>
#include <thread>
namespace softbus::daemon {
namespace fs = std::filesystem;
CoreService::CoreService() = default;
CoreService::~CoreService()
{
stop();
}
std::string CoreService::resolvePluginPath(const std::string& pluginName) const
{
#ifdef SOFTBUS_PLATFORM_WINDOWS
const std::string fileName = pluginName + ".dll";
char exePath[MAX_PATH]{};
GetModuleFileNameA(nullptr, exePath, MAX_PATH);
const fs::path exeDir = fs::path(exePath).parent_path();
#else
const std::string fileName = "lib" + pluginName + ".so";
fs::path exeDir = fs::current_path();
#endif
const fs::path candidates[] = {
exeDir / fileName,
exeDir / ".." / "lib" / fileName,
fs::path("build") / "bin" / fileName,
fs::path("lib") / fileName,
fs::path("bin") / fileName,
fs::current_path() / "build" / "bin" / fileName,
fs::current_path() / "lib" / fileName,
fs::current_path() / fileName
};
for (const auto& candidate : candidates) {
if (fs::exists(candidate)) {
return fs::absolute(candidate).string();
}
}
return (exeDir / fileName).string();
}
bool CoreService::start(const std::string& profilePath, const std::string& modelPath)
{
if (!profile_.loadFromFile(profilePath)) {
softbus::core::Logger::instance().error("CoreService", "Failed to load profile: ", profilePath);
return false;
}
objectRegistry_ = std::make_unique<ObjectModelRegistry>();
if (!objectRegistry_->loadFromFile(modelPath)) {
softbus::core::Logger::instance().error("CoreService", "Failed to load model: ", modelPath);
return false;
}
model_ = objectRegistry_->tree();
const std::string pluginPath = resolvePluginPath(profile_.plugins.modbus);
if (!modbusPlugin_.load(pluginPath)) {
softbus::core::Logger::instance().error("CoreService", "Failed to load modbus plugin: ", pluginPath);
return false;
}
softbus::opcua::SecurityConfig security;
security.enableTls = profile_.security.tlsEnabled;
security.certPath = profile_.security.certPath;
security.keyPath = profile_.security.keyPath;
uaEngine_ = std::make_unique<softbus::opcua::UaServerEngine>();
if (!uaEngine_->start(profile_.opcua.port, profile_.opcua.applicationName, security)) {
return false;
}
uaBinder_ = std::make_unique<softbus::opcua::UaModelBinder>(uaEngine_->nativeServer());
uaMethodBinder_ = std::make_unique<softbus::opcua::UaMethodBinder>(uaEngine_->nativeServer());
uaEventBridge_ = std::make_unique<softbus::opcua::UaEventBridge>(uaEngine_->nativeServer());
softbus::opcua::UaAddressSpaceBuilder builder(uaEngine_->nativeServer());
if (!builder.buildFromModel(model_, uaBinder_.get())) {
softbus::core::Logger::instance().error("CoreService", "Failed to build OPC UA address space");
return false;
}
pipeline_ = std::make_unique<PipelineEngine>(&modbusPlugin_, uaBinder_.get());
uplink_ = std::make_unique<softbus::transport::TcpUplinkServer>(
io_, profile_.uplink.listenHost, profile_.uplink.listenPort);
commandDispatcher_ = std::make_unique<CommandDispatcher>(uplink_.get());
uplink_->setRawPacketHandler([this](const softbus::core::RawPacket& packet) {
softbus::core::Logger::instance().infoTrace("CoreService", packet.traceId, "RawPacket received");
pipeline_->process(packet);
});
uaMethodBinder_->bindExecuteCommand([this](const softbus::api::ExecuteCommand& cmd) {
return commandDispatcher_->dispatch(cmd);
});
uplink_->start();
ioThread_ = std::thread([this]() { io_.run(); });
running_ = true;
softbus::core::Logger::instance().info("CoreService", "Daemon started");
return true;
}
void CoreService::stop()
{
if (!running_) {
return;
}
running_ = false;
if (uplink_) {
uplink_->stop();
}
io_.stop();
if (ioThread_.joinable()) {
ioThread_.join();
}
if (uaEngine_) {
uaEngine_->stop();
}
modbusPlugin_.unload();
softbus::core::Logger::instance().info("CoreService", "Daemon stopped");
}
void CoreService::run(bool selfTest)
{
softbus::core::PlatformSignal::install(nullptr);
bool selfTestDone = false;
while (!softbus::core::PlatformSignal::shouldStop()) {
if (uaEngine_) {
uaEngine_->iterate();
}
if (selfTest && !selfTestDone && commandDispatcher_) {
softbus::api::ExecuteCommand cmd;
cmd.traceId = softbus::core::DeviceIdentity::generateTraceId();
cmd.objectRef = "DemoSite/DemoSystem/DemoAsset/SimModbusDevice/Temperature";
cmd.command = "write";
cmd.params = {{"value", 42.5}};
commandDispatcher_->dispatch(cmd);
selfTestDone = true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
stop();
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,21 @@
#include <softbus_daemon/api/IpcOptionalDBus.h>
#include <softbus_core/logging/Logger.h>
namespace softbus::daemon {
bool IpcOptionalDBus::initialize()
{
#if SOFTBUS_HAS_DBUS
softbus::core::Logger::instance().info("IpcOptionalDBus", "DBus IPC stub initialized");
#else
softbus::core::Logger::instance().info("IpcOptionalDBus", "DBus IPC disabled on this platform");
#endif
return true;
}
void IpcOptionalDBus::shutdown()
{
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,19 @@
#include <softbus_daemon/registry/MetadataRegistry.h>
namespace softbus::daemon {
void MetadataRegistry::registerMetadata(const softbus::core::MetadataDef& metadata)
{
entries_[metadata.objectRef] = metadata;
}
std::optional<softbus::core::MetadataDef> MetadataRegistry::findByObjectRef(const std::string& objectRef) const
{
const auto it = entries_.find(objectRef);
if (it == entries_.end()) {
return std::nullopt;
}
return it->second;
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,25 @@
#include <softbus_daemon/registry/ObjectModelRegistry.h>
#include <softbus_core/identity/DeviceIdentity.h>
namespace softbus::daemon {
bool ObjectModelRegistry::loadFromFile(const std::string& path)
{
return tree_.loadFromFile(path);
}
std::optional<softbus::core::ObjectRef> ObjectModelRegistry::findPointByRegister(int registerAddress) const
{
for (const auto& device : tree_.devices) {
for (const auto& point : device.points) {
if (point.registerAddress == registerAddress) {
return softbus::core::DeviceIdentity::buildObjectRef(
tree_, device.stableDeviceKey, point.name);
}
}
}
return std::nullopt;
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,48 @@
#include <softbus_daemon/pipeline/PipelineEngine.h>
#include <softbus_core/logging/Logger.h>
#include <vector>
namespace softbus::daemon {
PipelineEngine::PipelineEngine(softbus::core::PluginLoader* plugin, softbus::opcua::UaModelBinder* binder)
: plugin_(plugin)
, binder_(binder)
{
}
void PipelineEngine::process(const softbus::core::RawPacket& packet)
{
if (!plugin_ || !plugin_->parser() || !binder_) {
return;
}
const uint8_t* data = packet.payload.data();
std::size_t size = packet.payload.size();
std::vector<uint8_t> frame = packet.payload;
if (plugin_->framer()) {
const auto framed = plugin_->framer()->extract(data, size);
if (framed.success) {
frame = framed.frame;
data = frame.data();
size = frame.size();
}
}
auto parsed = plugin_->parser()->parseFrame(data, size, packet.sourceId);
if (!parsed.success) {
softbus::core::Logger::instance().warnTrace("PipelineEngine", packet.traceId,
"Failed to parse RawPacket");
return;
}
parsed.message.traceId = packet.traceId;
parsed.message.timestampNs = packet.timestampNs;
softbus::core::Logger::instance().infoTrace("PipelineEngine", packet.traceId,
"RawPacket converted to DOMMessage id=", parsed.message.id);
binder_->bind(parsed.message);
}
} // namespace softbus::daemon

View File

@@ -0,0 +1,30 @@
#include <softbus_daemon/core/CoreService.h>
#include <iostream>
#include <string>
int main(int argc, char* argv[])
{
std::string profilePath = "config/daemon_profile.json";
std::string modelPath = "config/softbus_model.json";
bool selfTest = false;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--config" && i + 1 < argc) {
profilePath = argv[++i];
} else if (arg == "--model" && i + 1 < argc) {
modelPath = argv[++i];
} else if (arg == "--self-test") {
selfTest = true;
}
}
softbus::daemon::CoreService service;
if (!service.start(profilePath, modelPath)) {
std::cerr << "Failed to start softbus_daemon" << std::endl;
return 1;
}
service.run(selfTest);
return 0;
}

View File

@@ -0,0 +1,14 @@
softbus_package(softbus_edge)
add_executable(softbus_edge
src/main.cpp
src/MockModbusIngress.cpp
src/UplinkClient.cpp
src/DeviceFactory.cpp
src/DeviceDiscovery.cpp
)
softbus_export_include(softbus_edge "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_edge PRIVATE softbus_core softbus_transport softbus_api asio)
softbus_apply_platform_libs(softbus_edge)

View File

@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
namespace softbus::edge {
struct DiscoveredDevice {
std::string portName;
std::string protocol;
};
class DeviceDiscovery {
public:
std::vector<DiscoveredDevice> discover();
};
} // namespace softbus::edge

View File

@@ -0,0 +1,28 @@
#pragma once
#include <softbus_api/ExecuteCommand.h>
#include <softbus_core/models/RawPacket.h>
#include <softbus_transport/TcpUplinkClient.h>
#include <functional>
#include <memory>
#include <string>
namespace softbus::edge {
class UplinkClient {
public:
UplinkClient(std::string host, uint16_t port);
~UplinkClient();
bool connect();
void disconnect();
bool sendRawPacket(const softbus::core::RawPacket& packet);
void setCommandHandler(std::function<void(const softbus::api::ExecuteCommand&)> handler);
void poll();
private:
std::unique_ptr<softbus::transport::TcpUplinkClient> client_;
};
} // namespace softbus::edge

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace softbus::edge {
class DeviceFactory {
public:
std::string createRuntimeDeviceId();
};
} // namespace softbus::edge

View File

@@ -0,0 +1,17 @@
#pragma once
#include <softbus_core/models/RawPacket.h>
#include <vector>
namespace softbus::edge {
class IIngressPort {
public:
virtual ~IIngressPort() = default;
virtual bool open() = 0;
virtual void close() = 0;
virtual std::vector<softbus::core::RawPacket> poll() = 0;
};
} // namespace softbus::edge

View File

@@ -0,0 +1,27 @@
#pragma once
#include <softbus_edge/ingress/IIngressPort.h>
#include <cstdint>
#include <string>
namespace softbus::edge {
class MockModbusIngress : public IIngressPort {
public:
explicit MockModbusIngress(std::string objectRef, std::string runtimeDeviceId);
bool open() override;
void close() override;
std::vector<softbus::core::RawPacket> poll() override;
private:
std::vector<uint8_t> buildFrame(uint16_t registerValue) const;
std::string objectRef_;
std::string runtimeDeviceId_;
bool open_{false};
uint16_t counter_{100};
};
} // namespace softbus::edge

View File

@@ -0,0 +1,10 @@
#include <softbus_edge/discovery/DeviceDiscovery.h>
namespace softbus::edge {
std::vector<DiscoveredDevice> DeviceDiscovery::discover()
{
return {};
}
} // namespace softbus::edge

View File

@@ -0,0 +1,12 @@
#include <softbus_edge/factory/DeviceFactory.h>
#include <softbus_core/identity/DeviceIdentity.h>
namespace softbus::edge {
std::string DeviceFactory::createRuntimeDeviceId()
{
return softbus::core::DeviceIdentity::generateRuntimeDeviceId();
}
} // namespace softbus::edge

View File

@@ -0,0 +1,54 @@
#include <softbus_edge/ingress/MockModbusIngress.h>
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/time/HwClock.h>
namespace softbus::edge {
MockModbusIngress::MockModbusIngress(std::string objectRef, std::string runtimeDeviceId)
: objectRef_(std::move(objectRef))
, runtimeDeviceId_(std::move(runtimeDeviceId))
{
}
bool MockModbusIngress::open()
{
open_ = true;
return true;
}
void MockModbusIngress::close()
{
open_ = false;
}
std::vector<uint8_t> MockModbusIngress::buildFrame(uint16_t registerValue) const
{
// slave=1, func=0x03, byteCount=2, value hi/lo, crc lo/hi (placeholder)
return {
0x01, 0x03, 0x02,
static_cast<uint8_t>((registerValue >> 8) & 0xFF),
static_cast<uint8_t>(registerValue & 0xFF),
0x00, 0x00
};
}
std::vector<softbus::core::RawPacket> MockModbusIngress::poll()
{
if (!open_) {
return {};
}
const auto traceId = softbus::core::DeviceIdentity::generateTraceId();
softbus::core::RawPacket packet;
packet.traceId = traceId;
packet.timestampNs = softbus::core::HwClock::nowNs();
packet.runtimeDeviceId = runtimeDeviceId_;
packet.sourceId = objectRef_;
packet.protocolTag = "modbus";
packet.payload = buildFrame(counter_);
counter_ = static_cast<uint16_t>(counter_ + 1);
return {packet};
}
} // namespace softbus::edge

View File

@@ -0,0 +1,40 @@
#include <softbus_edge/egress/UplinkClient.h>
namespace softbus::edge {
UplinkClient::UplinkClient(std::string host, uint16_t port)
: client_(std::make_unique<softbus::transport::TcpUplinkClient>(std::move(host), port))
{
}
UplinkClient::~UplinkClient()
{
disconnect();
}
bool UplinkClient::connect()
{
return client_->connect();
}
void UplinkClient::disconnect()
{
client_->disconnect();
}
bool UplinkClient::sendRawPacket(const softbus::core::RawPacket& packet)
{
return client_->sendRawPacket(packet);
}
void UplinkClient::setCommandHandler(std::function<void(const softbus::api::ExecuteCommand&)> handler)
{
client_->setCommandHandler(std::move(handler));
}
void UplinkClient::poll()
{
client_->poll();
}
} // namespace softbus::edge

View File

@@ -0,0 +1,85 @@
#include <softbus_edge/egress/UplinkClient.h>
#include <softbus_edge/factory/DeviceFactory.h>
#include <softbus_edge/ingress/MockModbusIngress.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_core/platform/PlatformSignal.h>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
namespace {
struct EdgeOptions {
std::string daemonHost{"127.0.0.1"};
uint16_t daemonPort{9000};
std::string objectRef{"DemoSite/DemoSystem/DemoAsset/SimModbusDevice/Temperature"};
};
EdgeOptions parseArgs(int argc, char* argv[])
{
EdgeOptions options;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--daemon" && i + 1 < argc) {
const std::string endpoint = argv[++i];
const auto pos = endpoint.find(':');
if (pos != std::string::npos) {
options.daemonHost = endpoint.substr(0, pos);
options.daemonPort = static_cast<uint16_t>(std::stoi(endpoint.substr(pos + 1)));
} else {
options.daemonHost = endpoint;
}
} else if (arg == "--object-ref" && i + 1 < argc) {
options.objectRef = argv[++i];
}
}
return options;
}
} // namespace
int main(int argc, char* argv[])
{
const EdgeOptions options = parseArgs(argc, argv);
softbus::edge::DeviceFactory factory;
const std::string runtimeDeviceId = factory.createRuntimeDeviceId();
softbus::core::Logger::instance().info("Edge", "runtimeDeviceId=", runtimeDeviceId,
" (local only, not persisted)");
softbus::edge::UplinkClient uplink(options.daemonHost, options.daemonPort);
uplink.setCommandHandler([](const softbus::api::ExecuteCommand& cmd) {
softbus::core::Logger::instance().infoTrace("Edge", cmd.traceId,
"executeCommand received: ", cmd.toJson().dump());
});
if (!uplink.connect()) {
std::cerr << "Failed to connect to daemon at "
<< options.daemonHost << ":" << options.daemonPort << std::endl;
return 1;
}
softbus::edge::MockModbusIngress ingress(options.objectRef, runtimeDeviceId);
ingress.open();
softbus::core::PlatformSignal::install([]() {
softbus::core::Logger::instance().info("Edge", "Shutdown requested");
});
while (!softbus::core::PlatformSignal::shouldStop()) {
uplink.poll();
for (const auto& packet : ingress.poll()) {
softbus::core::Logger::instance().infoTrace("Edge", packet.traceId,
"Uplink RawPacket objectRef=", packet.sourceId);
uplink.sendRawPacket(packet);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
ingress.close();
uplink.disconnect();
return 0;
}

View File

@@ -0,0 +1,15 @@
softbus_package(softbus_opcua)
add_library(softbus_opcua STATIC
src/UaServerEngine.cpp
src/UaAddressSpaceBuilder.cpp
src/UaModelBinder.cpp
src/UaMethodBinder.cpp
src/UaEventBridge.cpp
)
softbus_export_include(softbus_opcua "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_opcua PUBLIC softbus_core softbus_api)
softbus_apply_open62541(softbus_opcua)
softbus_apply_platform_libs(softbus_opcua)

View File

@@ -0,0 +1,27 @@
#pragma once
#include <softbus_core/models/ObjectModel.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/types.h>
#else
typedef struct UA_Server UA_Server;
#endif
struct UA_Server;
namespace softbus::opcua {
class UaModelBinder;
class UaAddressSpaceBuilder {
public:
explicit UaAddressSpaceBuilder(UA_Server* server);
bool buildFromModel(const softbus::core::ObjectModelTree& tree, UaModelBinder* binder = nullptr);
private:
UA_Server* server_{nullptr};
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,22 @@
#pragma once
#include <string>
struct UA_Server;
namespace softbus::opcua {
class UaEventBridge {
public:
explicit UaEventBridge(UA_Server* server);
void emitHeartbeat(const std::string& objectRef);
void emitDeviceOnline(const std::string& stableDeviceKey);
void emitDeviceOffline(const std::string& stableDeviceKey);
void emitParseError(const std::string& objectRef, const std::string& detail);
private:
UA_Server* server_{nullptr};
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,26 @@
#pragma once
#include <softbus_api/ExecuteCommand.h>
#include <functional>
#include <string>
struct UA_Server;
namespace softbus::opcua {
using ExecuteCommandHandler = std::function<bool(const softbus::api::ExecuteCommand&)>;
class UaMethodBinder {
public:
explicit UaMethodBinder(UA_Server* server);
bool bindExecuteCommand(ExecuteCommandHandler handler);
bool dispatchCommand(const softbus::api::ExecuteCommand& command);
private:
UA_Server* server_{nullptr};
ExecuteCommandHandler handler_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,28 @@
#pragma once
#include <softbus_core/models/DOMMessage.h>
#include <map>
#include <string>
#if SOFTBUS_HAS_OPCUA
#include <open62541/types.h>
#else
typedef struct UA_NodeId UA_NodeId;
#endif
namespace softbus::opcua {
class UaModelBinder {
public:
explicit UaModelBinder(UA_Server* server);
void registerPointNode(const std::string& objectRef, const UA_NodeId& nodeId);
bool bind(const softbus::core::DOMMessage& message);
private:
UA_Server* server_{nullptr};
std::map<std::string, UA_NodeId> pointNodes_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
struct UA_Server;
namespace softbus::opcua {
struct SecurityConfig {
bool enableTls{false};
std::string certPath;
std::string keyPath;
std::string trustListPath;
};
class UaServerEngine {
public:
UaServerEngine();
~UaServerEngine();
UaServerEngine(const UaServerEngine&) = delete;
UaServerEngine& operator=(const UaServerEngine&) = delete;
bool start(uint16_t port, const std::string& applicationName, const SecurityConfig& security);
void stop();
void iterate();
bool isRunning() const { return running_; }
UA_Server* nativeServer() { return server_; }
private:
UA_Server* server_{nullptr};
bool running_{false};
SecurityConfig security_;
};
} // namespace softbus::opcua

View File

@@ -0,0 +1,119 @@
#include <softbus_opcua/UaAddressSpaceBuilder.h>
#include <softbus_core/identity/DeviceIdentity.h>
#include <softbus_core/logging/Logger.h>
#include <softbus_opcua/UaModelBinder.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
UaAddressSpaceBuilder::UaAddressSpaceBuilder(UA_Server* server)
: server_(server)
{
}
#if SOFTBUS_HAS_OPCUA
namespace {
UA_NodeId makeStringNodeId(const std::string& nsAndId)
{
return UA_NODEID_STRING_ALLOC(1, nsAndId.c_str());
}
bool addObject(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
UA_NodeId* outNodeId)
{
UA_ObjectAttributes attr = UA_ObjectAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
const UA_NodeId nodeId = makeStringNodeId(browseName);
const UA_StatusCode status = UA_Server_addObjectNode(
server,
nodeId,
parent,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
attr,
nullptr,
outNodeId);
UA_ObjectAttributes_clear(&attr);
return status == UA_STATUSCODE_GOOD;
}
bool addVariable(UA_Server* server, const UA_NodeId& parent, const std::string& browseName,
const std::string& nodeKey, UA_NodeId* outNodeId)
{
UA_VariableAttributes attr = UA_VariableAttributes_default;
attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", browseName.c_str());
attr.dataType = UA_TYPES[UA_TYPES_DOUBLE].typeId;
UA_Double initial = 0.0;
UA_Variant_setScalarCopy(&attr.value, &initial, &UA_TYPES[UA_TYPES_DOUBLE]);
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
const UA_NodeId nodeId = makeStringNodeId(nodeKey);
const UA_StatusCode status = UA_Server_addVariableNode(
server,
nodeId,
parent,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, browseName.c_str()),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr,
nullptr,
outNodeId);
UA_VariableAttributes_clear(&attr);
return status == UA_STATUSCODE_GOOD;
}
} // namespace
#endif
bool UaAddressSpaceBuilder::buildFromModel(const softbus::core::ObjectModelTree& tree, UaModelBinder* binder)
{
#if !SOFTBUS_HAS_OPCUA
(void)tree;
return false;
#else
if (!server_) {
return false;
}
UA_NodeId siteNode;
if (!addObject(server_, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), tree.site, &siteNode)) {
return false;
}
UA_NodeId systemNode;
if (!addObject(server_, siteNode, tree.system, &systemNode)) {
return false;
}
UA_NodeId assetNode;
if (!addObject(server_, systemNode, tree.asset, &assetNode)) {
return false;
}
for (const auto& device : tree.devices) {
UA_NodeId deviceNode;
if (!addObject(server_, assetNode, device.stableDeviceKey, &deviceNode)) {
continue;
}
for (const auto& point : device.points) {
const auto objectRef = softbus::core::DeviceIdentity::buildObjectRef(
tree, device.stableDeviceKey, point.name);
UA_NodeId pointNode;
if (addVariable(server_, deviceNode, point.name, objectRef.toPath(), &pointNode) && binder) {
binder->registerPointNode(objectRef.toPath(), pointNode);
}
}
}
softbus::core::Logger::instance().info("UaAddressSpaceBuilder", "Address space built for ", tree.site);
return true;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,36 @@
#include <softbus_opcua/UaEventBridge.h>
#include <softbus_core/logging/Logger.h>
namespace softbus::opcua {
UaEventBridge::UaEventBridge(UA_Server* server)
: server_(server)
{
}
void UaEventBridge::emitHeartbeat(const std::string& objectRef)
{
softbus::core::Logger::instance().debug("UaEventBridge", "Heartbeat stub for ", objectRef);
(void)server_;
}
void UaEventBridge::emitDeviceOnline(const std::string& stableDeviceKey)
{
softbus::core::Logger::instance().info("UaEventBridge", "Device online stub: ", stableDeviceKey);
(void)server_;
}
void UaEventBridge::emitDeviceOffline(const std::string& stableDeviceKey)
{
softbus::core::Logger::instance().info("UaEventBridge", "Device offline stub: ", stableDeviceKey);
(void)server_;
}
void UaEventBridge::emitParseError(const std::string& objectRef, const std::string& detail)
{
softbus::core::Logger::instance().warn("UaEventBridge", "Parse error at ", objectRef, ": ", detail);
(void)server_;
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,157 @@
#include <softbus_opcua/UaMethodBinder.h>
#include <softbus_core/logging/Logger.h>
#include <nlohmann/json.hpp>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
#if SOFTBUS_HAS_OPCUA
namespace {
UaMethodBinder* g_methodBinder = nullptr;
UA_StatusCode executeCommandMethod(UA_Server* server,
const UA_NodeId* sessionId,
void* sessionContext,
const UA_NodeId* methodId,
void* methodContext,
const UA_NodeId* objectId,
void* objectContext,
size_t inputSize,
const UA_Variant* input,
size_t outputSize,
UA_Variant* output)
{
(void)server;
(void)sessionId;
(void)sessionContext;
(void)methodId;
(void)methodContext;
(void)objectId;
(void)objectContext;
(void)outputSize;
if (!g_methodBinder || inputSize < 1) {
return UA_STATUSCODE_BADINVALIDARGUMENT;
}
const UA_String* jsonInput = static_cast<const UA_String*>(input[0].data);
if (!jsonInput) {
return UA_STATUSCODE_BADTYPEMISMATCH;
}
const std::string jsonStr(reinterpret_cast<const char*>(jsonInput->data), jsonInput->length);
const auto j = nlohmann::json::parse(jsonStr, nullptr, false);
if (j.is_discarded()) {
return UA_STATUSCODE_BADINVALIDARGUMENT;
}
softbus::api::ExecuteCommand cmd = softbus::api::ExecuteCommand::fromJson(j);
std::string error;
if (!cmd.validate(&error)) {
const std::string result = std::string(R"({"success":false,"error":")") + error + "\"}";
UA_String out = UA_STRING_ALLOC(result.c_str());
UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
UA_String_clear(&out);
return UA_STATUSCODE_GOOD;
}
const bool ok = g_methodBinder->dispatchCommand(cmd);
const std::string result = ok ? R"({"success":true})" : R"({"success":false})";
UA_String out = UA_STRING_ALLOC(result.c_str());
UA_Variant_setScalarCopy(output, &out, &UA_TYPES[UA_TYPES_STRING]);
UA_String_clear(&out);
return UA_STATUSCODE_GOOD;
}
} // namespace
#endif
UaMethodBinder::UaMethodBinder(UA_Server* server)
: server_(server)
{
}
bool UaMethodBinder::dispatchCommand(const softbus::api::ExecuteCommand& command)
{
if (!handler_) {
return false;
}
softbus::core::Logger::instance().infoTrace("UaMethodBinder", command.traceId,
"ExecuteCommand invoked via OPC UA method");
return handler_(command);
}
bool UaMethodBinder::bindExecuteCommand(ExecuteCommandHandler handler)
{
#if !SOFTBUS_HAS_OPCUA
(void)handler;
return false;
#else
handler_ = std::move(handler);
g_methodBinder = this;
UA_ObjectAttributes objAttr = UA_ObjectAttributes_default;
objAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "SoftBusControl");
UA_NodeId controlObjectId = UA_NODEID_STRING_ALLOC(1, "SoftBusControl");
UA_NodeId createdObjectId;
UA_StatusCode status = UA_Server_addObjectNode(
server_,
controlObjectId,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, "SoftBusControl"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
objAttr,
nullptr,
&createdObjectId);
UA_ObjectAttributes_clear(&objAttr);
if (status != UA_STATUSCODE_GOOD) {
return false;
}
UA_Argument inputArgument;
UA_Argument_init(&inputArgument);
inputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Command JSON");
inputArgument.name = UA_STRING_ALLOC("commandJson");
inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
inputArgument.valueRank = UA_VALUERANK_SCALAR;
UA_Argument outputArgument;
UA_Argument_init(&outputArgument);
outputArgument.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "Result JSON");
outputArgument.name = UA_STRING_ALLOC("resultJson");
outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
outputArgument.valueRank = UA_VALUERANK_SCALAR;
UA_MethodAttributes methodAttr = UA_MethodAttributes_default;
methodAttr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "ExecuteCommand");
methodAttr.executable = true;
methodAttr.userExecutable = true;
status = UA_Server_addMethodNode(
server_,
UA_NODEID_STRING_ALLOC(1, "ExecuteCommand"),
createdObjectId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME_ALLOC(1, "ExecuteCommand"),
methodAttr,
&executeCommandMethod,
1,
&inputArgument,
1,
&outputArgument,
nullptr,
nullptr);
UA_Argument_clear(&inputArgument);
UA_Argument_clear(&outputArgument);
UA_MethodAttributes_clear(&methodAttr);
return status == UA_STATUSCODE_GOOD;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,71 @@
#include <softbus_opcua/UaModelBinder.h>
#include <softbus_core/logging/Logger.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#endif
namespace softbus::opcua {
UaModelBinder::UaModelBinder(UA_Server* server)
: server_(server)
{
}
void UaModelBinder::registerPointNode(const std::string& objectRef, const UA_NodeId& nodeId)
{
#if SOFTBUS_HAS_OPCUA
UA_NodeId copy{};
UA_NodeId_copy(&nodeId, &copy);
pointNodes_[objectRef] = copy;
#else
(void)objectRef;
(void)nodeId;
#endif
}
bool UaModelBinder::bind(const softbus::core::DOMMessage& message)
{
#if !SOFTBUS_HAS_OPCUA
(void)message;
return false;
#else
if (!server_) {
return false;
}
const std::string key = message.id.empty() ? message.sourceId : message.id;
const auto it = pointNodes_.find(key);
if (it == pointNodes_.end()) {
softbus::core::Logger::instance().warnTrace("UaModelBinder", message.traceId,
"No OPC UA node registered for ", key);
return false;
}
double numericValue = 0.0;
if (message.value.contains("value")) {
numericValue = message.value["value"].get<double>();
} else if (message.value.contains("registerValue")) {
numericValue = message.value["registerValue"].get<double>();
}
UA_Variant variant;
UA_Variant_init(&variant);
UA_Variant_setScalarCopy(&variant, &numericValue, &UA_TYPES[UA_TYPES_DOUBLE]);
const UA_StatusCode status = UA_Server_writeValue(server_, it->second, variant);
UA_Variant_clear(&variant);
if (status == UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().infoTrace("UaModelBinder", message.traceId,
"Bound DOMMessage to OPC UA node ", key, " value=", numericValue);
return true;
}
softbus::core::Logger::instance().errorTrace("UaModelBinder", message.traceId,
"Failed to write OPC UA node ", key);
return false;
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,80 @@
#include <softbus_opcua/UaServerEngine.h>
#include <softbus_core/logging/Logger.h>
#if SOFTBUS_HAS_OPCUA
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#endif
namespace softbus::opcua {
UaServerEngine::UaServerEngine() = default;
UaServerEngine::~UaServerEngine()
{
stop();
}
bool UaServerEngine::start(uint16_t port, const std::string& applicationName, const SecurityConfig& security)
{
#if !SOFTBUS_HAS_OPCUA
(void)port;
(void)applicationName;
(void)security;
softbus::core::Logger::instance().error("UaServerEngine", "OPC UA support not built in");
return false;
#else
security_ = security;
if (security.enableTls) {
softbus::core::Logger::instance().warn("UaServerEngine",
"TLS/mTLS requested but not enabled in Phase1 build");
}
server_ = UA_Server_new();
UA_ServerConfig* config = UA_Server_getConfig(server_);
UA_StatusCode status = UA_ServerConfig_setMinimal(config, port, nullptr);
if (status != UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().error("UaServerEngine", "Failed to configure server");
UA_Server_delete(server_);
server_ = nullptr;
return false;
}
config->applicationDescription.applicationName = UA_LOCALIZEDTEXT_ALLOC("en-US", applicationName.c_str());
status = UA_Server_run_startup(server_);
if (status != UA_STATUSCODE_GOOD) {
softbus::core::Logger::instance().error("UaServerEngine", "Failed to start server");
UA_Server_delete(server_);
server_ = nullptr;
return false;
}
running_ = true;
softbus::core::Logger::instance().info("UaServerEngine", "OPC UA server started on port ", port);
return true;
#endif
}
void UaServerEngine::stop()
{
#if SOFTBUS_HAS_OPCUA
if (server_) {
UA_Server_run_shutdown(server_);
UA_Server_delete(server_);
server_ = nullptr;
}
#endif
running_ = false;
}
void UaServerEngine::iterate()
{
#if SOFTBUS_HAS_OPCUA
if (server_ && running_) {
UA_Server_run_iterate(server_, false);
}
#endif
}
} // namespace softbus::opcua

View File

@@ -0,0 +1,12 @@
softbus_package(softbus_transport)
add_library(softbus_transport STATIC
src/UplinkWire.cpp
src/TcpUplinkServer.cpp
src/TcpUplinkClient.cpp
)
softbus_export_include(softbus_transport "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(softbus_transport PUBLIC softbus_core softbus_api asio)
softbus_apply_platform_libs(softbus_transport)

View File

@@ -0,0 +1,46 @@
#pragma once
#include <softbus_transport/UplinkWire.h>
#include <asio.hpp>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
namespace softbus::transport {
using CommandHandler = std::function<void(const softbus::api::ExecuteCommand&)>;
class TcpUplinkClient {
public:
TcpUplinkClient(std::string host, uint16_t port);
~TcpUplinkClient();
void setCommandHandler(CommandHandler handler);
bool connect();
void disconnect();
bool sendRawPacket(const softbus::core::RawPacket& packet);
void poll();
bool isConnected() const { return connected_; }
private:
void doRead();
void handleLine(const std::string& line);
std::string host_;
uint16_t port_;
asio::io_context io_;
std::unique_ptr<asio::ip::tcp::socket> socket_;
std::unique_ptr<asio::executor_work_guard<asio::io_context::executor_type>> workGuard_;
std::thread ioThread_;
CommandHandler commandHandler_;
std::string readBuffer_;
std::mutex sendMutex_;
bool connected_{false};
};
} // namespace softbus::transport

View File

@@ -0,0 +1,43 @@
#pragma once
#include <softbus_transport/UplinkWire.h>
#include <asio.hpp>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
namespace softbus::transport {
using RawPacketHandler = std::function<void(const softbus::core::RawPacket&)>;
using CommandSender = std::function<bool(const softbus::api::ExecuteCommand&)>;
class TcpUplinkServer {
public:
TcpUplinkServer(asio::io_context& io, std::string host, uint16_t port);
~TcpUplinkServer();
void setRawPacketHandler(RawPacketHandler handler);
void start();
void stop();
bool sendCommand(const softbus::api::ExecuteCommand& command);
private:
void doAccept();
void doRead(std::shared_ptr<asio::ip::tcp::socket> socket);
void handleLine(const std::string& line);
asio::io_context& io_;
asio::ip::tcp::acceptor acceptor_;
RawPacketHandler rawHandler_;
std::shared_ptr<asio::ip::tcp::socket> clientSocket_;
std::string readBuffer_;
std::mutex sendMutex_;
bool running_{false};
};
} // namespace softbus::transport

View File

@@ -0,0 +1,33 @@
#pragma once
#include <softbus_core/models/RawPacket.h>
#include <softbus_api/ExecuteCommand.h>
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace softbus::transport {
enum class WireMessageType {
RawPacket,
ExecuteCommand,
Unknown
};
struct WireMessage {
WireMessageType type{WireMessageType::Unknown};
softbus::core::RawPacket rawPacket;
softbus::api::ExecuteCommand command;
};
std::string encodeWireMessage(const softbus::core::RawPacket& packet);
std::string encodeWireMessage(const softbus::api::ExecuteCommand& command);
std::optional<WireMessage> decodeWireMessage(const std::string& line);
std::string base64Encode(const uint8_t* data, std::size_t size);
std::vector<uint8_t> base64Decode(const std::string& encoded);
} // namespace softbus::transport

View File

@@ -0,0 +1,120 @@
#include <softbus_transport/TcpUplinkClient.h>
#include <softbus_core/logging/Logger.h>
#include <array>
namespace softbus::transport {
TcpUplinkClient::TcpUplinkClient(std::string host, uint16_t port)
: host_(std::move(host))
, port_(port)
, workGuard_(std::make_unique<asio::executor_work_guard<asio::io_context::executor_type>>(
asio::make_work_guard(io_)))
{
}
TcpUplinkClient::~TcpUplinkClient()
{
disconnect();
}
void TcpUplinkClient::setCommandHandler(CommandHandler handler)
{
commandHandler_ = std::move(handler);
}
bool TcpUplinkClient::connect()
{
if (connected_) {
return true;
}
socket_ = std::make_unique<asio::ip::tcp::socket>(io_);
std::error_code ec;
socket_->connect(asio::ip::tcp::endpoint(asio::ip::make_address(host_), port_), ec);
if (ec) {
softbus::core::Logger::instance().error("TcpUplinkClient", "Connect failed: ", ec.message());
socket_.reset();
return false;
}
connected_ = true;
ioThread_ = std::thread([this]() { io_.run(); });
doRead();
softbus::core::Logger::instance().info("TcpUplinkClient", "Connected to ", host_, ":", port_);
return true;
}
void TcpUplinkClient::disconnect()
{
connected_ = false;
if (socket_) {
std::error_code ec;
socket_->close(ec);
socket_.reset();
}
workGuard_.reset();
io_.stop();
if (ioThread_.joinable()) {
ioThread_.join();
}
io_.restart();
workGuard_ = std::make_unique<asio::executor_work_guard<asio::io_context::executor_type>>(
asio::make_work_guard(io_));
}
bool TcpUplinkClient::sendRawPacket(const softbus::core::RawPacket& packet)
{
if (!socket_ || !socket_->is_open()) {
return false;
}
const auto line = encodeWireMessage(packet);
std::lock_guard<std::mutex> lock(sendMutex_);
std::error_code ec;
asio::write(*socket_, asio::buffer(line), ec);
return !ec;
}
void TcpUplinkClient::poll()
{
io_.poll();
}
void TcpUplinkClient::doRead()
{
if (!socket_ || !socket_->is_open()) {
return;
}
auto buffer = std::make_shared<std::array<char, 4096>>();
socket_->async_read_some(asio::buffer(*buffer),
[this, buffer](const std::error_code& ec, std::size_t bytes) {
if (ec) {
connected_ = false;
softbus::core::Logger::instance().warn("TcpUplinkClient", "Disconnected from daemon");
return;
}
readBuffer_.append(buffer->data(), bytes);
for (;;) {
const auto pos = readBuffer_.find('\n');
if (pos == std::string::npos) {
break;
}
const std::string line = readBuffer_.substr(0, pos);
readBuffer_.erase(0, pos + 1);
handleLine(line);
}
doRead();
});
}
void TcpUplinkClient::handleLine(const std::string& line)
{
const auto msg = decodeWireMessage(line);
if (!msg) {
return;
}
if (msg->type == WireMessageType::ExecuteCommand && commandHandler_) {
commandHandler_(msg->command);
}
}
} // namespace softbus::transport

View File

@@ -0,0 +1,115 @@
#include <softbus_transport/TcpUplinkServer.h>
#include <softbus_core/logging/Logger.h>
#include <array>
namespace softbus::transport {
TcpUplinkServer::TcpUplinkServer(asio::io_context& io, std::string host, uint16_t port)
: io_(io)
, acceptor_(io, asio::ip::tcp::endpoint(asio::ip::make_address(host), port))
{
}
TcpUplinkServer::~TcpUplinkServer()
{
stop();
}
void TcpUplinkServer::setRawPacketHandler(RawPacketHandler handler)
{
rawHandler_ = std::move(handler);
}
void TcpUplinkServer::start()
{
running_ = true;
doAccept();
softbus::core::Logger::instance().info("TcpUplinkServer", "Listening on ",
acceptor_.local_endpoint().address().to_string(), ":",
acceptor_.local_endpoint().port());
}
void TcpUplinkServer::stop()
{
running_ = false;
std::error_code ec;
acceptor_.close(ec);
if (clientSocket_) {
clientSocket_->close(ec);
clientSocket_.reset();
}
}
bool TcpUplinkServer::sendCommand(const softbus::api::ExecuteCommand& command)
{
if (!clientSocket_ || !clientSocket_->is_open()) {
return false;
}
const auto line = encodeWireMessage(command);
std::lock_guard<std::mutex> lock(sendMutex_);
std::error_code ec;
asio::write(*clientSocket_, asio::buffer(line), ec);
return !ec;
}
void TcpUplinkServer::doAccept()
{
if (!running_) {
return;
}
auto socket = std::make_shared<asio::ip::tcp::socket>(io_);
acceptor_.async_accept(*socket, [this, socket](const std::error_code& ec) {
if (ec) {
if (running_) {
doAccept();
}
return;
}
clientSocket_ = socket;
readBuffer_.clear();
softbus::core::Logger::instance().info("TcpUplinkServer", "Edge connected");
doRead(socket);
doAccept();
});
}
void TcpUplinkServer::doRead(const std::shared_ptr<asio::ip::tcp::socket> socket)
{
auto buffer = std::make_shared<std::array<char, 4096>>();
socket->async_read_some(asio::buffer(*buffer),
[this, socket, buffer](const std::error_code& ec, std::size_t bytes) {
if (ec) {
softbus::core::Logger::instance().warn("TcpUplinkServer", "Edge disconnected");
if (clientSocket_ == socket) {
clientSocket_.reset();
}
return;
}
readBuffer_.append(buffer->data(), bytes);
for (;;) {
const auto pos = readBuffer_.find('\n');
if (pos == std::string::npos) {
break;
}
const std::string line = readBuffer_.substr(0, pos);
readBuffer_.erase(0, pos + 1);
handleLine(line);
}
doRead(socket);
});
}
void TcpUplinkServer::handleLine(const std::string& line)
{
const auto msg = decodeWireMessage(line);
if (!msg) {
return;
}
if (msg->type == WireMessageType::RawPacket && rawHandler_) {
rawHandler_(msg->rawPacket);
}
}
} // namespace softbus::transport

View File

@@ -0,0 +1,110 @@
#include <softbus_transport/UplinkWire.h>
#include <nlohmann/json.hpp>
namespace softbus::transport {
namespace {
static const char kBase64Table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
} // namespace
std::string base64Encode(const uint8_t* data, std::size_t size)
{
std::string out;
out.reserve(((size + 2) / 3) * 4);
for (std::size_t i = 0; i < size; i += 3) {
const uint32_t block = (static_cast<uint32_t>(data[i]) << 16)
| ((i + 1 < size ? data[i + 1] : 0) << 8)
| (i + 2 < size ? data[i + 2] : 0);
out.push_back(kBase64Table[(block >> 18) & 0x3F]);
out.push_back(kBase64Table[(block >> 12) & 0x3F]);
out.push_back(i + 1 < size ? kBase64Table[(block >> 6) & 0x3F] : '=');
out.push_back(i + 2 < size ? kBase64Table[block & 0x3F] : '=');
}
return out;
}
std::vector<uint8_t> base64Decode(const std::string& encoded)
{
auto val = [](char c) -> int {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
};
std::vector<uint8_t> out;
int buffer = 0;
int bits = 0;
for (char c : encoded) {
if (c == '=' || c == '\n' || c == '\r') {
continue;
}
const int v = val(c);
if (v < 0) {
continue;
}
buffer = (buffer << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
out.push_back(static_cast<uint8_t>((buffer >> bits) & 0xFF));
}
}
return out;
}
std::string encodeWireMessage(const softbus::core::RawPacket& packet)
{
nlohmann::json j{
{"type", "rawPacket"},
{"traceId", packet.traceId},
{"timestampNs", packet.timestampNs},
{"runtimeDeviceId", packet.runtimeDeviceId},
{"sourceId", packet.sourceId},
{"protocolTag", packet.protocolTag},
{"payloadBase64", base64Encode(packet.payload.data(), packet.payload.size())}
};
return j.dump() + "\n";
}
std::string encodeWireMessage(const softbus::api::ExecuteCommand& command)
{
return command.toJson().dump() + "\n";
}
std::optional<WireMessage> decodeWireMessage(const std::string& line)
{
if (line.empty()) {
return std::nullopt;
}
const auto j = nlohmann::json::parse(line, nullptr, false);
if (j.is_discarded()) {
return std::nullopt;
}
const std::string type = j.value("type", "");
WireMessage msg;
if (type == "rawPacket") {
msg.type = WireMessageType::RawPacket;
msg.rawPacket.traceId = j.value("traceId", "");
msg.rawPacket.timestampNs = j.value("timestampNs", int64_t{0});
msg.rawPacket.runtimeDeviceId = j.value("runtimeDeviceId", "");
msg.rawPacket.sourceId = j.value("sourceId", "");
msg.rawPacket.protocolTag = j.value("protocolTag", "");
msg.rawPacket.payload = base64Decode(j.value("payloadBase64", ""));
return msg;
}
if (type == "executeCommand") {
msg.type = WireMessageType::ExecuteCommand;
msg.command = softbus::api::ExecuteCommand::fromJson(j);
return msg;
}
return std::nullopt;
}
} // namespace softbus::transport