mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-04-20 11:18:16 +00:00
Update sdv_packager (#6)
This commit is contained in:
36
sdv_services/uds_win_sockets/CMakeLists.txt
Normal file
36
sdv_services/uds_win_sockets/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
#*******************************************************************************
|
||||
# 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
|
||||
#*******************************************************************************
|
||||
|
||||
if(WIN32)
|
||||
# Define project
|
||||
project(uds_win_sockets VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(uds_win_sockets SHARED
|
||||
"channel_mgnt.h"
|
||||
"channel_mgnt.cpp"
|
||||
"connection.h"
|
||||
"connection.cpp")
|
||||
|
||||
target_link_libraries(uds_win_sockets ${CMAKE_THREAD_LIBS_INIT} Ws2_32.lib)
|
||||
|
||||
target_link_options(uds_win_sockets PRIVATE)
|
||||
target_include_directories(uds_win_sockets PRIVATE ./include/)
|
||||
|
||||
set_target_properties(uds_win_sockets PROPERTIES PREFIX "")
|
||||
set_target_properties(uds_win_sockets PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(uds_win_sockets CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} uds_win_sockets PARENT_SCOPE)
|
||||
|
||||
endif()
|
||||
485
sdv_services/uds_win_sockets/channel_mgnt.cpp
Normal file
485
sdv_services/uds_win_sockets/channel_mgnt.cpp
Normal file
@@ -0,0 +1,485 @@
|
||||
/********************************************************************************
|
||||
* 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
|
||||
********************************************************************************/
|
||||
|
||||
#include "channel_mgnt.h"
|
||||
#include "connection.h"
|
||||
|
||||
#include "../../global/base64.h"
|
||||
#include <support/toml.h>
|
||||
#include <interfaces/process.h>
|
||||
|
||||
#include <future>
|
||||
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <afunix.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
|
||||
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)
|
||||
{
|
||||
constexpr const char* protoKey = "proto=uds";
|
||||
constexpr const char* pathKey = "path=";
|
||||
|
||||
// Must contain "proto=uds" to be considered UDS
|
||||
if (cs.find(protoKey) == std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Expand Windows environment variables in the form %VAR%.
|
||||
*
|
||||
* Example:
|
||||
* "%TEMP%\\sdv\\vapi.sock" → "C:\\Users\\...\\AppData\\Local\\Temp\\sdv\\vapi.sock"
|
||||
*
|
||||
* If environment expansion fails, the original string is returned unchanged
|
||||
*
|
||||
* @param[in] in Input string that may contain %VAR% tokens
|
||||
*
|
||||
* @return Expanded string, or the original input on failure
|
||||
*/
|
||||
static std::string ExpandEnvVars(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clamp a Unix Domain Socket pathname to the maximum allowed size
|
||||
*
|
||||
* Windows AF_UNIX pathname sockets require that `sun_path` fits in
|
||||
* `sizeof(sockaddr_un.sun_path) - 1` bytes (including terminating NUL)
|
||||
*
|
||||
* If the input exceeds this limit, it is truncated
|
||||
*
|
||||
* @param[in] p The original pathname
|
||||
*
|
||||
* @return A safe pathname guaranteed to fit into sun_path
|
||||
*/
|
||||
static std::string ClampUdsPath(const std::string& p)
|
||||
{
|
||||
SOCKADDR_UN tmp{};
|
||||
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
|
||||
|
||||
if (p.size() <= kMax)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
return p.substr(0, kMax);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize a UDS path for display/logging purposes
|
||||
*
|
||||
* Extracts the basename from an input path and clamps it to the
|
||||
* AF_UNIX pathname size limit. This is *not* the final path used for
|
||||
* the socket bind/connection — it is intended only for user-visible logs.
|
||||
*
|
||||
* Example:
|
||||
* Input: "C:/Users/.../very/long/path/vapi.sock"
|
||||
* Output: "vapi.sock"
|
||||
*
|
||||
* @param[in] raw Raw input path (may contain directories or %VAR%)
|
||||
*
|
||||
* @return Normalized/clamped basename suitable for logging
|
||||
*/
|
||||
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);
|
||||
|
||||
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`
|
||||
*
|
||||
* AF_UNIX pathname sockets on Windows require short, absolute paths
|
||||
* under the OS temporary directory.
|
||||
*
|
||||
* Algorithm:
|
||||
* 1. Expand environment variables
|
||||
* 2. Extract the basename
|
||||
* 3. Place it under "%TEMP%\\sdv\\"
|
||||
* 4. Ensure the directory exists
|
||||
* 5. Clamp to AF_UNIX size limits
|
||||
*
|
||||
* Example:
|
||||
* raw: "%TEMP%\\sdv\\vapi.sock"
|
||||
* -> "<expanded temp>\\sdv\\vapi.sock"
|
||||
*
|
||||
* @param[in] raw Raw input path (may contain %VAR%)
|
||||
*
|
||||
* @return Fully expanded, clamped, absolute path suitable for bind()/connect()
|
||||
*/
|
||||
static std::string MakeShortWinUdsPath(const std::string& raw)
|
||||
{
|
||||
// Expand raw first (may already contain environment variables)
|
||||
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);
|
||||
|
||||
if (base.empty())
|
||||
{
|
||||
base = "sdv.sock";
|
||||
}
|
||||
|
||||
// Use %TEMP%\sdv\ as a base directory
|
||||
std::string dir = ExpandEnvVars("%TEMP%\\sdv\\");
|
||||
CreateDirectoryA(dir.c_str(), nullptr); // OK if already exists
|
||||
|
||||
const std::string full = dir + base;
|
||||
|
||||
// Ensure it fits into sun_path
|
||||
return ClampUdsPath(full);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a listening AF_UNIX socket on Windows
|
||||
*
|
||||
* This creates a WinSock AF_UNIX stream socket, constructs a pathname
|
||||
* using MakeShortWinUdsPath(), removes any stale socket file, binds,
|
||||
* and marks it for listening.
|
||||
*
|
||||
* It logs all success/error cases for diagnostic purposes
|
||||
*
|
||||
* @param[in] rawPath Raw path string from configuration/connect-string
|
||||
*
|
||||
* @return A valid SOCKET on success, or INVALID_SOCKET on failure
|
||||
*/
|
||||
static SOCKET CreateUnixListenSocket(const std::string& rawPath)
|
||||
{
|
||||
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (listen), WSA=", WSAGetLastError());
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
// Short absolute path, common for server and clients
|
||||
std::string udsPath = MakeShortWinUdsPath(rawPath);
|
||||
|
||||
SOCKADDR_UN addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
|
||||
|
||||
// Effective length: offsetof + strlen + 1 for "pathname" AF_UNIX
|
||||
const int addrlen = static_cast<int>(
|
||||
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1
|
||||
);
|
||||
|
||||
// Remove any leftover file for that path
|
||||
::remove(udsPath.c_str());
|
||||
|
||||
if (bind(s, reinterpret_cast<sockaddr*>(&addr), addrlen) == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR(
|
||||
"[AF_UNIX] bind FAIL (pathname), WSA=",
|
||||
WSAGetLastError(), ", path='", udsPath, "'"
|
||||
);
|
||||
closesocket(s);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (listen(s, SOMAXCONN) == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR(
|
||||
"[AF_UNIX] listen FAIL, WSA=",
|
||||
WSAGetLastError(), ", path='", udsPath, "'"
|
||||
);
|
||||
closesocket(s);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] bind OK (pathname), listen OK, path='", udsPath, "'");
|
||||
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)
|
||||
{
|
||||
const std::string udsPath = MakeShortWinUdsPath(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);
|
||||
|
||||
int lastError = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
lastError = WSAGetLastError();
|
||||
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", lastError);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
|
||||
{
|
||||
SDV_LOG_INFO("[AF_UNIX] connect OK (pathname), path='", udsPath, "'");
|
||||
return s;
|
||||
}
|
||||
|
||||
lastError = WSAGetLastError();
|
||||
closesocket(s);
|
||||
|
||||
if (std::chrono::steady_clock::now() >= deadline)
|
||||
{
|
||||
SDV_LOG_ERROR(
|
||||
"[AF_UNIX] connect TIMEOUT (pathname), last WSA=",
|
||||
lastError, ", 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, CConnection* 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);
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::OnShutdown()
|
||||
{}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
|
||||
SDV_LOG_INFO("[AF_UNIX] endpoint udsPath=", udsPath);
|
||||
|
||||
SOCKET listenSocket = CreateUnixListenSocket(udsPath);
|
||||
if (listenSocket == INVALID_SOCKET)
|
||||
{
|
||||
// Endpoint creation failed
|
||||
return {};
|
||||
}
|
||||
|
||||
// Server-side CConnection, it will accept() a client on first use
|
||||
auto server = std::make_shared<CConnection>(listenSocket, /*acceptRequired*/ true);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_udsMtx);
|
||||
m_udsServers[udsPath] = server;
|
||||
m_udsServerClaimed.erase(udsPath);
|
||||
}
|
||||
|
||||
sdv::ipc::SChannelEndpoint ep{};
|
||||
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
|
||||
ep.ssConnectString = "proto=uds;path=" + udsPath + ";";
|
||||
|
||||
return ep;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& cs)
|
||||
{
|
||||
// Ensure WinSock is initialized
|
||||
if (StartUpWinSock() != 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string udsRaw;
|
||||
if (!ParseUdsPath(cs, udsRaw))
|
||||
{
|
||||
// Not a UDS connect string
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (udsRaw.empty())
|
||||
{
|
||||
udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock";
|
||||
}
|
||||
|
||||
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
|
||||
SDV_LOG_INFO("[AF_UNIX] Access udsPath=", udsPath);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_udsMtx);
|
||||
|
||||
auto it = m_udsServers.find(udsPath);
|
||||
if (it != m_udsServers.end() && it->second != nullptr)
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// CLIENT: create a socket connected to the same udsPath
|
||||
SOCKET s = ConnectUnixSocket(udsPath,
|
||||
/*totalTimeoutMs*/ 5000,
|
||||
/*retryDelayMs*/ 50);
|
||||
|
||||
if (s == INVALID_SOCKET)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] Access -> CREATE CLIENT for ", udsPath);
|
||||
|
||||
// Client-side connection object (acceptRequired=false)
|
||||
// Ownership is transferred to the caller (VAPI runtime)
|
||||
return new CConnection(s, /*acceptRequired*/ false);
|
||||
}
|
||||
237
sdv_services/uds_win_sockets/channel_mgnt.h
Normal file
237
sdv_services/uds_win_sockets/channel_mgnt.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/********************************************************************************
|
||||
* 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
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef CHANNEL_MGNT_H
|
||||
#define CHANNEL_MGNT_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include "connection.h"
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* @brief RAII wrapper for addrinfo returned by getaddrinfo().
|
||||
*
|
||||
* This structure owns the addrinfo list and frees it automatically in the
|
||||
* destructor. Copy and move operations are disabled to avoid double-free.
|
||||
*/
|
||||
struct CAddrInfo
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAddrInfo() = default;
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAddrInfo(const CAddrInfo&) = delete;
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
*/
|
||||
CAddrInfo& operator=(const CAddrInfo&) = delete;
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] other Reference to the structure to move
|
||||
*/
|
||||
CAddrInfo(CAddrInfo&& other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator.
|
||||
* @param[in] other Reference to the structure to move
|
||||
* @return Returns reference to CAddrInfo structure
|
||||
*/
|
||||
CAddrInfo& operator=(CAddrInfo&& other) = delete;
|
||||
|
||||
~CAddrInfo()
|
||||
{
|
||||
if (AddressInfo != nullptr)
|
||||
{
|
||||
freeaddrinfo(AddressInfo);
|
||||
AddressInfo = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Pointer to the addrinfo list returned by getaddrinfo()
|
||||
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
|
||||
*
|
||||
* This is reserved for potential future use (e.g. TCP/IP or in-proc socket pairs)
|
||||
*/
|
||||
struct SocketConnection
|
||||
{
|
||||
SOCKET From { INVALID_SOCKET }; ///< Socket used by the "from" side (child process)
|
||||
SOCKET To { INVALID_SOCKET }; ///< Socket used by the "to" side (child process)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief IPC channel management class for socket-based communication on Windows
|
||||
*
|
||||
* This implementation uses AF_UNIX (Unix domain sockets) provided by WinSock
|
||||
* on Windows to establish a local IPC channel between VAPI processes
|
||||
*
|
||||
* The class:
|
||||
* - implements IObjectControl (lifecycle, operation mode)
|
||||
* - implements ipc::ICreateEndpoint (endpoint creation)
|
||||
* - implements ipc::IChannelAccess (client access via connect string)
|
||||
*/
|
||||
class CSocketsChannelMgnt
|
||||
: public sdv::CSdvObject
|
||||
, public sdv::ipc::ICreateEndpoint
|
||||
, public sdv::ipc::IChannelAccess
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IChannelAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::ICreateEndpoint)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// 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_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
|
||||
* @return Returns 'true' when the initialization was successful, 'false' when not.
|
||||
*/
|
||||
virtual bool OnInitialize() override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
|
||||
*/
|
||||
virtual void OnShutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Create an IPC endpoint and return its connection information
|
||||
*
|
||||
* Overload of sdv::ipc::ICreateEndpoint::CreateEndpoint
|
||||
*
|
||||
* The endpoint is implemented as a local AF_UNIX server socket
|
||||
* (listen socket) on Windows. The connect string has the format:
|
||||
*
|
||||
* proto=uds;path=<udsPath>
|
||||
*
|
||||
* If no configuration is provided or no path is specified, a default
|
||||
* path is used (under %LOCALAPPDATA%/sdv)
|
||||
*
|
||||
* Example configuration:
|
||||
* @code
|
||||
* [IpcChannel]
|
||||
* Interface = "127.0.0.1"
|
||||
* Port = 2000
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] ssChannelConfig Optional channel-specific configuration string.
|
||||
* @return SChannelEndpoint structure containing the connection object and
|
||||
* the connect string
|
||||
*/
|
||||
sdv::ipc::SChannelEndpoint CreateEndpoint(/*in*/ const sdv::u8string& ssChannelConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Create or access a connection object from a connection string
|
||||
*
|
||||
* Overload of sdv::ipc::IChannelAccess::Access
|
||||
*
|
||||
* The connect string is expected to contain:
|
||||
* proto=uds;path=<udsPath>
|
||||
*
|
||||
* For the first Access() call with a given path, the server-side
|
||||
* connection object created by CreateEndpoint() can be returned.
|
||||
* Subsequent calls will create client-side connections
|
||||
*
|
||||
* @param[in] ssConnectString String containing the channel connection parameters
|
||||
* @return Pointer to IInterfaceAccess interface of the connection object or
|
||||
* nullptr when the object cannot be created
|
||||
*/
|
||||
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
|
||||
|
||||
/**
|
||||
* @brief Called by a CConnection instance when the server side is closed
|
||||
*
|
||||
* Used to clean up internal registries for a given UDS path
|
||||
*
|
||||
* @param udsPath Path to the UDS endpoint
|
||||
* @param ptr Pointer to the CConnection instance that was closed
|
||||
*/
|
||||
void OnServerClosed(const std::string& udsPath, CConnection* ptr);
|
||||
|
||||
private:
|
||||
/// @brief Registry of AF_UNIX server connections keyed by normalized UDS path
|
||||
std::map<std::string, std::shared_ptr<CConnection>> 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;
|
||||
};
|
||||
|
||||
// SDV object factory macro
|
||||
DEFINE_SDV_OBJECT(CSocketsChannelMgnt)
|
||||
|
||||
#endif // ! defined CHANNEL_MGNT_H
|
||||
952
sdv_services/uds_win_sockets/connection.cpp
Normal file
952
sdv_services/uds_win_sockets/connection.cpp
Normal file
@@ -0,0 +1,952 @@
|
||||
/********************************************************************************
|
||||
* 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
|
||||
********************************************************************************/
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include <numeric>
|
||||
#include <WS2tcpip.h>
|
||||
#include <cstring>
|
||||
#include <chrono>
|
||||
|
||||
CConnection::CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired)
|
||||
: m_ConnectionStatus(sdv::ipc::EConnectStatus::uninitialized)
|
||||
, m_AcceptConnectionRequired(acceptConnectionRequired)
|
||||
, m_CancelWait(false)
|
||||
{
|
||||
// Initialize legacy buffers with zero (kept for potential compatibility)
|
||||
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
|
||||
std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0');
|
||||
|
||||
if (m_AcceptConnectionRequired)
|
||||
{
|
||||
// Server side: we own a listening socket, active socket is not yet accepted
|
||||
m_ListenSocket = preconfiguredSocket;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Client side: we already have a connected socket
|
||||
m_ListenSocket = INVALID_SOCKET;
|
||||
m_ConnectionSocket = preconfiguredSocket;
|
||||
}
|
||||
}
|
||||
|
||||
CConnection::~CConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
StopThreadsAndCloseSockets();
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Destructors must not throw
|
||||
}
|
||||
}
|
||||
|
||||
void CConnection::SetStatus(sdv::ipc::EConnectStatus status)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_MtxConnect);
|
||||
m_ConnectionStatus.store(status, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Wake up any waiter
|
||||
m_CvConnect.notify_all();
|
||||
|
||||
// Notify event callback if registered
|
||||
if (m_pEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_pEvent->SetStatus(status);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore callbacks throwing exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t CConnection::Send(const char* data, int32_t dataLength)
|
||||
{
|
||||
int32_t total = 0;
|
||||
|
||||
while (total < dataLength)
|
||||
{
|
||||
const int32_t n = ::send(m_ConnectionSocket, data + total, dataLength - total, 0);
|
||||
if (n == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("send failed with error: ", std::to_string(WSAGetLastError()));
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
return (total > 0) ? total : SOCKET_ERROR;
|
||||
}
|
||||
total += n;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
int CConnection::SendExact(const char* data, int len)
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
while (total < len)
|
||||
{
|
||||
const int n = ::send(m_ConnectionSocket, data + total, len - total, 0);
|
||||
if (n <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
total += n;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
|
||||
{
|
||||
// Must be connected and have a valid socket
|
||||
if (m_ConnectionStatus != sdv::ipc::EConnectStatus::connected ||
|
||||
m_ConnectionSocket == INVALID_SOCKET)
|
||||
{
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lk(m_SendMutex);
|
||||
|
||||
// Build SDV length table
|
||||
sdv::sequence<const sdv::pointer<uint8_t>*> seqTemp;
|
||||
sdv::pointer<uint8_t> table;
|
||||
|
||||
const uint32_t nChunks = static_cast<uint32_t>(seqData.size());
|
||||
const uint32_t tableBytes = (nChunks + 1u) * sizeof(uint32_t);
|
||||
|
||||
table.resize(tableBytes);
|
||||
|
||||
// First uint32_t = chunk count
|
||||
std::memcpy(table.get(), &nChunks, sizeof(uint32_t));
|
||||
|
||||
uint32_t offset = sizeof(uint32_t);
|
||||
uint64_t required = sizeof(uint32_t);
|
||||
|
||||
// Next are n chunk sizes
|
||||
for (auto& buf : seqData)
|
||||
{
|
||||
const uint32_t len = static_cast<uint32_t>(buf.size());
|
||||
std::memcpy(table.get() + offset, &len, sizeof(uint32_t));
|
||||
offset += sizeof(uint32_t);
|
||||
required += sizeof(uint32_t);
|
||||
required += len;
|
||||
seqTemp.push_back(&buf);
|
||||
}
|
||||
|
||||
// Prepend table as the first "chunk"
|
||||
seqTemp.insert(seqTemp.begin(), &table);
|
||||
|
||||
const uint32_t maxPayloadData =
|
||||
(kMaxUdsPacketSize > sizeof(SMsgHdr)) ?
|
||||
(kMaxUdsPacketSize - static_cast<uint32_t>(sizeof(SMsgHdr))) :
|
||||
0;
|
||||
|
||||
const uint32_t maxPayloadFrag =
|
||||
(kMaxUdsPacketSize > sizeof(SFragmentedMsgHdr)) ?
|
||||
(kMaxUdsPacketSize - static_cast<uint32_t>(sizeof(SFragmentedMsgHdr))) :
|
||||
0;
|
||||
|
||||
if (maxPayloadFrag == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Single-frame vs. fragmented
|
||||
const bool fitsSingle = (required <= static_cast<uint64_t>(maxPayloadData));
|
||||
|
||||
auto itChunk = seqTemp.cbegin();
|
||||
size_t pos = 0;
|
||||
|
||||
if (fitsSingle)
|
||||
{
|
||||
const uint32_t payloadBytes = static_cast<uint32_t>(required);
|
||||
const uint32_t totalBytes = payloadBytes + static_cast<uint32_t>(sizeof(SMsgHdr));
|
||||
|
||||
std::vector<uint8_t> frame(totalBytes);
|
||||
uint32_t msgOff = 0;
|
||||
|
||||
// SDV header
|
||||
{
|
||||
auto* hdr = reinterpret_cast<SMsgHdr*>(frame.data());
|
||||
hdr->uiVersion = SDVFrameworkInterfaceVersion;
|
||||
hdr->eType = EMsgType::data;
|
||||
msgOff = static_cast<uint32_t>(sizeof(SMsgHdr));
|
||||
}
|
||||
|
||||
// Copy table + chunks
|
||||
while (itChunk != seqTemp.cend() && msgOff < totalBytes)
|
||||
{
|
||||
const auto& ref = *itChunk;
|
||||
const uint32_t len = static_cast<uint32_t>(ref->size());
|
||||
const uint8_t* src = ref->get();
|
||||
const uint32_t canCopy =
|
||||
std::min<uint32_t>(len - static_cast<uint32_t>(pos), totalBytes - msgOff);
|
||||
|
||||
if (canCopy)
|
||||
{
|
||||
std::memcpy(frame.data() + msgOff, src + pos, canCopy);
|
||||
}
|
||||
|
||||
pos += canCopy;
|
||||
msgOff += canCopy;
|
||||
|
||||
if (pos >= len)
|
||||
{
|
||||
pos = 0;
|
||||
++itChunk;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t packetSize = totalBytes;
|
||||
if (SendExact(reinterpret_cast<char*>(&packetSize), sizeof(packetSize)) < 0)
|
||||
return false;
|
||||
if (SendExact(reinterpret_cast<char*>(frame.data()), totalBytes) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fragmented sending
|
||||
uint32_t sentOffset = 0;
|
||||
|
||||
while (itChunk != seqTemp.cend() && sentOffset < required)
|
||||
{
|
||||
const uint32_t remaining = static_cast<uint32_t>(required - sentOffset);
|
||||
const uint32_t dataBytes = std::min<uint32_t>(remaining, maxPayloadFrag);
|
||||
const uint32_t allocBytes = dataBytes + static_cast<uint32_t>(sizeof(SFragmentedMsgHdr));
|
||||
|
||||
std::vector<uint8_t> frame(allocBytes);
|
||||
uint32_t msgOff = 0;
|
||||
|
||||
// Fragment header
|
||||
{
|
||||
auto* hdr = reinterpret_cast<SFragmentedMsgHdr*>(frame.data());
|
||||
hdr->uiVersion = SDVFrameworkInterfaceVersion;
|
||||
hdr->eType = EMsgType::data_fragment;
|
||||
hdr->uiTotalLength = static_cast<uint32_t>(required);
|
||||
hdr->uiOffset = sentOffset;
|
||||
msgOff = static_cast<uint32_t>(sizeof(SFragmentedMsgHdr));
|
||||
}
|
||||
|
||||
// Copy slice across the sequence
|
||||
uint32_t copied = 0;
|
||||
|
||||
while (itChunk != seqTemp.cend() && copied < dataBytes)
|
||||
{
|
||||
const auto& ref = *itChunk;
|
||||
const uint8_t* src = ref->get();
|
||||
const uint32_t len = static_cast<uint32_t>(ref->size());
|
||||
|
||||
const uint32_t canCopy =
|
||||
std::min<uint32_t>(len - static_cast<uint32_t>(pos), dataBytes - copied);
|
||||
|
||||
if (canCopy)
|
||||
{
|
||||
std::memcpy(frame.data() + msgOff, src + pos, canCopy);
|
||||
}
|
||||
|
||||
pos += canCopy;
|
||||
msgOff += canCopy;
|
||||
copied += canCopy;
|
||||
|
||||
if (pos >= len)
|
||||
{
|
||||
pos = 0;
|
||||
++itChunk;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t packetSize = allocBytes;
|
||||
if (SendExact(reinterpret_cast<char*>(&packetSize), sizeof(packetSize)) < 0)
|
||||
return false;
|
||||
if (SendExact(reinterpret_cast<char*>(frame.data()), allocBytes) < 0)
|
||||
return false;
|
||||
|
||||
sentOffset += copied;
|
||||
}
|
||||
|
||||
return (sentOffset == required);
|
||||
}
|
||||
|
||||
SOCKET CConnection::AcceptConnection()
|
||||
{
|
||||
if (m_ListenSocket == INVALID_SOCKET)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
while (!m_StopConnectThread.load())
|
||||
{
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(m_ListenSocket, &rfds);
|
||||
|
||||
TIMEVAL tv{};
|
||||
tv.tv_sec = 0;
|
||||
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());
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (sr == 0)
|
||||
{
|
||||
continue; // timeout, re-check stop flag
|
||||
}
|
||||
|
||||
SOCKET c = ::accept(m_ListenSocket, nullptr, nullptr);
|
||||
if (c == INVALID_SOCKET)
|
||||
{
|
||||
const int err = WSAGetLastError();
|
||||
if (err == WSAEINTR || err == WSAEWOULDBLOCK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SDV_LOG_ERROR("[AF_UNIX] accept FAIL, WSA=", err);
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] accept OK, socket=", static_cast<uint64_t>(c));
|
||||
return c;
|
||||
}
|
||||
|
||||
SDV_LOG_ERROR("[AF_UNIX] accept canceled (stop flag)");
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
|
||||
{
|
||||
// Store callbacks
|
||||
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
|
||||
m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
|
||||
// Reset stop flags
|
||||
m_StopReceiveThread.store(false);
|
||||
m_StopConnectThread.store(false);
|
||||
m_CancelWait.store(false);
|
||||
|
||||
// Join old threads if any
|
||||
if (m_ReceiveThread.joinable())
|
||||
m_ReceiveThread.join();
|
||||
if (m_ConnectThread.joinable())
|
||||
m_ConnectThread.join();
|
||||
|
||||
// Start the connect worker
|
||||
m_ConnectThread = std::thread(&CConnection::ConnectWorker, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConnection::WaitForConnection(uint32_t uiWaitMs)
|
||||
{
|
||||
if (m_ConnectionStatus.load(std::memory_order_acquire) ==
|
||||
sdv::ipc::EConnectStatus::connected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lk(m_MtxConnect);
|
||||
|
||||
if (uiWaitMs == 0xFFFFFFFFu) // INFINITE
|
||||
{
|
||||
m_CvConnect.wait(
|
||||
lk,
|
||||
[this]
|
||||
{
|
||||
return m_ConnectionStatus.load(std::memory_order_acquire) ==
|
||||
sdv::ipc::EConnectStatus::connected;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uiWaitMs == 0u) // zero wait
|
||||
{
|
||||
return (m_ConnectionStatus.load(std::memory_order_acquire) ==
|
||||
sdv::ipc::EConnectStatus::connected);
|
||||
}
|
||||
|
||||
// finite wait
|
||||
return m_CvConnect.wait_for(
|
||||
lk,
|
||||
std::chrono::milliseconds(uiWaitMs),
|
||||
[this]
|
||||
{
|
||||
return m_ConnectionStatus.load(std::memory_order_acquire) ==
|
||||
sdv::ipc::EConnectStatus::connected;
|
||||
});
|
||||
}
|
||||
|
||||
void CConnection::CancelWait()
|
||||
{
|
||||
m_CancelWait.store(true);
|
||||
}
|
||||
|
||||
void CConnection::Disconnect()
|
||||
{
|
||||
m_CancelWait.store(true);
|
||||
StopThreadsAndCloseSockets();
|
||||
SetStatus(sdv::ipc::EConnectStatus::disconnected);
|
||||
}
|
||||
|
||||
uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
|
||||
{
|
||||
// Extract IConnectEventCallback interface
|
||||
m_pEvent = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
|
||||
// Only one callback is supported; cookie 1 = valid
|
||||
return (m_pEvent != nullptr) ? 1ULL : 0ULL;
|
||||
}
|
||||
|
||||
void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie)
|
||||
{
|
||||
// Only one callback supported -> cookie value is 1
|
||||
if (uiCookie == 1ULL)
|
||||
{
|
||||
m_pEvent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
sdv::ipc::EConnectStatus CConnection::GetStatus() const
|
||||
{
|
||||
return m_ConnectionStatus;
|
||||
}
|
||||
|
||||
void CConnection::DestroyObject()
|
||||
{
|
||||
m_StopReceiveThread = true;
|
||||
m_StopConnectThread = true;
|
||||
|
||||
StopThreadsAndCloseSockets();
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
|
||||
}
|
||||
|
||||
void CConnection::ConnectWorker()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_AcceptConnectionRequired)
|
||||
{
|
||||
// SERVER SIDE
|
||||
if (m_ListenSocket == INVALID_SOCKET)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return;
|
||||
}
|
||||
|
||||
SetStatus(sdv::ipc::EConnectStatus::initializing);
|
||||
SDV_LOG_INFO("[AF_UNIX] Srv ConnectWorker start: listen=%llu",
|
||||
static_cast<uint64_t>(m_ListenSocket));
|
||||
|
||||
SOCKET c = AcceptConnection();
|
||||
SDV_LOG_INFO("[AF_UNIX] Srv AcceptConnection returned: sock=%llu status=%d",
|
||||
static_cast<uint64_t>(c),
|
||||
static_cast<int>(m_ConnectionStatus.load()));
|
||||
|
||||
if (c == INVALID_SOCKET)
|
||||
{
|
||||
if (m_pEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_pEvent->SetStatus(m_ConnectionStatus);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
m_ConnectionSocket = c;
|
||||
SetStatus(sdv::ipc::EConnectStatus::connected);
|
||||
StartReceiveThread_Unsafe();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// CLIENT SIDE
|
||||
if (m_ConnectionSocket == INVALID_SOCKET)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Client side: socket is already connected
|
||||
SetStatus(sdv::ipc::EConnectStatus::connected);
|
||||
StartReceiveThread_Unsafe();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::connection_error);
|
||||
}
|
||||
}
|
||||
|
||||
void CConnection::StartReceiveThread_Unsafe()
|
||||
{
|
||||
if (m_ReceiveThread.joinable())
|
||||
{
|
||||
m_ReceiveThread.join();
|
||||
}
|
||||
|
||||
m_StopReceiveThread.store(false);
|
||||
m_ReceiveThread = std::thread(&CConnection::ReceiveMessages, this);
|
||||
}
|
||||
|
||||
void CConnection::StopThreadsAndCloseSockets()
|
||||
{
|
||||
// Signal stop
|
||||
m_StopReceiveThread.store(true);
|
||||
m_StopConnectThread.store(true);
|
||||
|
||||
// Close listen socket to break select()/accept
|
||||
SOCKET l = m_ListenSocket;
|
||||
m_ListenSocket = INVALID_SOCKET;
|
||||
if (l != INVALID_SOCKET)
|
||||
{
|
||||
::closesocket(l);
|
||||
}
|
||||
|
||||
// Close active connection socket
|
||||
SOCKET s = m_ConnectionSocket;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
if (s != INVALID_SOCKET)
|
||||
{
|
||||
::shutdown(s, SD_BOTH);
|
||||
::closesocket(s);
|
||||
}
|
||||
|
||||
const auto self = std::this_thread::get_id();
|
||||
|
||||
if (m_ReceiveThread.joinable())
|
||||
{
|
||||
if (m_ReceiveThread.get_id() == self)
|
||||
{
|
||||
m_ReceiveThread.detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ReceiveThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ConnectThread.joinable())
|
||||
{
|
||||
if (m_ConnectThread.get_id() == self)
|
||||
{
|
||||
m_ConnectThread.detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ConnectThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("[AF_UNIX] StopThreadsAndCloseSockets: closing listen=%llu conn=%llu",
|
||||
static_cast<uint64_t>(l),
|
||||
static_cast<uint64_t>(s));
|
||||
}
|
||||
|
||||
bool CConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
|
||||
{
|
||||
uint32_t received = 0;
|
||||
|
||||
while (!m_StopReceiveThread.load() && received < length)
|
||||
{
|
||||
const int n = ::recv(m_ConnectionSocket, buffer + received, length - received, 0);
|
||||
if (n == 0)
|
||||
{
|
||||
// Graceful close
|
||||
return false;
|
||||
}
|
||||
if (n < 0)
|
||||
{
|
||||
const int err = ::WSAGetLastError();
|
||||
if (err == WSAEINTR)
|
||||
continue; // retry
|
||||
if (err == WSAEWOULDBLOCK)
|
||||
{
|
||||
::Sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
SDV_LOG_WARNING("[UDS][RX] recv() error: ", std::strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
received += static_cast<uint32_t>(n);
|
||||
}
|
||||
|
||||
return (received == length);
|
||||
}
|
||||
|
||||
bool CConnection::ValidateHeader(const SMsgHeader& msgHeader)
|
||||
{
|
||||
// Kept only for compatibility with any legacy users (not used in SDV path)
|
||||
if ((msgHeader.msgStart == m_MsgStart) && (msgHeader.msgEnd == m_MsgEnd))
|
||||
{
|
||||
return (msgHeader.msgSize != 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint32_t CConnection::ReadDataTable(const CMessage& message, SDataContext& dataCtx)
|
||||
{
|
||||
uint32_t offset = 0;
|
||||
|
||||
switch (message.GetMsgHdr().eType)
|
||||
{
|
||||
case EMsgType::data:
|
||||
offset = static_cast<uint32_t>(sizeof(SMsgHdr));
|
||||
dataCtx.uiTotalSize = message.GetSize() - static_cast<uint32_t>(sizeof(SMsgHdr));
|
||||
break;
|
||||
|
||||
case EMsgType::data_fragment:
|
||||
offset = static_cast<uint32_t>(sizeof(SFragmentedMsgHdr));
|
||||
if (message.GetFragmentedHdr().uiOffset != 0)
|
||||
return 0; // table only in first fragment
|
||||
dataCtx.uiTotalSize = message.GetFragmentedHdr().uiTotalLength;
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
dataCtx.uiCurrentOffset = 0;
|
||||
|
||||
if (message.GetSize() < (offset + static_cast<uint32_t>(sizeof(uint32_t))))
|
||||
return 0;
|
||||
|
||||
const uint32_t count = *reinterpret_cast<const uint32_t*>(message.GetData() + offset);
|
||||
|
||||
offset += static_cast<uint32_t>(sizeof(uint32_t));
|
||||
dataCtx.uiCurrentOffset += static_cast<uint32_t>(sizeof(uint32_t));
|
||||
|
||||
if (message.GetSize() <
|
||||
(offset + count * static_cast<uint32_t>(sizeof(uint32_t))))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<size_t> sizes;
|
||||
sizes.reserve(count);
|
||||
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
const uint32_t sz = *reinterpret_cast<const uint32_t*>(message.GetData() + offset);
|
||||
sizes.push_back(static_cast<size_t>(sz));
|
||||
offset += static_cast<uint32_t>(sizeof(uint32_t));
|
||||
dataCtx.uiCurrentOffset += static_cast<uint32_t>(sizeof(uint32_t));
|
||||
}
|
||||
|
||||
const uint32_t computed =
|
||||
dataCtx.uiCurrentOffset +
|
||||
static_cast<uint32_t>(
|
||||
std::accumulate(
|
||||
sizes.begin(),
|
||||
sizes.end(),
|
||||
static_cast<size_t>(0)));
|
||||
|
||||
if (computed != dataCtx.uiTotalSize)
|
||||
return 0;
|
||||
|
||||
dataCtx.seqDataChunks.clear();
|
||||
for (size_t n : sizes)
|
||||
{
|
||||
dataCtx.seqDataChunks.push_back(sdv::pointer<uint8_t>());
|
||||
dataCtx.seqDataChunks.back().resize(n);
|
||||
}
|
||||
|
||||
dataCtx.nChunkIndex = 0;
|
||||
dataCtx.uiChunkOffset = 0;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool CConnection::ReadDataChunk(const CMessage& message, uint32_t offset, SDataContext& dataCtx)
|
||||
{
|
||||
if (offset < sizeof(SMsgHdr))
|
||||
return false;
|
||||
|
||||
if (message.GetMsgHdr().eType == EMsgType::data_fragment &&
|
||||
offset < sizeof(SFragmentedMsgHdr))
|
||||
return false;
|
||||
|
||||
while (offset < message.GetSize() &&
|
||||
dataCtx.nChunkIndex < dataCtx.seqDataChunks.size())
|
||||
{
|
||||
const uint32_t avail = message.GetSize() - offset;
|
||||
sdv::pointer<uint8_t>& chunk = dataCtx.seqDataChunks[dataCtx.nChunkIndex];
|
||||
|
||||
if (dataCtx.uiChunkOffset > static_cast<uint32_t>(chunk.size()))
|
||||
return false;
|
||||
|
||||
const uint32_t need =
|
||||
static_cast<uint32_t>(chunk.size()) - dataCtx.uiChunkOffset;
|
||||
const uint32_t toCopy = std::min(avail, need);
|
||||
|
||||
std::copy(
|
||||
message.GetData() + offset,
|
||||
message.GetData() + offset + toCopy,
|
||||
chunk.get() + dataCtx.uiChunkOffset);
|
||||
|
||||
offset += toCopy;
|
||||
dataCtx.uiChunkOffset += toCopy;
|
||||
|
||||
if (dataCtx.uiChunkOffset >= static_cast<uint32_t>(chunk.size()))
|
||||
{
|
||||
dataCtx.uiChunkOffset = 0;
|
||||
++dataCtx.nChunkIndex;
|
||||
|
||||
if (dataCtx.nChunkIndex == dataCtx.seqDataChunks.size())
|
||||
{
|
||||
#if ENABLE_DECOUPLING > 0
|
||||
// optional queueing path...
|
||||
#else
|
||||
if (m_pReceiver)
|
||||
m_pReceiver->ReceiveData(dataCtx.seqDataChunks);
|
||||
dataCtx = SDataContext(); // reset context
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CConnection::ReceiveSyncRequest(const CMessage& message)
|
||||
{
|
||||
const auto hdr = message.GetMsgHdr();
|
||||
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
|
||||
SMsgHdr reply{};
|
||||
reply.uiVersion = SDVFrameworkInterfaceVersion;
|
||||
reply.eType = EMsgType::sync_answer;
|
||||
|
||||
uint32_t packetSize = static_cast<uint32_t>(sizeof(reply));
|
||||
|
||||
if (SendExact(reinterpret_cast<char*>(&packetSize), sizeof(packetSize)) < 0)
|
||||
return;
|
||||
if (SendExact(reinterpret_cast<char*>(&reply), sizeof(reply)) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
void CConnection::ReceiveSyncAnswer(const CMessage& message)
|
||||
{
|
||||
const auto hdr = message.GetMsgHdr();
|
||||
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
|
||||
SConnectMsg req{};
|
||||
req.uiVersion = SDVFrameworkInterfaceVersion;
|
||||
req.eType = EMsgType::connect_request;
|
||||
req.tProcessID = static_cast<uint64_t>(::GetCurrentProcessId());
|
||||
|
||||
const uint32_t packetSize = static_cast<uint32_t>(sizeof(req));
|
||||
|
||||
if (SendExact(reinterpret_cast<char const*>(&packetSize), sizeof(packetSize)) < 0)
|
||||
return;
|
||||
if (SendExact(reinterpret_cast<char const*>(&req), sizeof(req)) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
void CConnection::ReceiveConnectRequest(const CMessage& message)
|
||||
{
|
||||
const auto hdr = message.GetConnectHdr();
|
||||
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
|
||||
SConnectMsg reply{};
|
||||
reply.uiVersion = SDVFrameworkInterfaceVersion;
|
||||
reply.eType = EMsgType::connect_answer;
|
||||
reply.tProcessID = static_cast<uint64_t>(::GetCurrentProcessId());
|
||||
|
||||
const uint32_t packetSize = static_cast<uint32_t>(sizeof(reply));
|
||||
|
||||
if (SendExact(reinterpret_cast<char const*>(&packetSize), sizeof(packetSize)) < 0)
|
||||
return;
|
||||
if (SendExact(reinterpret_cast<char const*>(&reply), sizeof(reply)) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
void CConnection::ReceiveConnectAnswer(const CMessage& message)
|
||||
{
|
||||
const auto hdr = message.GetConnectHdr();
|
||||
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handshake complete (client side)
|
||||
SetStatus(sdv::ipc::EConnectStatus::connected);
|
||||
}
|
||||
|
||||
void CConnection::ReceiveConnectTerm(const CMessage& /*message*/)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::disconnected);
|
||||
m_StopReceiveThread.store(true);
|
||||
}
|
||||
|
||||
void CConnection::ReceiveDataMessage(const CMessage& message, SDataContext& dataCtx)
|
||||
{
|
||||
const uint32_t payloadOffset = ReadDataTable(message, dataCtx);
|
||||
if (payloadOffset == 0)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ReadDataChunk(message, payloadOffset, dataCtx))
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CConnection::ReceiveDataFragmentMessage(const CMessage& message, SDataContext& dataCtx)
|
||||
{
|
||||
uint32_t offset = static_cast<uint32_t>(sizeof(SFragmentedMsgHdr));
|
||||
|
||||
if (message.GetFragmentedHdr().uiOffset == 0)
|
||||
{
|
||||
offset = ReadDataTable(message, dataCtx);
|
||||
if (offset == 0)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ReadDataChunk(message, offset, dataCtx))
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CConnection::ReceiveMessages()
|
||||
{
|
||||
try
|
||||
{
|
||||
SDataContext dataCtx;
|
||||
|
||||
while (!m_StopReceiveThread.load() &&
|
||||
m_ConnectionSocket != INVALID_SOCKET)
|
||||
{
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(m_ConnectionSocket, &rfds);
|
||||
|
||||
TIMEVAL tv{};
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 50 * 1000; // 50 ms
|
||||
|
||||
int sr = ::select(0, &rfds, nullptr, nullptr, &tv);
|
||||
if (sr == SOCKET_ERROR)
|
||||
{
|
||||
::Sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sr == 0)
|
||||
{
|
||||
continue; // timeout
|
||||
}
|
||||
|
||||
// 1. Transport header: packet size (LE, 4 bytes)
|
||||
uint32_t packetSize = 0;
|
||||
if (!ReadNumberOfBytes(reinterpret_cast<char*>(&packetSize),
|
||||
sizeof(packetSize)))
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::disconnected);
|
||||
SDV_LOG_WARNING("[UDS][RX] Incomplete payload read -> disconnected");
|
||||
break;
|
||||
}
|
||||
|
||||
if (packetSize == 0 || packetSize > kMaxUdsPacketSize)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Payload
|
||||
std::vector<uint8_t> payload(packetSize);
|
||||
if (!ReadNumberOfBytes(reinterpret_cast<char*>(payload.data()),
|
||||
packetSize))
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
SDV_LOG_WARNING("[UDS][RX] Invalid SDV message (envelope)");
|
||||
break;
|
||||
}
|
||||
|
||||
// 3. Parse & dispatch SDV message
|
||||
CMessage msg(std::move(payload));
|
||||
if (!msg.IsValid())
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::communication_error);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (msg.GetMsgHdr().eType)
|
||||
{
|
||||
case EMsgType::sync_request: ReceiveSyncRequest(msg); break;
|
||||
case EMsgType::sync_answer: ReceiveSyncAnswer(msg); break;
|
||||
case EMsgType::connect_request: ReceiveConnectRequest(msg); break;
|
||||
case EMsgType::connect_answer: ReceiveConnectAnswer(msg); break;
|
||||
case EMsgType::connect_term: ReceiveConnectTerm(msg); break;
|
||||
case EMsgType::data: ReceiveDataMessage(msg, dataCtx); break;
|
||||
case EMsgType::data_fragment: ReceiveDataFragmentMessage(msg, dataCtx); break;
|
||||
default:
|
||||
// Ignore unknown types
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_ConnectionStatus == sdv::ipc::EConnectStatus::terminating)
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
SetStatus(sdv::ipc::EConnectStatus::disconnected);
|
||||
}
|
||||
}
|
||||
510
sdv_services/uds_win_sockets/connection.h
Normal file
510
sdv_services/uds_win_sockets/connection.h
Normal file
@@ -0,0 +1,510 @@
|
||||
/********************************************************************************
|
||||
* 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
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/component_impl.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <WinSock2.h>
|
||||
# ifdef _MSC_VER
|
||||
# pragma comment(lib, "Ws2_32.lib")
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/// @brief Legacy framing markers for the old message header (not used by SDV envelope)
|
||||
constexpr uint32_t m_MsgStart = 0x01020304; ///< Value to mark the start of the legacy message header
|
||||
constexpr uint32_t m_MsgEnd = 0x05060708; ///< Value to mark the end of the legacy message header
|
||||
|
||||
/**
|
||||
* @brief Legacy message header used before the SDV envelope was introduced
|
||||
*
|
||||
* Today, the active transport framing is:
|
||||
* - 4 bytes: packet size (LE)
|
||||
* - N bytes: SDV protocol envelope (SMsgHdr + payload)
|
||||
*
|
||||
* This header is kept for compatibility / potential reuse, but is not used in
|
||||
* the current SDV-based protocol
|
||||
*/
|
||||
struct SMsgHeader
|
||||
{
|
||||
uint32_t msgStart = 0; ///< Marker for the start of the header
|
||||
uint32_t msgId = 0; ///< Message ID, must match for all message fragments
|
||||
uint32_t msgSize = 0; ///< Size of the message without the header
|
||||
uint32_t packetNumber = 0; ///< Index of this packet (starting at 1)
|
||||
uint32_t totalPacketCount = 0; ///< Total number of packets required for the message
|
||||
uint32_t msgEnd = 0; ///< Marker for the end of the header
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Remote IPC connection used by the AF_UNIX-over-WinSock transport.
|
||||
*
|
||||
* Instances of this class are typically created and owned by the
|
||||
* CSocketsChannelMgnt implementation (see channel_mgnt.*).
|
||||
*
|
||||
* Exposes:
|
||||
* - sdv::ipc::IDataSend : sending SDV-framed messages
|
||||
* - sdv::ipc::IConnect : async connect / wait / status / events
|
||||
* - sdv::IObjectDestroy : explicit destruction hook for SDV runtime
|
||||
*/
|
||||
class CConnection
|
||||
: public sdv::IInterfaceAccess
|
||||
, public sdv::ipc::IDataSend
|
||||
, public sdv::ipc::IConnect
|
||||
, public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief default constructor used by create endpoint - allocates new buffers m_Sender and m_Receiver
|
||||
*/
|
||||
CConnection();
|
||||
|
||||
/**
|
||||
* @brief access existing connection
|
||||
* @param[in] preconfiguredSocket Prepared socket for the connection
|
||||
* @param[in] acceptConnectionRequired If true connection has to be accepted before receive thread can be started
|
||||
*/
|
||||
CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor needed for "delete this;"
|
||||
*/
|
||||
virtual ~CConnection();
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Sends data consisting of multiple data chunks via the IPC connection
|
||||
*
|
||||
* Overload of sdv::ipc::IDataSend::SendData
|
||||
*
|
||||
* The sequence may be rearranged internally to avoid copying payload
|
||||
* where possible. The transport uses an SDV envelope compatible with
|
||||
* the Linux implementation (length table + chunks, with fragmentation)
|
||||
*
|
||||
* @param[in,out] seqData Sequence of data buffers to be sent
|
||||
* @return true if all data could be sent; false otherwise
|
||||
*/
|
||||
bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
|
||||
|
||||
/**
|
||||
* @brief Establish a connection and start sending/receiving messages
|
||||
*
|
||||
* Overload of sdv::ipc::IConnect::AsyncConnect
|
||||
*
|
||||
* @param[in] pReceiver Object that exposes IDataReceiveCallback and
|
||||
* optionally IConnectEventCallback
|
||||
* @return true when the connect worker was started successfully
|
||||
* Use GetStatus / WaitForConnection / callbacks for status
|
||||
*/
|
||||
bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a connection to be established
|
||||
*
|
||||
* Overload of sdv::ipc::IConnect::WaitForConnection
|
||||
*
|
||||
* @param[in] uiWaitMs
|
||||
* - 0 : do not wait, just check current status
|
||||
* - 0xFFFFFFFF: wait indefinitely
|
||||
* - otherwise : wait up to uiWaitMs milliseconds
|
||||
* @return true if the connection is in connected state when returning
|
||||
*/
|
||||
bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
|
||||
|
||||
/**
|
||||
* @brief Cancel a pending WaitForConnection
|
||||
*
|
||||
* Overload of sdv::ipc::IConnect::CancelWait
|
||||
*/
|
||||
void CancelWait() override;
|
||||
|
||||
/**
|
||||
* @brief Disconnect from the peer and stop I/O threads
|
||||
*
|
||||
* This sets the status to disconnected and releases the event interface
|
||||
*/
|
||||
void Disconnect() override;
|
||||
|
||||
/**
|
||||
* @brief Register event callback interface for connection status updates
|
||||
*
|
||||
* Overload of sdv::ipc::IConnect::RegisterStatusEventCallback
|
||||
*
|
||||
* @param[in] pEventCallback Pointer to an object exposing
|
||||
* sdv::ipc::IConnectEventCallback.
|
||||
* @return A registration cookie (1 = valid, 0 = failure)
|
||||
*/
|
||||
uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister the status event callback using its cookie
|
||||
*
|
||||
* Overload of sdv::ipc::IConnect::UnregisterStatusEventCallback
|
||||
*
|
||||
* @param[in] uiCookie Cookie returned by RegisterStatusEventCallback
|
||||
*/
|
||||
void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
|
||||
|
||||
/**
|
||||
* @brief Get current status of the connection
|
||||
*
|
||||
* @return Current sdv::ipc::EConnectStatus
|
||||
*/
|
||||
sdv::ipc::EConnectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Destroy the object
|
||||
*
|
||||
* Overload of sdv::IObjectDestroy::DestroyObject
|
||||
* After calling this, all exposed interfaces become invalid
|
||||
*/
|
||||
void DestroyObject() override;
|
||||
|
||||
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;
|
||||
std::atomic<bool> m_StopReceiveThread{false}; ///< bool variable to stop thread
|
||||
std::atomic<bool> m_StopConnectThread{false};
|
||||
std::atomic<sdv::ipc::EConnectStatus> m_ConnectionStatus; ///< the status of the connection
|
||||
|
||||
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages if available
|
||||
sdv::ipc::IConnectEventCallback* m_pEvent = nullptr; ///< Event receiver
|
||||
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)
|
||||
|
||||
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
|
||||
char m_SendBuffer[m_SendBufferSize]; ///< send buffer length
|
||||
char m_ReceiveBuffer[sizeof(SMsgHeader)]; ///< receive buffer, just for reading the message header
|
||||
uint32_t m_ReceiveBufferLength = sizeof(SMsgHeader); ///< receive buffer length
|
||||
|
||||
std::atomic<bool> m_CancelWait{false};
|
||||
|
||||
/// @brief Server accept loop / client connect confirmation
|
||||
void ConnectWorker();
|
||||
|
||||
/// @brief Start the RX thread (pre: status=connected, socket valid)
|
||||
void StartReceiveThread_Unsafe();
|
||||
|
||||
/// @brief Stop worker threads and close all sockets cleanly
|
||||
void StopThreadsAndCloseSockets();
|
||||
|
||||
/**
|
||||
* @brief Accept a connection from a client (server mode)
|
||||
*
|
||||
* Uses select() with a short timeout to remain responsive to stop flags
|
||||
*
|
||||
* @return A valid client socket, or INVALID_SOCKET on error
|
||||
*/
|
||||
SOCKET AcceptConnection();
|
||||
|
||||
/**
|
||||
* @brief Send exactly \p dataLength bytes over the connection socket
|
||||
*
|
||||
* @param[in] data Pointer to bytes to be sent
|
||||
* @param[in] dataLength Number of bytes to send
|
||||
* @return Number of bytes actually sent, or SOCKET_ERROR on failure
|
||||
*/
|
||||
int32_t Send(const char* data, int32_t dataLength);
|
||||
|
||||
/**
|
||||
* @brief Low-level helper: send exactly len bytes, retrying until done
|
||||
*
|
||||
* @param[in] data Pointer to buffer
|
||||
* @param[in] len Number of bytes to send
|
||||
* @return Total bytes sent, or -1 on failure
|
||||
*/
|
||||
int SendExact(const char* data, int len);
|
||||
|
||||
/// @brief Main receive loop (runs in m_ReceiveThread)
|
||||
void ReceiveMessages();
|
||||
|
||||
/**
|
||||
* @brief Legacy header validation (not used in current SDV path)
|
||||
*
|
||||
* @param[in] msgHeader Header to validate
|
||||
* @return true if header markers are valid and msgSize != 0
|
||||
*/
|
||||
bool ValidateHeader(const SMsgHeader& msgHeader);
|
||||
|
||||
/**
|
||||
* @brief Read exactly @p length bytes into @p buffer from the socket
|
||||
*
|
||||
* @param[out] buffer Target buffer
|
||||
* @param[in] length Number of bytes expected
|
||||
* @return true if all bytes were read, false on error/EOF
|
||||
*/
|
||||
bool ReadNumberOfBytes(char* buffer, uint32_t length);
|
||||
|
||||
// Protocol types
|
||||
/** @brief SDV message type */
|
||||
enum class EMsgType : uint32_t
|
||||
{
|
||||
sync_request = 0, ///< Sync request (version check; no payload)
|
||||
sync_answer = 1, ///< Sync answer (version check; no payload)
|
||||
connect_request = 10, ///< Connection initiation (SConnectMsg)
|
||||
connect_answer = 11, ///< Connection answer (SConnectMsg)
|
||||
connect_term = 90, ///< Connection terminated
|
||||
data = 0x10000000, ///< Data message
|
||||
data_fragment = 0x10000001 ///< Data fragment if payload exceeds frame size
|
||||
};
|
||||
|
||||
/** @brief SDV base message header */
|
||||
struct SMsgHdr
|
||||
{
|
||||
uint32_t uiVersion; ///< Protocol version
|
||||
EMsgType eType; ///< Message type
|
||||
};
|
||||
|
||||
/** @brief Connection initiation message (extends SMsgHdr) */
|
||||
struct SConnectMsg : SMsgHdr
|
||||
{
|
||||
sdv::process::TProcessID tProcessID; ///< Process ID for lifetime monitoring
|
||||
};
|
||||
|
||||
/** @brief Fragmented data message header */
|
||||
struct SFragmentedMsgHdr : SMsgHdr
|
||||
{
|
||||
uint32_t uiTotalLength; ///< Total payload length across all fragments
|
||||
uint32_t uiOffset; ///< Current byte offset into the total payload
|
||||
};
|
||||
|
||||
// Minimal SDV message wrapper
|
||||
class CMessage
|
||||
{
|
||||
public:
|
||||
explicit CMessage(std::vector<uint8_t>&& data) : m_Data(std::move(data)) {}
|
||||
|
||||
/** @return Pointer to message bytes (may be null if empty) */
|
||||
const uint8_t* GetData() const
|
||||
{
|
||||
return m_Data.empty() ? nullptr : m_Data.data();
|
||||
}
|
||||
|
||||
/** @return Message size in bytes */
|
||||
uint32_t GetSize() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_Data.size());
|
||||
}
|
||||
|
||||
/** @return Interpreted SDV base header (or a fallback) */
|
||||
SMsgHdr GetMsgHdr() const
|
||||
{
|
||||
if (GetSize() < sizeof(SMsgHdr))
|
||||
{
|
||||
return SMsgHdr{0, EMsgType::connect_term};
|
||||
}
|
||||
return *reinterpret_cast<const SMsgHdr*>(GetData());
|
||||
}
|
||||
|
||||
/** @return SDV connect header (or default if undersized) */
|
||||
SConnectMsg GetConnectHdr() const
|
||||
{
|
||||
if (GetSize() < sizeof(SConnectMsg))
|
||||
{
|
||||
return SConnectMsg{};
|
||||
}
|
||||
return *reinterpret_cast<const SConnectMsg*>(GetData());
|
||||
}
|
||||
|
||||
/** @return SDV fragmented header (or default if undersized) */
|
||||
SFragmentedMsgHdr GetFragmentedHdr() const
|
||||
{
|
||||
if (GetSize() < sizeof(SFragmentedMsgHdr))
|
||||
{
|
||||
return SFragmentedMsgHdr{};
|
||||
}
|
||||
return *reinterpret_cast<const SFragmentedMsgHdr*>(GetData());
|
||||
}
|
||||
|
||||
/** @return true if the SDV envelope is well-formed */
|
||||
bool IsValid() const
|
||||
{
|
||||
if (!GetData() || GetSize() < sizeof(SMsgHdr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const SMsgHdr hdr = GetMsgHdr();
|
||||
switch (hdr.eType)
|
||||
{
|
||||
case EMsgType::sync_request:
|
||||
case EMsgType::sync_answer:
|
||||
case EMsgType::connect_term:
|
||||
case EMsgType::data:
|
||||
return true;
|
||||
|
||||
case EMsgType::connect_request:
|
||||
case EMsgType::connect_answer:
|
||||
return GetSize() >= sizeof(SConnectMsg);
|
||||
|
||||
case EMsgType::data_fragment:
|
||||
return GetSize() >= sizeof(SFragmentedMsgHdr);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> m_Data;
|
||||
};
|
||||
|
||||
/** @brief Receive-time data reassembly context */
|
||||
struct SDataContext
|
||||
{
|
||||
uint32_t uiTotalSize = 0; ///< Total payload size (without SDV header)
|
||||
uint32_t uiCurrentOffset = 0; ///< Current filled byte count
|
||||
size_t nChunkIndex = 0; ///< Current chunk index being filled
|
||||
uint32_t uiChunkOffset = 0; ///< Offset within the current chunk
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqDataChunks; ///< Output buffers
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Event callback structure.
|
||||
*/
|
||||
struct SEventCallback
|
||||
{
|
||||
uint64_t uiCookie = 0; ///< Registration cookie
|
||||
sdv::ipc::IConnectEventCallback* pCallback = nullptr; ///< Pointer to the callback; Could be NULL when the callback
|
||||
///< was deleted
|
||||
};
|
||||
|
||||
/** @brief UDS transport maximum frame size (safety cap) */
|
||||
static constexpr uint32_t kMaxUdsPacketSize = 64u * 1024u * 1024u; // 64 MiB
|
||||
|
||||
/**
|
||||
* @brief Handle an incoming sync_request message
|
||||
*
|
||||
* Sent by the peer during the initial handshake
|
||||
* The server replies with sync_answer
|
||||
*
|
||||
* @param message SDV envelope containing the sync_request header
|
||||
*/
|
||||
void ReceiveSyncRequest(const CMessage& message);
|
||||
|
||||
/**
|
||||
* @brief Handle an incoming sync_answer message
|
||||
*
|
||||
* Received by the client after sending sync_request
|
||||
* Triggers the next handshake step: the client sends connect_request
|
||||
*
|
||||
* @param message SDV envelope containing the sync_answer header
|
||||
*/
|
||||
void ReceiveSyncAnswer(const CMessage& message);
|
||||
|
||||
/**
|
||||
* @brief Handle an incoming connect_request message
|
||||
*
|
||||
* Sent by the peer to request establishment of a logical SDV connection
|
||||
* The server replies with connect_answer
|
||||
*
|
||||
* @param message SDV envelope containing the connect_request header
|
||||
*/
|
||||
void ReceiveConnectRequest(const CMessage& message);
|
||||
|
||||
/**
|
||||
* @brief Handle an incoming connect_answer message
|
||||
*
|
||||
* Sent by the peer after receiving our connect_request
|
||||
* Marks completion of the SDV handshake on the client side
|
||||
*
|
||||
* @param message SDV envelope containing the connect_answer header
|
||||
*/
|
||||
void ReceiveConnectAnswer(const CMessage& message);
|
||||
|
||||
/**
|
||||
* @brief Handle an incoming connect_term message
|
||||
*
|
||||
* Indicates that the peer requests immediate termination of the connection
|
||||
* Sets status to disconnected and stops the RX loop
|
||||
*
|
||||
* @param message SDV envelope containing the connect_term header
|
||||
*/
|
||||
void ReceiveConnectTerm(const CMessage& message);
|
||||
|
||||
/**
|
||||
* @brief Handle a non-fragmented SDV data message
|
||||
*
|
||||
* Format:
|
||||
* [SMsgHdr][LengthTable][DataChunks...]
|
||||
* The function decodes the table, allocates chunk buffers,
|
||||
* copies the full payload, and dispatches it via IDataReceiveCallback.
|
||||
*
|
||||
* @param rMessage SDV envelope containing the full data frame
|
||||
* @param rsDataCtxt Reassembly context used to extract chunks
|
||||
*/
|
||||
void ReceiveDataMessage(const CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
|
||||
/**
|
||||
* @brief Handle a fragmented SDV data message
|
||||
*
|
||||
* Format:
|
||||
* [SFragmentedMsgHdr][(LengthTable only in first fragment)][PayloadSlice]
|
||||
*
|
||||
* This function appends each slice into the appropriate chunk buffer
|
||||
* When the final fragment is received and all chunks are complete,
|
||||
* the assembled data is dispatched via IDataReceiveCallback.
|
||||
*
|
||||
* @param rMessage SDV envelope containing the current fragment
|
||||
* @param rsDataCtxt Reassembly context shared across fragments
|
||||
*/
|
||||
void ReceiveDataFragmentMessage(const CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
|
||||
/**
|
||||
* @brief Read the data size table (buffer count + each buffer size)
|
||||
* @param rMessage SDV message containing the table
|
||||
* @param rsDataCtxt Output reassembly context; buffers are allocated
|
||||
* @return Offset within message data where the payload begins; 0 on error
|
||||
*/
|
||||
uint32_t ReadDataTable(const CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
|
||||
/**
|
||||
* @brief Copy payload bytes from 'rMessage' into the buffers allocated by ReadDataTable.
|
||||
* Multiple calls may be required for fragmented messages.
|
||||
* @param rMessage SDV message holding the current data/fragment.
|
||||
* @param uiOffset Offset within 'rMessage' where payload starts in this frame.
|
||||
* @param rsDataCtxt Reassembly context to be filled; callback fires when complete.
|
||||
* @return true if progress was made / completion achieved, false if invalid.
|
||||
*/
|
||||
bool ReadDataChunk(const CMessage& rMessage, uint32_t uiOffset, SDataContext& rsDataCtxt);
|
||||
|
||||
/// @brief Centralized status update (notifies waiters and callbacks)
|
||||
void SetStatus(sdv::ipc::EConnectStatus status);
|
||||
};
|
||||
|
||||
#endif // CONNECTION_H
|
||||
Reference in New Issue
Block a user