Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2021 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #pragma once
20 :
21 : #include <app-common/zap-generated/ids/Attributes.h>
22 : #include <app-common/zap-generated/ids/Clusters.h>
23 : #include <app/AttributePathParams.h>
24 : #include <app/ConcreteAttributePath.h>
25 : #include <app/InteractionModelTimeout.h>
26 : #include <app/MessageDef/AttributeDataIBs.h>
27 : #include <app/MessageDef/AttributeStatusIB.h>
28 : #include <app/MessageDef/StatusIB.h>
29 : #include <app/MessageDef/WriteRequestMessage.h>
30 : #include <app/data-model/Encode.h>
31 : #include <app/data-model/FabricScoped.h>
32 : #include <app/data-model/List.h>
33 : #include <lib/core/CHIPCore.h>
34 : #include <lib/core/TLVDebug.h>
35 : #include <lib/support/CodeUtils.h>
36 : #include <lib/support/DLLUtil.h>
37 : #include <lib/support/logging/CHIPLogging.h>
38 : #include <messaging/ExchangeHolder.h>
39 : #include <messaging/ExchangeMgr.h>
40 : #include <messaging/Flags.h>
41 : #include <platform/LockTracker.h>
42 : #include <protocols/Protocols.h>
43 : #include <system/SystemPacketBuffer.h>
44 : #include <system/TLVPacketBufferBackingStore.h>
45 :
46 : namespace chip {
47 : namespace app {
48 :
49 : class InteractionModelEngine;
50 :
51 : /**
52 : * @brief The write client represents the initiator side of a Write Interaction, and is responsible
53 : * for generating one Write Request for a particular set of attributes, and handling the Write response.
54 : * Consumer can allocate one write client, then call PrepareAttribute, insert attribute value, followed
55 : * by FinishAttribute for every attribute it wants to insert in write request, then call SendWriteRequest
56 : *
57 : * Note: When writing lists, you may receive multiple write status responses for a single list.
58 : * Please see ChunkedWriteCallback.h for a high level API which will merge status codes for
59 : * chunked write requests.
60 : *
61 : */
62 : class WriteClient : public Messaging::ExchangeDelegate
63 : {
64 : public:
65 : class Callback
66 : {
67 : public:
68 1072 : virtual ~Callback() = default;
69 :
70 : /**
71 : * OnResponse will be called when a write response has been received
72 : * and processed for the given path.
73 : *
74 : * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
75 : * receives an OnDone call before it shuts down the object.
76 : *
77 : * @param[in] apWriteClient The write client object that initiated the write transaction.
78 : * @param[in] aPath The attribute path field in write response.
79 : * @param[in] attributeStatus Attribute-specific status, containing an InteractionModel::Status code as well as
80 : * an optional cluster-specific status code.
81 : */
82 0 : virtual void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath,
83 : StatusIB attributeStatus)
84 0 : {}
85 :
86 : /**
87 : * OnError will be called when an error occurs *after* a successful call to SendWriteRequest(). The following
88 : * errors will be delivered through this call in the aError field:
89 : *
90 : * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout.
91 : * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server.
92 : * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific
93 : * status response from the server. In that case, constructing
94 : * a StatusIB from the error can be used to extract the status.
95 : * - CHIP_ERROR*: All other cases.
96 : *
97 : * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
98 : * receives an OnDone call before it shuts down the object.
99 : *
100 : * @param[in] apWriteClient The write client object that initiated the attribute write transaction.
101 : * @param[in] aError A system error code that conveys the overall error code.
102 : */
103 0 : virtual void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) {}
104 :
105 : /**
106 : * OnDone will be called when WriteClient has finished all work and is reserved for future WriteClient ownership change.
107 : * (#10366) Users may use this function to release their own objects related to this write interaction.
108 : *
109 : * This function will:
110 : * - Always be called exactly *once* for a given WriteClient instance.
111 : * - Be called even in error circumstances.
112 : * - Only be called after a successful call to SendWriteRequest has been made.
113 : *
114 : * @param[in] apWriteClient The write client object of the terminated write transaction.
115 : */
116 : virtual void OnDone(WriteClient * apWriteClient) = 0;
117 : };
118 :
119 : /**
120 : * Construct the client object. Within the lifetime
121 : * of this instance.
122 : *
123 : * @param[in] apExchangeMgr A pointer to the ExchangeManager object.
124 : * @param[in] apCallback Callback set by application.
125 : * @param[in] aTimedWriteTimeoutMs If provided, do a timed write using this timeout.
126 : * @param[in] aSuppressResponse If provided, set SuppressResponse field to the provided value
127 : */
128 718 : WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
129 718 : bool aSuppressResponse = false) :
130 718 : mpExchangeMgr(apExchangeMgr),
131 718 : mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
132 1436 : mSuppressResponse(aSuppressResponse), mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue())
133 : {
134 718 : assertChipStackLockedByCurrentThread();
135 718 : }
136 :
137 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
138 346 : WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
139 346 : uint16_t aReservedSize) :
140 346 : mpExchangeMgr(apExchangeMgr),
141 346 : mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize),
142 692 : mTimedRequestFieldValue(aTimedWriteTimeoutMs.HasValue())
143 : {
144 346 : assertChipStackLockedByCurrentThread();
145 346 : }
146 :
147 : // Tag type to distinguish the test constructor from the normal constructor
148 : struct TestOnlyOverrideTimedRequestFieldTag
149 : {
150 : };
151 :
152 : /**
153 : * TestOnly constructor that decouples the Timed Request action from the TimedRequest field value.
154 : *
155 : * IMPORTANT: Understanding the distinction between two concepts:
156 : * 1. TIMED REQUEST ACTION: A preceding TimedRequest protocol message sent before the actual Write Request.
157 : * This establishes a time window during which the server will accept the write.
158 : * This is controlled by the mTimedWriteTimeoutMs field.
159 : *
160 : * 2. TIMEDREQUEST FIELD: A boolean field in the WriteRequest message itself that indicates whether
161 : * the write was preceded by a Timed Request action.
162 : * This is controlled by the mTimedRequestFieldValue field.
163 : *
164 : * Normal behavior: When you provide a timeout value to the standard constructor, both happen together:
165 : * - A Timed Request action is sent (controlled by mTimedWriteTimeoutMs)
166 : * - The TimedRequest field in WriteRequest is set to true (mTimedRequestFieldValue = true)
167 : *
168 : * This test constructor allows you to decouple these for testing all edge cases:
169 : *
170 : * Test scenarios enabled by this constructor:
171 : * 1. Normal write (both false): Action = No, Field = False [aTimedWriteTimeoutMs = Missing,
172 : * aTimedRequestFieldValue = false]
173 : * 2. Normal timed write (both true): Action = Yes, Field = True [aTimedWriteTimeoutMs = value,
174 : * aTimedRequestFieldValue = true]
175 : * 3. Field true, no action (invalid): Action = No, Field = True [aTimedWriteTimeoutMs = Missing,
176 : * aTimedRequestFieldValue = true]
177 : * 4. Action present, field false (invalid): Action = Yes, Field = False [aTimedWriteTimeoutMs = value,
178 : * aTimedRequestFieldValue = false]
179 : *
180 : * @param[in] aTimedWriteTimeoutMs The timeout for the Timed Request action (if provided, action WILL be sent)
181 : * @param[in] aTimedRequestFieldValue The value of the TimedRequest field in WriteRequest (can mismatch the action for testing)
182 : */
183 : WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
184 : bool aTimedRequestFieldValue, TestOnlyOverrideTimedRequestFieldTag) :
185 : mpExchangeMgr(apExchangeMgr),
186 : mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
187 : mTimedRequestFieldValue(aTimedRequestFieldValue)
188 : {
189 : assertChipStackLockedByCurrentThread();
190 : }
191 : #endif
192 :
193 1120 : ~WriteClient() { assertChipStackLockedByCurrentThread(); }
194 :
195 : /**
196 : * Encode an attribute value that can be directly encoded using DataModel::Encode. Will create a new chunk when necessary.
197 : */
198 : template <class T>
199 19 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const T & value,
200 : const Optional<DataVersion> & aDataVersion = NullOptional)
201 : {
202 19 : ReturnErrorOnFailure(EnsureMessage());
203 :
204 : // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
205 19 : return EncodeSingleAttributeDataIB(
206 38 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
207 19 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
208 19 : value);
209 : }
210 :
211 : /**
212 : * Encode a possibly-chunked list attribute value. Will create a new chunk when necessary.
213 : *
214 : * Note: As an exception, for attributes in the Access Control cluster, this method will attempt to encode as many list items
215 : * as possible into a single AttributeDataIB with Change set to REPLACE.
216 : * If the list is too large, the WriteRequest will be chunked and remaining items will be encoded as individual AttributeDataIBs
217 : * with Change set to ADD, chunking them as needed.
218 : *
219 : */
220 : template <class T>
221 875 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & listValue,
222 : const Optional<DataVersion> & aDataVersion = NullOptional)
223 : {
224 : // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
225 1750 : ConcreteDataAttributePath path =
226 875 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
227 875 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion);
228 :
229 875 : ListIndex firstItemToAppendIndex = 0;
230 875 : uint16_t encodedItemCount = 0;
231 875 : bool chunkingNeeded = false;
232 :
233 : // By convention, and as tested against all cluster servers, clients have historically encoded an empty list as a
234 : // ReplaceAll, (i.e. the entire attribute contents are cleared before appending the new list’s items). However, this
235 : // behavior can be problematic, especially for the ACL attribute; sending an empty ReplaceAll list can cause clients to be
236 : // locked out. This is because the empty list first deletes all existing ACL entries, and if the new (malformed) ACL is
237 : // rejected, the server is left without valid (or with incomplete) ACLs.
238 : // SOLUTION: we treat ACL as an exception and avoid encoding an empty ReplaceAll list. Instead, we pack as many ACL entries
239 : // as possible into the ReplaceAll list, and send any remaining entries in subsequent chunks are part of the AppendItem
240 : // list operation.
241 : // TODO (#38270): Generalize this behavior; send a non-empty ReplaceAll list for all clusters in a later Matter version and
242 : // enforce all clusters to support it in testing and in certification.
243 875 : bool encodeEmptyListAsReplaceAll = !(path.mClusterId == Clusters::AccessControl::Id);
244 :
245 875 : ReturnErrorOnFailure(EnsureMessage());
246 :
247 875 : if (encodeEmptyListAsReplaceAll)
248 : {
249 466 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
250 : }
251 : else
252 : {
253 : // Encode as many list-items as possible into a single AttributeDataIB, which will be included in a single
254 : // WriteRequestMessage chunk.
255 409 : ReturnErrorOnFailure(TryEncodeListIntoSingleAttributeDataIB(path, listValue, chunkingNeeded, encodedItemCount));
256 :
257 : // If all list items fit perfectly into a single AttributeDataIB, there is no need for any `append-item` or chunking,
258 : // and we can exit early.
259 409 : VerifyOrReturnError(chunkingNeeded, CHIP_NO_ERROR);
260 :
261 : // Start a new WriteRequest chunk, as there are still remaining list items to encode. These remaining items will be
262 : // appended one by one, each into its own AttributeDataIB. Unlike the first chunk (which contains only one
263 : // AttributeDataIB), subsequent chunks may contain multiple AttributeDataIBs if space allows it.
264 391 : ReturnErrorOnFailure(StartNewMessage());
265 391 : firstItemToAppendIndex = encodedItemCount;
266 : }
267 :
268 857 : path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
269 :
270 857 : const size_t listSize = listValue.size();
271 4301 : for (size_t i = firstItemToAppendIndex; i < listSize; i++)
272 : {
273 3545 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, listValue[i]));
274 : }
275 :
276 756 : return CHIP_NO_ERROR;
277 : }
278 :
279 : /**
280 : * Encode a Nullable attribute value. This needs a separate overload so it can dispatch to the right
281 : * EncodeAttribute when writing a nullable list.
282 : */
283 : template <class T>
284 128 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::Nullable<T> & value,
285 : const Optional<DataVersion> & aDataVersion = NullOptional)
286 : {
287 128 : ReturnErrorOnFailure(EnsureMessage());
288 :
289 128 : if (value.IsNull())
290 : {
291 : // Here, we are using kInvalidEndpointId to for missing endpoint id, which is used when sending group write requests.
292 127 : return EncodeSingleAttributeDataIB(
293 254 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
294 127 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
295 127 : value);
296 : }
297 :
298 1 : return EncodeAttribute(attributePath, value.Value(), aDataVersion);
299 : }
300 :
301 : enum class TestListEncodingOverride
302 : {
303 : kNoOverride,
304 : kForceLegacyEncoding
305 : };
306 :
307 : /**
308 : * Encode an attribute value which is already encoded into a TLV. The TLVReader is expected to be initialized and the read head
309 : * is expected to point to the element to be encoded.
310 : *
311 : * Note: When encoding lists with this function, you may receive more than one write status for a single list. You can refer
312 : * to ChunkedWriteCallback.h for a high level API which will merge status codes for chunked write requests.
313 : *
314 : * Note: forceLegacyListEncoding is used by Test Harness and Python Tests to test backward compatibility and ensure end devices
315 : * support legacy WriteClients
316 : */
317 : CHIP_ERROR PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data,
318 : TestListEncodingOverride encodingBehavior = TestListEncodingOverride::kNoOverride);
319 :
320 : /**
321 : * Once SendWriteRequest returns successfully, the WriteClient will
322 : * handle calling Shutdown on itself once it decides it's done with waiting
323 : * for a response (i.e. times out or gets a response). Client can specify
324 : * the maximum time to wait for response (in milliseconds) via timeout parameter.
325 : * If the timeout is missing or is set to System::Clock::kZero, a value based on the MRP timeouts of the session will be used.
326 : * If SendWriteRequest is never called, or the call fails, the API
327 : * consumer is responsible for calling Shutdown on the WriteClient.
328 : */
329 : CHIP_ERROR SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout = System::Clock::kZero);
330 :
331 : /**
332 : * Returns true if the WriteRequest is Chunked.
333 : * WARNING: This method is only used for UnitTests. It should only be called AFTER a call
334 : * EncodeAttribute/PutPreencodedAttribute AND BEFORE a call to SendWriteRequest(); only during this window does
335 : * "!mChunks.IsNull()" reliably indicate that the WriteRequest is chunked.
336 : */
337 303 : bool IsWriteRequestChunked() const { return !mChunks.IsNull(); };
338 :
339 : private:
340 : friend class TestWriteInteraction;
341 : friend class InteractionModelEngine;
342 : enum class State
343 : {
344 : Initialized = 0, // The client has been initialized
345 : AddAttribute, // The client has added attribute and ready for a SendWriteRequest
346 : AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
347 : AwaitingResponse, // The client has sent out the write request message
348 : ResponseReceived, // We have gotten a response after sending write request
349 : AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
350 : };
351 :
352 : CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
353 : System::PacketBufferHandle && aPayload) override;
354 : void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
355 :
356 : void MoveToState(const State aTargetState);
357 : CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
358 : CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
359 : const char * GetStateStr() const;
360 :
361 : // TODO (#38453) Clarify and fix the API contract of EnsureListStarted, TryToStartList and EnsureListEnded; in the case of
362 : // encoding failure, should we just undo buffer reservation? rollback to a checkpoint that we create within EnsureListStarted?
363 : // or just leave the WriteClient in a bad state.
364 : /**
365 : * A wrapper for TryToStartList which will start a new chunk when TryToStartList fails with CHIP_ERROR_NO_MEMORY or
366 : * CHIP_ERROR_BUFFER_TOO_SMALL.
367 : *
368 : * @note Must always be followed by a call to EnsureListEnded(), to undo buffer reservation that took place within
369 : * it, and properly close TLV Containers.
370 : */
371 : CHIP_ERROR EnsureListStarted(const ConcreteDataAttributePath & attributePath);
372 :
373 : /**
374 : * Prepare the Encoding of an Attribute with List DataType into an AttributeDataIB.
375 : *
376 : */
377 : CHIP_ERROR TryToStartList(const ConcreteDataAttributePath & attributePath);
378 :
379 : /**
380 : * Complete the Encoding of an Attribute with List DataType into an AttributeDataIB.
381 : *
382 : * @note Must always be called after EnsureListStarted(), even in cases of encoding failures; to undo buffer reservation that
383 : * took place in EnsureListStarted.
384 : */
385 : CHIP_ERROR EnsureListEnded();
386 :
387 : /**
388 : * Encode an attribute value that can be directly encoded using DataModel::Encode.
389 : */
390 : template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
391 6728 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
392 : {
393 6728 : chip::TLV::TLVWriter * writer = nullptr;
394 :
395 6728 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
396 6379 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
397 6379 : ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
398 4182 : ReturnErrorOnFailure(FinishAttributeIB());
399 :
400 4164 : return CHIP_NO_ERROR;
401 : }
402 :
403 : template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
404 4 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
405 : {
406 4 : chip::TLV::TLVWriter * writer = nullptr;
407 :
408 4 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
409 4 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
410 4 : ReturnErrorOnFailure(
411 : DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
412 4 : ReturnErrorOnFailure(FinishAttributeIB());
413 :
414 4 : return CHIP_NO_ERROR;
415 : }
416 :
417 : template <class T>
418 409 : CHIP_ERROR TryEncodeListIntoSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath,
419 : const DataModel::List<T> & list, bool & outChunkingNeeded,
420 : uint16_t & outEncodedItemCount)
421 : {
422 409 : ReturnErrorOnFailure(EnsureListStarted(attributePath));
423 :
424 409 : AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
425 409 : TLV::TLVWriter backupWriter;
426 409 : outEncodedItemCount = 0;
427 :
428 1565 : for (auto & item : list)
429 : {
430 : // Try to put all the list items into the list we just started, until we either run out of items
431 : // or run out of space.
432 : // Make sure that if we run out of space we don't leave a partially-encoded list item around.
433 1547 : attributeDataIB.Checkpoint(backupWriter);
434 1547 : CHIP_ERROR err = CHIP_NO_ERROR;
435 :
436 : if constexpr (DataModel::IsFabricScoped<T>::value)
437 : {
438 0 : err = DataModel::EncodeForWrite(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
439 : }
440 : else
441 : {
442 1547 : err = DataModel::Encode(*attributeDataIB.GetWriter(), TLV::AnonymousTag(), item);
443 : }
444 :
445 4250 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
446 : {
447 : // Rollback through the attributeDataIB, which also resets the Builder's error state.
448 : // This returns the object to the state it was in before attempting to encode the list item.
449 391 : attributeDataIB.Rollback(backupWriter);
450 391 : outChunkingNeeded = true;
451 391 : break;
452 : }
453 1156 : ReturnErrorOnFailure(err);
454 1156 : outEncodedItemCount++;
455 : }
456 :
457 409 : return EnsureListEnded();
458 : }
459 :
460 : /**
461 : * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
462 : * CHIP_ERROR_BUFFER_TOO_SMALL.
463 : *
464 : * NOTE: This method must not be used for encoding non-empty lists, even if the template accepts a list type.
465 : * For such cases, use TryEncodeListIntoSingleAttributeDataIB as part of a suitable encoding strategy,
466 : * since it has a different contract and has different usage expectations.
467 : */
468 : template <class T>
469 4269 : CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
470 : {
471 4269 : TLV::TLVWriter backupWriter;
472 :
473 4269 : mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
474 :
475 : // First attempt to write this attribute.
476 4269 : CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
477 10344 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
478 : {
479 : // If it failed with no memory, then we create a new chunk for it.
480 2463 : mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
481 2463 : ReturnErrorOnFailure(StartNewMessage());
482 2463 : ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
483 : }
484 : else
485 : {
486 1806 : ReturnErrorOnFailure(err);
487 : }
488 :
489 4168 : return CHIP_NO_ERROR;
490 : }
491 :
492 : /**
493 : * Encode a preencoded attribute data, returns TLV encode error if the remaining space of current chunk is too small for the
494 : * AttributeDataIB.
495 : */
496 : CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
497 : const TLV::TLVReader & data);
498 :
499 : /**
500 : * Encode a preencoded attribute data, will try to create a new chunk when necessary.
501 : */
502 : CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
503 : const TLV::TLVReader & data);
504 :
505 : /**
506 : * Encodes preencoded attribute data into a list, that will be decoded by cluster servers as a REPLACE Change.
507 : * Returns outChunkingNeeded = true if it was not possible to fit all the data into a single list.
508 : */
509 : CHIP_ERROR TryPutPreencodedAttributeWritePayloadIntoList(const chip::app::ConcreteDataAttributePath & attributePath,
510 : TLV::TLVReader & valueReader, bool & outChunkingNeeded,
511 : uint16_t & outEncodedItemCount);
512 : CHIP_ERROR EnsureMessage();
513 :
514 : /**
515 : * Called internally to signal the completion of all work on this object, gracefully close the
516 : * exchange (by calling into the base class) and finally, signal to the application that it's
517 : * safe to release this object.
518 : */
519 : void Close();
520 :
521 : /**
522 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
523 : * not arise during normal message processing flows that all normally call Close() above. This can only
524 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
525 : * message payloads.
526 : */
527 : void Abort();
528 :
529 : // Send our queued-up Write Request message. Assumes the exchange is ready
530 : // and mPendingWriteData is populated.
531 : CHIP_ERROR SendWriteRequest();
532 :
533 : // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
534 : // is used when sending group write requests.
535 : // TODO(#14935) Update AttributePathParams to support more list operations.
536 : CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
537 : CHIP_ERROR FinishAttributeIB();
538 : TLV::TLVWriter * GetAttributeDataIBTLVWriter();
539 :
540 : /**
541 : * Create a new message (or a new chunk) for the write request.
542 : */
543 : CHIP_ERROR StartNewMessage();
544 :
545 : /**
546 : * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
547 : */
548 : CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
549 :
550 : Messaging::ExchangeManager * mpExchangeMgr = nullptr;
551 : Messaging::ExchangeHolder mExchangeCtx;
552 : Callback * mpCallback = nullptr;
553 : State mState = State::Initialized;
554 : System::PacketBufferTLVWriter mMessageWriter;
555 : WriteRequestMessage::Builder mWriteRequestBuilder;
556 : // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
557 : // but have it hold on to the buffer, and get the buffer from it later.
558 : // Then we could avoid this extra pointer-sized member.
559 : System::PacketBufferHandle mPendingWriteData;
560 : // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
561 : // write.
562 : Optional<uint16_t> mTimedWriteTimeoutMs;
563 : bool mSuppressResponse = false;
564 :
565 : // A list of buffers, one buffer for each chunk.
566 : System::PacketBufferHandle mChunks;
567 :
568 : // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
569 : // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
570 : // resolving it.
571 : // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
572 : uint16_t mReservedSize = 0;
573 : // #endif
574 :
575 : /**
576 : * The value of the TimedRequest field in the WriteRequest message.
577 : *
578 : * This tells the server whether this write was preceded by a Timed Request action.
579 : * Normally this matches whether mTimedWriteTimeoutMs has a value, but test constructors
580 : * can decouple these to test protocol mismatch scenarios.
581 : */
582 : bool mTimedRequestFieldValue = false;
583 :
584 : /**
585 : * Below we define several const variables for encoding overheads.
586 : * WriteRequestMessage =
587 : * {
588 : * timedRequest = false,
589 : * AttributeDataIBs =
590 : * [
591 : * AttributeDataIB = \
592 : * { |
593 : * DataVersion = 0x0, |
594 : * AttributePathIB = |
595 : * { |
596 : * Endpoint = 0x2, | "atomically" encoded via
597 : * Cluster = 0x50f, > EncodeAttribute or
598 : * Attribute = 0x0000_0006, | PutPreencodedAttribute
599 : * ListIndex = Null, |
600 : * } |
601 : * Data = ... |
602 : * }, /
603 : * (...)
604 : * ], <-- 1 byte "end of AttributeDataIB" (end of container)
605 : * moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
606 : * InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
607 : * } <-- 1 byte "end of WriteRequestMessage" (end of container)
608 : */
609 :
610 : // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
611 : static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
612 : // End Of Container (0x18) uses one byte.
613 : static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
614 : // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
615 : // context tag, 1 byte for value
616 : static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
617 : // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
618 : // of WriteRequestMessage (another end of container)).
619 : static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
620 : kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
621 : bool mHasDataVersion = false;
622 :
623 : static constexpr uint16_t kReservedSizeForEndOfListAttributeIB =
624 : kReservedSizeForEndOfContainer + AttributeDataIB::Builder::GetSizeToEndAttributeDataIB();
625 :
626 : static constexpr TLV::TLVType kAttributeDataIBType = TLV::kTLVType_Structure;
627 : };
628 :
629 : } // namespace app
630 : } // namespace chip
|