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 22 : CHIP_ERROR HostSwapBySize(uint8_t * data, size_t size)
35 : {
36 22 : 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 21 : case 1:
65 : case 2:
66 : case 4:
67 : case 8:
68 21 : return CHIP_NO_ERROR;
69 : #endif
70 1 : default:
71 1 : return CHIP_ERROR_INVALID_ARGUMENT;
72 : }
73 : }
74 :
75 154 : CHIP_ERROR MigrateValueFromSafe(const ConcreteAttributePath & attrPath, SafeAttributePersistenceProvider & provider,
76 : MutableByteSpan & buffer, size_t valueSize, bool isScalar)
77 : {
78 :
79 154 : 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 154 : ReturnErrorOnFailure(provider.SafeReadValue(attrPath, buffer));
85 : // For scalar values in LE the function HostSwapBySize just checks that a valid size was provided.
86 23 : if (isScalar)
87 : {
88 22 : VerifyOrReturnError(buffer.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
89 22 : ReturnErrorOnFailure(HostSwapBySize(buffer.data(), valueSize));
90 : }
91 : else
92 : {
93 1 : VerifyOrReturnError(buffer.size() <= valueSize, CHIP_ERROR_INCORRECT_STATE);
94 : }
95 : #else
96 :
97 : // On big-endian, no need to swap non-scalar values.
98 : // For non-scalar values, just return the raw data.
99 : if (!isScalar)
100 : {
101 : ReturnErrorOnFailure(provider.SafeReadValue(attrPath, buffer));
102 : return buffer.size() <= valueSize ? CHIP_NO_ERROR : CHIP_ERROR_INCORRECT_STATE;
103 : }
104 :
105 : // For scalar values, read into a temp buffer, byte-swap, then copy out.
106 : uint8_t attrData[sizeof(uint64_t)];
107 : MutableByteSpan tempVal(attrData);
108 :
109 : ReturnErrorOnFailure(provider.SafeReadValue(attrPath, tempVal));
110 : VerifyOrReturnError(tempVal.size() == valueSize, CHIP_ERROR_INCORRECT_STATE);
111 : ReturnErrorOnFailure(HostSwapBySize(tempVal.data(), valueSize));
112 : memcpy(buffer.data(), tempVal.data(), valueSize);
113 : buffer.reduce_size(valueSize);
114 : #endif
115 22 : return CHIP_NO_ERROR;
116 : }
117 : } // namespace
118 : namespace chip::app {
119 :
120 23 : CHIP_ERROR MigrateFromSafeToAttributePersistenceProvider(SafeAttributePersistenceProvider & safeProvider,
121 : AttributePersistenceProvider & dstProvider,
122 : const ConcreteClusterPath & cluster,
123 : Span<const AttrMigrationData> attributes, MutableByteSpan buffer)
124 : {
125 23 : bool hadMigrationErrors = false;
126 23 : ConcreteAttributePath attrPath;
127 :
128 181 : for (const auto & entry : attributes)
129 : {
130 158 : attrPath = ConcreteAttributePath(cluster.mEndpointId, cluster.mClusterId, entry.attributeId);
131 :
132 : // Create a copy of the buffer to check if the value is already in the AttributePersistence.
133 : // If the attribute value is already stored in AttributePersistence, skip it.
134 : // Note: we assume any other error (e.g. including buffer too small) to be an indication that
135 : // the key exists. The only case we migrate is if we are explicitly told "not found".
136 158 : MutableByteSpan readAttrBuffer = buffer;
137 316 : if (dstProvider.ReadValue(attrPath, readAttrBuffer) != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
138 : {
139 136 : continue;
140 : }
141 :
142 : // We make a copy of the buffer so it can be resized
143 : // Still refers to same internal buffer though
144 : // Read value from the safe provider, will resize copyOfBuffer to read size
145 154 : MutableByteSpan copyOfBuffer = buffer;
146 : // If there was an error reading from SafeAttributePersistence, then we shouldn't try to write that value
147 : // to AttributePersistence
148 : ChipError attributeMigrationError =
149 154 : MigrateValueFromSafe(attrPath, safeProvider, copyOfBuffer, entry.valueSize, entry.isScalar);
150 308 : if (attributeMigrationError != CHIP_NO_ERROR)
151 : {
152 : // If the value was not found in SafeAttributePersistence, it means that it was already migrated or that
153 : // there wasn't a value stored for this attribute in the first place, so we skip it.
154 264 : if (attributeMigrationError != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
155 : {
156 1 : hadMigrationErrors = true;
157 1 : ChipLogError(DataManagement,
158 : "AttributeMigration: Error reading SafeAttribute '" ChipLogFormatMEI
159 : "' from cluster '" ChipLogFormatMEI "' (err=%" CHIP_ERROR_FORMAT ")",
160 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId),
161 : attributeMigrationError.Format());
162 : }
163 132 : continue;
164 : }
165 :
166 : // Delete from the safe provider immediately after a successful read, so we only try to
167 : // migrate the persisted values once and avoid re-migrating after a reset.
168 22 : attributeMigrationError = safeProvider.SafeDeleteValue(attrPath);
169 44 : if (attributeMigrationError != CHIP_NO_ERROR)
170 : {
171 0 : hadMigrationErrors = true;
172 0 : ChipLogError(DataManagement,
173 : "AttributeMigration: Error deleting SafeAttribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
174 : "' (err=%" CHIP_ERROR_FORMAT ")",
175 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
176 : }
177 :
178 : // Write value from SafeAttributePersistence into AttributePersistence
179 22 : attributeMigrationError = dstProvider.WriteValue(attrPath, copyOfBuffer);
180 44 : if (attributeMigrationError != CHIP_NO_ERROR)
181 : {
182 1 : hadMigrationErrors = true;
183 1 : ChipLogError(DataManagement,
184 : "AttributeMigration: Error writing Attribute '" ChipLogFormatMEI "' from cluster '" ChipLogFormatMEI
185 : "' (err=%" CHIP_ERROR_FORMAT ")",
186 : ChipLogValueMEI(entry.attributeId), ChipLogValueMEI(cluster.mClusterId), attributeMigrationError.Format());
187 : }
188 : }
189 23 : return hadMigrationErrors ? CHIP_ERROR_HAD_FAILURES : CHIP_NO_ERROR;
190 : }
191 : } // namespace chip::app
|