Skip to content

Latest commit

 

History

History
836 lines (664 loc) · 21.9 KB

File metadata and controls

836 lines (664 loc) · 21.9 KB

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

  1. Quick Integration
  2. Library Integration
  3. CMake Integration
  4. Safety-Oriented Integration
  5. Transport Layer Integration
  6. Service Development
  7. Testing Integration
  8. Deployment Considerations
  9. 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

  1. 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
  2. Link in your application:

    // main.cpp
    #include "someip/message.h"
    
    int main() {
        Message msg(MessageId(0x1000, 0x0001), RequestId(0x1234, 0x5678));
        return 0;
    }
  3. Compile and link:

    clang++ -std=c++17 -I/path/to/someip/include \
        main.cpp \
        -L/path/to/someip/lib \
        -lsomeip-common -lsomeip-serialization \
        -o my_app

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

find_package(SomeIP REQUIRED)
target_link_libraries(my_app SomeIP::someip-common)

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() returns nullptr.
  • 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.