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 : /**
20 : * @file
21 : * This file defines the initiator side of a CHIP Write Interaction.
22 : *
23 : */
24 :
25 : #include "lib/core/CHIPError.h"
26 : #include <app/AppConfig.h>
27 : #include <app/InteractionModelEngine.h>
28 : #include <app/TimedRequest.h>
29 : #include <app/WriteClient.h>
30 :
31 : namespace chip {
32 : namespace app {
33 :
34 962 : void WriteClient::Close()
35 : {
36 962 : MoveToState(State::AwaitingDestruction);
37 962 : mExchangeCtx.Release();
38 :
39 962 : if (mpCallback)
40 : {
41 962 : mpCallback->OnDone(this);
42 : }
43 962 : }
44 :
45 3864 : CHIP_ERROR WriteClient::ProcessWriteResponseMessage(System::PacketBufferHandle && payload)
46 : {
47 3864 : CHIP_ERROR err = CHIP_NO_ERROR;
48 3864 : System::PacketBufferTLVReader reader;
49 3864 : TLV::TLVReader attributeStatusesReader;
50 3864 : WriteResponseMessage::Parser writeResponse;
51 3864 : AttributeStatusIBs::Parser attributeStatusesParser;
52 :
53 3864 : reader.Init(std::move(payload));
54 :
55 3864 : ReturnErrorOnFailure(writeResponse.Init(reader));
56 :
57 : #if CHIP_CONFIG_IM_PRETTY_PRINT
58 3863 : TEMPORARY_RETURN_IGNORED writeResponse.PrettyPrint();
59 : #endif
60 :
61 3863 : err = writeResponse.GetWriteResponses(&attributeStatusesParser);
62 7726 : if (err == CHIP_END_OF_TLV)
63 : {
64 0 : return CHIP_NO_ERROR;
65 : }
66 3863 : ReturnErrorOnFailure(err);
67 :
68 3863 : attributeStatusesParser.GetReader(&attributeStatusesReader);
69 :
70 18118 : while (CHIP_NO_ERROR == (err = attributeStatusesReader.Next()))
71 : {
72 5196 : VerifyOrReturnError(TLV::AnonymousTag() == attributeStatusesReader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
73 :
74 5196 : AttributeStatusIB::Parser element;
75 :
76 5196 : ReturnErrorOnFailure(element.Init(attributeStatusesReader));
77 5196 : ReturnErrorOnFailure(ProcessAttributeStatusIB(element));
78 : }
79 :
80 : // if we have exhausted this container
81 7726 : if (CHIP_END_OF_TLV == err)
82 : {
83 3863 : err = CHIP_NO_ERROR;
84 : }
85 3863 : ReturnErrorOnFailure(err);
86 3863 : return writeResponse.ExitContainer();
87 3864 : }
88 :
89 8177 : CHIP_ERROR WriteClient::PrepareAttributeIB(const ConcreteDataAttributePath & aPath)
90 : {
91 8177 : AttributeDataIBs::Builder & writeRequests = mWriteRequestBuilder.GetWriteRequests();
92 8177 : AttributeDataIB::Builder & attributeDataIB = writeRequests.CreateAttributeDataIBBuilder();
93 8177 : ReturnErrorOnFailure(writeRequests.GetError());
94 8155 : if (aPath.mDataVersion.HasValue())
95 : {
96 12 : attributeDataIB.DataVersion(aPath.mDataVersion.Value());
97 12 : mHasDataVersion = true;
98 : }
99 8155 : ReturnErrorOnFailure(attributeDataIB.GetError());
100 8155 : AttributePathIB::Builder & path = attributeDataIB.CreatePath();
101 :
102 : // We are using kInvalidEndpointId just for group write requests. This is not the correct use of ConcreteDataAttributePath.
103 : // TODO: update AttributePathParams or ConcreteDataAttributePath for a class supports both nullable list index and missing
104 : // endpoint id.
105 8155 : if (aPath.mEndpointId != kInvalidEndpointId)
106 : {
107 8155 : path.Endpoint(aPath.mEndpointId);
108 : }
109 8155 : path.Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId);
110 8155 : if (aPath.IsListItemOperation())
111 : {
112 6901 : if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
113 : {
114 6901 : path.ListIndex(DataModel::NullNullable);
115 : }
116 : else
117 : {
118 : // We do not support other list operations (i.e. update, delete etc) for now.
119 0 : return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
120 : }
121 : }
122 8155 : ReturnErrorOnFailure(path.EndOfAttributePathIB());
123 :
124 7683 : return CHIP_NO_ERROR;
125 : }
126 :
127 5425 : CHIP_ERROR WriteClient::FinishAttributeIB()
128 : {
129 5425 : AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
130 5425 : ReturnErrorOnFailure(attributeDataIB.EndOfAttributeDataIB());
131 5393 : MoveToState(State::AddAttribute);
132 5393 : return CHIP_NO_ERROR;
133 : }
134 :
135 8153 : TLV::TLVWriter * WriteClient::GetAttributeDataIBTLVWriter()
136 : {
137 8153 : return mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder().GetWriter();
138 : }
139 :
140 4087 : CHIP_ERROR WriteClient::FinalizeMessage(bool aHasMoreChunks)
141 : {
142 4087 : System::PacketBufferHandle packet;
143 4087 : VerifyOrReturnError(mState == State::AddAttribute, CHIP_ERROR_INCORRECT_STATE);
144 :
145 4087 : TLV::TLVWriter * writer = mWriteRequestBuilder.GetWriter();
146 4087 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
147 4087 : ReturnErrorOnFailure(writer->UnreserveBuffer(kReservedSizeForTLVEncodingOverhead));
148 :
149 4087 : ReturnErrorOnFailure(mWriteRequestBuilder.GetWriteRequests().EndOfAttributeDataIBs());
150 :
151 4087 : ReturnErrorOnFailure(mWriteRequestBuilder.MoreChunkedMessages(aHasMoreChunks).EndOfWriteRequestMessage());
152 4087 : ReturnErrorOnFailure(mMessageWriter.Finalize(&packet));
153 4087 : mChunks.AddToEnd(std::move(packet));
154 4087 : return CHIP_NO_ERROR;
155 4087 : }
156 :
157 1205 : CHIP_ERROR WriteClient::EnsureMessage()
158 : {
159 1205 : if (mState != State::AddAttribute)
160 : {
161 1071 : return StartNewMessage();
162 : }
163 134 : return CHIP_NO_ERROR;
164 : }
165 :
166 4190 : CHIP_ERROR WriteClient::StartNewMessage()
167 : {
168 4190 : uint16_t reservedSize = 0;
169 :
170 4190 : if (mState == State::AddAttribute)
171 : {
172 3119 : ReturnErrorOnFailure(FinalizeMessage(true));
173 : }
174 :
175 : // Per the Matter specification a chunked Write Request cannot be part of a Timed Write Interaction, and it cannot
176 : // suppress the response: intermediate WriteResponses are required to pace the chunks, so SuppressResponse must be
177 : // false when the request is chunked.
178 4190 : VerifyOrReturnError(!((mTimedWriteTimeoutMs.HasValue() || mSuppressResponse) && !mChunks.IsNull()),
179 : CHIP_ERROR_INVALID_MESSAGE_TYPE);
180 :
181 4189 : System::PacketBufferHandle packet = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes);
182 4189 : VerifyOrReturnError(!packet.IsNull(), CHIP_ERROR_NO_MEMORY);
183 :
184 : // Always limit the size of the packet to fit within kMaxSecureSduLengthBytes regardless of the available buffer capacity.
185 4189 : if (packet->AvailableDataLength() > kMaxSecureSduLengthBytes)
186 : {
187 0 : reservedSize = static_cast<uint16_t>(packet->AvailableDataLength() - kMaxSecureSduLengthBytes);
188 : }
189 :
190 : // ... and we need to reserve some extra space for the MIC field.
191 4189 : reservedSize = static_cast<uint16_t>(reservedSize + Crypto::CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES);
192 :
193 : // ... and the overhead for end of AttributeDataIBs (end of container), more chunks flag, end of WriteRequestMessage (another
194 : // end of container).
195 4189 : reservedSize = static_cast<uint16_t>(reservedSize + kReservedSizeForTLVEncodingOverhead);
196 :
197 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
198 : // ... and for unit tests.
199 4189 : reservedSize = static_cast<uint16_t>(reservedSize + mReservedSize);
200 : #endif
201 :
202 4189 : mMessageWriter.Init(std::move(packet));
203 :
204 4189 : ReturnErrorOnFailure(mMessageWriter.ReserveBuffer(reservedSize));
205 :
206 4189 : ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter));
207 4189 : mWriteRequestBuilder.SuppressResponse(mSuppressResponse);
208 4189 : mWriteRequestBuilder.TimedRequest(mTimedRequestFieldValue);
209 4189 : ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
210 4189 : mWriteRequestBuilder.CreateWriteRequests();
211 4189 : ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
212 :
213 4189 : TLV::TLVWriter * writer = mWriteRequestBuilder.GetWriter();
214 4189 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
215 :
216 4189 : return CHIP_NO_ERROR;
217 4189 : }
218 :
219 950 : CHIP_ERROR WriteClient::TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
220 : const TLV::TLVReader & data)
221 : {
222 950 : TLV::TLVReader dataToWrite;
223 950 : dataToWrite.Init(data);
224 :
225 950 : TLV::TLVWriter * writer = nullptr;
226 :
227 950 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
228 816 : VerifyOrReturnError((writer = GetAttributeDataIBTLVWriter()) != nullptr, CHIP_ERROR_INCORRECT_STATE);
229 816 : ReturnErrorOnFailure(writer->CopyElement(TLV::ContextTag(AttributeDataIB::Tag::kData), dataToWrite));
230 755 : ReturnErrorOnFailure(FinishAttributeIB());
231 741 : return CHIP_NO_ERROR;
232 : }
233 :
234 741 : CHIP_ERROR WriteClient::PutSinglePreencodedAttributeWritePayload(const chip::app::ConcreteDataAttributePath & attributePath,
235 : const TLV::TLVReader & data)
236 : {
237 741 : TLV::TLVWriter backupWriter;
238 :
239 741 : mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
240 :
241 : // First attempt to write this attribute.
242 741 : CHIP_ERROR err = TryPutSinglePreencodedAttributeWritePayload(attributePath, data);
243 2014 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
244 : {
245 : // If it failed with no memory, then we create a new chunk for it.
246 209 : mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
247 209 : ReturnErrorOnFailure(StartNewMessage());
248 209 : err = TryPutSinglePreencodedAttributeWritePayload(attributePath, data);
249 : // Since we have created a new chunk for this element, the encode is expected to succeed.
250 : }
251 741 : return err;
252 : }
253 :
254 179 : CHIP_ERROR WriteClient::PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data,
255 : TestListEncodingOverride testListEncodingOverride)
256 : {
257 179 : ReturnErrorOnFailure(EnsureMessage());
258 :
259 : // ListIndex is missing and the data is an array -- we are writing a whole list.
260 179 : if (!attributePath.IsListOperation() && data.GetType() == TLV::TLVType::kTLVType_Array)
261 : {
262 173 : TLV::TLVReader dataReader;
263 173 : TLV::TLVReader valueReader;
264 173 : uint16_t encodedItemCount = 0;
265 173 : ConcreteDataAttributePath path = attributePath;
266 :
267 : // By convention, and as tested against all cluster servers, clients have historically encoded an empty list as a
268 : // ReplaceAll, (i.e. the entire attribute contents are cleared before appending the new list’s items). However, this
269 : // behavior can be problematic, especially for the ACL attribute; sending an empty ReplaceAll list can cause clients to be
270 : // locked out. This is because the empty list first deletes all existing ACL entries, and if the new (malformed) ACL is
271 : // rejected, the server is left without valid (or with incomplete) ACLs.
272 : // SOLUTION: we treat ACL as an exception and avoid encoding an empty ReplaceAll list. Instead, we pack as many ACL entries
273 : // as possible into the ReplaceAll list, and send any remaining entries in subsequent chunks are part of the AppendItem
274 : // list operation.
275 : // TODO (#38270): Generalize this behavior; send a non-empty ReplaceAll list for all clusters in a later Matter version and
276 : // enforce all clusters to support it in testing and in certification.
277 173 : bool encodeEmptyListAsReplaceAll = (path.mClusterId != Clusters::AccessControl::Id) ||
278 : (testListEncodingOverride == TestListEncodingOverride::kForceLegacyEncoding);
279 :
280 173 : if (encodeEmptyListAsReplaceAll)
281 : {
282 112 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
283 : }
284 : else
285 : {
286 :
287 61 : dataReader.Init(data);
288 61 : TEMPORARY_RETURN_IGNORED dataReader.OpenContainer(valueReader);
289 61 : bool chunkingNeeded = false;
290 :
291 : // Encode as many list-items as possible into a single AttributeDataIB, which will be included in a single
292 : // WriteRequestMessage chunk.
293 61 : ReturnErrorOnFailure(
294 : TryPutPreencodedAttributeWritePayloadIntoList(path, valueReader, chunkingNeeded, encodedItemCount));
295 :
296 : // If all list items fit perfectly into a single AttributeDataIB, there is no need for any `append-item` or chunking,
297 : // and we can exit early.
298 61 : VerifyOrReturnError(chunkingNeeded, CHIP_NO_ERROR);
299 :
300 : // Start a new WriteRequest chunk, as there are still remaining list items to encode. These remaining items will be
301 : // appended one by one, each into its own AttributeDataIB. Unlike the first chunk (which contains only one
302 : // AttributeDataIB), subsequent chunks may contain multiple AttributeDataIBs if space allows it.
303 43 : ReturnErrorOnFailure(StartNewMessage());
304 : }
305 155 : path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
306 :
307 : // We will restart iterating on ValueReader, only appending the items we need to append.
308 155 : dataReader.Init(data);
309 155 : TEMPORARY_RETURN_IGNORED dataReader.OpenContainer(valueReader);
310 :
311 155 : CHIP_ERROR err = CHIP_NO_ERROR;
312 155 : uint16_t currentItemCount = 0;
313 :
314 2722 : while ((err = valueReader.Next()) == CHIP_NO_ERROR)
315 : {
316 1206 : currentItemCount++;
317 :
318 1206 : if (currentItemCount <= encodedItemCount)
319 : {
320 : // Element already encoded via `TryPutPreencodedAttributeWritePayloadIntoList`
321 471 : continue;
322 : }
323 :
324 735 : ReturnErrorOnFailure(PutSinglePreencodedAttributeWritePayload(path, valueReader));
325 : }
326 :
327 310 : if (err == CHIP_END_OF_TLV)
328 : {
329 155 : err = CHIP_NO_ERROR;
330 : }
331 155 : return err;
332 : }
333 :
334 : // We are writing a non-list attribute, or we are writing a single element of a list.
335 6 : return PutSinglePreencodedAttributeWritePayload(attributePath, data);
336 : }
337 :
338 470 : CHIP_ERROR WriteClient::EnsureListStarted(const ConcreteDataAttributePath & attributePath)
339 : {
340 470 : TLV::TLVWriter backupWriter;
341 470 : mWriteRequestBuilder.GetWriteRequests().Checkpoint(backupWriter);
342 :
343 470 : CHIP_ERROR err = TryToStartList(attributePath);
344 1408 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
345 : {
346 : // If it failed with no memory, then we create a new chunk for it.
347 2 : mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
348 2 : ReturnErrorOnFailure(StartNewMessage());
349 2 : ReturnErrorOnFailure(TryToStartList(attributePath));
350 : }
351 :
352 470 : return CHIP_NO_ERROR;
353 : }
354 :
355 472 : CHIP_ERROR WriteClient::TryToStartList(const ConcreteDataAttributePath & attributePath)
356 : {
357 :
358 : // TODO (#38414) : Move reservation/unreservtion of Buffer for TLV Writing to AttributeDataIB Builder instead of WriteClient
359 472 : ReturnErrorOnFailure(mMessageWriter.ReserveBuffer(kReservedSizeForEndOfListAttributeIB));
360 :
361 470 : ReturnErrorOnFailure(PrepareAttributeIB(attributePath));
362 :
363 470 : TLV::TLVWriter * writer = GetAttributeDataIBTLVWriter();
364 470 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
365 :
366 : TLV::TLVType outerType;
367 470 : ReturnErrorOnFailure(writer->StartContainer(TLV::ContextTag(AttributeDataIB::Tag::kData), TLV::kTLVType_Array, outerType));
368 :
369 470 : VerifyOrReturnError(outerType == kAttributeDataIBType, CHIP_ERROR_INCORRECT_STATE);
370 :
371 470 : return CHIP_NO_ERROR;
372 : }
373 :
374 470 : CHIP_ERROR WriteClient::EnsureListEnded()
375 : {
376 470 : TLV::TLVWriter * writer = GetAttributeDataIBTLVWriter();
377 470 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
378 :
379 : // Undo the reservation made in EnsureListStarted() to free up space for the EndOfContainer TLV Elements
380 : // (for both the List and AttributeDataIB).
381 470 : ReturnErrorOnFailure(writer->UnreserveBuffer(kReservedSizeForEndOfListAttributeIB));
382 470 : ReturnErrorOnFailure(writer->EndContainer(kAttributeDataIBType));
383 :
384 470 : return FinishAttributeIB();
385 : }
386 :
387 : CHIP_ERROR
388 61 : WriteClient::TryPutPreencodedAttributeWritePayloadIntoList(const ConcreteDataAttributePath & attributePath,
389 : TLV::TLVReader & valueReader, bool & outChunkingNeeded,
390 : ListIndex & outEncodedItemCount)
391 : {
392 :
393 61 : ReturnErrorOnFailure(EnsureListStarted(attributePath));
394 :
395 61 : AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
396 61 : TLV::TLVWriter backupWriter;
397 61 : CHIP_ERROR err = CHIP_NO_ERROR;
398 61 : outEncodedItemCount = 0;
399 :
400 1784 : while ((err = valueReader.Next()) == CHIP_NO_ERROR)
401 : {
402 : // Try to put all the list items into the list we just started, until we either run out of items
403 : // or run out of space.
404 : // Make sure that if we run out of space we don't leave a partially-encoded list item around.
405 874 : attributeDataIB.Checkpoint(backupWriter);
406 874 : err = attributeDataIB.GetWriter()->CopyElement(TLV::AnonymousTag(), valueReader);
407 :
408 2579 : if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
409 : {
410 : // Rollback through the attributeDataIB, which also resets the Builder's error state.
411 : // This returns the object to the state it was in before attempting to copy the element.
412 43 : attributeDataIB.Rollback(backupWriter);
413 43 : outChunkingNeeded = true;
414 43 : err = CHIP_NO_ERROR;
415 43 : break;
416 : }
417 831 : ReturnErrorOnFailure(err);
418 831 : outEncodedItemCount++;
419 : }
420 165 : VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_NO_ERROR, err);
421 :
422 61 : return EnsureListEnded();
423 : }
424 :
425 0 : const char * WriteClient::GetStateStr() const
426 : {
427 : #if CHIP_DETAIL_LOGGING
428 0 : switch (mState)
429 : {
430 0 : case State::Initialized:
431 0 : return "Initialized";
432 :
433 0 : case State::AddAttribute:
434 0 : return "AddAttribute";
435 :
436 0 : case State::AwaitingTimedStatus:
437 0 : return "AwaitingTimedStatus";
438 :
439 0 : case State::AwaitingResponse:
440 0 : return "AwaitingResponse";
441 :
442 0 : case State::ResponseReceived:
443 0 : return "ResponseReceived";
444 :
445 0 : case State::AwaitingDestruction:
446 0 : return "AwaitingDestruction";
447 : }
448 : #endif // CHIP_DETAIL_LOGGING
449 0 : return "N/A";
450 : }
451 :
452 14100 : void WriteClient::MoveToState(const State aTargetState)
453 : {
454 14100 : mState = aTargetState;
455 14100 : ChipLogDetail(DataManagement, "WriteClient moving to [%10.10s]", GetStateStr());
456 14100 : }
457 :
458 968 : CHIP_ERROR WriteClient::SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout)
459 : {
460 968 : CHIP_ERROR err = CHIP_NO_ERROR;
461 :
462 968 : VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE);
463 :
464 968 : err = FinalizeMessage(false /* hasMoreChunks */);
465 968 : SuccessOrExit(err);
466 :
467 : {
468 : // Create a new exchange context.
469 968 : auto exchange = mpExchangeMgr->NewContext(session, this);
470 968 : VerifyOrExit(exchange != nullptr, err = CHIP_ERROR_NO_MEMORY);
471 :
472 968 : mExchangeCtx.Grab(exchange);
473 : }
474 :
475 968 : VerifyOrReturnError(!(mExchangeCtx->IsGroupExchangeContext() && mHasDataVersion), CHIP_ERROR_INVALID_MESSAGE_TYPE);
476 :
477 968 : if (timeout == System::Clock::kZero)
478 : {
479 968 : ReturnErrorOnFailure(mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime));
480 : }
481 : else
482 : {
483 0 : mExchangeCtx->SetResponseTimeout(timeout);
484 : }
485 :
486 968 : if (mTimedWriteTimeoutMs.HasValue())
487 : {
488 0 : err = TimedRequest::Send(mExchangeCtx.Get(), mTimedWriteTimeoutMs.Value());
489 0 : SuccessOrExit(err);
490 0 : MoveToState(State::AwaitingTimedStatus);
491 : }
492 : else
493 : {
494 968 : err = SendWriteRequest();
495 968 : SuccessOrExit(err);
496 : }
497 :
498 968 : exit:
499 1936 : if (err != CHIP_NO_ERROR)
500 : {
501 0 : ChipLogError(DataManagement, "Write client failed to SendWriteRequest: %" CHIP_ERROR_FORMAT, err.Format());
502 : }
503 : else
504 : {
505 : // TODO: Ideally this would happen async, but to make sure that we
506 : // handle this object dying (e.g. due to IM enging shutdown) while the
507 : // async bits are pending we'd need to malloc some state bit that we can
508 : // twiddle if we die. For now just do the OnDone callback sync.
509 968 : if (session->IsGroupSession() || (mSuppressResponse && mState != State::AwaitingTimedStatus))
510 : {
511 : // Always shutdown on Group communication
512 6 : ChipLogDetail(DataManagement, "Closing on group Communication ");
513 :
514 : // Tell the application to release the object.
515 : // TODO: Consumers expect to hand off ownership of the WriteClient and wait for OnDone
516 : // after SendWriteRequest returns success. Calling OnDone before returning is weird.
517 : // Need to refactor the code to avoid this.
518 6 : Close();
519 : }
520 : }
521 :
522 968 : return err;
523 : }
524 :
525 3882 : CHIP_ERROR WriteClient::SendWriteRequest()
526 : {
527 : using namespace Protocols::InteractionModel;
528 : using namespace Messaging;
529 :
530 3882 : System::PacketBufferHandle data = mChunks.PopHead();
531 :
532 3882 : bool isGroupWrite = mExchangeCtx->IsGroupExchangeContext();
533 3882 : if (!mChunks.IsNull() && (isGroupWrite || mSuppressResponse))
534 : {
535 : // Reject this request if we have more than one chunk (mChunks is not null after PopHead()) and either this is a
536 : // group exchange context or the response is suppressed; a chunked write requires intermediate WriteResponses.
537 0 : return CHIP_ERROR_INCORRECT_STATE;
538 : }
539 :
540 3882 : if (mSuppressResponse)
541 : {
542 4 : return mExchangeCtx->SendMessage(MsgType::WriteRequest, std::move(data), SendMessageFlags::kNone);
543 : }
544 :
545 : // kExpectResponse is ignored by ExchangeContext in case of groupcast
546 3878 : ReturnErrorOnFailure(mExchangeCtx->SendMessage(MsgType::WriteRequest, std::move(data), SendMessageFlags::kExpectResponse));
547 3878 : MoveToState(State::AwaitingResponse);
548 3878 : return CHIP_NO_ERROR;
549 3882 : }
550 :
551 3867 : CHIP_ERROR WriteClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
552 : System::PacketBufferHandle && aPayload)
553 : {
554 : using namespace Protocols::InteractionModel;
555 :
556 3867 : if (mState == State::AwaitingResponse)
557 : {
558 : // NOTE: if we have more chunks (i.e. `!mChunks.IsNull()`), then the
559 : // SendWriteRequest() call below will move back to an AwaitingResponse state
560 3867 : MoveToState(State::ResponseReceived);
561 : }
562 :
563 3867 : CHIP_ERROR err = CHIP_NO_ERROR;
564 3867 : bool sendStatusResponse = false;
565 : // Assert that the exchange context matches the client's current context.
566 : // This should never fail because even if SendWriteRequest is called
567 : // back-to-back, the second call will call Close() on the first exchange,
568 : // which clears the OnMessageReceived callback.
569 3867 : VerifyOrExit(apExchangeContext == mExchangeCtx.Get(), err = CHIP_ERROR_INCORRECT_STATE);
570 :
571 3867 : sendStatusResponse = true;
572 :
573 3867 : if (mState == State::AwaitingTimedStatus)
574 : {
575 0 : if (aPayloadHeader.HasMessageType(MsgType::StatusResponse))
576 : {
577 0 : CHIP_ERROR statusError = CHIP_NO_ERROR;
578 0 : SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
579 0 : sendStatusResponse = false;
580 0 : SuccessOrExit(err = statusError);
581 0 : err = SendWriteRequest();
582 : }
583 : else
584 : {
585 0 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
586 : }
587 : // Skip all other processing here (which is for the response to the
588 : // write request), no matter whether err is success or not.
589 0 : goto exit;
590 : }
591 :
592 3867 : if (aPayloadHeader.HasMessageType(MsgType::WriteResponse))
593 : {
594 3862 : err = ProcessWriteResponseMessage(std::move(aPayload));
595 3862 : SuccessOrExit(err);
596 3861 : sendStatusResponse = false;
597 3861 : if (!mChunks.IsNull())
598 : {
599 : // Send the next chunk.
600 2914 : SuccessOrExit(err = SendWriteRequest());
601 : }
602 : }
603 5 : else if (aPayloadHeader.HasMessageType(MsgType::StatusResponse))
604 : {
605 4 : CHIP_ERROR statusError = CHIP_NO_ERROR;
606 7 : SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
607 3 : SuccessOrExit(err = statusError);
608 0 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
609 : }
610 : else
611 : {
612 1 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
613 : }
614 :
615 3867 : exit:
616 3867 : if (mpCallback != nullptr)
617 : {
618 7734 : if (err != CHIP_NO_ERROR)
619 : {
620 6 : mpCallback->OnError(this, err);
621 : }
622 : }
623 :
624 3867 : if (sendStatusResponse)
625 : {
626 6 : TEMPORARY_RETURN_IGNORED StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/);
627 : }
628 :
629 3867 : if (mState != State::AwaitingResponse)
630 : {
631 953 : Close();
632 : }
633 : // Else we got a response to a Timed Request and just sent the write.
634 :
635 3867 : return err;
636 : }
637 :
638 1 : void WriteClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
639 : {
640 1 : ChipLogError(DataManagement, "Time out! failed to receive write response from Exchange: " ChipLogFormatExchange,
641 : ChipLogValueExchange(apExchangeContext));
642 :
643 1 : if (mpCallback != nullptr)
644 : {
645 1 : mpCallback->OnError(this, CHIP_ERROR_TIMEOUT);
646 : }
647 1 : Close();
648 1 : }
649 :
650 5196 : CHIP_ERROR WriteClient::ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB)
651 : {
652 5196 : CHIP_ERROR err = CHIP_NO_ERROR;
653 5196 : AttributePathIB::Parser attributePathParser;
654 5196 : StatusIB statusIB;
655 5196 : StatusIB::Parser StatusIBParser;
656 5196 : ConcreteDataAttributePath attributePath;
657 :
658 5196 : err = aAttributeStatusIB.GetPath(&attributePathParser);
659 5196 : SuccessOrExit(err);
660 :
661 5196 : err = attributePathParser.GetConcreteAttributePath(attributePath);
662 5196 : SuccessOrExit(err);
663 :
664 5196 : err = aAttributeStatusIB.GetErrorStatus(&(StatusIBParser));
665 10392 : if (CHIP_NO_ERROR == err)
666 : {
667 5196 : err = StatusIBParser.DecodeStatusIB(statusIB);
668 5196 : SuccessOrExit(err);
669 5196 : if (mpCallback != nullptr)
670 : {
671 5196 : mpCallback->OnResponse(this, attributePath, statusIB);
672 : }
673 : }
674 :
675 0 : exit:
676 5196 : return err;
677 : }
678 :
679 : } // namespace app
680 : } // namespace chip
|