Line data Source code
1 : /*
2 : * Copyright (c) 2026 Project CHIP Authors
3 : *
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : */
16 :
17 : #include <app/persistence/AttributePersistenceMigration.h>
18 : #include <lib/core/CHIPEncoding.h>
19 : #include <lib/support/logging/CHIPLogging.h>
20 : #include <nlbyteorder.h>
21 :
22 : #include <cstring>
23 :
24 : namespace {
25 :
26 : using namespace ::chip;
27 : using namespace chip::app;
28 :
29 : /**
30 : * Validates that the given size is a supported scalar width (1, 2, 4, or 8 bytes).
31 : * On big-endian systems, also performs an in-place little-endian to host byte-order swap.
32 : * On little-endian systems, no swap is needed so only the size validation is performed.
33 : */
34 8 : CHIP_ERROR HostSwapBySize(uint8_t * data, size_t size)
35 : {
36 8 : switch (size)
37 : {
38 : #if (NLBYTEORDER == NLBYTEORDER_BIG_ENDIAN)
39 : case 1:
40 : // Single byte, no swap needed.
41 : return CHIP_NO_ERROR;
42 : case 2: {
43 : uint16_t val;
44 : memcpy(&val, data, sizeof(val));
45 : val = chip::Encoding::LittleEndian::HostSwap16(val);
46 : memcpy(data, &val, sizeof(val));
47 : return CHIP_NO_ERROR;
48 : }
49 : case 4: {
50 : uint32_t val;
51 : memcpy(&val, data, sizeof(val));
52 : val = chip::Encoding::LittleEndian::HostSwap32(val);
53 : memcpy(data, &val, sizeof(val));
54 : return CHIP_NO_ERROR;
55 : }
56 : case 8: {
57 : uint64_t val;
58 : memcpy(&val, data, sizeof(val));
59 : val = chip::Encoding::LittleEndian::HostSwap64(val);
60 : memcpy(data, &val, sizeof(val));
61 : return CHIP_NO_ERROR;
62 : }
63 : #else
64 7 : case 1:
65 : case 2:
66 : case 4:
67 : case 8:
68 7 : return CHIP_NO_ERROR;
69 : #endif
70 1 : default:
71 1 : return CHIP_ERROR_INVALID_ARGUMENT;
72 : }
73 : }
74 :
75 11 : CHIP_ERROR MigrateValueFromSafe(const ConcreteAttributePath & attrPath, SafeAttributePersistenceProvider & provider,
76 : MutableByteSpan & buffer, size_t valueSize, bool isScalar)
77 : {
78 :
79 11 : VerifyOrReturnError(valueSize <= buffer.size(), CHIP_ERROR_BUFFER_TOO_SMALL);
80 :
81 : #if (NLBYTEORDER == NLBYTEORDER_LITTLE_ENDIAN)
82 : // On little-endian systems, no byte swap is needed for scalar values.
83 : // Read directly into the output buffer and validate the size.
84 11 : ReturnErrorOnFailure(provider.SafeReadValue(attrPath, buffer));
85 9 : VerifyOrReturnError(buffer.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
86 : // For scalar values in LE the function HostSwapBySize just checks that a valid size was provided.
87 9 : if (isScalar)
88 : {
89 8 : ReturnErrorOnFailure(HostSwapBySize(buffer.data(), valueSize));
90 : }
91 : #else
92 :
93 : // On big-endian, no need to swap non-scalar values.
94 : // For non-scalar values, just return the raw data.
95 : if (!isScalar)
96 : {
97 : return provider.SafeReadValue(attrPath, buffer);
98 : }
99 :
100 : // For scalar values, read into a temp buffer, byte-swap, then copy out.
101 : uint8_t attrData[sizeof(uint64_t)];
102 : MutableByteSpan tempVal(attrData);
103 :
104 : ReturnErrorOnFailure(provider.SafeReadValue(attrPath, tempVal));
105 : VerifyOrReturnError(tempVal.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
106 : ReturnErrorOnFailure(HostSwapBySize(tempVal.data(), valueSize));
107 : memcpy(buffer.data(), tempVal.data(), valueSize);
108 : buffer.reduce_size(valueSize);
109 : #endif
110 8 : return CHIP_NO_ERROR;
111 : }
112 : } // namespace
113 : namespace chip::app {
114 :
115 10 : CHIP_ERROR MigrateFromSafeToAttributePersistenceProvider(SafeAttributePersistenceProvider & safeProvider,
116 : AttributePersistenceProvider & dstProvider,
117 : const ConcreteClusterPath & cluster,
118 : Span<const AttrMigrationData> attributes, MutableByteSpan buffer)
119 : {
120 10 : bool hadMigrationErrors = false;
121 10 : ConcreteAttributePath attrPath;
122 :
123 22 : for (const auto & entry : attributes)
124 : {
125 12 : attrPath = ConcreteAttributePath(cluster.mEndpointId, cluster.mClusterId, entry.attributeId);
126 :
127 : // Create a copy of the buffer to check if the value is already in the AttributePersistence.
128 : // If the attribute value is already stored in AttributePersistence, skip it.
129 : // Note: we assume any other error (e.g. including buffer too small) to be an indication that
130 : // the key exists. The only case we migrate is if we are explicitly told "not found".
131 12 : MutableByteSpan readAttrBuffer = buffer;
132 24 : if (dstProvider.ReadValue(attrPath, readAttrBuffer) != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
133 : {
134 4 : continue;
135 : }
136 :
137 : // We make a copy of the buffer so it can be resized
138 : // Still refers to same internal buffer though
139 : // Read value from the safe provider, will resize copyOfBuffer to read size
140 11 : MutableByteSpan copyOfBuffer = buffer;
141 : // If there was an error reading from SafeAttributePersistence, then we shouldn't try to write that value
142 : // to AttributePersistence
143 : ChipError attributeMigrationError =
144 11 : MigrateValueFromSafe(attrPath, safeProvider, copyOfBuffer, entry.valueSize, entry.isScalar);
145 22 : if (attributeMigrationError != CHIP_NO_ERROR)
146 : {
147 : // If the value was not found in SafeAttributePersistence, it means that it was already migrated or that
148 : // there wasn't a value stored for this attribute in the first place, so we skip it.
149 6 : if (attributeMigrationError != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
150 : {
151 1 : hadMigrationErrors = true;
152 1 : ChipLogError(DataManagement,
153 : "AttributeMigration: Error reading SafeAttribute '" ChipLogFormatMEI
154 : "' from cluster '" ChipLogFormatMEI "' (err=%" CHIP_ERROR_FORMAT ")",
155 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId),
156 : attributeMigrationError.Format());
157 : }
158 3 : continue;
159 : }
160 :
161 : // Delete from the safe provider immediately after a successful read, so we only try to
162 : // migrate the persisted values once and avoid re-migrating after a reset.
163 8 : attributeMigrationError = safeProvider.SafeDeleteValue(attrPath);
164 16 : if (attributeMigrationError != CHIP_NO_ERROR)
165 : {
166 0 : hadMigrationErrors = true;
167 0 : ChipLogError(DataManagement,
168 : "AttributeMigration: Error deleting SafeAttribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
169 : "' (err=%" CHIP_ERROR_FORMAT ")",
170 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
171 : }
172 :
173 : // Write value from SafeAttributePersistence into AttributePersistence
174 8 : attributeMigrationError = dstProvider.WriteValue(attrPath, copyOfBuffer);
175 16 : if (attributeMigrationError != CHIP_NO_ERROR)
176 : {
177 1 : hadMigrationErrors = true;
178 1 : ChipLogError(DataManagement,
179 : "AttributeMigration: Error writing Attribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
180 : "' (err=%" CHIP_ERROR_FORMAT ")",
181 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
182 : }
183 : }
184 10 : return hadMigrationErrors ? CHIP_ERROR_HAD_FAILURES : CHIP_NO_ERROR;
185 : }
186 : } // namespace chip::app
|