Matter SDK Coverage Report
Current view: top level - lib/support - TestPersistentStorageDelegate.h (source / functions) Coverage Total Hit
Test: SHA:704d97f9c619242ad76fcf75aeabc67802fa72d4 Lines: 94.9 % 98 93
Test Date: 2026-05-18 07:37:39 Functions: 100.0 % 20 20

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2021-2022 Project CHIP Authors
       4              :  *    All rights reserved.
       5              :  *
       6              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7              :  *    you may not use this file except in compliance with the License.
       8              :  *    You may obtain a copy of the License at
       9              :  *
      10              :  *        http://www.apache.org/licenses/LICENSE-2.0
      11              :  *
      12              :  *    Unless required by applicable law or agreed to in writing, software
      13              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15              :  *    See the License for the specific language governing permissions and
      16              :  *    limitations under the License.
      17              :  */
      18              : 
      19              : #pragma once
      20              : 
      21              : #include <algorithm>
      22              : #include <lib/core/CHIPCore.h>
      23              : #include <lib/core/CHIPPersistentStorageDelegate.h>
      24              : #include <lib/support/DLLUtil.h>
      25              : #include <lib/support/SafeInt.h>
      26              : #include <lib/support/logging/CHIPLogging.h>
      27              : #include <map>
      28              : #include <set>
      29              : #include <string>
      30              : #include <vector>
      31              : 
      32              : namespace chip {
      33              : 
      34              : /**
      35              :  * Implementation of PersistentStorageDelegate suitable for unit tests,
      36              :  * where persistence lasts for the object's lifetime and where all data is retained
      37              :  * is memory.
      38              :  *
      39              :  * This version also has "poison keys" which, if accessed, yield an error. This can
      40              :  * be used in unit tests to make sure a module making use of the PersistentStorageDelegate
      41              :  * does not access some particular keys which should remain untouched by underlying
      42              :  * logic.
      43              :  */
      44              : class TestPersistentStorageDelegate : public PersistentStorageDelegate
      45              : {
      46              : public:
      47              :     enum class LoggingLevel : unsigned
      48              :     {
      49              :         kDisabled            = 0,
      50              :         kLogMutation         = 1,
      51              :         kLogMutationAndReads = 2,
      52              :     };
      53              : 
      54         2007 :     TestPersistentStorageDelegate() {}
      55              : 
      56       101789 :     CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override
      57              :     {
      58       101789 :         if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
      59              :         {
      60           23 :             ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncGetKeyValue: Get key '%s'", StringOrNullMarker(key));
      61              :         }
      62              : 
      63       101789 :         CHIP_ERROR err = SyncGetKeyValueInternal(key, buffer, size);
      64              : 
      65       101789 :         if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
      66              :         {
      67           46 :             if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
      68              :             {
      69           14 :                 ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' not found",
      70              :                               StringOrNullMarker(key));
      71              :             }
      72           18 :             else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
      73              :             {
      74            0 :                 ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncGetKeyValue: Key '%s' is a poison key",
      75              :                               StringOrNullMarker(key));
      76              :             }
      77              :         }
      78              : 
      79       101789 :         return err;
      80              :     }
      81              : 
      82       355101 :     CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override
      83              :     {
      84       355101 :         if (mLoggingLevel >= LoggingLevel::kLogMutation)
      85              :         {
      86           26 :             ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncSetKeyValue, Set key '%s' with data size %u", key,
      87              :                           static_cast<unsigned>(size));
      88              :         }
      89              : 
      90       355101 :         CHIP_ERROR err = SyncSetKeyValueInternal(key, value, size);
      91              : 
      92       355101 :         if (mLoggingLevel >= LoggingLevel::kLogMutationAndReads)
      93              :         {
      94           24 :             if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
      95              :             {
      96            0 :                 ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncSetKeyValue: Key '%s' is a poison key",
      97              :                               StringOrNullMarker(key));
      98              :             }
      99              :         }
     100              : 
     101       355101 :         return err;
     102              :     }
     103              : 
     104        72110 :     CHIP_ERROR SyncDeleteKeyValue(const char * key) override
     105              :     {
     106        72110 :         if (mLoggingLevel >= LoggingLevel::kLogMutation)
     107              :         {
     108            7 :             ChipLogDetail(Test, "TestPersistentStorageDelegate::SyncDeleteKeyValue, Delete key '%s'", StringOrNullMarker(key));
     109              :         }
     110        72110 :         CHIP_ERROR err = SyncDeleteKeyValueInternal(key);
     111              : 
     112        72110 :         if (mLoggingLevel >= LoggingLevel::kLogMutation)
     113              :         {
     114           14 :             if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
     115              :             {
     116            0 :                 ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' not found",
     117              :                               StringOrNullMarker(key));
     118              :             }
     119           14 :             else if (err == CHIP_ERROR_PERSISTED_STORAGE_FAILED)
     120              :             {
     121            0 :                 ChipLogDetail(Test, "--> TestPersistentStorageDelegate::SyncDeleteKeyValue: Key '%s' is a poison key",
     122              :                               StringOrNullMarker(key));
     123              :             }
     124              :         }
     125              : 
     126        72110 :         return err;
     127              :     }
     128              : 
     129              :     /**
     130              :      * @brief Adds a "poison key" that causes all operations on that key to fail.
     131              :      */
     132           16 :     virtual void AddPoisonKey(const std::string & key) { mPoisonKeys[key] = { 0, 0 }; }
     133              : 
     134              :     /**
     135              :      * @brief Adds a "poison key" with separate read and write success patterns.
     136              :      *
     137              :      * Each pattern is a bit field that controls the sequence of outcomes for successive
     138              :      * operations on the key. The lowest bit determines the next outcome: 1 = success,
     139              :      * 0 = failure. After each operation the pattern is arithmetically right-shifted.
     140              :      * (This means the highest bit repeats indefinitely once all other bits have been consumed).
     141              :      *
     142              :      * Examples:
     143              :      *   0          always fail
     144              :      *   0b1        succeed once, then fail forever
     145              :      *   0b11       succeed twice, then fail forever
     146              :      *   0b010      fail once, succeed once, then fail forever
     147              :      *   -1         always succeed (effectively not poisoned for this operation type)
     148              :      *
     149              :      * Note: Write patterns will not advance while `SetRejectWrites(true)` is in effect.
     150              :      *
     151              :      * @param key          Key to poison.
     152              :      * @param readPattern  Bit pattern for read (Get) operations.
     153              :      * @param writePattern Bit pattern for write (Set/Delete) operations.
     154              :      */
     155           12 :     virtual void AddPoisonKey(const std::string & key, int readPattern, int writePattern)
     156              :     {
     157           12 :         mPoisonKeys[key] = { readPattern, writePattern };
     158           12 :     }
     159              : 
     160              :     /**
     161              :      * Allows subsequent writes to be rejected for unit testing purposes.
     162              :      */
     163            2 :     virtual void SetRejectWrites(bool rejectWrites) { mRejectWrites = rejectWrites; }
     164              : 
     165              :     /**
     166              :      * @brief Clear all "poison keys"
     167              :      *
     168              :      */
     169           24 :     virtual void ClearPoisonKeys() { mPoisonKeys.clear(); }
     170              : 
     171              :     /**
     172              :      * @brief Reset entire contents back to empty. This does NOT clear the "poison keys"
     173              :      *
     174              :      */
     175          581 :     virtual void ClearStorage() { mStorage.clear(); }
     176              : 
     177              :     /**
     178              :      * @return the number of keys currently written in storage
     179              :      */
     180          109 :     virtual size_t GetNumKeys() { return mStorage.size(); }
     181              : 
     182              :     /**
     183              :      * @return a set of all the keys stored
     184              :      */
     185            2 :     virtual std::set<std::string> GetKeys()
     186              :     {
     187            2 :         std::set<std::string> keys;
     188              : 
     189           14 :         for (auto it = mStorage.begin(); it != mStorage.end(); ++it)
     190              :         {
     191           12 :             keys.insert(it->first);
     192              :         }
     193              : 
     194            2 :         return keys;
     195              :     }
     196              : 
     197              :     /**
     198              :      * @brief Determine if storage has a given key
     199              :      *
     200              :      * @param key - key to find (case-sensitive)
     201              :      * @return true if key is present in storage, false otherwise
     202              :      */
     203       173898 :     virtual bool HasKey(const std::string & key) { return (mStorage.find(key) != mStorage.end()); }
     204              : 
     205              :     /**
     206              :      * @brief Set the logging verbosity for debugging
     207              :      *
     208              :      * @param loggingLevel - logging verbosity level to set
     209              :      */
     210            2 :     virtual void SetLoggingLevel(LoggingLevel loggingLevel) { mLoggingLevel = loggingLevel; }
     211              : 
     212              :     /**
     213              :      * @brief Dump a list of all storage keys, sorted alphabetically
     214              :      */
     215            1 :     virtual void DumpKeys()
     216              :     {
     217            1 :         ChipLogError(Test, "TestPersistentStorageDelegate::DumpKeys: %u keys", static_cast<unsigned>(GetNumKeys()));
     218              : 
     219            1 :         auto allKeys = GetKeys();
     220            1 :         std::vector<std::string> allKeysSorted(allKeys.cbegin(), allKeys.cend());
     221            1 :         std::sort(allKeysSorted.begin(), allKeysSorted.end());
     222              : 
     223           11 :         for (const std::string & key : allKeysSorted)
     224              :         {
     225           10 :             (void) key.c_str(); // Guard against log level disabling  error logging which would make `key` unused.
     226           10 :             ChipLogError(Test, "  -> %s", key.c_str());
     227              :         }
     228            1 :     }
     229              : 
     230              : protected:
     231       101789 :     virtual CHIP_ERROR SyncGetKeyValueInternal(const char * key, void * buffer, uint16_t & size)
     232              :     {
     233       101789 :         VerifyOrReturnError((buffer != nullptr) || (size == 0), CHIP_ERROR_INVALID_ARGUMENT);
     234              : 
     235              :         // Making sure poison keys are not accessed
     236       101787 :         if (IsReadPoisoned(key))
     237              :         {
     238            2 :             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
     239              :         }
     240              : 
     241       101785 :         bool contains = HasKey(key);
     242       101785 :         VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
     243              : 
     244        89453 :         std::vector<uint8_t> & value = mStorage[key];
     245        89453 :         size_t valueSize             = value.size();
     246        89453 :         if (!CanCastTo<uint16_t>(valueSize))
     247              :         {
     248            0 :             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
     249              :         }
     250              : 
     251        89453 :         uint16_t valueSizeUint16 = static_cast<uint16_t>(valueSize);
     252        89453 :         VerifyOrReturnError(size != 0 || valueSizeUint16 != 0, CHIP_NO_ERROR);
     253        89448 :         VerifyOrReturnError(buffer != nullptr, CHIP_ERROR_BUFFER_TOO_SMALL);
     254              : 
     255        89340 :         uint16_t sizeToCopy = std::min(size, valueSizeUint16);
     256              : 
     257        89340 :         size = sizeToCopy;
     258        89340 :         memcpy(buffer, value.data(), size);
     259        89340 :         return size < valueSizeUint16 ? CHIP_ERROR_BUFFER_TOO_SMALL : CHIP_NO_ERROR;
     260              :     }
     261              : 
     262       355101 :     virtual CHIP_ERROR SyncSetKeyValueInternal(const char * key, const void * value, uint16_t size)
     263              :     {
     264              :         // Make sure writes are allowed and poison keys are not accessed
     265       355101 :         if (mRejectWrites || /* has side effect */ IsWritePoisoned(key))
     266              :         {
     267           24 :             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
     268              :         }
     269              : 
     270              :         // Handle empty values
     271       355077 :         if (value == nullptr)
     272              :         {
     273            4 :             if (size == 0)
     274              :             {
     275            3 :                 mStorage[key] = std::vector<uint8_t>();
     276            3 :                 return CHIP_NO_ERROR;
     277              :             }
     278              : 
     279            1 :             return CHIP_ERROR_INVALID_ARGUMENT;
     280              :         }
     281              :         // Handle non-empty values
     282              : 
     283       355073 :         const uint8_t * bytes = static_cast<const uint8_t *>(value);
     284       355073 :         mStorage[key]         = std::vector<uint8_t>(bytes, bytes + size);
     285       355073 :         return CHIP_NO_ERROR;
     286              :     }
     287              : 
     288        72110 :     virtual CHIP_ERROR SyncDeleteKeyValueInternal(const char * key)
     289              :     {
     290              :         // Make sure writes are allowed and poison keys are not accessed
     291        72110 :         if (mRejectWrites || /* has side effect */ IsWritePoisoned(key))
     292              :         {
     293            1 :             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
     294              :         }
     295              : 
     296        72109 :         bool contains = HasKey(key);
     297        72109 :         VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
     298        71831 :         mStorage.erase(key);
     299        71831 :         return CHIP_NO_ERROR;
     300              :     }
     301              : 
     302              :     std::map<std::string, std::vector<uint8_t>> mStorage;
     303              : 
     304              :     struct PoisonPattern
     305              :     {
     306              :         int read;
     307              :         int write;
     308              :     };
     309              :     std::map<std::string, PoisonPattern> mPoisonKeys;
     310              :     bool mRejectWrites         = false;
     311              :     LoggingLevel mLoggingLevel = LoggingLevel::kDisabled;
     312              : 
     313              :     // Advances and checks the given pattern. Returns true if the operation should fail.
     314           32 :     static bool TakePoison(int & pattern)
     315              :     {
     316           32 :         bool poisoned = (pattern & 1) == 0;
     317           32 :         pattern >>= 1; // arithmetic right shift (sign bit repeats, so -1 stays -1)
     318           32 :         return poisoned;
     319              :     }
     320              : 
     321       101787 :     bool IsReadPoisoned(const char * key)
     322              :     {
     323       101787 :         auto it = mPoisonKeys.find(std::string(key));
     324       101787 :         return it != mPoisonKeys.end() && TakePoison(it->second.read);
     325              :     }
     326              : 
     327       427209 :     bool IsWritePoisoned(const char * key)
     328              :     {
     329       427209 :         auto it = mPoisonKeys.find(std::string(key));
     330       427209 :         return it != mPoisonKeys.end() && TakePoison(it->second.write);
     331              :     }
     332              : };
     333              : 
     334              : } // namespace chip
        

Generated by: LCOV version 2.0-1