From e3d78160e1f4d2d449ac91f0cbf2ea7711857ca6 Mon Sep 17 00:00:00 2001 From: Tennessee Carmel-Veilleux Date: Mon, 14 Mar 2022 13:49:18 -0400 Subject: [PATCH] Fix PersistentStorageDelegate 0-storage behavior (#16139) * Fix PersistentStorageDelegate 0-storage behavior - Make Server's PersistentStorageDelegate and TestPersistentStorage delegate properly handle nullptr input for zero-length. - Document behavior expected of PersistentStorageDelegate interface Issue #16130 * Add unit tests for TestPersistentStorageDelegate * Address review comments * Restyled by clang-format * Apply suggestions from code review Co-authored-by: Boris Zbarsky Co-authored-by: Restyled.io Co-authored-by: Boris Zbarsky --- src/app/server/Server.h | 34 ++- src/app/tests/TestBindingTable.cpp | 12 +- src/lib/core/CHIPPersistentStorageDelegate.h | 64 ++++-- src/lib/support/DefaultStorageKeyAllocator.h | 5 +- .../support/TestPersistentStorageDelegate.h | 85 ++++++-- src/lib/support/tests/BUILD.gn | 1 + .../TestTestPersistentStorageDelegate.cpp | 199 ++++++++++++++++++ 7 files changed, 352 insertions(+), 48 deletions(-) create mode 100644 src/lib/support/tests/TestTestPersistentStorageDelegate.cpp diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 642e7ea398551a..e108629c87f694 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -115,19 +115,51 @@ class Server { CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override { + uint8_t emptyPlaceholder = 0; + if (buffer == nullptr) + { + if (size != 0) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + else + { + // When size is zero, let's give a non-nullptr to the KVS backend + buffer = &emptyPlaceholder; + } + } + size_t bytesRead = 0; CHIP_ERROR err = DeviceLayer::PersistedStorage::KeyValueStoreMgr().Get(key, buffer, size, &bytesRead); + // Update size only if it made sense + if ((CHIP_ERROR_BUFFER_TOO_SMALL == err) || (CHIP_NO_ERROR == err)) + { + size = CanCastTo(bytesRead) ? static_cast(bytesRead) : 0; + } + if (err == CHIP_NO_ERROR) { ChipLogProgress(AppServer, "Retrieved from server storage: %s", key); } - size = static_cast(bytesRead); + return err; } CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override { + uint8_t placeholderForEmpty = 0; + if (value == nullptr) + { + if (size == 0) + { + value = &placeholderForEmpty; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } return DeviceLayer::PersistedStorage::KeyValueStoreMgr().Put(key, value, size); } diff --git a/src/app/tests/TestBindingTable.cpp b/src/app/tests/TestBindingTable.cpp index 389d5820dc92d1..c8203c962bdd02 100644 --- a/src/app/tests/TestBindingTable.cpp +++ b/src/app/tests/TestBindingTable.cpp @@ -150,26 +150,26 @@ void TestPersistentStorage(nlTestSuite * aSuite, void * aContext) VerifyRestored(aSuite, testStorage, expected); // Verify storage untouched if add fails - testStorage.AddErrorKey(key.BindingTableEntry(4)); + testStorage.AddPoisonKey(key.BindingTableEntry(4)); NL_TEST_ASSERT(aSuite, table.Add(EmberBindingTableEntry::ForNode(4, 4, 0, 0, NullOptional)) != CHIP_NO_ERROR); VerifyRestored(aSuite, testStorage, expected); - testStorage.ClearErrorKey(); + testStorage.ClearPoisonKeys(); // Verify storage untouched if removing head fails - testStorage.AddErrorKey(key.BindingTable()); + testStorage.AddPoisonKey(key.BindingTable()); auto iter = table.begin(); NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) != CHIP_NO_ERROR); VerifyTableSame(aSuite, table, expected); - testStorage.ClearErrorKey(); + testStorage.ClearPoisonKeys(); VerifyRestored(aSuite, testStorage, expected); // Verify storage untouched if removing other nodes fails - testStorage.AddErrorKey(key.BindingTableEntry(0)); + testStorage.AddPoisonKey(key.BindingTableEntry(0)); iter = table.begin(); ++iter; NL_TEST_ASSERT(aSuite, table.RemoveAt(iter) != CHIP_NO_ERROR); VerifyTableSame(aSuite, table, expected); - testStorage.ClearErrorKey(); + testStorage.ClearPoisonKeys(); VerifyRestored(aSuite, testStorage, expected); // Verify removing head diff --git a/src/lib/core/CHIPPersistentStorageDelegate.h b/src/lib/core/CHIPPersistentStorageDelegate.h index f8640acb7d1c5b..df74b82b3d8e3e 100644 --- a/src/lib/core/CHIPPersistentStorageDelegate.h +++ b/src/lib/core/CHIPPersistentStorageDelegate.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2022 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,14 +18,22 @@ #pragma once -#include +#include #include +#include +#include namespace chip { class DLL_EXPORT PersistentStorageDelegate { public: + /** + * Maximum length of a key required by implementations. Any implementer of PersistentStorageDelegate + * must support keys AT LEAST this long. + */ + static constexpr size_t kKeyLengthMax = 32; + virtual ~PersistentStorageDelegate() {} /** @@ -38,35 +46,44 @@ class DLL_EXPORT PersistentStorageDelegate * Caller is responsible to take care of any special formatting needs (e.g. byte * order, null terminators, consistency checks or versioning). * + * This API allows for determining the size of a stored value. Whenever + * the passed `size` is smaller than needed and the key exists in storage, the error + * CHIP_ERROR_BUFFER_TOO_SMALL will be given, and the `size` will be updated to the + * size of the stored value. It is legal to use `nullptr` for `buffer` if `size` is 0. + * + * If a key is found and the `buffer`'s `size` is large enough, then the value will + * be copied to `buffer` and `size` will be updated to the actual size used. + * + * The easiest way to determine if a key exists (and the value's size if so) is to pass + * `size` of 0, which is always valid to do, and will return CHIP_ERROR_BUFFER_TOO_SMALL + * if the key exists and CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND if the + * key is not found. * * @param[in] key Key to lookup - * @param[out] buffer Value for the key - * @param[in, out] size Input value buffer size, output length of value. - * The output length could be larger than input value. In - * such cases, the user should allocate the buffer large - * enough (>= output length), and call the API again. + * @param[out] buffer Pointer to a buffer where the place the read value. + * @param[in, out] size Input is maximum buffer size, output updated to length of value. * - * @return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND the key is unknown. - * @return CHIP_ERROR_BUFFER_TOO_SMALL the provided buffer is not big - * enough. In this case "size" will - * indicate the needed buffer size. - * Some data may or may not be placed - * in "buffer" in this case; consumers - * should not rely on that behavior. - * CHIP_ERROR_BUFFER_TOO_SMALL combined - * with setting "size" to 0 means the - * actual size was too large to fit in - * uint16_t. + * @return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND the key is not found in storage. + * @return CHIP_ERROR_BUFFER_TOO_SMALL the provided buffer is not big enough. In this case + * "size" will indicate the needed buffer size. Some data + * may or may not be placed in "buffer" in this case; consumers + * should not rely on that behavior. CHIP_ERROR_BUFFER_TOO_SMALL + * combined with setting "size" to 0 means the actual size was + * too large to fit in uint16_t. */ virtual CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) = 0; /** * @brief - * Set the value for the key to a byte buffer. + * Set the value for the key to a byte buffer. Empty values can be stored + * with size == 0, in which case `value` may be nullptr. + * + * @param[in] key Key to set + * @param[in] value Pointer to bytes of value to be set. `value` can only be `nullptr` if size == 0. + * @param[in] size Size of the `value` to store. * - * @param[in] key Key to be set - * @param[in] value Value to be set - * @param[in] size Size of the Value + * @return CHIP_NO_ERROR on success, CHIP_INVALID_ARGUMENT on `value` being `nullptr` with size > 0, + * or another CHIP_ERROR value from implementation on failure. */ virtual CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) = 0; @@ -76,7 +93,8 @@ class DLL_EXPORT PersistentStorageDelegate * * @param[in] key Key to be deleted * - * @return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND the key is unknown. + * @return CHIP_NO_ERROR on success, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND the key is not found in storage, + * or another CHIP_ERROR value from implementation on failure. */ virtual CHIP_ERROR SyncDeleteKeyValue(const char * key) = 0; }; diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index b310f6451be665..d9df01301ef9b2 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -80,8 +81,6 @@ class DefaultStorageKeyAllocator static const char * OTAUpdateToken() { return "o/ut"; } private: - static const size_t kKeyLengthMax = 32; - // The ENFORCE_FORMAT args are "off by one" because this is a class method, // with an implicit "this" as first arg. const char * ENFORCE_FORMAT(2, 3) Format(const char * format, ...) @@ -93,7 +92,7 @@ class DefaultStorageKeyAllocator return mKeyName; } - char mKeyName[kKeyLengthMax + 1] = { 0 }; + char mKeyName[PersistentStorageDelegate::kKeyLengthMax + 1] = { 0 }; }; } // namespace chip diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h index d8f4a7f1371053..9ae5cae339950e 100644 --- a/src/lib/support/TestPersistentStorageDelegate.h +++ b/src/lib/support/TestPersistentStorageDelegate.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,16 @@ namespace chip { +/** + * Implementation of PersistentStorageDelegate suitable for unit tests, + * where persistence lasts for the object's lifetime and where all data is retained + * is memory. + * + * This version also has "poison keys" which, if accessed, yield an error. This can + * be used in unit tests to make sure a module making use of the PersistentStorageDelegate + * does not access some particular keys which should remain untouched by underlying + * logic. + */ class TestPersistentStorageDelegate : public PersistentStorageDelegate { public: @@ -37,52 +48,96 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override { - if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + if ((buffer == nullptr) && (size != 0)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // Making sure poison keys are not accessed + if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end()) { return CHIP_ERROR_PERSISTED_STORAGE_FAILED; } + bool contains = mStorage.find(key) != mStorage.end(); VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); std::vector & value = mStorage[key]; - uint16_t value_size = static_cast(value.size()); - VerifyOrReturnError(value_size <= size, CHIP_ERROR_BUFFER_TOO_SMALL); - - size = std::min(value_size, size); - memcpy(buffer, value.data(), size); - return CHIP_NO_ERROR; + size_t valueSize = value.size(); + if (size < valueSize) + { + size = CanCastTo(valueSize) ? static_cast(valueSize) : 0; + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + else + { + size = static_cast(valueSize); + memcpy(buffer, value.data(), size); + return CHIP_NO_ERROR; + } } CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override { - if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + // Make sure poison keys are not accessed + if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end()) { return CHIP_ERROR_PERSISTED_STORAGE_FAILED; } - const uint8_t * bytes = static_cast(value); - mStorage[key] = std::vector(bytes, bytes + size); - return CHIP_NO_ERROR; + + // Handle empty values + if (value == nullptr) + { + if (size == 0) + { + mStorage[key] = std::vector(); + return CHIP_NO_ERROR; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + // Handle non-empty values + else + { + const uint8_t * bytes = static_cast(value); + mStorage[key] = std::vector(bytes, bytes + size); + return CHIP_NO_ERROR; + } } CHIP_ERROR SyncDeleteKeyValue(const char * key) override { - if (mErrorKeys.find(std::string(key)) != mErrorKeys.end()) + // Make sure poison keys are not accessed + if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end()) { return CHIP_ERROR_PERSISTED_STORAGE_FAILED; } + bool contains = mStorage.find(key) != mStorage.end(); VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); mStorage.erase(key); return CHIP_NO_ERROR; } - void AddErrorKey(const std::string & key) { mErrorKeys.insert(key); } + /** + * @brief Adds a "poison key": a key that, if read/written, implies some bad + * behavior occurred. + * + * @param key - Poison key to add to the set. + */ + void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); } - void ClearErrorKey() { mErrorKeys.clear(); } + /** + * @brief Clear all "poison keys" + * + */ + void ClearPoisonKeys() { mPoisonKeys.clear(); } protected: std::map> mStorage; - std::set mErrorKeys; + std::set mPoisonKeys; }; } // namespace chip diff --git a/src/lib/support/tests/BUILD.gn b/src/lib/support/tests/BUILD.gn index a5d22ed00e064e..f7f6cd97ae3028 100644 --- a/src/lib/support/tests/BUILD.gn +++ b/src/lib/support/tests/BUILD.gn @@ -44,6 +44,7 @@ chip_test_suite("tests") { "TestSpan.cpp", "TestStateMachine.cpp", "TestStringBuilder.cpp", + "TestTestPersistentStorageDelegate.cpp", "TestThreadOperationalDataset.cpp", "TestTimeUtils.cpp", "TestTlvToJson.cpp", diff --git a/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp new file mode 100644 index 00000000000000..3f42dca88d5b12 --- /dev/null +++ b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp @@ -0,0 +1,199 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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 + +#include +#include + +using namespace chip; + +namespace { + +void TestBasicApi(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storage; + + uint8_t buf[16]; + uint16_t size = sizeof(buf); + + // Key not there + CHIP_ERROR err; + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, size == sizeof(buf)); + + err = storage.SyncDeleteKeyValue("roboto"); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + // Add basic key, read it back, erase it + const char * kStringValue1 = "abcd"; + err = storage.SyncSetKeyValue("roboto", kStringValue1, static_cast(strlen(kStringValue1))); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue1)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], kStringValue1, strlen(kStringValue1))); + + err = storage.SyncDeleteKeyValue("roboto"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, size == sizeof(buf)); + + // Validate adding 2 different keys + const char * kStringValue2 = "0123abcd"; + const char * kStringValue3 = "cdef89"; + err = storage.SyncSetKeyValue("key2", kStringValue2, static_cast(strlen(kStringValue2))); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = storage.SyncSetKeyValue("key3", kStringValue3, static_cast(strlen(kStringValue3))); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Read them back + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue2)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], kStringValue2, strlen(kStringValue2))); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("key3", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue3)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], kStringValue3, strlen(kStringValue3))); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue2)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], kStringValue2, strlen(kStringValue2))); + + // Pre-clear buffer to make sure next operations don't change contents + uint8_t all_zeroes[sizeof(buf)]; + memset(&buf[0], 0, sizeof(buf)); + memset(&all_zeroes[0], 0, sizeof(all_zeroes)); + + // Read in too small a buffer: no data read, but correct size given + memset(&buf[0], 0, sizeof(buf)); + size = static_cast(strlen(kStringValue2) - 1); + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue2)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + // Read in too small a buffer, which is nullptr and size == 0: check correct size given + size = 0; + err = storage.SyncGetKeyValue("key2", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue2)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + // Read in too small a buffer, which is nullptr and size != 0: error + size = static_cast(strlen(kStringValue2) - 1); + err = storage.SyncGetKeyValue("key2", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + // Read in zero size buffer, which is also nullptr (i.e. just try to find if key exists without + // using a buffer). + size = 0; + err = storage.SyncGetKeyValue("key2", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUFFER_TOO_SMALL); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue2)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + // When key not found, size is not touched. + size = sizeof(buf); + err = storage.SyncGetKeyValue("keyDOES_NOT_EXIST", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, sizeof(buf) == size); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + size = 0; + err = storage.SyncGetKeyValue("keyDOES_NOT_EXIST", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, 0 == size); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], sizeof(buf))); + + // Even when key not found, cannot pass nullptr with size != 0. + size = static_cast(sizeof(buf)); + err = storage.SyncGetKeyValue("keyDOES_NOT_EXIST", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, sizeof(buf) == size); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], &all_zeroes[0], size)); + + // Attempt an empty key write with either nullptr or zero size works + err = storage.SyncSetKeyValue("key2", kStringValue2, 0); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + size = 0; + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == 0); + + err = storage.SyncSetKeyValue("key2", nullptr, 0); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + size = 0; + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == 0); + + // Failure to set key if buffer is nullptr and size != 0 + size = 10; + err = storage.SyncSetKeyValue("key4", nullptr, size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_ARGUMENT); + + // Can delete empty key + err = storage.SyncDeleteKeyValue("key2"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + size = static_cast(sizeof(buf)); + err = storage.SyncGetKeyValue("key2", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, size == sizeof(buf)); +} + +const nlTest sTests[] = { NL_TEST_DEF("Test basic API", TestBasicApi), NL_TEST_SENTINEL() }; + +} // namespace + +int TestTestPersistentStorageDelegate(void) +{ + nlTestSuite theSuite = { "TestPersistentStorageDelegate tests", &sTests[0], nullptr, nullptr }; + + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestTestPersistentStorageDelegate);