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, constructing
92 : * a StatusIB from the error 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 0 : ~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 (size_t 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 : private:
232 : friend class TestWriteInteraction;
233 : friend class InteractionModelEngine;
234 :
235 : enum class State
236 : {
237 : Initialized = 0, // The client has been initialized
238 : AddAttribute, // The client has added attribute and ready for a SendWriteRequest
239 : AwaitingTimedStatus, // Sent a Tiemd Request, waiting for response.
240 : AwaitingResponse, // The client has sent out the write request message
241 : ResponseReceived, // We have gotten a response after sending write request
242 : AwaitingDestruction, // The object has completed its work and is awaiting destruction by the application.
243 : };
244 :
245 : CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
246 : System::PacketBufferHandle && aPayload) override;
247 : void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
248 :
249 : void MoveToState(const State aTargetState);
250 : CHIP_ERROR ProcessWriteResponseMessage(System::PacketBufferHandle && payload);
251 : CHIP_ERROR ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB);
252 : const char * GetStateStr() const;
253 :
254 : /**
255 : * Encode an attribute value that can be directly encoded using DataModel::Encode.
256 : */
257 : template <class T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, int> = 0>
258 0 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
259 : {
260 0 : chip::TLV::TLVWriter * writer = nullptr;
261 :
262 0 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
263 0 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
264 0 : ReturnErrorOnFailure(DataModel::Encode(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
265 0 : ReturnErrorOnFailure(FinishAttributeIB());
266 :
267 0 : return CHIP_NO_ERROR;
268 : }
269 :
270 : template <class T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, int> = 0>
271 : CHIP_ERROR TryEncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
272 : {
273 : chip::TLV::TLVWriter * writer = nullptr;
274 :
275 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
276 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
277 : ReturnErrorOnFailure(
278 : DataModel::EncodeForWrite(*writer, chip::TLV::ContextTag(chip::app::AttributeDataIB::Tag::kData), value));
279 : ReturnErrorOnFailure(FinishAttributeIB());
280 :
281 : return CHIP_NO_ERROR;
282 : }
283 :
284 : /**
285 : * A wrapper for TryEncodeSingleAttributeDataIB which will start a new chunk when failed with CHIP_ERROR_NO_MEMORY or
286 : * CHIP_ERROR_BUFFER_TOO_SMALL.
287 : */
288 : template <class T>
289 0 : CHIP_ERROR EncodeSingleAttributeDataIB(const ConcreteDataAttributePath & attributePath, const T & value)
290 : {
291 0 : TLV::TLVWriter backupWriter;
292 :
293 0 : mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
294 :
295 : // First attempt to write this attribute.
296 0 : CHIP_ERROR err = TryEncodeSingleAttributeDataIB(attributePath, value);
297 0 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
298 : {
299 : // If it failed with no memory, then we create a new chunk for it.
300 0 : mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
301 0 : ReturnErrorOnFailure(StartNewMessage());
302 0 : ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
303 : }
304 : else
305 : {
306 0 : ReturnErrorOnFailure(err);
307 : }
308 :
309 0 : return CHIP_NO_ERROR;
310 : }
311 :
312 : /**
313 : * Encode a preencoded attribute data, returns TLV encode error if the ramaining space of current chunk is too small for the
314 : * AttributeDataIB.
315 : */
316 : CHIP_ERROR TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
317 : const TLV::TLVReader & data);
318 :
319 : /**
320 : * Encode a preencoded attribute data, will try to create a new chunk when necessary.
321 : */
322 : CHIP_ERROR PutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
323 : const TLV::TLVReader & data);
324 :
325 : CHIP_ERROR EnsureMessage();
326 :
327 : /**
328 : * Called internally to signal the completion of all work on this object, gracefully close the
329 : * exchange (by calling into the base class) and finally, signal to the application that it's
330 : * safe to release this object.
331 : */
332 : void Close();
333 :
334 : /**
335 : * This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
336 : * not arise during normal message processing flows that all normally call Close() above. This can only
337 : * arise due to application-initiated destruction of the object when this object is handling receiving/sending
338 : * message payloads.
339 : */
340 : void Abort();
341 :
342 : // Send our queued-up Write Request message. Assumes the exchange is ready
343 : // and mPendingWriteData is populated.
344 : CHIP_ERROR SendWriteRequest();
345 :
346 : // Encodes the header of an AttributeDataIB, a special case for attributePath is its EndpointId can be kInvalidEndpointId, this
347 : // is used when sending group write requests.
348 : // TODO(#14935) Update AttributePathParams to support more list operations.
349 : CHIP_ERROR PrepareAttributeIB(const ConcreteDataAttributePath & attributePath);
350 : CHIP_ERROR FinishAttributeIB();
351 : TLV::TLVWriter * GetAttributeDataIBTLVWriter();
352 :
353 : /**
354 : * Create a new message (or a new chunk) for the write request.
355 : */
356 : CHIP_ERROR StartNewMessage();
357 :
358 : /**
359 : * Finalize Write Request Message TLV Builder and retrieve final data from tlv builder for later sending
360 : */
361 : CHIP_ERROR FinalizeMessage(bool aHasMoreChunks);
362 :
363 : Messaging::ExchangeManager * mpExchangeMgr = nullptr;
364 : Messaging::ExchangeHolder mExchangeCtx;
365 : Callback * mpCallback = nullptr;
366 : State mState = State::Initialized;
367 : System::PacketBufferTLVWriter mMessageWriter;
368 : WriteRequestMessage::Builder mWriteRequestBuilder;
369 : // TODO Maybe we should change PacketBufferTLVWriter so we can finalize it
370 : // but have it hold on to the buffer, and get the buffer from it later.
371 : // Then we could avoid this extra pointer-sized member.
372 : System::PacketBufferHandle mPendingWriteData;
373 : // If mTimedWriteTimeoutMs has a value, we are expected to do a timed
374 : // write.
375 : Optional<uint16_t> mTimedWriteTimeoutMs;
376 : bool mSuppressResponse = false;
377 :
378 : // A list of buffers, one buffer for each chunk.
379 : System::PacketBufferHandle mChunks;
380 :
381 : // TODO: This file might be compiled with different build flags on Darwin platform (when building WriteClient.cpp and
382 : // CHIPClustersObjc.mm), which will cause undefined behavior when building write requests. Uncomment the #if and #endif after
383 : // resolving it.
384 : // #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
385 : uint16_t mReservedSize = 0;
386 : // #endif
387 :
388 : /**
389 : * Below we define several const variables for encoding overheads.
390 : * WriteRequestMessage =
391 : * {
392 : * timedRequest = false,
393 : * AttributeDataIBs =
394 : * [
395 : * AttributeDataIB = \
396 : * { |
397 : * DataVersion = 0x0, |
398 : * AttributePathIB = |
399 : * { |
400 : * Endpoint = 0x2, | "atomically" encoded via
401 : * Cluster = 0x50f, > EncodeAttribute or
402 : * Attribute = 0x0000_0006, | PutPreencodedAttribute
403 : * ListIndex = Null, |
404 : * } |
405 : * Data = ... |
406 : * }, /
407 : * (...)
408 : * ], <-- 1 byte "end of AttributeDataIB" (end of container)
409 : * moreChunkedMessages = false, <-- 2 bytes "kReservedSizeForMoreChunksFlag"
410 : * InteractionModelRevision = 1,<-- 3 bytes "kReservedSizeForIMRevision"
411 : * } <-- 1 byte "end of WriteRequestMessage" (end of container)
412 : */
413 :
414 : // Reserved size for the MoreChunks boolean flag, which takes up 1 byte for the control tag and 1 byte for the context tag.
415 : static constexpr uint16_t kReservedSizeForMoreChunksFlag = 1 + 1;
416 : // End Of Container (0x18) uses one byte.
417 : static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
418 : // Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
419 : // context tag, 1 byte for value
420 : static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
421 : // Reserved buffer for TLV level overhead (the overhead for end of AttributeDataIBs (end of container), more chunks flag, end
422 : // of WriteRequestMessage (another end of container)).
423 : static constexpr uint16_t kReservedSizeForTLVEncodingOverhead = kReservedSizeForIMRevision + kReservedSizeForMoreChunksFlag +
424 : kReservedSizeForEndOfContainer + kReservedSizeForEndOfContainer;
425 : bool mHasDataVersion = false;
426 : };
427 :
428 : } // namespace app
429 : } // namespace chip
|