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 <app/util/odd-sized-integers.h>
27 : #include <lib/core/CHIPConfig.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 : EmberAfWriteDataInput completeInput = input;
171 :
172 0 : if (completeInput.changeListener == nullptr)
173 : {
174 0 : completeInput.SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener());
175 : }
176 :
177 0 : return emAfWriteAttribute(path, completeInput, false /* overrideReadOnlyAndDataType */);
178 : }
179 :
180 0 : Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
181 : EmberAfAttributeType dataType)
182 : {
183 :
184 0 : return emberAfWriteAttribute(
185 0 : ConcreteAttributePath(endpoint, cluster, attributeID),
186 0 : EmberAfWriteDataInput(dataPtr, dataType).SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener()));
187 : }
188 :
189 0 : Status emberAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input)
190 : {
191 0 : EmberAfWriteDataInput completeInput = input;
192 :
193 0 : if (completeInput.changeListener == nullptr)
194 : {
195 0 : completeInput.SetChangeListener(emberAfGlobalInteractionModelAttributesChangedListener());
196 : }
197 :
198 0 : return emAfWriteAttribute(path, completeInput, true /* overrideReadOnlyAndDataType */);
199 : }
200 :
201 : //------------------------------------------------------------------------------
202 : // Internal Functions
203 :
204 : // Helper for determining whether a value is a null value.
205 : template <typename T>
206 0 : static bool IsNullValue(const uint8_t * data)
207 : {
208 : using Traits = app::NumericAttributeTraits<T>;
209 : // We don't know how data is aligned, so safely copy it over to the relevant
210 : // StorageType value.
211 : typename Traits::StorageType val;
212 0 : memcpy(&val, data, sizeof(val));
213 0 : return Traits::IsNullValue(val);
214 : }
215 :
216 0 : static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttributeSigned)
217 : {
218 0 : if (dataLen > 4)
219 : {
220 : // We don't support this, just like emberAfCompareValues does not.
221 0 : return false;
222 : }
223 :
224 0 : switch (dataLen)
225 : {
226 0 : case 1: {
227 0 : if (isAttributeSigned)
228 : {
229 0 : return IsNullValue<int8_t>(data);
230 : }
231 0 : return IsNullValue<uint8_t>(data);
232 : }
233 0 : case 2: {
234 0 : if (isAttributeSigned)
235 : {
236 0 : return IsNullValue<int16_t>(data);
237 : }
238 0 : return IsNullValue<uint16_t>(data);
239 : }
240 0 : case 3: {
241 0 : if (isAttributeSigned)
242 : {
243 0 : return IsNullValue<app::OddSizedInteger<3, true>>(data);
244 : }
245 0 : return IsNullValue<app::OddSizedInteger<3, false>>(data);
246 : }
247 0 : case 4: {
248 0 : if (isAttributeSigned)
249 : {
250 0 : return IsNullValue<int32_t>(data);
251 : }
252 0 : return IsNullValue<uint32_t>(data);
253 : }
254 : }
255 :
256 : // Not reached.
257 0 : return false;
258 : }
259 :
260 : namespace {
261 :
262 : /**
263 : * Helper function to determine whether the attribute value for the given
264 : * attribute is changing. On success, the isChanging outparam will be set to
265 : * whether the value is changing.
266 : */
267 0 : Status AttributeValueIsChanging(EndpointId endpoint, ClusterId cluster, AttributeId attributeID,
268 : const EmberAfAttributeMetadata * metadata, uint8_t * newValueData, bool * isChanging)
269 : {
270 0 : EmberAfAttributeType attributeType = metadata->attributeType;
271 :
272 : // We don't know how to size our buffer for strings in general, but if the
273 : // string happens to fit into our fixed-size buffer, great.
274 0 : size_t valueSize = metadata->size;
275 0 : constexpr size_t kMaxValueSize = 16; // ipv6adr
276 0 : if (valueSize > kMaxValueSize)
277 : {
278 0 : if (emberAfIsStringAttributeType(attributeType) || emberAfIsLongStringAttributeType(attributeType))
279 : {
280 : // It's a string that may not fit in our buffer. Just claim it's
281 : // changing, since we have no way to tell.
282 0 : *isChanging = true;
283 0 : return Status::Success;
284 : }
285 :
286 : // Very much unexpected
287 0 : ChipLogError(Zcl, "Attribute type %d has too-large size %u", attributeType, static_cast<unsigned>(valueSize));
288 0 : return Status::ConstraintError;
289 : }
290 :
291 : uint8_t oldValueBuffer[kMaxValueSize];
292 : // Cast to uint16_t is safe, because we checked valueSize <= kMaxValueSize above.
293 0 : if (emberAfReadAttribute(endpoint, cluster, attributeID, oldValueBuffer, static_cast<uint16_t>(valueSize)) != Status::Success)
294 : {
295 : // We failed to read the old value, so flag the value as changing to be safe.
296 0 : *isChanging = true;
297 0 : return Status::Success;
298 : }
299 :
300 0 : if (emberAfIsStringAttributeType(attributeType))
301 : {
302 0 : size_t oldLength = emberAfStringLength(oldValueBuffer);
303 0 : size_t newLength = emberAfStringLength(newValueData);
304 : // The first byte of the buffer is the string length, and
305 : // oldLength/newLength refer to the number of bytes after that. We want
306 : // to include that first byte in our comparison, because null and empty
307 : // string have different values there but both return 0 from
308 : // emberAfStringLength.
309 0 : *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 1) != 0);
310 : }
311 0 : else if (emberAfIsLongStringAttributeType(attributeType))
312 : {
313 0 : size_t oldLength = emberAfLongStringLength(oldValueBuffer);
314 0 : size_t newLength = emberAfLongStringLength(newValueData);
315 : // The first two bytes of the buffer are the string length, and
316 : // oldLength/newLength refer to the number of bytes after that. We want
317 : // to include those first two bytes in our comparison, because null and
318 : // empty string have different values there but both return 0 from
319 : // emberAfLongStringLength.
320 0 : *isChanging = (oldLength != newLength) || (memcmp(oldValueBuffer, newValueData, oldLength + 2) != 0);
321 : }
322 : else
323 : {
324 0 : *isChanging = (memcmp(newValueData, oldValueBuffer, valueSize) != 0);
325 : }
326 :
327 0 : return Status::Success;
328 : }
329 :
330 0 : Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input, bool overrideReadOnlyAndDataType)
331 : {
332 0 : const EmberAfAttributeMetadata * metadata = nullptr;
333 : EmberAfAttributeSearchRecord record;
334 0 : record.endpoint = path.mEndpointId;
335 0 : record.clusterId = path.mClusterId;
336 0 : record.attributeId = path.mAttributeId;
337 0 : Status status = emAfReadOrWriteAttribute(&record, &metadata,
338 : nullptr, // buffer
339 : 0, // buffer size
340 : false); // write?
341 :
342 : // if we dont support that attribute
343 0 : if (metadata == nullptr)
344 : {
345 0 : ChipLogProgress(Zcl, "WRITE ERR: ep %x clus " ChipLogFormatMEI " attr " ChipLogFormatMEI " not supported", path.mEndpointId,
346 : ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mAttributeId));
347 0 : return status;
348 : }
349 :
350 : // if the data type specified by the caller is incorrect
351 0 : if (!(overrideReadOnlyAndDataType))
352 : {
353 0 : if (input.dataType != metadata->attributeType)
354 : {
355 0 : ChipLogProgress(Zcl, "WRITE ERR: invalid data type");
356 0 : return Status::InvalidDataType;
357 : }
358 :
359 0 : if (metadata->IsReadOnly())
360 : {
361 0 : ChipLogProgress(Zcl, "WRITE ERR: attr not writable");
362 0 : return Status::UnsupportedWrite;
363 : }
364 : }
365 :
366 : // if the value the attribute is being set to is out of range
367 : // return Status::ConstraintError
368 0 : if ((metadata->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U)
369 : {
370 0 : EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue;
371 0 : EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue;
372 0 : uint16_t dataLen = emberAfAttributeSize(metadata);
373 : const uint8_t * minBytes;
374 : const uint8_t * maxBytes;
375 0 : if (dataLen <= 2)
376 : {
377 : static_assert(sizeof(minv.defaultValue) == 2, "if statement relies on size of minv.defaultValue being 2");
378 : static_assert(sizeof(maxv.defaultValue) == 2, "if statement relies on size of maxv.defaultValue being 2");
379 0 : minBytes = reinterpret_cast<const uint8_t *>(&(minv.defaultValue));
380 0 : maxBytes = reinterpret_cast<const uint8_t *>(&(maxv.defaultValue));
381 : // On big endian cpu with length 1 only the second byte counts
382 : #if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
383 : if (dataLen == 1)
384 : {
385 : minBytes++;
386 : maxBytes++;
387 : }
388 : #endif // CHIP_CONFIG_BIG_ENDIAN_TARGET
389 : }
390 : else
391 : {
392 0 : minBytes = minv.ptrToDefaultValue;
393 0 : maxBytes = maxv.ptrToDefaultValue;
394 : }
395 :
396 0 : bool isAttributeSigned = metadata->IsSignedIntegerAttribute();
397 0 : bool isOutOfRange = emberAfCompareValues(minBytes, input.dataPtr, dataLen, isAttributeSigned) == 1 ||
398 0 : emberAfCompareValues(maxBytes, input.dataPtr, dataLen, isAttributeSigned) == -1;
399 :
400 0 : if (isOutOfRange &&
401 : // null value is always in-range for a nullable attribute.
402 0 : (!metadata->IsNullable() || !IsNullValue(input.dataPtr, dataLen, isAttributeSigned)))
403 : {
404 0 : return Status::ConstraintError;
405 : }
406 : }
407 :
408 : // Check whether anything is actually changing, before we do any work here.
409 : bool valueChanging;
410 : Status imStatus =
411 0 : AttributeValueIsChanging(path.mEndpointId, path.mClusterId, path.mAttributeId, metadata, input.dataPtr, &valueChanging);
412 0 : if (imStatus != Status::Success)
413 : {
414 0 : return imStatus;
415 : }
416 :
417 0 : if (!valueChanging)
418 : {
419 : // Just do nothing, except triggering reporting if forced.
420 0 : if (input.markDirty == MarkAttributeDirty::kYes)
421 : {
422 0 : emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, input.changeListener);
423 : }
424 :
425 0 : return Status::Success;
426 : }
427 :
428 0 : const app::ConcreteAttributePath attributePath(path.mEndpointId, path.mClusterId, path.mAttributeId);
429 :
430 : // Pre write attribute callback for all attribute changes,
431 : // regardless of cluster.
432 0 : imStatus = MatterPreAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
433 0 : if (imStatus != Protocols::InteractionModel::Status::Success)
434 : {
435 0 : return imStatus;
436 : }
437 :
438 : // Pre-write attribute callback specific
439 : // to the cluster that the attribute lives in.
440 0 : status = emAfClusterPreAttributeChangedCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
441 :
442 : // Ignore the following write operation and return success
443 0 : if (status == Status::WriteIgnored)
444 : {
445 0 : return Status::Success;
446 : }
447 :
448 0 : if (status != Status::Success)
449 : {
450 0 : return status;
451 : }
452 :
453 : // write the attribute
454 0 : status = emAfReadOrWriteAttribute(&record,
455 : nullptr, // metadata
456 0 : input.dataPtr,
457 : 0, // buffer size - unused
458 : true); // write?
459 :
460 0 : if (status != Status::Success)
461 : {
462 0 : return status;
463 : }
464 :
465 : // Save the attribute to persistent storage if needed
466 : // The callee will weed out attributes that do not need to be stored.
467 0 : emAfSaveAttributeToStorageIfNeeded(input.dataPtr, path.mEndpointId, path.mClusterId, metadata);
468 :
469 0 : if (input.markDirty != MarkAttributeDirty::kNo)
470 : {
471 0 : emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, input.changeListener);
472 : }
473 :
474 : // Post write attribute callback for all attributes changes, regardless
475 : // of cluster.
476 0 : MatterPostAttributeChangeCallback(attributePath, input.dataType, emberAfAttributeSize(metadata), input.dataPtr);
477 :
478 : // Post-write attribute callback specific
479 : // to the cluster that the attribute lives in.
480 0 : emAfClusterAttributeChangedCallback(attributePath);
481 :
482 0 : return Status::Success;
483 : }
484 :
485 : } // anonymous namespace
486 :
487 0 : Status emberAfReadAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr, uint16_t readLength)
488 : {
489 0 : const EmberAfAttributeMetadata * metadata = nullptr;
490 : EmberAfAttributeSearchRecord record;
491 : Status status;
492 0 : record.endpoint = endpoint;
493 0 : record.clusterId = cluster;
494 0 : record.attributeId = attributeID;
495 0 : status = emAfReadOrWriteAttribute(&record, &metadata, dataPtr, readLength,
496 : false); // write?
497 :
498 : // failed, print debug info
499 0 : if (status == Status::ResourceExhausted)
500 : {
501 0 : ChipLogProgress(Zcl, "READ: attribute size too large for caller");
502 : }
503 :
504 0 : return status;
505 : }
|