Line data Source code
1 : /**
2 : *
3 : * Copyright (c) 2020 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #include <app/util/attribute-table.h>
18 :
19 : #include <app/util/attribute-table-detail.h>
20 :
21 : #include <app/util/attribute-storage-detail.h>
22 : #include <app/util/attribute-storage.h>
23 : #include <app/util/config.h>
24 : #include <app/util/ember-strings.h>
25 : #include <app/util/generic-callbacks.h>
26 : #include <lib/core/CHIPConfig.h>
27 : #include <lib/support/odd-sized-integers.h>
28 :
29 : #include <app/reporting/reporting.h>
30 : #include <protocols/interaction_model/Constants.h>
31 :
32 : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
33 : #define EM_BIG_ENDIAN true
34 : #else
35 : #define EM_BIG_ENDIAN false
36 : #endif
37 :
38 : using chip::Protocols::InteractionModel::Status;
39 :
40 : using namespace chip;
41 : using namespace chip::app;
42 :
43 : namespace {
44 : /**
45 : * @brief Simple integer comparison function.
46 : * Compares two values of a known length as integers.
47 : * Signed integer comparison are supported for numbers with length of
48 : * 4 (bytes) or less.
49 : * The integers are in native endianness.
50 : *
51 : * @return -1, if val1 is smaller
52 : * 0, if they are the same or if two negative numbers with length
53 : * greater than 4 is being compared
54 : * 1, if val2 is smaller.
55 : *
56 : * You can pass in val1 as NULL, which will assume that it is
57 : * pointing to an array of all zeroes. This is used so that
58 : * default value of NULL is treated as all zeroes.
59 : */
60 0 : int8_t emberAfCompareValues(const uint8_t * val1, const uint8_t * val2, uint16_t len, bool signedNumber)
61 : {
62 0 : if (len == 0)
63 : {
64 : // no length means nothing to compare. Shouldn't even happen, since len is sizeof(some-integer-type).
65 0 : return 0;
66 : }
67 :
68 0 : if (signedNumber)
69 : { // signed number comparison
70 0 : if (len <= 4)
71 : { // only number with 32-bits or less is supported
72 0 : int32_t accum1 = 0x0;
73 0 : int32_t accum2 = 0x0;
74 0 : int32_t all1s = -1;
75 :
76 0 : for (uint16_t i = 0; i < len; i++)
77 : {
78 0 : uint8_t j = (val1 == nullptr ? 0 : (EM_BIG_ENDIAN ? val1[i] : val1[(len - 1) - i]));
79 0 : accum1 |= j << (8 * (len - 1 - i));
80 :
81 0 : uint8_t k = (EM_BIG_ENDIAN ? val2[i] : val2[(len - 1) - i]);
82 0 : accum2 |= k << (8 * (len - 1 - i));
83 : }
84 :
85 : // sign extending, no need for 32-bits numbers
86 0 : if (len < 4)
87 : {
88 0 : if ((accum1 & (1 << (8 * len - 1))) != 0)
89 : { // check sign
90 0 : accum1 |= all1s - ((1 << (len * 8)) - 1);
91 : }
92 0 : if ((accum2 & (1 << (8 * len - 1))) != 0)
93 : { // check sign
94 0 : accum2 |= all1s - ((1 << (len * 8)) - 1);
95 : }
96 : }
97 :
98 0 : if (accum1 > accum2)
99 : {
100 0 : return 1;
101 : }
102 0 : if (accum1 < accum2)
103 : {
104 0 : return -1;
105 : }
106 :
107 0 : return 0;
108 : }
109 :
110 : // not supported
111 0 : return 0;
112 : }
113 :
114 : // regular unsigned number comparison
115 0 : for (uint16_t i = 0; i < len; i++)
116 : {
117 0 : uint8_t j = (val1 == nullptr ? 0 : (EM_BIG_ENDIAN ? val1[i] : val1[(len - 1) - i]));
118 0 : uint8_t k = (EM_BIG_ENDIAN ? val2[i] : val2[(len - 1) - i]);
119 :
120 0 : if (j > k)
121 : {
122 0 : return 1;
123 : }
124 0 : if (k > j)
125 : {
126 0 : return -1;
127 : }
128 : }
129 0 : return 0;
130 : }
131 :
132 : /**
133 : * @brief write an attribute, performing all the checks.
134 : *
135 : * This function will attempt to write the attribute value from
136 : * the provided pointer. This function will only check that the
137 : * attribute exists. If it does it will write the value into
138 : * the attribute table for the given attribute.
139 : *
140 : * This function will not check to see if the attribute is
141 : * writable since the read only / writable characteristic
142 : * of an attribute only pertains to external devices writing
143 : * over the air. Because this function is being called locally
144 : * it assumes that the device knows what it is doing and has permission
145 : * to perform the given operation.
146 : *
147 : * if true is passed in for overrideReadOnlyAndDataType then the data type is
148 : * not checked and the read-only flag is ignored. This mode is meant for
149 : * testing or setting the initial value of the attribute on the device.
150 : *
151 : * this returns:
152 : * - Status::UnsupportedEndpoint: if endpoint isn't supported by the device.
153 : * - Status::UnsupportedCluster: if cluster isn't supported on the endpoint.
154 : * - Status::UnsupportedAttribute: if attribute isn't supported in the cluster.
155 : * - Status::InvalidDataType: if the data type passed in doesnt match the type
156 : * stored in the attribute table
157 : * - Status::UnsupportedWrite: if the attribute isnt writable
158 : * - Status::ConstraintError: if the value is set out of the allowable range for
159 : * the attribute
160 : * - Status::Success: if the attribute was found and successfully written
161 : */
162 : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input,
163 : bool overrideReadOnlyAndDataType);
164 :
165 : } // anonymous namespace
166 :
167 0 : Protocols::InteractionModel::Status emAfWriteAttributeExternal(const ConcreteAttributePath & path,
168 : const EmberAfWriteDataInput & input)
169 : {
170 0 : return emAfWriteAttribute(path, input, false /* overrideReadOnlyAndDataType */);
171 : }
172 :
173 0 : Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
174 : EmberAfAttributeType dataType)
175 : {
176 :
177 0 : return emberAfWriteAttribute(ConcreteAttributePath(endpoint, cluster, attributeID), EmberAfWriteDataInput(dataPtr, dataType));
178 : }
179 :
180 0 : Status emberAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input)
181 : {
182 0 : return emAfWriteAttribute(path, input, true /* overrideReadOnlyAndDataType */);
183 : }
184 :
185 : //------------------------------------------------------------------------------
186 : // Internal Functions
187 :
188 : // Helper for determining whether a value is a null value.
189 : template <typename T>
190 0 : static bool IsNullValue(const uint8_t * data)
191 : {
192 : using Traits = app::NumericAttributeTraits<T>;
193 : // We don't know how data is aligned, so safely copy it over to the relevant
194 : // StorageType value.
195 : typename Traits::StorageType val;
196 0 : memcpy(&val, data, sizeof(val));
197 0 : return Traits::IsNullValue(val);
198 : }
199 :
200 0 : static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttributeSigned)
201 : {
202 0 : if (dataLen > 4)
203 : {
204 : // We don't support this, just like emberAfCompareValues does not.
205 0 : return false;
206 : }
207 :
208 0 : switch (dataLen)
209 : {
210 0 : case 1: {
211 0 : if (isAttributeSigned)
212 : {
213 0 : return IsNullValue<int8_t>(data);
214 : }
215 0 : return IsNullValue<uint8_t>(data);
216 : }
217 0 : case 2: {
218 0 : if (isAttributeSigned)
219 : {
220 0 : return IsNullValue<int16_t>(data);
221 : }
222 0 : return IsNullValue<uint16_t>(data);
223 : }
224 0 : case 3: {
225 0 : if (isAttributeSigned)
226 : {
227 0 : return IsNullValue<app::OddSizedInteger<3, true>>(data);
228 : }
229 0 : return IsNullValue<app::OddSizedInteger<3, false>>(data);
230 : }
231 0 : case 4: {
232 0 : if (isAttributeSigned)
233 : {
234 0 : return IsNullValue<int32_t>(data);
235 : }
236 0 : return IsNullValue<uint32_t>(data);
237 : }
238 : }
239 :
240 : // Not reached.
241 0 : return false;
242 : }
243 :
244 : namespace {
245 :
246 : /**
247 : * Helper function to determine whether the attribute value for the given
248 : * attribute is changing. On success, the isChanging outparam will be set to
249 : * whether the value is changing.
250 : */
251 0 : Status AttributeValueIsChanging(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
252 : const EmberAfAttributeMetadata * metadata, uint8_t * newValueData, bool * isChanging)
253 : {
254 0 : EmberAfAttributeType attributeType = metadata->attributeType;
255 :
256 : // We don't know how to size our buffer for strings in general, but if the
257 : // string happens to fit into our fixed-size buffer, great.
258 0 : size_t valueSize = metadata->size;
259 0 : constexpr size_t kMaxValueSize = 16; // ipv6adr
260 0 : if (valueSize > kMaxValueSize)
261 : {
262 0 : if (emberAfIsStringAttributeType(attributeType) || emberAfIsLongStringAttributeType(attributeType))
263 : {
264 : // It's a string that may not fit in our buffer. Just claim it's
265 : // changing, since we have no way to tell.
266 0 : *isChanging = true;
267 0 : return Status::Success;
268 : }
269 :
270 : // Very much unexpected
271 0 : ChipLogError(Zcl, "Attribute type %d has too-large size %u", attributeType, static_cast<unsigned>(valueSize));
272 0 : return Status::ConstraintError;
273 : }
274 :
275 : uint8_t oldValueBuffer[kMaxValueSize];
276 : // Cast to uint16_t is safe, because we checked valueSize <= kMaxValueSize above.
277 0 : if (emberAfReadAttribute(endpoint, cluster, attributeID, oldValueBuffer, static_cast<uint16_t>(valueSize)) != Status::Success)
278 : {
279 : // We failed to read the old value, so flag the value as changing to be safe.
280 0 : *isChanging = true;
281 0 : return Status::Success;
282 : }
283 :
284 0 : if (emberAfIsStringAttributeType(attributeType))
285 : {
286 0 : size_t oldLength = emberAfStringLength(oldValueBuffer);
287 0 : size_t newLength = emberAfStringLength(newValueData);
288 : // The first byte of the buffer is the string length, and
289 : // oldLength/newLength refer to the number of bytes after that. We want
290 : // to include that first byte in our comparison, because null and empty
291 : // string have different values there but both return 0 from
292 : // emberAfStringLength.
293 0 : *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 1) != 0);
294 : }
295 0 : else if (emberAfIsLongStringAttributeType(attributeType))
296 : {
297 0 : size_t oldLength = emberAfLongStringLength(oldValueBuffer);
298 0 : size_t newLength = emberAfLongStringLength(newValueData);
299 : // The first two bytes of the buffer are the string length, and
300 : // oldLength/newLength refer to the number of bytes after that. We want
301 : // to include those first two bytes in our comparison, because null and
302 : // empty string have different values there but both return 0 from
303 : // emberAfLongStringLength.
304 0 : *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 2) != 0);
305 : }
306 : else
307 : {
308 0 : *isChanging = (memcmp(newValueData, oldValueBuffer, valueSize) != 0);
309 : }
310 :
311 0 : return Status::Success;
312 : }
313 :
314 0 : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input, bool overrideReadOnlyAndDataType)
315 : {
316 0 : const EmberAfAttributeMetadata * metadata = nullptr;
317 : EmberAfAttributeSearchRecord record;
318 0 : record.endpoint = path.mEndpointId;
319 0 : record.clusterId = path.mClusterId;
320 0 : record.attributeId = path.mAttributeId;
321 0 : Status status = emAfReadOrWriteAttribute(&record, &metadata,
322 : nullptr, // buffer
323 : 0, // buffer size
324 : false); // write?
325 :
326 : // if we dont support that attribute
327 0 : if (metadata == nullptr)
328 : {
329 0 : ChipLogProgress(Zcl, "WRITE ERR: ep %x clus " ChipLogFormatMEI " attr " ChipLogFormatMEI " not supported", path.mEndpointId,
330 : ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId));
331 0 : return status;
332 : }
333 :
334 : // if the data type specified by the caller is incorrect
335 0 : if (!(overrideReadOnlyAndDataType))
336 : {
337 0 : if (input.dataType != metadata->attributeType)
338 : {
339 0 : ChipLogProgress(Zcl, "WRITE ERR: invalid data type");
340 0 : return Status::InvalidDataType;
341 : }
342 :
343 0 : if (!metadata->IsWritable())
344 : {
345 0 : ChipLogProgress(Zcl, "WRITE ERR: attr not writable");
346 0 : return Status::UnsupportedWrite;
347 : }
348 : }
349 :
350 : // if the value the attribute is being set to is out of range
351 : // return Status::ConstraintError
352 0 : if ((metadata->mask & MATTER_ATTRIBUTE_FLAG_MIN_MAX) != 0U)
353 : {
354 0 : EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue;
355 0 : EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue;
356 0 : uint16_t dataLen = emberAfAttributeSize(metadata);
357 : const uint8_t * minBytes;
358 : const uint8_t * maxBytes;
359 0 : if (dataLen <= 2)
360 : {
361 : static_assert(sizeof(minv.defaultValue) == 2, "if statement relies on size of minv.defaultValue being 2");
362 : static_assert(sizeof(maxv.defaultValue) == 2, "if statement relies on size of maxv.defaultValue being 2");
363 0 : minBytes = reinterpret_cast<const uint8_t *>(&(minv.defaultValue));
364 0 : maxBytes = reinterpret_cast<const uint8_t *>(&(maxv.defaultValue));
365 : // On big endian cpu with length 1 only the second byte counts
366 : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
367 : if (dataLen == 1)
368 : {
369 : minBytes++;
370 : maxBytes++;
371 : }
372 : #endif // CHIP_CONFIG_BIG_ENDIAN_TARGET
373 : }
374 : else
375 : {
376 0 : minBytes = minv.ptrToDefaultValue;
377 0 : maxBytes = maxv.ptrToDefaultValue;
378 : }
379 :
380 0 : bool isAttributeSigned = metadata->IsSignedIntegerAttribute();
381 0 : bool isOutOfRange = emberAfCompareValues(minBytes, input.dataPtr, dataLen, isAttributeSigned) == 1 ||
382 0 : emberAfCompareValues(maxBytes, input.dataPtr, dataLen, isAttributeSigned) == -1;
383 :
384 0 : if (isOutOfRange &&
385 : // null value is always in-range for a nullable attribute.
386 0 : (!metadata->IsNullable() || !IsNullValue(input.dataPtr, dataLen, isAttributeSigned)))
387 : {
388 0 : return Status::ConstraintError;
389 : }
390 : }
391 :
392 : // Check whether anything is actually changing, before we do any work here.
393 : bool valueChanging;
394 : Status imStatus =
395 0 : AttributeValueIsChanging(path.mEndpointId, path.mClusterId, path.mAttributeId, metadata, input.dataPtr, &valueChanging);
396 0 : if (imStatus != Status::Success)
397 : {
398 0 : return imStatus;
399 : }
400 :
401 0 : if (!valueChanging)
402 : {
403 : // Just do nothing, except triggering reporting if forced.
404 0 : if (input.markDirty == MarkAttributeDirty::kYes)
405 : {
406 0 : emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId);
407 : }
408 :
409 0 : return Status::Success;
410 : }
411 :
412 0 : const app::ConcreteAttributePath attributePath(path.mEndpointId, path.mClusterId, path.mAttributeId);
413 :
414 : // Pre write attribute callback for all attribute changes,
415 : // regardless of cluster.
416 0 : imStatus = MatterPreAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
417 0 : if (imStatus != Protocols::InteractionModel::Status::Success)
418 : {
419 0 : return imStatus;
420 : }
421 :
422 : // Pre-write attribute callback specific
423 : // to the cluster that the attribute lives in.
424 0 : status = emAfClusterPreAttributeChangedCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
425 :
426 : // Ignore the following write operation and return success
427 0 : if (status == Status::WriteIgnored)
428 : {
429 0 : return Status::Success;
430 : }
431 :
432 0 : if (status != Status::Success)
433 : {
434 0 : return status;
435 : }
436 :
437 : // write the attribute
438 0 : status = emAfReadOrWriteAttribute(&record,
439 : nullptr, // metadata
440 0 : input.dataPtr,
441 : 0, // buffer size - unused
442 : true); // write?
443 :
444 0 : if (status != Status::Success)
445 : {
446 0 : return status;
447 : }
448 :
449 : // Save the attribute to persistent storage if needed
450 : // The callee will weed out attributes that do not need to be stored.
451 0 : emAfSaveAttributeToStorageIfNeeded(input.dataPtr, path.mEndpointId, path.mClusterId, metadata);
452 :
453 0 : if (input.markDirty != MarkAttributeDirty::kNo)
454 : {
455 0 : emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId);
456 : }
457 :
458 : // Post write attribute callback for all attributes changes, regardless
459 : // of cluster.
460 0 : MatterPostAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
461 :
462 : // Post-write attribute callback specific
463 : // to the cluster that the attribute lives in.
464 0 : emAfClusterAttributeChangedCallback(attributePath);
465 :
466 0 : return Status::Success;
467 : }
468 :
469 : } // anonymous namespace
470 :
471 0 : Status emberAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
472 : {
473 0 : const EmberAfAttributeMetadata * metadata = nullptr;
474 : EmberAfAttributeSearchRecord record;
475 : Status status;
476 0 : record.endpoint = endpoint;
477 0 : record.clusterId = cluster;
478 0 : record.attributeId = attributeID;
479 0 : status = emAfReadOrWriteAttribute(&record, &metadata, dataPtr, readLength,
480 : false); // write?
481 :
482 : // failed, print debug info
483 0 : if (status == Status::ResourceExhausted)
484 : {
485 0 : ChipLogProgress(Zcl, "READ: attribute size too large for caller");
486 : }
487 :
488 0 : return status;
489 : }
|