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