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.hfor clients (service consumers).<ServiceName>Skeleton.hfor providers (service servers).<ServiceName>Common.hwith shared data types and generated identifiers.
For unit and component tests, generation can additionally provide:
<ServiceName>ProxyMock.hwith a GoogleMock-based proxy replacement.<ServiceName>SkeletonMock.hwith 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.
Communication pattern |
SOME/IP equivalent |
Description |
|---|---|---|
methods |
method |
The client sends a request and the server returns 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. Ifinit(...)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:
initinitializes middleware state and registers handlers.deInitunregisters 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>Resultalias. The callback receives either a const reference to the payload data or an asynchronous failure state.FireAndForgetmethods 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
SkeletonResponseInfoand 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:
Code |
Meaning |
Typical action |
|---|---|---|
|
Operation accepted by middleware |
Continue normal flow |
|
Target service instance not available |
Verify |
|
Service temporarily cannot process more requests |
Retry later or apply backoff |
|
Transport queue is full |
Retry later and review queue sizing/load |
|
No free request slots for new method calls |
Reduce outstanding requests or increase configured capacity |
|
Inconsistent request tracking state |
Check callback/request lifecycle usage |
Method callback completion states (Future::State):
State |
Meaning |
|---|---|
|
Response payload was received successfully and is available to the callback |
|
No response within configured timeout |
|
Remote application reported an application-level error |
|
Provider could not accept/process request resources |
|
Target provider not reachable |
|
Payload conversion failed |
|
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.