Update sdv_packager (#6)

This commit is contained in:
tompzf
2026-03-27 14:12:49 +01:00
committed by GitHub
parent 234be8917f
commit aefefd52f7
717 changed files with 42252 additions and 11334 deletions

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

View 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);
}

View 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=&lt;udsPath&gt;
*
* 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=&lt;udsPath&gt;
*
* 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

View 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);
}
}

View 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