diff --git a/score/mw/com/design/events_fields/NestedCallbacks.svg b/score/mw/com/design/events_fields/NestedCallbacks.svg
old mode 100755
new mode 100644
diff --git a/score/mw/com/design/skeleton_proxy/README.md b/score/mw/com/design/skeleton_proxy/README.md
index 1ef4c8335..d8958e391 100644
--- a/score/mw/com/design/skeleton_proxy/README.md
+++ b/score/mw/com/design/skeleton_proxy/README.md
@@ -4,8 +4,8 @@
The following structural view shows, how the separation of generic/binding independent part of a proxy/skeleton from
its flexible/variable technical binding implementation is achieved. **Note**: It does **only** reflect the common use
-case of strongly typed proxies. The special case of "generic proxies" is described in
-[design extension for generic proxies](generic_proxy/README.md#) to not bloat this class diagram even more:
+case of strongly typed proxies and skeletons. The special case of "generic proxies" and "generic skeletons" are described in
+[design extension for generic proxies](generic_proxy/README.md#) and [design extension for generic skeletons](generic_skeleton/README.md#) to not bloat this class diagram even more:
@@ -225,6 +225,8 @@ aggregates an object of type `lola::ProxyEventCommon`, to which it dispatches al
calls, it has to implement to fulfill its interface `ProxyEventBindingBase`.
The reason for this architectural decision is described in the [design extension for generic proxies](./generic_proxy/README.md)
+Similarly, on the skeleton side, `lola::SkeletonEvent` aggregates an object of type `lola::SkeletonEventCommon`. This class encapsulates all `SampleTyp`e agnostic logic (such as interaction with `lola::Skeleton` for offering services, timestamp management, and notification handling). This allows both strongly typed skeletons and generic skeletons to share the same core implementation logic.
+
### Proxy auto-reconnect functionality
According to our requirements (namely requirement `SCR-29682823`), we need to support a functionality, which is known as
diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md
new file mode 100644
index 000000000..195ec2929
--- /dev/null
+++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/README.md
@@ -0,0 +1,86 @@
+# Generic Skeleton
+
+A `GenericSkeleton` is a provider-side (skeleton) object that can be configured at runtime to offer any service. Unlike standard, code-generated skeletons, it is not tied to a specific service interface at compile-time.
+
+It operates on raw data , making it ideal for applications that need to handle data without understanding its structure, such as:
+* **Gateways/Bridges**: Forwarding data between different communication protocols.
+
+## How to Use a Generic Skeleton
+
+Using a `GenericSkeleton` requires two steps: defining the service in the deployment configuration and creating the skeleton instance with its runtime configuration.
+
+### 1. Prerequisite: Deployment Configuration
+
+The service instance must be defined in the `mw_com_config.json` file, just like any other service.
+
+### 2. Creating and Using the Skeleton
+
+A `GenericSkeleton` is created by providing it with metadata about the service elements (e.g., events, fields) it will offer. This metadata, including memory size and alignment for each element, is passed via the `GenericSkeletonCreateParams` struct.
+
+The following example demonstrates how to create a generic skeleton that provides a single event.
+
+**Example: Creating a Generic Skeleton with an Event**
+
+```cpp
+#include "score/mw/com/impl/generic_skeleton.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
+#include
+
+// The service instance specifier, as defined in mw_com_config.json
+// const score::mw::com::InstanceSpecifier instance_specifier = ...;
+
+// 1. Define the metadata for the event.
+// The name must match the service definition.
+const auto event_name = "map_api_lanes_stamped";
+// The middleware needs the size and alignment for memory allocation.
+const score::mw::com::impl::DataTypeMetaInfo event_meta_info{
+ sizeof(MapApiLanesStamped),
+ alignof(MapApiLanesStamped)
+};
+
+// 2. Collect all event definitions.
+const std::vector events = {
+ {event_name, event_meta_info}
+};
+
+// 3. Populate the creation parameters.
+// Similar spans can be provided for fields and methods if they were supported.
+score::mw::com::impl::GenericSkeletonCreateParams create_params;
+create_params.events = events;
+
+// 4. Create the Generic Skeleton instance.
+auto create_result = score::mw::com::impl::GenericSkeleton::Create(
+ instance_specifier,
+ create_params
+);
+
+if (!create_result.has_value()) {
+ // Handle creation error
+ return;
+}
+auto& skeleton = create_result.value();
+
+// 5. Offer the service.
+skeleton.OfferService();
+
+// 6. Retrieve the event by name to send data.
+auto event_it = skeleton.GetEvents().find(event_name);
+if (event_it != skeleton.GetEvents().cend()) {
+ // Get a non-const reference to the event to call non-const methods.
+ auto& generic_event = const_cast(event_it->second);
+
+ // Allocate a sample from the middleware. The size is known from the metadata
+ auto allocate_result = generic_event.Allocate();
+ if (allocate_result.has_value()) {
+ auto sample = std::move(allocate_result.value());
+
+ // Get the raw pointer from the smart pointer for populating the data.
+ auto* typed_sample = static_cast(sample.Get());
+
+ // ... populate the typed_sample ...
+
+ // Send the sample.
+ generic_event.Send(std::move(sample));
+ }
+}
+```
\ No newline at end of file
diff --git a/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml
new file mode 100644
index 000000000..8ce3b5637
--- /dev/null
+++ b/score/mw/com/design/skeleton_proxy/generic_skeleton/generic_skeleton_model.puml
@@ -0,0 +1,183 @@
+@startuml generic_skeleton_model
+title "Generic Skeleton Extension"
+
+class "score::mw::com::impl::HandleType" {
+ -indentifier_: InstanceIdentifier
+ +operator==(const HandleType& other): bool
+ +operator<(const HandleType& other): bool
+ +GetInstanceId(): InstanceIdentifier&
+ +GetServiceInstanceDeployment(): ServiceInstanceDeployment&
+}
+
+class "SkeletonBindingFactory" {
+ +{static} Create(InstanceIdentifier identifier): std::unique_ptr
+}
+
+abstract class "score::mw::com::impl::SkeletonBase"
+{
+ -skeleton_binding_ : std::unique_ptr
+ -identifier_ : InstanceIdentifier
+ +GetAssociatedInstanceIdentifier() : const InstanceIdentifier&
+ +OfferService(): ResultBlank
+ +StopOfferService(): void
+ ..
+ Notes:
+ SkeletonBase is not copyable but moveable
+}
+
+abstract class "SkeletonBinding" {
+ +{abstract} PrepareOffer() = 0: ResultBlank
+ +{abstract} PrepareStopOffer() = 0: void
+}
+
+class "mw::com::impl::GenericSkeleton" #yellow {
+ using EventMap = ServiceElementMap
+ ..
+ +GenericSkeleton(const InstanceIdentifier&, std::unique_ptr, MethodCallProcessingMode)
+ +Create(const InstanceIdentifier&, const GenericSkeletonCreateParams&, MethodCallProcessingMode): Result
+ +Create(const InstanceSpecifier&, const GenericSkeletonCreateParams&, MethodCallProcessingMode): Result
+ +events_ : EventMap
+ +OfferService(): ResultBlank
+ +StopOfferService(): void
+}
+
+class "lola::Skeleton" {
+ +Skeleton(const InstanceIdentifier&, MethodCallProcessingMode)
+ +PrepareOffer(): ResultBlank
+ +PrepareStopOffer(): void
+ +Register(ElementFqId event_fqn, const SkeletonEventProperties& event_properties, std::size_t size, std::size_t alignment): std::pair, EventDataControlComposite>
+ +RegisterGeneric(ElementFqId event_fqn, const SkeletonEventProperties& event_properties, std::size_t size, std::size_t alignment): std::pair, EventDataControlComposite>
+ +DisconnectQmConsumers(): void
+ +GetInstanceQualityType(): QualityType
+ +GetSourcePid(): pid_t
+}
+
+class "mw::com::impl::SkeletonEvent" {
+ +Send(const SampleType&): ResultBlank
+ +Allocate(): Result>
+}
+
+abstract class "SkeletonEventBindingBase" {
+ +{abstract} PrepareOffer() = 0: ResultBlank
+ +{abstract} PrepareStopOffer() = 0: void
+ +{abstract} GetBindingType() = 0: BindingType
+ +{abstract} SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) = 0: void
+}
+
+abstract class "SkeletonEventBinding"
+{
+ +{abstract} Send(const SampleType&) = 0: ResultBlank
+ +{abstract} Allocate() = 0: Result>
+}
+
+class "SampleAllocateePtr" {
+ +Get(): SampleType*
+ +operator->(): SampleType*
+ +operator*(): SampleType&
+}
+
+class "lola::SkeletonEvent" {
+ +SkeletonEvent(lola::Skeleton& parent, const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn)
+ +Send(const SampleType&) : ResultBlank
+ +Allocate(): Result>
+ -event_shared_impl_ : lola::SkeletonEventCommon
+}
+
+class "GenericSkeletonEventBindingFactory" {
+ +{static} Create(SkeletonBase& parent, std::string_view event_name, const DataTypeMetaInfo& size_info): Result>
+}
+
+class "ServiceElementMap" {
+ using key_type = score::cpp::stringview
+ using mapped_type = GenericSkeletonEvent
+ using value_type = std::pair
+ using const_iterator = LegacyBidirectionalIterator to const value_type
+ ..
+ +cbegin() : const_iterator
+ +cend() : const_iterator
+ +find() : const_iterator
+ +size() : std::size_t
+ +empty() : bool
+}
+
+abstract class "mw::com::impl::SkeletonEventBase" #yellow {
+ +SkeletonEventBase(SkeletonBase&, const std::string_view, std::unique_ptr)
+ +OfferService(): ResultBlank
+ +StopOfferService(): void
+}
+
+class "mw::com::impl::GenericSkeletonEvent" #yellow {
+ +GenericSkeletonEvent(SkeletonBase&, const std::string_view, std::unique_ptr)
+ +Send(SampleAllocateePtr sample): ResultBlank
+ +Allocate(): Result>
+ +GetSizeInfo() const : DataTypeMetaInfo
+}
+
+abstract class "GenericSkeletonEventBinding" #yellow {
+ +{abstract} Send(SampleAllocateePtr sample) = 0: ResultBlank
+ +{abstract} Allocate() = 0: Result>
+ +{abstract} GetSizeInfo() const = 0: std::pair
+}
+
+class "lola::SkeletonEventCommon" #yellow {
+ -parent_: lola::Skeleton&
+ -element_fq_id_: ElementFqId
+ -control_: score::cpp::optional&
+ -current_timestamp_: EventSlotStatus::EventTimeStamp&
+ +SkeletonEventCommon(lola::Skeleton&, const ElementFqId&, score::cpp::optional&, EventSlotStatus::EventTimeStamp&, impl::tracing::SkeletonEventTracingData)
+ +PrepareOfferCommon(): void
+ +PrepareStopOfferCommon(): void
+ +GetParent(): lola::Skeleton&
+ +GetElementFQId(): const ElementFqId&
+ +IsQmNotificationsRegistered(): bool
+ +IsAsilBNotificationsRegistered(): bool
+ +GetTracingData(): impl::tracing::SkeletonEventTracingData&
+}
+
+class "lola::GenericSkeletonEvent" #yellow {
+ +GenericSkeletonEvent(lola::Skeleton& parent, const SkeletonEventProperties& event_properties, const ElementFqId& event_fqn, const DataTypeMetaInfo& size_info, impl::tracing::SkeletonEventTracingData tracing_data)
+ +Send(score::mw::com::impl::SampleAllocateePtr sample): ResultBlank
+ +Allocate(): Result>
+ +GetSizeInfo() const : std::pair
+ +PrepareOffer(): ResultBlank
+ +PrepareStopOffer(): void
+ +GetBindingType(): BindingType
+ +SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data): void
+ +GetMaxSize() const : std::size_t
+ -event_shared_impl_ : lola::SkeletonEventCommon
+}
+
+class "DummySkeleton" <> {
+ +DummySkeleton(const InstanceIdentifier&, MethodCallProcessingMode)
+ +DummyEvent : events::DummyEvent
+}
+
+' Relationships
+"score::mw::com::impl::SkeletonBase" *--> "SkeletonBinding"
+"score::mw::com::impl::SkeletonBase" <|-- "DummySkeleton"
+"score::mw::com::impl::SkeletonBase" <|-- "mw::com::impl::GenericSkeleton"
+"mw::com::impl::GenericSkeleton" *-- "ServiceElementMap"
+"SkeletonBindingFactory" ..> "SkeletonBinding" : creates
+"SkeletonBinding" <|-- "lola::Skeleton"
+
+"mw::com::impl::SkeletonEventBase" <|-- "mw::com::impl::SkeletonEvent"
+"mw::com::impl::SkeletonEventBase" <|-- "mw::com::impl::GenericSkeletonEvent"
+
+"SkeletonEventBindingBase" <|-- "SkeletonEventBinding"
+"SkeletonEventBindingBase" <|-- "GenericSkeletonEventBinding"
+
+"SkeletonEventBinding" <|-- "lola::SkeletonEvent"
+"GenericSkeletonEventBinding" <|-- "lola::GenericSkeletonEvent"
+
+"mw::com::impl::GenericSkeletonEvent" ..> "GenericSkeletonEventBindingFactory" : uses
+"mw::com::impl::SkeletonEvent" ..> "GenericSkeletonEventBindingFactory" : uses
+
+"lola::SkeletonEvent" *-- "lola::SkeletonEventCommon" : event_shared_impl_
+"lola::GenericSkeletonEvent" *-- "lola::SkeletonEventCommon" : event_shared_impl_
+
+"lola::SkeletonEventCommon" o-- "1" "lola::Skeleton"
+
+"DummySkeleton" *--> "mw::com::impl::SkeletonEvent" : "0..n"
+"mw::com::impl::GenericSkeleton" *--> "mw::com::impl::GenericSkeletonEvent" : "0..n"
+
+@enduml
\ No newline at end of file
diff --git a/score/mw/com/example/ipc_bridge/main.cpp b/score/mw/com/example/ipc_bridge/main.cpp
index 0c39e7146..5dea98cf7 100644
--- a/score/mw/com/example/ipc_bridge/main.cpp
+++ b/score/mw/com/example/ipc_bridge/main.cpp
@@ -51,7 +51,7 @@ Params ParseCommandLineArguments(const int argc, const char** argv)
"Number of cycles that are executed before determining success or failure. 0 indicates no limit.");
options.add_options()("mode,m",
po::value(),
- "Set to either send/skeleton or recv/proxy to determine the role of the process");
+ "Set to: send/skeleton (typed skeleton), gen_skeleton (generic skeleton) or recv/proxy to determine the role of the process");
options.add_options()("cycle-time,t", po::value(), "Cycle time in milliseconds for sending/polling");
options.add_options()(
"service_instance_manifest,s", po::value(), "Path to the com configuration file");
@@ -119,6 +119,10 @@ int main(const int argc, const char** argv)
{
return event_sender_receiver.RunAsSkeleton(instance_specifier, cycle_time, cycles);
}
+ else if (mode == "gen_skeleton")
+ {
+ return event_sender_receiver.RunAsGenericSkeleton(instance_specifier, cycle_time, cycles);
+ }
else if (mode == "recv" || mode == "proxy")
{
return event_sender_receiver.RunAsProxy(instance_specifier, cycle_time, cycles, false, check_sample_hash);
diff --git a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp
index db24972df..f1f87b965 100644
--- a/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp
+++ b/score/mw/com/example/ipc_bridge/sample_sender_receiver.cpp
@@ -13,6 +13,7 @@
#include "sample_sender_receiver.h"
#include "score/mw/com/impl/generic_proxy.h"
#include "score/mw/com/impl/generic_proxy_event.h"
+#include "score/mw/com/impl/generic_skeleton.h"
#include "score/mw/com/impl/handle_type.h"
#include "score/concurrency/notification.h"
@@ -30,6 +31,7 @@
#include
#include
#include
+#include
using namespace std::chrono_literals;
@@ -268,6 +270,36 @@ Result> PrepareMapLaneSample(IpcBridgeSke
return sample;
}
+Result> PrepareMapLaneSample(impl::GenericSkeletonEvent& event,
+ const std::size_t cycle)
+{
+ const std::default_random_engine::result_type seed{static_cast(
+ std::chrono::steady_clock::now().time_since_epoch().count())};
+ std::default_random_engine rng{seed};
+
+ auto sample_result = event.Allocate();
+
+ if (!sample_result.has_value())
+ {
+ return sample_result;
+ }
+ auto sample = std::move(sample_result).value();
+ auto* typed_sample = static_cast(sample.Get());
+ typed_sample->hash_value = START_HASH;
+ typed_sample->x = static_cast(cycle);
+
+ std::cout << ToString("Sending sample: ", typed_sample->x, "\n");
+ for (MapApiLaneData& lane : typed_sample->lanes) {
+ for (LaneIdType& successor : lane.successor_lanes)
+ {
+ successor = std::uniform_int_distribution()(rng);
+ }
+
+ HashArray(lane.successor_lanes, typed_sample->hash_value);
+ }
+ return sample;
+}
+
} // namespace
template
@@ -447,6 +479,70 @@ int EventSenderReceiver::RunAsSkeleton(const score::mw::com::InstanceSpecifier&
return EXIT_SUCCESS;
}
+int EventSenderReceiver::RunAsGenericSkeleton(const score::mw::com::InstanceSpecifier& instance_specifier,
+ const std::chrono::milliseconds cycle_time,
+ const std::size_t num_cycles)
+{
+ const auto event_name = "map_api_lanes_stamped";
+
+ const impl::DataTypeMetaInfo size_info{sizeof(MapApiLanesStamped), alignof(MapApiLanesStamped)};
+
+ impl::GenericSkeletonCreateParams create_params;
+ // Use a temporary vector to construct the span
+ const std::vector events_vec = {
+ {event_name, size_info}
+ };
+ create_params.events = events_vec;
+ // create_params.fields = {}; // No fields yet
+
+ auto create_result = impl::GenericSkeleton::Create(instance_specifier, create_params);
+
+ if (!create_result.has_value())
+ {
+ std::cerr << "Unable to construct skeleton: " << create_result.error() << ", bailing!\n";
+ return EXIT_FAILURE;
+ }
+ auto& skeleton = create_result.value();
+
+ // Retrieve event using its name
+ auto event_it = skeleton.GetEvents().find(event_name);
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_it != skeleton.GetEvents().cend(), "Event not found in GenericSkeleton");
+
+ auto& event = const_cast(event_it->second);
+
+ const auto offer_result = skeleton.OfferService();
+ if (!offer_result.has_value())
+ {
+ std::cerr << "Unable to offer service for skeleton: " << offer_result.error() << ", bailing!\n";
+ return EXIT_FAILURE;
+ }
+ std::cout << "Starting to send data\n";
+
+ for (std::size_t cycle = 0U; cycle < num_cycles || num_cycles == 0U; ++cycle)
+ {
+ auto sample_result = PrepareMapLaneSample(event, cycle);
+ if (!sample_result.has_value())
+ {
+ std::cerr << "No sample received. Exiting.\n";
+ return EXIT_FAILURE;
+ }
+ auto sample = std::move(sample_result).value();
+
+ {
+ std::lock_guard lock{event_sending_mutex_};
+ event.Send(std::move(sample));
+ event_published_ = true;
+ }
+ std::this_thread::sleep_for(cycle_time);
+ }
+
+ std::cout << "Stop offering service...";
+ skeleton.StopOfferService();
+ std::cout << "and terminating, bye bye\n";
+
+ return EXIT_SUCCESS;
+}
+
template int EventSenderReceiver::RunAsProxy>(
const score::mw::com::InstanceSpecifier&,
const score::cpp::optional,
@@ -460,4 +556,4 @@ template int EventSenderReceiver::RunAsProxy>
int RunAsProxy(const score::mw::com::InstanceSpecifier& instance_specifier,
diff --git a/score/mw/com/impl/BUILD b/score/mw/com/impl/BUILD
index 1f78b91aa..2e0ccf4f9 100644
--- a/score/mw/com/impl/BUILD
+++ b/score/mw/com/impl/BUILD
@@ -31,6 +31,7 @@ cc_library(
":generic_proxy_event",
":proxy_event",
":proxy_field",
+ ":generic_skeleton",
":skeleton_event",
":skeleton_field",
":traits",
@@ -111,6 +112,84 @@ cc_library(
],
)
+cc_library(
+ name = "generic_skeleton",
+ srcs = ["generic_skeleton.cpp"],
+ hdrs = ["generic_skeleton.h"],
+ features = COMPILER_WARNING_FEATURES,
+ implementation_deps = [
+ ":error",
+ "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory",
+ "//score/mw/com/impl/plumbing",
+ ],
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com:__subpackages__",
+ ],
+ deps = [
+ ":generic_skeleton_event",
+ ":instance_identifier",
+ ":instance_specifier",
+ ":runtime",
+ ":skeleton_base",
+ ":skeleton_binding",
+ ":data_type_meta_info",
+ ":service_element_map",
+ "@score_baselibs//score/result",
+
+ ],
+)
+
+cc_library(
+ name = "generic_skeleton_event",
+ srcs = ["generic_skeleton_event.cpp"],
+ hdrs = ["generic_skeleton_event.h"],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com:__subpackages__",
+ ],
+ deps = [
+ "//score/mw/com/impl/bindings/lola:generic_skeleton_event",
+ ":generic_skeleton_event_binding",
+ ":skeleton_base",
+ ":skeleton_event_base",
+ ":skeleton_event_binding",
+ ":data_type_meta_info",
+ "//score/mw/com/impl/plumbing:sample_allocatee_ptr",
+ "@score_baselibs//score/result",
+ ],
+)
+
+cc_library(
+ name = "generic_skeleton_event_binding",
+ hdrs = [
+ "generic_skeleton_event_binding.h",
+ ],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com/impl/bindings/lola:__pkg__",
+ "//score/mw/com/impl/bindings/mock_binding:__pkg__",
+ "//score/mw/com/impl/plumbing:__pkg__",
+ ],
+ deps = [
+ ":skeleton_event_binding",
+ "@score_baselibs//score/result",
+ ],
+)
+
+cc_library(
+ name = "data_type_meta_info",
+ hdrs = ["data_type_meta_info.h"],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com:__pkg__",
+ "//score/mw/com/impl:__subpackages__",
+ ],
+)
+
cc_library(
name = "skeleton_event",
srcs = ["skeleton_event.cpp"],
@@ -384,6 +463,8 @@ cc_library(
],
deps = [
":binding_type",
+ ":generic_skeleton_event_binding",
+ ":data_type_meta_info",
"//score/mw/com/impl/configuration",
"@score_baselibs//score/language/futurecpp",
"@score_baselibs//score/memory/shared:i_shared_memory_resource",
@@ -672,6 +753,20 @@ cc_library(
],
)
+cc_library(
+ name = "i_generic_skeleton_event_binding_factory",
+ hdrs = ["i_generic_skeleton_event_binding_factory.h"],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com/impl:__subpackages__",
+ ],
+ deps = [
+ ":generic_skeleton_event_binding",
+ ":skeleton_base",
+ ],
+)
+
cc_library(
name = "event_receive_handler",
srcs = ["event_receive_handler.cpp"],
@@ -1004,6 +1099,40 @@ cc_gtest_unit_test(
],
)
+cc_gtest_unit_test(
+ name = "generic_skeleton_test",
+ srcs = ["generic_skeleton_test.cpp"],
+ deps = [
+ ":generic_skeleton",
+ ":runtime_mock",
+ ":service_discovery_mock",
+ "//score/mw/com/impl/bindings/mock_binding:generic_skeleton_event",
+ "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory_mock",
+ "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory",
+ ":service_discovery_client_mock",
+ "//score/mw/com/impl/test:dummy_instance_identifier_builder",
+ "//score/mw/com/impl/test:binding_factory_resources",
+ "//score/mw/com/impl/test:runtime_mock_guard",
+ ],
+)
+
+cc_gtest_unit_test(
+ name = "generic_skeleton_event_test",
+ srcs = ["generic_skeleton_event_test.cpp",
+ "service_discovery_client_mock.h"],
+ deps = [
+ ":generic_skeleton",
+ ":generic_skeleton_event",
+ ":service_discovery_mock",
+ "//score/mw/com/impl/bindings/mock_binding:generic_skeleton_event",
+ "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory",
+ "//score/mw/com/impl/plumbing:generic_skeleton_event_binding_factory_mock",
+ "//score/mw/com/impl/test:binding_factory_resources",
+ "//score/mw/com/impl/test:dummy_instance_identifier_builder",
+ "//score/mw/com/impl/test:runtime_mock_guard",
+ ],
+)
+
cc_gtest_unit_test(
name = "traits_test",
srcs = [
@@ -1136,6 +1265,8 @@ cc_unit_test_suites_for_host_and_qnx(
":com_error_test",
":flag_owner_test",
":find_service_handle_test",
+ ":generic_skeleton_test",
+ ":generic_skeleton_event_test",
":generic_proxy_test",
":proxy_field_test",
":runtime_test",
diff --git a/score/mw/com/impl/bindings/lola/BUILD b/score/mw/com/impl/bindings/lola/BUILD
index 77817cc99..42e91d2cc 100644
--- a/score/mw/com/impl/bindings/lola/BUILD
+++ b/score/mw/com/impl/bindings/lola/BUILD
@@ -198,21 +198,11 @@ cc_library(
"//score/mw/com/impl:__subpackages__",
],
deps = [
- ":data_type_meta_info",
+ "//score/mw/com/impl:data_type_meta_info",
"@score_baselibs//score/memory/shared",
],
)
-cc_library(
- name = "data_type_meta_info",
- srcs = ["data_type_meta_info.cpp"],
- hdrs = ["data_type_meta_info.h"],
- features = COMPILER_WARNING_FEATURES,
- tags = ["FFI"],
- visibility = [
- "//score/mw/com/impl:__subpackages__",
- ],
-)
cc_library(
name = "shared_data_structures",
@@ -241,19 +231,48 @@ cc_library(
],
)
+cc_library(
+ name = "generic_skeleton_event",
+ srcs = [
+ "generic_skeleton_event.cpp",
+ "skeleton_event_common.cpp",
+ ],
+ hdrs = [
+ "generic_skeleton_event.h",
+ "skeleton_event_common.h",
+ ],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = [
+ "//score/mw/com/impl:__subpackages__",
+ ],
+ deps = [
+ ":event",
+ ":skeleton",
+ "//score/mw/com/impl:error",
+ "//score/mw/com/impl:generic_skeleton_event_binding",
+ "//score/mw/com/impl/tracing:skeleton_event_tracing",
+ ":type_erased_sample_ptrs_guard",
+ ],
+)
+
cc_library(
name = "skeleton",
srcs = [
"skeleton.cpp",
"skeleton_event.cpp",
+ "skeleton_event_common.cpp",
"skeleton_event_properties.cpp",
"skeleton_method.cpp",
+ "generic_skeleton_event.cpp",
],
hdrs = [
"skeleton.h",
"skeleton_event.h",
+ "skeleton_event_common.h",
"skeleton_event_properties.h",
"skeleton_method.h",
+ "generic_skeleton_event.h",
],
features = COMPILER_WARNING_FEATURES,
tags = ["FFI"],
@@ -284,6 +303,8 @@ cc_library(
"//score/mw/com/impl/plumbing:sample_allocatee_ptr",
"//score/mw/com/impl/tracing:skeleton_event_tracing",
"//score/mw/com/impl/util:arithmetic_utils",
+ "//score/mw/com/impl:generic_skeleton_event_binding",
+ "//score/mw/com/impl:error",
"@score_baselibs//score/filesystem",
"@score_baselibs//score/language/futurecpp",
"@score_baselibs//score/language/safecpp/safe_math",
diff --git a/score/mw/com/impl/bindings/lola/event_meta_info.h b/score/mw/com/impl/bindings/lola/event_meta_info.h
index 961c12897..223dc5002 100644
--- a/score/mw/com/impl/bindings/lola/event_meta_info.h
+++ b/score/mw/com/impl/bindings/lola/event_meta_info.h
@@ -14,7 +14,7 @@
#define SCORE_MW_COM_IMPL_BINDINGS_LOLA_EVENT_META_INFO_H
#include "score/memory/shared/offset_ptr.h"
-#include "score/mw/com/impl/bindings/lola/data_type_meta_info.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
namespace score::mw::com::impl::lola
{
@@ -26,7 +26,7 @@ namespace score::mw::com::impl::lola
class EventMetaInfo
{
public:
- EventMetaInfo(const DataTypeMetaInfo data_type_info, const memory::shared::OffsetPtr event_slots_raw_array)
+ EventMetaInfo(const impl::DataTypeMetaInfo data_type_info, const memory::shared::OffsetPtr event_slots_raw_array)
: data_type_info_(data_type_info), event_slots_raw_array_(event_slots_raw_array)
{
}
@@ -35,7 +35,7 @@ class EventMetaInfo
// be private.". There are no class invariants to maintain which could be violated by directly accessing member
// variables.
// coverity[autosar_cpp14_m11_0_1_violation]
- DataTypeMetaInfo data_type_info_;
+ impl::DataTypeMetaInfo data_type_info_;
// coverity[autosar_cpp14_m11_0_1_violation]
memory::shared::OffsetPtr event_slots_raw_array_;
};
diff --git a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp
index 37e303e4f..7097e15cb 100644
--- a/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp
+++ b/score/mw/com/impl/bindings/lola/generic_proxy_event.cpp
@@ -78,7 +78,7 @@ inline Result GenericProxyEvent::GetNewSamples(Callback&& receiver,
std::size_t GenericProxyEvent::GetSampleSize() const noexcept
{
- return meta_info_.data_type_info_.size_of_;
+ return meta_info_.data_type_info_.size;
}
bool GenericProxyEvent::HasSerializedFormat() const noexcept
@@ -129,8 +129,8 @@ Result GenericProxyEvent::GetNewSamplesImpl(Callback&& receiver, Tr
auto& event_control = proxy_event_common_.GetEventControl();
- const std::size_t sample_size = meta_info_.data_type_info_.size_of_;
- const std::uint8_t sample_alignment = meta_info_.data_type_info_.align_of_;
+ const std::size_t sample_size = meta_info_.data_type_info_.size;
+ const std::size_t sample_alignment = meta_info_.data_type_info_.alignment;
const std::size_t aligned_size =
memory::shared::CalculateAlignedSize(sample_size, static_cast(sample_alignment));
@@ -146,6 +146,7 @@ Result GenericProxyEvent::GetNewSamplesImpl(Callback&& receiver, Tr
score::mw::log::LogFatal("lola") << "Could not calculate the event slots raw array size. Terminating.";
std::terminate();
}
+
const void* const event_slots_raw_array = meta_info_.event_slots_raw_array_.get(event_slots_raw_array_size.value());
// AMP assert that the event_slots_raw_array address is according to sample_alignment
diff --git a/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp b/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp
index 91d8ce425..ef82c098e 100644
--- a/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp
+++ b/score/mw/com/impl/bindings/lola/generic_proxy_event_test.cpp
@@ -117,10 +117,10 @@ TEST_F(LolaGenericProxyEventDeathTest, OverflowWhenCalculatingRawEventsSlotsArra
// Given a mocked SkeletonEvent whose metainfo stores a size which will lead to an overflow when calculating the raw
// event slot array size
- const auto align_of = fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.align_of_;
+ const auto align_of = fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.alignment;
// Subtract the align of from the max size to prevent an overflow when calculating the aligned size
- fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.size_of_ =
+ fake_data_->data_storage->events_metainfo_.at(element_fq_id_).data_type_info_.size =
std::numeric_limits::max() - align_of;
// and given a GenericProxyEvent which has subscribed
diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp
new file mode 100644
index 000000000..fe84bb776
--- /dev/null
+++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.cpp
@@ -0,0 +1,140 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h"
+#include "score/memory/shared/pointer_arithmetic_util.h"
+#include "score/mw/com/impl/bindings/lola/skeleton.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h"
+#include "score/mw/com/impl/runtime.h"
+
+namespace score::mw::com::impl::lola
+{
+GenericSkeletonEvent::GenericSkeletonEvent(Skeleton& parent,
+ const SkeletonEventProperties& event_properties,
+ const ElementFqId& event_fqn,
+ const DataTypeMetaInfo& size_info,
+ impl::tracing::SkeletonEventTracingData tracing_data)
+ : size_info_(size_info),
+ event_properties_(event_properties),
+ event_shared_impl_(parent, event_fqn, control_, current_timestamp_, tracing_data)
+{
+
+}
+
+ResultBlank GenericSkeletonEvent::PrepareOffer() noexcept
+{
+ std::tie(data_storage_, control_) =
+ event_shared_impl_.GetParent().RegisterGeneric(event_shared_impl_.GetElementFQId(), event_properties_, size_info_.size, size_info_.alignment);
+ event_shared_impl_.PrepareOfferCommon();
+
+ return {};
+}
+
+Result GenericSkeletonEvent::Send(score::mw::com::impl::SampleAllocateePtr sample) noexcept
+{
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(control_.has_value());
+ const impl::SampleAllocateePtrView view{sample};
+ auto ptr = view.template As>();
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(nullptr != ptr);
+
+ auto control_slot_indicator = ptr->GetReferencedSlot();
+ control_.value().EventReady(control_slot_indicator, ++current_timestamp_);
+
+ // Only call NotifyEvent if there are any registered receive handlers for each quality level (QM and ASIL-B).
+ // This avoids the expensive lock operation in the common case where no handlers are registered.
+ // Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent
+ // handler registration, the next Send() will pick it up.
+
+
+ if (event_shared_impl_.IsQmNotificationsRegistered() && !qm_disconnect_)
+ {
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .NotifyEvent(QualityType::kASIL_QM, event_shared_impl_.GetElementFQId());
+ }
+ if (event_shared_impl_.IsAsilBNotificationsRegistered() && event_shared_impl_.GetParent().GetInstanceQualityType() == QualityType::kASIL_B)
+ {
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .NotifyEvent(QualityType::kASIL_B, event_shared_impl_.GetElementFQId());
+ }
+
+ return {};
+}
+
+Result> GenericSkeletonEvent::Allocate() noexcept
+{
+ if (!control_.has_value())
+ {
+ ::score::mw::log::LogError("lola") << "Tried to allocate event, but the EventDataControl does not exist!";
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+ const auto slot = control_.value().AllocateNextSlot();
+
+ if (!qm_disconnect_ && control_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM())
+ {
+ qm_disconnect_ = true;
+ score::mw::log::LogWarn("lola")
+ << __func__ << __LINE__
+ << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_shared_impl_.GetElementFQId();
+ event_shared_impl_.GetParent().DisconnectQmConsumers();
+ }
+
+ if (slot.IsValidQM() || slot.IsValidAsilB())
+ {
+ // Get the actual CONTAINER object (using the max_align_t type we allocated it with!)
+ using StorageType = lola::EventDataStorage;
+ StorageType* storage_ptr = data_storage_.get();
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD(storage_ptr != nullptr);
+
+ std::uint8_t* byte_ptr = reinterpret_cast(storage_ptr->data());
+
+ // Calculate the exact slot spacing based on alignment padding
+ const auto aligned_size = memory::shared::CalculateAlignedSize(size_info_.size, size_info_.alignment);
+ std::size_t offset = static_cast(slot.GetIndex()) * aligned_size;
+ void* data_ptr = byte_ptr + offset;
+
+ auto lola_ptr = lola::SampleAllocateePtr(data_ptr, control_.value(), slot);
+ return impl::MakeSampleAllocateePtr(std::move(lola_ptr));
+ }
+ else
+ {
+ if (!event_properties_.enforce_max_samples)
+ {
+ ::score::mw::log::LogError("lola")
+ << "GenericSkeletonEvent: Allocation of event slot failed. Hint: enforceMaxSamples was "
+ "disabled by config. Might be the root cause!";
+ }
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+}
+
+std::pair GenericSkeletonEvent::GetSizeInfo() const noexcept
+{
+ return {size_info_.size, size_info_.alignment};
+}
+
+void GenericSkeletonEvent::PrepareStopOffer() noexcept
+{
+ event_shared_impl_.PrepareStopOfferCommon();
+ control_.reset();
+ data_storage_ = nullptr;
+}
+
+BindingType GenericSkeletonEvent::GetBindingType() const noexcept
+{
+ return BindingType::kLoLa;
+}
+
+
+} // namespace score::mw::com::impl::lola
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/lola/generic_skeleton_event.h b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h
new file mode 100644
index 000000000..bab500692
--- /dev/null
+++ b/score/mw/com/impl/bindings/lola/generic_skeleton_event.h
@@ -0,0 +1,72 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "score/mw/com/impl/generic_skeleton_event_binding.h"
+#include "score/mw/com/impl/bindings/lola/element_fq_id.h"
+#include "score/mw/com/impl/bindings/lola/event_data_storage.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event_properties.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h"
+
+
+namespace score::mw::com::impl::lola
+{
+
+class TransactionLogRegistrationGuard;
+class Skeleton;
+
+/// @brief The LoLa binding implementation for a generic skeleton event.
+class GenericSkeletonEvent : public GenericSkeletonEventBinding
+{
+ public:
+
+ GenericSkeletonEvent(Skeleton& parent,
+ const SkeletonEventProperties& event_properties,
+ const ElementFqId& event_fqn,
+ const DataTypeMetaInfo& size_info,
+ impl::tracing::SkeletonEventTracingData tracing_data = {});
+
+ Result Send(score::mw::com::impl::SampleAllocateePtr sample) noexcept override;
+
+ Result> Allocate() noexcept override;
+
+ std::pair GetSizeInfo() const noexcept override;
+
+
+ ResultBlank PrepareOffer() noexcept override;
+ void PrepareStopOffer() noexcept override;
+ BindingType GetBindingType() const noexcept override;
+ void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override
+ {
+ event_shared_impl_.GetTracingData() = tracing_data;
+ }
+
+ std::size_t GetMaxSize() const noexcept override
+ {
+ return size_info_.size;
+ }
+
+ private:
+ DataTypeMetaInfo size_info_;
+ const SkeletonEventProperties event_properties_;
+ score::cpp::optional control_{};
+ EventSlotStatus::EventTimeStamp current_timestamp_{1U};
+ score::memory::shared::OffsetPtr data_storage_{nullptr};
+ bool qm_disconnect_{false};
+
+ SkeletonEventCommon event_shared_impl_;
+
+};
+
+} // namespace score::mw::com::impl::lola
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/lola/proxy_event.h b/score/mw/com/impl/bindings/lola/proxy_event.h
index 1be45fa0f..d22bdf9af 100644
--- a/score/mw/com/impl/bindings/lola/proxy_event.h
+++ b/score/mw/com/impl/bindings/lola/proxy_event.h
@@ -217,4 +217,4 @@ inline Result ProxyEvent::GetNewSamplesImpl(Callback&&
} // namespace score::mw::com::impl::lola
-#endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_PROXY_EVENT_H
+#endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_PROXY_EVENT_H
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/lola/proxy_test.cpp b/score/mw/com/impl/bindings/lola/proxy_test.cpp
index 7ef43f0e8..22de96350 100644
--- a/score/mw/com/impl/bindings/lola/proxy_test.cpp
+++ b/score/mw/com/impl/bindings/lola/proxy_test.cpp
@@ -596,8 +596,8 @@ TEST_F(ProxyGetEventMetaInfoFixture, GetEventMetaInfoWillReturnDataForEventThatW
const auto event_meta_info = proxy_->GetEventMetaInfo(kDummyElementFqId);
// Then the EventMetaInfo will contain the meta info of the SkeletonEvent type
- EXPECT_EQ(event_meta_info.data_type_info_.size_of_, sizeof(ProxyMockedMemoryFixture::SampleType));
- EXPECT_EQ(event_meta_info.data_type_info_.align_of_, alignof(ProxyMockedMemoryFixture::SampleType));
+ EXPECT_EQ(event_meta_info.data_type_info_.size, sizeof(ProxyMockedMemoryFixture::SampleType));
+ EXPECT_EQ(event_meta_info.data_type_info_.alignment, alignof(ProxyMockedMemoryFixture::SampleType));
}
using ProxyGetEventMetaInfoDeathTest = ProxyGetEventMetaInfoFixture;
diff --git a/score/mw/com/impl/bindings/lola/skeleton.cpp b/score/mw/com/impl/bindings/lola/skeleton.cpp
index cecc374e8..29f867035 100644
--- a/score/mw/com/impl/bindings/lola/skeleton.cpp
+++ b/score/mw/com/impl/bindings/lola/skeleton.cpp
@@ -29,6 +29,7 @@
#include "score/mw/com/impl/configuration/lola_event_instance_deployment.h"
#include "score/mw/com/impl/configuration/lola_method_id.h"
#include "score/mw/com/impl/configuration/lola_service_instance_deployment.h"
+#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h"
#include "score/mw/com/impl/configuration/lola_service_type_deployment.h"
#include "score/mw/com/impl/configuration/quality_type.h"
#include "score/mw/com/impl/runtime.h"
@@ -217,7 +218,7 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier,
std::unique_ptr partial_restart_path_builder)
{
SCORE_LANGUAGE_FUTURECPP_PRECONDITION_PRD_MESSAGE(partial_restart_path_builder != nullptr,
- "Skeleton::Create: partial restart path builder pointer is Null");
+ "Skeleton::Create: partial restart path builder pointer is Null");
const auto partial_restart_dir_creation_result =
CreatePartialRestartDirectory(filesystem, *partial_restart_path_builder);
if (!partial_restart_dir_creation_result)
@@ -230,12 +231,13 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier,
const auto lola_instance_id = lola_service_instance_deployment.instance_id_.value().GetId();
auto service_instance_existence_marker_file =
CreateOrOpenServiceInstanceExistenceMarkerFile(lola_instance_id, *partial_restart_path_builder);
+
if (!service_instance_existence_marker_file.has_value())
{
score::mw::log::LogError("lola") << "Could not create or open service instance existence marker file.";
return nullptr;
}
-
+
auto service_instance_existence_mutex_and_lock =
std::make_unique>(
*service_instance_existence_marker_file);
@@ -246,7 +248,7 @@ std::unique_ptr Skeleton::Create(const InstanceIdentifier& identifier,
"actively offering the same service instance.";
return nullptr;
}
-
+
const auto& lola_service_type_deployment = GetLolaServiceTypeDeployment(identifier);
// Since we were able to flock the existence marker file, it means that either we created it or the skeleton that
// created it previously crashed. Either way, we take ownership of the LockFile so that it's destroyed when this
@@ -1020,6 +1022,129 @@ void Skeleton::InitializeSharedMemoryForControl(
control = memory->construct(memory->getMemoryResourceProxy());
}
+EventDataControlComposite Skeleton::CreateEventControlComposite(const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties) noexcept
+{
+ auto control_qm = control_qm_->event_controls_.emplace(std::piecewise_construct,
+ std::forward_as_tuple(element_fq_id),
+ std::forward_as_tuple(element_properties.number_of_slots,
+ element_properties.max_subscribers,
+ element_properties.enforce_max_samples,
+ control_qm_resource_->getMemoryResourceProxy()));
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section.");
+
+ EventDataControl* control_asil_result{nullptr};
+ if (control_asil_resource_ != nullptr)
+ {
+ auto iterator = control_asil_b_->event_controls_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(element_fq_id),
+ std::forward_as_tuple(element_properties.number_of_slots,
+ element_properties.max_subscribers,
+ element_properties.enforce_max_samples,
+ control_asil_resource_->getMemoryResourceProxy()));
+
+ // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares:
+ // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined
+ // within the function.
+ // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to
+ // another object that may persist after the first object has ceased to exist.
+ // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive.
+ // coverity[autosar_cpp14_m7_5_1_violation]
+ // coverity[autosar_cpp14_m7_5_2_violation]
+ // coverity[autosar_cpp14_a3_8_1_violation]
+ control_asil_result = &iterator.first->second.data_control;
+ }
+ // clang-format off
+ // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive.
+ // coverity[autosar_cpp14_m7_5_1_violation]
+ // coverity[autosar_cpp14_m7_5_2_violation]
+ // coverity[autosar_cpp14_a3_8_1_violation]
+ return EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result};
+}
+
+std::pair, EventDataControlComposite>
+Skeleton::CreateEventDataFromOpenedSharedMemory(
+ const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties,
+ size_t sample_size,
+ size_t sample_alignment) noexcept
+{
+
+ // Guard against over-aligned types (Short-term solution protection)
+ if (sample_alignment > alignof(std::max_align_t))
+ {
+ score::mw::log::LogFatal("Skeleton")
+ << "Requested sample alignment (" << sample_alignment
+ << ") exceeds max_align_t (" << alignof(std::max_align_t)
+ << "). Safe shared memory layout cannot be guaranteed.";
+
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(sample_alignment <= alignof(std::max_align_t),"Requested sample alignment exceeds maximum supported alignment.");
+ }
+
+ // Calculate the aligned size for a single sample to ensure proper padding between slots
+ const auto aligned_sample_size = memory::shared::CalculateAlignedSize(sample_size, sample_alignment);
+ const auto total_data_size_bytes = aligned_sample_size * element_properties.number_of_slots;
+
+ // Convert total bytes to the number of std::max_align_t elements needed (round up)
+ const size_t num_max_align_elements =
+ (total_data_size_bytes + sizeof(std::max_align_t) - 1) / sizeof(std::max_align_t);
+
+ auto* data_storage = storage_resource_->construct>(
+ num_max_align_elements,
+ memory::shared::PolymorphicOffsetPtrAllocator(storage_resource_->getMemoryResourceProxy()));
+
+ auto inserted_data_slots = storage_->events_.emplace(std::piecewise_construct,
+ std::forward_as_tuple(element_fq_id),
+ std::forward_as_tuple(data_storage));
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_data_slots.second,
+ "Couldn't register/emplace event-storage in data-section.");
+
+
+ const DataTypeMetaInfo sample_meta_info{sample_size, static_cast(sample_alignment)};
+ void* const event_data_raw_array = data_storage->data();
+
+ auto inserted_meta_info = storage_->events_metainfo_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(element_fq_id),
+ std::forward_as_tuple(sample_meta_info, event_data_raw_array));
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second,
+ "Couldn't register/emplace event-meta-info in data-section.");
+
+ return {score::memory::shared::OffsetPtr(data_storage), CreateEventControlComposite(element_fq_id, element_properties)};
+}
+std::pair, EventDataControlComposite> Skeleton::RegisterGeneric(
+ const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties,
+ const size_t sample_size,
+ const size_t sample_alignment) noexcept
+{
+ if (was_old_shm_region_reopened_)
+ {
+ auto [data_storage, control_composite] = OpenEventDataFromOpenedSharedMemory(element_fq_id);
+
+ auto& event_data_control_qm = control_composite.GetQmEventDataControl();
+ auto rollback_result = event_data_control_qm.GetTransactionLogSet().RollbackSkeletonTracingTransactions(
+ [&event_data_control_qm](const TransactionLog::SlotIndexType slot_index) {
+ event_data_control_qm.DereferenceEventWithoutTransactionLogging(slot_index);
+ });
+ if (!rollback_result.has_value())
+ {
+ ::score::mw::log::LogWarn("lola")
+ << "SkeletonEvent: PrepareOffer failed: Could not rollback tracing consumer after "
+ "crash. Disabling tracing.";
+ impl::Runtime::getInstance().GetTracingRuntime()->DisableTracing();
+ }
+
+ return {data_storage, control_composite};
+ }
+ else
+ {
+ return CreateEventDataFromOpenedSharedMemory(
+ element_fq_id, element_properties, sample_size, sample_alignment);
+ }
+}
+
ResultBlank Skeleton::OnServiceMethodsSubscribed(const ProxyInstanceIdentifier& proxy_instance_identifier,
const uid_t proxy_uid,
const QualityType asil_level,
diff --git a/score/mw/com/impl/bindings/lola/skeleton.h b/score/mw/com/impl/bindings/lola/skeleton.h
index aac08cf48..5d8b7be5c 100644
--- a/score/mw/com/impl/bindings/lola/skeleton.h
+++ b/score/mw/com/impl/bindings/lola/skeleton.h
@@ -110,6 +110,20 @@ class Skeleton final : public SkeletonBinding
return BindingType::kLoLa;
};
+ /// \brief Enables dynamic registration of Generic (type-erased) Events at the Skeleton.
+ /// \param element_fq_id The full qualified ID of the element (event) that shall be registered.
+ /// \param element_properties Properties of the element (e.g. number of slots, max subscribers).
+ /// \param sample_size The size of a single data sample in bytes.
+ /// \param sample_alignment The alignment requirement of the data sample in bytes.
+ /// \return A pair containing:
+ /// - An OffsetPtr to the allocated data storage (void*).
+ /// - The EventDataControlComposite for managing the event's control data.
+ std::pair, EventDataControlComposite> RegisterGeneric(
+ const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties,
+ const size_t sample_size,
+ const size_t sample_alignment) noexcept;
+
/// \brief Enables dynamic registration of Events at the Skeleton.
/// \tparam SampleType The type of the event
/// \param element_fq_id The full qualified of the element (event or field) that shall be registered
@@ -182,6 +196,25 @@ class Skeleton final : public SkeletonBinding
const ElementFqId element_fq_id,
const SkeletonEventProperties& element_properties);
+ /// \brief Creates the control structures (QM and optional ASIL-B) for an event.
+ /// \param element_fq_id The full qualified ID of the element.
+ /// \param element_properties Properties of the event.
+ /// \return The EventDataControlComposite containing pointers to the control structures.
+ EventDataControlComposite CreateEventControlComposite(const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties) noexcept;
+
+ /// \brief Creates shared memory storage for a generic (type-erased) event.
+ /// \param element_fq_id The full qualified ID of the element.
+ /// \param element_properties Properties of the event.
+ /// \param sample_size The size of a single data sample.
+ /// \param sample_alignment The alignment of the data sample.
+ /// \return A pair containing the data storage pointer (void*) and the control composite.
+ std::pair, EventDataControlComposite>
+ CreateEventDataFromOpenedSharedMemory(const ElementFqId element_fq_id,
+ const SkeletonEventProperties& element_properties,
+ size_t sample_size,
+ size_t sample_alignment) noexcept;
+
class ShmResourceStorageSizes
{
public:
@@ -433,44 +466,7 @@ auto Skeleton::CreateEventDataFromOpenedSharedMemory(const ElementFqId element_f
std::forward_as_tuple(sample_meta_info, event_data_raw_array));
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(inserted_meta_info.second, "Couldn't register/emplace event-meta-info in data-section.");
- auto control_qm =
- control_qm_->event_controls_.emplace(std::piecewise_construct,
- std::forward_as_tuple(element_fq_id),
- std::forward_as_tuple(element_properties.number_of_slots,
- element_properties.max_subscribers,
- element_properties.enforce_max_samples,
- control_qm_resource_->getMemoryResourceProxy()));
- SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(control_qm.second, "Couldn't register/emplace event-meta-info in data-section.");
-
- EventDataControl* control_asil_result{nullptr};
- if (control_asil_resource_ != nullptr)
- {
- auto iterator = control_asil_b_->event_controls_.emplace(
- std::piecewise_construct,
- std::forward_as_tuple(element_fq_id),
- std::forward_as_tuple(element_properties.number_of_slots,
- element_properties.max_subscribers,
- element_properties.enforce_max_samples,
- control_asil_resource_->getMemoryResourceProxy()));
-
- // Suppress "AUTOSAR C++14 M7-5-1" rule. This rule declares:
- // A function shall not return a reference or a pointer to an automatic variable (including parameters), defined
- // within the function.
- // Suppress "AUTOSAR C++14 M7-5-2": The address of an object with automatic storage shall not be assigned to
- // another object that may persist after the first object has ceased to exist.
- // The result pointer is still valid outside this method until Skeleton object (as a holder) is alive.
- // coverity[autosar_cpp14_m7_5_1_violation]
- // coverity[autosar_cpp14_m7_5_2_violation]
- // coverity[autosar_cpp14_a3_8_1_violation]
- control_asil_result = &iterator.first->second.data_control;
- }
- // clang-format off
- // The lifetime of the "control_asil_result" object lasts as long as the Skeleton is alive.
- // coverity[autosar_cpp14_m7_5_1_violation]
- // coverity[autosar_cpp14_m7_5_2_violation]
- // coverity[autosar_cpp14_a3_8_1_violation]
- return {typed_event_data_storage_ptr, EventDataControlComposite{&control_qm.first->second.data_control, control_asil_result}};
- // clang-format on
+ return {typed_event_data_storage_ptr, CreateEventControlComposite(element_fq_id, element_properties)};
}
} // namespace score::mw::com::impl::lola
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.cpp b/score/mw/com/impl/bindings/lola/skeleton_event.cpp
index 19362be40..a065c651c 100644
--- a/score/mw/com/impl/bindings/lola/skeleton_event.cpp
+++ b/score/mw/com/impl/bindings/lola/skeleton_event.cpp
@@ -11,3 +11,4 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
#include "score/mw/com/impl/bindings/lola/skeleton_event.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h"
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event.h b/score/mw/com/impl/bindings/lola/skeleton_event.h
index cb9c035e1..7f4efa235 100644
--- a/score/mw/com/impl/bindings/lola/skeleton_event.h
+++ b/score/mw/com/impl/bindings/lola/skeleton_event.h
@@ -28,6 +28,7 @@
#include "score/mw/com/impl/skeleton_event_binding.h"
#include "score/mw/com/impl/tracing/skeleton_event_tracing.h"
#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h"
+#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h"
#include "score/mw/log/logging.h"
@@ -63,12 +64,12 @@ class SkeletonEvent final : public SkeletonEventBinding
// coverity[autosar_cpp14_a11_3_1_violation]
friend class SkeletonEventAttorney;
- public:
+ public:
using typename SkeletonEventBinding::SendTraceCallback;
using typename SkeletonEventBindingBase::SubscribeTraceCallback;
using typename SkeletonEventBindingBase::UnsubscribeTraceCallback;
- SkeletonEvent(Skeleton& parent,
+ SkeletonEvent(Skeleton& parent,
const ElementFqId event_fqn,
const std::string_view event_name,
const SkeletonEventProperties properties,
@@ -95,48 +96,27 @@ class SkeletonEvent final : public SkeletonEventBinding
void PrepareStopOffer() noexcept override;
- BindingType GetBindingType() const noexcept override
- {
- return BindingType::kLoLa;
+ BindingType GetBindingType() const noexcept override
+ {
+ return BindingType::kLoLa;
}
- void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override
- {
- skeleton_event_tracing_data_ = tracing_data;
+ void SetSkeletonEventTracingData(impl::tracing::SkeletonEventTracingData tracing_data) noexcept override
+ {
+ event_shared_impl_.GetTracingData() = tracing_data;
}
- ElementFqId GetElementFQId() const noexcept
- {
- return event_fqn_;
- };
-
- private:
- Skeleton& parent_;
- const ElementFqId event_fqn_;
+
+ private:
const std::string_view event_name_;
const SkeletonEventProperties event_properties_;
EventDataStorage* event_data_storage_;
- std::optional event_data_control_composite_;
+ score::cpp::optional event_data_control_composite_;
EventSlotStatus::EventTimeStamp current_timestamp_;
bool qm_disconnect_;
- impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data_;
-
- /// \brief Atomic flags indicating whether any receive handlers are currently registered for this event
- /// at each quality level (QM and ASIL-B).
- /// \details These flags are updated via callbacks from MessagePassingServiceInstance when handler
- /// registration status changes. They allow Send() to skip the NotifyEvent() call when no
- /// handlers are registered for a specific quality level, avoiding unnecessary lock overhead
- /// in the main path. Uses memory_order_relaxed as the flags are optimisation hints - false
- /// positives (thinking handlers exist when they don't) are harmless, and false negatives
- /// (missing handlers) are prevented by the callback mechanism.
- std::atomic qm_event_update_notifications_registered_{false};
- std::atomic asil_b_event_update_notifications_registered_{false};
-
- /// \brief optional RAII guards for tracing transaction log registration/un-registration and cleanup of "pending"
- /// type erased sample pointers which are created in PrepareOffer() and destroyed in PrepareStopoffer() - optional
- /// as only needed when tracing is enabled and when they haven't been cleaned up via a call to PrepareStopoffer().
- std::optional transaction_log_registration_guard_;
- std::optional type_erased_sample_ptrs_guard_;
+
+ SkeletonEventCommon event_shared_impl_;
+
};
template
@@ -146,17 +126,13 @@ SkeletonEvent::SkeletonEvent(Skeleton& parent,
const SkeletonEventProperties properties,
impl::tracing::SkeletonEventTracingData skeleton_event_tracing_data) noexcept
: SkeletonEventBinding{},
- parent_{parent},
- event_fqn_{event_fqn},
event_name_{event_name},
event_properties_{properties},
event_data_storage_{nullptr},
- event_data_control_composite_{std::nullopt},
+ event_data_control_composite_{score::cpp::nullopt},
current_timestamp_{1U},
qm_disconnect_{false},
- skeleton_event_tracing_data_{skeleton_event_tracing_data},
- transaction_log_registration_guard_{},
- type_erased_sample_ptrs_guard_{}
+ event_shared_impl_(parent, event_fqn, event_data_control_composite_, current_timestamp_, skeleton_event_tracing_data)
{
}
@@ -207,18 +183,17 @@ ResultBlank SkeletonEvent::Send(impl::SampleAllocateePtr
// This avoids the expensive lock operation in the common case where no handlers are registered.
// Using memory_order_relaxed is safe here as this is an optimisation, if we miss a very recent
// handler registration, the next Send() will pick it up.
- if (qm_event_update_notifications_registered_.load() && !qm_disconnect_)
+ if (event_shared_impl_.IsQmNotificationsRegistered() && !qm_disconnect_)
{
GetBindingRuntime(BindingType::kLoLa)
.GetLolaMessaging()
- .NotifyEvent(QualityType::kASIL_QM, event_fqn_);
+ .NotifyEvent(QualityType::kASIL_QM, event_shared_impl_.GetElementFQId());
}
- if (asil_b_event_update_notifications_registered_.load() &&
- parent_.GetInstanceQualityType() == QualityType::kASIL_B)
+ if (event_shared_impl_.IsAsilBNotificationsRegistered() && event_shared_impl_.GetParent().GetInstanceQualityType() == QualityType::kASIL_B)
{
GetBindingRuntime(BindingType::kLoLa)
.GetLolaMessaging()
- .NotifyEvent(QualityType::kASIL_B, event_fqn_);
+ .NotifyEvent(QualityType::kASIL_B, event_shared_impl_.GetElementFQId());
}
return {};
}
@@ -245,10 +220,10 @@ Result> SkeletonEvent::Allocate
if (!qm_disconnect_ && event_data_control_composite_->GetAsilBEventDataControl().has_value() && !slot.IsValidQM())
{
qm_disconnect_ = true;
- score::mw::log::LogWarn("lola")
+ score::mw::log::LogWarn("lola")
<< __func__ << __LINE__
- << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_fqn_;
- parent_.DisconnectQmConsumers();
+ << "Disconnecting unsafe QM consumers as slot allocation failed on an ASIL-B enabled event: " << event_shared_impl_.GetElementFQId();
+ event_shared_impl_.GetParent().DisconnectQmConsumers();
}
if (slot.IsValidQM() || slot.IsValidAsilB())
@@ -280,46 +255,14 @@ template
// coverity[autosar_cpp14_a15_5_3_violation : FALSE]
ResultBlank SkeletonEvent::PrepareOffer() noexcept
{
+
std::tie(event_data_storage_, event_data_control_composite_) =
- parent_.Register(event_fqn_, event_properties_);
+ event_shared_impl_.GetParent().template Register(event_shared_impl_.GetElementFQId(), event_properties_);
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_.has_value(),
"Defensive programming as event_data_control_composite_ is set by Register above.");
- current_timestamp_ = event_data_control_composite_.value().GetLatestTimestamp();
-
- const bool tracing_for_skeleton_event_enabled =
- skeleton_event_tracing_data_.enable_send || skeleton_event_tracing_data_.enable_send_with_allocate;
- // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in
- // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code
- // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in
- // Ticket-188259).
- if (tracing_for_skeleton_event_enabled)
- {
- // LCOV_EXCL_BR_STOP
- score::cpp::ignore = transaction_log_registration_guard_.emplace(
- TransactionLogRegistrationGuard::Create(event_data_control_composite_->GetQmEventDataControl()));
- score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(skeleton_event_tracing_data_.service_element_tracing_data);
- }
- // Register callbacks to be notified when event notification existence changes.
- // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered.
- // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access.
- GetBindingRuntime(BindingType::kLoLa)
- .GetLolaMessaging()
- .RegisterEventNotificationExistenceChangedCallback(
- QualityType::kASIL_QM, event_fqn_, [this](const bool has_handlers) noexcept {
- qm_event_update_notifications_registered_.store(has_handlers);
- });
-
- if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
- {
- GetBindingRuntime(BindingType::kLoLa)
- .GetLolaMessaging()
- .RegisterEventNotificationExistenceChangedCallback(
- QualityType::kASIL_B, event_fqn_, [this](const bool has_handlers) noexcept {
- asil_b_event_update_notifications_registered_.store(has_handlers);
- });
- }
+ event_shared_impl_.PrepareOfferCommon();
return {};
}
@@ -333,27 +276,7 @@ template
// coverity[autosar_cpp14_a15_5_3_violation : FALSE]
void SkeletonEvent::PrepareStopOffer() noexcept
{
- // Unregister event notification existence changed callbacks
- GetBindingRuntime(BindingType::kLoLa)
- .GetLolaMessaging()
- .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event_fqn_);
-
- if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
- {
- GetBindingRuntime(BindingType::kLoLa)
- .GetLolaMessaging()
- .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event_fqn_);
- }
-
- // Reset the flags to indicate no handlers are registered
- qm_event_update_notifications_registered_.store(false);
- asil_b_event_update_notifications_registered_.store(false);
-
- type_erased_sample_ptrs_guard_.reset();
- if (event_data_control_composite_.has_value())
- {
- transaction_log_registration_guard_.reset();
- }
+ event_shared_impl_.PrepareStopOfferCommon();
}
} // namespace score::mw::com::impl::lola
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp
new file mode 100644
index 000000000..e8979aada
--- /dev/null
+++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.cpp
@@ -0,0 +1,151 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/bindings/lola/skeleton_event_common.h"
+#include "score/mw/com/impl/bindings/lola/i_runtime.h" // For GetBindingRuntime
+#include "score/mw/com/impl/bindings/lola/messaging/i_message_passing_service.h" // For RegisterEventNotificationExistenceChangedCallback
+#include "score/mw/com/impl/bindings/lola/skeleton.h"
+
+namespace score::mw::com::impl::lola
+{
+
+SkeletonEventCommon::SkeletonEventCommon(Skeleton& parent,
+ const ElementFqId& event_fqn,
+ score::cpp::optional& event_data_control_composite_ref,
+ EventSlotStatus::EventTimeStamp& current_timestamp_ref,
+ impl::tracing::SkeletonEventTracingData tracing_data) noexcept
+ : parent_{parent},
+ event_fqn_{event_fqn},
+ event_data_control_composite_ref_{event_data_control_composite_ref},
+ current_timestamp_ref_{current_timestamp_ref},
+ tracing_data_{tracing_data}
+{
+}
+
+void SkeletonEventCommon::PrepareOfferCommon() noexcept
+{
+ const bool tracing_globally_enabled = ((impl::Runtime::getInstance().GetTracingRuntime() != nullptr) &&
+ (impl::Runtime::getInstance().GetTracingRuntime()->IsTracingEnabled()));
+ if (!tracing_globally_enabled)
+ {
+ DisableAllTracePoints(tracing_data_);
+ }
+
+ const bool tracing_for_skeleton_event_enabled =
+ tracing_data_.enable_send || tracing_data_.enable_send_with_allocate;
+ // LCOV_EXCL_BR_START (Tool incorrectly marks the decision as "Decision couldn't be analyzed" despite all lines in
+ // both branches (true / false) being covered. "Decision couldn't be analyzed" only appeared after changing the code
+ // within the if statement (without changing the condition / tests). Suppression can be removed when bug is fixed in
+ // Ticket-188259).
+ if (tracing_for_skeleton_event_enabled)
+ {
+ EmplaceTransactionLogRegistrationGuard();
+ EmplaceTypeErasedSamplePtrsGuard();
+ }
+
+ UpdateCurrentTimestamp();
+
+ // Register callbacks to be notified when event notification existence changes.
+ // This allows us to optimise the Send() path by skipping NotifyEvent() when no handlers are registered.
+ // Separate callbacks for QM and ASIL-B update their respective atomic flags for lock-free access.
+ if (parent_.GetInstanceQualityType() == QualityType::kASIL_QM)
+ {
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .RegisterEventNotificationExistenceChangedCallback(
+ QualityType::kASIL_QM, event_fqn_, [this](const bool has_handlers) noexcept {
+ SetQmNotificationsRegistered(has_handlers);
+ });
+ }
+ if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
+ {
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .RegisterEventNotificationExistenceChangedCallback(
+ QualityType::kASIL_B, event_fqn_, [this](const bool has_handlers) noexcept {
+ SetAsilBNotificationsRegistered(has_handlers);
+ });
+ }
+
+}
+
+void SkeletonEventCommon::PrepareStopOfferCommon() noexcept
+{
+ // Unregister event notification existence changed callbacks
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_QM, event_fqn_);
+
+ if (parent_.GetInstanceQualityType() == QualityType::kASIL_B)
+ {
+ GetBindingRuntime(BindingType::kLoLa)
+ .GetLolaMessaging()
+ .UnregisterEventNotificationExistenceChangedCallback(QualityType::kASIL_B, event_fqn_);
+ }
+
+ // Reset the flags to indicate no handlers are registered
+ SetQmNotificationsRegistered(false);
+ SetAsilBNotificationsRegistered(false);
+
+ ResetGuards();
+}
+
+void SkeletonEventCommon::EmplaceTransactionLogRegistrationGuard()
+{
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_ref_.has_value(), "EventDataControlComposite must be initialized.");
+ score::cpp::ignore = transaction_log_registration_guard_.emplace(
+ TransactionLogRegistrationGuard::Create(event_data_control_composite_ref_.value().GetQmEventDataControl()));
+}
+
+void SkeletonEventCommon::EmplaceTypeErasedSamplePtrsGuard()
+{
+ score::cpp::ignore = type_erased_sample_ptrs_guard_.emplace(tracing_data_.service_element_tracing_data);
+}
+
+void SkeletonEventCommon::UpdateCurrentTimestamp()
+{
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(event_data_control_composite_ref_.has_value(), "EventDataControlComposite must be initialized.");
+ current_timestamp_ref_ = event_data_control_composite_ref_.value().GetLatestTimestamp();
+}
+
+void SkeletonEventCommon::SetQmNotificationsRegistered(bool value)
+{
+ qm_event_update_notifications_registered_.store(value);
+}
+
+void SkeletonEventCommon::SetAsilBNotificationsRegistered(bool value)
+{
+ asil_b_event_update_notifications_registered_.store(value);
+}
+
+void SkeletonEventCommon::ResetGuards() noexcept
+{
+ type_erased_sample_ptrs_guard_.reset();
+ if (event_data_control_composite_ref_.has_value())
+ {
+ transaction_log_registration_guard_.reset();
+ }
+}
+
+bool SkeletonEventCommon::IsQmNotificationsRegistered() const noexcept
+{
+
+ return qm_event_update_notifications_registered_.load();
+}
+
+bool SkeletonEventCommon::IsAsilBNotificationsRegistered() const noexcept
+{
+
+ return asil_b_event_update_notifications_registered_.load();
+}
+
+} // namespace score::mw::com::impl::lola
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_common.h b/score/mw/com/impl/bindings/lola/skeleton_event_common.h
new file mode 100644
index 000000000..0772b03f1
--- /dev/null
+++ b/score/mw/com/impl/bindings/lola/skeleton_event_common.h
@@ -0,0 +1,110 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+
+#include "score/mw/com/impl/bindings/lola/element_fq_id.h"
+#include "score/mw/com/impl/bindings/lola/transaction_log_registration_guard.h"
+#include "score/mw/com/impl/bindings/lola/type_erased_sample_ptrs_guard.h"
+#include "score/mw/com/impl/runtime.h"
+#include "score/mw/com/impl/tracing/skeleton_event_tracing.h"
+#include "score/mw/com/impl/tracing/skeleton_event_tracing_data.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace score::mw::com::impl::lola
+{
+
+template
+class SkeletonEventAttorney;
+
+class Skeleton;
+
+/// @brief Common implementation for LoLa skeleton events, shared between SkeletonEvent and GenericSkeletonEvent.
+class SkeletonEventCommon
+{
+ // Grant friendship to allow access to private helpers
+ // The "SkeletonEventAttorney" class is a helper, which sets the internal state of "SkeletonEventCommon" accessing
+ // private members and used for testing purposes only.
+
+ template
+ friend class SkeletonEventAttorney;
+
+ public:
+ SkeletonEventCommon(Skeleton& parent,
+ const ElementFqId& event_fqn,
+ score::cpp::optional& event_data_control_composite_ref,
+ EventSlotStatus::EventTimeStamp& current_timestamp_ref,
+ impl::tracing::SkeletonEventTracingData tracing_data = {}) noexcept;
+
+
+ SkeletonEventCommon(const SkeletonEventCommon&) = delete;
+ SkeletonEventCommon(SkeletonEventCommon&&) noexcept = delete;
+ SkeletonEventCommon& operator=(const SkeletonEventCommon&) & = delete;
+ SkeletonEventCommon& operator=(SkeletonEventCommon&&) & noexcept = delete;
+
+ ~SkeletonEventCommon() = default;
+
+ void PrepareOfferCommon() noexcept;
+ void PrepareStopOfferCommon() noexcept;
+
+ // Accessors for members used by PrepareOfferCommon/PrepareStopOfferCommon
+ impl::tracing::SkeletonEventTracingData& GetTracingData() { return tracing_data_; }
+ const ElementFqId& GetElementFQId() const { return event_fqn_; }
+ Skeleton& GetParent() { return parent_; }
+
+ // Accessors for atomic flags for derived classes' Send() method
+ bool IsQmNotificationsRegistered() const noexcept;
+ bool IsAsilBNotificationsRegistered() const noexcept;
+
+
+ private:
+ Skeleton& parent_;
+ const ElementFqId event_fqn_;
+ score::cpp::optional& event_data_control_composite_ref_; // Reference to the optional in derived class
+ EventSlotStatus::EventTimeStamp& current_timestamp_ref_; // Reference to the timestamp in derived class
+ impl::tracing::SkeletonEventTracingData tracing_data_;
+
+ /// \brief Atomic flags indicating whether any receive handlers are currently registered for this event
+ /// at each quality level (QM and ASIL-B).
+ /// \details These flags are updated via callbacks from MessagePassingServiceInstance when handler
+ /// registration status changes. They allow Send() to skip the NotifyEvent() call when no
+ /// handlers are registered for a specific quality level, avoiding unnecessary lock overhead
+ /// in the main path. Uses memory_order_relaxed as the flags are optimisation hints - false
+ /// positives (thinking handlers exist when they don't) are harmless, and false negatives
+ /// (missing handlers) are prevented by the callback mechanism.
+ std::atomic qm_event_update_notifications_registered_{false};
+ std::atomic asil_b_event_update_notifications_registered_{false};
+
+ /// \brief optional RAII guards for tracing transaction log registration/un-registration and cleanup of "pending"
+ /// type erased sample pointers which are created in PrepareOfferCommon() and destroyed in PrepareStopOfferCommon() - optional
+ /// as only needed when tracing is enabled and when they haven't been cleaned up via a call to PrepareStopOfferCommon().
+ std::optional transaction_log_registration_guard_{};
+ std::optional type_erased_sample_ptrs_guard_{};
+
+ void EmplaceTransactionLogRegistrationGuard();
+ void EmplaceTypeErasedSamplePtrsGuard();
+ void UpdateCurrentTimestamp();
+ void SetQmNotificationsRegistered(bool value);
+ void SetAsilBNotificationsRegistered(bool value);
+ void ResetGuards() noexcept;
+
+};
+
+} // namespace score::mw::com::impl::lola
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp
index 134d0ae04..e16202e76 100644
--- a/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp
+++ b/score/mw/com/impl/bindings/lola/skeleton_event_test.cpp
@@ -133,6 +133,41 @@ TEST_F(SkeletonEventAllocateFixture, SkeletonEventWithNotMaxSamplesEnforcementAl
EXPECT_EQ(allocate_result.error(), ComErrc::kBindingFailure);
}
+TEST_F(SkeletonEventAllocateFixture, AllocateReturnsUniquePointersForMultipleCalls)
+{
+ RecordProperty("Description", "Checks that multiple calls to Allocate() return unique memory pointers.");
+ RecordProperty("TestType", "Unit Test");
+
+ const bool enforce_max_samples{true};
+ const size_t num_allocations = 3;
+ ASSERT_LE(num_allocations, max_samples_);
+
+ // Given an offered event
+ InitialiseSkeletonEvent(fake_element_fq_id_, fake_event_name_, max_samples_, max_subscribers_, enforce_max_samples);
+ skeleton_event_->PrepareOffer();
+
+ // When allocating multiple samples without sending them
+ std::vector> allocated_pointers;
+ std::vector raw_pointers;
+
+ for (size_t i = 0; i < num_allocations; ++i)
+ {
+ auto alloc_result = skeleton_event_->Allocate();
+ ASSERT_TRUE(alloc_result.has_value()) << "Allocation " << i << " failed";
+
+ // Store the raw pointer to check for uniqueness
+ raw_pointers.push_back(alloc_result.value().Get());
+
+ // Keep the SampleAllocateePtr alive to keep the slot busy
+ allocated_pointers.push_back(std::move(alloc_result.value()));
+ }
+
+ // Then all allocated raw pointers should be unique
+ std::sort(raw_pointers.begin(), raw_pointers.end());
+ auto it = std::unique(raw_pointers.begin(), raw_pointers.end());
+ EXPECT_EQ(it, raw_pointers.end()) << "Duplicate memory addresses were allocated.";
+}
+
using SkeletonEventPrepareOfferFixture = SkeletonEventFixture;
TEST_F(SkeletonEventPrepareOfferFixture, SubscriptionsAcceptedIfMaxSamplesCanBeProvided)
{
diff --git a/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp
index 8e996be04..073290c3e 100644
--- a/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp
+++ b/score/mw/com/impl/bindings/lola/skeleton_event_tracing_test.cpp
@@ -50,7 +50,7 @@ class SkeletonEventAttorney
skeleton_event_.qm_disconnect_ = qm_disconnect_value;
}
- std::optional& GetEventDataControlComposite()
+ score::cpp::optional& GetEventDataControlComposite()
{
return skeleton_event_.event_data_control_composite_;
}
diff --git a/score/mw/com/impl/bindings/lola/skeleton_test.cpp b/score/mw/com/impl/bindings/lola/skeleton_test.cpp
index 316083429..f1d6b8f04 100644
--- a/score/mw/com/impl/bindings/lola/skeleton_test.cpp
+++ b/score/mw/com/impl/bindings/lola/skeleton_test.cpp
@@ -1153,11 +1153,11 @@ TEST_P(SkeletonRegisterParamaterisedFixture, ValidEventMetaInfoExistAfterEventIs
ASSERT_TRUE(event_foo_meta_info_ptr.has_value());
ASSERT_TRUE(event_dumb_meta_info_ptr.has_value());
// and they have the expected properties
- ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.size_of_, sizeof(std::uint8_t));
- ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.align_of_, alignof(std::uint8_t));
+ ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.size, sizeof(std::uint8_t));
+ ASSERT_EQ(event_foo_meta_info_ptr->data_type_info_.alignment, alignof(std::uint8_t));
- ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.size_of_, sizeof(VeryComplexType));
- ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.align_of_, alignof(VeryComplexType));
+ ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.size, sizeof(VeryComplexType));
+ ASSERT_EQ(event_dumb_meta_info_ptr->data_type_info_.alignment, alignof(VeryComplexType));
const auto GetEventSlotsArraySize = [](const std::size_t sample_size,
const std::size_t sample_alignment,
@@ -1167,13 +1167,13 @@ TEST_P(SkeletonRegisterParamaterisedFixture, ValidEventMetaInfoExistAfterEventIs
return aligned_size * number_of_sample_slots;
};
- const auto foo_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size_of_,
- event_foo_meta_info_ptr->data_type_info_.align_of_,
+ const auto foo_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size,
+ event_foo_meta_info_ptr->data_type_info_.alignment,
test::kDefaultEventProperties.number_of_slots);
ASSERT_EQ(event_foo_meta_info_ptr->event_slots_raw_array_.get(foo_event_slots_size), foo_event_data_storage);
- const auto dumb_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size_of_,
- event_foo_meta_info_ptr->data_type_info_.align_of_,
+ const auto dumb_event_slots_size = GetEventSlotsArraySize(event_foo_meta_info_ptr->data_type_info_.size,
+ event_foo_meta_info_ptr->data_type_info_.alignment,
test::kDefaultEventProperties.number_of_slots);
ASSERT_EQ(event_dumb_meta_info_ptr->event_slots_raw_array_.get(dumb_event_slots_size), dumb_event_data_storage);
diff --git a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp
index 6c87efdb0..0325af54c 100644
--- a/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp
+++ b/score/mw/com/impl/bindings/lola/test/skeleton_event_component_test.cpp
@@ -56,8 +56,8 @@ class SkeletonEventAttorney
/// \brief Set handler availability flags for testing purposes
void SetHandlerAvailability(bool qm_available, bool asil_b_available)
{
- skeleton_event_.qm_event_update_notifications_registered_.store(qm_available);
- skeleton_event_.asil_b_event_update_notifications_registered_.store(asil_b_available);
+ skeleton_event_.event_shared_impl_.SetQmNotificationsRegistered(qm_available);
+ skeleton_event_.event_shared_impl_.SetAsilBNotificationsRegistered(asil_b_available);
}
private:
@@ -316,8 +316,8 @@ TEST_F(SkeletonEventComponentTestFixture, SkeletonWillCalculateEventMetaInfoFrom
// Then the event meta info should correspond to the type of the skeleton event
ASSERT_TRUE(event_meta_info.has_value());
- EXPECT_EQ(event_meta_info.value().data_type_info_.align_of_, alignof(SkeletonEventSampleType));
- EXPECT_EQ(event_meta_info.value().data_type_info_.size_of_, sizeof(SkeletonEventSampleType));
+ EXPECT_EQ(event_meta_info.value().data_type_info_.alignment, alignof(SkeletonEventSampleType));
+ EXPECT_EQ(event_meta_info.value().data_type_info_.size, sizeof(SkeletonEventSampleType));
}
using SkeletonEventComponentDeathTest = SkeletonEventComponentTestFixture;
diff --git a/score/mw/com/impl/bindings/mock_binding/BUILD b/score/mw/com/impl/bindings/mock_binding/BUILD
index 62c023624..5f72e1239 100644
--- a/score/mw/com/impl/bindings/mock_binding/BUILD
+++ b/score/mw/com/impl/bindings/mock_binding/BUILD
@@ -19,6 +19,7 @@ cc_library(
testonly = True,
srcs = [
"generic_proxy_event.cpp",
+ "generic_skeleton_event.cpp",
"proxy.cpp",
"proxy_event.cpp",
"proxy_method.cpp",
@@ -28,6 +29,7 @@ cc_library(
],
hdrs = [
"generic_proxy_event.h",
+ "generic_skeleton_event.h",
"proxy.h",
"proxy_event.h",
"proxy_method.h",
@@ -42,6 +44,7 @@ cc_library(
],
deps = [
":sample_ptr",
+ "//score/mw/com/impl:generic_skeleton_event_binding",
"//score/mw/com/impl:generic_proxy_event_binding",
"//score/mw/com/impl:proxy_binding",
"//score/mw/com/impl:proxy_event_binding",
@@ -55,6 +58,19 @@ cc_library(
],
)
+cc_library(
+ name = "generic_skeleton_event",
+ testonly = True,
+ srcs = ["generic_skeleton_event.cpp"],
+ hdrs = ["generic_skeleton_event.h"],
+ features = COMPILER_WARNING_FEATURES,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//score/mw/com/impl:generic_skeleton_event_binding",
+ "@googletest//:gtest",
+ ],
+)
+
cc_library(
name = "sample_ptr",
srcs = ["sample_ptr.cpp"],
@@ -66,3 +82,15 @@ cc_library(
"@score_baselibs//score/language/futurecpp",
],
)
+
+
+cc_library(
+ name = "sample_allocatee_ptr",
+ hdrs = ["sample_allocatee_ptr.h"],
+ features = COMPILER_WARNING_FEATURES,
+ tags = ["FFI"],
+ visibility = ["//score/mw/com/impl/plumbing:__pkg__"],
+ deps = [
+ "@score_baselibs//score/language/futurecpp",
+ ],
+)
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp
new file mode 100644
index 000000000..89b8190a0
--- /dev/null
+++ b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.cpp
@@ -0,0 +1,13 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h"
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h
new file mode 100644
index 000000000..e7ba1f37b
--- /dev/null
+++ b/score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#ifndef SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H
+#define SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H
+
+#include "score/mw/com/impl/generic_skeleton_event_binding.h"
+#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
+
+#include
+
+namespace score::mw::com::impl::mock_binding
+{
+
+class GenericSkeletonEvent : public GenericSkeletonEventBinding
+{
+ public:
+ // Use explicit 'score::mw::com::impl::SampleAllocateePtr' to avoid ambiguity
+ // with the 'mock_binding::SampleAllocateePtr' alias (which is a unique_ptr).
+
+ MOCK_METHOD(Result, Send,
+ (score::mw::com::impl::SampleAllocateePtr),
+ (noexcept, override));
+
+ MOCK_METHOD(Result>, Allocate,
+ (),
+ (noexcept, override));
+
+ MOCK_METHOD((std::pair), GetSizeInfo, (), (const, noexcept, override));
+ MOCK_METHOD(ResultBlank, PrepareOffer, (), (noexcept, override));
+ MOCK_METHOD(void, PrepareStopOffer, (), (noexcept, override));
+ MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override));
+ MOCK_METHOD(void, SetSkeletonEventTracingData, (impl::tracing::SkeletonEventTracingData), (noexcept, override));
+ MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override));
+};
+
+} // namespace score::mw::com::impl::mock_binding
+
+#endif // SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_GENERIC_SKELETON_EVENT_H
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp
new file mode 100644
index 000000000..37464c942
--- /dev/null
+++ b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.cpp
@@ -0,0 +1,13 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h"
diff --git a/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h
new file mode 100644
index 000000000..63c26004f
--- /dev/null
+++ b/score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#ifndef SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H
+#define SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H
+
+#include
+#include
+
+namespace score::mw::com::impl::mock_binding
+{
+
+template
+using CustomDeleter = score::cpp::callback;
+
+/// \brief SampleAllocateePtr used for the mock binding.
+///
+/// The SampleAllocateePtr is an alias for a unique_ptr with a custom deleter.
+/// This matches the logic used in SamplePtr to support 'void' types safely.
+///
+/// @tparam SampleType The data type managed by this pointer.
+template
+using SampleAllocateePtr = std::unique_ptr>;
+
+} // namespace score::mw::com::impl::mock_binding
+
+#endif // SCORE_MW_COM_IMPL_BINDINGS_MOCK_BINDING_SAMPLE_ALLOCATEE_PTR_H
\ No newline at end of file
diff --git a/score/mw/com/impl/bindings/mock_binding/skeleton_event.h b/score/mw/com/impl/bindings/mock_binding/skeleton_event.h
index 7bf6e5e74..e6ac6df1e 100644
--- a/score/mw/com/impl/bindings/mock_binding/skeleton_event.h
+++ b/score/mw/com/impl/bindings/mock_binding/skeleton_event.h
@@ -43,10 +43,10 @@ class SkeletonEvent : public SkeletonEventBinding
(noexcept, override));
MOCK_METHOD(ResultBlank,
Send,
- (SampleAllocateePtr sample,
+ (score::mw::com::impl::SampleAllocateePtr sample,
score::cpp::optional::SendTraceCallback>),
(noexcept, override));
- MOCK_METHOD(Result>, Allocate, (), (noexcept, override));
+ MOCK_METHOD(Result>, Allocate, (), (noexcept, override));
MOCK_METHOD(ResultBlank, PrepareOffer, (), (noexcept, override));
MOCK_METHOD(void, PrepareStopOffer, (), (noexcept, override));
MOCK_METHOD(std::size_t, GetMaxSize, (), (const, noexcept, override));
@@ -73,12 +73,12 @@ class SkeletonEventFacade : public SkeletonEventBinding
return skeleton_event_.Send(value, std::move(callback));
};
ResultBlank Send(
- SampleAllocateePtr sample,
+ score::mw::com::impl::SampleAllocateePtr sample,
score::cpp::optional::SendTraceCallback> callback) noexcept override
{
return skeleton_event_.Send(std::move(sample), std::move(callback));
}
- Result> Allocate() noexcept override
+ Result> Allocate() noexcept override
{
return skeleton_event_.Allocate();
};
diff --git a/score/mw/com/impl/com_error.h b/score/mw/com/impl/com_error.h
index f37e097e5..3ca1145dc 100644
--- a/score/mw/com/impl/com_error.h
+++ b/score/mw/com/impl/com_error.h
@@ -59,6 +59,7 @@ enum class ComErrc : score::result::ErrorCode
kFindServiceHandlerFailure,
kInvalidHandle,
kCallQueueFull,
+ kServiceElementAlreadyExists,
kNumEnumElements
};
@@ -199,6 +200,9 @@ class ComErrorDomain final : public score::result::ErrorDomain
case static_cast(ComErrc::kCallQueueFull):
return "Call queue of service method is already full.";
// coverity[autosar_cpp14_m6_4_5_violation]
+ case static_cast(ComErrc::kServiceElementAlreadyExists):
+ return "A service element (event, field, method) with the same name already exists.";
+ // coverity[autosar_cpp14_m6_4_5_violation]
case static_cast(ComErrc::kInvalid):
case static_cast(ComErrc::kNumEnumElements):
SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(false,
diff --git a/score/mw/com/impl/bindings/lola/data_type_meta_info.cpp b/score/mw/com/impl/data_type_meta_info.cpp
similarity index 89%
rename from score/mw/com/impl/bindings/lola/data_type_meta_info.cpp
rename to score/mw/com/impl/data_type_meta_info.cpp
index c87ba95c0..9e0ba97c7 100644
--- a/score/mw/com/impl/bindings/lola/data_type_meta_info.cpp
+++ b/score/mw/com/impl/data_type_meta_info.cpp
@@ -10,4 +10,4 @@
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
-#include "score/mw/com/impl/bindings/lola/data_type_meta_info.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
diff --git a/score/mw/com/impl/bindings/lola/data_type_meta_info.h b/score/mw/com/impl/data_type_meta_info.h
similarity index 88%
rename from score/mw/com/impl/bindings/lola/data_type_meta_info.h
rename to score/mw/com/impl/data_type_meta_info.h
index 9e81895f7..63285578e 100644
--- a/score/mw/com/impl/bindings/lola/data_type_meta_info.h
+++ b/score/mw/com/impl/data_type_meta_info.h
@@ -16,17 +16,17 @@
#include
#include
-namespace score::mw::com::impl::lola
+namespace score::mw::com::impl
{
/// \brief Meta-info of a data type exchanged via mw::com/LoLa. I.e. can be the data type of an event/filed/method arg.
struct DataTypeMetaInfo
{
//@todo -> std::uint64_t fingerprint
- std::size_t size_of_;
- std::uint8_t align_of_;
+ std::size_t size;
+ std::size_t alignment;
};
-} // namespace score::mw::com::impl::lola
+} // namespace score::mw::com::impl
#endif // SCORE_MW_COM_IMPL_BINDINGS_LOLA_DATA_TYPE_META_INFO_H
diff --git a/score/mw/com/impl/generic_skeleton.cpp b/score/mw/com/impl/generic_skeleton.cpp
new file mode 100644
index 000000000..8297f5379
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton.cpp
@@ -0,0 +1,153 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/generic_skeleton.h"
+
+#include "score/mw/com/impl/com_error.h"
+#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h"
+#include "score/mw/com/impl/runtime.h"
+#include "score/mw/com/impl/plumbing/skeleton_binding_factory.h"
+#include "score/mw/com/impl/skeleton_binding.h"
+#include "score/mw/com/impl/configuration/lola_service_type_deployment.h"
+
+#include
+
+#include
+#include
+#include
+
+namespace score::mw::com::impl
+{
+
+namespace
+{
+// Helper to fetch the stable event name from the Configuration
+std::string_view GetEventName(const InstanceIdentifier& identifier, std::string_view search_name)
+{
+ const auto& service_type_deployment = InstanceIdentifierView{identifier}.GetServiceTypeDeployment();
+
+ auto visitor = score::cpp::overload(
+ [&](const LolaServiceTypeDeployment& deployment) -> std::string_view {
+ const auto it = deployment.events_.find(std::string{search_name});
+ if (it != deployment.events_.end())
+ {
+ return it->first; // Return the stable address of the Key from the Config Map
+ }
+ return {};
+ },
+ [](const score::cpp::blank&) noexcept -> std::string_view {
+ return {};
+ }
+ );
+
+ return std::visit(visitor, service_type_deployment.binding_info_);
+}
+} // namespace
+
+Result GenericSkeleton::Create(
+ const InstanceSpecifier& specifier,
+ const GenericSkeletonCreateParams& in) noexcept
+{
+ const auto instance_identifier_result = GetInstanceIdentifier(specifier);
+
+ if (!instance_identifier_result.has_value())
+ {
+ score::mw::log::LogError("GenericSkeleton") << "Failed to resolve instance identifier from instance specifier";
+ return MakeUnexpected(ComErrc::kInstanceIDCouldNotBeResolved);
+ }
+
+ return Create(instance_identifier_result.value(), in);
+}
+
+Result GenericSkeleton::Create(
+ const InstanceIdentifier& identifier,
+ const GenericSkeletonCreateParams& in) noexcept
+{
+ auto binding = SkeletonBindingFactory::Create(identifier);
+ if (!binding)
+ {
+
+ score::mw::log::LogError("GenericSkeleton") << "Failed to create SkeletonBinding for the given identifier.";
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+
+ // 1. Create the Skeleton (Private Constructor)
+ GenericSkeleton skeleton(identifier, std::move(binding));
+
+ // 2. Create events directly in the map
+ for (const auto& info : in.events)
+ {
+ // Check for duplicates
+ if (skeleton.events_.find(info.name) != skeleton.events_.cend())
+ {
+ score::mw::log::LogError("GenericSkeleton") << "Duplicate event name provided: " << info.name;
+ return MakeUnexpected(ComErrc::kServiceElementAlreadyExists);
+ }
+
+ // 1. Fetch the STABLE Name from Configuration
+ std::string_view stable_name = GetEventName(identifier, info.name);
+
+ if (stable_name.empty())
+ {
+ score::mw::log::LogError("GenericSkeleton") << "Event name not found in configuration: " << info.name;
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+
+ auto event_binding_result = GenericSkeletonEventBindingFactory::Create(skeleton, info.name, info.data_type_meta_info);
+
+ if (!event_binding_result.has_value())
+ {
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+
+ const auto emplace_result = skeleton.events_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(stable_name),
+ std::forward_as_tuple(
+ skeleton,
+ stable_name,
+ std::move(event_binding_result).value()
+ )
+ );
+
+ if (!emplace_result.second)
+ {
+ score::mw::log::LogError("GenericSkeleton") << "Failed to emplace event in map: " << info.name;
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+ }
+
+ return skeleton;
+}
+
+const GenericSkeleton::EventMap& GenericSkeleton::GetEvents() const noexcept
+{
+ return events_;
+}
+
+Result GenericSkeleton::OfferService() noexcept
+{
+ return SkeletonBase::OfferService();
+}
+
+void GenericSkeleton::StopOfferService() noexcept
+{
+ SkeletonBase::StopOfferService();
+}
+
+GenericSkeleton::GenericSkeleton(const InstanceIdentifier& identifier,
+ std::unique_ptr binding)
+ : SkeletonBase(std::move(binding), identifier)
+{
+}
+
+} // namespace score::mw::com::impl
\ No newline at end of file
diff --git a/score/mw/com/impl/generic_skeleton.h b/score/mw/com/impl/generic_skeleton.h
new file mode 100644
index 000000000..4a44d666b
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton.h
@@ -0,0 +1,114 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "score/mw/com/impl/generic_skeleton_event.h"
+// #include "score/mw/com/impl/generic_skeleton_field_binding.h" // New include - commented out as field not implemented
+// #include "score/mw/com/impl/generic_skeleton_field.h" // commented out as field not implemented
+#include "score/mw/com/impl/instance_identifier.h"
+#include "score/mw/com/impl/instance_specifier.h"
+#include "score/mw/com/impl/skeleton_base.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
+#include "score/mw/com/impl/service_element_map.h"
+#include "score/result/result.h"
+#include
+
+#include
+#include
+#include
+#include
+
+namespace score::mw::com::impl
+{
+
+struct EventInfo
+{
+ std::string_view name;
+ DataTypeMetaInfo data_type_meta_info;
+};
+// struct FieldInfo // commented out as field not implemented
+// {
+// std::string_view name;
+// DataTypeMetaInfo data_type_meta_info;
+// /// @brief The initial value for the field.
+// /// @note Must be non-empty.
+// /// @note The data must remain valid only for the duration of the Create() call.
+// /// `initial_value_bytes.size()` must be less than or equal to `size_info.size`.
+// /// The bytes are in the middleware’s “generic” field representation.
+// score::cpp::span initial_value_bytes;
+//
+// };
+struct GenericSkeletonCreateParams
+{
+ score::cpp::span events{};
+ //score::cpp::span fields{};
+};
+
+/// @brief Represents a type-erased, runtime-configurable skeleton for a service instance.
+///
+/// A `GenericSkeleton` is created at runtime based on configuration data. It manages
+/// a collection of `GenericSkeletonEvent` and `GenericSkeletonField` instances.
+
+class GenericSkeleton : public SkeletonBase
+{
+ public:
+ using EventMap = ServiceElementMap;
+// using FieldMap = ServiceElementMap; // commented out as field not implemented
+/// @brief Creates a GenericSkeleton and all its service elements (events + fields) atomically.
+///
+/// @contract
+/// - Empty spans are allowed for `in.events` and/or `in.fields`
+/// - Each provided name must exist in the binding deployment for this instance (events/fields respectively).
+/// - All element names must be unique across all element kinds within this skeleton.
+/// - For each field, `initial_value_bytes` must be non-empty and
+/// `initial_value_bytes.size()` must be <= `size_info.size`.
+/// - On error, no partially-created elements are left behind.
+[[nodiscard]] static Result Create(
+ const InstanceIdentifier& identifier,
+ const GenericSkeletonCreateParams& in) noexcept;
+
+/// @brief Same as Create(InstanceIdentifier, ...) but resolves the specifier first.
+/// @param specifier The instance specifier.
+/// @param in Input parameters for creation.
+/// @param mode The method call processing mode.
+/// @return A GenericSkeleton or an error.
+ [[nodiscard]] static Result Create(
+ const InstanceSpecifier& specifier,
+ const GenericSkeletonCreateParams& in) noexcept;
+
+/// @brief Returns a const reference to the name-keyed map of events.
+/// @note The returned reference is valid as long as the GenericSkeleton lives.
+[[nodiscard]] const EventMap& GetEvents() const noexcept;
+
+
+/// @brief Returns a const reference to the name-keyed map of fields.
+/// @note The returned reference is valid as long as the GenericSkeleton lives.
+//[[nodiscard]] const FieldMap& GetFields() const noexcept;
+
+/// @brief Offers the service instance.
+/// @return A blank result, or an error if offering fails.
+[[nodiscard]] Result OfferService() noexcept;
+
+/// @brief Stops offering the service instance.
+void StopOfferService() noexcept;
+ private:
+ // Private constructor, only callable by static Create methods.
+ GenericSkeleton(const InstanceIdentifier& identifier, std::unique_ptr binding);
+
+ /// @brief This map owns all GenericSkeletonEvent instances.
+ EventMap events_;
+
+ /// @brief This map owns all GenericSkeletonField instances.
+// FieldMap fields_; // commented out as field not implemented
+};
+} // namespace score::mw::com::impl
\ No newline at end of file
diff --git a/score/mw/com/impl/generic_skeleton_event.cpp b/score/mw/com/impl/generic_skeleton_event.cpp
new file mode 100644
index 000000000..59d93a4d8
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton_event.cpp
@@ -0,0 +1,101 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/generic_skeleton_event.h"
+#include "score/mw/com/impl/generic_skeleton_event_binding.h"
+#include "score/mw/com/impl/tracing/skeleton_event_tracing.h"
+#include "score/mw/com/impl/skeleton_base.h"
+#include "score/mw/com/impl/com_error.h"
+
+#include
+
+namespace score::mw::com::impl
+{
+
+GenericSkeletonEvent::GenericSkeletonEvent(SkeletonBase& skeleton_base,
+ const std::string_view event_name,
+ std::unique_ptr binding)
+ : SkeletonEventBase(skeleton_base, event_name, std::move(binding))
+{
+ SkeletonBaseView{skeleton_base}.RegisterEvent(event_name, *this);
+
+ if (binding_ != nullptr)
+ {
+ const SkeletonBaseView skeleton_base_view{skeleton_base};
+ const auto& instance_identifier = skeleton_base_view.GetAssociatedInstanceIdentifier();
+ auto* const binding_ptr = static_cast(binding_.get());
+ if (binding_ptr)
+ {
+ const auto binding_type = binding_ptr->GetBindingType();
+ auto tracing_data =
+ tracing::GenerateSkeletonTracingStructFromEventConfig(instance_identifier, binding_type, event_name);
+ binding_ptr->SetSkeletonEventTracingData(tracing_data);
+ }
+ }
+}
+
+Result GenericSkeletonEvent::Send(SampleAllocateePtr sample) noexcept
+{
+ if (!service_offered_flag_.IsSet())
+ {
+ score::mw::log::LogError("lola")
+ << "GenericSkeletonEvent::Send failed as Event has not yet been offered or has been stop offered";
+ return MakeUnexpected(ComErrc::kNotOffered);
+ }
+
+ SCORE_LANGUAGE_FUTURECPP_ASSERT_PRD_MESSAGE(binding_ != nullptr, "Binding is not initialized!");
+ auto* const binding = static_cast(binding_.get());
+
+
+ const auto send_result = binding->Send(std::move(sample));
+
+ if (!send_result.has_value())
+ {
+ score::mw::log::LogError("lola") << "GenericSkeletonEvent::Send failed: " << send_result.error().Message()
+ << ": " << send_result.error().UserMessage();
+ return MakeUnexpected(ComErrc::kBindingFailure);
+ }
+ return send_result;
+}
+
+Result> GenericSkeletonEvent::Allocate() noexcept
+{
+ if (!service_offered_flag_.IsSet())
+ {
+ score::mw::log::LogError("lola")
+ << "GenericSkeletonEvent::Allocate failed as Event has not yet been offered or has been stop offered";
+ return MakeUnexpected(ComErrc::kNotOffered);
+ }
+ auto* const binding = static_cast(binding_.get());
+
+
+ auto result = binding->Allocate();
+
+ if (!result.has_value())
+ {
+ score::mw::log::LogError("lola") << "SkeletonEvent::Allocate failed: " << result.error().Message()
+ << ": " << result.error().UserMessage();
+
+ return MakeUnexpected>(ComErrc::kSampleAllocationFailure);
+ }
+ return result;
+}
+
+DataTypeMetaInfo GenericSkeletonEvent::GetSizeInfo() const noexcept
+{
+ const auto* const binding = static_cast(binding_.get());
+ if (!binding) return {};
+ const auto size_info_pair = binding->GetSizeInfo();
+ return {size_info_pair.first, size_info_pair.second};
+}
+
+} // namespace score::mw::com::impl
\ No newline at end of file
diff --git a/score/mw/com/impl/generic_skeleton_event.h b/score/mw/com/impl/generic_skeleton_event.h
new file mode 100644
index 000000000..c20d0d953
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton_event.h
@@ -0,0 +1,41 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "score/mw/com/impl/skeleton_event_base.h"
+#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
+#include "score/result/result.h"
+#include "score/mw/com/impl/data_type_meta_info.h"
+
+#include
+
+namespace score::mw::com::impl
+{
+
+class GenericSkeletonEventBinding;
+
+class GenericSkeletonEvent : public SkeletonEventBase
+{
+ public:
+ GenericSkeletonEvent(SkeletonBase& skeleton_base,
+ const std::string_view event_name,
+ std::unique_ptr binding);
+
+ Result Send(SampleAllocateePtr sample) noexcept;
+
+ Result> Allocate() noexcept;
+
+ DataTypeMetaInfo GetSizeInfo() const noexcept;
+};
+
+} // namespace score::mw::com::impl
\ No newline at end of file
diff --git a/score/mw/com/impl/generic_skeleton_event_binding.h b/score/mw/com/impl/generic_skeleton_event_binding.h
new file mode 100644
index 000000000..cd025607a
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton_event_binding.h
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "score/mw/com/impl/skeleton_event_binding.h"
+
+#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
+#include "score/result/result.h"
+
+#include
+#include
+
+namespace score::mw::com::impl
+{
+
+class GenericSkeletonEventBinding : public SkeletonEventBindingBase
+{
+ public:
+ virtual Result Send(SampleAllocateePtr sample) noexcept = 0;
+
+ virtual Result> Allocate() noexcept = 0;
+
+ virtual std::pair GetSizeInfo() const noexcept = 0;
+};
+
+} // namespace score::mw::com::impl
\ No newline at end of file
diff --git a/score/mw/com/impl/generic_skeleton_event_test.cpp b/score/mw/com/impl/generic_skeleton_event_test.cpp
new file mode 100644
index 000000000..1bcebd1b6
--- /dev/null
+++ b/score/mw/com/impl/generic_skeleton_event_test.cpp
@@ -0,0 +1,356 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "score/mw/com/impl/generic_skeleton.h"
+#include "score/mw/com/impl/generic_skeleton_event.h"
+
+#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h"
+#include "score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h"
+#include "score/mw/com/impl/bindings/mock_binding/generic_skeleton_event.h"
+#include "score/mw/com/impl/bindings/mock_binding/skeleton.h"
+#include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h"
+
+#include "score/mw/com/impl/test/binding_factory_resources.h"
+#include "score/mw/com/impl/i_binding_runtime.h"
+#include "score/mw/com/impl/service_discovery_client_mock.h"
+#include "score/mw/com/impl/test/runtime_mock_guard.h"
+#include "score/mw/com/impl/runtime_mock.h"
+#include "score/mw/com/impl/service_discovery_mock.h"
+#include "score/mw/com/impl/com_error.h"
+
+#include "score/mw/com/impl/test/dummy_instance_identifier_builder.h"
+#include
+#include
+
+#include
+#include
+
+namespace score::mw::com::impl {
+namespace {
+
+using ::testing::_;
+using ::testing::ByMove;
+using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+using ::testing::Invoke;
+using ::testing::Field;
+using ::testing::AllOf;
+
+// --- Helper Mocks ---
+class IBindingRuntimeMock : public IBindingRuntime {
+public:
+ MOCK_METHOD(IServiceDiscoveryClient&, GetServiceDiscoveryClient, (), (noexcept, override));
+ MOCK_METHOD(BindingType, GetBindingType, (), (const, noexcept, override));
+ MOCK_METHOD(tracing::IBindingTracingRuntime*, GetTracingRuntime, (), (noexcept, override));
+};
+
+class GenericSkeletonEventTest : public ::testing::Test {
+public:
+ GenericSkeletonEventTest() {
+ GenericSkeletonEventBindingFactory::mock_ = &generic_event_binding_factory_mock_;
+
+ ON_CALL(runtime_mock_guard_.runtime_mock_, GetBindingRuntime(BindingType::kLoLa))
+ .WillByDefault(Return(&binding_runtime_mock_));
+
+ ON_CALL(runtime_mock_guard_.runtime_mock_, GetServiceDiscovery())
+ .WillByDefault(ReturnRef(service_discovery_mock_));
+
+ ON_CALL(binding_runtime_mock_, GetBindingType())
+ .WillByDefault(Return(BindingType::kLoLa));
+
+ ON_CALL(binding_runtime_mock_, GetServiceDiscoveryClient())
+ .WillByDefault(ReturnRef(service_discovery_client_mock_));
+
+ ON_CALL(service_discovery_mock_, OfferService(_)).WillByDefault(Return(score::Blank{}));
+
+ ON_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_))
+ .WillByDefault(Invoke([this](const auto&) {
+ auto mock = std::make_unique>();
+ this->skeleton_binding_mock_ = mock.get();
+ ON_CALL(*mock, PrepareOffer(_, _, _)).WillByDefault(Return(score::Blank{}));
+ return mock;
+ }));
+ }
+
+ ~GenericSkeletonEventTest() override {
+ GenericSkeletonEventBindingFactory::mock_ = nullptr;
+ }
+
+ // Mocks
+ NiceMock generic_event_binding_factory_mock_;
+ RuntimeMockGuard runtime_mock_guard_{};
+ NiceMock binding_runtime_mock_{};
+ NiceMock service_discovery_mock_{};
+ NiceMock service_discovery_client_mock_{};
+ SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{};
+
+ // Pointers and Helpers
+ mock_binding::Skeleton* skeleton_binding_mock_{nullptr};
+ DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_;
+};
+
+TEST_F(GenericSkeletonEventTest, AllocateBeforeOfferReturnsError)
+{
+ RecordProperty("Description", "Checks that calling Allocate() before OfferService() returns kNotOffered.");
+ RecordProperty("TestType", "Requirements-based test");
+
+ // Given a skeleton created with one event "test_event"
+ const DataTypeMetaInfo size_info{16, 8};
+ const std::string event_name = "test_event";
+
+ GenericSkeletonCreateParams create_params;
+ std::vector events;
+ events.push_back({event_name, size_info});
+ create_params.events = events;
+
+ EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _))
+ .WillOnce(Return(ByMove(std::make_unique>())));
+
+ auto skeleton_result = GenericSkeleton::Create(
+ dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(),
+ create_params
+ );
+ ASSERT_TRUE(skeleton_result.has_value());
+
+ // And given the event instance
+ auto& skeleton = skeleton_result.value();
+ const auto& events_map = skeleton.GetEvents();
+ auto it = events_map.find(event_name);
+ ASSERT_NE(it, events_map.cend());
+
+ auto* event = const_cast(&it->second);
+
+ // When calling Allocate() before OfferService()
+ auto alloc_result = event->Allocate();
+
+ // Then it fails with kNotOffered
+ ASSERT_FALSE(alloc_result.has_value());
+ EXPECT_EQ(alloc_result.error(), ComErrc::kNotOffered);
+}
+
+TEST_F(GenericSkeletonEventTest, SendBeforeOfferReturnsError)
+{
+ RecordProperty("Description", "Checks that calling Send() before OfferService() returns kNotOffered.");
+ RecordProperty("TestType", "Requirements-based test");
+
+ // Given a skeleton created with one event "test_event"
+ const std::string event_name = "test_event";
+
+ GenericSkeletonCreateParams create_params;
+ std::vector events;
+ events.push_back({event_name, {16, 8}});
+ create_params.events = events;
+
+ EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _))
+ .WillOnce(Return(ByMove(std::make_unique>())));
+
+ auto skeleton_result = GenericSkeleton::Create(
+ dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(),
+ create_params
+ );
+ ASSERT_TRUE(skeleton_result.has_value());
+
+ auto& skeleton = skeleton_result.value();
+
+ auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second);
+
+ // And a valid sample to send
+ mock_binding::SampleAllocateePtr dummy_sample{nullptr, [](void*){}};
+
+ // When calling Send() before OfferService()
+ auto send_result = event->Send(MakeSampleAllocateePtr(std::move(dummy_sample)));
+
+ // Then it fails with kNotOffered
+ ASSERT_FALSE(send_result.has_value());
+ EXPECT_EQ(send_result.error(), ComErrc::kNotOffered);
+}
+
+TEST_F(GenericSkeletonEventTest, AllocateAndSendDispatchesToBindingAfterOffer)
+{
+ RecordProperty("Description", "Checks that Allocate and Send dispatch to the binding when the service is offered.");
+ RecordProperty("TestType", "Requirements-based test");
+
+ // Given a skeleton configured with an event binding mock
+ const std::string event_name = "test_event";
+ auto mock_event_binding = std::make_unique>();
+ auto* mock_event_binding_ptr = mock_event_binding.get();
+
+ EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _))
+ .WillOnce(Return(ByMove(std::move(mock_event_binding))));
+
+ GenericSkeletonCreateParams create_params;
+ std::vector events;
+ events.push_back({event_name, {16, 8}});
+ create_params.events = events;
+
+ auto skeleton_result = GenericSkeleton::Create(
+ dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(),
+ create_params
+ );
+ ASSERT_TRUE(skeleton_result.has_value());
+ auto& skeleton = skeleton_result.value();
+
+ auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second);
+
+ // And Given the service is Offered
+ EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{}));
+ ASSERT_TRUE(skeleton.OfferService().has_value());
+
+ // When calling Allocate()
+ mock_binding::SampleAllocateePtr dummy_alloc{nullptr, [](void*){}};
+ EXPECT_CALL(*mock_event_binding_ptr, Allocate())
+ .WillOnce(Return(ByMove(MakeSampleAllocateePtr(std::move(dummy_alloc)))));
+
+ auto alloc_result = event->Allocate();
+ ASSERT_TRUE(alloc_result.has_value());
+
+ // And When calling Send() with the allocated sample
+ EXPECT_CALL(*mock_event_binding_ptr, Send(_)).WillOnce(Return(score::Blank{}));
+
+ auto send_result = event->Send(std::move(alloc_result.value()));
+
+ // Then both operations succeed
+ ASSERT_TRUE(send_result.has_value());
+}
+
+TEST_F(GenericSkeletonEventTest, AllocateReturnsErrorWhenBindingFails)
+{
+ RecordProperty("Description", "Checks that Allocate returns kSampleAllocationFailure if the binding allocation fails.");
+ RecordProperty("TestType", "Requirements-based test");
+
+ // Given a skeleton configured with an event binding mock
+ const std::string event_name = "test_event";
+ auto mock_event_binding = std::make_unique>();
+ auto* mock_event_binding_ptr = mock_event_binding.get();
+
+ EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _))
+ .WillOnce(Return(ByMove(std::move(mock_event_binding))));
+
+ GenericSkeletonCreateParams create_params;
+ std::vector events;
+ events.push_back({event_name, {16, 8}});
+ create_params.events = events;
+
+ auto skeleton_result = GenericSkeleton::Create(
+ dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(),
+ create_params
+ );
+ ASSERT_TRUE(skeleton_result.has_value());
+ auto& skeleton = skeleton_result.value();
+ auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second);
+
+ // And Given the service is Offered
+ EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{}));
+ ASSERT_TRUE(skeleton.OfferService().has_value());
+
+ // Expect the binding to fail allocation
+ EXPECT_CALL(*mock_event_binding_ptr, Allocate())
+ .WillOnce(Return(MakeUnexpected(ComErrc::kSampleAllocationFailure)));
+
+ // When calling Allocate()
+ auto alloc_result = event->Allocate();
+
+ // Then it fails with kSampleAllocationFailure
+ ASSERT_FALSE(alloc_result.has_value());
+ EXPECT_EQ(alloc_result.error(), ComErrc::kSampleAllocationFailure);
+}
+
+
+TEST_F(GenericSkeletonEventTest, SendReturnsErrorWhenBindingFails)
+{
+ RecordProperty("Description", "Checks that Send returns kBindingFailure if the binding send fails.");
+ RecordProperty("TestType", "Requirements-based test");
+
+ // Given a skeleton configured with an event binding mock
+ const std::string event_name = "test_event";
+ auto mock_event_binding = std::make_unique>();
+ auto* mock_event_binding_ptr = mock_event_binding.get();
+
+ EXPECT_CALL(generic_event_binding_factory_mock_, Create(_, event_name, _))
+ .WillOnce(Return(ByMove(std::move(mock_event_binding))));
+
+ GenericSkeletonCreateParams create_params;
+ std::vector events;
+ events.push_back({event_name, {16, 8}});
+ create_params.events = events;
+
+ auto skeleton_result = GenericSkeleton::Create(
+ dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(),
+ create_params
+ );
+ ASSERT_TRUE(skeleton_result.has_value());
+ auto& skeleton = skeleton_result.value();
+ auto* event = const_cast(&skeleton.GetEvents().find(event_name)->second);
+
+ // And Given the service is Offered
+ EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mock_event_binding_ptr, PrepareOffer()).WillOnce(Return(score::Blank{}));
+ ASSERT_TRUE(skeleton.OfferService().has_value());
+
+ // Expect the binding to fail sending
+ EXPECT_CALL(*mock_event_binding_ptr, Send(_))
+ .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure)));
+
+ // When calling Send() with a dummy sample
+ mock_binding::SampleAllocateePtr