connection between 2 systems and bug fixes (#14)

This commit is contained in:
tompzf
2026-07-03 14:53:48 +02:00
committed by GitHub
parent 0fb5660d27
commit 46842200f1
284 changed files with 35200 additions and 8265 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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();

View 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

View 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