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..82bd3b223 100644 --- a/bundles/event_admin/event_admin/README.md +++ b/bundles/event_admin/event_admin/README.md @@ -63,6 +63,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..ed702c956 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..4efbf14f8 --- /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 + 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..2f8ac7293 100644 --- a/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc +++ b/bundles/event_admin/event_admin/gtest/src/CelixEventAdminTestSuite.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "CelixEventAdminTestSuiteBaseClass.h" @@ -467,6 +468,262 @@ 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, AddRemoteProviderServiceWithoutServiceIdTest) { + TestEventAdmin([](celix_event_admin_t* ea, celix_bundle_context_t*) { + 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_autoptr(celix_properties_t) props = celix_properties_create(); + auto status = celix_eventAdmin_addRemoteProviderService(ea, &remoteProviderService, props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + }); +} + +TEST_F(CelixEventAdminTestSuite, RemoveRemoteProviderServiceWithoutServiceIdTest) { + TestEventAdmin([](celix_event_admin_t* ea, celix_bundle_context_t*) { + 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_autoptr(celix_properties_t) props = celix_properties_create(); + auto status = celix_eventAdmin_removeRemoteProviderService(ea, &remoteProviderService, props); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + }); +} + +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_GT(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_GT(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, 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; 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..bb28870dc 100644 --- a/bundles/event_admin/event_admin/src/celix_event_admin.c +++ b/bundles/event_admin/event_admin/src/celix_event_admin.c @@ -33,6 +33,7 @@ #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 @@ -42,6 +43,8 @@ #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; @@ -61,15 +64,24 @@ typedef struct celix_event_entry { celix_long_hash_map_t* eventHandlers;//key: event handler service id, value: null }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 + const char* fwUUID; + 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* + long nextSeqId; + 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,11 +99,17 @@ celix_event_admin_t* celix_eventAdmin_create(celix_bundle_context_t* ctx) { } ea->ctx = ctx; ea->threadsRunning = false; + ea->nextSeqId = 1; 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); @@ -127,6 +145,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 +189,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 +241,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); @@ -415,6 +454,131 @@ 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); + if (serviceId < 0) { + celix_logHelper_error(ea->logHelper, "Remote provider service id is invalid."); + return CELIX_ILLEGAL_ARGUMENT; + } + 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); + if (serviceId < 0) { + celix_logHelper_error(ea->logHelper, "Remote provider service id is invalid."); + return CELIX_ILLEGAL_ARGUMENT; + } + 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_retrieveLongTimeUnusedEventSeqIdCache(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) > 60*60/*1h*/) { + 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; + } + 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_retrieveLongTimeUnusedEventSeqIdCache(ea); + + return false; +} + +static long celix_eventAdmin_getEventSeqId(celix_event_admin_t* ea) { + celix_auto(celix_rwlock_wlock_guard_t) wLockGuard = celixRwlockWlockGuard_init(&ea->lock); + long seqId = ea->nextSeqId++; + if (seqId <= 0) { + seqId = 1; + ea->nextSeqId = seqId + 1; + } + 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_error(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 +613,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); @@ -533,7 +701,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 +750,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_constants.h b/bundles/event_admin/event_admin_api/include/celix_event_constants.h index 2e8309db5..b298c4a6f 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,47 @@ 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 + */ +#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_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..1c6b4a9d7 --- /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..e5e49de25 --- /dev/null +++ b/bundles/event_admin/examples/res/mosquitto.conf @@ -0,0 +1,904 @@ +# Config file for mosquitto +# +# See mosquitto.conf(5) for more information. +# +# Default values are shown, uncomment to change. +# +# Use the # character to indicate a comment, but only if it is the +# very first character on the line. + +# ================================================================= +# General configuration +# ================================================================= + +# Use per listener security settings. +# +# It is recommended this option be set before any other options. +# +# If this option is set to true, then all authentication and access control +# options are controlled on a per listener basis. The following options are +# affected: +# +# acl_file +allow_anonymous true +# allow_zero_length_clientid +# auto_id_prefix +# password_file +# plugin +# plugin_opt_* +# psk_file +# +# Note that if set to true, then a durable client (i.e. with clean session set +# to false) that has disconnected will use the ACL settings defined for the +# listener that it was most recently connected to. +# +# The default behaviour is for this to be set to false, which maintains the +# setting behaviour from previous versions of mosquitto. +#per_listener_settings false + + +# This option controls whether a client is allowed to connect with a zero +# length client id or not. This option only affects clients using MQTT v3.1.1 +# and later. If set to false, clients connecting with a zero length client id +# are disconnected. If set to true, clients will be allocated a client id by +# the broker. This means it is only useful for clients with clean session set +# to true. +#allow_zero_length_clientid true + +# If allow_zero_length_clientid is true, this option allows you to set a prefix +# to automatically generated client ids to aid visibility in logs. +# Defaults to 'auto-' +#auto_id_prefix auto- + +# This option affects the scenario when a client subscribes to a topic that has +# retained messages. It is possible that the client that published the retained +# message to the topic had access at the time they published, but that access +# has been subsequently removed. If check_retain_source is set to true, the +# default, the source of a retained message will be checked for access rights +# before it is republished. When set to false, no check will be made and the +# retained message will always be published. This affects all listeners. +#check_retain_source true + +# QoS 1 and 2 messages will be allowed inflight per client until this limit +# is exceeded. Defaults to 0. (No maximum) +# See also max_inflight_messages +#max_inflight_bytes 0 + +# The maximum number of QoS 1 and 2 messages currently inflight per +# client. +# This includes messages that are partway through handshakes and +# those that are being retried. Defaults to 20. Set to 0 for no +# maximum. Setting to 1 will guarantee in-order delivery of QoS 1 +# and 2 messages. +#max_inflight_messages 20 + +# For MQTT v5 clients, it is possible to have the server send a "server +# keepalive" value that will override the keepalive value set by the client. +# This is intended to be used as a mechanism to say that the server will +# disconnect the client earlier than it anticipated, and that the client should +# use the new keepalive value. The max_keepalive option allows you to specify +# that clients may only connect with keepalive less than or equal to this +# value, otherwise they will be sent a server keepalive telling them to use +# max_keepalive. This only applies to MQTT v5 clients. The default, and maximum +# value allowable, is 65535. +# +# Set to 0 to allow clients to set keepalive = 0, which means no keepalive +# checks are made and the client will never be disconnected by the broker if no +# messages are received. You should be very sure this is the behaviour that you +# want. +# +# For MQTT v3.1.1 and v3.1 clients, there is no mechanism to tell the client +# what keepalive value they should use. If an MQTT v3.1.1 or v3.1 client +# specifies a keepalive time greater than max_keepalive they will be sent a +# CONNACK message with the "identifier rejected" reason code, and disconnected. +# +#max_keepalive 65535 + +# For MQTT v5 clients, it is possible to have the server send a "maximum packet +# size" value that will instruct the client it will not accept MQTT packets +# with size greater than max_packet_size bytes. This applies to the full MQTT +# packet, not just the payload. Setting this option to a positive value will +# set the maximum packet size to that number of bytes. If a client sends a +# packet which is larger than this value, it will be disconnected. This applies +# to all clients regardless of the protocol version they are using, but v3.1.1 +# and earlier clients will of course not have received the maximum packet size +# information. Defaults to no limit. Setting below 20 bytes is forbidden +# because it is likely to interfere with ordinary client operation, even with +# very small payloads. +#max_packet_size 0 + +# QoS 1 and 2 messages above those currently in-flight will be queued per +# client until this limit is exceeded. Defaults to 0. (No maximum) +# See also max_queued_messages. +# If both max_queued_messages and max_queued_bytes are specified, packets will +# be queued until the first limit is reached. +#max_queued_bytes 0 + +# Set the maximum QoS supported. Clients publishing at a QoS higher than +# specified here will be disconnected. +#max_qos 2 + +# The maximum number of QoS 1 and 2 messages to hold in a queue per client +# above those that are currently in-flight. Defaults to 1000. Set +# to 0 for no maximum (not recommended). +# See also queue_qos0_messages. +# See also max_queued_bytes. +#max_queued_messages 1000 +# +# This option sets the maximum number of heap memory bytes that the broker will +# allocate, and hence sets a hard limit on memory use by the broker. Memory +# requests that exceed this value will be denied. The effect will vary +# depending on what has been denied. If an incoming message is being processed, +# then the message will be dropped and the publishing client will be +# disconnected. If an outgoing message is being sent, then the individual +# message will be dropped and the receiving client will be disconnected. +# Defaults to no limit. +#memory_limit 0 + +# This option sets the maximum publish payload size that the broker will allow. +# Received messages that exceed this size will not be accepted by the broker. +# The default value is 0, which means that all valid MQTT messages are +# accepted. MQTT imposes a maximum payload size of 268435455 bytes. +#message_size_limit 0 + +# This option allows the session of persistent clients (those with clean +# session set to false) that are not currently connected to be removed if they +# do not reconnect within a certain time frame. This is a non-standard option +# in MQTT v3.1. MQTT v3.1.1 and v5.0 allow brokers to remove client sessions. +# +# Badly designed clients may set clean session to false whilst using a randomly +# generated client id. This leads to persistent clients that connect once and +# never reconnect. This option allows these clients to be removed. This option +# allows persistent clients (those with clean session set to false) to be +# removed if they do not reconnect within a certain time frame. +# +# The expiration period should be an integer followed by one of h d w m y for +# hour, day, week, month and year respectively. For example +# +# persistent_client_expiration 2m +# persistent_client_expiration 14d +# persistent_client_expiration 1y +# +# The default if not set is to never expire persistent clients. +#persistent_client_expiration + +# Write process id to a file. Default is a blank string which means +# a pid file shouldn't be written. +# This should be set to /var/run/mosquitto/mosquitto.pid if mosquitto is +# being run automatically on boot with an init script and +# start-stop-daemon or similar. +#pid_file + +# Set to true to queue messages with QoS 0 when a persistent client is +# disconnected. These messages are included in the limit imposed by +# max_queued_messages and max_queued_bytes +# Defaults to false. +# This is a non-standard option for the MQTT v3.1 spec but is allowed in +# v3.1.1. +#queue_qos0_messages false + +# Set to false to disable retained message support. If a client publishes a +# message with the retain bit set, it will be disconnected if this is set to +# false. +#retain_available true + +# Disable Nagle's algorithm on client sockets. This has the effect of reducing +# latency of individual messages at the potential cost of increasing the number +# of packets being sent. +#set_tcp_nodelay false + +# Time in seconds between updates of the $SYS tree. +# Set to 0 to disable the publishing of the $SYS tree. +#sys_interval 10 + +# The MQTT specification requires that the QoS of a message delivered to a +# subscriber is never upgraded to match the QoS of the subscription. Enabling +# this option changes this behaviour. If upgrade_outgoing_qos is set true, +# messages sent to a subscriber will always match the QoS of its subscription. +# This is a non-standard option explicitly disallowed by the spec. +#upgrade_outgoing_qos false + +# When run as root, drop privileges to this user and its primary +# group. +# Set to root to stay as root, but this is not recommended. +# If set to "mosquitto", or left unset, and the "mosquitto" user does not exist +# then it will drop privileges to the "nobody" user instead. +# If run as a non-root user, this setting has no effect. +# Note that on Windows this has no effect and so mosquitto should be started by +# the user you wish it to run as. +#user mosquitto + +# ================================================================= +# relatedListeners +# ================================================================= + +# Listen on a port/ip address combination. By using this variable +# multiple times, mosquitto can listen on more than one port. If +# this variable is used and neither bind_address nor port given, +# then the default listener will not be started. +# The port number to listen on must be given. Optionally, an ip +# address or host name may be supplied as a second argument. In +# this case, mosquitto will attempt to bind the listener to that +# address and so restrict access to the associated network and +# interface. By default, mosquitto will listen on all interfaces. +# Note that for a websockets listener it is not possible to bind to a host +# name. +# +# On systems that support Unix Domain Sockets, it is also possible +# to create a # Unix socket rather than opening a TCP socket. In +# this case, the port number should be set to 0 and a unix socket +# path must be provided, e.g. +# listener 0 /tmp/mosquitto.sock +# +# listener port-number [ip address/host name/unix socket path] +listener 1883 + +# By default, a listener will attempt to listen on all supported IP protocol +# versions. If you do not have an IPv4 or IPv6 interface you may wish to +# disable support for either of those protocol versions. In particular, note +# that due to the limitations of the websockets library, it will only ever +# attempt to open IPv6 sockets if IPv6 support is compiled in, and so will fail +# if IPv6 is not available. +# +# Set to `ipv4` to force the listener to only use IPv4, or set to `ipv6` to +# force the listener to only use IPv6. If you want support for both IPv4 and +# IPv6, then do not use the socket_domain option. +# +socket_domain ipv4 + +# Bind the listener to a specific interface. This is similar to +# the [ip address/host name] part of the listener definition, but is useful +# when an interface has multiple addresses or the address may change. If used +# with the [ip address/host name] part of the listener definition, then the +# bind_interface option will take priority. +# Not available on Windows. +# +# Example: bind_interface eth0 +#bind_interface + +# When a listener is using the websockets protocol, it is possible to serve +# http data as well. Set http_dir to a directory which contains the files you +# wish to serve. If this option is not specified, then no normal http +# connections will be possible. +#http_dir + +# The maximum number of client connections to allow. This is +# a per listener setting. +# Default is -1, which means unlimited connections. +# Note that other process limits mean that unlimited connections +# are not really possible. Typically the default maximum number of +# connections possible is around 1024. +#max_connections -1 + +# The listener can be restricted to operating within a topic hierarchy using +# the mount_point option. This is achieved be prefixing the mount_point string +# to all topics for any clients connected to this listener. This prefixing only +# happens internally to the broker; the client will not see the prefix. +#mount_point + +# Choose the protocol to use when listening. +# This can be either mqtt or websockets. +# Certificate based TLS may be used with websockets, except that only the +# cafile, certfile, keyfile, ciphers, and ciphers_tls13 options are supported. +#protocol mqtt + +# Set use_username_as_clientid to true to replace the clientid that a client +# connected with with its username. This allows authentication to be tied to +# the clientid, which means that it is possible to prevent one client +# disconnecting another by using the same clientid. +# If a client connects with no username it will be disconnected as not +# authorised when this option is set to true. +# Do not use in conjunction with clientid_prefixes. +# See also use_identity_as_username. +# This does not apply globally, but on a per-listener basis. +#use_username_as_clientid + +# Change the websockets headers size. This is a global option, it is not +# possible to set per listener. This option sets the size of the buffer used in +# the libwebsockets library when reading HTTP headers. If you are passing large +# header data such as cookies then you may need to increase this value. If left +# unset, or set to 0, then the default of 1024 bytes will be used. +#websockets_headers_size + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable certificate based SSL/TLS support +# for this listener. Note that the recommended port for MQTT over TLS is 8883, +# but this must be set manually. +# +# See also the mosquitto-tls man page and the "Pre-shared-key based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# Both of certfile and keyfile must be defined to enable certificate based +# TLS encryption. + +# Path to the PEM encoded server certificate. +#certfile + +# Path to the PEM encoded keyfile. +#keyfile + +# If you wish to control which encryption ciphers are used, use the ciphers +# option. The list of available ciphers can be optained using the "openssl +# ciphers" command and should be provided in the same format as the output of +# that command. This applies to TLS 1.2 and earlier versions only. Use +# ciphers_tls1.3 for TLS v1.3. +#ciphers + +# Choose which TLS v1.3 ciphersuites are used for this listener. +# Defaults to "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256" +#ciphers_tls1.3 + +# If you have require_certificate set to true, you can create a certificate +# revocation list file to revoke access to particular client certificates. If +# you have done this, use crlfile to point to the PEM encoded revocation file. +#crlfile + +# To allow the use of ephemeral DH key exchange, which provides forward +# security, the listener must load DH parameters. This can be specified with +# the dhparamfile option. The dhparamfile can be generated with the command +# e.g. "openssl dhparam -out dhparam.pem 2048" +#dhparamfile + +# By default an TLS enabled listener will operate in a similar fashion to a +# https enabled web server, in that the server has a certificate signed by a CA +# and the client will verify that it is a trusted certificate. The overall aim +# is encryption of the network traffic. By setting require_certificate to true, +# the client must provide a valid certificate in order for the network +# connection to proceed. This allows access to the broker to be controlled +# outside of the mechanisms provided by MQTT. +#require_certificate false + +# cafile and capath define methods of accessing the PEM encoded +# Certificate Authority certificates that will be considered trusted when +# checking incoming client certificates. +# cafile defines the path to a file containing the CA certificates. +# capath defines a directory that will be searched for files +# containing the CA certificates. For capath to work correctly, the +# certificate files must have ".crt" as the file ending and you must run +# "openssl rehash " each time you add/remove a certificate. +#cafile +#capath + + +# If require_certificate is true, you may set use_identity_as_username to true +# to use the CN value from the client certificate as a username. If this is +# true, the password_file option will not be used for this listener. +#use_identity_as_username false + +# ----------------------------------------------------------------- +# Pre-shared-key based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable PSK based SSL/TLS support for +# this listener. Note that the recommended port for MQTT over TLS is 8883, but +# this must be set manually. +# +# See also the mosquitto-tls man page and the "Certificate based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# The psk_hint option enables pre-shared-key support for this listener and also +# acts as an identifier for this listener. The hint is sent to clients and may +# be used locally to aid authentication. The hint is a free form string that +# doesn't have much meaning in itself, so feel free to be creative. +# If this option is provided, see psk_file to define the pre-shared keys to be +# used or create a security plugin to handle them. +#psk_hint + +# When using PSK, the encryption ciphers used will be chosen from the list of +# available PSK ciphers. If you want to control which ciphers are available, +# use the "ciphers" option. The list of available ciphers can be optained +# using the "openssl ciphers" command and should be provided in the same format +# as the output of that command. +#ciphers + +# Set use_identity_as_username to have the psk identity sent by the client used +# as its username. Authentication will be carried out using the PSK rather than +# the MQTT username/password and so password_file will not be used for this +# listener. +#use_identity_as_username false + + +# ================================================================= +# Persistence +# ================================================================= + +# If persistence is enabled, save the in-memory database to disk +# every autosave_interval seconds. If set to 0, the persistence +# database will only be written when mosquitto exits. See also +# autosave_on_changes. +# Note that writing of the persistence database can be forced by +# sending mosquitto a SIGUSR1 signal. +#autosave_interval 1800 + +# If true, mosquitto will count the number of subscription changes, retained +# messages received and queued messages and if the total exceeds +# autosave_interval then the in-memory database will be saved to disk. +# If false, mosquitto will save the in-memory database to disk by treating +# autosave_interval as a time in seconds. +#autosave_on_changes false + +# Save persistent message data to disk (true/false). +# This saves information about all messages, including +# subscriptions, currently in-flight messages and retained +# messages. +# retained_persistence is a synonym for this option. +#persistence false + +# The filename to use for the persistent database, not including +# the path. +#persistence_file mosquitto.db + +# Location for persistent database. +# Default is an empty string (current directory). +# Set to e.g. /var/lib/mosquitto if running as a proper service on Linux or +# similar. +#persistence_location + + +# ================================================================= +# Logging +# ================================================================= + +# Places to log to. Use multiple log_dest lines for multiple +# logging destinations. +# Possible destinations are: stdout stderr syslog topic file dlt +# +# stdout and stderr log to the console on the named output. +# +# syslog uses the userspace syslog facility which usually ends up +# in /var/log/messages or similar. +# +# topic logs to the broker topic '$SYS/broker/log/', +# where severity is one of D, E, W, N, I, M which are debug, error, +# warning, notice, information and message. Message type severity is used by +# the subscribe/unsubscribe log_types and publishes log messages to +# $SYS/broker/log/M/susbcribe or $SYS/broker/log/M/unsubscribe. +# +# The file destination requires an additional parameter which is the file to be +# logged to, e.g. "log_dest file /var/log/mosquitto.log". The file will be +# closed and reopened when the broker receives a HUP signal. Only a single file +# destination may be configured. +# +# The dlt destination is for the automotive `Diagnostic Log and Trace` tool. +# This requires that Mosquitto has been compiled with DLT support. +# +# Note that if the broker is running as a Windows service it will default to +# "log_dest none" and neither stdout nor stderr logging is available. +# Use "log_dest none" if you wish to disable logging. +#log_dest stderr + +# Types of messages to log. Use multiple log_type lines for logging +# multiple types of messages. +# Possible types are: debug, error, warning, notice, information, +# none, subscribe, unsubscribe, websockets, all. +# Note that debug type messages are for decoding the incoming/outgoing +# network packets. They are not logged in "topics". +#log_type error +#log_type warning +#log_type notice +#log_type information + + +# If set to true, client connection and disconnection messages will be included +# in the log. +#connection_messages true + +# If using syslog logging (not on Windows), messages will be logged to the +# "daemon" facility by default. Use the log_facility option to choose which of +# local0 to local7 to log to instead. The option value should be an integer +# value, e.g. "log_facility 5" to use local5. +#log_facility + +# If set to true, add a timestamp value to each log message. +#log_timestamp true + +# Set the format of the log timestamp. If left unset, this is the number of +# seconds since the Unix epoch. +# This is a free text string which will be passed to the strftime function. To +# get an ISO 8601 datetime, for example: +# log_timestamp_format %Y-%m-%dT%H:%M:%S +#log_timestamp_format + +# Change the websockets logging level. This is a global option, it is not +# possible to set per listener. This is an integer that is interpreted by +# libwebsockets as a bit mask for its lws_log_levels enum. See the +# libwebsockets documentation for more details. "log_type websockets" must also +# be enabled. +#websockets_log_level 0 + + +# ================================================================= +# Security +# ================================================================= + +# If set, only clients that have a matching prefix on their +# clientid will be allowed to connect to the broker. By default, +# all clients may connect. +# For example, setting "secure-" here would mean a client "secure- +# client" could connect but another with clientid "mqtt" couldn't. +#clientid_prefixes + +# Boolean value that determines whether clients that connect +# without providing a username are allowed to connect. If set to +# false then a password file should be created (see the +# password_file option) to control authenticated client access. +# +# Defaults to false, unless there are no listeners defined in the configuration +# file, in which case it is set to true, but connections are only allowed from +# the local machine. +#allow_anonymous false + +# ----------------------------------------------------------------- +# Default authentication and topic access control +# ----------------------------------------------------------------- + +# Control access to the broker using a password file. This file can be +# generated using the mosquitto_passwd utility. If TLS support is not compiled +# into mosquitto (it is recommended that TLS support should be included) then +# plain text passwords are used, in which case the file should be a text file +# with lines in the format: +# username:password +# The password (and colon) may be omitted if desired, although this +# offers very little in the way of security. +# +# See the TLS client require_certificate and use_identity_as_username options +# for alternative authentication options. If a plugin is used as well as +# password_file, the plugin check will be made first. +#password_file + +# Access may also be controlled using a pre-shared-key file. This requires +# TLS-PSK support and a listener configured to use it. The file should be text +# lines in the format: +# identity:key +# The key should be in hexadecimal format without a leading "0x". +# If an plugin is used as well, the plugin check will be made first. +#psk_file + +# Control access to topics on the broker using an access control list +# file. If this parameter is defined then only the topics listed will +# have access. +# If the first character of a line of the ACL file is a # it is treated as a +# comment. +# Topic access is added with lines of the format: +# +# topic [read|write|readwrite|deny] +# +# The access type is controlled using "read", "write", "readwrite" or "deny". +# This parameter is optional (unless contains a space character) - if +# not given then the access is read/write. can contain the + or # +# wildcards as in subscriptions. +# +# The "deny" option can used to explicity deny access to a topic that would +# otherwise be granted by a broader read/write/readwrite statement. Any "deny" +# topics are handled before topics that grant read/write access. +# +# The first set of topics are applied to anonymous clients, assuming +# allow_anonymous is true. User specific topic ACLs are added after a +# user line as follows: +# +# user +# +# The username referred to here is the same as in password_file. It is +# not the clientid. +# +# +# If is also possible to define ACLs based on pattern substitution within the +# topic. The patterns available for substition are: +# +# %c to match the client id of the client +# %u to match the username of the client +# +# The substitution pattern must be the only text for that level of hierarchy. +# +# The form is the same as for the topic keyword, but using pattern as the +# keyword. +# Pattern ACLs apply to all users even if the "user" keyword has previously +# been given. +# +# If using bridges with usernames and ACLs, connection messages can be allowed +# with the following pattern: +# pattern write $SYS/broker/connection/%c/state +# +# pattern [read|write|readwrite] +# +# Example: +# +# pattern write sensor/%u/data +# +# If an plugin is used as well as acl_file, the plugin check will be +# made first. +#acl_file + +# ----------------------------------------------------------------- +# External authentication and topic access plugin options +# ----------------------------------------------------------------- + +# External authentication and access control can be supported with the +# plugin option. This is a path to a loadable plugin. See also the +# plugin_opt_* options described below. +# +# The plugin option can be specified multiple times to load multiple +# plugins. The plugins will be processed in the order that they are specified +# here. If the plugin option is specified alongside either of +# password_file or acl_file then the plugin checks will be made first. +# +# If the per_listener_settings option is false, the plugin will be apply to all +# listeners. If per_listener_settings is true, then the plugin will apply to +# the current listener being defined only. +# +# This option is also available as `auth_plugin`, but this use is deprecated +# and will be removed in the future. +# +#plugin + +# If the plugin option above is used, define options to pass to the +# plugin here as described by the plugin instructions. All options named +# using the format plugin_opt_* will be passed to the plugin, for example: +# +# This option is also available as `auth_opt_*`, but this use is deprecated +# and will be removed in the future. +# +# plugin_opt_db_host +# plugin_opt_db_port +# plugin_opt_db_username +# plugin_opt_db_password + + +# ================================================================= +# Bridges +# ================================================================= + +# A bridge is a way of connecting multiple MQTT brokers together. +# Create a new bridge using the "connection" option as described below. Set +# options for the bridges using the remaining parameters. You must specify the +# address and at least one topic to subscribe to. +# +# Each connection must have a unique name. +# +# The address line may have multiple host address and ports specified. See +# below in the round_robin description for more details on bridge behaviour if +# multiple addresses are used. Note that if you use an IPv6 address, then you +# are required to specify a port. +# +# The direction that the topic will be shared can be chosen by +# specifying out, in or both, where the default value is out. +# The QoS level of the bridged communication can be specified with the next +# topic option. The default QoS level is 0, to change the QoS the topic +# direction must also be given. +# +# The local and remote prefix options allow a topic to be remapped when it is +# bridged to/from the remote broker. This provides the ability to place a topic +# tree in an appropriate location. +# +# For more details see the mosquitto.conf man page. +# +# Multiple topics can be specified per connection, but be careful +# not to create any loops. +# +# If you are using bridges with cleansession set to false (the default), then +# you may get unexpected behaviour from incoming topics if you change what +# topics you are subscribing to. This is because the remote broker keeps the +# subscription for the old topic. If you have this problem, connect your bridge +# with cleansession set to true, then reconnect with cleansession set to false +# as normal. +#connection +#address [:] [[:]] +#topic [[[out | in | both] qos-level] local-prefix remote-prefix] + +# If you need to have the bridge connect over a particular network interface, +# use bridge_bind_address to tell the bridge which local IP address the socket +# should bind to, e.g. `bridge_bind_address 192.168.1.10` +#bridge_bind_address + +# If a bridge has topics that have "out" direction, the default behaviour is to +# send an unsubscribe request to the remote broker on that topic. This means +# that changing a topic direction from "in" to "out" will not keep receiving +# incoming messages. Sending these unsubscribe requests is not always +# desirable, setting bridge_attempt_unsubscribe to false will disable sending +# the unsubscribe request. +#bridge_attempt_unsubscribe true + +# Set the version of the MQTT protocol to use with for this bridge. Can be one +# of mqttv50, mqttv311 or mqttv31. Defaults to mqttv311. +#bridge_protocol_version mqttv311 + +# Set the clean session variable for this bridge. +# When set to true, when the bridge disconnects for any reason, all +# messages and subscriptions will be cleaned up on the remote +# broker. Note that with cleansession set to true, there may be a +# significant amount of retained messages sent when the bridge +# reconnects after losing its connection. +# When set to false, the subscriptions and messages are kept on the +# remote broker, and delivered when the bridge reconnects. +#cleansession false + +# Set the amount of time a bridge using the lazy start type must be idle before +# it will be stopped. Defaults to 60 seconds. +#idle_timeout 60 + +# Set the keepalive interval for this bridge connection, in +# seconds. +#keepalive_interval 60 + +# Set the clientid to use on the local broker. If not defined, this defaults to +# 'local.'. If you are bridging a broker to itself, it is important +# that local_clientid and clientid do not match. +#local_clientid + +# If set to true, publish notification messages to the local and remote brokers +# giving information about the state of the bridge connection. Retained +# messages are published to the topic $SYS/broker/connection//state +# unless the notification_topic option is used. +# If the message is 1 then the connection is active, or 0 if the connection has +# failed. +# This uses the last will and testament feature. +#notifications true + +# Choose the topic on which notification messages for this bridge are +# published. If not set, messages are published on the topic +# $SYS/broker/connection//state +#notification_topic + +# Set the client id to use on the remote end of this bridge connection. If not +# defined, this defaults to 'name.hostname' where name is the connection name +# and hostname is the hostname of this computer. +# This replaces the old "clientid" option to avoid confusion. "clientid" +# remains valid for the time being. +#remote_clientid + +# Set the password to use when connecting to a broker that requires +# authentication. This option is only used if remote_username is also set. +# This replaces the old "password" option to avoid confusion. "password" +# remains valid for the time being. +#remote_password + +# Set the username to use when connecting to a broker that requires +# authentication. +# This replaces the old "username" option to avoid confusion. "username" +# remains valid for the time being. +#remote_username + +# Set the amount of time a bridge using the automatic start type will wait +# until attempting to reconnect. +# This option can be configured to use a constant delay time in seconds, or to +# use a backoff mechanism based on "Decorrelated Jitter", which adds a degree +# of randomness to when the restart occurs. +# +# Set a constant timeout of 20 seconds: +# restart_timeout 20 +# +# Set backoff with a base (start value) of 10 seconds and a cap (upper limit) of +# 60 seconds: +# restart_timeout 10 30 +# +# Defaults to jitter with a base of 5 and cap of 30 +#restart_timeout 5 30 + +# If the bridge has more than one address given in the address/addresses +# configuration, the round_robin option defines the behaviour of the bridge on +# a failure of the bridge connection. If round_robin is false, the default +# value, then the first address is treated as the main bridge connection. If +# the connection fails, the other secondary addresses will be attempted in +# turn. Whilst connected to a secondary bridge, the bridge will periodically +# attempt to reconnect to the main bridge until successful. +# If round_robin is true, then all addresses are treated as equals. If a +# connection fails, the next address will be tried and if successful will +# remain connected until it fails +#round_robin false + +# Set the start type of the bridge. This controls how the bridge starts and +# can be one of three types: automatic, lazy and once. Note that RSMB provides +# a fourth start type "manual" which isn't currently supported by mosquitto. +# +# "automatic" is the default start type and means that the bridge connection +# will be started automatically when the broker starts and also restarted +# after a short delay (30 seconds) if the connection fails. +# +# Bridges using the "lazy" start type will be started automatically when the +# number of queued messages exceeds the number set with the "threshold" +# parameter. It will be stopped automatically after the time set by the +# "idle_timeout" parameter. Use this start type if you wish the connection to +# only be active when it is needed. +# +# A bridge using the "once" start type will be started automatically when the +# broker starts but will not be restarted if the connection fails. +#start_type automatic + +# Set the number of messages that need to be queued for a bridge with lazy +# start type to be restarted. Defaults to 10 messages. +# Must be less than max_queued_messages. +#threshold 10 + +# If try_private is set to true, the bridge will attempt to indicate to the +# remote broker that it is a bridge not an ordinary client. If successful, this +# means that loop detection will be more effective and that retained messages +# will be propagated correctly. Not all brokers support this feature so it may +# be necessary to set try_private to false if your bridge does not connect +# properly. +#try_private true + +# Some MQTT brokers do not allow retained messages. MQTT v5 gives a mechanism +# for brokers to tell clients that they do not support retained messages, but +# this is not possible for MQTT v3.1.1 or v3.1. If you need to bridge to a +# v3.1.1 or v3.1 broker that does not support retained messages, set the +# bridge_outgoing_retain option to false. This will remove the retain bit on +# all outgoing messages to that bridge, regardless of any other setting. +#bridge_outgoing_retain true + +# If you wish to restrict the size of messages sent to a remote bridge, use the +# bridge_max_packet_size option. This sets the maximum number of bytes for +# the total message, including headers and payload. +# Note that MQTT v5 brokers may provide their own maximum-packet-size property. +# In this case, the smaller of the two limits will be used. +# Set to 0 for "unlimited". +#bridge_max_packet_size 0 + + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# Either bridge_cafile or bridge_capath must be defined to enable TLS support +# for this bridge. +# bridge_cafile defines the path to a file containing the +# Certificate Authority certificates that have signed the remote broker +# certificate. +# bridge_capath defines a directory that will be searched for files containing +# the CA certificates. For bridge_capath to work correctly, the certificate +# files must have ".crt" as the file ending and you must run "openssl rehash +# " each time you add/remove a certificate. +#bridge_cafile +#bridge_capath + + +# If the remote broker has more than one protocol available on its port, e.g. +# MQTT and WebSockets, then use bridge_alpn to configure which protocol is +# requested. Note that WebSockets support for bridges is not yet available. +#bridge_alpn + +# When using certificate based encryption, bridge_insecure disables +# verification of the server hostname in the server certificate. This can be +# useful when testing initial server configurations, but makes it possible for +# a malicious third party to impersonate your server through DNS spoofing, for +# example. Use this option in testing only. If you need to resort to using this +# option in a production environment, your setup is at fault and there is no +# point using encryption. +#bridge_insecure false + +# Path to the PEM encoded client certificate, if required by the remote broker. +#bridge_certfile + +# Path to the PEM encoded client private key, if required by the remote broker. +#bridge_keyfile + +# ----------------------------------------------------------------- +# PSK based SSL/TLS support +# ----------------------------------------------------------------- +# Pre-shared-key encryption provides an alternative to certificate based +# encryption. A bridge can be configured to use PSK with the bridge_identity +# and bridge_psk options. These are the client PSK identity, and pre-shared-key +# in hexadecimal format with no "0x". Only one of certificate and PSK based +# encryption can be used on one +# bridge at once. +#bridge_identity +#bridge_psk + + +# ================================================================= +# External config files +# ================================================================= + +# External configuration files may be included by using the +# include_dir option. This defines a directory that will be searched +# for config files. All files that end in '.conf' will be loaded as +# a configuration file. It is best to have this as the last option +# in the main file. This option will only be processed from the main +# configuration file. The directory specified must not contain the +# main configuration file. +# Files within include_dir will be loaded sorted in case-sensitive +# alphabetical order, with capital letters ordered first. If this option is +# given multiple times, all of the files from the first instance will be +# processed before the next instance. See the man page for examples. +#include_dir 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..6aa815dc8 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..c058e3e30 --- /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 + +@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..e8ec4abd9 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto.conf @@ -0,0 +1,904 @@ +# Config file for mosquitto +# +# See mosquitto.conf(5) for more information. +# +# Default values are shown, uncomment to change. +# +# Use the # character to indicate a comment, but only if it is the +# very first character on the line. + +# ================================================================= +# General configuration +# ================================================================= + +# Use per listener security settings. +# +# It is recommended this option be set before any other options. +# +# If this option is set to true, then all authentication and access control +# options are controlled on a per listener basis. The following options are +# affected: +# +# acl_file +allow_anonymous true +# allow_zero_length_clientid +# auto_id_prefix +# password_file +# plugin +# plugin_opt_* +# psk_file +# +# Note that if set to true, then a durable client (i.e. with clean session set +# to false) that has disconnected will use the ACL settings defined for the +# listener that it was most recently connected to. +# +# The default behaviour is for this to be set to false, which maintains the +# setting behaviour from previous versions of mosquitto. +#per_listener_settings false + + +# This option controls whether a client is allowed to connect with a zero +# length client id or not. This option only affects clients using MQTT v3.1.1 +# and later. If set to false, clients connecting with a zero length client id +# are disconnected. If set to true, clients will be allocated a client id by +# the broker. This means it is only useful for clients with clean session set +# to true. +#allow_zero_length_clientid true + +# If allow_zero_length_clientid is true, this option allows you to set a prefix +# to automatically generated client ids to aid visibility in logs. +# Defaults to 'auto-' +#auto_id_prefix auto- + +# This option affects the scenario when a client subscribes to a topic that has +# retained messages. It is possible that the client that published the retained +# message to the topic had access at the time they published, but that access +# has been subsequently removed. If check_retain_source is set to true, the +# default, the source of a retained message will be checked for access rights +# before it is republished. When set to false, no check will be made and the +# retained message will always be published. This affects all listeners. +#check_retain_source true + +# QoS 1 and 2 messages will be allowed inflight per client until this limit +# is exceeded. Defaults to 0. (No maximum) +# See also max_inflight_messages +#max_inflight_bytes 0 + +# The maximum number of QoS 1 and 2 messages currently inflight per +# client. +# This includes messages that are partway through handshakes and +# those that are being retried. Defaults to 20. Set to 0 for no +# maximum. Setting to 1 will guarantee in-order delivery of QoS 1 +# and 2 messages. +#max_inflight_messages 20 + +# For MQTT v5 clients, it is possible to have the server send a "server +# keepalive" value that will override the keepalive value set by the client. +# This is intended to be used as a mechanism to say that the server will +# disconnect the client earlier than it anticipated, and that the client should +# use the new keepalive value. The max_keepalive option allows you to specify +# that clients may only connect with keepalive less than or equal to this +# value, otherwise they will be sent a server keepalive telling them to use +# max_keepalive. This only applies to MQTT v5 clients. The default, and maximum +# value allowable, is 65535. +# +# Set to 0 to allow clients to set keepalive = 0, which means no keepalive +# checks are made and the client will never be disconnected by the broker if no +# messages are received. You should be very sure this is the behaviour that you +# want. +# +# For MQTT v3.1.1 and v3.1 clients, there is no mechanism to tell the client +# what keepalive value they should use. If an MQTT v3.1.1 or v3.1 client +# specifies a keepalive time greater than max_keepalive they will be sent a +# CONNACK message with the "identifier rejected" reason code, and disconnected. +# +#max_keepalive 65535 + +# For MQTT v5 clients, it is possible to have the server send a "maximum packet +# size" value that will instruct the client it will not accept MQTT packets +# with size greater than max_packet_size bytes. This applies to the full MQTT +# packet, not just the payload. Setting this option to a positive value will +# set the maximum packet size to that number of bytes. If a client sends a +# packet which is larger than this value, it will be disconnected. This applies +# to all clients regardless of the protocol version they are using, but v3.1.1 +# and earlier clients will of course not have received the maximum packet size +# information. Defaults to no limit. Setting below 20 bytes is forbidden +# because it is likely to interfere with ordinary client operation, even with +# very small payloads. +#max_packet_size 0 + +# QoS 1 and 2 messages above those currently in-flight will be queued per +# client until this limit is exceeded. Defaults to 0. (No maximum) +# See also max_queued_messages. +# If both max_queued_messages and max_queued_bytes are specified, packets will +# be queued until the first limit is reached. +#max_queued_bytes 0 + +# Set the maximum QoS supported. Clients publishing at a QoS higher than +# specified here will be disconnected. +#max_qos 2 + +# The maximum number of QoS 1 and 2 messages to hold in a queue per client +# above those that are currently in-flight. Defaults to 1000. Set +# to 0 for no maximum (not recommended). +# See also queue_qos0_messages. +# See also max_queued_bytes. +#max_queued_messages 1000 +# +# This option sets the maximum number of heap memory bytes that the broker will +# allocate, and hence sets a hard limit on memory use by the broker. Memory +# requests that exceed this value will be denied. The effect will vary +# depending on what has been denied. If an incoming message is being processed, +# then the message will be dropped and the publishing client will be +# disconnected. If an outgoing message is being sent, then the individual +# message will be dropped and the receiving client will be disconnected. +# Defaults to no limit. +#memory_limit 0 + +# This option sets the maximum publish payload size that the broker will allow. +# Received messages that exceed this size will not be accepted by the broker. +# The default value is 0, which means that all valid MQTT messages are +# accepted. MQTT imposes a maximum payload size of 268435455 bytes. +#message_size_limit 0 + +# This option allows the session of persistent clients (those with clean +# session set to false) that are not currently connected to be removed if they +# do not reconnect within a certain time frame. This is a non-standard option +# in MQTT v3.1. MQTT v3.1.1 and v5.0 allow brokers to remove client sessions. +# +# Badly designed clients may set clean session to false whilst using a randomly +# generated client id. This leads to persistent clients that connect once and +# never reconnect. This option allows these clients to be removed. This option +# allows persistent clients (those with clean session set to false) to be +# removed if they do not reconnect within a certain time frame. +# +# The expiration period should be an integer followed by one of h d w m y for +# hour, day, week, month and year respectively. For example +# +# persistent_client_expiration 2m +# persistent_client_expiration 14d +# persistent_client_expiration 1y +# +# The default if not set is to never expire persistent clients. +#persistent_client_expiration + +# Write process id to a file. Default is a blank string which means +# a pid file shouldn't be written. +# This should be set to /var/run/mosquitto/mosquitto.pid if mosquitto is +# being run automatically on boot with an init script and +# start-stop-daemon or similar. +#pid_file + +# Set to true to queue messages with QoS 0 when a persistent client is +# disconnected. These messages are included in the limit imposed by +# max_queued_messages and max_queued_bytes +# Defaults to false. +# This is a non-standard option for the MQTT v3.1 spec but is allowed in +# v3.1.1. +#queue_qos0_messages false + +# Set to false to disable retained message support. If a client publishes a +# message with the retain bit set, it will be disconnected if this is set to +# false. +#retain_available true + +# Disable Nagle's algorithm on client sockets. This has the effect of reducing +# latency of individual messages at the potential cost of increasing the number +# of packets being sent. +#set_tcp_nodelay false + +# Time in seconds between updates of the $SYS tree. +# Set to 0 to disable the publishing of the $SYS tree. +#sys_interval 10 + +# The MQTT specification requires that the QoS of a message delivered to a +# subscriber is never upgraded to match the QoS of the subscription. Enabling +# this option changes this behaviour. If upgrade_outgoing_qos is set true, +# messages sent to a subscriber will always match the QoS of its subscription. +# This is a non-standard option explicitly disallowed by the spec. +#upgrade_outgoing_qos false + +# When run as root, drop privileges to this user and its primary +# group. +# Set to root to stay as root, but this is not recommended. +# If set to "mosquitto", or left unset, and the "mosquitto" user does not exist +# then it will drop privileges to the "nobody" user instead. +# If run as a non-root user, this setting has no effect. +# Note that on Windows this has no effect and so mosquitto should be started by +# the user you wish it to run as. +#user mosquitto + +# ================================================================= +# relatedListeners +# ================================================================= + +# Listen on a port/ip address combination. By using this variable +# multiple times, mosquitto can listen on more than one port. If +# this variable is used and neither bind_address nor port given, +# then the default listener will not be started. +# The port number to listen on must be given. Optionally, an ip +# address or host name may be supplied as a second argument. In +# this case, mosquitto will attempt to bind the listener to that +# address and so restrict access to the associated network and +# interface. By default, mosquitto will listen on all interfaces. +# Note that for a websockets listener it is not possible to bind to a host +# name. +# +# On systems that support Unix Domain Sockets, it is also possible +# to create a # Unix socket rather than opening a TCP socket. In +# this case, the port number should be set to 0 and a unix socket +# path must be provided, e.g. +# listener 0 /tmp/mosquitto.sock +# +# listener port-number [ip address/host name/unix socket path] +listener 1883 + +# By default, a listener will attempt to listen on all supported IP protocol +# versions. If you do not have an IPv4 or IPv6 interface you may wish to +# disable support for either of those protocol versions. In particular, note +# that due to the limitations of the websockets library, it will only ever +# attempt to open IPv6 sockets if IPv6 support is compiled in, and so will fail +# if IPv6 is not available. +# +# Set to `ipv4` to force the listener to only use IPv4, or set to `ipv6` to +# force the listener to only use IPv6. If you want support for both IPv4 and +# IPv6, then do not use the socket_domain option. +# +#socket_domain + +# Bind the listener to a specific interface. This is similar to +# the [ip address/host name] part of the listener definition, but is useful +# when an interface has multiple addresses or the address may change. If used +# with the [ip address/host name] part of the listener definition, then the +# bind_interface option will take priority. +# Not available on Windows. +# +# Example: bind_interface eth0 +#bind_interface + +# When a listener is using the websockets protocol, it is possible to serve +# http data as well. Set http_dir to a directory which contains the files you +# wish to serve. If this option is not specified, then no normal http +# connections will be possible. +#http_dir + +# The maximum number of client connections to allow. This is +# a per listener setting. +# Default is -1, which means unlimited connections. +# Note that other process limits mean that unlimited connections +# are not really possible. Typically the default maximum number of +# connections possible is around 1024. +#max_connections -1 + +# The listener can be restricted to operating within a topic hierarchy using +# the mount_point option. This is achieved be prefixing the mount_point string +# to all topics for any clients connected to this listener. This prefixing only +# happens internally to the broker; the client will not see the prefix. +#mount_point + +# Choose the protocol to use when listening. +# This can be either mqtt or websockets. +# Certificate based TLS may be used with websockets, except that only the +# cafile, certfile, keyfile, ciphers, and ciphers_tls13 options are supported. +#protocol mqtt + +# Set use_username_as_clientid to true to replace the clientid that a client +# connected with with its username. This allows authentication to be tied to +# the clientid, which means that it is possible to prevent one client +# disconnecting another by using the same clientid. +# If a client connects with no username it will be disconnected as not +# authorised when this option is set to true. +# Do not use in conjunction with clientid_prefixes. +# See also use_identity_as_username. +# This does not apply globally, but on a per-listener basis. +#use_username_as_clientid + +# Change the websockets headers size. This is a global option, it is not +# possible to set per listener. This option sets the size of the buffer used in +# the libwebsockets library when reading HTTP headers. If you are passing large +# header data such as cookies then you may need to increase this value. If left +# unset, or set to 0, then the default of 1024 bytes will be used. +#websockets_headers_size + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable certificate based SSL/TLS support +# for this listener. Note that the recommended port for MQTT over TLS is 8883, +# but this must be set manually. +# +# See also the mosquitto-tls man page and the "Pre-shared-key based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# Both of certfile and keyfile must be defined to enable certificate based +# TLS encryption. + +# Path to the PEM encoded server certificate. +#certfile + +# Path to the PEM encoded keyfile. +#keyfile + +# If you wish to control which encryption ciphers are used, use the ciphers +# option. The list of available ciphers can be optained using the "openssl +# ciphers" command and should be provided in the same format as the output of +# that command. This applies to TLS 1.2 and earlier versions only. Use +# ciphers_tls1.3 for TLS v1.3. +#ciphers + +# Choose which TLS v1.3 ciphersuites are used for this listener. +# Defaults to "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256" +#ciphers_tls1.3 + +# If you have require_certificate set to true, you can create a certificate +# revocation list file to revoke access to particular client certificates. If +# you have done this, use crlfile to point to the PEM encoded revocation file. +#crlfile + +# To allow the use of ephemeral DH key exchange, which provides forward +# security, the listener must load DH parameters. This can be specified with +# the dhparamfile option. The dhparamfile can be generated with the command +# e.g. "openssl dhparam -out dhparam.pem 2048" +#dhparamfile + +# By default an TLS enabled listener will operate in a similar fashion to a +# https enabled web server, in that the server has a certificate signed by a CA +# and the client will verify that it is a trusted certificate. The overall aim +# is encryption of the network traffic. By setting require_certificate to true, +# the client must provide a valid certificate in order for the network +# connection to proceed. This allows access to the broker to be controlled +# outside of the mechanisms provided by MQTT. +#require_certificate false + +# cafile and capath define methods of accessing the PEM encoded +# Certificate Authority certificates that will be considered trusted when +# checking incoming client certificates. +# cafile defines the path to a file containing the CA certificates. +# capath defines a directory that will be searched for files +# containing the CA certificates. For capath to work correctly, the +# certificate files must have ".crt" as the file ending and you must run +# "openssl rehash " each time you add/remove a certificate. +#cafile +#capath + + +# If require_certificate is true, you may set use_identity_as_username to true +# to use the CN value from the client certificate as a username. If this is +# true, the password_file option will not be used for this listener. +#use_identity_as_username false + +# ----------------------------------------------------------------- +# Pre-shared-key based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable PSK based SSL/TLS support for +# this listener. Note that the recommended port for MQTT over TLS is 8883, but +# this must be set manually. +# +# See also the mosquitto-tls man page and the "Certificate based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# The psk_hint option enables pre-shared-key support for this listener and also +# acts as an identifier for this listener. The hint is sent to clients and may +# be used locally to aid authentication. The hint is a free form string that +# doesn't have much meaning in itself, so feel free to be creative. +# If this option is provided, see psk_file to define the pre-shared keys to be +# used or create a security plugin to handle them. +#psk_hint + +# When using PSK, the encryption ciphers used will be chosen from the list of +# available PSK ciphers. If you want to control which ciphers are available, +# use the "ciphers" option. The list of available ciphers can be optained +# using the "openssl ciphers" command and should be provided in the same format +# as the output of that command. +#ciphers + +# Set use_identity_as_username to have the psk identity sent by the client used +# as its username. Authentication will be carried out using the PSK rather than +# the MQTT username/password and so password_file will not be used for this +# listener. +#use_identity_as_username false + + +# ================================================================= +# Persistence +# ================================================================= + +# If persistence is enabled, save the in-memory database to disk +# every autosave_interval seconds. If set to 0, the persistence +# database will only be written when mosquitto exits. See also +# autosave_on_changes. +# Note that writing of the persistence database can be forced by +# sending mosquitto a SIGUSR1 signal. +#autosave_interval 1800 + +# If true, mosquitto will count the number of subscription changes, retained +# messages received and queued messages and if the total exceeds +# autosave_interval then the in-memory database will be saved to disk. +# If false, mosquitto will save the in-memory database to disk by treating +# autosave_interval as a time in seconds. +#autosave_on_changes false + +# Save persistent message data to disk (true/false). +# This saves information about all messages, including +# subscriptions, currently in-flight messages and retained +# messages. +# retained_persistence is a synonym for this option. +#persistence false + +# The filename to use for the persistent database, not including +# the path. +#persistence_file mosquitto.db + +# Location for persistent database. +# Default is an empty string (current directory). +# Set to e.g. /var/lib/mosquitto if running as a proper service on Linux or +# similar. +#persistence_location + + +# ================================================================= +# Logging +# ================================================================= + +# Places to log to. Use multiple log_dest lines for multiple +# logging destinations. +# Possible destinations are: stdout stderr syslog topic file dlt +# +# stdout and stderr log to the console on the named output. +# +# syslog uses the userspace syslog facility which usually ends up +# in /var/log/messages or similar. +# +# topic logs to the broker topic '$SYS/broker/log/', +# where severity is one of D, E, W, N, I, M which are debug, error, +# warning, notice, information and message. Message type severity is used by +# the subscribe/unsubscribe log_types and publishes log messages to +# $SYS/broker/log/M/susbcribe or $SYS/broker/log/M/unsubscribe. +# +# The file destination requires an additional parameter which is the file to be +# logged to, e.g. "log_dest file /var/log/mosquitto.log". The file will be +# closed and reopened when the broker receives a HUP signal. Only a single file +# destination may be configured. +# +# The dlt destination is for the automotive `Diagnostic Log and Trace` tool. +# This requires that Mosquitto has been compiled with DLT support. +# +# Note that if the broker is running as a Windows service it will default to +# "log_dest none" and neither stdout nor stderr logging is available. +# Use "log_dest none" if you wish to disable logging. +#log_dest stderr + +# Types of messages to log. Use multiple log_type lines for logging +# multiple types of messages. +# Possible types are: debug, error, warning, notice, information, +# none, subscribe, unsubscribe, websockets, all. +# Note that debug type messages are for decoding the incoming/outgoing +# network packets. They are not logged in "topics". +#log_type error +#log_type warning +#log_type notice +#log_type information + + +# If set to true, client connection and disconnection messages will be included +# in the log. +#connection_messages true + +# If using syslog logging (not on Windows), messages will be logged to the +# "daemon" facility by default. Use the log_facility option to choose which of +# local0 to local7 to log to instead. The option value should be an integer +# value, e.g. "log_facility 5" to use local5. +#log_facility + +# If set to true, add a timestamp value to each log message. +#log_timestamp true + +# Set the format of the log timestamp. If left unset, this is the number of +# seconds since the Unix epoch. +# This is a free text string which will be passed to the strftime function. To +# get an ISO 8601 datetime, for example: +# log_timestamp_format %Y-%m-%dT%H:%M:%S +#log_timestamp_format + +# Change the websockets logging level. This is a global option, it is not +# possible to set per listener. This is an integer that is interpreted by +# libwebsockets as a bit mask for its lws_log_levels enum. See the +# libwebsockets documentation for more details. "log_type websockets" must also +# be enabled. +#websockets_log_level 0 + + +# ================================================================= +# Security +# ================================================================= + +# If set, only clients that have a matching prefix on their +# clientid will be allowed to connect to the broker. By default, +# all clients may connect. +# For example, setting "secure-" here would mean a client "secure- +# client" could connect but another with clientid "mqtt" couldn't. +#clientid_prefixes + +# Boolean value that determines whether clients that connect +# without providing a username are allowed to connect. If set to +# false then a password file should be created (see the +# password_file option) to control authenticated client access. +# +# Defaults to false, unless there are no listeners defined in the configuration +# file, in which case it is set to true, but connections are only allowed from +# the local machine. +#allow_anonymous false + +# ----------------------------------------------------------------- +# Default authentication and topic access control +# ----------------------------------------------------------------- + +# Control access to the broker using a password file. This file can be +# generated using the mosquitto_passwd utility. If TLS support is not compiled +# into mosquitto (it is recommended that TLS support should be included) then +# plain text passwords are used, in which case the file should be a text file +# with lines in the format: +# username:password +# The password (and colon) may be omitted if desired, although this +# offers very little in the way of security. +# +# See the TLS client require_certificate and use_identity_as_username options +# for alternative authentication options. If a plugin is used as well as +# password_file, the plugin check will be made first. +#password_file + +# Access may also be controlled using a pre-shared-key file. This requires +# TLS-PSK support and a listener configured to use it. The file should be text +# lines in the format: +# identity:key +# The key should be in hexadecimal format without a leading "0x". +# If an plugin is used as well, the plugin check will be made first. +#psk_file + +# Control access to topics on the broker using an access control list +# file. If this parameter is defined then only the topics listed will +# have access. +# If the first character of a line of the ACL file is a # it is treated as a +# comment. +# Topic access is added with lines of the format: +# +# topic [read|write|readwrite|deny] +# +# The access type is controlled using "read", "write", "readwrite" or "deny". +# This parameter is optional (unless contains a space character) - if +# not given then the access is read/write. can contain the + or # +# wildcards as in subscriptions. +# +# The "deny" option can used to explicity deny access to a topic that would +# otherwise be granted by a broader read/write/readwrite statement. Any "deny" +# topics are handled before topics that grant read/write access. +# +# The first set of topics are applied to anonymous clients, assuming +# allow_anonymous is true. User specific topic ACLs are added after a +# user line as follows: +# +# user +# +# The username referred to here is the same as in password_file. It is +# not the clientid. +# +# +# If is also possible to define ACLs based on pattern substitution within the +# topic. The patterns available for substition are: +# +# %c to match the client id of the client +# %u to match the username of the client +# +# The substitution pattern must be the only text for that level of hierarchy. +# +# The form is the same as for the topic keyword, but using pattern as the +# keyword. +# Pattern ACLs apply to all users even if the "user" keyword has previously +# been given. +# +# If using bridges with usernames and ACLs, connection messages can be allowed +# with the following pattern: +# pattern write $SYS/broker/connection/%c/state +# +# pattern [read|write|readwrite] +# +# Example: +# +# pattern write sensor/%u/data +# +# If an plugin is used as well as acl_file, the plugin check will be +# made first. +#acl_file + +# ----------------------------------------------------------------- +# External authentication and topic access plugin options +# ----------------------------------------------------------------- + +# External authentication and access control can be supported with the +# plugin option. This is a path to a loadable plugin. See also the +# plugin_opt_* options described below. +# +# The plugin option can be specified multiple times to load multiple +# plugins. The plugins will be processed in the order that they are specified +# here. If the plugin option is specified alongside either of +# password_file or acl_file then the plugin checks will be made first. +# +# If the per_listener_settings option is false, the plugin will be apply to all +# listeners. If per_listener_settings is true, then the plugin will apply to +# the current listener being defined only. +# +# This option is also available as `auth_plugin`, but this use is deprecated +# and will be removed in the future. +# +#plugin + +# If the plugin option above is used, define options to pass to the +# plugin here as described by the plugin instructions. All options named +# using the format plugin_opt_* will be passed to the plugin, for example: +# +# This option is also available as `auth_opt_*`, but this use is deprecated +# and will be removed in the future. +# +# plugin_opt_db_host +# plugin_opt_db_port +# plugin_opt_db_username +# plugin_opt_db_password + + +# ================================================================= +# Bridges +# ================================================================= + +# A bridge is a way of connecting multiple MQTT brokers together. +# Create a new bridge using the "connection" option as described below. Set +# options for the bridges using the remaining parameters. You must specify the +# address and at least one topic to subscribe to. +# +# Each connection must have a unique name. +# +# The address line may have multiple host address and ports specified. See +# below in the round_robin description for more details on bridge behaviour if +# multiple addresses are used. Note that if you use an IPv6 address, then you +# are required to specify a port. +# +# The direction that the topic will be shared can be chosen by +# specifying out, in or both, where the default value is out. +# The QoS level of the bridged communication can be specified with the next +# topic option. The default QoS level is 0, to change the QoS the topic +# direction must also be given. +# +# The local and remote prefix options allow a topic to be remapped when it is +# bridged to/from the remote broker. This provides the ability to place a topic +# tree in an appropriate location. +# +# For more details see the mosquitto.conf man page. +# +# Multiple topics can be specified per connection, but be careful +# not to create any loops. +# +# If you are using bridges with cleansession set to false (the default), then +# you may get unexpected behaviour from incoming topics if you change what +# topics you are subscribing to. This is because the remote broker keeps the +# subscription for the old topic. If you have this problem, connect your bridge +# with cleansession set to true, then reconnect with cleansession set to false +# as normal. +#connection +#address [:] [[:]] +#topic [[[out | in | both] qos-level] local-prefix remote-prefix] + +# If you need to have the bridge connect over a particular network interface, +# use bridge_bind_address to tell the bridge which local IP address the socket +# should bind to, e.g. `bridge_bind_address 192.168.1.10` +#bridge_bind_address + +# If a bridge has topics that have "out" direction, the default behaviour is to +# send an unsubscribe request to the remote broker on that topic. This means +# that changing a topic direction from "in" to "out" will not keep receiving +# incoming messages. Sending these unsubscribe requests is not always +# desirable, setting bridge_attempt_unsubscribe to false will disable sending +# the unsubscribe request. +#bridge_attempt_unsubscribe true + +# Set the version of the MQTT protocol to use with for this bridge. Can be one +# of mqttv50, mqttv311 or mqttv31. Defaults to mqttv311. +#bridge_protocol_version mqttv311 + +# Set the clean session variable for this bridge. +# When set to true, when the bridge disconnects for any reason, all +# messages and subscriptions will be cleaned up on the remote +# broker. Note that with cleansession set to true, there may be a +# significant amount of retained messages sent when the bridge +# reconnects after losing its connection. +# When set to false, the subscriptions and messages are kept on the +# remote broker, and delivered when the bridge reconnects. +#cleansession false + +# Set the amount of time a bridge using the lazy start type must be idle before +# it will be stopped. Defaults to 60 seconds. +#idle_timeout 60 + +# Set the keepalive interval for this bridge connection, in +# seconds. +#keepalive_interval 60 + +# Set the clientid to use on the local broker. If not defined, this defaults to +# 'local.'. If you are bridging a broker to itself, it is important +# that local_clientid and clientid do not match. +#local_clientid + +# If set to true, publish notification messages to the local and remote brokers +# giving information about the state of the bridge connection. Retained +# messages are published to the topic $SYS/broker/connection//state +# unless the notification_topic option is used. +# If the message is 1 then the connection is active, or 0 if the connection has +# failed. +# This uses the last will and testament feature. +#notifications true + +# Choose the topic on which notification messages for this bridge are +# published. If not set, messages are published on the topic +# $SYS/broker/connection//state +#notification_topic + +# Set the client id to use on the remote end of this bridge connection. If not +# defined, this defaults to 'name.hostname' where name is the connection name +# and hostname is the hostname of this computer. +# This replaces the old "clientid" option to avoid confusion. "clientid" +# remains valid for the time being. +#remote_clientid + +# Set the password to use when connecting to a broker that requires +# authentication. This option is only used if remote_username is also set. +# This replaces the old "password" option to avoid confusion. "password" +# remains valid for the time being. +#remote_password + +# Set the username to use when connecting to a broker that requires +# authentication. +# This replaces the old "username" option to avoid confusion. "username" +# remains valid for the time being. +#remote_username + +# Set the amount of time a bridge using the automatic start type will wait +# until attempting to reconnect. +# This option can be configured to use a constant delay time in seconds, or to +# use a backoff mechanism based on "Decorrelated Jitter", which adds a degree +# of randomness to when the restart occurs. +# +# Set a constant timeout of 20 seconds: +# restart_timeout 20 +# +# Set backoff with a base (start value) of 10 seconds and a cap (upper limit) of +# 60 seconds: +# restart_timeout 10 30 +# +# Defaults to jitter with a base of 5 and cap of 30 +#restart_timeout 5 30 + +# If the bridge has more than one address given in the address/addresses +# configuration, the round_robin option defines the behaviour of the bridge on +# a failure of the bridge connection. If round_robin is false, the default +# value, then the first address is treated as the main bridge connection. If +# the connection fails, the other secondary addresses will be attempted in +# turn. Whilst connected to a secondary bridge, the bridge will periodically +# attempt to reconnect to the main bridge until successful. +# If round_robin is true, then all addresses are treated as equals. If a +# connection fails, the next address will be tried and if successful will +# remain connected until it fails +#round_robin false + +# Set the start type of the bridge. This controls how the bridge starts and +# can be one of three types: automatic, lazy and once. Note that RSMB provides +# a fourth start type "manual" which isn't currently supported by mosquitto. +# +# "automatic" is the default start type and means that the bridge connection +# will be started automatically when the broker starts and also restarted +# after a short delay (30 seconds) if the connection fails. +# +# Bridges using the "lazy" start type will be started automatically when the +# number of queued messages exceeds the number set with the "threshold" +# parameter. It will be stopped automatically after the time set by the +# "idle_timeout" parameter. Use this start type if you wish the connection to +# only be active when it is needed. +# +# A bridge using the "once" start type will be started automatically when the +# broker starts but will not be restarted if the connection fails. +#start_type automatic + +# Set the number of messages that need to be queued for a bridge with lazy +# start type to be restarted. Defaults to 10 messages. +# Must be less than max_queued_messages. +#threshold 10 + +# If try_private is set to true, the bridge will attempt to indicate to the +# remote broker that it is a bridge not an ordinary client. If successful, this +# means that loop detection will be more effective and that retained messages +# will be propagated correctly. Not all brokers support this feature so it may +# be necessary to set try_private to false if your bridge does not connect +# properly. +#try_private true + +# Some MQTT brokers do not allow retained messages. MQTT v5 gives a mechanism +# for brokers to tell clients that they do not support retained messages, but +# this is not possible for MQTT v3.1.1 or v3.1. If you need to bridge to a +# v3.1.1 or v3.1 broker that does not support retained messages, set the +# bridge_outgoing_retain option to false. This will remove the retain bit on +# all outgoing messages to that bridge, regardless of any other setting. +#bridge_outgoing_retain true + +# If you wish to restrict the size of messages sent to a remote bridge, use the +# bridge_max_packet_size option. This sets the maximum number of bytes for +# the total message, including headers and payload. +# Note that MQTT v5 brokers may provide their own maximum-packet-size property. +# In this case, the smaller of the two limits will be used. +# Set to 0 for "unlimited". +#bridge_max_packet_size 0 + + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# Either bridge_cafile or bridge_capath must be defined to enable TLS support +# for this bridge. +# bridge_cafile defines the path to a file containing the +# Certificate Authority certificates that have signed the remote broker +# certificate. +# bridge_capath defines a directory that will be searched for files containing +# the CA certificates. For bridge_capath to work correctly, the certificate +# files must have ".crt" as the file ending and you must run "openssl rehash +# " each time you add/remove a certificate. +#bridge_cafile +#bridge_capath + + +# If the remote broker has more than one protocol available on its port, e.g. +# MQTT and WebSockets, then use bridge_alpn to configure which protocol is +# requested. Note that WebSockets support for bridges is not yet available. +#bridge_alpn + +# When using certificate based encryption, bridge_insecure disables +# verification of the server hostname in the server certificate. This can be +# useful when testing initial server configurations, but makes it possible for +# a malicious third party to impersonate your server through DNS spoofing, for +# example. Use this option in testing only. If you need to resort to using this +# option in a production environment, your setup is at fault and there is no +# point using encryption. +#bridge_insecure false + +# Path to the PEM encoded client certificate, if required by the remote broker. +#bridge_certfile + +# Path to the PEM encoded client private key, if required by the remote broker. +#bridge_keyfile + +# ----------------------------------------------------------------- +# PSK based SSL/TLS support +# ----------------------------------------------------------------- +# Pre-shared-key encryption provides an alternative to certificate based +# encryption. A bridge can be configured to use PSK with the bridge_identity +# and bridge_psk options. These are the client PSK identity, and pre-shared-key +# in hexadecimal format with no "0x". Only one of certificate and PSK based +# encryption can be used on one +# bridge at once. +#bridge_identity +#bridge_psk + + +# ================================================================= +# External config files +# ================================================================= + +# External configuration files may be included by using the +# include_dir option. This defines a directory that will be searched +# for config files. All files that end in '.conf' will be loaded as +# a configuration file. It is best to have this as the last option +# in the main file. This option will only be processed from the main +# configuration file. The directory specified must not contain the +# main configuration file. +# Files within include_dir will be loaded sorted in case-sensitive +# alphabetical order, with capital letters ordered first. If this option is +# given multiple times, all of the files from the first instance will be +# processed before the next instance. See the man page for examples. +#include_dir 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..d91bfb57c --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_all_if.conf @@ -0,0 +1 @@ +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..ae3dfee6f --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host.conf @@ -0,0 +1 @@ +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..b2d97b885 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_host_and_if.conf @@ -0,0 +1,2 @@ +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..06dc1dfb0 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if.conf @@ -0,0 +1,2 @@ +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..4ea23cce7 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_if_val_lost.conf @@ -0,0 +1,2 @@ +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..c6204aedf --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_bind_unix_socket.conf @@ -0,0 +1 @@ +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..d1f4a20cb --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_invalid_listener.conf @@ -0,0 +1,12 @@ +#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..f1549caf1 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv4.conf @@ -0,0 +1,3 @@ +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..92b3e021f --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_ipv6.conf @@ -0,0 +1,3 @@ +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..09d821f51 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_multi_listener.conf @@ -0,0 +1,10 @@ +#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..2068d7f85 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_socket_domain_val_lost.conf @@ -0,0 +1,3 @@ +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..fccd11916 --- /dev/null +++ b/bundles/event_admin/remote_provider/remote_provider_mqtt/gtest/res/mosquitto_start_with_space.conf @@ -0,0 +1 @@ + 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..bf16bb387 --- /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..ae7857789 --- /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..66b196feb --- /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..1d955ed0a --- /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..d0808eae1 --- /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) {