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 427 : void WriteClient::Close()
35 : {
36 427 : MoveToState(State::AwaitingDestruction);
37 :
38 427 : if (mpCallback)
39 : {
40 427 : mpCallback->OnDone(this);
41 : }
42 427 : }
43 :
44 1727 : CHIP_ERROR WriteClient::ProcessWriteResponseMessage(System::PacketBufferHandle && payload)
45 : {
46 1727 : CHIP_ERROR err = CHIP_NO_ERROR;
47 1727 : System::PacketBufferTLVReader reader;
48 : TLV::TLVReader attributeStatusesReader;
49 1727 : WriteResponseMessage::Parser writeResponse;
50 1727 : AttributeStatusIBs::Parser attributeStatusesParser;
51 :
52 1727 : reader.Init(std::move(payload));
53 :
54 1727 : ReturnErrorOnFailure(writeResponse.Init(reader));
55 :
56 : #if CHIP_CONFIG_IM_PRETTY_PRINT
57 1726 : writeResponse.PrettyPrint();
58 : #endif
59 :
60 1726 : err = writeResponse.GetWriteResponses(&attributeStatusesParser);
61 1726 : if (err == CHIP_END_OF_TLV)
62 : {
63 0 : return CHIP_NO_ERROR;
64 : }
65 1726 : ReturnErrorOnFailure(err);
66 :
67 1726 : attributeStatusesParser.GetReader(&attributeStatusesReader);
68 :
69 4207 : while (CHIP_NO_ERROR == (err = attributeStatusesReader.Next()))
70 : {
71 2481 : VerifyOrReturnError(TLV::AnonymousTag() == attributeStatusesReader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
72 :
73 2481 : AttributeStatusIB::Parser element;
74 :
75 2481 : ReturnErrorOnFailure(element.Init(attributeStatusesReader));
76 2481 : ReturnErrorOnFailure(ProcessAttributeStatusIB(element));
77 : }
78 :
79 : // if we have exhausted this container
80 1726 : if (CHIP_END_OF_TLV == err)
81 : {
82 1726 : err = CHIP_NO_ERROR;
83 : }
84 1726 : ReturnErrorOnFailure(err);
85 1726 : return writeResponse.ExitContainer();
86 1727 : }
87 :
88 3963 : CHIP_ERROR WriteClient::PrepareAttributeIB(const ConcreteDataAttributePath & aPath)
89 : {
90 3963 : AttributeDataIBs::Builder & writeRequests = mWriteRequestBuilder.GetWriteRequests();
91 3963 : AttributeDataIB::Builder & attributeDataIB = writeRequests.CreateAttributeDataIBBuilder();
92 3963 : ReturnErrorOnFailure(writeRequests.GetError());
93 3955 : if (aPath.mDataVersion.HasValue())
94 : {
95 12 : attributeDataIB.DataVersion(aPath.mDataVersion.Value());
96 12 : mHasDataVersion = true;
97 : }
98 3955 : ReturnErrorOnFailure(attributeDataIB.GetError());
99 3955 : 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 3955 : if (aPath.mEndpointId != kInvalidEndpointId)
105 : {
106 3955 : path.Endpoint(aPath.mEndpointId);
107 : }
108 3955 : path.Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId);
109 3955 : if (aPath.IsListItemOperation())
110 : {
111 3473 : if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
112 : {
113 3473 : 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 3955 : ReturnErrorOnFailure(path.EndOfAttributePathIB());
122 :
123 3769 : return CHIP_NO_ERROR;
124 : }
125 :
126 2554 : CHIP_ERROR WriteClient::FinishAttributeIB()
127 : {
128 2554 : AttributeDataIB::Builder & attributeDataIB = mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder();
129 2554 : ReturnErrorOnFailure(attributeDataIB.EndOfAttributeDataIB());
130 2547 : MoveToState(State::AddAttribute);
131 2547 : return CHIP_NO_ERROR;
132 : }
133 :
134 3769 : TLV::TLVWriter * WriteClient::GetAttributeDataIBTLVWriter()
135 : {
136 3769 : return mWriteRequestBuilder.GetWriteRequests().GetAttributeDataIBBuilder().GetWriter();
137 : }
138 :
139 1788 : CHIP_ERROR WriteClient::FinalizeMessage(bool aHasMoreChunks)
140 : {
141 1788 : System::PacketBufferHandle packet;
142 1788 : VerifyOrReturnError(mState == State::AddAttribute, CHIP_ERROR_INCORRECT_STATE);
143 :
144 1788 : TLV::TLVWriter * writer = mWriteRequestBuilder.GetWriter();
145 1788 : ReturnErrorCodeIf(writer == nullptr, CHIP_ERROR_INCORRECT_STATE);
146 1788 : ReturnErrorOnFailure(writer->UnreserveBuffer(kReservedSizeForTLVEncodingOverhead));
147 :
148 1788 : ReturnErrorOnFailure(mWriteRequestBuilder.GetWriteRequests().EndOfAttributeDataIBs());
149 :
150 1788 : ReturnErrorOnFailure(mWriteRequestBuilder.MoreChunkedMessages(aHasMoreChunks).EndOfWriteRequestMessage());
151 1788 : ReturnErrorOnFailure(mMessageWriter.Finalize(&packet));
152 1788 : mChunks.AddToEnd(std::move(packet));
153 1788 : return CHIP_NO_ERROR;
154 1788 : }
155 :
156 483 : CHIP_ERROR WriteClient::EnsureMessage()
157 : {
158 483 : if (mState != State::AddAttribute)
159 : {
160 477 : return StartNewMessage();
161 : }
162 6 : return CHIP_NO_ERROR;
163 : }
164 :
165 1841 : CHIP_ERROR WriteClient::StartNewMessage()
166 : {
167 1841 : uint16_t reservedSize = 0;
168 :
169 1841 : if (mState == State::AddAttribute)
170 : {
171 1364 : ReturnErrorOnFailure(FinalizeMessage(true));
172 : }
173 :
174 : // Do not allow timed request with chunks.
175 1841 : VerifyOrReturnError(!(mTimedWriteTimeoutMs.HasValue() && !mChunks.IsNull()), CHIP_ERROR_NO_MEMORY);
176 :
177 1841 : System::PacketBufferHandle packet = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes);
178 1841 : 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 1841 : 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 1841 : 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 1841 : reservedSize = static_cast<uint16_t>(reservedSize + kReservedSizeForTLVEncodingOverhead);
192 :
193 : #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
194 : // ... and for unit tests.
195 1841 : reservedSize = static_cast<uint16_t>(reservedSize + mReservedSize);
196 : #endif
197 :
198 1841 : mMessageWriter.Init(std::move(packet));
199 :
200 1841 : ReturnErrorOnFailure(mMessageWriter.ReserveBuffer(reservedSize));
201 :
202 1841 : ReturnErrorOnFailure(mWriteRequestBuilder.Init(&mMessageWriter));
203 1841 : mWriteRequestBuilder.SuppressResponse(mSuppressResponse);
204 1841 : mWriteRequestBuilder.TimedRequest(mTimedWriteTimeoutMs.HasValue());
205 1841 : ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
206 1841 : mWriteRequestBuilder.CreateWriteRequests();
207 1841 : ReturnErrorOnFailure(mWriteRequestBuilder.GetError());
208 :
209 1841 : TLV::TLVWriter * writer = mWriteRequestBuilder.GetWriter();
210 1841 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
211 :
212 1841 : return CHIP_NO_ERROR;
213 1841 : }
214 :
215 0 : CHIP_ERROR WriteClient::TryPutSinglePreencodedAttributeWritePayload(const ConcreteDataAttributePath & attributePath,
216 : const TLV::TLVReader & data)
217 : {
218 : 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 0 : CHIP_ERROR WriteClient::PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data)
251 : {
252 0 : ReturnErrorOnFailure(EnsureMessage());
253 :
254 : // ListIndex is missing and the data is an array -- we are writing a whole list.
255 0 : if (!attributePath.IsListOperation() && data.GetType() == TLV::TLVType::kTLVType_Array)
256 : {
257 : TLV::TLVReader dataReader;
258 : TLV::TLVReader valueReader;
259 0 : CHIP_ERROR err = CHIP_NO_ERROR;
260 :
261 0 : ConcreteDataAttributePath path = attributePath;
262 :
263 0 : dataReader.Init(data);
264 0 : dataReader.OpenContainer(valueReader);
265 :
266 : // Encode an empty list for the chunking protocol.
267 0 : ReturnErrorOnFailure(EncodeSingleAttributeDataIB(path, DataModel::List<uint8_t>()));
268 :
269 0 : if (err == CHIP_NO_ERROR)
270 : {
271 0 : path.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
272 0 : while ((err = valueReader.Next()) == CHIP_NO_ERROR)
273 : {
274 0 : ReturnErrorOnFailure(PutSinglePreencodedAttributeWritePayload(path, valueReader));
275 : }
276 : }
277 :
278 0 : if (err == CHIP_END_OF_TLV)
279 : {
280 0 : err = CHIP_NO_ERROR;
281 : }
282 0 : return err;
283 0 : }
284 : // We are writing a non-list attribute, or we are writing a single element of a list.
285 0 : return PutSinglePreencodedAttributeWritePayload(attributePath, data);
286 : }
287 :
288 5128 : const char * WriteClient::GetStateStr() const
289 : {
290 : #if CHIP_DETAIL_LOGGING
291 5128 : switch (mState)
292 : {
293 0 : case State::Initialized:
294 0 : return "Initialized";
295 :
296 2547 : case State::AddAttribute:
297 2547 : return "AddAttribute";
298 :
299 0 : case State::AwaitingTimedStatus:
300 0 : return "AwaitingTimedStatus";
301 :
302 1734 : case State::AwaitingResponse:
303 1734 : return "AwaitingResponse";
304 :
305 420 : case State::ResponseReceived:
306 420 : return "ResponseReceived";
307 :
308 427 : case State::AwaitingDestruction:
309 427 : return "AwaitingDestruction";
310 : }
311 : #endif // CHIP_DETAIL_LOGGING
312 0 : return "N/A";
313 : }
314 :
315 5128 : void WriteClient::MoveToState(const State aTargetState)
316 : {
317 5128 : mState = aTargetState;
318 5128 : ChipLogDetail(DataManagement, "WriteClient moving to [%10.10s]", GetStateStr());
319 5128 : }
320 :
321 424 : CHIP_ERROR WriteClient::SendWriteRequest(const SessionHandle & session, System::Clock::Timeout timeout)
322 : {
323 424 : CHIP_ERROR err = CHIP_NO_ERROR;
324 :
325 424 : VerifyOrExit(mState == State::AddAttribute, err = CHIP_ERROR_INCORRECT_STATE);
326 :
327 424 : err = FinalizeMessage(false /* hasMoreChunks */);
328 424 : SuccessOrExit(err);
329 :
330 : {
331 : // Create a new exchange context.
332 424 : auto exchange = mpExchangeMgr->NewContext(session, this);
333 424 : VerifyOrExit(exchange != nullptr, err = CHIP_ERROR_NO_MEMORY);
334 :
335 424 : mExchangeCtx.Grab(exchange);
336 : }
337 :
338 424 : VerifyOrReturnError(!(mExchangeCtx->IsGroupExchangeContext() && mHasDataVersion), CHIP_ERROR_INVALID_MESSAGE_TYPE);
339 :
340 424 : if (timeout == System::Clock::kZero)
341 : {
342 424 : mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime);
343 : }
344 : else
345 : {
346 0 : mExchangeCtx->SetResponseTimeout(timeout);
347 : }
348 :
349 424 : if (mTimedWriteTimeoutMs.HasValue())
350 : {
351 0 : err = TimedRequest::Send(mExchangeCtx.Get(), mTimedWriteTimeoutMs.Value());
352 0 : SuccessOrExit(err);
353 0 : MoveToState(State::AwaitingTimedStatus);
354 : }
355 : else
356 : {
357 424 : err = SendWriteRequest();
358 424 : SuccessOrExit(err);
359 : }
360 :
361 424 : exit:
362 424 : if (err != CHIP_NO_ERROR)
363 : {
364 0 : ChipLogError(DataManagement, "Write client failed to SendWriteRequest: %" CHIP_ERROR_FORMAT, err.Format());
365 : }
366 : else
367 : {
368 : // TODO: Ideally this would happen async, but to make sure that we
369 : // handle this object dying (e.g. due to IM enging shutdown) while the
370 : // async bits are pending we'd need to malloc some state bit that we can
371 : // twiddle if we die. For now just do the OnDone callback sync.
372 424 : if (session->IsGroupSession())
373 : {
374 : // Always shutdown on Group communication
375 1 : ChipLogDetail(DataManagement, "Closing on group Communication ");
376 :
377 : // Tell the application to release the object.
378 : // TODO: Consumers expect to hand off ownership of the WriteClient and wait for OnDone
379 : // after SendWriteRequest returns success. Calling OnDone before returning is weird.
380 : // Need to refactor the code to avoid this.
381 1 : Close();
382 : }
383 : }
384 :
385 424 : return err;
386 : }
387 :
388 1734 : CHIP_ERROR WriteClient::SendWriteRequest()
389 : {
390 : using namespace Protocols::InteractionModel;
391 : using namespace Messaging;
392 :
393 1734 : System::PacketBufferHandle data = mChunks.PopHead();
394 :
395 1734 : bool isGroupWrite = mExchangeCtx->IsGroupExchangeContext();
396 1734 : if (!mChunks.IsNull() && isGroupWrite)
397 : {
398 : // Reject this request if we have more than one chunk (mChunks is not null after PopHead()), and this is a group
399 : // exchange context.
400 0 : return CHIP_ERROR_INCORRECT_STATE;
401 : }
402 :
403 : // kExpectResponse is ignored by ExchangeContext in case of groupcast
404 1734 : ReturnErrorOnFailure(mExchangeCtx->SendMessage(MsgType::WriteRequest, std::move(data), SendMessageFlags::kExpectResponse));
405 :
406 1734 : MoveToState(State::AwaitingResponse);
407 1734 : return CHIP_NO_ERROR;
408 1734 : }
409 :
410 1730 : CHIP_ERROR WriteClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
411 : System::PacketBufferHandle && aPayload)
412 : {
413 : using namespace Protocols::InteractionModel;
414 :
415 3460 : if (mState == State::AwaitingResponse &&
416 : // We had sent the last chunk of data, and received all responses
417 1730 : mChunks.IsNull())
418 : {
419 420 : MoveToState(State::ResponseReceived);
420 : }
421 :
422 1730 : CHIP_ERROR err = CHIP_NO_ERROR;
423 1730 : bool sendStatusResponse = false;
424 : // Assert that the exchange context matches the client's current context.
425 : // This should never fail because even if SendWriteRequest is called
426 : // back-to-back, the second call will call Close() on the first exchange,
427 : // which clears the OnMessageReceived callback.
428 1730 : VerifyOrExit(apExchangeContext == mExchangeCtx.Get(), err = CHIP_ERROR_INCORRECT_STATE);
429 :
430 1730 : sendStatusResponse = true;
431 :
432 1730 : if (mState == State::AwaitingTimedStatus)
433 : {
434 0 : if (aPayloadHeader.HasMessageType(MsgType::StatusResponse))
435 : {
436 0 : CHIP_ERROR statusError = CHIP_NO_ERROR;
437 0 : SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
438 0 : sendStatusResponse = false;
439 0 : SuccessOrExit(err = statusError);
440 0 : err = SendWriteRequest();
441 : }
442 : else
443 : {
444 0 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
445 : }
446 : // Skip all other processing here (which is for the response to the
447 : // write request), no matter whether err is success or not.
448 0 : goto exit;
449 : }
450 :
451 1730 : if (aPayloadHeader.HasMessageType(MsgType::WriteResponse))
452 : {
453 1726 : err = ProcessWriteResponseMessage(std::move(aPayload));
454 1726 : SuccessOrExit(err);
455 1725 : sendStatusResponse = false;
456 1725 : if (!mChunks.IsNull())
457 : {
458 : // Send the next chunk.
459 1310 : SuccessOrExit(err = SendWriteRequest());
460 : }
461 : }
462 4 : else if (aPayloadHeader.HasMessageType(MsgType::StatusResponse))
463 : {
464 3 : CHIP_ERROR statusError = CHIP_NO_ERROR;
465 5 : SuccessOrExit(err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError));
466 2 : SuccessOrExit(err = statusError);
467 0 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
468 : }
469 : else
470 : {
471 1 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
472 : }
473 :
474 1730 : exit:
475 1730 : if (mpCallback != nullptr)
476 : {
477 1730 : if (err != CHIP_NO_ERROR)
478 : {
479 5 : mpCallback->OnError(this, err);
480 : }
481 : }
482 :
483 1730 : if (sendStatusResponse)
484 : {
485 5 : StatusResponse::Send(Status::InvalidAction, apExchangeContext, false /*aExpectResponse*/);
486 : }
487 :
488 1730 : if (mState != State::AwaitingResponse)
489 : {
490 420 : Close();
491 : }
492 : // Else we got a response to a Timed Request and just sent the write.
493 :
494 1730 : return err;
495 : }
496 :
497 5 : void WriteClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
498 : {
499 5 : ChipLogError(DataManagement, "Time out! failed to receive write response from Exchange: " ChipLogFormatExchange,
500 : ChipLogValueExchange(apExchangeContext));
501 :
502 5 : if (mpCallback != nullptr)
503 : {
504 5 : mpCallback->OnError(this, CHIP_ERROR_TIMEOUT);
505 : }
506 5 : Close();
507 5 : }
508 :
509 2481 : CHIP_ERROR WriteClient::ProcessAttributeStatusIB(AttributeStatusIB::Parser & aAttributeStatusIB)
510 : {
511 2481 : CHIP_ERROR err = CHIP_NO_ERROR;
512 2481 : AttributePathIB::Parser attributePathParser;
513 2481 : StatusIB statusIB;
514 2481 : StatusIB::Parser StatusIBParser;
515 2481 : ConcreteDataAttributePath attributePath;
516 :
517 2481 : err = aAttributeStatusIB.GetPath(&attributePathParser);
518 2481 : SuccessOrExit(err);
519 :
520 2481 : err = attributePathParser.GetConcreteAttributePath(attributePath);
521 2481 : SuccessOrExit(err);
522 :
523 2481 : err = aAttributeStatusIB.GetErrorStatus(&(StatusIBParser));
524 2481 : if (CHIP_NO_ERROR == err)
525 : {
526 2481 : err = StatusIBParser.DecodeStatusIB(statusIB);
527 2481 : SuccessOrExit(err);
528 2481 : if (mpCallback != nullptr)
529 : {
530 2481 : mpCallback->OnResponse(this, attributePath, statusIB);
531 : }
532 : }
533 :
534 0 : exit:
535 2481 : return err;
536 2481 : }
537 :
538 : } // namespace app
539 : } // namespace chip
|