mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-08-30 04:15:10 +00:00
connection between 2 systems and bug fixes (#14)
This commit is contained in:
@@ -12,31 +12,59 @@ if(WIN32)
|
||||
# Define project
|
||||
project(uds_win_sockets VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(uds_win_sockets STATIC
|
||||
# Build sources once and expose them in two forms:
|
||||
# - uds_win_sockets: static link target for tests / other local libraries on MinGW
|
||||
# - uds_win_sockets_module: runtime-loadable .sdv module for ModuleControl
|
||||
set(UDS_WIN_SOCKETS_SOURCES
|
||||
channel_mgnt.cpp
|
||||
connection.cpp
|
||||
watchdog.cpp
|
||||
)
|
||||
|
||||
target_include_directories(uds_win_sockets
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
./include/
|
||||
add_library(uds_win_sockets STATIC
|
||||
${UDS_WIN_SOCKETS_SOURCES}
|
||||
)
|
||||
|
||||
add_library(uds_win_sockets_module SHARED
|
||||
${UDS_WIN_SOCKETS_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(uds_win_sockets
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
./include/
|
||||
)
|
||||
|
||||
target_include_directories(uds_win_sockets_module
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
./include/
|
||||
)
|
||||
|
||||
target_link_libraries(uds_win_sockets
|
||||
PUBLIC
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
Ws2_32.lib
|
||||
)
|
||||
|
||||
set_target_properties(uds_win_sockets PROPERTIES PREFIX "")
|
||||
set_target_properties(uds_win_sockets PROPERTIES SUFFIX ".sdv")
|
||||
target_link_libraries(uds_win_sockets_module
|
||||
PUBLIC
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
Ws2_32.lib
|
||||
)
|
||||
|
||||
set_target_properties(uds_win_sockets_module PROPERTIES
|
||||
PREFIX ""
|
||||
SUFFIX ".sdv"
|
||||
OUTPUT_NAME "uds_win_sockets"
|
||||
ARCHIVE_OUTPUT_NAME "uds_win_sockets_module"
|
||||
)
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(uds_win_sockets CompileCoreIDL)
|
||||
add_dependencies(uds_win_sockets_module CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} uds_win_sockets PARENT_SCOPE)
|
||||
set(SDV_Service_List ${SDV_Service_List} uds_win_sockets_module PARENT_SCOPE)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -11,15 +11,20 @@
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "channel_mgnt.h"
|
||||
#include "connection.h"
|
||||
|
||||
#include "../../global/base64.h"
|
||||
#include <support/toml.h>
|
||||
#include <interfaces/process.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <cstring>
|
||||
|
||||
#include "../../global/base64.h"
|
||||
#include <interfaces/app.h>
|
||||
#include <interfaces/process.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/toml.h>
|
||||
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
@@ -44,53 +49,23 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Parse a UDS connect/config string and extract the path
|
||||
*
|
||||
* Expected format (substring-based, not strict):
|
||||
* "proto=uds;path=<something>;"
|
||||
*
|
||||
* Behavior:
|
||||
* - If "proto=uds" is missing -> returns false (not a UDS config)
|
||||
* - If "path=" is missing -> returns true and outPath is cleared
|
||||
* - If "path=" is present -> extracts the substring until ';' or end
|
||||
*
|
||||
* @param cs Input configuration / connect string
|
||||
* @param outPath Output: extracted path (possibly empty)
|
||||
* @return true if this looks like a UDS string, false otherwise
|
||||
*/
|
||||
static bool ParseUdsPath(const std::string& cs, std::string& outPath)
|
||||
static bool EnsureWSAInitialized()
|
||||
{
|
||||
constexpr const char* protoKey = "proto=uds";
|
||||
constexpr const char* pathKey = "path=";
|
||||
static std::once_flag s_once;
|
||||
static bool s_ok = false;
|
||||
|
||||
// Must contain "proto=uds" to be considered UDS
|
||||
if (cs.find(protoKey) == std::string::npos)
|
||||
std::call_once(s_once, []()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
WSADATA wsa{};
|
||||
const int rc = WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
s_ok = (rc == 0);
|
||||
if (!s_ok)
|
||||
{
|
||||
SDV_LOG_ERROR("[AF_UNIX] WSAStartup failed, rc=", rc);
|
||||
}
|
||||
});
|
||||
|
||||
const auto p = cs.find(pathKey);
|
||||
if (p == std::string::npos)
|
||||
{
|
||||
// No path given, but proto=uds is present -> use default later
|
||||
outPath.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start = p + std::strlen(pathKey);
|
||||
const auto end = cs.find(';', start);
|
||||
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
outPath = cs.substr(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
outPath = cs.substr(start, end - start);
|
||||
}
|
||||
|
||||
return true;
|
||||
return s_ok;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +80,7 @@ static bool ParseUdsPath(const std::string& cs, std::string& outPath)
|
||||
*
|
||||
* @return Expanded string, or the original input on failure
|
||||
*/
|
||||
|
||||
static std::string ExpandEnvVars(const std::string& in)
|
||||
{
|
||||
if (in.find('%') == std::string::npos)
|
||||
@@ -112,9 +88,8 @@ static std::string ExpandEnvVars(const std::string& in)
|
||||
return in;
|
||||
}
|
||||
|
||||
char buf[4096] = {};
|
||||
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast<DWORD>(sizeof(buf)));
|
||||
|
||||
char buf[4096] = {};
|
||||
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast<DWORD>(sizeof(buf)));
|
||||
if (n > 0 && n < sizeof(buf))
|
||||
{
|
||||
return std::string(buf);
|
||||
@@ -135,19 +110,181 @@ static std::string ExpandEnvVars(const std::string& in)
|
||||
*
|
||||
* @return A safe pathname guaranteed to fit into sun_path
|
||||
*/
|
||||
|
||||
static std::string ClampUdsPath(const std::string& p)
|
||||
{
|
||||
SOCKADDR_UN tmp{};
|
||||
SOCKADDR_UN tmp{};
|
||||
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
|
||||
|
||||
if (p.size() <= kMax)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
return p.substr(0, kMax);
|
||||
}
|
||||
|
||||
/*static std::string BuildFinalUdsPath(const std::string& rawPath)
|
||||
{
|
||||
std::string full = ExpandEnvVars(rawPath);
|
||||
|
||||
auto pos = full.find_last_of("\\/");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
const std::string dir = full.substr(0, pos);
|
||||
CreateDirectoryA(dir.c_str(), nullptr);
|
||||
}
|
||||
|
||||
return ClampUdsPath(full);
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Parse a UDS connect/config string and extract the path
|
||||
*
|
||||
* Expected format (substring-based, not strict):
|
||||
* "proto=uds;path=<something>;"
|
||||
*
|
||||
* Behavior:
|
||||
* - If "proto=uds" is missing -> returns false (not a UDS config)
|
||||
* - If "path=" is missing -> returns true and outPath is cleared
|
||||
* - If "path=" is present -> extracts the substring until ';' or end
|
||||
*
|
||||
* @param cs Input configuration / connect string
|
||||
* @param outPath Output: extracted path (possibly empty)
|
||||
* @return true if this looks like a UDS string, false otherwise
|
||||
*/
|
||||
static bool ParseUdsPath(const std::string& cs, std::string& outPath)
|
||||
{
|
||||
constexpr const char* protoKey = "proto=uds";
|
||||
constexpr const char* pathKey = "path=";
|
||||
|
||||
// Strict raw connect string only
|
||||
if (cs.rfind(protoKey, 0) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto p = cs.find(pathKey);
|
||||
if (p == std::string::npos)
|
||||
{
|
||||
outPath.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto start = p + std::strlen(pathKey);
|
||||
const auto end = cs.find(';', start);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
outPath = cs.substr(start);
|
||||
}
|
||||
else
|
||||
{
|
||||
outPath = cs.substr(start, end - start);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string SanitizeUdsName(std::string name)
|
||||
{
|
||||
for (char& ch : name)
|
||||
{
|
||||
const bool isAlphaNum = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
|
||||
if (!isAlphaNum && ch != '_' && ch != '-')
|
||||
{
|
||||
ch = '_';
|
||||
}
|
||||
}
|
||||
|
||||
if (name.empty())
|
||||
{
|
||||
name = "sdv";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a named UDS raw path using the channel name
|
||||
*
|
||||
* @param[in] channelName The name of the channel
|
||||
*
|
||||
* @return A raw UDS path suitable for further processing
|
||||
*/
|
||||
static std::string BuildNamedUdsRawPath(const std::string& channelName)
|
||||
{
|
||||
return "%LOCALAPPDATA%/sdv/" + SanitizeUdsName(channelName) + ".sock";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extract a UDS connect string from either raw UDS format or Provider TOML.
|
||||
*
|
||||
* Supported inputs:
|
||||
* - "proto=uds;path=<...>;"
|
||||
* - [Provider]\nName="unix_domain_sockets"\nConnectString="proto=uds;path=<...>;"
|
||||
*/
|
||||
static bool ExtractUdsConnectString(const std::string& in,
|
||||
std::string& outUdsConnectString)
|
||||
{
|
||||
std::string path;
|
||||
|
||||
// Case 1: strict raw UDS connect string
|
||||
if (ParseUdsPath(in, path))
|
||||
{
|
||||
outUdsConnectString = in;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not run the TOML parser on obviously non-TOML garbage
|
||||
//if (!LooksLikeToml(in))
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// Case 2: structured TOML
|
||||
sdv::toml::CTOMLParser parser(in);
|
||||
if (!parser.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
|
||||
if (!providerName.empty() &&
|
||||
providerName != "unix_domain_sockets" &&
|
||||
providerName != "WinSocketsChannelControl")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
|
||||
if (!nested.empty())
|
||||
{
|
||||
if (ParseUdsPath(nested, path))
|
||||
{
|
||||
outUdsConnectString = nested;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
|
||||
if (!cfgPath.empty())
|
||||
{
|
||||
outUdsConnectString = "proto=uds;path=" + cfgPath + ";";
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
|
||||
if (!cfgName.empty())
|
||||
{
|
||||
outUdsConnectString = "proto=uds;path=" + BuildNamedUdsRawPath(cfgName) + ";";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Normalize a UDS path for display/logging purposes
|
||||
*
|
||||
@@ -166,20 +303,18 @@ static std::string ClampUdsPath(const std::string& p)
|
||||
static std::string NormalizeUdsPathForWindows(const std::string& raw)
|
||||
{
|
||||
std::string p = ExpandEnvVars(raw);
|
||||
|
||||
const size_t pos = p.find_last_of("/\\");
|
||||
std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1);
|
||||
|
||||
const size_t pos = p.find_last_of("/\\");
|
||||
std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1);
|
||||
if (base.empty())
|
||||
{
|
||||
base = "sdv.sock";
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] Normalize raw='", raw, "' -> base='", base, "'");
|
||||
|
||||
return ClampUdsPath(base);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Build a short absolute Win32 path suitable for AF_UNIX `sun_path`
|
||||
*
|
||||
@@ -224,6 +359,41 @@ static std::string MakeShortWinUdsPath(const std::string& raw)
|
||||
return ClampUdsPath(full);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the default UDS path using the current SDV instance ID.
|
||||
*
|
||||
* Using a single global socket filename makes independently running test
|
||||
* processes trample each other when the build executes multiple post-build
|
||||
* tests in parallel. Namespace the implicit endpoint per instance instead.
|
||||
*/
|
||||
static std::string GetDefaultUdsRawPath()
|
||||
{
|
||||
uint32_t instanceId = 1000u;
|
||||
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (pAppContext && pAppContext->GetInstanceID() != 0u)
|
||||
{
|
||||
instanceId = pAppContext->GetInstanceID();
|
||||
}
|
||||
|
||||
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + ".sock";
|
||||
}
|
||||
|
||||
static std::string GetUniqueEndpointUdsRawPath()
|
||||
{
|
||||
static std::atomic<uint32_t> nextEndpointId { 0u };
|
||||
|
||||
uint32_t instanceId = 1000u;
|
||||
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (pAppContext && pAppContext->GetInstanceID() != 0u)
|
||||
{
|
||||
instanceId = pAppContext->GetInstanceID();
|
||||
}
|
||||
|
||||
const uint32_t endpointId = nextEndpointId.fetch_add(1u, std::memory_order_relaxed);
|
||||
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + "_" +
|
||||
std::to_string(static_cast<uint32_t>(GetCurrentProcessId())) + "_" + std::to_string(endpointId) + ".sock";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a listening AF_UNIX socket on Windows
|
||||
*
|
||||
@@ -239,6 +409,9 @@ static std::string MakeShortWinUdsPath(const std::string& raw)
|
||||
*/
|
||||
static SOCKET CreateUnixListenSocket(const std::string& rawPath)
|
||||
{
|
||||
if (!EnsureWSAInitialized())
|
||||
return INVALID_SOCKET;
|
||||
|
||||
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
@@ -285,118 +458,99 @@ static SOCKET CreateUnixListenSocket(const std::string& rawPath)
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connect to a Windows AF_UNIX server socket with retry logic
|
||||
*
|
||||
* Repeatedly attempts to connect to the server's UDS path until either:
|
||||
* - connection succeeds, or
|
||||
* - total timeout is exceeded
|
||||
*
|
||||
* On each attempt:
|
||||
* - a new socket() is created
|
||||
* - connect() is attempted
|
||||
* - on failure the socket is closed and retried
|
||||
*
|
||||
* This mirrors Linux AF_UNIX behavior where the client waits for the
|
||||
* server's socket file to appear/become ready
|
||||
*
|
||||
* @param[in] rawPath Raw UDS path from configuration
|
||||
* @param[in] totalTimeoutMs Maximum total wait time in milliseconds
|
||||
* @param[in] retryDelayMs Delay between retries in milliseconds
|
||||
*
|
||||
* @return Connected SOCKET on success, INVALID_SOCKET on timeout or error
|
||||
*/
|
||||
static SOCKET ConnectUnixSocket(
|
||||
const std::string& rawPath,
|
||||
uint32_t totalTimeoutMs,
|
||||
uint32_t retryDelayMs)
|
||||
/*static SOCKET ConnectUnixSocket(const std::string& rawPath,
|
||||
uint32_t totalTimeoutMs,
|
||||
uint32_t retryDelayMs)
|
||||
{
|
||||
const std::string udsPath = MakeShortWinUdsPath(rawPath);
|
||||
if (!EnsureWSAInitialized())
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
const std::string udsPath = BuildFinalUdsPath(rawPath);
|
||||
|
||||
SOCKADDR_UN addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
|
||||
|
||||
const int addrlen = static_cast<int>(
|
||||
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1
|
||||
);
|
||||
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1);
|
||||
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(totalTimeoutMs);
|
||||
auto deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(totalTimeoutMs);
|
||||
|
||||
while (true)
|
||||
{
|
||||
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", WSAGetLastError());
|
||||
SDV_LOG_ERROR("[AF_UNIX] socket FAIL (client), WSA=", WSAGetLastError());
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
|
||||
{
|
||||
SDV_LOG_INFO("[AF_UNIX] connect OK (pathname), path='", udsPath, "'");
|
||||
SDV_LOG_INFO("[AF_UNIX] connect OK: ", udsPath);
|
||||
return s;
|
||||
}
|
||||
|
||||
int lastError = WSAGetLastError();
|
||||
const int err = WSAGetLastError();
|
||||
closesocket(s);
|
||||
|
||||
if (std::chrono::steady_clock::now() >= deadline)
|
||||
{
|
||||
SDV_LOG_ERROR(
|
||||
"[AF_UNIX] connect TIMEOUT (pathname), last WSA=",
|
||||
lastError, ", path='", udsPath, "'"
|
||||
);
|
||||
SDV_LOG_ERROR("[AF_UNIX] connect TIMEOUT, WSA=", err, ", path=", udsPath);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool CSocketsChannelMgnt::OnInitialize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_udsMtx);
|
||||
|
||||
auto it = m_udsServers.find(udsPath);
|
||||
if (it != m_udsServers.end() && it->second.get() == ptr)
|
||||
{
|
||||
// Remove the server entry only if it matches the pointer we know
|
||||
m_udsServers.erase(it);
|
||||
}
|
||||
|
||||
// Mark this UDS path as no longer claimed
|
||||
m_udsServerClaimed.erase(udsPath);
|
||||
return EnsureWSAInitialized();
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::OnShutdown()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::OnDestroy()
|
||||
{
|
||||
m_watchdog.Clear();
|
||||
}
|
||||
|
||||
sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8string& cfgStr)
|
||||
{
|
||||
// Ensure WinSock is initialized on Windows
|
||||
if (StartUpWinSock() != 0)
|
||||
{
|
||||
// If WinSock cannot be initialized, we cannot create an endpoint
|
||||
return {};
|
||||
}
|
||||
|
||||
// Parse UDS path from config. If proto!=uds, we still default to UDS
|
||||
std::string udsRaw;
|
||||
bool udsRequested = ParseUdsPath(cfgStr, udsRaw);
|
||||
|
||||
if (!udsRequested || udsRaw.empty())
|
||||
{
|
||||
// Default path if not provided or not UDS-specific
|
||||
udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock";
|
||||
sdv::toml::CTOMLParser parser(cfgStr);
|
||||
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
|
||||
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
|
||||
|
||||
if (!cfgPath.empty())
|
||||
{
|
||||
udsRaw = cfgPath;
|
||||
}
|
||||
else if (!cfgName.empty())
|
||||
{
|
||||
udsRaw = BuildNamedUdsRawPath(cfgName);
|
||||
}
|
||||
else if (cfgStr.empty())
|
||||
{
|
||||
udsRaw = GetUniqueEndpointUdsRawPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
udsRaw = GetDefaultUdsRawPath();
|
||||
}
|
||||
}
|
||||
|
||||
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
|
||||
@@ -411,75 +565,81 @@ sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8stri
|
||||
|
||||
// Server-side CWinsockConnection, it will accept() a client on first use
|
||||
auto server = std::make_shared<CWinsockConnection>(listenSocket, /*acceptRequired*/ true);
|
||||
|
||||
// Ignore cppcheck warning; if construction failed, an exception is expected first.
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!server)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_udsMtx);
|
||||
m_udsServers[udsPath] = server;
|
||||
m_udsServerClaimed.erase(udsPath);
|
||||
return {};
|
||||
}
|
||||
server->SetWatchDogRemoveCallback([this](const void* connection)
|
||||
{
|
||||
m_watchdog.RemoveConnection(connection);
|
||||
});
|
||||
m_watchdog.AddConnection(server);
|
||||
|
||||
sdv::ipc::SChannelEndpoint ep{};
|
||||
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
|
||||
ep.ssConnectString = "proto=uds;path=" + udsPath + ";";
|
||||
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
|
||||
|
||||
// Keep compatibility with CCommunicationControl::CreateClientConnection,
|
||||
// which expects a Provider TOML section with Provider.Name.
|
||||
const std::string udsConnectString = "proto=uds;path=" + udsPath + ";";
|
||||
ep.ssConnectString = udsConnectString;
|
||||
|
||||
return ep;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& cs)
|
||||
{
|
||||
// Ensure WinSock is initialized
|
||||
if (StartUpWinSock() != 0)
|
||||
std::string udsConnectString;
|
||||
if (!ExtractUdsConnectString(cs, udsConnectString))
|
||||
{
|
||||
// Not a UDS connect string / provider description
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string udsRaw;
|
||||
if (!ParseUdsPath(cs, udsRaw))
|
||||
{
|
||||
// Not a UDS connect string
|
||||
return nullptr;
|
||||
}
|
||||
ParseUdsPath(udsConnectString, udsRaw);
|
||||
|
||||
if (udsRaw.empty())
|
||||
{
|
||||
udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock";
|
||||
udsRaw = GetDefaultUdsRawPath();
|
||||
}
|
||||
|
||||
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
|
||||
SDV_LOG_INFO("[AF_UNIX] Access udsPath=", udsPath);
|
||||
|
||||
const bool isServer = (udsConnectString.find("role=server") != std::string::npos);
|
||||
std::shared_ptr<CWinsockConnection> connection;
|
||||
if (isServer)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_udsMtx);
|
||||
|
||||
auto it = m_udsServers.find(udsPath);
|
||||
if (it != m_udsServers.end() && it->second != nullptr)
|
||||
SOCKET listenSocket = CreateUnixListenSocket(udsPath);
|
||||
if (listenSocket == INVALID_SOCKET)
|
||||
{
|
||||
// Return the server-side object only once for this UDS path
|
||||
if (!m_udsServerClaimed.count(udsPath))
|
||||
{
|
||||
m_udsServerClaimed.insert(udsPath);
|
||||
SDV_LOG_INFO("[AF_UNIX] Access -> RETURN SERVER for ", udsPath);
|
||||
return it->second.get(); // server object (acceptRequired=true)
|
||||
}
|
||||
// Otherwise, later calls will create a client socket below
|
||||
return nullptr;
|
||||
}
|
||||
connection = std::make_shared<CWinsockConnection>(listenSocket, /*acceptRequired*/ true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Client-side endpoint object. The actual socket connect is deferred to AsyncConnect,
|
||||
// matching the semantic contract used by ipc_com and shared memory.
|
||||
connection = std::make_shared<CWinsockConnection>(udsPath);
|
||||
}
|
||||
|
||||
// CLIENT: create a socket connected to the same udsPath
|
||||
SOCKET s = ConnectUnixSocket(udsPath,
|
||||
/*totalTimeoutMs*/ 5000,
|
||||
/*retryDelayMs*/ 50);
|
||||
|
||||
if (s == INVALID_SOCKET)
|
||||
// Ignore cppcheck warning; if construction failed, an exception is expected first.
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!connection)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] Access -> CREATE CLIENT for ", udsPath);
|
||||
connection->SetWatchDogRemoveCallback([this](const void* instance)
|
||||
{
|
||||
m_watchdog.RemoveConnection(instance);
|
||||
});
|
||||
|
||||
// Client-side connection object (acceptRequired=false)
|
||||
// Ownership is transferred to the caller (VAPI runtime)
|
||||
return new CWinsockConnection(s, /*acceptRequired*/ false);
|
||||
m_watchdog.AddConnection(connection);
|
||||
return static_cast<IInterfaceAccess*>(connection.get());
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -18,16 +18,14 @@
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include "connection.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Winsock headers are required for SOCKET / AF_UNIX / WSAStartup
|
||||
// NOTE: The actual initialization is done via StartUpWinSock()
|
||||
# include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
@@ -77,40 +75,6 @@ struct CAddrInfo
|
||||
addrinfo* AddressInfo { nullptr };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize WinSock on Windows (idempotent)
|
||||
*
|
||||
* This helper ensures WSAStartup() is called only once in the process
|
||||
*
|
||||
* @return 0 on success, otherwise a WinSock error code
|
||||
*/
|
||||
inline int StartUpWinSock()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static bool isInitialized = false;
|
||||
if (isInitialized)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
WSADATA wsaData {};
|
||||
const int error = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (error != 0)
|
||||
{
|
||||
SDV_LOG_ERROR("WSAStartup failed with error: ", std::to_string(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_INFO("WSAStartup initialized");
|
||||
isInitialized = true;
|
||||
}
|
||||
return error;
|
||||
#else
|
||||
// Non-Windows: nothing to do. Return success for symmetry
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Simple pair of sockets used to connect two child processes
|
||||
*
|
||||
@@ -147,8 +111,8 @@ public:
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
|
||||
DECLARE_OBJECT_CLASS_NAME("WinSocketsChannelControl")
|
||||
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
|
||||
DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
virtual ~CSocketsChannelMgnt() = default;
|
||||
@@ -164,6 +128,11 @@ public:
|
||||
*/
|
||||
virtual void OnShutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy.
|
||||
*/
|
||||
virtual void OnDestroy() override;
|
||||
|
||||
/**
|
||||
* @brief Create an IPC endpoint and return its connection information
|
||||
*
|
||||
@@ -208,29 +177,8 @@ public:
|
||||
*/
|
||||
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
|
||||
|
||||
/**
|
||||
* @brief Called by a CWinsockConnection instance when the server side is closed
|
||||
*
|
||||
* Used to clean up internal registries for a given UDS path
|
||||
*
|
||||
* @param ptr Pointer to the CWinsockConnection instance that was closed
|
||||
*/
|
||||
void OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr);
|
||||
|
||||
private:
|
||||
/// @brief Registry of AF_UNIX server connections keyed by normalized UDS path
|
||||
std::map<std::string, std::shared_ptr<CWinsockConnection>> m_udsServers;
|
||||
|
||||
/**
|
||||
* @brief Set of UDS paths that already returned their server-side
|
||||
* connection once via Access()
|
||||
*
|
||||
* This prevents returning the same server object multiple times
|
||||
*/
|
||||
std::unordered_set<std::string> m_udsServerClaimed;
|
||||
|
||||
/// @brief Mutex protecting m_udsServers and m_udsServerClaimed
|
||||
std::mutex m_udsMtx;
|
||||
CWinSocketsConnectionWatchDog m_watchdog;
|
||||
};
|
||||
|
||||
// SDV object factory macro
|
||||
|
||||
@@ -17,11 +17,92 @@
|
||||
#include <WS2tcpip.h>
|
||||
#include <cstring>
|
||||
#include <chrono>
|
||||
#include <afunix.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::string ExpandEnvVarsLocal(const std::string& in)
|
||||
{
|
||||
if (in.find('%') == std::string::npos)
|
||||
{
|
||||
return in;
|
||||
}
|
||||
|
||||
char buf[4096] = {};
|
||||
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast<DWORD>(sizeof(buf)));
|
||||
if (n > 0 && n < sizeof(buf))
|
||||
{
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
static std::string ClampUdsPathLocal(const std::string& path)
|
||||
{
|
||||
sockaddr_un tmp{};
|
||||
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
|
||||
return path.size() <= kMax ? path : path.substr(0, kMax);
|
||||
}
|
||||
|
||||
static std::string MakeShortWinUdsPathLocal(const std::string& raw)
|
||||
{
|
||||
std::string path = ExpandEnvVarsLocal(raw);
|
||||
const size_t pos = path.find_last_of("/\\");
|
||||
std::string base = (pos == std::string::npos) ? path : path.substr(pos + 1);
|
||||
|
||||
if (base.empty())
|
||||
{
|
||||
base = "sdv.sock";
|
||||
}
|
||||
|
||||
std::string dir = ExpandEnvVarsLocal("%TEMP%\\sdv\\");
|
||||
CreateDirectoryA(dir.c_str(), nullptr);
|
||||
return ClampUdsPathLocal(dir + base);
|
||||
}
|
||||
|
||||
static SOCKET ConnectUnixSocketLocal(const std::string& rawPath, uint32_t totalTimeoutMs, uint32_t retryDelayMs)
|
||||
{
|
||||
const std::string udsPath = MakeShortWinUdsPathLocal(rawPath);
|
||||
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
|
||||
|
||||
const int addrlen = static_cast<int>(offsetof(sockaddr_un, sun_path) + std::strlen(addr.sun_path) + 1);
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(totalTimeoutMs);
|
||||
|
||||
while (true)
|
||||
{
|
||||
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
const int lastError = WSAGetLastError();
|
||||
closesocket(s);
|
||||
|
||||
if (std::chrono::steady_clock::now() >= deadline)
|
||||
{
|
||||
SDV_LOG_ERROR("[AF_UNIX] lazy connect TIMEOUT, last WSA=", lastError, ", path='", udsPath, "'");
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired)
|
||||
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
|
||||
, m_AcceptConnectionRequired(acceptConnectionRequired)
|
||||
, m_CancelWait(false)
|
||||
, m_UdsPath()
|
||||
{
|
||||
// Initialize legacy buffers with zero (kept for potential compatibility)
|
||||
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
|
||||
@@ -41,6 +122,17 @@ CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, b
|
||||
}
|
||||
}
|
||||
|
||||
CWinsockConnection::CWinsockConnection(const std::string& udsPath)
|
||||
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
|
||||
, m_AcceptConnectionRequired(false)
|
||||
, m_UdsPath(udsPath)
|
||||
{
|
||||
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
|
||||
std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0');
|
||||
m_ListenSocket = INVALID_SOCKET;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/*CWinsockConnection::CWinsockConnection()
|
||||
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
|
||||
, m_AcceptConnectionRequired(false)
|
||||
@@ -65,28 +157,47 @@ CWinsockConnection::~CWinsockConnection()
|
||||
}
|
||||
}
|
||||
|
||||
void CWinsockConnection::SetWatchDogRemoveCallback(std::function<void(const void*)> callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
|
||||
m_WatchdogRemoveCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void CWinsockConnection::SetConnectState(sdv::ipc::EConnectState state)
|
||||
{
|
||||
sdv::ipc::IConnectEventCallback* pEventLocal = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_ConnectionState.store(state, std::memory_order_release);
|
||||
|
||||
// Main receiver has priority over registered state listener
|
||||
if (m_pMainEvent)
|
||||
{
|
||||
pEventLocal = m_pMainEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEventLocal = m_pRegisteredEvent;
|
||||
}
|
||||
}
|
||||
|
||||
// Wake up any waiter
|
||||
m_CvConnect.notify_all();
|
||||
|
||||
// Notify event callback if registered
|
||||
if (m_pEvent)
|
||||
if (pEventLocal)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_pEvent->SetConnectState(state);
|
||||
pEventLocal->SetConnectState(state);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore callbacks throwing exceptions
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int32_t CWinsockConnection::Send(const char* data, int32_t dataLength)
|
||||
@@ -177,7 +288,7 @@ bool CWinsockConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>
|
||||
(kMaxUdsPacketSize - static_cast<uint32_t>(sizeof(SFragmentedMsgHdr))) :
|
||||
0;
|
||||
|
||||
if (maxPayloadFrag == 0)
|
||||
if constexpr (maxPayloadFrag == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -318,9 +429,18 @@ SOCKET CWinsockConnection::AcceptConnection()
|
||||
tv.tv_usec = 50 * 1000; // 50 ms
|
||||
|
||||
const int sr = ::select(0, &rfds, nullptr, nullptr, &tv);
|
||||
|
||||
if (sr == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", WSAGetLastError());
|
||||
const int err = WSAGetLastError();
|
||||
|
||||
if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_INFO("[AF_UNIX] select(listen) canceled during shutdown, WSA=", err);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", err);
|
||||
SetConnectState(sdv::ipc::EConnectState::connection_error);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
@@ -331,9 +451,17 @@ SOCKET CWinsockConnection::AcceptConnection()
|
||||
}
|
||||
|
||||
SOCKET c = ::accept(m_ListenSocket, nullptr, nullptr);
|
||||
|
||||
if (c == INVALID_SOCKET)
|
||||
{
|
||||
const int err = WSAGetLastError();
|
||||
|
||||
if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET || err == WSAENOTSOCK)
|
||||
{
|
||||
SDV_LOG_INFO("[AF_UNIX] accept canceled during shutdown, WSA=", err);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (err == WSAEINTR || err == WSAEWOULDBLOCK)
|
||||
{
|
||||
continue;
|
||||
@@ -355,23 +483,53 @@ SOCKET CWinsockConnection::AcceptConnection()
|
||||
|
||||
bool CWinsockConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
|
||||
{
|
||||
auto expectedState = m_ConnectionState.load(std::memory_order_acquire);
|
||||
while (true)
|
||||
{
|
||||
if (expectedState == sdv::ipc::EConnectState::connected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expectedState == sdv::ipc::EConnectState::initializing)
|
||||
{
|
||||
SDV_LOG_WARNING("[AF_UNIX] AsyncConnect ignored: connect worker already running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ConnectionState.compare_exchange_weak(
|
||||
expectedState,
|
||||
sdv::ipc::EConnectState::initializing,
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Store callbacks
|
||||
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
|
||||
m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
auto* pReceiverIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
|
||||
auto* pEventIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_pReceiver = pReceiverIfc;
|
||||
m_pMainEvent = pEventIfc;
|
||||
}
|
||||
|
||||
// Reset stop flags
|
||||
m_StopReceiveThread.store(false);
|
||||
m_StopConnectThread.store(false);
|
||||
m_CancelWait.store(false);
|
||||
|
||||
// Join old threads if any
|
||||
// Join old workers only after they completed.
|
||||
// Joining an active worker here can block forever when a previous connect is still pending.
|
||||
if (m_ReceiveThread.joinable())
|
||||
m_ReceiveThread.join();
|
||||
if (m_ConnectThread.joinable())
|
||||
m_ConnectThread.join();
|
||||
|
||||
// Start the connect worker
|
||||
m_ConnectThread = std::thread(&CWinsockConnection::ConnectWorker, this);
|
||||
m_ConnectThread = sdv::core::secure_thread(&CWinsockConnection::ConnectWorker, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -428,11 +586,16 @@ void CWinsockConnection::Disconnect()
|
||||
|
||||
uint64_t CWinsockConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
|
||||
{
|
||||
auto* pEventIfc = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
|
||||
// Extract IConnectEventCallback interface
|
||||
m_pEvent = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_pRegisteredEvent = pEventIfc;
|
||||
}
|
||||
|
||||
// Only one callback is supported; cookie 1 = valid
|
||||
return (m_pEvent != nullptr) ? 1ULL : 0ULL;
|
||||
return (pEventIfc != nullptr) ? 1ULL : 0ULL;
|
||||
}
|
||||
|
||||
void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
|
||||
@@ -440,7 +603,9 @@ void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
|
||||
// Only one callback supported -> cookie value is 1
|
||||
if (uiCookie == 1ULL)
|
||||
{
|
||||
m_pEvent = nullptr;
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_pMainEvent = nullptr;
|
||||
m_pRegisteredEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,11 +616,38 @@ sdv::ipc::EConnectState CWinsockConnection::GetConnectState() const
|
||||
|
||||
void CWinsockConnection::DestroyObject()
|
||||
{
|
||||
m_StopReceiveThread = true;
|
||||
m_StopConnectThread = true;
|
||||
|
||||
StopThreadsAndCloseSockets();
|
||||
m_ConnectionState = sdv::ipc::EConnectState::disconnected;
|
||||
bool expected = false;
|
||||
if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_StopReceiveThread.store(true);
|
||||
m_StopConnectThread.store(true);
|
||||
|
||||
// Optional: notify terminating before releasing callbacks
|
||||
SetConnectState(sdv::ipc::EConnectState::terminating);
|
||||
|
||||
Disconnect();
|
||||
|
||||
// Release callbacks only during final teardown
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_pReceiver = nullptr;
|
||||
m_pMainEvent = nullptr;
|
||||
m_pRegisteredEvent = nullptr;
|
||||
}
|
||||
|
||||
std::function<void(const void*)> removeCallback;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
|
||||
removeCallback = std::move(m_WatchdogRemoveCallback);
|
||||
}
|
||||
if (removeCallback)
|
||||
{
|
||||
removeCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
void CWinsockConnection::ConnectWorker()
|
||||
@@ -482,16 +674,6 @@ void CWinsockConnection::ConnectWorker()
|
||||
|
||||
if (c == INVALID_SOCKET)
|
||||
{
|
||||
if (m_pEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_pEvent->SetConnectState(m_ConnectionState);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -505,8 +687,22 @@ void CWinsockConnection::ConnectWorker()
|
||||
// CLIENT SIDE
|
||||
if (m_ConnectionSocket == INVALID_SOCKET)
|
||||
{
|
||||
SetConnectState(sdv::ipc::EConnectState::connection_error);
|
||||
return;
|
||||
SetConnectState(sdv::ipc::EConnectState::initializing);
|
||||
|
||||
if (m_UdsPath.empty())
|
||||
{
|
||||
SetConnectState(sdv::ipc::EConnectState::connection_error);
|
||||
return;
|
||||
}
|
||||
|
||||
SOCKET s = ConnectUnixSocketLocal(m_UdsPath, 5000, 50);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
SetConnectState(sdv::ipc::EConnectState::connection_error);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ConnectionSocket = s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +724,7 @@ void CWinsockConnection::StartReceiveThread_Unsafe()
|
||||
}
|
||||
|
||||
m_StopReceiveThread.store(false);
|
||||
m_ReceiveThread = std::thread(&CWinsockConnection::ReceiveMessages, this);
|
||||
m_ReceiveThread = sdv::core::secure_thread(&CWinsockConnection::ReceiveMessages, this);
|
||||
}
|
||||
|
||||
void CWinsockConnection::StopThreadsAndCloseSockets()
|
||||
@@ -583,6 +779,7 @@ void CWinsockConnection::StopThreadsAndCloseSockets()
|
||||
SDV_LOG_INFO("[AF_UNIX] StopThreadsAndCloseSockets: closing listen=%llu conn=%llu",
|
||||
static_cast<uint64_t>(l),
|
||||
static_cast<uint64_t>(s));
|
||||
|
||||
}
|
||||
|
||||
bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
|
||||
@@ -608,7 +805,7 @@ bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
|
||||
continue;
|
||||
}
|
||||
|
||||
SDV_LOG_WARNING("[UDS][RX] recv() error: ", std::strerror(err));
|
||||
SDV_LOG_WARNING("[UDS][RX] recv() FAIL, WSA=", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -742,8 +939,13 @@ bool CWinsockConnection::ReadDataChunk(const CMessage& message, uint32_t offset,
|
||||
#if ENABLE_DECOUPLING > 0
|
||||
// optional queueing path...
|
||||
#else
|
||||
if (m_pReceiver)
|
||||
m_pReceiver->ReceiveData(dataCtx.seqDataChunks);
|
||||
sdv::ipc::IDataReceiveCallback* pReceiverLocal = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
pReceiverLocal = m_pReceiver;
|
||||
}
|
||||
if (pReceiverLocal)
|
||||
pReceiverLocal->ReceiveData(dataCtx.seqDataChunks);
|
||||
dataCtx = SDataContext(); // reset context
|
||||
#endif
|
||||
break;
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <WinSock2.h>
|
||||
@@ -70,11 +72,12 @@ struct SMsgHeader
|
||||
* - sdv::ipc::IConnect : async connect / wait / state / events
|
||||
* - sdv::IObjectDestroy : explicit destruction hook for SDV runtime
|
||||
*/
|
||||
class CWinsockConnection
|
||||
: public sdv::IInterfaceAccess
|
||||
, public sdv::ipc::IDataSend
|
||||
, public sdv::ipc::IConnect
|
||||
, public sdv::IObjectDestroy
|
||||
class CWinsockConnection :
|
||||
//public sdv::CSharedLifetimeControlImpl<CWinsockConnection>, public sdv::ipc::IDataSend, public sdv::ipc::IConnect
|
||||
public sdv::IInterfaceAccess,
|
||||
public sdv::ipc::IDataSend,
|
||||
public sdv::ipc::IConnect,
|
||||
public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -89,6 +92,12 @@ public:
|
||||
*/
|
||||
CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired);
|
||||
|
||||
/**
|
||||
* @brief Create a client endpoint that connects lazily in AsyncConnect.
|
||||
* @param[in] udsPath Normalized path for AF_UNIX connection.
|
||||
*/
|
||||
explicit CWinsockConnection(const std::string& udsPath);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor needed for "delete this;"
|
||||
*/
|
||||
@@ -98,6 +107,7 @@ public:
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
//SDV_INTERFACE_CHAIN_BASE(sdv::CSharedLifetimeControlImpl<CWinsockConnection>)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
@@ -188,23 +198,28 @@ public:
|
||||
*/
|
||||
void DestroyObject() override;
|
||||
|
||||
/** @brief Register callback used to remove this connection from manager watchdog storage. */
|
||||
void SetWatchDogRemoveCallback(std::function<void(const void*)> callback);
|
||||
|
||||
private:
|
||||
|
||||
std::mutex m_MtxConnect;
|
||||
std::condition_variable m_CvConnect;
|
||||
std::thread m_ReceiveThread; ///< Thread which receives data from the socket
|
||||
std::thread m_ConnectThread;
|
||||
sdv::core::secure_thread m_ReceiveThread; ///< Thread which receives data from the socket
|
||||
sdv::core::secure_thread m_ConnectThread;
|
||||
std::atomic<bool> m_StopReceiveThread{false}; ///< bool variable to stop thread
|
||||
std::atomic<bool> m_StopConnectThread{false};
|
||||
std::atomic<sdv::ipc::EConnectState> m_ConnectionState; ///< the state of the connection
|
||||
|
||||
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages if available
|
||||
sdv::ipc::IConnectEventCallback* m_pEvent = nullptr; ///< Event receiver
|
||||
sdv::ipc::IDataReceiveCallback* m_pReceiver{nullptr}; ///< Receiver to pass the messages if available
|
||||
sdv::ipc::IConnectEventCallback* m_pMainEvent{nullptr}; ///< Event receiver
|
||||
sdv::ipc::IConnectEventCallback* m_pRegisteredEvent{nullptr};
|
||||
bool m_AcceptConnectionRequired; ///< if true connection has to be accepted before receive thread can be started
|
||||
mutable std::recursive_mutex m_SendMutex; ///< Synchronize all packages to be send
|
||||
|
||||
SOCKET m_ListenSocket{INVALID_SOCKET}; ///< Server-side listening socket
|
||||
SOCKET m_ConnectionSocket{INVALID_SOCKET}; ///< Active connected socket (client <-> server)
|
||||
std::string m_UdsPath; ///< Client-side AF_UNIX path (lazy connect)
|
||||
|
||||
static constexpr uint32_t m_SendMessageSize{ 1024 }; ///< size for the message to be send
|
||||
static constexpr uint32_t m_SendBufferSize = sizeof(SMsgHeader) + m_SendMessageSize; ///< Initial size of the send buffer
|
||||
@@ -213,6 +228,9 @@ private:
|
||||
uint32_t m_ReceiveBufferLength = sizeof(SMsgHeader); ///< receive buffer length
|
||||
|
||||
std::atomic<bool> m_CancelWait{false};
|
||||
std::mutex m_WatchdogMtx;
|
||||
std::function<void(const void*)> m_WatchdogRemoveCallback;
|
||||
std::atomic<bool> m_DestroyObjectCalled{false};
|
||||
|
||||
/// @brief Server accept loop / client connect confirmation
|
||||
void ConnectWorker();
|
||||
|
||||
58
sdv_services/uds_win_sockets/watchdog.cpp
Normal file
58
sdv_services/uds_win_sockets/watchdog.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/********************************************************************************
|
||||
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Apache License Version 2.0 which is available at
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "watchdog.h"
|
||||
|
||||
void CWinSocketsConnectionWatchDog::AddConnectionImpl(const std::shared_ptr<void>& connection)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
m_Connections[connection.get()] = connection;
|
||||
}
|
||||
|
||||
void CWinSocketsConnectionWatchDog::RemoveConnection(const void* connection)
|
||||
{
|
||||
if (!connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<void> removed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
auto it = m_Connections.find(connection);
|
||||
if (it == m_Connections.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
removed = std::move(it->second);
|
||||
m_Connections.erase(it);
|
||||
}
|
||||
|
||||
removed.reset();
|
||||
}
|
||||
|
||||
void CWinSocketsConnectionWatchDog::Clear()
|
||||
{
|
||||
std::map<const void*, std::shared_ptr<void>> localConnections;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_Mutex);
|
||||
localConnections.swap(m_Connections);
|
||||
}
|
||||
|
||||
localConnections.clear();
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
47
sdv_services/uds_win_sockets/watchdog.h
Normal file
47
sdv_services/uds_win_sockets/watchdog.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/********************************************************************************
|
||||
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Apache License Version 2.0 which is available at
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Contributors:
|
||||
* Denisa Ros - initial API and implementation
|
||||
********************************************************************************/
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef UDS_WIN_SOCKETS_WATCHDOG_H
|
||||
#define UDS_WIN_SOCKETS_WATCHDOG_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class CWinSocketsConnectionWatchDog
|
||||
{
|
||||
public:
|
||||
template<typename T>
|
||||
void AddConnection(const std::shared_ptr<T>& connection)
|
||||
{
|
||||
if (!connection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddConnectionImpl(std::static_pointer_cast<void>(connection));
|
||||
}
|
||||
|
||||
void RemoveConnection(const void* connection);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
void AddConnectionImpl(const std::shared_ptr<void>& connection);
|
||||
|
||||
std::mutex m_Mutex;
|
||||
std::map<const void*, std::shared_ptr<void>> m_Connections;
|
||||
};
|
||||
|
||||
#endif // UDS_WIN_SOCKETS_WATCHDOG_H
|
||||
#endif // _WIN32
|
||||
Reference in New Issue
Block a user