blob: a85c1b2c0b1fef7ceddbaef7a7531a06650df2d0 [file]
/*
* Copyright (c) 2026 Project CHIP Authors
*
* 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 <pw_unit_test/framework.h>
#include <app/ConcreteAttributePath.h>
#include <app/DefaultSafeAttributePersistenceProvider.h>
#include <app/data-model/Nullable.h>
#include <app/persistence/AttributePersistenceMigration.h>
#include <app/persistence/DefaultAttributePersistenceProvider.h>
#include <lib/core/CHIPError.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/support/Span.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <cstring>
namespace {
using namespace chip;
using namespace chip::app;
// A test-only provider that always fails on WriteValue, used to verify
// that the safe value is deleted even when the destination write fails.
class WriteFailingAttributeProvider : public DefaultAttributePersistenceProvider
{
public:
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override
{
return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
};
void ExpectSafeReadNotFound(DefaultSafeAttributePersistenceProvider & provider, const ConcreteAttributePath & path)
{
uint8_t readBuf[sizeof(uint8_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(provider.SafeReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
void ExpectDstReadNotFound(DefaultAttributePersistenceProvider & provider, const ConcreteAttributePath & path)
{
uint8_t readBuf[sizeof(uint8_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(provider.ReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
void ExpectDstUint8(DefaultAttributePersistenceProvider & provider, const ConcreteAttributePath & path, uint8_t expected)
{
uint8_t readBuf[sizeof(uint8_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(provider.ReadValue(path, readBuffer), CHIP_NO_ERROR);
ASSERT_EQ(readBuffer.size(), sizeof(uint8_t));
EXPECT_EQ(readBuffer.data()[0], expected);
}
void ExpectDstNullableUint8(DefaultAttributePersistenceProvider & provider, const ConcreteAttributePath & path,
const DataModel::Nullable<uint8_t> & expected)
{
NumericAttributeTraits<uint8_t>::StorageType expectedStorage;
DataModel::NullableToStorage(expected, expectedStorage);
uint8_t readBuf[sizeof(uint8_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(provider.ReadValue(path, readBuffer), CHIP_NO_ERROR);
ASSERT_EQ(readBuffer.size(), sizeof(uint8_t));
EXPECT_EQ(readBuffer.data()[0], expectedStorage);
}
// Single attribute migrated successfully: value appears in AttributePersistence, deleted from SafeAttribute.
TEST(TestAttributePersistenceMigration, TestMigrationSuccess)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath path(1, 2, 3);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueToStore = 42;
// Store a value in the safe provider
ASSERT_EQ(safeRamProvider.WriteScalarValue(path, kValueToStore), CHIP_NO_ERROR);
// Run migration
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Value should now exist in the normal provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(path, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kValueToStore);
}
// Value should be deleted from the safe provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// Attribute not present in SafeAttribute: AttributePersistence untouched.
TEST(TestAttributePersistenceMigration, TestMigrationSkipsAbsentAttribute)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath path(1, 2, 3);
const ConcreteClusterPath cluster(1, 2);
// Don't write anything to the safe provider
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Normal provider should have nothing
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// When WriteValue to AttributePersistence fails, the value is still deleted from SafeAttribute ("one time" migration guarantee).
// Uses a custom migrator (PoisonAfterReadMigrator) that poisons the normal provider key after the migrator runs, so that the
// initial ReadValue check passes (returns CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) but the subsequent WriteValue
// hits the poisoned key and fails.
TEST(TestAttributePersistenceMigration, TestMigrationDeletesFromSafeOnWriteFailure)
{
TestPersistentStorageDelegate storageDelegate;
WriteFailingAttributeProvider failingProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(failingProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath path(1, 2, 3);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueToStore = 42;
// Store a value in the safe provider
ASSERT_EQ(safeRamProvider.WriteScalarValue(path, kValueToStore), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_NE(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, failingProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Value should still be deleted from the safe provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// After a successful migration, calling it again is a no-op (value already deleted from SafeAttribute).
TEST(TestAttributePersistenceMigration, TestMigrationTwice)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath path(1, 2, 3);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueToStore = 42;
ASSERT_EQ(safeRamProvider.WriteScalarValue(path, kValueToStore), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
// First migration
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Overwrite the value in dstProvider to detect if a second migration would overwrite it
constexpr uint32_t kNewValue = 99;
ASSERT_EQ(ramProvider.WriteValue(path, ByteSpan(reinterpret_cast<const uint8_t *>(&kNewValue), sizeof(kNewValue))),
CHIP_NO_ERROR);
// Second migration, shouldn't affect the new value and return no error.
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// The new value should still be intact
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(path, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
ASSERT_EQ(readBuffer.size(), sizeof(uint32_t));
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kNewValue);
}
}
// Two attributes in the list, only the second is present in SafeAttribute. First is skipped, second is migrated.
TEST(TestAttributePersistenceMigration, TestMultipleAttributesMixedPresence)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath pathA(1, 2, 3);
const ConcreteAttributePath pathB(1, 2, 4);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueB = 77;
// Only write attribute B to the safe provider
ASSERT_EQ(safeRamProvider.WriteScalarValue(pathB, kValueB), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
{ 4, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Attribute A should not exist in the normal provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathA, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
// Attribute B should be migrated to the normal provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathB, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
ASSERT_EQ(readBuffer.size(), sizeof(uint32_t));
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kValueB);
}
// Attribute B should be deleted from the safe provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(pathB, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// Two attributes both present in SafeAttribute: both are migrated and deleted.
TEST(TestAttributePersistenceMigration, TestMultipleAttributesAllPresent)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath pathA(1, 2, 3);
const ConcreteAttributePath pathB(1, 2, 4);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueA = 10;
constexpr uint32_t kValueB = 20;
ASSERT_EQ(safeRamProvider.WriteScalarValue(pathA, kValueA), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(pathB, kValueB), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint32_t), true },
{ 4, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Both should be in the normal provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathA, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
ASSERT_EQ(readBuffer.size(), sizeof(uint32_t));
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kValueA);
}
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathB, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
ASSERT_EQ(readBuffer.size(), sizeof(uint32_t));
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kValueB);
}
// Both should be deleted from the safe provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(pathA, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(pathB, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// Empty attribute list: migration is a no-op, returns CHIP_NO_ERROR.
TEST(TestAttributePersistenceMigration, TestMigrationWithEmptyAttributeList)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteClusterPath cluster(1, 2);
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span<const AttrMigrationData>(),
buffer),
CHIP_NO_ERROR);
}
// Migration using DefaultMigrators::SafeValue which reads raw bytes via SafeReadValue.
TEST(TestAttributePersistenceMigration, TestMigrationWithSafeValueMigrator)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath path(1, 2, 3);
const ConcreteClusterPath cluster(1, 2);
// Write raw bytes to the safe provider
const uint8_t kRawValue[] = { 0xDE, 0xAD, 0xBE, 0xEF };
ASSERT_EQ(safeRamProvider.SafeWriteValue(path, ByteSpan(kRawValue)), CHIP_NO_ERROR);
// Migrate raw bytes using AttrMigrationData { valueSize, isScalar }.
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(kRawValue), false },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
// Value should now exist in the normal provider with the same raw bytes
{
uint8_t readBuf[sizeof(kRawValue)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(path, readBuffer), CHIP_NO_ERROR);
EXPECT_EQ(readBuffer.size(), sizeof(kRawValue));
EXPECT_EQ(memcmp(readBuffer.data(), kRawValue, sizeof(kRawValue)), 0);
}
// Value should be deleted from the safe provider
{
uint8_t readBuf[sizeof(kRawValue)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(path, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// Invalid scalar size: reports failure for the invalid entry but still migrates the valid entry.
TEST(TestAttributePersistenceMigration, TestMigrationWithInvalidScalarSize)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath pathA(1, 2, 3);
const ConcreteAttributePath pathB(1, 2, 4);
const ConcreteClusterPath cluster(1, 2);
constexpr uint32_t kValueB = 55;
// Store 3 raw bytes for attribute A (unsupported scalar size)
const uint8_t kRawA[] = { 0x01, 0x02, 0x03 };
ASSERT_EQ(safeRamProvider.SafeWriteValue(pathA, ByteSpan(kRawA)), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(pathB, kValueB), CHIP_NO_ERROR);
// Attribute A has an invalid 3-byte scalar size, attribute B is valid
const AttrMigrationData attributesToMigrate[] = {
{ 3, 3, true },
{ 4, sizeof(uint32_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
// Should report failure due to invalid scalar size on attribute A
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_ERROR_HAD_FAILURES);
// Attribute A should not exist in the normal provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathA, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
// Attribute B should still be migrated successfully
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(ramProvider.ReadValue(pathB, readBuffer), CHIP_NO_ERROR);
uint32_t readValue = 0;
ASSERT_EQ(readBuffer.size(), sizeof(uint32_t));
memcpy(&readValue, readBuffer.data(), sizeof(uint32_t));
EXPECT_EQ(readValue, kValueB);
}
// Attribute B should be deleted from the safe provider
{
uint8_t readBuf[sizeof(uint32_t)] = {};
MutableByteSpan readBuffer(readBuf);
EXPECT_EQ(safeRamProvider.SafeReadValue(pathB, readBuffer), CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND);
}
}
// Three uint8_t attributes (one scalar, two nullable): all present in SafeAttribute and migrated.
TEST(TestAttributePersistenceMigration, TestMigrationMultipleUint8ScalarsIncludingNullable)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath scalarPath(1, 2, 3);
const ConcreteAttributePath nullablePathA(1, 2, 4);
const ConcreteAttributePath nullablePathB(1, 2, 5);
const ConcreteClusterPath cluster(1, 2);
constexpr uint8_t kScalarValue = 2;
const DataModel::Nullable<uint8_t> kNullableValueA(1);
const DataModel::Nullable<uint8_t> kNullableValueB(0);
ASSERT_EQ(safeRamProvider.WriteScalarValue(scalarPath, kScalarValue), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(nullablePathA, kNullableValueA), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(nullablePathB, kNullableValueB), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint8_t), true },
{ 4, sizeof(uint8_t), true },
{ 5, sizeof(uint8_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
ExpectDstUint8(ramProvider, scalarPath, kScalarValue);
ExpectDstNullableUint8(ramProvider, nullablePathA, kNullableValueA);
ExpectDstNullableUint8(ramProvider, nullablePathB, kNullableValueB);
ExpectSafeReadNotFound(safeRamProvider, scalarPath);
ExpectSafeReadNotFound(safeRamProvider, nullablePathA);
ExpectSafeReadNotFound(safeRamProvider, nullablePathB);
}
// Nullable uint8_t null sentinel values are preserved during migration.
TEST(TestAttributePersistenceMigration, TestMigrationNullableNullUint8Values)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath scalarPath(1, 2, 3);
const ConcreteAttributePath nullablePathA(1, 2, 4);
const ConcreteAttributePath nullablePathB(1, 2, 5);
const ConcreteClusterPath cluster(1, 2);
constexpr uint8_t kScalarValue = 1;
DataModel::Nullable<uint8_t> nullNullableValueA;
DataModel::Nullable<uint8_t> nullNullableValueB;
nullNullableValueA.SetNull();
nullNullableValueB.SetNull();
ASSERT_EQ(safeRamProvider.WriteScalarValue(scalarPath, kScalarValue), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(nullablePathA, nullNullableValueA), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(nullablePathB, nullNullableValueB), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint8_t), true },
{ 4, sizeof(uint8_t), true },
{ 5, sizeof(uint8_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
ExpectDstUint8(ramProvider, scalarPath, kScalarValue);
ExpectDstNullableUint8(ramProvider, nullablePathA, nullNullableValueA);
ExpectDstNullableUint8(ramProvider, nullablePathB, nullNullableValueB);
ExpectSafeReadNotFound(safeRamProvider, nullablePathA);
ExpectSafeReadNotFound(safeRamProvider, nullablePathB);
}
// Scalar attribute migrated; nullable attributes absent from SafeAttribute are skipped.
TEST(TestAttributePersistenceMigration, TestMigrationSkipsAbsentNullableUint8Attributes)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath scalarPath(1, 2, 3);
const ConcreteAttributePath nullablePathA(1, 2, 4);
const ConcreteAttributePath nullablePathB(1, 2, 5);
const ConcreteClusterPath cluster(1, 2);
constexpr uint8_t kScalarValue = 1;
ASSERT_EQ(safeRamProvider.WriteScalarValue(scalarPath, kScalarValue), CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint8_t), true },
{ 4, sizeof(uint8_t), true },
{ 5, sizeof(uint8_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
ExpectDstUint8(ramProvider, scalarPath, kScalarValue);
ExpectDstReadNotFound(ramProvider, nullablePathA);
ExpectDstReadNotFound(ramProvider, nullablePathB);
ExpectSafeReadNotFound(safeRamProvider, scalarPath);
}
// Attributes already present in AttributePersistence are not overwritten; skipped safe values remain.
TEST(TestAttributePersistenceMigration, TestMigrationSkipsWhenDestinationAlreadyHasNullableUint8)
{
TestPersistentStorageDelegate storageDelegate;
DefaultAttributePersistenceProvider ramProvider;
DefaultSafeAttributePersistenceProvider safeRamProvider;
ASSERT_EQ(ramProvider.Init(&storageDelegate), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.Init(&storageDelegate), CHIP_NO_ERROR);
const ConcreteAttributePath scalarPath(1, 2, 3);
const ConcreteAttributePath nullablePathA(1, 2, 4);
const ConcreteClusterPath cluster(1, 2);
constexpr uint8_t kSafeScalarValue = 2;
constexpr uint8_t kDstScalarValue = 5;
const DataModel::Nullable<uint8_t> kSafeNullableValueA(1);
const DataModel::Nullable<uint8_t> kDstNullableValueA(3);
ASSERT_EQ(safeRamProvider.WriteScalarValue(scalarPath, kSafeScalarValue), CHIP_NO_ERROR);
ASSERT_EQ(safeRamProvider.WriteScalarValue(nullablePathA, kSafeNullableValueA), CHIP_NO_ERROR);
ASSERT_EQ(
ramProvider.WriteValue(scalarPath, ByteSpan(reinterpret_cast<const uint8_t *>(&kDstScalarValue), sizeof(kDstScalarValue))),
CHIP_NO_ERROR);
NumericAttributeTraits<uint8_t>::StorageType dstNullableStorageValue;
DataModel::NullableToStorage(kDstNullableValueA, dstNullableStorageValue);
ASSERT_EQ(
ramProvider.WriteValue(
nullablePathA, ByteSpan(reinterpret_cast<const uint8_t *>(&dstNullableStorageValue), sizeof(dstNullableStorageValue))),
CHIP_NO_ERROR);
const AttrMigrationData attributesToMigrate[] = {
{ 3, sizeof(uint8_t), true },
{ 4, sizeof(uint8_t), true },
};
uint8_t buf[128] = {};
MutableByteSpan buffer(buf);
EXPECT_EQ(
MigrateFromSafeToAttributePersistenceProvider(safeRamProvider, ramProvider, cluster, Span(attributesToMigrate), buffer),
CHIP_NO_ERROR);
ExpectDstUint8(ramProvider, scalarPath, kDstScalarValue);
ExpectDstNullableUint8(ramProvider, nullablePathA, kDstNullableValueA);
uint8_t safeScalarValue = 0;
ASSERT_EQ(safeRamProvider.ReadScalarValue(scalarPath, safeScalarValue), CHIP_NO_ERROR);
EXPECT_EQ(safeScalarValue, kSafeScalarValue);
DataModel::Nullable<uint8_t> safeNullableValueA;
ASSERT_EQ(safeRamProvider.ReadScalarValue(nullablePathA, safeNullableValueA), CHIP_NO_ERROR);
EXPECT_EQ(safeNullableValueA, kSafeNullableValueA);
}
} // namespace