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/AttributePathParams.h>
22 : #include <app/ConcreteAttributePath.h>
23 : #include <app/InteractionModelTimeout.h>
24 : #include <app/MessageDef/AttributeDataIBs.h>
25 : #include <app/MessageDef/AttributeStatusIB.h>
26 : #include <app/MessageDef/StatusIB.h>
27 : #include <app/MessageDef/WriteRequestMessage.h>
28 : #include <app/data-model/Encode.h>
29 : #include <app/data-model/FabricScoped.h>
30 : #include <app/data-model/List.h>
31 : #include <lib/core/CHIPCore.h>
32 : #include <lib/core/TLVDebug.h>
33 : #include <lib/support/CodeUtils.h>
34 : #include <lib/support/DLLUtil.h>
35 : #include <lib/support/logging/CHIPLogging.h>
36 : #include <messaging/ExchangeHolder.h>
37 : #include <messaging/ExchangeMgr.h>
38 : #include <messaging/Flags.h>
39 : #include <platform/LockTracker.h>
40 : #include <protocols/Protocols.h>
41 : #include <system/SystemPacketBuffer.h>
42 : #include <system/TLVPacketBufferBackingStore.h>
43 :
44 : namespace chip {
45 : namespace app {
46 :
47 : class InteractionModelEngine;
48 :
49 : /**
50 : * @brief The write client represents the initiator side of a Write Interaction, and is responsible
51 : * for generating one Write Request for a particular set of attributes, and handling the Write response.
52 : * Consumer can allocate one write client, then call PrepareAttribute, insert attribute value, followed
53 : * by FinishAttribute for every attribute it wants to insert in write request, then call SendWriteRequest
54 : *
55 : * Note: When writing lists, you may receive multiple write status responses for a single list.
56 : * Please see ChunkedWriteCallback.h for a high level API which will merge status codes for
57 : * chunked write requests.
58 : *
59 : */
60 : class WriteClient : public Messaging::ExchangeDelegate
61 : {
62 : public:
63 : class Callback
64 : {
65 : public:
66 0 : virtual ~Callback() = default;
67 :
68 : /**
69 : * OnResponse will be called when a write response has been received
70 : * and processed for the given path.
71 : *
72 : * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
73 : * receives an OnDone call before it shuts down the object.
74 : *
75 : * @param[in] apWriteClient The write client object that initiated the write transaction.
76 : * @param[in] aPath The attribute path field in write response.
77 : * @param[in] attributeStatus Attribute-specific status, containing an InteractionModel::Status code as well as
78 : * an optional cluster-specific status code.
79 : */
80 0 : virtual void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath,
81 : StatusIB attributeStatus)
82 0 : {}
83 :
84 : /**
85 : * OnError will be called when an error occurs *after* a successful call to SendWriteRequest(). The following
86 : * errors will be delivered through this call in the aError field:
87 : *
88 : * - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout.
89 : * - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server.
90 : * - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific
91 : * status response from the server. In that case,
92 : * StatusIB::InitFromChipError can be used to extract the status.
93 : * - CHIP_ERROR*: All other cases.
94 : *
95 : * The WriteClient object MUST continue to exist after this call is completed. The application shall wait until it
96 : * receives an OnDone call before it shuts down the object.
97 : *
98 : * @param[in] apWriteClient The write client object that initiated the attribute write transaction.
99 : * @param[in] aError A system error code that conveys the overall error code.
100 : */
101 0 : virtual void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) {}
102 :
103 : /**
104 : * OnDone will be called when WriteClient has finished all work and is reserved for future WriteClient ownership change.
105 : * (#10366) Users may use this function to release their own objects related to this write interaction.
106 : *
107 : * This function will:
108 : * - Always be called exactly *once* for a given WriteClient instance.
109 : * - Be called even in error circumstances.
110 : * - Only be called after a successful call to SendWriteRequest has been made.
111 : *
112 : * @param[in] apWriteClient The write client object of the terminated write transaction.
113 : */
114 : virtual void OnDone(WriteClient * apWriteClient) = 0;
115 : };
116 :
117 : /**
118 : * Construct the client object. Within the lifetime
119 : * of this instance.
120 : *
121 : * @param[in] apExchangeMgr A pointer to the ExchangeManager object.
122 : * @param[in] apCallback Callback set by application.
123 : * @param[in] aTimedWriteTimeoutMs If provided, do a timed write using this timeout.
124 : * @param[in] aSuppressResponse If provided, set SuppressResponse field to the provided value
125 : */
126 : WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
127 : bool aSuppressResponse = false) :
128 : mpExchangeMgr(apExchangeMgr),
129 : mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs),
130 : mSuppressResponse(aSuppressResponse)
131 : {
132 : assertChipStackLockedByCurrentThread();
133 : }
134 :
135 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
136 : WriteClient(Messaging::ExchangeManager * apExchangeMgr, Callback * apCallback, const Optional<uint16_t> & aTimedWriteTimeoutMs,
137 : uint16_t aReservedSize) :
138 : mpExchangeMgr(apExchangeMgr),
139 : mExchangeCtx(*this), mpCallback(apCallback), mTimedWriteTimeoutMs(aTimedWriteTimeoutMs), mReservedSize(aReservedSize)
140 : {
141 : assertChipStackLockedByCurrentThread();
142 : }
143 : #endif
144 :
145 7 : ~WriteClient() { assertChipStackLockedByCurrentThread(); }
146 :
147 : /**
148 : * Encode an attribute value that can be directly encoded using DataModel::Encode. Will create a new chunk when necessary.
149 : */
150 : template <class T>
151 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const T & value,
152 : const Optional<DataVersion> & aDataVersion = NullOptional)
153 : {
154 : ReturnErrorOnFailure(EnsureMessage());
155 :
156 : // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
157 : return EncodeSingleAttributeDataIB(
158 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
159 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
160 : value);
161 : }
162 :
163 : /**
164 : * Encode a possibly-chunked list attribute value. Will create a new chunk when necessary.
165 : */
166 : template <class T>
167 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::List<T> & value,
168 : const Optional<DataVersion> & aDataVersion = NullOptional)
169 : {
170 : // Here, we are using kInvalidEndpointId for missing endpoint id, which is used when sending group write requests.
171 : ConcreteDataAttributePath path =
172 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
173 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion);
174 :
175 : ReturnErrorOnFailure(EnsureMessage());
176 :
177 : // Encode an empty list for the chunking protocol.
178 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
179 :
180 : path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
181 : for (ListIndex i = 0; i < value.size(); i++)
182 : {
183 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, value.data()[i]));
184 : }
185 :
186 : return CHIP_NO_ERROR;
187 : }
188 :
189 : /**
190 : * Encode a Nullable attribute value. This needs a separate overload so it can dispatch to the right
191 : * EncodeAttribute when writing a nullable list.
192 : */
193 : template <class T>
194 : CHIP_ERROR EncodeAttribute(const AttributePathParams & attributePath, const DataModel::Nullable<T> & value,
195 : const Optional<DataVersion> & aDataVersion = NullOptional)
196 : {
197 : ReturnErrorOnFailure(EnsureMessage());
198 :
199 : if (value.IsNull())
200 : {
201 : // Here, we are using kInvalidEndpointId to for missing endpoint id, which is used when sending group write requests.
202 : return EncodeSingleAttributeDataIB(
203 : ConcreteDataAttributePath(attributePath.HasWildcardEndpointId() ? kInvalidEndpointId : attributePath.mEndpointId,
204 : attributePath.mClusterId, attributePath.mAttributeId, aDataVersion),
205 : value);
206 : }
207 :
208 : return EncodeAttribute(attributePath, value.Value(), aDataVersion);
209 : }
210 :
211 : /**
212 : * Encode an attribute value which is already encoded into a TLV. The TLVReader is expected to be initialized and the read head
213 : * is expected to point to the element to be encoded.
214 : *
215 : * Note: When encoding lists with this function, you may receive more than one write status for a single list. You can refer
216 : * to ChunkedWriteCallback.h for a high level API which will merge status codes for chunked write requests.
217 : */
218 : CHIP_ERROR PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data);
219 :
220 : /**
221 : * Once SendWriteRequest returns successfully, the WriteClient will
222 : * handle calling Shutdown on itself once it decides it's done with waiting
223 : * for a response (i.e. times out or gets a response). Client can specify
224 : * the maximum time to wait for response (in milliseconds) via timeout parameter.
225 : * 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.
226 : * If SendWriteRequest is never called, or the call fails, the API
227 : * consumer is responsible for calling Shutdown on the WriteClient.
228 : */
229 : CHIP_ERROR SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout = System::Clock::kZero);
230 :
231 : /**
232 : * Shutdown the WriteClient. This terminates this instance
233 : * of the object and releases all held resources.
234 : */
235 : void Shutdown();
236 :
237 : private:
238 : friend class TestWriteInteraction;
239 : friend class InteractionModelEngine;
240 :
241 : enum class State
242 : {
243 : Initialized = 0, // The client has been initialized
244 : AddAttribute, // The client has added attribute and ready for a SendWriteRequest
245 : AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
246 : AwaitingResponse, // The client has sent out the write request message
247 : ResponseReceived, // We have gotten a response after sending write request
248 : AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
249 : };
250 :
251 : CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
252 : System::PacketBufferHandle && aPayload) override;
253 : void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
254 :
255 : void MoveToState(const State aTargetState);
256 : CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
257 : CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
258 : const char * GetStateStr() const;
259 :
260 : /**
261 : * Encode an attribute value that can be directly encoded using DataModel::Encode.
262 : */
263 : template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
264 0 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
265 : {
266 0 : chip::TLV::TLVWriter * writer = nullptr;
267 :
268 0 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
269 0 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
270 0 : ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
271 0 : ReturnErrorOnFailure(FinishAttributeIB());
272 :
273 0 : return CHIP_NO_ERROR;
274 : }
275 :
276 : template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
277 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
278 : {
279 : chip::TLV::TLVWriter * writer = nullptr;
280 :
281 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
282 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
283 : ReturnErrorOnFailure(
284 : DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
285 : ReturnErrorOnFailure(FinishAttributeIB());
286 :
287 : return CHIP_NO_ERROR;
288 : }
289 :
290 : /**
291 : * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
292 : * CHIP_ERROR_BUFFER_TOO_SMALL.
293 : */
294 : template <class T>
295 0 : CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
296 : {
297 0 : TLV::TLVWriter backupWriter;
298 :
299 0 : mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
300 :
301 : // First attempt to write this attribute.
302 0 : CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
303 0 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
304 : {
305 : // If it failed with no memory, then we create a new chunk for it.
306 0 : mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
307 0 : ReturnErrorOnFailure(StartNewMessage());
308 0 : ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
309 : }
310 : else
311 : {
312 0 : ReturnErrorOnFailure(err);
313 : }
314 :
315 0 : return CHIP_NO_ERROR;
316 : }
317 :
318 : /**
319 : * Encode a preencoded attribute data, returns TLV encode error if the ramaining space of current chunk is too small for the
320 : * AttributeDataIB.
321 : */
322 : CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
323 : const TLV::TLVReader & data);
324 :
325 : /**
326 : * Encode a preencoded attribute data, will try to create a new chunk when necessary.
327 : */
328 : CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
329 : const TLV::TLVReader & data);
330 :
331 : CHIP_ERROR EnsureMessage();
332 :
333 : /**
334 : * Called internally to signal the completion of all work on this object, gracefully close the
335 : * exchange (by calling into the base class) and finally, signal to the application that it's
336 : * safe to release this object.
337 : */
338 : void Close();
339 :
340 : /**
341 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
342 : * not arise during normal message processing flows that all normally call Close() above. This can only
343 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
344 : * message payloads.
345 : */
346 : void Abort();
347 :
348 : // Send our queued-up Write Request message. Assumes the exchange is ready
349 : // and mPendingWriteData is populated.
350 : CHIP_ERROR SendWriteRequest();
351 :
352 : // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
353 : // is used when sending group write requests.
354 : // TODO(#14935) Update AttributePathParams to support more list operations.
355 : CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
356 : CHIP_ERROR FinishAttributeIB();
357 : TLV::TLVWriter * GetAttributeDataIBTLVWriter();
358 :
359 : /**
360 : * Create a new message (or a new chunk) for the write request.
361 : */
362 : CHIP_ERROR StartNewMessage();
363 :
364 : /**
365 : * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
366 : */
367 : CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
368 :
369 : Messaging::ExchangeManager * mpExchangeMgr = nullptr;
370 : Messaging::ExchangeHolder mExchangeCtx;
371 : Callback * mpCallback = nullptr;
372 : State mState = State::Initialized;
373 : System::PacketBufferTLVWriter mMessageWriter;
374 : WriteRequestMessage::Builder mWriteRequestBuilder;
375 : // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
376 : // but have it hold on to the buffer, and get the buffer from it later.
377 : // Then we could avoid this extra pointer-sized member.
378 : System::PacketBufferHandle mPendingWriteData;
379 : // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
380 : // write.
381 : Optional<uint16_t> mTimedWriteTimeoutMs;
382 : bool mSuppressResponse = false;
383 :
384 : // A list of buffers, one buffer for each chunk.
385 : System::PacketBufferHandle mChunks;
386 :
387 : // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
388 : // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
389 : // resolving it.
390 : // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
391 : uint16_t mReservedSize = 0;
392 : // #endif
393 :
394 : /**
395 : * Below we define several const variables for encoding overheads.
396 : * WriteRequestMessage =
397 : * {
398 : * timedRequest = false,
399 : * AttributeDataIBs =
400 : * [
401 : * AttributeDataIB = \
402 : * { |
403 : * DataVersion = 0x0, |
404 : * AttributePathIB = |
405 : * { |
406 : * Endpoint = 0x2, | "atomically" encoded via
407 : * Cluster = 0x50f, > EncodeAttribute or
408 : * Attribute = 0x0000_0006, | PutPreencodedAttribute
409 : * ListIndex = Null, |
410 : * } |
411 : * Data = ... |
412 : * }, /
413 : * (...)
414 : * ], <-- 1 byte "end of AttributeDataIB" (end of container)
415 : * moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
416 : * InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
417 : * } <-- 1 byte "end of WriteRequestMessage" (end of container)
418 : */
419 :
420 : // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
421 : static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
422 : // End Of Container (0x18) uses one byte.
423 : static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
424 : // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
425 : // context tag, 1 byte for value
426 : static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
427 : // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
428 : // of WriteRequestMessage (another end of container)).
429 : static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
430 : kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
431 : bool mHasDataVersion = false;
432 : };
433 :
434 : } // namespace app
435 : } // namespace chip
|