|
41 | 41 | #include <system/SystemPacketBuffer.h>
|
42 | 42 | #include <system/TLVPacketBufferBackingStore.h>
|
43 | 43 |
|
| 44 | +#include <app-common/zap-generated/ids/Attributes.h> |
| 45 | +#include <app-common/zap-generated/ids/Clusters.h> |
44 | 46 | namespace chip {
|
45 | 47 | namespace app {
|
46 | 48 |
|
@@ -162,25 +164,71 @@ class WriteClient : public Messaging::ExchangeDelegate
|
162 | 164 |
|
163 | 165 | /**
|
164 | 166 | * Encode a possibly-chunked list attribute value. Will create a new chunk when necessary.
|
| 167 | + * |
| 168 | + * This function will Attempt to to encode as many list items as possible into a SingleAttributeDataIB, which will be handled by |
| 169 | + cluster server as a ReplaceAll Item operation |
| 170 | + * If the list is too large, WriteRequest will be chunked and remaining items will be encoded as AppendItem operations, chunking |
| 171 | + them as needed |
| 172 | + * |
| 173 | + // The items in this list will be decoded by servers as part of the ReplaceAll list. |
| 174 | +
|
165 | 175 | */
|
166 | 176 | template <class T>
|
167 |
| - CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & value, |
| 177 | + CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & listValue, |
168 | 178 | const Optional<DataVersion> & aDataVersion = NullOptional)
|
169 | 179 | {
|
170 | 180 | // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
|
171 | 181 | ConcreteDataAttributePath path =
|
172 | 182 | ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
|
173 | 183 | attributePath.mClusterId, attributePath.mAttributeId, aDataVersion);
|
174 | 184 |
|
175 |
| - ReturnErrorOnFailure(EnsureMessage()); |
| 185 | + ListIndex nextItemToAppendIndex = kInvalidListIndex; |
| 186 | + // TODO CHANGE NAME OF numEncodedItems and maybe not make it listindex? |
| 187 | + uint16_t numSuccessfullyEncodedItems = kInvalidListIndex; |
| 188 | + bool chunkingNeeded = false; |
| 189 | + |
| 190 | + VerifyOrReturnError(!listValue.empty(), CHIP_ERROR_INVALID_LIST_LENGTH); |
| 191 | + |
| 192 | + // BACKWARD COMPATIBILITY: Legacy OTA Requestor Servers( Pre-Matter 1.4) only support having an empty list for ReplaceAll |
| 193 | + // when writing to the DefaultOTAProviders attribute and they will only Decode List Items that are of the AppendItem List |
| 194 | + // operation. So we must allow this for backward compatiblity. |
| 195 | + // TODO remove this special case when Matter 1.3 is Sunsetted |
| 196 | + bool encodeEmptyListAsReplaceAll = |
| 197 | + (path.mClusterId == Clusters::OtaSoftwareUpdateRequestor::Id && |
| 198 | + path.mAttributeId == Clusters::OtaSoftwareUpdateRequestor::Attributes::DefaultOTAProviders::Id); |
| 199 | + |
| 200 | + if (encodeEmptyListAsReplaceAll) |
| 201 | + { |
| 202 | + ReturnErrorOnFailure(EnsureMessage()); |
| 203 | + ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>())); |
| 204 | + } |
| 205 | + else |
| 206 | + { |
| 207 | + // StartNewMessage was called to make sure we always create a new chunk when we have a new Attribute to Encode, this |
| 208 | + // might be more efficient |
| 209 | + // TODO should we keep this or just use EnsureMessage; if we choose EnsureMessage, we will have to adapt |
| 210 | + // EnsureListStarted to make sure we create a New Chunk if it fails (could happens when a new Attribute is added) |
| 211 | + ReturnErrorOnFailure(StartNewMessage()); |
176 | 212 |
|
177 |
| - // Encode an empty list for the chunking protocol. |
178 |
| - ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>())); |
| 213 | + // TODO CHANGE NAME OF numEncodedItems |
| 214 | + ReturnErrorOnFailure( |
| 215 | + TryEncodeListIntoSingleAttributeDataIB(path, listValue, chunkingNeeded, numSuccessfullyEncodedItems)); |
| 216 | + if (!chunkingNeeded) |
| 217 | + { |
| 218 | + return CHIP_NO_ERROR; |
| 219 | + } |
| 220 | + // Start a new WriteRequest chunk. |
| 221 | + ReturnErrorOnFailure(StartNewMessage()); |
| 222 | + nextItemToAppendIndex = numSuccessfullyEncodedItems; |
| 223 | + } |
179 | 224 |
|
180 | 225 | path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
|
181 |
| - for (size_t i = 0; i < value.size(); i++) |
| 226 | + |
| 227 | + ListIndex startIndex = encodeEmptyListAsReplaceAll ? 0 : nextItemToAppendIndex; |
| 228 | + |
| 229 | + for (ListIndex i = startIndex; i < listValue.size(); i++) |
182 | 230 | {
|
183 |
| - ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, value.data()[i])); |
| 231 | + ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, listValue[i])); |
184 | 232 | }
|
185 | 233 |
|
186 | 234 | return CHIP_NO_ERROR;
|
@@ -254,31 +302,108 @@ class WriteClient : public Messaging::ExchangeDelegate
|
254 | 302 | /**
|
255 | 303 | * Encode an attribute value that can be directly encoded using DataModel::Encode.
|
256 | 304 | */
|
257 |
| - template <class T> |
| 305 | + template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0> |
258 | 306 | CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
|
259 | 307 | {
|
260 | 308 | chip::TLV::TLVWriter * writer = nullptr;
|
261 | 309 |
|
262 | 310 | ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
|
263 | 311 | VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
|
264 |
| - // Use if constexpr to make compile-time decision on which encoding method to use |
265 |
| - if constexpr (DataModel::IsFabricScoped<T>::value) |
266 |
| - { |
267 |
| - ReturnErrorOnFailure( |
268 |
| - DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value)); |
269 |
| - } |
270 |
| - else |
| 312 | + ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value)); |
| 313 | + ReturnErrorOnFailure(FinishAttributeIB()); |
| 314 | + |
| 315 | + return CHIP_NO_ERROR; |
| 316 | + } |
| 317 | + |
| 318 | + template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0> |
| 319 | + CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value) |
| 320 | + { |
| 321 | + chip::TLV::TLVWriter * writer = nullptr; |
| 322 | + |
| 323 | + ReturnErrorOnFailure(PrepareAttributeIB(attributePath)); |
| 324 | + VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| 325 | + ReturnErrorOnFailure( |
| 326 | + DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value)); |
| 327 | + ReturnErrorOnFailure(FinishAttributeIB()); |
| 328 | + |
| 329 | + return CHIP_NO_ERROR; |
| 330 | + } |
| 331 | + |
| 332 | + CHIP_ERROR EnsureListStarted(const ConcreteDataAttributePath & attributePath) |
| 333 | + { |
| 334 | + chip::TLV::TLVWriter * writer = nullptr; |
| 335 | + |
| 336 | + TLV::TLVType outerType; |
| 337 | + |
| 338 | + ReturnErrorOnFailure(PrepareAttributeIB(attributePath)); |
| 339 | + VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| 340 | + ReturnErrorOnFailure( |
| 341 | + writer->StartContainer(chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), TLV::kTLVType_Array, outerType)); |
| 342 | + |
| 343 | + VerifyOrDie(outerType == kAttributeDataIBType); |
| 344 | + |
| 345 | + return CHIP_NO_ERROR; |
| 346 | + } |
| 347 | + |
| 348 | + CHIP_ERROR EnsureListEnded() |
| 349 | + { |
| 350 | + chip::TLV::TLVWriter * writer = nullptr; |
| 351 | + VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| 352 | + |
| 353 | + // In the event of Chunking (we have a CHIP_ERROR_NO_MEMORY), we need to Unreserve two more Bytes in order to be able to |
| 354 | + // Append EndOfContainer of the List + EndOfContainer of the AttributeDataIB |
| 355 | + VerifyOrDie(writer->UnreserveBuffer(kReservedSizeForEndOfListContainer + kReservedSizeForEndOfAttributeDataIB) == |
| 356 | + CHIP_NO_ERROR); |
| 357 | + ReturnErrorOnFailure(writer->EndContainer(kAttributeDataIBType)); |
| 358 | + ReturnErrorOnFailure(FinishAttributeIB()); |
| 359 | + |
| 360 | + return CHIP_NO_ERROR; |
| 361 | + } |
| 362 | + |
| 363 | + template <class T> |
| 364 | + CHIP_ERROR TryEncodeListIntoSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, |
| 365 | + const DataModel::List<T> & list, bool & chunkingNeeded, |
| 366 | + ListIndex & numSuccessfullyEncodedItems) |
| 367 | + { |
| 368 | + CHIP_ERROR err = CHIP_NO_ERROR; |
| 369 | + TLV::TLVWriter backupWriter; |
| 370 | + |
| 371 | + ReturnErrorOnFailure(EnsureListStarted(attributePath)); |
| 372 | + |
| 373 | + chip::TLV::TLVWriter * writer = nullptr; |
| 374 | + VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| 375 | + |
| 376 | + numSuccessfullyEncodedItems = 0; |
| 377 | + |
| 378 | + for (auto & item : list) |
271 | 379 | {
|
272 |
| - ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value)); |
| 380 | + mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter); |
| 381 | + if constexpr (DataModel::IsFabricScoped<T>::value) |
| 382 | + { |
| 383 | + err = DataModel::EncodeForWrite(*writer, TLV::AnonymousTag(), item); |
| 384 | + } |
| 385 | + else |
| 386 | + { |
| 387 | + err = DataModel::Encode(*writer, TLV::AnonymousTag(), item); |
| 388 | + } |
| 389 | + |
| 390 | + if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL) |
| 391 | + { |
| 392 | + mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter); |
| 393 | + chunkingNeeded = true; |
| 394 | + break; |
| 395 | + } |
| 396 | + ReturnErrorOnFailure(err); |
| 397 | + numSuccessfullyEncodedItems++; |
273 | 398 | }
|
274 |
| - ReturnErrorOnFailure(FinishAttributeIB()); |
275 | 399 |
|
| 400 | + ReturnErrorOnFailure(EnsureListEnded()); |
276 | 401 | return CHIP_NO_ERROR;
|
277 | 402 | }
|
278 | 403 |
|
279 | 404 | /**
|
280 | 405 | * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
|
281 |
| - * CHIP_ERROR_BUFFER_TOO_SMALL. |
| 406 | + * CHIP_ERROR_BUFFER_TOO_SMALL. This will not be used for Lists |
282 | 407 | */
|
283 | 408 | template <class T>
|
284 | 409 | CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
|
@@ -418,6 +543,13 @@ class WriteClient : public Messaging::ExchangeDelegate
|
418 | 543 | static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
|
419 | 544 | kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
|
420 | 545 | bool mHasDataVersion = false;
|
| 546 | + |
| 547 | + // This was not added to kReservedSizeForTLVEncodingOverhead since it will be "Unreserved" before the others. |
| 548 | + static constexpr uint32_t kReservedSizeForEndOfListContainer = 1; |
| 549 | + // TODO should this be 2 ? |
| 550 | + static constexpr uint32_t kReservedSizeForEndOfAttributeDataIB = 1; |
| 551 | + |
| 552 | + static constexpr TLV::TLVType kAttributeDataIBType = TLV::kTLVType_Structure; |
421 | 553 | };
|
422 | 554 |
|
423 | 555 | } // namespace app
|
|
0 commit comments