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