Service Interface

Overview

The middleware framework enables communication between processes running on the same core and on a different core. For service-based communication, generated code provides three integration points:

  • <ServiceName>Proxy.h for clients (service consumers).

  • <ServiceName>Skeleton.h for providers (service servers).

  • <ServiceName>Common.h with shared data types and generated identifiers.

For unit and component tests, generation can additionally provide:

  • <ServiceName>ProxyMock.h with a GoogleMock-based proxy replacement.

  • <ServiceName>SkeletonMock.h with a GoogleMock-based skeleton replacement.

This chapter explains recommended middleware usage for application code that uses proxy and skeleton APIs.

Communication Patterns

Middleware supports three communication patterns. Their exact API details are generated from the service model.

Middleware communication patterns

Communication pattern

SOME/IP equivalent

Description

methods

method

The client sends a request and the server returns a response. FireAndForget methods are one-way requests without a response.

broadcasts

events

The server sends an event to all subscribed clients. The server does not get any feedback about message reception.

attributes

fields

The server stores an attribute value. Clients can read and/or write it based on model configuration. Attribute updates can also be sent as notifications to subscribed clients.

Key Terminology

  • Cluster: middleware routes messages between application instances based on their configured Application Cluster. Proxies and skeletons are initialized with generated instance identifiers plus cluster context.

  • Connected: for static service mapping, a proxy or skeleton is considered connected when init(...) returns a successful registration result (normally ::middleware::core::HRESULT::Ok). An already-registered result is also treated as initialized by the generated proxy. If init(...) fails, do not call communication APIs.

  • Subscribed: a client subscribes to a broadcast or attribute updates by registering a receive handler on the generated proxy event/attribute object.

  • Method: request/response RPC from proxy to skeleton.

  • FireAndForget: one-way method from proxy to skeleton with no response callback.

  • Event (broadcast): push-style update from skeleton to all subscribed proxies.

  • Attribute: stateful value on the skeleton side with generated getter/setter and optional update notifications.

Compile-Check and Runnable Integration Strategy

This guide uses snippets from compile-checked sources under libs/bsw/middleware/doc/examples. Code examples demonstrating middleware API usage are available in the examples directory.

For runtime validation, integrate the same generated API calls in a component runnable and execute them in your target-specific integration tests.

Proxy Use Cases (Client Application)

Instantiation and Initialization

Proxy classes provide two lifecycle methods:

  • init initializes middleware state and registers handlers.

  • deInit unregisters the proxy from its cluster connection and clears pending method and attribute-getter futures, including their callbacks. It also clears registered event and attribute receive handlers through the proxy’s internal notification-handler cleanup.

init returns ::middleware::core::HRESULT. Continue if the return value is a successful registration result, normally ::middleware::core::HRESULT::Ok or ::middleware::core::HRESULT::InstanceAlreadyRegistered.

Proxy init requires:

  • InstanceId: target server instance.

  • ClusterId: source application cluster of the client.

Code Example for Client Side

The following example shows proxy startup and shutdown.

class ProxyApp : public features::communication::DummyService::proxy::DummyServiceProxy
{
public:
    using Base                 = features::communication::DummyService::proxy::DummyServiceProxy;
    using Foo                  = features::communication::DummyService::Foo;
    using Baz                  = features::communication::DummyService::Baz;
    using MwInstanceId         = features::communication::DummyService::internal::InstanceId;
    using FireAndForgetPayload = features::communication::DummyService::FireAndForgetPayload;
    using AttributeType
        = features::communication::DummyService::proxy::SimpleFieldAttribute::AttributeType;
    using AsyncMethodResult
        = features::communication::DummyService::proxy::DummyServiceProxy::AsyncMethodResult;
    using AsyncMethodCallback
        = features::communication::DummyService::proxy::DummyServiceProxy::AsyncMethodCallback;
    using AttributeGetterCallback
        = features::communication::DummyService::proxy::SimpleFieldAttribute::GetterCallback;
    using AttributeGetterResult
        = features::communication::DummyService::proxy::SimpleFieldAttribute::GetterResult;
    using AttributeReceiveCallback = features::communication::DummyService::proxy::
        SimpleFieldAttribute::OnFieldChangedCallback;
    using EventReceiveCallback = features::communication::DummyService::proxy::
        SimpleBroadcastEvent::OnFieldChangedCallback;
    using MwResult = etl::expected<uint16_t, ::middleware::core::HRESULT>;

    void startup()
    {
        if (init())
        {
            setEventReceiveHandler();
            setAttributeReceiveHandler();
        }
    }

    void shutdown() { Base::deInit(); }

    // [service-proxy-method-start]
    void runAsyncMethod(Foo const& input)
    {
        MwResult const result = Base::asyncMethod(
            input, etl::make_delegate<ProxyApp, &ProxyApp::asyncMethodResponse_>(*this));
        currentActiveRequestId_
            = result.has_value() ? etl::optional<uint16_t>{result.value()} : etl::nullopt;
    }

    // [service-proxy-method-end]

    // [service-proxy-fire-and-forget-start]
    MwResult runFireAndForgetMethod(FireAndForgetPayload const& payload)
    {
        return Base::fireAndForgetMethod(payload);
    }

    // [service-proxy-fire-and-forget-end]

    // [service-proxy-attribute-read-start]
    MwResult runAttributeGet()
    {
        return this->simpleField.get(
            etl::make_delegate<ProxyApp, &ProxyApp::attributeGetterResponse_>(*this));
    }

    // [service-proxy-attribute-read-end]

    // [service-proxy-attribute-write-start]
    MwResult runAttributeSet(uint32_t const value) { return this->simpleField.set(value); }

    // [service-proxy-attribute-write-end]

    [[nodiscard]] uint32_t receivedAttributeValue() const { return receivedAttributeValue_; }

    [[nodiscard]] uint32_t receivedEventValue() const { return receivedEventValue_; }

    [[nodiscard]] bool isRequestIdActive() const { return currentActiveRequestId_.has_value(); }

    [[nodiscard]] uint16_t getRequestIdActive() const { return currentActiveRequestId_.value(); }

    [[nodiscard]] bool isResponseValid() const { return latestResult_.has_value(); }

    [[nodiscard]] Baz getResponseValue() const { return latestResult_.value(); }

private:
    bool init()
    {
        return Base::init(MwInstanceId::InstanceId_1, ::middleware::core::ClusterId::Core1)
                   == ::middleware::core::HRESULT::Ok
               || Base::isInitialized();
    }

    // [service-attribute-subscription-start]
    void setAttributeReceiveHandler()
    {
        this->simpleField.setReceiveHandler(
            etl::make_delegate<ProxyApp, &ProxyApp::attributeChanged_>(*this));
    }

    // [service-attribute-subscription-end]

    // [service-proxy-method-subscription-start]
    void setEventReceiveHandler()
    {
        this->simpleBroadcast.setReceiveHandler(
            etl::make_delegate<ProxyApp, &ProxyApp::eventReceived_>(*this));
    }

    // [service-proxy-method-subscription-end]

    void asyncMethodResponse_(AsyncMethodResult const& result)
    {
        latestResult_
            = result.has_value() ? etl::optional<Baz>{result.value().get()} : etl::nullopt;
        currentActiveRequestId_ = etl::nullopt;
    }

    void attributeGetterResponse_(AttributeGetterResult const& result)
    {
        if (result.has_value())
        {
            receivedAttributeValue_ = result.value().get();
        }
    }

    // [service-proxy-attribute-subscription-callback-start]
    void attributeChanged_(uint32_t const& value) { receivedAttributeValue_ = value; }

    // [service-proxy-attribute-subscription-callback-end]

    // [service-proxy-broadcast-callback-start]
    void eventReceived_(uint32_t const& value) { receivedEventValue_ = value; }

    // [service-proxy-broadcast-callback-end]

    etl::optional<uint16_t> currentActiveRequestId_;
    etl::optional<Baz> latestResult_;
    uint32_t receivedAttributeValue_{};
    uint32_t receivedEventValue_{};
};

Triggering and Receiving Method Calls as a Proxy

Relevant proxy-side behavior:

  • Generated method calls return ::etl::expected<uint16_t, ::middleware::core::HRESULT>. On success, the value is the request identifier. On failure, the error code explains why the request was rejected immediately.

  • Request/response methods require a callback with signature etl::expected<etl::reference_wrapper<MethodOutputType const>, ::middleware::core::Future::State>, also exposed as the generated <MethodName>Result alias. The callback receives either a const reference to the payload data or an asynchronous failure state.

  • FireAndForget methods do not have response callbacks.

The example below covers asynchronous and FireAndForget method calls:

    void runAsyncMethod(Foo const& input)
    {
        MwResult const result = Base::asyncMethod(
            input, etl::make_delegate<ProxyApp, &ProxyApp::asyncMethodResponse_>(*this));
        currentActiveRequestId_
            = result.has_value() ? etl::optional<uint16_t>{result.value()} : etl::nullopt;
    }
    MwResult runFireAndForgetMethod(FireAndForgetPayload const& payload)
    {
        return Base::fireAndForgetMethod(payload);
    }

Testing Method Requests as a Proxy

Generated proxy mocks let a client test verify the request arguments, immediate request identifier, and later response callback without a live transport.

TEST_F(ProxyAppTestFixture, AsyncMethodForwardsToMock)
{
    // ARRANGE
    AsyncMethodCallback asyncMethodCb;
    Foo input{1U, 2U};
    uint16_t const kExpectedRequestId = 5U;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, asyncMethod(::testing::_, ::testing::_))
        .WillOnce(::testing::DoAll(
            ::testing::SaveArg<1>(&asyncMethodCb),
            ::testing::Return(
                etl::expected<uint16_t, middleware::core::HRESULT>{kExpectedRequestId})));

    // ACT
    app_.startup();
    app_.runAsyncMethod(input);

    // ASSERT
    EXPECT_TRUE(app_.isRequestIdActive());
    EXPECT_EQ(app_.getRequestIdActive(), kExpectedRequestId);

    // ARRANGE
    Baz resultPayload{3U, 4U, 0U, 1U};
    AsyncMethodResult result{etl::reference_wrapper<Baz const>(resultPayload)};

    // ACT
    asyncMethodCb(result); // We trigger our internal callback here.

    // ASSERT
    EXPECT_TRUE(app_.isResponseValid());
    EXPECT_EQ(app_.getResponseValue().a, resultPayload.a);
    EXPECT_EQ(app_.getResponseValue().b, resultPayload.b);
    EXPECT_EQ(app_.getResponseValue().c, resultPayload.c);
    EXPECT_EQ(app_.getResponseValue().d, resultPayload.d);
}
TEST_F(ProxyAppTestFixture, FireAndForgetMethodForwardsToMock)
{
    // ARRANGE
    ProxyApp::FireAndForgetPayload payload{{1U, 2U}, {3U, 4U}};
    uint16_t const kExpectedRequestId = 5U;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, fireAndForgetMethod(::testing::Ref(payload)))
        .WillOnce(testing::Return(
            etl::expected<uint16_t, middleware::core::HRESULT>{kExpectedRequestId}));

    // ACT
    app_.startup();
    ProxyApp::ProxyApp::MwResult const result = app_.runFireAndForgetMethod(payload);

    // ASSERT
    ASSERT_TRUE(result.has_value());
    EXPECT_EQ(result.value(), kExpectedRequestId);
}

Subscribing and Receiving Events as a Proxy

For proxy usage, event subscription means registering a receive handler on the generated event object. After registration, each received event triggers the callback.

  • setReceiveHandler(...) for event notifications.

    void setEventReceiveHandler()
    {
        this->simpleBroadcast.setReceiveHandler(
            etl::make_delegate<ProxyApp, &ProxyApp::eventReceived_>(*this));
    }
    void eventReceived_(uint32_t const& value) { receivedEventValue_ = value; }

Testing Event Subscriptions as a Proxy

The proxy mock captures the registered handler so the test can simulate an incoming event and verify that the application receives its payload.

TEST_F(ProxyAppTestFixture, EventReceiveForwardsToMock)
{
    // ARRANGE
    ProxyApp::EventReceiveCallback eventCallback;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleBroadcast, setReceiveHandler(::testing::_))
        .WillOnce(::testing::SaveArg<0>(&eventCallback));

    uint32_t const eventValue = 99U;

    // ACT
    app_.startup();
    eventCallback(eventValue);

    // ASSERT
    EXPECT_EQ(app_.receivedEventValue(), eventValue);
}

Reading and Writing Attributes as a Proxy

Generated proxy attributes usually provide:

  • get(callback) for asynchronous reads.

    MwResult runAttributeGet()
    {
        return this->simpleField.get(
            etl::make_delegate<ProxyApp, &ProxyApp::attributeGetterResponse_>(*this));
    }
  • set(value) for writes, if the model enables writing.

    MwResult runAttributeSet(uint32_t const value) { return this->simpleField.set(value); }
  • setReceiveHandler(...) for update notifications. The application must explicitly send an attribute notification. Changing the local value alone does not notify subscribers.

  • unsetReceiveHandler() to remove an event or attribute receive handler.

    void setAttributeReceiveHandler()
    {
        this->simpleField.setReceiveHandler(
            etl::make_delegate<ProxyApp, &ProxyApp::attributeChanged_>(*this));
    }
    void attributeChanged_(uint32_t const& value) { receivedAttributeValue_ = value; }

Testing Attribute Getters as a Proxy

The getter test captures the generated callback, checks the request identifier, and then simulates the asynchronous attribute result.

TEST_F(ProxyAppTestFixture, AttributeGetterForwardsToMock)
{
    // ARRANGE
    ProxyApp::AttributeGetterCallback getterCallback;
    uint32_t const attributeValue           = 42U;
    uint16_t const kExpectedGetterRequestId = 6U;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleField, get(::testing::_))
        .WillOnce(::testing::DoAll(
            ::testing::SaveArg<0>(&getterCallback),
            ::testing::Return(
                etl::expected<uint16_t, middleware::core::HRESULT>{kExpectedGetterRequestId})));

    // ACT
    app_.startup();
    ProxyApp::ProxyApp::MwResult const getterResult = app_.runAttributeGet();

    // ASSERT
    ASSERT_TRUE(getterResult.has_value());
    EXPECT_EQ(getterResult.value(), kExpectedGetterRequestId);

    // ARRANGE
    ProxyApp::AttributeGetterResult const attributeResult{
        etl::reference_wrapper<uint32_t const>(attributeValue)};

    // ACT
    getterCallback(attributeResult);

    // ASSERT
    ASSERT_TRUE(attributeResult.has_value());
    EXPECT_EQ(attributeResult.value().get(), attributeValue);
    EXPECT_EQ(app_.receivedAttributeValue(), attributeValue);
}

Testing Attribute Setters as a Proxy

The setter test verifies that the value and immediate request result are forwarded through the generated proxy mock.

TEST_F(ProxyAppTestFixture, AttributeSetterForwardsToMock)
{
    // ARRANGE
    uint16_t const kExpectedSetterRequestId = 7U;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleField, set(42U))
        .WillOnce(testing::Return(
            etl::expected<uint16_t, middleware::core::HRESULT>{kExpectedSetterRequestId}));

    // ACT
    app_.startup();
    ProxyApp::ProxyApp::MwResult const setterResult = app_.runAttributeSet(42U);

    // ASSERT
    ASSERT_TRUE(setterResult.has_value());
    EXPECT_EQ(setterResult.value(), kExpectedSetterRequestId);
}

Testing Attribute Notifications as a Proxy

The notification test captures the registered handler, triggers it with a simulated value, and checks the application state.

TEST_F(ProxyAppTestFixture, AttributeReceiveForwardsToMock)
{
    // ARRANGE
    ProxyApp::AttributeReceiveCallback attributeCallback;
    EXPECT_CALL(
        mock_,
        init(
            features::communication::DummyService::internal::InstanceId_1,
            middleware::core::ClusterId::Core1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleField, setReceiveHandler(::testing::_))
        .WillOnce(::testing::SaveArg<0>(&attributeCallback));

    uint32_t const attributeValue = 42U;

    // ACT
    app_.startup();
    attributeCallback(attributeValue);

    // ASSERT
    EXPECT_EQ(app_.receivedAttributeValue(), attributeValue);
}

Skeleton Use Cases (Server Application)

Code Example for Server Side

Skeleton classes are server-side adapters. The application derives from the skeleton class, implements generated virtual methods, and answers method requests.

Skeleton init receives only InstanceId (the server instance provided by this application).

The following example shows skeleton startup and shutdown.

class SkeletonApp : public features::communication::DummyService::skeleton::DummyServiceSkeleton
{
public:
    using Base = features::communication::DummyService::skeleton::DummyServiceSkeleton;
    using Foo  = features::communication::DummyService::Foo;
    using Baz  = features::communication::DummyService::Baz;
    using FireAndForgetPayload = features::communication::DummyService::FireAndForgetPayload;
    using MwInstanceId         = features::communication::DummyService::internal::InstanceId;
    using SkeletonResponseInfo = ::middleware::core::ResponseBufferBase::SkeletonResponseInfo;

    bool init()
    {
        return Base::init(MwInstanceId::InstanceId_1) == ::middleware::core::HRESULT::Ok;
    }

    void shutdown() { Base::deInit(); }

    // [service-skeleton-method-start]
    void asyncMethod([[maybe_unused]] Foo const& input, SkeletonResponseInfo& response) override
    {
        Base::asyncMethod(input, response);
        pendingAsyncResponse_ = &response;
    }

    // [service-skeleton-method-end]

    // [service-skeleton-fire-and-forget-method-start]
    void fireAndForgetMethod(FireAndForgetPayload const& payload) override
    {
        Base::fireAndForgetMethod(payload);
    }

    // [service-skeleton-fire-and-forget-method-end]

    // [service-skeleton-execute-start]
    void execute()
    {
        if (Base::isInitialized() && pendingAsyncResponse_ != nullptr)
        {
            [[maybe_unused]] ::middleware::core::HRESULT const result = Base::respondAsyncMethod(
                *pendingAsyncResponse_, features::communication::DummyService::Baz{9U, 8U, 7U, 6U});
            pendingAsyncResponse_ = nullptr;
        }
    }

    // [service-skeleton-execute-end]

    // [service-skeleton-broadcast-start]
    void publishBroadcast()
    {
        if (Base::isInitialized())
        {
            [[maybe_unused]] ::middleware::core::HRESULT const result
                = this->simpleBroadcast.send(BROADCAST_VALUE);
        }
    }

    // [service-skeleton-broadcast-end]

    // [service-skeleton-attribute-broadcast-start]
    void publishAttribute(uint32_t const value)
    {
        if (Base::isInitialized())
        {
            this->simpleField.set(value);
            [[maybe_unused]] ::middleware::core::HRESULT const result = this->simpleField.send();
        }
    }

    // [service-skeleton-attribute-broadcast-end]

    // [service-skeleton-attribute-get-start]
    void getSimpleFieldAttribute(SkeletonResponseInfo& response) override
    {
        Base::getSimpleFieldAttribute(response);
    }

    // [service-skeleton-attribute-get-end]

    // [service-skeleton-attribute-set-start]
    void setSimpleFieldAttribute(uint32_t const& value) override
    {
        Base::setSimpleFieldAttribute(value);
    }

    // [service-skeleton-attribute-set-end]

private:
    SkeletonResponseInfo* pendingAsyncResponse_{};
};

For skeleton-side methods:

  • Each generated method is a virtual function to implement in the derived application class.

  • Request/response methods receive input arguments plus SkeletonResponseInfo.

  • respond<MethodName>(...) must be called exactly once for request/response methods (immediately or later).

  • For deferred responses, store SkeletonResponseInfo and respond in a later cycle.

  • If no response can be sent, call the generated cancel...Response(...) API to release the pending request.

Handling Method Requests as a Skeleton

  • Asynchronous request/response methods.

    void asyncMethod([[maybe_unused]] Foo const& input, SkeletonResponseInfo& response) override
    {
        Base::asyncMethod(input, response);
        pendingAsyncResponse_ = &response;
    }
    void execute()
    {
        if (Base::isInitialized() && pendingAsyncResponse_ != nullptr)
        {
            [[maybe_unused]] ::middleware::core::HRESULT const result = Base::respondAsyncMethod(
                *pendingAsyncResponse_, features::communication::DummyService::Baz{9U, 8U, 7U, 6U});
            pendingAsyncResponse_ = nullptr;
        }
    }
  • Fire-and-Forget methods.

    void fireAndForgetMethod(FireAndForgetPayload const& payload) override
    {
        Base::fireAndForgetMethod(payload);
    }

Testing Method Requests as a Skeleton

The skeleton mock verifies initialization and forwarding of request/response and fire-and-forget calls from the derived application class.

TEST_F(SkeletonAppTestFixture, SkeletonAsyncMethodForwardsToMock)
{
    // ARRANGE
    SkeletonResponseInfo response{};
    Foo input{1U, 2U};
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, asyncMethod(::testing::Ref(input), ::testing::Ref(response)));
    EXPECT_CALL(
        mock_,
        respondAsyncMethod(
            ::testing::Ref(response),
            ::testing::Matcher<SkeletonApp::Baz const&>(::testing::Truly(
                [](SkeletonApp::Baz const& result)
                { return result.a == 9U && result.b == 8U && result.c == 7U && result.d == 6U; })),
            true))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));

    // ACT
    bool const initResult = app_.init();
    app_.asyncMethod(input, response);
    app_.execute();

    // ASSERT
    EXPECT_TRUE(initResult);
}
TEST_F(SkeletonAppTestFixture, SkeletonFireAndForgetMethodForwardsToMock)
{
    // ARRANGE
    features::communication::DummyService::FireAndForgetPayload payload{{1U, 2U}, {3U, 4U}};
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, fireAndForgetMethod(::testing::Ref(payload)));

    // ACT
    bool const initResult = app_.init();
    app_.fireAndForgetMethod(payload);

    // ASSERT
    EXPECT_TRUE(initResult);
}

Publishing Broadcasts/Events as a Skeleton

Skeleton broadcasts are generated as <broadcastName> event objects with send(payload). All connected and subscribed proxies receive the event.

    void publishBroadcast()
    {
        if (Base::isInitialized())
        {
            [[maybe_unused]] ::middleware::core::HRESULT const result
                = this->simpleBroadcast.send(BROADCAST_VALUE);
        }
    }

Testing Event Publishing as a Skeleton

The skeleton mock verifies that an application event is sent with the expected payload and return status.

TEST_F(SkeletonAppTestFixture, SkeletonEventForwardsToMock)
{
    // ARRANGE
    uint32_t const broadcastValue = 99U;
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleBroadcast, send(broadcastValue))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));

    // ACT
    bool const initResult                           = app_.init();
    middleware::core::HRESULT const broadcastResult = app_.simpleBroadcast.send(broadcastValue);

    // ASSERT
    EXPECT_TRUE(initResult);
    EXPECT_EQ(broadcastResult, middleware::core::HRESULT::Ok);
}

Publishing and Handling Attributes/Events as a Skeleton

On the skeleton side, generated attributes expose:

  • get() for local state access. Optionally overrides for generated get request handlers from proxies.

    void getSimpleFieldAttribute(SkeletonResponseInfo& response) override
    {
        Base::getSimpleFieldAttribute(response);
    }
  • set(value) for local state access. Optionally overrides for generated set request handlers from proxies.

    void setSimpleFieldAttribute(uint32_t const& value) override
    {
        Base::setSimpleFieldAttribute(value);
    }
  • send() to notify subscribed proxies about the current attribute value.

    void publishAttribute(uint32_t const value)
    {
        if (Base::isInitialized())
        {
            this->simpleField.set(value);
            [[maybe_unused]] ::middleware::core::HRESULT const result = this->simpleField.send();
        }
    }

Testing Attribute Getters and Setters as a Skeleton

The skeleton mock verifies that generated attribute getter and setter requests reach the corresponding application overrides.

TEST_F(SkeletonAppTestFixture, SkeletonAttributeGetterForwardsToMock)
{
    // ARRANGE
    SkeletonResponseInfo response{};
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, getSimpleFieldAttribute(::testing::Ref(response)));

    // ACT
    bool const initResult = app_.init();
    app_.getSimpleFieldAttribute(response);

    // ASSERT
    EXPECT_TRUE(initResult);
}
TEST_F(SkeletonAppTestFixture, SkeletonAttributeSetterForwardsToMock)
{
    // ARRANGE
    uint32_t const value = 42U;
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_, setSimpleFieldAttribute(::testing::Ref(value)));

    // ACT
    bool const initResult = app_.init();
    app_.setSimpleFieldAttribute(value);

    // ASSERT
    EXPECT_TRUE(initResult);
}

Testing Attribute Publishing as a Skeleton

The attribute send test verifies that the application publishes the expected attribute value and handles the generated status result.

TEST_F(SkeletonAppTestFixture, SkeletonAttributeEventForwardsToMock)
{
    // ARRANGE
    uint32_t const attributeValue = 42U;
    EXPECT_CALL(mock_, init(features::communication::DummyService::internal::InstanceId_1))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));
    EXPECT_CALL(mock_.simpleField, send(attributeValue))
        .WillOnce(testing::Return(middleware::core::HRESULT::Ok));

    // ACT
    bool const initResult                           = app_.init();
    middleware::core::HRESULT const attributeResult = app_.simpleField.send(attributeValue);

    // ASSERT
    EXPECT_TRUE(initResult);
    EXPECT_EQ(attributeResult, middleware::core::HRESULT::Ok);
}

Error Handling

The generated APIs use two error channels:

  • Immediate middleware return codes via ::middleware::core::HRESULT.

  • Asynchronous method completion states via ::middleware::core::Future::State.

Common HRESULT values for service APIs:

Typical HRESULT values in service APIs

Code

Meaning

Typical action

Ok

Operation accepted by middleware

Continue normal flow

ServiceNotFound

Target service instance not available

Verify InstanceId and startup order

ServiceBusy

Service temporarily cannot process more requests

Retry later or apply backoff

QueueFull

Transport queue is full

Retry later and review queue sizing/load

RequestPoolDepleted

No free request slots for new method calls

Reduce outstanding requests or increase configured capacity

FutureAlreadyInUse / FutureNotFound

Inconsistent request tracking state

Check callback/request lifecycle usage

Method callback completion states (Future::State):

Method callback states

State

Meaning

Ready

Response payload was received successfully and is available to the callback

Timeout

No response within configured timeout

UserError

Remote application reported an application-level error

ServiceBusy

Provider could not accept/process request resources

ServiceNotFound

Target provider not reachable

SerializationError / DeserializationError

Payload conversion failed

CouldNotDeliverError

Transport delivery failed

For asynchronous skeleton methods, ensure every received request is eventually completed by calling either respond<MethodName>(...) / respondGet<AttributeName>Attribute(...) / respondSet<AttributeName>Attribute(...) or the matching cancel...Response(...) API.