diff --git a/CMakeLists.txt b/CMakeLists.txt index 09f1830e4c..803fc6b38f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,9 @@ if(PLUGIN_DEVICE_PROVISIONING) add_subdirectory(DeviceProvisioning) endif() +if(PLUGIN_MIGRATIONPREPARER) + add_subdirectory(MigrationPreparer) +endif() # if(BLUETOOTH_SETTINGS) # add_subdirectory(BluetoothSettings) diff --git a/MigrationPreparer/CHANGELOG.md b/MigrationPreparer/CHANGELOG.md new file mode 100644 index 0000000000..e81a6361cb --- /dev/null +++ b/MigrationPreparer/CHANGELOG.md @@ -0,0 +1,19 @@ +All notable changes to this RDK Service will be documented in this file. + + Each RDK Service has a CHANGELOG file that contains all changes done so far. When version is updated, add a entry in the CHANGELOG.md at the top with user friendly information on what was changed with the new version. Please don't mention JIRA tickets in CHANGELOG. + + Please Add entry in the CHANGELOG for each version change and indicate the type of change with these labels: + Added for new features. + Changed for changes in existing functionality. + Deprecated for soon-to-be removed features. + Removed for now removed features. + Fixed for any bug fixes. + Security in case of vulnerabilities. + + Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. + + For more details, refer to versioning section under Main README. + +## [1.0.0] - 2024-12-01 +### Added +- New RDK Service MigrationPreparer to aid the Data Harvesting process, where it exposes APIs to applications such as ResidentApp and others to store user settings in a standardized format (as a JSON file) suitable for EntOS consumption. The MigrationPreparer Thunder plugin will exist only in the RDKV ecosystem. diff --git a/MigrationPreparer/CMakeLists.txt b/MigrationPreparer/CMakeLists.txt new file mode 100644 index 0000000000..0b2dd080e5 --- /dev/null +++ b/MigrationPreparer/CMakeLists.txt @@ -0,0 +1,89 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 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(PLUGIN_NAME MigrationPreparer) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) +set(PLUGIN_IMPLEMENTATION ${MODULE_NAME}Implementation) + +set(PLUGIN_MIGRATIONPREPARER_AUTOSTART "true" CACHE STRING "Automatically start MigrationPreparer plugin") +set(PLUGIN_MIGRATIONPREPARER_STARTUPORDER "" CACHE STRING "To configure startup order of MigrationPreparer plugin") +set(PLUGIN_MIGRATIONPREPARER_MODE "Off" CACHE STRING "Controls if the plugin should run in process or out of process") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) +find_package(CompileSettingsDebug CONFIG REQUIRED) + +add_library(${MODULE_NAME} SHARED + MigrationPreparer.cpp + MigrationPreparerJsonRpc.cpp + Module.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_link_libraries(${MODULE_NAME} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions) + +install(TARGETS ${MODULE_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${STORAGE_DIRECTORY}/plugins) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) +target_include_directories(${MODULE_NAME} PRIVATE ../common) +target_include_directories(${MODULE_NAME} PRIVATE ./) + +add_library(${PLUGIN_IMPLEMENTATION} SHARED + MigrationPreparerImplementation.cpp + Module.cpp) + +set_target_properties(${PLUGIN_IMPLEMENTATION} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + + target_include_directories(${PLUGIN_IMPLEMENTATION} PRIVATE ../helpers) +target_include_directories(${PLUGIN_IMPLEMENTATION} PRIVATE ../common) +target_include_directories(${PLUGIN_IMPLEMENTATION} PRIVATE ./) + +# if (RDK_SERVICE_L2_TEST) +# find_library(TESTMOCKLIB_LIBRARIES NAMES TestMocklib) +# if (TESTMOCKLIB_LIBRARIES) +# message ("linking mock libraries ${TESTMOCKLIB_LIBRARIES} library") +# target_link_libraries(${PLUGIN_IMPLEMENTATION} PRIVATE ${TESTMOCKLIB_LIBRARIES}) +# else (TESTMOCKLIB_LIBRARIES) +# message ("Require ${TESTMOCKLIB_LIBRARIES} library") +# endif (TESTMOCKLIB_LIBRARIES) +# endif (RDK_SERVICES_L2_TEST) + +target_link_libraries(${PLUGIN_IMPLEMENTATION} + PRIVATE + CompileSettingsDebug::CompileSettingsDebug + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${RBUS_LIBRARIES}) + +install(TARGETS ${PLUGIN_IMPLEMENTATION} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/MigrationPreparer/MigrationPreparer.conf.in b/MigrationPreparer/MigrationPreparer.conf.in new file mode 100644 index 0000000000..3ef0c9410e --- /dev/null +++ b/MigrationPreparer/MigrationPreparer.conf.in @@ -0,0 +1,11 @@ +callsign = "org.rdk.MigrationPreparer" +precondition = ["Platform"] +autostart = "@PLUGIN_MIGRATIONPREPARER_AUTOSTART@" +startuporder = "@PLUGIN_MIGRATIONPREPARER_STARTUPORDER@" + +configuration = JSON() + +rootobject = JSON() +rootobject.add("mode", "@PLUGIN_MIGRATIONPREPARER_MODE@") +rootobject.add("locator", "lib@PLUGIN_IMPLEMENTATION@.so") +configuration.add("root", rootobject) diff --git a/MigrationPreparer/MigrationPreparer.config b/MigrationPreparer/MigrationPreparer.config new file mode 100644 index 0000000000..cb1f4caa02 --- /dev/null +++ b/MigrationPreparer/MigrationPreparer.config @@ -0,0 +1,15 @@ +set(autostart ${PLUGIN_MIGRATIONPREPARER_AUTOSTART}) +set(preconditions Platform) + +if(PLUGIN_MIGRATIONPREPARER_STARTUPORDER) + set (startuporder ${PLUGIN_MIGRATIONPREPARER_STARTUPORDER}) +endif() + +map() + key(root) + map() + kv(mode ${PLUGIN_MIGRATIONPREPARER_MODE}) + kv(locator lib${PLUGIN_IMPLEMENTATION}.so) + end() +end() +ans(configuration) \ No newline at end of file diff --git a/MigrationPreparer/MigrationPreparer.cpp b/MigrationPreparer/MigrationPreparer.cpp new file mode 100644 index 0000000000..1fd16b7797 --- /dev/null +++ b/MigrationPreparer/MigrationPreparer.cpp @@ -0,0 +1,135 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 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 "MigrationPreparer.h" + +#define API_VERSION_NUMBER_MAJOR 1 +#define API_VERSION_NUMBER_MINOR 0 +#define API_VERSION_NUMBER_PATCH 0 + +namespace WPEFramework +{ + + namespace { + + static Plugin::Metadata metadata( + // Version (Major, Minor, Patch) + API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH, + // Preconditions + {}, + // Terminations + {}, + // Controls + {} + ); + } + + namespace Plugin + { + + /* + *Register MigrationPreparer module as wpeframework plugin + **/ + SERVICE_REGISTRATION(MigrationPreparer, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); + + MigrationPreparer::MigrationPreparer() : _service(nullptr), _connectionId(0), _migrationPreparer(nullptr) + { + SYSLOG(Logging::Startup, (_T("MigrationPreparer Constructor"))); + RegisterAll(); + } + + MigrationPreparer::~MigrationPreparer() + { + SYSLOG(Logging::Shutdown, (string(_T("MigrationPreparer Destructor")))); + UnregisterAll(); + } + + const string MigrationPreparer::Initialize(PluginHost::IShell* service) + { + string message=""; + + ASSERT(nullptr != service); + ASSERT(nullptr == _service); + ASSERT(nullptr == _migrationPreparer); + ASSERT(0 == _connectionId); + + SYSLOG(Logging::Startup, (_T("MigrationPreparer::Initialize: PID=%u"), getpid())); + + _service = service; + _service->AddRef(); + _migrationPreparer = _service->Root(_connectionId, 5000, _T("MigrationPreparerImplementation")); + + if(nullptr == _migrationPreparer) + { + SYSLOG(Logging::Startup, (_T("MigrationPreparer::Initialize: Failed to initialise MigrationPreparer plugin"))); + message = _T("MigrationPreparer plugin could not be initialised"); + } + + if (0 != message.length()) + { + Deinitialize(service); + } + + return message; + } + + void MigrationPreparer::Deinitialize(PluginHost::IShell* service) + { + ASSERT(_service == service); + + SYSLOG(Logging::Shutdown, (string(_T("MigrationPreparer::Deinitialize")))); + + if (nullptr != _migrationPreparer) + { + // Stop processing: + RPC::IRemoteConnection* connection = service->RemoteConnection(_connectionId); + VARIABLE_IS_NOT_USED uint32_t result = _migrationPreparer->Release(); + + _migrationPreparer = nullptr; + + // It should have been the last reference we are releasing, + // so it should endup in a DESTRUCTION_SUCCEEDED, if not we + // are leaking... + ASSERT(result == Core::ERROR_DESTRUCTION_SUCCEEDED); + + // If this was running in a (container) process... + if (nullptr != connection) + { + // Lets trigger the cleanup sequence for + // out-of-process code. Which will guard + // that unwilling processes, get shot if + // not stopped friendly :-) + connection->Terminate(); + connection->Release(); + } + } + + _connectionId = 0; + _service->Release(); + _service = nullptr; + SYSLOG(Logging::Shutdown, (string(_T("MigrationPreparer de-initialised")))); + } + + string MigrationPreparer::Information() const + { + return (string("{\"service\": \"") + string("org.rdk.MigrationPreparer") + string("\"}")); + } + +} // namespace Plugin +} // namespace WPEFramework \ No newline at end of file diff --git a/MigrationPreparer/MigrationPreparer.h b/MigrationPreparer/MigrationPreparer.h new file mode 100644 index 0000000000..ea1629d8ea --- /dev/null +++ b/MigrationPreparer/MigrationPreparer.h @@ -0,0 +1,86 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 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 "Module.h" +#include +#include +#include +#include "UtilsLogging.h" +#include "tracing/Logging.h" +#include "UtilsJsonRpc.h" +#include + +// PLUGIN SPECIFIC CUSTOM ERROR CODES +#define ERROR_CREATE 4 +#define ERROR_OPEN 5 +#define ERROR_WRITE 6 +#define ERROR_NAME 7 +#define ERROR_DELETE 8 +#define ERROR_SET 9 +#define ERROR_RESET 10 +#define ERROR_INVALID 11 +#define ERROR_NOFILE 12 +#define ERROR_FILEEMPTY 13 + +namespace WPEFramework { +namespace Plugin { + + class MigrationPreparer: public PluginHost::IPlugin, public PluginHost::JSONRPC + { + public: + // We do not allow this plugin to be copied !! + MigrationPreparer(const MigrationPreparer&) = delete; + MigrationPreparer& operator=(const MigrationPreparer&) = delete; + + MigrationPreparer(); + virtual ~MigrationPreparer(); + + BEGIN_INTERFACE_MAP(MigrationPreparer) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + INTERFACE_AGGREGATE(Exchange::IMigrationPreparer, _migrationPreparer) + END_INTERFACE_MAP + + // IPlugin methods + // ------------------------------------------------------------------------------------------------------- + const string Initialize(PluginHost::IShell* service) override; + void Deinitialize(PluginHost::IShell* service) override; + string Information() const override; + + private: + void RegisterAll(); + void UnregisterAll(); + + uint32_t endpoint_write(const JsonObject& parameters, JsonObject& response); + uint32_t endpoint_read(const JsonObject& parameters, JsonObject& response); + uint32_t endpoint_delete(const JsonObject& parameters, JsonObject& response); + uint32_t endpoint_getComponentReadiness(const JsonObject& parameters, JsonData::MigrationPreparer::GetcomponentreadinessResultData& response); + uint32_t endpoint_setComponentReadiness(const JsonObject& parameters, JsonObject& response); + uint32_t endpoint_reset(const JsonObject& parameters, JsonObject& response); + + private: + PluginHost::IShell* _service{}; + uint32_t _connectionId{}; + Exchange::IMigrationPreparer* _migrationPreparer{}; + }; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/MigrationPreparer/MigrationPreparer.json b/MigrationPreparer/MigrationPreparer.json new file mode 100644 index 0000000000..7ac00daa4c --- /dev/null +++ b/MigrationPreparer/MigrationPreparer.json @@ -0,0 +1,237 @@ +{ + "$schema": "interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "MigrationPreparer API", + "class": "MigrationPreparer", + "description": "MigrationPreparer Store JSON-RPC interface" + }, + "common": { + "$ref": "common.json" + }, + "definitions": { + "name": { + "summary": "key", + "type": "string", + "example": "name1" + }, + "value": { + "summary": "value", + "type": "string", + "example": "value1" + }, + "success": { + "summary": "Legacy parameter (always true)", + "type": "boolean", + "default": true, + "example": true + }, + "componentName": { + "summary": "Name of the component that is ready for migration", + "type": "string", + "example": "RA01" + }, + "componentList": { + "summary": "Name of the component that is ready for migration", + "type": "string", + "example": "RA02" + }, + "resetType": { + "summary": "Type indicates whether it should delete all entries in the datastore or componentList for migration or both", + "type": "string", + "example": "RESET_ALL" + } + }, + "methods": { + "write": { + "summary": "Write key-value to the dataStore", + "params": { + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + }, + "value": { + "$ref": "#/definitions/value" + } + }, + "required": [ + "name", + "value" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "Delete": { + "summary": "Delete key-value from the dataStore", + "params": { + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + } + }, + "required": [ + "name" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "read": { + "summary": "Read value from the dataStore", + "params": { + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + } + }, + "required": [ + "name" + ] + }, + "result": { + "type": "object", + "properties": { + "value": { + "$ref": "#/definitions/value" + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "value", + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "getcomponentreadiness": { + "summary": "Returns the list of components that are ready for migration", + "result": { + "type": "object", + "properties": { + "componentList": { + "type": "array", + "items": { + "$ref": "#/definitions/componentList" + } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "componentList", + "sucecss" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "setcomponentreadiness": { + "summary": "Set the component that is ready for migration", + "params": { + "type": "object", + "properties": { + "componentName": { + "$ref": "#/definitions/componentName" + } + }, + "required": [ + "componentName" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "reset": { + "summary": "Reset based on resettype, should delete all entries in the datastore or componentList for migration or both", + "params": { + "type": "object", + "properties":{ + "resetType": { + "$ref": "#/definitions/resetType" + } + }, + "required": [ + "resetType" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + } + } +} \ No newline at end of file diff --git a/MigrationPreparer/MigrationPreparerImplementation.cpp b/MigrationPreparer/MigrationPreparerImplementation.cpp new file mode 100644 index 0000000000..24ad32edfc --- /dev/null +++ b/MigrationPreparer/MigrationPreparerImplementation.cpp @@ -0,0 +1,549 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 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 "MigrationPreparerImplementation.h" + +namespace WPEFramework { +namespace Plugin { + + SERVICE_REGISTRATION(MigrationPreparerImplementation, 1, 0); + + MigrationPreparerImplementation::MigrationPreparerImplementation() + : _adminLock() + { + LOGINFO("Create MigrationPreparerImplementation Instance"); + // Init + _fileExist = false; + WPEFramework::Core::File dataStore(DATASTORE_PATH); + if(dataStore.Exists()) { + _fileExist = true; + storeKeys(); + } + } + + MigrationPreparerImplementation::~MigrationPreparerImplementation() + { + } + + /*Helper's: Begin*/ + string MigrationPreparerImplementation::escapeSed(string input, enum sedType type) { + // Set of special characters in sed that need to be escaped + std::unordered_set specialChars = { + '.', '*', '+', '?', '^', '$', '(', ')', '[', ']', '{', '}', '\\', '|', '/', '&' + }; + + std::string escapedString; + + if(type == PATTERN) { + // Iterate through each character in the string + for(char ch : input) { + // If ch is a special character replace with . + if (specialChars.find(ch) != specialChars.end()) + escapedString += "."; + else + escapedString += ch; + } + escapedString = "\":"+ escapedString; + } + else if(type == REPLACEMENT) { + // Iterate through each character in the string + for(char ch : input) { + // If ch is a special character prepend it with a backslash + if (specialChars.find(ch) != specialChars.end()) + escapedString += "\\"; + + escapedString += ch; + } + escapedString = "\":"+ escapedString; + } + return escapedString; + } + + void MigrationPreparerImplementation::storeKeys(void) { + _dataStoreMutex.lock(); + std::ifstream inputFile(DATASTORE_PATH); + string key, line; + size_t start, end, colPos; + LINE_NUMBER_TYPE lineIndex = 1; + while(std::getline(inputFile, line)) { + // trim white space, new line and comma from the line + start = line.find_first_not_of(" \n"); + end = line.find_last_not_of(" \n,"); + line = line.substr(start, end-start+1); + if(start == string::npos || end == string::npos) { + LOGWARN("Invalid line in dataStore file, skipping"); + continue; + } + ASSERT(start > end); + // if line is empty or { or } continue + if(line.empty() || line == "{" || line == "}") + continue; + + // increment line index + lineIndex++; + //extract key + // below is the dataStore json format + // { + // "key1":value1, + // "key2":value2 + // } + colPos = line.find(":"); + key = line.substr(1,colPos-2); + + //add key to the map + _lineNumber[key] = lineIndex; + + string value = line.substr(colPos+1); + _valueEntry.push_back(value); + } + // update the last line index + _curLineIndex = lineIndex; + _dataStoreMutex.unlock(); + } + + + bool MigrationPreparerImplementation::resetDatastore(void){ + WPEFramework::Core::File dataStore(DATASTORE_PATH); + if(!dataStore.Exists()) { + LOGWARN("DataStore file does not exist"); + return true; + } + _dataStoreMutex.lock(); + // remove dataStore file + if(!dataStore.Destroy()){ + LOGERR("Unable to delete dataStore file"); + return false; + } + // clear the _lineNumber map and _valueEntry vector internal data structures + if (!_lineNumber.empty()) + _lineNumber.clear(); + _curLineIndex = 1; + if (!_valueEntry.empty()) + _valueEntry.clear(); + _dataStoreMutex.unlock(); + return true; + } + bool MigrationPreparerImplementation::resetMigrationready(void){ + WPEFramework::Core::File migrationReady(MIGRATIONREADY_PATH); + if(!migrationReady.Exists()) { + LOGWARN("MigrationReady file does not exist"); + return true; + } + _adminLock.Lock(); + // remove migrationReady file + LOGWARN("Deleting migrationReady file"); + if(!migrationReady.Destroy()){ + LOGERR("Unable to delete migrationReady file"); + return false; + } + _adminLock.Unlock(); + return true; + } + + int8_t MigrationPreparerImplementation::split(std::list& list, string& value, std::string delimiter) { + LOGINFO("split: %s", value.c_str()); + string::size_type start = 0, pos = 0; + while ((pos = value.find(delimiter, start) )!= std::string::npos) { + list.emplace_back(value.substr(start, pos - start)); + start = pos + 1; // Move start to the next character after the delimiter + } + // add the last element or the only element to the list + list.emplace_back(value.substr(start)); + return Core::ERROR_NONE; + } + + int8_t MigrationPreparerImplementation::join(string& value, std::list& list, std::string delimiter) { + //if the list is with only one element also it won't be a problem + if (list.empty()) return Core::ERROR_NONE; // Handle empty list case + auto it = list.begin(); + // Use the for loop to concatenate elements with '_' + for (; std::next(it) != list.end(); ++it) { + value += *it + delimiter; // Concatenate element with underscore + } + // Add the last element without an underscore + value += *it; + LOGINFO("join: %s", value.c_str()); + return Core::ERROR_NONE; + } + + bool MigrationPreparerImplementation::isDuplicate(string& searchString, std::list& list) { + // Use std::find to check if the string is present + if (list.empty()) return Core::ERROR_NONE; // Handle empty list case + auto it = std::find(list.begin(), list.end(), searchString); + if (it != list.end()) { + return true; + } + return false; + } + + /*Helper's: End*/ + + uint32_t MigrationPreparerImplementation::write(const string& name, const string &value) { + + LOGINFO("[WRITE] params={name: %s, value: %s}", name.c_str(), value.c_str()); + string entry; + string key = name; + string newValue = value; + WPEFramework::Core::File dataStore(DATASTORE_PATH); + WPEFramework::Core::Directory dataStoreDir(DATASTORE_DIR); + + // check if someone deletes the dataStore in the middle of the operation + if(!_lineNumber.empty() && !dataStore.Exists()) { + LOGWARN("Migration datastore %s deleted in the middle of the operation, resetting the internal data structures", DATASTORE_PATH); + _lineNumber.clear(); + _curLineIndex = 1; + if (!_valueEntry.empty()) + _valueEntry.clear(); + } + + _dataStoreMutex.lock(); + // Handle first Write request + if (_lineNumber.empty()) { + _curLineIndex = 1; + if(!dataStore.Exists()) { + // check if the directory exist + if(!dataStoreDir.Exists()) + // assuming /opt/secure path already exist and "migration" folder needs to be under /opt/secure/ + dataStoreDir.Create(); + + if(!dataStore.Create()) { + LOGERR("Failed to create migration datastore %s, errno: %d, reason: %s", DATASTORE_PATH, errno, strerror(errno)); + _dataStoreMutex.unlock(); + return ERROR_CREATE; + } + _fileExist = true; + } + + if(!dataStore.Open(false)) { + LOGERR("Failed to open migration datastore %s, errno: %d, reason: %s", DATASTORE_PATH, errno, strerror(errno)); + LOGERR("Failed to create entry for {%s:%s} in migration datastore", key.c_str(), newValue.c_str()); + _dataStoreMutex.unlock(); + return ERROR_OPEN; + } + // write entry to the dataStore + entry = string("{\n \"") + key + string("\":") + newValue + string("\n}"); + dataStore.Write(reinterpret_cast(&entry[0]), entry.size()); + // update the entry in _lineNumber map + _lineNumber[key] = ++_curLineIndex; + _valueEntry.push_back(newValue); + dataStore.Close(); + _dataStoreMutex.unlock(); + return Core::ERROR_NONE; + } + + // Handle Update Request + if(_lineNumber.find(key) != _lineNumber.end()) { + + string oldValue = _valueEntry[_lineNumber[key] - 2]; + + if(oldValue == newValue) { + LOGWARN("Entry {%s:%s} is already existing in the dataStore, returning success", oldValue.c_str(), key.c_str()); + _dataStoreMutex.unlock(); + return Core::ERROR_NONE; + } + + // since v_secure_system syscall wrapper has set limitation on command length + if(newValue.length() > 200 || oldValue.length() > 200) { + _dataStoreMutex.unlock(); + LOGINFO("[WRITE] Value for the key %s is long, hence deleting existing entry and adding it as new entry at the end of dataStore", key.c_str()); + if(Delete(key) != Core::ERROR_NONE) + return ERROR_WRITE; + + return write(key, newValue); + } + + // sed command to replace value in the dataStore + oldValue = escapeSed(oldValue, PATTERN); + string escapedNewValue = escapeSed(newValue, REPLACEMENT); + int result = v_secure_system("/bin/sed -i -E '%ss/%s/%s/' %s", std::to_string(_lineNumber[key]).c_str(), oldValue.c_str(), escapedNewValue.c_str(), DATASTORE_PATH); + + if (result != -1 && WIFEXITED(result)) { + _valueEntry[_lineNumber[key] - 2] = newValue; + _dataStoreMutex.unlock(); + return Core::ERROR_NONE; + } + result = WEXITSTATUS(result); + LOGERR("Failed to update entry for {%s:%s} in migration datastore, v_secure_system failed with error %d",key.c_str(), newValue.c_str(), result); + _dataStoreMutex.unlock(); + return ERROR_WRITE; + } + + // Handle subsequent Write request + if(!dataStore.Open(false)) { + LOGERR("Failed to create migration datastore %s, errno: %d, reason: %s", DATASTORE_PATH, errno, strerror(errno)); + LOGERR("Failed to create entry for {%s:%s} in migration datastore", key.c_str(), newValue.c_str()); + _dataStoreMutex.unlock(); + return ERROR_OPEN; + } + + // append new key-value pair to the dataStore + if(!dataStore.Position(false, dataStore.Size() - 2)) { + LOGERR("DataStore truncate failed with errno: %d, reason: %s\n", errno, strerror(errno)); + LOGERR("Failed to create entry for {%s:%s} in migration datastore", key.c_str(), newValue.c_str()); + dataStore.Close(); + _dataStoreMutex.unlock(); + return ERROR_WRITE; + } + entry = string(",\n \"") + key + string("\":") + newValue + string("\n}"); + dataStore.Write(reinterpret_cast(&entry[0]), entry.size()); + _lineNumber[key] = ++_curLineIndex; + _valueEntry.push_back(newValue); + dataStore.Close(); + _dataStoreMutex.unlock(); + return Core::ERROR_NONE; + } + + uint32_t MigrationPreparerImplementation::read(const string& name, string &result) { + + LOGINFO("[READ] params={name: %s}", name.c_str()); + string key = name; + + if(!_fileExist) { + LOGERR("Failed to read key: %s, migration dataStore %s do not exist", key.c_str(), DATASTORE_PATH); + return ERROR_NOFILE; + } + + if(_lineNumber.empty()) { + LOGERR("Failed to read key: %s, migration dataStore %s is empty", key.c_str(), DATASTORE_PATH); + return ERROR_FILEEMPTY; + } + + // check if list is not empty and _lineNumber for given key exists + if(_lineNumber.find(key) == _lineNumber.end()) { + LOGERR("Failed to read key: %s, Key do not exist in migration dataStore", key.c_str()); + return ERROR_NAME; + } + + result = _valueEntry[_lineNumber[key] - 2]; + return Core::ERROR_NONE; + } + + uint32_t MigrationPreparerImplementation::Delete(const string& name) { + + LOGINFO("[DELETE] params={name: %s}", name.c_str()); + string key = name; + int result; + + if(_lineNumber.empty()) { + LOGERR("Failed to delete key: %s, migration dataStore %s is empty", key.c_str(), DATASTORE_PATH); + return ERROR_FILEEMPTY; + } + + _dataStoreMutex.lock(); + if(_lineNumber.find(key) != _lineNumber.end()) { + // sed command to delete an key-value entry in the dataStore + result = v_secure_system("/bin/sed -i '%sd' %s", std::to_string(_lineNumber[key]).c_str(), DATASTORE_PATH); + + if (result != -1 && WIFEXITED(result)) { + // check if last entry is deleted + if(_lineNumber[key] == _curLineIndex) { + WPEFramework::Core::File dataStore(DATASTORE_PATH); + dataStore.Append(); + // if last entry is deleted remove comma from the previous line + dataStore.Position(false, dataStore.Size()); + if(!dataStore.SetSize(dataStore.Size() - 3)) { + LOGERR("DataStore truncate failed with errno: %d, reason: %s\n", errno, strerror(errno)); + } + string entry; + if(_lineNumber.size() == 1) // if no other entries left + entry = string("{\n}"); + else + entry = string("\n}"); + + dataStore.Write(reinterpret_cast(&entry[0]), entry.size()); + dataStore.Close(); + } + // Adjust line line number for other entries + for (auto it = _lineNumber.begin(); it != _lineNumber.end(); ++it) { + if(it->second > _lineNumber[key]) { + it->second--; + } + } + + _valueEntry.erase(_valueEntry.begin() + _lineNumber[key] - 2); + + // remove key from the _lineNumber map + _lineNumber.erase(key); + _curLineIndex--; + _dataStoreMutex.unlock(); + return Core::ERROR_NONE; + } + + result = WEXITSTATUS(result); + LOGERR("Failed to delete entry for key: %s in migration datastore, v_secure_system failed with error %d",key.c_str(), result); + _dataStoreMutex.unlock(); + return ERROR_DELETE; + } + + LOGERR("Failed to delete entry for key: %s in migration datastore, Key does not exist in dataStore",key.c_str()); + _dataStoreMutex.unlock(); + return ERROR_NAME; + } + + + uint32_t MigrationPreparerImplementation::setComponentReadiness(const string& compName) + { + uint32_t status = Core::ERROR_GENERAL; + string _compName = compName; + _adminLock.Lock(); + LOGINFO("Component Name: %s", _compName.c_str()); + WPEFramework::Core::File migrationReady(MIGRATIONREADY_PATH); + WPEFramework::Core::Directory migrationReadyDir(MIGRATIONREADY_DIR); + + // check whether file is exist or not, if not create one + if(!migrationReady.Exists()) { + // check if the directory exist + if(!migrationReadyDir.Exists()){ + // assuming /opt/secure path already exist and "migration" folder needs to be under /opt/secure/ + if(migrationReadyDir.Create()){ + LOGERR("Failed to create migration ready %s, errno: %d, reason: %s", MIGRATIONREADY_PATH, errno, strerror(errno)); + _adminLock.Unlock(); + return ERROR_CREATE; + } + } + if(!migrationReady.Create()) { + LOGERR("Failed to create migration ready %s, errno: %d, reason: %s", MIGRATIONREADY_PATH, errno, strerror(errno)); + _adminLock.Unlock(); + return ERROR_CREATE; + } + } + + // open file in append mode + if(!migrationReady.Open(false)) { + LOGERR("Failed to open migration ready %s, errno: %d, reason: %s", MIGRATIONREADY_PATH, errno, strerror(errno)); + LOGERR("Failed to add component{%s} in migration ready", _compName.c_str()); + _adminLock.Unlock(); + return ERROR_OPEN; + } + // read component list and add new component in order into file + string outComponentString = _compName; + // Lambda to compare strings in lexicographical order + auto componentComparator = [](const std::string& a, const std::string& b) { + return a < b; + }; + if(migrationReady.Size() > 0){ + uint8_t datatoread[migrationReady.Size()+1]={'\0'}; + uint32_t size = migrationReady.Read(datatoread, static_cast(sizeof(datatoread))); + if(size){ + std::string inComponentString(reinterpret_cast(datatoread)); + std::list componentlist; + outComponentString = ""; + split(componentlist, inComponentString); + // check whether it is duplicate or not + if(isDuplicate(_compName, componentlist)){ + migrationReady.Close(); + status = Core::ERROR_NONE; + _adminLock.Unlock(); + return status; + } + componentlist.push_back(_compName); + componentlist.sort(componentComparator); + // Print component of the set + for (const auto& str : componentlist) { + std::cout << str << " "; + } + std::cout << std::endl; + join(outComponentString, componentlist); + }else{ + LOGERR("Failed to read migration ready %s, errno: %d, reason: %s", MIGRATIONREADY_PATH, errno, strerror(errno)); + LOGERR("Failed to add component{%s} in migration ready", _compName.c_str()); + _adminLock.Unlock(); + return ERROR_SET; + } + } + uint8_t datatowrite[outComponentString.size()]; + ::memcpy(datatowrite, outComponentString.data(), outComponentString.size()); + // write componentList to the migration ready file from begining + migrationReady.Position(false, 0); + migrationReady.Write(datatowrite, static_cast(sizeof(datatowrite))); + migrationReady.Close(); + status = Core::ERROR_NONE; + _adminLock.Unlock(); + return status; + } + + + uint32_t MigrationPreparerImplementation::getComponentReadiness(RPC::IStringIterator*& compList) + { + uint32_t status = Core::ERROR_GENERAL; + _adminLock.Lock(); + std::list componentlist; + WPEFramework::Core::File migrationReady(MIGRATIONREADY_PATH); + WPEFramework::Core::Directory migrationReadyDir(MIGRATIONREADY_DIR); + + // check whether file is exist or not, if not return empty string + if(!migrationReady.Exists()) { + LOGERR("MigrationReady file %s does not exist", MIGRATIONREADY_PATH); + _adminLock.Unlock(); + return Core::ERROR_NONE; + } + + // open file in append mode + if(!migrationReady.Open(false)) { + LOGERR("Failed to open migration ready %s, errno: %d, reason: %s", MIGRATIONREADY_PATH, errno, strerror(errno)); + _adminLock.Unlock(); + return Core::ERROR_GENERAL; + } + // read component list and add new component in order into file + if(migrationReady.Size() > 0){ + uint8_t datatoread[migrationReady.Size() + 1] ={'\0'}; + uint32_t size = migrationReady.Read(datatoread, static_cast(sizeof(datatoread))); + if(size){ + std::string inComponentString(reinterpret_cast(datatoread)); + split(componentlist, inComponentString); + } + } + compList = (Core::Service::Create(componentlist)); + status = Core::ERROR_NONE; + LOGINFO("Component status[%d]", status); + _adminLock.Unlock(); + return status; + } + + + uint32_t MigrationPreparerImplementation::reset(const string& resetType) + { + string empty; + _adminLock.Lock(); + LOGINFO("[RESET] params={resetType: %s}", resetType.c_str()); + if(resetType == "RESET_ALL") { + if(!resetDatastore()) + return ERROR_RESET; + if(!resetMigrationready()) + return ERROR_RESET; + } + else if (resetType == "RESET_DATA") { + if(!resetDatastore()) + return ERROR_RESET; + } + else if (resetType == "RESET_READINESS") { + if(!resetMigrationready()) + return ERROR_RESET; + } + else { + // Invalid parameter + return ERROR_INVALID; + } + _adminLock.Unlock(); + + return Core::ERROR_NONE; + } + +} // namespace Plugin +} // namespace WPEFramework \ No newline at end of file diff --git a/MigrationPreparer/MigrationPreparerImplementation.h b/MigrationPreparer/MigrationPreparerImplementation.h new file mode 100644 index 0000000000..f340840347 --- /dev/null +++ b/MigrationPreparer/MigrationPreparerImplementation.h @@ -0,0 +1,128 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2024 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 "Module.h" +#include +#include "tracing/Logging.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "secure_wrapper.h" +#include "UtilsJsonRpc.h" +#include "Module.h" + + +#define MIGRATIONPREPARER_NAMESPACE "MigrationPreparer" +#define DATASTORE_PATH _T("/opt/secure/migration/migration_data_store.json") +#define DATASTORE_DIR _T("/opt/secure/migration/") +#define TR181_MIGRATION_READY "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Bootstrap.MigrationReady" +#define MIGRATIONREADY_PATH _T("/opt/secure/migration/migrationready.txt") +#define MIGRATIONREADY_DIR _T("/opt/secure/migration/") + +// PLUGIN SPECIFIC CUSTOM ERROR CODES +#define ERROR_CREATE 4 +#define ERROR_OPEN 5 +#define ERROR_WRITE 6 +#define ERROR_NAME 7 +#define ERROR_DELETE 8 +#define ERROR_SET 9 +#define ERROR_RESET 10 +#define ERROR_INVALID 11 +#define ERROR_NOFILE 12 +#define ERROR_FILEEMPTY 13 + +typedef uint64_t LINE_NUMBER_TYPE; +using std::string; + + + +namespace WPEFramework { +namespace Plugin { + class MigrationPreparerImplementation : public Exchange::IMigrationPreparer + { + private: + // We do not allow this plugin to be copied !! + MigrationPreparerImplementation(const MigrationPreparerImplementation&) = delete; + MigrationPreparerImplementation& operator=(const MigrationPreparerImplementation&) = delete; + + public: + MigrationPreparerImplementation(); + ~MigrationPreparerImplementation() override; + + /*Methods: Begin*/ + // DataStore - here represents a JSON File + // API to write and update dataStore + uint32_t write(const string& name, const string &value) override; + // API to delete dataStore entry + uint32_t Delete(const string& name) override; + // API to read dataStore entry + uint32_t read(const string& name, string &result) override; + + + uint32_t setComponentReadiness(const string& compName) override; + uint32_t getComponentReadiness(RPC::IStringIterator*& compList) override; + uint32_t reset(const string& resetType) override; + /*Methods: End*/ + + BEGIN_INTERFACE_MAP(MigrationPreparerImplementation) + INTERFACE_ENTRY(Exchange::IMigrationPreparer) + END_INTERFACE_MAP + + private: + mutable Core::CriticalSection _adminLock; + + // A map to hold "Key" vs "Line Number" in the dataStore + std::map _lineNumber; + // A mutex to protect the dataStore from concurrent read, write and delete access + std::mutex _dataStoreMutex; + // A tracker for the last key-value line number in the dataStore + LINE_NUMBER_TYPE _curLineIndex; + // A vector to hold value entries present in data Store file + std::vector _valueEntry; + // A flag to track if dataStore file exists + bool _fileExist; + + /*Helpers: Begin*/ + //escape any special characters in string to make sed compatible pattern string and replacement string + enum sedType {PATTERN, REPLACEMENT}; + string escapeSed(string, enum sedType); + // Fn. to store the keys and their line numbers from dataStore to lineNumber map + void storeKeys(void); + // Fn. to delete dataStore + bool resetDatastore(void); + //Fn. to populate list with values from a string + void get_components(std::list& list, string& value, string input = ""); + //Fn. to populate value from a list with delimiter(_) + void tokenize(string& value, std::list& list); + + int8_t split(std::list& list, string& value, std::string delimiter = "_"); + int8_t join(string& value, std::list& list, std::string delimiter = "_"); + bool isDuplicate(string& searchString, std::list& list); + bool resetMigrationready(void); + /*Helpers: End*/ + }; +} // namespace Plugin +} // namespace WPEFramework \ No newline at end of file diff --git a/MigrationPreparer/MigrationPreparerJsonRpc.cpp b/MigrationPreparer/MigrationPreparerJsonRpc.cpp new file mode 100644 index 0000000000..1ae2ad8845 --- /dev/null +++ b/MigrationPreparer/MigrationPreparerJsonRpc.cpp @@ -0,0 +1,385 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2022 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 "MigrationPreparer.h" + +namespace WPEFramework { +namespace Plugin { + + using namespace JsonData::MigrationPreparer; + + void MigrationPreparer::RegisterAll() + { + Register(_T("write"), &MigrationPreparer::endpoint_write, this); + Register(_T("read"), &MigrationPreparer::endpoint_read, this); + Register(_T("delete"), &MigrationPreparer::endpoint_delete, this); + Register(_T("getComponentReadiness"), &MigrationPreparer::endpoint_getComponentReadiness, this); + Register(_T("setComponentReadiness"), &MigrationPreparer::endpoint_setComponentReadiness, this); + Register(_T("reset"), &MigrationPreparer::endpoint_reset, this); + } + + void MigrationPreparer::UnregisterAll() + { + Unregister(_T("write")); + Unregister(_T("read")); + Unregister(_T("delete")); + Unregister(_T("getComponentReadiness")); + Unregister(_T("setComponentReadiness")); + Unregister(_T("reset")); + } + + uint32_t MigrationPreparer::endpoint_write(const JsonObject& parameters, JsonObject& response) + { + bool status = false; + JsonObject error; + + // check if required filds - name, value exists + if (!parameters.HasLabel("name") || !parameters.HasLabel("value")) { + LOGERR("Invalid input - missing name or value lables"); + error["message"] = "Missing param"; + error["code"] = "-32001"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are strigified + if ((JsonValue::type::STRING != parameters["name"].Content()) || (JsonValue::type::STRING != parameters["value"].Content())) { + LOGERR("Invalid input - name or value is not a string eg. \"params\": {\"name\":\"key\", \"value\":\"val\"}"); + error["message"] = "Param not a string"; + error["code"] = "-32002"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are empty + if (parameters["name"].String().empty() || parameters["value"].String().empty()) { + LOGERR("Invalid input - name or value is empty"); + error["message"] = "Missing param"; + error["code"] = "-32003"; + response["error"] = error; + returnResponse(status); + } + + auto result = _migrationPreparer->write( + parameters["name"].String(), + parameters["value"].String()); + + switch(result) { + case ERROR_CREATE: + error["message"] = "File create Failed"; + error["code"] = "-32004"; + response["error"] = error; + break; + case ERROR_OPEN: + error["message"] = "File Open Failed"; + error["code"] = "-32005"; + response["error"] = error; + break; + case ERROR_WRITE: + error["message"] = "Write Failed"; + error["code"] = "-32006"; + response["error"] = error; + break; + case Core::ERROR_NONE: + status = true; + break; + default: + error["message"] = "Unknown Error"; + error["code"] = "-32099"; + response["error"] = error; + } + + returnResponse(status); + } + + uint32_t MigrationPreparer::endpoint_read(const JsonObject& parameters, JsonObject& response) + { + bool status = false; + JsonObject error; + + // check if required filds - name, value exists + if (!parameters.HasLabel("name")) { + LOGERR("Invalid input - missing name"); + error["message"] = "Missing param"; + error["code"] = "-32001"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are strigified + if (JsonValue::type::STRING != parameters["name"].Content()) { + LOGERR("Invalid input - name param is not a string eg. \"params\": {\"name\":\"key\"}"); + error["message"] = "Param not a string"; + error["code"] = "-32002"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are empty + if (parameters["name"].String().empty()) { + LOGERR("Invalid input - name param is empty"); + error["message"] = "Empty param"; + error["code"] = "-32003"; + response["error"] = error; + returnResponse(status); + } + + string value; + auto result = _migrationPreparer->read( + parameters["name"].String(), + value); + + switch(result) { + case ERROR_NOFILE: + error["message"] = "Error file not exist"; + error["code"] = "-32012"; + response["error"] = error; + break; + case ERROR_FILEEMPTY: + error["message"] = "Error file empty"; + error["code"] = "-32013"; + response["error"] = error; + break; + case ERROR_NAME: + error["message"] = "Name not found"; + error["code"] = "-32007"; + response["error"] = error; + break; + case Core::ERROR_NONE: + status = true; + response["value"] = value; + break; + default: + error["message"] = "Unknown Error"; + error["code"] = "-32099"; + response["error"] = error; + } + + returnResponse(status); + } + + uint32_t MigrationPreparer::endpoint_delete(const JsonObject& parameters, JsonObject& response) { + bool status = false; + JsonObject error; + + // check if required filds - name, value exists + if (!parameters.HasLabel("name")) { + LOGERR("Invalid input - missing name"); + error["message"] = "Missing param"; + error["code"] = "-32001"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are strigified + if (JsonValue::type::STRING != parameters["name"].Content()) { + LOGERR("Invalid input - name param is not a string eg. \"params\": {\"name\":\"key\"}"); + error["message"] = "Param not a string"; + error["code"] = "-32002"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are empty + if (parameters["name"].String().empty()) { + LOGERR("Invalid input - name param is empty"); + error["message"] = "Empty param"; + error["code"] = "-32003"; + response["error"] = error; + returnResponse(status); + } + + auto result = _migrationPreparer->Delete( + parameters["name"].String()); + + switch(result) { + case ERROR_NAME: + error["message"] = "Name not found"; + error["code"] = "-32007"; + response["error"] = error; + break; + case ERROR_DELETE: + error["message"] = "Delete failed"; + error["code"] = "-32008"; + response["error"] = error; + break; + case ERROR_NOFILE: + error["message"] = "Error file not exist"; + error["code"] = "-32012"; + response["error"] = error; + break; + case ERROR_FILEEMPTY: + error["message"] = "Error file empty"; + error["code"] = "-32013"; + response["error"] = error; + break; + case Core::ERROR_NONE: + status = true; + break; + default: + error["message"] = "Unknown Error"; + error["code"] = "-32099"; + response["error"] = error; + } + + returnResponse(status); + } + + + uint32_t MigrationPreparer::endpoint_getComponentReadiness(const JsonObject& parameters, GetcomponentreadinessResultData& response) { + + // condition check to block the response if any params are passed + if(parameters.IsSet()) { + LOGERR("Invalid input - provide no params"); + return Core::ERROR_GENERAL; + } + + RPC::IStringIterator* componentList = nullptr; + auto result = _migrationPreparer->getComponentReadiness(componentList); + if (result == Core::ERROR_NONE) { + string component; + if(componentList != nullptr) { + while (componentList->Next(component) == true) { + response.ComponentList.Add() = component; + } + componentList->Release(); + } + else { + response.ComponentList.Add() = string("\0"); //empty string array + } + response.Success = true; + } + return result; + } + + + uint32_t MigrationPreparer::endpoint_setComponentReadiness(const JsonObject& parameters, JsonObject& response) { + bool status = false; + JsonObject error; + + // check if required filds - name, value exists + if (!parameters.HasLabel("componentName")) { + LOGERR("Invalid input - missing componentName"); + error["message"] = "Missing param"; + error["code"] = "-32001"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are strigified + if (JsonValue::type::STRING != parameters["componentName"].Content()) { + LOGERR("Invalid input - componentName param is not a string eg. \"params\": {\"componentName\":\"RA01\"}"); + error["message"] = "Param not a string"; + error["code"] = "-32002"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are empty + if (parameters["componentName"].String().empty()) { + LOGERR("Invalid input - componentName param is empty"); + error["message"] = "Empty param"; + error["code"] = "-32003"; + response["error"] = error; + returnResponse(status); + } + + auto result = _migrationPreparer->setComponentReadiness( + parameters["componentName"].String()); + + switch(result) { + case ERROR_CREATE: + error["message"] = "File create Failed"; + error["code"] = "-32004"; + response["error"] = error; + break; + case ERROR_OPEN: + error["message"] = "File Open Failed"; + error["code"] = "-32005"; + response["error"] = error; + break; + case ERROR_WRITE: + error["message"] = "Write Failed"; + error["code"] = "-32006"; + response["error"] = error; + break; + case ERROR_SET: + error["message"] = "Set Operation Failed"; + error["code"] = "-32009"; + response["error"] = error; + break; + case Core::ERROR_NONE: + status = true; + break; + default: + error["message"] = "Unknown Error"; + error["code"] = "-32099"; + response["error"] = error; + } + + returnResponse(status); + } + + + uint32_t MigrationPreparer::endpoint_reset(const JsonObject& parameters, JsonObject& response) { + bool status = false; + JsonObject error; + + // check if required filds - name, value exists + if (!parameters.HasLabel("resetType")) { + LOGERR("Invalid input - missing resetType"); + error["message"] = "Missing param"; + error["code"] = "-32001"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are strigified + if (JsonValue::type::STRING != parameters["resetType"].Content()) { + LOGERR("Invalid input - resetType param is not a string eg. \"params\": {\"resetType\":\"RESET_ALL\"}"); + error["message"] = "Param not a string"; + error["code"] = "-32002"; + response["error"] = error; + returnResponse(status); + } + // check if provided params are empty + if (parameters["resetType"].String().empty()) { + LOGERR("Invalid input - resetType param is empty"); + error["message"] = "Empty param"; + error["code"] = "-32003"; + response["error"] = error; + returnResponse(status); + } + + auto result = _migrationPreparer->reset(parameters["resetType"].String()); + switch(result) { + case ERROR_RESET: + error["message"] = "Unexpected error while resetting"; + error["code"] = "-32010"; + response["error"] = error; + break; + case ERROR_INVALID: + error["message"] = "Invalid parameter"; + error["code"] = "-32011"; + response["error"] = error; + break; + case Core::ERROR_NONE: + status = true; + break; + default: + error["message"] = "Unknown Error"; + error["code"] = "-32099"; + response["error"] = error; + } + + returnResponse(status); + } +} // namespace Plugin +} // namespace WPEFramework diff --git a/MigrationPreparer/MigrationPreparerPlugin.json b/MigrationPreparer/MigrationPreparerPlugin.json new file mode 100644 index 0000000000..79274da638 --- /dev/null +++ b/MigrationPreparer/MigrationPreparerPlugin.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/plugin.schema.json", + "info": { + "title": "MigrationPreparer Plugin", + "callsign": "org.rdk.MigrationPreparer", + "locator": "libWPEFrameworkMigrationPreparer.so", + "status": "production", + "description": "The `MigrationPreparer` that is responsible for persisting and notifying listeners of any change of key/value pairs and get/set value of MigrationReady RFC value" + }, + "interface": { + "$ref": "MigrationPreparer.json#" + } +} \ No newline at end of file diff --git a/MigrationPreparer/Module.cpp b/MigrationPreparer/Module.cpp new file mode 100644 index 0000000000..1123d63cd6 --- /dev/null +++ b/MigrationPreparer/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2024 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) \ No newline at end of file diff --git a/MigrationPreparer/Module.h b/MigrationPreparer/Module.h new file mode 100644 index 0000000000..07d6ab7e73 --- /dev/null +++ b/MigrationPreparer/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2024 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 +#ifndef MODULE_NAME +#define MODULE_NAME MigrationPreparer +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL \ No newline at end of file diff --git a/MigrationPreparer/README.md b/MigrationPreparer/README.md new file mode 100644 index 0000000000..ab47744272 --- /dev/null +++ b/MigrationPreparer/README.md @@ -0,0 +1,29 @@ +----------------- +# MigrationPreparer + +## Versions +`org.rdk.MigrationPreparer` + +## Methods: +``` +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"1","method": "org.rdk.MigrationPreparer.write", "params": {"name":"picture/viewing-mode","value":"\"vivid\""}}' http://127.0.0.1:9998/jsonrpc +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"1","method": "org.rdk.MigrationPreparer.read", "params": {"name":"picture/viewing-mode"}}' http://127.0.0.1:9998/jsonrpc +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"1","method": "org.rdk.MigrationPreparer.delete", "params": {"name":"picture/viewing-mode"}}' http://127.0.0.1:9998/jsonrpc +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "org.rdk.MigrationPreparer.setComponentReadiness", "params": {"componentName":RA01}}' http://127.0.0.1:9998/jsonrpc +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "org.rdk.MigrationPreparer.getComponentReadiness"}' http://127.0.0.1:9998/jsonrpc +curl -H "Authorization: Bearer `WPEFrameworkSecurityUtility | cut -d '"' -f 4`" --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "org.rdk.MigrationPreparer.reset", "params": {"resetType":"RESET_ALL"}}' http://127.0.0.1:9998/jsonrpc + +``` +## Responses +``` +{"jsonrpc":"2.0","id":1,"result":{"success":true}} +{"jsonrpc":"2.0","id":1,"result":{"value":"\"vivid\"","success":true}} +{"jsonrpc":"2.0","id":1,"result":{"success":true}} +{"jsonrpc":"2.0","id":3,"result":{"success":true}} +{"jsonrpc":"2.0","id":3,"result":{"componentList":["RA01"],"success":true}} +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +``` + +## Full Reference +https://etwiki.sys.comcast.net/display/RDKV/MigrationPreparer+Plugin+Design+Document \ No newline at end of file