blob: 0d135ef3371a4bb52bac23d074b4449b243a1e68 [file] [edit]
/*
* 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 <app/persistence/AttributePersistenceMigration.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/support/logging/CHIPLogging.h>
#include <nlbyteorder.h>
#include <cstring>
namespace {
using namespace ::chip;
using namespace chip::app;
/**
* Validates that the given size is a supported scalar width (1, 2, 4, or 8 bytes).
* On big-endian systems, also performs an in-place little-endian to host byte-order swap.
* On little-endian systems, no swap is needed so only the size validation is performed.
*/
CHIP_ERROR HostSwapBySize(uint8_t * data, size_t size)
{
switch (size)
{
#if (NLBYTEORDER == NLBYTEORDER_BIG_ENDIAN)
case 1:
// Single byte, no swap needed.
return CHIP_NO_ERROR;
case 2: {
uint16_t val;
memcpy(&val, data, sizeof(val));
val = chip::Encoding::LittleEndian::HostSwap16(val);
memcpy(data, &val, sizeof(val));
return CHIP_NO_ERROR;
}
case 4: {
uint32_t val;
memcpy(&val, data, sizeof(val));
val = chip::Encoding::LittleEndian::HostSwap32(val);
memcpy(data, &val, sizeof(val));
return CHIP_NO_ERROR;
}
case 8: {
uint64_t val;
memcpy(&val, data, sizeof(val));
val = chip::Encoding::LittleEndian::HostSwap64(val);
memcpy(data, &val, sizeof(val));
return CHIP_NO_ERROR;
}
#else
case 1:
case 2:
case 4:
case 8:
return CHIP_NO_ERROR;
#endif
default:
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
CHIP_ERROR MigrateValueFromSafe(const ConcreteAttributePath & attrPath, SafeAttributePersistenceProvider & provider,
MutableByteSpan & buffer, size_t valueSize, bool isScalar)
{
VerifyOrReturnError(valueSize <= buffer.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
#if (NLBYTEORDER == NLBYTEORDER_LITTLE_ENDIAN)
// On little-endian systems, no byte swap is needed for scalar values.
// Read directly into the output buffer and validate the size.
ReturnErrorOnFailure(provider.SafeReadValue(attrPath, buffer));
VerifyOrReturnError(buffer.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
// For scalar values in LE the function HostSwapBySize just checks that a valid size was provided.
if (isScalar)
{
ReturnErrorOnFailure(HostSwapBySize(buffer.data(), valueSize));
}
#else
// On big-endian, no need to swap non-scalar values.
// For non-scalar values, just return the raw data.
if (!isScalar)
{
return provider.SafeReadValue(attrPath, buffer);
}
// For scalar values, read into a temp buffer, byte-swap, then copy out.
uint8_t attrData[sizeof(uint64_t)];
MutableByteSpan tempVal(attrData);
ReturnErrorOnFailure(provider.SafeReadValue(attrPath, tempVal));
VerifyOrReturnError(tempVal.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(HostSwapBySize(tempVal.data(), valueSize));
memcpy(buffer.data(), tempVal.data(), valueSize);
buffer.reduce_size(valueSize);
#endif
return CHIP_NO_ERROR;
}
} // namespace
namespace chip::app {
CHIP_ERROR MigrateFromSafeToAttributePersistenceProvider(SafeAttributePersistenceProvider & safeProvider,
AttributePersistenceProvider & dstProvider,
const ConcreteClusterPath & cluster,
Span<const AttrMigrationData> attributes, MutableByteSpan buffer)
{
bool hadMigrationErrors = false;
ConcreteAttributePath attrPath;
for (const auto & entry : attributes)
{
attrPath = ConcreteAttributePath(cluster.mEndpointId, cluster.mClusterId, entry.attributeId);
// Create a copy of the buffer to check if the value is already in the AttributePersistence.
// If the attribute value is already stored in AttributePersistence, skip it.
// Note: we assume any other error (e.g. including buffer too small) to be an indication that
// the key exists. The only case we migrate is if we are explicitly told "not found".
MutableByteSpan readAttrBuffer = buffer;
if (dstProvider.ReadValue(attrPath, readAttrBuffer) != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
continue;
}
// We make a copy of the buffer so it can be resized
// Still refers to same internal buffer though
// Read value from the safe provider, will resize copyOfBuffer to read size
MutableByteSpan copyOfBuffer = buffer;
// If there was an error reading from SafeAttributePersistence, then we shouldn't try to write that value
// to AttributePersistence
ChipError attributeMigrationError =
MigrateValueFromSafe(attrPath, safeProvider, copyOfBuffer, entry.valueSize, entry.isScalar);
if (attributeMigrationError != CHIP_NO_ERROR)
{
// If the value was not found in SafeAttributePersistence, it means that it was already migrated or that
// there wasn't a value stored for this attribute in the first place, so we skip it.
if (attributeMigrationError != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
hadMigrationErrors = true;
ChipLogError(DataManagement,
"AttributeMigration: Error reading SafeAttribute '" ChipLogFormatMEI
"' from cluster '" ChipLogFormatMEI "' (err=%" CHIP_ERROR_FORMAT ")",
ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId),
attributeMigrationError.Format());
}
continue;
}
// Delete from the safe provider immediately after a successful read, so we only try to
// migrate the persisted values once and avoid re-migrating after a reset.
attributeMigrationError = safeProvider.SafeDeleteValue(attrPath);
if (attributeMigrationError != CHIP_NO_ERROR)
{
hadMigrationErrors = true;
ChipLogError(DataManagement,
"AttributeMigration: Error deleting SafeAttribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
"' (err=%" CHIP_ERROR_FORMAT ")",
ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
}
// Write value from SafeAttributePersistence into AttributePersistence
attributeMigrationError = dstProvider.WriteValue(attrPath, copyOfBuffer);
if (attributeMigrationError != CHIP_NO_ERROR)
{
hadMigrationErrors = true;
ChipLogError(DataManagement,
"AttributeMigration: Error writing Attribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
"' (err=%" CHIP_ERROR_FORMAT ")",
ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
}
}
return hadMigrationErrors ? CHIP_ERROR_HAD_FAILURES : CHIP_NO_ERROR;
}
} // namespace chip::app