Integration Guide¶
This guide provides comprehensive instructions for integrating the SOME/IP stack into your projects, whether as a static library, dynamic library, or embedded component.
Table of Contents¶
- Quick Integration
- Library Integration
- CMake Integration
- Safety-Oriented Integration
- Transport Layer Integration
- Service Development
- Testing Integration
- Deployment Considerations
- Troubleshooting
Quick Integration¶
Minimal Example¶
// Include headers
#include "someip/message.h"
#include "serialization/serializer.h"
// Create and use a message
MessageId msg_id(0x1000, 0x0001);
Message msg(msg_id, RequestId(0x1234, 0x5678));
// Serialize data
Serializer serializer;
serializer.serialize_uint32(42);
msg.set_payload(serializer.get_buffer());
// Process message
auto data = msg.serialize();
std::cout << "Message: " << msg.to_string() << std::endl;
Build Integration¶
# Add to your build system
clang++ -std=c++17 -I/path/to/someip/include \
-L/path/to/someip/lib -lsomeip-common \
your_code.cpp -o your_app
Library Integration¶
Static Library Linking¶
-
Build the SOME/IP libraries:
# Build core components clang++ -std=c++17 -Iinclude -c src/common/result.cpp clang++ -std=c++17 -Iinclude -c src/someip/types.cpp ar rcs libsomeip-common.a result.o types.o # Build serialization clang++ -std=c++17 -Iinclude -c src/serialization/serializer.cpp ar rcs libsomeip-serialization.a serializer.o -
Link in your application:
-
Compile and link:
Dynamic Library Linking¶
# Build shared libraries
clang++ -std=c++17 -fPIC -Iinclude -c src/common/result.cpp
clang++ -std=c++17 -fPIC -Iinclude -c src/someip/types.cpp
clang++ -shared -o libsomeip-common.so result.o types.o
# Link dynamically
clang++ -std=c++17 -Iinclude main.cpp -L. -lsomeip-common -o my_app
# Set library path at runtime
export LD_LIBRARY_PATH=/path/to/someip/lib:$LD_LIBRARY_PATH
./my_app
Header-Only Integration¶
For minimal deployments, you can copy the header files and implement only what you need:
// Copy these headers to your project
#include "someip/types.h" // Basic types and enums
#include "common/result.h" // Error handling
// Implement serialization as needed
CMake Integration¶
Add as Subdirectory¶
cmake_minimum_required(VERSION 3.20)
project(my_project)
add_subdirectory(vendor/opensomeip)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE someip-transport)
Lightweight Integration¶
By default, OpenSOME/IP builds tests (which downloads GoogleTest), examples, and registers development tool targets (clang-tidy, clang-format, traceability). When integrating the library into your own project, you can disable everything you don't need:
cmake_minimum_required(VERSION 3.20)
project(my_project)
# Disable tests (avoids GoogleTest download), examples, and dev tools
set(BUILD_TESTS OFF CACHE BOOL "")
set(BUILD_EXAMPLES OFF CACHE BOOL "")
set(SOMEIP_DEV_TOOLS OFF CACHE BOOL "")
add_subdirectory(vendor/opensomeip)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE someip-transport)
| Option | Default | What it controls |
|---|---|---|
BUILD_TESTS |
ON |
Unit tests and GoogleTest download |
BUILD_EXAMPLES |
ON |
Example programs |
SOMEIP_DEV_TOOLS |
ON |
Static analysis, formatting, and traceability targets |
SOMEIP_USE_FREERTOS |
OFF |
FreeRTOS kernel (FetchContent) |
SOMEIP_USE_THREADX |
OFF |
Eclipse ThreadX kernel (FetchContent) |
SOMEIP_USE_LWIP |
OFF |
lwIP networking stack (FetchContent) |
Platform backends (FreeRTOS, ThreadX, lwIP) are never fetched unless explicitly enabled. The Zephyr port uses a separate West/Zephyr build and is not part of the root CMake tree.
External Project¶
include(ExternalProject)
ExternalProject_Add(someip
GIT_REPOSITORY https://github.com/vtz/opensomeip.git
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/vendor
-DBUILD_TESTS=OFF
-DBUILD_EXAMPLES=OFF
-DSOMEIP_DEV_TOOLS=OFF
)
include_directories(${CMAKE_BINARY_DIR}/vendor/include)
link_directories(${CMAKE_BINARY_DIR}/vendor/lib)
Find Package¶
Safety-Oriented Integration (non-certified)¶
Safety Setup (optional)¶
// Enable available safety checks (non-certified)
#define SOMEIP_SAFETY_CHECKS
#include "someip/message.h"
// Operations now include additional safety checks
Message msg(MessageId(0x1000, 0x0001), RequestId(0x1234, 0x5678));
if (!msg.is_valid()) {
// Handle safety violation
safety_error_handler();
}
Fault Containment¶
// Implement fault containment boundaries
class SafeSomeIPHandler {
public:
Result process_message(const Message& msg) {
// Validate input
if (!msg.is_valid()) {
return Result::INVALID_MESSAGE;
}
// Process in safe context
try {
return do_process(msg);
} catch (const std::exception& e) {
// Log safety event
safety_logger.log_fault(e.what());
return Result::SAFETY_VIOLATION;
}
}
private:
Result do_process(const Message& msg) {
// Actual processing logic
return Result::SUCCESS;
}
};
Error Recovery¶
class SafeTransport : public ITransportListener {
public:
void on_error(Result error) override {
switch (error) {
case Result::NETWORK_ERROR:
// Attempt reconnection
initiate_recovery();
break;
case Result::SAFETY_VIOLATION:
// Enter safe state
enter_safe_state();
break;
default:
// Log and continue
logger.log_error(error);
break;
}
}
private:
void initiate_recovery() {
// Implement recovery logic
reconnect_transport();
resend_pending_messages();
}
void enter_safe_state() {
// Implement safe state logic
stop_all_operations();
notify_safety_monitor();
}
};
Transport Layer Integration¶
Receive Model: Listener vs Polling¶
Transports support two mutually exclusive receive modes:
| Mode | Activation | How messages arrive |
|---|---|---|
| Listener (async) | Call set_listener(ptr) with a non-null ITransportListener* |
on_message_received() callback on the transport's receive thread |
| Polling (sync) | Never call set_listener(), or pass nullptr |
Call receive_message() (or receive_message_with_sender() for UDP) |
Key rules:
- When a listener is installed, incoming messages are dispatched only to the listener. They are not enqueued, so
receive_message()returnsnullptr. - When no listener is set, incoming messages are enqueued for polling via
receive_message(). - Messages already queued before a listener is installed remain drainable via
receive_message(); only new arrivals go to the listener. - Clearing the listener (
set_listener(nullptr)) restores polling mode for subsequent messages. - Do not mix listener and polling for the same traffic. Prior to v0.2.0, messages were both enqueued and dispatched, causing memory leaks (POSIX) or pool exhaustion (FreeRTOS).
Listener Mode (recommended for most applications)¶
#include "transport/udp_transport.h"
class MyApplication : public ITransportListener {
public:
bool initialize() {
transport_ = std::make_unique<UdpTransport>(SOMEIP_DEFAULT_UDP_ENDPOINT);
transport_->set_listener(this);
return transport_->start() == Result::SUCCESS;
}
void send_message(const Message& msg) {
Endpoint server("192.168.1.100", 30490);
transport_->send_message(msg, server);
}
private:
void on_message_received(MessagePtr message, const Endpoint& sender) override {
process_message(*message);
}
void on_connection_lost(const Endpoint& endpoint) override {
handle_disconnect(endpoint);
}
void on_connection_established(const Endpoint& /*endpoint*/) override {}
void on_error(Result /*error*/) override {}
std::unique_ptr<UdpTransport> transport_;
};
Polling Mode (for simple request/response or event loops)¶
#include "transport/udp_transport.h"
// No listener — messages are enqueued for polling
UdpTransport transport(Endpoint{"127.0.0.1", 0});
transport.start();
// Send a request
transport.send_message(request, server_endpoint);
// Poll for response
while (running) {
MessagePtr msg = transport.receive_message();
if (msg) {
process(msg);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// UDP-specific: poll with sender endpoint for reply addressing
Endpoint sender;
MessagePtr msg = transport.receive_message_with_sender(sender);
if (msg) {
// sender contains the originating address/port
transport.send_message(reply, sender);
}
Custom Transport Implementation¶
class CustomTransport : public ITransport {
public:
Result send_message(const Message& message, const Endpoint& endpoint) override {
// Implement custom transport logic
return send_via_custom_protocol(message, endpoint);
}
MessagePtr receive_message() override {
// Implement custom receive logic
return receive_via_custom_protocol();
}
// ... other ITransport methods
};
Service Development¶
Service Definition¶
// Service constants
static constexpr uint16_t MY_SERVICE_ID = 0x2000;
static constexpr uint16_t ECHO_METHOD_ID = 0x0001;
static constexpr uint16_t CALCULATE_METHOD_ID = 0x0002;
// Request/Response structures
struct EchoRequest {
std::string message;
};
struct EchoResponse {
std::string echo_message;
uint32_t timestamp;
};
struct CalculateRequest {
uint32_t operand1;
uint32_t operand2;
std::string operation;
};
struct CalculateResponse {
uint32_t result;
bool success;
};
Service Implementation¶
class CalculatorService {
public:
MessagePtr handle_method_call(const Message& request) {
switch (request.get_method_id()) {
case ECHO_METHOD_ID:
return handle_echo(request);
case CALCULATE_METHOD_ID:
return handle_calculate(request);
default:
return create_error_response(request, ReturnCode::E_UNKNOWN_METHOD);
}
}
private:
MessagePtr handle_echo(const Message& request) {
// Deserialize request
Deserializer deserializer(request.get_payload());
std::string message = deserializer.deserialize_string();
// Create response
MessageId response_id(MY_SERVICE_ID, ECHO_METHOD_ID);
Message response(response_id, request.get_request_id(),
MessageType::RESPONSE, ReturnCode::E_OK);
// Serialize response
Serializer serializer;
serializer.serialize_string(message);
serializer.serialize_uint32(get_timestamp());
response.set_payload(serializer.get_buffer());
return std::make_shared<Message>(response);
}
MessagePtr handle_calculate(const Message& request) {
// Deserialize request
Deserializer deserializer(request.get_payload());
uint32_t op1 = deserializer.deserialize_uint32();
uint32_t op2 = deserializer.deserialize_uint32();
std::string operation = deserializer.deserialize_string();
// Perform calculation
uint32_t result = 0;
bool success = false;
if (operation == "add") {
result = op1 + op2;
success = true;
} else if (operation == "multiply") {
result = op1 * op2;
success = true;
}
// Create response
MessageId response_id(MY_SERVICE_ID, CALCULATE_METHOD_ID);
Message response(response_id, request.get_request_id(),
MessageType::RESPONSE, ReturnCode::E_OK);
// Serialize response
Serializer serializer;
serializer.serialize_uint32(result);
serializer.serialize_bool(success);
response.set_payload(serializer.get_buffer());
return std::make_shared<Message>(response);
}
MessagePtr create_error_response(const Message& request, ReturnCode error_code) {
MessageId response_id(request.get_service_id(), request.get_method_id());
Message response(response_id, request.get_request_id(),
MessageType::ERROR, error_code);
return std::make_shared<Message>(response);
}
uint32_t get_timestamp() {
return static_cast<uint32_t>(std::time(nullptr));
}
};
Client Implementation¶
class CalculatorClient {
public:
void call_echo(const std::string& message) {
MessageId msg_id(MY_SERVICE_ID, ECHO_METHOD_ID);
Message request(msg_id, RequestId(client_id_, next_session_id_++),
MessageType::REQUEST, ReturnCode::E_OK);
// Serialize request
Serializer serializer;
serializer.serialize_string(message);
request.set_payload(serializer.get_buffer());
// Send request (implementation depends on transport)
send_request(request);
}
void call_calculate(uint32_t op1, uint32_t op2, const std::string& operation) {
MessageId msg_id(MY_SERVICE_ID, CALCULATE_METHOD_ID);
Message request(msg_id, RequestId(client_id_, next_session_id_++),
MessageType::REQUEST, ReturnCode::E_OK);
// Serialize request
Serializer serializer;
serializer.serialize_uint32(op1);
serializer.serialize_uint32(op2);
serializer.serialize_string(operation);
request.set_payload(serializer.get_buffer());
// Send request
send_request(request);
}
private:
uint16_t client_id_ = 0x1000;
uint16_t next_session_id_ = 1;
};
Testing Integration¶
Unit Test Integration¶
#include <gtest/gtest.h>
#include "someip/message.h"
// Test fixture
class MessageTest : public ::testing::Test {
protected:
void SetUp() override {
// Setup test data
msg_id_ = MessageId(0x1000, 0x0001);
req_id_ = RequestId(0x1234, 0x5678);
}
MessageId msg_id_;
RequestId req_id_;
};
TEST_F(MessageTest, SerializationRoundTrip) {
Message original(msg_id_, req_id_);
// Serialize
auto data = original.serialize();
// Deserialize
Message deserialized;
ASSERT_TRUE(deserialized.deserialize(data));
// Verify
EXPECT_EQ(deserialized.get_service_id(), original.get_service_id());
EXPECT_EQ(deserialized.get_payload(), original.get_payload());
}
Integration Test Setup¶
class IntegrationTest : public ::testing::Test {
protected:
void SetUp() override {
// Start test transport
server_transport_ = std::make_unique<UdpTransport>(Endpoint("127.0.0.1", 0));
client_transport_ = std::make_unique<UdpTransport>(Endpoint("127.0.0.1", 0));
server_transport_->set_listener(&server_handler_);
client_transport_->set_listener(&client_handler_);
ASSERT_EQ(server_transport_->start(), Result::SUCCESS);
ASSERT_EQ(client_transport_->start(), Result::SUCCESS);
}
void TearDown() override {
server_transport_->stop();
client_transport_->stop();
}
std::unique_ptr<UdpTransport> server_transport_;
std::unique_ptr<UdpTransport> client_transport_;
TestMessageHandler server_handler_;
TestMessageHandler client_handler_;
};
Deployment Considerations¶
Memory Constraints¶
// For embedded systems, minimize allocations
class EmbeddedMessagePool {
public:
MessagePtr acquire() {
// Return from pool or create new
return pool_.empty() ? std::make_shared<Message>() : pool_.extract(pool_.begin()).value();
}
void release(MessagePtr msg) {
// Reset and return to pool
msg->set_payload(std::vector<uint8_t>()); // Clear payload
pool_.insert(std::move(msg));
}
private:
std::set<MessagePtr> pool_;
};
Threading Considerations¶
// Thread-safe message processing
class ThreadSafeProcessor {
public:
void process_message_async(MessagePtr message) {
std::lock_guard<std::mutex> lock(queue_mutex_);
message_queue_.push(message);
queue_cv_.notify_one();
}
private:
void processing_thread() {
while (running_) {
MessagePtr message;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
queue_cv_.wait(lock, [this]() {
return !message_queue_.empty() || !running_;
});
if (!running_) break;
message = message_queue_.front();
message_queue_.pop();
}
// Process message in thread context
process_message(*message);
}
}
std::mutex queue_mutex_;
std::condition_variable queue_cv_;
std::queue<MessagePtr> message_queue_;
std::atomic<bool> running_{true};
};
Performance Optimization¶
// Zero-copy message handling where possible
class ZeroCopyTransport {
public:
// Use shared buffers to avoid copying
using BufferPtr = std::shared_ptr<std::vector<uint8_t>>;
Result send_zero_copy(BufferPtr buffer, const Endpoint& endpoint) {
// Send buffer without copying
return send_buffer_directly(buffer, endpoint);
}
BufferPtr receive_zero_copy() {
// Receive into shared buffer
return receive_buffer_directly();
}
};
Troubleshooting¶
Common Integration Issues¶
Linker Errors¶
# Check library dependencies
nm -D libsomeip-common.so | grep "U "
# Ensure correct link order
clang++ ... -lsomeip-transport -lsomeip-common
Runtime Errors¶
// Enable detailed logging
#define SOMEIP_ENABLE_LOGGING
#include "common/logging.h"
// Check message validity before processing
if (!message.is_valid()) {
SOMEIP_LOG_ERROR("Invalid message received");
return Result::INVALID_MESSAGE;
}
Memory Issues¶
// Monitor memory usage
class MemoryMonitor {
public:
void check_memory_usage() {
// Implement memory monitoring
if (get_current_usage() > MAX_MEMORY) {
trigger_gc();
}
}
};
Threading Issues¶
// Use thread sanitizer
clang++ -fsanitize=thread -g ...
// Check for race conditions
std::mutex debug_mutex;
#define DEBUG_LOCK() std::lock_guard<std::mutex> lock(debug_mutex)
Debug Builds¶
# Build with debug information
cmake .. -DCMAKE_BUILD_TYPE=Debug \
-DSOMEIP_ENABLE_DEBUG=ON \
-DENABLE_LOGGING=ON
# Run with verbose output
export SOMEIP_LOG_LEVEL=DEBUG
./my_application
Performance Profiling¶
# Profile message processing
perf record -g ./my_application
perf report
# Memory profiling
valgrind --tool=massif ./my_application
ms_print massif.out.*
Best Practices¶
Code Organization¶
my_project/
├── include/
│ └── my_service.h # Service interfaces
├── src/
│ ├── main.cpp # Application entry point
│ ├── service_impl.cpp # Service implementations
│ └── transport_config.cpp # Transport configuration
├── vendor/
│ └── some-ip/ # SOME/IP stack
└── CMakeLists.txt
Error Handling¶
Result process_message(const Message& msg) {
// Validate first
if (!msg.is_valid()) {
return Result::INVALID_MESSAGE;
}
// Process with error checking
try {
return do_process(msg);
} catch (const std::exception& e) {
log_error(e.what());
return Result::INTERNAL_ERROR;
}
}
Resource Management¶
class SafeResourceManager {
public:
~SafeResourceManager() {
// Ensure clean shutdown
cleanup_resources();
}
Result allocate_resource() {
std::lock_guard<std::mutex> lock(resource_mutex_);
if (resources_.size() >= MAX_RESOURCES) {
return Result::RESOURCE_EXHAUSTED;
}
resources_.push_back(create_resource());
return Result::SUCCESS;
}
private:
std::mutex resource_mutex_;
std::vector<ResourcePtr> resources_;
};
Testing Strategy¶
// Unit tests for components
TEST(MessageTest, Serialization) { ... }
// Integration tests for workflows
TEST(IntegrationTest, ClientServerCommunication) { ... }
// Safety tests for critical functions
TEST(SafetyTest, FaultInjection) { ... }
// Performance tests for requirements
TEST(PerformanceTest, LatencyRequirements) { ... }
This integration guide provides a comprehensive foundation for incorporating the SOME/IP stack into your projects while maintaining safety-oriented practices; no safety certification is implied.