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(UNIX)
# Define project
project(uds_unix_sockets VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_unix_sockets SHARED
"channel_mgnt.h"
"channel_mgnt.cpp"
"connection.h"
"connection.cpp"
)
target_link_libraries(uds_unix_sockets rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
target_link_options(uds_unix_sockets PRIVATE)
target_include_directories(uds_unix_sockets PRIVATE ./include/)
set_target_properties(uds_unix_sockets PROPERTIES PREFIX "")
set_target_properties(uds_unix_sockets PROPERTIES SUFFIX ".sdv")
# Build dependencies
add_dependencies(uds_unix_sockets CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_unix_sockets PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,161 @@
/********************************************************************************
* 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
********************************************************************************/
#if defined(__unix__)
#include "channel_mgnt.h"
#include "connection.h"
#include <support/toml.h>
#include <sstream>
#include <map>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
// Anonymous namespace for internal helpers
namespace
{
/**
* @brief Parse a semicolon-separated list of key=value pairs
*
* Expected input format:
*    "key1=value1;key2=value2;key3=value3;"
*
* Whitespace is not trimmed and empty entries are ignored
* Keys without '=' are skipped
*
* Example:
*    Input:  "proto=uds;path=/tmp/test.sock;"
*    Output: { {"proto","uds"}, {"path","/tmp/test.sock"} }
*
* @param[in] s   Raw string containing "key=value;" pairs
*
* @return A map of parsed key/value pairs (order not preserved)
*/
static std::map<std::string, std::string> ParseKV(const std::string& s)
{
std::map<std::string, std::string> kv;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, ';'))
{
auto pos = item.find('=');
if (pos != std::string::npos)
kv[item.substr(0, pos)] = item.substr(pos + 1);
}
return kv;
}
/**
* @brief Clamp an AF_UNIX pathname to Linux `sockaddr_un::sun_path` size
*
* Linux allows paths up to ~108 bytes inside `sun_path`
* If the input string exceeds the allowed size, it is truncated so that:
*    resulting_length <= sizeof(sockaddr_un::sun_path) - 1
*
* @param[in] p  Original path string
*
* @return A safe, clamped path that fits inside `sun_path`
*/
static std::string ClampSunPath(const std::string& p)
{
constexpr size_t MaxLen = sizeof(sockaddr_un::sun_path);
return (p.size() < MaxLen) ? p : p.substr(0, MaxLen - 1);
}
}
// Directory selection (/run/user/<uid>/sdv or /tmp/sdv)
std::string CUnixDomainSocketsChannelMgnt::MakeUserRuntimeDir()
{
std::ostringstream oss;
oss << "/run/user/" << ::getuid();
struct stat st{};
if (::stat(oss.str().c_str(), &st) == 0)
{
std::string path = oss.str() + "/sdv";
::mkdir(path.c_str(), 0770);
return path;
}
::mkdir("/tmp/sdv", 0770);
return "/tmp/sdv";
}
bool CUnixDomainSocketsChannelMgnt::OnInitialize()
{
return true;
}
void CUnixDomainSocketsChannelMgnt::OnShutdown()
{
}
// Endpoint creation (server)
sdv::ipc::SChannelEndpoint CUnixDomainSocketsChannelMgnt::CreateEndpoint(const sdv::u8string& ssEndpointConfig)
{
const std::string baseDir = MakeUserRuntimeDir();
std::string name = "UDS_" + std::to_string(::getpid());
std::string path = baseDir + "/" + name + ".sock";
if (!ssEndpointConfig.empty())
{
sdv::toml::CTOMLParser cfg(ssEndpointConfig.c_str());
auto nameNode = cfg.GetDirect("IpcChannel.Name");
if (nameNode.GetType() == sdv::toml::ENodeType::node_string)
name = static_cast<std::string>(nameNode.GetValue());
auto pathNode = cfg.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
path = static_cast<std::string>(pathNode.GetValue());
else
path = baseDir + "/" + name + ".sock";
}
path = ClampSunPath(path);
// Use a shared_ptr and store it to keep the server connection alive
auto server = std::make_shared<CUnixSocketConnection>(-1, true, path);
m_ServerConnections.push_back(server);
sdv::ipc::SChannelEndpoint ep{};
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
ep.ssConnectString = server->GetConnectionString();
return ep;
}
// Access existing endpoint (server or client)
sdv::IInterfaceAccess* CUnixDomainSocketsChannelMgnt::Access(const sdv::u8string& ssConnectString)
{
const auto kv = ParseKV(static_cast<std::string>(ssConnectString));
const bool isServer = (kv.count("role") && kv.at("role") == "server");
const std::string path = kv.count("path") ? kv.at("path") : (MakeUserRuntimeDir() + "/UDS_auto.sock");
if (isServer)
{
auto server = std::make_shared<CUnixSocketConnection>(-1, true, path);
m_ServerConnections.push_back(server);
return static_cast<IInterfaceAccess*>(server.get());
}
// Client: allocated raw pointer (expected to be managed by SDV framework)
auto* client = new CUnixSocketConnection(-1, false, path);
return static_cast<IInterfaceAccess*>(client);
}
#endif // defined(__unix__)

View File

@@ -0,0 +1,90 @@
/********************************************************************************
* 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
********************************************************************************/
#if defined(__unix__)
#ifndef UNIX_SOCKET_CHANNEL_MGNT_H
#define UNIX_SOCKET_CHANNEL_MGNT_H
#include <support/component_impl.h>
#include <interfaces/ipc.h>
class CUnixSocketConnection;
/**
* @brief IPC channel management class for Unix Domain Sockets communication.
*/
class CUnixDomainSocketsChannelMgnt :
public sdv::CSdvObject,
public sdv::ipc::ICreateEndpoint,
public sdv::ipc::IChannelAccess
{
public:
// Interface map
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("UnixDomainSocketsChannelControl")
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 IPC connection object and return the endpoint information. Overload of
* sdv::ipc::ICreateEndpoint::CreateEndpoint.
* @details The endpoints are generated using either a size and a name based on the provided channel configuration or if no
* configuration is supplied a default size of 10k and a randomly generated name. The following configuration
* can be supplied:
* @code
* [IpcChannel]
* Name = "CHANNEL_1234"
* Size = 10240
* @endcode
* @param[in] ssChannelConfig Optional channel type specific endpoint configuration.
* @return IPC connection object
*/
sdv::ipc::SChannelEndpoint CreateEndpoint(/*in*/ const sdv::u8string& ssChannelConfig) override;
/**
* @brief Create a connection object from the channel connection parameters string
* @param[in] ssConnectString Reference to the string containing the channel connection parameters.
* @return Pointer to IInterfaceAccess interface of the connection object or NULL when the object cannot be created.
*/
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
private:
// Helper: choose runtime dir (/run/user/<uid>/sdv) or fallback (/tmp/sdv)
static std::string MakeUserRuntimeDir();
std::vector<std::shared_ptr<CUnixSocketConnection>> m_ServerConnections;
};
DEFINE_SDV_OBJECT(CUnixDomainSocketsChannelMgnt)
#endif // UNIX_SOCKET_CHANNEL_MGNT_H
#endif // defined(__unix__)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
/********************************************************************************
* 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
********************************************************************************/
#if defined(__unix__)
#ifndef CONNECTION_H
#define CONNECTION_H
#include <interfaces/ipc.h>
#include <support/component_impl.h>
#include <support/interface_ptr.h>
#include <atomic>
#include <cstdint>
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <list>
/**
* @brief Local IPC connection over Unix Domain Sockets (UDS).
*
* Frames on the wire are sent as size-prefixed buffers:
* [ uint32_t packetSize ][ SDV message bytes ... ]
*
* SDV protocol decoding (SMsgHdr / EMsgType / fragmentation)
*/
class CUnixSocketConnection
: public std::enable_shared_from_this<CUnixSocketConnection>
, public sdv::IInterfaceAccess
, public sdv::IObjectDestroy
, public sdv::ipc::IDataSend
, public sdv::ipc::IConnect
{
public:
/**
* @brief Construct a UDS connection.
* @param preconfiguredFd Already-open FD (>=0) or -1 if none.
* @param acceptConnectionRequired true for server (must accept()); false for client.
* @param udsPath Filesystem path of the UDS socket.
*/
CUnixSocketConnection(int preconfiguredFd, bool acceptConnectionRequired, const std::string& udsPath);
/** @brief Virtual destructor. */
virtual ~CUnixSocketConnection();
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 Returns the connection string (proto/role/path/timeout). */
std::string GetConnectionString();
// ---------- IDataSend ----------
/** @brief Send a sequence of data buffers (may be fragmented). */
bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
// ---------- IConnect ----------
/** @brief Start asynchronous connect/accept worker. */
bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
/**
* @brief Wait for the connection to become 'connected'.
* @param uiWaitMs Timeout in milliseconds (0 = immediate, 0xFFFFFFFFu = infinite).
* @return true if connected within the timeout; false otherwise.
*/
bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
/** @brief Optionally cancel WaitForConnection (no-op in current implementation). */
void CancelWait() override;
/** @brief Disconnect and teardown threads/FDs; sets status to 'disconnected'. */
void Disconnect() override;
// ---------- IConnect: event callbacks ----------
/** @brief Register a status event callback (no-op storage in UDS). */
uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/** @brief Unregister a previously registered callback (no-op storage in UDS). */
void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
/** @brief Get current connection status. */
sdv::ipc::EConnectStatus GetStatus() const override;
/** @brief Destroy object (IObjectDestroy). */
void DestroyObject() override;
/** @brief Set status and notify listeners (callback-safe). */
void SetStatus(sdv::ipc::EConnectStatus eStatus);
/** @brief @return true if this side is server (needs accept()), false otherwise. */
bool IsServer() const;
// ---------- 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;
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
// ---------- Transport helpers ----------
/**
* @brief Accept incoming client (server side).
* @return Returns a connected socket FD or -1 on error.
* @deprecated The poll-based accept loop in ConnectWorker() is used instead.
*/
int AcceptConnection();
/**
* @brief Read exactly 'bufferLength' bytes from m_Fd into 'buffer' (blocking).
* @return true on success; false on EOF/error.
*/
bool ReadNumberOfBytes(char* buffer, uint32_t bufferLength);
/**
* @brief Read UDS transport header (size prefix) into packetSize.
* @param[out] packetSize SDV message size to follow.
* @return true if the header was read and valid; false otherwise.
*/
bool ReadTransportHeader(uint32_t& packetSize);
/**
* @brief Send an SDV message as a UDS frame: [packetSize][payload].
* @param pData Pointer to serialized SDV payload.
* @param uiDataLength Payload size.
* @return true if fully sent; false otherwise.
*/
bool SendSizedPacket(const void* pData, uint32_t uiDataLength);
/**
* @brief Receive loop: read UDS frames and dispatch to the SDV state machine.
*/
void ReceiveMessages();
/**
* @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);
// ---------- Internal threading ----------
/** @brief Connect worker (server accept loop or client connect retry). */
void ConnectWorker();
/** @brief Start RX thread (precondition: status=connected, FD valid). */
void StartReceiveThread_Unsafe();
/**
* @brief Stop workers and close sockets, then optionally unlink path.
* @param unlinkPath If true and server, unlink the socket path on exit.
*/
void StopThreadsAndCloseSockets(bool unlinkPath);
private:
//Transport state
int m_Fd { -1 }; ///< Active connection FD.
int m_ListenFd { -1 }; ///< Server-side listening FD.
bool m_AcceptConnectionRequired { false };
std::string m_UdsPath;
//Threads & control
std::atomic<bool> m_StopReceiveThread { false };
std::atomic<bool> m_StopConnectThread { false };
std::thread m_ReceiveThread;
std::thread m_ConnectThread;
//Status & synchronization
std::condition_variable m_StateCv;
std::atomic<sdv::ipc::EConnectStatus> m_eStatus { sdv::ipc::EConnectStatus::uninitialized };
sdv::ipc::IDataReceiveCallback* m_pReceiver { nullptr };
sdv::ipc::IConnectEventCallback* m_pEvent { nullptr };
std::mutex m_MtxConnect;
std::condition_variable m_CvConnect;
std::mutex m_StateMtx; ///< Protects receiver/event assignment.
std::list<SEventCallback> m_lstEventCallbacks; ///< List containing event callbacks
std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list
//TX synchronization
std::mutex m_SendMtx;
};
#endif // CONNECTION_H
#endif // defined(__unix__)