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 dummy_alloc{nullptr, [](void*){}}; + auto send_result = event->Send(MakeSampleAllocateePtr(std::move(dummy_alloc))); + + // Then it fails with kBindingFailure + ASSERT_FALSE(send_result.has_value()); + EXPECT_EQ(send_result.error(), ComErrc::kBindingFailure); +} + +TEST_F(GenericSkeletonEventTest, GetSizeInfoDispatchesToBinding) +{ + RecordProperty("Description", "Checks that GetSizeInfo returns the correct DataTypeMetaInfo from the binding."); + 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}}); // Original creation info + 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); + + // Expect the binding to return specific size info + std::pair expected_size_info{32, 16}; + EXPECT_CALL(*mock_event_binding_ptr, GetSizeInfo()) + .WillOnce(Return(expected_size_info)); + + // When calling GetSizeInfo + auto result_info = event->GetSizeInfo(); + + // Then it matches the binding's return values + EXPECT_EQ(result_info.size, expected_size_info.first); + EXPECT_EQ(result_info.alignment, expected_size_info.second); +} + +} // namespace +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/generic_skeleton_test.cpp b/score/mw/com/impl/generic_skeleton_test.cpp new file mode 100644 index 000000000..5ef6736d0 --- /dev/null +++ b/score/mw/com/impl/generic_skeleton_test.cpp @@ -0,0 +1,331 @@ +/******************************************************************************** + * 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/bindings/mock_binding/generic_skeleton_event.h" +#include "score/mw/com/impl/bindings/mock_binding/skeleton.h" +#include "score/mw/com/impl/i_binding_runtime.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/runtime_mock.h" +#include "score/mw/com/impl/service_discovery_client_mock.h" +#include "score/mw/com/impl/service_discovery_mock.h" +#include "score/mw/com/impl/test/binding_factory_resources.h" +#include "score/mw/com/impl/test/dummy_instance_identifier_builder.h" +#include "score/mw/com/impl/test/runtime_mock_guard.h" +#include "score/mw/com/impl/com_error.h" + +#include +#include + +#include +#include +#include + +namespace score::mw::com::impl +{ +namespace +{ + +using ::testing::_; +using ::testing::AllOf; +using ::testing::ByMove; +using ::testing::Field; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::ReturnRef; + +// --- 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 GenericSkeletonTest : public ::testing::Test +{ + public: + GenericSkeletonTest() + { + GenericSkeletonEventBindingFactory::mock_ = &generic_skeleton_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(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; + })); + } + + ~GenericSkeletonTest() override + { + GenericSkeletonEventBindingFactory::mock_ = nullptr; + } + + RuntimeMockGuard runtime_mock_guard_{}; + SkeletonBindingFactoryMockGuard skeleton_binding_factory_mock_guard_{}; + NiceMock generic_skeleton_event_binding_factory_mock_{}; + + NiceMock binding_runtime_mock_{}; + NiceMock service_discovery_mock_{}; + NiceMock service_discovery_client_mock_{}; + + mock_binding::Skeleton* skeleton_binding_mock_{nullptr}; + + DummyInstanceIdentifierBuilder dummy_instance_identifier_builder_{}; +}; + + +TEST_F(GenericSkeletonTest, CreateWithInstanceSpecifierResolvesIdentifier) +{ + RecordProperty("Description", "Checks that GenericSkeleton resolves the InstanceSpecifier."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a valid string specifier + auto instance_specifier = InstanceSpecifier::Create(std::string("path/to/my/service")).value(); + auto expected_identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + + // Expect the Runtime to be asked to resolve it + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, resolve(instance_specifier)) + .WillOnce(Return(std::vector{expected_identifier})); + + // When creating the skeleton + GenericSkeletonCreateParams params; + auto result = GenericSkeleton::Create(instance_specifier, params); + + // Then creation succeeds + ASSERT_TRUE(result.has_value()); +} + +TEST_F(GenericSkeletonTest, CreateWithUnresolvedInstanceSpecifierFails) +{ + RecordProperty("Description", "Checks that GenericSkeleton returns kInstanceIDCouldNotBeResolved when InstanceSpecifier cannot be resolved."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a valid string specifier + auto instance_specifier = InstanceSpecifier::Create(std::string("path/to/unknown/service")).value(); + + // Expect the Runtime to attempt to resolve it, but simulate failure by returning an empty vector + EXPECT_CALL(runtime_mock_guard_.runtime_mock_, resolve(instance_specifier)) + .WillOnce(Return(std::vector{})); + + // When creating the skeleton + GenericSkeletonCreateParams params; + auto result = GenericSkeleton::Create(instance_specifier, params); + + // Then creation fails with the expected error + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kInstanceIDCouldNotBeResolved); +} + +TEST_F(GenericSkeletonTest, CreateFailsIfEventBindingCannotBeCreated) +{ + RecordProperty("Description", "Checks that creation fails if the GenericSkeletonEventBindingFactory returns an error for any event."); + RecordProperty("TestType", "Requirements-based test"); + + // 1. Given an identifier and configuration with one valid event + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + + std::vector event_storage; + event_storage.push_back({event_name, {16, 8}}); + + GenericSkeletonCreateParams params; + params.events = event_storage; + + // 2. Expect the Event Binding Factory to be called, but force it to FAIL + // We simulate an internal failure by returning MakeUnexpected + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, _)) + .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); + + // 3. When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // 4. Then creation fails and correctly propagates the kBindingFailure error + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); +} + +TEST_F(GenericSkeletonTest, CreateWithEventsInitializesEventBindings) +{ + RecordProperty("Description", "Checks that GenericSkeleton creates bindings for configured events."); + RecordProperty("TestType", "Requirements-based test"); + + // Given configuration for one event + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + const DataTypeMetaInfo meta_info{16, 8}; + + std::vector event_storage; + event_storage.push_back({event_name, meta_info}); + + GenericSkeletonCreateParams params; + params.events = event_storage; + + // Expect the Event Factory to be called + auto MetaMatcher = AllOf( + Field(&DataTypeMetaInfo::size, meta_info.size), + Field(&DataTypeMetaInfo::alignment, meta_info.alignment) + ); + + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, MetaMatcher)) + .WillOnce(Return(ByMove(std::make_unique>()))); + + // When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // Then the skeleton contains the event + ASSERT_TRUE(result.has_value()); + const auto& events = result.value().GetEvents(); + ASSERT_EQ(events.size(), 1); + + EXPECT_NE(events.find(event_name), events.cend()); +} + + +TEST_F(GenericSkeletonTest, CreateWithDuplicateEventNamesFails) +{ + RecordProperty("Description", "Checks that creating a skeleton with duplicate event names returns an error."); + RecordProperty("TestType", "Requirements-based test"); + + // Given an identifier and configuration with duplicate event names + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifierWithEvent(); + const std::string event_name = "test_event"; + + std::vector event_storage; + event_storage.push_back({event_name, {1, 1}}); + event_storage.push_back({event_name, {2, 2}}); // Duplicate + + GenericSkeletonCreateParams params; + params.events = event_storage; + + // Expecting at least one attempt to create an event binding + EXPECT_CALL(generic_skeleton_event_binding_factory_mock_, Create(_, event_name, _)) + .WillRepeatedly(Return(ByMove(std::make_unique>()))); + + // When creating the skeleton + auto result = GenericSkeleton::Create(identifier, params); + + // Then creation fails with kServiceElementAlreadyExists + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kServiceElementAlreadyExists); +} + + +TEST_F(GenericSkeletonTest, CreateFailsIfMainBindingCannotBeCreated) +{ + RecordProperty("Description", "Checks that creation fails if the main SkeletonBinding factory returns null."); + RecordProperty("TestType", "Requirements-based test"); + + // Given the binding factory returns nullptr + EXPECT_CALL(skeleton_binding_factory_mock_guard_.factory_mock_, Create(_)) + .WillOnce(Return(ByMove(nullptr))); + + GenericSkeletonCreateParams params; + + // When creating the skeleton + auto result = GenericSkeleton::Create(dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(), params); + + // Then creation fails with kBindingFailure + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); +} + + +TEST_F(GenericSkeletonTest, OfferServicePropagatesToBindingAndDiscovery) +{ + RecordProperty("Description", "Checks that OfferService calls PrepareOffer on the binding and notifies ServiceDiscovery."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); + + // Expecting OfferService to trigger binding and discovery + EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::Blank{})); + EXPECT_CALL(service_discovery_mock_, OfferService(identifier)).WillOnce(Return(score::Blank{})); + + // When offering service + auto result = skeleton.OfferService(); + + // Then it succeeds + ASSERT_TRUE(result.has_value()); +} + + +TEST_F(GenericSkeletonTest, StopOfferServicePropagatesToBindingAndDiscovery) +{ + RecordProperty("Description", "Checks that StopOfferService calls PrepareStopOffer and notifies ServiceDiscovery."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); + + // And given the service is already Offered + EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)).WillOnce(Return(score::Blank{})); + EXPECT_CALL(service_discovery_mock_, OfferService(identifier)).WillOnce(Return(score::Blank{})); + ASSERT_TRUE(skeleton.OfferService().has_value()); + + // Expecting StopOffer to trigger binding and discovery + EXPECT_CALL(*skeleton_binding_mock_, PrepareStopOffer(_)); + EXPECT_CALL(service_discovery_mock_, StopOfferService(identifier)); + + // When stopping offer + skeleton.StopOfferService(); + + // Then (Verified by mock expectations) +} + + +TEST_F(GenericSkeletonTest, OfferServiceReturnsErrorIfBindingFails) +{ + RecordProperty("Description", "Checks that OfferService returns an error if the binding's PrepareOffer fails."); + RecordProperty("TestType", "Requirements-based test"); + + // Given a created skeleton + auto identifier = dummy_instance_identifier_builder_.CreateValidLolaInstanceIdentifier(); + auto skeleton = GenericSkeleton::Create(identifier, {}).value(); + + // Expecting Binding to fail + EXPECT_CALL(*skeleton_binding_mock_, VerifyAllMethodsRegistered()).WillRepeatedly(Return(true)); + EXPECT_CALL(*skeleton_binding_mock_, PrepareOffer(_, _, _)) + .WillOnce(Return(MakeUnexpected(ComErrc::kBindingFailure))); + + // Expecting ServiceDiscovery NOT to be called + EXPECT_CALL(service_discovery_mock_, OfferService(_)).Times(0); + + // When offering service + auto result = skeleton.OfferService(); + + // Then it fails with kBindingFailure + ASSERT_FALSE(result.has_value()); + EXPECT_EQ(result.error(), ComErrc::kBindingFailure); +} + +} // namespace +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h b/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h new file mode 100644 index 000000000..1bb3edffb --- /dev/null +++ b/score/mw/com/impl/i_generic_skeleton_event_binding_factory.h @@ -0,0 +1,37 @@ +/******************************************************************************** + * 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_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#define SCORE_MW_COM_IMPL_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H + +#include "score/mw/com/impl/generic_skeleton_event_binding.h" +#include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/data_type_meta_info.h" +#include +#include + +namespace score::mw::com::impl { + +class IGenericSkeletonEventBindingFactory { +public: + virtual ~IGenericSkeletonEventBindingFactory() noexcept = default; + + // Changed SizeInfo -> DataTypeMetaInfo + virtual score::Result> Create( + SkeletonBase&, + std::string_view, + const DataTypeMetaInfo&) noexcept = 0; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_I_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/BUILD b/score/mw/com/impl/plumbing/BUILD index 86f8d29c4..b95c9e903 100644 --- a/score/mw/com/impl/plumbing/BUILD +++ b/score/mw/com/impl/plumbing/BUILD @@ -448,6 +448,9 @@ cc_library( features = COMPILER_WARNING_FEATURES, implementation_deps = [ "skeleton_binding_factory_impl", + ], + visibility = [ + "//score/mw/com/impl:__subpackages__", ], tags = ["FFI"], deps = [ @@ -587,6 +590,54 @@ cc_library( ], ) +cc_library( + name = "generic_skeleton_event_binding_factory_impl", + srcs = ["generic_skeleton_event_binding_factory_impl.cpp"], + hdrs = ["generic_skeleton_event_binding_factory_impl.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = ["//score/mw/com/impl/bindings/lola"], + tags = ["FFI"], # Increased visibility as it's now in score::mw::com::impl namespace + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", + ":skeleton_service_element_binding_factory_impl", + ], +) + +cc_library( + name = "generic_skeleton_event_binding_factory", + srcs = ["generic_skeleton_event_binding_factory.cpp"], + hdrs = ["generic_skeleton_event_binding_factory.h", + "skeleton_service_element_binding_factory_impl.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":generic_skeleton_event_binding_factory_impl", + ], + tags = ["FFI"], + visibility = [ + "//score/mw/com/impl:__subpackages__", + ], + deps = [ # Removed unnecessary dependency + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", + ], +) + +cc_library( + name = "generic_skeleton_event_binding_factory_mock", + testonly = True, + srcs = ["generic_skeleton_event_binding_factory_mock.cpp"], + hdrs = ["generic_skeleton_event_binding_factory_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//visibility:public", # platform_only + ], + deps = [ + "//score/mw/com/impl:i_generic_skeleton_event_binding_factory", + "@googletest//:gtest", + ], +) cc_library( name = "runtime", srcs = [ @@ -667,7 +718,9 @@ cc_library( features = COMPILER_WARNING_FEATURES, tags = ["FFI"], visibility = ["//score/mw/com/impl:__subpackages__"], - deps = ["//score/mw/com/impl/bindings/lola:event"], + deps = ["//score/mw/com/impl/bindings/lola:event", + "//score/mw/com/impl/bindings/mock_binding:sample_allocatee_ptr", + ], ) cc_library( diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.cpp new file mode 100644 index 000000000..7be462ed4 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.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/plumbing/generic_skeleton_event_binding_factory.h" diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h new file mode 100644 index 000000000..4158a7795 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory.h @@ -0,0 +1,69 @@ +/******************************************************************************** + * 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_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H + +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.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_type.h" +#include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" + +#include "score/result/result.h" + +#include +#include + + +namespace score::mw::com::impl +{ + +class GenericSkeletonEventBindingFactory +{ + public: + // C++17 inline static allows defining this variable directly in the header. + // This serves as the "hook" for your unit tests. + inline static IGenericSkeletonEventBindingFactory* mock_ = nullptr; + + // This static method allows your Source Code (generic_skeleton.cpp) + // to call GenericSkeletonEventBindingFactory::Create(...) directly. + static Result> Create( + SkeletonBase& skeleton_base, + std::string_view event_name, + const DataTypeMetaInfo& meta_info) noexcept + { + // A. If a Mock is registered (during Unit Tests), use it. + if (mock_ != nullptr) + { + // Pass meta_info to mock + return mock_->Create(skeleton_base, event_name, meta_info); + } + + // B. Otherwise (in Production), use the Real Implementation. + const auto& instance_identifier = SkeletonBaseView{skeleton_base}.GetAssociatedInstanceIdentifier(); + + + return CreateGenericSkeletonServiceElement( + instance_identifier, + skeleton_base, + event_name, + meta_info); + } +}; + + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp new file mode 100644 index 000000000..885bcdf63 --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.cpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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/plumbing/generic_skeleton_event_binding_factory_impl.h" +#include "score/mw/com/impl/bindings/lola/generic_skeleton_event.h" +#include "score/mw/com/impl/service_element_type.h" +#include "score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h" +#include "score/mw/com/impl/skeleton_base.h" + +// Updated signature to use DataTypeMetaInfo +score::Result> +score::mw::com::impl::GenericSkeletonEventBindingFactoryImpl::Create( + SkeletonBase& parent, + std::string_view event_name, + const DataTypeMetaInfo& meta_info) noexcept +{ + const auto& instance_identifier = SkeletonBaseView{parent}.GetAssociatedInstanceIdentifier(); + + + return CreateGenericSkeletonServiceElement( + instance_identifier, parent, event_name, meta_info); +} \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h new file mode 100644 index 000000000..885999f6a --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_impl.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H + +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/data_type_meta_info.h" + +namespace score::mw::com::impl { + +class GenericSkeletonEventBindingFactoryImpl : public IGenericSkeletonEventBindingFactory { +public: + + score::Result> Create( + SkeletonBase&, + std::string_view, + const DataTypeMetaInfo&) noexcept override; +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_IMPL_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.cpp new file mode 100644 index 000000000..34e868cee --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.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/plumbing/generic_skeleton_event_binding_factory_mock.h" \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h new file mode 100644 index 000000000..1bcbd90ea --- /dev/null +++ b/score/mw/com/impl/plumbing/generic_skeleton_event_binding_factory_mock.h @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H +#define SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H + +#include "score/mw/com/impl/i_generic_skeleton_event_binding_factory.h" +#include "score/mw/com/impl/data_type_meta_info.h" + +#include + +namespace score::mw::com::impl { + +class GenericSkeletonEventBindingFactoryMock : public IGenericSkeletonEventBindingFactory { +public: + MOCK_METHOD(score::Result>, Create, + (SkeletonBase&, std::string_view, const DataTypeMetaInfo&), + (noexcept, override)); +}; + +} // namespace score::mw::com::impl + +#endif // SCORE_MW_COM_IMPL_PLUMBING_GENERIC_SKELETON_EVENT_BINDING_FACTORY_MOCK_H \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs b/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs index bd89e2369..1d90a1f0e 100644 --- a/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs +++ b/score/mw/com/impl/plumbing/rust/sample_allocatee_ptr.rs @@ -68,7 +68,7 @@ struct AllocationVariant { _index: u8, } -#[repr(C)] +#[repr(C, align(16))] pub struct SampleAllocateePtr { _internal: AllocationVariant, } diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h index c12d712d1..b30f265f6 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr.h +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr.h @@ -14,14 +14,17 @@ #define SCORE_MW_COM_IMPL_PLUMBING_SAMPLE_ALLOCATEE_PTR_H #include "score/mw/com/impl/bindings/lola/sample_allocatee_ptr.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" #include #include #include +#include #include #include #include +#include namespace score::mw::com::impl { @@ -93,16 +96,25 @@ class SampleAllocateePtr // coverity[autosar_cpp14_a15_5_3_violation : FALSE] explicit operator bool() const noexcept; + // ------------------------------------------------------------------------- + // [CRITICAL UPDATE] Declarations MUST be templates to support SFINAE + // This allows disabling these operators when SampleType is void. + // ------------------------------------------------------------------------- + + /// \brief operator* - Enabled via SFINAE only if SampleType is NOT void /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will /// terminate. // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) // coverity[autosar_cpp14_a15_5_3_violation : FALSE] - typename std::add_lvalue_reference::type operator*() const noexcept(noexcept(*std::declval())); + template ::value, int>::type = 0> + typename std::add_lvalue_reference::type operator*() const noexcept; + /// \brief operator-> - Enabled via SFINAE only if SampleType is NOT void /// \brief operator* and operator-> provide access to the object owned by *this. If no object is hold, will /// terminate. // Suppress "AUTOSAR C++14 A15-5-3" rule finding: See rationale above (fix in Ticket-173043) // coverity[autosar_cpp14_a15_5_3_violation : FALSE] + template ::value, int>::type = 0> pointer operator->() const noexcept; private: @@ -131,9 +143,10 @@ class SampleAllocateePtr template // coverity[autosar_cpp14_a11_3_1_violation] friend class SampleAllocateePtrMutableView; - + // We don't use the pimpl idiom because it would require dynamic memory allocation (that we want to avoid) - std::variant, std::unique_ptr> internal_; + // Stores either the LoLa pointer or the Mock Binding pointer (which handles void safely) + std::variant, mock_binding::SampleAllocateePtr> internal_; }; template @@ -171,8 +184,8 @@ void SampleAllocateePtr::reset() noexcept internal_ptr.reset(); }, // coverity[autosar_cpp14_a7_1_7_violation] - [](std::unique_ptr& internal_ptr) noexcept -> void { - internal_ptr.reset(nullptr); + [](mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> void { + internal_ptr.reset(nullptr); }, // coverity[autosar_cpp14_a7_1_7_violation] [](const score::cpp::blank&) noexcept -> void {}); @@ -207,7 +220,7 @@ auto SampleAllocateePtr::Get() const noexcept -> pointer // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return internal_ptr.get(); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -235,7 +248,7 @@ SampleAllocateePtr::operator bool() const noexcept // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> bool { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> bool { return static_cast(internal_ptr); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -247,8 +260,8 @@ SampleAllocateePtr::operator bool() const noexcept } template -typename std::add_lvalue_reference::type SampleAllocateePtr::operator*() const - noexcept(noexcept(*std::declval())) +template ::value, int>::type> +typename std::add_lvalue_reference::type SampleAllocateePtr::operator*() const noexcept { using ReturnType = typename std::add_lvalue_reference::type; @@ -267,7 +280,7 @@ typename std::add_lvalue_reference::type SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return *internal_ptr; }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -279,6 +292,7 @@ typename std::add_lvalue_reference::type SampleAllocateePtr +template ::value, int>::type> auto SampleAllocateePtr::operator->() const noexcept -> pointer { using ReturnType = pointer; @@ -298,7 +312,7 @@ auto SampleAllocateePtr::operator->() const noexcept -> pointer // This is a false positive, we here using lvalue reference. // coverity[autosar_cpp14_a8_4_12_violation : FALSE] // coverity[autosar_cpp14_a7_1_7_violation] - [](const std::unique_ptr& internal_ptr) noexcept -> ReturnType { + [](const mock_binding::SampleAllocateePtr& internal_ptr) noexcept -> ReturnType { return internal_ptr.get(); }, // coverity[autosar_cpp14_a7_1_7_violation] @@ -328,7 +342,7 @@ bool operator!=(const SampleAllocateePtr& lhs, const SampleAllocateePtr& template void swap(SampleAllocateePtr& lhs, SampleAllocateePtr& rhs) noexcept { - lhs.swap(rhs); + lhs.Swap(rhs); } /// \brief Helper function to create a SampleAllocateePtr within the middleware (not to be used by the user) @@ -353,7 +367,7 @@ class SampleAllocateePtrView return std::get_if(&ptr_.internal_); } - const std::variant, std::unique_ptr>& + const std::variant, mock_binding::SampleAllocateePtr>& GetUnderlyingVariant() const noexcept { return ptr_.internal_; @@ -370,7 +384,7 @@ class SampleAllocateePtrMutableView public: explicit SampleAllocateePtrMutableView(SampleAllocateePtr& ptr) : ptr_{ptr} {} - std::variant, std::unique_ptr>& + std::variant, mock_binding::SampleAllocateePtr>& GetUnderlyingVariant() noexcept { // Suppress "AUTOSAR C++14 A9-3-1", The rule states: "Member functions shall not return non-const “raw” pointers diff --git a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp index 87b0190bc..bb35a4af4 100644 --- a/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp +++ b/score/mw/com/impl/plumbing/sample_allocatee_ptr_test.cpp @@ -15,6 +15,8 @@ #include "score/mw/com/impl/bindings/lola/control_slot_types.h" #include "score/mw/com/impl/bindings/lola/test_doubles/fake_memory_resource.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" + #include #include @@ -54,7 +56,10 @@ class SampleAllocateePtrFixture : public ::testing::Test lola::SlotIndexType event_data_slot_index_{std::numeric_limits::max()}; lola::SampleAllocateePtr lola_allocatee_ptr_{&value_, event_data_ctrl_, {}}; SampleAllocateePtr valid_unit_{MakeSampleAllocateePtr(std::move(lola_allocatee_ptr_))}; - SampleAllocateePtr unit_with_unique_ptr_{MakeSampleAllocateePtr(std::make_unique(42))}; + + + SampleAllocateePtr unit_with_unique_ptr_{ + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(42), [](std::uint8_t* p) { delete p; } ))}; }; TEST_F(SampleAllocateePtrFixture, ConstructFromNullptr) @@ -198,14 +203,6 @@ TEST(SampleAllocateePtrTest, InterfaceMatchesRequirements) // Move assignment operator static_assert(std::is_move_assignable_v>, "Should be move assignable"); - // Dereferences the stored pointer (operator->) - static_assert(std::is_member_function_pointer_v::operator->)>, - "Should contain operator->"); - - // Dereferences the stored pointer (operator*) - static_assert(std::is_member_function_pointer_v::operator*)>, - "Should contain operator*"); - // Checks if the stored pointer is null static_assert(std::is_member_function_pointer_v::operator bool)>, "Should contain operator bool"); @@ -235,7 +232,8 @@ TEST(SampleAllocateePtrTest, NullLolaSampleAllocateePtrConvertsToFalse) TEST(SampleAllocateePtrTest, NullUniquePtrConvertsToFalse) { // Given a unique_ptr which holds a nullptr - std::unique_ptr null_unique_ptr{nullptr}; + + mock_binding::SampleAllocateePtr null_unique_ptr{nullptr}; // When creating an impl::SampleAllocateePtr from the unique_ptr auto ptr = MakeSampleAllocateePtr(std::move(null_unique_ptr)); @@ -266,7 +264,8 @@ TEST_F(SampleAllocateePtrFixture, ValidLolaSampleAllocateePtrConvertsToTrue) TEST_F(SampleAllocateePtrFixture, ValidUniquePtrConvertsToTrue) { // Given a valid unique_ptr - auto valid_unique_ptr = std::make_unique(10); + + mock_binding::SampleAllocateePtr valid_unique_ptr(new std::uint8_t(10),[](std::uint8_t* p) { delete p; } ); EXPECT_TRUE(valid_unique_ptr); // When creating an impl::SampleAllocateePtr from the unique_ptr @@ -361,7 +360,7 @@ TEST_F(SampleAllocateePtrFixture, CanResetUnderlyingPointerUsingUniquePtr) // Given a SampleAllocateePtr with an underlying unique_ptr bool is_destructed{false}; SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(std::make_unique(is_destructed))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed),[](ObjectDestructionNotifier* p) { delete p; }))}; // When calling Reset unit_with_unique_ptr.reset(); @@ -406,7 +405,8 @@ TEST_F(SampleAllocateePtrFixture, CanDereferenceUsingArrowUsingUniquePtr) std::uint8_t bar{}; }; - auto value = std::make_unique(); + + auto value = mock_binding::SampleAllocateePtr(new Foo(),[](Foo* p) { delete p; }); value->bar = 42; const auto unit = MakeSampleAllocateePtr(std::move(value)); @@ -416,11 +416,13 @@ TEST_F(SampleAllocateePtrFixture, CanDereferenceUsingArrowUsingUniquePtr) TEST_F(SampleAllocateePtrFixture, CanWrapUniquePtr) { // Given a SampleAllocateePtr with an underlying unique_ptr - const auto ptr = MakeSampleAllocateePtr(std::make_unique()); + + const auto ptr = MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(),[](std::uint8_t* p) { delete p; })); const auto unit = SampleAllocateePtrView{ptr}; // When trying to read its underlying implementation - const auto* underlying_impl = unit.As>(); + + const auto* underlying_impl = unit.As>(); // This is possible and we can interact with it ASSERT_NE(underlying_impl, nullptr); @@ -430,7 +432,8 @@ TEST_F(SampleAllocateePtrFixture, CanCompareTwoUnequalPtrs) { // Given a valid_unit and a second SampleAllocateePtr pointing to a different value std::uint8_t value{0x43}; - SampleAllocateePtr unit2{MakeSampleAllocateePtr(std::make_unique(value))}; + + SampleAllocateePtr unit2{MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new std::uint8_t(value),[](std::uint8_t* p) { delete p; }))}; // When testing equality // Then the pointers are considered unequal @@ -479,8 +482,9 @@ TEST(SampleAllocateePtrTest, UnderlyingUniquePtrIsFreedOnDestruction) bool is_destructed{false}; { // Given a SampleAllocateePtr with an underlying unique_ptr + SampleAllocateePtr unit_with_unique_ptr{ - MakeSampleAllocateePtr(std::make_unique(is_destructed))}; + MakeSampleAllocateePtr(mock_binding::SampleAllocateePtr(new ObjectDestructionNotifier(is_destructed),[](ObjectDestructionNotifier* p) { delete p; }))}; // The underlying object will not be freed while the SampleAllocateePtr has not been destructed EXPECT_FALSE(is_destructed); @@ -523,4 +527,4 @@ TEST_F(SampleAllocateePtrFixture, UnderlyingLolaPtrIsFreedOnDestruction) } } // namespace -} // namespace score::mw::com::impl +} // namespace score::mw::com::impl \ No newline at end of file diff --git a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h index 9ffee4f68..c6faa7c90 100644 --- a/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h +++ b/score/mw/com/impl/plumbing/skeleton_service_element_binding_factory_impl.h @@ -21,6 +21,7 @@ #include "score/mw/com/impl/configuration/service_instance_deployment.h" #include "score/mw/com/impl/configuration/someip_service_instance_deployment.h" #include "score/mw/com/impl/skeleton_base.h" +#include "score/mw/com/impl/data_type_meta_info.h" #include "score/mw/log/logging.h" @@ -94,7 +95,7 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, if (lola_parent == nullptr) { score::mw::log::LogFatal("lola") << "Skeleton service element could not be created because parent " - "skeleton binding is a nullptr."; + "skeleton binding is a nullptr."; return nullptr; } @@ -133,6 +134,60 @@ auto CreateSkeletonServiceElement(const InstanceIdentifier& identifier, return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); } +/// @brief Overload for typed skeletons (which do not have a DataTypeMetaInfo). +template +// coverity[autosar_cpp14_a15_5_3_violation : FALSE] +auto CreateGenericSkeletonServiceElement(const InstanceIdentifier& identifier, + SkeletonBase& parent, + const std::string_view service_element_name, + const DataTypeMetaInfo& meta_info) noexcept + -> std::unique_ptr +{ + static_assert(element_type != ServiceElementType::INVALID); + + const InstanceIdentifierView identifier_view{identifier}; + + using ReturnType = std::unique_ptr; + auto visitor = score::cpp::overload( + [identifier_view, &parent, &service_element_name, &meta_info]( + const LolaServiceTypeDeployment& lola_service_type_deployment) -> ReturnType { + auto* const lola_parent = dynamic_cast(SkeletonBaseView{parent}.GetBinding()); + if (lola_parent == nullptr) + { + score::mw::log::LogFatal("lola") << "Skeleton service element could not be created because parent " + "skeleton binding is a nullptr."; + return nullptr; + } + + const auto& service_instance_deployment = identifier_view.GetServiceInstanceDeployment(); + const auto& lola_service_instance_deployment = + GetServiceInstanceDeploymentBinding(service_instance_deployment); + + const auto& lola_service_element_instance_deployment = GetServiceElementInstanceDeployment( + lola_service_instance_deployment, std::string{service_element_name}); + const auto skeleton_event_properties = + detail::GetSkeletonEventProperties(lola_service_element_instance_deployment); + + const auto lola_service_element_id = + GetServiceElementId(lola_service_type_deployment, std::string{service_element_name}); + const lola::ElementFqId element_fq_id{lola_service_type_deployment.service_id_, + lola_service_element_id, + lola_service_instance_deployment.instance_id_.value().GetId(), + element_type}; + + return std::make_unique( + *lola_parent, skeleton_event_properties, element_fq_id, meta_info, tracing::SkeletonEventTracingData{} ); + }, + [](const SomeIpServiceInstanceDeployment&) noexcept -> ReturnType { + return nullptr; + }, + [](const score::cpp::blank&) noexcept -> ReturnType { + return nullptr; + }); + + return std::visit(visitor, identifier_view.GetServiceTypeDeployment().binding_info_); +} + } // namespace score::mw::com::impl -#endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H +#endif // SCORE_MW_COM_IMPL_PLUMBING_SKELETON_SERVICE_ELEMENT_BINDING_FACTORY_IMPL_H \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_base.h b/score/mw/com/impl/skeleton_base.h index 1011799e7..c0b3771d3 100644 --- a/score/mw/com/impl/skeleton_base.h +++ b/score/mw/com/impl/skeleton_base.h @@ -70,7 +70,7 @@ class SkeletonBase * \api * \brief Offer the respective service to other applications * \return On failure, returns an error code according to the SW Component Requirements SCR-17434118 and - * SCR-566325. + * SCR-566325. */ [[nodiscard]] ResultBlank OfferService() noexcept; @@ -220,4 +220,4 @@ score::cpp::optional GetInstanceIdentifier(const InstanceSpe } // namespace score::mw::com::impl -#endif // SCORE_MW_COM_IMPL_SKELETON_BASE_H +#endif // SCORE_MW_COM_IMPL_SKELETON_BASE_H \ No newline at end of file diff --git a/score/mw/com/impl/skeleton_binding.h b/score/mw/com/impl/skeleton_binding.h index a89173196..30f1d2afb 100644 --- a/score/mw/com/impl/skeleton_binding.h +++ b/score/mw/com/impl/skeleton_binding.h @@ -17,6 +17,7 @@ #include "score/result/result.h" #include "score/mw/com/impl/binding_type.h" #include "score/mw/com/impl/service_element_type.h" +#include "score/mw/com/impl/generic_skeleton_event_binding.h" #include #include diff --git a/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp b/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp index 144360efb..1366b30d8 100644 --- a/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp +++ b/score/mw/com/impl/test/dummy_instance_identifier_builder.cpp @@ -58,6 +58,16 @@ InstanceIdentifier DummyInstanceIdentifierBuilder::CreateValidLolaInstanceIdenti service_instance_deployment_.instance_id_ = LolaServiceInstanceId{0x42}; service_instance_deployment_.allowed_consumer_ = {{QualityType::kASIL_QM, {42}}}; service_instance_deployment_.events_ = events; + + // The GenericSkeleton needs the event names to be present in the Type Deployment + // to perform the stable string lookup. We sync it here. + service_type_deployment_.events_.clear(); + for (const auto& event_pair : events) { + // Add the event name to the type deployment map. + // We assume default construction of the value (EventId) is sufficient for this mock. + service_type_deployment_.events_[event_pair.first] = {}; + } + type_deployment_.binding_info_ = service_type_deployment_; instance_deployment_ = std::make_unique( type_, service_instance_deployment_, QualityType::kASIL_QM, instance_specifier_); diff --git a/score/mw/com/impl/tracing/skeleton_event_tracing.h b/score/mw/com/impl/tracing/skeleton_event_tracing.h index 87f0bf091..c4b21fa2b 100644 --- a/score/mw/com/impl/tracing/skeleton_event_tracing.h +++ b/score/mw/com/impl/tracing/skeleton_event_tracing.h @@ -19,6 +19,7 @@ #include "score/mw/com/impl/bindings/lola/sample_ptr.h" #include "score/mw/com/impl/bindings/lola/transaction_log_set.h" #include "score/mw/com/impl/bindings/mock_binding/sample_ptr.h" +#include "score/mw/com/impl/bindings/mock_binding/sample_allocatee_ptr.h" #include "score/mw/com/impl/instance_identifier.h" #include "score/mw/com/impl/plumbing/sample_allocatee_ptr.h" #include "score/mw/com/impl/skeleton_event_binding.h" @@ -85,7 +86,7 @@ TracingData ExtractBindingTracingData(const impl::SampleAllocateePtr // Here we can't use a raw pointer / reference since we're using score::cpp::overload, and the function is not // replaceing the managed object, so this should be a const reference. // coverity[autosar_cpp14_a8_4_12_violation] - [](const std::unique_ptr& ptr) -> TracingData { + [](const mock_binding::SampleAllocateePtr& ptr) -> TracingData { return {0U, {ptr.get(), sizeof(SampleType)}}; }, [](const score::cpp::blank&) noexcept -> TracingData { @@ -126,7 +127,7 @@ TypeErasedSamplePtr CreateTypeErasedSamplePtr(impl::SampleAllocateePtr& ptr) -> TypeErasedSamplePtr { + [](mock_binding::SampleAllocateePtr& ptr) -> TypeErasedSamplePtr { impl::tracing::TypeErasedSamplePtr type_erased_sample_ptr{std::make_unique(*ptr)}; return type_erased_sample_ptr; }, diff --git a/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp b/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp index 03555307f..8b900cb8a 100644 --- a/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp +++ b/score/mw/com/test/shared_memory_storage/shared_memory_storage_application.cpp @@ -37,6 +37,37 @@ #include #include + +namespace score::mw::com::impl::lola +{ + // The Attorney uses the friend declaration to access private SkeletonEvent members + template + class SkeletonEventAttorney + { + public: + static ElementFqId GetElementFQId(const SkeletonEvent& event) noexcept + { + // Access the private event_shared_impl_ and call its public GetElementFQId() + return event.event_shared_impl_.GetElementFQId(); + } + }; +} // namespace score::mw::com::impl::lola + +// Overload for SkeletonEvent (uses Attorney to bypass private access) +template +score::mw::com::impl::lola::ElementFqId ExtractId(const score::mw::com::impl::lola::SkeletonEvent* binding) +{ + return score::mw::com::impl::lola::SkeletonEventAttorney::GetElementFQId(*binding); +} + +// Overload for ProxyEvent (method is still public) +template +score::mw::com::impl::lola::ElementFqId ExtractId(const BindingType* binding) +{ + return binding->GetElementFQId(); +} + + namespace { @@ -57,7 +88,7 @@ score::cpp::optional GetElementFqId(Ser { return {}; } - return map_api_lanes_stamped_binding->GetElementFQId(); + return ExtractId(map_api_lanes_stamped_binding); } template