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