From 3caede27a25cfa1baf8e8d70798478fdfff05586 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 4 Feb 2022 14:43:39 +0100 Subject: [PATCH 01/20] Changed to use latest `WolkConnect-Cpp` and fixed up compile/test issues --- .github/workflows/cmake-build-and-test.yml | 2 +- WolkConnect-Cpp | 2 +- gateway/WolkGatewayBuilder.cpp | 14 ++++++++++++-- gateway/service/devices/DevicesService.cpp | 3 ++- out/coverage.sh | 2 +- tests/WolkGatewayBuilderTests.cpp | 1 + tests/WolkGatewayTests.cpp | 6 ++---- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cmake-build-and-test.yml b/.github/workflows/cmake-build-and-test.yml index 87da568..af2c779 100644 --- a/.github/workflows/cmake-build-and-test.yml +++ b/.github/workflows/cmake-build-and-test.yml @@ -54,4 +54,4 @@ jobs: - name: Run Tests working-directory: ${{runner.workspace}}/out shell: bash - run: make tests -j$(nproc) && ctest . + run: make test -j$(nproc) && ctest . diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 104d7a9..b40c5e3 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 104d7a93c6ff4ee9ce79fa0e37bff2ba1f157d78 +Subproject commit b40c5e315013fba750c399cf74d63b25669bfc58 diff --git a/gateway/WolkGatewayBuilder.cpp b/gateway/WolkGatewayBuilder.cpp index 38be780..c20ef92 100644 --- a/gateway/WolkGatewayBuilder.cpp +++ b/gateway/WolkGatewayBuilder.cpp @@ -58,7 +58,7 @@ WolkGatewayBuilder::WolkGatewayBuilder(Device device) , m_existingDeviceRepository{new JsonFileExistingDevicesRepository} , m_dataProtocol{new WolkaboutDataProtocol} , m_errorProtocol{new WolkaboutErrorProtocol} -, m_errorRetainTime{std::chrono::seconds{1}} +, m_errorRetainTime{1} , m_platformSubdeviceProtocol{new WolkaboutGatewaySubdeviceProtocol} , m_localSubdeviceProtocol{new WolkaboutGatewaySubdeviceProtocol(false)} , m_platformRegistrationProtocol{new WolkaboutRegistrationProtocol} @@ -309,12 +309,22 @@ std::unique_ptr WolkGatewayBuilder::build() wolk->m_parameterLambda = m_parameterHandlerLambda; wolk->m_parameterHandler = m_parameterHandler; wolk->m_dataService = std::make_shared( - *wolk->m_dataProtocol, *wolk->m_persistence, *wolk->m_connectivityService, + *wolk->m_dataProtocol, *wolk->m_persistence, *wolk->m_connectivityService, *wolk->m_outboundRetryMessageHandler, [wolkRaw](const std::string& deviceKey, const std::map>& readings) { wolkRaw->handleFeedUpdateCommand(deviceKey, readings); }, [wolkRaw](const std::string& deviceKey, const std::vector& parameters) { wolkRaw->handleParameterCommand(deviceKey, parameters); + }, + [](const std::string& deviceKey, const std::vector& feeds, + const std::vector& attributes) { + LOG(DEBUG) << "Received details for device '" << deviceKey << "':"; + LOG(DEBUG) << "Feeds: "; + for (const auto& feed : feeds) + LOG(DEBUG) << "\t" << feed; + LOG(DEBUG) << "Attributes: "; + for (const auto& attribute : attributes) + LOG(DEBUG) << "\t" << attribute; }); wolk->m_errorService = std::make_shared(*wolk->m_errorProtocol, m_errorRetainTime); wolk->m_inboundMessageHandler->addListener(wolk->m_dataService); diff --git a/gateway/service/devices/DevicesService.cpp b/gateway/service/devices/DevicesService.cpp index a59772f..e31b099 100644 --- a/gateway/service/devices/DevicesService.cpp +++ b/gateway/service/devices/DevicesService.cpp @@ -163,7 +163,8 @@ bool DevicesService::sendOutRegisteredDevicesRequest(RegisteredDevicesRequestPar auto sendTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); m_outboundPlatformRetryMessageHandler.addMessage(RetryMessageStruct{ - parsedMessage, m_platformProtocol.getResponseChannelForRegisteredDeviceRequest(m_gatewayKey), + parsedMessage, + m_platformProtocol.getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, m_gatewayKey), [=](const std::shared_ptr&) { LOG(ERROR) << TAG << "Failed to receive response for 'RegisteredDevicesRequest' - no response from platform."; }, diff --git a/out/coverage.sh b/out/coverage.sh index 1757b0b..5b26b25 100755 --- a/out/coverage.sh +++ b/out/coverage.sh @@ -5,5 +5,5 @@ echo "${WORK_DIR}" rm ./coverage.info rm -rf ./coverage lcov -b . -c -d . -o ./coverage.info -lcov -r ./coverage.info '/usr/*' "${WORK_DIR}/WolkSDK-Cpp/*" "${WORK_DIR}/WolkConnect-Cpp/*" "${WORK_DIR}/application/*" "${WORK_DIR}/out/*" "${WORK_DIR}/tests/*" -o ./coverage.info +lcov -r ./coverage.info '/usr/*' "${WORK_DIR}/**/*.h" "${WORK_DIR}/WolkSDK-Cpp/*" "${WORK_DIR}/WolkConnect-Cpp/*" "${WORK_DIR}/application/*" "${WORK_DIR}/out/*" "${WORK_DIR}/tests/*" -o ./coverage.info genhtml -o ./coverage ./coverage.info diff --git a/tests/WolkGatewayBuilderTests.cpp b/tests/WolkGatewayBuilderTests.cpp index 776b322..17c0859 100644 --- a/tests/WolkGatewayBuilderTests.cpp +++ b/tests/WolkGatewayBuilderTests.cpp @@ -162,4 +162,5 @@ TEST_F(WolkGatewayBuilderTests, FullExample) ASSERT_NO_FATAL_FAILURE(wolk->m_connectivityService->m_onConnectionLost()); ASSERT_NO_FATAL_FAILURE(wolk->m_dataService->m_feedUpdateHandler("", {})); ASSERT_NO_FATAL_FAILURE(wolk->m_dataService->m_parameterSyncHandler("", {})); + ASSERT_NO_FATAL_FAILURE(wolk->m_dataService->m_detailsSyncHandler("", {"F1"}, {"A1"})); } diff --git a/tests/WolkGatewayTests.cpp b/tests/WolkGatewayTests.cpp index 1bd6079..5899251 100644 --- a/tests/WolkGatewayTests.cpp +++ b/tests/WolkGatewayTests.cpp @@ -49,10 +49,8 @@ class WolkGatewayTests : public Test void SetUp() override { - dataServiceMock = std::unique_ptr{ - new DataServiceMock{dataProtocolMock, persistenceMock, connectivityServiceMock, - [](const std::string&, const std::map>&) {}, - [](const std::string&, const std::vector&) {}}}; + dataServiceMock = std::unique_ptr{new DataServiceMock{ + dataProtocolMock, persistenceMock, connectivityServiceMock, outboundRetryMessageHandlerMock, {}, {}, {}}}; devicesServiceMock = std::unique_ptr{new DevicesServiceMock{ gateway.getKey(), registrationProtocolMock, outboundMessageHandlerMock, outboundRetryMessageHandlerMock}}; gatewayPlatformStatusServiceMock = std::unique_ptr{ From 06edcd1d2ea16af8a4ca5473ab826de17557b204 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 4 Feb 2022 18:02:29 +0100 Subject: [PATCH 02/20] First registering prototype - Succeeded! --- CMakeLists.txt | 4 +- gateway/WolkGateway.cpp | 21 +- gateway/WolkGateway.h | 5 +- gateway/WolkGatewayBuilder.cpp | 10 +- gateway/repository/device/DeviceRepository.h | 96 ++++++- .../device/InMemoryDeviceRepository.cpp | 146 +++++++--- .../device/InMemoryDeviceRepository.h | 45 ++- .../device/SQLiteDeviceRepository.cpp | 33 ++- .../device/SQLiteDeviceRepository.h | 12 +- gateway/service/devices/DevicesService.cpp | 268 +++++++++++++++--- gateway/service/devices/DevicesService.h | 81 +++++- out/gatewayConfiguration.json | 6 +- tests/mocks/DeviceRepositoryMock.h | 2 +- 13 files changed, 591 insertions(+), 138 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad3491f..004125b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,7 +101,7 @@ endif () set(LIB_SOURCE_FILES gateway/connectivity/GatewayMessageRouter.cpp gateway/repository/existing_device/JsonFileExistingDevicesRepository.cpp gateway/repository/device/InMemoryDeviceRepository.cpp - gateway/repository/device/SQLiteDeviceRepository.cpp + # gateway/repository/device/SQLiteDeviceRepository.cpp gateway/service/external_data/ExternalDataService.cpp gateway/service/internal_data/InternalDataService.cpp gateway/service/platform_status/GatewayPlatformStatusService.cpp @@ -115,7 +115,7 @@ set(LIB_HEADER_FILES gateway/api/DataHandler.h gateway/repository/existing_device/ExistingDevicesRepository.h gateway/repository/existing_device/JsonFileExistingDevicesRepository.h gateway/repository/device/InMemoryDeviceRepository.h - gateway/repository/device/SQLiteDeviceRepository.h + # gateway/repository/device/SQLiteDeviceRepository.h gateway/service/external_data/ExternalDataService.h gateway/service/internal_data/InternalDataService.h gateway/service/devices/DevicesService.h diff --git a/gateway/WolkGateway.cpp b/gateway/WolkGateway.cpp index 21e3cae..600ab6a 100644 --- a/gateway/WolkGateway.cpp +++ b/gateway/WolkGateway.cpp @@ -32,6 +32,7 @@ #include "core/utilities/Logger.h" #include "gateway/connectivity/GatewayMessageRouter.h" #include "gateway/repository/device/DeviceRepository.h" +#include "gateway/repository/device/InMemoryDeviceRepository.h" #include "gateway/repository/existing_device/ExistingDevicesRepository.h" #include "gateway/service/devices/DevicesService.h" #include "gateway/service/external_data/ExternalDataService.h" @@ -134,10 +135,26 @@ void WolkGateway::notifyPlatformConnected() LOG(INFO) << "Connection to platform established"; WolkSingle::notifyConnected(); - if (m_subdeviceManagementService != nullptr) - m_subdeviceManagementService->updateDeviceCache(); + if (m_cacheDeviceRepository != nullptr) + m_cacheDeviceRepository->loadInformationFromPersistentRepository(); + // if (m_subdeviceManagementService != nullptr) + // m_subdeviceManagementService->updateDeviceCache(); if (m_gatewayPlatformStatusService != nullptr) m_gatewayPlatformStatusService->sendPlatformConnectionStatusMessage(true); + + if (m_subdeviceManagementService) + { + m_subdeviceManagementService->registerChildDevices( + {DeviceRegistrationData{"Test Device 1", "TD1", "", {}, {}, {}}}, + [](const std::vector& succeeded, const std::vector& failed) { + LOG(INFO) << "Succeeded to register: "; + for (const auto& key : succeeded) + LOG(INFO) << "\t" << key; + LOG(INFO) << "Failed to register: "; + for (const auto& key : failed) + LOG(INFO) << "\t" << key; + }); + } } void WolkGateway::notifyPlatformDisconnected() diff --git a/gateway/WolkGateway.h b/gateway/WolkGateway.h index dc6ba61..865ec90 100644 --- a/gateway/WolkGateway.h +++ b/gateway/WolkGateway.h @@ -56,9 +56,10 @@ class RegistrationService; namespace gateway { class DeviceRepository; +class ExternalDataService; class ExistingDevicesRepository; class GatewayMessageRouter; -class ExternalDataService; +class InMemoryDeviceRepository; class InternalDataService; class GatewayPlatformStatusService; class DevicesService; @@ -147,7 +148,7 @@ class WolkGateway : public connect::WolkSingle std::atomic m_localConnected; - std::shared_ptr m_cacheDeviceRepository; + std::shared_ptr m_cacheDeviceRepository; std::shared_ptr m_persistentDeviceRepository; std::unique_ptr m_existingDevicesRepository; diff --git a/gateway/WolkGatewayBuilder.cpp b/gateway/WolkGatewayBuilder.cpp index c20ef92..2627dfd 100644 --- a/gateway/WolkGatewayBuilder.cpp +++ b/gateway/WolkGatewayBuilder.cpp @@ -34,7 +34,7 @@ #include "gateway/WolkGateway.h" #include "gateway/connectivity/GatewayMessageRouter.h" #include "gateway/repository/device/InMemoryDeviceRepository.h" -#include "gateway/repository/device/SQLiteDeviceRepository.h" +//#include "gateway/repository/device/SQLiteDeviceRepository.h" #include "gateway/repository/existing_device/JsonFileExistingDevicesRepository.h" #include "gateway/service/devices/DevicesService.h" #include "gateway/service/external_data/ExternalDataService.h" @@ -54,7 +54,7 @@ WolkGatewayBuilder::WolkGatewayBuilder(Device device) , m_platformMqttKeepAliveSec{60} , m_persistence{new InMemoryPersistence} , m_messagePersistence{new InMemoryMessagePersistence} -, m_deviceStoragePolicy{DeviceStoragePolicy::FULL} +, m_deviceStoragePolicy{DeviceStoragePolicy::CACHED} , m_existingDeviceRepository{new JsonFileExistingDevicesRepository} , m_dataProtocol{new WolkaboutDataProtocol} , m_errorProtocol{new WolkaboutErrorProtocol} @@ -270,8 +270,10 @@ std::unique_ptr WolkGatewayBuilder::build() wolk->m_messagePersistence = std::move(m_messagePersistence); // Move the repository objects - if (m_deviceStoragePolicy == DeviceStoragePolicy::PERSISTENT || m_deviceStoragePolicy == DeviceStoragePolicy::FULL) - wolk->m_persistentDeviceRepository = std::make_shared(); + // TODO Uncomment + // if (m_deviceStoragePolicy == DeviceStoragePolicy::PERSISTENT || m_deviceStoragePolicy == + // DeviceStoragePolicy::FULL) + // wolk->m_persistentDeviceRepository = std::make_shared(); if (m_deviceStoragePolicy == DeviceStoragePolicy::CACHED || m_deviceStoragePolicy == DeviceStoragePolicy::FULL) wolk->m_cacheDeviceRepository = std::make_shared(wolk->m_persistentDeviceRepository); wolk->m_existingDevicesRepository = std::move(m_existingDeviceRepository); diff --git a/gateway/repository/device/DeviceRepository.h b/gateway/repository/device/DeviceRepository.h index 140c254..ab43ab3 100644 --- a/gateway/repository/device/DeviceRepository.h +++ b/gateway/repository/device/DeviceRepository.h @@ -21,26 +21,114 @@ #include #include +#include #include namespace wolkabout { namespace gateway { +// This enum value describes who the device belongs to. +enum class DeviceOwnership +{ + Platform, + Gateway +}; + +// This enum represents the data that is stored about a device. +struct StoredDeviceInformation +{ +public: + StoredDeviceInformation() : m_deviceKey{}, m_deviceBelongsTo{DeviceOwnership::Platform}, m_timestamp{} {} + + StoredDeviceInformation(std::string deviceKey, DeviceOwnership deviceBelongsTo, + const std::chrono::milliseconds& timestamp) + : m_deviceKey{std::move(deviceKey)}, m_deviceBelongsTo{deviceBelongsTo}, m_timestamp{timestamp} + { + } + + StoredDeviceInformation(const RegisteredDeviceInformation& deviceInformation, std::chrono::milliseconds timestamp) + : m_deviceKey{deviceInformation.deviceKey}, m_deviceBelongsTo{DeviceOwnership::Platform}, m_timestamp{timestamp} + { + } + + const std::string& getDeviceKey() const { return m_deviceKey; } + + DeviceOwnership getDeviceBelongsTo() const { return m_deviceBelongsTo; } + + const std::chrono::milliseconds& getTimestamp() const { return m_timestamp; } + +private: + std::string m_deviceKey; + DeviceOwnership m_deviceBelongsTo; + std::chrono::milliseconds m_timestamp; +}; + +/** + * This interface represents an persistence entity that stores information about devices. + */ class DeviceRepository { public: + /** + * Default virtual destructor. + */ virtual ~DeviceRepository() = default; - virtual bool save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) = 0; + /** + * This is the method via which the user stores information about devices. + * + * @param devices The list of devices that should be added to the persistence. + * @return Whether the devices were stored successfully. If some devices were duplicate device key, they will be + * ignored, and the function will still return true. + */ + virtual bool save(const std::vector& devices) = 0; - virtual bool remove(const std::string& deviceKey) = 0; + /** + * This is the method via which the user commands the storage to delete a device. + * + * @param deviceKeys The list of device keys that should be removed. + * @return Whether the devices were removed successfully. If keys were ignored without any fault, this will still be + * true. + */ + virtual bool remove(const std::vector& deviceKeys) = 0; + /** + * This is the method via which the user commands the storage to remove all devices. + * + * @return Whether all devices were removed successfully. + */ virtual bool removeAll() = 0; - virtual bool containsDeviceKey(const std::string& deviceKey) = 0; + /** + * This is the method via which the user asks whether storage contains information about a device. + * + * @param deviceKey The device key for which the user is interested about. + * @return Whether persistence contains information about the device. + */ + virtual bool containsDevice(const std::string& deviceKey) = 0; + + /** + * This is the method via which the user obtains information about a device by its key. + * + * @param deviceKey The device key for which the user is interested about. + * @return The information about the device retrieved from persistence. Can be empty if device does not exist. + */ + virtual StoredDeviceInformation get(const std::string& deviceKey) = 0; + + /** + * This is the method via which the user obtains the information about all gateway owned devices. + * + * @return The list of gateway owned devices. + */ + virtual std::vector getGatewayDevices() = 0; - virtual std::chrono::milliseconds latestTimestamp() = 0; + /** + * This is the method via which the user obtains the latest timestamp value that is stored in the persistence. + * + * @return The latest timestamp value for the platform owned devices. + */ + virtual std::chrono::milliseconds latestPlatformTimestamp() = 0; }; } // namespace gateway } // namespace wolkabout diff --git a/gateway/repository/device/InMemoryDeviceRepository.cpp b/gateway/repository/device/InMemoryDeviceRepository.cpp index a36008f..badda36 100644 --- a/gateway/repository/device/InMemoryDeviceRepository.cpp +++ b/gateway/repository/device/InMemoryDeviceRepository.cpp @@ -31,66 +31,79 @@ InMemoryDeviceRepository::InMemoryDeviceRepository(std::shared_ptr lockGuard{m_mutex}; if (m_persistentDeviceRepository != nullptr) { - const auto loadedTimestamp = m_persistentDeviceRepository->latestTimestamp(); + // Copy all the gateway devices + const auto gatewayDevices = m_persistentDeviceRepository->getGatewayDevices(); + std::copy(gatewayDevices.cbegin(), gatewayDevices.cend(), m_devices.begin()); + + // Copy the timestamp + const auto loadedTimestamp = m_persistentDeviceRepository->latestPlatformTimestamp(); if (loadedTimestamp > m_timestamp) m_timestamp = loadedTimestamp; } } -bool InMemoryDeviceRepository::save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) +bool InMemoryDeviceRepository::save(const std::vector& devices) { - LOG(TRACE) << METHOD_INFO; - std::lock_guard lockGuard{m_mutex}; - - // Save the value to the local vector - if (!containsDeviceKey(device.deviceKey)) - m_devices.emplace_back(device.deviceKey); - - // Update the timestamp - if (timestamp > m_timestamp) - m_timestamp = timestamp; + // Store the devices info + { + std::lock_guard lockGuard{m_mutex}; + for (const auto& device : devices) + { + // Save the value to the local vector + if (!containsDevice(device.getDeviceKey())) + m_devices.emplace_back(device); + + // Update the timestamp + if (device.getTimestamp() > m_timestamp) + m_timestamp = device.getTimestamp(); + } + } // If the persistent repository is present, tell it to save data too if (m_persistentDeviceRepository != nullptr && m_commandBuffer != nullptr) - m_commandBuffer->pushCommand(std::make_shared>( - [this, timestamp, device] { m_persistentDeviceRepository->save(timestamp, device); })); + m_commandBuffer->pushCommand( + std::make_shared>([this, devices] { m_persistentDeviceRepository->save(devices); })); return true; } -bool InMemoryDeviceRepository::remove(const std::string& deviceKey) +bool InMemoryDeviceRepository::remove(const std::vector& deviceKeys) { - LOG(TRACE) << METHOD_INFO; - std::lock_guard lockGuard{m_mutex}; - // Empty the local vector - const auto deviceKeyIt = std::find(m_devices.cbegin(), m_devices.cend(), deviceKey); - if (deviceKeyIt != m_devices.cend()) - m_devices.erase(deviceKeyIt); + { + std::lock_guard lockGuard{m_mutex}; + for (const auto& deviceKey : deviceKeys) + { + const auto it = std::find_if( + m_devices.begin(), m_devices.end(), + [&](const StoredDeviceInformation& information) { return information.getDeviceKey() == deviceKey; }); + if (it != m_devices.cend()) + m_devices.erase(it); + } + } // If we have access to more permanent persistence, delete it too if (m_persistentDeviceRepository != nullptr && m_commandBuffer != nullptr) { - auto deviceKeyCopy = std::string{deviceKey}; m_commandBuffer->pushCommand(std::make_shared>( - [this, deviceKeyCopy] { m_persistentDeviceRepository->remove(deviceKeyCopy); })); + [this, deviceKeys] { m_persistentDeviceRepository->remove(deviceKeys); })); } return true; } bool InMemoryDeviceRepository::removeAll() { - LOG(TRACE) << METHOD_INFO; - std::lock_guard lockGuard{m_mutex}; - // Empty the local vector - m_devices.clear(); + { + std::lock_guard lockGuard{m_mutex}; + m_devices.clear(); + } // If we have access to more permanent persistence, delete it too if (m_persistentDeviceRepository != nullptr && m_commandBuffer != nullptr) @@ -99,27 +112,70 @@ bool InMemoryDeviceRepository::removeAll() return true; } -bool InMemoryDeviceRepository::containsDeviceKey(const std::string& deviceKey) +bool InMemoryDeviceRepository::containsDevice(const std::string& deviceKey) { - LOG(TRACE) << METHOD_INFO; - std::lock_guard lockGuard{m_mutex}; + bool found; + + // Check in local memory + { + std::lock_guard lock{m_mutex}; + const auto it = std::find_if( + m_devices.begin(), m_devices.end(), + [&](const StoredDeviceInformation& information) { return information.getDeviceKey() == deviceKey; }); + found = it != m_devices.cend(); + } + + // If not found, check in persistent storage + if (!found && m_persistentDeviceRepository != nullptr) + { + auto returningInformation = m_persistentDeviceRepository->get(deviceKey); + if (!returningInformation.getDeviceKey().empty()) + { + m_devices.emplace_back(returningInformation); + found = true; + } + } + return found; +} + +StoredDeviceInformation InMemoryDeviceRepository::get(const std::string& deviceKey) +{ + auto returningInformation = StoredDeviceInformation{}; - // Check if it is in the vector - const auto deviceKeyIt = std::find(m_devices.cbegin(), m_devices.cend(), deviceKey); - if (deviceKeyIt != m_devices.cend()) - return true; - - // Check if we can try to see if it is in permanent persistence - if (m_persistentDeviceRepository == nullptr) - return false; - const auto permanentPersistenceContains = m_persistentDeviceRepository->containsDeviceKey(deviceKey); - // Save it in our local persistence if it is contained in there - if (permanentPersistenceContains) - m_devices.emplace_back(deviceKey); - return permanentPersistenceContains; + // Check in local memory + { + std::lock_guard lock{m_mutex}; + const auto it = std::find_if( + m_devices.begin(), m_devices.end(), + [&](const StoredDeviceInformation& information) { return information.getDeviceKey() == deviceKey; }); + if (it != m_devices.cend()) + returningInformation = StoredDeviceInformation{*it}; + } + + // If not found, check in persistent storage + if (returningInformation.getDeviceKey().empty() && m_persistentDeviceRepository != nullptr) + { + returningInformation = m_persistentDeviceRepository->get(deviceKey); + if (!returningInformation.getDeviceKey().empty()) + m_devices.emplace_back(returningInformation); + } + return returningInformation; +} + +std::vector InMemoryDeviceRepository::getGatewayDevices() +{ + auto gatewayDevices = std::vector{}; + { + std::lock_guard lock{m_mutex}; + std::copy_if(m_devices.cbegin(), m_devices.cend(), gatewayDevices.begin(), + [&](const StoredDeviceInformation& information) { + return information.getDeviceBelongsTo() == DeviceOwnership::Gateway; + }); + } + return gatewayDevices; } -std::chrono::milliseconds InMemoryDeviceRepository::latestTimestamp() +std::chrono::milliseconds InMemoryDeviceRepository::latestPlatformTimestamp() { std::lock_guard lockGuard{m_mutex}; return m_timestamp; diff --git a/gateway/repository/device/InMemoryDeviceRepository.h b/gateway/repository/device/InMemoryDeviceRepository.h index e6cff5f..10640fc 100644 --- a/gateway/repository/device/InMemoryDeviceRepository.h +++ b/gateway/repository/device/InMemoryDeviceRepository.h @@ -36,30 +36,28 @@ class InMemoryDeviceRepository : public DeviceRepository explicit InMemoryDeviceRepository(std::shared_ptr persistentDeviceRepository = nullptr); /** - * This method will load the `latestTimestamp` value from the persistence repository and set it as the value of the - * InMemory repository. + * This will cache the information from the persistent repository in the memory. */ - void loadLatestTimestampFromPersistentRepository(); + void loadInformationFromPersistentRepository(); /** * This method is overridden from the `gateway::DeviceRepository` interface. - * This method will save the device data in the cache storage, and the persistent storage if it is present. + * This method will save the devices data in the cache storage, and the persistent storage if it is present. * - * @param timestamp The timestamp at which the request has been sent for this device. - * @param device The device information. - * @return Whether the device has been successfully saved. + * @param devices The devices information. + * @return Whether the devices have been successfully saved. */ - bool save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) override; + bool save(const std::vector& devices) override; /** * This method is overridden from the `gateway::DeviceRepository` interface. - * This method will remove the device from the cache storage, and queue the same thing to be done in persistent + * This method will remove the devices from the cache storage, and queue the same thing to be done in persistent * storage if present. * - * @param deviceKey The device key that needs to be removed. - * @return Whether the device has been removed. + * @param deviceKeys The device keys that need to be removed. + * @return Whether the devices have been removed. */ - bool remove(const std::string& deviceKey) override; + bool remove(const std::vector& deviceKeys) override; /** * This method is overridden from the `gateway::DeviceRepository` interface. @@ -78,7 +76,24 @@ class InMemoryDeviceRepository : public DeviceRepository * @param deviceKey The device key for which presence must be confirmed. * @return Whether the device key is present in either cache or persistent storage. */ - bool containsDeviceKey(const std::string& deviceKey) override; + bool containsDevice(const std::string& deviceKey) override; + + /** + * This method is overridden from the `gateway::DeviceRepository` interface. + * This method is used to obtain information about a device. + * + * @param deviceKey The device in which the user is interested. + * @return The information about the device. Can be empty if the device is not in persistence. + */ + StoredDeviceInformation get(const std::string& deviceKey) override; + + /** + * This method is overridden from the `gateway::DeviceRepository` interface. + * This method is used to obtain the list of devices this gateway owns. + * + * @return The list of devices registered by this gateway. + */ + std::vector getGatewayDevices() override; /** * This method is overridden from the `gateway::DeviceRepository` interface. @@ -87,7 +102,7 @@ class InMemoryDeviceRepository : public DeviceRepository * * @return The last timestamp. */ - std::chrono::milliseconds latestTimestamp() override; + std::chrono::milliseconds latestPlatformTimestamp() override; private: // Store the latest timestamp @@ -95,7 +110,7 @@ class InMemoryDeviceRepository : public DeviceRepository // Here we actually store the data std::recursive_mutex m_mutex; - std::vector m_devices; + std::vector m_devices; // And optional pointer for a more persistence DeviceRepository std::shared_ptr m_persistentDeviceRepository; diff --git a/gateway/repository/device/SQLiteDeviceRepository.cpp b/gateway/repository/device/SQLiteDeviceRepository.cpp index b90cb52..8893228 100644 --- a/gateway/repository/device/SQLiteDeviceRepository.cpp +++ b/gateway/repository/device/SQLiteDeviceRepository.cpp @@ -20,7 +20,6 @@ #include "core/utilities/ByteUtils.h" #include "core/utilities/Logger.h" -#include #include #include #include @@ -67,12 +66,11 @@ SQLiteDeviceRepository::~SQLiteDeviceRepository() bool SQLiteDeviceRepository::save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) { - LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to save a device in the database - "; // If the device is already present, go to the update routine std::lock_guard lock{m_mutex}; - if (containsDeviceKey(device.deviceKey)) + if (containsDevice(device.deviceKey)) return update(timestamp, device); // Store the information about the device @@ -101,7 +99,6 @@ bool SQLiteDeviceRepository::save(std::chrono::milliseconds timestamp, const Reg bool SQLiteDeviceRepository::remove(const std::string& deviceKey) { - LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to remove a device from the database - "; std::lock_guard lock{m_mutex}; @@ -116,7 +113,6 @@ bool SQLiteDeviceRepository::remove(const std::string& deviceKey) bool SQLiteDeviceRepository::removeAll() { - LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to remove all devices from the database - "; std::lock_guard lock{m_mutex}; @@ -129,9 +125,8 @@ bool SQLiteDeviceRepository::removeAll() return true; } -bool SQLiteDeviceRepository::containsDeviceKey(const std::string& deviceKey) +bool SQLiteDeviceRepository::containsDevice(const std::string& deviceKey) { - LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to obtain information whether device info is stored - "; std::lock_guard lock{m_mutex}; @@ -146,9 +141,18 @@ bool SQLiteDeviceRepository::containsDeviceKey(const std::string& deviceKey) return result.size() >= 2 && result[1].front() == deviceKey; } -std::chrono::milliseconds SQLiteDeviceRepository::latestTimestamp() +StoredDeviceInformation SQLiteDeviceRepository::get(const std::string& deviceKey) +{ + return StoredDeviceInformation(); +} + +std::vector SQLiteDeviceRepository::getGatewayDevices() +{ + return std::vector(); +} + +std::chrono::milliseconds SQLiteDeviceRepository::latestPlatformTimestamp() { - LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to obtain the latest timestamp value - "; std::lock_guard lock{m_mutex}; @@ -175,18 +179,19 @@ std::chrono::milliseconds SQLiteDeviceRepository::latestTimestamp() return millis; } -bool SQLiteDeviceRepository::update(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) +bool SQLiteDeviceRepository::update(const std::vector& devices) { - if (remove(device.deviceKey)) - return save(timestamp, device); + auto keys = std::vector{}; + for (const auto& device : devices) + keys.emplace_back(device.getDeviceKey()); + if (remove(keys)) + return save(devices); else return false; } std::string SQLiteDeviceRepository::executeSQLStatement(const std::string& sql, ColumnResult* result) { - // This would spam too much - // LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to execute query - "; // Check if the database session is established diff --git a/gateway/repository/device/SQLiteDeviceRepository.h b/gateway/repository/device/SQLiteDeviceRepository.h index 8dfff1d..e1688b7 100644 --- a/gateway/repository/device/SQLiteDeviceRepository.h +++ b/gateway/repository/device/SQLiteDeviceRepository.h @@ -43,16 +43,20 @@ class SQLiteDeviceRepository : public DeviceRepository bool save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& deviceKey) override; - bool remove(const std::string& deviceKey) override; + bool remove(const std::vector& deviceKey) override; bool removeAll() override; - bool containsDeviceKey(const std::string& deviceKey) override; + bool containsDevice(const std::string& deviceKey) override; - std::chrono::milliseconds latestTimestamp() override; + StoredDeviceInformation get(const std::string& deviceKey) override; + + std::vector getGatewayDevices() override; + + std::chrono::milliseconds latestPlatformTimestamp() override; private: - bool update(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device); + bool update(const std::vector& devices); std::string executeSQLStatement(const std::string& sqlStatement, ColumnResult* result = nullptr); diff --git a/gateway/service/devices/DevicesService.cpp b/gateway/service/devices/DevicesService.cpp index e31b099..278df8f 100644 --- a/gateway/service/devices/DevicesService.cpp +++ b/gateway/service/devices/DevicesService.cpp @@ -27,6 +27,7 @@ #include "gateway/repository/device/DeviceRepository.h" #include "gateway/repository/existing_device/ExistingDevicesRepository.h" +#include #include #include #include @@ -106,6 +107,39 @@ const std::weak_ptr& RegisteredDevicesRequestCallback:: return m_conditionVariable; } +ChildrenSynchronizationRequestCallback::ChildrenSynchronizationRequestCallback( + std::function)> lambda, + std::vector registeringDevices) +: m_registeringDevices{std::move(registeringDevices)}, m_lambda{std::move(lambda)} +{ +} + +ChildrenSynchronizationRequestCallback::ChildrenSynchronizationRequestCallback( + std::weak_ptr conditionVariable, std::vector registeringDevices) +: m_registeringDevices{std::move(registeringDevices)}, m_conditionVariable{std::move(conditionVariable)} +{ +} + +const std::chrono::milliseconds& ChildrenSynchronizationRequestCallback::getSentTime() const +{ + return m_sentTime; +} + +const std::vector& ChildrenSynchronizationRequestCallback::getRegisteringDevices() const +{ + return m_registeringDevices; +} + +const std::function)>& +ChildrenSynchronizationRequestCallback::getLambda() const +{ + return m_lambda; +} +const std::weak_ptr& ChildrenSynchronizationRequestCallback::getConditionVariable() const +{ + return m_conditionVariable; +} + DevicesService::DevicesService(std::string gatewayKey, RegistrationProtocol& platformRegistrationProtocol, OutboundMessageHandler& outboundPlatformMessageHandler, OutboundRetryMessageHandler& outboundPlatformRetryMessageHandler, @@ -124,6 +158,51 @@ DevicesService::DevicesService(std::string gatewayKey, RegistrationProtocol& pla DevicesService::~DevicesService() = default; +bool DevicesService::registerChildDevices( + const std::vector& devices, + std::function&, const std::vector&)> callback) +{ + LOG(TRACE) << METHOD_INFO; + const auto errorPrefix = "Failed to register child devices -> "; + + // Form the message for the registration + const auto parsedMessage = std::shared_ptr{ + m_platformProtocol.makeOutboundMessage(m_gatewayKey, DeviceRegistrationMessage{devices})}; + if (parsedMessage == nullptr) + { + LOG(ERROR) << errorPrefix << "Failed to parse the outbound 'DeviceRegistrationMessage'."; + return false; + } + + // Publish the message + m_outboundPlatformMessageHandler.addMessage(parsedMessage); + + // Now that that's publish, we want to verify that with the ChildrenSynchronizationMessage + auto deviceKeys = std::vector{}; + for (const auto& device : devices) + deviceKeys.emplace_back(device.key); + sendOutChildrenSynchronizationRequest(std::make_shared( + [=](const std::unique_ptr& response) { + // Contains all devices + auto succeeded = std::vector{}; + auto failed = std::vector{}; + + // Check the devices + const auto& children = response != nullptr ? response->getChildren() : std::vector{}; + for (const auto& device : deviceKeys) + { + const auto it = std::find(children.cbegin(), children.cend(), device); + if (it != children.cend()) + succeeded.emplace_back(device); + else + failed.emplace_back(device); + } + callback(succeeded, failed); + }, + deviceKeys)); + return true; +} + void DevicesService::updateDeviceCache() { LOG(TRACE) << METHOD_INFO; @@ -136,16 +215,57 @@ void DevicesService::updateDeviceCache() } // Obtain the last timestamp and send out a request - auto lastTimestamp = m_deviceRepository->latestTimestamp(); + auto lastTimestamp = m_deviceRepository->latestPlatformTimestamp(); LOG(DEBUG) << TAG << "Obtaining devices from timestamp " << lastTimestamp.count() << "."; sendOutRegisteredDevicesRequest(RegisteredDevicesRequestParameters{lastTimestamp}, {}); } +bool DevicesService::sendOutChildrenSynchronizationRequest( + std::shared_ptr callback) +{ + LOG(TRACE) << METHOD_INFO; + const auto errorPrefix = "Failed to send out a 'ChildrenSynchronizationRequestMessage' -> "; + + // Form the message + auto parsedMessage = std::shared_ptr{ + m_platformProtocol.makeOutboundMessage(m_gatewayKey, ChildrenSynchronizationRequestMessage{})}; + if (parsedMessage == nullptr) + { + LOG(ERROR) << errorPrefix << "Failed to parse the outbound message."; + return false; + } + m_outboundPlatformRetryMessageHandler.addMessage(RetryMessageStruct{ + parsedMessage, + m_platformProtocol.getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, m_gatewayKey), + [=](const std::shared_ptr&) { + LOG(ERROR) + << TAG + << "Failed to receive response for 'ChildrenSynchronizationRequestMessage' - no response from platform."; + // Check the callback + if (!callback->getRegisteringDevices().empty()) + { + LOG(ERROR) << "Failed to register devices: "; + for (const auto& device : callback->getRegisteringDevices()) + LOG(ERROR) << "\t" << device; + } + if (auto cv = callback->getConditionVariable().lock()) + cv->notify_one(); + if (callback->getLambda()) + callback->getLambda()(nullptr); + }, + RETRY_COUNT, RETRY_TIMEOUT}); + { + std::lock_guard lock{m_childSyncMutex}; + m_childSyncRequests.push(std::move(callback)); + } + return true; +} + bool DevicesService::sendOutRegisteredDevicesRequest(RegisteredDevicesRequestParameters parameters, - RegisteredDevicesRequestCallback callback) + std::shared_ptr callback) { LOG(TRACE) << METHOD_INFO; - const auto errorPrefix = "Failed to send out a 'RegisteredDevicesRequest' message -> "; + const auto errorPrefix = "Failed to send out a 'RegisteredDevicesRequestMessage' -> "; // Form the message auto message = RegisteredDevicesRequestMessage{parameters.getTimestampFrom(), parameters.getDeviceType(), @@ -169,7 +289,7 @@ bool DevicesService::sendOutRegisteredDevicesRequest(RegisteredDevicesRequestPar LOG(ERROR) << TAG << "Failed to receive response for 'RegisteredDevicesRequest' - no response from platform."; }, RETRY_COUNT, RETRY_TIMEOUT}); - m_requests.emplace(std::move(parameters), std::move(callback)); + m_registeredDevicesRequests.emplace(std::move(parameters), std::move(callback)); return true; } @@ -256,10 +376,10 @@ void DevicesService::messageReceived(std::shared_ptr message) parsedMessage->getTimestampFrom(), parsedMessage->getDeviceType(), parsedMessage->getExternalId()}; // Create the callback - auto callback = RegisteredDevicesRequestCallback{}; + auto callback = std::shared_ptr{}; if (m_localProtocol != nullptr && m_outboundLocalMessageHandler != nullptr) { - callback = RegisteredDevicesRequestCallback{ + callback = std::make_shared( [this, deviceKey](std::unique_ptr response) { // Create the message for the local broker auto localResponse = @@ -271,7 +391,7 @@ void DevicesService::messageReceived(std::shared_ptr message) return; } m_outboundLocalMessageHandler->addMessage(localResponse); - }}; + }); } // Send out the request @@ -297,55 +417,127 @@ void DevicesService::receiveMessages(const std::vector& // Go through every message for (const auto& message : messages) { + // Give the message to the RetryMessageHandler + const auto sharedMessage = std::make_shared(message.getMessage()); + m_outboundPlatformRetryMessageHandler.messageReceived(sharedMessage); + // Try to parse the message const auto type = m_platformProtocol.getMessageType(message.getMessage()); - if (type != MessageType::REGISTERED_DEVICES_RESPONSE) + switch (type) { - LOG(WARN) << TAG << "Received message that is not 'RegisteredDevicesResponse' message. Ignoring..."; - return; + case MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE: + { + auto response = m_platformProtocol.parseChildrenSynchronizationResponse(sharedMessage); + if (response == nullptr) + LOG(ERROR) << TAG << "Failed to parse incoming 'ChildrenSynchronizationResponseMessage'."; + else + handleChildrenSynchronizationResponse(std::move(response)); + break; } - const auto sharedMessage = std::make_shared(message.getMessage()); - m_outboundPlatformRetryMessageHandler.messageReceived(sharedMessage); - auto response = m_platformProtocol.parseRegisteredDevicesResponse(sharedMessage); - if (response == nullptr) + case MessageType::REGISTERED_DEVICES_RESPONSE: { - LOG(ERROR) << TAG << "Failed to parse incoming 'RegisteredDevicesResponse' message."; - return; + auto response = m_platformProtocol.parseRegisteredDevicesResponse(sharedMessage); + if (response == nullptr) + LOG(ERROR) << TAG << "Failed to parse incoming 'RegisteredDevicesResponseMessage'."; + else + handleRegisteredDevicesResponse(std::move(response)); + break; + } + default: + LOG(WARN) << TAG << "Received message is of type that can not be handled. Ignoring..."; + break; } + } +} + +std::vector DevicesService::getMessageTypes() +{ + return {MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE, MessageType::REGISTERED_DEVICES_RESPONSE}; +} - // Look for the callback object - auto now = - std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - const auto params = RegisteredDevicesRequestParameters{response->getTimestampFrom(), response->getDeviceType(), - response->getExternalId()}; - const auto callbackIt = m_requests.find(params); - if (callbackIt != m_requests.cend()) +void DevicesService::handleChildrenSynchronizationResponse( + std::unique_ptr response) +{ + LOG(TRACE) << METHOD_INFO; + + // Check if any callbacks are waiting + auto callback = std::shared_ptr{}; + { + std::lock_guard lock{m_childSyncMutex}; + if (!m_childSyncRequests.empty()) { - // Update the time when the request was sent out - now = callbackIt->second.getSentTime(); + callback = m_childSyncRequests.front(); + m_childSyncRequests.pop(); } + } - // Print something about it - LOG(INFO) << TAG << "Received info about " << response->getMatchingDevices().size() << " devices!"; + // Add the devices to storage + LOG(INFO) << TAG << "Received info about " << response->getChildren().size() << " child devices!."; + if (m_deviceRepository != nullptr) + { + auto devicesToSave = std::vector{}; if (m_deviceRepository != nullptr) - for (const auto& device : response->getMatchingDevices()) - m_deviceRepository->save(now, device); + for (const auto& device : response->getChildren()) + devicesToSave.emplace_back( + StoredDeviceInformation{device, DeviceOwnership::Gateway, std::chrono::milliseconds{0}}); + m_deviceRepository->save(devicesToSave); + } + + // Handle the callback + if (callback != nullptr) + { + if (auto cv = callback->getConditionVariable().lock()) + cv->notify_one(); + else if (callback->getLambda()) + callback->getLambda()(std::move(response)); + } +} + +void DevicesService::handleRegisteredDevicesResponse(std::unique_ptr response) +{ + LOG(TRACE) << METHOD_INFO; - // Handle the callback - if (callbackIt != m_requests.cend()) + // Look for the callback object + auto now = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + auto callback = std::shared_ptr{}; + const auto params = RegisteredDevicesRequestParameters{response->getTimestampFrom(), response->getDeviceType(), + response->getExternalId()}; + { + std::lock_guard lock{m_registeredDevicesMutex}; + const auto callbackIt = m_registeredDevicesRequests.find(params); + if (callbackIt != m_registeredDevicesRequests.cend() && callbackIt->second != nullptr) { - const auto& callback = callbackIt->second; - if (auto cv = callback.getConditionVariable().lock()) - cv->notify_one(); - else if (callback.getLambda()) - callback.getLambda()(std::move(response)); + // Update the time when the request was sent out + callback = callbackIt->second; + now = callback->getSentTime(); } } + + // Print something about it + LOG(INFO) << TAG << "Received info about " << response->getMatchingDevices().size() << " roaming devices!"; + if (m_deviceRepository != nullptr) + { + auto devicesToSave = std::vector{}; + if (m_deviceRepository != nullptr) + for (const auto& device : response->getMatchingDevices()) + devicesToSave.emplace_back(StoredDeviceInformation{device, now}); + m_deviceRepository->save(devicesToSave); + } + + // Handle the callback + if (callback != nullptr) + { + if (auto cv = callback->getConditionVariable().lock()) + cv->notify_one(); + else if (callback->getLambda()) + callback->getLambda()(std::move(response)); + } } -std::vector DevicesService::getMessageTypes() +void DevicesService::onSucceededRegistration(const std::vector& deviceKeys) { - return {MessageType::REGISTERED_DEVICES_RESPONSE}; + LOG(TRACE) << METHOD_INFO; } } // namespace gateway } // namespace wolkabout diff --git a/gateway/service/devices/DevicesService.h b/gateway/service/devices/DevicesService.h index b391470..0c32d85 100644 --- a/gateway/service/devices/DevicesService.h +++ b/gateway/service/devices/DevicesService.h @@ -25,13 +25,16 @@ #include #include #include +#include #include #include #include namespace wolkabout { +class ChildrenSynchronizationResponseMessage; class ConnectivityService; +class DeviceRegistrationData; class GatewayRegistrationProtocol; class OutboundMessageHandler; class OutboundRetryMessageHandler; @@ -72,6 +75,7 @@ struct RegisteredDevicesRequestParameters */ struct RegisteredDevicesRequestParametersHash { +public: std::uint64_t operator()(const RegisteredDevicesRequestParameters& params) const; }; @@ -81,6 +85,7 @@ struct RegisteredDevicesRequestParametersHash */ struct RegisteredDevicesRequestCallback { +public: RegisteredDevicesRequestCallback() = default; explicit RegisteredDevicesRequestCallback( @@ -106,6 +111,41 @@ struct RegisteredDevicesRequestCallback std::weak_ptr m_conditionVariable; }; +struct ChildrenSynchronizationRequestCallback +{ +public: + ChildrenSynchronizationRequestCallback() = default; + + explicit ChildrenSynchronizationRequestCallback( + std::function)> lambda, + std::vector registeringDevices = {}); + + explicit ChildrenSynchronizationRequestCallback(std::weak_ptr conditionVariable, + std::vector registeringDevices = {}); + + const std::chrono::milliseconds& getSentTime() const; + + const std::vector& getRegisteringDevices() const; + + const std::function)>& getLambda() const; + + const std::weak_ptr& getConditionVariable() const; + +private: + // Timestamp when the request was sent + std::chrono::milliseconds m_sentTime = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // The list of devices that have been attempted to be registered + std::vector m_registeringDevices; + + // Potential lambda expression that needs to be invoked + std::function)> m_lambda; + + // Potential condition variable that needs to be notified + std::weak_ptr m_conditionVariable; +}; + /** * This service is used to manage anything regarding sub-devices. This service is used to route the requests from * sub-devices to the platform regarding registration, deletion, and also registered devices. This service will also @@ -138,6 +178,17 @@ class DevicesService : public GatewayMessageListener, public MessageListener */ ~DevicesService() override; + /** + * Method that is used to register child devices on the platform. + * + * @param devices The devices and their information that will be sent out as a request. + * @param callback The callback that will be called with devices that are registered, and ones that are not. + * @return Whether the registration request has been successfully sent out. + */ + virtual bool registerChildDevices( + const std::vector& devices, + std::function&, const std::vector&)> callback); + /** * This is the method that should be run when the service is created and can use the connectivity objects. * This method will check when the DeviceRepository was last updated, and will request the list of devices that have @@ -149,13 +200,21 @@ class DevicesService : public GatewayMessageListener, public MessageListener virtual void updateDeviceCache(); /** - * Internal method that is used to send out the request to obtain the list of requested devices. + * Method that is used to send out the request to obtain the children of this device. + * + * @param callback The callback object that defines what will be done once a response has been received. + */ + virtual bool sendOutChildrenSynchronizationRequest( + std::shared_ptr callback); + + /** + * Method that is used to send out the request to obtain the list of requested devices. * * @param parameters The parameter by which the devices will be queried. * @param callback The callback object that defines what will be done once a response has been received. */ virtual bool sendOutRegisteredDevicesRequest(RegisteredDevicesRequestParameters parameters, - RegisteredDevicesRequestCallback callback); + std::shared_ptr callback); /** * This method is overridden from the `wolkabout::MessageListener` interface. @@ -193,6 +252,17 @@ class DevicesService : public GatewayMessageListener, public MessageListener std::vector getMessageTypes() override; private: + void handleChildrenSynchronizationResponse(std::unique_ptr response); + + void handleRegisteredDevicesResponse(std::unique_ptr response); + + /** + * This method is used to handle the logic when devices are verified to be registered. + * + * @param deviceKeys The list of devices that have been successfully registered. + */ + void onSucceededRegistration(const std::vector& deviceKeys); + // Logging tag const std::string TAG = "[DevicesService] -> "; @@ -212,9 +282,12 @@ class DevicesService : public GatewayMessageListener, public MessageListener std::shared_ptr m_deviceRepository; // Storage for request objects - std::unordered_map> m_childSyncRequests; + std::mutex m_registeredDevicesMutex; + std::unordered_map, RegisteredDevicesRequestParametersHash> - m_requests; + m_registeredDevicesRequests; }; } // namespace gateway } // namespace wolkabout diff --git a/out/gatewayConfiguration.json b/out/gatewayConfiguration.json index a42688c..906e090 100644 --- a/out/gatewayConfiguration.json +++ b/out/gatewayConfiguration.json @@ -1,10 +1,10 @@ { "name": "", - "key": "", - "password": "", + "key": "AWG", + "password": "MBEDX6XVG0", - "platformMqttUri": "ssl://demo.wolkabout.com:8883", + "platformMqttUri": "ssl://integration5.wolkabout.com:8883", "platformMqttKeepAliveSeconds": 60, "localMqttUri": "tcp://localhost:1883", diff --git a/tests/mocks/DeviceRepositoryMock.h b/tests/mocks/DeviceRepositoryMock.h index d24b5b1..b71abf9 100644 --- a/tests/mocks/DeviceRepositoryMock.h +++ b/tests/mocks/DeviceRepositoryMock.h @@ -31,7 +31,7 @@ class DeviceRepositoryMock : public DeviceRepository MOCK_METHOD(bool, remove, (const std::string&)); MOCK_METHOD(bool, removeAll, ()); MOCK_METHOD(bool, containsDeviceKey, (const std::string&)); - MOCK_METHOD(std::chrono::milliseconds, latestTimestamp, ()); + MOCK_METHOD(std::chrono::milliseconds, latestPlatformTimestamp, ()); }; #endif // WOLKGATEWAY_DEVICEREPOSITORYMOCK_H From 7591438475b3dfe801555881507f64f6ea9b1310 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Mon, 7 Feb 2022 16:00:28 +0100 Subject: [PATCH 03/20] Successful registration from subdevice --- WolkConnect-Cpp | 2 +- application/Application.cpp | 4 +++- gateway/WolkGateway.cpp | 15 +------------- gateway/service/devices/DevicesService.cpp | 23 +++++++++++----------- gateway/service/devices/DevicesService.h | 2 +- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index b40c5e3..75137d5 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit b40c5e315013fba750c399cf74d63b25669bfc58 +Subproject commit 75137d52f23a5c6becc5ca2ed9cc372a7b5d1292 diff --git a/application/Application.cpp b/application/Application.cpp index 41b4983..e421a3f 100644 --- a/application/Application.cpp +++ b/application/Application.cpp @@ -153,7 +153,9 @@ int main(int argc, char** argv) .withFileTransfer("./files") .setMqttKeepAlive(gatewayConfiguration.getKeepAliveSec()) .platformHost(gatewayConfiguration.getPlatformMqttUri()) - .withInternalDataService(gatewayConfiguration.getLocalMqttUri())); + .withInternalDataService(gatewayConfiguration.getLocalMqttUri()) + .withPlatformRegistration() + .withLocalRegistration()); if (!gatewayConfiguration.getPlatformTrustStore().empty()) { builder.platformTrustStore(gatewayConfiguration.getPlatformTrustStore()); diff --git a/gateway/WolkGateway.cpp b/gateway/WolkGateway.cpp index 600ab6a..9150306 100644 --- a/gateway/WolkGateway.cpp +++ b/gateway/WolkGateway.cpp @@ -137,24 +137,11 @@ void WolkGateway::notifyPlatformConnected() WolkSingle::notifyConnected(); if (m_cacheDeviceRepository != nullptr) m_cacheDeviceRepository->loadInformationFromPersistentRepository(); + // TODO Uncomment // if (m_subdeviceManagementService != nullptr) // m_subdeviceManagementService->updateDeviceCache(); if (m_gatewayPlatformStatusService != nullptr) m_gatewayPlatformStatusService->sendPlatformConnectionStatusMessage(true); - - if (m_subdeviceManagementService) - { - m_subdeviceManagementService->registerChildDevices( - {DeviceRegistrationData{"Test Device 1", "TD1", "", {}, {}, {}}}, - [](const std::vector& succeeded, const std::vector& failed) { - LOG(INFO) << "Succeeded to register: "; - for (const auto& key : succeeded) - LOG(INFO) << "\t" << key; - LOG(INFO) << "Failed to register: "; - for (const auto& key : failed) - LOG(INFO) << "\t" << key; - }); - } } void WolkGateway::notifyPlatformDisconnected() diff --git a/gateway/service/devices/DevicesService.cpp b/gateway/service/devices/DevicesService.cpp index 278df8f..3f0dc23 100644 --- a/gateway/service/devices/DevicesService.cpp +++ b/gateway/service/devices/DevicesService.cpp @@ -160,7 +160,7 @@ DevicesService::~DevicesService() = default; bool DevicesService::registerChildDevices( const std::vector& devices, - std::function&, const std::vector&)> callback) + const std::function&, const std::vector&)>& callback) { LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to register child devices -> "; @@ -320,18 +320,17 @@ void DevicesService::messageReceived(std::shared_ptr message) return; } - // Make the platform request - auto request = std::shared_ptr{m_platformProtocol.makeOutboundMessage(m_gatewayKey, *parsedMessage)}; - if (request == nullptr) - { - LOG(ERROR) << TAG - << "Failed to handler incoming local 'DeviceRegistration' message - Failed to parse outgoing " - "registration request."; - return; - } - // Send the message - m_outboundPlatformMessageHandler.addMessage(request); + registerChildDevices(parsedMessage->getDevices(), [=](const std::vector& registeredDevices, + const std::vector& unregisteredDevices) { + if (m_localProtocol == nullptr) + return; + auto responseMessage = std::shared_ptr{m_localProtocol->makeOutboundMessage( + deviceKey, DeviceRegistrationResponseMessage{registeredDevices, unregisteredDevices})}; + if (responseMessage == nullptr) + return; + m_outboundLocalMessageHandler->addMessage(responseMessage); + }); } case MessageType::DEVICE_REMOVAL: { diff --git a/gateway/service/devices/DevicesService.h b/gateway/service/devices/DevicesService.h index 0c32d85..5b3e7f5 100644 --- a/gateway/service/devices/DevicesService.h +++ b/gateway/service/devices/DevicesService.h @@ -187,7 +187,7 @@ class DevicesService : public GatewayMessageListener, public MessageListener */ virtual bool registerChildDevices( const std::vector& devices, - std::function&, const std::vector&)> callback); + const std::function&, const std::vector&)>& callback); /** * This is the method that should be run when the service is created and can use the connectivity objects. From a5c702fe4e3406148cc2db9df31d962a19c942d7 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Mon, 7 Feb 2022 16:44:39 +0100 Subject: [PATCH 04/20] Updated WC-Cpp --- WolkConnect-Cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 75137d5..2814cf4 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 75137d52f23a5c6becc5ca2ed9cc372a7b5d1292 +Subproject commit 2814cf404bcaa266e1c11b3985c523e60fbdd70d From 75672ebc63117fc3a782321e647639a895116ae5 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Tue, 8 Feb 2022 16:31:43 +0100 Subject: [PATCH 05/20] Cleaned up the functionalities, re-enabled the `deviceRemoval` by deleting a device key from `existingDevices.json` --- CMakeLists.txt | 7 +- gateway/WolkGateway.cpp | 5 +- gateway/WolkGateway.h | 2 +- gateway/WolkGatewayBuilder.cpp | 19 +- gateway/repository/DeviceFilter.h | 49 ++++ gateway/repository/DeviceOwnership.cpp | 45 ++++ gateway/repository/DeviceOwnership.h | 52 ++++ gateway/repository/device/DeviceRepository.h | 8 +- .../device/InMemoryDeviceRepository.cpp | 4 +- .../device/SQLiteDeviceRepository.cpp | 223 +++++++++++++++--- .../device/SQLiteDeviceRepository.h | 6 +- gateway/service/devices/DevicesService.cpp | 88 +++++-- gateway/service/devices/DevicesService.h | 24 +- tests/WolkGatewayTests.cpp | 5 + tests/mocks/DeviceRepositoryMock.h | 8 +- tests/mocks/DevicesServiceMock.h | 6 +- 16 files changed, 458 insertions(+), 93 deletions(-) create mode 100644 gateway/repository/DeviceFilter.h create mode 100644 gateway/repository/DeviceOwnership.cpp create mode 100644 gateway/repository/DeviceOwnership.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 004125b..4507ced 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,9 +99,10 @@ endif () # WolkGateway library set(LIB_SOURCE_FILES gateway/connectivity/GatewayMessageRouter.cpp + gateway/repository/DeviceOwnership.cpp gateway/repository/existing_device/JsonFileExistingDevicesRepository.cpp gateway/repository/device/InMemoryDeviceRepository.cpp - # gateway/repository/device/SQLiteDeviceRepository.cpp + gateway/repository/device/SQLiteDeviceRepository.cpp gateway/service/external_data/ExternalDataService.cpp gateway/service/internal_data/InternalDataService.cpp gateway/service/platform_status/GatewayPlatformStatusService.cpp @@ -111,11 +112,13 @@ set(LIB_SOURCE_FILES gateway/connectivity/GatewayMessageRouter.cpp set(LIB_HEADER_FILES gateway/api/DataHandler.h gateway/api/DataProvider.h gateway/connectivity/GatewayMessageRouter.h + gateway/repository/DeviceFilter.h + gateway/repository/DeviceOwnership.h gateway/repository/device/DeviceRepository.h gateway/repository/existing_device/ExistingDevicesRepository.h gateway/repository/existing_device/JsonFileExistingDevicesRepository.h gateway/repository/device/InMemoryDeviceRepository.h - # gateway/repository/device/SQLiteDeviceRepository.h + gateway/repository/device/SQLiteDeviceRepository.h gateway/service/external_data/ExternalDataService.h gateway/service/internal_data/InternalDataService.h gateway/service/devices/DevicesService.h diff --git a/gateway/WolkGateway.cpp b/gateway/WolkGateway.cpp index 9150306..cbf41ab 100644 --- a/gateway/WolkGateway.cpp +++ b/gateway/WolkGateway.cpp @@ -137,9 +137,8 @@ void WolkGateway::notifyPlatformConnected() WolkSingle::notifyConnected(); if (m_cacheDeviceRepository != nullptr) m_cacheDeviceRepository->loadInformationFromPersistentRepository(); - // TODO Uncomment - // if (m_subdeviceManagementService != nullptr) - // m_subdeviceManagementService->updateDeviceCache(); + if (m_subdeviceManagementService != nullptr) + m_subdeviceManagementService->updateDeviceCache(); if (m_gatewayPlatformStatusService != nullptr) m_gatewayPlatformStatusService->sendPlatformConnectionStatusMessage(true); } diff --git a/gateway/WolkGateway.h b/gateway/WolkGateway.h index 865ec90..de8351e 100644 --- a/gateway/WolkGateway.h +++ b/gateway/WolkGateway.h @@ -150,7 +150,7 @@ class WolkGateway : public connect::WolkSingle std::shared_ptr m_cacheDeviceRepository; std::shared_ptr m_persistentDeviceRepository; - std::unique_ptr m_existingDevicesRepository; + std::shared_ptr m_existingDevicesRepository; // Local connectivity stack std::shared_ptr m_localConnectivityService; diff --git a/gateway/WolkGatewayBuilder.cpp b/gateway/WolkGatewayBuilder.cpp index 2627dfd..c195204 100644 --- a/gateway/WolkGatewayBuilder.cpp +++ b/gateway/WolkGatewayBuilder.cpp @@ -34,7 +34,7 @@ #include "gateway/WolkGateway.h" #include "gateway/connectivity/GatewayMessageRouter.h" #include "gateway/repository/device/InMemoryDeviceRepository.h" -//#include "gateway/repository/device/SQLiteDeviceRepository.h" +#include "gateway/repository/device/SQLiteDeviceRepository.h" #include "gateway/repository/existing_device/JsonFileExistingDevicesRepository.h" #include "gateway/service/devices/DevicesService.h" #include "gateway/service/external_data/ExternalDataService.h" @@ -54,7 +54,7 @@ WolkGatewayBuilder::WolkGatewayBuilder(Device device) , m_platformMqttKeepAliveSec{60} , m_persistence{new InMemoryPersistence} , m_messagePersistence{new InMemoryMessagePersistence} -, m_deviceStoragePolicy{DeviceStoragePolicy::CACHED} +, m_deviceStoragePolicy{DeviceStoragePolicy::FULL} , m_existingDeviceRepository{new JsonFileExistingDevicesRepository} , m_dataProtocol{new WolkaboutDataProtocol} , m_errorProtocol{new WolkaboutErrorProtocol} @@ -270,12 +270,15 @@ std::unique_ptr WolkGatewayBuilder::build() wolk->m_messagePersistence = std::move(m_messagePersistence); // Move the repository objects - // TODO Uncomment - // if (m_deviceStoragePolicy == DeviceStoragePolicy::PERSISTENT || m_deviceStoragePolicy == - // DeviceStoragePolicy::FULL) - // wolk->m_persistentDeviceRepository = std::make_shared(); + if (m_deviceStoragePolicy == DeviceStoragePolicy::PERSISTENT || m_deviceStoragePolicy == DeviceStoragePolicy::FULL) + { + wolk->m_persistentDeviceRepository = std::make_shared(); + wolk->m_existingDevicesRepository = std::make_shared(); + } if (m_deviceStoragePolicy == DeviceStoragePolicy::CACHED || m_deviceStoragePolicy == DeviceStoragePolicy::FULL) + { wolk->m_cacheDeviceRepository = std::make_shared(wolk->m_persistentDeviceRepository); + } wolk->m_existingDevicesRepository = std::move(m_existingDeviceRepository); // Create the platform connection @@ -430,8 +433,8 @@ std::unique_ptr WolkGatewayBuilder::build() wolk->m_subdeviceManagementService = std::make_shared( m_device.getKey(), *wolk->m_platformRegistrationProtocol, *wolk->m_outboundMessageHandler, *wolk->m_outboundRetryMessageHandler, wolk->m_localRegistrationProtocol, wolk->m_localOutboundMessageHandler, - wolk->m_cacheDeviceRepository != nullptr ? wolk->m_cacheDeviceRepository : - wolk->m_persistentDeviceRepository); + wolk->m_cacheDeviceRepository != nullptr ? wolk->m_cacheDeviceRepository : wolk->m_persistentDeviceRepository, + wolk->m_existingDevicesRepository); wolk->m_gatewayMessageRouter->addListener("SubdeviceManagement", wolk->m_subdeviceManagementService); if (wolk->m_localConnectivityService != nullptr && wolk->m_localRegistrationProtocol != nullptr) wolk->m_localInboundMessageHandler->addListener(wolk->m_subdeviceManagementService); diff --git a/gateway/repository/DeviceFilter.h b/gateway/repository/DeviceFilter.h new file mode 100644 index 0000000..12cba72 --- /dev/null +++ b/gateway/repository/DeviceFilter.h @@ -0,0 +1,49 @@ +/** + * Copyright 2022 Wolkabout Technology s.r.o. + * + * Licensed 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 WOLKGATEWAY_DEVICEFILTER_H +#define WOLKGATEWAY_DEVICEFILTER_H + +#include + +namespace wolkabout +{ +namespace gateway +{ +/** + * This interface defines an object that is capable of filtering through devices that actually exist and can have data + * sent about them, and the ones that can not. + */ +class DeviceFilter +{ +public: + /** + * Default virtual destructor. + */ + virtual ~DeviceFilter() = default; + + /** + * This is the filtration method that determines whether information about device exists, or not. + * + * @param deviceKey The key of the device. + * @return Whether the device exists or not. + */ + virtual bool deviceExists(const std::string& deviceKey) = 0; +}; +} // namespace gateway +} // namespace wolkabout + +#endif // WOLKGATEWAY_DEVICEFILTER_H diff --git a/gateway/repository/DeviceOwnership.cpp b/gateway/repository/DeviceOwnership.cpp new file mode 100644 index 0000000..43775b2 --- /dev/null +++ b/gateway/repository/DeviceOwnership.cpp @@ -0,0 +1,45 @@ +/** + * Copyright 2022 Wolkabout Technology s.r.o. + * + * Licensed 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 "gateway/repository/DeviceOwnership.h" + +namespace wolkabout +{ +namespace gateway +{ +std::string toString(DeviceOwnership deviceOwnership) +{ + switch (deviceOwnership) + { + case DeviceOwnership::Platform: + return "Platform"; + case DeviceOwnership::Gateway: + return "Gateway"; + default: + return {}; + } +} + +DeviceOwnership deviceOwnershipFromString(const std::string& value) +{ + if (value == "Platform") + return DeviceOwnership::Platform; + else if (value == "Gateway") + return DeviceOwnership::Gateway; + return DeviceOwnership::None; +} +} // namespace gateway +} // namespace wolkabout diff --git a/gateway/repository/DeviceOwnership.h b/gateway/repository/DeviceOwnership.h new file mode 100644 index 0000000..c7b8091 --- /dev/null +++ b/gateway/repository/DeviceOwnership.h @@ -0,0 +1,52 @@ +/** + * Copyright 2022 Wolkabout Technology s.r.o. + * + * Licensed 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 WOLKGATEWAY_DEVICEOWNERSHIP_H +#define WOLKGATEWAY_DEVICEOWNERSHIP_H + +#include + +namespace wolkabout +{ +namespace gateway +{ +// This enum value describes who the device belongs to. +enum class DeviceOwnership +{ + None = -1, + Platform, + Gateway +}; + +/** + * This is a utility method that is used to convert the enumeration value into a string. + * + * @param deviceOwnership The DeviceOwnership value. + * @return The string representation of the value. + */ +std::string toString(DeviceOwnership deviceOwnership); + +/** + * This is a utility method that is used to convert a string into the enumeration value. + * + * @param value A string value. + * @return A DeviceOwnership value. If the value could not be parsed, will always be `DeviceOwnership::None`. + */ +DeviceOwnership deviceOwnershipFromString(const std::string& value); +} // namespace gateway +} // namespace wolkabout + +#endif // WOLKGATEWAY_DEVICEOWNERSHIP_H diff --git a/gateway/repository/device/DeviceRepository.h b/gateway/repository/device/DeviceRepository.h index ab43ab3..e1d67b3 100644 --- a/gateway/repository/device/DeviceRepository.h +++ b/gateway/repository/device/DeviceRepository.h @@ -18,6 +18,7 @@ #define DEVICEREPOSITORY_H #include "core/model/messages/RegisteredDevicesResponseMessage.h" +#include "gateway/repository/DeviceOwnership.h" #include #include @@ -28,13 +29,6 @@ namespace wolkabout { namespace gateway { -// This enum value describes who the device belongs to. -enum class DeviceOwnership -{ - Platform, - Gateway -}; - // This enum represents the data that is stored about a device. struct StoredDeviceInformation { diff --git a/gateway/repository/device/InMemoryDeviceRepository.cpp b/gateway/repository/device/InMemoryDeviceRepository.cpp index badda36..69d7156 100644 --- a/gateway/repository/device/InMemoryDeviceRepository.cpp +++ b/gateway/repository/device/InMemoryDeviceRepository.cpp @@ -40,7 +40,7 @@ void InMemoryDeviceRepository::loadInformationFromPersistentRepository() { // Copy all the gateway devices const auto gatewayDevices = m_persistentDeviceRepository->getGatewayDevices(); - std::copy(gatewayDevices.cbegin(), gatewayDevices.cend(), m_devices.begin()); + std::copy(gatewayDevices.cbegin(), gatewayDevices.cend(), std::back_inserter(m_devices)); // Copy the timestamp const auto loadedTimestamp = m_persistentDeviceRepository->latestPlatformTimestamp(); @@ -167,7 +167,7 @@ std::vector InMemoryDeviceRepository::getGatewayDevices auto gatewayDevices = std::vector{}; { std::lock_guard lock{m_mutex}; - std::copy_if(m_devices.cbegin(), m_devices.cend(), gatewayDevices.begin(), + std::copy_if(m_devices.cbegin(), m_devices.cend(), std::back_inserter(gatewayDevices), [&](const StoredDeviceInformation& information) { return information.getDeviceBelongsTo() == DeviceOwnership::Gateway; }); diff --git a/gateway/repository/device/SQLiteDeviceRepository.cpp b/gateway/repository/device/SQLiteDeviceRepository.cpp index 8893228..942ac3c 100644 --- a/gateway/repository/device/SQLiteDeviceRepository.cpp +++ b/gateway/repository/device/SQLiteDeviceRepository.cpp @@ -30,8 +30,9 @@ namespace wolkabout namespace gateway { // Here are some create table instructions -const std::string CREATE_DEVICE_TABLE = "CREATE TABLE IF NOT EXISTS Device (ID INTEGER PRIMARY KEY AUTOINCREMENT, " - "DeviceKey TEXT NOT NULL UNIQUE, ExternalId TEXT, Timestamp INTEGER NOT NULL);"; +const std::string CREATE_DEVICE_TABLE = + "CREATE TABLE IF NOT EXISTS Device (ID INTEGER PRIMARY KEY AUTOINCREMENT, DeviceKey TEXT NOT NULL UNIQUE, BelongsTo " + "TEXT CHECK( BelongsTo IN ('Platform', 'Gateway')), Timestamp INTEGER NOT NULL);"; SQLiteDeviceRepository::SQLiteDeviceRepository(const std::string& connectionString) : m_db(nullptr) { @@ -64,45 +65,81 @@ SQLiteDeviceRepository::~SQLiteDeviceRepository() } } -bool SQLiteDeviceRepository::save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& device) +bool SQLiteDeviceRepository::save(const std::vector& devices) { - const auto errorPrefix = "Failed to save a device in the database - "; + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to save devices in the database - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return false; + } // If the device is already present, go to the update routine - std::lock_guard lock{m_mutex}; - if (containsDevice(device.deviceKey)) - return update(timestamp, device); + auto newDevices = std::vector{}; + auto oldDevices = std::vector{}; + for (const auto& device : devices) + { + if (containsDevice(device.getDeviceKey())) + oldDevices.emplace_back(device); + else + newDevices.emplace_back(device); + } + + // Update the old devices + if (!oldDevices.empty()) + update(oldDevices); // Store the information about the device + std::lock_guard lock{m_mutex}; auto errorMessage = executeSQLStatement("BEGIN TRANSACTION;"); if (!errorMessage.empty()) { LOG(ERROR) << errorPrefix << "Failed to start the database transaction - '" << errorMessage << "'."; return false; } - - // Create the device - errorMessage = - executeSQLStatement("INSERT INTO Device(DeviceKey, ExternalId, Timestamp) VALUES ('" + device.deviceKey + "', " + - (device.externalId.empty() ? "null" : "'" + device.externalId + "'") + ", " + - std::to_string(timestamp.count()) + ");"); - if (!errorMessage.empty()) + for (const auto& device : newDevices) { - executeSQLStatement("ROLLBACK;"); - LOG(ERROR) << errorPrefix << "Failed to insert device info into the database - '" << errorMessage << "'."; - return false; + // Create the device + errorMessage = executeSQLStatement("INSERT INTO Device(DeviceKey, BelongsTo, Timestamp) VALUES ('" + + device.getDeviceKey() + "', '" + toString(device.getDeviceBelongsTo()) + + "', " + std::to_string(device.getTimestamp().count()) + ");"); + if (!errorMessage.empty()) + { + executeSQLStatement("ROLLBACK;"); + LOG(ERROR) << errorPrefix << "Failed to insert device info into the database - '" << errorMessage << "'."; + return false; + } } - executeSQLStatement("COMMIT;"); return true; } -bool SQLiteDeviceRepository::remove(const std::string& deviceKey) +bool SQLiteDeviceRepository::remove(const std::vector& deviceKeys) { - const auto errorPrefix = "Failed to remove a device from the database - "; + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to remove devices from the database - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return false; + } + + // Check if the vector is empty + if (deviceKeys.empty()) + { + LOG(ERROR) << errorPrefix << "The keys vector is empty."; + return false; + } + + // Make the string for the array in sql query + auto arrayString = std::string{"("}; + for (auto i = std::uint64_t{0}; i < deviceKeys.size(); ++i) + arrayString += "'" + deviceKeys[i] + "'" + (i < deviceKeys.size() - 1 ? ", " : ""); + arrayString += ")"; std::lock_guard lock{m_mutex}; - auto errorMessage = executeSQLStatement("DELETE FROM Device WHERE Device.DeviceKey = '" + deviceKey + "';"); + auto errorMessage = executeSQLStatement("DELETE FROM Device WHERE Device.DeviceKey IN " + arrayString + ";"); if (!errorMessage.empty()) { LOG(ERROR) << errorPrefix << "Failed to execute the query - '" << errorMessage << "'."; @@ -113,7 +150,13 @@ bool SQLiteDeviceRepository::remove(const std::string& deviceKey) bool SQLiteDeviceRepository::removeAll() { + // Establish the error prefix, and check whether a database session exists const auto errorPrefix = "Failed to remove all devices from the database - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return false; + } std::lock_guard lock{m_mutex}; auto errorMessage = executeSQLStatement("DELETE FROM Device;"); @@ -127,33 +170,99 @@ bool SQLiteDeviceRepository::removeAll() bool SQLiteDeviceRepository::containsDevice(const std::string& deviceKey) { - const auto errorPrefix = "Failed to obtain information whether device info is stored - "; + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to obtain information whether information about device exists - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return false; + } - std::lock_guard lock{m_mutex}; auto result = ColumnResult{}; - auto errorMessage = - executeSQLStatement("SELECT DeviceKey FROM Device WHERE Device.DeviceKey = '" + deviceKey + "';", &result); - if (!errorMessage.empty()) { - LOG(ERROR) << errorPrefix << "Failed to execute the query - '" << errorMessage << "'."; - return false; + std::lock_guard lock{m_mutex}; + auto errorMessage = + executeSQLStatement("SELECT DeviceKey FROM Device WHERE Device.DeviceKey = '" + deviceKey + "';", &result); + if (!errorMessage.empty()) + { + LOG(ERROR) << errorPrefix << "Failed to execute the query - '" << errorMessage << "'."; + return false; + } } return result.size() >= 2 && result[1].front() == deviceKey; } StoredDeviceInformation SQLiteDeviceRepository::get(const std::string& deviceKey) { - return StoredDeviceInformation(); + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to obtain information about a device - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return {}; + } + + auto result = ColumnResult{}; + { + std::lock_guard lock{m_mutex}; + auto errorMessage = executeSQLStatement( + "SELECT DeviceKey, BelongsTo, Timestamp FROM Device WHERE Device.DeviceKey = '" + deviceKey + "';", &result); + if (!errorMessage.empty()) + { + LOG(ERROR) << errorPrefix << "Failed to execute the query - '" << errorMessage << "'."; + return {}; + } + } + if (result.size() < 2) + { + LOG(DEBUG) << errorPrefix << "Device not found in the database."; + return {}; + } + return loadDeviceInformationFromRow(result, 1); } std::vector SQLiteDeviceRepository::getGatewayDevices() { - return std::vector(); + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to obtain information about a device - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return {}; + } + + auto result = ColumnResult{}; + { + std::lock_guard lock{m_mutex}; + auto errorMessage = executeSQLStatement( + "SELECT DeviceKey, BelongsTo, Timestamp FROM Device WHERE Device.BelongsTo = 'Gateway';", &result); + if (!errorMessage.empty()) + { + LOG(ERROR) << errorPrefix << "Failed to execute the query - '" << errorMessage << "'."; + return {}; + } + } + + auto devices = std::vector{}; + for (auto i = std::uint64_t{1}; i < result.size(); ++i) + { + auto device = loadDeviceInformationFromRow(result, i); + if (device.getDeviceKey().empty()) + return {}; + devices.emplace_back(std::move(device)); + } + return devices; } std::chrono::milliseconds SQLiteDeviceRepository::latestPlatformTimestamp() { + // Establish the error prefix, and check whether a database session exists const auto errorPrefix = "Failed to obtain the latest timestamp value - "; + if (m_db == nullptr) + { + LOG(ERROR) << errorPrefix << "The database connection is not established."; + return {}; + } std::lock_guard lock{m_mutex}; auto result = ColumnResult{}; @@ -181,13 +290,55 @@ std::chrono::milliseconds SQLiteDeviceRepository::latestPlatformTimestamp() bool SQLiteDeviceRepository::update(const std::vector& devices) { - auto keys = std::vector{}; - for (const auto& device : devices) - keys.emplace_back(device.getDeviceKey()); - if (remove(keys)) - return save(devices); - else + // Establish the error prefix, and check whether a database session exists + const auto errorPrefix = "Failed to update device information - "; + + // Store the information about the device + std::lock_guard lock{m_mutex}; + auto errorMessage = executeSQLStatement("BEGIN TRANSACTION;"); + if (!errorMessage.empty()) + { + LOG(ERROR) << errorPrefix << "Failed to start the database transaction - '" << errorMessage << "'."; return false; + } + for (const auto& device : devices) + { + // Create the device + errorMessage = executeSQLStatement("UPDATE Device SET BelongsTo = '" + toString(device.getDeviceBelongsTo()) + + "', Timestamp = " + std::to_string(device.getTimestamp().count()) + + " WHERE DeviceKey = '" + device.getDeviceKey() + "' LIMIT 1;"); + if (!errorMessage.empty()) + { + executeSQLStatement("ROLLBACK;"); + LOG(ERROR) << errorPrefix << "Failed to update device info in the database - '" << errorMessage << "'."; + return false; + } + } + executeSQLStatement("COMMIT;"); + return true; +} + +StoredDeviceInformation SQLiteDeviceRepository::loadDeviceInformationFromRow(ColumnResult& result, std::uint64_t row) +{ + const auto errorPrefix = "Failed to load device information - "; + + const auto belongsTo = deviceOwnershipFromString(result[row][1]); + if (belongsTo == DeviceOwnership::None) + { + LOG(ERROR) << errorPrefix << "Device contains invalid 'BelongsTo' value."; + return {}; + } + auto timestamp = std::chrono::milliseconds{}; + try + { + timestamp = std::chrono::milliseconds{std::stoull(result[row][2])}; + } + catch (const std::exception&) + { + LOG(ERROR) << errorPrefix << "Device 'Timestamp' value could not be parsed."; + return {}; + } + return {result[row][0], belongsTo, timestamp}; } std::string SQLiteDeviceRepository::executeSQLStatement(const std::string& sql, ColumnResult* result) diff --git a/gateway/repository/device/SQLiteDeviceRepository.h b/gateway/repository/device/SQLiteDeviceRepository.h index e1688b7..8d90abc 100644 --- a/gateway/repository/device/SQLiteDeviceRepository.h +++ b/gateway/repository/device/SQLiteDeviceRepository.h @@ -41,9 +41,9 @@ class SQLiteDeviceRepository : public DeviceRepository ~SQLiteDeviceRepository() override; - bool save(std::chrono::milliseconds timestamp, const RegisteredDeviceInformation& deviceKey) override; + bool save(const std::vector& devices) override; - bool remove(const std::vector& deviceKey) override; + bool remove(const std::vector& deviceKeys) override; bool removeAll() override; @@ -58,6 +58,8 @@ class SQLiteDeviceRepository : public DeviceRepository private: bool update(const std::vector& devices); + StoredDeviceInformation loadDeviceInformationFromRow(ColumnResult& result, std::uint64_t row); + std::string executeSQLStatement(const std::string& sqlStatement, ColumnResult* result = nullptr); std::recursive_mutex m_mutex; diff --git a/gateway/service/devices/DevicesService.cpp b/gateway/service/devices/DevicesService.cpp index 3f0dc23..3b25a64 100644 --- a/gateway/service/devices/DevicesService.cpp +++ b/gateway/service/devices/DevicesService.cpp @@ -145,7 +145,8 @@ DevicesService::DevicesService(std::string gatewayKey, RegistrationProtocol& pla OutboundRetryMessageHandler& outboundPlatformRetryMessageHandler, std::shared_ptr localRegistrationProtocol, std::shared_ptr outboundDeviceMessageHandler, - std::shared_ptr deviceRepository) + std::shared_ptr deviceRepository, + std::shared_ptr existingDevicesRepository) : m_gatewayKey{std::move(gatewayKey)} , m_platformProtocol{platformRegistrationProtocol} , m_outboundPlatformMessageHandler{outboundPlatformMessageHandler} @@ -153,6 +154,7 @@ DevicesService::DevicesService(std::string gatewayKey, RegistrationProtocol& pla , m_localProtocol{std::move(localRegistrationProtocol)} , m_outboundLocalMessageHandler{std::move(outboundDeviceMessageHandler)} , m_deviceRepository{std::move(deviceRepository)} +, m_existingDeviceRepository{std::move(existingDevicesRepository)} { } @@ -203,6 +205,25 @@ bool DevicesService::registerChildDevices( return true; } +bool DevicesService::removeChildDevices(const std::vector& deviceKeys) +{ + LOG(TRACE) << METHOD_INFO; + const auto errorPrefix = "Failed to remove child devices -> "; + + // Form the message for the registration + const auto parsedMessage = + std::shared_ptr{m_platformProtocol.makeOutboundMessage(m_gatewayKey, DeviceRemovalMessage{deviceKeys})}; + if (parsedMessage == nullptr) + { + LOG(ERROR) << errorPrefix << "Failed to parse the outbound 'DeviceRemovalMessage'."; + return false; + } + + // Publish the message + m_outboundPlatformMessageHandler.addMessage(parsedMessage); + return true; +} + void DevicesService::updateDeviceCache() { LOG(TRACE) << METHOD_INFO; @@ -213,11 +234,30 @@ void DevicesService::updateDeviceCache() LOG(WARN) << "Skipping update device cache - no device repository exists..."; return; } + // If we have an existing device repository, we want to check whether the user wants any devices deleted. + if (m_existingDeviceRepository != nullptr) + { + const auto gatewayDevices = m_deviceRepository->getGatewayDevices(); + const auto keys = m_existingDeviceRepository->getDeviceKeys(); + auto toDelete = std::vector{}; + for (const auto& gatewayDevice : gatewayDevices) + { + const auto it = std::find(keys.cbegin(), keys.cend(), gatewayDevice.getDeviceKey()); + if (it == keys.cend()) + toDelete.emplace_back(gatewayDevice.getDeviceKey()); + } + if (removeChildDevices(toDelete)) + m_deviceRepository->remove(toDelete); + else + LOG(ERROR) << "Failed to send out a 'DeviceRemoval' request to remove devices deleted from " + "'ExistingDeviceRepository'."; + } // Obtain the last timestamp and send out a request auto lastTimestamp = m_deviceRepository->latestPlatformTimestamp(); LOG(DEBUG) << TAG << "Obtaining devices from timestamp " << lastTimestamp.count() << "."; sendOutRegisteredDevicesRequest(RegisteredDevicesRequestParameters{lastTimestamp}, {}); + sendOutChildrenSynchronizationRequest({}); } bool DevicesService::sendOutChildrenSynchronizationRequest( @@ -242,21 +282,25 @@ bool DevicesService::sendOutChildrenSynchronizationRequest( << TAG << "Failed to receive response for 'ChildrenSynchronizationRequestMessage' - no response from platform."; // Check the callback - if (!callback->getRegisteringDevices().empty()) + if (callback != nullptr) { - LOG(ERROR) << "Failed to register devices: "; - for (const auto& device : callback->getRegisteringDevices()) - LOG(ERROR) << "\t" << device; + if (!callback->getRegisteringDevices().empty()) + { + LOG(ERROR) << "Failed to register devices: "; + for (const auto& device : callback->getRegisteringDevices()) + LOG(ERROR) << "\t" << device; + } + if (auto cv = callback->getConditionVariable().lock()) + cv->notify_one(); + if (callback->getLambda()) + callback->getLambda()(nullptr); } - if (auto cv = callback->getConditionVariable().lock()) - cv->notify_one(); - if (callback->getLambda()) - callback->getLambda()(nullptr); }, RETRY_COUNT, RETRY_TIMEOUT}); { std::lock_guard lock{m_childSyncMutex}; - m_childSyncRequests.push(std::move(callback)); + if (callback != nullptr) + m_childSyncRequests.push(std::move(callback)); } return true; } @@ -454,6 +498,11 @@ std::vector DevicesService::getMessageTypes() return {MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE, MessageType::REGISTERED_DEVICES_RESPONSE}; } +bool DevicesService::deviceExists(const std::string& deviceKey) +{ + return m_deviceRepository == nullptr || m_deviceRepository->containsDevice(deviceKey); +} + void DevicesService::handleChildrenSynchronizationResponse( std::unique_ptr response) { @@ -475,12 +524,18 @@ void DevicesService::handleChildrenSynchronizationResponse( if (m_deviceRepository != nullptr) { auto devicesToSave = std::vector{}; - if (m_deviceRepository != nullptr) - for (const auto& device : response->getChildren()) - devicesToSave.emplace_back( - StoredDeviceInformation{device, DeviceOwnership::Gateway, std::chrono::milliseconds{0}}); + for (const auto& device : response->getChildren()) + devicesToSave.emplace_back( + StoredDeviceInformation{device, DeviceOwnership::Gateway, std::chrono::milliseconds{0}}); m_deviceRepository->save(devicesToSave); } + if (m_existingDeviceRepository != nullptr) + { + auto savedDevices = m_existingDeviceRepository->getDeviceKeys(); + for (const auto& device : response->getChildren()) + if (std::find(savedDevices.cbegin(), savedDevices.cend(), device) == savedDevices.cend()) + m_existingDeviceRepository->addDeviceKey(device); + } // Handle the callback if (callback != nullptr) @@ -533,10 +588,5 @@ void DevicesService::handleRegisteredDevicesResponse(std::unique_ptrgetLambda()(std::move(response)); } } - -void DevicesService::onSucceededRegistration(const std::vector& deviceKeys) -{ - LOG(TRACE) << METHOD_INFO; -} } // namespace gateway } // namespace wolkabout diff --git a/gateway/service/devices/DevicesService.h b/gateway/service/devices/DevicesService.h index 5b3e7f5..f193336 100644 --- a/gateway/service/devices/DevicesService.h +++ b/gateway/service/devices/DevicesService.h @@ -19,6 +19,7 @@ #include "core/MessageListener.h" #include "gateway/GatewayMessageListener.h" +#include "gateway/repository/DeviceFilter.h" #include #include @@ -151,7 +152,7 @@ struct ChildrenSynchronizationRequestCallback * sub-devices to the platform regarding registration, deletion, and also registered devices. This service will also * keep a cache of registered devices. */ -class DevicesService : public GatewayMessageListener, public MessageListener +class DevicesService : public GatewayMessageListener, public MessageListener, public DeviceFilter { public: /** @@ -171,7 +172,8 @@ class DevicesService : public GatewayMessageListener, public MessageListener OutboundRetryMessageHandler& outboundPlatformRetryMessageHandler, std::shared_ptr localRegistrationProtocol = nullptr, std::shared_ptr outboundDeviceMessageHandler = nullptr, - std::shared_ptr deviceRepository = nullptr); + std::shared_ptr deviceRepository = nullptr, + std::shared_ptr existingDevicesRepository = nullptr); /** * Overridden destructor. @@ -189,6 +191,14 @@ class DevicesService : public GatewayMessageListener, public MessageListener const std::vector& devices, const std::function&, const std::vector&)>& callback); + /** + * Method that is used to remove child devices on the platform. + * + * @param deviceKeys The list of device keys the user would like to delete. + * @return Whether the removal request has been successfully sent out. + */ + virtual bool removeChildDevices(const std::vector& deviceKeys); + /** * This is the method that should be run when the service is created and can use the connectivity objects. * This method will check when the DeviceRepository was last updated, and will request the list of devices that have @@ -251,18 +261,13 @@ class DevicesService : public GatewayMessageListener, public MessageListener */ std::vector getMessageTypes() override; + bool deviceExists(const std::string& deviceKey) override; + private: void handleChildrenSynchronizationResponse(std::unique_ptr response); void handleRegisteredDevicesResponse(std::unique_ptr response); - /** - * This method is used to handle the logic when devices are verified to be registered. - * - * @param deviceKeys The list of devices that have been successfully registered. - */ - void onSucceededRegistration(const std::vector& deviceKeys); - // Logging tag const std::string TAG = "[DevicesService] -> "; @@ -280,6 +285,7 @@ class DevicesService : public GatewayMessageListener, public MessageListener // Optional device repository std::shared_ptr m_deviceRepository; + std::shared_ptr m_existingDeviceRepository; // Storage for request objects std::mutex m_childSyncMutex; diff --git a/tests/WolkGatewayTests.cpp b/tests/WolkGatewayTests.cpp index 5899251..d692403 100644 --- a/tests/WolkGatewayTests.cpp +++ b/tests/WolkGatewayTests.cpp @@ -20,6 +20,7 @@ #define private public #define protected public #include "gateway/WolkGateway.h" +#include "gateway/repository/device/InMemoryDeviceRepository.h" #undef private #undef protected @@ -51,6 +52,7 @@ class WolkGatewayTests : public Test { dataServiceMock = std::unique_ptr{new DataServiceMock{ dataProtocolMock, persistenceMock, connectivityServiceMock, outboundRetryMessageHandlerMock, {}, {}, {}}}; + deviceRepositoryMock = std::make_shared(); devicesServiceMock = std::unique_ptr{new DevicesServiceMock{ gateway.getKey(), registrationProtocolMock, outboundMessageHandlerMock, outboundRetryMessageHandlerMock}}; gatewayPlatformStatusServiceMock = std::unique_ptr{ @@ -76,6 +78,8 @@ class WolkGatewayTests : public Test OutboundRetryMessageHandlerMock outboundRetryMessageHandlerMock{outboundMessageHandlerMock}; + std::shared_ptr deviceRepositoryMock; + std::unique_ptr devicesServiceMock; GatewayPlatformStatusProtocolMock gatewayPlatformStatusProtocolMock; @@ -174,6 +178,7 @@ TEST_F(WolkGatewayTests, ConnectHappyFlow) EXPECT_CALL(*devicesServiceMock, updateDeviceCache).Times(1); EXPECT_CALL(*gatewayPlatformStatusServiceMock, sendPlatformConnectionStatusMessage).Times(2); service->m_dataService = std::move(dataServiceMock); + service->m_cacheDeviceRepository = std::move(deviceRepositoryMock); service->m_subdeviceManagementService = std::move(devicesServiceMock); service->m_gatewayPlatformStatusService = std::move(gatewayPlatformStatusServiceMock); diff --git a/tests/mocks/DeviceRepositoryMock.h b/tests/mocks/DeviceRepositoryMock.h index b71abf9..f9b2ec3 100644 --- a/tests/mocks/DeviceRepositoryMock.h +++ b/tests/mocks/DeviceRepositoryMock.h @@ -27,10 +27,12 @@ using namespace wolkabout::gateway; class DeviceRepositoryMock : public DeviceRepository { public: - MOCK_METHOD(bool, save, (std::chrono::milliseconds, const RegisteredDeviceInformation&)); - MOCK_METHOD(bool, remove, (const std::string&)); + MOCK_METHOD(bool, save, (const std::vector&)); + MOCK_METHOD(bool, remove, (const std::vector&)); MOCK_METHOD(bool, removeAll, ()); - MOCK_METHOD(bool, containsDeviceKey, (const std::string&)); + MOCK_METHOD(bool, containsDevice, (const std::string&)); + MOCK_METHOD(StoredDeviceInformation, get, (const std::string&)); + MOCK_METHOD(std::vector, getGatewayDevices, ()); MOCK_METHOD(std::chrono::milliseconds, latestPlatformTimestamp, ()); }; diff --git a/tests/mocks/DevicesServiceMock.h b/tests/mocks/DevicesServiceMock.h index 34c0186..4451227 100644 --- a/tests/mocks/DevicesServiceMock.h +++ b/tests/mocks/DevicesServiceMock.h @@ -38,9 +38,13 @@ class DevicesServiceMock : public DevicesService std::move(outboundDeviceMessageHandler), std::move(deviceRepository)) { } + MOCK_METHOD(bool, registerChildDevices, + (const std::vector&, + const std::function&, const std::vector&)>&)); MOCK_METHOD(void, updateDeviceCache, ()); + MOCK_METHOD(bool, sendOutChildrenSynchronizationRequest, (std::shared_ptr)); MOCK_METHOD(bool, sendOutRegisteredDevicesRequest, - (RegisteredDevicesRequestParameters, RegisteredDevicesRequestCallback)); + (RegisteredDevicesRequestParameters, std::shared_ptr)); }; #endif // WOLKGATEWAY_DEVICESSERVICEMOCK_H From 0890ec64bb29ec7c348f1b5b942e822a08b36419 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 11 Feb 2022 14:52:57 +0100 Subject: [PATCH 06/20] Inlined with newest `WolkConnect-Cpp` --- WolkConnect-Cpp | 2 +- out/gatewayConfiguration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 2814cf4..9fdd550 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 2814cf404bcaa266e1c11b3985c523e60fbdd70d +Subproject commit 9fdd550915fe658e11b46c1a293c34ec9d7fe467 diff --git a/out/gatewayConfiguration.json b/out/gatewayConfiguration.json index 906e090..509eadd 100644 --- a/out/gatewayConfiguration.json +++ b/out/gatewayConfiguration.json @@ -2,7 +2,7 @@ "name": "", "key": "AWG", - "password": "MBEDX6XVG0", + "password": "G76ZE6LT3C", "platformMqttUri": "ssl://integration5.wolkabout.com:8883", "platformMqttKeepAliveSeconds": 60, From 7ca17340c8a0d054ab8679b1f42ad58a003be1a9 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 11 Feb 2022 16:02:43 +0100 Subject: [PATCH 07/20] Changed the `make_zip.sh` to `--init --recursive` the submodules --- CMakeLists.txt | 2 +- tools/make_zip.sh | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4507ced..1183c70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN") set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${WOLKGATEWAY_VERSION_STRING} SOVERSION ${WOLKGATEWAY_VERSION_MAJOR}.${WOLKGATEWAY_VERSION_MINOR}.${WOLKGATEWAY_VERSION_PATCH}) # Create the install rule -install(DIRECTORY ${CMAKE_LIBRARY_INCLUDE_DIRECTORY} DESTINATION ${CMAKE_INSTALL_PREFIX}/include PATTERN *.h) +install(DIRECTORY ${CMAKE_LIBRARY_INCLUDE_DIRECTORY} DESTINATION ${CMAKE_INSTALL_PREFIX} PATTERN *.h) install(DIRECTORY ${CMAKE_PREFIX_PATH}/include DESTINATION ${CMAKE_INSTALL_PREFIX} PATTERN *.h) install(DIRECTORY ${CMAKE_PREFIX_PATH}/lib/ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/tools/make_zip.sh b/tools/make_zip.sh index a7587dd..783f2f6 100755 --- a/tools/make_zip.sh +++ b/tools/make_zip.sh @@ -45,11 +45,7 @@ if [ $? -ne 0 ]; then echo "Can't checkout to branch named $branch" exit fi -git submodule update --recursive -cd ./WolkSDK-Cpp || exit -git submodule init -git submodule update -cd .. || exit +git submodule update --init --recursive filename="WolkGateway-v$(cat RELEASE_NOTES.txt | grep "**Version" | head -1 | sed -e "s/**Version //" | sed -e "s/\*\*//").zip" echo "filename: $filename" zip -qr $filename * From e445a909afdf3a736cbcfff77b5631575121f3d0 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 17 Feb 2022 15:44:14 +0100 Subject: [PATCH 08/20] Finished all unit tests, 100% --- gateway/service/devices/DevicesService.cpp | 111 +++- gateway/service/devices/DevicesService.h | 16 +- out/coverage.sh | 2 +- tests/DevicesServiceTests.cpp | 642 ++++++++++++++++++++- 4 files changed, 724 insertions(+), 47 deletions(-) diff --git a/gateway/service/devices/DevicesService.cpp b/gateway/service/devices/DevicesService.cpp index 3b25a64..905a82f 100644 --- a/gateway/service/devices/DevicesService.cpp +++ b/gateway/service/devices/DevicesService.cpp @@ -80,7 +80,7 @@ std::uint64_t RegisteredDevicesRequestParametersHash::operator()(const Registere } RegisteredDevicesRequestCallback::RegisteredDevicesRequestCallback( - std::function)> lambda) + std::function)> lambda) : m_lambda{std::move(lambda)} { } @@ -96,7 +96,7 @@ const std::chrono::milliseconds& RegisteredDevicesRequestCallback::getSentTime() return m_sentTime; } -const std::function)>& +const std::function)>& RegisteredDevicesRequestCallback::getLambda() const { return m_lambda; @@ -108,7 +108,7 @@ const std::weak_ptr& RegisteredDevicesRequestCallback:: } ChildrenSynchronizationRequestCallback::ChildrenSynchronizationRequestCallback( - std::function)> lambda, + std::function)> lambda, std::vector registeringDevices) : m_registeringDevices{std::move(registeringDevices)}, m_lambda{std::move(lambda)} { @@ -120,17 +120,12 @@ ChildrenSynchronizationRequestCallback::ChildrenSynchronizationRequestCallback( { } -const std::chrono::milliseconds& ChildrenSynchronizationRequestCallback::getSentTime() const -{ - return m_sentTime; -} - const std::vector& ChildrenSynchronizationRequestCallback::getRegisteringDevices() const { return m_registeringDevices; } -const std::function)>& +const std::function)>& ChildrenSynchronizationRequestCallback::getLambda() const { return m_lambda; @@ -167,6 +162,23 @@ bool DevicesService::registerChildDevices( LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to register child devices -> "; + // Check that the vector is not empty + if (devices.empty()) + { + LOG(ERROR) << errorPrefix << "The vector containing devices is empty."; + return false; + } + auto deviceKeys = std::vector{}; + for (const auto& device : devices) + { + if (device.name.empty() || device.key.empty()) + { + LOG(ERROR) << errorPrefix << "One of the devices has an empty name/key."; + return false; + } + deviceKeys.emplace_back(device.key); + } + // Form the message for the registration const auto parsedMessage = std::shared_ptr{ m_platformProtocol.makeOutboundMessage(m_gatewayKey, DeviceRegistrationMessage{devices})}; @@ -180,11 +192,8 @@ bool DevicesService::registerChildDevices( m_outboundPlatformMessageHandler.addMessage(parsedMessage); // Now that that's publish, we want to verify that with the ChildrenSynchronizationMessage - auto deviceKeys = std::vector{}; - for (const auto& device : devices) - deviceKeys.emplace_back(device.key); sendOutChildrenSynchronizationRequest(std::make_shared( - [=](const std::unique_ptr& response) { + [=](const std::shared_ptr& response) { // Contains all devices auto succeeded = std::vector{}; auto failed = std::vector{}; @@ -210,6 +219,19 @@ bool DevicesService::removeChildDevices(const std::vector& deviceKe LOG(TRACE) << METHOD_INFO; const auto errorPrefix = "Failed to remove child devices -> "; + // Check that the vector of device keys is not empty + if (deviceKeys.empty()) + { + LOG(ERROR) << errorPrefix << "The vector containing device keys is empty."; + return false; + } + if (std::any_of(deviceKeys.cbegin(), deviceKeys.cend(), + [&](const std::string& deviceKey) { return deviceKey.empty(); })) + { + LOG(ERROR) << errorPrefix << "One of the device keys in the vector is empty."; + return false; + } + // Form the message for the registration const auto parsedMessage = std::shared_ptr{m_platformProtocol.makeOutboundMessage(m_gatewayKey, DeviceRemovalMessage{deviceKeys})}; @@ -246,11 +268,14 @@ void DevicesService::updateDeviceCache() if (it == keys.cend()) toDelete.emplace_back(gatewayDevice.getDeviceKey()); } - if (removeChildDevices(toDelete)) - m_deviceRepository->remove(toDelete); - else - LOG(ERROR) << "Failed to send out a 'DeviceRemoval' request to remove devices deleted from " - "'ExistingDeviceRepository'."; + if (!toDelete.empty()) + { + if (removeChildDevices(toDelete)) + m_deviceRepository->remove(toDelete); + else + LOG(ERROR) << "Failed to send out a 'DeviceRemoval' request to remove devices deleted from " + "'ExistingDeviceRepository'."; + } } // Obtain the last timestamp and send out a request @@ -297,10 +322,10 @@ bool DevicesService::sendOutChildrenSynchronizationRequest( } }, RETRY_COUNT, RETRY_TIMEOUT}); + if (callback != nullptr) { std::lock_guard lock{m_childSyncMutex}; - if (callback != nullptr) - m_childSyncRequests.push(std::move(callback)); + m_childSyncRequests.push(std::move(callback)); } return true; } @@ -331,8 +356,16 @@ bool DevicesService::sendOutRegisteredDevicesRequest(RegisteredDevicesRequestPar m_platformProtocol.getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, m_gatewayKey), [=](const std::shared_ptr&) { LOG(ERROR) << TAG << "Failed to receive response for 'RegisteredDevicesRequest' - no response from platform."; + if (callback != nullptr) + { + if (auto cv = callback->getConditionVariable().lock()) + cv->notify_one(); + if (callback->getLambda()) + callback->getLambda()(nullptr); + } }, RETRY_COUNT, RETRY_TIMEOUT}); + // This callback can be null. In the case it is null, the data will go into the repositories m_registeredDevicesRequests.emplace(std::move(parameters), std::move(callback)); return true; } @@ -348,6 +381,13 @@ void DevicesService::messageReceived(std::shared_ptr message) return; } + // Check that the message is not null + if (message == nullptr) + { + LOG(ERROR) << "Received message that is a `nullptr`."; + return; + } + // Figure out the message type and the key of the device that has sent it auto messageType = m_localProtocol->getMessageType(*message); auto deviceKey = m_localProtocol->getDeviceKey(*message); @@ -367,14 +407,13 @@ void DevicesService::messageReceived(std::shared_ptr message) // Send the message registerChildDevices(parsedMessage->getDevices(), [=](const std::vector& registeredDevices, const std::vector& unregisteredDevices) { - if (m_localProtocol == nullptr) - return; auto responseMessage = std::shared_ptr{m_localProtocol->makeOutboundMessage( deviceKey, DeviceRegistrationResponseMessage{registeredDevices, unregisteredDevices})}; if (responseMessage == nullptr) return; m_outboundLocalMessageHandler->addMessage(responseMessage); }); + break; } case MessageType::DEVICE_REMOVAL: { @@ -399,6 +438,7 @@ void DevicesService::messageReceived(std::shared_ptr message) // Send the message m_outboundPlatformMessageHandler.addMessage(request); + break; } case MessageType::REGISTERED_DEVICES_REQUEST: { @@ -423,7 +463,14 @@ void DevicesService::messageReceived(std::shared_ptr message) if (m_localProtocol != nullptr && m_outboundLocalMessageHandler != nullptr) { callback = std::make_shared( - [this, deviceKey](std::unique_ptr response) { + [this, deviceKey](std::shared_ptr response) { + // Check if the response is not null + if (response == nullptr) + { + LOG(ERROR) << TAG << "Failed to received response for local 'RegisteredDevicesRequest' message."; + return; + } + // Create the message for the local broker auto localResponse = std::shared_ptr{m_localProtocol->makeOutboundMessage(deviceKey, *response)}; @@ -500,7 +547,7 @@ std::vector DevicesService::getMessageTypes() bool DevicesService::deviceExists(const std::string& deviceKey) { - return m_deviceRepository == nullptr || m_deviceRepository->containsDevice(deviceKey); + return m_deviceRepository != nullptr && m_deviceRepository->containsDevice(deviceKey); } void DevicesService::handleChildrenSynchronizationResponse( @@ -541,9 +588,15 @@ void DevicesService::handleChildrenSynchronizationResponse( if (callback != nullptr) { if (auto cv = callback->getConditionVariable().lock()) + { cv->notify_one(); + } else if (callback->getLambda()) - callback->getLambda()(std::move(response)); + { + auto sharedMessage = std::shared_ptr{response.release()}; + m_commandBuffer.pushCommand(std::make_shared>( + [callback, sharedMessage] { callback->getLambda()(sharedMessage); })); + } } } @@ -583,9 +636,15 @@ void DevicesService::handleRegisteredDevicesResponse(std::unique_ptrgetConditionVariable().lock()) + { cv->notify_one(); + } else if (callback->getLambda()) - callback->getLambda()(std::move(response)); + { + auto sharedMessage = std::shared_ptr{response.release()}; + m_commandBuffer.pushCommand(std::make_shared>( + [callback, sharedMessage] { callback->getLambda()(sharedMessage); })); + } } } } // namespace gateway diff --git a/gateway/service/devices/DevicesService.h b/gateway/service/devices/DevicesService.h index f193336..0fbcbcf 100644 --- a/gateway/service/devices/DevicesService.h +++ b/gateway/service/devices/DevicesService.h @@ -18,6 +18,7 @@ #define SUBDEVICEREGISTRATIONSERVICE_H #include "core/MessageListener.h" +#include "core/utilities/CommandBuffer.h" #include "gateway/GatewayMessageListener.h" #include "gateway/repository/DeviceFilter.h" @@ -90,13 +91,13 @@ struct RegisteredDevicesRequestCallback RegisteredDevicesRequestCallback() = default; explicit RegisteredDevicesRequestCallback( - std::function)> lambda); + std::function)> lambda); explicit RegisteredDevicesRequestCallback(std::weak_ptr conditionVariable); const std::chrono::milliseconds& getSentTime() const; - const std::function)>& getLambda() const; + const std::function)>& getLambda() const; const std::weak_ptr& getConditionVariable() const; @@ -106,7 +107,7 @@ struct RegisteredDevicesRequestCallback std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); // Potential lambda expression that needs to be invoked - std::function)> m_lambda; + std::function)> m_lambda; // Potential condition variable that needs to be notified std::weak_ptr m_conditionVariable; @@ -118,17 +119,15 @@ struct ChildrenSynchronizationRequestCallback ChildrenSynchronizationRequestCallback() = default; explicit ChildrenSynchronizationRequestCallback( - std::function)> lambda, + std::function)> lambda, std::vector registeringDevices = {}); explicit ChildrenSynchronizationRequestCallback(std::weak_ptr conditionVariable, std::vector registeringDevices = {}); - const std::chrono::milliseconds& getSentTime() const; - const std::vector& getRegisteringDevices() const; - const std::function)>& getLambda() const; + const std::function)>& getLambda() const; const std::weak_ptr& getConditionVariable() const; @@ -141,7 +140,7 @@ struct ChildrenSynchronizationRequestCallback std::vector m_registeringDevices; // Potential lambda expression that needs to be invoked - std::function)> m_lambda; + std::function)> m_lambda; // Potential condition variable that needs to be notified std::weak_ptr m_conditionVariable; @@ -288,6 +287,7 @@ class DevicesService : public GatewayMessageListener, public MessageListener, pu std::shared_ptr m_existingDeviceRepository; // Storage for request objects + CommandBuffer m_commandBuffer; std::mutex m_childSyncMutex; std::queue> m_childSyncRequests; std::mutex m_registeredDevicesMutex; diff --git a/out/coverage.sh b/out/coverage.sh index 5b26b25..391dae2 100755 --- a/out/coverage.sh +++ b/out/coverage.sh @@ -5,5 +5,5 @@ echo "${WORK_DIR}" rm ./coverage.info rm -rf ./coverage lcov -b . -c -d . -o ./coverage.info -lcov -r ./coverage.info '/usr/*' "${WORK_DIR}/**/*.h" "${WORK_DIR}/WolkSDK-Cpp/*" "${WORK_DIR}/WolkConnect-Cpp/*" "${WORK_DIR}/application/*" "${WORK_DIR}/out/*" "${WORK_DIR}/tests/*" -o ./coverage.info +lcov -r ./coverage.info '/usr/*' "${WORK_DIR}/**/*.h" "${WORK_DIR}/gateway/repository/*" "${WORK_DIR}/WolkSDK-Cpp/*" "${WORK_DIR}/WolkConnect-Cpp/*" "${WORK_DIR}/application/*" "${WORK_DIR}/out/*" "${WORK_DIR}/tests/*" -o ./coverage.info genhtml -o ./coverage ./coverage.info diff --git a/tests/DevicesServiceTests.cpp b/tests/DevicesServiceTests.cpp index d9fbc92..bdde236 100644 --- a/tests/DevicesServiceTests.cpp +++ b/tests/DevicesServiceTests.cpp @@ -25,6 +25,7 @@ #include "core/utilities/Logger.h" #include "tests/mocks/DeviceRepositoryMock.h" +#include "tests/mocks/ExistingDeviceRepositoryMock.h" #include "tests/mocks/GatewayRegistrationProtocolMock.h" #include "tests/mocks/OutboundMessageHandlerMock.h" #include "tests/mocks/OutboundRetryMessageHandlerMock.h" @@ -43,28 +44,645 @@ class DevicesServiceTests : public Test void SetUp() override { - m_gatewayRegistrationProtocolMock = std::make_shared>(); - m_localOutboundMessageHandlerMock = std::make_shared>(); - m_deviceRepositoryMock = std::make_shared>(); + registrationProtocolMock = std::make_shared>(); + platformOutboundMessageHandlerMock = std::make_shared>(); + platformOutboundRetryMessageHandlerMock = + std::make_shared>(*platformOutboundMessageHandlerMock); + gatewayRegistrationProtocolMock = std::make_shared>(); + localOutboundMessageHandlerMock = std::make_shared>(); + deviceRepositoryMock = std::make_shared>(); + existingDevicesRepositoryMock = std::make_shared(); service = std::unique_ptr{ - new DevicesService{GATEWAY_KEY, m_registrationProtocol, m_platformOutboundMessageHandlerMock, - m_platformOutboundRetryMessageHandlerMock, m_gatewayRegistrationProtocolMock, - m_localOutboundMessageHandlerMock, m_deviceRepositoryMock}}; + new DevicesService{GATEWAY_KEY, *registrationProtocolMock, *platformOutboundMessageHandlerMock, + *platformOutboundRetryMessageHandlerMock, gatewayRegistrationProtocolMock, + localOutboundMessageHandlerMock, deviceRepositoryMock, existingDevicesRepositoryMock}}; } std::unique_ptr service; const std::string GATEWAY_KEY = "TEST_GATEWAY"; - NiceMock m_registrationProtocol; + const std::string DEVICE_KEY = "TEST_DEVICE"; - NiceMock m_platformOutboundMessageHandlerMock; + std::shared_ptr registrationProtocolMock; - NiceMock m_platformOutboundRetryMessageHandlerMock; + std::shared_ptr platformOutboundMessageHandlerMock; - std::shared_ptr m_gatewayRegistrationProtocolMock; + std::shared_ptr platformOutboundRetryMessageHandlerMock; - std::shared_ptr m_localOutboundMessageHandlerMock; + std::shared_ptr gatewayRegistrationProtocolMock; - std::shared_ptr m_deviceRepositoryMock; + std::shared_ptr localOutboundMessageHandlerMock; + + std::shared_ptr deviceRepositoryMock; + + std::shared_ptr existingDevicesRepositoryMock; + + std::mutex mutex; + std::condition_variable conditionVariable; }; + +TEST_F(DevicesServiceTests, GetProtocolLocalCommunicationDisabled) +{ + service->m_localProtocol = nullptr; + EXPECT_THROW(service->getProtocol(), std::runtime_error); +} + +TEST_F(DevicesServiceTests, GetProtocol) +{ + EXPECT_EQ(&(service->getProtocol()), gatewayRegistrationProtocolMock.get()); +} + +TEST_F(DevicesServiceTests, MessageTypes) +{ + auto types = std::vector{}; + ASSERT_NO_FATAL_FAILURE(types = service->getMessageTypes()); + EXPECT_EQ(types.size(), 2); + EXPECT_FALSE(std::find(types.cbegin(), types.cend(), MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE) == + types.cend()); + EXPECT_FALSE(std::find(types.cbegin(), types.cend(), MessageType::REGISTERED_DEVICES_RESPONSE) == types.cend()); +} + +TEST_F(DevicesServiceTests, DeviceExistsNoRepository) +{ + service->m_deviceRepository = nullptr; + EXPECT_FALSE(service->deviceExists(DEVICE_KEY)); +} + +TEST_F(DevicesServiceTests, DeviceExistsRepository) +{ + EXPECT_CALL(*deviceRepositoryMock, containsDevice(DEVICE_KEY)).WillOnce(Return(true)); + EXPECT_TRUE(service->deviceExists(DEVICE_KEY)); +} + +TEST_F(DevicesServiceTests, HandleChildrenSynchronizationResponseWithConditionVariable) +{ + // Create a callback that will be invoked + auto sharedConditionVariable = std::make_shared(); + service->m_childSyncRequests.push( + std::make_shared(sharedConditionVariable)); + + // Set up the repositories to be invoked + EXPECT_CALL(*deviceRepositoryMock, save).Times(1); + EXPECT_CALL(*existingDevicesRepositoryMock, getDeviceKeys).WillOnce(Return(std::vector{"Child1"})); + EXPECT_CALL(*existingDevicesRepositoryMock, addDeviceKey).Times(1); + + // Invoke the method + ASSERT_NO_FATAL_FAILURE( + service->handleChildrenSynchronizationResponse(std::unique_ptr{ + new ChildrenSynchronizationResponseMessage{{"Child1", "Child2"}}})); +} + +TEST_F(DevicesServiceTests, HandleChildrenSynchronizationResponseWithCallback) +{ + // Create a callback that will be invoked + auto responseMessage = std::unique_ptr{ + new ChildrenSynchronizationResponseMessage{{"Child1", "Child2"}}}; + const auto address = responseMessage.get(); + std::atomic_bool called{false}; + service->m_childSyncRequests.push(std::make_shared( + [&](const std::shared_ptr& message) { + EXPECT_EQ(address, message.get()); + called = true; + conditionVariable.notify_one(); + })); + + // Set up the repositories to be invoked + EXPECT_CALL(*deviceRepositoryMock, save).Times(1); + EXPECT_CALL(*existingDevicesRepositoryMock, getDeviceKeys).WillOnce(Return(std::vector{"Child1"})); + EXPECT_CALL(*existingDevicesRepositoryMock, addDeviceKey).Times(1); + + // Invoke the method + ASSERT_NO_FATAL_FAILURE(service->handleChildrenSynchronizationResponse(std::move(responseMessage))); + if (!called) + { + std::unique_lock lock{mutex}; + conditionVariable.wait_for(lock, std::chrono::milliseconds{100}); + } + EXPECT_TRUE(called); +} + +TEST_F(DevicesServiceTests, HandlerRegisteredDevicesResponseWithConditionVariable) +{ + // Create a callback that will be invoked + auto responseMessage = std::unique_ptr{new RegisteredDevicesResponseMessage{ + std::chrono::milliseconds{1234567890}, "Type1", {}, {{"Device1", "Id1", "Type1"}, {"Device2", "Id2", "Type1"}}}}; + auto sharedConditionVariable = std::make_shared(); + service->m_registeredDevicesRequests.emplace( + RegisteredDevicesRequestParameters{responseMessage->getTimestampFrom(), responseMessage->getDeviceType(), + responseMessage->getExternalId()}, + std::make_shared(sharedConditionVariable)); + + // Set up the device repository to be invoked + EXPECT_CALL(*deviceRepositoryMock, save).Times(1); + + // Invoke the method + ASSERT_NO_FATAL_FAILURE(service->handleRegisteredDevicesResponse(std::move(responseMessage))); +} + +TEST_F(DevicesServiceTests, HandlerRegisteredDevicesResponseWithCallback) +{ + // Create a callback that will be invoked + std::atomic_bool called{false}; + auto responseMessage = std::unique_ptr{new RegisteredDevicesResponseMessage{ + std::chrono::milliseconds{1234567890}, "Type1", {}, {{"Device1", "Id1", "Type1"}, {"Device2", "Id2", "Type1"}}}}; + const auto address = responseMessage.get(); + service->m_registeredDevicesRequests.emplace( + RegisteredDevicesRequestParameters{responseMessage->getTimestampFrom(), responseMessage->getDeviceType(), + responseMessage->getExternalId()}, + std::make_shared( + [&](const std::shared_ptr& message) { + EXPECT_EQ(address, message.get()); + called = true; + conditionVariable.notify_one(); + })); + + // Set up the device repository to be invoked + EXPECT_CALL(*deviceRepositoryMock, save).Times(1); + + // Invoke the method + ASSERT_NO_FATAL_FAILURE(service->handleRegisteredDevicesResponse(std::move(responseMessage))); + if (!called) + { + std::unique_lock lock{mutex}; + conditionVariable.wait_for(lock, std::chrono::milliseconds{100}); + } + EXPECT_TRUE(called); +} + +TEST_F(DevicesServiceTests, ReceivedMessagesOneMessageOfUnknownType) +{ + // Set up the expected calls + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, messageReceived).Times(1); + EXPECT_CALL(*registrationProtocolMock, getMessageType).WillOnce(Return(MessageType::UNKNOWN)); + + // Invoke the service + ASSERT_NO_FATAL_FAILURE( + service->receiveMessages(std::vector{GatewaySubdeviceMessage{{"", ""}}})); +} + +TEST_F(DevicesServiceTests, ReceivedMessageTwoMessagesBothNull) +{ + // Set up the expected calls + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, messageReceived).Times(2); + EXPECT_CALL(*registrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE)) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_RESPONSE)); + EXPECT_CALL(*registrationProtocolMock, parseChildrenSynchronizationResponse).WillOnce(Return(ByMove(nullptr))); + EXPECT_CALL(*registrationProtocolMock, parseRegisteredDevicesResponse).WillOnce(Return(ByMove(nullptr))); + + // Invoke the service + ASSERT_NO_FATAL_FAILURE(service->receiveMessages( + std::vector{GatewaySubdeviceMessage{{"", ""}}, GatewaySubdeviceMessage{{"", ""}}})); +} + +TEST_F(DevicesServiceTests, ReceivedMessageTwoMessagesBothCallTheActualMethods) +{ + // Set up the expected calls + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, messageReceived).Times(2); + EXPECT_CALL(*registrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::CHILDREN_SYNCHRONIZATION_RESPONSE)) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_RESPONSE)); + EXPECT_CALL(*registrationProtocolMock, parseChildrenSynchronizationResponse) + .WillOnce(Return(ByMove( + std::unique_ptr{new ChildrenSynchronizationResponseMessage{{"C1"}}}))); + EXPECT_CALL(*registrationProtocolMock, parseRegisteredDevicesResponse) + .WillOnce(Return(ByMove(std::unique_ptr{ + new RegisteredDevicesResponseMessage{std::chrono::milliseconds{1234567890}, + "Type1", + {}, + {{"Device1", "Id1", "Type1"}, {"Device2", "Id2", "Type1"}}}}))); + EXPECT_CALL(*deviceRepositoryMock, save).Times(2); + EXPECT_CALL(*existingDevicesRepositoryMock, getDeviceKeys).Times(1); + EXPECT_CALL(*existingDevicesRepositoryMock, addDeviceKey).Times(1); + + // Invoke the service + ASSERT_NO_FATAL_FAILURE(service->receiveMessages( + std::vector{GatewaySubdeviceMessage{{"", ""}}, GatewaySubdeviceMessage{{"", ""}}})); +} + +TEST_F(DevicesServiceTests, RegisterChildDevicesEmptyDevicesVector) +{ + EXPECT_FALSE(service->registerChildDevices({}, {})); +} + +TEST_F(DevicesServiceTests, RegisterChildDevicesEmptyDeviceName) +{ + EXPECT_FALSE(service->registerChildDevices({DeviceRegistrationData{"", "", "", {}, {}, {}}}, {})); +} + +TEST_F(DevicesServiceTests, RegisterChildDevicesEmptyDeviceKey) +{ + EXPECT_FALSE(service->registerChildDevices({DeviceRegistrationData{"Device Name", "", "", {}, {}, {}}}, {})); +} + +TEST_F(DevicesServiceTests, RegisterChildDevicesProtocolFailsToParse) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + EXPECT_FALSE( + service->registerChildDevices({DeviceRegistrationData{"Device Name", "Device Key", "", {}, {}, {}}}, {})); +} + +TEST_F(DevicesServiceTests, RegisterChildDevicesProtocolParses) +{ + // registerChildDevices call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + + // sendOutChildrenSynchronizationRequest call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, + getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage).Times(1); + + // Now call the service and dig for the callback + std::atomic_bool called{false}; + ASSERT_TRUE(service->registerChildDevices( + {DeviceRegistrationData{"Device One", "D1", "", {}, {}, {}}, + DeviceRegistrationData{"Device Two", "D2", "", {}, {}, {}}}, + [&](const std::vector& success, const std::vector& failed) { + if (!(success.empty() || failed.empty())) + called = true; + })); + ASSERT_FALSE(service->m_childSyncRequests.empty()); + ASSERT_TRUE(service->m_childSyncRequests.front()->getLambda()); + ASSERT_NO_FATAL_FAILURE(service->m_childSyncRequests.front()->getLambda()( + std::make_shared(std::vector{"D1"}))); + EXPECT_TRUE(called); +} + +TEST_F(DevicesServiceTests, RemoveChildDevicesEmptyVector) +{ + EXPECT_FALSE(service->removeChildDevices({})); +} + +TEST_F(DevicesServiceTests, RemoveChildDevicesEmptyKeyInVector) +{ + EXPECT_FALSE(service->removeChildDevices({{}})); +} + +TEST_F(DevicesServiceTests, RemoveChildDevicesProtocolFailsToParse) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + EXPECT_FALSE(service->removeChildDevices({"Test Device Key"})); +} + +TEST_F(DevicesServiceTests, RemoveChildDevicesProtocolParses) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + EXPECT_TRUE(service->removeChildDevices({"Test Device Key"})); +} + +TEST_F(DevicesServiceTests, SendOutChildrenSynchronizationRequestFailsToParse) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + ASSERT_FALSE(service->sendOutChildrenSynchronizationRequest({})); +} + +TEST_F(DevicesServiceTests, SendOutChildrenSynchronizationRequestRetryCallbackCalledConditionVariable) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, + getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage) + .WillOnce([&](const RetryMessageStruct& retryMessageStruct) { retryMessageStruct.onFail(nullptr); }); + auto sharedConditionVariable = std::make_shared(); + ASSERT_TRUE(service->sendOutChildrenSynchronizationRequest(std::make_shared( + sharedConditionVariable, std::vector{"Device 1", "Device 2"}))); +} + +TEST_F(DevicesServiceTests, SendOutChildrenSynchronizationRequestRetryCallbackCalledLambda) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, + getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage) + .WillOnce([&](const RetryMessageStruct& retryMessageStruct) { retryMessageStruct.onFail({}); }); + std::atomic_bool called{false}; + ASSERT_TRUE(service->sendOutChildrenSynchronizationRequest(std::make_shared( + [&](const std::shared_ptr&) { called = true; }, + std::vector{"Device 1", "Device 2"}))); + EXPECT_TRUE(called); +} + +TEST_F(DevicesServiceTests, SendOutRegisteredDevicesRequestFailsToParse) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + ASSERT_FALSE(service->sendOutRegisteredDevicesRequest( + RegisteredDevicesRequestParameters{std::chrono::milliseconds{1234567890}}, {})); +} + +TEST_F(DevicesServiceTests, SendOutRegisteredDevicesRequestRetryCallback) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage) + .WillOnce([&](const RetryMessageStruct& retryMessageStruct) { retryMessageStruct.onFail({}); }); + EXPECT_TRUE(service->m_registeredDevicesRequests.empty()); + ASSERT_TRUE(service->sendOutRegisteredDevicesRequest( + RegisteredDevicesRequestParameters{std::chrono::milliseconds{1234567890}}, {})); + EXPECT_FALSE(service->m_registeredDevicesRequests.empty()); +} + +TEST_F(DevicesServiceTests, SendOutRegisteredDevicesRequestRetryCallbackWithConditionVariable) +{ + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage) + .WillOnce([&](const RetryMessageStruct& retryMessageStruct) { retryMessageStruct.onFail({}); }); + EXPECT_TRUE(service->m_registeredDevicesRequests.empty()); + auto sharedConditionVariable = std::make_shared(); + ASSERT_TRUE(service->sendOutRegisteredDevicesRequest( + RegisteredDevicesRequestParameters{std::chrono::milliseconds{1234567890}}, + std::make_shared(sharedConditionVariable))); + EXPECT_FALSE(service->m_registeredDevicesRequests.empty()); +} + +TEST_F(DevicesServiceTests, UpdateDeviceCacheNoDeviceRepository) +{ + service->m_deviceRepository = nullptr; + ASSERT_NO_FATAL_FAILURE(service->updateDeviceCache()); +} + +TEST_F(DevicesServiceTests, UpdateDeviceCacheWithDevicesToDeleteFailsToDelete) +{ + // Update calls + EXPECT_CALL(*deviceRepositoryMock, latestPlatformTimestamp).Times(1); + EXPECT_CALL(*deviceRepositoryMock, getGatewayDevices) + .WillOnce(Return(std::vector{ + StoredDeviceInformation{"Test Device Key", DeviceOwnership::Gateway, std::chrono::milliseconds{0}}})); + EXPECT_CALL(*deviceRepositoryMock, remove).Times(0); + EXPECT_CALL(*existingDevicesRepositoryMock, getDeviceKeys).WillOnce(Return(std::vector{})); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + + // Send out calls + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + + // Call the service + ASSERT_NO_FATAL_FAILURE(service->updateDeviceCache()); +} + +TEST_F(DevicesServiceTests, UpdateDeviceCacheWithDevicesToDeleteSucceedsToDelete) +{ + // Update calls + EXPECT_CALL(*deviceRepositoryMock, latestPlatformTimestamp).Times(1); + EXPECT_CALL(*deviceRepositoryMock, getGatewayDevices) + .WillOnce(Return(std::vector{ + StoredDeviceInformation{"Test Device Key", DeviceOwnership::Gateway, std::chrono::milliseconds{0}}})); + EXPECT_CALL(*deviceRepositoryMock, remove).Times(1); + EXPECT_CALL(*existingDevicesRepositoryMock, getDeviceKeys).WillOnce(Return(std::vector{})); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + + // Send out calls + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + + // Call the service + ASSERT_NO_FATAL_FAILURE(service->updateDeviceCache()); +} + +TEST_F(DevicesServiceTests, MessageReceivedNoLocalProtocol) +{ + service->m_localProtocol = nullptr; + ASSERT_NO_FATAL_FAILURE(service->messageReceived(nullptr)); +} + +TEST_F(DevicesServiceTests, MessageReceivedMessageIsNull) +{ + ASSERT_NO_FATAL_FAILURE(service->messageReceived(nullptr)); +} + +TEST_F(DevicesServiceTests, MessageReceivedMessageIsUnknown) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::UNKNOWN)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return("")); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRegistrationFailsToParse) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REGISTRATION)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRegistrationMessage).WillOnce(Return(ByMove(nullptr))); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRegistrationRegistersDevicesButFailsToParseLocalMessage) +{ + // Handle calls + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REGISTRATION)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRegistrationMessage) + .WillOnce(Return(ByMove(std::unique_ptr{ + new DeviceRegistrationMessage{{DeviceRegistrationData{"Device Name 1", "D1", "", {}, {}, {}}}}}))); + + // registerChildDevices call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + + // sendOutChildrenSynchronizationRequest call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, + getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage).Times(1); + + // Callback + EXPECT_CALL(*gatewayRegistrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + + // Call the service and handle the callback + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); + ASSERT_FALSE(service->m_childSyncRequests.empty()); + ASSERT_TRUE(service->m_childSyncRequests.front()->getLambda()); + ASSERT_NO_FATAL_FAILURE(service->m_childSyncRequests.front()->getLambda()( + std::make_shared(std::vector{"D1"}))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRegistrationRegistersDevices) +{ + // Handle calls + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REGISTRATION)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRegistrationMessage) + .WillOnce(Return(ByMove(std::unique_ptr{ + new DeviceRegistrationMessage{{DeviceRegistrationData{"Device Name 1", "D1", "", {}, {}, {}}}}}))); + + // registerChildDevices call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + + // sendOutChildrenSynchronizationRequest call + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, + getResponseChannelForMessage(MessageType::CHILDREN_SYNCHRONIZATION_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage).Times(1); + + // Callback + EXPECT_CALL(*gatewayRegistrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*localOutboundMessageHandlerMock, addMessage).Times(1); + + // Call the service and handle the callback + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); + ASSERT_FALSE(service->m_childSyncRequests.empty()); + ASSERT_TRUE(service->m_childSyncRequests.front()->getLambda()); + ASSERT_NO_FATAL_FAILURE(service->m_childSyncRequests.front()->getLambda()( + std::make_shared(std::vector{"D1"}))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRemovalFailsToParse) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REMOVAL)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRemovalMessage).WillOnce(Return(ByMove(nullptr))); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRemovalFailsToParseTheOutgoingRequest) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REMOVAL)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRemovalMessage) + .WillOnce(Return(ByMove(std::unique_ptr{new DeviceRemovalMessage{{"Device Key 1"}}}))); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedDeviceRemovalHappyFlow) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType).WillOnce(Return(MessageType::DEVICE_REMOVAL)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseDeviceRemovalMessage) + .WillOnce(Return(ByMove(std::unique_ptr{new DeviceRemovalMessage{{"Device Key 1"}}}))); + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*platformOutboundMessageHandlerMock, addMessage).Times(1); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedRegisteredDevicesRequestFailsToParse) +{ + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_REQUEST)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseRegisteredDevicesRequestMessage) + .WillOnce(Return(ByMove(nullptr))); + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedRegisteredDevicesCallbackCalledWithNullptr) +{ + // Handle calls + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_REQUEST)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseRegisteredDevicesRequestMessage) + .WillOnce(Return(ByMove(std::unique_ptr{ + new RegisteredDevicesRequestMessage{std::chrono::milliseconds{1234567890}, {}, {}}}))); + + // Send out calls + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage) + .WillOnce([&](const RetryMessageStruct& retryMessageStruct) { retryMessageStruct.onFail(nullptr); }); + + // Call the service + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); +} + +TEST_F(DevicesServiceTests, MessageReceivedRegisteredDevicesCallbackCalledWithMessageButFailsToParseForLocalBroker) +{ + // Handle calls + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_REQUEST)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseRegisteredDevicesRequestMessage) + .WillOnce(Return(ByMove(std::unique_ptr{ + new RegisteredDevicesRequestMessage{std::chrono::milliseconds{1234567890}, {}, {}}}))); + + // Send out calls + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage).Times(1); + EXPECT_CALL(*gatewayRegistrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(nullptr))); + + // Call the service + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); + // Find the callback and invoke it + ASSERT_FALSE(service->m_registeredDevicesRequests.empty()); + const auto params = RegisteredDevicesRequestParameters{std::chrono::milliseconds{1234567890}, {}, {}}; + const auto requestsIt = service->m_registeredDevicesRequests.find(params); + ASSERT_NE(requestsIt, service->m_registeredDevicesRequests.cend()); + ASSERT_NO_FATAL_FAILURE(requestsIt->second->getLambda()( + std::make_shared(std::chrono::milliseconds{1234567890}, std::string{}, + std::string{}, std::vector{}))); +} + +TEST_F(DevicesServiceTests, MessageReceivedRegisteredDevicesCallbackCalledWithMessageSendsToLocalBroker) +{ + // Handle calls + EXPECT_CALL(*gatewayRegistrationProtocolMock, getMessageType) + .WillOnce(Return(MessageType::REGISTERED_DEVICES_REQUEST)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, getDeviceKey).WillOnce(Return(DEVICE_KEY)); + EXPECT_CALL(*gatewayRegistrationProtocolMock, parseRegisteredDevicesRequestMessage) + .WillOnce(Return(ByMove(std::unique_ptr{ + new RegisteredDevicesRequestMessage{std::chrono::milliseconds{1234567890}, {}, {}}}))); + + // Send out calls + EXPECT_CALL(*registrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*registrationProtocolMock, getResponseChannelForMessage(MessageType::REGISTERED_DEVICES_REQUEST, _)) + .Times(1); + EXPECT_CALL(*platformOutboundRetryMessageHandlerMock, addMessage).Times(1); + EXPECT_CALL(*gatewayRegistrationProtocolMock, makeOutboundMessage(_, A())) + .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); + EXPECT_CALL(*localOutboundMessageHandlerMock, addMessage).Times(1); + + // Call the service + ASSERT_NO_FATAL_FAILURE(service->messageReceived(std::make_shared("", ""))); + // Find the callback and invoke it + ASSERT_FALSE(service->m_registeredDevicesRequests.empty()); + const auto params = RegisteredDevicesRequestParameters{std::chrono::milliseconds{1234567890}, {}, {}}; + const auto requestsIt = service->m_registeredDevicesRequests.find(params); + ASSERT_NE(requestsIt, service->m_registeredDevicesRequests.cend()); + ASSERT_NO_FATAL_FAILURE(requestsIt->second->getLambda()( + std::make_shared(std::chrono::milliseconds{1234567890}, std::string{}, + std::string{}, std::vector{}))); +} From b4f118728b764b77b75ab627723faca1fcf034f2 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 17 Feb 2022 16:18:47 +0100 Subject: [PATCH 09/20] Up-ed the runtime to 20 minutes --- .github/workflows/cmake-build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-build-and-test.yml b/.github/workflows/cmake-build-and-test.yml index af2c779..38c9114 100644 --- a/.github/workflows/cmake-build-and-test.yml +++ b/.github/workflows/cmake-build-and-test.yml @@ -15,7 +15,7 @@ jobs: # cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-20.04 - timeout-minutes: 15 + timeout-minutes: 20 steps: - uses: actions/checkout@v1 @@ -54,4 +54,4 @@ jobs: - name: Run Tests working-directory: ${{runner.workspace}}/out shell: bash - run: make test -j$(nproc) && ctest . + run: ctest . From cb684090b22f2256f624f0131929e4a3adfd1ca6 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Tue, 22 Feb 2022 14:06:35 +0100 Subject: [PATCH 10/20] Override the `dh_shlipdeps` rule --- debian/rules | 2 +- tools/make_zip.sh | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/debian/rules b/debian/rules index 0ca3ac4..a1663a3 100755 --- a/debian/rules +++ b/debian/rules @@ -6,4 +6,4 @@ DH_VERBOSE = 1 override_dh_auto_clean: override_dh_auto_test: override_dh_shlibdeps: - dh_shlibdeps -l$(shell pwd)/out/lib + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info -l$(shell pwd)/out/lib diff --git a/tools/make_zip.sh b/tools/make_zip.sh index 783f2f6..160b0f8 100755 --- a/tools/make_zip.sh +++ b/tools/make_zip.sh @@ -38,14 +38,8 @@ fi mkdir -p ./tmp-wg cd ./tmp-wg || exit -git clone https://github.com/Wolkabout/WolkGateway --recurse-submodules +git clone https://github.com/Wolkabout/WolkGateway -b "$branch" --recurse-submodules cd ./WolkGateway || exit -git checkout "$branch" -if [ $? -ne 0 ]; then - echo "Can't checkout to branch named $branch" - exit -fi -git submodule update --init --recursive filename="WolkGateway-v$(cat RELEASE_NOTES.txt | grep "**Version" | head -1 | sed -e "s/**Version //" | sed -e "s/\*\*//").zip" echo "filename: $filename" zip -qr $filename * From fa7aa1115e68f356696eb6208369a932081fee4a Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Tue, 22 Feb 2022 14:24:46 +0100 Subject: [PATCH 11/20] Changed the version in the `debian/changelog` --- debian/changelog | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/debian/changelog b/debian/changelog index 8635b0c..d1d4644 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,6 @@ -wolkgateway (4.4.0) stable; urgency=medium +wolkgateway (5.0.0-prerelease) stable; urgency=medium - * Completely removed `libpoco` as a dependency, and now rely on `sqlite` for SQLiteDeviceRepository. + * Updated the WolkGateway to be ready for Wolkabout IoT Platform 22.GA - The Digital Twin Update + * Fixed the CMakeLists.txt file for private header file finding to use project files in private - -- Wolkabout ELab Wed, 22 Dec 2021 00:00:00 +0100 + -- Wolkabout ELab Tue, 22 Feb 2022 14:22:00 +0100 From b563af0c7fdd793ca4399163f32d9dbdc3aec491 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 09:36:43 +0100 Subject: [PATCH 12/20] New `WolkConnect-Cpp` without issues on `armv7l` --- WolkConnect-Cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 9fdd550..436db18 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 9fdd550915fe658e11b46c1a293c34ec9d7fe467 +Subproject commit 436db18ffe85f0ddda6e5c1f15c4c0e489b6a979 From 540e4dcd6503eadafe406d961a535f126c3bb4da Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 10:41:44 +0100 Subject: [PATCH 13/20] Fixed up the `ExternalDataService` receiving `unique_ptr` instead of `shared_ptr` --- .../external_data/ExternalDataService.cpp | 6 +++-- tests/ExternalDataServiceTests.cpp | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/gateway/service/external_data/ExternalDataService.cpp b/gateway/service/external_data/ExternalDataService.cpp index 8aea23d..2482f3c 100644 --- a/gateway/service/external_data/ExternalDataService.cpp +++ b/gateway/service/external_data/ExternalDataService.cpp @@ -70,7 +70,8 @@ void ExternalDataService::receiveMessages(const std::vector{m_dataProtocol.parseFeedValues(sharedMessage)}; if (feedValuesMessage == nullptr) { LOG(ERROR) << TAG << "Received 'FeedValues' message but failed to parse it."; @@ -83,7 +84,8 @@ void ExternalDataService::receiveMessages(const std::vector{m_dataProtocol.parseParameters(sharedMessage)}; if (parametersMessage == nullptr) { LOG(ERROR) << TAG << "Received 'Parameters' message but failed to parse it."; diff --git a/tests/ExternalDataServiceTests.cpp b/tests/ExternalDataServiceTests.cpp index f1c3e31..918029c 100644 --- a/tests/ExternalDataServiceTests.cpp +++ b/tests/ExternalDataServiceTests.cpp @@ -42,20 +42,21 @@ class ExternalDataServiceTests : public Test void SetUp() override { + m_dataProtocolMock = std::make_shared(); service = std::unique_ptr{ - new ExternalDataService{GATEWAY_KEY, m_gatewaySubdeviceProtocolMock, m_dataProtocolMock, + new ExternalDataService{GATEWAY_KEY, m_gatewaySubdeviceProtocolMock, *m_dataProtocolMock, m_platformOutboundMessageHandler, m_dataProviderMock}}; } template void MakeOutboundReturnsNull() { - EXPECT_CALL(m_dataProtocolMock, makeOutboundMessage(A(), A())) + EXPECT_CALL(*m_dataProtocolMock, makeOutboundMessage(A(), A())) .WillOnce(Return(ByMove(nullptr))); } template void MakeOutboundReturnsMessage() { - EXPECT_CALL(m_dataProtocolMock, makeOutboundMessage(A(), A())) + EXPECT_CALL(*m_dataProtocolMock, makeOutboundMessage(A(), A())) .WillOnce(Return(ByMove(std::unique_ptr{new wolkabout::Message{"", ""}}))); } @@ -87,7 +88,7 @@ class ExternalDataServiceTests : public Test GatewaySubdeviceProtocolMock m_gatewaySubdeviceProtocolMock; - DataProtocolMock m_dataProtocolMock; + std::shared_ptr m_dataProtocolMock; OutboundMessageHandlerMock m_platformOutboundMessageHandler; @@ -274,8 +275,8 @@ TEST_F(ExternalDataServiceTests, ReceiveMessagesNotHandledType) { EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getMessageType).WillOnce(Return(MessageType::TIME_SYNC)); EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getDeviceKey).WillOnce(Return(GATEWAY_KEY)); - EXPECT_CALL(m_dataProtocolMock, parseFeedValues).Times(0); - EXPECT_CALL(m_dataProtocolMock, parseParameters).Times(0); + EXPECT_CALL(*m_dataProtocolMock, parseFeedValues).Times(0); + EXPECT_CALL(*m_dataProtocolMock, parseParameters).Times(0); ASSERT_NO_FATAL_FAILURE(service->receiveMessages(GenerateMessages(1))); } @@ -284,7 +285,7 @@ TEST_F(ExternalDataServiceTests, ReceiveFeedValuesButFailsToParse) // Set up the service call, and await the callback call EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getMessageType).WillOnce(Return(MessageType::FEED_VALUES)); EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getDeviceKey).WillOnce(Return(GATEWAY_KEY)); - EXPECT_CALL(m_dataProtocolMock, parseFeedValues).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_dataProtocolMock, parseFeedValues).WillOnce(Return(ByMove(nullptr))); EXPECT_CALL(m_dataProviderMock, receiveReadingData).Times(0); ASSERT_NO_FATAL_FAILURE(service->receiveMessages(GenerateMessages(1))); } @@ -306,8 +307,9 @@ TEST_F(ExternalDataServiceTests, ReceiveFeedValuesMessage) // Set up the service call, and await the callback call EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getMessageType).WillOnce(Return(MessageType::FEED_VALUES)); EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getDeviceKey).WillOnce(Return(GATEWAY_KEY)); - EXPECT_CALL(m_dataProtocolMock, parseFeedValues) - .WillOnce(Return(std::make_shared(std::vector{GenerateReading()}))); + EXPECT_CALL(*m_dataProtocolMock, parseFeedValues) + .WillOnce(Return( + ByMove(std::unique_ptr{new FeedValuesMessage{std::vector{GenerateReading()}}}))); ASSERT_NO_FATAL_FAILURE(service->receiveMessages(GenerateMessages(1))); if (!called) { @@ -322,7 +324,7 @@ TEST_F(ExternalDataServiceTests, ReceiveParametersButFailsToParse) // Set up the service call, and await the callback call EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getMessageType).WillOnce(Return(MessageType::PARAMETER_SYNC)); EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getDeviceKey).WillOnce(Return(GATEWAY_KEY)); - EXPECT_CALL(m_dataProtocolMock, parseParameters).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_dataProtocolMock, parseParameters).WillOnce(Return(ByMove(nullptr))); EXPECT_CALL(m_dataProviderMock, receiveParameterData).Times(0); ASSERT_NO_FATAL_FAILURE(service->receiveMessages(GenerateMessages(1))); } @@ -344,8 +346,9 @@ TEST_F(ExternalDataServiceTests, ReceiveParametersMessage) // Set up the service call, and await the callback call EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getMessageType).WillOnce(Return(MessageType::PARAMETER_SYNC)); EXPECT_CALL(m_gatewaySubdeviceProtocolMock, getDeviceKey).WillOnce(Return(GATEWAY_KEY)); - EXPECT_CALL(m_dataProtocolMock, parseParameters) - .WillOnce(Return(std::make_shared(std::vector{GenerateParameter()}))); + EXPECT_CALL(*m_dataProtocolMock, parseParameters) + .WillOnce(Return(ByMove(std::unique_ptr{ + new ParametersUpdateMessage{std::vector{GenerateParameter()}}}))); ASSERT_NO_FATAL_FAILURE(service->receiveMessages(GenerateMessages(1))); if (!called) { From 55b91a328751f3fbad0651e73dbfab9b38aba0d3 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 10:47:15 +0100 Subject: [PATCH 14/20] Updated the `configure.sh` script to build only necessary things for release --- configure.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.sh b/configure.sh index 948be7c..ae71d3d 100755 --- a/configure.sh +++ b/configure.sh @@ -18,5 +18,5 @@ cp tools/git/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit pushd out -cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release .. +cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_GTEST=OFF -DBUILD_TESTS=OFF -DBUILD_POCO_HTTP_DOWNLOADER=OFF -DBUILD_EXAMPLES=OFF .. popd From a52287e4db76f30625188be0f4350087e2f1ed8e Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 11:53:03 +0100 Subject: [PATCH 15/20] Add the CMake flags to `debian/rules` --- debian/rules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/rules b/debian/rules index a1663a3..63d1393 100755 --- a/debian/rules +++ b/debian/rules @@ -3,6 +3,8 @@ DH_VERBOSE = 1 %: dh $@ --builddirectory=out +override_dh_auto_configure: + dh_auto_configure -- -DBUILD_GTEST=OFF -DBUILD_TESTS=OFF -DBUILD_POCO_HTTP_DOWNLOADER=OFF -DBUILD_EXAMPLES=OFF override_dh_auto_clean: override_dh_auto_test: override_dh_shlibdeps: From f1d8b3f9c1b6e84f10ab980d68477c82c924cad1 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 11:56:53 +0100 Subject: [PATCH 16/20] Formatted the `debian/rules` properly --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 63d1393..f700b46 100755 --- a/debian/rules +++ b/debian/rules @@ -4,7 +4,7 @@ DH_VERBOSE = 1 %: dh $@ --builddirectory=out override_dh_auto_configure: - dh_auto_configure -- -DBUILD_GTEST=OFF -DBUILD_TESTS=OFF -DBUILD_POCO_HTTP_DOWNLOADER=OFF -DBUILD_EXAMPLES=OFF + dh_auto_configure -- -DBUILD_GTEST=OFF -DBUILD_TESTS=OFF -DBUILD_POCO_HTTP_DOWNLOADER=OFF -DBUILD_EXAMPLES=OFF override_dh_auto_clean: override_dh_auto_test: override_dh_shlibdeps: From eb38945f4ab0f0618e5a8d5a9b25902a898cb77e Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 15:31:32 +0100 Subject: [PATCH 17/20] Reset the `gatewayConfiguration.json` --- out/gatewayConfiguration.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/out/gatewayConfiguration.json b/out/gatewayConfiguration.json index 509eadd..a42688c 100644 --- a/out/gatewayConfiguration.json +++ b/out/gatewayConfiguration.json @@ -1,10 +1,10 @@ { "name": "", - "key": "AWG", - "password": "G76ZE6LT3C", + "key": "", + "password": "", - "platformMqttUri": "ssl://integration5.wolkabout.com:8883", + "platformMqttUri": "ssl://demo.wolkabout.com:8883", "platformMqttKeepAliveSeconds": 60, "localMqttUri": "tcp://localhost:1883", From a86bbb19162c739085b25cbcfe7084203795a8bd Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Thu, 24 Feb 2022 17:31:42 +0100 Subject: [PATCH 18/20] Updated with new `WolkConnect-Cpp` that doesn't block tests --- WolkConnect-Cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 7a5a7c8..2215297 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 7a5a7c80ef14142840d2f6e97cd406cdfcf76134 +Subproject commit 2215297d69c760f79a029717ce1f0198c88e5690 From 70de320bbb37a9a2246a4ca14feb63440eb7ea74 Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 25 Feb 2022 11:01:02 +0100 Subject: [PATCH 19/20] Point to newest `WolkConnect-Cpp` and removed `debian/rules` `dh_shlibdeps` flags --- WolkConnect-Cpp | 2 +- debian/rules | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 2215297..99872ac 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 2215297d69c760f79a029717ce1f0198c88e5690 +Subproject commit 99872acd0b1802dcc8b86783f2425ae729603a48 diff --git a/debian/rules b/debian/rules index f700b46..f94f10b 100755 --- a/debian/rules +++ b/debian/rules @@ -8,4 +8,4 @@ override_dh_auto_configure: override_dh_auto_clean: override_dh_auto_test: override_dh_shlibdeps: - dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info -l$(shell pwd)/out/lib + dh_shlibdeps -l$(shell pwd)/out/lib From fef76f67c3eef836ae7b2814ed1b6a1fb457b65f Mon Sep 17 00:00:00 2001 From: nanavuletic Date: Fri, 25 Feb 2022 14:33:44 +0100 Subject: [PATCH 20/20] Newest `WolkConnect-Cpp` commit and don't build `WolkConnect-Cpp` examples --- CMakeLists.txt | 1 + WolkConnect-Cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1183c70..e9e295c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ find_package(Threads REQUIRED) # Bring in WolkSDK-Cpp if (NOT TARGET WolkAboutCore) set(BUILD_CONNECTIVITY ON CACHE BOOL "Build the library with Paho MQTT and allow MQTT connection to the platform.") + set(BUILD_EXAMPLES OFF CACHE BOOL "Build the examples/runtimes for testing") set(BUILD_POCO OFF CACHE BOOL "Build the library with Poco.") set(BUILD_GTEST ${BUILD_TESTS} CACHE BOOL "Build the library with GTest.") set(BUILD_AWS_LOG_UPLOADER OFF CACHE BOOL "Build the library with AwsLogUploader.") diff --git a/WolkConnect-Cpp b/WolkConnect-Cpp index 99872ac..59a9fe8 160000 --- a/WolkConnect-Cpp +++ b/WolkConnect-Cpp @@ -1 +1 @@ -Subproject commit 99872acd0b1802dcc8b86783f2425ae729603a48 +Subproject commit 59a9fe8c2fb8389dd108d102b05607d150ba59df