diff --git a/.github/workflows/BuildThunder.sh b/.github/workflows/BuildThunder.sh new file mode 100755 index 0000000000..2d0975bd2c --- /dev/null +++ b/.github/workflows/BuildThunder.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +############################ +# From https://rdkcentral.github.io/Thunder/introduction/build_linux/ +# DO NOT MODIFY + +############################ +# 1. Install Dependencies + +sudo apt install -y build-essential cmake ninja-build libusb-1.0-0-dev zlib1g-dev libssl-dev + +pip install jsonref + +############################ +# 2. Build Thunder Tools + +git clone https://github.com/rdkcentral/ThunderTools.git + +cmake -G Ninja -S ThunderTools -B build/ThunderTools -DCMAKE_INSTALL_PREFIX="install/usr" + +cmake --build build/ThunderTools --target install + +############################ +# 3. Build Thunder + +git clone https://github.com/rdkcentral/Thunder.git + +cmake -G Ninja -S Thunder -B build/Thunder \ + -DBUILD_SHARED_LIBS=ON \ + -DBINDING="127.0.0.1" \ + -DCMAKE_BUILD_TYPE="Debug" \ + -DCMAKE_INSTALL_PREFIX="install/usr" \ + -DCMAKE_MODULE_PATH="${PWD}/install/usr/include/WPEFramework/Modules" \ + -DDATA_PATH="${PWD}/install/usr/share/WPEFramework" \ + -DPERSISTENT_PATH="${PWD}/install/var/wpeframework" \ + -DPORT="55555" \ + -DPROXYSTUB_PATH="${PWD}/install/usr/lib/wpeframework/proxystubs" \ + -DSYSTEM_PATH="${PWD}/install/usr/lib/wpeframework/plugins" \ + -DVOLATILE_PATH="tmp" + +cmake --build build/Thunder --target install + +############################ +# 4. Build ThunderInterfaces + +git clone https://github.com/rdkcentral/ThunderInterfaces.git + +cmake -G Ninja -S ThunderInterfaces -B build/ThunderInterfaces \ + -DCMAKE_INSTALL_PREFIX="install/usr" \ + -DCMAKE_MODULE_PATH="${PWD}/install/usr/include/WPEFramework/Modules" + +cmake --build build/ThunderInterfaces --target install diff --git a/.github/workflows/L0-PersistentStore.yml b/.github/workflows/L0-PersistentStore.yml new file mode 100644 index 0000000000..1b68bf704f --- /dev/null +++ b/.github/workflows/L0-PersistentStore.yml @@ -0,0 +1,53 @@ +name: L0-PersistentStore + +on: + push: + paths: + - PersistentStore/** + pull_request: + paths: + - PersistentStore/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: ${{github.repository}} + + - name: Install valgrind, coverage, cmake + run: | + sudo apt update + sudo apt install -y valgrind lcov cmake + + - name: Build Thunder + working-directory: ${{github.workspace}} + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh + + - name: Build + working-directory: ${{github.workspace}} + run: | + cmake -S ${GITHUB_REPOSITORY}/PersistentStore/l0test -B build/persistentstorel0test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + cmake --build build/persistentstorel0test --target install + + - name: Run + working-directory: ${{github.workspace}} + run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try persistentstorel0test + + - name: Generate coverage + working-directory: ${{github.workspace}} + run: | + lcov -c -o coverage.info -d build/persistentstorel0test + genhtml -o coverage coverage.info + + - name: Upload artifacts + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: | + coverage/ + valgrind_log + if-no-files-found: warn diff --git a/.github/workflows/L1-PersistentStore-sqlite.yml b/.github/workflows/L1-PersistentStore-sqlite.yml new file mode 100644 index 0000000000..52912a79ee --- /dev/null +++ b/.github/workflows/L1-PersistentStore-sqlite.yml @@ -0,0 +1,53 @@ +name: L1-PersistentStore-sqlite + +on: + push: + paths: + - PersistentStore/sqlite/** + pull_request: + paths: + - PersistentStore/sqlite/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: ${{github.repository}} + + - name: Install valgrind, coverage, cmake, sqlite + run: | + sudo apt update + sudo apt install -y valgrind lcov cmake libsqlite3-dev + + - name: Build Thunder + working-directory: ${{github.workspace}} + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh + + - name: Build + working-directory: ${{github.workspace}} + run: | + cmake -S ${GITHUB_REPOSITORY}/PersistentStore/sqlite/l1test -B build/sqlitel1test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + cmake --build build/sqlitel1test --target install + + - name: Run + working-directory: ${{github.workspace}} + run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try sqlitel1test + + - name: Generate coverage + working-directory: ${{github.workspace}} + run: | + lcov -c -o coverage.info -d build/sqlitel1test + genhtml -o coverage coverage.info + + - name: Upload artifacts + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: | + coverage/ + valgrind_log + if-no-files-found: warn diff --git a/.github/workflows/L1-PersistentStore.yml b/.github/workflows/L1-PersistentStore.yml new file mode 100644 index 0000000000..1766744bc4 --- /dev/null +++ b/.github/workflows/L1-PersistentStore.yml @@ -0,0 +1,53 @@ +name: L1-PersistentStore + +on: + push: + paths: + - PersistentStore/** + pull_request: + paths: + - PersistentStore/** + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: ${{github.repository}} + + - name: Install valgrind, coverage, cmake + run: | + sudo apt update + sudo apt install -y valgrind lcov cmake + + - name: Build Thunder + working-directory: ${{github.workspace}} + run: sh +x ${GITHUB_REPOSITORY}/.github/workflows/BuildThunder.sh + + - name: Build + working-directory: ${{github.workspace}} + run: | + cmake -S ${GITHUB_REPOSITORY}/PersistentStore/l1test -B build/persistentstorel1test -DCMAKE_INSTALL_PREFIX="install/usr" -DCMAKE_CXX_FLAGS="--coverage -Wall -Werror" + cmake --build build/persistentstorel1test --target install + + - name: Run + working-directory: ${{github.workspace}} + run: PATH=${PWD}/install/usr/bin:${PATH} LD_LIBRARY_PATH=${PWD}/install/usr/lib:${LD_LIBRARY_PATH} valgrind --tool=memcheck --log-file=valgrind_log --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try persistentstorel1test + + - name: Generate coverage + working-directory: ${{github.workspace}} + run: | + lcov -c -o coverage.info -d build/persistentstorel1test + genhtml -o coverage coverage.info + + - name: Upload artifacts + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: artifacts + path: | + coverage/ + valgrind_log + if-no-files-found: warn diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 42e182662f..2fd08fe57b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -268,7 +268,6 @@ jobs: -DPLUGIN_DATACAPTURE=ON -DPLUGIN_DEVICEDIAGNOSTICS=ON -DPLUGIN_LOCATIONSYNC=ON - -DPLUGIN_PERSISTENTSTORE=ON -DPLUGIN_TIMER=ON -DPLUGIN_SECURITYAGENT=ON -DPLUGIN_DEVICEIDENTIFICATION=ON diff --git a/PersistentStore/CHANGELOG.md b/PersistentStore/CHANGELOG.md index 7779a8e49e..4e79175f37 100644 --- a/PersistentStore/CHANGELOG.md +++ b/PersistentStore/CHANGELOG.md @@ -16,6 +16,17 @@ All notable changes to this RDK Service will be documented in this file. * For more details, refer to [versioning](https://github.com/rdkcentral/rdkservices#versioning) section under Main README. +## [1.0.6] - 2024-03-11 +### Added +- RPC COM interface IStore2, IStoreInspector, IStoreLimit +- JSON RPC API getStorageSizes, getNamespaceStorageLimit, setNamespaceStorageLimit +- "scope" parameter (defaults to "device") +- "ttl" parameter +- Configuration "limit" parameter +- L0, L1 unit tests +- Thunder master branch support +- Out-of-process support + ## [1.0.5] - 2024-01-02 ### Security - resolved security vulnerabilities diff --git a/PersistentStore/CMakeLists.txt b/PersistentStore/CMakeLists.txt index f1c1939065..1afd301c4d 100644 --- a/PersistentStore/CMakeLists.txt +++ b/PersistentStore/CMakeLists.txt @@ -15,59 +15,56 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(PLUGIN_NAME PersistentStore) -set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) +cmake_minimum_required(VERSION 3.14) -set(PLUGIN_PERSISTENTSTORE_PATH /opt/secure/persistent/rdkservicestore CACHE STRING "Path") -set(PLUGIN_PERSISTENTSTORE_KEY "" CACHE STRING "Encryption key") -set(PLUGIN_PERSISTENTSTORE_STARTUPORDER "" CACHE STRING "To configure startup order of PersistentStore plugin") - -find_package(${NAMESPACE}Plugins REQUIRED) +project(PersistentStore) -find_package(PkgConfig) +set(CMAKE_CXX_STANDARD 11) -# enabling the secure extension requires a license: https://www.hwaci.com/cgi-bin/see-step1 -if (USE_SQLITE_SEE) - find_package(SqliteSee REQUIRED) -else () - find_package(Sqlite REQUIRED) -endif () +find_package(WPEFramework) +set(MODULE_NAME "${NAMESPACE}${PROJECT_NAME}") -# nonce generator -if (BUILD_WITH_PLABELS AND USE_SQLITE_SEE) - find_package(DL REQUIRED) - find_package(Plabels REQUIRED) - find_package(GLIB REQUIRED) -endif () +set(PLUGIN_PERSISTENTSTORE_MODE "Off" CACHE STRING "Controls if the plugin should run in its own process, in process or remote") +set(PLUGIN_PERSISTENTSTORE_URI "ss.eu.prod.developer.comcast.com:443" CACHE STRING "Account scope endpoint") +set(PLUGIN_PERSISTENTSTORE_PATH "/opt/secure/persistent/rdkservicestore" CACHE STRING "Path") +set(PLUGIN_PERSISTENTSTORE_LEGACYPATH "/opt/persistent/rdkservicestore" CACHE STRING "Previously used path") +set(PLUGIN_PERSISTENTSTORE_KEY "" CACHE STRING "Encryption key") +set(PLUGIN_PERSISTENTSTORE_MAXSIZE "1000000" CACHE STRING "For all text data, in bytes") +set(PLUGIN_PERSISTENTSTORE_MAXVALUE "3000" CACHE STRING "For single text data, in bytes") +set(PLUGIN_PERSISTENTSTORE_LIMIT "10000" CACHE STRING "Default for all text data in namespace, in bytes") +set(PLUGIN_PERSISTENTSTORE_STARTUPORDER "" CACHE STRING "To configure startup order of PersistentStore plugin") add_library(${MODULE_NAME} SHARED PersistentStore.cpp PersistentStoreJsonRpc.cpp - SqliteStore.cpp + sqlite/Store2.cpp + sqlite/StoreCache.cpp + sqlite/StoreInspector.cpp + sqlite/StoreLimit.cpp Module.cpp - ) - -set_target_properties(${MODULE_NAME} PROPERTIES - CXX_STANDARD 11 - CXX_STANDARD_REQUIRED YES) - -add_definitions(${SQLITE_CFLAGS_OTHER} ${PLABELS_FLAGS}) -link_directories(${SQLITE_LIBRARY_DIRS}) - -target_include_directories(${MODULE_NAME} PRIVATE - ${SQLITE_INCLUDE_DIRS} - ${PLABELS_INCLUDE_DIRS} - ${GLIB_INCLUDE_DIRS} - ../helpers) +) +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins - ${SQLITE_LIBRARIES} - ${PLABELS_LIBRARIES} - ${GLIB_LIBRARIES} - ${DL_LIBRARIES}) + ${NAMESPACE}Definitions::${NAMESPACE}Definitions +) + +find_package(PkgConfig REQUIRED) +pkg_search_module(SQLITE REQUIRED sqlite3) +target_link_libraries(${MODULE_NAME} PRIVATE ${SQLITE_LIBRARIES}) + +find_library(IARMBUS_LIBRARIES NAMES IARMBus) +if (IARMBUS_LIBRARIES) + find_path(IARMBUS_INCLUDE_DIRS NAMES libIBus.h PATH_SUFFIXES rdk/iarmbus REQUIRED) + find_path(IARMSYS_INCLUDE_DIRS NAMES sysMgr.h PATH_SUFFIXES rdk/iarmmgrs/sysmgr REQUIRED) + target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS} ${IARMSYS_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${IARMBUS_LIBRARIES}) + add_definitions(-DWITH_SYSMGR) +endif () install(TARGETS ${MODULE_NAME} DESTINATION lib/${STORAGE_DIRECTORY}/plugins) -write_config(${PLUGIN_NAME}) +write_config() diff --git a/PersistentStore/CountingLock.h b/PersistentStore/CountingLock.h deleted file mode 100644 index bd6ddf440b..0000000000 --- a/PersistentStore/CountingLock.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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. - */ - -#ifndef COUNTINGLOCK_H -#define COUNTINGLOCK_H - -#include "Module.h" - -namespace WPEFramework { -namespace Plugin { - -class CountingLock -{ -private: - CountingLock(const CountingLock &) = delete; - CountingLock &operator=(const CountingLock &) = delete; - -public: - CountingLock() - : _lock(), _count(0) - { - } - - void Lock(unsigned int count) - { - _lock.Lock(); - - while (_count > count); - - if (count != 0) { - _count++; - - _lock.Unlock(); - } - } - - void Unlock() - { - if (_count == 0) { - _lock.Unlock(); - } - else { - _count--; - } - } - -private: - Core::CriticalSection _lock; - std::atomic_uint _count; -}; - -class CountingLockSync -{ -private: - CountingLockSync() = delete; - CountingLockSync(const CountingLockSync &) = delete; - CountingLockSync &operator=(const CountingLockSync &) = delete; - -public: - explicit CountingLockSync(CountingLock &lock, unsigned int count = -1) - : _lock(lock) - { - _lock.Lock(count); - } - - ~CountingLockSync() - { - _lock.Unlock(); - } - -private: - CountingLock &_lock; -}; - -} // namespace Plugin -} // namespace WPEFramework - -#endif //COUNTINGLOCK_H diff --git a/PersistentStore/IStoreListing.h b/PersistentStore/IStoreListing.h deleted file mode 100644 index adb1445e4e..0000000000 --- a/PersistentStore/IStoreListing.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include "Module.h" - -namespace WPEFramework { -namespace Plugin { - -struct IStoreListing : virtual public Core::IUnknown { - virtual uint32_t GetKeys(const string &ns, std::vector &keys /* @out */) = 0; - virtual uint32_t GetNamespaces(std::vector &namespaces /* @out */) = 0; - virtual uint32_t GetStorageSize(std::map &namespaceSizes /* @out */) = 0; -}; - -} // namespace Plugin -} // namespace WPEFramework diff --git a/PersistentStore/Module.h b/PersistentStore/Module.h index 733840eddf..509556a238 100644 --- a/PersistentStore/Module.h +++ b/PersistentStore/Module.h @@ -23,7 +23,18 @@ #endif #include -#include +#if (((THUNDER_VERSION_MAJOR >= 4) && (THUNDER_VERSION_MINOR == 4)) || ((THUNDER_VERSION >= 4) && !defined(THUNDER_VERSION_MINOR))) +#include +#else +#include +#endif + +#define URI_ENV "PERSISTENTSTORE_URI" +#define PATH_ENV "PERSISTENTSTORE_PATH" +#define MAXSIZE_ENV "PERSISTENTSTORE_MAXSIZE" +#define MAXVALUE_ENV "PERSISTENTSTORE_MAXVALUE" +#define LIMIT_ENV "PERSISTENTSTORE_LIMIT" +#define IARM_INIT_NAME "Thunder_Plugins" #undef EXTERNAL #define EXTERNAL diff --git a/PersistentStore/PersistentStore.conf.in b/PersistentStore/PersistentStore.conf.in index 2fc15a6e4f..3bdad23e1f 100644 --- a/PersistentStore/PersistentStore.conf.in +++ b/PersistentStore/PersistentStore.conf.in @@ -1,11 +1,17 @@ precondition = ["Platform"] callsign = "org.rdk.PersistentStore" -autostart = "true" startuporder = "@PLUGIN_PERSISTENTSTORE_STARTUPORDER@" configuration = JSON() +rootobject = JSON() +rootobject.add("mode", "@PLUGIN_PERSISTENTSTORE_MODE@") +configuration.add("root", rootobject) + +configuration.add("uri", "@PLUGIN_PERSISTENTSTORE_URI@") configuration.add("path", "@PLUGIN_PERSISTENTSTORE_PATH@") +configuration.add("legacypath", "@PLUGIN_PERSISTENTSTORE_LEGACYPATH@") configuration.add("key", "@PLUGIN_PERSISTENTSTORE_KEY@") -configuration.add("maxsize", 1000000) -configuration.add("maxvalue", 3000) +configuration.add("maxsize", "@PLUGIN_PERSISTENTSTORE_MAXSIZE@") +configuration.add("maxvalue", "@PLUGIN_PERSISTENTSTORE_MAXVALUE@") +configuration.add("limit", "@PLUGIN_PERSISTENTSTORE_LIMIT@") diff --git a/PersistentStore/PersistentStore.config b/PersistentStore/PersistentStore.config index 77651be239..d76b03859e 100644 --- a/PersistentStore/PersistentStore.config +++ b/PersistentStore/PersistentStore.config @@ -7,9 +7,19 @@ set (startuporder ${PLUGIN_PERSISTENTSTORE_STARTUPORDER}) endif() map() + kv(mode ${PLUGIN_PERSISTENTSTORE_MODE}) +end() +ans(rootobject) + +map() + kv(uri ${PLUGIN_PERSISTENTSTORE_URI}) kv(path ${PLUGIN_PERSISTENTSTORE_PATH}) + kv(legacypath ${PLUGIN_PERSISTENTSTORE_LEGACYPATH}) kv(key ${PLUGIN_PERSISTENTSTORE_KEY}) - kv(maxsize 1000000) - kv(maxvalue 1000) + kv(maxsize ${PLUGIN_PERSISTENTSTORE_MAXSIZE}) + kv(maxvalue ${PLUGIN_PERSISTENTSTORE_MAXVALUE}) + kv(limit ${PLUGIN_PERSISTENTSTORE_LIMIT}) end() ans(configuration) + +map_append(${configuration} root ${rootobject}) diff --git a/PersistentStore/PersistentStore.cpp b/PersistentStore/PersistentStore.cpp index c3b9c86a95..7062180194 100644 --- a/PersistentStore/PersistentStore.cpp +++ b/PersistentStore/PersistentStore.cpp @@ -18,14 +18,11 @@ */ #include "PersistentStore.h" - -#include "SqliteStore.h" - -#include "UtilsFile.h" +#include #define API_VERSION_NUMBER_MAJOR 1 #define API_VERSION_NUMBER_MINOR 0 -#define API_VERSION_NUMBER_PATCH 5 +#define API_VERSION_NUMBER_PATCH 6 namespace WPEFramework { @@ -39,92 +36,100 @@ namespace { // Terminations {}, // Controls - {} - ); + {}); } namespace Plugin { -SERVICE_REGISTRATION(PersistentStore, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); - -PersistentStore::PersistentStore() - : PluginHost::JSONRPC(), - _store(Core::Service::Create()), - _storeCache(static_cast(_store)), - _storeListing(static_cast(_store)), - _storeSink(this) -{ - RegisterAll(); -} - -PersistentStore::~PersistentStore() -{ - UnregisterAll(); - - _store->Release(); -} + SERVICE_REGISTRATION(PersistentStore, API_VERSION_NUMBER_MAJOR, API_VERSION_NUMBER_MINOR, API_VERSION_NUMBER_PATCH); -const string PersistentStore::Initialize(PluginHost::IShell *service) -{ - string result; + const string PersistentStore::Initialize(PluginHost::IShell* service) + { + string result; - ASSERT(service != nullptr); + ASSERT(service != nullptr); - string configLine = service->ConfigLine(); - _config.FromString(configLine); + auto configLine = service->ConfigLine(); + _config.FromString(configLine); - ASSERT(!_config.Path.Value().empty()); + { + Core::File file(_config.Path.Value()); + Core::File oldFile(_config.LegacyPath.Value()); + if (file.Name() != oldFile.Name()) { + if (oldFile.Exists()) { + if (!file.Exists()) { + std::ifstream in(oldFile.Name(), std::ios::in | std::ios::binary); + std::ofstream out(file.Name(), std::ios::out | std::ios::binary); + out << in.rdbuf(); + } - Core::File file(_config.Path.Value()); + oldFile.Destroy(); + } + } + } - Core::Directory(file.PathName().c_str()).CreatePath(); + Core::SystemInfo::SetEnvironment(URI_ENV, _config.Uri.Value()); + Core::SystemInfo::SetEnvironment(PATH_ENV, _config.Path.Value()); + Core::SystemInfo::SetEnvironment(MAXSIZE_ENV, std::to_string(_config.MaxSize.Value())); + Core::SystemInfo::SetEnvironment(MAXVALUE_ENV, std::to_string(_config.MaxValue.Value())); + Core::SystemInfo::SetEnvironment(LIMIT_ENV, std::to_string(_config.Limit.Value())); - if (!file.Exists()) { - for (auto i : LegacyLocations()) { - Core::File from(i); + uint32_t connectionId; - if (from.Exists()) { - if (!Utils::MoveFile(from.Name(), file.Name())) { - result = "move failed"; - } - break; - } + Store2::ScopeMapType initList1; + auto deviceStore2 = service->Root(connectionId, 2000, _T("SqliteStore2")); + if (deviceStore2 != nullptr) { + initList1.emplace(Exchange::IStore2::ScopeType::DEVICE, deviceStore2); + } + auto accountStore2 = service->Root(connectionId, 2000, _T("GrpcStore2")); + if (accountStore2 != nullptr) { + initList1.emplace(Exchange::IStore2::ScopeType::ACCOUNT, accountStore2); + } + _store2 = Core::Service::Create(initList1); + if (deviceStore2 != nullptr) { + deviceStore2->Release(); + } + if (accountStore2 != nullptr) { + accountStore2->Release(); } + _store2->Register(&_store2Sink); + _store = Core::Service::Create(_store2); + _storeCache = service->Root(connectionId, 2000, _T("SqliteStoreCache")); + _storeInspector = service->Root(connectionId, 2000, _T("SqliteStoreInspector")); + _storeLimit = service->Root(connectionId, 2000, _T("SqliteStoreLimit")); + + return result; } - if (result.empty()) { - if (static_cast(_store)->Open( - _config.Path.Value(), - _config.Key.Value(), - _config.MaxSize.Value(), - _config.MaxValue.Value()) != Core::ERROR_NONE) { - result = "init failed"; + void PersistentStore::Deinitialize(PluginHost::IShell* /* service */) + { + if (_store != nullptr) { + _store->Release(); + _store = nullptr; + } + if (_store2 != nullptr) { + _store2->Unregister(&_store2Sink); + _store2->Release(); + _store2 = nullptr; + } + if (_storeCache != nullptr) { + _storeCache->Release(); + _storeCache = nullptr; + } + if (_storeInspector != nullptr) { + _storeInspector->Release(); + _storeInspector = nullptr; + } + if (_storeLimit != nullptr) { + _storeLimit->Release(); + _storeLimit = nullptr; } } - if (result.empty()) { - _storeSink.Initialize(_store); + string PersistentStore::Information() const + { + return (string()); } - return result; -} - -void PersistentStore::Deinitialize(PluginHost::IShell * /* service */) -{ - static_cast(_store)->Term(); - - _storeSink.Deinitialize(); -} - -string PersistentStore::Information() const -{ - return (string()); -} - -std::vector PersistentStore::LegacyLocations() const -{ - return {"/opt/persistent/rdkservicestore"}; -} - } // namespace Plugin } // namespace WPEFramework diff --git a/PersistentStore/PersistentStore.h b/PersistentStore/PersistentStore.h index c59533e62b..41b84512ce 100644 --- a/PersistentStore/PersistentStore.h +++ b/PersistentStore/PersistentStore.h @@ -21,150 +21,333 @@ #include "Module.h" -#include "IStoreListing.h" - #include +#include #include +#include namespace WPEFramework { namespace Plugin { -class PersistentStore: public PluginHost::IPlugin, public PluginHost::JSONRPC -{ -private: - class Config: public Core::JSON::Container - { + class PersistentStore : public PluginHost::IPlugin, public PluginHost::JSONRPC { private: - Config(const Config &) = delete; - Config &operator=(const Config &) = delete; + class Config : public Core::JSON::Container { + private: + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; - public: - Config() - : Core::JSON::Container(), - Path(), - Key(), - MaxSize(0), - MaxValue(0) - { - Add(_T("path"), &Path); - Add(_T("key"), &Key); - Add(_T("maxsize"), &MaxSize); - Add(_T("maxvalue"), &MaxValue); - } + public: + Config() + : Core::JSON::Container() + , MaxSize(0) + , MaxValue(0) + , Limit(0) + { + Add(_T("uri"), &Uri); + Add(_T("path"), &Path); + Add(_T("legacypath"), &LegacyPath); + Add(_T("key"), &Key); + Add(_T("maxsize"), &MaxSize); + Add(_T("maxvalue"), &MaxValue); + Add(_T("limit"), &Limit); + } - public: - Core::JSON::String Path; - Core::JSON::String Key; - Core::JSON::DecUInt64 MaxSize; - Core::JSON::DecUInt64 MaxValue; - }; + public: + Core::JSON::String Uri; + Core::JSON::String Path; + Core::JSON::String LegacyPath; + Core::JSON::String Key; + Core::JSON::DecUInt64 MaxSize; + Core::JSON::DecUInt64 MaxValue; + Core::JSON::DecUInt64 Limit; + }; - class StoreNotification: protected Exchange::IStore::INotification - { - private: - StoreNotification(const StoreNotification &) = delete; - StoreNotification &operator=(const StoreNotification &) = delete; + class Store2Notification : public Exchange::IStore2::INotification { + private: + Store2Notification(const Store2Notification&) = delete; + Store2Notification& operator=(const Store2Notification&) = delete; - public: - explicit StoreNotification(PersistentStore *parent) - : _parent(*parent), - _client(nullptr) - { - ASSERT(parent != nullptr); - } - ~StoreNotification() = default; + public: + explicit Store2Notification(PersistentStore* parent) + : _parent(*parent) + { + } + ~Store2Notification() override = default; - public: - void Initialize(Exchange::IStore *client) - { - ASSERT(_client == nullptr); - ASSERT(client != nullptr); + public: + // IStore2::INotification methods - _client = client; - _client->AddRef(); - _client->Register(this); - } - void Deinitialize() - { - ASSERT(_client != nullptr); + void ValueChanged(const Exchange::IStore2::ScopeType scope, const string& ns, const string& key, const string& value) override + { + JsonData::PersistentStore::SetValueParamsData params; + params.Scope = JsonData::PersistentStore::ScopeType(scope); + params.Namespace = ns; + params.Key = key; + params.Value = value; - if (_client != nullptr) { - _client->Unregister(this); - _client->Release(); - _client = nullptr; + _parent.event_onValueChanged(params); } - } - public: - // IStore::INotification methods + BEGIN_INTERFACE_MAP(Store2Notification) + INTERFACE_ENTRY(Exchange::IStore2::INotification) + END_INTERFACE_MAP + + private: + PersistentStore& _parent; + }; + + // Deprecated + class Store : public Exchange::IStore { + private: + Store(const Store&) = delete; + Store& operator=(const Store&) = delete; + + private: + class Store2Notification : public Exchange::IStore2::INotification { + private: + Store2Notification(const Store2Notification&) = delete; + Store2Notification& operator=(const Store2Notification&) = delete; + + public: + explicit Store2Notification(Store* parent) + : _parent(*parent) + { + } + ~Store2Notification() override = default; + + public: + // IStore2::INotification methods + + void ValueChanged(const Exchange::IStore2::ScopeType, const string& ns, const string& key, const string& value) override + { + Core::SafeSyncType lock(_parent._clientLock); + + std::list::iterator + index(_parent._clients.begin()); + + while (index != _parent._clients.end()) { + (*index)->ValueChanged(ns, key, value); + index++; + } + } + + BEGIN_INTERFACE_MAP(Store2Notification) + INTERFACE_ENTRY(Exchange::IStore2::INotification) + END_INTERFACE_MAP + + private: + Store& _parent; + }; + + public: + Store(Exchange::IStore2* store2) + : _store2(store2) + , _store2Sink(this) + { + ASSERT(_store2 != nullptr); + _store2->AddRef(); + _store2->Register(&_store2Sink); + } + ~Store() override + { + _store2->Unregister(&_store2Sink); + _store2->Release(); + } + + public: + // IStore methods + + uint32_t Register(Exchange::IStore::INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + ASSERT(std::find(_clients.begin(), _clients.end(), notification) == _clients.end()); + + notification->AddRef(); + _clients.push_back(notification); + + return Core::ERROR_NONE; + } + uint32_t Unregister(Exchange::IStore::INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + std::list::iterator + index(std::find(_clients.begin(), _clients.end(), notification)); - virtual void ValueChanged(const string &ns, const string &key, const string &value) override + ASSERT(index != _clients.end()); + + if (index != _clients.end()) { + notification->Release(); + _clients.erase(index); + } + + return Core::ERROR_NONE; + } + uint32_t SetValue(const string& ns, const string& key, const string& value) override + { + return _store2->SetValue(Exchange::IStore2::ScopeType::DEVICE, ns, key, value, 0); + } + uint32_t GetValue(const string& ns, const string& key, string& value) override + { + uint32_t ttl; + return _store2->GetValue(Exchange::IStore2::ScopeType::DEVICE, ns, key, value, ttl); + } + uint32_t DeleteKey(const string& ns, const string& key) override + { + return _store2->DeleteKey(Exchange::IStore2::ScopeType::DEVICE, ns, key); + } + uint32_t DeleteNamespace(const string& ns) override + { + return _store2->DeleteNamespace(Exchange::IStore2::ScopeType::DEVICE, ns); + } + + BEGIN_INTERFACE_MAP(Store) + INTERFACE_ENTRY(Exchange::IStore) + END_INTERFACE_MAP + + private: + Exchange::IStore2* _store2; + std::list _clients; + Core::CriticalSection _clientLock; + Core::Sink _store2Sink; + }; + + class Store2 : public Exchange::IStore2 { + private: + Store2(const Store2&) = delete; + Store2& operator=(const Store2&) = delete; + + public: + typedef std::map ScopeMapType; + + Store2(const ScopeMapType& map) + : _scopeMap(map) + { + for (auto const& x : _scopeMap) { + x.second->AddRef(); + } + } + ~Store2() override + { + for (auto const& x : _scopeMap) { + x.second->Release(); + } + } + + public: + // IStore2 methods + + uint32_t Register(Exchange::IStore2::INotification* notification) override + { + for (auto const& x : _scopeMap) { + x.second->Register(notification); + } + return Core::ERROR_NONE; + } + uint32_t Unregister(Exchange::IStore2::INotification* notification) override + { + for (auto const& x : _scopeMap) { + x.second->Unregister(notification); + } + return Core::ERROR_NONE; + } + uint32_t SetValue(const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) override + { + return _scopeMap.at(scope)->SetValue(scope, ns, key, value, ttl); + } + uint32_t GetValue(const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) override + { + return _scopeMap.at(scope)->GetValue(scope, ns, key, value, ttl); + } + uint32_t DeleteKey(const ScopeType scope, const string& ns, const string& key) override + { + return _scopeMap.at(scope)->DeleteKey(scope, ns, key); + } + uint32_t DeleteNamespace(const ScopeType scope, const string& ns) override + { + return _scopeMap.at(scope)->DeleteNamespace(scope, ns); + } + + BEGIN_INTERFACE_MAP(Store2) + INTERFACE_ENTRY(Exchange::IStore2) + END_INTERFACE_MAP + + private: + ScopeMapType _scopeMap; + }; + + private: + PersistentStore(const PersistentStore&) = delete; + PersistentStore& operator=(const PersistentStore&) = delete; + + public: + PersistentStore() + : PluginHost::JSONRPC() + , _store(nullptr) + , _store2(nullptr) + , _storeCache(nullptr) + , _storeInspector(nullptr) + , _storeLimit(nullptr) + , _store2Sink(this) { - _parent.event_onValueChanged(ns, key, value); + RegisterAll(); } - virtual void StorageExceeded() override + ~PersistentStore() override { - _parent.event_onStorageExceeded(); + UnregisterAll(); } - BEGIN_INTERFACE_MAP(StoreNotification) - INTERFACE_ENTRY(Exchange::IStore::INotification) + // Build QueryInterface implementation, specifying all possible interfaces to be returned. + BEGIN_INTERFACE_MAP(PersistentStore) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + INTERFACE_AGGREGATE(Exchange::IStore, _store) + INTERFACE_AGGREGATE(Exchange::IStore2, _store2) + INTERFACE_AGGREGATE(Exchange::IStoreCache, _storeCache) + INTERFACE_AGGREGATE(Exchange::IStoreInspector, _storeInspector) + INTERFACE_AGGREGATE(Exchange::IStoreLimit, _storeLimit) END_INTERFACE_MAP + public: + // IPlugin methods + // ------------------------------------------------------------------------------------------------------- + const string Initialize(PluginHost::IShell* service) override; + void Deinitialize(PluginHost::IShell* service) override; + string Information() const override; + private: - PersistentStore &_parent; - Exchange::IStore *_client; - }; + // JSON RPC + + void RegisterAll(); + void UnregisterAll(); -private: - PersistentStore(const PersistentStore &) = delete; - PersistentStore &operator=(const PersistentStore &) = delete; - -public: - PersistentStore(); - virtual ~PersistentStore(); - - // Build QueryInterface implementation, specifying all possible interfaces to be returned. - BEGIN_INTERFACE_MAP(PersistentStore) - INTERFACE_ENTRY(PluginHost::IPlugin) - INTERFACE_ENTRY(PluginHost::IDispatcher) - INTERFACE_AGGREGATE(Exchange::IStore, _store) - INTERFACE_AGGREGATE(Exchange::IStoreCache, _storeCache) - END_INTERFACE_MAP - -public: - // IPlugin methods - // ------------------------------------------------------------------------------------------------------- - virtual const string Initialize(PluginHost::IShell *service) override; - virtual void Deinitialize(PluginHost::IShell *service) override; - virtual string Information() const override; - -protected: - void RegisterAll(); - void UnregisterAll(); - - uint32_t endpoint_setValue(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_getValue(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_deleteKey(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_deleteNamespace(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_getKeys(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_getNamespaces(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_getStorageSize(const JsonObject ¶meters, JsonObject &response); - uint32_t endpoint_flushCache(const JsonObject ¶meters, JsonObject &response); - - virtual void event_onValueChanged(const string &ns, const string &key, const string &value); - virtual void event_onStorageExceeded(); - -protected: - virtual std::vector LegacyLocations() const; - -private: - Config _config; - Exchange::IStore *_store; - Exchange::IStoreCache *_storeCache; - IStoreListing *_storeListing; - Core::Sink _storeSink; -}; + uint32_t endpoint_setValue(const JsonData::PersistentStore::SetValueParamsData& params, JsonData::PersistentStore::DeleteKeyResultInfo& response); + uint32_t endpoint_getValue(const JsonData::PersistentStore::DeleteKeyParamsInfo& params, JsonData::PersistentStore::GetValueResultData& response); + uint32_t endpoint_deleteKey(const JsonData::PersistentStore::DeleteKeyParamsInfo& params, JsonData::PersistentStore::DeleteKeyResultInfo& response); + uint32_t endpoint_deleteNamespace(const JsonData::PersistentStore::DeleteNamespaceParamsInfo& params, JsonData::PersistentStore::DeleteKeyResultInfo& response); + uint32_t endpoint_getKeys(const JsonData::PersistentStore::DeleteNamespaceParamsInfo& params, JsonData::PersistentStore::GetKeysResultData& response); + uint32_t endpoint_getNamespaces(const JsonData::PersistentStore::GetNamespacesParamsInfo& params, JsonData::PersistentStore::GetNamespacesResultData& response); + uint32_t endpoint_getStorageSize(const JsonData::PersistentStore::GetNamespacesParamsInfo& params, JsonObject& response); // Deprecated + uint32_t endpoint_getStorageSizes(const JsonData::PersistentStore::GetNamespacesParamsInfo& params, JsonData::PersistentStore::GetStorageSizesResultData& response); + uint32_t endpoint_flushCache(JsonData::PersistentStore::DeleteKeyResultInfo& response); + uint32_t endpoint_getNamespaceStorageLimit(const JsonData::PersistentStore::DeleteNamespaceParamsInfo& params, JsonData::PersistentStore::GetNamespaceStorageLimitResultData& response); + uint32_t endpoint_setNamespaceStorageLimit(const JsonData::PersistentStore::SetNamespaceStorageLimitParamsData& params); + + void event_onValueChanged(const JsonData::PersistentStore::SetValueParamsData& params) + { + Notify(_T("onValueChanged"), params); + } + + private: + Config _config; + Exchange::IStore* _store; // Deprecated + Exchange::IStore2* _store2; + Exchange::IStoreCache* _storeCache; + Exchange::IStoreInspector* _storeInspector; + Exchange::IStoreLimit* _storeLimit; + Core::Sink _store2Sink; + }; } // namespace Plugin } // namespace WPEFramework diff --git a/PersistentStore/PersistentStore.json b/PersistentStore/PersistentStore.json index 8bcb9a272f..da77bf50a2 100644 --- a/PersistentStore/PersistentStore.json +++ b/PersistentStore/PersistentStore.json @@ -1,251 +1,480 @@ { - "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/interface.schema.json", - "jsonrpc": "2.0", - "info": { - "title": "PertsistentStore API", - "class": "PersistentStore", - "description": "The `PersistentStore` plugin allows you to persist key/value pairs by namespace" + "$schema": "https://raw.githubusercontent.com/rdkcentral/rdkservices/main/Tools/json_generator/schemas/interface.schema.json", + "jsonrpc": "2.0", + "info": { + "title": "PertsistentStore API", + "class": "PersistentStore", + "description": "Persistent Store JSON-RPC interface" + }, + "common": { + "$ref": "../common/common.json" + }, + "definitions": { + "namespace": { + "summary": "Namespace", + "type": "string", + "example": "ns1" }, - "common": { - "$ref": "../common/common.json" + "key": { + "summary": "Key", + "type": "string", + "example": "key1" }, - "definitions": { - "namespace": { - "summary": "A namespace in the datastore as a valid UTF-8 string", - "type": "string", - "example": "ns1" - }, - "key": { - "summary": "The key name as a valid UTF-8 string", - "type": "string", - "example": "key1" - }, - "value": { - "summary": "The key value. Values are capped at 1000 characters in size.", - "type": "string", - "example": "value1" + "value": { + "summary": "Value", + "type": "string", + "example": "value1" + }, + "scope": { + "summary": "Scope", + "type": "string", + "enum": [ + "device", + "account" + ], + "default": "device", + "example": "device" + }, + "size": { + "summary": "Size in bytes", + "type": "number", + "example": 100 + }, + "ttl": { + "summary": "Time in seconds", + "type": "number", + "example": 100 + }, + "success": { + "summary": "Legacy parameter (always true)", + "type": "boolean", + "default": true, + "example": true + } + }, + "methods": { + "deleteKey": { + "summary": "Deletes a key from the specified namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "key": { + "$ref": "#/definitions/key" + }, + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [ + "namespace", + "key" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" } + ] }, - "methods": { - "deleteKey":{ - "summary": "Deletes a key from the specified namespace.", - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - }, - "key": { - "$ref": "#/definitions/key" - } - }, - "required": [ - "namespace", - "key" - ] - }, - "result": { - "$ref": "#/common/result" - } + "deleteNamespace": { + "summary": "Deletes the specified namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "scope": { + "$ref": "#/definitions/scope" + } }, - "deleteNamespace":{ - "summary": "Deletes the specified namespace.", - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - } - }, - "required": [ - "namespace" - ] - }, - "result": { - "$ref": "#/common/result" - } - }, - "flushCache":{ - "summary": "Flushes the database cache by invoking `flush` in SQLite.", - "result": { - "$ref": "#/common/result" - } + "required": [ + "namespace" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } }, - "getKeys":{ - "summary": "Returns the keys that are stored in the specified namespace.", - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - } - }, - "required": [ - "namespace" - ] - }, - "result": { - "type": "object", - "properties": { - "keys": { - "summary": "A list of keys", - "type": "array", - "items": { - "type": "string", - "example": "key1" - } - }, - "success":{ - "$ref": "#/common/success" - } - }, - "required": [ - "keys", - "success" - ] - } + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "flushCache": { + "summary": "Flushes the device cache", + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } }, - "getNamespaces":{ - "summary": "Returns the namespaces in the datastore.", - "result": { - "type": "object", - "properties": { - "namespaces": { - "summary": "A list of namespaces", - "type": "array", - "items": { - "type": "string", - "example": "ns1" - } - }, - "success":{ - "$ref": "#/common/success" - } - }, - "required": [ - "namespaces", - "success" - ] - } + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "getKeys": { + "summary": "Returns the keys that are stored in the specified namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "scope": { + "$ref": "#/definitions/scope" + } }, - "getStorageSize":{ - "summary": "Returns the size occupied by each namespace. This is a processing-intense operation. The total size of the datastore should not exceed more than 1MB in size. If the storage size is exceeded then, new values are not stored and the `onStorageExceeded` event is sent.", - "result": { - "type": "object", - "properties": { - "namespaceSizes": { - "summary": "The namespaces and their respective size", - "type": "object", - "properties": { - "ns1": { - "type": "integer", - "example": 534 - }, - "ns2": { - "type": "integer", - "example": 234 - } - }, - "required": [] - }, - "success":{ - "$ref": "#/common/success" - } - }, - "required": [ - "namespaceSizes", - "success" - ] + "required": [ + "namespace" + ] + }, + "result": { + "type": "object", + "properties": { + "keys": { + "type": "array", + "items": { + "$ref": "#/definitions/key" } + }, + "success": { + "$ref": "#/definitions/success" + } }, - "getValue":{ - "summary": "Returns the value of a key from the specified namespace.", - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - }, - "key": { - "$ref": "#/definitions/key" - } - }, - "required": [ - "namespace", - "key" - ] - }, - "result": { - "type": "object", - "properties": { - "value": { - "$ref": "#/definitions/value" - }, - "success":{ - "$ref": "#/common/success" - } - }, - "required": [ - "value", - "success" - ] - } + "required": [ + "keys", + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "getNamespaces": { + "summary": "Returns the namespaces", + "params": { + "type": "object", + "properties": { + "scope": { + "$ref": "#/definitions/scope" + } }, - "setValue": { - "summary": "Sets the value of a key in the the specified namespace.", - "events": { - "onStorageExceeded" : "Triggered if the storage size has surpassed 1 MB storage size", - "onValueChanged" : "Triggered whenever any of the values stored are changed using setValue" - }, - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - }, - "key": { - "$ref": "#/definitions/key" - }, - "value": { - "$ref": "#/definitions/value" - } - }, - "required": [ - "namespace", - "key", - "value" - ] - }, - "result": { - "$ref": "#/common/result" + "required": [] + }, + "result": { + "type": "object", + "properties": { + "namespaces": { + "type": "array", + "items": { + "$ref": "#/definitions/namespace" } + }, + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "namespaces", + "success" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" } + ] }, - "events": { - "onStorageExceeded":{ - "summary": "Triggered when the storage size has surpassed the storage capacity. The total size of the datastore should not exceed more than 1MB in size." - }, - "onValueChanged": { - "summary": "Triggered whenever any of the values stored are changed using setValue.", - "params": { - "type": "object", - "properties": { - "namespace": { - "$ref": "#/definitions/namespace" - }, - "key": { - "$ref": "#/definitions/key" - }, - "value": { - "$ref": "#/definitions/value" - } + "getStorageSizes": { + "summary": "Returns the size occupied by each namespace", + "params": { + "type": "object", + "properties": { + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [] + }, + "result": { + "type": "object", + "properties": { + "storageList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" }, - "required": [ - "namespace", - "key", - "value" - ] + "size": { + "$ref": "#/definitions/size" + } + }, + "required": [ + "namespace", + "size" + ] } + } + }, + "required": [ + "storageList" + ] + }, + "errors": [ + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "getValue": { + "summary": "Returns the value of a key from the specified namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "key": { + "$ref": "#/definitions/key" + }, + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [ + "namespace", + "key" + ] + }, + "result": { + "type": "object", + "properties": { + "value": { + "$ref": "#/definitions/value" + }, + "success": { + "$ref": "#/definitions/success" + }, + "ttl": { + "$ref": "#/definitions/ttl" + } + }, + "required": [ + "value", + "success" + ] + }, + "errors": [ + { + "description": "Time is not synced", + "$ref": "#/common/errors/pendingconditions" + }, + { + "description": "Unknown namespace", + "$ref": "#/common/errors/notexist" + }, + { + "description": "Unknown key", + "$ref": "#/common/errors/unknownkey" + }, + { + "description": "Unknown error", + "$ref": "#/common/errors/general" } + ] + }, + "setValue": { + "summary": "Sets the value of a key in the the specified namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "key": { + "$ref": "#/definitions/key" + }, + "value": { + "$ref": "#/definitions/value" + }, + "scope": { + "$ref": "#/definitions/scope" + }, + "ttl": { + "$ref": "#/definitions/ttl" + } + }, + "required": [ + "namespace", + "key", + "value" + ] + }, + "result": { + "type": "object", + "properties": { + "success": { + "$ref": "#/definitions/success" + } + }, + "required": [ + "success" + ] + }, + "errors": [ + { + "description": "Time is not synced", + "$ref": "#/common/errors/pendingconditions" + }, + { + "description": "Empty/too large namespace or key, or the storage doesn't have enough space", + "$ref": "#/common/errors/invalidinputlength" + }, + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "setNamespaceStorageLimit": { + "summary": "Sets the storage limit for a given namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "storageLimit": { + "$ref": "#/definitions/size" + }, + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [ + "namespace", + "storageLimit" + ] + }, + "result": { + "$ref": "#/common/results/void" + }, + "errors": [ + { + "description": "Empty/too large namespace, or the storage doesn't have enough space", + "$ref": "#/common/errors/invalidinputlength" + }, + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + }, + "getNamespaceStorageLimit": { + "summary": "Returns the storage limit for a given namespace", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [ + "namespace" + ] + }, + "result": { + "type": "object", + "properties": { + "storageLimit": { + "$ref": "#/definitions/size" + } + }, + "required": [ + "storageLimit" + ] + }, + "errors": [ + { + "description": "Unknown namespace", + "$ref": "#/common/errors/notexist" + }, + { + "description": "Unknown error", + "$ref": "#/common/errors/general" + } + ] + } + }, + "events": { + "onValueChanged": { + "summary": "Triggered whenever any of the values stored are changed using setValue", + "params": { + "type": "object", + "properties": { + "namespace": { + "$ref": "#/definitions/namespace" + }, + "key": { + "$ref": "#/definitions/key" + }, + "value": { + "$ref": "#/definitions/value" + }, + "scope": { + "$ref": "#/definitions/scope" + } + }, + "required": [ + "namespace", + "key", + "value", + "scope" + ] + } } + } } \ No newline at end of file diff --git a/PersistentStore/PersistentStoreJsonRpc.cpp b/PersistentStore/PersistentStoreJsonRpc.cpp index 409cb20e3a..faa89dd147 100644 --- a/PersistentStore/PersistentStoreJsonRpc.cpp +++ b/PersistentStore/PersistentStoreJsonRpc.cpp @@ -19,226 +19,210 @@ #include "PersistentStore.h" -#include "UtilsJsonRpc.h" - namespace WPEFramework { namespace Plugin { -void PersistentStore::RegisterAll() -{ - Register(_T("setValue"), &PersistentStore::endpoint_setValue, this); - Register(_T("getValue"), &PersistentStore::endpoint_getValue, this); - Register(_T("deleteKey"), &PersistentStore::endpoint_deleteKey, this); - Register(_T("deleteNamespace"), &PersistentStore::endpoint_deleteNamespace, this); - Register(_T("getKeys"), &PersistentStore::endpoint_getKeys, this); - Register(_T("getNamespaces"), &PersistentStore::endpoint_getNamespaces, this); - Register(_T("getStorageSize"), &PersistentStore::endpoint_getStorageSize, this); - Register(_T("flushCache"), &PersistentStore::endpoint_flushCache, this); -} - -void PersistentStore::UnregisterAll() -{ - Unregister(_T("setValue")); - Unregister(_T("getValue")); - Unregister(_T("deleteKey")); - Unregister(_T("deleteNamespace")); - Unregister(_T("getKeys")); - Unregister(_T("getNamespaces")); - Unregister(_T("getStorageSize")); - Unregister(_T("flushCache")); -} - -uint32_t PersistentStore::endpoint_setValue(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); - - bool success = false; - - if (!parameters.HasLabel("namespace") || - !parameters.HasLabel("key") || - !parameters.HasLabel("value")) { - response["error"] = "params missing"; + using namespace JsonData::PersistentStore; + + void PersistentStore::RegisterAll() + { + Register(_T("setValue"), &PersistentStore::endpoint_setValue, this); + Register(_T("getValue"), &PersistentStore::endpoint_getValue, this); + Register(_T("deleteKey"), &PersistentStore::endpoint_deleteKey, this); + Register(_T("deleteNamespace"), &PersistentStore::endpoint_deleteNamespace, this); + Register(_T("getKeys"), &PersistentStore::endpoint_getKeys, this); + Register(_T("getNamespaces"), &PersistentStore::endpoint_getNamespaces, this); + Register(_T("getStorageSize"), &PersistentStore::endpoint_getStorageSize, this); // Deprecated + Register(_T("getStorageSizes"), &PersistentStore::endpoint_getStorageSizes, this); + Register(_T("flushCache"), &PersistentStore::endpoint_flushCache, this); + Register(_T("getNamespaceStorageLimit"), &PersistentStore::endpoint_getNamespaceStorageLimit, this); + Register(_T("setNamespaceStorageLimit"), &PersistentStore::endpoint_setNamespaceStorageLimit, this); } - else { - string ns = parameters["namespace"].String(); - string key = parameters["key"].String(); - string value = parameters["value"].String(); - if (ns.empty() || key.empty()) { - response["error"] = "params empty"; - } - else { - auto status = _store->SetValue(ns, key, value); - if (status == Core::ERROR_INVALID_INPUT_LENGTH) { - response["error"] = "params too long"; - } - success = (status == Core::ERROR_NONE); - } + void PersistentStore::UnregisterAll() + { + Unregister(_T("setValue")); + Unregister(_T("getValue")); + Unregister(_T("deleteKey")); + Unregister(_T("deleteNamespace")); + Unregister(_T("getKeys")); + Unregister(_T("getNamespaces")); + Unregister(_T("getStorageSize")); + Unregister(_T("getStorageSizes")); + Unregister(_T("flushCache")); + Unregister(_T("getNamespaceStorageLimit")); + Unregister(_T("setNamespaceStorageLimit")); } - returnResponse(success); -} - -uint32_t PersistentStore::endpoint_getValue(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); - - bool success = false; - - if (!parameters.HasLabel("namespace") || - !parameters.HasLabel("key")) { - response["error"] = "params missing"; - } - else { - string ns = parameters["namespace"].String(); - string key = parameters["key"].String(); - if (ns.empty() || key.empty()) { - response["error"] = "params empty"; - } - else { - string value; - success = (_store->GetValue(ns, key, value) == Core::ERROR_NONE); - if (success) - response["value"] = value; + uint32_t PersistentStore::endpoint_setValue(const SetValueParamsData& params, DeleteKeyResultInfo& response) + { + auto result = _store2->SetValue( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + params.Key.Value(), + params.Value.Value(), + params.Ttl.Value()); + if (result == Core::ERROR_NONE) { + response.Success = true; } - } - - returnResponse(success); -} -uint32_t PersistentStore::endpoint_deleteKey(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); - - bool success = false; - - if (!parameters.HasLabel("namespace") || - !parameters.HasLabel("key")) { - response["error"] = "params missing"; - } - else { - string ns = parameters["namespace"].String(); - string key = parameters["key"].String(); - if (ns.empty() || key.empty()) { - response["error"] = "params empty"; - } - else { - success = (_store->DeleteKey(ns, key) == Core::ERROR_NONE); - } + return result; } - returnResponse(success); -} - -uint32_t PersistentStore::endpoint_deleteNamespace(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); - - bool success = false; - - if (!parameters.HasLabel("namespace")) { - response["error"] = "params missing"; - } - else { - string ns = parameters["namespace"].String(); - if (ns.empty()) { - response["error"] = "params empty"; - } - else { - success = (_store->DeleteNamespace(ns) == Core::ERROR_NONE); + uint32_t PersistentStore::endpoint_getValue(const DeleteKeyParamsInfo& params, GetValueResultData& response) + { + string value; + uint32_t ttl; + auto result = _store2->GetValue( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + params.Key.Value(), + value, + ttl); + if (result == Core::ERROR_NONE) { + response.Value = value; + if (ttl > 0) { + response.Ttl = ttl; + } + response.Success = true; } + + return result; } - returnResponse(success); -} + uint32_t PersistentStore::endpoint_deleteKey(const DeleteKeyParamsInfo& params, DeleteKeyResultInfo& response) + { + auto result = _store2->DeleteKey( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + params.Key.Value()); + if (result == Core::ERROR_NONE) { + response.Success = true; + } -uint32_t PersistentStore::endpoint_getKeys(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); + return result; + } - bool success = false; + uint32_t PersistentStore::endpoint_deleteNamespace(const DeleteNamespaceParamsInfo& params, DeleteKeyResultInfo& response) + { + auto result = _store2->DeleteNamespace( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value()); + if (result == Core::ERROR_NONE) { + response.Success = true; + } - if (!parameters.HasLabel("namespace")) { - response["error"] = "params missing"; + return result; } - else { - string ns = parameters["namespace"].String(); - if (ns.empty()) - response["error"] = "params empty"; - else { - std::vector keys; - success = (_storeListing->GetKeys(ns, keys) == Core::ERROR_NONE); - if (success) { - JsonArray jsonKeys; - for (auto it = keys.begin(); it != keys.end(); ++it) - jsonKeys.Add(*it); - response["keys"] = jsonKeys; + + uint32_t PersistentStore::endpoint_getKeys(const DeleteNamespaceParamsInfo& params, GetKeysResultData& response) + { + RPC::IStringIterator* it; + auto result = _storeInspector->GetKeys( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + it); + if (result == Core::ERROR_NONE) { + string element; + while (it->Next(element) == true) { + response.Keys.Add() = element; } + it->Release(); + response.Success = true; } - } - returnResponse(success); -} - -uint32_t PersistentStore::endpoint_getNamespaces(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); - - bool success = false; - - std::vector namespaces; - success = (_storeListing->GetNamespaces(namespaces) == Core::ERROR_NONE); - if (success) { - JsonArray jsonNamespaces; - for (auto it = namespaces.begin(); it != namespaces.end(); ++it) - jsonNamespaces.Add(*it); - response["namespaces"] = jsonNamespaces; + return result; } - returnResponse(success); -} + uint32_t PersistentStore::endpoint_getNamespaces(const GetNamespacesParamsInfo& params, GetNamespacesResultData& response) + { + RPC::IStringIterator* it; + auto result = _storeInspector->GetNamespaces( + Exchange::IStore2::ScopeType(params.Scope.Value()), + it); + if (result == Core::ERROR_NONE) { + string element; + while (it->Next(element) == true) { + response.Namespaces.Add() = element; + } + it->Release(); + response.Success = true; + } -uint32_t PersistentStore::endpoint_getStorageSize(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); + return result; + } - bool success = false; + // Deprecated + uint32_t PersistentStore::endpoint_getStorageSize(const GetNamespacesParamsInfo& params, JsonObject& response) + { + Exchange::IStoreInspector::INamespaceSizeIterator* it; + auto result = _storeInspector->GetStorageSizes( + Exchange::IStore2::ScopeType(params.Scope.Value()), + it); + if (result == Core::ERROR_NONE) { + JsonObject jsonObject; + Exchange::IStoreInspector::NamespaceSize element; + while (it->Next(element) == true) { + jsonObject[element.ns.c_str()] = element.size; + } + it->Release(); + response["namespaceSizes"] = jsonObject; + response["success"] = true; + } - std::map namespaceSizes; - success = (_storeListing->GetStorageSize(namespaceSizes) == Core::ERROR_NONE); - if (success) { - JsonObject jsonNamespaceSizes; - for (auto it = namespaceSizes.begin(); it != namespaceSizes.end(); ++it) - jsonNamespaceSizes[it->first.c_str()] = it->second; - response["namespaceSizes"] = jsonNamespaceSizes; + return result; } - returnResponse(success); -} + uint32_t PersistentStore::endpoint_getStorageSizes(const GetNamespacesParamsInfo& params, GetStorageSizesResultData& response) + { + Exchange::IStoreInspector::INamespaceSizeIterator* it; + auto result = _storeInspector->GetStorageSizes( + Exchange::IStore2::ScopeType(params.Scope.Value()), + it); + if (result == Core::ERROR_NONE) { + Exchange::IStoreInspector::NamespaceSize element; + while (it->Next(element) == true) { + auto& item = response.StorageList.Add(); + item.Namespace = element.ns; + item.Size = element.size; + } + it->Release(); + } -uint32_t PersistentStore::endpoint_flushCache(const JsonObject ¶meters, JsonObject &response) -{ - LOGINFOMETHOD(); + return result; + } - bool success = (_storeCache->FlushCache() == Core::ERROR_NONE); + uint32_t PersistentStore::endpoint_flushCache(DeleteKeyResultInfo& response) + { + auto result = _storeCache->FlushCache(); + if (result == Core::ERROR_NONE) { + response.Success = true; + } - returnResponse(success); -} + return result; + } -void PersistentStore::event_onValueChanged(const string &ns, const string &key, const string &value) -{ - JsonObject params; - params["namespace"] = ns; - params["key"] = key; - params["value"] = value; + uint32_t PersistentStore::endpoint_getNamespaceStorageLimit(const DeleteNamespaceParamsInfo& params, GetNamespaceStorageLimitResultData& response) + { + uint32_t size; + auto result = _storeLimit->GetNamespaceStorageLimit( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + size); + if (result == Core::ERROR_NONE) { + response.StorageLimit = size; + } - sendNotify(_T("onValueChanged"), params); -} + return result; + } -void PersistentStore::event_onStorageExceeded() -{ - sendNotify(_T("onStorageExceeded"), JsonObject()); -} + uint32_t PersistentStore::endpoint_setNamespaceStorageLimit(const SetNamespaceStorageLimitParamsData& params) + { + return _storeLimit->SetNamespaceStorageLimit( + Exchange::IStore2::ScopeType(params.Scope.Value()), + params.Namespace.Value(), + params.StorageLimit.Value()); + } } // namespace Plugin -} // namespace WPEFramework \ No newline at end of file +} // namespace WPEFramework diff --git a/PersistentStore/README.md b/PersistentStore/README.md deleted file mode 100644 index f95dc3510a..0000000000 --- a/PersistentStore/README.md +++ /dev/null @@ -1,36 +0,0 @@ ------------------ -# PersistentStore - -## Versions -`org.rdk.PersistentStore.1` - -## Methods: -``` -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.setValue","params":{"namespace":"foo","key":"key1","value":"value1"}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.getValue","params":{"namespace":"foo","key":"key1"}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.deleteKey","params":{"namespace":"foo","key":"key1"}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.deleteNamespace","params":{"namespace":"foo"}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.getKeys","params":{"namespace":"foo"}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.getNamespaces","params":{}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.getStorageSize","params":{}}' http://127.0.0.1:9998/jsonrpc -curl -d '{"jsonrpc":"2.0","id":"3","method":"org.rdk.PersistentStore.1.flushCache"}' http://127.0.0.1:9998/jsonrpc -``` - -## Responses -``` -{"jsonrpc":"2.0","id":3,"result":{"success":true}} -{"jsonrpc":"2.0","id":3,"result":{"value":"value1","success":true}} -{"jsonrpc":"2.0","id":3,"result":{"success":true}} -{"jsonrpc":"2.0","id":3,"result":{"success":true}} -{"jsonrpc":"2.0","id":3,"result":{"keys":["key1","key2","keyN"],"success":true}} -{"jsonrpc":"2.0","id":3,"result":{"namespaces":["ns1","ns2","nsN"],"success":true}} -{"jsonrpc":"2.0","id":3,"result":{"namespaceSizes":{"ns1":534,"ns2":234,"nsN":298},"success":true}} -``` - -## Events -``` -none -``` - -## Full Reference -https://etwiki.sys.comcast.net/display/RDK/PersistentStore diff --git a/PersistentStore/SqliteStore.cpp b/PersistentStore/SqliteStore.cpp deleted file mode 100644 index ac4306d6ba..0000000000 --- a/PersistentStore/SqliteStore.cpp +++ /dev/null @@ -1,725 +0,0 @@ -/* - * 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 "SqliteStore.h" - -#include "UtilsLogging.h" - -#include - -#if defined(USE_PLABELS) -#include "pbnj_utils.hpp" -#include -#endif - -#ifndef SQLITE_FILE_HEADER -#define SQLITE_FILE_HEADER "SQLite format 3" -#endif - -#define SQLITE *(sqlite3**)&_data -#define SQLITE_IS_ERROR_DBWRITE(rc) (rc == SQLITE_READONLY || rc == SQLITE_CORRUPT) - -namespace { -#if defined(USE_PLABELS) -bool GenerateKey(const char *key, std::vector &pKey) -{ - // NOTE: pbnj_utils stores the nonce under XDG_DATA_HOME/data. - // If the dir doesn't exist it will fail - - auto path = g_build_filename(g_get_user_data_dir(), "data", nullptr); - if (!Core::File(string(path)).Exists()) - g_mkdir_with_parents(path, 0755); - g_free(path); - - return pbnj_utils::prepareBufferForOrigin(key, [&pKey](const std::vector &buffer) - { - pKey = buffer; - }); -} -#endif -} - -namespace WPEFramework { -namespace Plugin { - -SqliteStore::SqliteStore() - : _data(nullptr), - _path(), - _key(), - _maxSize(0), - _maxValue(0), - _clients(), - _clientLock(), - _lock() -{ -} - -uint32_t SqliteStore::Register(Exchange::IStore::INotification *notification) -{ - Core::SafeSyncType lock(_clientLock); - - ASSERT(std::find(_clients.begin(), _clients.end(), notification) == _clients.end()); - - notification->AddRef(); - _clients.push_back(notification); - - return Core::ERROR_NONE; -} - -uint32_t SqliteStore::Unregister(Exchange::IStore::INotification *notification) -{ - Core::SafeSyncType lock(_clientLock); - - std::list::iterator - index(std::find(_clients.begin(), _clients.end(), notification)); - - ASSERT(index != _clients.end()); - - if (index != _clients.end()) { - notification->Release(); - _clients.erase(index); - } - - return Core::ERROR_NONE; -} - -uint32_t SqliteStore::Open(const string &path, const string &key, uint32_t maxSize, uint32_t maxValue) -{ - CountingLockSync lock(_lock, 0); - - if (IsOpen()) { - // Seems open! - - return Core::ERROR_ILLEGAL_STATE; - } - - Close(); - - _path = path; - _key = key; - _maxSize = maxSize; - _maxValue = maxValue; - -#if defined(SQLITE_HAS_CODEC) - bool shouldEncrypt = !key.empty(); - bool shouldReKey = shouldEncrypt && IsValid() && !IsEncrypted(); - - std::vector pKey; - if (shouldEncrypt) { -#if defined(USE_PLABELS) - if (!GenerateKey(key.c_str(), pKey)) { - LOGERR("pbnj_utils fail"); - - Close(); - - return Core::ERROR_GENERAL; - } -#else - LOGWARN("Key is not secure"); - pKey = std::vector(key.begin(), key.end()); -#endif - } -#endif - - sqlite3* &db = SQLITE; - - int rc = sqlite3_open(path.c_str(), &db); - if (rc != SQLITE_OK) { - LOGERR("%d : %s", rc, sqlite3_errstr(rc)); - - Close(); - - return Core::ERROR_OPENING_FAILED; - } - -#if defined(SQLITE_HAS_CODEC) - if (shouldEncrypt) { - rc = Encrypt(pKey); - if (rc != SQLITE_OK) { - LOGERR("Failed to attach key - %s", sqlite3_errstr(rc)); - - Close(); - - return Core::ERROR_GENERAL; - } - } -#endif - - rc = CreateTables(); - -#if defined(SQLITE_HAS_CODEC) - if (rc == SQLITE_NOTADB - && shouldEncrypt - && !shouldReKey // re-key should never fail - ) { - LOGWARN("The key doesn't work"); - - Close(); - - if (!Core::File(path).Destroy() || IsValid()) { - LOGERR("Can't remove file"); - - return Core::ERROR_DESTRUCTION_FAILED; - } - - sqlite3* &db = SQLITE; - - rc = sqlite3_open(path.c_str(), &db); - if (rc != SQLITE_OK) { - LOGERR("Can't create file"); - - return Core::ERROR_OPENING_FAILED; - } - - LOGWARN("SQLite database has been reset, trying re-key"); - - rc = Encrypt(pKey); - if (rc != SQLITE_OK) { - LOGERR("Failed to attach key - %s", sqlite3_errstr(rc)); - - Close(); - - return Core::ERROR_GENERAL; - } - - rc = CreateTables(); - } -#endif - - return Core::ERROR_NONE; -} - -uint32_t SqliteStore::SetValue(const string &ns, const string &key, const string &value) -{ - if (ns.size() > _maxValue || - key.size() > _maxValue || - value.size() > _maxValue) { - return Core::ERROR_INVALID_INPUT_LENGTH; - } - - uint32_t result = Core::ERROR_GENERAL; - - int retry = 0; - int rc; - do { - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - if (!db) - break; - - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT sum(s) FROM (" - " SELECT sum(length(key)+length(value)) s FROM item" - " UNION ALL" - " SELECT sum(length(name)) s FROM namespace" - ");", -1, &stmt, nullptr); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - int64_t size = sqlite3_column_int64(stmt, 0); - if (size > _maxSize) { - LOGWARN("max size exceeded: %" PRId64 "\n", size); - - result = Core::ERROR_WRITE_ERROR; - } else { - result = Core::ERROR_NONE; - } - } - else - LOGERR("ERROR getting size: %s", sqlite3_errstr(rc)); - - sqlite3_finalize(stmt); - - if (result == Core::ERROR_NONE) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO namespace (name) values (?);", -1, &stmt, nullptr); - - sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) { - LOGERR("ERROR inserting data: %s", sqlite3_errstr(rc)); - result = Core::ERROR_GENERAL; - } - - sqlite3_finalize(stmt); - } - - if (result == Core::ERROR_NONE) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "INSERT INTO item (ns,key,value)" - " SELECT id, ?, ?" - " FROM namespace" - " WHERE name = ?" - ";", -1, &stmt, nullptr); - - sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, value.c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 3, ns.c_str(), -1, SQLITE_TRANSIENT); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) { - LOGERR("ERROR inserting data: %s", sqlite3_errstr(rc)); - result = Core::ERROR_GENERAL; - } - else { - ValueChanged(ns, key, value); - } - - sqlite3_finalize(stmt); - } - } - while ((result != Core::ERROR_NONE) && - SQLITE_IS_ERROR_DBWRITE(rc) && - (++retry < 2) && - (Open(_path, _key, _maxSize, _maxValue) == Core::ERROR_NONE)); - - if (result == Core::ERROR_NONE) { - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT sum(s) FROM (" - " SELECT sum(length(key)+length(value)) s FROM item" - " UNION ALL" - " SELECT sum(length(name)) s FROM namespace" - ");", -1, &stmt, nullptr); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - int64_t size = sqlite3_column_int64(stmt, 0); - if (size > _maxSize) { - LOGWARN("max size exceeded: %" PRId64 "\n", size); - - StorageExceeded(); - } - } - else - LOGERR("ERROR getting size: %s", sqlite3_errstr(rc)); - - sqlite3_finalize(stmt); - } - - return result; -} - -uint32_t SqliteStore::GetValue(const string &ns, const string &key, string &value) -{ - uint32_t result = Core::ERROR_GENERAL; - - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - if (db) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT value" - " FROM item" - " INNER JOIN namespace ON namespace.id = item.ns" - " where name = ? and key = ?" - ";", -1, &stmt, nullptr); - - sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, key.c_str(), -1, SQLITE_TRANSIENT); - - int rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - value = (const char *) sqlite3_column_text(stmt, 0); - result = Core::ERROR_NONE; - } - else - LOGWARN("not found: %d", rc); - - sqlite3_finalize(stmt); - } - - return result; -} - -uint32_t SqliteStore::DeleteKey(const string &ns, const string &key) -{ - uint32_t result = Core::ERROR_GENERAL; - - int retry = 0; - int rc; - do { - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - if (!db) - break; - - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "DELETE FROM item" - " where ns in (select id from namespace where name = ?)" - " and key = ?" - ";", -1, &stmt, NULL); - - sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, key.c_str(), -1, SQLITE_TRANSIENT); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) - LOGERR("ERROR removing data: %s", sqlite3_errstr(rc)); - else - result = Core::ERROR_NONE; - - sqlite3_finalize(stmt); - } - while ((result != Core::ERROR_NONE) && - SQLITE_IS_ERROR_DBWRITE(rc) && - (++retry < 2) && - (Open(_path, _key, _maxSize, _maxValue) == Core::ERROR_NONE)); - - return result; -} - -uint32_t SqliteStore::DeleteNamespace(const string &ns) -{ - uint32_t result = Core::ERROR_GENERAL; - - int retry = 0; - int rc; - do { - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - if (!db) - break; - - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "DELETE FROM namespace where name = ?;", -1, &stmt, NULL); - - sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) - LOGERR("ERROR removing data: %s", sqlite3_errstr(rc)); - else - result = Core::ERROR_NONE; - - sqlite3_finalize(stmt); - } - while ((result != Core::ERROR_NONE) && - SQLITE_IS_ERROR_DBWRITE(rc) && - (++retry < 2) && - (Open(_path, _key, _maxSize, _maxValue) == Core::ERROR_NONE)); - - return result; -} - -uint32_t SqliteStore::GetKeys(const string &ns, std::vector &keys) -{ - uint32_t result = Core::ERROR_GENERAL; - - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - keys.clear(); - - if (db) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT key" - " FROM item" - " where ns in (select id from namespace where name = ?)" - ";", -1, &stmt, NULL); - - sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); - - while (sqlite3_step(stmt) == SQLITE_ROW) - keys.push_back((const char *) sqlite3_column_text(stmt, 0)); - - sqlite3_finalize(stmt); - - result = Core::ERROR_NONE; - } - - return result; -} - -uint32_t SqliteStore::GetNamespaces(std::vector &namespaces) -{ - uint32_t result = Core::ERROR_GENERAL; - - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - namespaces.clear(); - - if (db) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT name FROM namespace;", -1, &stmt, NULL); - - while (sqlite3_step(stmt) == SQLITE_ROW) - namespaces.push_back((const char *) sqlite3_column_text(stmt, 0)); - - sqlite3_finalize(stmt); - - result = Core::ERROR_NONE; - } - - return result; -} - -uint32_t SqliteStore::GetStorageSize(std::map &namespaceSizes) -{ - uint32_t result = Core::ERROR_GENERAL; - - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - namespaceSizes.clear(); - - if (db) { - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "SELECT name, sum(length(key)+length(value))" - " FROM item" - " INNER JOIN namespace ON namespace.id = item.ns" - " GROUP BY name" - ";", -1, &stmt, NULL); - - while (sqlite3_step(stmt) == SQLITE_ROW) - namespaceSizes[(const char *) sqlite3_column_text(stmt, 0)] = sqlite3_column_int(stmt, 1); - - sqlite3_finalize(stmt); - - result = Core::ERROR_NONE; - } - - return result; -} - -uint32_t SqliteStore::FlushCache() -{ - uint32_t result = Core::ERROR_GENERAL; - - CountingLockSync lock(_lock); - - sqlite3* &db = SQLITE; - - if (db) { - int rc = sqlite3_db_cacheflush(db); - if (rc != SQLITE_OK) { - LOGERR("Error while flushing sqlite database cache: %d", rc); - } - else { - result = Core::ERROR_NONE; - } - } - - sync(); - - return result; -} - -uint32_t SqliteStore::Term() -{ - CountingLockSync lock(_lock, 0); - - Close(); - - return Core::ERROR_NONE; -} - -void SqliteStore::ValueChanged(const string &ns, const string &key, const string &value) -{ - Core::SafeSyncType lock(_clientLock); - - std::list::iterator - index(_clients.begin()); - - while (index != _clients.end()) { - (*index)->ValueChanged(ns, key, value); - index++; - } -} - -void SqliteStore::StorageExceeded() -{ - Core::SafeSyncType lock(_clientLock); - - std::list::iterator - index(_clients.begin()); - - while (index != _clients.end()) { - (*index)->StorageExceeded(); - index++; - } -} - -int SqliteStore::Encrypt(const std::vector &key) -{ - int rc = SQLITE_OK; - -#if defined(SQLITE_HAS_CODEC) - sqlite3* &db = SQLITE; - - bool shouldReKey = !IsEncrypted(); - - if (!shouldReKey) { - rc = sqlite3_key_v2(db, nullptr, key.data(), key.size()); - } else { - rc = sqlite3_rekey_v2(db, nullptr, key.data(), key.size()); - if (rc == SQLITE_OK) - Vacuum(); - } - - if (shouldReKey && !IsEncrypted()) { - LOGERR("SQLite database file is clear after re-key"); - } -#endif - - return rc; -} - -int SqliteStore::CreateTables() -{ - sqlite3* &db = SQLITE; - - char *errmsg; - - int rc = sqlite3_exec(db, "CREATE TABLE if not exists namespace (" - "id INTEGER PRIMARY KEY," - "name TEXT UNIQUE" - ");", 0, 0, &errmsg); - if (rc != SQLITE_OK || errmsg) { - if (errmsg) { - LOGERR("%d : %s", rc, errmsg); - sqlite3_free(errmsg); - } - else - LOGERR("%d", rc); - - return rc; - } - - rc = sqlite3_exec(db, "CREATE TABLE if not exists item (" - "ns INTEGER," - "key TEXT," - "value TEXT," - "FOREIGN KEY(ns) REFERENCES namespace(id) ON DELETE CASCADE ON UPDATE NO ACTION," - "UNIQUE(ns,key) ON CONFLICT REPLACE" - ");", 0, 0, &errmsg); - if (rc != SQLITE_OK || errmsg) { - if (errmsg) { - LOGERR("%d : %s", rc, errmsg); - sqlite3_free(errmsg); - } - else - LOGERR("%d", rc); - - return rc; - } - - rc = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", 0, 0, &errmsg); - if (rc != SQLITE_OK || errmsg) { - if (errmsg) { - LOGERR("%d : %s", rc, errmsg); - sqlite3_free(errmsg); - } - else - LOGERR("%d", rc); - - return rc; - } - - return SQLITE_OK; -} - -int SqliteStore::Vacuum() -{ - sqlite3* &db = SQLITE; - - char *errmsg; - - int rc = sqlite3_exec(db, "VACUUM", 0, 0, &errmsg); - - if (rc != SQLITE_OK || errmsg) { - if (errmsg) { - LOGERR("%s", errmsg); - sqlite3_free(errmsg); - } - else { - LOGERR("%d", rc); - } - - return rc; - } - - return SQLITE_OK; -} - -int SqliteStore::Close() -{ - sqlite3* &db = SQLITE; - - if (db) { - int rc = sqlite3_db_cacheflush(db); - if (rc != SQLITE_OK) { - LOGERR("Error while flushing sqlite database cache: %d", rc); - } - - sqlite3_close_v2(db); - } - - db = nullptr; - - return SQLITE_OK; -} - -bool SqliteStore::IsOpen() const -{ - sqlite3* &db = SQLITE; - - return (db && IsValid()); -} - -bool SqliteStore::IsValid() const -{ - return (Core::File(_path).Exists()); -} - -bool SqliteStore::IsEncrypted() const -{ - bool result = false; - - Core::File file(_path); - - if (file.Exists() && file.Open(true)) { - const uint32_t bufLen = strlen(SQLITE_FILE_HEADER); - char buffer[bufLen + 1]; - - result = - (file.Read(reinterpret_cast(buffer), bufLen) != bufLen) || - (::memcmp(buffer, SQLITE_FILE_HEADER, bufLen) != 0); - } - - return result; -} - -} // namespace Plugin -} // namespace WPEFramework diff --git a/PersistentStore/SqliteStore.h b/PersistentStore/SqliteStore.h deleted file mode 100644 index 70bd3a4410..0000000000 --- a/PersistentStore/SqliteStore.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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. - */ - -#ifndef RDKSERVICES_SQLITESTORE_H -#define RDKSERVICES_SQLITESTORE_H - -#include "Module.h" - -#include "CountingLock.h" - -#include "IStoreListing.h" - -#include -#include - -namespace WPEFramework { -namespace Plugin { - -class SqliteStore - : public Exchange::IStore - , public Exchange::IStoreCache - , public IStoreListing -{ -private: - SqliteStore(const SqliteStore &) = delete; - SqliteStore &operator=(const SqliteStore &) = delete; - -public: - SqliteStore(); - virtual ~SqliteStore() = default; - - uint32_t Open(const string &path, const string &key, uint32_t maxSize, uint32_t maxValue); - uint32_t Term(); - -public: - // IStore methods - - virtual uint32_t Register(Exchange::IStore::INotification *notification) override; - virtual uint32_t Unregister(Exchange::IStore::INotification *notification) override; - virtual uint32_t SetValue(const string &ns, const string &key, const string &value) override; - virtual uint32_t GetValue(const string &ns, const string &key, string &value) override; - virtual uint32_t DeleteKey(const string &ns, const string &key) override; - virtual uint32_t DeleteNamespace(const string &ns) override; - - // IStoreListing methods - - virtual uint32_t GetKeys(const string &ns, std::vector &keys) override; - virtual uint32_t GetNamespaces(std::vector &namespaces) override; - virtual uint32_t GetStorageSize(std::map &namespaceSizes) override; - - // IStoreCache methods - - virtual uint32_t FlushCache() override; - - BEGIN_INTERFACE_MAP(SqliteStore) - INTERFACE_ENTRY(Exchange::IStore) - INTERFACE_ENTRY(Exchange::IStoreCache) - END_INTERFACE_MAP - -protected: - virtual void ValueChanged(const string &ns, const string &key, const string &value); - virtual void StorageExceeded(); - -private: - int Encrypt(const std::vector &key); - int CreateTables(); - int Vacuum(); - int Close(); - -private: - bool IsOpen() const; - bool IsValid() const; - bool IsEncrypted() const; - -private: - void *_data; - string _path; - string _key; - uint32_t _maxSize; - uint32_t _maxValue; - std::list _clients; - Core::CriticalSection _clientLock; - CountingLock _lock; -}; - -} // namespace Plugin -} // namespace WPEFramework - -#endif //RDKSERVICES_SQLITESTORE_H diff --git a/PersistentStore/l0test/CMakeLists.txt b/PersistentStore/l0test/CMakeLists.txt new file mode 100644 index 0000000000..d8f1e6ee20 --- /dev/null +++ b/PersistentStore/l0test/CMakeLists.txt @@ -0,0 +1,48 @@ +# 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. + +cmake_minimum_required(VERSION 3.14) + +project(persistentstorel0test) + +set(CMAKE_CXX_STANDARD 11) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +FetchContent_MakeAvailable(googletest) + +find_package(WPEFramework) +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) + +add_executable(${PROJECT_NAME} + ../Module.cpp + ../PersistentStore.cpp + ../PersistentStoreJsonRpc.cpp + PersistentStoreTest.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + gmock_main + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions +) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/PersistentStore/l0test/PersistentStoreTest.cpp b/PersistentStore/l0test/PersistentStoreTest.cpp new file mode 100644 index 0000000000..dfd7d8c980 --- /dev/null +++ b/PersistentStore/l0test/PersistentStoreTest.cpp @@ -0,0 +1,624 @@ +#include +#include + +#include "../PersistentStore.h" +#include "ServiceMock.h" +#include "Store2Mock.h" +#include "StoreCacheMock.h" +#include "StoreInspectorMock.h" +#include "StoreLimitMock.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::Test; +using ::WPEFramework::Core::PublishedServiceType; +using ::WPEFramework::Exchange::IStore; +using ::WPEFramework::Exchange::IStoreInspector; +using ::WPEFramework::JsonData::PersistentStore::DeleteKeyParamsInfo; +using ::WPEFramework::JsonData::PersistentStore::DeleteNamespaceParamsInfo; +using ::WPEFramework::JsonData::PersistentStore::GetKeysResultData; +using ::WPEFramework::JsonData::PersistentStore::GetNamespacesResultData; +using ::WPEFramework::JsonData::PersistentStore::GetNamespaceStorageLimitResultData; +using ::WPEFramework::JsonData::PersistentStore::GetStorageSizesResultData; +using ::WPEFramework::JsonData::PersistentStore::GetValueResultData; +using ::WPEFramework::JsonData::PersistentStore::SetNamespaceStorageLimitParamsData; +using ::WPEFramework::JsonData::PersistentStore::SetValueParamsData; +using ::WPEFramework::Plugin::PersistentStore; +using ::WPEFramework::PluginHost::ILocalDispatcher; +using ::WPEFramework::PluginHost::IPlugin; +using ::WPEFramework::RPC::IStringIterator; +using ::WPEFramework::RPC::IteratorType; +using ::WPEFramework::RPC::StringIterator; + +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; +const auto kTtl = 100; +const std::vector kKeys{ "key_1", "key_2" }; +const std::vector kAppIds{ "app_id_1", "app_id_2" }; +const std::vector kSizes{ { "app_id_1", 10 }, { "app_id_2", 20 } }; +const auto kSize = 1000; + +class APersistentStore : public Test { +protected: + NiceMock* service; + IPlugin* plugin; + APersistentStore() + : service(WPEFramework::Core::Service>::Create>()) + , plugin(WPEFramework::Core::Service::Create()) + { + } + ~APersistentStore() override + { + plugin->Release(); + service->Release(); + } +}; + +TEST_F(APersistentStore, GetsValueInDeviceScopeViaJsonRpc) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + ttl = kTtl; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteKeyParamsInfo params; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetValueResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.Value.Value(), Eq(kValue)); + EXPECT_THAT(result.Ttl.Value(), Eq(kTtl)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsValueInAccountScopeViaJsonRpc) +{ + class GrpcStore2 : public NiceMock { + public: + GrpcStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + ttl = kTtl; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteKeyParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetValueResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.Value.Value(), Eq(kValue)); + EXPECT_THAT(result.Ttl.Value(), Eq(kTtl)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, SetsValueInDeviceScopeViaJsonRpc) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(kTtl)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + SetValueParamsData params; + params.Namespace = kAppId; + params.Key = kKey; + params.Value = kValue; + params.Ttl = kTtl; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, SetsValueInAccountScopeViaJsonRpc) +{ + class GrpcStore2 : public NiceMock { + public: + GrpcStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(kTtl)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + SetValueParamsData params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + params.Value = kValue; + params.Ttl = kTtl; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setValue", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesKeyInDeviceScopeViaJsonRpc) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteKeyParamsInfo params; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesKeyInAccountScopeViaJsonRpc) +{ + class GrpcStore2 : public NiceMock { + public: + GrpcStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteKeyParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + params.Key = kKey; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteKey", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesNamespaceInDeviceScopeViaJsonRpc) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesNamespaceInAccountScopeViaJsonRpc) +{ + class GrpcStore2 : public NiceMock { + public: + GrpcStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::ACCOUNT)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteNamespaceParamsInfo params; + params.Scope = WPEFramework::JsonData::PersistentStore::ScopeType::ACCOUNT; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "deleteNamespace", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, FlushesCacheViaJsonRpc) +{ + class SqliteStoreCache : public NiceMock { + public: + SqliteStoreCache() + { + EXPECT_CALL(*this, FlushCache()) + .WillRepeatedly(Return(WPEFramework::Core::ERROR_NONE)); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "flushCache", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsKeysInDeviceScopeViaJsonRpc) +{ + class SqliteStoreInspector : public NiceMock { + public: + SqliteStoreInspector() + { + EXPECT_CALL(*this, GetKeys(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, IStringIterator*& keys) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + keys = (WPEFramework::Core::Service::Create(kKeys)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getKeys", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetKeysResultData result; + result.FromString(resultJsonStr); + auto index(result.Keys.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kKeys.at(0))); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kKeys.at(1))); + EXPECT_THAT(index.Next(), IsFalse()); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsNamespacesInDeviceScopeViaJsonRpc) +{ + class SqliteStoreInspector : public NiceMock { + public: + SqliteStoreInspector() + { + EXPECT_CALL(*this, GetNamespaces(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, IStringIterator*& namespaces) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + namespaces = (WPEFramework::Core::Service::Create(kAppIds)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaces", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetNamespacesResultData result; + result.FromString(resultJsonStr); + auto index(result.Namespaces.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kAppIds.at(0))); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Value(), Eq(kAppIds.at(1))); + EXPECT_THAT(index.Next(), IsFalse()); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsStorageSizesInDeviceScopeViaJsonRpc) +{ + class SqliteStoreInspector : public NiceMock { + public: + SqliteStoreInspector() + { + EXPECT_CALL(*this, GetStorageSizes(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, INamespaceSizeIterator*& storageList) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + storageList = (WPEFramework::Core::Service>::Create(kSizes)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getStorageSizes", "", resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetStorageSizesResultData result; + result.FromString(resultJsonStr); + auto index(result.StorageList.Elements()); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Namespace.Value(), Eq(kSizes.at(0).ns)); + EXPECT_THAT(index.Current().Size.Value(), Eq(kSizes.at(0).size)); + ASSERT_THAT(index.Next(), IsTrue()); + EXPECT_THAT(index.Current().Namespace.Value(), Eq(kSizes.at(1).ns)); + EXPECT_THAT(index.Current().Size.Value(), Eq(kSizes.at(1).size)); + EXPECT_THAT(index.Next(), IsFalse()); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsNamespaceStorageLimitInDeviceScopeViaJsonRpc) +{ + class SqliteStoreLimit : public NiceMock { + public: + SqliteStoreLimit() + { + EXPECT_CALL(*this, GetNamespaceStorageLimit(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, uint32_t& size) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + size = kSize; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + DeleteNamespaceParamsInfo params; + params.Namespace = kAppId; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + ASSERT_THAT(jsonRpc->Invoke(0, 0, "", "getNamespaceStorageLimit", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + GetNamespaceStorageLimitResultData result; + result.FromString(resultJsonStr); + EXPECT_THAT(result.StorageLimit.Value(), Eq(kSize)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, SetsNamespaceStorageLimitInDeviceScopeViaJsonRpc) +{ + class SqliteStoreLimit : public NiceMock { + public: + SqliteStoreLimit() + { + EXPECT_CALL(*this, SetNamespaceStorageLimit(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const uint32_t size) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(size, Eq(kSize)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto jsonRpc = plugin->QueryInterface(); + ASSERT_THAT(jsonRpc, NotNull()); + SetNamespaceStorageLimitParamsData params; + params.Namespace = kAppId; + params.StorageLimit = kSize; + string paramsJsonStr; + params.ToString(paramsJsonStr); + string resultJsonStr; + EXPECT_THAT(jsonRpc->Invoke(0, 0, "", "setNamespaceStorageLimit", paramsJsonStr, resultJsonStr), Eq(WPEFramework::Core::ERROR_NONE)); + jsonRpc->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, GetsValueInDeviceScopeViaIStore) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, GetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + value = kValue; + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + string value; + ASSERT_THAT(store->GetValue(kAppId, kKey, value), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kValue)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, SetsValueInDeviceScopeViaIStore) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, SetValue(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Eq(0)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->SetValue(kAppId, kKey, kValue), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesKeyInDeviceScopeViaIStore) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, DeleteKey(_, _, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns, const string& key) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->DeleteKey(kAppId, kKey), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} + +TEST_F(APersistentStore, DeletesNamespaceInDeviceScopeViaIStore) +{ + class SqliteStore2 : public NiceMock { + public: + SqliteStore2() + { + EXPECT_CALL(*this, DeleteNamespace(_, _)) + .WillRepeatedly(Invoke( + [](const ScopeType scope, const string& ns) { + EXPECT_THAT(scope, Eq(ScopeType::DEVICE)); + EXPECT_THAT(ns, Eq(kAppId)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + PublishedServiceType metadata(WPEFramework::Core::System::MODULE_NAME, 1, 0, 0); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + auto store = plugin->QueryInterface(); + ASSERT_THAT(store, NotNull()); + EXPECT_THAT(store->DeleteNamespace(kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + store->Release(); + plugin->Deinitialize(service); +} diff --git a/PersistentStore/l0test/ServiceMock.h b/PersistentStore/l0test/ServiceMock.h new file mode 100644 index 0000000000..f18d2c9c29 --- /dev/null +++ b/PersistentStore/l0test/ServiceMock.h @@ -0,0 +1,53 @@ +#pragma once + +#include "../Module.h" +#include + +class ServiceMock : public WPEFramework::PluginHost::IShell { +public: + ~ServiceMock() override = default; + MOCK_METHOD(string, Versions, (), (const, override)); + MOCK_METHOD(string, Locator, (), (const, override)); + MOCK_METHOD(string, ClassName, (), (const, override)); + MOCK_METHOD(string, Callsign, (), (const, override)); + MOCK_METHOD(string, WebPrefix, (), (const, override)); + MOCK_METHOD(string, ConfigLine, (), (const, override)); + MOCK_METHOD(string, PersistentPath, (), (const, override)); + MOCK_METHOD(string, VolatilePath, (), (const, override)); + MOCK_METHOD(string, DataPath, (), (const, override)); + MOCK_METHOD(state, State, (), (const, override)); + MOCK_METHOD(bool, Resumed, (), (const, override)); + MOCK_METHOD(bool, IsSupported, (const uint8_t), (const, override)); + MOCK_METHOD(void, EnableWebServer, (const string&, const string&), (override)); + MOCK_METHOD(void, DisableWebServer, (), (override)); + MOCK_METHOD(WPEFramework::PluginHost::ISubSystem*, SubSystems, (), (override)); + MOCK_METHOD(uint32_t, Submit, (const uint32_t, const WPEFramework::Core::ProxyType&), (override)); + MOCK_METHOD(void, Notify, (const string&), (override)); + MOCK_METHOD(void*, QueryInterfaceByCallsign, (const uint32_t, const string&), (override)); + MOCK_METHOD(void, Register, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); + MOCK_METHOD(void, Unregister, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); + MOCK_METHOD(string, Model, (), (const, override)); + MOCK_METHOD(bool, Background, (), (const, override)); + MOCK_METHOD(string, Accessor, (), (const, override)); + MOCK_METHOD(string, ProxyStubPath, (), (const, override)); + MOCK_METHOD(string, HashKey, (), (const, override)); + MOCK_METHOD(string, Substitute, (const string&), (const, override)); + MOCK_METHOD(WPEFramework::PluginHost::IShell::ICOMLink*, COMLink, (), (override)); + MOCK_METHOD(uint32_t, Activate, (const reason), (override)); + MOCK_METHOD(uint32_t, Deactivate, (const reason), (override)); + MOCK_METHOD(uint32_t, Unavailable, (const reason), (override)); + MOCK_METHOD(reason, Reason, (), (const, override)); + MOCK_METHOD(uint32_t, ConfigLine, (const string& config), (override)); + MOCK_METHOD(string, SystemRootPath, (), (const, override)); + MOCK_METHOD(uint32_t, SystemRootPath, (const string& systemRootPath), (override)); + MOCK_METHOD(string, SystemPath, (), (const, override)); + MOCK_METHOD(string, PluginPath, (), (const, override)); + MOCK_METHOD(WPEFramework::PluginHost::IShell::startmode, StartMode, (), (const, override)); + MOCK_METHOD(WPEFramework::Core::hresult, StartMode, (const startmode value), (override)); + MOCK_METHOD(WPEFramework::Core::hresult, Resumed, (const bool value), (override)); + MOCK_METHOD(WPEFramework::Core::hresult, Metadata, (string & info /* @out */), (const, override)); + MOCK_METHOD(WPEFramework::Core::hresult, Hibernate, (const uint32_t timeout), (override)); + BEGIN_INTERFACE_MAP(ServiceMock) + INTERFACE_ENTRY(IShell) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/Store2Mock.h b/PersistentStore/l0test/Store2Mock.h new file mode 100644 index 0000000000..35ac62674f --- /dev/null +++ b/PersistentStore/l0test/Store2Mock.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class Store2Mock : public WPEFramework::Exchange::IStore2 { +public: + ~Store2Mock() override = default; + MOCK_METHOD(uint32_t, Register, (INotification*), (override)); + MOCK_METHOD(uint32_t, Unregister, (INotification*), (override)); + MOCK_METHOD(uint32_t, SetValue, (const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl), (override)); + MOCK_METHOD(uint32_t, GetValue, (const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl), (override)); + MOCK_METHOD(uint32_t, DeleteKey, (const ScopeType scope, const string& ns, const string& key), (override)); + MOCK_METHOD(uint32_t, DeleteNamespace, (const ScopeType scope, const string& ns), (override)); + BEGIN_INTERFACE_MAP(Store2Mock) + INTERFACE_ENTRY(IStore2) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreCacheMock.h b/PersistentStore/l0test/StoreCacheMock.h new file mode 100644 index 0000000000..ed7715a013 --- /dev/null +++ b/PersistentStore/l0test/StoreCacheMock.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class StoreCacheMock : public WPEFramework::Exchange::IStoreCache { +public: + ~StoreCacheMock() override = default; + MOCK_METHOD(uint32_t, FlushCache, (), (override)); + BEGIN_INTERFACE_MAP(StoreCacheMock) + INTERFACE_ENTRY(IStoreCache) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreInspectorMock.h b/PersistentStore/l0test/StoreInspectorMock.h new file mode 100644 index 0000000000..7765f744a7 --- /dev/null +++ b/PersistentStore/l0test/StoreInspectorMock.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class StoreInspectorMock : public WPEFramework::Exchange::IStoreInspector { +public: + ~StoreInspectorMock() override = default; + MOCK_METHOD(uint32_t, GetKeys, (const ScopeType scope, const string& ns, IStringIterator*& keys), (override)); + MOCK_METHOD(uint32_t, GetNamespaces, (const ScopeType scope, IStringIterator*& namespaces), (override)); + MOCK_METHOD(uint32_t, GetStorageSizes, (const ScopeType scope, INamespaceSizeIterator*& storageList), (override)); + BEGIN_INTERFACE_MAP(StoreInspectorMock) + INTERFACE_ENTRY(IStoreInspector) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l0test/StoreLimitMock.h b/PersistentStore/l0test/StoreLimitMock.h new file mode 100644 index 0000000000..2b818984e9 --- /dev/null +++ b/PersistentStore/l0test/StoreLimitMock.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class StoreLimitMock : public WPEFramework::Exchange::IStoreLimit { +public: + ~StoreLimitMock() override = default; + MOCK_METHOD(uint32_t, GetNamespaceStorageLimit, (const ScopeType scope, const string& ns, uint32_t& size), (override)); + MOCK_METHOD(uint32_t, SetNamespaceStorageLimit, (const ScopeType scope, const string& ns, const uint32_t size), (override)); + BEGIN_INTERFACE_MAP(StoreLimitMock) + INTERFACE_ENTRY(IStoreLimit) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/l1test/CMakeLists.txt b/PersistentStore/l1test/CMakeLists.txt new file mode 100644 index 0000000000..67d7b50ff5 --- /dev/null +++ b/PersistentStore/l1test/CMakeLists.txt @@ -0,0 +1,48 @@ +# 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. + +cmake_minimum_required(VERSION 3.14) + +project(persistentstorel1test) + +set(CMAKE_CXX_STANDARD 11) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +FetchContent_MakeAvailable(googletest) + +find_package(WPEFramework) +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Definitions REQUIRED) + +add_executable(${PROJECT_NAME} + ../Module.cpp + ../PersistentStore.cpp + ../PersistentStoreJsonRpc.cpp + PersistentStoreTest.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + gmock_main + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${NAMESPACE}Definitions::${NAMESPACE}Definitions +) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/PersistentStore/l1test/PersistentStoreTest.cpp b/PersistentStore/l1test/PersistentStoreTest.cpp new file mode 100644 index 0000000000..e20200cacd --- /dev/null +++ b/PersistentStore/l1test/PersistentStoreTest.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "../PersistentStore.h" +#include "ServiceMock.h" + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::Test; +using ::WPEFramework::Plugin::PersistentStore; +using ::WPEFramework::PluginHost::IPlugin; + +const auto kFile1 = "/tmp/persistentstore/l0test/persistentstoretest1"; +const auto kFile2 = "/tmp/persistentstore/l0test/persistentstoretest2"; +const uint8_t kFileContent[12]{ 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0xFE, 0x03, 0x01, 0xC1, 0x00, 0x01 }; +const auto kFileContentSize = sizeof(kFileContent) / sizeof(uint8_t); + +class APersistentStore : public Test { +protected: + NiceMock* service; + IPlugin* plugin; + APersistentStore() + : service(WPEFramework::Core::Service>::Create>()) + , plugin(WPEFramework::Core::Service::Create()) + { + } + ~APersistentStore() override + { + plugin->Release(); + service->Release(); + } +}; + +TEST_F(APersistentStore, MovesFileWhenInitializedWithNewAndPreviousPath) +{ + WPEFramework::Core::File file1(kFile1); + WPEFramework::Core::File file2(kFile2); + file1.Destroy(); + file2.Destroy(); + WPEFramework::Core::Directory(file1.PathName().c_str()).CreatePath(); + ASSERT_THAT(file1.Create(), IsTrue()); + file1.Write(kFileContent, kFileContentSize); + JsonObject config; + config["path"] = kFile2; + config["legacypath"] = kFile1; + string configJsonStr; + config.ToString(configJsonStr); + ON_CALL(*service, ConfigLine()) + .WillByDefault(Return(configJsonStr)); + ASSERT_THAT(plugin->Initialize(service), Eq("")); + plugin->Deinitialize(service); + ASSERT_THAT(WPEFramework::Core::File(kFile1).Exists(), IsFalse()); + ASSERT_THAT(file2.Open(true), IsTrue()); + uint8_t buffer[1024]; + ASSERT_THAT(kFileContentSize, file2.Read(buffer, 1024)); + EXPECT_THAT(memcmp(buffer, kFileContent, kFileContentSize), Eq(0)); +} diff --git a/PersistentStore/l1test/ServiceMock.h b/PersistentStore/l1test/ServiceMock.h new file mode 100644 index 0000000000..f18d2c9c29 --- /dev/null +++ b/PersistentStore/l1test/ServiceMock.h @@ -0,0 +1,53 @@ +#pragma once + +#include "../Module.h" +#include + +class ServiceMock : public WPEFramework::PluginHost::IShell { +public: + ~ServiceMock() override = default; + MOCK_METHOD(string, Versions, (), (const, override)); + MOCK_METHOD(string, Locator, (), (const, override)); + MOCK_METHOD(string, ClassName, (), (const, override)); + MOCK_METHOD(string, Callsign, (), (const, override)); + MOCK_METHOD(string, WebPrefix, (), (const, override)); + MOCK_METHOD(string, ConfigLine, (), (const, override)); + MOCK_METHOD(string, PersistentPath, (), (const, override)); + MOCK_METHOD(string, VolatilePath, (), (const, override)); + MOCK_METHOD(string, DataPath, (), (const, override)); + MOCK_METHOD(state, State, (), (const, override)); + MOCK_METHOD(bool, Resumed, (), (const, override)); + MOCK_METHOD(bool, IsSupported, (const uint8_t), (const, override)); + MOCK_METHOD(void, EnableWebServer, (const string&, const string&), (override)); + MOCK_METHOD(void, DisableWebServer, (), (override)); + MOCK_METHOD(WPEFramework::PluginHost::ISubSystem*, SubSystems, (), (override)); + MOCK_METHOD(uint32_t, Submit, (const uint32_t, const WPEFramework::Core::ProxyType&), (override)); + MOCK_METHOD(void, Notify, (const string&), (override)); + MOCK_METHOD(void*, QueryInterfaceByCallsign, (const uint32_t, const string&), (override)); + MOCK_METHOD(void, Register, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); + MOCK_METHOD(void, Unregister, (WPEFramework::PluginHost::IPlugin::INotification*), (override)); + MOCK_METHOD(string, Model, (), (const, override)); + MOCK_METHOD(bool, Background, (), (const, override)); + MOCK_METHOD(string, Accessor, (), (const, override)); + MOCK_METHOD(string, ProxyStubPath, (), (const, override)); + MOCK_METHOD(string, HashKey, (), (const, override)); + MOCK_METHOD(string, Substitute, (const string&), (const, override)); + MOCK_METHOD(WPEFramework::PluginHost::IShell::ICOMLink*, COMLink, (), (override)); + MOCK_METHOD(uint32_t, Activate, (const reason), (override)); + MOCK_METHOD(uint32_t, Deactivate, (const reason), (override)); + MOCK_METHOD(uint32_t, Unavailable, (const reason), (override)); + MOCK_METHOD(reason, Reason, (), (const, override)); + MOCK_METHOD(uint32_t, ConfigLine, (const string& config), (override)); + MOCK_METHOD(string, SystemRootPath, (), (const, override)); + MOCK_METHOD(uint32_t, SystemRootPath, (const string& systemRootPath), (override)); + MOCK_METHOD(string, SystemPath, (), (const, override)); + MOCK_METHOD(string, PluginPath, (), (const, override)); + MOCK_METHOD(WPEFramework::PluginHost::IShell::startmode, StartMode, (), (const, override)); + MOCK_METHOD(WPEFramework::Core::hresult, StartMode, (const startmode value), (override)); + MOCK_METHOD(WPEFramework::Core::hresult, Resumed, (const bool value), (override)); + MOCK_METHOD(WPEFramework::Core::hresult, Metadata, (string & info /* @out */), (const, override)); + MOCK_METHOD(WPEFramework::Core::hresult, Hibernate, (const uint32_t timeout), (override)); + BEGIN_INTERFACE_MAP(ServiceMock) + INTERFACE_ENTRY(IShell) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/sqlite/Store2.cpp b/PersistentStore/sqlite/Store2.cpp new file mode 100644 index 0000000000..e14f855f8c --- /dev/null +++ b/PersistentStore/sqlite/Store2.cpp @@ -0,0 +1,8 @@ +#include "Store2.h" + +namespace WPEFramework { +namespace Plugin { + class SqliteStore2 : public Sqlite::Store2 {}; + SERVICE_REGISTRATION(SqliteStore2, 1, 0); +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/Store2.h b/PersistentStore/sqlite/Store2.h new file mode 100644 index 0000000000..c106b088b8 --- /dev/null +++ b/PersistentStore/sqlite/Store2.h @@ -0,0 +1,335 @@ +/* + * 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. + */ + +#pragma once + +#include "../Module.h" +#include +#include +#ifdef WITH_SYSMGR +#include +#include +#endif + +namespace WPEFramework { +namespace Plugin { + namespace Sqlite { + + class Store2 : public Exchange::IStore2 { + private: + Store2(const Store2&) = delete; + Store2& operator=(const Store2&) = delete; + + public: + Store2() + : Store2( + getenv(PATH_ENV), + std::stoul(getenv(MAXSIZE_ENV)), + std::stoul(getenv(MAXVALUE_ENV)), + std::stoul(getenv(LIMIT_ENV))) + { + } + Store2(const string& path, const uint64_t maxSize, const uint64_t maxValue, const uint64_t limit) + : IStore2() + , _path(path) + , _maxSize(maxSize) + , _maxValue(maxValue) + , _limit(limit) + { + Open(); + } + ~Store2() override + { + Close(); + } + + private: + void Open() + { + Core::File file(_path); + Core::Directory(file.PathName().c_str()).CreatePath(); + auto rc = sqlite3_open(_path.c_str(), &_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + const std::vector statements = { + "pragma foreign_keys = on;", + "pragma busy_timeout = 1000000;", + "create table if not exists namespace (id integer primary key,name text unique);", + "create table if not exists item (ns integer,key text,value text,foreign key(ns) references namespace(id) on delete cascade on update no action,unique(ns,key) on conflict replace);", + "create table if not exists limits (n integer,size integer,foreign key(n) references namespace(id) on delete cascade on update no action,unique(n) on conflict replace);", + "alter table item add column ttl integer;", + "create temporary trigger if not exists ns_empty insert on namespace begin select case when length(new.name) = 0 then raise (fail, 'empty') end; end;", + "create temporary trigger if not exists key_empty insert on item begin select case when length(new.key) = 0 then raise (fail, 'empty') end; end;", + "create temporary trigger if not exists ns_maxvalue insert on namespace begin select case when length(new.name) > " + std::to_string(_maxValue) + " then raise (fail, 'max value') end; end;", + "create temporary trigger if not exists key_maxvalue insert on item begin select case when length(new.key) > " + std::to_string(_maxValue) + " then raise (fail, 'max value') end; end;", + "create temporary trigger if not exists value_maxvalue insert on item begin select case when length(new.value) > " + std::to_string(_maxValue) + " then raise (fail, 'max value') end; end;", + "create temporary trigger if not exists ns_maxsize insert on namespace begin select case when (select sum(s) from (select sum(length(key)+length(value)) s from item union all select sum(length(name)) s from namespace union all select length(new.name) s)) > " + std::to_string(_maxSize) + " then raise (fail, 'max size') end; end;", + "create temporary trigger if not exists item_maxsize insert on item begin select case when (select sum(s) from (select sum(length(key)+length(value)) s from item union all select sum(length(name)) s from namespace union all select length(new.key)+length(new.value) s)) > " + std::to_string(_maxSize) + " then raise (fail, 'max size') end; end;", + "create temporary trigger if not exists item_limit_default insert on item begin select case when (select length(new.key)+length(new.value)+sum(length(key)+length(value)) from item where ns = new.ns) > " + std::to_string(_limit) + " then raise (fail, 'limit') end; end;", + "create temporary trigger if not exists item_limit insert on item begin select case when (select size-length(new.key)-length(new.value)-sum(length(key)+length(value)) from limits inner join item on limits.n = item.ns where n = new.ns) < 0 then raise (fail, 'limit') end; end;" + }; + for (auto& sql : statements) { + auto rc = sqlite3_exec(_data, sql.c_str(), nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + } + void Close() + { + auto rc = sqlite3_close_v2(_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + static bool IsTimeSynced() + { +#ifdef WITH_SYSMGR + IARM_Bus_Init(IARM_INIT_NAME); + IARM_Bus_Connect(); + IARM_Bus_SYSMgr_GetSystemStates_Param_t param; + if ((IARM_Bus_Call(IARM_BUS_SYSMGR_NAME, IARM_BUS_SYSMGR_API_GetSystemStates, ¶m, sizeof(param)) != IARM_RESULT_SUCCESS) + || !param.time_source.state) { + return false; + } +#endif + return true; + } + + public: + uint32_t Register(INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + ASSERT(std::find(_clients.begin(), _clients.end(), notification) == _clients.end()); + + notification->AddRef(); + _clients.push_back(notification); + + return Core::ERROR_NONE; + } + uint32_t Unregister(INotification* notification) override + { + Core::SafeSyncType lock(_clientLock); + + std::list::iterator + index(std::find(_clients.begin(), _clients.end(), notification)); + + ASSERT(index != _clients.end()); + + if (index != _clients.end()) { + notification->Release(); + _clients.erase(index); + } + + return Core::ERROR_NONE; + } + + uint32_t SetValue(const ScopeType scope, const string& ns, const string& key, const string& value, const uint32_t ttl) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + if (ttl != 0) { + if (!IsTimeSynced()) { + return Core::ERROR_PENDING_CONDITIONS; + } + } + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "insert or ignore into namespace (name) values (?);", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc == SQLITE_DONE) { + sqlite3_prepare_v2(_data, "insert into item (ns,key,value,ttl)" + " select id, ?, ?, ?" + " from namespace" + " where name = ?" + ";", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, value.c_str(), -1, SQLITE_TRANSIENT); + if (ttl != 0) { + sqlite3_bind_int64(stmt, 3, (int64_t)ttl + time(nullptr)); + } else { + sqlite3_bind_null(stmt, 3); + } + sqlite3_bind_text(stmt, 4, ns.c_str(), -1, SQLITE_TRANSIENT); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + if (rc == SQLITE_DONE) { + OnValueChanged(ns, key, value); + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + if (rc == SQLITE_CONSTRAINT) { + result = Core::ERROR_INVALID_INPUT_LENGTH; + } else { + result = Core::ERROR_GENERAL; + } + } + + return result; + } + uint32_t GetValue(const ScopeType scope, const string& ns, const string& key, string& value, uint32_t& ttl) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + string k, v; + int64_t t = 0; + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "select key, value, ttl" + " from namespace" + " left join item on (namespace.id = item.ns and key = ?)" + " where name = ?" + ";", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, ns.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + if (sqlite3_column_type(stmt, 0) != SQLITE_NULL) { + k = (const char*)sqlite3_column_text(stmt, 0); + v = (const char*)sqlite3_column_text(stmt, 1); + t = sqlite3_column_int64(stmt, 2); + } + } + sqlite3_finalize(stmt); + + if (rc == SQLITE_ROW) { + if (!k.empty()) { + if (t == 0) { + value = v; + ttl = 0; + result = Core::ERROR_NONE; + } else if (IsTimeSynced()) { + t -= time(nullptr); + if (t > 0) { + value = v; + ttl = t; + result = Core::ERROR_NONE; + } else { + result = Core::ERROR_UNKNOWN_KEY; + } + } else { + result = Core::ERROR_PENDING_CONDITIONS; + } + + } else { + result = Core::ERROR_UNKNOWN_KEY; + } + } else if (rc == SQLITE_DONE) { + result = Core::ERROR_NOT_EXIST; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t DeleteKey(const ScopeType scope, const string& ns, const string& key) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "delete from item" + " where ns in (select id from namespace where name = ?)" + " and key = ?" + ";", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, key.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t DeleteNamespace(const ScopeType scope, const string& ns) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "delete from namespace where name = ?;", -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + + BEGIN_INTERFACE_MAP(Store2) + INTERFACE_ENTRY(IStore2) + END_INTERFACE_MAP + + private: + void OnValueChanged(const string& ns, const string& key, const string& value) + { + Core::SafeSyncType lock(_clientLock); + + std::list::iterator + index(_clients.begin()); + + while (index != _clients.end()) { + (*index)->ValueChanged(ScopeType::DEVICE, ns, key, value); + index++; + } + } + void OnError(const char* fn, const int status) const + { + TRACE(Trace::Error, (_T("%s sqlite error %d"), fn, status)); + } + + private: + const string _path; + const uint64_t _maxSize; + const uint64_t _maxValue; + const uint64_t _limit; + sqlite3* _data; + std::list _clients; + Core::CriticalSection _clientLock; + }; + + } // namespace Sqlite +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreCache.cpp b/PersistentStore/sqlite/StoreCache.cpp new file mode 100644 index 0000000000..ec05da258b --- /dev/null +++ b/PersistentStore/sqlite/StoreCache.cpp @@ -0,0 +1,8 @@ +#include "StoreCache.h" + +namespace WPEFramework { +namespace Plugin { + class SqliteStoreCache : public Sqlite::StoreCache {}; + SERVICE_REGISTRATION(SqliteStoreCache, 1, 0); +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreCache.h b/PersistentStore/sqlite/StoreCache.h new file mode 100644 index 0000000000..d6811766af --- /dev/null +++ b/PersistentStore/sqlite/StoreCache.h @@ -0,0 +1,114 @@ +/* + * 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. + */ + +#pragma once + +#include "../Module.h" +#include +#include + +namespace WPEFramework { +namespace Plugin { + namespace Sqlite { + + class StoreCache : public Exchange::IStoreCache { + private: + StoreCache(const StoreCache&) = delete; + StoreCache& operator=(const StoreCache&) = delete; + + public: + StoreCache() + : StoreCache(getenv(PATH_ENV)) + { + } + StoreCache(const string& path) + : IStoreCache() + , _path(path) + { + Open(); + } + ~StoreCache() override + { + Close(); + } + + private: + void Open() + { + Core::File file(_path); + Core::Directory(file.PathName().c_str()).CreatePath(); + auto rc = sqlite3_open(_path.c_str(), &_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + const std::vector statements = { + "pragma busy_timeout = 1000000;" + }; + for (auto& sql : statements) { + auto rc = sqlite3_exec(_data, sql.c_str(), nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + } + void Close() + { + auto rc = sqlite3_close_v2(_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + + public: + uint32_t FlushCache() override + { + uint32_t result; + + auto rc = sqlite3_db_cacheflush(_data); + + if (rc == SQLITE_OK) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + sync(); + + return result; + } + + BEGIN_INTERFACE_MAP(StoreCache) + INTERFACE_ENTRY(IStoreCache) + END_INTERFACE_MAP + + private: + void OnError(const char* fn, const int status) const + { + TRACE(Trace::Error, (_T("%s sqlite error %d"), fn, status)); + } + + private: + const string _path; + sqlite3* _data; + }; + + } // namespace Sqlite +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreInspector.cpp b/PersistentStore/sqlite/StoreInspector.cpp new file mode 100644 index 0000000000..543faab05a --- /dev/null +++ b/PersistentStore/sqlite/StoreInspector.cpp @@ -0,0 +1,8 @@ +#include "StoreInspector.h" + +namespace WPEFramework { +namespace Plugin { + class SqliteStoreInspector : public Sqlite::StoreInspector {}; + SERVICE_REGISTRATION(SqliteStoreInspector, 1, 0); +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreInspector.h b/PersistentStore/sqlite/StoreInspector.h new file mode 100644 index 0000000000..e1c03a99a8 --- /dev/null +++ b/PersistentStore/sqlite/StoreInspector.h @@ -0,0 +1,190 @@ +/* + * 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. + */ + +#pragma once + +#include "../Module.h" +#include +#include + +namespace WPEFramework { +namespace Plugin { + namespace Sqlite { + + class StoreInspector : public Exchange::IStoreInspector { + private: + StoreInspector(const StoreInspector&) = delete; + StoreInspector& operator=(const StoreInspector&) = delete; + + typedef RPC::IteratorType NamespaceSizeIterator; + + public: + StoreInspector() + : StoreInspector(getenv(PATH_ENV)) + { + } + StoreInspector(const string& path) + : IStoreInspector() + , _path(path) + { + Open(); + } + ~StoreInspector() override + { + Close(); + } + + private: + void Open() + { + Core::File file(_path); + Core::Directory(file.PathName().c_str()).CreatePath(); + auto rc = sqlite3_open(_path.c_str(), &_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + const std::vector statements = { + "pragma foreign_keys = on;", + "pragma busy_timeout = 1000000;", + "create table if not exists namespace (id integer primary key,name text unique);", + "create table if not exists item (ns integer,key text,value text,foreign key(ns) references namespace(id) on delete cascade on update no action,unique(ns,key) on conflict replace);" + }; + for (auto& sql : statements) { + auto rc = sqlite3_exec(_data, sql.c_str(), nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + } + void Close() + { + auto rc = sqlite3_close_v2(_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + + public: + uint32_t GetKeys(const ScopeType scope, const string& ns, RPC::IStringIterator*& keys) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "select key" + " from item" + " where ns in (select id from namespace where name = ?)" + ";", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + std::list list; + int rc; + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + list.emplace_back((const char*)sqlite3_column_text(stmt, 0)); + } + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + keys = (Core::Service::Create(list)); + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t GetNamespaces(const ScopeType scope, RPC::IStringIterator*& namespaces) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "select name from namespace;", -1, &stmt, nullptr); + std::list list; + int rc; + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + list.emplace_back((const char*)sqlite3_column_text(stmt, 0)); + } + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + namespaces = (Core::Service::Create(list)); + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + uint32_t GetStorageSizes(const ScopeType scope, INamespaceSizeIterator*& storageList) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "select name, sum(length(key)+length(value))" + " from item" + " inner join namespace on namespace.id = item.ns" + " group by name" + ";", + -1, &stmt, nullptr); + std::list list; + int rc; + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + NamespaceSize namespaceSize; + namespaceSize.ns = (const char*)sqlite3_column_text(stmt, 0); + namespaceSize.size = sqlite3_column_int(stmt, 1); + list.emplace_back(namespaceSize); + } + sqlite3_finalize(stmt); + + if (rc == SQLITE_DONE) { + storageList = (Core::Service::Create(list)); + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + + BEGIN_INTERFACE_MAP(StoreInspector) + INTERFACE_ENTRY(IStoreInspector) + END_INTERFACE_MAP + + private: + void OnError(const char* fn, const int status) const + { + TRACE(Trace::Error, (_T("%s sqlite error %d"), fn, status)); + } + + private: + const string _path; + sqlite3* _data; + }; + + } // namespace Sqlite +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreLimit.cpp b/PersistentStore/sqlite/StoreLimit.cpp new file mode 100644 index 0000000000..c37cd1507a --- /dev/null +++ b/PersistentStore/sqlite/StoreLimit.cpp @@ -0,0 +1,8 @@ +#include "StoreLimit.h" + +namespace WPEFramework { +namespace Plugin { + class SqliteStoreLimit : public Sqlite::StoreLimit {}; + SERVICE_REGISTRATION(SqliteStoreLimit, 1, 0); +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/StoreLimit.h b/PersistentStore/sqlite/StoreLimit.h new file mode 100644 index 0000000000..86f9300a7f --- /dev/null +++ b/PersistentStore/sqlite/StoreLimit.h @@ -0,0 +1,183 @@ +/* + * 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. + */ + +#pragma once + +#include "../Module.h" +#include +#include + +namespace WPEFramework { +namespace Plugin { + namespace Sqlite { + + class StoreLimit : public Exchange::IStoreLimit { + private: + StoreLimit(const StoreLimit&) = delete; + StoreLimit& operator=(const StoreLimit&) = delete; + + public: + StoreLimit() + : StoreLimit( + getenv(PATH_ENV), + std::stoul(getenv(MAXSIZE_ENV)), + std::stoul(getenv(MAXVALUE_ENV))) + { + } + StoreLimit(const string& path, const uint64_t maxSize, const uint64_t maxValue) + : IStoreLimit() + , _path(path) + , _maxSize(maxSize) + , _maxValue(maxValue) + { + Open(); + } + ~StoreLimit() override + { + Close(); + } + + private: + void Open() + { + Core::File file(_path); + Core::Directory(file.PathName().c_str()).CreatePath(); + auto rc = sqlite3_open(_path.c_str(), &_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + const std::vector statements = { + "pragma foreign_keys = on;", + "pragma busy_timeout = 1000000;", + "create table if not exists namespace (id integer primary key,name text unique);", + "create table if not exists item (ns integer,key text,value text,foreign key(ns) references namespace(id) on delete cascade on update no action,unique(ns,key) on conflict replace);", + "create table if not exists limits (n integer,size integer,foreign key(n) references namespace(id) on delete cascade on update no action,unique(n) on conflict replace);", + "create temporary trigger if not exists ns_empty insert on namespace begin select case when length(new.name) = 0 then raise (fail, 'empty') end; end;", + "create temporary trigger if not exists ns_maxvalue insert on namespace begin select case when length(new.name) > " + std::to_string(_maxValue) + " then raise (fail, 'max value') end; end;", + "create temporary trigger if not exists ns_maxsize insert on namespace begin select case when (select sum(s) from (select sum(length(key)+length(value)) s from item union all select sum(length(name)) s from namespace union all select length(new.name) s)) > " + std::to_string(_maxSize) + " then raise (fail, 'max size') end; end;" + }; + for (auto& sql : statements) { + auto rc = sqlite3_exec(_data, sql.c_str(), nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + } + void Close() + { + auto rc = sqlite3_close_v2(_data); + if (rc != SQLITE_OK) { + OnError(__FUNCTION__, rc); + } + } + + public: + uint32_t SetNamespaceStorageLimit(const ScopeType scope, const string& ns, const uint32_t size) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "insert or ignore into namespace (name) values (?);", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (rc == SQLITE_DONE) { + sqlite3_prepare_v2(_data, "insert into limits (n,size)" + " select id, ?" + " from namespace" + " where name = ?" + ";", + -1, &stmt, nullptr); + sqlite3_bind_int(stmt, 1, size); + sqlite3_bind_text(stmt, 2, ns.c_str(), -1, SQLITE_TRANSIENT); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + if (rc == SQLITE_DONE) { + result = Core::ERROR_NONE; + } else { + OnError(__FUNCTION__, rc); + if (rc == SQLITE_CONSTRAINT) { + result = Core::ERROR_INVALID_INPUT_LENGTH; + } else { + result = Core::ERROR_GENERAL; + } + } + + return result; + } + uint32_t GetNamespaceStorageLimit(const ScopeType scope, const string& ns, uint32_t& size) override + { + ASSERT(scope == ScopeType::DEVICE); + + uint32_t result; + + uint32_t s; + sqlite3_stmt* stmt; + sqlite3_prepare_v2(_data, "select size" + " from limits" + " inner join namespace on namespace.id = limits.n" + " where name = ?" + ";", + -1, &stmt, nullptr); + sqlite3_bind_text(stmt, 1, ns.c_str(), -1, SQLITE_TRANSIENT); + auto rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + s = (uint32_t)sqlite3_column_int(stmt, 0); + result = Core::ERROR_NONE; + } + sqlite3_finalize(stmt); + + if (rc == SQLITE_ROW) { + size = s; + result = Core::ERROR_NONE; + } else if (rc == SQLITE_DONE) { + result = Core::ERROR_NOT_EXIST; + } else { + OnError(__FUNCTION__, rc); + result = Core::ERROR_GENERAL; + } + + return result; + } + + BEGIN_INTERFACE_MAP(StoreLimit) + INTERFACE_ENTRY(IStoreLimit) + END_INTERFACE_MAP + + private: + void OnError(const char* fn, const int status) const + { + TRACE(Trace::Error, (_T("%s sqlite error %d"), fn, status)); + } + + private: + const string _path; + const uint64_t _maxSize; + const uint64_t _maxValue; + sqlite3* _data; + }; + + } // namespace Sqlite +} // namespace Plugin +} // namespace WPEFramework diff --git a/PersistentStore/sqlite/l1test/CMakeLists.txt b/PersistentStore/sqlite/l1test/CMakeLists.txt new file mode 100644 index 0000000000..168d5ee7bc --- /dev/null +++ b/PersistentStore/sqlite/l1test/CMakeLists.txt @@ -0,0 +1,51 @@ +# 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. + +cmake_minimum_required(VERSION 3.14) + +project(sqlitel1test) + +set(CMAKE_CXX_STANDARD 11) + +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip +) +FetchContent_MakeAvailable(googletest) + +find_package(WPEFramework REQUIRED) +find_package(${NAMESPACE}Plugins REQUIRED) + +add_executable(${PROJECT_NAME} + ../../Module.cpp + Store2Test.cpp + StoreCacheTest.cpp + StoreInspectorTest.cpp + StoreLimitTest.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + gmock_main + ${NAMESPACE}Plugins::${NAMESPACE}Plugins +) + +find_package(PkgConfig REQUIRED) +pkg_search_module(SQLITE REQUIRED sqlite3) +target_link_libraries(${PROJECT_NAME} PRIVATE ${SQLITE_LIBRARIES}) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/PersistentStore/sqlite/l1test/Store2NotificationMock.h b/PersistentStore/sqlite/l1test/Store2NotificationMock.h new file mode 100644 index 0000000000..4e422bf710 --- /dev/null +++ b/PersistentStore/sqlite/l1test/Store2NotificationMock.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class Store2NotificationMock : public WPEFramework::Exchange::IStore2::INotification { +public: + ~Store2NotificationMock() override = default; + MOCK_METHOD(void, ValueChanged, (const WPEFramework::Exchange::IStore2::ScopeType scope, const string& ns, const string& key, const string& value), (override)); + BEGIN_INTERFACE_MAP(Store2NotificationMock) + INTERFACE_ENTRY(INotification) + END_INTERFACE_MAP +}; diff --git a/PersistentStore/sqlite/l1test/Store2Test.cpp b/PersistentStore/sqlite/l1test/Store2Test.cpp new file mode 100644 index 0000000000..9d30c9471d --- /dev/null +++ b/PersistentStore/sqlite/l1test/Store2Test.cpp @@ -0,0 +1,182 @@ +#include +#include + +#include "../Store2.h" +#include "Store2NotificationMock.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::Invoke; +using ::testing::Le; +using ::testing::NiceMock; +using ::testing::Test; +using ::WPEFramework::Exchange::IStore2; +using ::WPEFramework::Plugin::Sqlite::Store2; + +const auto kPath = "/tmp/persistentstore/sqlite/l1test/store2test"; +const auto kMaxSize = 100; +const auto kMaxValue = 10; +const auto kLimit = 50; +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; +const auto kTtl = 2; +const auto kNoTtl = 0; +const auto kScope = IStore2::ScopeType::DEVICE; +const auto kEmpty = ""; +const auto kOversize = "this is too large"; +const auto kUnknown = "unknown"; + +class AStore2 : public Test { +protected: + IStore2* store2; + AStore2() + : store2(WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit)) + { + } + ~AStore2() override + { + store2->Release(); + } +}; + +TEST_F(AStore2, DoesNotSetValueWhenNamespaceEmpty) +{ + EXPECT_THAT(store2->SetValue(kScope, kEmpty, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStore2, DoesNotSetValueWhenKeyEmpty) +{ + EXPECT_THAT(store2->SetValue(kScope, kAppId, kEmpty, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStore2, DoesNotSetValueWhenNamespaceOversize) +{ + EXPECT_THAT(store2->SetValue(kScope, kOversize, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStore2, DoesNotSetValueWhenKeyOversize) +{ + EXPECT_THAT(store2->SetValue(kScope, kAppId, kOversize, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStore2, DoesNotSetValueWhenValueOversize) +{ + EXPECT_THAT(store2->SetValue(kScope, kAppId, kKey, kOversize, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStore2, DoesNotGetValueWhenNamespaceUnknown) +{ + string value; + uint32_t ttl; + EXPECT_THAT(store2->GetValue(kScope, kUnknown, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_NOT_EXIST)); +} + +TEST_F(AStore2, DeletesKeyWhenNamespaceUnknown) +{ + EXPECT_THAT(store2->DeleteKey(kScope, kUnknown, kKey), Eq(WPEFramework::Core::ERROR_NONE)); +} + +TEST_F(AStore2, DeletesNamespaceWhenNamespaceUnknown) +{ + EXPECT_THAT(store2->DeleteNamespace(kScope, kUnknown), Eq(WPEFramework::Core::ERROR_NONE)); +} + +TEST_F(AStore2, SetsValueWhenValueEmpty) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kEmpty, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + string value; + uint32_t ttl; + ASSERT_THAT(store2->GetValue(kScope, kAppId, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kEmpty)); + EXPECT_THAT(ttl, Eq(kNoTtl)); +} + +TEST_F(AStore2, GetsValueWhenTtlNotExpired) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kTtl), Eq(WPEFramework::Core::ERROR_NONE)); + string value; + uint32_t ttl; + ASSERT_THAT(store2->GetValue(kScope, kAppId, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kValue)); + EXPECT_THAT(ttl, Le(kTtl)); + EXPECT_THAT(ttl, Gt(kNoTtl)); +} + +TEST_F(AStore2, DoesNotGetValueWhenTtlExpired) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kTtl), Eq(WPEFramework::Core::ERROR_NONE)); + WPEFramework::Core::Event lock(false, true); + lock.Lock(kTtl * WPEFramework::Core::Time::MilliSecondsPerSecond); + string value; + uint32_t ttl; + EXPECT_THAT(store2->GetValue(kScope, kAppId, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_UNKNOWN_KEY)); +} + +TEST_F(AStore2, ValueChangedWhenSetValue) +{ + class Store2Notification : public NiceMock { + public: + Store2Notification() + { + EXPECT_CALL(*this, ValueChanged(_, _, _, _)) + .WillRepeatedly(Invoke( + [](const IStore2::ScopeType scope, const string& ns, const string& key, const string& value) { + EXPECT_THAT(scope, Eq(kScope)); + EXPECT_THAT(ns, Eq(kAppId)); + EXPECT_THAT(key, Eq(kKey)); + EXPECT_THAT(value, Eq(kValue)); + return WPEFramework::Core::ERROR_NONE; + })); + } + }; + WPEFramework::Core::Sink sink; + store2->Register(&sink); + EXPECT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + store2->Unregister(&sink); +} + +TEST_F(AStore2, DoesNotGetValueWhenKeyUnknown) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + string value; + uint32_t ttl; + EXPECT_THAT(store2->GetValue(kScope, kAppId, kUnknown, value, ttl), Eq(WPEFramework::Core::ERROR_UNKNOWN_KEY)); +} + +TEST_F(AStore2, DeletesKeyWhenKeyUnknown) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteKey(kScope, kAppId, kUnknown), Eq(WPEFramework::Core::ERROR_NONE)); +} + +TEST_F(AStore2, DeletesKey) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->DeleteKey(kScope, kAppId, kKey), Eq(WPEFramework::Core::ERROR_NONE)); + string value; + uint32_t ttl; + EXPECT_THAT(store2->GetValue(kScope, kAppId, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_UNKNOWN_KEY)); +} + +TEST_F(AStore2, DeletesNamespace) +{ + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->DeleteNamespace(kScope, kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + string value; + uint32_t ttl; + EXPECT_THAT(store2->GetValue(kScope, kAppId, kKey, value, ttl), Eq(WPEFramework::Core::ERROR_NOT_EXIST)); +} + +TEST_F(AStore2, DoesNotSetValueWhenReachedMaxSize) +{ + ASSERT_THAT(store2->DeleteNamespace(kScope, kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "8InMXXU4hM", "YWKN74ODMf", "N0ed2C2h4n", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "XhrICnuerw", "jPKODBDk5K", "d3BarkA5xF", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "WNeBknDDI2", "GC96ZN6Fuq", "IBF2E1MLQh", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "8InMXXU4hM"), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "XhrICnuerw"), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "WNeBknDDI2"), Eq(WPEFramework::Core::ERROR_NONE)); +} diff --git a/PersistentStore/sqlite/l1test/StoreCacheTest.cpp b/PersistentStore/sqlite/l1test/StoreCacheTest.cpp new file mode 100644 index 0000000000..5d2af5fd1d --- /dev/null +++ b/PersistentStore/sqlite/l1test/StoreCacheTest.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include "../StoreCache.h" + +using ::testing::Eq; +using ::testing::Test; +using ::WPEFramework::Exchange::IStoreCache; +using ::WPEFramework::Plugin::Sqlite::StoreCache; + +const auto kPath = "/tmp/persistentstore/sqlite/l1test/storecachetest"; + +class AStoreCache : public Test { +protected: + IStoreCache* cache; + AStoreCache() + : cache(WPEFramework::Core::Service::Create(kPath)) + { + } + ~AStoreCache() override + { + cache->Release(); + } +}; + +TEST_F(AStoreCache, FlushesCache) +{ + EXPECT_THAT(cache->FlushCache(), Eq(WPEFramework::Core::ERROR_NONE)); +} diff --git a/PersistentStore/sqlite/l1test/StoreInspectorTest.cpp b/PersistentStore/sqlite/l1test/StoreInspectorTest.cpp new file mode 100644 index 0000000000..ec3dd3866f --- /dev/null +++ b/PersistentStore/sqlite/l1test/StoreInspectorTest.cpp @@ -0,0 +1,96 @@ +#include +#include + +#include "../Store2.h" +#include "../StoreInspector.h" + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::Test; +using ::WPEFramework::Exchange::IStore2; +using ::WPEFramework::Exchange::IStoreInspector; +using ::WPEFramework::Plugin::Sqlite::Store2; +using ::WPEFramework::Plugin::Sqlite::StoreInspector; +using ::WPEFramework::RPC::IStringIterator; + +const auto kPath = "/tmp/persistentstore/sqlite/l1test/storeinspectortest"; +const auto kMaxSize = 100; +const auto kMaxValue = 10; +const auto kLimit = 50; +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; +const auto kNoTtl = 0; +const auto kScope = IStoreInspector::ScopeType::DEVICE; +const auto kUnknown = "unknown"; + +class AStoreInspector : public Test { +protected: + IStoreInspector* inspector; + AStoreInspector() + : inspector(WPEFramework::Core::Service::Create(kPath)) + { + } + ~AStoreInspector() override + { + inspector->Release(); + } +}; + +TEST_F(AStoreInspector, GetsKeysWhenNamespaceUnknown) +{ + IStringIterator* it; + ASSERT_THAT(inspector->GetKeys(kScope, kUnknown, it), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(it, NotNull()); + string element; + EXPECT_THAT(it->Next(element), IsFalse()); + it->Release(); +} + +TEST_F(AStoreInspector, GetsKeys) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + store2->Release(); + IStringIterator* it; + ASSERT_THAT(inspector->GetKeys(kScope, kAppId, it), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(it, NotNull()); + string element; + ASSERT_THAT(it->Next(element), IsTrue()); + EXPECT_THAT(element, Eq(kKey)); + EXPECT_THAT(it->Next(element), IsFalse()); + it->Release(); +} + +TEST_F(AStoreInspector, GetsNamespaces) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + store2->Release(); + IStringIterator* it; + ASSERT_THAT(inspector->GetNamespaces(kScope, it), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(it, NotNull()); + string element; + ASSERT_THAT(it->Next(element), IsTrue()); + EXPECT_THAT(element, Eq(kAppId)); + EXPECT_THAT(it->Next(element), IsFalse()); + it->Release(); +} + +TEST_F(AStoreInspector, GetsStorageSizes) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + store2->Release(); + IStoreInspector::INamespaceSizeIterator* it; + ASSERT_THAT(inspector->GetStorageSizes(kScope, it), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(it, NotNull()); + IStoreInspector::NamespaceSize element; + ASSERT_THAT(it->Next(element), IsTrue()); + EXPECT_THAT(element.ns, Eq(kAppId)); + EXPECT_THAT(element.size, Eq(strlen(kKey) + strlen(kValue))); + EXPECT_THAT(it->Next(element), IsFalse()); + it->Release(); +} diff --git a/PersistentStore/sqlite/l1test/StoreLimitTest.cpp b/PersistentStore/sqlite/l1test/StoreLimitTest.cpp new file mode 100644 index 0000000000..49409e91d3 --- /dev/null +++ b/PersistentStore/sqlite/l1test/StoreLimitTest.cpp @@ -0,0 +1,112 @@ +#include +#include + +#include "../Store2.h" +#include "../StoreLimit.h" + +using ::testing::Eq; +using ::testing::Test; +using ::WPEFramework::Exchange::IStore2; +using ::WPEFramework::Exchange::IStoreLimit; +using ::WPEFramework::Plugin::Sqlite::Store2; +using ::WPEFramework::Plugin::Sqlite::StoreLimit; + +const auto kPath = "/tmp/persistentstore/sqlite/l1test/storelimittest"; +const auto kMaxSize = 100; +const auto kMaxValue = 10; +const auto kLimit = 50; +const auto kValue = "value_1"; +const auto kKey = "key_1"; +const auto kAppId = "app_id_1"; +const auto kNoTtl = 0; +const auto kScope = IStoreLimit::ScopeType::DEVICE; +const auto kEmpty = ""; +const auto kOversize = "this is too large"; +const auto kUnknown = "unknown"; +const auto kLimit20 = 20; +const auto kLimit30 = 30; +const auto kLimit40 = 40; + +class AStoreLimit : public Test { +protected: + IStoreLimit* limit; + AStoreLimit() + : limit(WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue)) + { + } + ~AStoreLimit() override + { + limit->Release(); + } +}; + +TEST_F(AStoreLimit, DoesNotGetNamespaceStorageLimitWhenNamespaceUnknown) +{ + uint32_t value; + EXPECT_THAT(limit->GetNamespaceStorageLimit(kScope, kUnknown, value), Eq(WPEFramework::Core::ERROR_NOT_EXIST)); +} + +TEST_F(AStoreLimit, DoesNotSetNamespaceStorageLimitWhenNamespaceEmpty) +{ + EXPECT_THAT(limit->SetNamespaceStorageLimit(kScope, kEmpty, kLimit20), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStoreLimit, DoesNotSetNamespaceStorageLimitWhenNamespaceOversize) +{ + EXPECT_THAT(limit->SetNamespaceStorageLimit(kScope, kOversize, kLimit20), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); +} + +TEST_F(AStoreLimit, SetsNamespaceStorageLimit) +{ + ASSERT_THAT(limit->SetNamespaceStorageLimit(kScope, kAppId, kLimit20), Eq(WPEFramework::Core::ERROR_NONE)); + uint32_t value; + ASSERT_THAT(limit->GetNamespaceStorageLimit(kScope, kAppId, value), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kLimit20)); +} + +TEST_F(AStoreLimit, SetsNamespaceStorageLimitWhenAlreadySet) +{ + ASSERT_THAT(limit->SetNamespaceStorageLimit(kScope, kAppId, kLimit30), Eq(WPEFramework::Core::ERROR_NONE)); + uint32_t value; + ASSERT_THAT(limit->GetNamespaceStorageLimit(kScope, kAppId, value), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kLimit30)); + ASSERT_THAT(limit->SetNamespaceStorageLimit(kScope, kAppId, kLimit40), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(limit->GetNamespaceStorageLimit(kScope, kAppId, value), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(value, Eq(kLimit40)); +} + +TEST_F(AStoreLimit, DoesNotSetNamespaceStorageLimitWhenReachedMaxSize) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->DeleteNamespace(kScope, kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "8InMXXU4hM", "YWKN74ODMf", "N0ed2C2h4n", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "XhrICnuerw", "jPKODBDk5K", "d3BarkA5xF", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "WNeBknDDI2", "GC96ZN6Fuq", "IBF2E1MLQh", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, "V92", "R1R", "rHk", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(limit->SetNamespaceStorageLimit(kScope, kAppId, kLimit), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "8InMXXU4hM"), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "XhrICnuerw"), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "WNeBknDDI2"), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(store2->DeleteNamespace(kScope, "V92"), Eq(WPEFramework::Core::ERROR_NONE)); + store2->Release(); +} + +TEST_F(AStoreLimit, EnforcesSetValueToFailWhenReachedDefaultLimit) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->DeleteNamespace(kScope, kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, kAppId, "YWKN74ODMf", "N0ed2C2h4n", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, kAppId, "jPKODBDk5K", "d3BarkA5xF", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); + store2->Release(); +} + +TEST_F(AStoreLimit, EnforcesSetValueToFailWhenReachedLimit) +{ + auto store2 = WPEFramework::Core::Service::Create(kPath, kMaxSize, kMaxValue, kLimit); + ASSERT_THAT(store2->DeleteNamespace(kScope, kAppId), Eq(WPEFramework::Core::ERROR_NONE)); + EXPECT_THAT(limit->SetNamespaceStorageLimit(kScope, kAppId, kLimit20), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, kAppId, "YWKN74ODMf", "N0ed2C2h4n", kNoTtl), Eq(WPEFramework::Core::ERROR_NONE)); + ASSERT_THAT(store2->SetValue(kScope, kAppId, kKey, kValue, kNoTtl), Eq(WPEFramework::Core::ERROR_INVALID_INPUT_LENGTH)); + store2->Release(); +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 7210bee61b..6d7a81bdbc 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -51,7 +51,6 @@ set_source_files_properties( PROPERTIES COMPILE_FLAGS "-fexceptions") include_directories(../LocationSync - ../PersistentStore ../SecurityAgent ../DeviceIdentification ../DeviceDiagnostics @@ -91,7 +90,6 @@ include_directories(../LocationSync ../SystemAudioPlayer ) link_directories(../LocationSync - ../PersistentStore ../SecurityAgent ../DeviceIdentification ../DeviceDiagnostics @@ -136,7 +134,6 @@ target_link_libraries(${PROJECT_NAME} ${NAMESPACE}DataCapture ${NAMESPACE}DeviceDiagnostics ${NAMESPACE}LocationSync - ${NAMESPACE}PersistentStore ${NAMESPACE}SecurityAgent ${NAMESPACE}DeviceIdentification ${NAMESPACE}FrameRate diff --git a/Tests/tests/test_CountingLock.cpp b/Tests/tests/test_CountingLock.cpp deleted file mode 100644 index 4eb2e2ea93..0000000000 --- a/Tests/tests/test_CountingLock.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 - -#include "CountingLock.h" - -#include "WorkerPoolImplementation.h" - -using namespace WPEFramework; - -namespace { -Plugin::CountingLock Lock; - -Core::Event Job1Lock(false, true); -Core::Event Job1WillUnlock(false, true); -Core::Event Job2Lock(false, true); -Core::Event Job2WillUnlock(false, true); -Core::Event Job3TryLock(false, true); -Core::Event Job3Lock(false, true); - -struct Job1 { - void Dispatch() - { - Plugin::CountingLockSync lockSync(Lock); - Job1Lock.SetEvent(); - EXPECT_EQ(Core::ERROR_NONE, Job2Lock.Lock()); // wait Job2 lock - EXPECT_EQ(Core::ERROR_NONE, Job3TryLock.Lock()); // wait Job3 start trying - EXPECT_EQ(Core::ERROR_TIMEDOUT, Job3Lock.Lock(100)); // expect no Job3 lock in 100 ms - Job1WillUnlock.SetEvent(); - } -}; - -struct Job2 { - void Dispatch() - { - Plugin::CountingLockSync lockSync(Lock); - Job2Lock.SetEvent(); - EXPECT_EQ(Core::ERROR_NONE, Job1Lock.Lock()); // wait Job1 lock - EXPECT_EQ(Core::ERROR_NONE, Job3TryLock.Lock()); // wait Job3 start trying - EXPECT_EQ(Core::ERROR_TIMEDOUT, Job3Lock.Lock(100)); // expect no Job3 lock in 100 ms - Job2WillUnlock.SetEvent(); - } -}; - -struct Job3 { - void Dispatch() - { - EXPECT_EQ(Core::ERROR_NONE, Job1Lock.Lock()); // wait Job1 Lock - EXPECT_EQ(Core::ERROR_NONE, Job2Lock.Lock()); // wait Job2 Lock - Job3TryLock.SetEvent(); - Plugin::CountingLockSync lockSync(Lock, 0); - Job3Lock.SetEvent(); - } -}; -} - -class CountingLockTest : public ::testing::Test { -protected: - Core::ProxyType workerPool; - - CountingLockTest() - : workerPool(Core::ProxyType::Create( - 5, Core::Thread::DefaultStackSize(), 16)) - { - Core::IWorkerPool::Assign(&(*workerPool)); - workerPool->Run(); - } - virtual ~CountingLockTest() - { - Core::IWorkerPool::Assign(nullptr); - workerPool.Release(); - } -}; - -TEST_F(CountingLockTest, countingLockTest) -{ - Job1 job1; - Job2 job2; - Job3 job3; - - auto job1Activity = Core::ProxyType>::Create(job1); - auto job2Activity = Core::ProxyType>::Create(job2); - auto job3Activity = Core::ProxyType>::Create(job3); - - job1Activity->Submit(); - job2Activity->Submit(); - job3Activity->Submit(); - - EXPECT_EQ(Core::ERROR_NONE, Job3Lock.Lock()); - EXPECT_EQ(Core::ERROR_NONE, Job1WillUnlock.Lock()); - EXPECT_EQ(Core::ERROR_NONE, Job2WillUnlock.Lock()); - - job1Activity->Revoke(); - job2Activity->Revoke(); - job3Activity->Revoke(); - - job1Activity.Release(); - job2Activity.Release(); - job3Activity.Release(); -} diff --git a/Tests/tests/test_PersistentStore.cpp b/Tests/tests/test_PersistentStore.cpp deleted file mode 100644 index b643a04acb..0000000000 --- a/Tests/tests/test_PersistentStore.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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 -#include - -#include "PersistentStore.h" - -#include "FactoriesImplementation.h" -#include "ServiceMock.h" - -namespace { -class PersistentStoreMock : public WPEFramework::Plugin::PersistentStore { -public: - virtual ~PersistentStoreMock() = default; - - MOCK_METHOD(std::vector, LegacyLocations, (), (const, override)); -}; -} - -using namespace WPEFramework; - -using ::testing::NiceMock; - -class PersistentStoreTest : public ::testing::Test { -protected: - Core::ProxyType plugin; - Core::JSONRPC::Handler& handler; - Core::JSONRPC::Connection connection; - string response; - - PersistentStoreTest() - : plugin(Core::ProxyType::Create()) - , handler(*plugin) - , connection(1, 0) - { - } - virtual ~PersistentStoreTest() = default; -}; - -class PersistentStoreInitializedTest : public PersistentStoreTest { -protected: - NiceMock service; - - PersistentStoreInitializedTest() - : PersistentStoreTest() - { - ON_CALL(service, ConfigLine()) - .WillByDefault( - ::testing::Return("{" - "\"path\":\"/tmp/rdkservicestore\"," - "\"key\":null," - "\"maxsize\":20," - "\"maxvalue\":10" - "}")); - ON_CALL(*plugin, LegacyLocations) - .WillByDefault(::testing::Return(std::vector())); - - EXPECT_EQ(string(""), plugin->Initialize(&service)); - } - virtual ~PersistentStoreInitializedTest() override - { - plugin->Deinitialize(&service); - } -}; - -class PersistentStoreInitializedEventTest : public PersistentStoreInitializedTest { -protected: - Core::JSONRPC::Message message; - NiceMock factoriesImplementation; - PluginHost::IDispatcher* dispatcher; - - PersistentStoreInitializedEventTest() - : PersistentStoreInitializedTest() - { - PluginHost::IFactories::Assign(&factoriesImplementation); - - dispatcher = static_cast( - plugin->QueryInterface(PluginHost::IDispatcher::ID)); - dispatcher->Activate(&service); - } - virtual ~PersistentStoreInitializedEventTest() override - { - dispatcher->Deactivate(); - dispatcher->Release(); - - PluginHost::IFactories::Assign(nullptr); - } -}; - -TEST_F(PersistentStoreTest, registeredMethods) -{ - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("setValue"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("getValue"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("deleteKey"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("deleteNamespace"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("getKeys"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("getNamespaces"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("getStorageSize"))); - EXPECT_EQ(Core::ERROR_NONE, handler.Exists(_T("flushCache"))); -} - -TEST_F(PersistentStoreTest, paramsMissing) -{ - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("setValue"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getValue"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getKeys"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteKey"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteNamespace"), _T("{}"), response)); -} - -TEST_F(PersistentStoreTest, paramsEmpty) -{ - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"\",\"key\":\"\",\"value\":\"\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getValue"), _T("{\"namespace\":\"\",\"key\":\"\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getKeys"), _T("{\"namespace\":\"\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteKey"), _T("{\"namespace\":\"\",\"key\":\"\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteNamespace"), _T("{\"namespace\":\"\"}"), response)); -} - -TEST_F(PersistentStoreTest, notInitialized) -{ - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"a\",\"value\":\"1\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getValue"), _T("{\"namespace\":\"test\",\"key\":\"a\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getNamespaces"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getStorageSize"), _T("{}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("getKeys"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteKey"), _T("{\"namespace\":\"test\",\"key\":\"a\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("deleteNamespace"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("flushCache"), _T("{}"), response)); -} - -TEST_F(PersistentStoreInitializedEventTest, jsonRpc) -{ - Core::Event onValueChanged(false, true); - - EXPECT_CALL(service, Submit(::testing::_, ::testing::_)) - .Times(1) - .WillOnce(::testing::Invoke( - [&](const uint32_t, const Core::ProxyType& json) { - string text; - EXPECT_TRUE(json->ToString(text)); - EXPECT_EQ(text, string(_T("{" - "\"jsonrpc\":\"2.0\"," - "\"method\":\"org.rdk.PersistentStore.onValueChanged\"," - "\"params\":{\"namespace\":\"test\",\"key\":\"a\",\"value\":\"1\"}" - "}"))); - - onValueChanged.SetEvent(); - - return Core::ERROR_NONE; - })); - - handler.Subscribe(0, _T("onValueChanged"), _T("org.rdk.PersistentStore"), message); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"a\",\"value\":\"1\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - - EXPECT_EQ(Core::ERROR_NONE, onValueChanged.Lock()); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("getValue"), _T("{\"namespace\":\"test\",\"key\":\"a\"}"), response)); - EXPECT_EQ(response, _T("{\"value\":\"1\",\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("getNamespaces"), _T("{}"), response)); - EXPECT_EQ(response, _T("{\"namespaces\":[\"test\"],\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("getStorageSize"), _T("{}"), response)); - EXPECT_EQ(response, _T("{\"namespaceSizes\":{\"test\":2},\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("getKeys"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(response, _T("{\"keys\":[\"a\"],\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("deleteKey"), _T("{\"namespace\":\"test\",\"key\":\"a\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("deleteNamespace"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("flushCache"), _T("{}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - - handler.Unsubscribe(0, _T("onValueChanged"), _T("org.rdk.PersistentStore"), message); -} - -TEST_F(PersistentStoreInitializedTest, maxValue) -{ - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"a\",\"value\":\"123456789123456789\"}"), response)); -} - -TEST_F(PersistentStoreInitializedEventTest, onStorageExceeded) -{ - Core::Event onStorageExceeded(false, true); - - EXPECT_CALL(service, Submit(::testing::_, ::testing::_)) - .Times(1) - .WillOnce(::testing::Invoke( - [&](const uint32_t, const Core::ProxyType& json) { - string text; - EXPECT_TRUE(json->ToString(text)); - EXPECT_EQ(text, string(_T("{" - "\"jsonrpc\":\"2.0\"," - "\"method\":\"org.rdk.PersistentStore.onStorageExceeded\"," - "\"params\":{}" - "}"))); - - onStorageExceeded.SetEvent(); - - return Core::ERROR_NONE; - })); - - handler.Subscribe(0, _T("onStorageExceeded"), _T("org.rdk.PersistentStore"), message); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"a\",\"value\":\"123456789\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"b\",\"value\":\"12345678\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - EXPECT_EQ(Core::ERROR_GENERAL, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"c\",\"value\":\"1\"}"), response)); - - EXPECT_EQ(Core::ERROR_NONE, onStorageExceeded.Lock()); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("deleteNamespace"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - - handler.Unsubscribe(0, _T("onStorageExceeded"), _T("org.rdk.PersistentStore"), message); -} - -TEST_F(PersistentStoreTest, legacyLocation) -{ - ServiceMock service; - - EXPECT_CALL(service, ConfigLine()) - .Times(2) - .WillOnce( - ::testing::Return("{" - "\"path\":\"/tmp/path/to/legacy/location/store\"," - "\"key\":null," - "\"maxsize\":20," - "\"maxvalue\":10" - "}")) - .WillOnce( - ::testing::Return("{" - "\"path\":\"/tmp/rdkservicestore\"," - "\"key\":null," - "\"maxsize\":20," - "\"maxvalue\":10" - "}")); - EXPECT_CALL(*plugin, LegacyLocations) - .Times(2) - .WillOnce(::testing::Return(std::vector())) - .WillOnce(::testing::Return(std::vector{ "/tmp/path/to/legacy/location/store" })); - - EXPECT_EQ(string(""), plugin->Initialize(&service)); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("setValue"), _T("{\"namespace\":\"test\",\"key\":\"d\",\"value\":\"abc\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - - plugin->Deinitialize(&service); - - if (Core::File(string("/tmp/rdkservicestore")).Exists()) { - EXPECT_TRUE(Core::File(string("/tmp/rdkservicestore")).Destroy()); - EXPECT_FALSE(Core::File(string("/tmp/rdkservicestore")).Exists()); - } - - EXPECT_EQ(string(""), plugin->Initialize(&service)); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("getValue"), _T("{\"namespace\":\"test\",\"key\":\"d\"}"), response)); - EXPECT_EQ(response, _T("{\"value\":\"abc\",\"success\":true}")); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("deleteNamespace"), _T("{\"namespace\":\"test\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":true}")); - - plugin->Deinitialize(&service); -} diff --git a/Tests/tests/test_SqliteStore.cpp b/Tests/tests/test_SqliteStore.cpp deleted file mode 100644 index 0338c68322..0000000000 --- a/Tests/tests/test_SqliteStore.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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 -#include - -#include "SqliteStore.h" - -using namespace WPEFramework; - -namespace { -class StoreNotificationMock : public WPEFramework::Exchange::IStore::INotification { -public: - virtual ~StoreNotificationMock() = default; - - MOCK_METHOD(void, ValueChanged, (const string&, const string&, const string&), (override)); - MOCK_METHOD(void, StorageExceeded, (), (override)); - - BEGIN_INTERFACE_MAP(StoreNotificationMock) - INTERFACE_ENTRY(WPEFramework::Exchange::IStore::INotification) - END_INTERFACE_MAP -}; -} - -class SqliteStoreTest : public ::testing::Test { -protected: - Core::ProxyType store; - - SqliteStoreTest() - : store(Core::ProxyType::Create()) - { - } - virtual ~SqliteStoreTest() = default; -}; - -class SqliteStoreNotificationTest : public SqliteStoreTest { -protected: - Core::ProxyType notification; - - SqliteStoreNotificationTest() - : SqliteStoreTest() - , notification(Core::ProxyType::Create()) - { - EXPECT_EQ(Core::ERROR_NONE, store->Register(&*notification)); - } - virtual ~SqliteStoreNotificationTest() override - { - EXPECT_EQ(Core::ERROR_NONE, store->Unregister(&*notification)); - } -}; - -TEST_F(SqliteStoreTest, interface) -{ - string value; - std::vector namespaces; - std::map namespaceSizes; - std::vector keys; - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "1")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "a", value)); - EXPECT_EQ(value, "1"); - EXPECT_EQ(Core::ERROR_NONE, store->GetNamespaces(namespaces)); - EXPECT_EQ(namespaces.size(), 1); - EXPECT_EQ(namespaces.at(0), "test"); - EXPECT_EQ(Core::ERROR_NONE, store->GetStorageSize(namespaceSizes)); - EXPECT_EQ(namespaceSizes.size(), 1); - EXPECT_EQ(namespaceSizes.at("test"), 2); - EXPECT_EQ(Core::ERROR_NONE, store->GetKeys("test", keys)); - EXPECT_EQ(keys.size(), 1); - EXPECT_EQ(keys.at(0), "a"); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteKey("test", "a")); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteNamespace("test")); - EXPECT_EQ(Core::ERROR_NONE, store->FlushCache()); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreNotificationTest, valueChanged) -{ - EXPECT_CALL(*notification, ValueChanged("test", "a", "12345")) - .Times(1) - .WillOnce(::testing::Return()); - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "12345")); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteNamespace("test")); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreNotificationTest, storageExceeded) -{ - string value; - - EXPECT_CALL(*notification, ValueChanged("test", "a", "123456789123456789")) - .Times(1) - .WillOnce(::testing::Return()); - EXPECT_CALL(*notification, StorageExceeded()) - .Times(1) - .WillOnce(::testing::Return()); - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 20)); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "123456789123456789")); - EXPECT_EQ(Core::ERROR_WRITE_ERROR, store->SetValue("test", "b", "1")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "a", value)); - EXPECT_EQ(value, "123456789123456789"); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteNamespace("test")); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreTest, maxValue) -{ - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_INVALID_INPUT_LENGTH, store->SetValue("test", "a", "123456789123456789")); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreTest, corrupt) -{ - string value; - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "1")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "a", value)); - EXPECT_EQ(value, "1"); - - EXPECT_TRUE(Core::File(string("/tmp/rdkservicestore")).Destroy()); - EXPECT_FALSE(Core::File(string("/tmp/rdkservicestore")).Exists()); - - EXPECT_EQ(Core::ERROR_NONE, store->FlushCache()); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "b", "1")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "b", value)); - EXPECT_EQ(value, "1"); - EXPECT_EQ(Core::ERROR_GENERAL, store->GetValue("test", "a", value)); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteNamespace("test")); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreTest, replaceValue) -{ - string value; - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "1")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "a", value)); - EXPECT_EQ(value, "1"); - EXPECT_EQ(Core::ERROR_NONE, store->SetValue("test", "a", "2")); - EXPECT_EQ(Core::ERROR_NONE, store->GetValue("test", "a", value)); - EXPECT_EQ(value, "2"); - EXPECT_EQ(Core::ERROR_NONE, store->DeleteNamespace("test")); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} - -TEST_F(SqliteStoreTest, unknownKey) -{ - string value; - - EXPECT_EQ(Core::ERROR_NONE, store->Open("/tmp/rdkservicestore", "", 20, 10)); - EXPECT_EQ(Core::ERROR_GENERAL, store->GetValue("test", "unknown", value)); - EXPECT_EQ(Core::ERROR_NONE, store->Term()); -} diff --git a/tests.cmake b/tests.cmake index 01e959b16a..15196b6d46 100644 --- a/tests.cmake +++ b/tests.cmake @@ -183,7 +183,6 @@ set(LIBOPKG_INCLUDE_DIRS ${LIBOPKG_INCLUDE_DIRS} CACHE PATH "Path to LIBOPKG inc set(PLUGIN_DATACAPTURE ON) set(PLUGIN_DEVICEDIAGNOSTICS ON) set(PLUGIN_LOCATIONSYNC ON) -set(PLUGIN_PERSISTENTSTORE ON) set(PLUGIN_TIMER ON) set(PLUGIN_SECURITYAGENT ON) set(PLUGIN_DEVICEIDENTIFICATION ON)