From 33fedd2a4ddf60a635c7e47469734ce4d8e7c70d Mon Sep 17 00:00:00 2001 From: Adrian Muzyka Date: Tue, 22 Oct 2024 12:45:16 +0200 Subject: [PATCH] RDK-53686 Analytics sysTime validation, generate sessionId and attributes source alignment --- Analytics/Analytics.conf.in | 1 + Analytics/Analytics.config | 1 + Analytics/Analytics.h | 4 - Analytics/AnalyticsJsonRpc.cpp | 45 -- Analytics/CHANGELOG.md | 3 + Analytics/CMakeLists.txt | 11 +- .../AnalyticsImplementation.cpp | 70 +-- .../Implementation/AnalyticsImplementation.h | 12 +- .../Backend/AnalyticsBackend.cpp | 7 +- .../Implementation/Backend/AnalyticsBackend.h | 10 +- .../Backend/Sift/CMakeLists.txt | 5 +- .../Backend/Sift/SiftBackend.cpp | 44 +- .../Implementation/Backend/Sift/SiftBackend.h | 4 +- .../Backend/Sift/SiftConfig.cpp | 133 +++-- .../Implementation/Backend/Sift/SiftConfig.h | 13 +- .../Backend/Sift/SiftUploader.cpp | 13 +- .../Backend/Sift/SiftUploader.h | 4 +- .../Implementation/SystemTime/CMakeLists.txt | 27 + .../Implementation/SystemTime/SystemTime.cpp | 493 ++++++++++++++++++ .../Implementation/SystemTime/SystemTime.h | 99 ++++ .../0001-Add-IAnalytics-interface-R2.patch | 14 +- Tests/L1Tests/tests/test_Analytics.cpp | 14 - 22 files changed, 795 insertions(+), 232 deletions(-) create mode 100644 Analytics/Implementation/SystemTime/CMakeLists.txt create mode 100644 Analytics/Implementation/SystemTime/SystemTime.cpp create mode 100644 Analytics/Implementation/SystemTime/SystemTime.h diff --git a/Analytics/Analytics.conf.in b/Analytics/Analytics.conf.in index 0a6fe352e8..c5c8f738df 100644 --- a/Analytics/Analytics.conf.in +++ b/Analytics/Analytics.conf.in @@ -15,6 +15,7 @@ if boolean("@PLUGIN_ANALYTICS_SIFT_BACKEND_ENABLED@"): sift.add("productname", "@PLUGIN_ANALYTICS_SIFT_PRODUCT_NAME@") sift.add("loggername", "@PLUGIN_ANALYTICS_SIFT_LOGGER_NAME@") sift.add("loggerversion", "@PLUGIN_ANALYTICS_SIFT_LOGGER_VERSION@") + sift.add("platformdefault", "@PLUGIN_ANALYTICS_SIFT_PLATFORM_DEFAULT@") sift.add("maxrandomisationwindowtime", "@PLUGIN_ANALYTICS_SIFT_MAX_RANDOMISATION_WINDOW_TIME@") sift.add("maxeventsinpost", "@PLUGIN_ANALYTICS_SIFT_MAX_EVENTS_IN_POST@") sift.add("maxretries", "@PLUGIN_ANALYTICS_SIFT_MAX_RETRIES@") diff --git a/Analytics/Analytics.config b/Analytics/Analytics.config index 55d1e98150..e525e36a3d 100644 --- a/Analytics/Analytics.config +++ b/Analytics/Analytics.config @@ -20,6 +20,7 @@ if(PLUGIN_ANALYTICS_SIFT_BACKEND_ENABLED) kv(productname ${PLUGIN_ANALYTICS_SIFT_PRODUCT_NAME}) kv(loggername ${PLUGIN_ANALYTICS_SIFT_LOGGER_NAME}) kv(loggerversion ${PLUGIN_ANALYTICS_SIFT_LOGGER_VERSION}) + kv(platformdefault ${PLUGIN_ANALYTICS_SIFT_PLATFORM_DEFAULT}) kv(maxrandomisationwindowtime, ${PLUGIN_ANALYTICS_SIFT_MAX_RANDOMISATION_WINDOW_TIME}) kv(maxeventsinpost, ${PLUGIN_ANALYTICS_SIFT_MAX_EVENTS_IN_POST}) kv(maxretries, ${PLUGIN_ANALYTICS_SIFT_MAX_RETRIES}) diff --git a/Analytics/Analytics.h b/Analytics/Analytics.h index 8b8b1434a3..8baed6a4bd 100644 --- a/Analytics/Analytics.h +++ b/Analytics/Analytics.h @@ -68,8 +68,6 @@ namespace WPEFramework { END_INTERFACE_MAP static const string ANALYTICS_METHOD_SEND_EVENT; - static const string ANALYTICS_METHOD_SET_SESSION_ID; - static const string ANALYTICS_METHOD_SET_TIME_READY; private: void Deactivated(RPC::IRemoteConnection* connection); @@ -78,8 +76,6 @@ namespace WPEFramework { void UnregisterAll(); uint32_t SendEventWrapper(const JsonObject& parameters, JsonObject& response); - uint32_t SetSessionIdWrapper(const JsonObject& parameters, JsonObject& response); - uint32_t SetTimeReadyWrapper(const JsonObject& parameters, JsonObject& response); private: PluginHost::IShell* mService; diff --git a/Analytics/AnalyticsJsonRpc.cpp b/Analytics/AnalyticsJsonRpc.cpp index f5139ec1e5..d3fb127ad0 100644 --- a/Analytics/AnalyticsJsonRpc.cpp +++ b/Analytics/AnalyticsJsonRpc.cpp @@ -21,9 +21,6 @@ #include "UtilsJsonRpc.h" const string WPEFramework::Plugin::Analytics::ANALYTICS_METHOD_SEND_EVENT = "sendEvent"; -// TODO: To be removed once the Analytics is capable of handling it internally -const string WPEFramework::Plugin::Analytics::ANALYTICS_METHOD_SET_SESSION_ID = "setSessionId"; -const string WPEFramework::Plugin::Analytics::ANALYTICS_METHOD_SET_TIME_READY = "setTimeReady"; namespace WPEFramework { @@ -34,15 +31,11 @@ namespace Plugin { void Analytics::RegisterAll() { Register(_T(ANALYTICS_METHOD_SEND_EVENT), &Analytics::SendEventWrapper, this); - Register(_T(ANALYTICS_METHOD_SET_SESSION_ID), &Analytics::SetSessionIdWrapper, this); - Register(_T(ANALYTICS_METHOD_SET_TIME_READY), &Analytics::SetTimeReadyWrapper, this); } void Analytics::UnregisterAll() { Unregister(_T(ANALYTICS_METHOD_SEND_EVENT)); - Unregister(_T(ANALYTICS_METHOD_SET_SESSION_ID)); - Unregister(_T(ANALYTICS_METHOD_SET_TIME_READY)); } // API implementation @@ -93,44 +86,6 @@ namespace Plugin { returnResponse(result == Core::ERROR_NONE); } - // Method: setSessionId - Set the session ID - // Return codes: - // - ERROR_NONE: Success - // - ERROR_GENERAL: Failed to set the session ID - uint32_t Analytics::SetSessionIdWrapper(const JsonObject& parameters, JsonObject& response) - { - LOGINFOMETHOD(); - - uint32_t result = Core::ERROR_NONE; - - returnIfStringParamNotFound(parameters, "sessionId"); - - string sessionId = parameters["sessionId"].String(); - - if (mAnalytics != nullptr) { - result = mAnalytics->SetSessionId(sessionId); - } - - returnResponse(result == Core::ERROR_NONE); - } - - // Method: setTimeReady - Set the time ready - // Return codes: - // - ERROR_NONE: Success - // - ERROR_GENERAL: Failed to set the time ready - uint32_t Analytics::SetTimeReadyWrapper(const JsonObject& parameters, JsonObject& response) - { - LOGINFOMETHOD(); - - uint32_t result = Core::ERROR_NONE; - - if (mAnalytics != nullptr) { - result = mAnalytics->SetTimeReady(); - } - - returnResponse(result == Core::ERROR_NONE); - } - } } \ No newline at end of file diff --git a/Analytics/CHANGELOG.md b/Analytics/CHANGELOG.md index e23241b2ce..053a2bab2b 100644 --- a/Analytics/CHANGELOG.md +++ b/Analytics/CHANGELOG.md @@ -14,6 +14,9 @@ All notable changes to this RDK Service will be documented in this file. For more details, refer to versioning section under Main README. +## [1.0.1] - 2024-10-16 +- Support generation of sessionID, SysTime validation and limit nbr of external attributes for Sift + ## [1.0.0] - 2024-07-25 ### Added - New RDK Service Analytics to handle analytics events and send them to dedicated backends diff --git a/Analytics/CMakeLists.txt b/Analytics/CMakeLists.txt index 247f6200d2..b350bccc2b 100644 --- a/Analytics/CMakeLists.txt +++ b/Analytics/CMakeLists.txt @@ -20,7 +20,7 @@ set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) set(VERSION_MAJOR 1) set(VERSION_MINOR 0) -set(VERSION_PATCH 0) +set(VERSION_PATCH 1) add_compile_definitions(ANALYTICS_MAJOR_VERSION=${VERSION_MAJOR}) add_compile_definitions(ANALYTICS_MINOR_VERSION=${VERSION_MINOR}) @@ -42,13 +42,14 @@ set(PLUGIN_ANALYTICS_SIFT_ENV "prod" CACHE STRING "Sift environment") set(PLUGIN_ANALYTICS_SIFT_PRODUCT_NAME "entos" CACHE STRING "Sift product name") #entos-immerse in Sift2.0 set(PLUGIN_ANALYTICS_SIFT_LOGGER_NAME "Analytics" CACHE STRING "Sift logger name") set(PLUGIN_ANALYTICS_SIFT_LOGGER_VERSION "${MODULE_VERSION}" CACHE STRING "Sift logger version") +set(PLUGIN_ANALYTICS_SIFT_PLATFORM_DEFAULT "entos:rdk" CACHE STRING "Sift platform default value") set(PLUGIN_ANALYTICS_SIFT_MAX_RANDOMISATION_WINDOW_TIME 300 CACHE STRING "Sift max randomisation window time of posting queued events") set(PLUGIN_ANALYTICS_SIFT_MAX_EVENTS_IN_POST 10 CACHE STRING "Sift max events in post") set(PLUGIN_ANALYTICS_SIFT_MAX_RETRIES 10 CACHE STRING "Sift max retries posting events") set(PLUGIN_ANALYTICS_SIFT_MIN_RETRY_PERIOD 1 CACHE STRING "Sift min retry period seconds") set(PLUGIN_ANALYTICS_SIFT_MAX_RETRY_PERIOD 30 CACHE STRING "Sift max retry period seconds") set(PLUGIN_ANALYTICS_SIFT_EXPONENTIAL_PERIODIC_FACTOR 2 CACHE STRING "Sift exponential periodic factor") -set(PLUGIN_ANALYTICS_SIFT_STORE_PATH "/opt/persistent/sky/AnalyticsSiftStore" CACHE STRING "Sift store path") +set(PLUGIN_ANALYTICS_SIFT_STORE_PATH "/persistent/AnalyticsSiftStore" CACHE STRING "Sift store path") set(PLUGIN_ANALYTICS_SIFT_STORE_EVENTS_LIMIT 1000 CACHE STRING "Sift store events limit") set(PLUGIN_ANALYTICS_SIFT_URL "" CACHE STRING "Sift URL") @@ -67,8 +68,11 @@ add_library(${MODULE_NAME} SHARED Module.cpp) target_include_directories(${MODULE_NAME} PRIVATE Implementation) +target_include_directories(${MODULE_NAME} PRIVATE Implementation/SystemTime) +target_include_directories(${MODULE_NAME} PRIVATE ../) target_include_directories(${MODULE_NAME} PRIVATE ../helpers) +add_subdirectory(Implementation/SystemTime) add_subdirectory(Implementation/LocalStore) add_subdirectory(Implementation/Backend) @@ -85,7 +89,8 @@ target_link_libraries(${MODULE_NAME} ${NAMESPACE}Definitions::${NAMESPACE}Definitions ${DS_LIBRARIES} ${IARMBUS_LIBRARIES} - ${MODULE_NAME}Backends) + ${MODULE_NAME}Backends + ${MODULE_NAME}SystemTime) install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) diff --git a/Analytics/Implementation/AnalyticsImplementation.cpp b/Analytics/Implementation/AnalyticsImplementation.cpp index 4a2d2e211c..2f4c03829f 100644 --- a/Analytics/Implementation/AnalyticsImplementation.cpp +++ b/Analytics/Implementation/AnalyticsImplementation.cpp @@ -19,6 +19,7 @@ #include "AnalyticsImplementation.h" #include "Backend/AnalyticsBackend.h" #include "UtilsLogging.h" +#include "SystemTime.h" #include #include @@ -36,7 +37,7 @@ namespace Plugin { mQueueCondition(), mActionQueue(), mEventQueue(), - mBackends(IAnalyticsBackendAdministrator::Instances()), + mBackends(IAnalyticsBackendAdministrator::Create()), mSysTimeValid(false), mShell(nullptr) { @@ -45,6 +46,7 @@ namespace Plugin { AnalyticsImplementation::~AnalyticsImplementation() { + LOGINFO("AnalyticsImplementation::~AnalyticsImplementation()"); std::unique_lock lock(mQueueMutex); mActionQueue.push({ACTION_TYPE_SHUTDOWN, nullptr}); lock.unlock(); @@ -52,13 +54,13 @@ namespace Plugin { mThread.join(); } - /* virtual */ uint32_t AnalyticsImplementation::SendEvent(const string& eventName, + /* virtual */ Core::hresult AnalyticsImplementation::SendEvent(const string& eventName, const string& eventVersion, const string& eventSource, const string& eventSourceVersion, - RPC::IStringIterator* const& cetList, - const uint64_t& epochTimestamp, - const uint64_t& uptimeTimestamp, + IStringIterator* const& cetList, + const uint64_t epochTimestamp, + const uint64_t uptimeTimestamp, const string& eventPayload) { std::shared_ptr event = std::make_shared(); @@ -98,28 +100,6 @@ namespace Plugin { return Core::ERROR_NONE; } - uint32_t AnalyticsImplementation::SetSessionId(const string& id) - { - uint32_t ret = Core::ERROR_GENERAL; - // set session id in sift backend - if (mBackends.find(IAnalyticsBackend::SIFT) != mBackends.end()) - { - ret = mBackends.at(IAnalyticsBackend::SIFT).SetSessionId(id); - } - - return ret; - } - - uint32_t AnalyticsImplementation::SetTimeReady() - { - // set time ready action - std::unique_lock lock(mQueueMutex); - mActionQueue.push({ACTION_TYPE_SET_TIME_READY, nullptr}); - lock.unlock(); - mQueueCondition.notify_one(); - return Core::ERROR_NONE; - } - uint32_t AnalyticsImplementation::Configure(PluginHost::IShell* shell) { LOGINFO("Configuring Analytics"); @@ -127,10 +107,16 @@ namespace Plugin { ASSERT(shell != nullptr); mShell = shell; + mSysTime = std::make_shared(shell); + if(mSysTime == nullptr) + { + LOGERR("Failed to create SystemTime instance"); + } + for (auto &backend : mBackends) { LOGINFO("Configuring backend: %s", backend.first.c_str()); - backend.second.Configure(shell); + backend.second->Configure(shell, mSysTime); } return result; @@ -177,7 +163,7 @@ namespace Plugin { switch (action.type) { case ACTION_POPULATE_TIME_INFO: - //mSysTimeValid = IsSysTimeValid(); + mSysTimeValid = IsSysTimeValid(); if ( mSysTimeValid ) { @@ -225,24 +211,8 @@ namespace Plugin { } break; case ACTION_TYPE_SHUTDOWN: + LOGINFO("Shutting down Analytics"); return; - case ACTION_TYPE_SET_TIME_READY: - { - mSysTimeValid = true; - // Send the events from the queue, if there are any. - while ( !mEventQueue.empty() ) - { - AnalyticsImplementation::Event event = mEventQueue.front(); - // convert uptime to epoch timestamp - if (event.epochTimestamp == 0) - { - event.epochTimestamp = ConvertUptimeToTimestampInMs(event.uptimeTimestamp); - } - - SendEventToBackend( event ); - mEventQueue.pop(); - } - }break; default: break; } @@ -254,8 +224,10 @@ namespace Plugin { bool AnalyticsImplementation::IsSysTimeValid() { bool ret = false; - //TODO: Add system time validationm - // For now, relay on setTimeReady call + if (mSysTime != nullptr) + { + ret = mSysTime->IsSystemTimeAvailable(); + } return ret; } @@ -279,7 +251,7 @@ namespace Plugin { else if (mBackends.find(IAnalyticsBackend::SIFT) != mBackends.end()) { LOGINFO("Sending event to Sift backend: %s", event.eventName.c_str()); - mBackends.at(IAnalyticsBackend::SIFT).SendEvent(backendEvent); + mBackends.at(IAnalyticsBackend::SIFT)->SendEvent(backendEvent); } } diff --git a/Analytics/Implementation/AnalyticsImplementation.h b/Analytics/Implementation/AnalyticsImplementation.h index 2dc6f98377..1a1255bfbd 100644 --- a/Analytics/Implementation/AnalyticsImplementation.h +++ b/Analytics/Implementation/AnalyticsImplementation.h @@ -22,6 +22,7 @@ #include #include #include "Backend/AnalyticsBackend.h" +#include "SystemTime.h" #include #include @@ -76,16 +77,14 @@ namespace Plugin { // IAnalyticsImplementation interface - uint32_t SendEvent(const string& eventName, + Core::hresult SendEvent(const string& eventName, const string& eventVersion, const string& eventSource, const string& eventSourceVersion, - RPC::IStringIterator* const& cetList, - const uint64_t& epochTimestamp, - const uint64_t& uptimeTimestamp, + IStringIterator* const& cetList, + const uint64_t epochTimestamp, + const uint64_t uptimeTimestamp, const string& eventPayload) override; - uint32_t SetSessionId(const string& id) override; - uint32_t SetTimeReady() override; // IConfiguration interface @@ -107,6 +106,7 @@ namespace Plugin { const IAnalyticsBackends mBackends; bool mSysTimeValid; PluginHost::IShell* mShell; + SystemTimePtr mSysTime; }; } } \ No newline at end of file diff --git a/Analytics/Implementation/Backend/AnalyticsBackend.cpp b/Analytics/Implementation/Backend/AnalyticsBackend.cpp index 9e43324792..ea3520801d 100644 --- a/Analytics/Implementation/Backend/AnalyticsBackend.cpp +++ b/Analytics/Implementation/Backend/AnalyticsBackend.cpp @@ -27,12 +27,11 @@ namespace Plugin { const std::string IAnalyticsBackend::SIFT = "Sift"; -IAnalyticsBackends& IAnalyticsBackendAdministrator::Instances() +IAnalyticsBackends IAnalyticsBackendAdministrator::Create() { - static SiftBackend siftBackend; - static IAnalyticsBackends backendInstances = { + IAnalyticsBackends backendInstances = { #ifdef ANALYTICS_SIFT_BACKEND - {IAnalyticsBackend::SIFT, siftBackend}, + {IAnalyticsBackend::SIFT, std::make_shared()}, #endif }; return (backendInstances); diff --git a/Analytics/Implementation/Backend/AnalyticsBackend.h b/Analytics/Implementation/Backend/AnalyticsBackend.h index 5ee93b37b7..4330e58d34 100644 --- a/Analytics/Implementation/Backend/AnalyticsBackend.h +++ b/Analytics/Implementation/Backend/AnalyticsBackend.h @@ -21,6 +21,7 @@ #include #include #include "../../Module.h" +#include "../SystemTime/SystemTime.h" // Interface for Analytics Backedn namespace WPEFramework { @@ -42,15 +43,16 @@ namespace Plugin { const static std::string SIFT; - virtual uint32_t Configure(PluginHost::IShell* shell) = 0; + virtual uint32_t Configure(PluginHost::IShell* shell, SystemTimePtr sysTime) = 0; virtual uint32_t SendEvent(const Event& event) = 0; - virtual uint32_t SetSessionId(const std::string& sessionId) = 0; }; - typedef std::map IAnalyticsBackends; + typedef std::shared_ptr IAnalyticsBackendPtr; + + typedef std::map IAnalyticsBackends; struct IAnalyticsBackendAdministrator { - static IAnalyticsBackends& Instances(); + static IAnalyticsBackends Create(); virtual ~IAnalyticsBackendAdministrator() = default; }; diff --git a/Analytics/Implementation/Backend/Sift/CMakeLists.txt b/Analytics/Implementation/Backend/Sift/CMakeLists.txt index 5c7992fc32..8ebc4a350d 100644 --- a/Analytics/Implementation/Backend/Sift/CMakeLists.txt +++ b/Analytics/Implementation/Backend/Sift/CMakeLists.txt @@ -32,6 +32,9 @@ endif (CURL_FOUND) target_include_directories(${TARGET_LIB} PUBLIC "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") target_include_directories(${TARGET_LIB} PRIVATE ../../../../helpers) target_include_directories(${TARGET_LIB} PRIVATE ../../LocalStore) +target_include_directories(${TARGET_LIB} PRIVATE ../../SystemTime) set_property(TARGET ${TARGET_LIB} PROPERTY POSITION_INDEPENDENT_CODE ON) set_target_properties(${TARGET_LIB} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF) -target_link_libraries(${TARGET_LIB} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${NAMESPACE}${PLUGIN_NAME}LocalStore) \ No newline at end of file +target_link_libraries(${TARGET_LIB} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) +target_link_libraries(${TARGET_LIB} PRIVATE ${NAMESPACE}${PLUGIN_NAME}LocalStore) +target_link_libraries(${TARGET_LIB} PRIVATE ${NAMESPACE}${PLUGIN_NAME}SystemTime) \ No newline at end of file diff --git a/Analytics/Implementation/Backend/Sift/SiftBackend.cpp b/Analytics/Implementation/Backend/Sift/SiftBackend.cpp index 5a028fb74a..6ffb80536b 100644 --- a/Analytics/Implementation/Backend/Sift/SiftBackend.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftBackend.cpp @@ -40,12 +40,14 @@ namespace WPEFramework , mConfigPtr(nullptr) , mStorePtr(nullptr) , mUploaderPtr(nullptr) + , mSessionId() { mThread = std::thread(&SiftBackend::ActionLoop, this); } SiftBackend::~SiftBackend() { + LOGINFO("SiftBackend::~SiftBackend"); Action action = {ACTION_TYPE_SHUTDOWN, nullptr}; { std::lock_guard lock(mQueueMutex); @@ -55,12 +57,12 @@ namespace WPEFramework mThread.join(); } - /* virtual */ uint32_t SiftBackend::Configure(PluginHost::IShell *shell) + /* virtual */ uint32_t SiftBackend::Configure(PluginHost::IShell *shell, SystemTimePtr sysTime) { ASSERT(shell != nullptr); std::unique_lock lock(mQueueMutex); mShell = shell; - mConfigPtr = std::unique_ptr(new SiftConfig(shell)); + mConfigPtr = std::unique_ptr(new SiftConfig(shell, sysTime)); return Core::ERROR_NONE; } @@ -76,17 +78,6 @@ namespace WPEFramework return Core::ERROR_NONE; } - /* virtual */ uint32_t SiftBackend::SetSessionId(const std::string &sessionId) - { - std::unique_lock lock(mQueueMutex); - if (mConfigPtr != nullptr) - { - mConfigPtr->SetSessionId(sessionId); - return Core::ERROR_NONE; - } - return Core::ERROR_GENERAL; - } - void SiftBackend::ActionLoop() { std::unique_lock lock(mQueueMutex); @@ -153,7 +144,6 @@ namespace WPEFramework { mUploaderPtr = std::unique_ptr(new SiftUploader(mStorePtr, uploaderConfig.url, - uploaderConfig.apiKey, uploaderConfig.maxRandomisationWindowTime, uploaderConfig.maxEventsInPost, uploaderConfig.maxRetries, @@ -172,9 +162,9 @@ namespace WPEFramework configValid = true; // For Sift 1.0 update uploader with auth values if avaliable // So they will be added to the events if missing - if (!attributes.schema2Enabled && !attributes.accountId.empty() && !attributes.deviceId.empty() && !attributes.partnerId.empty()) + if (!attributes.schema2Enabled && !attributes.xboAccountId.empty() && !attributes.xboDeviceId.empty() && !attributes.partnerId.empty()) { - mUploaderPtr->setDeviceInfoRequiredFields(attributes.accountId, attributes.deviceId, attributes.partnerId); + mUploaderPtr->setDeviceInfoRequiredFields(attributes.xboAccountId, attributes.xboDeviceId, attributes.partnerId); } } else @@ -212,6 +202,7 @@ namespace WPEFramework } break; case ACTION_TYPE_SHUTDOWN: + LOGINFO("Shutting down SiftBackend"); return; default: break; @@ -223,6 +214,11 @@ namespace WPEFramework bool SiftBackend::SendEventInternal(const Event &event, const SiftConfig::Attributes &attributes) { + if (mSessionId.empty()) + { + mSessionId = GenerateRandomUUID(); + } + JsonObject eventJson = JsonObject(); if (attributes.schema2Enabled) { @@ -260,13 +256,13 @@ namespace WPEFramework } eventJson["device_model"] = attributes.deviceModel; eventJson["device_type"] = attributes.deviceType; - eventJson["device_timezone"] = std::stoi(attributes.deviceTimeZone); + eventJson["device_timezone"] = attributes.deviceTimeZone; eventJson["device_os_name"] = attributes.deviceOsName; eventJson["device_os_version"] = attributes.deviceOsVersion; eventJson["platform"] = attributes.platform; eventJson["device_manufacturer"] = attributes.deviceManufacturer; eventJson["authenticated"] = attributes.authenticated; - eventJson["session_id"] = attributes.sessionId; + eventJson["session_id"] = mSessionId; eventJson["proposition"] = attributes.proposition; if (!attributes.retailer.empty()) { @@ -318,13 +314,13 @@ namespace WPEFramework eventJson["event_name"] = event.eventName; eventJson["event_schema"] = attributes.productName + "/" + event.eventName + "/" + event.eventVersion; eventJson["event_payload"] = JsonObject(event.eventPayload); - eventJson["session_id"] = attributes.sessionId; + eventJson["session_id"] = mSessionId; eventJson["event_id"] = GenerateRandomUUID(); - if (!attributes.accountId.empty() && !attributes.deviceId.empty() && !attributes.partnerId.empty()) + if (!attributes.xboAccountId.empty() && !attributes.xboDeviceId.empty() && !attributes.partnerId.empty()) { - eventJson["account_id"] = attributes.accountId; - eventJson["device_id"] = attributes.deviceId; + eventJson["account_id"] = attributes.xboAccountId; + eventJson["device_id"] = attributes.xboDeviceId; eventJson["partner_id"] = attributes.partnerId; } else @@ -335,9 +331,9 @@ namespace WPEFramework eventJson["app_name"] = attributes.deviceAppName; eventJson["app_ver"] = attributes.deviceAppVersion; eventJson["device_model"] = attributes.deviceModel; - eventJson["device_timezone"] = std::stoi(attributes.deviceTimeZone); + eventJson["device_timezone"] = attributes.deviceTimeZone; eventJson["platform"] = attributes.platform; - eventJson["os_ver"] = attributes.deviceSoftwareVersion; + eventJson["os_ver"] = attributes.deviceOsVersion; eventJson["device_language"] = ""; // Empty for now eventJson["timestamp"] = event.epochTimestamp; eventJson["device_type"] = attributes.deviceType; diff --git a/Analytics/Implementation/Backend/Sift/SiftBackend.h b/Analytics/Implementation/Backend/Sift/SiftBackend.h index e7f167a0b4..2057ab6508 100644 --- a/Analytics/Implementation/Backend/Sift/SiftBackend.h +++ b/Analytics/Implementation/Backend/Sift/SiftBackend.h @@ -40,8 +40,7 @@ namespace Plugin { SiftBackend(); ~SiftBackend(); uint32_t SendEvent(const Event& event) override; - uint32_t Configure(PluginHost::IShell* shell) override; - uint32_t SetSessionId(const std::string& sessionId) override; + uint32_t Configure(PluginHost::IShell* shell, SystemTimePtr sysTime) override; private: @@ -83,6 +82,7 @@ namespace Plugin { SiftConfigPtr mConfigPtr; SiftStorePtr mStorePtr; SiftUploaderPtr mUploaderPtr; + std::string mSessionId; }; } diff --git a/Analytics/Implementation/Backend/Sift/SiftConfig.cpp b/Analytics/Implementation/Backend/Sift/SiftConfig.cpp index ab8f23de51..71abc1ff52 100644 --- a/Analytics/Implementation/Backend/Sift/SiftConfig.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftConfig.cpp @@ -55,6 +55,7 @@ namespace WPEFramework , ProductName() , LoggerName() , LoggerVersion() + , PlatformDfl("entos:rdk") , MaxRandomisationWindowTime(300) , MaxEventsInPost(10) , MaxRetries(10) @@ -72,6 +73,7 @@ namespace WPEFramework Add(_T("productname"), &ProductName); Add(_T("loggername"), &LoggerName); Add(_T("loggerversion"), &LoggerVersion); + Add(_T("platformdefalut"), &PlatformDfl); Add(_T("maxrandomisationwindowtime"), &MaxRandomisationWindowTime); Add(_T("maxeventsinpost"), &MaxEventsInPost); Add(_T("maxretries"), &MaxRetries); @@ -91,6 +93,7 @@ namespace WPEFramework Core::JSON::String ProductName; Core::JSON::String LoggerName; Core::JSON::String LoggerVersion; + Core::JSON::String PlatformDfl; Core::JSON::DecUInt32 MaxRandomisationWindowTime; Core::JSON::DecUInt32 MaxEventsInPost; Core::JSON::DecUInt32 MaxRetries; @@ -303,13 +306,14 @@ namespace WPEFramework } }; - SiftConfig::SiftConfig(PluginHost::IShell *shell) : mInitializationThread(), + SiftConfig::SiftConfig(PluginHost::IShell *shell, SystemTimePtr systemTime) : mInitializationThread(), mMonitorKeys(), mMutex(), mAttributes(), mStoreConfig(), mUploaderConfig(), - mShell(shell) + mShell(shell), + mSystemTime(systemTime) { ASSERT(shell != nullptr); ParsePluginConfig(); @@ -319,6 +323,7 @@ namespace WPEFramework SiftConfig::~SiftConfig() { + LOGINFO("SiftConfig::~SiftConfig"); mInitializationThread.join(); // Unregister for notifications auto interface = mShell->QueryInterfaceByCallsign(PERSISTENT_STORE_CALLSIGN); @@ -338,6 +343,7 @@ namespace WPEFramework { // Get latest values from AuthService GetAuthServiceValues(); + bool timeZoneValid = GetTimeZone(); mMutex.lock(); @@ -349,8 +355,10 @@ namespace WPEFramework bool activatedValid = mAttributes.activated ? (!mAttributes.xboDeviceId.empty() && !mAttributes.xboAccountId.empty()) : true; - valid = (!mAttributes.sessionId.empty() - && !mAttributes.commonSchema.empty() + // Sift2.0 platform equals proposition + mAttributes.platform = mAttributes.proposition; + + valid = (!mAttributes.commonSchema.empty() && !mAttributes.productName.empty() && !mAttributes.productVersion.empty() && !mAttributes.loggerName.empty() @@ -359,15 +367,14 @@ namespace WPEFramework && activatedValid && !mAttributes.deviceModel.empty() && !mAttributes.deviceType.empty() - && !mAttributes.deviceTimeZone.empty() && !mAttributes.deviceOsName.empty() && !mAttributes.deviceOsVersion.empty() && !mAttributes.platform.empty() && !mAttributes.deviceManufacturer.empty() - && !mAttributes.sessionId.empty() && !mAttributes.proposition.empty() && !mAttributes.deviceSerialNumber.empty() - && !mAttributes.deviceMacAddress.empty()); + && !mAttributes.deviceMacAddress.empty() + && timeZoneValid); LOGINFO(" commonSchema: %s," " productName: %s," @@ -378,15 +385,14 @@ namespace WPEFramework " activatedValid %d," " deviceModel: %s," " deviceType: %s," - " deviceTimeZone: %s," " deviceOsName: %s," " deviceOsVersion: %s," " platform: %s," " deviceManufacturer: %s," - " sessionId: %s," " proposition: %s," " deviceSerialNumber: %s," - " deviceMacAddress: %s,", + " deviceMacAddress: %s," + " timeZoneValid: %d", mAttributes.commonSchema.c_str(), mAttributes.productName.c_str(), mAttributes.productVersion.c_str(), @@ -396,15 +402,14 @@ namespace WPEFramework activatedValid, mAttributes.deviceModel.c_str(), mAttributes.deviceType.c_str(), - mAttributes.deviceTimeZone.c_str(), mAttributes.deviceOsName.c_str(), mAttributes.deviceOsVersion.c_str(), mAttributes.platform.c_str(), mAttributes.deviceManufacturer.c_str(), - mAttributes.sessionId.c_str(), mAttributes.proposition.c_str(), mAttributes.deviceSerialNumber.c_str(), - mAttributes.deviceMacAddress.c_str()); + mAttributes.deviceMacAddress.c_str(), + timeZoneValid); if (valid) { @@ -420,24 +425,25 @@ namespace WPEFramework } else //Sift 1.0 required attributes { - valid = (!mAttributes.sessionId.empty() - && !mAttributes.productName.empty() + + valid = (!mAttributes.productName.empty() && !mAttributes.deviceAppName.empty() && !mAttributes.deviceAppVersion.empty() && !mAttributes.deviceModel.empty() - && !mAttributes.deviceTimeZone.empty() && !mAttributes.platform.empty() - && !mAttributes.deviceSoftwareVersion.empty() - && !mAttributes.deviceType.empty()); + && !mAttributes.deviceOsVersion.empty() + && !mAttributes.deviceType.empty() + && timeZoneValid); - LOGINFO("%s, %s, %s, %s, %s, %s, %s", + LOGINFO("%s, %s, %s, %s, %s, %s, %s, %d", mAttributes.productName.c_str(), mAttributes.deviceAppName.c_str(), mAttributes.deviceAppVersion.c_str(), mAttributes.deviceModel.c_str(), mAttributes.platform.c_str(), - mAttributes.deviceSoftwareVersion.c_str(), - mAttributes.deviceType.c_str()); + mAttributes.deviceOsVersion.c_str(), + mAttributes.deviceType.c_str(), + timeZoneValid); } if (valid) @@ -464,8 +470,7 @@ namespace WPEFramework bool SiftConfig::GetUploaderConfig(UploaderConfig &config) { mMutex.lock(); - bool valid = !mUploaderConfig.url.empty() - && !mUploaderConfig.apiKey.empty(); + bool valid = !mUploaderConfig.url.empty(); if (valid) { config = mUploaderConfig; @@ -474,13 +479,6 @@ namespace WPEFramework return valid; } - void SiftConfig::SetSessionId(const std::string &sessionId) - { - mMutex.lock(); - mAttributes.sessionId = sessionId; - mMutex.unlock(); - } - void SiftConfig::TriggerInitialization() { mInitializationThread = std::thread(&SiftConfig::Initialize, this); @@ -489,38 +487,32 @@ namespace WPEFramework void SiftConfig::InitializeKeysMap() { //SIFT 2.0 attributes from persistent storage - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceHardwareModel"] = &mAttributes.deviceModel; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceType"] = &mAttributes.deviceType; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["devicePlatform"] = &mAttributes.platform; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["modelNumber"] = &mAttributes.deviceOsVersion; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["manufacturer"] = &mAttributes.deviceManufacturer; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["serialNumber"] = &mAttributes.deviceSerialNumber; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["macAddress"] = &mAttributes.deviceMacAddress; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["entertainmentOSVersion"] = &mAttributes.productVersion; - mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["proposition"] = &mAttributes.proposition; + //For Sift 1.0 use default values form config + if (mAttributes.schema2Enabled) + { + mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["proposition"] = &mAttributes.proposition; + } mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["retailer"] = &mAttributes.retailer; mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["jvagent"] = &mAttributes.jvAgent; mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["coam"] = &mAttributes.coam; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["country"] = &mAttributes.country;//TODO - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["region"] = &mAttributes.region;//TODO mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["accountType"] = &mAttributes.accountType; mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["operator"] = &mAttributes.accountOperator; mKeysMap[PERSISTENT_STORE_ACCOUNT_PROFILE_NAMESPACE]["detailType"] = &mAttributes.accountDetailType; - //TODO: Values provided by AS but should be provided by RDK - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceTimeZone"] = &mAttributes.deviceTimeZone; - //SIFT 1.0 attributes from persistent storage - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceSoftwareVersion"] = &mAttributes.deviceSoftwareVersion; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceAppName"] = &mAttributes.deviceAppName; mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceAppVersion"] = &mAttributes.deviceAppVersion; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["accountId"] = &mAttributes.accountId; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["deviceId"] = &mAttributes.deviceId; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["partnerId"] = &mAttributes.partnerId; // If Sift url empty, try to get from persistent store - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["sift_url"] = &mUploaderConfig.url; - mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["sift_xapikey"] = &mUploaderConfig.apiKey; + if (mUploaderConfig.url.empty()) + { + mKeysMap[PERSISTENT_STORE_ANALYTICS_NAMESPACE]["sift_url"] = &mUploaderConfig.url; + } } void SiftConfig::ParsePluginConfig() @@ -546,6 +538,10 @@ namespace WPEFramework mAttributes.loggerName = config.Sift.LoggerName.Value(); mAttributes.loggerVersion = config.Sift.LoggerVersion.Value(); mAttributes.deviceOsName = config.DeviceOsName.Value(); + + // Set a default values for platform and proposition if not available in Persistent Store + mAttributes.platform = config.Sift.PlatformDfl.Value(); + mAttributes.proposition = config.Sift.PlatformDfl.Value(); mStoreConfig.path = config.Sift.StorePath.Value(); mStoreConfig.eventsLimit = config.Sift.EventsLimit.Value(); @@ -558,12 +554,13 @@ namespace WPEFramework mUploaderConfig.maxRetryPeriod = config.Sift.MaxRetryPeriod.Value(); mUploaderConfig.exponentialPeriodicFactor = config.Sift.ExponentialPeriodicFactor.Value(); - SYSLOG(Logging::Startup, (_T("Parsed Analytics config: '%s', '%s', '%s', '%s', '%s', '%s'."), + SYSLOG(Logging::Startup, (_T("Parsed Analytics config: '%s', '%s', '%s', '%s', '%s', '%s', '%s'."), mAttributes.commonSchema.c_str(), mAttributes.env.c_str(), mAttributes.productName.c_str(), mAttributes.loggerName.c_str(), mAttributes.loggerVersion.c_str(), + mAttributes.platform.c_str(), mAttributes.deviceOsName.c_str() )); } @@ -607,8 +604,6 @@ namespace WPEFramework { ActivatePlugin(mShell, AUTHSERVICE_CALLSIGN); } - - GetAuthServiceValues(); //Activate System plugin if needed if (IsPluginActivated(mShell, SYSTEM_CALLSIGN) == false) @@ -635,6 +630,22 @@ namespace WPEFramework LOGINFO("Got env %s", mAttributes.env.c_str()); } + if (result == Core::ERROR_NONE && response.HasLabel("estb_mac")) + { + mMutex.lock(); + mAttributes.deviceMacAddress = response["estb_mac"].String(); + mMutex.unlock(); + LOGINFO("Got deviceMacAddress %s", mAttributes.deviceMacAddress.c_str()); + } + + if (result == Core::ERROR_NONE && response.HasLabel("model_number")) + { + mMutex.lock(); + mAttributes.deviceModel = response["model_number"].String(); + mMutex.unlock(); + LOGINFO("Got deviceModel %s", mAttributes.deviceModel.c_str()); + } + // Get deviceFriendlyName from System.1.getFriendlyName[friendlyName] result = systemLink->Invoke(JSONRPC_THUNDER_TIMEOUT, "getFriendlyName", params, response); if (result == Core::ERROR_NONE && response.HasLabel("friendlyName")) @@ -766,8 +777,8 @@ namespace WPEFramework { mMutex.lock(); mAttributes.xboAccountId = response["serviceAccountId"].String(); - mMutex.unlock(); LOGINFO("Got xboAccountId %s", mAttributes.xboAccountId.c_str()); + mMutex.unlock(); } // get xboDeviceId from AuthService.getXDeviceId @@ -776,8 +787,8 @@ namespace WPEFramework { mMutex.lock(); mAttributes.xboDeviceId = response["xDeviceId"].String(); - mMutex.unlock(); LOGINFO("Got xboDeviceId %s", mAttributes.xboDeviceId.c_str()); + mMutex.unlock(); } mMutex.lock(); @@ -792,6 +803,28 @@ namespace WPEFramework } } + bool SiftConfig::GetTimeZone() + { + int32_t timeZoneOffsetSec = 0; + SystemTime::TimeZoneAccuracy accuracy = SystemTime::TimeZoneAccuracy::ACC_UNDEFINED; + + if (mSystemTime != nullptr && mSystemTime->IsSystemTimeAvailable()) + { + accuracy = mSystemTime->GetTimeZoneOffset(timeZoneOffsetSec); + mMutex.lock(); + mAttributes.deviceTimeZone = timeZoneOffsetSec * 1000; + LOGINFO("Got deviceTimeZone %d, accuracy %d", mAttributes.deviceTimeZone, accuracy); + mMutex.unlock(); + } + + if (accuracy == SystemTime::TimeZoneAccuracy::FINAL) + { + return true; + } + + return false; + } + void SiftConfig::ActivatePlugin(PluginHost::IShell *shell, const char *callSign) { JsonObject joParams; diff --git a/Analytics/Implementation/Backend/Sift/SiftConfig.h b/Analytics/Implementation/Backend/Sift/SiftConfig.h index 09032dddc2..46aa0c9635 100644 --- a/Analytics/Implementation/Backend/Sift/SiftConfig.h +++ b/Analytics/Implementation/Backend/Sift/SiftConfig.h @@ -19,6 +19,7 @@ #pragma once #include "../../../Module.h" +#include "../../SystemTime/SystemTime.h" #include #include #include @@ -47,13 +48,12 @@ namespace WPEFramework bool activated; std::string deviceModel; std::string deviceType; - std::string deviceTimeZone; + int32_t deviceTimeZone; std::string deviceOsName; std::string deviceOsVersion; std::string platform; std::string deviceManufacturer; bool authenticated; - std::string sessionId; std::string proposition; std::string retailer; std::string jvAgent; @@ -68,11 +68,8 @@ namespace WPEFramework std::string accountDetailType; // Sift 1.0 atributes that left - std::string deviceSoftwareVersion; std::string deviceAppName; std::string deviceAppVersion; - std::string accountId; - std::string deviceId; }; struct StoreConfig @@ -84,7 +81,6 @@ namespace WPEFramework struct UploaderConfig { std::string url; - std::string apiKey; uint32_t maxRandomisationWindowTime; uint32_t maxEventsInPost; uint32_t maxRetries; @@ -96,13 +92,12 @@ namespace WPEFramework SiftConfig(const SiftConfig &) = delete; SiftConfig &operator=(const SiftConfig &) = delete; - SiftConfig(PluginHost::IShell *shell); + SiftConfig(PluginHost::IShell *shell, SystemTimePtr systemTime); ~SiftConfig(); bool GetAttributes(Attributes &attributes); bool GetStoreConfig(StoreConfig &config); bool GetUploaderConfig(UploaderConfig &config); - void SetSessionId(const std::string &sessionId); private: class MonitorKeys : public Exchange::IStore::INotification { @@ -136,6 +131,7 @@ namespace WPEFramework uint32_t GetValueFromPersistent(const string &ns, const string &key, string &value); void GetAuthServiceValues(); + bool GetTimeZone(); static void ActivatePlugin(PluginHost::IShell *shell, const char *callSign); static bool IsPluginActivated(PluginHost::IShell *shell, const char *callSign); @@ -147,6 +143,7 @@ namespace WPEFramework UploaderConfig mUploaderConfig; PluginHost::IShell *mShell; std::map> mKeysMap; + SystemTimePtr mSystemTime; }; typedef std::unique_ptr SiftConfigPtr; diff --git a/Analytics/Implementation/Backend/Sift/SiftUploader.cpp b/Analytics/Implementation/Backend/Sift/SiftUploader.cpp index e3a839bd7d..814551730e 100644 --- a/Analytics/Implementation/Backend/Sift/SiftUploader.cpp +++ b/Analytics/Implementation/Backend/Sift/SiftUploader.cpp @@ -31,7 +31,6 @@ namespace WPEFramework { SiftUploader::SiftUploader(SiftStorePtr storePtr, const std::string &url, - const std::string &apiKey, const uint32_t &maxRandomisationWindowTime, const uint32_t &maxEventsInPost, const uint32_t &maxRetries, @@ -40,7 +39,6 @@ namespace WPEFramework const uint32_t &exponentialPeriodicFactor) : mStorePtr(storePtr) , mUrl(url) - , mApiKey(apiKey) , mMaxRandomisationWindowTime(maxRandomisationWindowTime) , mMaxEventsInPost(maxEventsInPost) , mMaxRetries(maxRetries) @@ -57,6 +55,7 @@ namespace WPEFramework SiftUploader::~SiftUploader() { + LOGINFO("SiftUploader::~SiftUploader"); { std::lock_guard lock(mMutex); mStop = true; @@ -88,7 +87,7 @@ namespace WPEFramework [this] () { return mStop; } ); if (mStop) { - LOGINFO("SiftUploader exit"); + LOGINFO("SiftUploader Run exit"); return; } @@ -142,7 +141,7 @@ namespace WPEFramework do { - respcode = PostJson(mUrl, mApiKey, jsonEventPayload, resp); + respcode = PostJson(mUrl, jsonEventPayload, resp); } while ((respcode != 200) && (respcode != 400) && PerformWaitIfRetryNeeded()); if ((respcode == 200) || (respcode == 400)) @@ -375,13 +374,13 @@ namespace WPEFramework return size * nmemb; } - uint32_t SiftUploader::PostJson(const std::string &url, const std::string &apiKey, const std::string &json, std::string &response) + uint32_t SiftUploader::PostJson(const std::string &url, const std::string &json, std::string &response) { CURL *curl; CURLcode res; uint32_t retHttpCode = 0; - if (url.empty() || apiKey.empty() || json.empty()) + if (url.empty() || json.empty()) { LOGERR("Invalid parameters for postJson"); return retHttpCode; @@ -400,9 +399,7 @@ namespace WPEFramework // Create a linked list of custom headers struct curl_slist *headers = NULL; - std::string keyHeader("X-Api-Key: " + apiKey); headers = curl_slist_append(headers, "Content-Type: application/json"); - headers = curl_slist_append(headers, keyHeader.data()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); diff --git a/Analytics/Implementation/Backend/Sift/SiftUploader.h b/Analytics/Implementation/Backend/Sift/SiftUploader.h index 7da53d549d..a3856394c4 100644 --- a/Analytics/Implementation/Backend/Sift/SiftUploader.h +++ b/Analytics/Implementation/Backend/Sift/SiftUploader.h @@ -38,7 +38,6 @@ namespace WPEFramework SiftUploader(SiftStorePtr storePtr, const std::string &url, - const std::string &apiKey, const uint32_t &maxRandomisationWindowTime, const uint32_t &maxEventsInPost, const uint32_t &maxRetries, @@ -69,11 +68,10 @@ namespace WPEFramework void updateEventDeviceInfoIfRequired(JsonObject &event) const; void validateResponse(const std::string &response, const std::vector &events) const; - static uint32_t PostJson(const std::string& url, const std::string& apiKey, const std::string& json, std::string &response); + static uint32_t PostJson(const std::string& url, const std::string& json, std::string &response); SiftStorePtr mStorePtr; std::string mUrl; - std::string mApiKey; uint32_t mMaxRandomisationWindowTime; uint32_t mMaxEventsInPost; uint32_t mMaxRetries; diff --git a/Analytics/Implementation/SystemTime/CMakeLists.txt b/Analytics/Implementation/SystemTime/CMakeLists.txt new file mode 100644 index 0000000000..23f1e3ad53 --- /dev/null +++ b/Analytics/Implementation/SystemTime/CMakeLists.txt @@ -0,0 +1,27 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# 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. +set(TARGET_LIB ${NAMESPACE}${PLUGIN_NAME}SystemTime) + +add_library(${TARGET_LIB} STATIC) + +target_sources(${TARGET_LIB} PRIVATE SystemTime.cpp) + +target_include_directories(${TARGET_LIB} PUBLIC "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") +target_include_directories(${TARGET_LIB} PRIVATE ../../../helpers) +set_property(TARGET ${TARGET_LIB} PROPERTY POSITION_INDEPENDENT_CODE ON) +set_target_properties(${TARGET_LIB} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF) +target_link_libraries(${TARGET_LIB} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) \ No newline at end of file diff --git a/Analytics/Implementation/SystemTime/SystemTime.cpp b/Analytics/Implementation/SystemTime/SystemTime.cpp new file mode 100644 index 0000000000..36aee1d972 --- /dev/null +++ b/Analytics/Implementation/SystemTime/SystemTime.cpp @@ -0,0 +1,493 @@ +/** + * If not stated otherwise in this file or this component's LICENSE + * file the following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * 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 "SystemTime.h" +#include "UtilsLogging.h" +#include "secure_wrapper.h" + +#define JSONRPC_THUNDER_TIMEOUT 2000 +#define THUNDER_ACCESS_DEFAULT_VALUE "127.0.0.1:9998" +#define SYSTEM_CALLSIGN "org.rdk.System.1" + +namespace WPEFramework +{ + namespace Plugin + { + const std::string TIME_QUALITY_STALE{"Stale"}; + const std::string TIME_QUALITY_GOOD{"Good"}; + const std::string TIME_QUALITY_SECURE{"Secure"}; + + SystemTime::SystemTime(PluginHost::IShell *shell) : mQueueLock(), + mQueueCondition(), + mQueue(), + mLock(), + mSystemLink(nullptr), + mTimeQuality(TIME_QUALITY_STALE), + mTimeZone(), + mTimeZoneAccuracyString(), + mTimeZoneAccuracy(ACC_UNDEFINED), + mTimeZoneOffsetSec(0), + mTransitionMap(), + mIsSystemTimeAvailable(false), + mShell(shell) + { + mEventThread = std::thread(&SystemTime::EventLoop, this); + + Event event = {EVENT_INITIALISE, std::string()}; + std::unique_lock lock(mQueueLock); + mQueue.push(event); + lock.unlock(); + mQueueCondition.notify_one(); + } + + SystemTime::~SystemTime() + { + LOGINFO("SystemTime::~SystemTime"); + Event event = {EVENT_SHUTDOWN, std::string()}; + { + std::lock_guard lock(mQueueLock); + mQueue.push(event); + } + mQueueCondition.notify_one(); + mEventThread.join(); + if (mSystemLink != nullptr) + { + mSystemLink->Unsubscribe(JSONRPC_THUNDER_TIMEOUT, _T("onTimeStatusChanged")); + mSystemLink->Unsubscribe(JSONRPC_THUNDER_TIMEOUT, _T("onTimeZoneDSTChanged")); + } + } + + bool SystemTime::IsSystemTimeAvailable() + { + bool isAvailable = false; + { + std::lock_guard guard(mLock); + isAvailable = mIsSystemTimeAvailable; + } + + if (isAvailable == false && mSystemLink != nullptr) + { + JsonObject params; + JsonObject response; + + uint32_t result = mSystemLink->Invoke(JSONRPC_THUNDER_TIMEOUT, "getTimeStatus", params, response); + if (result == Core::ERROR_NONE && response.HasLabel("TimeQuality")) + { + mTimeQuality = response["TimeQuality"].String(); + if (mTimeQuality == TIME_QUALITY_GOOD || mTimeQuality == TIME_QUALITY_SECURE) + { + std::lock_guard guard(mLock); + mIsSystemTimeAvailable = true; + isAvailable = true; + } + } + } + + LOGINFO("IsSystemTimeAvailable: %d", isAvailable); + return isAvailable; + } + + SystemTime::TimeZoneAccuracy SystemTime::GetTimeZoneOffset(int32_t &offsetSec) + { + SystemTime::TimeZoneAccuracy accuracy = ACC_UNDEFINED; + { + std::lock_guard guard(mLock); + offsetSec = mTimeZoneOffsetSec; + accuracy = mTimeZoneAccuracy; + } + + return accuracy; + } + + void SystemTime::onTimeStatusChanged(const JsonObject& parameters) + { + std::string parametersString; + parameters.ToString(parametersString); + LOGINFO("onTimeStatusChanged: %s", parametersString.c_str()); + Event event = {EVENT_TIME_STATUS_CHANGED, parametersString}; + std::lock_guard lock(mQueueLock); + mQueue.push(event); + mQueueCondition.notify_one(); + } + + void SystemTime::onTimeZoneDSTChanged(const JsonObject& parameters) + { + std::string parametersString; + parameters.ToString(parametersString); + LOGINFO("onTimeZoneDSTChanged: %s", parametersString.c_str()); + Event event = {EVENT_TIME_ZONE_CHANGED, parametersString}; + std::lock_guard lock(mQueueLock); + mQueue.push(event); + mQueueCondition.notify_one(); + } + + void SystemTime::CreateSystemLink() + { + if (mSystemLink == nullptr) + { + std::string thunderAccessValue = THUNDER_ACCESS_DEFAULT_VALUE; + char *thunderAccessValueEnv = getenv("THUNDER_ACCESS_VALUE"); + if (NULL != thunderAccessValueEnv) + { + thunderAccessValue = thunderAccessValueEnv; + } + + // Generate jsonrpc token + std::string token; + // TODO: use interfaces and remove token + auto security = mShell->QueryInterfaceByCallsign("SecurityAgent"); + if (security != nullptr) + { + std::string payload = "http://localhost"; + if (security->CreateToken( + static_cast(payload.length()), + reinterpret_cast(payload.c_str()), + token) == Core::ERROR_NONE) + { + LOGINFO("Got security token\n"); + } + else + { + LOGINFO("Failed to get security token\n"); + } + security->Release(); + } + else + { + LOGINFO("No security agent\n"); + } + + std::string query = "token=" + token; + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(thunderAccessValue))); + mSystemLink = std::make_shared>(SYSTEM_CALLSIGN, "", false, query); + } + } + + void SystemTime::SubscribeForEvents() + { + if (mSystemLink != nullptr) + { + uint32_t ret = mSystemLink->Subscribe(JSONRPC_THUNDER_TIMEOUT, _T("onTimeStatusChanged"), &SystemTime::onTimeStatusChanged, this); + if (ret != Core::ERROR_NONE) + { + LOGERR("Failed to subscribe to onTimeStatusChanged"); + } + + ret = mSystemLink->Subscribe(JSONRPC_THUNDER_TIMEOUT, _T("onTimeZoneDSTChanged"), &SystemTime::onTimeZoneDSTChanged, this); + if (ret != Core::ERROR_NONE) + { + LOGERR("Failed to subscribe to onTimeZoneDSTChanged"); + } + } + } + + void SystemTime::UpdateTimeStatus() + { + JsonObject params; + JsonObject response; + + uint32_t result = mSystemLink->Invoke(JSONRPC_THUNDER_TIMEOUT, "getTimeStatus", params, response); + if (result == Core::ERROR_NONE && response.HasLabel("TimeQuality")) + { + std::lock_guard guard(mLock); + mTimeQuality = response["TimeQuality"].String(); + if (mTimeQuality == TIME_QUALITY_GOOD || mTimeQuality == TIME_QUALITY_SECURE) + { + mIsSystemTimeAvailable = true; + } + else + { + mIsSystemTimeAvailable = false; + } + } + } + + void SystemTime::UpdateTimeZone() + { + JsonObject params; + JsonObject response; + // Get timeZone from System.1.getTimeZoneDST + uint32_t result = mSystemLink->Invoke(JSONRPC_THUNDER_TIMEOUT, "getTimeZoneDST", params, response); + if (result == Core::ERROR_NONE && response.HasLabel("timeZone") && response.HasLabel("accuracy")) + { + std::string tz = response["timeZone"].String(); + std::string accuracy = response["accuracy"].String(); + + std::lock_guard guard(mLock); + if (mTimeZone != tz || mTimeZoneAccuracyString != accuracy) + { + std::pair tzParsed = ParseTimeZone(tz, accuracy); + mTimeZone = tz; + mTimeZoneAccuracyString = accuracy; + mTimeZoneAccuracy = tzParsed.first; + mTimeZoneOffsetSec = tzParsed.second; + } + } + } + + std::pair SystemTime::ParseTimeZone(const string &timeZone, const std::string &accuracy) + { + std::pair result = {ACC_UNDEFINED, 0}; + if (timeZone.empty()) + { + return result; + } + + static const std::map accuracyMap = { + {"INITIAL", INITIAL}, + {"INTERIM", INTERIM}, + {"FINAL", FINAL}}; + + auto accuracyItr = accuracyMap.find(accuracy); + if (accuracyItr == accuracyMap.end()) + { + result.first = ACC_UNDEFINED; + } else { + result.first = accuracyItr->second; + } + + if (timeZone == "Universal") + { + result.second = 0; + LOGINFO("timeZoneOff: %d", result.second); + return result; + } + + PopulateTimeZoneTransitionMap(timeZone, accuracy); + + if (mTransitionMap.empty()) + { + result.first = ACC_UNDEFINED; + return result; + } + + time_t currentTime = time(NULL); + auto currentTimeEndItr = mTransitionMap.lower_bound(currentTime); + + if (currentTimeEndItr != mTransitionMap.end()) + { + result.second = currentTimeEndItr->second; + LOGINFO("timeZoneOff: %d", result.second); + } + else if (mTransitionMap.empty() == false) + { + currentTimeEndItr--; // take the last transition when all transitions are from past + result.second = currentTimeEndItr->second; + LOGINFO("timeZoneOff: %d", result.second); + } + else + { + LOGERR( "There is no time transition information for this timezone: %s", mTimeZone.c_str()); + result.second = 0; + result.first = ACC_UNDEFINED; + } + + return result; + } + + void SystemTime::PopulateTimeZoneTransitionMap(const std::string &newTimeZone, const std::string &accuracy) + { + if (accuracy == "FINAL") + { + if (mTimeZone != newTimeZone) + { + mTransitionMap.clear(); + mTimeZone = newTimeZone; + } + } + else + { + mTimeZone.clear(); + mTransitionMap.clear(); + } + + if (mTransitionMap.empty() && !mTimeZone.empty()) + { + FILE *fp = v_secure_popen("r", "zdump -v %s", mTimeZone.c_str()); + if (fp != NULL) + { + LOGINFO("v_secure_popen of zdump -v %s succeeded", mTimeZone.c_str()); + + // Tue Jan 19 03:14:07 2038 UT = Tue Jan 19 04:14:07 2038 CET isdst=0 gmtoff=3600 + char buf[256] = {0}; + + while (fgets(buf, sizeof(buf), fp) != NULL) + { + // " Tue Jan 19 03:14:07 2038 UT = Tue Jan 19 04:14:07 2038 CET isdst=0 gmtoff=3600" + std::string temp(buf); + struct tm utcTime = {0}; + + temp.erase(0, temp.find(" ") + 2); // Remove " " -> 2 spaces + + auto utOff = temp.find(" UT = "); + std::string date = temp.substr(0, utOff); // Capture UTC date "Tue Jan 19 03:14:07 2038" + + // Remove everything to " UT = ", "Tue Jan 19 04:14:07 2038 CET isdst=0 gmtoff=3600" is left + temp.erase(0, utOff + sizeof(" UT = ") - 1); + + // Remove until next 5 spaces, "CET isdst=0 gmtoff=3600" will be left + auto spaceCount = 5; + while (spaceCount > 0) + { + auto spOff = temp.find(" "); + // In case of one-digit day of month there are two spaces before that + if (spaceCount == 4) + { + // thus + 2 to remove it (it applies also to two-digits but it's ok because it's removed anyway) + temp.erase(0, spOff + 2); + } + else + { + temp.erase(0, spOff + 1); + } + spaceCount--; + } + + char timeZone[5] = {0}; + int32_t isDst = 0; + int32_t gmtOff = 0; + sscanf(temp.c_str(), "%s isdst=%d gmtoff=%d", timeZone, &isDst, &gmtOff); + strptime(date.c_str(), "%A %B %d %H:%M:%S %Y", &utcTime); + + // Years below 70 are not supported by epoch + if (utcTime.tm_year > 70) + { + utcTime.tm_zone = "UTC"; + utcTime.tm_gmtoff = 0; + + // years after 2038 are rounding off to -1 + if (utcTime.tm_year < 138) + { + time_t utcTimeGm = timegm(&utcTime); + mTransitionMap[utcTimeGm] = gmtOff; + } + else + { + break; // stop parsing as we wont add anything after 2038 to map + } + } + else + { + // validate the line + if (utcTime.tm_year > 0) + { + mTransitionMap[utcTime.tm_year] = gmtOff; + } + } + } + + v_secure_pclose(fp); + } + else + { + LOGERR("v_secure_popen of zdump -v %s failed", mTimeZone.c_str()); + } + } + else + { + LOGINFO("No update required"); + } + } + + + void SystemTime::EventLoop() + { + while (true) + { + Event event; + { + std::unique_lock lock(mQueueLock); + mQueueCondition.wait(lock, [this] + { return !mQueue.empty(); }); + event = mQueue.front(); + mQueue.pop(); + } + + switch (event.type) + { + case EVENT_INITIALISE: + { + if (mSystemLink == nullptr) + { + CreateSystemLink(); + if (mSystemLink != nullptr) + { + SubscribeForEvents(); + UpdateTimeStatus(); + UpdateTimeZone(); + } + else + { + LOGERR("Failed to create JSONRPC link with %s", SYSTEM_CALLSIGN); + } + } + } + break; + case EVENT_TIME_STATUS_CHANGED: + { + JsonObject response(event.payload); + if (response.HasLabel("TimeQuality")) + { + std::lock_guard guard(mLock); + mTimeQuality = response["TimeQuality"].String(); + if (mTimeQuality == TIME_QUALITY_GOOD || mTimeQuality == TIME_QUALITY_SECURE) + { + mIsSystemTimeAvailable = true; + } + else + { + mIsSystemTimeAvailable = false; + } + } + } + break; + case EVENT_TIME_ZONE_CHANGED: + { + JsonObject response(event.payload); + if (response.HasLabel("newTimeZone") && response.HasLabel("newAccuracy")) + { + std::string tz = response["newTimeZone"].String(); + std::string accuracy = response["newAccuracy"].String(); + + std::lock_guard guard(mLock); + if (mTimeZone != tz || mTimeZoneAccuracyString != accuracy) + { + std::pair tzParsed = ParseTimeZone(tz, accuracy); + mTimeZone = tz; + mTimeZoneAccuracyString = accuracy; + mTimeZoneAccuracy = tzParsed.first; + mTimeZoneOffsetSec = tzParsed.second; + } + } + } + break; + case EVENT_SHUTDOWN: + { + LOGINFO("Shutting down SystemTime event loop"); + return; + } + default: + { + LOGERR("Unhandled event received, event: %d", event.type); + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/Analytics/Implementation/SystemTime/SystemTime.h b/Analytics/Implementation/SystemTime/SystemTime.h new file mode 100644 index 0000000000..8b4022a411 --- /dev/null +++ b/Analytics/Implementation/SystemTime/SystemTime.h @@ -0,0 +1,99 @@ +/** + * If not stated otherwise in this file or this component's LICENSE + * file the following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * 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. + **/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "../../Module.h" + +namespace WPEFramework +{ + namespace Plugin + { + class SystemTime + { + public: + enum TimeZoneAccuracy + { + INITIAL, + INTERIM, + FINAL, + ACC_UNDEFINED + }; + + SystemTime(PluginHost::IShell *shell); + ~SystemTime(); + + bool IsSystemTimeAvailable(); + TimeZoneAccuracy GetTimeZoneOffset(int32_t &offsetSec); + + private: + enum EventType + { + EVENT_UNDEF, + EVENT_INITIALISE, + EVENT_TIME_STATUS_CHANGED, + EVENT_TIME_ZONE_CHANGED, + EVENT_SHUTDOWN + }; + + struct Event + { + EventType type; + std::string payload; + }; + + void onTimeStatusChanged(const JsonObject& parameters); + void onTimeZoneDSTChanged(const JsonObject& parameters); + + void CreateSystemLink(); + void SubscribeForEvents(); + void UpdateTimeStatus(); + void UpdateTimeZone(); + std::pair ParseTimeZone(const string &timeZone, const std::string &accuracy); + void PopulateTimeZoneTransitionMap(const std::string &newTimeZone, const std::string &accuracy); + void EventLoop(); + + + std::mutex mQueueLock; + std::condition_variable mQueueCondition; + std::queue mQueue; + + std::thread mEventThread; + std::mutex mLock; + std::shared_ptr> mSystemLink; + std::string mTimeQuality; + std::string mTimeZone; + std::string mTimeZoneAccuracyString; + TimeZoneAccuracy mTimeZoneAccuracy; + int32_t mTimeZoneOffsetSec; + std::map mTransitionMap; + bool mIsSystemTimeAvailable; + PluginHost::IShell *mShell; + }; + + typedef std::shared_ptr SystemTimePtr; + } +} \ No newline at end of file diff --git a/Tests/L1Tests/patches/0001-Add-IAnalytics-interface-R2.patch b/Tests/L1Tests/patches/0001-Add-IAnalytics-interface-R2.patch index 8045170a33..413b3b7c1d 100644 --- a/Tests/L1Tests/patches/0001-Add-IAnalytics-interface-R2.patch +++ b/Tests/L1Tests/patches/0001-Add-IAnalytics-interface-R2.patch @@ -1,4 +1,4 @@ -From 9a2398b7bc356f341e25c414e45b6919df135d58 Mon Sep 17 00:00:00 2001 +From 326e1f2371147945ec609cfc7f2555625c69611f Mon Sep 17 00:00:00 2001 From: Adrian Muzyka Date: Thu, 19 Sep 2024 12:34:27 +0200 Subject: [PATCH] Add IAnalytics interface R2 @@ -11,7 +11,7 @@ Subject: [PATCH] Add IAnalytics interface R2 diff --git a/interfaces/IAnalytics.h b/interfaces/IAnalytics.h new file mode 100644 -index 0000000..19f5a3a +index 0000000..00fd04e --- /dev/null +++ b/interfaces/IAnalytics.h @@ -0,0 +1,27 @@ @@ -29,16 +29,16 @@ index 0000000..19f5a3a + + virtual ~IAnalytics() override = default; + ++ using IStringIterator = RPC::IIteratorType; ++ + virtual uint32_t SendEvent(const string& eventName /* @in */, + const string& eventVersion /* @in */, + const string& eventSource /* @in */, + const string& eventSourceVersion /* @in */, -+ RPC::IStringIterator* const& cetList /* @in */, -+ const uint64_t& epochTimestamp /* @in */, -+ const uint64_t& uptimeTimestamp /* @in */, ++ IStringIterator* const& cetList /* @in */, ++ const uint64_t epochTimestamp /* @in */, ++ const uint64_t uptimeTimestamp /* @in */, + const string& eventPayload /* @in */ ) = 0; -+ virtual uint32_t SetSessionId(const string& id /* @in */) = 0; -+ virtual uint32_t SetTimeReady() = 0; + }; +} +} diff --git a/Tests/L1Tests/tests/test_Analytics.cpp b/Tests/L1Tests/tests/test_Analytics.cpp index 92a96fbc34..68d6f5eb5a 100644 --- a/Tests/L1Tests/tests/test_Analytics.cpp +++ b/Tests/L1Tests/tests/test_Analytics.cpp @@ -23,8 +23,6 @@ class AnalyticsTest : public ::testing::Test { TEST_F(AnalyticsTest, RegisteredMethods) { EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("sendEvent"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("setSessionId"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("setTimeReady"))); } TEST_F(AnalyticsTest, sendEvent) @@ -33,17 +31,5 @@ TEST_F(AnalyticsTest, sendEvent) EXPECT_EQ(response, string("{\"success\":true}")); } -TEST_F(AnalyticsTest, setSessionId) -{ - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setSessionId"), _T("{\"sessionId\":\"123456789\"}"), response)); - EXPECT_EQ(response, string("{\"success\":true}")); -} - -TEST_F(AnalyticsTest, setTimeReady) -{ - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setTimeReady"), _T("{}"), response)); - EXPECT_EQ(response, string("{\"success\":true}")); -} -