diff --git a/bundles/event_admin/CMakeLists.txt b/bundles/event_admin/CMakeLists.txt index 6b9464159..30cb79fad 100644 --- a/bundles/event_admin/CMakeLists.txt +++ b/bundles/event_admin/CMakeLists.txt @@ -18,7 +18,9 @@ celix_subproject(EVENT_ADMIN "Option to enable building the Event Admin bundles" ON) if (EVENT_ADMIN) add_subdirectory(event_admin_api) + add_subdirectory(event_admin_spi) add_subdirectory(event_admin) + add_subdirectory(remote_provider) add_subdirectory(examples) endif() diff --git a/bundles/event_admin/README.md b/bundles/event_admin/README.md index ada60685d..7b017ffcd 100644 --- a/bundles/event_admin/README.md +++ b/bundles/event_admin/README.md @@ -39,4 +39,5 @@ If we want to build the event admin examples, the cmake option `BUILD_EVENT_ADMI ## Event Admin Bundles -* [EventAdmin](event_admin/README.md) - The event admin implementation. \ No newline at end of file +* [EventAdmin](event_admin/README.md) - The event admin implementation. +* [RemoteProviders](remote_provider/README.md) - The remote providers implementation for the event admin. It is used to deliver events to remote frameworks. It is not a part of the OSGi Event Admin specification. \ No newline at end of file diff --git a/bundles/event_admin/event_admin/CMakeLists.txt b/bundles/event_admin/event_admin/CMakeLists.txt index 5dd7e2d88..5766e8a18 100644 --- a/bundles/event_admin/event_admin/CMakeLists.txt +++ b/bundles/event_admin/event_admin/CMakeLists.txt @@ -24,6 +24,7 @@ set(EVENT_ADMIN_SRC set(EVENT_ADMIN_DEPS Celix::event_admin_api + Celix::event_admin_spi Celix::log_helper Celix::framework Celix::utils diff --git a/bundles/event_admin/event_admin/README.md b/bundles/event_admin/event_admin/README.md index d9f2be504..1c9582ada 100644 --- a/bundles/event_admin/event_admin/README.md +++ b/bundles/event_admin/event_admin/README.md @@ -38,9 +38,10 @@ event admin pubsub model, and events are delivered asynchronously. ### Properties/Configuration -| **Properties** | **Type** | **Description** | **Default value** | -|----------------------------------------|----------|---------------------------------------------------------------|-----| -| **CELIX_EVENT_ADMIN_HANDLER_THREADS** | long | The number of event handler threads. Its maximum value is 20. | 5 | +| **Properties** | **Type** | **Description** | **Default value** | +|----------------------------------------------------------|----------|---------------------------------------------------------------|-------------------| +| **CELIX_EVENT_ADMIN_HANDLER_THREADS** | long | The number of event handler threads. Its maximum value is 20. | 5 | +| **CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL** | long | The event sequence id cache will be cleaned up when it has not been used for this interval. The unit is seconds. The event sequence id cache is used to prevent duplicate events. | (60*60)s | ### Software Design @@ -63,6 +64,17 @@ at most one event-delivery thread at a time, so that events can be delivered in "event.delivery" property to "async.unordered", the event handler can hold multiple event-delivery threads at the same time, so that events can be delivered in parallel. +#### Remote Event Delivery + +If the event property "celix.event.remote.enable" is set to true, the event will be delivered to the local event handlers +and remote event handlers. For delivering events to local event handlers, it can refer to the section of synchronous delivery +and asynchronous delivery. For delivering events to remote event handlers, event admin will forward the event to the +[remote provider](../remote_provider/README.md). The remote provider will serialize the event and send it to the remote framework. +The remote framework will deserialize the event and deliver it to the remote event handler. The diagram of remote event delivery +is as follows: + +![remote_delivery_seq.png](diagrams/remote_event_delivery_seq.png) + #### Event Adapter diff --git a/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.png b/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.png new file mode 100644 index 000000000..35e2b47f2 Binary files /dev/null and b/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.png differ diff --git a/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.puml b/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.puml new file mode 100644 index 000000000..a56bab764 --- /dev/null +++ b/bundles/event_admin/event_admin/diagrams/remote_event_delivery_seq.puml @@ -0,0 +1,39 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/sequence-diagram + +box FrameworkA +participant "Event Admin" as EventAdmin1 +participant "Remote Provider" as RemoteProvider1 +end box + +box FrameworkB +participant "Remote Provider" as RemoteProvider2 +participant "Event Admin" as EventAdmin2 +end box + +-\EventAdmin1:postEvent/sendEvent +EventAdmin1->EventAdmin1:Delivery event to local event handlers +alt "celix.event.remote.enable" is true + EventAdmin1->EventAdmin1:Unset the "celix.event.remote.enable" property + EventAdmin1->RemoteProvider1:postEvent/sendEvent + RemoteProvider1->RemoteProvider2:IPC or Network + RemoteProvider2->EventAdmin2:postEvent/sendEvent + EventAdmin2 -> EventAdmin2:Delivery event to event handlers in FrameworkB +end alt + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminActivatorErrorInjectionTestSuite.cc b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminActivatorErrorInjectionTestSuite.cc index 238c00c5c..1a927c46c 100644 --- a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminActivatorErrorInjectionTestSuite.cc +++ b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminActivatorErrorInjectionTestSuite.cc @@ -103,6 +103,30 @@ TEST_F(CelixEventAdminActTestSuite, FailedToAddEventHandlerDependencyToEventAdmi }); } +TEST_F(CelixEventAdminActTestSuite, FailedToCreateRemoteProviderDependencyForEventAdminTest) { + TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(CELIX_ENOMEM, status); + }); +} + +TEST_F(CelixEventAdminActTestSuite, FailedToSetServiceToRemoteProviderDependencyTest) { + TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(CELIX_ENOMEM, status); + }); +} + +TEST_F(CelixEventAdminActTestSuite, FailedToAddRemoteProviderDependencyToEventAdminComponentTest) { + TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(CELIX_ENOMEM, status); + }); +} + TEST_F(CelixEventAdminActTestSuite, FailedToAddEventAdminServiceToComponentTest) { TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { celix_ei_expect_celix_dmComponent_addInterface((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM); @@ -129,7 +153,7 @@ TEST_F(CelixEventAdminActTestSuite, FailedToCreateEventAdapterTest) { TEST_F(CelixEventAdminActTestSuite, FailedToCreateEventAdminDependencyForEventAdapterTest) { TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { - celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr, 2); + celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr, 3); auto status = celix_bundleActivator_start(act, ctx); ASSERT_EQ(CELIX_ENOMEM, status); }); @@ -137,7 +161,7 @@ TEST_F(CelixEventAdminActTestSuite, FailedToCreateEventAdminDependencyForEventAd TEST_F(CelixEventAdminActTestSuite, FailedToSetServiceToEventAdminDependencyTest) { TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { - celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 2); + celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 3); auto status = celix_bundleActivator_start(act, ctx); ASSERT_EQ(CELIX_ENOMEM, status); }); @@ -145,7 +169,7 @@ TEST_F(CelixEventAdminActTestSuite, FailedToSetServiceToEventAdminDependencyTest TEST_F(CelixEventAdminActTestSuite, FailedToAddEventAdminDependencyToEventAdapterComponentTest) { TestEventAdminActivator([](void *act, celix_bundle_context_t *ctx) { - celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 2); + celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, CELIX_ENOMEM, 3); auto status = celix_bundleActivator_start(act, ctx); ASSERT_EQ(CELIX_ENOMEM, status); }); diff --git a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminErrorInjectionTestSuite.cc b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminErrorInjectionTestSuite.cc index 248adba8a..2ff9bd44e 100644 --- a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminErrorInjectionTestSuite.cc +++ b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminErrorInjectionTestSuite.cc @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +#include + #include "CelixEventAdminTestSuiteBaseClass.h" #include "celix_event_admin.h" #include "celix_event.h" @@ -27,6 +29,8 @@ #include "celix_long_hash_map_ei.h" #include "celix_threads_ei.h" #include "celix_utils_ei.h" +#include "celix_properties_ei.h" +#include "celix_bundle_context_ei.h" #include "malloc_ei.h" #include @@ -51,6 +55,11 @@ class CelixEventAdminErrorInjectionTestSuite : public CelixEventAdminTestSuiteBa celix_ei_expect_celix_arrayList_addLong(nullptr, 0, 0); celix_ei_expect_celix_elapsedtime(nullptr, 0, 0); celix_ei_expect_celix_arrayList_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_copy(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_set(nullptr, 0, 0); + celix_ei_expect_celix_properties_setLong(nullptr, 0, 0); + celix_ei_expect_celix_bundleContext_getProperty(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); } }; @@ -89,6 +98,12 @@ TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateLogHelperForEventAd EXPECT_EQ(nullptr, ea); } +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToGetFrameworkUUIDForEventAdminTest) { + celix_ei_expect_celix_bundleContext_getProperty((void*)&celix_eventAdmin_create, 0, nullptr); + auto ea = celix_eventAdmin_create(ctx.get()); + EXPECT_EQ(nullptr, ea); +} + TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateLockForEventAdminTest) { celix_ei_expect_celixThreadRwlock_create((void*)&celix_eventAdmin_create, 0, CELIX_ENOMEM); auto ea = celix_eventAdmin_create(ctx.get()); @@ -119,6 +134,18 @@ TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateEventHandlersMapFor EXPECT_EQ(nullptr, ea); } +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateEventSeqIdCacheForEventAdminTest) { + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)&celix_eventAdmin_create, 0, nullptr); + auto ea = celix_eventAdmin_create(ctx.get()); + EXPECT_EQ(nullptr, ea); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateRemoteProviderMapForEventAdminTest) { + celix_ei_expect_celix_longHashMap_create((void*)&celix_eventAdmin_create, 0, nullptr, 2); + auto ea = celix_eventAdmin_create(ctx.get()); + EXPECT_EQ(nullptr, ea); +} + TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCreateMutexForEventAdminTest) { celix_ei_expect_celixThreadMutex_create((void*)&celix_eventAdmin_create, 0, CELIX_ENOMEM); auto ea = celix_eventAdmin_create(ctx.get()); @@ -339,4 +366,128 @@ TEST_F(CelixEventAdminErrorInjectionTestSuite, PostEventToBlacklistHandlerTest) EXPECT_STRNE("org/celix/test1", topic); return CELIX_SUCCESS; }); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, RetrieveEventSeqIdCacheTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + for (int i = 0; i < 32; i++) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + std::string uuid = "9748f803-5766-49f1-a2e9-" + std::to_string(i); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, uuid.c_str()); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + } + celix_ei_expect_celix_elapsedtime(CELIX_EI_UNKNOWN_CALLER, 0, 60*60+1); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874b"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char*, const celix_properties_t*) { + receivedEventCount ++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(33, receivedEventCount); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToCopyRemoteEnableEventPropertiesTest) { + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = nullptr; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([](celix_event_admin_t *ea) { + celix_ei_expect_celix_properties_copy((void*)&celix_eventAdmin_sendEvent, 1, nullptr); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(ENOMEM, status); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToSetRemoteFrameworkUUIDToRemoteEnableEventTest) { + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = nullptr; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([](celix_event_admin_t *ea) { + celix_ei_expect_celix_properties_set((void*)&celix_eventAdmin_sendEvent, 1, ENOMEM); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(ENOMEM, status); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedToSetSeqIdToRemoteEnableEventTest) { + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = nullptr; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([](celix_event_admin_t *ea) { + celix_ei_expect_celix_properties_setLong((void*)&celix_eventAdmin_sendEvent, 1, ENOMEM); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(ENOMEM, status); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedAllocMemoryForSeqIdCacheWhenSendRemoteEventTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_ei_expect_calloc((void*)&celix_eventAdmin_sendEvent, 2, nullptr); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char*, const celix_properties_t*) { + receivedEventCount ++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(2, receivedEventCount); +} + +TEST_F(CelixEventAdminErrorInjectionTestSuite, FailedAddSeqIdCacheWhenSendRemoteEventTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_ei_expect_celix_stringHashMap_put((void*)&celix_eventAdmin_sendEvent, 2, ENOMEM); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char*, const celix_properties_t*) { + receivedEventCount ++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(2, receivedEventCount); } \ No newline at end of file diff --git a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc index e05cc19df..7bc7d7002 100644 --- a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc +++ b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include "CelixEventAdminTestSuiteBaseClass.h" @@ -90,6 +92,18 @@ TEST_F(CelixEventAdminTestSuite, CreateEventAdminWithMaxHandlerThreadNrTest) { unsetenv("CELIX_EVENT_ADMIN_HANDLER_THREADS"); } +TEST_F(CelixEventAdminTestSuite, CreateEventAdminWithInvalidSeqIdCacheCleanupIntervalTest) { + setenv("CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL", "0", 1); + auto ea = celix_eventAdmin_create(ctx.get()); + EXPECT_TRUE(ea == nullptr); + + setenv("CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL", "-1", 1); + ea = celix_eventAdmin_create(ctx.get()); + EXPECT_TRUE(ea == nullptr); + + unsetenv("CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL"); +} + TEST_F(CelixEventAdminTestSuite, StartStopEventAdminTest) { auto ea = celix_eventAdmin_create(ctx.get()); EXPECT_TRUE(ea != nullptr); @@ -467,6 +481,273 @@ TEST_F(CelixEventAdminTestSuite, PostMutilEventToUnorderedHandlerTest) { }, true); } +TEST_F(CelixEventAdminTestSuite, AddRemoteProviderServiceTest) { + TestEventAdmin([](celix_event_admin_t *ea, celix_bundle_context_t *ctx) { + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = nullptr; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + celix_service_registration_options_t svcOpts{}; + svcOpts.svc = &remoteProviderService; + svcOpts.serviceName = CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME; + svcOpts.serviceVersion = CELIX_EVENT_REMOTE_PROVIDER_SERVICE_VERSION; + auto remoteProviderSvcId = celix_bundleContext_registerServiceWithOptions(ctx, &svcOpts); + ASSERT_TRUE(remoteProviderSvcId >= 0); + + celix_service_tracking_options_t opts{}; + opts.filter.serviceName = CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME; + opts.filter.versionRange = CELIX_EVENT_REMOTE_PROVIDER_SERVICE_VERSION; + opts.callbackHandle = ea; + opts.addWithProperties = [](void *handle, void *svc, const celix_properties_t *props) { + auto status = celix_eventAdmin_addRemoteProviderService(handle, svc, props); + EXPECT_EQ(CELIX_SUCCESS, status); + }; + opts.removeWithProperties = [](void *handle, void *svc, const celix_properties_t *props) { + auto status = celix_eventAdmin_removeRemoteProviderService(handle, svc, props); + EXPECT_EQ(CELIX_SUCCESS, status); + }; + long remoteProviderTrkId = celix_bundleContext_trackServicesWithOptions(ctx, &opts); + EXPECT_TRUE(remoteProviderTrkId >= 0); + + celix_bundleContext_unregisterService(ctx, remoteProviderSvcId); + celix_bundleContext_stopTracker(ctx, remoteProviderTrkId); + }); +} + +TEST_F(CelixEventAdminTestSuite, PostRemoteEnableEventTest) { + std::atomic remoteProviderCalled{false}; + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = &remoteProviderCalled; + remoteProviderService.postEvent = [](void* handle, const char* topic, const celix_properties_t* props) { + auto called = static_cast *>(handle); + called->store(true); + EXPECT_STREQ("org/celix/test", topic); + EXPECT_NE(nullptr, celix_properties_get(props, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, nullptr)); + long seqId = celix_properties_getAsLong(props, CELIX_EVENT_REMOTE_SEQ_ID, -1L); + EXPECT_GE(seqId, 0); + EXPECT_FALSE(celix_properties_getBool(props, CELIX_EVENT_REMOTE_ENABLE, false)); + return CELIX_SUCCESS; + }; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([&remoteProviderCalled](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_postEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(remoteProviderCalled.load()); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminTestSuite, SendRemoteEnableEventTest) { + std::atomic remoteProviderCalled{false}; + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = &remoteProviderCalled; + remoteProviderService.sendEvent = [](void* handle, const char* topic, const celix_properties_t* props) { + auto called = static_cast *>(handle); + called->store(true); + EXPECT_STREQ("org/celix/test", topic); + EXPECT_NE(nullptr, celix_properties_get(props, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, nullptr)); + long seqId = celix_properties_getAsLong(props, CELIX_EVENT_REMOTE_SEQ_ID, -1L); + EXPECT_GE(seqId, 0); + EXPECT_FALSE(celix_properties_getBool(props, CELIX_EVENT_REMOTE_ENABLE, false)); + return CELIX_SUCCESS; + }; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([&remoteProviderCalled](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(remoteProviderCalled.load()); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminTestSuite, FailedToDelieverAsyncEventToRemoteProviderTest) { + std::atomic remoteProviderCalled{false}; + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = &remoteProviderCalled; + remoteProviderService.postEvent = [](void* handle, const char*, const celix_properties_t*) { + auto called = static_cast *>(handle); + called->store(true); + return ENOMEM; + }; + remoteProviderService.sendEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([&remoteProviderCalled](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_postEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status);//should not fail, only record error log + EXPECT_TRUE(remoteProviderCalled.load()); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminTestSuite, FailedToDelieverSyncEventToRemoteProviderTest) { + std::atomic remoteProviderCalled{false}; + celix_event_remote_provider_service_t remoteProviderService; + remoteProviderService.handle = &remoteProviderCalled; + remoteProviderService.sendEvent = [](void* handle, const char*, const celix_properties_t*) { + auto called = static_cast *>(handle); + called->store(true); + return ENOMEM; + }; + remoteProviderService.postEvent = [](void*, const char*, const celix_properties_t*) { + ADD_FAILURE() << "Should not be called"; + return CELIX_SUCCESS; + }; + TestPublishEventToRemote([&remoteProviderCalled](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_setBool(eventProps, CELIX_EVENT_REMOTE_ENABLE, true); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status);//should not fail, only record error log + EXPECT_TRUE(remoteProviderCalled.load()); + }, &remoteProviderService); +} + +TEST_F(CelixEventAdminTestSuite, PostRemoteEventTest) { + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_postEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + auto eventDone = WaitForEventDone(30); + EXPECT_TRUE(eventDone); + }, [](void*, const char* topic, const celix_properties_t*) { + EXPECT_STREQ("org/celix/test", topic); + HandleEventDone(); + return CELIX_SUCCESS; + }); +} + +TEST_F(CelixEventAdminTestSuite, PostRemoteEventWithoutSeqIdTest) { + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + auto status = celix_eventAdmin_postEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + auto eventDone = WaitForEventDone(30); + EXPECT_TRUE(eventDone); + }, [](void*, const char* topic, const celix_properties_t*) { + EXPECT_STREQ("org/celix/test", topic); + HandleEventDone(); + return CELIX_SUCCESS; + }); +} + +TEST_F(CelixEventAdminTestSuite, SendRemoteEventTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char* topic, const celix_properties_t*) { + EXPECT_STREQ("org/celix/test", topic); + receivedEventCount++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(1, receivedEventCount); +} + +TEST_F(CelixEventAdminTestSuite, SendRemoteEventWithoutSeqIdTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char* topic, const celix_properties_t*) { + EXPECT_STREQ("org/celix/test", topic); + receivedEventCount++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(1, receivedEventCount); +} + +TEST_F(CelixEventAdminTestSuite, SendDuplicateRemoteEventTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char*, const celix_properties_t*) { + receivedEventCount ++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(1, receivedEventCount); +} + +TEST_F(CelixEventAdminTestSuite, CleanupOldEventSeqIdCacheTest) { + setenv("CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL", "1", 1); + + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 0); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); + + for (int i = 0; i < 16 ; ++i) { + char fwuuid[64] = {0}; + snprintf(fwuuid, 64, "fw-uuid-%d", i); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, fwuuid); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + } + + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + }, [&receivedEventCount](void*, const char* topic, const celix_properties_t*) { + EXPECT_STREQ("org/celix/test", topic); + receivedEventCount++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(18, receivedEventCount); + + unsetenv("CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL"); +} + +TEST_F(CelixEventAdminTestSuite, SendRemoteEventWhichFromDifferentFrameworkTest) { + int receivedEventCount = 0; + TestPublishEvent("org/celix/test", nullptr, [](celix_event_admin_t *ea) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874a"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + auto status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_properties_set(eventProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, "9748f803-5766-49f1-a2e9-9bbb522e874b"); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_SEQ_ID, 1); + status = celix_eventAdmin_sendEvent(ea, "org/celix/test", eventProps); + EXPECT_EQ(CELIX_SUCCESS, status); + }, [&receivedEventCount](void*, const char*, const celix_properties_t*) { + receivedEventCount ++; + return CELIX_SUCCESS; + }); + EXPECT_EQ(2, receivedEventCount); +} + TEST_F(CelixEventAdminTestSuite, MutilpleEventHandlerSubscribeMutilpleTopicTest) { TestEventAdmin([](celix_event_admin_t *ea, celix_bundle_context_t *ctx) { celix_event_handler_service_t handler; @@ -531,20 +812,6 @@ TEST_F(CelixEventAdminTestSuite, EventHandlerNoTopicTest) { }); } -TEST_F(CelixEventAdminTestSuite, EventHandlerNoServiceIdTest) { - TestAddEventHandler([](void *handle, void *svc, const celix_properties_t *props) { - std::unique_ptr propsCopy{celix_properties_copy(props), celix_properties_destroy}; - celix_properties_unset(propsCopy.get(), CELIX_FRAMEWORK_SERVICE_ID); - auto status = celix_eventAdmin_addEventHandlerWithProperties(handle, svc, propsCopy.get()); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); - }, [](void *handle, void *svc, const celix_properties_t *props) { - std::unique_ptr propsCopy{celix_properties_copy(props), celix_properties_destroy}; - celix_properties_unset(propsCopy.get(), CELIX_FRAMEWORK_SERVICE_ID); - auto status = celix_eventAdmin_removeEventHandlerWithProperties(handle, svc, propsCopy.get()); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); - }); -} - TEST_F(CelixEventAdminTestSuite, EventHandlerWithInvalidFilterTopicTest) { TestAddEventHandler([](void *handle, void *svc, const celix_properties_t *props) { std::unique_ptr propsCopy{celix_properties_copy(props), celix_properties_destroy}; diff --git a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuiteBaseClass.h b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuiteBaseClass.h index 7da83485e..cf783db12 100644 --- a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuiteBaseClass.h +++ b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuiteBaseClass.h @@ -21,10 +21,11 @@ #define CELIX_CELIX_EVENT_ADMIN_TEST_SUITE_BASE_CLASS_H #include - +#include #include "celix_event_admin.h" #include "celix_event_handler_service.h" #include "celix_event_constants.h" +#include "celix_event_remote_provider_service.h" #include "celix_bundle_context.h" #include "celix_framework_factory.h" #include "celix_constants.h" @@ -138,14 +139,14 @@ class CelixEventAdminTestSuiteBaseClass : public ::testing::Test { celix_eventAdmin_destroy(ea); } - void TestPublishEvent(const char *handlerTopics, const char *eventFilter, std::function testBody, - std::function onHandleEvent, bool asyncUnordered = false) { + void TestPublishEvent(const char *handlerTopics, const char *eventFilter, const std::function& testBody, + const std::function& onHandleEvent, bool asyncUnordered = false) { auto ea = celix_eventAdmin_create(ctx.get()); EXPECT_TRUE(ea != nullptr); auto status = celix_eventAdmin_start(ea); EXPECT_EQ(CELIX_SUCCESS, status); struct celix_handle_event_callback_data { - std::function onHandleEvent; + const std::function& onHandleEvent; void* handle; } data{onHandleEvent, ea}; celix_event_handler_service_t handler; @@ -192,6 +193,16 @@ class CelixEventAdminTestSuiteBaseClass : public ::testing::Test { celix_eventAdmin_destroy(ea); } + void TestPublishEventToRemote(const std::function& testBody, celix_event_remote_provider_service_t* remoteProviderService) { + TestPublishEvent("*", nullptr, [&testBody, remoteProviderService](celix_event_admin_t* ea) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + auto status = celix_eventAdmin_addRemoteProviderService(ea, remoteProviderService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + testBody(ea); + }, [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}); + } + std::shared_ptr fw{}; std::shared_ptr ctx{}; }; diff --git a/bundles/event_admin/event_admin/src/celix_event_admin.c b/bundles/event_admin/event_admin/src/celix_event_admin.c index 2d02824a6..906152d1a 100644 --- a/bundles/event_admin/event_admin/src/celix_event_admin.c +++ b/bundles/event_admin/event_admin/src/celix_event_admin.c @@ -18,6 +18,7 @@ */ #include "celix_event_admin.h" +#include #include #include #include @@ -33,15 +34,20 @@ #include "celix_threads.h" #include "celix_utils.h" #include "celix_stdlib_cleanup.h" +#include "celix_event_remote_provider_service.h" #define CELIX_EVENT_ADMIN_MAX_HANDLER_THREADS 20 #define CELIX_EVENT_ADMIN_HANDLER_THREADS_DEFAULT 5 +#define CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL_DFT (60*60) //1h + //Belows parameters are not configurable, consider its configurability until a real need arises. #define CELIX_EVENT_ADMIN_MAX_PARALLEL_EVENTS_OF_HANDLER(handlerThNr) ((handlerThNr)/3 + 1) //max parallel async event for a single handler #define CELIX_EVENT_ADMIN_MAX_HANDLE_EVENT_TIME 60 //seconds #define CELIX_EVENT_ADMIN_MAX_EVENT_QUEUE_SIZE 512 //events +#define CELIX_EVENT_ADMIN_MAX_EVENT_SEQ_ID_CACHE_SIZE 1024 + typedef struct celix_event_handler { celix_event_handler_service_t* service; long serviceId; @@ -50,26 +56,36 @@ typedef struct celix_event_handler { celix_filter_t* eventFilter; bool blackListed;//Blacklisted handlers must not be notified of any events. unsigned int handlingAsyncEventCnt; -}celix_event_handler_t; +} celix_event_handler_t; typedef struct celix_event_channel { celix_array_list_t* eventHandlerSvcIdList; -}celix_event_channel_t; +} celix_event_channel_t; typedef struct celix_event_entry { celix_event_t* event; celix_long_hash_map_t* eventHandlers;//key: event handler service id, value: null -}celix_event_entry_t; +} celix_event_entry_t; + +typedef struct celix_event_seq_id_cache { + struct timespec lastModified; + long seqIdBuffer[CELIX_EVENT_ADMIN_MAX_EVENT_SEQ_ID_CACHE_SIZE]; +} celix_event_seq_id_cache_t; struct celix_event_admin { celix_bundle_context_t* ctx; celix_log_helper_t* logHelper; unsigned int handlerThreadNr; - celix_thread_rwlock_t lock;//projects: channels,eventHandlers + long seqIdCacheCleanupInterval; + const char* fwUUID; + long nextSeqId; + celix_thread_rwlock_t lock;//projects: channels,eventHandlers,eventSeqIdCache,remoteProviderServices celix_event_channel_t channelMatchingAllEvents; celix_string_hash_map_t* channelsMatchingTopic; //key: topic, value: celix_event_channel_t * celix_string_hash_map_t* channelsMatchingPrefixTopic;//key:prefix topic, value: celix_event_channel_t * celix_long_hash_map_t* eventHandlers;//key: event handler service id, value: celix_event_handler_t* + celix_string_hash_map_t* eventSeqIdCache;//key: remote framework uuid, value: celix_event_seq_id_cache_t* + celix_long_hash_map_t* remoteProviderServices;//key: service id, value: celix_event_remote_provider_service_t* celix_thread_mutex_t eventsMutex;// protect belows celix_thread_cond_t eventsTriggerCond; celix_array_list_t* asyncEventQueue;//array_list @@ -87,16 +103,27 @@ celix_event_admin_t* celix_eventAdmin_create(celix_bundle_context_t* ctx) { } ea->ctx = ctx; ea->threadsRunning = false; + ea->nextSeqId = 0; celix_autoptr(celix_log_helper_t) logHelper = ea->logHelper = celix_logHelper_create(ctx, "CelixEventAdmin"); if (logHelper == NULL) { return NULL; } + ea->fwUUID = celix_bundleContext_getProperty(ctx, CELIX_FRAMEWORK_UUID, NULL); + if (ea->fwUUID == NULL) { + celix_logHelper_error(logHelper, "Failed to get framework uuid."); + return NULL; + } ea->handlerThreadNr = (unsigned int)celix_bundleContext_getPropertyAsLong(ctx, "CELIX_EVENT_ADMIN_HANDLER_THREADS", CELIX_EVENT_ADMIN_HANDLER_THREADS_DEFAULT); if (ea->handlerThreadNr > CELIX_EVENT_ADMIN_MAX_HANDLER_THREADS || ea->handlerThreadNr == 0) { celix_logHelper_error(logHelper, "CELIX_EVENT_ADMIN_HANDLER_THREADS is set to %i, but max is %i.", ea->handlerThreadNr, CELIX_EVENT_ADMIN_MAX_HANDLER_THREADS); return NULL; } + ea->seqIdCacheCleanupInterval = celix_bundleContext_getPropertyAsLong(ctx, "CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL", CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL_DFT); + if (ea->seqIdCacheCleanupInterval <= 0) { + celix_logHelper_error(logHelper, "CELIX_EVENT_ADMIN_EVENT_SEQID_CACHE_CLEANUP_INTERVAL is set to %ld, but must be > 0.", ea->seqIdCacheCleanupInterval); + return NULL; + } celix_status_t status = celixThreadRwlock_create(&ea->lock, NULL); if (status != CELIX_SUCCESS) { celix_logHelper_error(logHelper, "Failed to create event admin lock."); @@ -127,6 +154,23 @@ celix_event_admin_t* celix_eventAdmin_create(celix_bundle_context_t* ctx) { celix_logHelper_error(logHelper, "Failed to create event handler map."); return NULL; } + celix_autoptr(celix_string_hash_map_t) eventSeqIdCache = NULL; + { + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = free; + eventSeqIdCache = ea->eventSeqIdCache = celix_stringHashMap_createWithOptions(&opts); + if (eventSeqIdCache == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create event seq id cache."); + return NULL; + } + } + celix_autoptr(celix_long_hash_map_t) remoteProviderServices = ea->remoteProviderServices = celix_longHashMap_create(); + if (remoteProviderServices == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create remote provider services map."); + return NULL; + } status = celixThreadMutex_create(&ea->eventsMutex, NULL); if (status != CELIX_SUCCESS) { @@ -154,6 +198,8 @@ celix_event_admin_t* celix_eventAdmin_create(celix_bundle_context_t* ctx) { celix_steal_ptr(asyncEventQueue); celix_steal_ptr(cond); celix_steal_ptr(mutex); + celix_steal_ptr(remoteProviderServices); + celix_steal_ptr(eventSeqIdCache); celix_steal_ptr(eventHandlers); celix_steal_ptr(channelsMatchingPrefixTopic); celix_steal_ptr(channelsMatchingTopic); @@ -204,6 +250,8 @@ void celix_eventAdmin_destroy(celix_event_admin_t* ea) { celix_arrayList_destroy(ea->asyncEventQueue); celixThreadCondition_destroy(&ea->eventsTriggerCond); celixThreadMutex_destroy(&ea->eventsMutex); + celix_longHashMap_destroy(ea->remoteProviderServices); + celix_stringHashMap_destroy(ea->eventSeqIdCache); assert(celix_longHashMap_size(ea->eventHandlers) == 0); celix_longHashMap_destroy(ea->eventHandlers); assert(celix_stringHashMap_size(ea->channelsMatchingPrefixTopic) == 0); @@ -316,10 +364,7 @@ int celix_eventAdmin_addEventHandlerWithProperties(void* handle, void* svc, cons return CELIX_ILLEGAL_ARGUMENT; } long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1L); - if (serviceId < 0) { - celix_logHelper_error(ea->logHelper, "Event handler service id is empty."); - return CELIX_ILLEGAL_ARGUMENT; - } + assert(serviceId >= 0); celix_autofree celix_event_handler_t *handler = calloc(1, sizeof(*handler)); if (handler == NULL) { @@ -397,10 +442,7 @@ int celix_eventAdmin_removeEventHandlerWithProperties(void* handle, void* svc, c celix_event_admin_t* ea = handle; long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1L); - if (serviceId < 0) { - celix_logHelper_error(ea->logHelper, "Event handler service id is empty."); - return CELIX_ILLEGAL_ARGUMENT; - } + assert(serviceId >= 0); celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&ea->lock); celix_event_handler_t *handler = celix_longHashMap_get(ea->eventHandlers, serviceId); @@ -415,6 +457,124 @@ int celix_eventAdmin_removeEventHandlerWithProperties(void* handle, void* svc, c return CELIX_SUCCESS; } +int celix_eventAdmin_addRemoteProviderService(void* handle, void* svc, const celix_properties_t* props) { + assert(handle != NULL); + assert(svc != NULL); + celix_event_admin_t* ea = handle; + long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1L); + assert(serviceId >= 0); + + celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&ea->lock); + celix_longHashMap_put(ea->remoteProviderServices, serviceId, svc); + return CELIX_SUCCESS; +} + +int celix_eventAdmin_removeRemoteProviderService(void* handle, void* svc, const celix_properties_t* props) { + assert(handle != NULL); + assert(svc != NULL); + celix_event_admin_t* ea = handle; + long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1L); + assert(serviceId >= 0); + + celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&ea->lock); + celix_longHashMap_remove(ea->remoteProviderServices, serviceId); + return CELIX_SUCCESS; +} + +static void celix_eventAdmin_cleanupOldEventSeqIdCache(celix_event_admin_t* ea) { + if (celix_stringHashMap_size(ea->eventSeqIdCache) > 16) { + celix_string_hash_map_iterator_t iter = celix_stringHashMap_begin(ea->eventSeqIdCache); + while (!celix_stringHashMapIterator_isEnd(&iter)) { + celix_event_seq_id_cache_t* cache = iter.value.ptrValue; + if (celix_elapsedtime(CLOCK_MONOTONIC, cache->lastModified) > ea->seqIdCacheCleanupInterval) { + celix_stringHashMapIterator_remove(&iter); + } else { + celix_stringHashMapIterator_next(&iter); + } + } + } + return; +} + +static bool celix_eventAdmin_isDuplicateEvent(celix_event_admin_t* ea, const char* topic CELIX_UNUSED, const celix_properties_t* properties) { + const char* remoteFwUUID = celix_properties_get(properties, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, NULL); + if (remoteFwUUID == NULL) { + return false; + } + long seqId = celix_properties_getAsLong(properties, CELIX_EVENT_REMOTE_SEQ_ID, -1L); + if (seqId < 0) { + return false; + } + long seqIdMod = seqId % CELIX_EVENT_ADMIN_MAX_EVENT_SEQ_ID_CACHE_SIZE; + celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&ea->lock); + celix_event_seq_id_cache_t* seqIdCache = celix_stringHashMap_get(ea->eventSeqIdCache, remoteFwUUID); + if (seqIdCache == NULL) { + celix_autofree celix_event_seq_id_cache_t* cache = calloc(1, sizeof(*cache)); + if (cache == NULL) { + celix_logHelper_error(ea->logHelper, "Failed to create event seq id cache for %s.", remoteFwUUID); + return false; + } + memset(cache->seqIdBuffer, -1, sizeof(cache->seqIdBuffer)); + celix_status_t status = celix_stringHashMap_put(ea->eventSeqIdCache, remoteFwUUID, cache); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(ea->logHelper, "Failed to add event seq id cache for %s.", remoteFwUUID); + return false; + } + seqIdCache = celix_steal_ptr(cache); + } + seqIdCache->lastModified = celix_gettime(CLOCK_MONOTONIC); + if (seqIdCache->seqIdBuffer[seqIdMod] == seqId) { + return true; + } + seqIdCache->seqIdBuffer[seqIdMod] = seqId; + + celix_eventAdmin_cleanupOldEventSeqIdCache(ea); + + return false; +} + +static long celix_eventAdmin_getEventSeqId(celix_event_admin_t* ea) { + long seqId = __atomic_fetch_add(&ea->nextSeqId, 1, __ATOMIC_RELAXED); + seqId = seqId & LONG_MAX;//avoid negative seq id when overflow + return seqId; +} + +static int celix_eventAdmin_deliverEventToRemote(celix_event_admin_t* ea, const char* topic, const celix_properties_t* props, bool async) { + celix_autoptr(celix_properties_t) remoteProps = celix_properties_copy(props); + if (remoteProps == NULL) { + celix_logHelper_error(ea->logHelper, "Failed to copy remote properties for event %s.", topic); + return ENOMEM; + } + celix_properties_unset(remoteProps, CELIX_EVENT_REMOTE_ENABLE); + celix_status_t status = celix_properties_set(remoteProps, CELIX_EVENT_REMOTE_FRAMEWORK_UUID, ea->fwUUID); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(ea->logHelper, "Failed to set remote framework uuid for event %s.", topic); + return status; + } + long seqId = celix_eventAdmin_getEventSeqId(ea); + status = celix_properties_setLong(remoteProps, CELIX_EVENT_REMOTE_SEQ_ID, seqId); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(ea->logHelper, "Failed to set remote seq id for event %s.", topic); + return status; + } + celix_auto(celix_rwlock_rlock_guard_t) rLockGuard = celixRwlockRlockGuard_init(&ea->lock); + CELIX_LONG_HASH_MAP_ITERATE(ea->remoteProviderServices, iter) { + celix_event_remote_provider_service_t* remoteProvider = iter.value.ptrValue; + if (async) { + celix_logHelper_trace(ea->logHelper, "Post event %s to remote provider.", topic); + status = remoteProvider->postEvent(remoteProvider->handle, topic, remoteProps); + } else { + celix_logHelper_trace(ea->logHelper, "Send event %s to remote provider.", topic); + status = remoteProvider->sendEvent(remoteProvider->handle, topic, remoteProps); + } + if (status != CELIX_SUCCESS) { + celix_logHelper_warning(ea->logHelper, "Failed to deliver event %s to remote provider(%ld).", topic, iter.key); + } + } + + return CELIX_SUCCESS; +} + static void celix_eventAdmin_collectEventHandlers(celix_event_admin_t* ea, const char* eventTopic, const celix_properties_t* eventProperties, celix_event_channel_t* channel, celix_long_hash_map_t* eventHandlers) { if (channel == NULL) { @@ -449,6 +609,10 @@ static void celix_eventAdmin_collectEventHandlers(celix_event_admin_t* ea, const static int celix_eventAdmin_deliverEvent(celix_event_admin_t* ea, const char* eventTopic, const celix_properties_t* eventProperties, int (*deliverAction)(celix_event_admin_t* ea, const char* topic, const celix_properties_t* properties, celix_long_hash_map_t* eventHandlers, bool* stealEventHandlers)) { + if (celix_eventAdmin_isDuplicateEvent(ea, eventTopic, eventProperties)) { + celix_logHelper_debug(ea->logHelper, "Duplicate event %s", eventTopic); + return CELIX_SUCCESS; + } celix_autoptr(celix_long_hash_map_t) eventHandlers = celix_longHashMap_create();//avoid duplicated event handler if (eventHandlers == NULL) { celix_logHelper_logTssErrors(ea->logHelper, CELIX_LOG_LEVEL_ERROR); @@ -505,7 +669,7 @@ static void celix_eventAdmin_deliverEventToHandler(celix_event_admin_t* ea, cons //If a Log Service is available, the exception should be logged. //Once the exception has been caught and dealt with, the event delivery must continue with the next handlers to be notified, if any. //See https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.event.html#d0e47600 - celix_logHelper_error(ea->logHelper, "Failed to handle event %s for handler(%s)", topic, eventHandler->serviceDescription); + celix_logHelper_warning(ea->logHelper, "Failed to handle event %s for handler(%s)", topic, eventHandler->serviceDescription); } double elapsedTime = celix_elapsedtime(CLOCK_MONOTONIC, startTime); if (elapsedTime > CELIX_EVENT_ADMIN_MAX_HANDLE_EVENT_TIME) { @@ -533,7 +697,16 @@ celix_status_t celix_eventAdmin_sendEvent(void* handle, const char* topic, const return CELIX_ILLEGAL_ARGUMENT; } celix_event_admin_t* ea = (celix_event_admin_t*)handle; - return celix_eventAdmin_deliverEvent(ea, topic, props, celix_eventAdmin_deliverEventSyncDo); + celix_status_t status = celix_eventAdmin_deliverEvent(ea, topic, props, celix_eventAdmin_deliverEventSyncDo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(ea->logHelper, "Failed to send event %s.", topic); + return status; + } + bool remoteEnable = celix_properties_getAsBool(props, CELIX_EVENT_REMOTE_ENABLE, false); + if (remoteEnable) { + return celix_eventAdmin_deliverEventToRemote(ea, topic, props, false); + } + return CELIX_SUCCESS; } static int celix_eventAdmin_deliverEventAsyncDo(celix_event_admin_t* ea, const char* topic, const celix_properties_t* props, @@ -573,7 +746,16 @@ celix_status_t celix_eventAdmin_postEvent(void* handle, const char* topic, const return CELIX_ILLEGAL_ARGUMENT; } celix_event_admin_t* ea = (celix_event_admin_t*)handle; - return celix_eventAdmin_deliverEvent(ea, topic, props, celix_eventAdmin_deliverEventAsyncDo); + celix_status_t status = celix_eventAdmin_deliverEvent(ea, topic, props, celix_eventAdmin_deliverEventAsyncDo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(ea->logHelper, "Failed to send event %s.", topic); + return status; + } + bool remoteEnable = celix_properties_getAsBool(props, CELIX_EVENT_REMOTE_ENABLE, false); + if (remoteEnable) { + return celix_eventAdmin_deliverEventToRemote(ea, topic, props, true); + } + return CELIX_SUCCESS; } static bool celix_eventAdmin_getPendingEvent(celix_event_admin_t* ea, celix_event_t** event, long* eventHandlerSvcId) { diff --git a/bundles/event_admin/event_admin/src/celix_event_admin.h b/bundles/event_admin/event_admin/src/celix_event_admin.h index 99ed8e8f3..3283f5a9b 100644 --- a/bundles/event_admin/event_admin/src/celix_event_admin.h +++ b/bundles/event_admin/event_admin/src/celix_event_admin.h @@ -37,6 +37,9 @@ int celix_eventAdmin_stop(celix_event_admin_t* ea); int celix_eventAdmin_addEventHandlerWithProperties(void* handle, void* svc, const celix_properties_t* props); int celix_eventAdmin_removeEventHandlerWithProperties(void* handle, void* svc, const celix_properties_t* props); +int celix_eventAdmin_addRemoteProviderService(void* handle, void* svc, const celix_properties_t* props); +int celix_eventAdmin_removeRemoteProviderService(void* handle, void* svc, const celix_properties_t* props); + celix_status_t celix_eventAdmin_sendEvent(void* handle, const char* topic, const celix_properties_t* props); celix_status_t celix_eventAdmin_postEvent(void* handle, const char* topic, const celix_properties_t* props); diff --git a/bundles/event_admin/event_admin/src/celix_event_admin_activator.c b/bundles/event_admin/event_admin/src/celix_event_admin_activator.c index 81b1f98ca..2396521a9 100644 --- a/bundles/event_admin/event_admin/src/celix_event_admin_activator.c +++ b/bundles/event_admin/event_admin/src/celix_event_admin_activator.c @@ -26,6 +26,7 @@ #include "celix_event_admin_service.h" #include "celix_event_handler_service.h" #include "celix_event_constants.h" +#include "celix_event_remote_provider_service.h" typedef struct celix_event_admin_activator { celix_event_admin_t *eventAdmin; @@ -71,6 +72,27 @@ celix_status_t celix_eventAdminActivator_start(celix_event_admin_activator_t *ac celix_steal_ptr(eventHandlerDep); } + { + celix_autoptr(celix_dm_service_dependency_t) remoteProviderDep = celix_dmServiceDependency_create(); + if (remoteProviderDep == NULL) { + return CELIX_ENOMEM; + } + status = celix_dmServiceDependency_setService(remoteProviderDep, CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME, CELIX_EVENT_REMOTE_PROVIDER_SERVICE_USE_RANGE, NULL); + if (status != CELIX_SUCCESS) { + return status; + } + celix_dmServiceDependency_setStrategy(remoteProviderDep, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING); + celix_dm_service_dependency_callback_options_t opts = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts.addWithProps = celix_eventAdmin_addRemoteProviderService; + opts.removeWithProps = celix_eventAdmin_removeRemoteProviderService; + celix_dmServiceDependency_setCallbacksWithOptions(remoteProviderDep, &opts); + status = celix_dmComponent_addServiceDependency(adminCmp, remoteProviderDep); + if (status != CELIX_SUCCESS) { + return status; + } + celix_steal_ptr(remoteProviderDep); + } + act->eventAdminService.handle = act->eventAdmin; act->eventAdminService.postEvent = celix_eventAdmin_postEvent; act->eventAdminService.sendEvent = celix_eventAdmin_sendEvent; diff --git a/bundles/event_admin/event_admin_api/include/celix_event_admin_service.h b/bundles/event_admin/event_admin_api/include/celix_event_admin_service.h index 6bffc18b3..f061d0520 100644 --- a/bundles/event_admin/event_admin_api/include/celix_event_admin_service.h +++ b/bundles/event_admin/event_admin_api/include/celix_event_admin_service.h @@ -58,7 +58,7 @@ typedef struct celix_event_admin_service { * @return Status code indicating failure or success. CELIX_SUCCESS if no errors are encountered. If an error is encountered, it should be a celix errno. */ celix_status_t (*sendEvent)(void* handle, const char* topic, const celix_properties_t* properties); -}celix_event_admin_service_t; +} celix_event_admin_service_t; #ifdef __cplusplus } diff --git a/bundles/event_admin/event_admin_api/include/celix_event_constants.h b/bundles/event_admin/event_admin_api/include/celix_event_constants.h index 2e8309db5..61a6f91f7 100644 --- a/bundles/event_admin/event_admin_api/include/celix_event_constants.h +++ b/bundles/event_admin/event_admin_api/include/celix_event_constants.h @@ -106,6 +106,52 @@ extern "C" { */ #define CELIX_EVENT_TIMESTAMP "timestamp" +/** + * @brief It is a property of event to indicate the event is a remote event. The type of the value for this event property is Boolean. + * + * If the value is true, the event will be delivered to remote + * event handlers and local event handlers, otherwise, the event will be only delivered to local event handlers. + */ +#define CELIX_EVENT_REMOTE_ENABLE "celix.event.remote.enable" + +/** + * @brief The QoS of the remote event. The type of the value for this event property is integer. It indicates the quality of service of the remote event. If the value is not set, the remote provider should use a proper default value. + * + * The value must be one of the following: + * - 0: At most once delivery + * - 1: At least once delivery + * - 2: Exactly once delivery + * + * The value of QOS will not impact whether `sendEvent` method of event admin will return successfully or not. + * But it will impact the remote provider's behavior. For example, if the remote provider has not established a connection, + * it maybe immediately return ENOTCONN if the QOS value is 0.(The event admin will not forward the error to the caller of `sendEvent` method.) + * If the QOS value is greater than 0, it will wait until the connection is established before sending. + */ +#define CELIX_EVENT_REMOTE_QOS "celix.event.remote.qos" + +/** + * @brief The expiry interval of the remote event in seconds. The type of the value for this event property is integer. + * If the value is not set, the remote provider should use a proper default value. + */ +#define CELIX_EVENT_REMOTE_EXPIRY_INTERVAL "celix.event.remote.expiryInterval" + +/** + * @brief The framework UUID of remote event producer. The type of the value for this event property is String. + * + * It is set by the event admin, and only readable for user. Local event does not contain this property. + * + */ +#define CELIX_EVENT_REMOTE_FRAMEWORK_UUID "celix.event.remote.framework.uuid" + +/** + * @brief The sequence id of the remote event. The type of the value for this event property is Long. + * + * It is set by the event admin, and only readable for user. Local event does not contain this property. + * + * It is used to filter out duplicate events in the event admin. + */ +#define CELIX_EVENT_REMOTE_SEQ_ID "celix.event.remote.seqId" + //end event constants #ifdef __cplusplus diff --git a/bundles/event_admin/event_admin_api/include/celix_event_handler_service.h b/bundles/event_admin/event_admin_api/include/celix_event_handler_service.h index 6559b909f..8c51250c0 100644 --- a/bundles/event_admin/event_admin_api/include/celix_event_handler_service.h +++ b/bundles/event_admin/event_admin_api/include/celix_event_handler_service.h @@ -58,7 +58,7 @@ typedef struct celix_event_handler_service { * @see CELIX_EVENT_TOPIC, CELIX_EVENT_FILTER, CELIX_EVENT_DELIVERY */ celix_status_t (*handleEvent)(void* handle, const char* topic, const celix_properties_t* properties); -}celix_event_handler_service_t; +} celix_event_handler_service_t; #ifdef __cplusplus } diff --git a/bundles/event_admin/event_admin_spi/CMakeLists.txt b/bundles/event_admin/event_admin_spi/CMakeLists.txt new file mode 100644 index 000000000..dd5ff8bbb --- /dev/null +++ b/bundles/event_admin/event_admin_spi/CMakeLists.txt @@ -0,0 +1,30 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +add_library(event_admin_spi INTERFACE) +target_include_directories(event_admin_spi INTERFACE + $ +) + +target_link_libraries(event_admin_spi INTERFACE Celix::utils) + +install(TARGETS event_admin_spi EXPORT celix DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT event_admin + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/event_admin) +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/event_admin COMPONENT event_admin) + +#Setup target aliases to match external usage +add_library(Celix::event_admin_spi ALIAS event_admin_spi) diff --git a/bundles/event_admin/event_admin_spi/include/celix_event_remote_provider_service.h b/bundles/event_admin/event_admin_spi/include/celix_event_remote_provider_service.h new file mode 100644 index 000000000..3857615a1 --- /dev/null +++ b/bundles/event_admin/event_admin_spi/include/celix_event_remote_provider_service.h @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EVENT_REMOTE_PROVIDER_SERVICE_H +#define CELIX_EVENT_REMOTE_PROVIDER_SERVICE_H +#ifdef __cplusplus +extern "C" { +#endif +#include "celix_properties.h" +#include "celix_errno.h" + +/** + * @brief The Event Remote Provider service name + */ +#define CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME "celix_event_remote_provider_service" + +/** + * @brief The Event Remote Provider service version + */ +#define CELIX_EVENT_REMOTE_PROVIDER_SERVICE_VERSION "1.0.0" +#define CELIX_EVENT_REMOTE_PROVIDER_SERVICE_USE_RANGE "[1.0.0,2)" + +typedef struct celix_event_remote_provider_service { + void* handle; + /** + * @brief Asynchronous event delivery . This method returns to the caller before delivery of the event is completed. + * @param[in] handle The handle as provided by the service registration. + * @param[in] topic The topic of the event. + * @param[in] properties The properties of the event. It can be NULL. + * @return Status code indicating failure or success. CELIX_SUCCESS if no errors are encountered. If an error is encountered, it should be return celix errno. + */ + celix_status_t (*postEvent)(void* handle, const char* topic, const celix_properties_t* properties); + /** + * @brief Synchronous event delivery. This method does not return to the caller until delivery of the event is completed. + * @param[in] handle The handle as provided by the service registration. + * @param[in] topic The topic of the event. + * @param[in] properties The properties of the event. It can be NULL. + * @return Status code indicating failure or success. CELIX_SUCCESS if no errors are encountered. If an error is encountered, it should be return celix errno. + */ + celix_status_t (*sendEvent)(void* handle, const char* topic, const celix_properties_t* properties); +} celix_event_remote_provider_service_t; + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EVENT_REMOTE_PROVIDER_SERVICE_H diff --git a/bundles/event_admin/examples/CMakeLists.txt b/bundles/event_admin/examples/CMakeLists.txt index 4e7776ec5..3672fb76d 100644 --- a/bundles/event_admin/examples/CMakeLists.txt +++ b/bundles/event_admin/examples/CMakeLists.txt @@ -33,6 +33,37 @@ if (EVENT_ADMIN_EXAMPLES) event_handler_example USE_CONFIG ) + + if (TARGET Celix::rsa_discovery_zeroconf)#Celix::rsa_discovery_zeroconf only available in linux + add_celix_container(remote_event_admin_mqtt_publisher + NAME "publisher" + GROUP "event_admin/mqtt" + BUNDLES + Celix::shell + Celix::shell_tui + Celix::log_admin + Celix::event_admin + event_publisher_example + Celix::rsa_discovery_zeroconf + Celix::event_admin_remote_provider_mqtt + PROPERTIES + CELIX_EARPM_BROKER_PROFILE=${CMAKE_CURRENT_SOURCE_DIR}/res/mosquitto.conf + ) + + add_celix_container(remote_event_admin_mqtt_subscriber + NAME "subscriber" + GROUP "event_admin/mqtt" + BUNDLES + Celix::shell + Celix::shell_tui + Celix::log_admin + Celix::event_admin + event_handler_example + Celix::rsa_discovery_zeroconf + Celix::event_admin_remote_provider_mqtt + ) + endif () + endif (EVENT_ADMIN_EXAMPLES) diff --git a/bundles/event_admin/examples/event_publisher/src/celix_example_event_publisher_activator.c b/bundles/event_admin/examples/event_publisher/src/celix_example_event_publisher_activator.c index c1ba82b0d..173006bb3 100644 --- a/bundles/event_admin/examples/event_publisher/src/celix_example_event_publisher_activator.c +++ b/bundles/event_admin/examples/event_publisher/src/celix_example_event_publisher_activator.c @@ -18,6 +18,7 @@ */ #include #include +#include #include "celix_event_admin_service.h" #include "celix_event_constants.h" @@ -52,6 +53,22 @@ static void *celix_eventPublisherExampleActivator_sendEventThread(void *handle) celix_properties_set(props, "example", "data"); svc->postEvent(svc->handle, "example/asyncEvent", props); } + { + printf("Sending remote enable sync event\n"); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "example/remoteSyncEvent"); + celix_properties_setBool(props, CELIX_EVENT_REMOTE_ENABLE, true); + celix_properties_set(props, "example", "data"); + svc->sendEvent(svc->handle, "example/remoteSyncEvent", props); + } + { + printf("Sending remote enable async event\n"); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "example/remoteAsyncEvent"); + celix_properties_setBool(props, CELIX_EVENT_REMOTE_ENABLE, true); + celix_properties_set(props, "example", "data"); + svc->postEvent(svc->handle, "example/remoteAsyncEvent", props); + } } celixThreadRwlock_unlock(&act->svcLock); sleep(3); diff --git a/bundles/event_admin/examples/res/mosquitto.conf b/bundles/event_admin/examples/res/mosquitto.conf new file mode 100644 index 000000000..2f6c71c7f --- /dev/null +++ b/bundles/event_admin/examples/res/mosquitto.conf @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +allow_anonymous true +listener 1883 +socket_domain ipv4 diff --git a/bundles/event_admin/remote_provider/CMakeLists.txt b/bundles/event_admin/remote_provider/CMakeLists.txt new file mode 100644 index 000000000..5939f1308 --- /dev/null +++ b/bundles/event_admin/remote_provider/CMakeLists.txt @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +add_subdirectory(remote_provider_mqtt) \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/README.md b/bundles/event_admin/remote_provider/README.md new file mode 100644 index 000000000..fa2a8fbb9 --- /dev/null +++ b/bundles/event_admin/remote_provider/README.md @@ -0,0 +1,39 @@ +--- +title: Remote Providers for Event Admin +--- + + + +## Introduction + +The remote provider of Event Admin can deliver events to remote frameworks. To extend new remote communication approaches +through a new remote provider, the remote provider should be implemented as a Celix bundle, and provide the `celix_event_remote_provider_service_t` service. + +## The Relationship Between Remote Provider And Event Admin + +The `celix_event_remote_provider_service_t` service provides asynchronous event publishing method and synchronous event +publishing method, which corresponds to the `celix_event_admin_service_t` service. When the Event Admin receives an event +that needs to be published to a remote framework, it forwards the event to the remote framework by calling the `celix_event_remote_provider_service_t` +service. Similarly, when the remote provider receives a remote event, it publishes the event to the local framework by +calling the `celix_event_admin_service_t` service. The component relationship diagram is as follows. + +![event_admin_remote_provider_component_diagram](diagrams/event_admin_remote_provider_component.png) + +## Remote Provider Bundles + +* [Event Admin Remote Provider Based On MQTT](remote_provider_mqtt/README.md) - The remote provider based on MQTT. \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.png b/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.png new file mode 100644 index 000000000..ea46a4079 Binary files /dev/null and b/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.png differ diff --git a/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.puml b/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.puml new file mode 100644 index 000000000..8566352f6 --- /dev/null +++ b/bundles/event_admin/remote_provider/diagrams/event_admin_remote_provider_component.puml @@ -0,0 +1,49 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/component-diagram + + +frame "Framework of publisher" { + ()EventAdminService1 as "Celix Event Admin Service" + ()RemoteProviderService1 as "Celix Event Remote Provider Service" + [Publisher] + EventAdmin1 as [Event Admin] + RemoteProvider1 as [Remote Provider] + + + Publisher -up-( EventAdminService1 + EventAdmin1 -up- EventAdminService1 + EventAdmin1 -up-( RemoteProviderService1 + RemoteProvider1 -up- RemoteProviderService1 +} + +frame "Framework of subscriber" { + ()EventAdminService2 as "Celix Event Admin Service" + ()EventHandlerService as "Celix Event Handler Service" + RemoteProvider2 as [Remote Provider] + EventAdmin2 as [Event Admin] + [Subscriber] + + RemoteProvider2 -down-( EventAdminService2 + EventAdmin2 -down- EventAdminService2 + EventAdmin2 -down-( EventHandlerService + Subscriber -down- EventHandlerService +} + +RemoteProvider1 <.down.> RemoteProvider2:IPC or Network + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/CMakeLists.txt b/bundles/event_admin/remote_provider/remote_provider_mqtt/CMakeLists.txt new file mode 100644 index 000000000..8c791ad1d --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/CMakeLists.txt @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +celix_subproject(EVENT_ADMIN_REMOTE_PROVIDER_MQTT "Option to enable building the event admin remote provider bundle which base on mqtt" OFF) +if (EVENT_ADMIN_REMOTE_PROVIDER_MQTT) + + find_package(mosquitto REQUIRED) + find_package(jansson REQUIRED) + find_package(libuuid REQUIRED) + + set(EARPM_SRC + src/celix_earpm_activator.c + src/celix_earpm_impl.c + src/celix_earpm_client.c + src/celix_earpm_event_deliverer.c + src/celix_earpm_broker_discovery.c + ) + + set(EARPM_DEPS + Celix::event_admin_api + Celix::event_admin_spi + Celix::shell_api + Celix::c_rsa_spi + Celix::rsa_common + Celix::log_helper + Celix::framework + Celix::utils + mosquitto::libmosquitto + jansson::jansson + libuuid::libuuid + ) + + add_celix_bundle(event_admin_remote_provider_mqtt + SYMBOLIC_NAME "apache_celix_event_remote_provider_mqtt" + VERSION "1.0.0" + NAME "Apache Celix Event Remote Provider Base On MQTT" + GROUP "Celix/event_admin" + FILENAME celix_event_admin_remote_provider_mqtt + SOURCES + ${EARPM_SRC} + ) + + target_link_libraries(event_admin_remote_provider_mqtt PRIVATE ${EARPM_DEPS}) + + target_include_directories(event_admin_remote_provider_mqtt PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/private) + + install_celix_bundle(event_admin_remote_provider_mqtt EXPORT celix COMPONENT event_admin) + + #Setup target aliases to match external usage + add_library(Celix::event_admin_remote_provider_mqtt ALIAS event_admin_remote_provider_mqtt) + + if (ENABLE_TESTING) + add_library(event_admin_remote_provider_mqtt_cut STATIC ${EARPM_SRC}) + target_include_directories(event_admin_remote_provider_mqtt_cut PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src ${CMAKE_CURRENT_LIST_DIR}/private) + target_link_libraries(event_admin_remote_provider_mqtt_cut PUBLIC ${EARPM_DEPS}) + add_subdirectory(gtest) + endif(ENABLE_TESTING) + +endif () diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/README.md b/bundles/event_admin/remote_provider/remote_provider_mqtt/README.md new file mode 100644 index 000000000..e34a82e37 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/README.md @@ -0,0 +1,127 @@ +--- +title: Event Admin Remote Provider Based On MQTT +--- + + + +## Event Admin Remote Provider Based On MQTT + +The remote provider based on MQTT is a remote provider for the event admin. It uses the MQTT protocol to deliver events to remote celix framework instances, and it is implemented based on the [mosquitto library](https://github.com/eclipse/mosquitto). In addition, the remote provider does not need to be configured with the mqtt broker address, it uses the service discovery to discover the broker server. + +### Supported Platform +- Linux +- MacOS + +### Properties/Configuration + +| **Properties** | **Type** | **Description** | **Default value** | +|-----------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------| +| CELIX_EARPM_BROKER_PROFILE | string | The MQTT broker profile to use. | /etc/mosquitto.conf | +| CELIX_EARPM_EVENT_DEFAULT_QOS | long | The default QoS of the remote event. | 0 (At most once) | +| CELIX_EARPM_MSG_QUEUE_CAPACITY | long | The capacity of the message cache queue. The maximum size is 2048. | 256 | +| CELIX_EARPM_PARALLEL_MSG_CAPACITY | long | The capacity of the parallel message. The maximum size is CELIX_EARPM_PARALLEL_MSG_CAPACITY. | 20 | +| CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS | long | The number of threads used to deliver the synchronous event. The maximum size is 20. | 5 | +| CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD | long | The threshold for the number of consecutive synchronous events that have not been acknowledged. If the threshold is reached, the event remote provider will not wait for the acknowledgment, until receiving a new message from the remote celix framework instance. | 10 | + +### Conan Option + build_event_admin_remote_provider_mqtt=True Default is False + +### CMake Option + EVENT_ADMIN_REMOTE_PROVIDER_MQTT=ON Default is OFF + +### Software Design + +#### The Relationship Between Event Admin Remote Provider MQTT And Other Components + +If you need to deliver events to remote celix framework instances, multiple different components need to work together, as shown in the diagram below: + +![event_admin_remote_provider_mqtt_component_diagram](diagrams/remote_provider_mqtt_component.png) + +In the diagram above, the event admin is responsible for publishing and subscribing to events. The remote provider based on MQTT is responsible for delivering events to remote frameworks. The MQTT broker is responsible for routing messages that from the remote provider based on MQTT. The service discovery is responsible for discovering the MQTT broker server, and it reuses the service discovery of celix remote service subproject. + +#### The MQTT Broker Server Discovery + +When `event_admin_remote_provider_mqtt` is started, it will try to load the MQTT broker profile, read the MQTT broker address information from the profile, and then announce the MQTT broker address information by the service discovery. On hosts without a mqtt broker profile, `event_admin_remote_provider_mqtt` will discover the MQTT broker server by the service discovery. The sequence diagram is as follows: + +![MQTT_broker_discovery_sequence_diagram](diagrams/mqtt_broker_discovery.png) + + +#### The Subscription Process Of Event Admin Remote Provider MQTT + +To obtain event handler information, an event handler service tracker will be created when `event_admin_remote_provider_mqtt` is initialized. Once an event handler service is registered, `event_admin_remote_provider_mqtt` will subscribe to the corresponding event topic on the mqtt broker, and send the event handler information to remote celix framework instances with the control message (`celix/EventAdminMqtt/HandlerInfo/add`). There are two purposes for sending event handler information to a remote celix framework instance: + +- Before the event message is forwarded to other celix framework instances, it can be filtered according to the existing remote event handler information to avoid unnecessary forwarding; +- `event_admin_remote_provider_mqtt` can use the remote event handler information to determine whether to wait for the response from the corresponding celix framework instance, when it forwards the synchronous event message. + +The sequence diagram is as follows: + +![subscribe_event_seq.png](diagrams/subscribe_event_seq.png) + +#### The Publishing Process Of Event Admin Remote Provider MQTT + +Event publishing includes asynchronous event publishing and synchronous event publishing. The steps of asynchronous event publishing are as follows: +- Serialize event properties to a JSON format string; +- Convert the event to an MQTT message, where the event topic is the MQTT message topic, and the event property is the MQTT message payload; +- Publish the corresponding MQTT message to the MQTT broker, and then MQTT broker forwards the message to the remote celix framework instance. + +The steps of synchronous event publishing are as follows: +- Serialize event properties to a JSON format string; +- Convert the event to an MQTT message, where the event topic is the MQTT message topic, the event property is the MQTT message payload, and the response message topic and sequence number are added to the MQTT message properties; +- Publish the corresponding MQTT message to the MQTT broker, and then MQTT broker forwards the message to the remote celix framework instance; +- Wait for response messages from all remote celix framework instances. + +The sequence diagram is as follows: + +![publish_event_seq.png](diagrams/publish_event_seq.png) + + +#### Exception Handling + +##### QOS Mechanism + +To ensure the reliability of messages, `event_admin_remote_provider_mqtt` reuses the [MQTT QOS mechanism](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901234). The QOS value includes three levels: 0, 1, and 2, which represent at most once, at least once, and exactly once, respectively. The default QOS value used by `event_admin_remote_provider_mqtt` is 0, and users can modify the default QOS value by the configuration property `CELIX_EARPM_EVENT_DEFAULT_QOS`. The event publisher can also specify a specific QOS value for the event with the event property `celix.event.remote.qos`. For QOS, events have the following features: +- When the network is disconnected, events with QOS 0 will be discarded directly, while events with QOS 1 and 2 will be retransmitted after the network is restored. (It is guaranteed by mosquitto) +- Ordered events with the same QOS value will be received in order, while events with different QOS values may be received in any order. + +##### Timeout Mechanism + +To avoid messages being waited forever, `event_admin_remote_provider_mqtt` provides a timeout mechanism. The timeout mechanism is designed as follows: + +- For synchronous event messages, the timeout value can be specified by the event property `celix.event.remote.expiryInterval`. If the event does not receive response messages from all remote frameworks within the specified time, the `sendEvent` interface will terminate the waiting and return a timeout error. If the event property is not set, the default timeout time(300 seconds) will be used. +- For asynchronous event messages, the timeout value can also be specified by the property `celix.event.remote.expiryInterval`. This property is equivalent to the [Message Expiry Interval in MQTT](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901112). + +##### BackPressure Mechanism + +When messages are generated faster than they are consumed, the message queue becomes congested and may cause the system to run out of memory. To solve this problem, `event_admin_remote_provider_mqtt` provides a backpressure processing mechanism. The mechanism is designed as follows: + +- The capacity of the message queue in `event_admin_remote_provider_mqtt` can be configured, and its default value is 256. +- Messages are classified into three types: user event messages, event response messages, and internal control messages, and the corresponding message priorities are high, medium, and low. Low-priority messages are rejected when the number of messages in the message queue exceeds 70% of the message queue capacity; medium-priority messages are rejected when the number of messages in the message queue exceeds 85% of the message queue capacity; and high-priority messages are rejected when the number of messages in the message queue exceeds 100% of the message queue capacity. +- For synchronous event messages, when the available capacity of the message queue is insufficient, if the event QOS value is greater than 0, the message will be sent after the message queue is available; if the QOS value is 0, the message will be directly discarded. + +##### Reconnect Mechanism + +When the MQTT connection is disconnected, `event_admin_remote_provider_mqtt` will first check whether there is an available MQTT broker address. If there is, it will try to reconnect to the MQTT broker. If the connection fails, it will reconnect at a linearly increasing time interval of 1 second (maximum interval value is 30 seconds) until the connection is successful. Once the connection is successful, `event_admin_remote_provider_mqtt` will resubscribe to the event topic and continue to transmit messages in the message queue; at the same time, to ensure the consistency of handler information, it will resend the local handler information to the remote celix framework instances and re-request the handler information of the remote celix framework instance. The sequence diagram is as follows: + +![reconnect_sequence_diagram](diagrams/reconnect_seq.png) + +### Example + +See the cmake target `remote_event_admin_mqtt_publisher` and `remote_event_admin_mqtt_subscriber` in the `event_admin/examples` directory. + +Note: Before running the example, make sure the `mosquitto broker` and the `mdnsd` are running. You can get `mosquitto` from [here](https://github.com/eclipse/mosquitto) and `mdnsd` from [here](https://github.com/apple-oss-distributions/mDNSResponder). And you should use command `mosquitto -c ` to start the `mosquitto broker`. The profile of the `mosquitto broker` can be got from [here](../../examples/res/mosquitto.conf). + diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.png b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.png new file mode 100644 index 000000000..1d5fb4c1c Binary files /dev/null and b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.png differ diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.puml b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.puml new file mode 100644 index 000000000..db9f7a491 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/mqtt_broker_discovery.puml @@ -0,0 +1,49 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/sequence-diagram + +!pragma teoz true +box Host1 #EEEBDC +box FrameworkA + participant "Remote Provider MQTT" as RemoteProviderMQTT1 + participant "Service Discovery" as ServiceDiscovery1 +end box + participant "MQTT Broker Profile" as MqttBrokerProfile + participant "MQTT Broker" as MqttBroker + + == Initialization == + MqttBroker -> MqttBrokerProfile : Load MQTT broker profile + RemoteProviderMQTT1 -> MqttBrokerProfile : Try load MQTT broker profile + == MQTT broker server discovery == + alt Profile loaded + RemoteProviderMQTT1 -> ServiceDiscovery1 : Announce MQTT broker server\n endpoint description + RemoteProviderMQTT1 -> MqttBroker : Connect to MQTT broker + end alt +end box + +box Host2 #EEEBDC + + box FrameworkB + participant "Service Discovery" as ServiceDiscovery2 + participant "Remote Provider MQTT" as RemoteProviderMQTT2 + ServiceDiscovery1 -> ServiceDiscovery2 :Announce MQTT broker server endpoint description to remote + ServiceDiscovery2 -> RemoteProviderMQTT2 : Add MQTT broker server\n endpoint description + RemoteProviderMQTT2 -> MqttBroker : Connect to MQTT broker + end box + +end box +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.png b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.png new file mode 100644 index 000000000..751464a02 Binary files /dev/null and b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.png differ diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.puml b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.puml new file mode 100644 index 000000000..be0d6d6db --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/publish_event_seq.puml @@ -0,0 +1,60 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/sequence-diagram + +box FrameworkA +participant "Event Admin" as EventAdmin1 +participant "Remote Provider MQTT" as RemoteProviderMQTT1 +end box + +participant "MQTT Broker" as MqttBroker + +box FrameworkB +participant "Remote Provider MQTT" as RemoteProviderMQTT2 +participant "Event Admin" as EventAdmin2 +end box + +==Initialization== + +RemoteProviderMQTT1 -> MqttBroker: Subscribe control messages +RemoteProviderMQTT1 -> MqttBroker: Use "celix/EventAdminMqtt/HandlerInfo/query"\n message to request all remote handler information + +==Publish event== + +-\EventAdmin1:postEvent +alt It is remote-enable event and has a remote handler + EventAdmin1->RemoteProviderMQTT1:postEvent + RemoteProviderMQTT1->MqttBroker:Publish event + MqttBroker->RemoteProviderMQTT2:Forward event + RemoteProviderMQTT2->EventAdmin2:postEvent + EventAdmin2 -> EventAdmin2:Forward event to local handlers +end alt + +-\EventAdmin1:sendEvent +alt It is remote-enable event and has a remote handler + EventAdmin1->RemoteProviderMQTT1:sendEvent + RemoteProviderMQTT1->MqttBroker:Publish event + MqttBroker->RemoteProviderMQTT2:Forward event to subscriber + RemoteProviderMQTT2->EventAdmin2:sendEvent\n(It is called in thread pool) + EventAdmin2 -> EventAdmin2:Forward event to local handlers + EventAdmin2 --> RemoteProviderMQTT2:Return form sendEvent + RemoteProviderMQTT2 --> MqttBroker: Publish the response message\n of the synchronous event + MqttBroker --> RemoteProviderMQTT1: Forward messages to subscriber + RemoteProviderMQTT1 --> EventAdmin1: Return from sendEvent +end alt + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.png b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.png new file mode 100644 index 000000000..11c3eed78 Binary files /dev/null and b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.png differ diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.puml b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.puml new file mode 100644 index 000000000..ac6b75c5f --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/reconnect_seq.puml @@ -0,0 +1,38 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/sequence-diagram + +participant "Remote Provider MQTT" as RemoteProviderMQTT +participant "MQTT Broker" as MqttBroker + +RemoteProviderMQTT x<-->x MqttBroker : Disconnect + +loop until connected + alt Has the MQTT broker server address + RemoteProviderMQTT -> MqttBroker: Try to connect to the broker at "1s*tries" intervals + else + RemoteProviderMQTT -> RemoteProviderMQTT: Wait for the MQTT Broker server address + end alt + + alt Connected + RemoteProviderMQTT -> MqttBroker: Resubscribe to the topics + RemoteProviderMQTT -> MqttBroker: Refresh remote handlers information + RemoteProviderMQTT -> MqttBroker: Publish events in the queue + end alt +end + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.png b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.png new file mode 100644 index 000000000..d4587688b Binary files /dev/null and b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.png differ diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.puml b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.puml new file mode 100644 index 000000000..77eba780c --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/remote_provider_mqtt_component.puml @@ -0,0 +1,64 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/component-diagram + + +frame "Framework of publisher" { + ()EventAdminService1 as "Celix Event Admin Service" + ()RemoteProviderService1 as "Celix Event Remote Provider Service" + ()EndpointListenerService1 as "Celix Endpoint Listener Service" + [Publisher] + EventAdmin1 as [Event Admin] + RemoteProvider1 as [Remote Provider MQTT] + ServiceDiscovery1 as [Service Discovery] + + + Publisher -up-( EventAdminService1 + EventAdmin1 -up- EventAdminService1 + EventAdmin1 -up-( RemoteProviderService1 + RemoteProvider1 -up- RemoteProviderService1 + RemoteProvider1 -up- EndpointListenerService1 + ServiceDiscovery1 -up-( EndpointListenerService1 +} + +frame "Framework of subscriber" { + ()EventAdminService2 as "Celix Event Admin Service" + ()EventHandlerService as "Celix Event Handler Service" + ()EndpointListenerService2 as "Celix Endpoint Listener Service" + ServiceDiscovery2 as [Service Discovery] + RemoteProvider2 as [Remote Provider MQTT] + EventAdmin2 as [Event Admin] + [Subscriber] + + RemoteProvider2 -down- EndpointListenerService2 + ServiceDiscovery2 -down-( EndpointListenerService2 + RemoteProvider2 -down-( EventAdminService2 + EventAdmin2 -down- EventAdminService2 + EventAdmin2 -down-( EventHandlerService + Subscriber -down- EventHandlerService +} + +MqttBroker as [MQTT BROKER] + +RemoteProvider1 <.down.> MqttBroker + +RemoteProvider2 <.up.> MqttBroker + +ServiceDiscovery1 <..> ServiceDiscovery2 + + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.png b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.png new file mode 100644 index 000000000..6157fb388 Binary files /dev/null and b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.png differ diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.puml b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.puml new file mode 100644 index 000000000..866123c06 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/diagrams/subscribe_event_seq.puml @@ -0,0 +1,30 @@ +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@startuml +'https://plantuml.com/sequence-diagram + +participant "Remote Provider MQTT" as RemoteProviderMQTT +participant "MQTT Broker" as MqttBroker + +==Initialization== +RemoteProviderMQTT -> RemoteProviderMQTT: Create an event handler service tracker +==Subscribe event== +alt Event handler service registered + RemoteProviderMQTT -> MqttBroker: Subscribe event + RemoteProviderMQTT -> MqttBroker: publish "celix/EventAdminMqtt/HandlerInfo/add" message +end alt + +@enduml \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/CMakeLists.txt b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/CMakeLists.txt new file mode 100644 index 000000000..0c9fed666 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/CMakeLists.txt @@ -0,0 +1,104 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +####integration test +find_package(mosquitto REQUIRED) +add_executable(integration_test_event_admin_remote_provider_mqtt + src/CelixEarpmIntegrationTestSuite.cc + ) + +target_link_libraries(integration_test_event_admin_remote_provider_mqtt PRIVATE + Celix::event_admin_api + Celix::framework + mosquitto::libmosquitto + GTest::gtest + GTest::gtest_main + ) + +target_include_directories(integration_test_event_admin_remote_provider_mqtt PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../src) + +celix_target_bundle_set_definition(integration_test_event_admin_remote_provider_mqtt NAME INTEGRATED_BUNDLES + Celix::event_admin + Celix::event_admin_remote_provider_mqtt +) + +target_compile_definitions(integration_test_event_admin_remote_provider_mqtt PRIVATE -DMOSQUITTO_CONF="${CMAKE_CURRENT_LIST_DIR}/res/mosquitto.conf") + +add_test(NAME run_integration_test_event_admin_remote_provider_mqtt COMMAND integration_test_event_admin_remote_provider_mqtt) +setup_target_for_coverage(integration_test_event_admin_remote_provider_mqtt SCAN_DIR ..) + +####unit test +add_executable(unit_test_event_admin_remote_provider_mqtt + src/CelixEarpmImplTestSuite.cc + src/CelixEarpmEventDelivererTestSuite.cc + src/CelixEarpmClientTestSuite.cc + src/CelixEarpmBrokerDiscoveryTestSuite.cc + src/CelixEarpmActivatorTestSuite.cc +) + +target_link_libraries(unit_test_event_admin_remote_provider_mqtt PRIVATE + event_admin_remote_provider_mqtt_cut + Celix::framework + GTest::gtest + GTest::gtest_main +) + +target_include_directories(unit_test_event_admin_remote_provider_mqtt PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src) + +target_compile_definitions(unit_test_event_admin_remote_provider_mqtt PRIVATE -DMOSQUITTO_CONF_PATH="${CMAKE_CURRENT_LIST_DIR}/res/") + +add_test(NAME run_unit_test_event_admin_remote_provider_mqtt COMMAND unit_test_event_admin_remote_provider_mqtt) +setup_target_for_coverage(unit_test_event_admin_remote_provider_mqtt SCAN_DIR ..) + +if (EI_TESTS) + ####unit test with error injection + add_executable(unit_test_event_admin_remote_provider_mqtt_with_error_injection + src/CelixEarpmActivatorErrorInjectionTestSuite.cc + src/CelixEarpmBrokerDiscoveryErrorInjectionTestSuite.cc + src/CelixEarpmEventDelivererErrorInjectionTestSuite.cc + src/CelixEarpmClientErrorInjectionTestSuite.cc + src/CelixEarpmImplErrorInjectionTestSuite.cc + ) + + target_link_libraries(unit_test_event_admin_remote_provider_mqtt_with_error_injection PRIVATE + event_admin_remote_provider_mqtt_cut + Celix::framework + Celix::threads_ei + Celix::bundle_ctx_ei + Celix::string_hash_map_ei + Celix::long_hash_map_ei + Celix::array_list_ei + Celix::properties_ei + Celix::utils_ei + Celix::dm_component_ei + Celix::log_helper_ei + Celix::malloc_ei + Celix::filter_ei + Celix::mosquitto_ei + Celix::asprintf_ei + Celix::jansson_ei + GTest::gtest + GTest::gtest_main + ) + + target_include_directories(unit_test_event_admin_remote_provider_mqtt_with_error_injection PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src) + + target_compile_definitions(unit_test_event_admin_remote_provider_mqtt_with_error_injection PRIVATE -DMOSQUITTO_CONF_PATH="${CMAKE_CURRENT_LIST_DIR}/res/") + + add_test(NAME run_unit_test_event_admin_remote_provider_mqtt_with_error_injection COMMAND unit_test_event_admin_remote_provider_mqtt_with_error_injection) + setup_target_for_coverage(unit_test_event_admin_remote_provider_mqtt_with_error_injection SCAN_DIR ..) +endif () \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto.conf new file mode 100644 index 000000000..4d49c1660 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto.conf @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +allow_anonymous true +listener 1883 diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_all_if.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_all_if.conf new file mode 100644 index 000000000..f1e918634 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_all_if.conf @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host.conf new file mode 100644 index 000000000..a54c2c44e --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host.conf @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 127.0.0.1 \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host_and_if.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host_and_if.conf new file mode 100644 index 000000000..a0f946bd6 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host_and_if.conf @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 127.0.0.1 +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if.conf new file mode 100644 index 000000000..955366e34 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if.conf @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if_val_lost.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if_val_lost.conf new file mode 100644 index 000000000..4d4b0304d --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if_val_lost.conf @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 +bind_interface \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_unix_socket.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_unix_socket.conf new file mode 100644 index 000000000..d4e3bf4dc --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_unix_socket.conf @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 0 /var/mosquitto.sock \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_invalid_listener.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_invalid_listener.conf new file mode 100644 index 000000000..09098e2a7 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_invalid_listener.conf @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#Not specifying port and host +listener + +#Not specifying port +listener 127.0.0.1 + +#Not specifying unix socket path(Port 0 indicates unix socket) +listener 0 + +#Not specifying listener +bind_interface lo +socket_domain ipv4 diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv4.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv4.conf new file mode 100644 index 000000000..bf231c0ad --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv4.conf @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 +socket_domain ipv4 +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv6.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv6.conf new file mode 100644 index 000000000..20cebca48 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv6.conf @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 +socket_domain ipv6 +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_multi_listener.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_multi_listener.conf new file mode 100644 index 000000000..285cc76e8 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_multi_listener.conf @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#first listener +listener 1883 +socket_domain ipv4 +bind_interface lo + + +#second listener +listener 1884 +socket_domain ipv4 +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_socket_domain_val_lost.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_socket_domain_val_lost.conf new file mode 100644 index 000000000..5d486705e --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_socket_domain_val_lost.conf @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +listener 1883 +socket_domain +bind_interface lo \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_start_with_space.conf b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_start_with_space.conf new file mode 100644 index 000000000..0415eab00 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_start_with_space.conf @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + listener 1883 127.0.0.1 \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorErrorInjectionTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorErrorInjectionTestSuite.cc new file mode 100644 index 000000000..229580628 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorErrorInjectionTestSuite.cc @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "celix_bundle_activator.h" +#include "celix_event_admin_service.h" +#include "celix_dm_component_ei.h" +#include "celix_bundle_context_ei.h" +#include "celix_properties_ei.h" +#include "malloc_ei.h" +#include "celix_earpm_impl.h" +#include "celix_earpm_broker_discovery.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +class CelixEarpmActErrorInjectionTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmActErrorInjectionTestSuite() : CelixEarpmTestSuiteBaseClass{".earpm_act_ej_test_cache"}{} + + ~CelixEarpmActErrorInjectionTestSuite() override { + celix_ei_expect_celix_dmComponent_create(nullptr, 0, nullptr); + celix_ei_expect_celix_dmServiceDependency_create(nullptr, 0, nullptr); + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_celix_bundleContext_getDependencyManager(nullptr, 0, nullptr); + celix_ei_expect_celix_dmServiceDependency_setService(nullptr, 0, 0); + celix_ei_expect_celix_dmComponent_addServiceDependency(nullptr, 0, 0); + celix_ei_expect_celix_dmComponent_addInterface(nullptr, 0, 0); + celix_ei_expect_celix_dependencyManager_addAsync(nullptr, 0, 0); + celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_set(nullptr, 0, 0); + celix_ei_expect_celix_properties_setBool(nullptr, 0, 0); + } + + void TestEarpmActivator(void (testBody)(void *act, celix_bundle_context_t *ctx)) { + void *act{}; + auto status = celix_bundleActivator_create(ctx.get(), &act); + ASSERT_EQ(CELIX_SUCCESS, status); + + testBody(act, ctx.get()); + + status = celix_bundleActivator_destroy(act, ctx.get()); + ASSERT_EQ(CELIX_SUCCESS, status); + } +}; + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEarpmDiscoveryComponentTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_create((void*)&celix_bundleActivator_start, 1, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEarpmDiscoveryTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_calloc((void*)&celix_earpmDiscovery_create, 0, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEndpointListenerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetServiceForEndpointListenerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEndpointListenerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEarpmComponentTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_create((void*)&celix_bundleActivator_start, 1, nullptr, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEarpmTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_calloc((void*)&celix_earpm_create, 0, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEventHandlerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetServiceForEventHandlerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEventHandlerDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEventAdminDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_create((void*)&celix_bundleActivator_start, 1, nullptr, 3); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetServiceForEventAdminDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmServiceDependency_setService((void*)&celix_bundleActivator_start, 1, ENOMEM, 3); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEventAdminDependencyTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addServiceDependency((void*)&celix_bundleActivator_start, 1, ENOMEM, 3); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEventRemoteProviderServiceTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addInterface((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateEndpointListenerServiceProperiesTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_properties_create((void*)&celix_bundleActivator_start, 1, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetEndpointListenerServiceScopeTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_properties_set((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetEndpointListenerServiceInterfaceSpecificEndpointsOptionTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_properties_setBool((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEndpointListenerServiceTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addInterface((void*)&celix_bundleActivator_start, 1, ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToCreateCommandServiceProperiesTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_properties_create((void*)&celix_bundleActivator_start, 1, nullptr, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToSetCommandNameTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_properties_set((void*)&celix_bundleActivator_start, 1, ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddCommandServiceTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dmComponent_addInterface((void*)&celix_bundleActivator_start, 1, ENOMEM, 3); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToGetDependencyManagerTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_bundleContext_getDependencyManager((void*)&celix_bundleActivator_start, 1, nullptr); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEarpmDiscoveryComponentTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dependencyManager_addAsync((void*)&celix_bundleActivator_start, 1, ENOMEM); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmActErrorInjectionTestSuite, FailedToAddEarpmComponentTest) { + TestEarpmActivator([](void *act, celix_bundle_context_t *ctx) { + celix_ei_expect_celix_dependencyManager_addAsync((void*)&celix_bundleActivator_start, 1, ENOMEM, 2); + auto status = celix_bundleActivator_start(act, ctx); + ASSERT_EQ(ENOMEM, status); + }); +} + diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorTestSuite.cc new file mode 100644 index 000000000..a70721ddd --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmActivatorTestSuite.cc @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_bundle_activator.h" +#include "endpoint_listener.h" +#include "celix_event_remote_provider_service.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +class CelixEarpmActTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmActTestSuite() : CelixEarpmTestSuiteBaseClass{".earpm_act_test_cache"}{ } + + ~CelixEarpmActTestSuite() override = default; +}; + +TEST_F(CelixEarpmActTestSuite, ActivatorStartTest) { + void *act{}; + auto status = celix_bundleActivator_create(ctx.get(), &act); + ASSERT_EQ(CELIX_SUCCESS, status); + status = celix_bundleActivator_start(act, ctx.get()); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_bundleContext_waitForEvents(ctx.get()); + long svcId = celix_bundleContext_findService(ctx.get(), CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME); + EXPECT_TRUE(svcId >= 0); + + svcId = celix_bundleContext_findService(ctx.get(), CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME); + EXPECT_TRUE(svcId >= 0); + + status = celix_bundleActivator_stop(act, ctx.get()); + ASSERT_EQ(CELIX_SUCCESS, status); + status = celix_bundleActivator_destroy(act, ctx.get()); + ASSERT_EQ(CELIX_SUCCESS, status); +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryErrorInjectionTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryErrorInjectionTestSuite.cc new file mode 100644 index 000000000..9489b1275 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryErrorInjectionTestSuite.cc @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include + +extern "C" { +#include "endpoint_listener.h" +#include "remote_constants.h" +#include "endpoint_description.h" +} +#include "malloc_ei.h" +#include "celix_log_helper_ei.h" +#include "celix_bundle_context_ei.h" +#include "celix_threads_ei.h" +#include "celix_long_hash_map_ei.h" +#include "celix_filter_ei.h" +#include "celix_array_list_ei.h" +#include "celix_utils_ei.h" +#include "celix_properties_ei.h" +#include "celix_earpm_broker_discovery.h" +#include "celix_earpm_constants.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +class CelixEarpmBrokerDiscoveryErrorInjectionTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmBrokerDiscoveryErrorInjectionTestSuite() : CelixEarpmTestSuiteBaseClass{".earpm_broker_discovery_ej_test_cache", "celix_earpm_discovery"} {}; + + ~CelixEarpmBrokerDiscoveryErrorInjectionTestSuite() override { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_celix_logHelper_create(nullptr, 0, nullptr); + celix_ei_expect_celix_bundleContext_getProperty(nullptr, 0, nullptr); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_bundleContext_scheduleEvent(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_put(nullptr, 0, 0); + celix_ei_expect_celix_filter_create(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_add(nullptr, 0, 0); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_create(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_set(nullptr, 0, 0); + }; + + void TestAddingEndpointListener(const std::function& testBody) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + celix_autoptr(celix_properties_t) properties = celix_properties_create(); + char scope[512] = {0}; + snprintf(scope, 512, R"((&(%s=%s)(%s=%s)))", CELIX_FRAMEWORK_SERVICE_NAME, CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME, + CELIX_RSA_SERVICE_IMPORTED_CONFIGS, CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE); + auto status = celix_properties_set(properties, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, scope); + EXPECT_EQ(status, CELIX_SUCCESS); + status = celix_properties_setBool(properties, CELIX_RSA_DISCOVERY_INTERFACE_SPECIFIC_ENDPOINTS_SUPPORT, true); + EXPECT_EQ(status, CELIX_SUCCESS); + status = celix_properties_setLong(properties, CELIX_FRAMEWORK_SERVICE_ID, 123); + EXPECT_EQ(status, CELIX_SUCCESS); + status = celix_properties_set(properties, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME); + EXPECT_EQ(status, CELIX_SUCCESS); + endpoint_listener_t endpointListener; + endpointListener.handle = nullptr; + endpointListener.endpointAdded = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS;}; + endpointListener.endpointRemoved = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS;}; + + testBody(discovery, &endpointListener, properties); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); + } +}; + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateLogHelperTest) { + celix_ei_expect_celix_logHelper_create((void*)&celix_earpmDiscovery_create, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAllocMemoryForDiscoveryTest) { + celix_ei_expect_calloc((void*)&celix_earpmDiscovery_create, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToGetFrameworkUUIDTest) { + celix_ei_expect_celix_bundleContext_getProperty((void*)&celix_earpmDiscovery_create, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateMutexTest) { + celix_ei_expect_celixThreadMutex_create((void*)&celix_earpmDiscovery_create, 0, ENOMEM); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateEndpointListenerMapTest) { + celix_ei_expect_celix_longHashMap_createWithOptions((void*)&celix_earpmDiscovery_create, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToScheduledProfileEventTest) { + celix_ei_expect_celix_bundleContext_scheduleEvent((void*)&celix_earpmDiscovery_create, 0, -1); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_EQ(nullptr, discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateEndpointListenerFilterTest) { + TestAddingEndpointListener([](celix_earpm_broker_discovery_t *discovery, void *endpointListener, + const celix_properties_t *properties) { + celix_ei_expect_celix_filter_create((void *) &celix_earpmDiscovery_addEndpointListener, 0, nullptr); + auto status = celix_earpmDiscovery_addEndpointListener(discovery, &endpointListener, properties); + ASSERT_EQ(status, CELIX_ENOMEM); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAllocMemoryForEndpointListenerEntryTest) { + TestAddingEndpointListener([](celix_earpm_broker_discovery_t *discovery, void *endpointListener, + const celix_properties_t *properties) { + celix_ei_expect_calloc((void *) &celix_earpmDiscovery_addEndpointListener, 0, nullptr); + auto status = celix_earpmDiscovery_addEndpointListener(discovery, &endpointListener, properties); + ASSERT_EQ(status, CELIX_ENOMEM); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAddEndpointListenerEntryToMapTest) { + TestAddingEndpointListener([](celix_earpm_broker_discovery_t *discovery, void *endpointListener, + const celix_properties_t *properties) { + celix_ei_expect_celix_longHashMap_put((void *) &celix_earpmDiscovery_addEndpointListener, 0, ENOMEM); + auto status = celix_earpmDiscovery_addEndpointListener(discovery, &endpointListener, properties); + ASSERT_EQ(status, CELIX_ENOMEM); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateBrokerListenerListTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_arrayList_createWithOptions(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create broker listeners list."); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAllocMemoryForBrokerListenerTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 3);//first calloc for log helper, second for discovery + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToDupBrokerListenerHostNameTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto_bind_host.conf", 1); + celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 2);//first for log helper + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAddBrokerListenerToListTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_arrayList_add(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to add broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToDupBrokerListenerBindInterfaceTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto_bind_if.conf", 1); + celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 2);//first for log helper + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to dup bind interface"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateBrokerEndpointListTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_arrayList_createWithOptions(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 2); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create broker endpoints list."); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreatePropertiesForBrokerEndpointTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_properties_create(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create properties for mqtt broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToSetPropertiesValForBrokerEndpointTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_properties_set(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to set properties for mqtt broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToCreateBrokerEndpointTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 4);//first calloc for log helper, second for discovery, third broker listener + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to create endpoint for mqtt broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryErrorInjectionTestSuite, FailedToAddBrokerEndpointToListTest) { + setenv(CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF_PATH"mosquitto.conf", 1); + celix_ei_expect_celix_arrayList_add(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM, 2); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(nullptr, discovery); + + auto ok = WaitForLogMessage("Failed to add endpoint for mqtt broker listener"); + ASSERT_TRUE(ok); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryTestSuite.cc new file mode 100644 index 000000000..3d01aecea --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmBrokerDiscoveryTestSuite.cc @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include + +#include "endpoint_listener.h" +#include "remote_constants.h" +#include "celix_earpm_broker_discovery.h" +#include "celix_earpm_constants.h" +#include "CelixEarpmTestSuiteBaseClass.h" + +class CelixEarpmBrokerDiscoveryTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmBrokerDiscoveryTestSuite(): CelixEarpmTestSuiteBaseClass{".earpm_broker_discovery_test_cache"} {}; + ~CelixEarpmBrokerDiscoveryTestSuite() override = default; + + static celix_properties_t* CreateBrokerEndpointListenerServiceProperties(void) { + auto properties = celix_properties_create(); + char scope[512] = {0}; + snprintf(scope, 512, R"((&(%s=%s)(%s=%s)))", CELIX_FRAMEWORK_SERVICE_NAME, CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME, + CELIX_RSA_SERVICE_IMPORTED_CONFIGS, CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE); + auto status = celix_properties_set(properties, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, scope); + EXPECT_EQ(status, CELIX_SUCCESS); + status = celix_properties_setBool(properties, CELIX_RSA_DISCOVERY_INTERFACE_SPECIFIC_ENDPOINTS_SUPPORT, true); + EXPECT_EQ(status, CELIX_SUCCESS); + return properties; + } + + using update_endpoint_fp = celix_status_t (void *handle, endpoint_description_t *endpoint, char *matchedFilter); + long RegisterBrokerEndpointListenerService(void* handle, update_endpoint_fp endpointAdded, update_endpoint_fp endpointRemoved = nullptr) { + static endpoint_listener_t listener{}; + listener.handle = handle; + listener.endpointAdded = endpointAdded; + listener.endpointRemoved = endpointRemoved != nullptr ? endpointRemoved : + [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS; }; + auto properties = CreateBrokerEndpointListenerServiceProperties(); + return celix_bundleContext_registerService(ctx.get(), &listener, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME, properties); + } + + long TrackEndpointListenerForDiscovery(celix_earpm_broker_discovery_t* discovery) { + celix_service_tracking_options_t opts{}; + opts.filter.serviceName = CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME; + opts.callbackHandle = discovery; + opts.addWithProperties = [](void* handle, void* svc, const celix_properties_t* props) { + celix_earpmDiscovery_addEndpointListener(handle, svc, props); + }; + opts.removeWithProperties = [](void* handle, void* svc, const celix_properties_t* props) { + celix_earpmDiscovery_removeEndpointListener(handle, svc, props); + }; + return celix_bundleContext_trackServicesWithOptions(ctx.get(), &opts); + } + + void ParseBrokerProfileTest(const char* profile, const std::function& checkBrokerInfoFp = nullptr) { + setenv(CELIX_EARPM_BROKER_PROFILE, profile, 1); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + + std::promise endpointPromise; + auto endpointFuture = endpointPromise.get_future(); + std::function checkBrokerInfoWrapper = [&endpointPromise, checkBrokerInfoFp](endpoint_description_t* endpoint) { + EXPECT_STREQ(CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME, endpoint->serviceName); + EXPECT_STREQ(CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE, celix_properties_get(endpoint->properties, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, nullptr)); + if (checkBrokerInfoFp) { + checkBrokerInfoFp(endpoint); + } + try { + endpointPromise.set_value(); + } catch (...) { + // Ignore + } + }; + auto eplId = RegisterBrokerEndpointListenerService(&checkBrokerInfoWrapper, [](void* handle, endpoint_description_t* endpoint, char*) { + auto checkBrokerInfoFunc = static_cast*>(handle); + (*checkBrokerInfoFunc)(endpoint); + return CELIX_SUCCESS; + }); + ASSERT_GE(eplId, 0); + + auto eplTrackerId = TrackEndpointListenerForDiscovery(discovery); + ASSERT_GE(eplTrackerId, 0); + + auto rc = endpointFuture.wait_for(std::chrono::seconds{30}); + EXPECT_EQ(rc, std::future_status::ready); + + celix_bundleContext_stopTracker(ctx.get(), eplTrackerId); + celix_bundleContext_unregisterService(ctx.get(), eplId); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); + } + + void ParseInvalidBrokerProfileTest(const char* profile) { + setenv(CELIX_EARPM_BROKER_PROFILE, profile, 1); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + + auto eplId = RegisterBrokerEndpointListenerService(nullptr, [](void*, endpoint_description_t*, char*) { + ADD_FAILURE() << "Unexpected endpoint added"; + return CELIX_SUCCESS; + }); + ASSERT_GE(eplId, 0); + auto eplTrackerId = TrackEndpointListenerForDiscovery(discovery); + ASSERT_GE(eplTrackerId, 0); + + std::this_thread::sleep_for(std::chrono::milliseconds{10});//let the discovery service process the profile + + celix_bundleContext_stopTracker(ctx.get(), eplTrackerId); + celix_bundleContext_unregisterService(ctx.get(), eplId); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); + } +}; + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, CreateBrokerDiscoveryTest) { + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + celix_earpmDiscovery_destroy(discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindAllInterfaceBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_all_if.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("all", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindSpecificInterfaceBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_if.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindHostBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_host.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 0)); + EXPECT_STREQ("127.0.0.1", celix_properties_get(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, nullptr)); + EXPECT_STREQ("all", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseStartWithSpaceBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_start_with_space.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 0)); + EXPECT_STREQ("127.0.0.1", celix_properties_get(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, nullptr)); + EXPECT_STREQ("all", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindHostAndInterfaceBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_host_and_if.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 0)); + EXPECT_STREQ("127.0.0.1", celix_properties_get(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindIpv4BrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_ipv4.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_EQ(AF_INET, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindIpv6BrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_ipv6.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_EQ(AF_INET6, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseMultilListenerBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_multi_listener.conf", [](endpoint_description_t* endpoint) { + auto port = celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0); + if (port != 1883 && port != 1884) { + FAIL() << "Unexpected broker port " << port; + } + EXPECT_EQ(AF_INET, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseSocketDomainLostedBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_socket_domain_val_lost.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_EQ(-1, celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, -1)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("lo", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindInterfaceLostedBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_if_val_lost.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("all", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseBindUnixSocketBrokerProfileTest) { + ParseBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_bind_unix_socket.conf", [](endpoint_description_t* endpoint) { + EXPECT_EQ(nullptr, celix_properties_get(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, nullptr)); + EXPECT_STREQ("/var/mosquitto.sock", celix_properties_get(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, nullptr)); + }); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, BrokerProfileNotExistedTest) { + ParseInvalidBrokerProfileTest(MOSQUITTO_CONF_PATH"not_existed.conf"); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, ParseInvalidBrokerProfileTest) { + ParseInvalidBrokerProfileTest(MOSQUITTO_CONF_PATH"mosquitto_invalid_listener.conf"); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, LoadBrokerProfileAfterEndpointListenerAddedTest) { + const char* confLinkPath = "./mosquitto.conf.link"; + setenv(CELIX_EARPM_BROKER_PROFILE, confLinkPath, 1); + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + + std::promise endpointPromise; + auto endpointFuture = endpointPromise.get_future(); + auto eplId = RegisterBrokerEndpointListenerService(&endpointPromise, [](void* handle, endpoint_description_t* endpoint, char*) { + EXPECT_STREQ(CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME, endpoint->serviceName); + EXPECT_STREQ(CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE, celix_properties_get(endpoint->properties, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, nullptr)); + EXPECT_EQ(1883, celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0)); + EXPECT_STREQ("", celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, nullptr)); + EXPECT_STREQ("all", celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, nullptr)); + + auto promise = static_cast*>(handle); + promise->set_value(); + return CELIX_SUCCESS; + }); + ASSERT_GE(eplId, 0); + + auto eplTrackerId = TrackEndpointListenerForDiscovery(discovery); + ASSERT_GE(eplTrackerId, 0); + + symlink(MOSQUITTO_CONF_PATH"mosquitto_bind_all_if.conf", confLinkPath); + + auto rc = endpointFuture.wait_for(std::chrono::seconds{30}); + EXPECT_EQ(rc, std::future_status::ready); + + unlink(confLinkPath); + + celix_bundleContext_stopTracker(ctx.get(), eplTrackerId); + celix_bundleContext_unregisterService(ctx.get(), eplId); + + celix_earpmDiscovery_destroy(discovery); + unsetenv(CELIX_EARPM_BROKER_PROFILE); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, EndpointListenerServicePropertiesInvalidTest) { + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + + celix_autoptr(celix_properties_t) properties = celix_properties_create(); + ASSERT_NE(properties, nullptr); + endpoint_listener_t listener{}; + listener.handle = nullptr; + listener.endpointAdded = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS; }; + listener.endpointRemoved = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS; }; + + auto status = celix_earpmDiscovery_addEndpointListener(discovery, &listener, properties); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmDiscovery_removeEndpointListener(discovery, &listener, properties); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + celix_earpmDiscovery_destroy(discovery); +} + +TEST_F(CelixEarpmBrokerDiscoveryTestSuite, RemoveNotExistedEndpointListenerServiceTest) { + auto discovery = celix_earpmDiscovery_create(ctx.get()); + ASSERT_NE(discovery, nullptr); + + celix_autoptr(celix_properties_t) properties = celix_properties_create(); + ASSERT_NE(properties, nullptr); + celix_properties_setLong(properties, CELIX_FRAMEWORK_SERVICE_ID, 123); + endpoint_listener_t listener{}; + listener.handle = nullptr; + listener.endpointAdded = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS; }; + listener.endpointRemoved = [](void*, endpoint_description_t*, char*) { return CELIX_SUCCESS; }; + + auto status = celix_earpmDiscovery_removeEndpointListener(discovery, &listener, properties); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpmDiscovery_destroy(discovery); +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientErrorInjectionTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientErrorInjectionTestSuite.cc new file mode 100644 index 000000000..94fd8b969 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientErrorInjectionTestSuite.cc @@ -0,0 +1,749 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include + +#include "celix_bundle_context_ei.h" +#include "malloc_ei.h" +#include "celix_threads_ei.h" +#include "celix_long_hash_map_ei.h" +#include "celix_string_hash_map_ei.h" +#include "celix_utils_ei.h" +#include "mosquitto_ei.h" +#include "celix_properties_ei.h" +#include "celix_earpm_client.h" +#include "celix_earpm_broker_discovery.h" +#include "CelixEarpmClientTestSuiteBaseClass.h" + +class CelixEarpmClientErrorInjectionTestSuite : public CelixEarpmClientTestSuiteBaseClass { +public: + CelixEarpmClientErrorInjectionTestSuite() : CelixEarpmClientTestSuiteBaseClass{".earpm_client_ei_test_cache"} {} + + ~CelixEarpmClientErrorInjectionTestSuite() override { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_malloc(nullptr, 0, nullptr); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, 0); + celix_ei_expect_celixThreadCondition_init(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_create(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_create(nullptr, 0, nullptr); + celix_ei_expect_celixThread_create(nullptr, 0, 0); + celix_ei_expect_mosquitto_property_add_int32(nullptr, 0, 0); + celix_ei_expect_mosquitto_property_add_string_pair(nullptr, 0, 0); + celix_ei_expect_mosquitto_new(nullptr, 0, nullptr); + celix_ei_expect_mosquitto_int_option(nullptr, 0, 0); + celix_ei_expect_mosquitto_will_set_v5(nullptr, 0, 0); + celix_ei_expect_celix_properties_getAsStringArrayList(nullptr, 0, 0); + celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0); + celix_ei_expect_celixThreadCondition_signal(nullptr, 0, 0); + celix_ei_expect_celix_stringHashMap_putLong(nullptr, 0, 0); + celix_ei_expect_mosquitto_subscribe_v5(nullptr, 0, 0); + celix_ei_expect_mosquitto_unsubscribe(nullptr, 0, 0); + celix_ei_expect_mosquitto_publish_v5(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_put(nullptr, 0, 0); + celix_ei_expect_mosquitto_property_read_string(nullptr, 0, nullptr); + celix_ei_expect_mosquitto_property_read_binary(nullptr, 0, nullptr); + celix_ei_expect_mosquitto_property_read_string_pair(nullptr, 0, nullptr); + }; +}; + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToGetFrameworkUUIDTest) { + celix_ei_expect_celix_bundleContext_getProperty((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAllocMemoryForClientTest) { + celix_ei_expect_calloc((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateMutexTest) { + celix_ei_expect_celixThreadMutex_create((void*)&celix_earpmClient_create, 0, ENOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateMessageStatusConditionTest) { + celix_ei_expect_celixThreadCondition_init((void*)&celix_earpmClient_create, 0, ENOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateMessagePoolTest) { + celix_ei_expect_calloc((void*)&celix_earpmClient_create, 1, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreatePublishingMessageQueueTest) { + celix_ei_expect_celix_longHashMap_createWithOptions((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateSubscriptionMapTest) { + celix_ei_expect_celix_stringHashMap_create((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateBrokerInfoMapTest) { + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateBrokerInfoChangedConditionTest) { + celix_ei_expect_celixThreadCondition_init((void*)&celix_earpmClient_create, 0, ENOMEM, 2); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddMqttSessionExpiryIntervalToConnectPropertyTest) { + celix_ei_expect_mosquitto_property_add_int32((void*)&celix_earpmClient_create, 0, MOSQ_ERR_NOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddMqttSessionExpiryIntervalToDisconnectPropertyTest) { + celix_ei_expect_mosquitto_property_add_int32((void*)&celix_earpmClient_create, 0, MOSQ_ERR_NOMEM, 2); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateMosquittoInstanceTest) { + celix_ei_expect_mosquitto_new((void*)&celix_earpmClient_create, 0, nullptr); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToSetMqttVersionOptionTest) { + celix_ei_expect_mosquitto_int_option((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOT_SUPPORTED); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToSetMqttNoDelayOptionTest) { + celix_ei_expect_mosquitto_int_option((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOT_SUPPORTED, 2); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddDelayIntervalToWillMessagePropertyTest) { + celix_ei_expect_mosquitto_property_add_int32((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddSenderUUIDToWillMessagePropertyTest) { + celix_ei_expect_mosquitto_property_add_string_pair((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddMsgVersionToWillMessagePropertyTest) { + celix_ei_expect_mosquitto_property_add_string_pair((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOMEM, 2); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToSetWillMessageTest) { + celix_ei_expect_mosquitto_will_set_v5((void*)&celix_earpmClient_create, 1, MOSQ_ERR_NOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToCreateWorkThreadTest) { + celix_ei_expect_celixThread_create((void*)&celix_earpmClient_create, 0, ENOMEM); + celix_earpm_client_create_options_t opts{defaultOpts}; + celix_earpm_client_t *client = celix_earpmClient_create(&opts); + ASSERT_EQ(nullptr, client); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToGetBrokerStaticAddressTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_celix_properties_getAsStringArrayList((void*)&celix_earpmClient_mqttBrokerEndpointAdded, 1, ENOMEM); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToGetBrokerDynamicAddressTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, 1883); + + celix_ei_expect_celix_properties_getAsStringArrayList((void *) &celix_earpmClient_mqttBrokerEndpointAdded, 1, + ENOMEM, 2); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupBrokerInterfaceNameTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, LOOP_BACK_INTERFACE); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, 1883); + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, ""); + + celix_ei_expect_celix_utils_strdup((void *) &celix_earpmClient_mqttBrokerEndpointAdded, 1, nullptr); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAllocMemoryForBrokerInfoTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_calloc((void *) &celix_earpmClient_mqttBrokerEndpointAdded, 1, nullptr); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddBrokerInfoToMapTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_celix_stringHashMap_put((void *) &celix_earpmClient_mqttBrokerEndpointAdded, 0, ENOMEM); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToNotifyBrokerInfoChangedTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_celixThreadCondition_signal((void *) &celix_earpmClient_mqttBrokerEndpointAdded, 0, EPERM); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(CELIX_SUCCESS, status); + auto ok = WaitForLogMessage("Failed to signal adding broker information"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupBrokeInfoMapWhenConnectingBrokerTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_celix_stringHashMap_createWithOptions(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(CELIX_SUCCESS, status); + auto ok = WaitForLogMessage("Failed to create broker info map."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToRetainBrokeInfoWhenConnectingBrokerTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1883); + + celix_ei_expect_celix_stringHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM, 2); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(CELIX_SUCCESS, status); + auto ok = WaitForLogMessage("Failed to copy broker info."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddSubscriptionToMapTest) { + TestClient([](celix_earpm_client_t* client) { + celix_ei_expect_celix_stringHashMap_putLong((void *) &celix_earpmClient_subscribe, 0, ENOMEM); + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_MOST_ONCE); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToSubscribeMessageTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_ei_expect_mosquitto_subscribe_v5((void *) &celix_earpmClient_subscribe, 0, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_MOST_ONCE); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToChangeSubscribedMessageQosTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_MOST_ONCE); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_mosquitto_subscribe_v5((void *) &celix_earpmClient_subscribe, 0, MOSQ_ERR_NOMEM); + status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_LEAST_ONCE); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToUnSubscribedMessageTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_MOST_ONCE); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_mosquitto_unsubscribe((void *) &celix_earpmClient_unsubscribe, 0, MOSQ_ERR_NOMEM); + status = celix_earpmClient_unsubscribe(client, "test/topic"); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToMarkMessageUnSubscribedWhenUnconnectBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_MOST_ONCE); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_celix_stringHashMap_putLong((void *) &celix_earpmClient_unsubscribe, 0, ENOMEM); + status = celix_earpmClient_unsubscribe(client, "test/topic"); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupAsyncMessageTopicTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_celix_utils_strdup((void *) &celix_earpmClient_publishAsync, 1, nullptr); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddSenderUUIDToAsyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_string_pair((void *) &celix_earpmClient_publishAsync, 1, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddVersionToAsyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_string_pair((void *) &celix_earpmClient_publishAsync, 1, MOSQ_ERR_NOMEM, 2); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToPublishAsyncMessageTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_publish_v5((void *) &celix_earpmClient_publishAsync, 2, MOSQ_ERR_OVERSIZE_PACKET); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToEnqueueAsyncMessageToPublishingQueueTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_celix_longHashMap_put((void *) &celix_earpmClient_publishAsync, 2, ENOMEM); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupAsyncMessagePayloadTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_malloc((void *) &celix_earpmClient_publishAsync, 2, nullptr); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupSyncMessageTopicTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_celix_utils_strdup((void *) &celix_earpmClient_publishSync, 1, nullptr); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddExpiryIntervalToSyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 1; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_int32((void *) &celix_earpmClient_publishSync, 1, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddResponseTopicToSyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 1; + requestInfo.responseTopic = "response/topic"; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_string((void *) &celix_earpmClient_publishSync, 1, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddCorrelationDataToSyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 1; + requestInfo.responseTopic = "response/topic"; + long correlationData{0}; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_binary((void *) &celix_earpmClient_publishSync, 1, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddSenderUUIDToSyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_string_pair((void *) &celix_earpmClient_publishSync, 1, MOSQ_ERR_NOMEM); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToAddVersionToSyncMessageTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 1; + requestInfo.responseTopic = "response/topic"; + long correlationData{0}; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_property_add_string_pair((void *) &celix_earpmClient_publishSync, 1, MOSQ_ERR_NOMEM, 2); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToPublishSyncMessageTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_publish_v5((void *) &celix_earpmClient_publishSync, 2, MOSQ_ERR_OVERSIZE_PACKET); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToEnqueueSyncMessageToPublishingQueueTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_celix_longHashMap_put((void *) &celix_earpmClient_publishSync, 2, ENOMEM); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToDupSyncMessagePayloadTest) { + TestClient([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_malloc((void *) &celix_earpmClient_publishSync, 2, nullptr); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToPublishSyncMessageWhichInWaitingQueueTest) { + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, "1", 1); + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo{}; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test payload"; + requestInfo.payloadSize = strlen(requestInfo.payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = fwUUID.c_str(); + requestInfo.version = "1.0.0"; + celix_ei_expect_mosquitto_publish_v5(CELIX_EI_UNKNOWN_CALLER, 0, MOSQ_ERR_OVERSIZE_PACKET, 2); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + ASSERT_EQ(CELIX_SUCCESS, status); + + status = celix_earpmClient_publishSync(client, &requestInfo); + ASSERT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToReadMessageResponseTopicTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client){ + auto status = celix_earpmClient_subscribe(client, "test/message", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_ei_expect_mosquitto_property_read_string(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) props = nullptr; + auto rc = mosquitto_property_add_int32(&props, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, 10); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string(&props, MQTT_PROP_RESPONSE_TOPIC, "test/response"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA, "1234", 4); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to get response topic from sync event"); + EXPECT_TRUE(ok); + + status = celix_earpmClient_unsubscribe(client, "test/message"); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear retained message + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + }, [](const celix_earpm_client_request_info_t*) { + ADD_FAILURE() << "Should not be called"; + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToReadMessageCorrelationDataTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client){ + auto status = celix_earpmClient_subscribe(client, "test/message", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_ei_expect_mosquitto_property_read_binary(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) props = nullptr; + auto rc = mosquitto_property_add_int32(&props, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, 10); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string(&props, MQTT_PROP_RESPONSE_TOPIC, "test/response"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA, "1234", 4); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to get correlation data from sync event"); + EXPECT_TRUE(ok); + + status = celix_earpmClient_unsubscribe(client, "test/message"); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear retained message + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + }, [](const celix_earpm_client_request_info_t*) { + ADD_FAILURE() << "Should not be called"; + }); +} + +TEST_F(CelixEarpmClientErrorInjectionTestSuite, FailedToReadMessageSenderUUIDTest) { + TestClientAfterConnectedBroker([this](celix_earpm_client_t* client){ + auto status = celix_earpmClient_subscribe(client, "test/message", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_ei_expect_mosquitto_property_read_string_pair(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) props = nullptr; + auto rc = mosquitto_property_add_int32(&props, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, 10); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string(&props, MQTT_PROP_RESPONSE_TOPIC, "test/response"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA, "1234", 4); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to get user property from sync event"); + EXPECT_TRUE(ok); + + status = celix_earpmClient_unsubscribe(client, "test/message"); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear retained message + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + }, [](const celix_earpm_client_request_info_t*) { + ADD_FAILURE() << "Should not be called"; + }); +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuite.cc new file mode 100644 index 000000000..55a63606d --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuite.cc @@ -0,0 +1,915 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "celix_earpm_client.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_broker_discovery.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "CelixEarpmClientTestSuiteBaseClass.h" + + +class CelixEarpmClientTestSuite : public CelixEarpmClientTestSuiteBaseClass { +public: + CelixEarpmClientTestSuite() : CelixEarpmClientTestSuiteBaseClass{} {} + + ~CelixEarpmClientTestSuite() override = default; + +}; + +TEST_F(CelixEarpmClientTestSuite, CreateClientTest) { + celix_earpm_client_create_options_t opts{defaultOpts}; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + celix_earpmClient_destroy(client); +} + +TEST_F(CelixEarpmClientTestSuite, CreateClientWithInvalidMsgQueueSizeTest) { + celix_earpm_client_create_options_t opts{defaultOpts}; + + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(CELIX_EARPM_MSG_QUEUE_MAX_SIZE+1).c_str(), 1); + auto client = celix_earpmClient_create(&opts); + ASSERT_EQ(client, nullptr); + + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(0).c_str(), 1); + client = celix_earpmClient_create(&opts); + ASSERT_EQ(client, nullptr); + + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, CreateClientWithInvalidParallelMsgSizeTest) { + celix_earpm_client_create_options_t opts{defaultOpts}; + + const int msgQueueCapacity = 256; + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(msgQueueCapacity).c_str(), 1); + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, std::to_string(msgQueueCapacity+1).c_str(), 1); + auto client = celix_earpmClient_create(&opts); + ASSERT_EQ(client, nullptr); + + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, std::to_string(0).c_str(), 1); + client = celix_earpmClient_create(&opts); + ASSERT_EQ(client, nullptr); + + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedIpEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedHostNameEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "localhost"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerDynamicIpEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, "127.0.0.1,::1"); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerDynamicIpv4EndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, "127.0.0.1,::1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerDynamicIpv6EndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, "127.0.0.1,::1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET6); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedHostNameAndSockDomainEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "localhost"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedInterfaceEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, ""); + celix_properties_set(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, LOOP_BACK_INTERFACE); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedInterfaceAndIpv4DomainEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, ""); + celix_properties_set(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, LOOP_BACK_INTERFACE); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, AddMqttBrokerSpecifiedInterfaceAndIpv6DomainEndpointTest) { + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, ""); + celix_properties_set(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, LOOP_BACK_INTERFACE); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET6); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, MqttBrokerEndpointWithInvalidPortTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, -1); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, MqttBrokerDynamicIpEndpointWithInvalidPortTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, -1); + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, MQTT_BROKER_ADDRESS); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, MqttBrokerEndpointWithoutAddressTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, MqttBrokerEndpointWithEmptyAddressTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_RSA_PORT, MQTT_BROKER_PORT); + celix_properties_set(endpoint->properties, CELIX_RSA_IP_ADDRESSES, ""); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, MqttBrokerEndpointWithInvalidSocketDomainTest) { + TestClient([](celix_earpm_client_t* client) { + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, "127.0.0.1"); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_INET6); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos0MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, ENOTCONN); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos1MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos2MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_EXACTLY_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncLowPriorityMsgWhenMsgQueueHungryTest) { + const int msgQueueCapacity = 4; + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(msgQueueCapacity).c_str(), 1); + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, "1", 1); + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + for (int i = 0; i < msgQueueCapacity*70/100; ++i) { + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + } + + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, ENOMEM); + }); + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncMiddlePriorityMsgWhenMsgQueueHungryTest) { + const int msgQueueCapacity = 4; + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(msgQueueCapacity).c_str(), 1); + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, "1", 1); + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + for (int i = 0; i < msgQueueCapacity*85/100; ++i) { + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + } + requestInfo.pri = CELIX_EARPM_MSG_PRI_MIDDLE; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, ENOMEM); + }); + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncHighPriorityMsgWhenMsgQueueHungryTest) { + const int msgQueueCapacity = 4; + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(msgQueueCapacity).c_str(), 1); + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, "1", 1); + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + for (int i = 0; i < msgQueueCapacity; ++i) { + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + } + + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, ENOMEM); + }); + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQos0MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 30; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, ENOTCONN); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQos1MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 0.01; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, ETIMEDOUT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQos2MsgWhenUnconnetToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_EXACTLY_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 0.01; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, ETIMEDOUT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQOS0MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 30; + requestInfo.responseTopic = "test/response/topic"; + long correlationData = 0; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQOS1MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 30; + requestInfo.responseTopic = "test/response/topic"; + long correlationData = 0; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncQOS2MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_EXACTLY_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 30; + requestInfo.responseTopic = "test/response/topic"; + long correlationData = 0; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncMsgWithInvalidTopicTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = nullptr; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncMsgWithInvalidQosTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.qos = CELIX_EARPM_QOS_UNKNOWN; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + requestInfo.qos = CELIX_EARPM_QOS_MAX; + status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncMsgWithInvalidPriorityTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.pri = (celix_earpm_client_message_priority_e)(CELIX_EARPM_MSG_PRI_LOW - 1); + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + requestInfo.pri = (celix_earpm_client_message_priority_e)(CELIX_EARPM_MSG_PRI_HIGH + 1); + status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishSyncMsgWhenMsgQueueHungryTest) { + const int msgQueueCapacity = 4; + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(msgQueueCapacity).c_str(), 1); + setenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY, "1", 1); + + std::promise clientConnectedPromise; + auto clientConnectedFuture = clientConnectedPromise.get_future(); + std::promise mqttThreadContinuePromise; + auto mqttThreadContinueFuture = mqttThreadContinuePromise.get_future(); + + TestClient([&clientConnectedFuture, &mqttThreadContinuePromise](celix_earpm_client_t* client) { + //wait for the client connected + auto result = clientConnectedFuture.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(result, std::future_status::ready); + + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + for (int i = 0; i < msgQueueCapacity*70/100; ++i) { + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + } + + //QOS0 msg + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 0.01; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, ENOMEM); + + //QOS1 msg + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.expiryInterval = 0.01; + status = celix_earpmClient_publishSync(client,&requestInfo); + EXPECT_EQ(status, ETIMEDOUT); + + //QOS2 msg + requestInfo.qos = CELIX_EARPM_QOS_EXACTLY_ONCE; + requestInfo.expiryInterval = 0.01; + status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, ETIMEDOUT); + + mqttThreadContinuePromise.set_value();// notify mqtt thread continue + },[&clientConnectedPromise, &mqttThreadContinueFuture](){ + clientConnectedPromise.set_value();// notify client connected + + //wait for mqtt thread continue + auto result = mqttThreadContinueFuture.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(result, std::future_status::ready); + }); + unsetenv(CELIX_EARPM_PARALLEL_MSG_CAPACITY); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos0MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos1MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncQos2MsgTest) { + TestClientAfterConnectedBroker([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_EXACTLY_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.senderUUID = "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"; + requestInfo.version = "1.0.0"; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncMsgWithInvalidTopicTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = nullptr; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncMsgWithInvalidQosTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.qos = CELIX_EARPM_QOS_UNKNOWN; + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + requestInfo.qos = CELIX_EARPM_QOS_MAX; + status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncMsgWithInvalidPriorityTest) { + TestClient([](celix_earpm_client_t* client) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.pri = (celix_earpm_client_message_priority_e)(CELIX_EARPM_MSG_PRI_LOW - 1); + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + requestInfo.pri = (celix_earpm_client_message_priority_e)(CELIX_EARPM_MSG_PRI_HIGH + 1); + status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, PublishMsgBeforeConnectedTest) { + TestClient([](celix_earpm_client_t* client) { + std::future future = std::async(std::launch::async, [client](){ + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = 30; + auto status = celix_earpmClient_publishSync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + }); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + future.get();//wait for the published msg complete + + status = celix_earpmClient_mqttBrokerEndpointRemoved(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + + +TEST_F(CelixEarpmClientTestSuite, PublishAsyncMsgAndThenClientDestroyTest) { + std::promise clientConnectedPromise; + auto clientConnectedFuture = clientConnectedPromise.get_future(); + std::promise msgPublishedPromise; + auto msgPublishedFuture = msgPublishedPromise.get_future(); + TestClient([&clientConnectedFuture,&msgPublishedPromise](celix_earpm_client_t* client) { + //wait for the client connected + auto result = clientConnectedFuture.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(result, std::future_status::ready); + + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = "test/topic"; + requestInfo.payload = "test"; + requestInfo.payloadSize = 4; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + for (int i = 0; i < CELIX_EARPM_MSG_QUEUE_CAPACITY_DEFAULT; ++i) { + auto status = celix_earpmClient_publishAsync(client, &requestInfo); + EXPECT_EQ(status, CELIX_SUCCESS); + } + msgPublishedPromise.set_value();// notify the message published + },[&clientConnectedPromise, &msgPublishedFuture](void){ + clientConnectedPromise.set_value();// notify client connected + + msgPublishedFuture.get();//wait for the message published + }); +} + +TEST_F(CelixEarpmClientTestSuite, SubscribeAndUnsubscribeWhenUnconnectedToBrokerTest) { + TestClient([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/topic", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + status = celix_earpmClient_unsubscribe(client, "test/topic"); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmClientTestSuite, SubscribeMessageWithSpecialTopicTest) { + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + + TestClientAfterConnectedBroker([&receivedFuture](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/topic1", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + int rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/topic1", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, nullptr); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + auto result = receivedFuture.wait_for(std::chrono::seconds(30)); + EXPECT_EQ(result, std::future_status::ready); + status = celix_earpmClient_unsubscribe(client, "test/topic1"); + EXPECT_EQ(status, CELIX_SUCCESS); + }, [&receivedPromise](const celix_earpm_client_request_info_t* requestInfo){ + EXPECT_STREQ(requestInfo->topic, "test/topic1"); + EXPECT_STREQ(requestInfo->payload, "test"); + try { + receivedPromise.set_value(); + } catch (...) { + //ignore + } + }); +} + +TEST_F(CelixEarpmClientTestSuite, SubscribeMessageWithPatternTopicTest) { + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + + TestClientAfterConnectedBroker([&receivedFuture](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test1/*", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + int rc = mosquitto_publish_v5(testMosqClient, nullptr, "test1/topic1", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, nullptr); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + auto result = receivedFuture.wait_for(std::chrono::seconds(30)); + EXPECT_EQ(result, std::future_status::ready); + + status = celix_earpmClient_unsubscribe(client, "test1/*"); + EXPECT_EQ(status, CELIX_SUCCESS); + }, [&receivedPromise](const celix_earpm_client_request_info_t* requestInfo){ + EXPECT_STREQ(requestInfo->topic, "test1/topic1"); + EXPECT_STREQ(requestInfo->payload, "test"); + try { + receivedPromise.set_value(); + } catch (...) { + //ignore + } + }); +} + +TEST_F(CelixEarpmClientTestSuite, SubscribeBeforeConnectedToBrokerTest) { + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + celix_earpm_client_create_options_t opts{defaultOpts}; + opts.callbackHandle = &receivedPromise; + opts.connectedCallback = [](void*) { }; + opts.receiveMsgCallback = [](void* handle, const celix_earpm_client_request_info_t* requestInfo) { + auto receivedPromise = static_cast*>(handle); + EXPECT_STREQ(requestInfo->topic, "test/topic2"); + EXPECT_STREQ(requestInfo->payload, "test"); + try { + receivedPromise->set_value(); + } catch (...) { + //ignore + } + }; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + + auto status = celix_earpmClient_subscribe(client, "test/topic2", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(status, CELIX_SUCCESS); + + int rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/topic2", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, nullptr); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto result = receivedFuture.wait_for(std::chrono::seconds(30)); + EXPECT_EQ(result, std::future_status::ready); + + status = celix_earpmClient_mqttBrokerEndpointRemoved(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpmClient_destroy(client); +} + +TEST_F(CelixEarpmClientTestSuite, UnsubscribeBeforeConnectedToBrokerTest) { + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + celix_earpm_client_create_options_t opts{defaultOpts}; + opts.callbackHandle = &receivedPromise; + opts.connectedCallback = [](void*) { }; + opts.receiveMsgCallback = [](void* handle, const celix_earpm_client_request_info_t*) { + auto receivedPromise = static_cast*>(handle); + receivedPromise->set_value(); + }; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + + auto status = celix_earpmClient_subscribe(client, "test/topic2", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpmClient_unsubscribe(client, "test/topic2"); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(status, CELIX_SUCCESS); + + int rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/topic2", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, nullptr); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto result = receivedFuture.wait_for(std::chrono::milliseconds(10)); + EXPECT_EQ(result, std::future_status::timeout); + + status = celix_earpmClient_mqttBrokerEndpointRemoved(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpmClient_destroy(client); +} + +TEST_F(CelixEarpmClientTestSuite, SubscribeWithInvalidTopicTest) { + TestClient([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + std::string topic(1024 + 1, 'a'); + status = celix_earpmClient_subscribe(client, topic.c_str(), CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_subscribe(client, "$a", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_subscribe(client, "+a", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_subscribe(client, "#a", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, UnsubscribeWithInvalidTopicTest) { + TestClient([](celix_earpm_client_t* client) { + auto status = celix_earpmClient_unsubscribe(client, ""); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + std::string topic(1024 + 1, 'a'); + status = celix_earpmClient_unsubscribe(client, topic.c_str()); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_unsubscribe(client, "$a"); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_unsubscribe(client, "+a"); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpmClient_unsubscribe(client, "#a"); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmClientTestSuite, ReceiveMessageTest) { + std::promise receivedPromise; + auto receivedFuture = receivedPromise.get_future(); + + TestClientAfterConnectedBroker([&receivedFuture](celix_earpm_client_t* client) { + auto status = celix_earpmClient_subscribe(client, "test/message", CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_autoptr(mosquitto_property) props = nullptr; + auto rc = mosquitto_property_add_int32(&props, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, 10); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string(&props, MQTT_PROP_RESPONSE_TOPIC, "test/response"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_binary(&props, MQTT_PROP_CORRELATION_DATA, "1234", 4); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&props, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", sizeof("test"), "test", CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + auto result = receivedFuture.wait_for(std::chrono::seconds(30)); + EXPECT_EQ(result, std::future_status::ready); + status = celix_earpmClient_unsubscribe(client, "test/message"); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear retained message + rc = mosquitto_publish_v5(testMosqClient, nullptr, "test/message", 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, + true, props); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + }, [&receivedPromise](const celix_earpm_client_request_info_t* requestInfo){ + EXPECT_STREQ(requestInfo->topic, "test/message"); + if (requestInfo->payload != nullptr) { + EXPECT_STREQ(requestInfo->payload, "test"); + EXPECT_EQ(requestInfo->payloadSize, sizeof("test")); + } + EXPECT_EQ(requestInfo->qos, CELIX_EARPM_QOS_AT_LEAST_ONCE); + EXPECT_EQ(requestInfo->pri, CELIX_EARPM_MSG_PRI_LOW); + EXPECT_EQ(requestInfo->expiryInterval, 10); + EXPECT_STREQ(requestInfo->responseTopic, "test/response"); + EXPECT_EQ(requestInfo->correlationDataSize, 4); + EXPECT_EQ(memcmp(requestInfo->correlationData, "1234", 4), 0); + EXPECT_STREQ(requestInfo->senderUUID, "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + EXPECT_STREQ(requestInfo->version, "1.0.0"); + try { + receivedPromise.set_value(); + } catch (...) { + //ignore + } + }); +} + +TEST_F(CelixEarpmClientTestSuite, ReconnectBrokerTest) { + pid_t pid1 = fork(); + ASSERT_GE(pid1, 0); + if (pid1 == 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(100));//let the parent run first + execlp("mosquitto", "mosquitto1", "-p","1884", nullptr); + ADD_FAILURE() << "Failed to start mosquitto"; + } + TestAddMqttBrokerEndpoint([](endpoint_description_t* endpoint){ + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 1884); + }); + + kill(pid1, SIGKILL); + waitpid(pid1, nullptr, 0); +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuiteBaseClass.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuiteBaseClass.h new file mode 100644 index 000000000..d869f9dbb --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmClientTestSuiteBaseClass.h @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_CELIXEARPMCLIENTTESTSUITEBASECLASS_H +#define CELIX_CELIXEARPMCLIENTTESTSUITEBASECLASS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "celix_log_helper.h" +extern "C" { +#include "endpoint_description.h" +} +#include "remote_constants.h" +#include "celix_earpm_client.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +namespace { + constexpr const char* MQTT_BROKER_ADDRESS = "127.0.0.1"; + constexpr int MQTT_BROKER_PORT = 1883; +#ifdef __APPLE__ + constexpr const char* LOOP_BACK_INTERFACE = "lo0"; +#else + constexpr const char* LOOP_BACK_INTERFACE = "lo"; +#endif +} + +class CelixEarpmClientTestSuiteBaseClass : public CelixEarpmTestSuiteBaseClass { +public: + static void SetUpTestSuite() { + mosquitto_lib_init(); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + execlp("mosquitto", "mosquitto", "-p", std::to_string(MQTT_BROKER_PORT).c_str(), nullptr); + ADD_FAILURE() << "Failed to start mosquitto"; + } + testMosqClient = mosquitto_new(nullptr, true, nullptr); + ASSERT_NE(testMosqClient, nullptr); + auto rc = mosquitto_int_option(testMosqClient, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + for (int i = 0; i < 6000; ++i) { + if(mosquitto_connect_bind_v5(testMosqClient, MQTT_BROKER_ADDRESS, MQTT_BROKER_PORT, 60, nullptr, nullptr) == MOSQ_ERR_SUCCESS) { + rc = mosquitto_loop_start(testMosqClient); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + ADD_FAILURE() << "Failed to init test environment"; + } + + static void TearDownTestSuite() { + mosquitto_disconnect(testMosqClient); + mosquitto_loop_stop(testMosqClient, false); + mosquitto_destroy(testMosqClient); + kill(pid, SIGKILL); + waitpid(pid, nullptr, 0); + mosquitto_lib_cleanup(); + } + + explicit CelixEarpmClientTestSuiteBaseClass(const char* testCache = ".earpm_client_test_cache") : CelixEarpmTestSuiteBaseClass{testCache, "earpm_client"} { + logHelper = std::shared_ptr + {celix_logHelper_create(ctx.get(), "earpm_client"), [](celix_log_helper_t *h) { celix_logHelper_destroy(h); }}; + + defaultOpts.ctx = ctx.get(); + defaultOpts.logHelper = logHelper.get(); + defaultOpts.sessionEndMsgTopic = CELIX_EARPM_SESSION_END_TOPIC; + defaultOpts.sessionEndMsgSenderUUID = fwUUID.c_str(); + defaultOpts.sessionEndMsgVersion = "1.0.0"; + defaultOpts.callbackHandle = nullptr; + defaultOpts.receiveMsgCallback = [](void*, const celix_earpm_client_request_info_t*) {}; + defaultOpts.connectedCallback = [](void*) {}; + } + + ~CelixEarpmClientTestSuiteBaseClass() override = default; + + static endpoint_description_t* CreateMqttBrokerEndpoint(void) { + auto props = celix_properties_create(); + EXPECT_NE(props, nullptr); + celix_properties_setLong(props, CELIX_RSA_ENDPOINT_SERVICE_ID, INT32_MAX); + celix_properties_set(props, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME); + celix_properties_set(props, CELIX_RSA_ENDPOINT_FRAMEWORK_UUID, "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + uuid_t uid; + uuid_generate(uid); + char endpointUUID[37]; + uuid_unparse_lower(uid, endpointUUID); + celix_properties_set(props, CELIX_RSA_ENDPOINT_ID, endpointUUID); + celix_properties_setBool(props, CELIX_RSA_SERVICE_IMPORTED, true); + celix_properties_set(props, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE); + endpoint_description_t* endpoint = nullptr; + auto status = endpointDescription_create(props, &endpoint); + EXPECT_EQ(status, CELIX_SUCCESS); + return endpoint; + } + + void TestAddMqttBrokerEndpoint(const std::function& modifyEndpoint) const { + std::promise clientConnectedPromise; + auto clientConnectedFuture = clientConnectedPromise.get_future(); + + celix_earpm_client_create_options_t opts{defaultOpts}; + opts.callbackHandle = &clientConnectedPromise; + opts.connectedCallback = [](void* handle){ + auto* promise = static_cast*>(handle); + try { + promise->set_value(); + } catch (...) { + //ignore + } + }; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + + modifyEndpoint(endpoint); + + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(status, CELIX_SUCCESS); + auto result = clientConnectedFuture.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(result, std::future_status::ready); + status = celix_earpmClient_mqttBrokerEndpointRemoved(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpmClient_destroy(client); + } + + void TestClient(const std::function& testBody) const { + celix_earpm_client_create_options_t opts{defaultOpts}; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + testBody(client); + celix_earpmClient_destroy(client); + } + + void TestClient(const std::function& testBody, const std::function& connectedCallback, + const std::function& receiveMsgCallBack = [](const celix_earpm_client_request_info_t*){}) const { + celix_earpm_client_create_options_t opts{defaultOpts}; + struct callback_data { + std::function connectedCallback; + std::function receiveMsgCallback; + } callbackData{connectedCallback, receiveMsgCallBack}; + opts.callbackHandle = &callbackData; + opts.connectedCallback = [](void* handle) { + auto callbackData = static_cast(handle); + callbackData->connectedCallback(); + }; + opts.receiveMsgCallback = [](void* handle, const celix_earpm_client_request_info_t* requestInfo) { + auto callbackData = static_cast(handle); + callbackData->receiveMsgCallback(requestInfo); + }; + auto* client = celix_earpmClient_create(&opts); + ASSERT_NE(client, nullptr); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + celix_properties_set(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + auto status = celix_earpmClient_mqttBrokerEndpointAdded(client, endpoint, nullptr); + ASSERT_EQ(status, CELIX_SUCCESS); + + testBody(client); + + status = celix_earpmClient_mqttBrokerEndpointRemoved(client, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpmClient_destroy(client); + } + + void TestClientAfterConnectedBroker(const std::function& testBody, + const std::function& receiveMsgCallBack = [](const celix_earpm_client_request_info_t*){}) const { + std::promise clientConnectedPromise; + auto clientConnectedFuture = clientConnectedPromise.get_future(); + TestClient([&clientConnectedFuture, testBody](celix_earpm_client_t* client) { + auto result = clientConnectedFuture.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(result, std::future_status::ready); + testBody(client); + }, [&clientConnectedPromise](void){ + try { + clientConnectedPromise.set_value(); + } catch (...) { + //ignore + } + }, receiveMsgCallBack); + } + + static pid_t pid; + static mosquitto* testMosqClient; + + std::shared_ptr logHelper{}; + celix_earpm_client_create_options_t defaultOpts{}; +}; + +pid_t CelixEarpmClientTestSuiteBaseClass::pid{0}; +mosquitto* CelixEarpmClientTestSuiteBaseClass::testMosqClient{nullptr}; + + +#endif //CELIX_CELIXEARPMCLIENTTESTSUITEBASECLASS_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererErrorInjectionTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererErrorInjectionTestSuite.cc new file mode 100644 index 000000000..520a91240 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererErrorInjectionTestSuite.cc @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include + +#include "celix_log_helper.h" +#include "malloc_ei.h" +#include "celix_threads_ei.h" +#include "celix_array_list_ei.h" +#include "celix_utils_ei.h" +#include "celix_earpm_event_deliverer.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +class CelixEarpmEventDelivererErrorInjectionTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmEventDelivererErrorInjectionTestSuite() : CelixEarpmTestSuiteBaseClass{".earpm_event_deliverer_ej_test_cache"} { + logHelper = std::shared_ptr + {celix_logHelper_create(ctx.get(), "EventDeliverer"), [](celix_log_helper_t *h) { celix_logHelper_destroy(h); }}; + } + + ~CelixEarpmEventDelivererErrorInjectionTestSuite() override { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_celixThreadRwlock_create(nullptr, 0, 0); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, 0); + celix_ei_expect_celixThreadCondition_init(nullptr, 0, 0); + celix_ei_expect_celix_arrayList_createPointerArray(nullptr, 0, nullptr); + celix_ei_expect_celixThread_create(nullptr, 0, 0); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + }; + + std::shared_ptr logHelper{}; +}; + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToAllocMemoryForEventDelivererTest) { + celix_ei_expect_calloc((void*)&celix_earpmDeliverer_create, 0, nullptr); + + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToCreateEventAdminLockTest) { + celix_ei_expect_celixThreadRwlock_create((void*)&celix_earpmDeliverer_create, 0, ENOMEM); + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToCreateMutexTest) { + celix_ei_expect_celixThreadMutex_create((void*)&celix_earpmDeliverer_create, 0, ENOMEM); + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToCreateSyncEventConditionTest) { + celix_ei_expect_celixThreadCondition_init((void*)&celix_earpmDeliverer_create, 0, ENOMEM); + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToCreateSyncEventQueueTest) { + celix_ei_expect_celix_arrayList_createPointerArray((void*)&celix_earpmDeliverer_create, 0, nullptr); + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToCreateThreadTest) { + celix_ei_expect_celixThread_create((void*)&celix_earpmDeliverer_create, 0, ENOMEM); + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); + + celix_ei_expect_celixThread_create((void*)&celix_earpmDeliverer_create, 0, ENOMEM, 2); + deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_EQ(nullptr, deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToAllocMemoryForSyncEventTest) { + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(nullptr, deliverer); + + celix_ei_expect_calloc((void*)&celix_earpmDeliverer_sendEvent, 0, nullptr); + auto status = celix_earpmDeliverer_sendEvent(deliverer, "test/event", nullptr, nullptr, nullptr); + ASSERT_EQ(ENOMEM, status); + + celix_earpmDeliverer_destroy(deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToDupSyncEventTopicTest) { + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(nullptr, deliverer); + + celix_ei_expect_celix_utils_strdup((void*)&celix_earpmDeliverer_sendEvent, 0, nullptr); + auto status = celix_earpmDeliverer_sendEvent(deliverer, "test/event", nullptr, nullptr, nullptr); + ASSERT_EQ(ENOMEM, status); + + celix_earpmDeliverer_destroy(deliverer); +} + +TEST_F(CelixEarpmEventDelivererErrorInjectionTestSuite, FailedToEnqueueSyncEventTest) { + celix_earpm_event_deliverer_t *deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(nullptr, deliverer); + + celix_ei_expect_celix_arrayList_add((void*)&celix_earpmDeliverer_sendEvent, 0, ENOMEM); + auto status = celix_earpmDeliverer_sendEvent(deliverer, "test/event", nullptr, nullptr, nullptr); + ASSERT_EQ(ENOMEM, status); + + celix_earpmDeliverer_destroy(deliverer); +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererTestSuite.cc new file mode 100644 index 000000000..c9e89681b --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmEventDelivererTestSuite.cc @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include + +#include "celix_log_helper.h" +#include "celix_earpm_event_deliverer.h" +#include "celix_earpm_constants.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +class CelixEarpmEventDelivererTestSuite : public CelixEarpmTestSuiteBaseClass { +public: + CelixEarpmEventDelivererTestSuite() : CelixEarpmTestSuiteBaseClass{".earpm_event_deliverer_test_cache"} { + logHelper = std::shared_ptr + {celix_logHelper_create(ctx.get(), "EventDeliverer"), [](celix_log_helper_t *h) { celix_logHelper_destroy(h); }}; + } + + ~CelixEarpmEventDelivererTestSuite() override = default; + + void TestEventDeliverer(const std::function& testBody) { + auto* deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(deliverer, nullptr); + testBody(deliverer); + celix_earpmDeliverer_destroy(deliverer); + } + + std::shared_ptr logHelper{}; +}; + +TEST_F(CelixEarpmEventDelivererTestSuite, CreateEventDelivererTest) { + auto* deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(deliverer, nullptr); + celix_earpmDeliverer_destroy(deliverer); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, CreateEventDelivererWithInvalidSyncEventThreadSizeTest) { + setenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS, "100", 1); + auto deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + EXPECT_EQ(deliverer, nullptr); + + setenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS, "0", 1); + deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + EXPECT_EQ(deliverer, nullptr); + + unsetenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, CreateEventDelivererWithInvalidMsgQueueSizeTest) { + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, std::to_string(CELIX_EARPM_MSG_QUEUE_MAX_SIZE+1).c_str(), 1); + auto deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + EXPECT_EQ(deliverer, nullptr); + + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, "0", 1); + deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + EXPECT_EQ(deliverer, nullptr); + + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, SetEventAdminServiceTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + }, + .sendEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, PostEventTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char* topic, const celix_properties_t* props) { + EXPECT_STREQ(topic, "test/topic"); + EXPECT_STREQ(celix_properties_get(props, "key", ""), "value"); + return CELIX_SUCCESS; + }, + .sendEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + status = celix_earpmDeliverer_postEvent(deliverer, "test/topic", props); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, EventAdminPostEventFailedTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_ILLEGAL_STATE; + }, + .sendEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + status = celix_earpmDeliverer_postEvent(deliverer, "test/topic", props); + EXPECT_EQ(status, CELIX_ILLEGAL_STATE); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, PostEventWithoutEventAdminServiceTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + auto status = celix_earpmDeliverer_postEvent(deliverer, "test/topic", props); + EXPECT_EQ(status, CELIX_ILLEGAL_STATE); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, SendEventTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + }, + .sendEvent = [](void*, const char* topic, const celix_properties_t* props) { + EXPECT_STREQ(topic, "test/topic"); + EXPECT_STREQ(celix_properties_get(props, "key", ""), "value"); + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + std::promise promise; + auto future = promise.get_future(); + celix_earpmDeliverer_sendEvent(deliverer, "test/topic", props, + [](void *data, const char *topic, celix_status_t status) { + auto promise = static_cast *>(data); + EXPECT_STREQ(topic, "test/topic"); + promise->set_value(status); + }, &promise); + auto rc = future.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(rc, std::future_status::ready); + status = future.get(); + EXPECT_EQ(status, CELIX_SUCCESS); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, EventAdminSendEventFailedTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + }, + .sendEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_ILLEGAL_STATE; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + std::promise promise; + auto future = promise.get_future(); + celix_earpmDeliverer_sendEvent(deliverer, "test/topic", props, + [](void *data, const char *topic, celix_status_t status) { + auto promise = static_cast *>(data); + (void) topic; + promise->set_value(status); + }, &promise); + auto rc = future.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(rc, std::future_status::ready); + status = future.get(); + EXPECT_EQ(status, CELIX_ILLEGAL_STATE); + }); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, SendEventWithoutEventAdminServiceTest) { + TestEventDeliverer([](celix_earpm_event_deliverer_t *deliverer) { + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + std::promise promise; + auto future = promise.get_future(); + celix_earpmDeliverer_sendEvent(deliverer, "test/topic", props, + [](void *data, const char *topic, celix_status_t status) { + auto promise = static_cast *>(data); + (void) topic; + promise->set_value(status); + }, &promise); + auto rc = future.wait_for(std::chrono::seconds(30)); + ASSERT_EQ(rc, std::future_status::ready); + auto status = future.get(); + EXPECT_EQ(status, CELIX_ILLEGAL_STATE); + }); +} + + +TEST_F(CelixEarpmEventDelivererTestSuite, HasUnprocessedSyncEventWhenDelieverDestroyTest) { + setenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS, "1", 1); + auto* deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(deliverer, nullptr); + + celix_event_admin_service_t eventAdminService = { + .handle = nullptr, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + }, + .sendEvent = [](void*, const char*, const celix_properties_t*) { + std::this_thread::sleep_for(std::chrono::milliseconds(10));//simulate slow send + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + for (int i = 0; i < 10; ++i) { + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + celix_earpmDeliverer_sendEvent(deliverer, "test/topic", props, + [](void*, const char*, celix_status_t){}, nullptr); + } + + celix_earpmDeliverer_destroy(deliverer); + unsetenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS); +} + +TEST_F(CelixEarpmEventDelivererTestSuite, SyncEventQueueFullTest) { + setenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS, "1", 1); + setenv(CELIX_EARPM_MSG_QUEUE_CAPACITY, "10", 1); + auto* deliverer = celix_earpmDeliverer_create(ctx.get(), logHelper.get()); + ASSERT_NE(deliverer, nullptr); + + std::promise promise; + auto future = promise.get_future(); + celix_event_admin_service_t eventAdminService = { + .handle = &future, + .postEvent = [](void*, const char*, const celix_properties_t*) { + return CELIX_SUCCESS; + }, + .sendEvent = [](void* handle, const char*, const celix_properties_t*) { + auto future = static_cast*>(handle); + try { + future->get(); + } catch (...) { + //Ignore + } + + return CELIX_SUCCESS; + } + }; + auto status = celix_earpmDeliverer_setEventAdminSvc(deliverer, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + for (int i = 0; i <= 11; ++i) { + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + status = celix_earpmDeliverer_sendEvent(deliverer, "test/topic", props, nullptr, nullptr); + if(status != CELIX_SUCCESS) { + break; + } + } + EXPECT_EQ(status, ENOMEM); + + promise.set_value(); + + celix_earpmDeliverer_destroy(deliverer); + unsetenv(CELIX_EARPM_MSG_QUEUE_CAPACITY); + unsetenv(CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS); +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplErrorInjectionTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplErrorInjectionTestSuite.cc new file mode 100644 index 000000000..a59aa1a50 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplErrorInjectionTestSuite.cc @@ -0,0 +1,1080 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include + + +#include "celix_stdlib_cleanup.h" +#include "malloc_ei.h" +#include "celix_threads_ei.h" +#include "celix_long_hash_map_ei.h" +#include "celix_string_hash_map_ei.h" +#include "celix_array_list_ei.h" +#include "celix_utils_ei.h" +#include "mosquitto_ei.h" +#include "celix_properties_ei.h" +#include "celix_log_helper_ei.h" +#include "celix_bundle_context_ei.h" +#include "celix_filter_ei.h" +#include "asprintf_ei.h" +#include "jansson_ei.h" +#include "celix_event_handler_service.h" +#include "celix_event_constants.h" +#include "celix_earpm_impl.h" +#include "celix_earpm_client.h" +#include "celix_earpm_event_deliverer.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "CelixEarpmImplTestSuiteBaseClass.h" + + +class CelixEarpmImplErrorInjectionTestSuite : public CelixEarpmImplTestSuiteBaseClass { +public: + CelixEarpmImplErrorInjectionTestSuite() : CelixEarpmImplTestSuiteBaseClass{".earpm_impl_ej_test_cache"} { } + + ~CelixEarpmImplErrorInjectionTestSuite() override { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_malloc(nullptr, 0, nullptr); + celix_ei_expect_celix_logHelper_create(nullptr, 0, nullptr); + celix_ei_expect_celix_bundleContext_getProperty(nullptr, 0, nullptr); + celix_ei_expect_asprintf(nullptr, 0, 0); + celix_ei_expect_celixThreadMutex_create(nullptr, 0, 0); + celix_ei_expect_celixThreadCondition_init(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_celix_longHashMap_put(nullptr, 0, 0); + celix_ei_expect_celix_arrayList_create(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0); + celix_ei_expect_celix_arrayList_addLong(nullptr, 0, 0); + celix_ei_expect_celix_stringHashMap_putLong(nullptr, 0, 0); + celix_ei_expect_json_object(nullptr, 0, nullptr); + celix_ei_expect_json_array(nullptr, 0, nullptr); + celix_ei_expect_json_array_append_new(nullptr, 0, 0); + celix_ei_expect_json_pack_ex(nullptr, 0, nullptr); + celix_ei_expect_json_object_set_new(nullptr, 0, 0); + celix_ei_expect_json_dumps(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_saveToString(nullptr, 0, 0); + celix_ei_expect_mosquitto_publish_v5(nullptr, 0, 0); + celix_ei_expect_celix_arrayList_createLongArray(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_createStringArray(nullptr, 0, nullptr); + celix_ei_expect_celix_arrayList_addString(nullptr, 0, 0); + celix_ei_expect_celix_filter_create(nullptr, 0, nullptr); + celix_ei_expect_celix_properties_loadFromString(nullptr, 0, 0); + } + + void TestAddEventHandlerServiceErrorInjection(const std::function& errorInjection, const std::function& checkResult) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(nullptr, earpm); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + + errorInjection(); + + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + + checkResult(status); + + celix_earpm_destroy(earpm); + } + + void TestRemoveEventHandlerServiceErrorInjection(const std::function& errorInjection, const std::function& checkResult) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(nullptr, earpm); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + ASSERT_EQ(CELIX_SUCCESS, status); + + errorInjection(); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + + checkResult(status); + + celix_earpm_destroy(earpm); + } + + void TestProcessSyncEventErrorInjection(const std::function& errorInjection, const std::function& checkResult) { + TestRemoteProvider([errorInjection, checkResult](celix_event_admin_remote_provider_mqtt_t* earpm) { + //subscribe to the sync event + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "syncEvent"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + errorInjection(); + + //publish the event + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + status = celix_properties_set(eventProps, "key", "value"); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autofree char* payload = nullptr; + status = celix_properties_saveToString(eventProps, 0, &payload); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autoptr(mosquitto_property) mqttProps = nullptr; + char responseTopic[128]{0}; + snprintf(responseTopic, sizeof(responseTopic), CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX"%s", FAKE_FW_UUID); + auto rc = mosquitto_property_add_string(&mqttProps, MQTT_PROP_RESPONSE_TOPIC, responseTopic); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + long correlationData {0}; + rc = mosquitto_property_add_binary(&mqttProps, MQTT_PROP_CORRELATION_DATA, &correlationData, sizeof(correlationData)); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&mqttProps, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "syncEvent", + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, mqttProps); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + //wait for the sync event ack that is sent by the remote provider + auto ok = mqttClient->WaitForReceivedMsg(responseTopic); + ASSERT_TRUE(ok); + + checkResult(); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear the retained message from the broker + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "syncEvent", + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, nullptr); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + }); + } +}; + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForEventAdminRemoteProviderTest) { + celix_ei_expect_calloc((void*)&celix_earpm_create, 0, nullptr); + auto provider = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, provider); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateLogHelperTest) { + celix_ei_expect_celix_logHelper_create((void*)&celix_earpm_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToGetFrameworkUUIDTest) { + celix_ei_expect_celix_bundleContext_getProperty((void*)&celix_earpm_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToGenerateSyncEventAckTopicTest) { + celix_ei_expect_asprintf((void*)&celix_earpm_create, 0, -1); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateMutexTest) { + celix_ei_expect_celixThreadMutex_create((void*)&celix_earpm_create, 0, ENOMEM); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateSyncEventAckConditionTest) { + celix_ei_expect_celixThreadCondition_init((void*)&celix_earpm_create, 0, ENOMEM); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateEventHandlerMapTest) { + celix_ei_expect_celix_longHashMap_createWithOptions((void*)&celix_earpm_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateEventSubscriptionMapTest) { + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)&celix_earpm_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateRemoteFrameworkMapTest) { + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)&celix_earpm_create, 0, nullptr, 2); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateEventDeliveryTest) { + celix_ei_expect_calloc((void*)&celix_earpmDeliverer_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateMqttClientTest) { + celix_ei_expect_calloc((void*)&celix_earpmClient_create, 0, nullptr); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToSubscribeCtrlMessageOfEventAdminRemoteProviderTest) { + celix_ei_expect_celix_stringHashMap_putLong((void*)&celix_earpmClient_subscribe, 0, ENOMEM); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(nullptr, earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDupDebugCommandTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(earpm, nullptr); + + celix_ei_expect_celix_utils_strdup((void*)&celix_earpm_executeCommand, 0, nullptr); + auto res = celix_earpm_executeCommand(earpm, "celix::earpm", stdout, stderr); + EXPECT_FALSE(res); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDupEventHandlerFilterInfoTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_utils_strdup((void*)&celix_earpm_addEventHandlerService, 1, nullptr); + }, + [](celix_status_t status) { + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForEventHandlerTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_calloc((void*)&celix_earpm_addEventHandlerService, 1, nullptr); + }, + [](celix_status_t status) { + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPutEventHandlerServiceToMapTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_longHashMap_put((void*)&celix_earpm_addEventHandlerService, 0, ENOMEM); + }, + [](celix_status_t status) { + EXPECT_EQ(status, ENOMEM); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForSubscriptionTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_calloc((void*)&celix_earpm_addEventHandlerService, 3, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to create subscription info"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerListForSubscriptionTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_arrayList_create((void*)&celix_earpm_addEventHandlerService, 3, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to create subscription info"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPutSubscriptionToMapTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_stringHashMap_put((void*)&celix_earpm_addEventHandlerService, 2, ENOMEM); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to add subscription info"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAttachHandlerToSubscriptionTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_arrayList_addLong((void*)&celix_earpm_addEventHandlerService, 2, ENOMEM); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to attach handler service"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToSubscribeTopicTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_celix_stringHashMap_putLong((void*)&celix_earpmClient_subscribe, 0, ENOMEM); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to subscribe topic with qos 0."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerInfoAddMessagePayloadTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_object((void*)&celix_earpm_addEventHandlerService, 1, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to create adding handler info message payload."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateTopicArrayListForHandlerInfoAddMessageTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_array((void*)&celix_earpm_addEventHandlerService, 2, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to create topic json array."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddTopicToHandlerInfoAddMessageTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_array_append_new((void*)&celix_earpm_addEventHandlerService, 2, -1); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to add topic to json array."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPackHandlerInfoOfHandlerInfoAddMessageTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_pack_ex((void*)&celix_earpm_addEventHandlerService, 2, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to pack handler information."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddEventHandlerFilterInfoToHandlerInfoAddMessageTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_object_set_new((void*)&celix_earpm_addEventHandlerService, 2, -1); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to add filter to handler information."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddHandlerInfoToMessagePayloadTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_object_set_new((void*)&celix_earpm_addEventHandlerService, 1, -1); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to add handler information to adding handler information message."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDumpHandlerInfoAddMessagePayloadTest) { + TestAddEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_dumps((void*)&celix_earpm_addEventHandlerService, 1, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to dump adding handler information message payload"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPublishHandlerInfoAddMessageToRemoteWhenAddingEventHandlerServiceTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_ei_expect_mosquitto_publish_v5((void*)&celix_earpmClient_publishAsync, 2, -1); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + + std::string expectedLog = "Failed to publish " + std::string(CELIX_EARPM_HANDLER_INFO_ADD_TOPIC); + auto ok = WaitForLogMessage(expectedLog); + EXPECT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerInfoRemoveMessagePayloadTest) { + TestRemoveEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_object((void*)&celix_earpm_removeEventHandlerService, 1, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to create removing handler info message payload."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddHandlerIdToHandlerInfoRemoveMessageTest) { + TestRemoveEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_object_set_new((void*)&celix_earpm_removeEventHandlerService, 1, -1); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to add handler id to removing handler info message."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDumpHandlerInfoRemoveMessagePayloadTest) { + TestRemoveEventHandlerServiceErrorInjection( + []() { + celix_ei_expect_json_dumps((void*)&celix_earpm_removeEventHandlerService, 1, nullptr); + }, + [this](celix_status_t) { + auto ok = WaitForLogMessage("Failed to dump removing handler information message payload"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPublishHandlerInfoRemoveMessageToRemoteWhenRemovingEventHandlerServiceTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_mosquitto_publish_v5((void*)&celix_earpmClient_publishAsync, 2, -1); + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + + std::string expectedLog = "Failed to publish " + std::string(CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC); + auto ok = WaitForLogMessage(expectedLog); + EXPECT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, NotFoundSubscriptionWhenRemoveEventHandlerTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(nullptr, earpm); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + + celix_ei_expect_celix_stringHashMap_put((void*)&celix_earpm_addEventHandlerService, 2, ENOMEM); + (void)celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + auto ok = WaitForLogMessage("Failed to add subscription info"); + EXPECT_TRUE(ok); + + auto status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + + ok = WaitForLogMessage("No subscription found"); + EXPECT_TRUE(ok); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, NotFoundHandlerInfoWhenRemoveEventHandlerTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(nullptr, earpm); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_celix_arrayList_addLong((void*)&celix_earpm_addEventHandlerService, 2, ENOMEM); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 124); + (void)celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + auto ok = WaitForLogMessage("Failed to attach handler service"); + EXPECT_TRUE(ok); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + ok = WaitForLogMessage("Not found handler"); + EXPECT_TRUE(ok); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToChangeSubscribedTopicQOSWhenRemoveEventHandlerTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props1 = celix_properties_create(); + celix_properties_setLong(props1, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_setLong(props1, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_AT_MOST_ONCE); + celix_properties_set(props1, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props1, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props1); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_autoptr(celix_properties_t) props2 = celix_properties_copy(props1); + celix_properties_setLong(props2, CELIX_FRAMEWORK_SERVICE_ID, 124); + celix_properties_setLong(props2, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_AT_LEAST_ONCE); + status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props2); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_ei_expect_celix_stringHashMap_putLong((void*)&celix_earpmClient_subscribe, 0, ENOMEM); + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props2); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("Failed to subscribe topic with qos 0."); + EXPECT_TRUE(ok); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props1); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToUnsubscribeTopicWhenRemoveEventHandlerTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(nullptr, earpm); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_celix_stringHashMap_putLong((void*)&celix_earpmClient_unsubscribe, 0, ENOMEM); + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(CELIX_SUCCESS, status); + + auto ok = WaitForLogMessage("Failed to unsubscribe topic."); + EXPECT_TRUE(ok); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToSerializeAsyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_celix_properties_saveToString((void*)&celix_earpm_postEvent, 1, ENOMEM); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_postEvent(earpm, "topic", props); + EXPECT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPublishAsyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_mosquitto_publish_v5((void*)&celix_earpmClient_publishAsync, 2, -1); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_postEvent(earpm, "topic", props); + EXPECT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToSerializeSyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_celix_properties_saveToString((void*)&celix_earpm_sendEvent, 1, ENOMEM); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_sendEvent(earpm, "topic", props); + EXPECT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPublishSyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_mosquitto_publish_v5((void*)&celix_earpmClient_publishSync, 2, -1); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_sendEvent(earpm, "topic", props); + EXPECT_EQ(CELIX_BUNDLE_EXCEPTION, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateRemoteHandlerListForSyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_celix_arrayList_createLongArray((void*)&celix_earpm_sendEvent, 3, nullptr); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_sendEvent(earpm, "topic", props); + EXPECT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddRemoteHandlerToSyncEventRemoteHandlerListTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_celix_arrayList_addLong((void*)&celix_earpm_sendEvent, 3, ENOMEM); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_sendEvent(earpm, "topic", props); + EXPECT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAssociateRemoteHandlerWithSyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + + celix_ei_expect_celix_longHashMap_put((void*)&celix_earpm_sendEvent, 3, ENOMEM); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_sendEvent(earpm, "topic", props); + EXPECT_EQ(ENOMEM, status); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForRemoteFrameworkInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to allocate memory for remote framework"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateEventAckMapForRemoteFrameworkInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_longHashMap_createWithOptions(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create event ack seq number map for remote framework"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerInfoMapForRemoteFrameworkInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_longHashMap_createWithOptions(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 2); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create handler information map for remote framework"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddRemoteFrameworkInfoToRemoteFrameworkMapWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_stringHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to add remote framework info"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForRemoteHandlerInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 2); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to allocate memory for handler information."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateTopicListForRemoteHandlerInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_arrayList_createStringArray(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create topics list for handler information."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddTopicToRemoteHandlerInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_arrayList_addString(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123, "topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to add topic(topic) to handler information."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateFilterForRemoteHandlerInfoWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_filter_create(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"s({"handler":{"handlerId":123, "topics":["topic"], "filter":"(a=b)"}})s"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create filter((a=b)) for handler information."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddRemoteHandlerInfoToRemoteHandlerMapWhenProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_celix_longHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"s({"handler":{"handlerId":123, "topics":["topic"], "filter":"(a=b)"}})s"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to add remote handler information."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateRemoteFrameworkInfoWhenProcessUpdateRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":123, "topics":["topic"]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to allocate memory for remote framework"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerInfoUpdateMessagePayloadWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_json_object(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create updating handlers info message payload."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToCreateHandlerInfoArrayWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_json_array(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create handler information array."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddHandlersInfoToHandlerInfoUpdateMessageWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_json_object_set_new(CELIX_EI_UNKNOWN_CALLER, 0, -1); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to add handlers information to updating handler info message."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDumpHandlerInfoUpdateMessagePayloadWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_json_dumps(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to dump updating handler information message payload."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToGenerateHandlerInfoWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_json_pack_ex(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to create handler information for handler 123."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAddHandlerInfoToHandlerInfoListWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + ASSERT_EQ(CELIX_SUCCESS, status); + + celix_ei_expect_json_array_append_new(CELIX_EI_UNKNOWN_CALLER, 0, -1, 2); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to add information of handler 123."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToPublishHandlerInfoUpdateMessageWhenProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_ei_expect_mosquitto_publish_v5((void*)&celix_earpmClient_publishAsync, 2, -1); + + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + std::string expectLog = "Failed to publish " + std::string(CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC); + auto ok = WaitForLogMessage(expectLog); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToUnserializeSyncEvent) { + TestProcessSyncEventErrorInjection( + []() { + celix_ei_expect_celix_properties_loadFromString(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + }, + [this]() { + auto ok = WaitForLogMessage("Failed to load event properties for syncEvent."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToAllocMemoryForSyncEventCallbackData) { + TestProcessSyncEventErrorInjection( + []() { + celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + }, + [this]() { + auto ok = WaitForLogMessage("Failed to allocate memory for send done callback data."); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDupSyncEventResponseTopic) { + TestProcessSyncEventErrorInjection( + []() { + celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + }, + [this]() { + auto ok = WaitForLogMessage("Failed to get response topic from sync event"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDupSyncEventCorrelationData) { + TestProcessSyncEventErrorInjection( + []() { + celix_ei_expect_malloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + }, + [this]() { + auto ok = WaitForLogMessage("Failed to allocate memory for correlation data of sync event"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToDelieverSyncEventToLocalHandler) { + TestProcessSyncEventErrorInjection( + []() { + celix_ei_expect_calloc((void*)&celix_earpmDeliverer_sendEvent, 0, nullptr); + }, + [this]() { + auto ok = WaitForLogMessage("Failed to send event to local handler"); + EXPECT_TRUE(ok); + } + ); +} + +TEST_F(CelixEarpmImplErrorInjectionTestSuite, FailedToUnserializeAsyncEvent) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + //subscribe to the async event + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "asyncEvent"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_ei_expect_celix_properties_loadFromString(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + + //publish the async event + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + status = celix_properties_set(eventProps, "key", "value"); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autofree char* payload = nullptr; + status = celix_properties_saveToString(eventProps, 0, &payload); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autoptr(mosquitto_property) mqttProperties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "asyncEvent", + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, mqttProperties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + auto ok = WaitForLogMessage("Failed to load event properties for asyncEvent."); + EXPECT_TRUE(ok); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear the retained message from the broker + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "asyncEvent", + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, nullptr); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + }); +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuite.cc new file mode 100644 index 000000000..508e41f47 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuite.cc @@ -0,0 +1,1031 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "celix_stdlib_cleanup.h" +#include "celix_event_handler_service.h" +#include "celix_event_admin_service.h" +#include "celix_event_constants.h" +#include "celix_earpm_impl.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "CelixEarpmImplTestSuiteBaseClass.h" + + +class CelixEarpmImplTestSuite : public CelixEarpmImplTestSuiteBaseClass { +public: + CelixEarpmImplTestSuite() : CelixEarpmImplTestSuiteBaseClass{} { } + + ~CelixEarpmImplTestSuite() override = default; +}; + +TEST_F(CelixEarpmImplTestSuite, CreateEarpmTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(earpm, nullptr); + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, CreateEarpmWithInvalidDefaultEventQosTest) { + setenv(CELIX_EARPM_EVENT_DEFAULT_QOS, std::to_string(CELIX_EARPM_QOS_MAX).c_str(), 1); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(earpm, nullptr); + + setenv(CELIX_EARPM_EVENT_DEFAULT_QOS, std::to_string(CELIX_EARPM_QOS_UNKNOWN).c_str(), 1); + earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(earpm, nullptr); + unsetenv(CELIX_EARPM_EVENT_DEFAULT_QOS); +} + +TEST_F(CelixEarpmImplTestSuite, CreateEarpmWithInvalidNoAckThresholdTest) { + setenv(CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD, "0", 1); + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_EQ(earpm, nullptr); + + unsetenv(CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD); +} + +TEST_F(CelixEarpmImplTestSuite, AddMqttBrokerEndpointTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + auto status = celix_earpm_mqttBrokerEndpointAdded(earpm, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_mqttBrokerEndpointRemoved(earpm, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, AddEventHandlerServiceTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, AddEventHandlerServicesThatHaveSameEventTopicTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props1 = celix_properties_create(); + celix_properties_setLong(props1, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_setLong(props1, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_AT_MOST_ONCE); + celix_properties_set(props1, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props1, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props1); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_autoptr(celix_properties_t) props2 = celix_properties_copy(props1); + celix_properties_setLong(props2, CELIX_FRAMEWORK_SERVICE_ID, 124); + celix_properties_setLong(props2, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_AT_LEAST_ONCE); + status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props2); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props2); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props1); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, AddEventHandlerServiceWithInvalidServiceIdTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, AddEventHandlerServiceWithInvalidQosTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_setLong(props, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_UNKNOWN); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_properties_setLong(props, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_MAX); + status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, AddEventHandlerServiceWithInvalidTopicTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_setLong(props, CELIX_EVENT_TOPIC, 111);//invalid topic type + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_properties_unset(props, CELIX_EVENT_TOPIC);//unset topic + status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_array_list_t* topics = celix_arrayList_createStringArray(); + celix_properties_assignArrayList(props, CELIX_EVENT_TOPIC, topics);//empty topic list + status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SERVICE_EXCEPTION); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, RemoveEventHandlerServiceWithInvalidServiceIdTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, RemoveNotExistedEventHandlerServiceTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 123); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + auto status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, SetEventAdminServiceTest) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_event_admin_service_t eventAdminService; + eventAdminService.handle = nullptr; + eventAdminService.postEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + eventAdminService.sendEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + auto status = celix_earpm_setEventAdminSvc(earpm, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})"); + RemoveRemoteHandlerInfoFromRemoteProvider(earpm, 123); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) == 0; }); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payLoad = R"({"handlers":[{"handlerId":123,"topics":["topic"]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payLoad), payLoad, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) != 0; }); + ASSERT_TRUE(ok); + + //use the update message to remove the handler + payLoad = R"({"handlers":[]})"; + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payLoad), payLoad, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) == 0; }); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessQueryRemoteHandlerInfoMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t*) { + mqttClient->Reset();//clear received messages cache + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + //The remote provider will send the handler description message when received the query message + auto received = mqttClient->WaitForReceivedMsg(CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC); + ASSERT_TRUE(received); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUnknownRemoteHandlerInfoControlMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"unknown", + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Unknown action"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessSessionEndMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["topic"]}})", FAKE_FW_UUID); + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_SESSION_END_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + int maxTries = 1; + while (celix_earpm_currentRemoteFrameworkCount(earpm) != 0 && maxTries < 30) { + usleep(100000*maxTries);//Wait for processing the session expiry message + maxTries++; + } + ASSERT_LT(maxTries, 30); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessMessageWithoutVersion) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, nullptr); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage(CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC" message version(null) is incompatible."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessMessageVersionFormatError) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = nullptr; + auto rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage(CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC" message version(1) is incompatible."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessMessageVersionStringTooLong) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = nullptr; + auto rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "100000.200000000.300000"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage(CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC" message version(100000.200000000.300000) is incompatible."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessMessageVersionIncompatible) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = nullptr; + auto rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "10.0.0"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage(CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC" message version(10.0.0) is incompatible."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessControlMessageWithoutSenderUUIDTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = nullptr; + auto rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC, + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("No sender UUID for control message"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithInvalidPayloadTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"(invalid)"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Failed to parse message"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithoutHandlerInfoTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("No handler information"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithoutHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithInvalidHandlerIdTypeTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":"invalid","topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithInvalidHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":-1,"topics":["topic"]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id(-1) is invalid"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithoutTopicsTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topics is lost or not array."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithInvalidTopicsTypeTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123,"topics":123}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topics is lost or not array."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAddRemoteHandlerInfoMessageWithInvalidTopicTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handler":{"handlerId":123,"topics":[123]}})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topic is not string."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageWithInvalidPayloadTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"(invalid)"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Failed to parse message"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageWithoutHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageWithInvalidHandlerIdTypeTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlerId":"invalid"})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageWithInvalidHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlerId":-1})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id(-1) is invalid"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessRemoveRemoteHandlerInfoMessageWhenHandlerInfoNotExistedTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + RemoveRemoteHandlerInfoFromRemoteProvider(earpm, 123); + auto ok = WaitForLogMessage("No remote framework info"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessSyncEventAckMessageWithoutCorrelationDataTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + char topic[128]{0}; + snprintf(topic, sizeof(topic), "%s%s", CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX, fwUUID.c_str()); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, topic, 0, nullptr, + CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Correlation data size is invalid"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessSyncEventAckMessageWithInvalidCorrelationDataTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + auto rc = mosquitto_property_add_binary(&properties, MQTT_PROP_CORRELATION_DATA, "invalid", strlen("invalid")); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + char topic[128]{0}; + snprintf(topic, sizeof(topic), "%s%s", CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX, fwUUID.c_str()); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, topic, 0, nullptr, + CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Correlation data size is invalid"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessSyncEventAckMessageBeforeRemoteHandlerInfoMessageTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + long correlationData = 1; + auto rc = mosquitto_property_add_binary(&properties, MQTT_PROP_CORRELATION_DATA, &correlationData, sizeof(correlationData)); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + char topic[128]{0}; + snprintf(topic, sizeof(topic), "%s%s", CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX, fwUUID.c_str()); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, topic, 0, nullptr, + CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("No remote framework info"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithInvalidPayloadTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payLoad = R"(invalid)"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payLoad), payLoad, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Failed to parse message"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithoutHandlerInfoTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payLoad = R"({})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payLoad), payLoad, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("No handler information"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithoutHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"topics":["topic"]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithInvalidHandlerIdTypeTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":"invalid","topics":["topic"]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id is lost or not integer"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithInvalidHandlerIdTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":-1,"topics":["topic"]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Handler id(-1) is invalid"); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithoutTopicsTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":123}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topics is lost or not array."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithInvalidTopicsTypeTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":123,"topics":123}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topics is lost or not array."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessUpdateRemoteHandlerInfoMessageWithInvalidTopicTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t*) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(); + const char* payload = R"({"handlers":[{"handlerId":123,"topics":[123]}]})"; + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitForLogMessage("Topic is not string."); + ASSERT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessAsyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + //subscribe to the async event + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_set(props, CELIX_EVENT_TOPIC, "asyncEvent"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //add event admin service to receive the async event + std::promise receivedEventPromise; + std::future receivedEventFuture = receivedEventPromise.get_future(); + static celix_event_admin_service_t eventAdminService; + eventAdminService.handle = &receivedEventPromise; + eventAdminService.postEvent = [](void* handle, const char* topic, const celix_properties_t* properties) { + EXPECT_STREQ(topic, "asyncEvent"); + EXPECT_STREQ("value", celix_properties_get(properties, "key", "")); + auto promise = static_cast*>(handle); + promise->set_value(); + return CELIX_SUCCESS; + }; + eventAdminService.sendEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + status = celix_earpm_setEventAdminSvc(earpm, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + //publish the async event + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + status = celix_properties_set(eventProps, "key", "value"); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autofree char* payload = nullptr; + status = celix_properties_saveToString(eventProps, 0, &payload); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autoptr(mosquitto_property) mqttProperties = CreateMqttProperties(); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "asyncEvent", + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, mqttProperties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + //wait for the async event + auto futureStatus = receivedEventFuture.wait_for(std::chrono::seconds{30}); + EXPECT_EQ(futureStatus, std::future_status::ready); + + status = celix_earpm_setEventAdminSvc(earpm, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear the retained message from the broker + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "asyncEvent", + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, nullptr); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ProcessSyncEventTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + //subscribe to the sync event + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS;}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_set(props, CELIX_EVENT_TOPIC, "syncEvent"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //add event admin service to receive the async event + static celix_event_admin_service_t eventAdminService; + eventAdminService.handle = nullptr; + eventAdminService.postEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + eventAdminService.sendEvent = [](void*, const char* topic, const celix_properties_t* properties) { + EXPECT_STREQ(topic, "syncEvent"); + EXPECT_STREQ("value", celix_properties_get(properties, "key", "")); + return CELIX_SUCCESS; + }; + status = celix_earpm_setEventAdminSvc(earpm, &eventAdminService); + EXPECT_EQ(status, CELIX_SUCCESS); + + //publish the sync event + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + status = celix_properties_set(eventProps, "key", "value"); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autofree char* payload = nullptr; + status = celix_properties_saveToString(eventProps, 0, &payload); + ASSERT_EQ(status, CELIX_SUCCESS); + celix_autoptr(mosquitto_property) mqttProps = nullptr; + char responseTopic[128]{0}; + snprintf(responseTopic, sizeof(responseTopic), CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX"%s", FAKE_FW_UUID); + auto rc = mosquitto_property_add_string(&mqttProps, MQTT_PROP_RESPONSE_TOPIC, responseTopic); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + long correlationData {0}; + rc = mosquitto_property_add_binary(&mqttProps, MQTT_PROP_CORRELATION_DATA, &correlationData, sizeof(correlationData)); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&mqttProps, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "syncEvent", + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, mqttProps); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + + //wait for the sync event ack that is sent by the remote provider + auto ok = mqttClient->WaitForReceivedMsg(responseTopic); + ASSERT_TRUE(ok); + + status = celix_earpm_setEventAdminSvc(earpm, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_earpm_removeEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + //clear the retained message from the broker + rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, "syncEvent", + 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, true, nullptr); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["subscribedEvent"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_set(eventProps, "key", "value"); + auto status = celix_earpm_postEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto notFoundSubscriber = WaitForLogMessage("No remote handler subscribe", 10); + EXPECT_FALSE(notFoundSubscriber); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventToSubscribeAnyTopicHandlerTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["*"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_set(eventProps, "key", "value"); + auto status = celix_earpm_postEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto notFoundSubscriber = WaitForLogMessage("No remote handler subscribe", 10); + EXPECT_FALSE(notFoundSubscriber); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventToSubscribeTopicPatternHandlerTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["test/*"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_set(eventProps, "key", "value"); + auto status = celix_earpm_postEvent(earpm, "test/Event", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto notFoundSubscriber = WaitForLogMessage("No remote handler subscribe", 10); + EXPECT_FALSE(notFoundSubscriber); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventButNoSubscriberTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_status_t status = celix_earpm_postEvent(earpm, "unsubscribedEvent", nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("No remote handler subscribe"); + EXPECT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventButEventPropertiesNotMatchTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"s({"handler":{"handlerId":123,"topics":["subscribedEvent"], "filter":"(key=value1)"}})s"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_set(eventProps, "key", "value"); + auto status = celix_earpm_postEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("No remote handler subscribe"); + EXPECT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventWithInvalidQosTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_UNKNOWN); + auto status = celix_earpm_postEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_QOS, CELIX_EARPM_QOS_MAX); + status = celix_earpm_postEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmImplTestSuite, PostEventWithInvalidArgsTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + auto status = celix_earpm_postEvent(nullptr, "topic", nullptr);//handle is null + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpm_postEvent(earpm, nullptr, nullptr);//topic is null + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["subscribedEvent"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + ASSERT_NE(eventProps, nullptr); + celix_properties_set(eventProps, "key", "value"); + auto status = celix_earpm_sendEvent(earpm, "subscribedEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("No remote handler subscribe", 10); + EXPECT_FALSE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventWithInvalidArgsTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + auto status = celix_earpm_sendEvent(nullptr, "topic", nullptr);//handle is null + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + + status = celix_earpm_sendEvent(earpm, nullptr, nullptr);//topic is null + EXPECT_EQ(status, CELIX_ILLEGAL_ARGUMENT); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventButNoSubscriberTest) { + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_status_t status = celix_earpm_sendEvent(earpm, "unsubscribedEvent", nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("No remote handler subscribe"); + EXPECT_TRUE(ok); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventButNoAckTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["noAckEvent"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, ETIMEDOUT); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventButAlwaysNoAckTest) { + setenv(CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD, "1", 1); + TestRemoteProvider([this](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["noAckEvent"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, ETIMEDOUT); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, ETIMEDOUT); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + auto ok = WaitForLogMessage("No waiting for the ack"); + EXPECT_TRUE(ok); + }); + unsetenv(CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventAndThenAllRemoteHandlerRemovedTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["noAckEvent"]}})"); + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + std::future futureResult = std::async(std::launch::async, [earpm] { + std::this_thread::sleep_for(std::chrono::milliseconds{30});//Wait for the remote provider to send event + RemoveRemoteHandlerInfoFromRemoteProvider(earpm, 123); + }); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + futureResult.wait(); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventAndThenRemoteHandlerRemovedTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + UpdateRemoteHandlerInfoToRemoteProvider(earpm, R"([{"handlerId":123,"topics":["noAckEvent"]}, {"handlerId":124,"topics":["topic"]}])"); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) != 0; }); + ASSERT_TRUE(ok); + + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + std::future futureResult = std::async(std::launch::async, [earpm] { + std::this_thread::sleep_for(std::chrono::milliseconds{30});//Wait for the remote provider to send event + RemoveRemoteHandlerInfoFromRemoteProvider(earpm, 123); + }); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + futureResult.wait(); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventAndThenAllRemoteHandlerRemovedByUpdateMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + UpdateRemoteHandlerInfoToRemoteProvider(earpm, R"([{"handlerId":123,"topics":["noAckEvent"]}, {"handlerId":124,"topics":["topic"]}])"); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) != 0; }); + ASSERT_TRUE(ok); + + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + std::future futureResult = std::async(std::launch::async, [earpm] { + std::this_thread::sleep_for(std::chrono::milliseconds{30});//Wait for the remote provider to send event + UpdateRemoteHandlerInfoToRemoteProvider(earpm, R"([])"); + }); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + futureResult.wait(); + }); +} + +TEST_F(CelixEarpmImplTestSuite, SendEventAndThenRemoteHandlerRemovedByUpdateMessageTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + UpdateRemoteHandlerInfoToRemoteProvider(earpm, R"([{"handlerId":123,"topics":["noAckEvent"]}, {"handlerId":124,"topics":["topic"]}])"); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) != 0; }); + ASSERT_TRUE(ok); + + celix_autoptr(celix_properties_t) eventProps = celix_properties_create(); + auto status = celix_properties_setLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, 1); + ASSERT_EQ(status, CELIX_SUCCESS); + std::future futureResult = std::async(std::launch::async, [earpm] { + std::this_thread::sleep_for(std::chrono::milliseconds{30});//Wait for the remote provider to send event + UpdateRemoteHandlerInfoToRemoteProvider(earpm, R"([{"handlerId":124,"topics":["topic"]}])"); + }); + status = celix_earpm_sendEvent(earpm, "noAckEvent", eventProps); + EXPECT_EQ(status, CELIX_SUCCESS); + futureResult.wait(); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ExecuteCommandTest) { + TestRemoteProvider([](celix_event_admin_remote_provider_mqtt_t* earpm) { + AddRemoteHandlerInfoToRemoteProviderAndCheck(earpm, R"({"handler":{"handlerId":123,"topics":["subscribedEvent"]}})"); + + celix_event_handler_service_t eventHandlerService; + eventHandlerService.handle = nullptr; + eventHandlerService.handleEvent = [](void*, const char*, const celix_properties_t*) { return CELIX_SUCCESS; }; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_ID, 234); + celix_properties_set(props, CELIX_EVENT_TOPIC, "topic"); + celix_properties_set(props, CELIX_EVENT_FILTER, "(a=b)"); + auto status = celix_earpm_addEventHandlerService(earpm, &eventHandlerService, props); + EXPECT_EQ(status, CELIX_SUCCESS); + + auto res = celix_earpm_executeCommand(earpm, "celix::earpm", stdout, stderr); + EXPECT_TRUE(res); + }); +} + +TEST_F(CelixEarpmImplTestSuite, ExecuteCommandFailedTest) { + auto earpm = celix_earpm_create(ctx.get()); + ASSERT_NE(earpm, nullptr); + + auto res = celix_earpm_executeCommand(earpm, "celix::earpm unexpectedSubCmd", stdout, stderr); + EXPECT_FALSE(res); + + celix_earpm_destroy(earpm); +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuiteBaseClass.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuiteBaseClass.h new file mode 100644 index 000000000..a9be7ee87 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmImplTestSuiteBaseClass.h @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_CELIXEARPMIMPLTESTSUITEBASECLASS_H +#define CELIX_CELIXEARPMIMPLTESTSUITEBASECLASS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "celix_stdlib_cleanup.h" +extern "C" { +#include "endpoint_description.h" +} +#include "remote_constants.h" +#include "celix_event_handler_service.h" +#include "celix_event_admin_service.h" +#include "celix_event_constants.h" +#include "celix_earpm_impl.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_broker_discovery.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "CelixEarpmTestSuiteBaseClass.h" + + +namespace { + constexpr const char* MQTT_BROKER_ADDRESS = "127.0.0.1"; + constexpr int MQTT_BROKER_PORT = 1883; + constexpr const char* FAKE_FW_UUID = "5936e9f4-c4a8-4fa8-b070-65d03a6d4d03"; +} + +class MqttClient { +public: + explicit MqttClient(std::vector subTopics) : subTopics{std::move(subTopics)} { + mosq = std::shared_ptr{mosquitto_new(nullptr, true, this), [](mosquitto* m) { mosquitto_destroy(m); }}; + EXPECT_NE(mosq, nullptr); + auto rc = mosquitto_int_option(mosq.get(), MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + mosquitto_connect_callback_set(mosq.get(), [](mosquitto*, void* handle, int rc) { + auto client = static_cast(handle); + ASSERT_EQ(rc, MQTT_RC_SUCCESS); + for (const auto& topic : client->subTopics) { + auto ret = mosquitto_subscribe_v5(client->mosq.get(), nullptr, topic.c_str(), 0, MQTT_SUB_OPT_NO_LOCAL, nullptr); + ASSERT_EQ(ret, MOSQ_ERR_SUCCESS); + } + }); + mosquitto_subscribe_v5_callback_set(mosq.get(), [](mosquitto*, void* handle, int, int, const int*, const mosquitto_property*) { + auto client = static_cast(handle); + std::lock_guard lock(client->mutex); + client->subscribedCnt++; + client->subscribedCond.notify_all(); + }); + mosquitto_message_v5_callback_set(mosq.get(), [](mosquitto*, void* handle, const mosquitto_message* msg, const mosquitto_property* props) { + auto client = static_cast(handle); + { + std::lock_guard lock(client->mutex); + client->receivedMsgTopics.emplace_back(msg->topic); + client->receivedMsgCond.notify_all(); + } + celix_autofree char* responseTopic{nullptr}; + celix_autofree void* correlationData{nullptr}; + uint16_t correlationDataSize = 0; + mosquitto_property_read_string(props, MQTT_PROP_RESPONSE_TOPIC, &responseTopic, false); + mosquitto_property_read_binary(props, MQTT_PROP_CORRELATION_DATA, &correlationData, &correlationDataSize, false); + if (responseTopic != nullptr) { + celix_autoptr(mosquitto_property) responseProps{nullptr}; + if (correlationData) { + auto rc = mosquitto_property_add_binary(&responseProps, MQTT_PROP_CORRELATION_DATA, correlationData, correlationDataSize); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + } + auto rc = mosquitto_property_add_string_pair(&responseProps, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", FAKE_FW_UUID); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&responseProps, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", "1.0.0"); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_publish_v5(client->mosq.get(), nullptr, responseTopic, 0, nullptr, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, responseProps); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + } + }); + } + + ~MqttClient() = default; + + void MqttClientStart() { + for (int i = 0; i < 512; ++i) { + if(mosquitto_connect_bind_v5(mosq.get(), MQTT_BROKER_ADDRESS, MQTT_BROKER_PORT, 60, nullptr, nullptr) == MOSQ_ERR_SUCCESS) { + auto rc = mosquitto_loop_start(mosq.get()); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + std::unique_lock lock(mutex); + auto subscribed = subscribedCond.wait_for(lock, std::chrono::seconds{60}, [this] { return subscribedCnt >= subTopics.size(); }); + ASSERT_TRUE(subscribed); + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds {100}); + } + ADD_FAILURE() << "Failed to start mqtt client"; + } + + void MqttClientStop() { + mosquitto_disconnect(mosq.get()); + mosquitto_loop_stop(mosq.get(), false); + } + + bool WaitForReceivedMsg(const std::string& topic, std::chrono::milliseconds timeout = std::chrono::milliseconds{30*1000}) { + std::unique_lock lock(mutex); + return receivedMsgCond.wait_for(lock, timeout, [&topic, this] { + return std::find(receivedMsgTopics.rbegin(), receivedMsgTopics.rend(), topic) != receivedMsgTopics.rend(); + }); + } + + void Reset() { + std::lock_guard lock(mutex); + receivedMsgTopics.clear(); + } + + std::shared_ptr mosq{nullptr}; + std::vector subTopics{}; + std::mutex mutex{}; + std::condition_variable subscribedCond{}; + size_t subscribedCnt{0}; + std::condition_variable receivedMsgCond{}; + std::vector receivedMsgTopics{}; + +}; + +class CelixEarpmImplTestSuiteBaseClass : public CelixEarpmTestSuiteBaseClass { +public: + static void SetUpTestSuite() { + mosquitto_lib_init(); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + execlp("mosquitto", "mosquitto", "-p", std::to_string(MQTT_BROKER_PORT).c_str(), nullptr); + ADD_FAILURE() << "Failed to start mosquitto"; + } + mqttClient = new MqttClient{std::vector{"subscribedEvent", CELIX_EARPM_TOPIC_PREFIX"#"}}; + mqttClient->MqttClientStart(); + } + + static void TearDownTestSuite() { + mqttClient->MqttClientStop(); + delete mqttClient; + kill(pid, SIGKILL); + waitpid(pid, nullptr, 0); + mosquitto_lib_cleanup(); + } + + void SetUp() override { + mqttClient->Reset(); + } + + explicit CelixEarpmImplTestSuiteBaseClass(const char* testCache = ".earpm_impl_test_cache") : CelixEarpmTestSuiteBaseClass{testCache} { } + + ~CelixEarpmImplTestSuiteBaseClass() override = default; + + static endpoint_description_t* CreateMqttBrokerEndpoint(void) { + auto props = celix_properties_create(); + EXPECT_NE(props, nullptr); + celix_properties_setLong(props, CELIX_RSA_ENDPOINT_SERVICE_ID, INT32_MAX); + celix_properties_set(props, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME); + celix_properties_set(props, CELIX_RSA_ENDPOINT_FRAMEWORK_UUID, "9d5b0a58-1e6f-4c4f-bd00-7298914b1a76"); + uuid_t uid; + uuid_generate(uid); + char endpointUUID[37]; + uuid_unparse_lower(uid, endpointUUID); + celix_properties_set(props, CELIX_RSA_ENDPOINT_ID, endpointUUID); + celix_properties_setBool(props, CELIX_RSA_SERVICE_IMPORTED, true); + celix_properties_set(props, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE); + celix_properties_set(props, CELIX_EARPM_MQTT_BROKER_ADDRESS, MQTT_BROKER_ADDRESS); + celix_properties_setLong(props, CELIX_EARPM_MQTT_BROKER_PORT, MQTT_BROKER_PORT); + endpoint_description_t* endpoint = nullptr; + auto status = endpointDescription_create(props, &endpoint); + EXPECT_EQ(status, CELIX_SUCCESS); + return endpoint; + } + + void TestRemoteProvider(const std::function& testBody) { + auto earpm = celix_earpm_create(ctx.get()); + + celix_autoptr(endpoint_description_t) endpoint = CreateMqttBrokerEndpoint(); + auto status = celix_earpm_mqttBrokerEndpointAdded(earpm, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + auto connected = WaitForRemoteProviderConnectToBroker(); + ASSERT_TRUE(connected); + + testBody(earpm); + + status = celix_earpm_mqttBrokerEndpointRemoved(earpm, endpoint, nullptr); + EXPECT_EQ(status, CELIX_SUCCESS); + + celix_earpm_destroy(earpm); + } + + static mosquitto_property* CreateMqttProperties(const char* senderUUID = FAKE_FW_UUID, const char* msgVersion = "1.0.0") { + celix_autoptr(mosquitto_property) properties = nullptr; + auto rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_SENDER_UUID", senderUUID); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + rc = mosquitto_property_add_string_pair(&properties, MQTT_PROP_USER_PROPERTY, "CELIX_EARPM_MSG_VERSION", msgVersion); + EXPECT_EQ(rc, MOSQ_ERR_SUCCESS); + return celix_steal_ptr(properties); + } + + static void AddRemoteHandlerInfoToRemoteProviderAndCheck(celix_event_admin_remote_provider_mqtt_t* earpm, const char* handlerInfo, const char* senderUUID = FAKE_FW_UUID) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(senderUUID); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_ADD_TOPIC, + (int)strlen(handlerInfo), handlerInfo, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + auto ok = WaitFor([earpm] { return celix_earpm_currentRemoteFrameworkCount(earpm) != 0; });//Wait for receive the handler info message + ASSERT_TRUE(ok); + } + + static void RemoveRemoteHandlerInfoFromRemoteProvider(celix_event_admin_remote_provider_mqtt_t*, long handlerServiceId, const char* senderUUID = FAKE_FW_UUID) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(senderUUID); + char payload[128]{0}; + snprintf(payload, sizeof(payload), R"({"handlerId":%ld})", handlerServiceId); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + } + + static void UpdateRemoteHandlerInfoToRemoteProvider(celix_event_admin_remote_provider_mqtt_t*, const char* handlers, const char* senderUUID = FAKE_FW_UUID) { + celix_autoptr(mosquitto_property) properties = CreateMqttProperties(senderUUID); + char payload[1024]{0}; + snprintf(payload, sizeof(payload), R"({"handlers":%s})", handlers); + auto rc = mosquitto_publish_v5(mqttClient->mosq.get(), nullptr, CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC, + (int)strlen(payload), payload, CELIX_EARPM_QOS_AT_MOST_ONCE, false, properties); + ASSERT_EQ(rc, MOSQ_ERR_SUCCESS); + } + + static bool WaitForRemoteProviderConnectToBroker(void) { + //When the remote provider is connected, it will send an update handler info message + return mqttClient->WaitForReceivedMsg(CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC); + } + + static bool WaitFor(const std::function& cond) { + int remainTries = 3000; + while (!cond() && remainTries > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds{10});//Wait for condition + remainTries--; + } + return remainTries > 0; + } + + static pid_t pid; + static MqttClient* mqttClient; +}; +pid_t CelixEarpmImplTestSuiteBaseClass::pid{0}; +MqttClient* CelixEarpmImplTestSuiteBaseClass::mqttClient{nullptr}; + + + +#endif //CELIX_CELIXEARPMIMPLTESTSUITEBASECLASS_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmIntegrationTestSuite.cc b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmIntegrationTestSuite.cc new file mode 100644 index 000000000..0d552c7cf --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmIntegrationTestSuite.cc @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include +#include +#include +#include +#include + +#include "celix_framework_utils.h" +#include "celix_constants.h" +#include "celix_log_constants.h" +#include "celix_bundle_context.h" +#include "celix_framework_factory.h" +#include "celix_event_constants.h" +#include "celix_event_admin_service.h" +#include "celix_event_handler_service.h" +#include "celix_earpm_constants.h" + + +class CelixEarpmIntegrationTestSuite : public ::testing::Test { +public: + static void SetUpTestSuite() { + mosquitto_lib_init(); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + execlp("mosquitto", "mosquitto", "-c", MOSQUITTO_CONF, nullptr); + ADD_FAILURE() << "Failed to start mosquitto"; + } + } + static void TearDownTestSuite() { + kill(pid, SIGKILL); + waitpid(pid, nullptr, 0); + mosquitto_lib_cleanup(); + } + CelixEarpmIntegrationTestSuite() { + { + auto props = celix_properties_create(); + celix_properties_set(props, CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, "true"); + celix_properties_set(props, CELIX_FRAMEWORK_CACHE_DIR, ".earpm_publisher"); + celix_properties_set(props, CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL_CONFIG_NAME, "trace"); + celix_properties_set(props, CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF); + publisherFw = std::shared_ptr{celix_frameworkFactory_createFramework(props), + celix_frameworkFactory_destroyFramework}; + publisherCtx = std::shared_ptr{celix_framework_getFrameworkContext(publisherFw.get()), + [](celix_bundle_context_t *){/*nop*/}}; + celix_framework_utils_installBundleSet(publisherFw.get(), INTEGRATED_BUNDLES, true); + } + { + auto props = celix_properties_create(); + celix_properties_set(props, CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, "true"); + celix_properties_set(props, CELIX_FRAMEWORK_CACHE_DIR, ".earpm_subscriber"); + celix_properties_set(props, CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL_CONFIG_NAME, "trace"); + celix_properties_set(props, CELIX_EARPM_BROKER_PROFILE, MOSQUITTO_CONF); + subscriberFw = std::shared_ptr{celix_frameworkFactory_createFramework(props), + celix_frameworkFactory_destroyFramework}; + subscriberCtx = std::shared_ptr{ + celix_framework_getFrameworkContext(subscriberFw.get()), [](celix_bundle_context_t *) {/*nop*/}}; + celix_framework_utils_installBundleSet(subscriberFw.get(), INTEGRATED_BUNDLES, true); + } + } + + ~CelixEarpmIntegrationTestSuite() override = default; + + void TestEventPublish(bool testAsyncEvent) { + std::promise receivedEventPromise; + std::future receivedEventFuture = receivedEventPromise.get_future(); + auto props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "testEvent"); + static celix_event_handler_service_t handler = { + .handle = &receivedEventPromise, + .handleEvent = [](void* handle, const char* topic, const celix_properties_t* properties) { + EXPECT_STREQ("testEvent", topic); + EXPECT_STREQ("value", celix_properties_get(properties, "key", "")); + auto promise = static_cast *>(handle); + try { + promise->set_value(); + } catch (...) { + //ignore + } + return CELIX_SUCCESS; + } + }; + celix_service_registration_options_t opts{}; + opts.svc = &handler; + opts.serviceName = CELIX_EVENT_HANDLER_SERVICE_NAME; + opts.serviceVersion = CELIX_EVENT_HANDLER_SERVICE_VERSION; + opts.properties = props; + long handlerServiceId = celix_bundleContext_registerServiceWithOptions(subscriberCtx.get(), &opts); + ASSERT_GE(handlerServiceId, 0); + + struct use_service_callback_handle { + bool publishAsyncEvent; + std::future& future; + }; + struct use_service_callback_handle handle{testAsyncEvent, receivedEventFuture}; + celix_service_use_options_t useOpts{}; + useOpts.filter.serviceName = CELIX_EVENT_ADMIN_SERVICE_NAME; + useOpts.filter.versionRange = CELIX_EVENT_ADMIN_SERVICE_USE_RANGE; + useOpts.callbackHandle = &handle; + useOpts.use = [](void* handle, void* svc) { + celix_event_admin_service_t *eventAdmin = static_cast(svc); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_EVENT_TOPIC, "testEvent"); + celix_properties_set(props, "key", "value"); + celix_properties_setBool(props, CELIX_EVENT_REMOTE_ENABLE, true); + auto callbackHandle = static_cast(handle); + int tryCount = 300; + while (tryCount-- > 0) {//wait remote handler online, and try again. + auto status = callbackHandle->publishAsyncEvent ? + eventAdmin->postEvent(eventAdmin->handle, "testEvent", props) : + eventAdmin->sendEvent(eventAdmin->handle, "testEvent", props); + EXPECT_EQ(CELIX_SUCCESS, status); + auto futureStatus = callbackHandle->future.wait_for(std::chrono::milliseconds{100}); + if (futureStatus == std::future_status::ready) { + break; + } + } + EXPECT_TRUE(tryCount >= 0); + }; + auto found= celix_bundleContext_useServiceWithOptions(publisherCtx.get(), &useOpts); + ASSERT_TRUE(found); + + celix_bundleContext_unregisterService(subscriberCtx.get(), handlerServiceId); + } + + static pid_t pid; + std::shared_ptr publisherFw{}; + std::shared_ptr publisherCtx{}; + std::shared_ptr subscriberFw{}; + std::shared_ptr subscriberCtx{}; +}; +pid_t CelixEarpmIntegrationTestSuite::pid{0}; + +TEST_F(CelixEarpmIntegrationTestSuite, SendEvent) { + TestEventPublish(false); +} + +TEST_F(CelixEarpmIntegrationTestSuite, PostEvent) { + TestEventPublish(true); +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmTestSuiteBaseClass.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmTestSuiteBaseClass.h new file mode 100644 index 000000000..d2ac3604d --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/src/CelixEarpmTestSuiteBaseClass.h @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_TEST_SUITE_BASE_CLASS_H +#define CELIX_EARPM_TEST_SUITE_BASE_CLASS_H + +#include +#include +#include +#include +#include + +#include "celix_bundle_context.h" +#include "celix_framework_factory.h" +#include "celix_constants.h" +#include "celix_log_constants.h" +#include "celix_log_service.h" +#include "celix_log_utils.h" + + +class CelixEarpmTestSuiteBaseClass : public ::testing::Test { +public: + CelixEarpmTestSuiteBaseClass() = delete; + explicit CelixEarpmTestSuiteBaseClass(const char* testCache, const char* logServiceName = "celix_earpm") { + auto props = celix_properties_create(); + celix_properties_set(props, CELIX_FRAMEWORK_CLEAN_CACHE_DIR_ON_CREATE, "true"); + celix_properties_set(props, CELIX_FRAMEWORK_CACHE_DIR, testCache); + celix_properties_set(props, CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL_CONFIG_NAME, "trace"); + auto fwPtr = celix_frameworkFactory_createFramework(props); + fw = std::shared_ptr < celix_framework_t > + {fwPtr, [](celix_framework_t *f) { celix_frameworkFactory_destroyFramework(f); }}; + ctx = std::shared_ptr < celix_bundle_context_t > + {celix_framework_getFrameworkContext(fw.get()), [](celix_bundle_context_t *) {/*nop*/}}; + fwUUID = celix_bundleContext_getProperty(ctx.get(), CELIX_FRAMEWORK_UUID, ""); + logServiceName_ = logServiceName; + logService.handle = this; + logService.vlogDetails = [](void *handle, celix_log_level_e level, const char* file, const char* function, + int line, const char* format, va_list formatArgs) { + auto self = static_cast(handle); + char log[1024]{0}; + va_list formatArgsCopy; + va_copy(formatArgsCopy, formatArgs); + vsnprintf(log, sizeof(log), format, formatArgsCopy); + celix_logUtils_vLogToStdoutDetails(self->logServiceName_.c_str(), level, file, function, line, format, formatArgs); + std::lock_guard lockGuard{self->logMessagesMutex}; + self->logMessages.emplace_back(log); + self->logMessagesCond.notify_all(); + }; + celix_service_registration_options_t opts{}; + opts.svc = &logService; + opts.serviceName = CELIX_LOG_SERVICE_NAME; + opts.serviceVersion = CELIX_LOG_SERVICE_VERSION; + opts.properties = celix_properties_create(); + EXPECT_NE(nullptr, opts.properties); + celix_properties_set(opts.properties, CELIX_LOG_SERVICE_PROPERTY_NAME, logServiceName); + logServiceId = celix_bundleContext_registerServiceWithOptions(ctx.get(), &opts); + EXPECT_LE(0, logServiceId); + } + + ~CelixEarpmTestSuiteBaseClass() override { + celix_bundleContext_unregisterService(ctx.get(), logServiceId); + } + + auto WaitForLogMessage(const std::string &msg, int timeoutInMs = 30000) { + std::unique_lock lock{logMessagesMutex}; + return logMessagesCond.wait_for(lock, std::chrono::milliseconds{timeoutInMs}, [&] { + return std::find_if(logMessages.rbegin(), logMessages.rend(), [msg](const std::string &m) { + return m.find(msg) != std::string::npos; + }) != logMessages.rend(); + }); + } + + std::shared_ptr fw{}; + std::shared_ptr ctx{}; + std::string fwUUID{}; + std::string logServiceName_{}; + celix_log_service_t logService{}; + long logServiceId{-1}; + std::vector logMessages{}; + std::mutex logMessagesMutex{}; + std::condition_variable logMessagesCond{}; +}; + +#endif //CELIX_EARPM_TEST_SUITE_BASE_CLASS_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_activator.c b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_activator.c new file mode 100644 index 000000000..78d903500 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_activator.c @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include + +#include "celix_errno.h" +#include "celix_shell_command.h" +#include "celix_bundle_activator.h" +#include "celix_dm_component.h" +#include "celix_dm_service_dependency.h" +#include "endpoint_listener.h" +#include "remote_constants.h" +#include "celix_event_constants.h" +#include "celix_event_admin_service.h" +#include "celix_event_handler_service.h" +#include "celix_event_remote_provider_service.h" +#include "celix_earpm_impl.h" +#include "celix_earpm_broker_discovery.h" + +typedef struct celix_event_admin_remote_provider_mqtt_activator { + celix_bundle_context_t *ctx; + celix_log_helper_t *logHelper; + celix_earpm_broker_discovery_t* brokerDiscovery; + celix_event_admin_remote_provider_mqtt_t* providerMqtt; + celix_event_remote_provider_service_t providerSvc; + endpoint_listener_t endpointListener; + celix_shell_command_t cmdSvc; +} celix_event_admin_remote_provider_mqtt_activator_t; + +static celix_status_t celix_eventAdminRemoteProviderMqttActivator_start(celix_event_admin_remote_provider_mqtt_activator_t *act, celix_bundle_context_t *ctx) { + assert(act != NULL); + assert(ctx != NULL); + act->ctx = ctx; + celix_autoptr(celix_dm_component_t) earpmDiscoveryCmp = celix_dmComponent_create(ctx, "CELIX_EARPM_DISCOVERY_CMP"); + if (earpmDiscoveryCmp == NULL) { + return ENOMEM; + } + act->brokerDiscovery = celix_earpmDiscovery_create(ctx); + if (act->brokerDiscovery == NULL) { + return CELIX_BUNDLE_EXCEPTION; + } + celix_dmComponent_setImplementation(earpmDiscoveryCmp, act->brokerDiscovery); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(earpmDiscoveryCmp, celix_earpm_broker_discovery_t, celix_earpmDiscovery_destroy); + + { + celix_autoptr(celix_dm_service_dependency_t) endpointListenerDep = celix_dmServiceDependency_create(); + if (endpointListenerDep == NULL) { + return ENOMEM; + } + celix_status_t status = celix_dmServiceDependency_setService(endpointListenerDep, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME, NULL, NULL); + if (status != CELIX_SUCCESS) { + return status; + } + celix_dmServiceDependency_setStrategy(endpointListenerDep, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING); + celix_dm_service_dependency_callback_options_t opts = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts.addWithProps = celix_earpmDiscovery_addEndpointListener; + opts.removeWithProps = celix_earpmDiscovery_removeEndpointListener; + celix_dmServiceDependency_setCallbacksWithOptions(endpointListenerDep, &opts); + status = celix_dmComponent_addServiceDependency(earpmDiscoveryCmp, endpointListenerDep); + if (status != CELIX_SUCCESS) { + return status; + } + celix_steal_ptr(endpointListenerDep); + } + + celix_autoptr(celix_dm_component_t) earpmCmp = celix_dmComponent_create(ctx, "CELIX_EARPM_CMP"); + if (earpmCmp == NULL) { + return ENOMEM; + } + act->providerMqtt = celix_earpm_create(ctx); + if (act->providerMqtt == NULL) { + return CELIX_BUNDLE_EXCEPTION; + } + celix_dmComponent_setImplementation(earpmCmp, act->providerMqtt); + CELIX_DM_COMPONENT_SET_IMPLEMENTATION_DESTROY_FUNCTION(earpmCmp, celix_event_admin_remote_provider_mqtt_t, celix_earpm_destroy); + + { + celix_autoptr(celix_dm_service_dependency_t) eventHandlerDep = celix_dmServiceDependency_create(); + if (eventHandlerDep == NULL) { + return ENOMEM; + } + celix_status_t status = celix_dmServiceDependency_setService(eventHandlerDep, CELIX_EVENT_HANDLER_SERVICE_NAME, CELIX_EVENT_HANDLER_SERVICE_USE_RANGE, "("CELIX_EVENT_TOPIC"=*)");//Event Handlers which have not specified the EVENT_TOPIC service property must not receive events. + if (status != CELIX_SUCCESS) { + return status; + } + celix_dmServiceDependency_setStrategy(eventHandlerDep, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING); + celix_dm_service_dependency_callback_options_t opts = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts.addWithProps = celix_earpm_addEventHandlerService; + opts.removeWithProps = celix_earpm_removeEventHandlerService; + celix_dmServiceDependency_setCallbacksWithOptions(eventHandlerDep, &opts); + status = celix_dmComponent_addServiceDependency(earpmCmp, eventHandlerDep); + if (status != CELIX_SUCCESS) { + return status; + } + celix_steal_ptr(eventHandlerDep); + } + + { + celix_autoptr(celix_dm_service_dependency_t) eventAdminDep = celix_dmServiceDependency_create(); + if (eventAdminDep == NULL) { + return ENOMEM; + } + celix_status_t status = celix_dmServiceDependency_setService(eventAdminDep, CELIX_EVENT_ADMIN_SERVICE_NAME, + CELIX_EVENT_ADMIN_SERVICE_USE_RANGE, NULL); + if (status != CELIX_SUCCESS) { + return status; + } + celix_dmServiceDependency_setStrategy(eventAdminDep, DM_SERVICE_DEPENDENCY_STRATEGY_LOCKING); + celix_dm_service_dependency_callback_options_t opts = CELIX_EMPTY_DM_SERVICE_DEPENDENCY_CALLBACK_OPTIONS; + opts.set = celix_earpm_setEventAdminSvc; + celix_dmServiceDependency_setCallbacksWithOptions(eventAdminDep, &opts); + status = celix_dmComponent_addServiceDependency(earpmCmp, eventAdminDep); + if (status != CELIX_SUCCESS) { + return status; + } + celix_steal_ptr(eventAdminDep); + } + + act->providerSvc.handle = act->providerMqtt; + act->providerSvc.postEvent = celix_earpm_postEvent; + act->providerSvc.sendEvent = celix_earpm_sendEvent; + celix_status_t status = celix_dmComponent_addInterface(earpmCmp, CELIX_EVENT_REMOTE_PROVIDER_SERVICE_NAME, + CELIX_EVENT_REMOTE_PROVIDER_SERVICE_VERSION, &act->providerSvc, NULL); + if (status != CELIX_SUCCESS) { + return status; + } + + act->endpointListener.handle = act->providerMqtt; + act->endpointListener.endpointAdded = celix_earpm_mqttBrokerEndpointAdded; + act->endpointListener.endpointRemoved = celix_earpm_mqttBrokerEndpointRemoved; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + if (props == NULL) { + return ENOMEM; + } + const char* scope = "(&("CELIX_FRAMEWORK_SERVICE_NAME"="CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME")("\ + CELIX_RSA_SERVICE_IMPORTED_CONFIGS"="CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE"))"; + status = celix_properties_set(props, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, scope); + if (status != CELIX_SUCCESS) { + return status; + } + status = celix_properties_setBool(props, CELIX_RSA_DISCOVERY_INTERFACE_SPECIFIC_ENDPOINTS_SUPPORT, true); + if (status != CELIX_SUCCESS) { + return status; + } + status = celix_dmComponent_addInterface(earpmCmp, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME, NULL, &act->endpointListener, + celix_steal_ptr(props)); + if (status != CELIX_SUCCESS) { + return status; + } + + act->cmdSvc.handle = act->providerMqtt; + act->cmdSvc.executeCommand = celix_earpm_executeCommand; + celix_autoptr(celix_properties_t) cmdProps = celix_properties_create(); + if (cmdProps == NULL) { + return ENOMEM; + } + status = celix_properties_set(cmdProps, CELIX_SHELL_COMMAND_NAME, "celix::earpm"); + if (status != CELIX_SUCCESS) { + return status; + } + (void)celix_properties_set(cmdProps, CELIX_SHELL_COMMAND_USAGE, "celix::earpm"); + (void)celix_properties_set(cmdProps, CELIX_SHELL_COMMAND_DESCRIPTION, "Show the status of the Event Admin Remote Provider Mqtt."); + status = celix_dmComponent_addInterface(earpmCmp, CELIX_SHELL_COMMAND_SERVICE_NAME, CELIX_SHELL_COMMAND_SERVICE_VERSION, + &act->cmdSvc, celix_steal_ptr(cmdProps)); + if (status != CELIX_SUCCESS) { + return status; + } + + celix_dependency_manager_t* mng = celix_bundleContext_getDependencyManager(ctx); + if (mng == NULL) { + return ENOMEM; + } + status = celix_dependencyManager_addAsync(mng, earpmDiscoveryCmp); + if (status != CELIX_SUCCESS) { + return status; + } + status = celix_dependencyManager_addAsync(mng, earpmCmp); + if (status != CELIX_SUCCESS) { + celix_dependencyManager_removeAsync(mng, celix_steal_ptr(earpmDiscoveryCmp), NULL, NULL); + return status; + } + celix_steal_ptr(earpmDiscoveryCmp); + celix_steal_ptr(earpmCmp); + return CELIX_SUCCESS; +} + +CELIX_GEN_BUNDLE_ACTIVATOR(celix_event_admin_remote_provider_mqtt_activator_t, celix_eventAdminRemoteProviderMqttActivator_start, NULL); \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.c b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.c new file mode 100644 index 000000000..283306870 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.c @@ -0,0 +1,461 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_earpm_broker_discovery.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "celix_stdlib_cleanup.h" +#include "celix_stdio_cleanup.h" +#include "celix_utils.h" +#include "celix_array_list.h" +#include "celix_long_hash_map.h" +#include "celix_threads.h" +#include "celix_log_helper.h" +#include "celix_framework.h" +#include "celix_constants.h" +#include "endpoint_listener.h" +#include "endpoint_description.h" +#include "remote_constants.h" +#include "celix_earpm_constants.h" + +#define CELIX_EARPM_LOAD_PROFILE_INTERVAL 2 //seconds +#define CELIX_EARPM_LOAD_PROFILE_TRIES_MAX (600/CELIX_EARPM_LOAD_PROFILE_INTERVAL) //10 minutes + + +typedef struct celix_earpm_broker_listener { + char* host; + uint16_t port; + char* bindInterface; + int family; +} celix_earpm_broker_listener_t; + +typedef struct celix_earpm_endpoint_listener_entry { + endpoint_listener_t* listener; + const celix_properties_t* properties; + long serviceId; + celix_filter_t* filter; +} celix_earpm_endpoint_listener_entry_t; + +struct celix_earpm_broker_discovery { + celix_bundle_context_t* ctx; + celix_log_helper_t* logHelper; + const char* fwUUID; + const char* brokerProfilePath; + long profileScheduledEventId; + int loadProfileTries; + celix_thread_mutex_t mutex;//protects below + celix_long_hash_map_t* endpointListeners;//key:long, value:celix_earpm_endpoint_listener_entry_t* + celix_array_list_t* brokerEndpoints;//element:endpoint_description_t* +}; + +static void celix_earpmDiscovery_destroyEndpointListenerEntry(celix_earpm_endpoint_listener_entry_t* elEntry); +static void celix_earpmDiscovery_onProfileScheduledEvent(void* data); + +celix_earpm_broker_discovery_t* celix_earpmDiscovery_create(celix_bundle_context_t* ctx) { + assert(ctx != NULL); + celix_autoptr(celix_log_helper_t) logHelper = celix_logHelper_create(ctx, "celix_earpm_discovery"); + if (logHelper == NULL) { + return NULL; + } + celix_autofree celix_earpm_broker_discovery_t* discovery = calloc(1, sizeof(*discovery)); + if (discovery == NULL) { + celix_logHelper_error(logHelper, "Failed to allocate memory for celix earpm broker discovery."); + return NULL; + } + discovery->fwUUID = celix_bundleContext_getProperty(ctx, CELIX_FRAMEWORK_UUID, NULL); + if (discovery->fwUUID == NULL) { + celix_logHelper_error(logHelper, "Failed to get framework uuid from context."); + return NULL; + } + discovery->ctx = ctx; + discovery->logHelper = logHelper; + discovery->profileScheduledEventId = -1; + discovery->loadProfileTries = 0; + discovery->brokerProfilePath = celix_bundleContext_getProperty(ctx, CELIX_EARPM_BROKER_PROFILE, CELIX_EARPM_BROKER_PROFILE_DEFAULT); + discovery->brokerEndpoints = NULL; + celix_status_t status = celixThreadMutex_create(&discovery->mutex, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create mutex for celix earpm broker discovery."); + return NULL; + } + celix_autoptr(celix_thread_mutex_t) mutex = &discovery->mutex; + celix_long_hash_map_create_options_t opts = CELIX_EMPTY_LONG_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void*)celix_earpmDiscovery_destroyEndpointListenerEntry; + celix_autoptr(celix_long_hash_map_t) endpointListeners = discovery->endpointListeners = celix_longHashMap_createWithOptions(&opts); + if (discovery->endpointListeners == NULL) { + celix_logHelper_error(logHelper, "Failed to create endpoint listeners map for celix earpm broker discovery."); + return NULL; + } + + celix_scheduled_event_options_t eventOpts = CELIX_EMPTY_SCHEDULED_EVENT_OPTIONS; + eventOpts.name = "MqttProfileCheckEvent"; + eventOpts.intervalInSeconds = CELIX_EARPM_LOAD_PROFILE_INTERVAL; + eventOpts.callbackData = discovery; + eventOpts.callback =celix_earpmDiscovery_onProfileScheduledEvent; + discovery->profileScheduledEventId = celix_bundleContext_scheduleEvent(discovery->ctx, &eventOpts); + if (discovery->profileScheduledEventId < 0) { + celix_logHelper_error(logHelper, "Failed to schedule profile check event."); + return NULL; + } + + celix_steal_ptr(endpointListeners); + celix_steal_ptr(mutex); + celix_steal_ptr(logHelper); + + return celix_steal_ptr(discovery); +} + +void celix_earpmDiscovery_destroy(celix_earpm_broker_discovery_t* discovery) { + assert(discovery != NULL); + if (celix_framework_isCurrentThreadTheEventLoop(celix_bundleContext_getFramework(discovery->ctx))) { + (void)celix_bundleContext_removeScheduledEventAsync(discovery->ctx, __atomic_load_n(&discovery->profileScheduledEventId, __ATOMIC_RELAXED)); + } else { + (void)celix_bundleContext_removeScheduledEvent(discovery->ctx, __atomic_load_n(&discovery->profileScheduledEventId, __ATOMIC_RELAXED)); + } + celix_longHashMap_destroy(discovery->endpointListeners); + celix_arrayList_destroy(discovery->brokerEndpoints); + celixThreadMutex_destroy(&discovery->mutex); + celix_logHelper_destroy(discovery->logHelper); + free(discovery); +} + +static void celix_earpmDiscovery_notifyEndpointsToListener(celix_earpm_broker_discovery_t* discovery, celix_earpm_endpoint_listener_entry_t* listenerEntry, bool added) { + celix_status_t (*process)(void *handle, endpoint_description_t *endpoint, char *matchedFilter) = listenerEntry->listener->endpointAdded; + if (!added) { + process = listenerEntry->listener->endpointRemoved; + } + bool discoverySupportsDynamicIp = celix_properties_getAsBool(listenerEntry->properties, CELIX_RSA_DISCOVERY_INTERFACE_SPECIFIC_ENDPOINTS_SUPPORT, false); + const char* listenerScope = celix_properties_get(listenerEntry->properties, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, NULL); + int size = discovery->brokerEndpoints == NULL ? 0 : celix_arrayList_size(discovery->brokerEndpoints); + for (int i = 0; i < size; ++i) { + endpoint_description_t *endpoint = celix_arrayList_get(discovery->brokerEndpoints, i); + bool needDynamicIp = celix_properties_get(endpoint->properties, CELIX_RSA_IP_ADDRESSES, NULL) != NULL; + if ((!needDynamicIp || discoverySupportsDynamicIp) && celix_filter_match(listenerEntry->filter, endpoint->properties)) { + celix_status_t status = process(listenerEntry->listener->handle, endpoint, (char*)listenerScope); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(discovery->logHelper, "Failed to %s endpoint to listener(%ld), %d.", added ? "add" : "remove", listenerEntry->serviceId, status); + } + } + } + return; +} + +static void celix_earpmDiscovery_destroyEndpointListenerEntry(celix_earpm_endpoint_listener_entry_t* elEntry) { + celix_filter_destroy(elEntry->filter); + free(elEntry); +} + +celix_status_t celix_earpmDiscovery_addEndpointListener(void* handle, void* service, const celix_properties_t* properties) { + celix_earpm_broker_discovery_t* discovery = handle; + assert(discovery != NULL); + long serviceId = celix_properties_getAsLong(properties, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId < 0) { + celix_logHelper_error(discovery->logHelper, "Failed to get service id from properties"); + return CELIX_ILLEGAL_ARGUMENT; + } + const char* listenerScope = celix_properties_get(properties, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, NULL); + celix_autoptr(celix_filter_t) filter = NULL; + if (listenerScope != NULL) { + filter = celix_filter_create(listenerScope); + if (filter == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to create filter for listener scope %s", listenerScope); + return ENOMEM; + } + } + celix_autofree celix_earpm_endpoint_listener_entry_t* svcEntry = calloc(1, sizeof(*svcEntry)); + if (svcEntry == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to allocate memory for endpoint listener entry"); + return ENOMEM; + } + svcEntry->serviceId = serviceId; + svcEntry->listener = service; + svcEntry->properties = properties; + svcEntry->filter = filter; + + { + celix_auto(celix_mutex_lock_guard_t) mutexLockGuard = celixMutexLockGuard_init(&discovery->mutex); + celix_status_t status = celix_longHashMap_put(discovery->endpointListeners, serviceId, svcEntry); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(discovery->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(discovery->logHelper, "Failed to add endpoint listener entry to map"); + return status; + } + celix_earpmDiscovery_notifyEndpointsToListener(discovery, svcEntry, true); + celix_steal_ptr(filter); + celix_steal_ptr(svcEntry); + } + + return CELIX_SUCCESS; +} + +celix_status_t celix_earpmDiscovery_removeEndpointListener(void* handle, void* service CELIX_UNUSED, const celix_properties_t* properties) { + celix_earpm_broker_discovery_t* discovery = handle; + assert(discovery != NULL); + long serviceId = celix_properties_getAsLong(properties, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId < 0) { + celix_logHelper_error(discovery->logHelper, "Failed to get service id from properties"); + return CELIX_ILLEGAL_ARGUMENT; + } + + { + celix_auto(celix_mutex_lock_guard_t) mutexLockGuard = celixMutexLockGuard_init(&discovery->mutex); + celix_earpm_endpoint_listener_entry_t* svcEntry = celix_longHashMap_get(discovery->endpointListeners, serviceId); + if (svcEntry == NULL) { + return CELIX_SUCCESS; + } + celix_earpmDiscovery_notifyEndpointsToListener(discovery, svcEntry, false); + celix_longHashMap_remove(discovery->endpointListeners, serviceId); + } + + return CELIX_SUCCESS; +} + +static celix_earpm_broker_listener_t* celix_earpmDiscovery_brokerListenerCreate(const char* host, uint16_t port) { + celix_autofree celix_earpm_broker_listener_t* listener = calloc(1, sizeof(*listener)); + if (listener == NULL) { + return NULL; + } + listener->port = port; + listener->family = AF_UNSPEC; + listener->bindInterface = NULL; + listener->host = NULL; + if (host) { + listener->host = celix_utils_strdup(host); + if (listener->host == NULL) { + return NULL; + } + } + return celix_steal_ptr(listener); +} + +static void celix_earpmDiscovery_brokerListenerDestroy(celix_earpm_broker_listener_t* listener) { + free(listener->host); + free(listener->bindInterface); + free(listener); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_broker_listener_t, celix_earpmDiscovery_brokerListenerDestroy) + +static void celix_earpmDiscovery_stripLine(char* line) { + size_t len = strlen(line); + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { + line[len - 1] = '\0'; + len = strlen(line); + } + return ; +} + +static celix_array_list_t* celix_earpmDiscovery_parseBrokerProfile(celix_earpm_broker_discovery_t* discovery, FILE* file) { + celix_array_list_create_options_t opts = CELIX_EMPTY_ARRAY_LIST_CREATE_OPTIONS; + opts.elementType = CELIX_ARRAY_LIST_ELEMENT_TYPE_POINTER; + opts.simpleRemovedCallback = (void *)celix_earpmDiscovery_brokerListenerDestroy; + celix_autoptr(celix_array_list_t) brokerListeners = celix_arrayList_createWithOptions(&opts); + if (brokerListeners == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to create broker listeners list."); + return NULL; + } + celix_earpm_broker_listener_t* curListener = NULL; + char line[512] = {0}; + char* token = NULL; + char* save = NULL; + while (fgets(line, sizeof(line), file) != NULL) { + if(line[0] == '#' || line[0] == '\n' || line[0] == '\r'){ + continue; + } + celix_earpmDiscovery_stripLine(line); + + token = strtok_r(line, " ", &save); + if (token == NULL) { + continue; + } + if (celix_utils_stringEquals(token, "listener")) { + curListener = NULL; + token = strtok_r(NULL, " ", &save); + if (token == NULL) { + celix_logHelper_error(discovery->logHelper, "Invalid listener line in broker profile file %s.", discovery->brokerProfilePath); + continue; + } + char* portEnd = NULL; + long port = strtol(token, &portEnd, 10); + if (portEnd == NULL || *portEnd != '\0' || portEnd == token || port < 0 || port > UINT16_MAX) { + celix_logHelper_error(discovery->logHelper, "Invalid port in listener line(%s) in broker profile file %s.", token, discovery->brokerProfilePath); + continue; + } + char* host = strtok_r(NULL, " ", &save); + if (port == 0 && host == NULL) { + celix_logHelper_error(discovery->logHelper, "Invalid unix socket listener line in broker profile file %s.", discovery->brokerProfilePath); + continue; + } + celix_autoptr(celix_earpm_broker_listener_t) listener = celix_earpmDiscovery_brokerListenerCreate(host,(uint16_t) port); + if (listener == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to create broker listener for port %ld.", port); + continue; + } + celix_status_t status = celix_arrayList_add(brokerListeners, listener); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(discovery->logHelper, "Failed to add broker listener for port %ld.", port); + continue; + } + curListener = celix_steal_ptr(listener); + } else if (celix_utils_stringEquals(token, "socket_domain") && curListener != NULL) { + token = strtok_r(NULL, " ", &save); + if (token == NULL) { + continue; + } + if (celix_utils_stringEquals(token, "ipv4")) { + curListener->family = AF_INET; + } else if (celix_utils_stringEquals(token, "ipv6")) { + curListener->family = AF_INET6; + } + } else if (celix_utils_stringEquals(token, "bind_interface") && curListener != NULL) { + token = strtok_r(NULL, " ", &save); + if (token == NULL) { + continue; + } + curListener->bindInterface = celix_utils_strdup(token); + if (curListener->bindInterface == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to dup bind interface %s.", token); + continue; + } + } + } + return celix_steal_ptr(brokerListeners); +} + +static celix_array_list_t* celix_earpmDiscovery_createBrokerEndpoints(celix_earpm_broker_discovery_t* discovery, celix_array_list_t* brokerListeners) { + celix_array_list_create_options_t options = CELIX_EMPTY_ARRAY_LIST_CREATE_OPTIONS; + options.elementType = CELIX_ARRAY_LIST_ELEMENT_TYPE_POINTER; + options.simpleRemovedCallback = (void *)endpointDescription_destroy; + celix_autoptr(celix_array_list_t) brokerEndpoints = celix_arrayList_createWithOptions(&options); + if (brokerEndpoints == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to create broker endpoints list."); + return NULL; + } + int size = celix_arrayList_size(brokerListeners); + for (int i = 0; i < size; ++i) { + celix_earpm_broker_listener_t* listener = celix_arrayList_get(brokerListeners, i); + const char* host = listener->host != NULL ? listener->host : ""; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + if (props == NULL) { + celix_logHelper_logTssErrors(discovery->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(discovery->logHelper, "Failed to create properties for mqtt broker listener %s:%d", host, listener->port); + return NULL; + } + uuid_t uid; + uuid_generate(uid); + char endpointUUID[37]; + uuid_unparse_lower(uid, endpointUUID); + celix_status_t status = celix_properties_set(props, CELIX_RSA_ENDPOINT_FRAMEWORK_UUID, discovery->fwUUID); + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME)); + status = CELIX_DO_IF(status, celix_properties_setLong(props, CELIX_RSA_ENDPOINT_SERVICE_ID, INT32_MAX)); + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_RSA_ENDPOINT_ID, endpointUUID)); + status = CELIX_DO_IF(status, celix_properties_setBool(props, CELIX_RSA_SERVICE_IMPORTED, true)); + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, + CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE)); + if (listener->host == NULL) {//use dynamic ip + status = CELIX_DO_IF(status, celix_properties_setLong(props, CELIX_RSA_PORT, listener->port)); + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_RSA_IP_ADDRESSES, ""));//service discovery will fill in dynamic ip + if (listener->family != AF_UNSPEC) { + status = CELIX_DO_IF(status, celix_properties_setLong(props, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, listener->family)); + } + const char* bindInterface = listener->bindInterface != NULL ? listener->bindInterface : "all"; + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, bindInterface)); + } else if (listener->port != 0) {//specific ip and hostname + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_EARPM_MQTT_BROKER_ADDRESS, listener->host)); + status = CELIX_DO_IF(status, celix_properties_setLong(props, CELIX_EARPM_MQTT_BROKER_PORT, listener->port)); + const char* bindInterface = listener->bindInterface != NULL ? listener->bindInterface : "all"; + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, bindInterface)); + } else {// unix socket + status = CELIX_DO_IF(status, celix_properties_set(props, CELIX_EARPM_MQTT_BROKER_ADDRESS, listener->host)); + } + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(discovery->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(discovery->logHelper, "Failed to set properties for mqtt broker listener %s:%d", host, listener->port); + return NULL; + } + + celix_autoptr(endpoint_description_t) endpoint = NULL; + status = endpointDescription_create(props, &endpoint); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(discovery->logHelper, "Failed to create endpoint for mqtt broker listener %s:%d", host, listener->port); + return NULL; + } + celix_steal_ptr(props); + status = celix_arrayList_add(brokerEndpoints, endpoint); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(discovery->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(discovery->logHelper, "Failed to add endpoint for mqtt broker listener %s:%d", host, listener->port); + return NULL; + } + celix_steal_ptr(endpoint); + } + return celix_steal_ptr(brokerEndpoints); +} + +static bool celix_earpmDiscovery_loadBrokerProfile(celix_earpm_broker_discovery_t* discovery) { + celix_autoptr(FILE) file = fopen(discovery->brokerProfilePath, "r"); + if (file == NULL) { + celix_log_level_e logLevel = errno != ENOENT ? CELIX_LOG_LEVEL_ERROR : CELIX_LOG_LEVEL_DEBUG; + celix_logHelper_log(discovery->logHelper, logLevel, "Failed to open broker profile file %s. %d", discovery->brokerProfilePath, errno); + return errno != ENOENT; + } + + celix_autoptr(celix_array_list_t) brokerListeners = celix_earpmDiscovery_parseBrokerProfile(discovery, file); + if (brokerListeners == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to parse broker profile."); + return false; + } + + celix_autoptr(celix_array_list_t) brokerEndpoints = celix_earpmDiscovery_createBrokerEndpoints(discovery, brokerListeners); + if (brokerEndpoints == NULL) { + celix_logHelper_error(discovery->logHelper, "Failed to create broker endpoints."); + return false; + } + celix_auto(celix_mutex_lock_guard_t) mutexLockGuard = celixMutexLockGuard_init(&discovery->mutex); + discovery->brokerEndpoints = celix_steal_ptr(brokerEndpoints); + + return true; +} + +static void celix_earpmDiscovery_onProfileScheduledEvent(void* data) { + celix_earpm_broker_discovery_t* discovery = data; + assert(discovery != NULL); + bool loaded = celix_earpmDiscovery_loadBrokerProfile(discovery); + if (loaded) { + celix_logHelper_info(discovery->logHelper, "Loaded broker profile from %s", discovery->brokerProfilePath); + celix_auto(celix_mutex_lock_guard_t) mutexLockGuard = celixMutexLockGuard_init(&discovery->mutex); + CELIX_LONG_HASH_MAP_ITERATE(discovery->endpointListeners, iter) { + celix_earpm_endpoint_listener_entry_t* listenerEntry = iter.value.ptrValue; + celix_earpmDiscovery_notifyEndpointsToListener(discovery, listenerEntry, true); + } + } + if (loaded || ++discovery->loadProfileTries >= CELIX_EARPM_LOAD_PROFILE_TRIES_MAX) { + celix_bundleContext_removeScheduledEventAsync(discovery->ctx, discovery->profileScheduledEventId); + __atomic_store_n(&discovery->profileScheduledEventId, -1, __ATOMIC_RELEASE); + } + return; +} + diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.h new file mode 100644 index 000000000..5db1d1c1b --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_broker_discovery.h @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_BROKER_DISCOVERY_H +#define CELIX_EARPM_BROKER_DISCOVERY_H +#ifdef __cplusplus +extern "C" { +#endif +#include "celix_bundle_context.h" +#include "celix_properties.h" +#include "celix_errno.h" + +#define CELIX_EARPM_MQTT_BROKER_INFO_SERVICE_NAME "celix_mqtt_broker_info" + +//Properties of broker info service endpoint +#define CELIX_EARPM_MQTT_BROKER_SERVICE_CONFIG_TYPE "celix.earpm.mqtt" +#define CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN "celix.earpm.mqtt.socket_domain" +#define CELIX_EARPM_MQTT_BROKER_ADDRESS "celix.earpm.mqtt.address" //IP address or hostname or unix socket path +#define CELIX_EARPM_MQTT_BROKER_PORT "celix.earpm.mqtt.port" + +typedef struct celix_earpm_broker_discovery celix_earpm_broker_discovery_t; + +celix_earpm_broker_discovery_t* celix_earpmDiscovery_create(celix_bundle_context_t* ctx); + +void celix_earpmDiscovery_destroy(celix_earpm_broker_discovery_t* discovery); + +celix_status_t celix_earpmDiscovery_addEndpointListener(void* handle, void* service, const celix_properties_t* properties); + +celix_status_t celix_earpmDiscovery_removeEndpointListener(void* handle, void* service, const celix_properties_t* properties); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_BROKER_DISCOVERY_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.c b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.c new file mode 100644 index 000000000..0a77da6a1 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.c @@ -0,0 +1,1249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_earpm_client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "celix_compiler.h" +#include "celix_cleanup.h" +#include "celix_stdlib_cleanup.h" +#include "celix_threads.h" +#include "celix_ref.h" +#include "celix_array_list.h" +#include "celix_long_hash_map.h" +#include "celix_string_hash_map.h" +#include "celix_utils.h" +#include "celix_constants.h" +#include "remote_constants.h" +#include "celix_earpm_mosquitto_cleanup.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_broker_discovery.h" + +#define CELIX_EARPM_SESSION_EXPIRY_INTERVAL (10*60) //seconds +#define CELIX_EARPM_CLIENT_KEEP_ALIVE 60 //seconds +#define CELIX_EARPM_CLIENT_RECONNECT_DELAY_MAX 30 //seconds + +#define CELIX_EARPM_MQTT_USER_PROP_SENDER_UUID "CELIX_EARPM_SENDER_UUID" +#define CELIX_EARPM_MQTT_USER_PROP_MSG_VERSION "CELIX_EARPM_MSG_VERSION" + +typedef struct celix_earpm_client_broker_info { + struct celix_ref ref; + celix_array_list_t* addresses; + int port; + char* bindInterface; + int family; +} celix_earpm_client_broker_info_t; + +typedef struct celix_earpm_client_msg { + struct celix_ref ref; + TAILQ_ENTRY(celix_earpm_client_msg) entry; + char* topic; + char* payload; + size_t payloadSize; + celix_earpm_qos_e qos; + celix_earpm_client_message_priority_e pri; + mosquitto_property* mqttProps; + int mqttMid; + celix_status_t error; + bool processDone; + struct celix_earpm_client_msg_pool* msgPool; +} celix_earpm_client_msg_t; + +typedef TAILQ_HEAD(celix_earpm_client_message_list, celix_earpm_client_msg) celix_earpm_client_message_list_t; + +typedef struct celix_earpm_client_msg_pool { + size_t cap; + size_t usedSize; + celix_earpm_client_message_list_t freeMsgList; + celix_earpm_client_msg_t* msgBlocks; +} celix_earpm_client_msg_pool_t; + +struct celix_earpm_client { + celix_bundle_context_t* ctx; + celix_log_helper_t* logHelper; + celix_earpm_client_receive_msg_fp receiveMsgCallback; + celix_earpm_client_connected_fp connectedCallback; + void* callbackHandle; + size_t parallelMsgCap; + struct mosquitto* mosq; + mosquitto_property* connProps; + mosquitto_property* disconnectProps; + celix_thread_t workerThread; + celix_thread_mutex_t mutex;// protects belows + celix_string_hash_map_t* brokerInfoMap;//key = endpoint uuid, value = celix_broker_info_t* + celix_thread_cond_t brokerInfoChangedOrExiting; + char* currentBrokerEndpointUUID; + celix_thread_cond_t msgStatusChanged; + celix_earpm_client_msg_pool_t freeMsgPool; + celix_earpm_client_message_list_t waitingMessages; + celix_long_hash_map_t* publishingMessages;//key = mid, value = celix_earpmc_message_t* + celix_string_hash_map_t* subscriptions;//key = topic, value = qos + bool connected; + bool running; +}; + +static celix_status_t celix_earpmClient_configMosq(mosquitto* mosq, celix_log_helper_t* logHelper, const char* sessionEndMsgTopic, const char* sessionEndMsgSenderUUID, const char* sessionEndMsgVersion); + static void celix_earpmClient_messageRelease(celix_earpm_client_msg_t* msg); +static void celix_earpmClient_brokerInfoRelease(celix_earpm_client_broker_info_t* info); +static void celix_earpmClient_connectCallback(struct mosquitto* mosq, void* handle, int rc, int flag, const mosquitto_property* props); +static void celix_earpmClient_disconnectCallback(struct mosquitto* mosq, void* handle, int rc, const mosquitto_property* props); +static void celix_earpmClient_messageCallback(struct mosquitto* mosq, void* handle, const struct mosquitto_message* message, const mosquitto_property* props); +static void celix_earpmClient_publishCallback(struct mosquitto* mosq, void* handle, int mid, int reasonCode, const mosquitto_property* props); +static void* celix_earpmClient_worker(void* data); + + +static celix_status_t celix_earpmClient_msgPoolInit(celix_earpm_client_msg_pool_t* pool, size_t cap) { + pool->cap = cap; + pool->usedSize = 0; + celix_earpm_client_msg_t* msgBlocks = pool->msgBlocks = (celix_earpm_client_msg_t*)calloc(cap, sizeof(celix_earpm_client_msg_t)); + if (msgBlocks == NULL) { + return CELIX_ENOMEM; + } + TAILQ_INIT(&pool->freeMsgList); + for (size_t i = 0; i < cap; ++i) { + TAILQ_INSERT_TAIL(&pool->freeMsgList, &msgBlocks[i], entry); + } + return CELIX_SUCCESS; +} + +static void celix_earpmClient_msgPoolDeInit(celix_earpm_client_msg_pool_t* pool) { + assert(pool->usedSize == 0); + free(pool->msgBlocks); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_client_msg_pool_t, celix_earpmClient_msgPoolDeInit) + +static celix_earpm_client_msg_t* celix_earpmClient_msgPoolAlloc(celix_earpm_client_msg_pool_t* pool) { + celix_earpm_client_msg_t* msg = TAILQ_FIRST(&pool->freeMsgList); + if (msg != NULL) { + TAILQ_REMOVE(&pool->freeMsgList, msg, entry); + pool->usedSize ++; + } + return msg; +} + +static void celix_earpmClient_msgPoolFree(celix_earpm_client_msg_pool_t* pool, celix_earpm_client_msg_t* msg) { + TAILQ_INSERT_TAIL(&pool->freeMsgList, msg, entry); + pool->usedSize --; + return; +} + +celix_earpm_client_t* celix_earpmClient_create(celix_earpm_client_create_options_t* options) { + assert(options != NULL); + assert(options->ctx != NULL); + assert(options->logHelper != NULL); + assert(options->sessionEndMsgTopic != NULL); + assert(options->sessionEndMsgSenderUUID != NULL); + assert(options->sessionEndMsgVersion != NULL); + assert(options->receiveMsgCallback != NULL); + assert(options->connectedCallback != NULL); + + celix_log_helper_t* logHelper = options->logHelper; + const char* fwUUID = celix_bundleContext_getProperty(options->ctx, CELIX_FRAMEWORK_UUID, NULL); + if (fwUUID == NULL) { + celix_logHelper_error(logHelper, "Failed to get framework UUID."); + return NULL; + } + long msgQueueCap = celix_bundleContext_getPropertyAsLong(options->ctx, CELIX_EARPM_MSG_QUEUE_CAPACITY, CELIX_EARPM_MSG_QUEUE_CAPACITY_DEFAULT); + if (msgQueueCap <= 0 || msgQueueCap > CELIX_EARPM_MSG_QUEUE_MAX_SIZE) { + celix_logHelper_error(logHelper, "Invalid message queue capacity %ld.", msgQueueCap); + return NULL; + } + long parallelMsgCap = celix_bundleContext_getPropertyAsLong(options->ctx, CELIX_EARPM_PARALLEL_MSG_CAPACITY, CELIX_EARPM_PARALLEL_MSG_CAPACITY_DEFAULT); + if (parallelMsgCap <= 0 || parallelMsgCap > msgQueueCap) { + celix_logHelper_error(logHelper, "Invalid parallel message capacity %ld.", parallelMsgCap); + return NULL; + } + + celix_autofree celix_earpm_client_t *client = calloc(1, sizeof(*client)); + if (client == NULL) { + celix_logHelper_error(logHelper, "Failed to allocate memory for earpm client."); + return NULL; + } + client->ctx = options->ctx; + client->logHelper = logHelper; + client->parallelMsgCap = (size_t)parallelMsgCap; + client->currentBrokerEndpointUUID = NULL; + client->connected = false; + client->receiveMsgCallback = options->receiveMsgCallback; + client->connectedCallback = options->connectedCallback; + client->callbackHandle = options->callbackHandle; + celix_status_t status = celixThreadMutex_create(&client->mutex, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create mqtt client mutex."); + return NULL; + } + celix_autoptr(celix_thread_mutex_t) mutex = &client->mutex; + + status = celixThreadCondition_init(&client->msgStatusChanged, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create mqtt client condition. %d.", status); + return NULL; + } + celix_autoptr(celix_thread_cond_t) msgStatusChanged = &client->msgStatusChanged; + + status = celix_earpmClient_msgPoolInit(&client->freeMsgPool, msgQueueCap); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create message pool. %d.", status); + return NULL; + } + celix_autoptr(celix_earpm_client_msg_pool_t) freeMsgPool = &client->freeMsgPool; + + TAILQ_INIT(&client->waitingMessages); + + celix_autoptr(celix_long_hash_map_t) publishingMessages = NULL; + { + celix_long_hash_map_create_options_t opts = CELIX_EMPTY_LONG_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *) celix_earpmClient_messageRelease; + publishingMessages = client->publishingMessages = celix_longHashMap_createWithOptions(&opts); + if (publishingMessages == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to create publishing message map."); + return NULL; + } + } + + celix_autoptr(celix_string_hash_map_t) subscriptions = client->subscriptions = celix_stringHashMap_create(); + if (subscriptions == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to create subscriptions map."); + return NULL; + } + + celix_autoptr(celix_string_hash_map_t) brokerInfoMap = NULL; + { + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *) celix_earpmClient_brokerInfoRelease; + brokerInfoMap = client->brokerInfoMap = celix_stringHashMap_createWithOptions(&opts); + if (brokerInfoMap == NULL) { + celix_logHelper_error(client->logHelper, "Failed to create broker info map."); + return NULL; + } + } + + status = celixThreadCondition_init(&client->brokerInfoChangedOrExiting, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create broker info changed condition. %d.", status); + return NULL; + } + celix_autoptr(celix_thread_cond_t) brokerInfoChanged = &client->brokerInfoChangedOrExiting; + + celix_autoptr(mosquitto_property) connProps = NULL; + int rc = mosquitto_property_add_int32(&connProps, MQTT_PROP_SESSION_EXPIRY_INTERVAL, CELIX_EARPM_SESSION_EXPIRY_INTERVAL); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to set mqtt session expiry interval. %d.", rc); + return NULL; + } + client->connProps = connProps; + + celix_autoptr(mosquitto_property) disconnectProps = NULL; + rc = mosquitto_property_add_int32(&disconnectProps, MQTT_PROP_SESSION_EXPIRY_INTERVAL, 0); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create disconnect properties. %d.", rc); + return NULL; + } + client->disconnectProps = disconnectProps; + + celix_autoptr(mosquitto) mosq = client->mosq = mosquitto_new(fwUUID, false, client); + if (client->mosq == NULL) { + celix_logHelper_error(client->logHelper, "Failed to create mosquitto instance."); + return NULL; + } + status = celix_earpmClient_configMosq(client->mosq, client->logHelper, options->sessionEndMsgTopic, options->sessionEndMsgSenderUUID, options->sessionEndMsgVersion); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to configure mosquitto instance."); + return NULL; + } + client->running = true; + status = celixThread_create(&client->workerThread, NULL, celix_earpmClient_worker, client); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to create mosq thread. %d.", status); + return NULL; + } + celixThread_setName(&client->workerThread, "CelixEarpmC"); + + celix_steal_ptr(mosq); + celix_steal_ptr(disconnectProps); + celix_steal_ptr(connProps); + celix_steal_ptr(brokerInfoChanged); + celix_steal_ptr(brokerInfoMap); + celix_steal_ptr(subscriptions); + celix_steal_ptr(publishingMessages); + celix_steal_ptr(freeMsgPool); + celix_steal_ptr(msgStatusChanged); + celix_steal_ptr(mutex); + + return celix_steal_ptr(client); +} + +void celix_earpmClient_destroy(celix_earpm_client_t* client) { + assert(client != NULL); + celixThreadMutex_lock(&client->mutex); + client->running = false; + celixThreadMutex_unlock(&client->mutex); + int rc = mosquitto_disconnect_v5(client->mosq, MQTT_RC_DISCONNECT_WITH_WILL_MSG, client->disconnectProps); + if (rc != MOSQ_ERR_SUCCESS && rc != MOSQ_ERR_NO_CONN) { + celix_logHelper_error(client->logHelper, "Failed to disconnect mosquitto, will try to force destroy. %d.", rc); + } + celixThreadCondition_signal(&client->brokerInfoChangedOrExiting); + celixThread_join(client->workerThread, NULL); + mosquitto_destroy(client->mosq); + mosquitto_property_free_all(&client->disconnectProps); + mosquitto_property_free_all(&client->connProps); + celixThreadCondition_destroy(&client->brokerInfoChangedOrExiting); + celix_stringHashMap_destroy(client->brokerInfoMap); + celix_stringHashMap_destroy(client->subscriptions); + celix_longHashMap_destroy(client->publishingMessages); + celix_earpm_client_msg_t* msg = TAILQ_FIRST(&client->waitingMessages); + while (msg != NULL) { + TAILQ_REMOVE(&client->waitingMessages, msg, entry); + celix_earpmClient_messageRelease(msg); + msg = TAILQ_FIRST(&client->waitingMessages); + } + celix_earpmClient_msgPoolDeInit(&client->freeMsgPool); + celixThreadCondition_destroy(&client->msgStatusChanged); + celixThreadMutex_destroy(&client->mutex); + free(client->currentBrokerEndpointUUID); + free(client); + return; +} + +void celix_earpmClient_info(celix_earpm_client_t* client, FILE* outStream) { + celixThreadMutex_lock(&client->mutex); + size_t queueSize = client->freeMsgPool.cap; + size_t usedSize = client->freeMsgPool.usedSize; + celixThreadMutex_unlock(&client->mutex); + + fprintf(outStream, "\nMessage Queue Info:\n"); + fprintf(outStream, "\tTotal:%zu, Used:%zu\n", queueSize, usedSize); +} + +static celix_status_t celix_earpmClient_configMosq(mosquitto *mosq, celix_log_helper_t* logHelper, const char* sessionEndMsgTopic, const char* sessionEndMsgSenderUUID, const char* sessionEndMsgVersion) { + assert(mosq != NULL); + int rc = mosquitto_int_option(mosq, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to set mqtt protocol version."); + return CELIX_ILLEGAL_STATE; + } + rc = mosquitto_int_option(mosq, MOSQ_OPT_TCP_NODELAY, 1); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to set mqtt tcp no delay."); + return CELIX_ILLEGAL_STATE; + } + celix_autoptr(mosquitto_property) sessionEndMsgProps = NULL; + //It will ensure that Will Message is sent when the session ends by setting the Will Delay Interval to be longer than + // the Session Expiry Interval. Because the Server delays publishing the Client’s Will Message until the Will Delay Interval + // has passed or the Session ends, whichever happens first. + if (mosquitto_property_add_int32(&sessionEndMsgProps, MQTT_PROP_WILL_DELAY_INTERVAL, CELIX_EARPM_SESSION_EXPIRY_INTERVAL * 2) != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to add will delay interval property for will message."); + return ENOMEM; + } + if (mosquitto_property_add_string_pair(&sessionEndMsgProps, MQTT_PROP_USER_PROPERTY, + CELIX_EARPM_MQTT_USER_PROP_SENDER_UUID, sessionEndMsgSenderUUID) != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to add sender UUID property for will message."); + return ENOMEM; + } + if (mosquitto_property_add_string_pair(&sessionEndMsgProps, MQTT_PROP_USER_PROPERTY, CELIX_EARPM_MQTT_USER_PROP_MSG_VERSION, sessionEndMsgVersion) != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to add message version property for will message."); + return ENOMEM; + } + rc = mosquitto_will_set_v5(mosq, sessionEndMsgTopic, 0, NULL, CELIX_EARPM_QOS_AT_LEAST_ONCE, false, sessionEndMsgProps); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to set mqtt will. %d.", rc); + return CELIX_ILLEGAL_STATE; + } + celix_steal_ptr(sessionEndMsgProps); + + mosquitto_connect_v5_callback_set(mosq, celix_earpmClient_connectCallback); + mosquitto_disconnect_v5_callback_set(mosq, celix_earpmClient_disconnectCallback); + mosquitto_message_v5_callback_set(mosq, celix_earpmClient_messageCallback); + mosquitto_publish_v5_callback_set(mosq, celix_earpmClient_publishCallback); + mosquitto_threaded_set(mosq, true); + return CELIX_SUCCESS; +} + +static bool celix_earpmClient_matchIpFamily(const char* address, int family) { + if (family == AF_UNSPEC) { + return true; + } + char buf[sizeof(struct in6_addr)]; + bool isIPv4 = (inet_pton(AF_INET, address, buf) != 0); + bool isIPv6 = isIPv4 ? false : (inet_pton(AF_INET6, address, buf) != 0); + if (!isIPv4 && !isIPv6) { + return true;//The address is host name, let the mqtt library to resolve it. + } + if (family == AF_INET && isIPv4) { + return true; + } + if (family == AF_INET6 && isIPv6) { + return true; + } + return false; +} + +static celix_status_t celix_earpmClient_getBrokerAddressesOrBindInterfaceFromEndpoint(const endpoint_description_t* endpoint, + celix_array_list_t** addresses, char** bindInterface) { + celix_autoptr(celix_array_list_t) addressList = NULL; + celix_autofree char* interfaceName = NULL; + celix_status_t status = celix_properties_getAsStringArrayList(endpoint->properties, CELIX_EARPM_MQTT_BROKER_ADDRESS, NULL, &addressList); + if (status != CELIX_SUCCESS) { + return status; + } + if (addressList == NULL) { + const char* interface = celix_properties_get(endpoint->properties, CELIX_RSA_EXPORTED_ENDPOINT_EXPOSURE_INTERFACE, NULL); + if (interface != NULL) { + interfaceName = celix_utils_strdup(interface); + if (interfaceName== NULL) { + return ENOMEM; + } + } + status = celix_properties_getAsStringArrayList(endpoint->properties, CELIX_RSA_IP_ADDRESSES, NULL, &addressList); + if (status != CELIX_SUCCESS) { + return status; + } + } + if (addressList != NULL) { + int family = (int)celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_UNSPEC); + for (int i = celix_arrayList_size(addressList) - 1; i >= 0; --i) { + const char* address = celix_arrayList_getString(addressList, i); + if (address[0] == '\0' || !celix_earpmClient_matchIpFamily(address, family)) { + celix_arrayList_removeAt(addressList, i); + } + } + if (celix_arrayList_size(addressList) == 0) { + celix_arrayList_destroy(addressList); + addressList = NULL; + } + } + if (addressList == NULL && interfaceName == NULL) { + return CELIX_ILLEGAL_ARGUMENT; + } + *addresses = celix_steal_ptr(addressList); + *bindInterface = celix_steal_ptr(interfaceName); + return CELIX_SUCCESS; +} + +static celix_earpm_client_broker_info_t* celix_earpmClient_brokerInfoCreate(celix_array_list_t* addresses, int port, char* bindInterface, int family) { + celix_autofree celix_earpm_client_broker_info_t* info = calloc(1, sizeof(*info)); + if (info == NULL) { + errno = ENOMEM; + return NULL; + } + celix_ref_init(&info->ref); + info->port = port; + info->family = family; + info->addresses = addresses; + info->bindInterface = bindInterface; + + return celix_steal_ptr(info); +} + +static bool celix_earpmClient_brokerInfoDestroy(struct celix_ref* ref) { + celix_earpm_client_broker_info_t* info = (celix_earpm_client_broker_info_t*)ref; + celix_arrayList_destroy(info->addresses); + free(info->bindInterface); + free(info); + return true; +} + +static void celix_earpmClient_brokerInfoRetain(celix_earpm_client_broker_info_t* info) { + celix_ref_get(&info->ref); + return; +} + +static void celix_earpmClient_brokerInfoRelease(celix_earpm_client_broker_info_t* info) { + celix_ref_put(&info->ref, celix_earpmClient_brokerInfoDestroy); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_client_broker_info_t, celix_earpmClient_brokerInfoRelease) + +celix_status_t celix_earpmClient_mqttBrokerEndpointAdded(void* handle, const endpoint_description_t* endpoint, char* matchedFilter CELIX_UNUSED) { + assert(handle != NULL); + assert(endpoint != NULL); + celix_earpm_client_t* client = handle; + int port = (int)celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_PORT, 0); + port = port != 0 ? port : (int)celix_properties_getAsLong(endpoint->properties, CELIX_RSA_PORT, 0); + if (port < 0) { + celix_logHelper_error(client->logHelper, "Invalid mqtt broker port %d.", port); + return CELIX_ILLEGAL_ARGUMENT; + } + celix_autoptr(celix_array_list_t) addresses = NULL; + celix_autofree char* bindInterface = NULL; + celix_status_t status = celix_earpmClient_getBrokerAddressesOrBindInterfaceFromEndpoint(endpoint, &addresses, &bindInterface); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to get broker addresses or bind interface from endpoint. %d.", status); + return status; + } + int family = (int)celix_properties_getAsLong(endpoint->properties, CELIX_EARPM_MQTT_BROKER_SOCKET_DOMAIN, AF_UNSPEC); + + { + celix_autoptr(celix_earpm_client_broker_info_t) info = celix_earpmClient_brokerInfoCreate(addresses, port, + bindInterface, family); + if (info == NULL) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to create broker information."); + return errno; + } + celix_steal_ptr(addresses); + celix_steal_ptr(bindInterface); + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + status = celix_stringHashMap_put(client->brokerInfoMap, endpoint->id, info); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to add broker information to map. %d.", status); + return status; + } + celix_steal_ptr(info); + } + + status = celixThreadCondition_signal(&client->brokerInfoChangedOrExiting); + if (status != CELIX_SUCCESS) { + celix_logHelper_warning(client->logHelper, "Failed to signal adding broker information. %d.", status); + } + return CELIX_SUCCESS; +} + +celix_status_t celix_earpmClient_mqttBrokerEndpointRemoved(void* handle, const endpoint_description_t* endpoint, char* matchedFilter CELIX_UNUSED) { + assert(handle != NULL); + assert(endpoint != NULL); + celix_earpm_client_t* client = handle; + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + celix_stringHashMap_remove(client->brokerInfoMap, endpoint->id); + return CELIX_SUCCESS; +} + +static bool celix_earpmClient_validateTopic(const char* topic) { + if (strlen(topic) == 0 || strlen(topic) > 1024) { + return false; + } + //The characters +, #, and $ are part of the MQTT topic pattern syntax, + // so they are not allowed in the topic name of celix event admin. + if (strpbrk(topic, "#+$")) { + return false; + } + return true; +} + +celix_status_t celix_earpmClient_subscribe(celix_earpm_client_t* client, const char* topic, celix_earpm_qos_e qos) { + assert(client != NULL); + assert(topic != NULL); + if (!celix_earpmClient_validateTopic(topic)) { + celix_logHelper_error(client->logHelper, "Invalid topic pattern %s.", topic); + return CELIX_ILLEGAL_ARGUMENT; + } + char mqttTopic[strlen(topic) + 1]; + if (topic[strlen(topic) - 1] == '*') { + strcpy(mqttTopic, topic); + mqttTopic[strlen(topic) - 1] = '#'; + topic = mqttTopic; + } + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + celix_earpm_qos_e oldQos = (celix_earpm_qos_e)celix_stringHashMap_getLong(client->subscriptions, topic, CELIX_EARPM_QOS_UNKNOWN); + celix_status_t status = celix_stringHashMap_putLong(client->subscriptions, topic, qos); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to add subscription to map."); + return status; + } + if (client->connected) { + int rc = mosquitto_subscribe_v5(client->mosq, NULL, topic, qos, MQTT_SUB_OPT_NO_LOCAL, NULL); + if (rc != MOSQ_ERR_SUCCESS) { + if (oldQos != CELIX_EARPM_QOS_UNKNOWN) {// rollback qos state + (void)celix_stringHashMap_putLong(client->subscriptions, topic, oldQos); + } else { + (void)celix_stringHashMap_remove(client->subscriptions, topic); + } + celix_logHelper_error(client->logHelper, "Failed to subscribe topic %s with qos %d. %d", topic, (int)qos, rc); + return CELIX_BUNDLE_EXCEPTION; + } + } + + return CELIX_SUCCESS; +} + +celix_status_t celix_earpmClient_unsubscribe(celix_earpm_client_t* client, const char* topic) { + assert(client != NULL); + assert(topic != NULL); + if (!celix_earpmClient_validateTopic(topic)) { + celix_logHelper_error(client->logHelper, "Invalid topic pattern %s.", topic); + return CELIX_ILLEGAL_ARGUMENT; + } + char mqttTopic[strlen(topic) + 1]; + if (topic[strlen(topic) - 1] == '*') { + strcpy(mqttTopic, topic); + mqttTopic[strlen(topic) - 1] = '#'; + topic = mqttTopic; + } + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + if (client->connected) { + (void)celix_stringHashMap_remove(client->subscriptions, topic); + int rc = mosquitto_unsubscribe(client->mosq, NULL, topic); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_warning(client->logHelper, "Failed to unsubscribe topic %s. %d", topic, rc); + return CELIX_BUNDLE_EXCEPTION; + } + } else { + celix_status_t status = celix_stringHashMap_putLong(client->subscriptions, topic, CELIX_EARPM_QOS_UNKNOWN); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_WARNING); + celix_logHelper_warning(client->logHelper, "Failed to mark subscription expiry, %d.", status); + return status; + } + } + return CELIX_SUCCESS; +} + +static inline bool celix_earpmClient_hasFreeMsgFor(celix_earpm_client_t* client, celix_earpm_client_message_priority_e msgPriority) { + switch (msgPriority) { + case CELIX_EARPM_MSG_PRI_LOW: + return client->freeMsgPool.usedSize < client->freeMsgPool.cap * 70 / 100; + case CELIX_EARPM_MSG_PRI_MIDDLE: + return client->freeMsgPool.usedSize < client->freeMsgPool.cap * 85 / 100; + case CELIX_EARPM_MSG_PRI_HIGH: + return client->freeMsgPool.usedSize < client->freeMsgPool.cap; + } + assert(0);//LCOV_EXCL_LINE, should never be reached + return false;//LCOV_EXCL_LINE, should never be reached +} + +static inline bool celix_earpmClient_isPublishingQueueFull(celix_earpm_client_t* client) { + return celix_longHashMap_size(client->publishingMessages) >= client->parallelMsgCap; +} + +static celix_earpm_client_msg_t* celix_earpmClient_messageCreate(celix_earpm_client_msg_pool_t* msgPool, + const celix_earpm_client_request_info_t* requestInfo) { + celix_autoptr(mosquitto_property) mqttProps = NULL; + if (requestInfo->expiryInterval > 0) { + int rc = mosquitto_property_add_int32(&mqttProps, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, (uint32_t)requestInfo->expiryInterval); + if (rc != MOSQ_ERR_SUCCESS) { + return NULL; + } + } + if (requestInfo->responseTopic != NULL) { + int rc = mosquitto_property_add_string(&mqttProps, MQTT_PROP_RESPONSE_TOPIC, requestInfo->responseTopic); + if (rc != MOSQ_ERR_SUCCESS) { + return NULL; + } + } + if (requestInfo->correlationData != NULL && requestInfo->correlationDataSize > 0) { + int rc = mosquitto_property_add_binary(&mqttProps, MQTT_PROP_CORRELATION_DATA, requestInfo->correlationData, requestInfo->correlationDataSize); + if (rc != MOSQ_ERR_SUCCESS) { + return NULL; + } + } + if (requestInfo->senderUUID != NULL) { + int rc = mosquitto_property_add_string_pair(&mqttProps, MQTT_PROP_USER_PROPERTY, CELIX_EARPM_MQTT_USER_PROP_SENDER_UUID, requestInfo->senderUUID); + if (rc != MOSQ_ERR_SUCCESS) { + return NULL; + } + } + if (requestInfo->version != NULL) { + int rc = mosquitto_property_add_string_pair(&mqttProps, MQTT_PROP_USER_PROPERTY, CELIX_EARPM_MQTT_USER_PROP_MSG_VERSION, requestInfo->version); + if (rc != MOSQ_ERR_SUCCESS) { + return NULL; + } + } + celix_earpm_client_msg_t* msg = celix_earpmClient_msgPoolAlloc(msgPool); + if (msg == NULL) { + return NULL; + } + celix_ref_init(&msg->ref); + msg->msgPool = msgPool; + msg->payloadSize = 0; + msg->qos = requestInfo->qos; + msg->pri = requestInfo->pri; + msg->processDone = false; + msg->error = CELIX_SUCCESS; + msg->mqttMid = -1; + msg->payload = NULL; + msg->payloadSize = 0; + msg->mqttProps = mqttProps; + msg->topic = celix_utils_strdup(requestInfo->topic); + if (msg->topic == NULL) { + celix_earpmClient_msgPoolFree(msgPool, msg); + return NULL; + } + celix_steal_ptr(mqttProps); + return msg; +} + +static void celix_earpmClient_messageDestroy(celix_earpm_client_msg_t* msg) { + mosquitto_property_free_all(&msg->mqttProps); + free(msg->payload); + free(msg->topic); + celix_earpmClient_msgPoolFree(msg->msgPool, msg); + return; +} + +static void celix_earpmClient_messageRetain(celix_earpm_client_msg_t* msg) { + celix_ref_get(&msg->ref); + return; +} + +static void celix_earpmClient_messageRelease(celix_earpm_client_msg_t* msg) { + celix_ref_put(&msg->ref, (void *) celix_earpmClient_messageDestroy); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_client_msg_t, celix_earpmClient_messageRelease) + +static bool celix_earpmClient_checkRequestInfo(const celix_earpm_client_request_info_t* requestInfo) { + if (requestInfo->topic == NULL || requestInfo->qos <= CELIX_EARPM_QOS_UNKNOWN || requestInfo->qos > CELIX_EARPM_QOS_EXACTLY_ONCE + || requestInfo->pri< CELIX_EARPM_MSG_PRI_LOW || requestInfo->pri > CELIX_EARPM_MSG_PRI_HIGH) { + return false; + } + return true; +} + +static celix_status_t celix_earpmClient_fillMessagePayload(celix_earpm_client_msg_t* msg, const char* payload, size_t payloadSize) { + celix_autofree char* _payload = NULL; + if (payloadSize > 0 && payload != NULL) { + _payload = malloc(payloadSize); + if (_payload == NULL) { + return ENOMEM; + } + memcpy(_payload, payload, payloadSize); + } + if (_payload != NULL) { + msg->payload = celix_steal_ptr(_payload); + msg->payloadSize = payloadSize; + } + return CELIX_SUCCESS; +} + +static celix_status_t celix_earpmClient_publishMessage(celix_earpm_client_t* client, celix_earpm_client_msg_t* msg, + const char* payload, size_t payloadSize) { + int mid = 0; + int rc = mosquitto_publish_v5(client->mosq, &mid, msg->topic, (int)payloadSize, payload, msg->qos, false, msg->mqttProps); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to publish topic %s with qos %d, %d.", msg->topic, (int)msg->qos, rc); + return CELIX_BUNDLE_EXCEPTION; + } + msg->mqttMid = mid; + celix_status_t status = celix_longHashMap_put(client->publishingMessages, mid, msg); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to add message to publishing queue. %s.", msg->topic); + return status; + } + celix_earpmClient_messageRetain(msg); + return CELIX_SUCCESS; +} + +static void celix_earpmClient_enqueueMsgToWaitingQueue(celix_earpm_client_t* client, celix_earpm_client_msg_t* msg) { + celix_earpm_client_msg_t* higherPriMsg = NULL; + TAILQ_FOREACH_REVERSE(higherPriMsg, &client->waitingMessages, celix_earpm_client_message_list, entry) { + if (higherPriMsg->pri >= msg->pri) { + break; + } + } + if (higherPriMsg == NULL) { + TAILQ_INSERT_HEAD(&client->waitingMessages, msg, entry); + } else { + TAILQ_INSERT_AFTER(&client->waitingMessages, higherPriMsg, msg, entry); + } + celix_earpmClient_messageRetain(msg); + return; +} + +static celix_status_t celix_earpmClient_publishDoNext(celix_earpm_client_t* client, celix_earpm_client_msg_t *msg, const char* payload, size_t payloadSize) { + celix_status_t status = CELIX_SUCCESS; + if (client->connected && !celix_earpmClient_isPublishingQueueFull(client)) { + //Publish directly, do not fill payload to message, decrease memory usage. + status = celix_earpmClient_publishMessage(client, msg, payload, payloadSize); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to publish message %s, %d.", msg->topic, status); + return status; + } + } else { + status = celix_earpmClient_fillMessagePayload(msg, payload, payloadSize); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to fill payload for message %s. %d.", msg->topic, status); + return status; + } + celix_earpmClient_enqueueMsgToWaitingQueue(client, msg); + } + return CELIX_SUCCESS; +} + +celix_status_t celix_earpmClient_publishAsync(celix_earpm_client_t* client, const celix_earpm_client_request_info_t* requestInfo) { + assert(client != NULL); + assert(requestInfo != NULL); + if (!celix_earpmClient_checkRequestInfo(requestInfo)) { + celix_logHelper_error(client->logHelper, "Invalid request info for %s.", requestInfo->topic == NULL ? "unknown topic" : requestInfo->topic); + return CELIX_ILLEGAL_ARGUMENT; + } + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + if (requestInfo->qos <= CELIX_EARPM_QOS_AT_MOST_ONCE && !client->connected) { + celix_logHelper_trace(client->logHelper, "Mqtt client not connected, dropping async message with qos %d. %s.", (int)requestInfo->qos, requestInfo->topic); + return ENOTCONN; + } + if (!celix_earpmClient_hasFreeMsgFor(client, requestInfo->pri)) { + celix_logHelper_error(client->logHelper, "Too many messages wait for publish, dropping message with qos %d priority %d. %s.", + (int)requestInfo->qos, (int)requestInfo->pri, requestInfo->topic); + return ENOMEM; + } + + celix_autoptr(celix_earpm_client_msg_t) msg = celix_earpmClient_messageCreate(&client->freeMsgPool, requestInfo); + if (msg == NULL) { + celix_logHelper_error(client->logHelper, "Failed to create message for %s.", requestInfo->topic); + return ENOMEM; + } + + return celix_earpmClient_publishDoNext(client, msg, requestInfo->payload, requestInfo->payloadSize); +} + +static void celix_earpmClient_retrieveMsg(celix_earpm_client_t* client, celix_earpm_client_msg_t* msg) { + bool removed = celix_longHashMap_remove(client->publishingMessages, msg->mqttMid); + if (!removed) { + celix_earpm_client_msg_t *curMsg = NULL; + TAILQ_FOREACH(curMsg, &client->waitingMessages, entry) { + if (msg == curMsg) { + TAILQ_REMOVE(&client->waitingMessages, msg, entry); + celix_earpmClient_messageRelease(msg); + break; + } + } + } + celixThreadCondition_broadcast(&client->msgStatusChanged); +} + +static celix_status_t celix_earpmClient_waitForMsgProcessDone(celix_earpm_client_t* client, celix_earpm_client_msg_t* msg, const struct timespec* absTime) { + while (!msg->processDone) { + celix_status_t status = celixThreadCondition_waitUntil(&client->msgStatusChanged, &client->mutex, absTime); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to waiting for message(%s) published. %d.", msg->topic, status); + celix_earpmClient_retrieveMsg(client, msg); + return status; + } + } + return msg->error; +} + +celix_status_t celix_earpmClient_publishSync(celix_earpm_client_t* client, const celix_earpm_client_request_info_t* requestInfo) { + assert(client != NULL); + assert(requestInfo != NULL); + if (!celix_earpmClient_checkRequestInfo(requestInfo)) { + celix_logHelper_error(client->logHelper, "Invalid request info for %s.", requestInfo->topic == NULL ? "unknown topic" : requestInfo->topic); + return CELIX_ILLEGAL_ARGUMENT; + } + celix_status_t status = CELIX_SUCCESS; + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + if (requestInfo->qos <= CELIX_EARPM_QOS_AT_MOST_ONCE && !client->connected) { + celix_logHelper_warning(client->logHelper, "Mqtt client not connected, dropping sync message with qos %d. %s.", (int)requestInfo->qos, requestInfo->topic); + return ENOTCONN; + } + + double expiryInterval = (requestInfo->expiryInterval > 0 && requestInfo->expiryInterval < (INT32_MAX/2)) ? requestInfo->expiryInterval : (INT32_MAX/2); + struct timespec expiryTime = celixThreadCondition_getDelayedTime(expiryInterval); + + while (!celix_earpmClient_hasFreeMsgFor(client, requestInfo->pri)) { + if (requestInfo->qos <= CELIX_EARPM_QOS_AT_MOST_ONCE) { + celix_logHelper_warning(client->logHelper, "Too many messages wait for publish, dropping sync message with qos %d. %s.", (int)requestInfo->qos, requestInfo->topic); + return ENOMEM; + } + celix_logHelper_warning(client->logHelper, "Too many messages wait for publish, waiting for message queue idle. %s.", requestInfo->topic); + status = celixThreadCondition_waitUntil(&client->msgStatusChanged, &client->mutex, &expiryTime); + if (status != CELIX_SUCCESS) { + celix_logHelper_warning(client->logHelper, "Failed to waiting for message queue idle. %d.", status); + return status; + } + } + celix_autoptr(celix_earpm_client_msg_t) msg = celix_earpmClient_messageCreate(&client->freeMsgPool, requestInfo); + if (msg == NULL) { + celix_logHelper_error(client->logHelper, "Failed to create message for %s.", requestInfo->topic); + return ENOMEM; + } + status = celix_earpmClient_publishDoNext(client, msg, requestInfo->payload, requestInfo->payloadSize); + if (status != CELIX_SUCCESS) { + return status; + } + + return celix_earpmClient_waitForMsgProcessDone(client, msg, &expiryTime); +} + +static void celix_earpmClient_markMsgProcessDone(celix_earpm_client_msg_t* msg, + celix_status_t error) { + msg->error = error; + msg->processDone = true; + return; +} + +static void celix_earpmClient_releaseWaitingMsgToPublishing(celix_earpm_client_t* client) { + for (celix_earpm_client_msg_t *curMsg = TAILQ_FIRST(&client->waitingMessages), *nextMsg = NULL; + (curMsg != NULL) && !celix_earpmClient_isPublishingQueueFull(client); curMsg = nextMsg) { + nextMsg = TAILQ_NEXT(curMsg, entry); + celix_status_t status = celix_earpmClient_publishMessage(client, curMsg, curMsg->payload, curMsg->payloadSize); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to publish waiting message %s, %d.", curMsg->topic, status); + celix_earpmClient_markMsgProcessDone(curMsg, status); + } + TAILQ_REMOVE(&client->waitingMessages, curMsg, entry); + celix_earpmClient_messageRelease(curMsg); + } + return; +} + +static void celix_earpmClient_refreshSubscriptions(celix_earpm_client_t* client) { + celix_string_hash_map_iterator_t iter = celix_stringHashMap_begin(client->subscriptions); + while (!celix_stringHashMapIterator_isEnd(&iter)) { + const char* topic = iter.key; + celix_earpm_qos_e qos = (celix_earpm_qos_e)iter.value.longValue; + if (qos > CELIX_EARPM_QOS_UNKNOWN) { + int rc = mosquitto_subscribe_v5(client->mosq, NULL, topic, qos, MQTT_SUB_OPT_NO_LOCAL, NULL); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_error(client->logHelper, "Error subscribing to topic %s with qos %d. %d.", topic, (int)qos, rc); + } + celix_stringHashMapIterator_next(&iter); + } else { + int rc = mosquitto_unsubscribe(client->mosq, NULL, topic); + if (rc != MOSQ_ERR_SUCCESS) { + celix_logHelper_warning(client->logHelper, "Error unsubscribing from topic %s. %d.", topic, rc); + } + celix_stringHashMapIterator_remove(&iter); + } + } + return; +} + +static void celix_earpmClient_connectCallback(struct mosquitto* mosq CELIX_UNUSED, void* handle, int rc, int flag CELIX_UNUSED, const mosquitto_property* props CELIX_UNUSED) { + assert(handle != NULL); + celix_earpm_client_t* client = handle; + if (rc != MQTT_RC_SUCCESS) { + celix_logHelper_error(client->logHelper, "Failed to connect to mqtt broker. %d.", rc); + return; + } + celix_logHelper_trace(client->logHelper, "Connected to broker."); + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + client->connected = true; + celix_earpmClient_refreshSubscriptions(client); + celix_earpmClient_releaseWaitingMsgToPublishing(client); + } + celixThreadCondition_broadcast(&client->msgStatusChanged); + + client->connectedCallback(client->callbackHandle); + return; +} + +static void celix_earpmClient_dropQos0Messages(celix_earpm_client_t* client) { + celix_long_hash_map_iterator_t iter = celix_longHashMap_begin(client->publishingMessages); + while (!celix_longHashMapIterator_isEnd(&iter)) { + celix_earpm_client_msg_t* msg = (celix_earpm_client_msg_t*)iter.value.ptrValue; + if (msg->qos <= CELIX_EARPM_QOS_AT_MOST_ONCE) { + celix_logHelper_warning(client->logHelper, "Mqtt disconnected, drop publishing message with qos %d. %s.", (int)msg->qos, msg->topic); + celix_earpmClient_markMsgProcessDone(msg, CELIX_ILLEGAL_STATE); + celix_longHashMapIterator_remove(&iter); + } else { + celix_longHashMapIterator_next(&iter); + } + } + + for (celix_earpm_client_msg_t *curMsg = TAILQ_FIRST(&client->waitingMessages), *nextMsg = NULL; curMsg != NULL; curMsg = nextMsg) { + nextMsg = TAILQ_NEXT(curMsg, entry); + if (curMsg->qos <= CELIX_EARPM_QOS_AT_MOST_ONCE) { + celix_logHelper_warning(client->logHelper, "Mqtt disconnected, drop waiting message with qos %d. %s.", (int)curMsg->qos, curMsg->topic); + celix_earpmClient_markMsgProcessDone(curMsg, CELIX_ILLEGAL_STATE); + TAILQ_REMOVE(&client->waitingMessages, curMsg, entry); + celix_earpmClient_messageRelease(curMsg); + } + } + return; +} + +static void celix_earpmClient_disconnectCallback(struct mosquitto* mosq CELIX_UNUSED, void* handle, int rc, const mosquitto_property* props CELIX_UNUSED) { + assert(handle != NULL); + celix_earpm_client_t* client = handle; + celix_logHelper_trace(client->logHelper, "Disconnected from broker. %d", rc); + + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + client->connected = false; + celix_earpmClient_dropQos0Messages(client); + //QOS1 and QOS2 messages will be resent when reconnecting. + } + celixThreadCondition_broadcast(&client->msgStatusChanged); + return; +} + +static void celix_earpmClient_messageCallback(struct mosquitto* mosq CELIX_UNUSED, void* handle, + const struct mosquitto_message* message, const mosquitto_property* props) { + assert(handle != NULL); + assert(message != NULL); + assert(message->topic != NULL); + celix_earpm_client_t* client = handle; + + celix_logHelper_trace(client->logHelper, "Received message on topic %s.", message->topic); + + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = message->topic; + requestInfo.payload = message->payload; + requestInfo.payloadSize = message->payloadlen; + requestInfo.qos = message->qos; + requestInfo.expiryInterval = -1; + uint32_t expiryInterval; + if (mosquitto_property_read_int32(props, MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, &expiryInterval, false) != NULL) { + requestInfo.expiryInterval = expiryInterval; + } + celix_autofree char* responseTopic = NULL; + celix_autofree void* correlationData = NULL; + uint16_t correlationDataSize = 0; + celix_autofree char* senderUUID = NULL; + celix_autofree char* version = NULL; + while (props) { + int id = mosquitto_property_identifier(props); + switch (id) { + case MQTT_PROP_RESPONSE_TOPIC: { + assert(responseTopic == NULL);//It is MQTT Protocol Error to include the Response Topic more than once + if (mosquitto_property_read_string(props, MQTT_PROP_RESPONSE_TOPIC, &responseTopic, false) == NULL) { + celix_logHelper_error(client->logHelper, "Failed to get response topic from sync event %s.", message->topic); + return; + } + break; + } + case MQTT_PROP_CORRELATION_DATA: { + assert(correlationData == NULL);//It is MQTT Protocol Error to include the Correlation Data more than once + if (mosquitto_property_read_binary(props, MQTT_PROP_CORRELATION_DATA, &correlationData, &correlationDataSize, false) == NULL) { + celix_logHelper_error(client->logHelper, "Failed to get correlation data from sync event %s.", message->topic); + return; + } + break; + } + case MQTT_PROP_USER_PROPERTY: { + celix_autofree char* strName = NULL; + celix_autofree char* strValue = NULL; + if (mosquitto_property_read_string_pair(props, MQTT_PROP_USER_PROPERTY, &strName, &strValue, false) == NULL) { + celix_logHelper_error(client->logHelper, "Failed to get user property from sync event %s.", message->topic); + return; + } + if (celix_utils_stringEquals(strName, CELIX_EARPM_MQTT_USER_PROP_SENDER_UUID) && senderUUID == NULL) { + senderUUID = celix_steal_ptr(strValue); + } else if (celix_utils_stringEquals(strName, CELIX_EARPM_MQTT_USER_PROP_MSG_VERSION) && version == NULL) { + version = celix_steal_ptr(strValue); + } + break; + } + default: + break;//do nothing + } + props = mosquitto_property_next(props); + } + requestInfo.responseTopic = responseTopic; + requestInfo.correlationData = correlationData; + requestInfo.correlationDataSize = correlationDataSize; + requestInfo.senderUUID = senderUUID; + requestInfo.version = version; + + client->receiveMsgCallback(client->callbackHandle, &requestInfo); + return; +} + +static void celix_earpmClient_publishCallback(struct mosquitto* mosq CELIX_UNUSED, void* handle, int mid, int reasonCode, + const mosquitto_property *props CELIX_UNUSED) { + assert(handle != NULL); + celix_earpm_client_t* client = handle; + celix_log_level_e logLevel = (reasonCode == MQTT_RC_SUCCESS || reasonCode == MQTT_RC_NO_MATCHING_SUBSCRIBERS) ? CELIX_LOG_LEVEL_TRACE : CELIX_LOG_LEVEL_ERROR; + celix_logHelper_log(client->logHelper, logLevel, "Published message(mid:%d). reason code %d", mid, reasonCode); + + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + celix_earpm_client_msg_t* msg = celix_longHashMap_get(client->publishingMessages, mid); + if (msg != NULL) { + celix_status_t status = (reasonCode == MQTT_RC_SUCCESS || reasonCode == MQTT_RC_NO_MATCHING_SUBSCRIBERS) ? CELIX_SUCCESS : CELIX_ILLEGAL_STATE; + celix_earpmClient_markMsgProcessDone(msg, status); + celix_longHashMap_remove(client->publishingMessages, mid); + } + celix_earpmClient_releaseWaitingMsgToPublishing(client); + } + celixThreadCondition_broadcast(&client->msgStatusChanged); + return; +} + +static int celix_earpmClient_connectBrokerWithHost(celix_earpm_client_t* client, const char* host, int port) { + int rc = mosquitto_connect_bind_v5(client->mosq, host, port, CELIX_EARPM_CLIENT_KEEP_ALIVE, NULL, client->connProps); + celix_logHelper_info(client->logHelper, "Connected to broker %s:%i. %d.", host, port, rc); + return rc == MOSQ_ERR_SUCCESS ? CELIX_SUCCESS : CELIX_ILLEGAL_STATE; +} + +static int celix_earpmClient_connectBrokerWithInterface(celix_earpm_client_t* client, const char* interfaceName, int port, int family) { + struct ifaddrs* ifaddr = NULL; + struct ifaddrs* ifa = NULL; + if (getifaddrs(&ifaddr) < 0) { + celix_logHelper_error(client->logHelper, "Failed to get interface address for %s. %d.", interfaceName, errno); + return errno; + } + celix_status_t status = CELIX_ILLEGAL_STATE; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)) { + continue; + } + if((celix_utils_stringEquals("all", interfaceName) || celix_utils_stringEquals(ifa->ifa_name, interfaceName)) + && (ifa->ifa_addr->sa_family == family || family == AF_UNSPEC)) { + char host[INET6_ADDRSTRLEN] = {0}; + if (ifa->ifa_addr->sa_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr, host, INET6_ADDRSTRLEN); + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr, host, INET6_ADDRSTRLEN); + } + status = celix_earpmClient_connectBrokerWithHost(client, host, port); + if (status == CELIX_SUCCESS) { + break; + } + } + } + if (ifaddr != NULL) { + freeifaddrs(ifaddr); + } + return status; +} + +static int celix_earpmClient_connectBroker(celix_earpm_client_t* client) { + celixThreadMutex_lock(&client->mutex); + bool curBrokerEndpointExisted = false; + if (client->currentBrokerEndpointUUID != NULL) { + curBrokerEndpointExisted = celix_stringHashMap_hasKey(client->brokerInfoMap, client->currentBrokerEndpointUUID); + } + celixThreadMutex_unlock(&client->mutex); + if (curBrokerEndpointExisted && mosquitto_reconnect(client->mosq) == MOSQ_ERR_SUCCESS) { + celix_logHelper_info(client->logHelper, "Reconnected to broker successfully"); + return CELIX_SUCCESS; + } + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *) celix_earpmClient_brokerInfoRelease; + celix_autoptr(celix_string_hash_map_t) brokerInfoMap = celix_stringHashMap_createWithOptions(&opts); + if (brokerInfoMap == NULL) { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to create broker info map."); + return ENOMEM; + } + celixThreadMutex_lock(&client->mutex); + CELIX_STRING_HASH_MAP_ITERATE(client->brokerInfoMap, iter) { + if (celix_stringHashMap_put(brokerInfoMap, iter.key, iter.value.ptrValue) == CELIX_SUCCESS) { + celix_earpmClient_brokerInfoRetain((celix_earpm_client_broker_info_t *) iter.value.ptrValue); + } else { + celix_logHelper_logTssErrors(client->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(client->logHelper, "Failed to copy broker info."); + } + } + celixThreadMutex_unlock(&client->mutex); + + celix_status_t status = CELIX_ILLEGAL_STATE; + CELIX_STRING_HASH_MAP_ITERATE(brokerInfoMap, iter) { + celix_earpm_client_broker_info_t* info = iter.value.ptrValue; + const char* address = ""; + if (info->bindInterface != NULL) { + status = celix_earpmClient_connectBrokerWithInterface(client, info->bindInterface, info->port, info->family); + } else { + int size = celix_arrayList_size(info->addresses); + for (int i = 0; i < size; ++i) { + address = celix_arrayList_getString(info->addresses, i); + status = celix_earpmClient_connectBrokerWithHost(client, address, info->port); + } + } + if (status == CELIX_SUCCESS) { + celix_logHelper_info(client->logHelper, "Connected to broker successfully"); + free(client->currentBrokerEndpointUUID); + client->currentBrokerEndpointUUID = celix_utils_strdup(iter.key); + return CELIX_SUCCESS; + } + } + return status; +} + +static void celix_earpmClient_waitForActiveBroker(celix_earpm_client_t* client, unsigned int timeoutInSec) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&client->mutex); + while (client->running && (celix_stringHashMap_size(client->brokerInfoMap) == 0 || timeoutInSec > 0)) { + if (celix_stringHashMap_size(client->brokerInfoMap) == 0) { + celixThreadCondition_wait(&client->brokerInfoChangedOrExiting, &client->mutex); + } else { + celixThreadCondition_timedwaitRelative(&client->brokerInfoChangedOrExiting, &client->mutex, timeoutInSec, 0); + } + timeoutInSec = 0; + } +} + +static celix_status_t celix_earpmClient_reconnectBroker(celix_earpm_client_t* client, unsigned int* connectFailedCount, unsigned int* nextReconnectInterval) { + celix_status_t status = celix_earpmClient_connectBroker(client); + if (status != CELIX_SUCCESS) { + *connectFailedCount += 1; + unsigned int reconnectInterval = *connectFailedCount * 1; + *nextReconnectInterval = (reconnectInterval < CELIX_EARPM_CLIENT_RECONNECT_DELAY_MAX) ? reconnectInterval : CELIX_EARPM_CLIENT_RECONNECT_DELAY_MAX; + celix_logHelper_info(client->logHelper, "Failed to connect to broker, retry after %u second, err:%d.", *nextReconnectInterval, status); + } else { + *connectFailedCount = 0; + *nextReconnectInterval = 0; + } + return status; +} + +static void celix_earpmClient_runMosqLoopUntilFailed(celix_earpm_client_t* client) { + celixThreadMutex_lock(&client->mutex); + bool loopRun = client->running; + celixThreadMutex_unlock(&client->mutex); + while (loopRun) { + loopRun = mosquitto_loop(client->mosq, CELIX_EARPM_CLIENT_KEEP_ALIVE * 1000, 1) == MOSQ_ERR_SUCCESS; + celixThreadMutex_lock(&client->mutex); + loopRun = loopRun && client->running; + celixThreadMutex_unlock(&client->mutex); + } + return; +} + +static void* celix_earpmClient_worker(void* data) { + assert(data != NULL); + unsigned int nextReconnectInterval = 0; + unsigned int connectFailedCount = 0; + celix_earpm_client_t* client = (celix_earpm_client_t*)data; + + celixThreadMutex_lock(&client->mutex); + bool running = client->running; + celixThreadMutex_unlock(&client->mutex); + + while (running) { + celix_earpmClient_waitForActiveBroker(client, nextReconnectInterval); + + celixThreadMutex_lock(&client->mutex); + running = client->running; + celixThreadMutex_unlock(&client->mutex); + + if (running) { + celix_status_t status = celix_earpmClient_reconnectBroker(client, &connectFailedCount, &nextReconnectInterval); + if (status != CELIX_SUCCESS) { + continue; + } + celix_earpmClient_runMosqLoopUntilFailed(client); + } + } + return NULL; +} diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.h new file mode 100644 index 000000000..7ea5e7356 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_client.h @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_CLIENT_H +#define CELIX_EARPM_CLIENT_H +#ifdef __cplusplus +extern "C" { +#endif +#include +#include + +#include "celix_errno.h" +#include "celix_cleanup.h" +#include "celix_properties.h" +#include "celix_bundle_context.h" +#include "celix_log_helper.h" +#include "endpoint_description.h" +#include "celix_earpm_constants.h" + + +typedef struct celix_earpm_client celix_earpm_client_t; +typedef struct celix_earpm_client_request_info celix_earpm_client_request_info_t; + +typedef void (*celix_earpm_client_receive_msg_fp)(void* handle, const celix_earpm_client_request_info_t* requestInfo); +typedef void (*celix_earpm_client_connected_fp)(void* handle); + +typedef struct celix_earpm_client_create_options { + celix_bundle_context_t* ctx; + celix_log_helper_t* logHelper; + const char* sessionEndMsgTopic; + const char* sessionEndMsgSenderUUID; + const char* sessionEndMsgVersion; + void* callbackHandle; + celix_earpm_client_receive_msg_fp receiveMsgCallback; + celix_earpm_client_connected_fp connectedCallback; +} celix_earpm_client_create_options_t; + +typedef enum celix_earpm_client_message_priority { + //The priority message will be sent after all middle-priority messages, + // and the new message which has this priority will be discarded if the message queue usage rate exceeds %70 + CELIX_EARPM_MSG_PRI_LOW = 0, + //The priority message will be sent after all high-priority messages, + // and the new message which has this priority will be discarded if the message queue usage rate exceeds %85 + CELIX_EARPM_MSG_PRI_MIDDLE = 1, + //The priority message will be sent as soon as possible, + // and the new message which has this priority will be discarded if the message queue usage rate exceeds %100 + CELIX_EARPM_MSG_PRI_HIGH = 2, +} celix_earpm_client_message_priority_e; + +struct celix_earpm_client_request_info { + const char* topic; + const char* payload; + size_t payloadSize; + celix_earpm_qos_e qos; + celix_earpm_client_message_priority_e pri; + double expiryInterval;//seconds + const char* responseTopic; + const void* correlationData; + size_t correlationDataSize; + const char* senderUUID; + const char* version; +}; + +celix_earpm_client_t* celix_earpmClient_create(celix_earpm_client_create_options_t* options); + +void celix_earpmClient_destroy(celix_earpm_client_t* client); + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_client_t, celix_earpmClient_destroy); + +celix_status_t celix_earpmClient_mqttBrokerEndpointAdded(void* handle, const endpoint_description_t* endpoint, char* matchedFilter); + +celix_status_t celix_earpmClient_mqttBrokerEndpointRemoved(void* handle, const endpoint_description_t* endpoint, char* matchedFilter); + +celix_status_t celix_earpmClient_subscribe(celix_earpm_client_t* client, const char* topic, celix_earpm_qos_e qos); +celix_status_t celix_earpmClient_unsubscribe(celix_earpm_client_t* client, const char* topic); + +celix_status_t celix_earpmClient_publishAsync(celix_earpm_client_t* client, const celix_earpm_client_request_info_t* requestInfo); + +celix_status_t celix_earpmClient_publishSync(celix_earpm_client_t* client, const celix_earpm_client_request_info_t* requestInfo); + +void celix_earpmClient_info(celix_earpm_client_t* client, FILE* outStream); + + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_CLIENT_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_constants.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_constants.h new file mode 100644 index 000000000..d2c4e17de --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_constants.h @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_CONSTANTS_H +#define CELIX_EARPM_CONSTANTS_H +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Control message topics for the EventAdminMqtt + * @{ + */ +#define CELIX_EARPM_TOPIC_PREFIX "celix/EventAdminMqtt/" +#define CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX CELIX_EARPM_TOPIC_PREFIX"HandlerInfo/" +#define CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"query" +#define CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"update" +#define CELIX_EARPM_HANDLER_INFO_ADD_TOPIC CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"add" +#define CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"remove" +#define CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX CELIX_EARPM_TOPIC_PREFIX"SyncEvent/ack/" // topic = topic prefix + requester framework UUID +#define CELIX_EARPM_SESSION_END_TOPIC CELIX_EARPM_TOPIC_PREFIX"session/end" + +/** @}*///end of Control message topics for the EventAdminMqtt + +/** + * Configuration properties for the EventAdminMqtt + * @{ + */ + +/** + * @brief The profile of the MQTT broker, which is used to configure the MQTT broker. Default value is /etc/mosquitto.conf. + */ +#define CELIX_EARPM_BROKER_PROFILE "CELIX_EARPM_BROKER_PROFILE" +#define CELIX_EARPM_BROKER_PROFILE_DEFAULT "/etc/mosquitto.conf" + +/** + * @brief The default QoS for events. If the event does not specify a QoS with the CELIX_EVENT_REMOTE_QOS property, this QoS is used. Default value is CELIX_EARPM_QOS_AT_MOST_ONCE. + * @relatedalso CELIX_EVENT_REMOTE_QOS + */ +#define CELIX_EARPM_EVENT_DEFAULT_QOS "CELIX_EARPM_EVENT_DEFAULT_QOS" +#define CELIX_EARPM_EVENT_DEFAULT_QOS_DEFAULT CELIX_EARPM_QOS_AT_MOST_ONCE + +/** + * @brief The capacity of the message queue. Default value is 256, and the maximum value is 1024*1024. + * The message queue is used to cache the messages when processing the messages. + */ +#define CELIX_EARPM_MSG_QUEUE_CAPACITY "CELIX_EARPM_MSG_QUEUE_CAPACITY" +#define CELIX_EARPM_MSG_QUEUE_CAPACITY_DEFAULT 256 +#define CELIX_EARPM_MSG_QUEUE_MAX_SIZE 2048 + +/** + * @brief The capacity of processing messages in parallel. Default value is 20. The maximum value is CELIX_EARPM_MSG_QUEUE_CAPACITY. + * It is used to limit the number of messages that are processed in parallel. + */ +#define CELIX_EARPM_PARALLEL_MSG_CAPACITY "CELIX_EARPM_PARALLEL_MSG_CAPACITY" +#define CELIX_EARPM_PARALLEL_MSG_CAPACITY_DEFAULT 20 + +/** + * @brief The thread pool size for delivering the sync events. Default value is 5. The maximum value is 20. + * It is used to deliver the remote sync events to the local event handlers. + */ +#define CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS "CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS" +#define CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_DEFAULT 5 + +/** + * @brief If remote celix framework instance does not acknowledge a sync event in specified time, and the exception count is larger than this value, + * the event remote provider will not wait for the acknowledgment anymore, until receiving a new message from the remote celix framework instance. Default value is 10. + */ +#define CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD "CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD" +#define CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD_DEFAULT 10 + +/** @}*///end of Configuration properties for the EventAdminMqtt + +/** + * @brief The QoS for the MQTT messages. + */ +typedef enum celix_earpm_qos { + CELIX_EARPM_QOS_UNKNOWN = -1, + CELIX_EARPM_QOS_AT_MOST_ONCE = 0, + CELIX_EARPM_QOS_AT_LEAST_ONCE = 1, + CELIX_EARPM_QOS_EXACTLY_ONCE = 2, + CELIX_EARPM_QOS_MAX, +}celix_earpm_qos_e; + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_CONSTANTS_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.c b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.c new file mode 100644 index 000000000..2b0fa7a73 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.c @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_earpm_event_deliverer.h" + +#include +#include + +#include "celix_threads.h" +#include "celix_array_list.h" +#include "celix_stdlib_cleanup.h" +#include "celix_utils.h" +#include "celix_earpm_constants.h" + +#define CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_MAX 20 + +typedef struct celix_earpm_deliverer_event_entry { + celix_properties_t* properties; + char* topic; + celix_earpm_deliver_done_callback done; + void* doneHandle; +} celix_earpm_deliverer_event_entry_t; + + +struct celix_earpm_event_deliverer { + celix_bundle_context_t* ctx; + celix_log_helper_t *logHelper; + + celix_thread_rwlock_t eaLock;//protects eventAdminSvc + celix_event_admin_service_t* eventAdminSvc; + long syncEventDeliveryThreadsNr; + long syncEventQueueSizeMax; + celix_thread_mutex_t mutex;//protects belows + celix_thread_t syncEventDeliveryThreads[CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_MAX]; + celix_thread_cond_t hasSyncEventsOrExiting; + celix_array_list_t* syncEventQueue;//element = celix_earpmd_event_entry_t* + bool syncEventDeliveryThreadRunning; +}; + +static void* celix_earpmDeliverer_syncEventDeliveryThread(void* data); + +celix_earpm_event_deliverer_t* celix_earpmDeliverer_create(celix_bundle_context_t* ctx, celix_log_helper_t* logHelper) { + assert(ctx != NULL); + assert(logHelper != NULL); + long syncEventDeliveryThreadsNr = celix_bundleContext_getPropertyAsLong(ctx, CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS, + CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_DEFAULT); + if (syncEventDeliveryThreadsNr <= 0 || syncEventDeliveryThreadsNr > CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_MAX) { + celix_logHelper_error(logHelper, "Invalid number of sync event delivery threads %ld, must be in range 1-%d.", syncEventDeliveryThreadsNr, CELIX_EARPM_SYNC_EVENT_DELIVERY_THREADS_MAX); + return NULL; + } + long syncEventQueueCap = celix_bundleContext_getPropertyAsLong(ctx, CELIX_EARPM_MSG_QUEUE_CAPACITY, CELIX_EARPM_MSG_QUEUE_CAPACITY_DEFAULT); + if (syncEventQueueCap <= 0 || syncEventQueueCap > CELIX_EARPM_MSG_QUEUE_MAX_SIZE) { + celix_logHelper_error(logHelper, "Invalid sync event queue capacity %ld, must be in range 1-%d.", syncEventQueueCap, CELIX_EARPM_MSG_QUEUE_MAX_SIZE); + return NULL; + } + celix_autofree celix_earpm_event_deliverer_t* deliverer = calloc(1, sizeof(*deliverer)); + if (deliverer == NULL) { + celix_logHelper_error(logHelper, "Failed to allocate memory for event deliverer."); + return NULL; + } + deliverer->ctx = ctx; + deliverer->logHelper = logHelper; + deliverer->eventAdminSvc = NULL; + deliverer->syncEventDeliveryThreadsNr = syncEventDeliveryThreadsNr; + deliverer->syncEventQueueSizeMax = syncEventQueueCap; + celix_status_t status = celixThreadRwlock_create(&deliverer->eaLock, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create event admin rwlock for event deliverer. %d.", status); + return NULL; + } + celix_autoptr(celix_thread_rwlock_t) eaLock = &deliverer->eaLock; + status = celixThreadMutex_create(&deliverer->mutex, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create event deliverer mutex. %d.", status); + return NULL; + } + celix_autoptr(celix_thread_mutex_t) mutex = &deliverer->mutex; + status = celixThreadCondition_init(&deliverer->hasSyncEventsOrExiting, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create event deliverer condition. %d.", status); + return NULL; + } + celix_autoptr(celix_thread_cond_t) cond = &deliverer->hasSyncEventsOrExiting; + celix_autoptr(celix_array_list_t) syncEventQueue = deliverer->syncEventQueue = celix_arrayList_createPointerArray(); + if (syncEventQueue == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create sync event queue."); + return NULL; + } + + deliverer->syncEventDeliveryThreadRunning = true; + for (int i = 0; i < syncEventDeliveryThreadsNr; ++i) { + status = celixThread_create(&deliverer->syncEventDeliveryThreads[i], NULL, + celix_earpmDeliverer_syncEventDeliveryThread, deliverer); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create sync event delivery thread %d. %d.", i, status); + for (int j = 0; j < i; ++j) { + celixThreadMutex_lock(mutex); + deliverer->syncEventDeliveryThreadRunning = false; + celixThreadMutex_unlock(mutex); + celixThreadCondition_broadcast(&deliverer->hasSyncEventsOrExiting); + celixThread_join(deliverer->syncEventDeliveryThreads[j], NULL); + } + return NULL; + } + char threadName[32]; + snprintf(threadName, 32, "CelixEarpmED%d", i); + celixThread_setName(&deliverer->syncEventDeliveryThreads[i], threadName); + } + + celix_steal_ptr(syncEventQueue); + celix_steal_ptr(cond); + celix_steal_ptr(mutex); + celix_steal_ptr(eaLock); + + return celix_steal_ptr(deliverer); +} + +void celix_earpmDeliverer_destroy(celix_earpm_event_deliverer_t* deliverer) { + assert(deliverer != NULL); + celixThreadMutex_lock(&deliverer->mutex); + deliverer->syncEventDeliveryThreadRunning = false; + celixThreadMutex_unlock(&deliverer->mutex); + celixThreadCondition_broadcast(&deliverer->hasSyncEventsOrExiting); + for (int i = 0; i < deliverer->syncEventDeliveryThreadsNr; ++i) { + celixThread_join(deliverer->syncEventDeliveryThreads[i], NULL); + } + int size = celix_arrayList_size(deliverer->syncEventQueue); + for (int i = 0; i < size; ++i) { + celix_earpm_deliverer_event_entry_t* entry = (celix_earpm_deliverer_event_entry_t*)celix_arrayList_get(deliverer->syncEventQueue, i); + if (entry->done != NULL) { + entry->done(entry->doneHandle, entry->topic, CELIX_ILLEGAL_STATE); + } + celix_properties_destroy(entry->properties); + free(entry->topic); + free(entry); + } + celix_arrayList_destroy(deliverer->syncEventQueue); + celixThreadCondition_destroy(&deliverer->hasSyncEventsOrExiting); + celixThreadMutex_destroy(&deliverer->mutex); + celixThreadRwlock_destroy(&deliverer->eaLock); + free(deliverer); + return; +} + +celix_status_t celix_earpmDeliverer_setEventAdminSvc(celix_earpm_event_deliverer_t* deliverer, celix_event_admin_service_t *eventAdminSvc) { + celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&deliverer->eaLock); + deliverer->eventAdminSvc = eventAdminSvc; + return CELIX_SUCCESS; +} + +celix_status_t celix_earpmDeliverer_postEvent(celix_earpm_event_deliverer_t* deliverer, const char* topic, celix_properties_t* properties) { + assert(deliverer != NULL); + assert(topic != NULL); + celix_autoptr(celix_properties_t) _properties = properties; + celix_auto(celix_rwlock_rlock_guard_t) rLockGuard = celixRwlockRlockGuard_init(&deliverer->eaLock); + celix_event_admin_service_t* eventAdminSvc = deliverer->eventAdminSvc; + if (eventAdminSvc == NULL) { + celix_logHelper_warning(deliverer->logHelper, "No event admin service available, drop event %s.", topic); + return CELIX_ILLEGAL_STATE; + } + celix_status_t status = eventAdminSvc->postEvent(eventAdminSvc->handle, topic, _properties); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(deliverer->logHelper, "Failed to post event %s. %d.", topic, status); + } + return status; +} + +celix_status_t celix_earpmDeliverer_sendEvent(celix_earpm_event_deliverer_t* deliverer, const char* topic, celix_properties_t* properties, celix_earpm_deliver_done_callback done, void* callbackData) { + assert(deliverer != NULL); + assert(topic != NULL); + celix_autoptr(celix_properties_t) _properties = properties; + celix_autofree celix_earpm_deliverer_event_entry_t* entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + celix_logHelper_error(deliverer->logHelper, "Failed to allocate memory for event entry for %s.", topic); + return ENOMEM; + } + celix_autofree char* _topic = entry->topic = celix_utils_strdup(topic); + if (entry->topic == NULL) { + celix_logHelper_error(deliverer->logHelper, "Failed to duplicate topic %s.", topic); + return ENOMEM; + } + entry->properties = properties; + entry->done = done; + entry->doneHandle = callbackData; + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&deliverer->mutex); + if (celix_arrayList_size(deliverer->syncEventQueue) >= deliverer->syncEventQueueSizeMax) { + celix_logHelper_error(deliverer->logHelper, "Sync event queue full, drop event %s.", topic); + return ENOMEM; + } + celix_status_t status = celix_arrayList_add(deliverer->syncEventQueue, entry); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(deliverer->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(deliverer->logHelper, "Failed to add event to sync event queue for %s. %d.", topic, status); + return ENOMEM; + } + } + celix_steal_ptr(_properties); + celix_steal_ptr(_topic); + celix_steal_ptr(entry); + celixThreadCondition_signal(&deliverer->hasSyncEventsOrExiting); + + return CELIX_SUCCESS; +} + +static celix_status_t celix_earpmDeliverer_sendEventToEventAdmin(celix_earpm_event_deliverer_t* deliverer, celix_earpm_deliverer_event_entry_t* eventEntry) { + celix_auto(celix_rwlock_rlock_guard_t) rLockGuard = celixRwlockRlockGuard_init(&deliverer->eaLock); + celix_event_admin_service_t* eventAdminSvc = deliverer->eventAdminSvc; + if (eventAdminSvc == NULL) { + celix_logHelper_warning(deliverer->logHelper, "No event admin service available, drop event %s.", eventEntry->topic); + return CELIX_ILLEGAL_STATE; + } + celix_status_t status = eventAdminSvc->sendEvent(eventAdminSvc->handle, eventEntry->topic, eventEntry->properties); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(deliverer->logHelper, "Failed to send event %s. %d.", eventEntry->topic, status); + } + return status; +} + +static void* celix_earpmDeliverer_syncEventDeliveryThread(void* data) { + assert(data != NULL); + celix_earpm_event_deliverer_t* deliverer = (celix_earpm_event_deliverer_t*)data; + celixThreadMutex_lock(&deliverer->mutex); + bool running = deliverer->syncEventDeliveryThreadRunning; + celixThreadMutex_unlock(&deliverer->mutex); + while (running) { + celix_earpm_deliverer_event_entry_t* entry = NULL; + celixThreadMutex_lock(&deliverer->mutex); + while (deliverer->syncEventDeliveryThreadRunning && celix_arrayList_size(deliverer->syncEventQueue) == 0) { + celixThreadCondition_wait(&deliverer->hasSyncEventsOrExiting, &deliverer->mutex); + } + running = deliverer->syncEventDeliveryThreadRunning; + if (running) { + entry = celix_arrayList_get(deliverer->syncEventQueue, 0); + celix_arrayList_removeAt(deliverer->syncEventQueue, 0); + } + celixThreadMutex_unlock(&deliverer->mutex); + if (entry != NULL) { + celix_status_t status = celix_earpmDeliverer_sendEventToEventAdmin(deliverer, entry); + if (entry->done != NULL) { + entry->done(entry->doneHandle, entry->topic, status); + } + celix_properties_destroy(entry->properties); + free(entry->topic); + free(entry); + } + } + return NULL; +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.h new file mode 100644 index 000000000..ee2233086 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_event_deliverer.h @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_EVENT_DELIVERER_H +#define CELIX_EARPM_EVENT_DELIVERER_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "celix_errno.h" +#include "celix_cleanup.h" +#include "celix_bundle_context.h" +#include "celix_log_helper.h" +#include "celix_event_admin_service.h" + +typedef struct celix_earpm_event_deliverer celix_earpm_event_deliverer_t; + +celix_earpm_event_deliverer_t* celix_earpmDeliverer_create(celix_bundle_context_t* ctx, celix_log_helper_t* logHelper); +void celix_earpmDeliverer_destroy(celix_earpm_event_deliverer_t* deliverer); + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_event_deliverer_t, celix_earpmDeliverer_destroy); + +celix_status_t celix_earpmDeliverer_setEventAdminSvc(celix_earpm_event_deliverer_t* deliverer, celix_event_admin_service_t *eventAdminSvc); + +typedef void (*celix_earpm_deliver_done_callback)(void* data, const char* topic, celix_status_t status); + +celix_status_t celix_earpmDeliverer_postEvent(celix_earpm_event_deliverer_t* deliverer, const char* topic, celix_properties_t* properties); + +celix_status_t celix_earpmDeliverer_sendEvent(celix_earpm_event_deliverer_t* deliverer, const char* topic, celix_properties_t* properties, celix_earpm_deliver_done_callback done, void* callbackData); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_EVENT_DELIVERER_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.c b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.c new file mode 100644 index 000000000..bf2f36a37 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.c @@ -0,0 +1,1477 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_earpm_impl.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "celix_cleanup.h" +#include "celix_stdlib_cleanup.h" +#include "celix_utils.h" +#include "celix_string_hash_map.h" +#include "celix_long_hash_map.h" +#include "celix_array_list.h" +#include "celix_threads.h" +#include "celix_constants.h" +#include "celix_filter.h" +#include "celix_event_constants.h" +#include "celix_event_remote_provider_service.h" +#include "celix_earpm_event_deliverer.h" +#include "celix_earpm_client.h" +#include "celix_earpm_constants.h" +#include "celix_earpm_broker_discovery.h" + +/** + * @brief The remote sync event default timeout in seconds. Remote event can use CELIX_EVENT_REMOTE_EXPIRY_INTERVAL to override this value. + * If the event remote provider does not receive an ack within this time, the event will be delivered failed. + */ +#define CELIX_EARPM_SYNC_EVENT_TIMEOUT_DEFAULT (5*60) //seconds + +/** + * @brief The version of the remote provider messages(It contains event messages and control messages). + */ +#define CELIX_EARPM_MSG_VERSION "1.0.0" + +typedef struct celix_earpm_event_handler { + celix_array_list_t* topics; + char* filter; + celix_earpm_qos_e qos; + long serviceId; +} celix_earpm_event_handler_t; + +typedef struct celix_earpm_event_subscription { + celix_array_list_t* handlerServiceIdList; + celix_earpm_qos_e curQos; +} celix_earpm_event_subscription_t; + +typedef struct celix_earpm_remote_handler_info { + celix_array_list_t* topics; + celix_filter_t* filter; +} celix_earpm_remote_handler_info_t; + +typedef struct celix_earpm_remote_framework_info { + celix_long_hash_map_t* handlerInfoMap;//key = serviceId, value = celix_earpm_remote_handler_info_t* + celix_long_hash_map_t* eventAckSeqNrMap;//key = seqNr, value = celix_array_list_t* of serviceIds + int continuousNoAckCount; +} celix_earpm_remote_framework_info_t; + +struct celix_earpm_sync_event_correlation_data { + long ackSeqNr; +}; + +struct celix_earpm_send_event_done_callback_data { + celix_event_admin_remote_provider_mqtt_t* earpm; + char* responseTopic; + void* correlationData; + size_t correlationDataSize; +}; + +struct celix_event_admin_remote_provider_mqtt { + celix_bundle_context_t* ctx; + celix_log_helper_t* logHelper; + const char* fwUUID; + celix_earpm_qos_e defaultQos; + int continuousNoAckThreshold; + char* syncEventAckTopic; + celix_earpm_event_deliverer_t* deliverer; + celix_earpm_client_t* mqttClient; + long lastAckSeqNr; + celix_thread_mutex_t mutex;//protects belows + celix_thread_cond_t ackCond; + celix_long_hash_map_t* eventHandlers;//key = serviceId, value = celix_earpm_event_handler_t* + celix_string_hash_map_t* eventSubscriptions;//key = topic, value = celix_earpm_event_subscription_t* + celix_string_hash_map_t* remoteFrameworks;// key = frameworkUUID of remote frameworks, value = celix_earpm_remote_framework_info_t* + bool destroying; +}; + +static void celix_earpm_eventHandlerDestroy(celix_earpm_event_handler_t* handler); +static void celix_earpm_subscriptionDestroy(celix_earpm_event_subscription_t* subscription); +static void celix_earpm_remoteFrameworkInfoDestroy(celix_earpm_remote_framework_info_t* info); +static void celix_earpm_receiveMsgCallback(void* handle, const celix_earpm_client_request_info_t* requestInfo); +static void celix_earpm_connectedCallback(void* handle); +static void celix_earpm_subOrUnsubRemoteEventsForHandler(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler, bool subscribe); +static void celix_earpm_addHandlerInfoToRemote(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler); +static void celix_earpm_removeHandlerInfoFromRemote(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler); +static celix_status_t celix_earpm_publishEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const char *topic, + const celix_properties_t *eventProps, bool async); + + +celix_event_admin_remote_provider_mqtt_t* celix_earpm_create(celix_bundle_context_t* ctx) { + assert(ctx != NULL); + celix_autofree celix_event_admin_remote_provider_mqtt_t* earpm = calloc(1, sizeof(*earpm)); + if (earpm == NULL) { + return NULL; + } + earpm->ctx = ctx; + earpm->lastAckSeqNr = 0; + earpm->destroying = false; + celix_autoptr(celix_log_helper_t) logHelper = earpm->logHelper = celix_logHelper_create(ctx, "celix_earpm"); + if (logHelper == NULL) { + return NULL; + } + earpm->fwUUID = celix_bundleContext_getProperty(ctx, CELIX_FRAMEWORK_UUID, NULL); + if (earpm->fwUUID == NULL) { + celix_logHelper_error(logHelper, "Failed to get framework UUID."); + return NULL; + } + earpm->defaultQos = (celix_earpm_qos_e)celix_bundleContext_getPropertyAsLong(ctx, CELIX_EARPM_EVENT_DEFAULT_QOS, CELIX_EARPM_EVENT_DEFAULT_QOS_DEFAULT); + if (earpm->defaultQos <= CELIX_EARPM_QOS_UNKNOWN || earpm->defaultQos >= CELIX_EARPM_QOS_MAX) { + celix_logHelper_error(logHelper, "Invalid default QOS(%d) value.", (int)earpm->defaultQos); + return NULL; + } + + earpm->continuousNoAckThreshold = (int)celix_bundleContext_getPropertyAsLong(ctx, CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD, CELIX_EARPM_SYNC_EVENT_CONTINUOUS_NO_ACK_THRESHOLD_DEFAULT); + if (earpm->continuousNoAckThreshold <= 0) { + celix_logHelper_error(logHelper, "Invalid continuous no ack threshold(%d) value.", earpm->continuousNoAckThreshold); + return NULL; + } + + if (asprintf(&earpm->syncEventAckTopic, CELIX_EARPM_SYNC_EVENT_ACK_TOPIC_PREFIX"%s", earpm->fwUUID) < 0) { + celix_logHelper_error(logHelper, "Failed to create sync event response topic."); + return NULL; + } + celix_autofree char* syncEventAckTopic = earpm->syncEventAckTopic; + celix_status_t status = celixThreadMutex_create(&earpm->mutex, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create mutex, %d.", status); + return NULL; + } + celix_autoptr(celix_thread_mutex_t) mutex = &earpm->mutex; + status = celixThreadCondition_init(&earpm->ackCond, NULL); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(logHelper, "Failed to create condition for event ack, %d.", status); + return NULL; + } + celix_autoptr(celix_thread_cond_t) ackCond = &earpm->ackCond; + celix_autoptr(celix_long_hash_map_t) eventHandlers = NULL; + { + celix_long_hash_map_create_options_t opts = CELIX_EMPTY_LONG_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *)celix_earpm_eventHandlerDestroy; + eventHandlers = earpm->eventHandlers = celix_longHashMap_createWithOptions(&opts); + if (eventHandlers == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create local event handler map."); + return NULL; + } + } + celix_autoptr(celix_string_hash_map_t) eventSubscriptions = NULL; + { + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *)celix_earpm_subscriptionDestroy; + eventSubscriptions = earpm->eventSubscriptions = celix_stringHashMap_createWithOptions(&opts); + if (eventSubscriptions == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create event subscription map for local event handler."); + return NULL; + } + } + celix_autoptr(celix_string_hash_map_t) remoteFrameworks = NULL; + { + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void*)celix_earpm_remoteFrameworkInfoDestroy; + remoteFrameworks = earpm->remoteFrameworks = celix_stringHashMap_createWithOptions(&opts); + if (remoteFrameworks == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create remote framework information map."); + return NULL; + } + } + celix_autoptr(celix_earpm_event_deliverer_t) deliverer = earpm->deliverer = celix_earpmDeliverer_create(ctx, logHelper); + if (deliverer == NULL) { + celix_logHelper_error(logHelper, "Failed to create event deliverer."); + return NULL; + } + celix_earpm_client_create_options_t opts; + memset(&opts, 0, sizeof(opts)); + opts.ctx = ctx; + opts.logHelper = logHelper; + opts.sessionEndMsgTopic = CELIX_EARPM_SESSION_END_TOPIC; + opts.sessionEndMsgSenderUUID = earpm->fwUUID; + opts.sessionEndMsgVersion = CELIX_EARPM_MSG_VERSION; + opts.callbackHandle = earpm; + opts.receiveMsgCallback = celix_earpm_receiveMsgCallback; + opts.connectedCallback = celix_earpm_connectedCallback; + celix_autoptr(celix_earpm_client_t) mqttClient = earpm->mqttClient = celix_earpmClient_create(&opts); + if (mqttClient == NULL) { + celix_logHelper_error(logHelper, "Failed to create mqtt client."); + return NULL; + } + status = celix_earpmClient_subscribe(earpm->mqttClient, CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX"*", CELIX_EARPM_QOS_AT_LEAST_ONCE); + status = CELIX_DO_IF(status, celix_earpmClient_subscribe(earpm->mqttClient, CELIX_EARPM_SESSION_END_TOPIC, CELIX_EARPM_QOS_AT_LEAST_ONCE)); + status = CELIX_DO_IF(status, celix_earpmClient_subscribe(earpm->mqttClient, syncEventAckTopic, CELIX_EARPM_QOS_AT_LEAST_ONCE)); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to subscribe control message. %d.", status); + return NULL; + } + + celix_steal_ptr(mqttClient); + celix_steal_ptr(deliverer); + celix_steal_ptr(remoteFrameworks); + celix_steal_ptr(eventSubscriptions); + celix_steal_ptr(eventHandlers); + celix_steal_ptr(ackCond); + celix_steal_ptr(syncEventAckTopic); + celix_steal_ptr(mutex); + celix_steal_ptr(logHelper); + + return celix_steal_ptr(earpm); +} + +void celix_earpm_destroy(celix_event_admin_remote_provider_mqtt_t* earpm) { + assert(earpm != NULL); + celixThreadMutex_lock(&earpm->mutex); + earpm->destroying = true; + celixThreadMutex_unlock(&earpm->mutex); + celix_earpmClient_destroy(earpm->mqttClient); + celix_earpmDeliverer_destroy(earpm->deliverer); + celix_stringHashMap_destroy(earpm->remoteFrameworks); + celix_stringHashMap_destroy(earpm->eventSubscriptions); + celix_longHashMap_destroy(earpm->eventHandlers); + celixThreadCondition_destroy(&earpm->ackCond); + celixThreadMutex_destroy(&earpm->mutex); + free(earpm->syncEventAckTopic); + celix_logHelper_destroy(earpm->logHelper); + free(earpm); + return; +} + +celix_status_t celix_earpm_mqttBrokerEndpointAdded(void* handle, endpoint_description_t* endpoint, char* matchedFilter) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + return celix_earpmClient_mqttBrokerEndpointAdded(earpm->mqttClient, endpoint, matchedFilter); +} + +celix_status_t celix_earpm_mqttBrokerEndpointRemoved(void* handle, endpoint_description_t* endpoint, char* matchedFilter) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + return celix_earpmClient_mqttBrokerEndpointRemoved(earpm->mqttClient, endpoint, matchedFilter); +} + +static celix_earpm_event_handler_t* celix_earpm_createEventHandler(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_properties_t* eventHandlerProperties) { + long serviceId = celix_properties_getAsLong(eventHandlerProperties, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId < 0 ) { + celix_logHelper_error(earpm->logHelper, "Failed to add event handler service, no service id found."); + return NULL; + } + celix_earpm_qos_e qos = (celix_earpm_qos_e)celix_properties_getAsLong(eventHandlerProperties, CELIX_EVENT_REMOTE_QOS, earpm->defaultQos); + if (qos <= CELIX_EARPM_QOS_UNKNOWN || qos >= CELIX_EARPM_QOS_MAX) { + celix_logHelper_error(earpm->logHelper, "Failed to add event handler service, invalid qos %d.", (int)qos); + return NULL; + } + celix_autoptr(celix_array_list_t) topics = NULL; + celix_status_t status = celix_properties_getAsStringArrayList(eventHandlerProperties, CELIX_EVENT_TOPIC, NULL, &topics); + if (status != CELIX_SUCCESS || topics == NULL || celix_arrayList_size(topics) == 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add event handler service, maybe topic pattern is invalid. %d.", status); + return NULL; + } + celix_autofree char* filterCopy = NULL; + const char* filter = celix_properties_get(eventHandlerProperties, CELIX_EVENT_FILTER, NULL); + if (filter != NULL) { + filterCopy = celix_utils_strdup(filter); + if (filterCopy == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to add event handler service, out of memory."); + return NULL; + } + } + celix_autofree celix_earpm_event_handler_t* handler = calloc(1, sizeof(*handler)); + if (handler == NULL) { + return NULL; + } + handler->serviceId = serviceId; + handler->topics = celix_steal_ptr(topics); + handler->filter = celix_steal_ptr(filterCopy); + handler->qos = qos; + + return celix_steal_ptr(handler); +} + +static void celix_earpm_eventHandlerDestroy(celix_earpm_event_handler_t* handler) { + celix_arrayList_destroy(handler->topics); + free(handler->filter); + free(handler); +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_event_handler_t, celix_earpm_eventHandlerDestroy) + +static celix_earpm_event_subscription_t* celix_earpm_subscriptionCreate(void) { + celix_autofree celix_earpm_event_subscription_t* subscription = calloc(1, sizeof(*subscription)); + if (subscription == NULL) { + return NULL; + } + subscription->curQos = CELIX_EARPM_QOS_UNKNOWN; + subscription->handlerServiceIdList = celix_arrayList_create(); + if (subscription->handlerServiceIdList == NULL) { + return NULL; + } + return celix_steal_ptr(subscription); +} + +static void celix_earpm_subscriptionDestroy(celix_earpm_event_subscription_t* subscription) { + celix_arrayList_destroy(subscription->handlerServiceIdList); + free(subscription); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_event_subscription_t, celix_earpm_subscriptionDestroy) + +static void celix_earpm_subscribeEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, celix_earpm_qos_e qos, long handlerServiceId) { + celix_status_t status = CELIX_SUCCESS; + celix_earpm_event_subscription_t* subscription= celix_stringHashMap_get(earpm->eventSubscriptions, topic); + if (subscription == NULL) { + celix_autoptr(celix_earpm_event_subscription_t) _subscription = celix_earpm_subscriptionCreate(); + if (_subscription == NULL) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to create subscription info for %s.", topic); + return; + } + status = celix_stringHashMap_put(earpm->eventSubscriptions, topic, _subscription); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to add subscription info for %s. %d.", topic, status); + return; + } + subscription = celix_steal_ptr(_subscription); + } + status = celix_arrayList_addLong(subscription->handlerServiceIdList, handlerServiceId); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to attach handler service(%ld) to subscription info for %s. %d.",handlerServiceId, topic, status); + if (celix_arrayList_size(subscription->handlerServiceIdList) == 0) { + celix_stringHashMap_remove(earpm->eventSubscriptions, topic); + } + return; + } + if (subscription->curQos < qos) { + status = celix_earpmClient_subscribe(earpm->mqttClient, topic, qos); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to subscribe %s with qos %d. %d.", topic, (int)qos, status); + return; + } + subscription->curQos = qos; + } + return; +} + +static void celix_earpm_unsubscribeEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, celix_earpm_qos_e qos, long handlerServiceId) { + celix_earpm_event_subscription_t* subscription= celix_stringHashMap_get(earpm->eventSubscriptions, topic); + if (subscription == NULL) { + celix_logHelper_debug(earpm->logHelper, "No subscription found for %s.", topic); + return; + } + celix_array_list_entry_t entry; + memset(&entry, 0, sizeof(entry)); + entry.longVal = handlerServiceId; + int index = celix_arrayList_indexOf(subscription->handlerServiceIdList, entry); + if (index < 0) { + celix_logHelper_debug(earpm->logHelper, "Not found handler(%ld) in %s subscription.", handlerServiceId, topic); + return; + } + celix_arrayList_removeAt(subscription->handlerServiceIdList, index); + int size = celix_arrayList_size(subscription->handlerServiceIdList); + if (size == 0) { + celix_status_t status = celix_earpmClient_unsubscribe(earpm->mqttClient, topic); + if (status != CELIX_SUCCESS) { + celix_logHelper_warning(earpm->logHelper, "Failed to unsubscribe %s.", topic); + } + celix_stringHashMap_remove(earpm->eventSubscriptions, topic); + } else if (qos == subscription->curQos) { + celix_earpm_qos_e maxQos = CELIX_EARPM_QOS_UNKNOWN; + for (int i = 0; i < size; ++i) { + long serviceId = celix_arrayList_getLong(subscription->handlerServiceIdList, i); + celix_earpm_event_handler_t* handler = celix_longHashMap_get(earpm->eventHandlers, serviceId); + if (handler != NULL && handler->qos > maxQos) { + maxQos = handler->qos; + } + } + if (maxQos != subscription->curQos) { + celix_status_t status = celix_earpmClient_subscribe(earpm->mqttClient, topic, maxQos); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to subscribe %s with qos %d.", topic, (int)maxQos); + return; + } + subscription->curQos = maxQos; + } + } + return; +} + +static void celix_earpm_subOrUnsubRemoteEventsForHandler(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler, bool subscribe) { + void (*action)(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, celix_earpm_qos_e qos, long handlerServiceId) = celix_earpm_unsubscribeEvent; + if (subscribe) { + action = celix_earpm_subscribeEvent; + } + + int size = celix_arrayList_size(handler->topics); + for (int i = 0; i < size; ++i) { + const char* topic = celix_arrayList_getString(handler->topics, i); + assert(topic != NULL); + action(earpm, topic, handler->qos, handler->serviceId); + } + return; +} + +static json_t* celix_earpm_genHandlerInformation(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler) { + json_auto_t* topics = json_array(); + if (topics == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create topic json array."); + return NULL; + } + int size = celix_arrayList_size(handler->topics); + for (int i = 0; i < size; ++i) { + const char* topic = celix_arrayList_getString(handler->topics, i); + assert(topic != NULL); + if (json_array_append_new(topics, json_string(topic)) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add topic to json array."); + return NULL; + } + } + json_error_t jsonError; + memset(&jsonError, 0, sizeof(jsonError)); + json_auto_t* handlerInfo = json_pack_ex(&jsonError, 0, "{sOsi}", "topics", topics, "handlerId", handler->serviceId); + if (handlerInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to pack handler information. %s", jsonError.text); + return NULL; + } + if (handler->filter != NULL) { + if (json_object_set_new(handlerInfo, "filter", json_string(handler->filter)) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add filter to handler information."); + return NULL; + } + } + + return json_incref(handlerInfo); +} + +static void celix_earpm_addHandlerInfoToRemote(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler) { + const char* topic = CELIX_EARPM_HANDLER_INFO_ADD_TOPIC; + json_auto_t* root = json_object(); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create adding handler info message payload."); + return; + } + json_t* handlerInfo = celix_earpm_genHandlerInformation(earpm, handler); + if (handlerInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create handler information for handler %li.", handler->serviceId); + return; + } + if (json_object_set_new(root, "handler", handlerInfo) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add handler information to adding handler information message."); + return; + } + + celix_autofree char* payload = json_dumps(root, JSON_COMPACT | JSON_ENCODE_ANY); + if (payload == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to dump adding handler information message payload for handler %li.", handler->serviceId); + return; + } + //If the mqtt connection is disconnected, we will resend the handler information + // when the connection is re-established in celix_earpm_connectedCallback, + // so we use CELIX_EARPM_QOS_AT_MOST_ONCE qos here. + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = topic; + requestInfo.payload = payload; + requestInfo.payloadSize = strlen(payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + requestInfo.senderUUID = earpm->fwUUID; + requestInfo.version = CELIX_EARPM_MSG_VERSION; + celix_status_t status = celix_earpmClient_publishAsync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS && status != ENOTCONN) { + celix_logHelper_error(earpm->logHelper, "Failed to publish %s. payload:%s. %d.", topic, payload, status); + } + return; +} + +static void celix_earpm_removeHandlerInfoFromRemote(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_event_handler_t* handler) { + const char* topic = CELIX_EARPM_HANDLER_INFO_REMOVE_TOPIC; + json_auto_t* root = json_object(); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create removing handler info message payload."); + return; + } + if (json_object_set_new(root, "handlerId", json_integer(handler->serviceId)) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add handler id to removing handler info message."); + return; + } + celix_autofree char* payload = json_dumps(root, JSON_COMPACT | JSON_ENCODE_ANY); + if (payload == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to dump removing handler information message payload for handler %li.", handler->serviceId); + return; + } + //If the mqtt connection is disconnected, we will resend the handler information + // when the connection is re-established in celix_earpm_connectedCallback, + // so we use CELIX_EARPM_QOS_AT_MOST_ONCE qos here. + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = topic; + requestInfo.payload = payload; + requestInfo.payloadSize = strlen(payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + requestInfo.senderUUID = earpm->fwUUID; + requestInfo.version = CELIX_EARPM_MSG_VERSION; + celix_status_t status = celix_earpmClient_publishAsync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS && status != ENOTCONN) { + celix_logHelper_error(earpm->logHelper, "Failed to publish %s. payload:%s. %d.", topic, payload, status); + } + return; +} + +celix_status_t celix_earpm_addEventHandlerService(void* handle , void* service CELIX_UNUSED, const celix_properties_t* properties) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + celix_autoptr(celix_earpm_event_handler_t) handler = celix_earpm_createEventHandler(earpm, properties); + if (handler == NULL) { + return CELIX_SERVICE_EXCEPTION; + } + + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_status_t status = celix_longHashMap_put(earpm->eventHandlers, handler->serviceId, handler); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to add event handler service, %d.", status); + return status; + } + + celix_earpm_subOrUnsubRemoteEventsForHandler(earpm, handler, true); + celix_earpm_addHandlerInfoToRemote(earpm, handler); + + celix_steal_ptr(handler); + return CELIX_SUCCESS; +} + +celix_status_t celix_earpm_removeEventHandlerService(void* handle , void* service CELIX_UNUSED, const celix_properties_t* properties) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + long serviceId = celix_properties_getAsLong(properties, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId < 0 ) { + celix_logHelper_error(earpm->logHelper, "Failed to remove event handler service, no service id found."); + return CELIX_ILLEGAL_ARGUMENT; + } + + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_event_handler_t* handler = (celix_earpm_event_handler_t*)celix_longHashMap_get(earpm->eventHandlers, serviceId); + if (handler == NULL) { + celix_logHelper_debug(earpm->logHelper, "No handler found for service id %li.", serviceId); + return CELIX_SUCCESS; + } + + celix_earpm_removeHandlerInfoFromRemote(earpm, handler); + celix_earpm_subOrUnsubRemoteEventsForHandler(earpm, handler, false); + + celix_longHashMap_remove(earpm->eventHandlers, serviceId); + + return CELIX_SUCCESS; +} + +celix_status_t celix_earpm_setEventAdminSvc(void* handle, void* eventAdminSvc) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + return celix_earpmDeliverer_setEventAdminSvc(earpm->deliverer, (celix_event_admin_service_t *) eventAdminSvc); +} + +static bool celix_event_matchRemoteHandler(const char* topic, const celix_properties_t* eventProps, const celix_earpm_remote_handler_info_t* info) { + assert(info->topics != NULL); + + if (!celix_filter_match(info->filter, eventProps)) { + return false; + } + int size = celix_arrayList_size(info->topics); + for (int i = 0; i < size; ++i) { + const char* topicPattern = celix_arrayList_getString(info->topics, i); + if (celix_utils_stringEquals(topicPattern, "*")) { + return true; + } + if (celix_utils_stringEquals(topicPattern, topic)) { + return true; + } + if (topicPattern[strlen(topicPattern) - 1] == '*') { + if (strncmp(topicPattern, topic, strlen(topicPattern) - 1) == 0) { + return true; + } + } + } + return false; +} + +static bool celix_earpm_filterEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const celix_properties_t* eventProps) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = iter.value.ptrValue; + CELIX_LONG_HASH_MAP_ITERATE(fwInfo->handlerInfoMap, handlerIter) { + celix_earpm_remote_handler_info_t* info = handlerIter.value.ptrValue; + if (celix_event_matchRemoteHandler(topic, eventProps, info)) { + return false; + } + } + } + return true; +} + +static celix_status_t celix_earpm_publishEventAsync(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const char* payload, size_t payloadSize, const celix_properties_t* eventProps, celix_earpm_qos_e qos) { + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = topic; + requestInfo.payload = payload; + requestInfo.payloadSize = payloadSize; + requestInfo.qos = qos; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = (int32_t)celix_properties_getAsLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, -1); + requestInfo.version = CELIX_EARPM_MSG_VERSION; + celix_status_t status = celix_earpmClient_publishAsync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to publish async event %s with qos %d. %d.", topic, (int)qos, status); + return status; + } + return CELIX_SUCCESS; +} + +static void celix_earpm_clearAckSeqNr(celix_event_admin_remote_provider_mqtt_t* earpm, long ackSeqNr) { + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = (celix_earpm_remote_framework_info_t*)iter.value.ptrValue; + celix_longHashMap_remove(fwInfo->eventAckSeqNrMap, ackSeqNr); + } +} + +static celix_status_t celix_earpm_associateAckSeqNrWithRemoteHandler(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const celix_properties_t* eventProps, long ackSeqNr) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = (celix_earpm_remote_framework_info_t*)iter.value.ptrValue; + if (fwInfo->continuousNoAckCount > earpm->continuousNoAckThreshold) { + celix_logHelper_warning(earpm->logHelper, "Continuous ack timeout for remote framework %s. No waiting for the ack of event %s.", iter.key, topic); + continue; + } + celix_autoptr(celix_array_list_t) matchedHandlerServiceIdList = celix_arrayList_createLongArray(); + if (matchedHandlerServiceIdList == NULL) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to create matched handler service id list."); + celix_earpm_clearAckSeqNr(earpm, ackSeqNr); + return ENOMEM; + } + CELIX_LONG_HASH_MAP_ITERATE(fwInfo->handlerInfoMap, handlerIter) { + celix_earpm_remote_handler_info_t* info = handlerIter.value.ptrValue; + if (celix_event_matchRemoteHandler(topic, eventProps, info)) { + celix_status_t status = celix_arrayList_addLong(matchedHandlerServiceIdList, handlerIter.key); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to add matched handler service id to list."); + celix_earpm_clearAckSeqNr(earpm, ackSeqNr); + return status; + } + } + } + celix_status_t status = celix_longHashMap_put(fwInfo->eventAckSeqNrMap, ackSeqNr, matchedHandlerServiceIdList); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Error adding event(%s,%ld) to list.", topic, ackSeqNr); + celix_earpm_clearAckSeqNr(earpm, ackSeqNr); + return status; + } + celix_steal_ptr(matchedHandlerServiceIdList); + } + return CELIX_SUCCESS; +} + +static bool celix_earpm_hasPendingAckFor(celix_event_admin_remote_provider_mqtt_t* earpm, long ackSeqNr) { + celix_array_list_entry_t listEntry; + memset(&listEntry, 0, sizeof(listEntry)); + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = (celix_earpm_remote_framework_info_t*)iter.value.ptrValue; + if (celix_longHashMap_hasKey(fwInfo->eventAckSeqNrMap, ackSeqNr)) { + return true; + } + } + return false; +} + +static void celix_earpm_handleNoAckEvent(celix_event_admin_remote_provider_mqtt_t* earpm, long ackSeqNr) { + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = (celix_earpm_remote_framework_info_t*)iter.value.ptrValue; + if (celix_longHashMap_hasKey(fwInfo->eventAckSeqNrMap, ackSeqNr)) { + fwInfo->continuousNoAckCount++; + } + } + return; +} + +static celix_status_t celix_earpm_waitForEventAck(celix_event_admin_remote_provider_mqtt_t* earpm, long ackSeqNr, const struct timespec* expiryTime) { + celix_status_t status = CELIX_SUCCESS; + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + while (celix_earpm_hasPendingAckFor(earpm, ackSeqNr)) { + status = celixThreadCondition_waitUntil(&earpm->ackCond, &earpm->mutex, expiryTime); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to wait for ack of event %ld.", ackSeqNr); + if (status == CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO, ETIMEDOUT)) { + celix_earpm_handleNoAckEvent(earpm, ackSeqNr); + } + celix_earpm_clearAckSeqNr(earpm, ackSeqNr); + return status; + } + } + return CELIX_SUCCESS; +} + +static celix_status_t celix_earpm_publishEventSync(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const char* payload, size_t payloadSize, const celix_properties_t* eventProps, celix_earpm_qos_e qos) { + long ackSeqNr = __atomic_add_fetch(&earpm->lastAckSeqNr, 1, __ATOMIC_RELAXED); + + celix_status_t status = celix_earpm_associateAckSeqNrWithRemoteHandler(earpm, topic, eventProps, ackSeqNr); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to associate ack seq nr with remote handler for %s. %d.", topic, status); + return status; + } + int32_t expiryInterval = (int32_t)celix_properties_getAsLong(eventProps, CELIX_EVENT_REMOTE_EXPIRY_INTERVAL, CELIX_EARPM_SYNC_EVENT_TIMEOUT_DEFAULT); + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = (char*)topic; + requestInfo.payload = (char*)payload; + requestInfo.payloadSize = payloadSize; + requestInfo.qos = qos; + requestInfo.pri = CELIX_EARPM_MSG_PRI_LOW; + requestInfo.expiryInterval = expiryInterval; + requestInfo.version = CELIX_EARPM_MSG_VERSION; + requestInfo.responseTopic = earpm->syncEventAckTopic; + struct celix_earpm_sync_event_correlation_data correlationData; + memset(&correlationData, 0, sizeof(correlationData)); + correlationData.ackSeqNr = ackSeqNr; + requestInfo.correlationData = &correlationData; + requestInfo.correlationDataSize = sizeof(correlationData); + + expiryInterval = (expiryInterval > 0 && expiryInterval < (INT32_MAX/2)) ? expiryInterval : (INT32_MAX/2); + struct timespec expiryTime = celixThreadCondition_getDelayedTime(expiryInterval); + + status = celix_earpmClient_publishSync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to publish sync event %s with qos %d. %d.", topic, (int)qos, status); + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_clearAckSeqNr(earpm, ackSeqNr); + return status; + } + + return celix_earpm_waitForEventAck(earpm, ackSeqNr, &expiryTime); +} + +static celix_status_t celix_earpm_publishEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const char *topic, + const celix_properties_t *eventProps, bool async) { + celix_earpm_qos_e qos = (celix_earpm_qos_e)celix_properties_getAsLong(eventProps, CELIX_EVENT_REMOTE_QOS, earpm->defaultQos); + if (qos <= CELIX_EARPM_QOS_UNKNOWN || qos >= CELIX_EARPM_QOS_MAX) { + celix_logHelper_error(earpm->logHelper, "Qos(%d) is invalid for event %s.", qos, topic); + return CELIX_ILLEGAL_ARGUMENT; + } + if (celix_earpm_filterEvent(earpm, topic, eventProps)) { + celix_logHelper_trace(earpm->logHelper, "No remote handler subscribe %s", topic); + return CELIX_SUCCESS; + } + celix_autofree char* payload = NULL; + size_t payloadSize = 0; + if (eventProps != NULL) { + celix_status_t status = celix_properties_saveToString(eventProps, 0, &payload); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to dump event properties to string for %s. %d.", topic, status); + return status; + } + payloadSize = strlen(payload); + } + if (async) { + return celix_earpm_publishEventAsync(earpm, topic, payload, payloadSize, eventProps, qos); + } + return celix_earpm_publishEventSync(earpm, topic, payload, payloadSize, eventProps, qos); +} + +celix_status_t celix_earpm_postEvent(void* handle , const char *topic, const celix_properties_t *eventProps) { + if (handle == NULL || topic == NULL) { + return CELIX_ILLEGAL_ARGUMENT; + } + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + celix_logHelper_trace(earpm->logHelper, "Post event %s", topic); + + return celix_earpm_publishEvent(earpm, topic, eventProps, true); +} + +celix_status_t celix_earpm_sendEvent(void* handle, const char *topic, const celix_properties_t *eventProps) { + if (handle == NULL || topic == NULL) { + return CELIX_ILLEGAL_ARGUMENT; + } + + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + celix_logHelper_trace(earpm->logHelper, "Send event %s.", topic); + + return celix_earpm_publishEvent(earpm, topic, eventProps, false); +} + +static void celix_earpm_processSyncEventAckMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + if (requestInfo->correlationData == NULL + || requestInfo->correlationDataSize != sizeof(struct celix_earpm_sync_event_correlation_data)) { + celix_logHelper_error(earpm->logHelper, "Correlation data size is invalid for %s message.", requestInfo->topic); + return; + } + + bool wakeupWaitAckThread = false; + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_remote_framework_info_t* fwInfo = celix_stringHashMap_get(earpm->remoteFrameworks, requestInfo->senderUUID); + if (fwInfo == NULL) { + celix_logHelper_debug(earpm->logHelper, "No remote framework info for %s.", requestInfo->senderUUID); + return; + } + struct celix_earpm_sync_event_correlation_data data; + memcpy(&data, requestInfo->correlationData, sizeof(data)); + if (celix_longHashMap_remove(fwInfo->eventAckSeqNrMap, data.ackSeqNr)) { + wakeupWaitAckThread = true; + } + } + if (wakeupWaitAckThread) { + celixThreadCondition_broadcast(&earpm->ackCond); + } + return; +} + +static celix_earpm_remote_handler_info_t* celix_earpm_remoteHandlerInfoCreate(const json_t* topicsJson, const char* filter, celix_log_helper_t* logHelper) { + celix_autofree celix_earpm_remote_handler_info_t* info = calloc(1, sizeof(*info)); + if (info == NULL) { + celix_logHelper_error(logHelper, "Failed to allocate memory for handler information."); + return NULL; + } + celix_autoptr(celix_array_list_t) topics = info->topics = celix_arrayList_createStringArray(); + if (topics == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(logHelper, "Failed to create topics list for handler information."); + return NULL; + } + + size_t index; + json_t* value; + json_array_foreach(topicsJson, index, value) { + const char* topic = json_string_value(value); + if (topic == NULL) { + celix_logHelper_error(logHelper, "Topic is not string."); + return NULL; + } + if (celix_arrayList_addString(topics, (void*)topic)) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_WARNING); + celix_logHelper_warning(logHelper, "Failed to add topic(%s) to handler information.", topic); + } + } + if (filter != NULL) { + info->filter = celix_filter_create(filter);//If return NULL, then only let event admin does event filter + if (info->filter == NULL) { + celix_logHelper_logTssErrors(logHelper, CELIX_LOG_LEVEL_WARNING); + celix_logHelper_warning(logHelper, "Failed to create filter(%s) for handler information.", filter); + } + } + + celix_steal_ptr(topics); + + return celix_steal_ptr(info); +} + +static void celix_earpm_remoteHandlerInfoDestroy(celix_earpm_remote_handler_info_t* info) { + celix_arrayList_destroy(info->topics); + celix_filter_destroy(info->filter); + free(info); +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_remote_handler_info_t, celix_earpm_remoteHandlerInfoDestroy) + + +static celix_earpm_remote_framework_info_t* celix_earpm_createAndAddRemoteFrameworkInfo(celix_event_admin_remote_provider_mqtt_t* earpm, + const char* remoteFwUUID) { + celix_autofree celix_earpm_remote_framework_info_t* fwInfo = calloc(1, sizeof(*fwInfo)); + if (fwInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to allocate memory for remote framework %s.", remoteFwUUID); + return NULL; + } + fwInfo->continuousNoAckCount = 0; + celix_autoptr(celix_long_hash_map_t) eventAckSeqNrMap = NULL; + { + celix_long_hash_map_create_options_t opts = CELIX_EMPTY_LONG_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void*) celix_arrayList_destroy; + eventAckSeqNrMap = fwInfo->eventAckSeqNrMap = celix_longHashMap_createWithOptions(&opts); + if (eventAckSeqNrMap == NULL) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to create event ack seq number map for remote framework %s.", remoteFwUUID); + return NULL; + } + } + celix_autoptr(celix_long_hash_map_t) handlerInfoMap = NULL; + { + celix_long_hash_map_create_options_t opts = CELIX_EMPTY_LONG_HASH_MAP_CREATE_OPTIONS; + opts.simpleRemovedCallback = (void *) celix_earpm_remoteHandlerInfoDestroy; + handlerInfoMap = fwInfo->handlerInfoMap = celix_longHashMap_createWithOptions(&opts); + if (handlerInfoMap == NULL) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to create handler information map for remote framework %s.", remoteFwUUID); + return NULL; + } + } + + if (celix_stringHashMap_put(earpm->remoteFrameworks, remoteFwUUID, fwInfo) != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to add remote framework info for %s.", remoteFwUUID); + return NULL; + } + celix_steal_ptr(handlerInfoMap); + celix_steal_ptr(eventAckSeqNrMap); + return celix_steal_ptr(fwInfo); +} + +static void celix_earpm_remoteFrameworkInfoDestroy(celix_earpm_remote_framework_info_t* info) { + celix_longHashMap_destroy(info->handlerInfoMap); + celix_longHashMap_destroy(info->eventAckSeqNrMap); + free(info); + return; +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_earpm_remote_framework_info_t, celix_earpm_remoteFrameworkInfoDestroy) + +static celix_status_t celix_earpm_addRemoteHandlerInfo(celix_event_admin_remote_provider_mqtt_t* earpm, const json_t* handlerInfoJson, celix_earpm_remote_framework_info_t* fwInfo) { + json_t* id = json_object_get(handlerInfoJson, "handlerId"); + if (!json_is_integer(id)) { + celix_logHelper_error(earpm->logHelper, "Handler id is lost or not integer."); + return CELIX_ILLEGAL_ARGUMENT; + } + long handlerServiceId = json_integer_value(id); + if (handlerServiceId < 0) { + celix_logHelper_error(earpm->logHelper, "Handler id(%ld) is invalid.", handlerServiceId); + return CELIX_ILLEGAL_ARGUMENT; + } + json_t* topics = json_object_get(handlerInfoJson, "topics"); + if (!json_is_array(topics)) { + celix_logHelper_error(earpm->logHelper, "Topics is lost or not array."); + return CELIX_ILLEGAL_ARGUMENT; + } + const char* filter = json_string_value(json_object_get(handlerInfoJson, "filter")); + celix_autoptr(celix_earpm_remote_handler_info_t) handlerInfo = celix_earpm_remoteHandlerInfoCreate(topics, filter, earpm->logHelper); + if (handlerInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create remote handler information."); + return ENOMEM; + } + celix_status_t status = celix_longHashMap_put(fwInfo->handlerInfoMap, handlerServiceId, handlerInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(earpm->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(earpm->logHelper, "Failed to add remote handler information."); + return status; + } + celix_steal_ptr(handlerInfo); + return CELIX_SUCCESS; +} + +static void celix_earpm_processHandlerInfoAddMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const char* payload, size_t payloadSize, const char* remoteFwUUID) { + json_error_t error; + json_auto_t* root = json_loadb(payload, payloadSize, 0, &error); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to parse message %s which from %s. %s.", topic, remoteFwUUID, + error.text); + return; + } + json_t* handlerInfo = json_object_get(root, "handler"); + if (!json_is_object(handlerInfo)) { + celix_logHelper_error(earpm->logHelper, "No handler information for %s which from %s.", topic, remoteFwUUID); + return; + } + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_remote_framework_info_t* fwInfo = celix_stringHashMap_get(earpm->remoteFrameworks, remoteFwUUID); + if (fwInfo == NULL) { + fwInfo = celix_earpm_createAndAddRemoteFrameworkInfo(earpm, remoteFwUUID); + if (fwInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create remote framework info for %s.", remoteFwUUID); + return; + } + } + celix_status_t status = celix_earpm_addRemoteHandlerInfo(earpm, handlerInfo, fwInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to add remote handler information with %s which from %s.", topic, remoteFwUUID); + return; + } + return; +} + +static bool celix_earpm_disassociateRemoteHandlerWithPendingAck(celix_earpm_remote_framework_info_t *fwInfo, long removedHandlerServiceId) { + bool pendingAckExpire = false; + celix_long_hash_map_iterator_t iter = celix_longHashMap_begin(fwInfo->eventAckSeqNrMap); + while (!celix_longHashMapIterator_isEnd(&iter)) { + celix_array_list_t* handlerServiceIdList = iter.value.ptrValue; + if (removedHandlerServiceId >= 0) { + celix_arrayList_removeLong(handlerServiceIdList, removedHandlerServiceId); + } else { + for(int i = celix_arrayList_size(handlerServiceIdList); i > 0; --i) { + long handlerServiceId = celix_arrayList_getLong(handlerServiceIdList, i - 1); + if (!celix_longHashMap_hasKey(fwInfo->handlerInfoMap, handlerServiceId)) { + celix_arrayList_removeAt(handlerServiceIdList, i - 1); + } + } + } + if (celix_arrayList_size(handlerServiceIdList) == 0) { + celix_longHashMapIterator_remove(&iter); + pendingAckExpire = true; + } else { + celix_longHashMapIterator_next(&iter); + } + } + return pendingAckExpire; +} + +static void celix_earpm_processHandlerInfoRemoveMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const char* payload, size_t payloadSize, const char* remoteFwUUID) { + json_error_t error; + json_auto_t* root = json_loadb(payload, payloadSize, 0, &error); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to parse message %s which from %s. %s.", topic, remoteFwUUID, error.text); + return; + } + json_t* id = json_object_get(root, "handlerId"); + if (!json_is_integer(id)) { + celix_logHelper_error(earpm->logHelper, "Handler id is lost or not integer for topic %s which from %s.", topic, remoteFwUUID); + return; + } + long handlerServiceId = json_integer_value(id); + if (handlerServiceId < 0) { + celix_logHelper_error(earpm->logHelper, "Handler id(%ld) is invalid for topic %s which from %s.", handlerServiceId, topic, remoteFwUUID); + return; + } + bool wakeupWaitAckThread = false; + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_remote_framework_info_t *fwInfo = celix_stringHashMap_get(earpm->remoteFrameworks, remoteFwUUID); + if (fwInfo == NULL) { + celix_logHelper_warning(earpm->logHelper, "No remote framework info for %s.", remoteFwUUID); + return; + } + celix_longHashMap_remove(fwInfo->handlerInfoMap, handlerServiceId); + if (celix_longHashMap_size(fwInfo->handlerInfoMap) == 0) { + celix_stringHashMap_remove(earpm->remoteFrameworks, remoteFwUUID); + wakeupWaitAckThread = true; + } else if (celix_earpm_disassociateRemoteHandlerWithPendingAck(fwInfo, handlerServiceId)){ + wakeupWaitAckThread = true; + } + } + if (wakeupWaitAckThread) { + celixThreadCondition_broadcast(&earpm->ackCond); + } + + return; +} + +static void celix_earpm_processHandlerInfoUpdateMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const char* topic, const char* payload, size_t payloadSize, const char* remoteFwUUID) { + json_error_t error; + json_auto_t* root = json_loadb(payload, payloadSize, 0, &error); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to parse message %s. %s.", topic, error.text); + return; + } + json_t* handlers = json_object_get(root, "handlers"); + if (!json_is_array(handlers)) { + celix_logHelper_error(earpm->logHelper, "No handler information for %s which from %s.", topic, remoteFwUUID); + return; + } + bool wakeupWaitAckThread = false; + size_t handlerSize = json_array_size(handlers); + if (handlerSize == 0) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + wakeupWaitAckThread = celix_stringHashMap_remove(earpm->remoteFrameworks, remoteFwUUID); + } else { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + bool handlerInfoRemoved = false; + celix_earpm_remote_framework_info_t* fwInfo = celix_stringHashMap_get(earpm->remoteFrameworks, remoteFwUUID); + if (fwInfo == NULL) { + fwInfo = celix_earpm_createAndAddRemoteFrameworkInfo(earpm, remoteFwUUID); + if (fwInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create remote framework info for %s.", remoteFwUUID); + return; + } + } else { + celix_longHashMap_clear(fwInfo->handlerInfoMap); + handlerInfoRemoved = true; + } + for (size_t i = 0; i < handlerSize; ++i) { + json_t* handlerDesc = json_array_get(handlers, i); + celix_status_t status = celix_earpm_addRemoteHandlerInfo(earpm, handlerDesc, fwInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to add handler information with %s.", topic); + continue; + } + } + if (celix_longHashMap_size(fwInfo->handlerInfoMap) == 0) { + celix_stringHashMap_remove(earpm->remoteFrameworks, remoteFwUUID); + wakeupWaitAckThread = handlerInfoRemoved; + } else if (handlerInfoRemoved && celix_earpm_disassociateRemoteHandlerWithPendingAck(fwInfo, -1/*check all handlers*/)) { + wakeupWaitAckThread = true; + } + } + if (wakeupWaitAckThread) { + celixThreadCondition_broadcast(&earpm->ackCond); + } + return; +} + +static void celix_earpm_refreshAllHandlerInfoToRemote(celix_event_admin_remote_provider_mqtt_t* earpm) { + const char* topic = CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC; + json_auto_t* root = json_object(); + if (root == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create updating handlers info message payload."); + return; + } + json_t* handlers = json_array(); + if (handlers == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create handler information array."); + return; + } + if (json_object_set_new(root, "handlers", handlers) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add handlers information to updating handler info message."); + return; + } + + //The mutex scope must end after celix_earpmc_publishAsync is called, + // otherwise it will cause the conflict between CELIX_EARPM_HANDLER_INFO_UPDATE_TOPIC and CELIX_EARPM_HANDLER_INFO_ADD/REMOVE_TOPIC + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + CELIX_LONG_HASH_MAP_ITERATE(earpm->eventHandlers, iter) { + celix_earpm_event_handler_t* handler = iter.value.ptrValue; + json_t* handlerInfo = celix_earpm_genHandlerInformation(earpm, handler); + if (handlerInfo == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to create handler information for handler %li.", handler->serviceId); + continue; + } + if (json_array_append_new(handlers, handlerInfo) != 0) { + celix_logHelper_error(earpm->logHelper, "Failed to add information of handler %li.", handler->serviceId); + continue; + } + } + celix_autofree char* payload = json_dumps(root, JSON_COMPACT | JSON_ENCODE_ANY); + if (payload == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to dump updating handler information message payload."); + return; + } + //If the mqtt connection is disconnected, we will resend the handler information + // when the connection is re-established in celix_earpm_connectedCallback, + // so we use CELIX_EARPM_QOS_AT_MOST_ONCE qos here. + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = topic; + requestInfo.payload = payload; + requestInfo.payloadSize = strlen(payload); + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + requestInfo.senderUUID = earpm->fwUUID; + requestInfo.version = CELIX_EARPM_MSG_VERSION; + celix_status_t status = celix_earpmClient_publishAsync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to publish %s. payload:%s.", topic, payload); + } + + return; +} + +static void celix_earpm_processHandlerInfoMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + const char* action = requestInfo->topic + sizeof(CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX) - 1; + if (strcmp(action, "add") == 0) { + celix_earpm_processHandlerInfoAddMessage(earpm, requestInfo->topic, requestInfo->payload, + requestInfo->payloadSize, requestInfo->senderUUID); + } else if (strcmp(action, "remove") == 0) { + celix_earpm_processHandlerInfoRemoveMessage(earpm, requestInfo->topic, requestInfo->payload, + requestInfo->payloadSize, requestInfo->senderUUID); + } else if (strcmp(action, "update") == 0) { + celix_earpm_processHandlerInfoUpdateMessage(earpm, requestInfo->topic, requestInfo->payload, + requestInfo->payloadSize, requestInfo->senderUUID); + } else if (strcmp(action, "query") == 0) { + celix_earpm_refreshAllHandlerInfoToRemote(earpm); + } else { + celix_logHelper_warning(earpm->logHelper, "Unknown action %s for handler information message.", requestInfo->topic); + } + return; +} + +static void celix_earpm_processSessionEndMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + bool wakeupWaitAckThread = false; + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + wakeupWaitAckThread = celix_stringHashMap_remove(earpm->remoteFrameworks, requestInfo->senderUUID); + } + if(wakeupWaitAckThread) { + celixThreadCondition_broadcast(&earpm->ackCond); + } + return; +} + +static void celix_earpm_markRemoteFrameworkActive(celix_event_admin_remote_provider_mqtt_t* earpm, const char* fwUUID) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + celix_earpm_remote_framework_info_t* fwInfo = celix_stringHashMap_get(earpm->remoteFrameworks, fwUUID); + if (fwInfo != NULL) { + fwInfo->continuousNoAckCount = 0; + } + return; +} + +static void celix_earpm_processControlMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + const char* topic = requestInfo->topic; + if (requestInfo->senderUUID == NULL) { + celix_logHelper_error(earpm->logHelper, "No sender UUID for control message %s.", topic); + return; + } + if (celix_utils_stringEquals(topic, earpm->syncEventAckTopic)) { + celix_earpm_processSyncEventAckMessage(earpm, requestInfo); + } else if (strncmp(topic, CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX, sizeof(CELIX_EARPM_HANDLER_INFO_TOPIC_PREFIX) - 1) == 0) { + celix_earpm_processHandlerInfoMessage(earpm, requestInfo); + } else if (celix_utils_stringEquals(topic, CELIX_EARPM_SESSION_END_TOPIC)) { + celix_earpm_processSessionEndMessage(earpm, requestInfo); + } + + celix_earpm_markRemoteFrameworkActive(earpm, requestInfo->senderUUID); + return; +} + +static celix_status_t celix_earpm_sendSyncEventAck(celix_event_admin_remote_provider_mqtt_t* earpm, const char* ackTopic, const void* correlationData, size_t correlationDataSize) { + celix_earpm_client_request_info_t ack; + memset(&ack, 0, sizeof(ack)); + ack.topic = ackTopic; + ack.correlationData = correlationData; + ack.correlationDataSize = correlationDataSize; + ack.qos = CELIX_EARPM_QOS_AT_LEAST_ONCE; + ack.pri = CELIX_EARPM_MSG_PRI_MIDDLE; + ack.senderUUID = earpm->fwUUID; + ack.version = CELIX_EARPM_MSG_VERSION; + return celix_earpmClient_publishAsync(earpm->mqttClient, &ack); +} + +static void celix_earpm_sendEventDone(void* data, const char* topic, celix_status_t rc CELIX_UNUSED) { + assert(data != NULL); + assert(topic != NULL); + struct celix_earpm_send_event_done_callback_data* callData = data; + celix_event_admin_remote_provider_mqtt_t* earpm = callData->earpm; + assert(earpm != NULL); + { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + if (!earpm->destroying) {//The mqtt client may be invalid when the earpm is destroying + (void)celix_earpm_sendSyncEventAck(earpm, callData->responseTopic, callData->correlationData, callData->correlationDataSize); + } + } + free(callData->correlationData); + free(callData->responseTopic); + free(callData); + return; +} + +static celix_status_t celix_earpm_deliverSyncEvent(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + celix_autoptr(celix_properties_t) eventProps = NULL; + if (requestInfo->payload != NULL && requestInfo->payloadSize > 0) { + celix_status_t status = celix_properties_loadFromString(requestInfo->payload, 0, &eventProps); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to load event properties for %s.", requestInfo->topic); + return status; + } + } + celix_autofree struct celix_earpm_send_event_done_callback_data* sendDoneCbData = calloc(1, sizeof(*sendDoneCbData)); + if (sendDoneCbData == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to allocate memory for send done callback data."); + return ENOMEM; + } + celix_autofree char* responseTopic = celix_utils_strdup(requestInfo->responseTopic); + if (responseTopic == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to get response topic from sync event %s.", requestInfo->topic); + return ENOMEM; + } + celix_autofree void* correlationData = NULL; + size_t correlationDataSize = 0; + if (requestInfo->correlationData != NULL && requestInfo->correlationDataSize != 0) { + correlationData = malloc(requestInfo->correlationDataSize); + if (correlationData == NULL) { + celix_logHelper_error(earpm->logHelper, "Failed to allocate memory for correlation data of sync event %s.", requestInfo->topic); + return ENOMEM; + } + memcpy(correlationData, requestInfo->correlationData, requestInfo->correlationDataSize); + correlationDataSize = requestInfo->correlationDataSize; + } + sendDoneCbData->earpm = earpm; + sendDoneCbData->responseTopic = responseTopic; + sendDoneCbData->correlationData = correlationData; + sendDoneCbData->correlationDataSize = correlationDataSize; + celix_status_t status = celix_earpmDeliverer_sendEvent(earpm->deliverer, requestInfo->topic, celix_steal_ptr(eventProps), + celix_earpm_sendEventDone, sendDoneCbData); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to send event to local handler for %s. %d.", requestInfo->topic, status); + return status; + } + celix_steal_ptr(responseTopic); + celix_steal_ptr(correlationData); + celix_steal_ptr(sendDoneCbData); + return CELIX_SUCCESS; +} + +static void celix_earpm_processSyncEventMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + celix_status_t status = celix_earpm_deliverSyncEvent(earpm, requestInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to deliver sync event %s.", requestInfo->topic); + (void)celix_earpm_sendSyncEventAck(earpm, requestInfo->responseTopic, requestInfo->correlationData, + requestInfo->correlationDataSize); + return; + } + return; +} + +static void celix_earpm_processAsyncEventMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + celix_autoptr(celix_properties_t) eventProps = NULL; + if (requestInfo->payload != NULL && requestInfo->payloadSize > 0) { + celix_status_t status = celix_properties_loadFromString(requestInfo->payload, 0, &eventProps); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to load event properties for %s.", requestInfo->topic); + return; + } + } + celix_earpmDeliverer_postEvent(earpm->deliverer, requestInfo->topic, celix_steal_ptr(eventProps)); + return; +} + +static void celix_earpm_processEventMessage(celix_event_admin_remote_provider_mqtt_t* earpm, const celix_earpm_client_request_info_t* requestInfo) { + if (requestInfo->responseTopic != NULL) { + celix_earpm_processSyncEventMessage(earpm, requestInfo); + } else { + celix_earpm_processAsyncEventMessage(earpm, requestInfo); + } + return; +} + +static bool celix_earpm_isMsgCompatible(const celix_earpm_client_request_info_t* requestInfo) { + char actualVersion[16]= {0}; + if (requestInfo->version == NULL) { + return false; + } + int ret = snprintf(actualVersion, sizeof(actualVersion), "%s", requestInfo->version); + if (ret < 0 || ret >= (int)sizeof(actualVersion)) { + return false; + } + char* endPtr = NULL; + long actualMajor = strtol(actualVersion, &endPtr, 10); + if (endPtr == NULL || endPtr[0] != '.') { + return false; + } + long actualMinor = strtol(endPtr + 1, NULL, 10); + long expectedMajor = strtol(CELIX_EARPM_MSG_VERSION, &endPtr, 10); + assert(endPtr[0] == '.'); + long expectedMinor = strtol(endPtr + 1, NULL, 10); + + if (actualMajor == expectedMajor && actualMinor <= expectedMinor) { + return true; + } + return false; +} + +static void celix_earpm_receiveMsgCallback(void* handle, const celix_earpm_client_request_info_t* requestInfo) { + assert(handle != NULL); + assert(requestInfo != NULL); + assert(requestInfo->topic != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + if (!celix_earpm_isMsgCompatible(requestInfo)) { + celix_logHelper_warning(earpm->logHelper, "%s message version(%s) is incompatible.",requestInfo->topic, requestInfo->version == NULL ? "null" : requestInfo->version); + return; + } + + if (strncmp(requestInfo->topic,CELIX_EARPM_TOPIC_PREFIX, sizeof(CELIX_EARPM_TOPIC_PREFIX)-1) == 0) { + celix_earpm_processControlMessage(earpm, requestInfo); + } else {// user topic + celix_earpm_processEventMessage(earpm, requestInfo); + } + return; +} + +static void celix_earpm_queryRemoteHandlerInfo(celix_event_admin_remote_provider_mqtt_t* earpm) { + const char* topic = CELIX_EARPM_HANDLER_INFO_QUERY_TOPIC; + //If the mqtt connection is disconnected, we will query the handler information again + // when the connection is re-established in celix_earpm_connectedCallback, + // so we use CELIX_EARPM_QOS_AT_MOST_ONCE qos here. + celix_earpm_client_request_info_t requestInfo; + memset(&requestInfo, 0, sizeof(requestInfo)); + requestInfo.topic = topic; + requestInfo.qos = CELIX_EARPM_QOS_AT_MOST_ONCE; + requestInfo.pri = CELIX_EARPM_MSG_PRI_HIGH; + requestInfo.senderUUID = earpm->fwUUID; + requestInfo.version = CELIX_EARPM_MSG_VERSION; + celix_status_t status = celix_earpmClient_publishAsync(earpm->mqttClient, &requestInfo); + if (status != CELIX_SUCCESS) { + celix_logHelper_error(earpm->logHelper, "Failed to publish %s, %d.", topic, status); + } + return; +} + +static void celix_earpm_connectedCallback(void* handle) { + assert(handle != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + + celix_earpm_queryRemoteHandlerInfo(earpm); + celix_earpm_refreshAllHandlerInfoToRemote(earpm); + + return; +} + +static void celix_earpm_infoCmd(celix_event_admin_remote_provider_mqtt_t* earpm, FILE* outStream) { + fprintf(outStream, "Event Admin Remote Provider Based On MQTT Info:\n"); + { + fprintf(outStream, "\nLocal Subscriptions:\n"); + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + CELIX_STRING_HASH_MAP_ITERATE(earpm->eventSubscriptions, iter) { + celix_earpm_event_subscription_t* subscription = iter.value.ptrValue; + fprintf(outStream, "\t%s -> QOS:%d, SubCnt:%d\n", iter.key, subscription->curQos, celix_arrayList_size(subscription->handlerServiceIdList)); + } + fprintf(outStream, "\nRemote Framework Info:\n"); + CELIX_STRING_HASH_MAP_ITERATE(earpm->remoteFrameworks, iter) { + celix_earpm_remote_framework_info_t* fwInfo = iter.value.ptrValue; + fprintf(outStream, "\t%s -> HandlerCnt:%zu, NoAckCnt:%d, PendingAckCnt:%zu\n", iter.key, + celix_longHashMap_size(fwInfo->handlerInfoMap), fwInfo->continuousNoAckCount, celix_longHashMap_size(fwInfo->eventAckSeqNrMap)); + } + } + + celix_earpmClient_info(earpm->mqttClient, outStream); +} + +bool celix_earpm_executeCommand(void* handle, const char* commandLine, FILE* outStream, FILE* errorStream) { + assert(handle != NULL); + assert(commandLine != NULL); + assert(outStream != NULL); + assert(errorStream != NULL); + celix_event_admin_remote_provider_mqtt_t* earpm = (celix_event_admin_remote_provider_mqtt_t*)handle; + celix_autofree char* cmd = celix_utils_strdup(commandLine); + if (cmd == NULL) { + fprintf(errorStream, "Failed to process command line %s.\n", commandLine); + return false; + } + const char* subCmd = NULL; + char* savePtr = NULL; + strtok_r(cmd, " ", &savePtr); + subCmd = strtok_r(NULL, " ", &savePtr); + if (subCmd == NULL) { + celix_earpm_infoCmd(earpm, outStream); + } else { + fprintf(errorStream, "Unexpected sub command %s\n", subCmd); + return false; + } + return true; +} + +size_t celix_earpm_currentRemoteFrameworkCount(celix_event_admin_remote_provider_mqtt_t* earpm) { + celix_auto(celix_mutex_lock_guard_t) mutexGuard = celixMutexLockGuard_init(&earpm->mutex); + size_t cnt = celix_stringHashMap_size(earpm->remoteFrameworks); + return cnt; +} \ No newline at end of file diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.h new file mode 100644 index 000000000..162972dba --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_impl.h @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_IMPL_H +#define CELIX_EARPM_IMPL_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "celix_errno.h" +#include "celix_compiler.h" +#include "celix_bundle_context.h" +#include "celix_properties.h" +#include "celix_log_helper.h" +#include "celix_event_admin_service.h" +#include "endpoint_description.h" + +typedef struct celix_event_admin_remote_provider_mqtt celix_event_admin_remote_provider_mqtt_t; + +celix_event_admin_remote_provider_mqtt_t* celix_earpm_create(celix_bundle_context_t* ctx); +void celix_earpm_destroy(celix_event_admin_remote_provider_mqtt_t* earpm); + +celix_status_t celix_earpm_mqttBrokerEndpointAdded(void* handle, endpoint_description_t* endpoint, char* matchedFilter); +celix_status_t celix_earpm_mqttBrokerEndpointRemoved(void* handle, endpoint_description_t* endpoint, char* matchedFilter); + +celix_status_t celix_earpm_addEventHandlerService(void* handle , void* service, const celix_properties_t* properties); +celix_status_t celix_earpm_removeEventHandlerService(void* handle , void* service, const celix_properties_t* properties); + +celix_status_t celix_earpm_setEventAdminSvc(void* handle, void* eventAdminSvc); + +celix_status_t celix_earpm_postEvent(void* handle , const char* topic, const celix_properties_t* eventProps); +celix_status_t celix_earpm_sendEvent(void* handle , const char* topic, const celix_properties_t* eventProps); + +bool celix_earpm_executeCommand(void *handle, const char *commandLine, FILE *outStream, FILE *errorStream); + +size_t celix_earpm_currentRemoteFrameworkCount(celix_event_admin_remote_provider_mqtt_t* earpm); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_IMPL_H diff --git a/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_mosquitto_cleanup.h b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_mosquitto_cleanup.h new file mode 100644 index 000000000..ad8681db0 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/src/celix_earpm_mosquitto_cleanup.h @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_EARPM_MOSQUITTO_CLEANUP_H +#define CELIX_EARPM_MOSQUITTO_CLEANUP_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "celix_cleanup.h" + +static inline void celix_earpm_mosquittoPropertyDestroy(mosquitto_property *prop) { + if (prop != NULL) { + mosquitto_property_free_all(&prop); + } +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(mosquitto_property, celix_earpm_mosquittoPropertyDestroy) + +typedef struct mosquitto mosquitto; + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(mosquitto, mosquitto_destroy) + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_EARPM_MOSQUITTO_CLEANUP_H diff --git a/bundles/remote_services/discovery_zeroconf/gtest/src/DiscoveryZeroconfWatcherTestSuite.cc b/bundles/remote_services/discovery_zeroconf/gtest/src/DiscoveryZeroconfWatcherTestSuite.cc index ebf332c0a..cfa023115 100644 --- a/bundles/remote_services/discovery_zeroconf/gtest/src/DiscoveryZeroconfWatcherTestSuite.cc +++ b/bundles/remote_services/discovery_zeroconf/gtest/src/DiscoveryZeroconfWatcherTestSuite.cc @@ -183,6 +183,7 @@ class DiscoveryZeroconfWatcherTestSuite : public ::testing::Test { celix_ei_expect_celix_properties_copy(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0); + celix_ei_expect_celix_longHashMap_put(nullptr, 0, 0); sem_destroy(&msgSyncSem); celix_bundleContext_unregisterService(ctx.get(), lsId); @@ -514,6 +515,36 @@ TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToCreateServiceMapForBrowserEntr }); } +TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToCreateRelatedListenerMapForBrowserEntry) { + TestRsaServiceAddAndRemove([](){ + celix_ei_expect_celix_longHashMap_create(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + ExpectMsgOutPut("Watcher: Failed to create related listeners map."); + }, [](){ + auto timeOut = CheckMsgWithTimeOutInS(1); + EXPECT_FALSE(timeOut); + }); +} + +TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToPutRelatedListenerToMapForBrowserEntry) { + TestRsaServiceAddAndRemove([](){ + celix_ei_expect_celix_longHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, ENOMEM); + ExpectMsgOutPut("Watcher: Failed to attach listener to service browser."); + }, [](){ + auto timeOut = CheckMsgWithTimeOutInS(1); + EXPECT_FALSE(timeOut); + }); +} + +TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToPutRelatedListenerToExistedMapForBrowserEntry) { + TestRsaServiceAddAndRemove([](){ + celix_ei_expect_celix_longHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, CELIX_ENOMEM, 2); + ExpectMsgOutPut("Watcher: Failed to attach listener to existed service browser."); + }, [](){ + auto timeOut = CheckMsgWithTimeOutInS(1); + EXPECT_FALSE(timeOut); + },nullptr, nullptr, "celix.test1.http,celix.test2.http"); +} + TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToPutBrowserEntryToCache) { TestRsaServiceAddAndRemove([](){ celix_ei_expect_celix_stringHashMap_put(CELIX_EI_UNKNOWN_CALLER, 0, CELIX_ENOMEM); @@ -532,6 +563,22 @@ TEST_F(DiscoveryZeroconfWatcherTestSuite, AddRsaServiceWithMultiConfigTypes) { TestRsaServiceAddAndRemove([](){}, [](){}, nullptr, nullptr, "celix.test1.http,celix.test1.http-json,celix.test2.http,celix.test2.http-json"); } +TEST_F(DiscoveryZeroconfWatcherTestSuite, RsaServiceWithoutServiceIdTest) { + discovery_zeroconf_watcher_t *watcher; + celix_status_t status = discoveryZeroconfWatcher_create(ctx.get(), logHelper.get(), &watcher); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_RSA_REMOTE_SERVICE_ADMIN); + status = discoveryZeroConfWatcher_addRSA(watcher, (void*)"dummy_service", props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + status = discoveryZeroConfWatcher_removeRSA(watcher, (void*)"dummy_service", props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + discoveryZeroconfWatcher_destroy(watcher); +} + static void OnDNSServiceRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *instanceName, const char *serviceType, const char *domain, void *data) { (void)sdRef;//unused (void)data;//unused @@ -1033,6 +1080,54 @@ TEST_F(DiscoveryZeroconfWatcherTestSuite, AddAndRemoveEndpointListener) { discoveryZeroconfWatcher_destroy(watcher); } +TEST_F(DiscoveryZeroconfWatcherTestSuite, EndpointListenerSpesificServiceConfigTypeTest) { + discovery_zeroconf_watcher_t *watcher; + celix_status_t status = discoveryZeroconfWatcher_create(ctx.get(), logHelper.get(), &watcher); + EXPECT_EQ(CELIX_SUCCESS, status); + + auto props = celix_properties_create(); + status = celix_properties_set(props, CELIX_RSA_ENDPOINT_LISTENER_SCOPE, "(" CELIX_RSA_SERVICE_IMPORTED_CONFIGS "=" DZC_TEST_CONFIG_TYPE ")"); + EXPECT_EQ(CELIX_SUCCESS, status); + long listenerId = celix_bundleContext_registerService(ctx.get(), &epListener, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME, props); + EXPECT_LE(0, listenerId); + + char txtBuf[1300] = {0}; + TXTRecordRef txtRecord; + TXTRecordCreate(&txtRecord, sizeof(txtBuf), txtBuf); + TXTRecordSetValue(&txtRecord, DZC_TXT_RECORD_VERSION_KEY, sizeof(DZC_CURRENT_TXT_RECORD_VERSION)-1, DZC_CURRENT_TXT_RECORD_VERSION); + TXTRecordSetValue(&txtRecord, CELIX_RSA_ENDPOINT_FRAMEWORK_UUID, strlen(DZC_TEST_ENDPOINT_FW_UUID), DZC_TEST_ENDPOINT_FW_UUID); + TXTRecordSetValue(&txtRecord, CELIX_FRAMEWORK_SERVICE_NAME, strlen("dzc_test_service"), "dzc_test_service"); + TXTRecordSetValue(&txtRecord, CELIX_RSA_ENDPOINT_ID, strlen("60f49d89-d105-430c-b12b-93fbb54b1d19"), "60f49d89-d105-430c-b12b-93fbb54b1d19"); + TXTRecordSetValue(&txtRecord, CELIX_RSA_ENDPOINT_SERVICE_ID, strlen("100"), "100"); + TXTRecordSetValue(&txtRecord, CELIX_RSA_SERVICE_IMPORTED, strlen("true"), "true"); + TXTRecordSetValue(&txtRecord, CELIX_RSA_SERVICE_IMPORTED_CONFIGS, sizeof(DZC_TEST_CONFIG_TYPE)-1, DZC_TEST_CONFIG_TYPE); + char propSizeStr[16]= {0}; + sprintf(propSizeStr, "%d", TXTRecordGetCount(TXTRecordGetLength(&txtRecord), TXTRecordGetBytesPtr(&txtRecord)) + 1); + TXTRecordSetValue(&txtRecord, DZC_SERVICE_PROPERTIES_SIZE_KEY, strlen(propSizeStr), propSizeStr); + + DNSServiceRef dsRef{}; + DNSServiceErrorType dnsErr = DNSServiceRegister(&dsRef, 0, kDNSServiceInterfaceIndexLocalOnly, "dzc_test_service", + DZC_TEST_SERVICE_TYPE, "local", nullptr, htons(DZC_PORT_DEFAULT), TXTRecordGetLength(&txtRecord), + TXTRecordGetBytesPtr(&txtRecord), OnDNSServiceRegisterCallback,nullptr); + EXPECT_EQ(dnsErr, kDNSServiceErr_NoError); + DNSServiceProcessResult(dsRef); + + ExpectMsgOutPut("Endpoint added: %s."); + auto eplTrkId = TrackEndpointListenerService(watcher); + auto timeOut = CheckMsgWithTimeOutInS(30); + EXPECT_FALSE(timeOut); + + ExpectMsgOutPut("Endpoint removed: %s."); + celix_bundleContext_stopTracker(ctx.get(), eplTrkId); + timeOut = CheckMsgWithTimeOutInS(30); + EXPECT_FALSE(timeOut); + + celix_bundleContext_unregisterService(ctx.get(), listenerId); + + DNSServiceRefDeallocate(dsRef); + discoveryZeroconfWatcher_destroy(watcher); +} + TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToAllocMemoryForEPL) { discovery_zeroconf_watcher_t *watcher; celix_status_t status = discoveryZeroconfWatcher_create(ctx.get(), logHelper.get(), &watcher); @@ -1049,6 +1144,22 @@ TEST_F(DiscoveryZeroconfWatcherTestSuite, FailedToAllocMemoryForEPL) { discoveryZeroconfWatcher_destroy(watcher); } +TEST_F(DiscoveryZeroconfWatcherTestSuite, EndpointListenerServiceWithoutServiceIdTest) { + discovery_zeroconf_watcher_t *watcher; + celix_status_t status = discoveryZeroconfWatcher_create(ctx.get(), logHelper.get(), &watcher); + EXPECT_EQ(CELIX_SUCCESS, status); + + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, CELIX_FRAMEWORK_SERVICE_NAME, CELIX_RSA_ENDPOINT_LISTENER_SERVICE_NAME); + status = discoveryZeroconfWatcher_addEPL(watcher, (void*)"dummy_service", props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + status = discoveryZeroconfWatcher_removeEPL(watcher, (void*)"dummy_service", props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + discoveryZeroconfWatcher_destroy(watcher); +} + TEST_F(DiscoveryZeroconfWatcherWatchServiceTestSuite, FailedToGetHostIpAddressesWhenCreateEndpoint) { TestWatchService([](){ celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr, 5); diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_watcher.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_watcher.c index 203cca0f8..0c9acc360 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_watcher.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_watcher.c @@ -101,7 +101,7 @@ typedef struct service_browser_entry { DNSServiceRef browseRef; celix_log_helper_t *logHelper; celix_string_hash_map_t *watchedServices;//key:instanceName+'/'+interfaceIndex, val:interfaceIndex - int refCnt; + celix_long_hash_map_t* relatedListeners;//key:service id, val:null int resolvedCnt; bool markDeleted; }service_browser_entry_t; @@ -222,6 +222,7 @@ void discoveryZeroconfWatcher_destroy(discovery_zeroconf_watcher_t *watcher) { CELIX_STRING_HASH_MAP_ITERATE(watcher->serviceBrowsers, iter) { service_browser_entry_t *browserEntry = (service_browser_entry_t *)iter.value.ptrValue; celix_stringHashMap_destroy(browserEntry->watchedServices); + celix_longHashMap_destroy(browserEntry->relatedListeners); free(browserEntry); } celix_stringHashMap_destroy(watcher->serviceBrowsers); @@ -231,6 +232,83 @@ void discoveryZeroconfWatcher_destroy(discovery_zeroconf_watcher_t *watcher) { return; } +static bool discoveryZeroConfWatcher_addServiceBrowser(discovery_zeroconf_watcher_t *watcher, const char *serviceConfigType, + long listenerId) { + const char *svcSubType = strrchr(serviceConfigType, '.');//We use the last word of the configuration type as mDNS service subtype + if (svcSubType != NULL) { + svcSubType += 1;//skip '.' + } else { + svcSubType = serviceConfigType; + } + size_t subTypeLen = strlen(svcSubType); + if (subTypeLen > 63) {//the subtype identifier is allowed to be up to 63 bytes,https://www.rfc-editor.org/rfc/rfc6763.txt#section-7.2 + celix_logHelper_error(watcher->logHelper, "Watcher: Invalid service type for %s.", serviceConfigType); + return false; + } + celix_auto(celix_mutex_lock_guard_t) lockGuard = celixMutexLockGuard_init(&watcher->mutex); + celix_autofree service_browser_entry_t *browserEntry = (service_browser_entry_t *)celix_stringHashMap_get(watcher->serviceBrowsers, svcSubType); + if (browserEntry != NULL) { + if (celix_longHashMap_put(browserEntry->relatedListeners, listenerId, NULL) != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to attach listener to existed service browser."); + } + celix_steal_ptr(browserEntry); + return false; + } + browserEntry = (service_browser_entry_t *)calloc(1, sizeof(*browserEntry)); + if (browserEntry == NULL) { + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to alloc service browser entry."); + return false; + } + celix_autoptr(celix_long_hash_map_t) relatedListeners = browserEntry->relatedListeners = celix_longHashMap_create(); + if (relatedListeners == NULL) { + celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to create related listeners map."); + return false; + } + if (celix_longHashMap_put(browserEntry->relatedListeners, listenerId, NULL) != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to attach listener to service browser."); + return false; + } + celix_autoptr(celix_string_hash_map_t) watchedServices = browserEntry->watchedServices = celix_stringHashMap_create(); + if (watchedServices == NULL) { + celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to create watched services map."); + return false; + } + browserEntry->resolvedCnt = 0; + browserEntry->browseRef = NULL; + browserEntry->logHelper = watcher->logHelper; + browserEntry->markDeleted = false; + if (celix_stringHashMap_put(watcher->serviceBrowsers, svcSubType, browserEntry) != CELIX_SUCCESS) { + celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to put service browser entry."); + return false; + } + celix_steal_ptr(watchedServices); + celix_steal_ptr(relatedListeners); + celix_steal_ptr(browserEntry); + return true; +} + +static bool discoveryZeroConfWatcher_removeServiceBrowser(discovery_zeroconf_watcher_t* watcher, const char* serviceConfigType, + long listenerId) { + const char *svcSubType = strrchr(serviceConfigType, '.'); + if (svcSubType != NULL) { + svcSubType += 1;//skip '.' + } else { + svcSubType = serviceConfigType; + } + celix_auto(celix_mutex_lock_guard_t) lockGuard = celixMutexLockGuard_init(&watcher->mutex); + service_browser_entry_t *browserEntry = (service_browser_entry_t *)celix_stringHashMap_get(watcher->serviceBrowsers, svcSubType); + if ((browserEntry != NULL)) { + celix_longHashMap_remove(browserEntry->relatedListeners, listenerId); + return celix_longHashMap_size(browserEntry->relatedListeners) == 0; + } + return false; +} + int discoveryZeroconfWatcher_addEPL(void *handle, void *svc, const celix_properties_t *props) { assert(handle != NULL); assert(svc != NULL); @@ -239,6 +317,7 @@ int discoveryZeroconfWatcher_addEPL(void *handle, void *svc, const celix_propert endpoint_listener_t *epl = (endpoint_listener_t *)svc; long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); if (serviceId == -1) { + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to get endpoint listener service id."); return CELIX_ILLEGAL_ARGUMENT; } @@ -265,6 +344,12 @@ int discoveryZeroconfWatcher_addEPL(void *handle, void *svc, const celix_propert celix_longHashMap_put(watcher->epls, serviceId, eplEntry); celixThreadMutex_unlock(&watcher->mutex); + const char* listenedConfigType = celix_filter_findAttribute(filter, CELIX_RSA_SERVICE_IMPORTED_CONFIGS); + if (listenedConfigType != NULL && discoveryZeroConfWatcher_addServiceBrowser(watcher, listenedConfigType, serviceId) == true) { + eventfd_t val = 1; + eventfd_write(watcher->eventFd, val); + } + return CELIX_SUCCESS; } @@ -276,9 +361,11 @@ int discoveryZeroconfWatcher_removeEPL(void *handle, void *svc, const celix_prop endpoint_listener_t *epl = (endpoint_listener_t *)svc; long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); if (serviceId == -1) { + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to get endpoint listener service id."); return CELIX_ILLEGAL_ARGUMENT; } + celix_autoptr(celix_filter_t) filter = NULL; celixThreadMutex_lock(&watcher->mutex); watched_epl_entry_t *eplEntry = (watched_epl_entry_t *)celix_longHashMap_get(watcher->epls, serviceId); if (eplEntry != NULL) { @@ -290,11 +377,17 @@ int discoveryZeroconfWatcher_removeEPL(void *handle, void *svc, const celix_prop } } celix_longHashMap_remove(watcher->epls, serviceId); - celix_filter_destroy(eplEntry->filter); + filter = celix_steal_ptr(eplEntry->filter); free(eplEntry); } celixThreadMutex_unlock(&watcher->mutex); + const char* listenedConfigType = celix_filter_findAttribute(filter, CELIX_RSA_SERVICE_IMPORTED_CONFIGS); + if (listenedConfigType != NULL && discoveryZeroConfWatcher_removeServiceBrowser(watcher, listenedConfigType, serviceId) == true) { + eventfd_t val = 1; + eventfd_write(watcher->eventFd, val); + } + return CELIX_SUCCESS; } @@ -303,6 +396,11 @@ int discoveryZeroConfWatcher_addRSA(void *handle, void *svc, const celix_propert assert(svc != NULL); assert(props != NULL); discovery_zeroconf_watcher_t *watcher = (discovery_zeroconf_watcher_t *)handle; + long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId == -1) { + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to get remote service admin service id."); + return CELIX_ILLEGAL_ARGUMENT; + } const char *configsSupported = celix_properties_get(props, CELIX_RSA_REMOTE_CONFIGS_SUPPORTED, NULL); celix_autofree char *configsSupportedCopy = celix_utils_strdup(configsSupported); if (configsSupportedCopy == NULL) { @@ -313,50 +411,9 @@ int discoveryZeroConfWatcher_addRSA(void *handle, void *svc, const celix_propert char *token = strtok(configsSupportedCopy, ","); while (token != NULL) { token = celix_utils_trimInPlace(token); - const char *svcSubType = strrchr(token, '.');//We use the last word of config type as mDNS service subtype - if (svcSubType != NULL) { - svcSubType += 1;//skip '.' - } else { - svcSubType = token; - } - do { - size_t subTypeLen = strlen(svcSubType); - if (subTypeLen > 63) {//the subtype identifier is allowed to be up to 63 bytes,https://www.rfc-editor.org/rfc/rfc6763.txt#section-7.2 - celix_logHelper_error(watcher->logHelper, "Watcher: Invalid service type for %s.", token); - break; - } - celix_auto(celix_mutex_lock_guard_t) lockGuard = celixMutexLockGuard_init(&watcher->mutex); - celix_autofree service_browser_entry_t *browserEntry = (service_browser_entry_t *)celix_stringHashMap_get(watcher->serviceBrowsers, svcSubType); - if (browserEntry != NULL) { - browserEntry->refCnt++; - celix_steal_ptr(browserEntry); - break; - } - browserEntry = (service_browser_entry_t *)calloc(1, sizeof(*browserEntry)); - if (browserEntry == NULL) { - celix_logHelper_error(watcher->logHelper, "Watcher: Failed to alloc service browser entry."); - break; - } - celix_autoptr(celix_string_hash_map_t) watchedServices = browserEntry->watchedServices = celix_stringHashMap_create(); - if (watchedServices == NULL) { - celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); - celix_logHelper_error(watcher->logHelper, "Watcher: Failed to create watched services map."); - break; - } - browserEntry->refCnt = 1; - browserEntry->resolvedCnt = 0; - browserEntry->browseRef = NULL; - browserEntry->logHelper = watcher->logHelper; - browserEntry->markDeleted = false; - if (celix_stringHashMap_put(watcher->serviceBrowsers, svcSubType, browserEntry) != CELIX_SUCCESS) { - celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); - celix_logHelper_error(watcher->logHelper, "Watcher: Failed to put service browser entry."); - break; - } - celix_steal_ptr(watchedServices); - celix_steal_ptr(browserEntry); + if (discoveryZeroConfWatcher_addServiceBrowser(watcher, token, serviceId)) { refreshBrowsers = true; - } while (0); + } token = strtok(NULL, ","); } @@ -373,6 +430,11 @@ int discoveryZeroConfWatcher_removeRSA(void *handle, void *svc, const celix_prop assert(svc != NULL); assert(props != NULL); discovery_zeroconf_watcher_t *watcher = (discovery_zeroconf_watcher_t *)handle; + long serviceId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, -1); + if (serviceId == -1) { + celix_logHelper_error(watcher->logHelper, "Watcher: Failed to get remote service admin service id."); + return CELIX_ILLEGAL_ARGUMENT; + } const char *configsSupported = celix_properties_get(props, CELIX_RSA_REMOTE_CONFIGS_SUPPORTED, NULL); celix_autofree char *configsSupportedCopy = celix_utils_strdup(configsSupported); if (configsSupportedCopy == NULL) { @@ -383,18 +445,9 @@ int discoveryZeroConfWatcher_removeRSA(void *handle, void *svc, const celix_prop char *token = strtok(configsSupportedCopy, ","); while (token != NULL) { token = celix_utils_trimInPlace(token); - const char *svcSubType = strrchr(token, '.'); - if (svcSubType != NULL) { - svcSubType += 1;//skip '.' - } else { - svcSubType = token; - } - celixThreadMutex_lock(&watcher->mutex); - service_browser_entry_t *browserEntry = (service_browser_entry_t *)celix_stringHashMap_get(watcher->serviceBrowsers, svcSubType); - if ((browserEntry != NULL) && (--browserEntry->refCnt == 0)) { + if (discoveryZeroConfWatcher_removeServiceBrowser(watcher, token, serviceId)) { refreshBrowsers = true; } - celixThreadMutex_unlock(&watcher->mutex); token = strtok(NULL, ","); } @@ -506,14 +559,15 @@ static void discoveryZeroconfWatcher_pickUpdatedServiceBrowsers(discovery_zeroco celix_string_hash_map_iterator_t iter = celix_stringHashMap_begin(watcher->serviceBrowsers); while (!celix_stringHashMapIterator_isEnd(&iter)) { service_browser_entry_t *browserEntry = (service_browser_entry_t *)iter.value.ptrValue; - if (watcher->sharedRef != NULL && browserEntry->browseRef == NULL && browserEntry->refCnt > 0 && browserEntry->resolvedCnt < DZC_MAX_RESOLVED_CNT) { + bool browserToBeDeleted = celix_longHashMap_size(browserEntry->relatedListeners) == 0; + if (watcher->sharedRef != NULL && browserEntry->browseRef == NULL && !browserToBeDeleted && browserEntry->resolvedCnt < DZC_MAX_RESOLVED_CNT) { status = celix_stringHashMap_put(updatedServiceBrowsers, iter.key, browserEntry); if (status != CELIX_SUCCESS) { nextWorkIntervalTime = MIN(nextWorkIntervalTime, DZC_MAX_RETRY_INTERVAL);//retry browse after 5 seconds celix_logHelper_logTssErrors(watcher->logHelper, CELIX_LOG_LEVEL_ERROR); celix_logHelper_error(watcher->logHelper, "Watcher: Failed to put browse entry, %d.", status); } - } else if (browserEntry->refCnt <= 0) { + } else if (browserToBeDeleted) { status = celix_stringHashMap_put(updatedServiceBrowsers, iter.key, browserEntry); if (status == CELIX_SUCCESS) { celix_stringHashMapIterator_remove(&iter); @@ -540,6 +594,7 @@ static void discoveryZeroconfWatcher_pickUpdatedServiceBrowsers(discovery_zeroco DNSServiceRefDeallocate(browserEntry->browseRef); } celix_stringHashMap_destroy(browserEntry->watchedServices); + celix_longHashMap_destroy(browserEntry->relatedListeners); free(browserEntry); } else { celix_stringHashMapIterator_next(&iter2); diff --git a/cmake/CelixDeps.cmake.in b/cmake/CelixDeps.cmake.in index 6567d959d..bea259e79 100644 --- a/cmake/CelixDeps.cmake.in +++ b/cmake/CelixDeps.cmake.in @@ -6,6 +6,7 @@ $<$>:find_dependency(ZLIB) $<$>:find_dependency(libffi)> $<$>:find_dependency(jansson)> $<$>:find_dependency(jansson)> +$<$>:find_dependency(jansson)> $<$>:find_dependency(CURL)> $<$>:find_dependency(CURL)> $<$>:find_dependency(CURL)> @@ -20,3 +21,4 @@ $<$>:find_dependency(civetweb $<$>:find_dependency(civetweb)> $<$>:find_dependency(DNSSD)> $<$>:find_dependency(DNSSD)> +$<$>:find_dependency(mosquitto)> diff --git a/conanfile.py b/conanfile.py index 4e423001a..de23229d9 100644 --- a/conanfile.py +++ b/conanfile.py @@ -82,6 +82,7 @@ class CelixConan(ConanFile): "build_utils": False, "build_event_admin": False, "build_event_admin_examples": False, + "build_event_admin_remote_provider_mqtt": False, "celix_cxx14": True, "celix_cxx17": True, "celix_install_deprecated_api": False, @@ -164,6 +165,14 @@ def configure(self): if opt.startswith('build_'): options[opt] = True + if options["build_event_admin_examples"]: + options["build_event_admin"] = True + options["build_log_service"] = True + options["build_shell_tui"] = True + options["build_launcher"] = True + options["build_event_admin_remote_provider_mqtt"] = True + options["build_rsa_discovery_zeroconf"] = True + if self.settings.os != "Linux": options["build_rsa_remote_service_admin_shm_v2"] = False options["build_rsa_discovery_zeroconf"] = False @@ -177,6 +186,11 @@ def configure(self): options["build_log_service"] = True options["build_syslog_writer"] = True + if options["build_event_admin_remote_provider_mqtt"]: + options["build_event_admin"] = True + options["build_remote_service_admin"] = True + options["build_shell_api"] = True + if options["build_cxx_rsa_integration"]: options["build_cxx_remote_service_admin"] = True options["build_pushstreams"] = True @@ -209,12 +223,6 @@ def configure(self): options["build_celix_dfi"] = True options["celix_install_deprecated_api"] = True - if options["build_event_admin_examples"]: - options["build_event_admin"] = True - options["build_log_service"] = True - options["build_shell_tui"] = True - options["build_launcher"] = True - if options["build_event_admin"]: options["build_framework"] = True options["build_log_helper"] = True @@ -309,8 +317,12 @@ def configure(self): self.options['openssl'].shared = True if self.options.build_celix_dfi: self.options['libffi'].shared = True - if self.options.build_utils or self.options.build_celix_dfi or self.options.build_celix_etcdlib: + if self.options.build_utils or self.options.build_celix_dfi or self.options.build_celix_etcdlib or self.options.build_event_admin_remote_provider_mqtt: self.options['jansson'].shared = True + if self.options.build_event_admin_remote_provider_mqtt: + self.options['mosquitto'].shared = True + if self.options.enable_testing: + self.options['mosquitto'].broker = True def requirements(self): if self.options.build_utils: @@ -332,7 +344,7 @@ def requirements(self): self.requires("civetweb/1.16") if self.options.build_celix_dfi: self.requires("libffi/[>=3.2.1 <4.0.0]") - if self.options.build_utils or self.options.build_celix_dfi or self.options.build_celix_etcdlib: + if self.options.build_utils or self.options.build_celix_dfi or self.options.build_celix_etcdlib or self.options.build_event_admin_remote_provider_mqtt: self.requires("jansson/[>=2.12 <3.0.0]") if self.options.build_rsa_discovery_zeroconf: # TODO: To be replaced with mdnsresponder/1790.80.10, resolve some problems of mdnsresponder @@ -340,6 +352,8 @@ def requirements(self): self.requires("mdnsresponder/1310.140.1") # 'libzip/1.10.1' requires 'zlib/1.2.13' while 'libcurl/7.64.1' requires 'zlib/1.2.12' self.requires("zlib/1.2.13", override=True) + if self.options.build_event_admin_remote_provider_mqtt: + self.requires("mosquitto/[>=2.0.3 <3.0.0]") self.validate() def generate(self): @@ -352,6 +366,8 @@ def generate(self): tc.cache_variables["BUILD_ERROR_INJECTOR_MDNSRESPONDER"] = "ON" if "jansson" in lst: tc.cache_variables["BUILD_ERROR_INJECTOR_JANSSON"] = "ON" + if "mosquitto" in lst: + tc.cache_variables["BUILD_ERROR_INJECTOR_MOSQUITTO"] = "ON" tc.cache_variables["CELIX_ERR_BUFFER_SIZE"] = str(self.options.celix_err_buffer_size) # tc.cache_variables["CMAKE_PROJECT_Celix_INCLUDE"] = os.path.join(self.build_folder, "conan_paths.cmake") # the following is workaround for https://github.com/conan-io/conan/issues/7192 diff --git a/examples/conan_test_package/CMakeLists.txt b/examples/conan_test_package/CMakeLists.txt index 48f109e6c..eb57342c5 100644 --- a/examples/conan_test_package/CMakeLists.txt +++ b/examples/conan_test_package/CMakeLists.txt @@ -300,6 +300,18 @@ if (TEST_EVENT_ADMIN) ) add_executable(use_event_admin_api test_event_admin_api.c) target_link_libraries(use_event_admin_api PRIVATE Celix::event_admin_api) + + add_executable(use_event_admin_spi test_event_admin_spi.c) + target_link_libraries(use_event_admin_spi PRIVATE Celix::event_admin_spi) +endif () + +option(TEST_EVENT_ADMIN_REMOTE_PROVIDER_MQTT "Test event admin remote provider based on MQTT" OFF) +if (TEST_EVENT_ADMIN_REMOTE_PROVIDER_MQTT) + add_celix_container(use_event_admin_remote_provider_mqtt + BUNDLES + Celix::event_admin_remote_provider_mqtt + hello + ) endif () option(TEST_COMPONENTS_READY_CHECK "Test the components.ready condition checking bundle" OFF) diff --git a/examples/conan_test_package/conanfile.py b/examples/conan_test_package/conanfile.py index 734def051..50d4b50f2 100644 --- a/examples/conan_test_package/conanfile.py +++ b/examples/conan_test_package/conanfile.py @@ -54,6 +54,7 @@ def build(self): cmake.definitions["TEST_CELIX_DFI"] = self.options["celix"].build_celix_dfi cmake.definitions["TEST_UTILS"] = self.options["celix"].build_utils cmake.definitions["TEST_EVENT_ADMIN"] = self.options["celix"].build_event_admin + cmake.definitions["TEST_EVENT_ADMIN_REMOTE_PROVIDER_MQTT"] = self.options["celix"].build_event_admin_remote_provider_mqtt cmake.definitions["TEST_COMPONENTS_READY_CHECK"] = self.options["celix"].build_components_ready_check cmake.definitions["CMAKE_PROJECT_test_package_INCLUDE"] = os.path.join(self.build_folder, "conan_paths.cmake") # the following is workaround https://github.com/conan-io/conan/issues/7192 @@ -127,6 +128,10 @@ def test(self): self.run("./use_event_admin", cwd=os.path.join("deploy", "use_event_admin"), run_environment=True) self.run("./use_event_admin_api", run_environment=True) + self.run("./use_event_admin_spi", run_environment=True) + if self.options["celix"].build_event_admin_remote_provider_mqtt: + self.run("./use_event_admin_remote_provider_mqtt", + cwd=os.path.join("deploy", "use_event_admin_remote_provider_mqtt"), run_environment=True) if self.options["celix"].build_components_ready_check: self.run("./use_components_ready_check", cwd=os.path.join("deploy", "use_components_ready_check"), run_environment=True) diff --git a/examples/conan_test_package/test_event_admin_spi.c b/examples/conan_test_package/test_event_admin_spi.c new file mode 100644 index 000000000..2f9c9df6c --- /dev/null +++ b/examples/conan_test_package/test_event_admin_spi.c @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include +#include + +int main() { + printf("sizeof(celix_event_remote_provider_service_t) = %zu\n", sizeof(celix_event_remote_provider_service_t)); + return 0; +} \ No newline at end of file diff --git a/examples/conan_test_package_v2/conanfile.py b/examples/conan_test_package_v2/conanfile.py index 8bba84bba..b757c2512 100644 --- a/examples/conan_test_package_v2/conanfile.py +++ b/examples/conan_test_package_v2/conanfile.py @@ -64,6 +64,7 @@ def generate(self): tc.cache_variables["TEST_CELIX_DFI"] = celix_options.build_celix_dfi tc.cache_variables["TEST_UTILS"] = celix_options.build_utils tc.cache_variables["TEST_EVENT_ADMIN"] = celix_options.build_event_admin + tc.cache_variables["TEST_EVENT_ADMIN_REMOTE_PROVIDER_MQTT"] = celix_options.build_event_admin_remote_provider_mqtt tc.cache_variables["TEST_COMPONENTS_READY_CHECK"] = celix_options.build_components_ready_check # the following is workaround https://github.com/conan-io/conan/issues/7192 if self.settings.os == "Linux": @@ -144,6 +145,10 @@ def test(self): self.run("./use_event_admin", cwd=os.path.join("deploy", "use_event_admin"), env="conanrun") self.run("./conan_test_package/use_event_admin_api", env="conanrun") + self.run("./conan_test_package/use_event_admin_spi", env="conanrun") + if celix_options.build_event_admin_remote_provider_mqtt: + self.run("./use_event_admin_remote_provider_mqtt", + cwd=os.path.join("deploy", "use_event_admin_remote_provider_mqtt"), env="conanrun") if celix_options.build_components_ready_check: self.run("./use_components_ready_check", cwd=os.path.join("deploy", "use_components_ready_check"), env="conanrun") diff --git a/libs/error_injector/CMakeLists.txt b/libs/error_injector/CMakeLists.txt index 76a9f99f7..0df4fdc0c 100644 --- a/libs/error_injector/CMakeLists.txt +++ b/libs/error_injector/CMakeLists.txt @@ -47,3 +47,8 @@ celix_subproject(ERROR_INJECTOR_JANSSON "Option to enable building the jansson e if (ERROR_INJECTOR_JANSSON) add_subdirectory(jansson) endif () + +celix_subproject(ERROR_INJECTOR_MOSQUITTO "Option to enable building the mosquitto error injector" OFF) +if (ERROR_INJECTOR_MOSQUITTO) + add_subdirectory(mosquitto) +endif () diff --git a/libs/error_injector/jansson/CMakeLists.txt b/libs/error_injector/jansson/CMakeLists.txt index ce4beaa9d..0da1580dc 100644 --- a/libs/error_injector/jansson/CMakeLists.txt +++ b/libs/error_injector/jansson/CMakeLists.txt @@ -38,5 +38,6 @@ target_link_options(jansson_ei INTERFACE LINKER:--wrap,json_vsprintf LINKER:--wrap,json_sprintf LINKER:--wrap,json_dumpf + LINKER:--wrap,json_pack_ex ) add_library(Celix::jansson_ei ALIAS jansson_ei) diff --git a/libs/error_injector/jansson/include/jansson_ei.h b/libs/error_injector/jansson/include/jansson_ei.h index b98bd8821..a5e239ab6 100644 --- a/libs/error_injector/jansson/include/jansson_ei.h +++ b/libs/error_injector/jansson/include/jansson_ei.h @@ -37,6 +37,7 @@ CELIX_EI_DECLARE(json_real, json_t*); CELIX_EI_DECLARE(json_vsprintf,json_t*); CELIX_EI_DECLARE(json_sprintf, json_t*); CELIX_EI_DECLARE(json_dumpf, int); +CELIX_EI_DECLARE(json_pack_ex, json_t*); #ifdef __cplusplus } diff --git a/libs/error_injector/jansson/src/jansson_ei.cc b/libs/error_injector/jansson/src/jansson_ei.cc index 98289c970..8c537b90c 100644 --- a/libs/error_injector/jansson/src/jansson_ei.cc +++ b/libs/error_injector/jansson/src/jansson_ei.cc @@ -113,4 +113,15 @@ int __wrap_json_dumpf(const json_t* json, FILE* output, size_t flags) { return __real_json_dumpf(json, output, flags); } +json_t* __real_json_pack_ex(json_error_t* error, size_t flags, const char* fmt, ...); +CELIX_EI_DEFINE(json_pack_ex, json_t*) +json_t* __wrap_json_pack_ex(json_error_t* error, size_t flags, const char* fmt, ...) { + CELIX_EI_IMPL(json_pack_ex); + va_list args; + va_start(args, fmt); + json_t *obj = json_vpack_ex(error, flags, fmt, args); + va_end(args); + return obj; +} + } \ No newline at end of file diff --git a/libs/error_injector/mosquitto/CMakeLists.txt b/libs/error_injector/mosquitto/CMakeLists.txt new file mode 100644 index 000000000..cdc40cf29 --- /dev/null +++ b/libs/error_injector/mosquitto/CMakeLists.txt @@ -0,0 +1,41 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +add_library(mosquitto_ei STATIC src/mosquitto_ei.cc) + +find_package(mosquitto REQUIRED) + +target_include_directories(mosquitto_ei PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(mosquitto_ei PUBLIC Celix::error_injector mosquitto::libmosquitto) + +target_link_options(mosquitto_ei INTERFACE + LINKER:--wrap,mosquitto_new + LINKER:--wrap,mosquitto_property_add_int32 + LINKER:--wrap,mosquitto_property_add_string_pair + LINKER:--wrap,mosquitto_property_add_string + LINKER:--wrap,mosquitto_property_add_binary + LINKER:--wrap,mosquitto_int_option + LINKER:--wrap,mosquitto_will_set_v5 + LINKER:--wrap,mosquitto_subscribe_v5 + LINKER:--wrap,mosquitto_unsubscribe + LINKER:--wrap,mosquitto_publish_v5 + LINKER:--wrap,mosquitto_property_copy_all + LINKER:--wrap,mosquitto_property_read_string + LINKER:--wrap,mosquitto_property_read_binary + LINKER:--wrap,mosquitto_property_read_string_pair +) +add_library(Celix::mosquitto_ei ALIAS mosquitto_ei) diff --git a/libs/error_injector/mosquitto/include/mosquitto_ei.h b/libs/error_injector/mosquitto/include/mosquitto_ei.h new file mode 100644 index 000000000..77491a297 --- /dev/null +++ b/libs/error_injector/mosquitto/include/mosquitto_ei.h @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_MOSQUITTO_EI_H +#define CELIX_MOSQUITTO_EI_H +#ifdef __cplusplus +extern "C" { +#endif +#include +#include "celix_error_injector.h" + +CELIX_EI_DECLARE(mosquitto_new, struct mosquitto*); +CELIX_EI_DECLARE(mosquitto_property_add_int32, int); +CELIX_EI_DECLARE(mosquitto_property_add_string_pair, int); +CELIX_EI_DECLARE(mosquitto_property_add_string, int); +CELIX_EI_DECLARE(mosquitto_property_add_binary, int); +CELIX_EI_DECLARE(mosquitto_int_option, int); +CELIX_EI_DECLARE(mosquitto_will_set_v5, int); +CELIX_EI_DECLARE(mosquitto_subscribe_v5, int); +CELIX_EI_DECLARE(mosquitto_unsubscribe, int); +CELIX_EI_DECLARE(mosquitto_publish_v5, int); +CELIX_EI_DECLARE(mosquitto_property_copy_all, int); +CELIX_EI_DECLARE(mosquitto_property_read_string, const mosquitto_property*); +CELIX_EI_DECLARE(mosquitto_property_read_binary, const mosquitto_property*); +CELIX_EI_DECLARE(mosquitto_property_read_string_pair, const mosquitto_property*); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_MOSQUITTO_EI_H diff --git a/libs/error_injector/mosquitto/src/mosquitto_ei.cc b/libs/error_injector/mosquitto/src/mosquitto_ei.cc new file mode 100644 index 000000000..036510e8c --- /dev/null +++ b/libs/error_injector/mosquitto/src/mosquitto_ei.cc @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "mosquitto_ei.h" + +extern "C" { + +struct mosquitto* __real_mosquitto_new(const char* id, bool clean_session, void* obj); +CELIX_EI_DEFINE(mosquitto_new, struct mosquitto*) +struct mosquitto* __wrap_mosquitto_new(const char* id, bool clean_session, void* obj) { + CELIX_EI_IMPL(mosquitto_new); + return __real_mosquitto_new(id, clean_session, obj); +} + +int __real_mosquitto_property_add_int32(struct mosquitto* mosq, int identifier, int value); +CELIX_EI_DEFINE(mosquitto_property_add_int32, int) +int __wrap_mosquitto_property_add_int32(struct mosquitto* mosq, int identifier, int value) { + CELIX_EI_IMPL(mosquitto_property_add_int32); + return __real_mosquitto_property_add_int32(mosq, identifier, value); +} + +int __real_mosquitto_property_add_string_pair(struct mosquitto* mosq, int identifier, const char* key, const char* value); +CELIX_EI_DEFINE(mosquitto_property_add_string_pair, int) +int __wrap_mosquitto_property_add_string_pair(struct mosquitto* mosq, int identifier, const char* key, const char* value) { + CELIX_EI_IMPL(mosquitto_property_add_string_pair); + return __real_mosquitto_property_add_string_pair(mosq, identifier, key, value); +} + +int __real_mosquitto_property_add_string(struct mosquitto* mosq, int identifier, const char* value); +CELIX_EI_DEFINE(mosquitto_property_add_string, int) +int __wrap_mosquitto_property_add_string(struct mosquitto* mosq, int identifier, const char* value) { + CELIX_EI_IMPL(mosquitto_property_add_string); + return __real_mosquitto_property_add_string(mosq, identifier, value); +} + +int __real_mosquitto_property_add_binary(struct mosquitto* mosq, int identifier, const void* value, int len); +CELIX_EI_DEFINE(mosquitto_property_add_binary, int) +int __wrap_mosquitto_property_add_binary(struct mosquitto* mosq, int identifier, const void* value, int len) { + CELIX_EI_IMPL(mosquitto_property_add_binary); + return __real_mosquitto_property_add_binary(mosq, identifier, value, len); +} + +int __real_mosquitto_int_option(struct mosquitto* mosq, int option, int value); +CELIX_EI_DEFINE(mosquitto_int_option, int) +int __wrap_mosquitto_int_option(struct mosquitto* mosq, int option, int value) { + CELIX_EI_IMPL(mosquitto_int_option); + return __real_mosquitto_int_option(mosq, option, value); +} + +int __real_mosquitto_will_set_v5(struct mosquitto* mosq, const char* topic, int payloadlen, const void* payload, int qos, bool retain, const mosquitto_property* properties); +CELIX_EI_DEFINE(mosquitto_will_set_v5, int) +int __wrap_mosquitto_will_set_v5(struct mosquitto* mosq, const char* topic, int payloadlen, const void* payload, int qos, bool retain, const mosquitto_property* properties) { + CELIX_EI_IMPL(mosquitto_will_set_v5); + return __real_mosquitto_will_set_v5(mosq, topic, payloadlen, payload, qos, retain, properties); +} + +int __real_mosquitto_subscribe_v5(struct mosquitto* mosq, int* mid, const char* sub, int qos, int options, const mosquitto_property* properties); +CELIX_EI_DEFINE(mosquitto_subscribe_v5, int) +int __wrap_mosquitto_subscribe_v5(struct mosquitto* mosq, int* mid, const char* sub, int qos, int options, const mosquitto_property* properties) { + CELIX_EI_IMPL(mosquitto_subscribe_v5); + return __real_mosquitto_subscribe_v5(mosq, mid, sub, qos, options, properties); +} + +int __real_mosquitto_unsubscribe(struct mosquitto* mosq, int* mid, const char* sub); +CELIX_EI_DEFINE(mosquitto_unsubscribe, int) +int __wrap_mosquitto_unsubscribe(struct mosquitto* mosq, int* mid, const char* sub) { + CELIX_EI_IMPL(mosquitto_unsubscribe); + return __real_mosquitto_unsubscribe(mosq, mid, sub); +} + +int __real_mosquitto_publish_v5(struct mosquitto* mosq, int* mid, const char* topic, int payloadlen, const void* payload, int qos, bool retain, const mosquitto_property* properties); +CELIX_EI_DEFINE(mosquitto_publish_v5, int) +int __wrap_mosquitto_publish_v5(struct mosquitto* mosq, int* mid, const char* topic, int payloadlen, const void* payload, int qos, bool retain, const mosquitto_property* properties) { + CELIX_EI_IMPL(mosquitto_publish_v5); + return __real_mosquitto_publish_v5(mosq, mid, topic, payloadlen, payload, qos, retain, properties); +} + +int __real_mosquitto_property_copy_all(struct mosquitto* mosq, const mosquitto_property* src); +CELIX_EI_DEFINE(mosquitto_property_copy_all, int) +int __wrap_mosquitto_property_copy_all(struct mosquitto* mosq, const mosquitto_property* src) { + CELIX_EI_IMPL(mosquitto_property_copy_all); + return __real_mosquitto_property_copy_all(mosq, src); +} + +const mosquitto_property* __real_mosquitto_property_read_string(const mosquitto_property* property, int identifier, const char** value, bool skip_first); +CELIX_EI_DEFINE(mosquitto_property_read_string, const mosquitto_property*) +const mosquitto_property* __wrap_mosquitto_property_read_string(const mosquitto_property* property, int identifier, const char** value, bool skip_first) { + CELIX_EI_IMPL(mosquitto_property_read_string); + return __real_mosquitto_property_read_string(property, identifier, value, skip_first); +} + +const mosquitto_property* __real_mosquitto_property_read_binary(const mosquitto_property* property, int identifier, const void** value, int* len, bool skip_first); +CELIX_EI_DEFINE(mosquitto_property_read_binary, const mosquitto_property*) +const mosquitto_property* __wrap_mosquitto_property_read_binary(const mosquitto_property* property, int identifier, const void** value, int* len, bool skip_first) { + CELIX_EI_IMPL(mosquitto_property_read_binary); + return __real_mosquitto_property_read_binary(property, identifier, value, len, skip_first); +} + +const mosquitto_property* __real_mosquitto_property_read_string_pair(const mosquitto_property* property, int identifier, const char** name, const char** value, bool skip_first); +CELIX_EI_DEFINE(mosquitto_property_read_string_pair, const mosquitto_property*) +const mosquitto_property* __wrap_mosquitto_property_read_string_pair(const mosquitto_property* property, int identifier, const char** name, const char** value, bool skip_first) { + CELIX_EI_IMPL(mosquitto_property_read_string_pair); + return __real_mosquitto_property_read_string_pair(property, identifier, name, value, skip_first); +} + +} // extern "C" diff --git a/libs/framework/error_injector/celix_dm_component/src/celix_dm_component_ei.cc b/libs/framework/error_injector/celix_dm_component/src/celix_dm_component_ei.cc index 1d095965e..10519e1fb 100644 --- a/libs/framework/error_injector/celix_dm_component/src/celix_dm_component_ei.cc +++ b/libs/framework/error_injector/celix_dm_component/src/celix_dm_component_ei.cc @@ -52,8 +52,9 @@ celix_status_t __wrap_celix_dmServiceDependency_setService(celix_dm_service_depe celix_status_t __real_celix_dmComponent_addInterface(celix_dm_component_t* component, const char* serviceName, const char* serviceVersion, const void* service, celix_properties_t* properties); CELIX_EI_DEFINE(celix_dmComponent_addInterface, celix_status_t) celix_status_t __wrap_celix_dmComponent_addInterface(celix_dm_component_t* component, const char* serviceName, const char* serviceVersion, const void* service, celix_properties_t* properties) { + celix_autoptr(celix_properties_t) autoProps = properties; CELIX_EI_IMPL(celix_dmComponent_addInterface); - return __real_celix_dmComponent_addInterface(component, serviceName, serviceVersion, service, properties); + return __real_celix_dmComponent_addInterface(component, serviceName, serviceVersion, service, celix_steal_ptr(autoProps)); } celix_status_t __real_celix_dependencyManager_addAsync(celix_dependency_manager_t *manager, celix_dm_component_t *component); diff --git a/libs/utils/error_injector/CMakeLists.txt b/libs/utils/error_injector/CMakeLists.txt index dcdd69d3d..cdf67c8e6 100644 --- a/libs/utils/error_injector/CMakeLists.txt +++ b/libs/utils/error_injector/CMakeLists.txt @@ -24,3 +24,4 @@ add_subdirectory(celix_utils) add_subdirectory(celix_version) add_subdirectory(zip) add_subdirectory(hash_map) +add_subdirectory(celix_filter) diff --git a/libs/utils/error_injector/celix_array_list/CMakeLists.txt b/libs/utils/error_injector/celix_array_list/CMakeLists.txt index 2962dba59..c60498564 100644 --- a/libs/utils/error_injector/celix_array_list/CMakeLists.txt +++ b/libs/utils/error_injector/celix_array_list/CMakeLists.txt @@ -24,6 +24,7 @@ target_link_options(array_list_ei INTERFACE LINKER:--wrap,celix_arrayList_createWithOptions LINKER:--wrap,celix_arrayList_createStringArray LINKER:--wrap,celix_arrayList_createLongArray + LINKER:--wrap,celix_arrayList_createPointerArray LINKER:--wrap,celix_arrayList_copy LINKER:--wrap,celix_arrayList_add LINKER:--wrap,celix_arrayList_addString diff --git a/libs/utils/error_injector/celix_array_list/include/celix_array_list_ei.h b/libs/utils/error_injector/celix_array_list/include/celix_array_list_ei.h index 3a6067a6e..52f805749 100644 --- a/libs/utils/error_injector/celix_array_list/include/celix_array_list_ei.h +++ b/libs/utils/error_injector/celix_array_list/include/celix_array_list_ei.h @@ -34,6 +34,8 @@ CELIX_EI_DECLARE(celix_arrayList_createStringArray, celix_array_list_t*); CELIX_EI_DECLARE(celix_arrayList_createLongArray, celix_array_list_t*); +CELIX_EI_DECLARE(celix_arrayList_createPointerArray, celix_array_list_t*); + CELIX_EI_DECLARE(celix_arrayList_copy, celix_array_list_t*); CELIX_EI_DECLARE(celix_arrayList_add, celix_status_t); diff --git a/libs/utils/error_injector/celix_array_list/src/celix_array_list_ei.cc b/libs/utils/error_injector/celix_array_list/src/celix_array_list_ei.cc index c7d02c055..8ef6214b3 100644 --- a/libs/utils/error_injector/celix_array_list/src/celix_array_list_ei.cc +++ b/libs/utils/error_injector/celix_array_list/src/celix_array_list_ei.cc @@ -57,6 +57,13 @@ void *__wrap_celix_arrayList_createLongArray() { return __real_celix_arrayList_createLongArray(); } +celix_array_list_t* __real_celix_arrayList_createPointerArray(); +CELIX_EI_DEFINE(celix_arrayList_createPointerArray, celix_array_list_t*) +celix_array_list_t* __wrap_celix_arrayList_createPointerArray() { + CELIX_EI_IMPL(celix_arrayList_createPointerArray); + return __real_celix_arrayList_createPointerArray(); +} + celix_status_t __real_celix_arrayList_add(celix_array_list_t* list, void* value); CELIX_EI_DEFINE(celix_arrayList_add, celix_status_t) celix_status_t __wrap_celix_arrayList_add(celix_array_list_t* list, void* value) { diff --git a/libs/utils/error_injector/celix_filter/CMakeLists.txt b/libs/utils/error_injector/celix_filter/CMakeLists.txt new file mode 100644 index 000000000..ba54e3e45 --- /dev/null +++ b/libs/utils/error_injector/celix_filter/CMakeLists.txt @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +add_library(filter_ei STATIC src/celix_filter_ei.cc) + +target_include_directories(filter_ei PUBLIC include) +target_link_libraries(filter_ei PUBLIC Celix::error_injector Celix::utils) +# It plays nicely with address sanitizer this way. +target_link_options(filter_ei INTERFACE + LINKER:--wrap,celix_filter_create +) +add_library(Celix::filter_ei ALIAS filter_ei) diff --git a/libs/utils/error_injector/celix_filter/include/celix_filter_ei.h b/libs/utils/error_injector/celix_filter/include/celix_filter_ei.h new file mode 100644 index 000000000..8b2b5b2e5 --- /dev/null +++ b/libs/utils/error_injector/celix_filter/include/celix_filter_ei.h @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef CELIX_FILTER_EI_H +#define CELIX_FILTER_EI_H +#ifdef __cplusplus +extern "C" { +#endif +#include "celix_error_injector.h" +#include "celix_filter.h" + +CELIX_EI_DECLARE(celix_filter_create, celix_filter_t*); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_FILTER_EI_H diff --git a/libs/utils/error_injector/celix_filter/src/celix_filter_ei.cc b/libs/utils/error_injector/celix_filter/src/celix_filter_ei.cc new file mode 100644 index 000000000..16abbf3b3 --- /dev/null +++ b/libs/utils/error_injector/celix_filter/src/celix_filter_ei.cc @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "celix_filter_ei.h" + +extern "C" { + +celix_filter_t* __real_celix_filter_create(const char* filterStr); +CELIX_EI_DEFINE(celix_filter_create, celix_filter_t*) +celix_filter_t* __wrap_celix_filter_create(const char* filterStr) { + CELIX_EI_IMPL(celix_filter_create); + return __real_celix_filter_create(filterStr); +} + +} \ No newline at end of file diff --git a/libs/utils/error_injector/celix_properties/CMakeLists.txt b/libs/utils/error_injector/celix_properties/CMakeLists.txt index 0155b75e7..f3aa1c72a 100644 --- a/libs/utils/error_injector/celix_properties/CMakeLists.txt +++ b/libs/utils/error_injector/celix_properties/CMakeLists.txt @@ -30,5 +30,9 @@ target_link_options(properties_ei INTERFACE LINKER:--wrap,celix_properties_save LINKER:--wrap,celix_properties_saveToStream LINKER:--wrap,celix_properties_load + LINKER:--wrap,celix_properties_setBool + LINKER:--wrap,celix_properties_getAsStringArrayList + LINKER:--wrap,celix_properties_saveToString + LINKER:--wrap,celix_properties_loadFromString ) add_library(Celix::properties_ei ALIAS properties_ei) diff --git a/libs/utils/error_injector/celix_properties/include/celix_properties_ei.h b/libs/utils/error_injector/celix_properties/include/celix_properties_ei.h index a98905c54..0cf0413f5 100644 --- a/libs/utils/error_injector/celix_properties/include/celix_properties_ei.h +++ b/libs/utils/error_injector/celix_properties/include/celix_properties_ei.h @@ -35,7 +35,10 @@ CELIX_EI_DECLARE(celix_properties_setEntry, celix_status_t); CELIX_EI_DECLARE(celix_properties_save, celix_status_t); CELIX_EI_DECLARE(celix_properties_saveToStream, celix_status_t); CELIX_EI_DECLARE(celix_properties_load, celix_status_t); - +CELIX_EI_DECLARE(celix_properties_setBool, celix_status_t); +CELIX_EI_DECLARE(celix_properties_getAsStringArrayList, celix_status_t); +CELIX_EI_DECLARE(celix_properties_saveToString, celix_status_t); +CELIX_EI_DECLARE(celix_properties_loadFromString, celix_status_t); #ifdef __cplusplus } #endif diff --git a/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc b/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc index c0bfe60a4..3e864aa8b 100644 --- a/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc +++ b/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc @@ -97,4 +97,32 @@ __wrap_celix_properties_load(const char* filename, return __real_celix_properties_load(filename, decodeFlags, out); } +celix_status_t __real_celix_properties_setBool(celix_properties_t* properties, const char* key, bool value); +CELIX_EI_DEFINE(celix_properties_setBool, celix_status_t) +celix_status_t __wrap_celix_properties_setBool(celix_properties_t* properties, const char* key, bool value) { + CELIX_EI_IMPL(celix_properties_setBool); + return __real_celix_properties_setBool(properties, key, value); +} + +celix_status_t __real_celix_properties_getAsStringArrayList(const celix_properties_t* properties, const char* key, const celix_array_list_t* defaultValue, celix_array_list_t** list); +CELIX_EI_DEFINE(celix_properties_getAsStringArrayList, celix_status_t) +celix_status_t __wrap_celix_properties_getAsStringArrayList(const celix_properties_t* properties, const char* key, const celix_array_list_t* defaultValue, celix_array_list_t** list) { + CELIX_EI_IMPL(celix_properties_getAsStringArrayList); + return __real_celix_properties_getAsStringArrayList(properties, key, defaultValue, list); +} + +celix_status_t __real_celix_properties_saveToString(const celix_properties_t* properties, int encodeFlags, char** out); +CELIX_EI_DEFINE(celix_properties_saveToString, celix_status_t) +celix_status_t __wrap_celix_properties_saveToString(const celix_properties_t* properties, int encodeFlags, char** out) { + CELIX_EI_IMPL(celix_properties_saveToString); + return __real_celix_properties_saveToString(properties, encodeFlags, out); +} + +celix_status_t __real_celix_properties_loadFromString(const char* data, int decodeFlags, celix_properties_t** out); +CELIX_EI_DEFINE(celix_properties_loadFromString, celix_status_t) +celix_status_t __wrap_celix_properties_loadFromString(const char* data, int decodeFlags, celix_properties_t** out) { + CELIX_EI_IMPL(celix_properties_loadFromString); + return __real_celix_properties_loadFromString(data, decodeFlags, out); +} + } \ No newline at end of file diff --git a/libs/utils/error_injector/celix_threads/CMakeLists.txt b/libs/utils/error_injector/celix_threads/CMakeLists.txt index ed97d3a58..f7b9c5681 100644 --- a/libs/utils/error_injector/celix_threads/CMakeLists.txt +++ b/libs/utils/error_injector/celix_threads/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(threads_ei PUBLIC Celix::error_injector Celix::utils) target_link_options(threads_ei INTERFACE LINKER:--wrap,celixThreadMutex_create LINKER:--wrap,celixThread_create + LINKER:--wrap,celixThreadCondition_signal LINKER:--wrap,celixThreadCondition_init LINKER:--wrap,celixThreadRwlock_create LINKER:--wrap,celix_tss_create diff --git a/libs/utils/error_injector/celix_threads/include/celix_threads_ei.h b/libs/utils/error_injector/celix_threads/include/celix_threads_ei.h index 991d7728e..80d3ea6aa 100644 --- a/libs/utils/error_injector/celix_threads/include/celix_threads_ei.h +++ b/libs/utils/error_injector/celix_threads/include/celix_threads_ei.h @@ -29,6 +29,7 @@ extern "C" { CELIX_EI_DECLARE(celixThreadMutex_create, celix_status_t); CELIX_EI_DECLARE(celixThread_create, celix_status_t); +CELIX_EI_DECLARE(celixThreadCondition_signal, celix_status_t); CELIX_EI_DECLARE(celixThreadCondition_init, celix_status_t); CELIX_EI_DECLARE(celixThreadRwlock_create, celix_status_t); diff --git a/libs/utils/error_injector/celix_threads/src/celix_threads_ei.cc b/libs/utils/error_injector/celix_threads/src/celix_threads_ei.cc index 5ec92ecb6..698a24acd 100644 --- a/libs/utils/error_injector/celix_threads/src/celix_threads_ei.cc +++ b/libs/utils/error_injector/celix_threads/src/celix_threads_ei.cc @@ -42,6 +42,13 @@ __wrap_celixThread_create(celix_thread_t *__new_thread, celix_thread_attr_t *__a return __real_celixThread_create(__new_thread, __attr, __func, __data); } +celix_status_t __real_celixThreadCondition_signal(celix_thread_cond_t* cond); +CELIX_EI_DEFINE(celixThreadCondition_signal, celix_status_t) +celix_status_t __wrap_celixThreadCondition_signal(celix_thread_cond_t* cond) { + CELIX_EI_IMPL(celixThreadCondition_signal); + return __real_celixThreadCondition_signal(cond); +} + celix_status_t __real_celixThreadCondition_init(celix_thread_cond_t *__condition, celix_thread_condattr_t *__attr); CELIX_EI_DEFINE(celixThreadCondition_init, celix_status_t) celix_status_t __wrap_celixThreadCondition_init(celix_thread_cond_t *__condition, celix_thread_condattr_t *__attr) {