Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 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 : #include <app/CommandHandlerImpl.h>
19 :
20 : #include <access/AccessControl.h>
21 : #include <access/SubjectDescriptor.h>
22 : #include <app-common/zap-generated/cluster-objects.h>
23 : #include <app/MessageDef/StatusIB.h>
24 : #include <app/RequiredPrivilege.h>
25 : #include <app/StatusResponse.h>
26 : #include <app/data-model-provider/OperationTypes.h>
27 : #include <app/util/MatterCallbacks.h>
28 : #include <credentials/GroupDataProvider.h>
29 : #include <lib/core/CHIPConfig.h>
30 : #include <lib/core/TLVData.h>
31 : #include <lib/core/TLVUtilities.h>
32 : #include <lib/support/IntrusiveList.h>
33 : #include <lib/support/TypeTraits.h>
34 : #include <messaging/ExchangeContext.h>
35 : #include <platform/LockTracker.h>
36 : #include <protocols/interaction_model/StatusCode.h>
37 : #include <protocols/secure_channel/Constants.h>
38 :
39 : namespace chip {
40 : namespace app {
41 : using Status = Protocols::InteractionModel::Status;
42 :
43 57 : CommandHandlerImpl::CommandHandlerImpl(Callback * apCallback) : mpCallback(apCallback), mSuppressResponse(false) {}
44 :
45 8 : CommandHandlerImpl::CommandHandlerImpl(TestOnlyOverrides & aTestOverride, Callback * apCallback) : CommandHandlerImpl(apCallback)
46 : {
47 8 : if (aTestOverride.commandPathRegistry)
48 : {
49 8 : mMaxPathsPerInvoke = aTestOverride.commandPathRegistry->MaxSize();
50 8 : mCommandPathRegistry = aTestOverride.commandPathRegistry;
51 : }
52 8 : if (aTestOverride.commandResponder)
53 : {
54 8 : SetExchangeInterface(aTestOverride.commandResponder);
55 : }
56 8 : }
57 :
58 57 : CommandHandlerImpl::~CommandHandlerImpl()
59 : {
60 57 : InvalidateHandles();
61 57 : }
62 :
63 70 : CHIP_ERROR CommandHandlerImpl::AllocateBuffer()
64 : {
65 : // We should only allocate a buffer if we will be sending out a response.
66 70 : VerifyOrReturnError(ResponsesAccepted(), CHIP_ERROR_INCORRECT_STATE);
67 :
68 70 : if (!mBufferAllocated)
69 : {
70 48 : mCommandMessageWriter.Reset();
71 :
72 48 : const size_t commandBufferMaxSize = mpResponder->GetCommandResponseMaxBufferSize();
73 48 : auto commandPacket = System::PacketBufferHandle::New(commandBufferMaxSize);
74 :
75 48 : VerifyOrReturnError(!commandPacket.IsNull(), CHIP_ERROR_NO_MEMORY);
76 :
77 48 : mCommandMessageWriter.Init(std::move(commandPacket));
78 48 : ReturnErrorOnFailure(mInvokeResponseBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
79 :
80 48 : if (mReserveSpaceForMoreChunkMessages)
81 : {
82 10 : ReturnErrorOnFailure(mInvokeResponseBuilder.ReserveSpaceForMoreChunkedMessages());
83 : }
84 :
85 : // Sending an InvokeResponse to an InvokeResponse is going to be removed from the spec soon.
86 : // It was never implemented in the SDK, and there are no command responses that expect a
87 : // command response. This means we will never receive an InvokeResponse Message in response
88 : // to an InvokeResponse Message that we are sending. This means that the only response
89 : // we are expecting to receive in response to an InvokeResponse Message that we are
90 : // sending-out is a status when we are chunking multiple responses. As a result, to satisfy the
91 : // condition that we don't set SuppressResponse to true while also setting
92 : // MoreChunkedMessages to true, we are hardcoding the value to false here.
93 48 : mInvokeResponseBuilder.SuppressResponse(/* aSuppressResponse = */ false);
94 48 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
95 :
96 48 : mInvokeResponseBuilder.CreateInvokeResponses(/* aReserveEndBuffer = */ true);
97 48 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
98 :
99 48 : mBufferAllocated = true;
100 48 : MoveToState(State::NewResponseMessage);
101 48 : }
102 :
103 70 : return CHIP_NO_ERROR;
104 : }
105 :
106 39 : Status CommandHandlerImpl::OnInvokeCommandRequest(CommandHandlerExchangeInterface & commandResponder,
107 : System::PacketBufferHandle && payload, bool isTimedInvoke)
108 : {
109 39 : VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "state should be Idle");
110 :
111 39 : SetExchangeInterface(&commandResponder);
112 :
113 : // Using RAII here: if this is the only handle remaining, DecrementHoldOff will
114 : // call the CommandHandlerImpl::OnDone callback when this function returns.
115 39 : Handle workHandle(this);
116 :
117 39 : Status status = ProcessInvokeRequest(std::move(payload), isTimedInvoke);
118 39 : mGoneAsync = true;
119 78 : return status;
120 39 : }
121 :
122 20 : CHIP_ERROR CommandHandlerImpl::TryAddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
123 : const DataModel::EncodableToTLV & aEncodable)
124 : {
125 20 : ConcreteCommandPath responseCommandPath = { aRequestCommandPath.mEndpointId, aRequestCommandPath.mClusterId,
126 20 : aResponseCommandId };
127 :
128 20 : InvokeResponseParameters prepareParams(aRequestCommandPath);
129 20 : prepareParams.SetStartOrEndDataStruct(false);
130 :
131 : {
132 20 : ScopedChange<bool> internalCallToAddResponse(mInternalCallToAddResponseData, true);
133 20 : ReturnErrorOnFailure(PrepareInvokeResponseCommand(responseCommandPath, prepareParams));
134 20 : }
135 :
136 18 : TLV::TLVWriter * writer = GetCommandDataIBTLVWriter();
137 18 : VerifyOrReturnError(writer != nullptr, CHIP_ERROR_INCORRECT_STATE);
138 18 : auto context = GetExchangeContext();
139 18 : if (context && context->HasSessionHandle())
140 : {
141 5 : ReturnErrorOnFailure(aEncodable.EncodeTo(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields),
142 : context->GetSessionHandle()->GetFabricIndex()));
143 : }
144 : else
145 : {
146 13 : ReturnErrorOnFailure(aEncodable.EncodeTo(*writer, TLV::ContextTag(CommandDataIB::Tag::kFields)));
147 : }
148 16 : return FinishCommand(/* aEndDataStruct = */ false);
149 : }
150 :
151 19 : CHIP_ERROR CommandHandlerImpl::AddResponseData(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
152 : const DataModel::EncodableToTLV & aEncodable)
153 : {
154 : // Return early when response should not be sent out.
155 19 : VerifyOrReturnValue(ResponsesAccepted(), CHIP_NO_ERROR);
156 36 : return TryAddingResponse(
157 38 : [&]() -> CHIP_ERROR { return TryAddResponseData(aRequestCommandPath, aResponseCommandId, aEncodable); });
158 : }
159 :
160 38 : CHIP_ERROR CommandHandlerImpl::ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage)
161 : {
162 38 : CHIP_ERROR err = CHIP_NO_ERROR;
163 38 : size_t commandCount = 0;
164 38 : bool commandRefExpected = false;
165 38 : InvokeRequests::Parser invokeRequests;
166 :
167 38 : ReturnErrorOnFailure(invokeRequestMessage.GetInvokeRequests(&invokeRequests));
168 38 : TLV::TLVReader invokeRequestsReader;
169 38 : invokeRequests.GetReader(&invokeRequestsReader);
170 :
171 38 : ReturnErrorOnFailure(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */));
172 :
173 : // If this is a GroupRequest the only thing to check is that there is only one
174 : // CommandDataIB.
175 38 : if (IsGroupRequest())
176 : {
177 0 : VerifyOrReturnError(commandCount == 1, CHIP_ERROR_INVALID_ARGUMENT);
178 0 : return CHIP_NO_ERROR;
179 : }
180 : // While technically any commandCount == 1 should already be unique and does not need
181 : // any further validation, we do need to read and populate the registry to help
182 : // in building the InvokeResponse.
183 :
184 38 : VerifyOrReturnError(commandCount <= MaxPathsPerInvoke(), CHIP_ERROR_INVALID_ARGUMENT);
185 :
186 : // If there is more than one CommandDataIB, spec states that CommandRef must be provided.
187 36 : commandRefExpected = commandCount > 1;
188 :
189 72 : while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
190 : {
191 40 : VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), CHIP_ERROR_INVALID_ARGUMENT);
192 38 : CommandDataIB::Parser commandData;
193 38 : ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
194 :
195 : // First validate that we can get a ConcreteCommandPath.
196 38 : CommandPathIB::Parser commandPath;
197 38 : ConcreteCommandPath concretePath(0, 0, 0);
198 38 : ReturnErrorOnFailure(commandData.GetPath(&commandPath));
199 38 : ReturnErrorOnFailure(commandPath.GetConcreteCommandPath(concretePath));
200 :
201 : // Grab the CommandRef if there is one, and validate that it's there when it
202 : // has to be.
203 37 : std::optional<uint16_t> commandRef;
204 : uint16_t ref;
205 37 : err = commandData.GetRef(&ref);
206 37 : VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
207 37 : if (err == CHIP_END_OF_TLV && commandRefExpected)
208 : {
209 0 : return CHIP_ERROR_INVALID_ARGUMENT;
210 : }
211 37 : if (err == CHIP_NO_ERROR)
212 : {
213 5 : commandRef.emplace(ref);
214 : }
215 :
216 : // Adding can fail if concretePath is not unique, or if commandRef is a value
217 : // and is not unique, or if we have already added more paths than we support.
218 37 : ReturnErrorOnFailure(GetCommandPathRegistry().Add(concretePath, commandRef));
219 : }
220 :
221 : // It's OK/expected to have reached the end of the container without failure.
222 34 : if (CHIP_END_OF_TLV == err)
223 : {
224 34 : err = CHIP_NO_ERROR;
225 : }
226 34 : ReturnErrorOnFailure(err);
227 34 : return invokeRequestMessage.ExitContainer();
228 : }
229 :
230 41 : Status CommandHandlerImpl::ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke)
231 : {
232 41 : CHIP_ERROR err = CHIP_NO_ERROR;
233 41 : System::PacketBufferTLVReader reader;
234 41 : InvokeRequestMessage::Parser invokeRequestMessage;
235 41 : InvokeRequests::Parser invokeRequests;
236 41 : reader.Init(std::move(payload));
237 41 : VerifyOrReturnError(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
238 : #if CHIP_CONFIG_IM_PRETTY_PRINT
239 40 : invokeRequestMessage.PrettyPrint();
240 : #endif
241 40 : VerifyOrDie(mpResponder);
242 40 : if (mpResponder->GetGroupId().HasValue())
243 : {
244 0 : SetGroupRequest(true);
245 : }
246 :
247 : // When updating this code, please remember to make corresponding changes to TestOnlyInvokeCommandRequestWithFaultsInjected.
248 40 : VerifyOrReturnError(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, Status::InvalidAction);
249 40 : VerifyOrReturnError(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, Status::InvalidAction);
250 40 : VerifyOrReturnError(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, Status::InvalidAction);
251 40 : VerifyOrReturnError(mTimedRequest == isTimedInvoke, Status::TimedRequestMismatch);
252 :
253 : {
254 38 : InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
255 38 : VerifyOrReturnError(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
256 : Status::InvalidAction);
257 : }
258 :
259 33 : TLV::TLVReader invokeRequestsReader;
260 33 : invokeRequests.GetReader(&invokeRequestsReader);
261 :
262 33 : size_t commandCount = 0;
263 33 : VerifyOrReturnError(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
264 : Status::InvalidAction);
265 33 : if (commandCount > 1)
266 : {
267 1 : mReserveSpaceForMoreChunkMessages = true;
268 : }
269 :
270 67 : while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
271 : {
272 34 : VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), Status::InvalidAction);
273 34 : CommandDataIB::Parser commandData;
274 34 : VerifyOrReturnError(commandData.Init(invokeRequestsReader) == CHIP_NO_ERROR, Status::InvalidAction);
275 34 : Status status = Status::Success;
276 34 : if (IsGroupRequest())
277 : {
278 0 : status = ProcessGroupCommandDataIB(commandData);
279 : }
280 : else
281 : {
282 34 : status = ProcessCommandDataIB(commandData);
283 : }
284 34 : if (status != Status::Success)
285 : {
286 0 : return status;
287 : }
288 : }
289 :
290 : // if we have exhausted this container
291 33 : if (CHIP_END_OF_TLV == err)
292 : {
293 33 : err = CHIP_NO_ERROR;
294 : }
295 33 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
296 33 : VerifyOrReturnError(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
297 33 : return Status::Success;
298 41 : }
299 :
300 46 : void CommandHandlerImpl::Close()
301 : {
302 46 : mSuppressResponse = false;
303 46 : mpResponder = nullptr;
304 46 : MoveToState(State::AwaitingDestruction);
305 :
306 : // We must finish all async work before we can shut down a CommandHandlerImpl. The actual CommandHandlerImpl MUST finish their
307 : // work in reasonable time or there is a bug. The only case for releasing CommandHandlerImpl without CommandHandler::Handle
308 : // releasing its reference is the stack shutting down, in which case Close() is not called. So the below check should always
309 : // pass.
310 46 : VerifyOrDieWithMsg(mPendingWork == 0, DataManagement, "CommandHandlerImpl::Close() called with %u unfinished async work items",
311 : static_cast<unsigned int>(mPendingWork));
312 46 : InvalidateHandles();
313 :
314 46 : if (mpCallback)
315 : {
316 43 : mpCallback->OnDone(*this);
317 : }
318 46 : }
319 :
320 87 : void CommandHandlerImpl::AddToHandleList(Handle * apHandle)
321 : {
322 87 : mpHandleList.PushBack(apHandle);
323 87 : }
324 :
325 87 : void CommandHandlerImpl::RemoveFromHandleList(Handle * apHandle)
326 : {
327 87 : VerifyOrDie(mpHandleList.Contains(apHandle));
328 87 : mpHandleList.Remove(apHandle);
329 87 : }
330 :
331 103 : void CommandHandlerImpl::InvalidateHandles()
332 : {
333 103 : for (auto handle = mpHandleList.begin(); handle != mpHandleList.end(); ++handle)
334 : {
335 0 : handle->Invalidate();
336 : }
337 103 : mpHandleList.Clear();
338 103 : }
339 :
340 87 : void CommandHandlerImpl::IncrementHoldOff(Handle * apHandle)
341 : {
342 87 : mPendingWork++;
343 87 : AddToHandleList(apHandle);
344 87 : }
345 :
346 87 : void CommandHandlerImpl::DecrementHoldOff(Handle * apHandle)
347 : {
348 :
349 87 : mPendingWork--;
350 87 : ChipLogDetail(DataManagement, "Decreasing reference count for CommandHandlerImpl, remaining %u",
351 : static_cast<unsigned int>(mPendingWork));
352 :
353 87 : RemoveFromHandleList(apHandle);
354 :
355 87 : if (mPendingWork != 0)
356 : {
357 41 : return;
358 : }
359 :
360 46 : if (mpResponder == nullptr)
361 : {
362 0 : ChipLogProgress(DataManagement, "Skipping command response: response sender is null");
363 : }
364 46 : else if (!IsGroupRequest())
365 : {
366 46 : CHIP_ERROR err = FinalizeLastInvokeResponseMessage();
367 46 : if (err != CHIP_NO_ERROR)
368 : {
369 9 : ChipLogError(DataManagement, "Failed to finalize command response: %" CHIP_ERROR_FORMAT, err.Format());
370 : }
371 : }
372 :
373 46 : Close();
374 : }
375 :
376 : namespace {
377 : // We use this when the sender did not actually provide a CommandFields struct,
378 : // to avoid downstream consumers having to worry about cases when there is or is
379 : // not a struct available. We use an empty struct with anonymous tag, since we
380 : // can't use a context tag at top level, and consumers should not care about the
381 : // tag here).
382 : constexpr uint8_t sNoFields[] = {
383 : CHIP_TLV_STRUCTURE(CHIP_TLV_TAG_ANONYMOUS),
384 : CHIP_TLV_END_OF_CONTAINER,
385 : };
386 : } // anonymous namespace
387 :
388 34 : Status CommandHandlerImpl::ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement)
389 : {
390 34 : CHIP_ERROR err = CHIP_NO_ERROR;
391 34 : CommandPathIB::Parser commandPath;
392 34 : ConcreteCommandPath concretePath(0, 0, 0);
393 34 : TLV::TLVReader commandDataReader;
394 :
395 : // NOTE: errors may occur before the concrete command path is even fully decoded.
396 :
397 34 : err = aCommandElement.GetPath(&commandPath);
398 34 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
399 :
400 34 : err = commandPath.GetConcreteCommandPath(concretePath);
401 34 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
402 :
403 : {
404 34 : Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
405 34 : DataModel::InvokeRequest request;
406 :
407 34 : request.path = concretePath;
408 34 : request.subjectDescriptor = &subjectDescriptor;
409 34 : request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
410 :
411 34 : Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
412 34 : if (preCheckStatus != Status::Success)
413 : {
414 7 : return FallibleAddStatus(concretePath, preCheckStatus) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
415 : }
416 : }
417 :
418 27 : err = aCommandElement.GetFields(&commandDataReader);
419 27 : if (CHIP_END_OF_TLV == err)
420 : {
421 2 : ChipLogDetail(DataManagement,
422 : "Received command without data for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
423 : concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
424 2 : commandDataReader.Init(sNoFields);
425 2 : err = commandDataReader.Next();
426 : }
427 27 : if (CHIP_NO_ERROR == err)
428 : {
429 27 : ChipLogDetail(DataManagement, "Received command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
430 : concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
431 27 : SuccessOrExit(err = DataModelCallbacks::GetInstance()->PreCommandReceived(concretePath, GetSubjectDescriptor()));
432 27 : mpCallback->DispatchCommand(*this, concretePath, commandDataReader);
433 27 : DataModelCallbacks::GetInstance()->PostCommandReceived(concretePath, GetSubjectDescriptor());
434 : }
435 :
436 0 : exit:
437 27 : if (err != CHIP_NO_ERROR)
438 : {
439 0 : return FallibleAddStatus(concretePath, Status::InvalidCommand) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
440 : }
441 :
442 : // We have handled the error status above and put the error status in response, now return success status so we can process
443 : // other commands in the invoke request.
444 27 : return Status::Success;
445 : }
446 :
447 0 : Status CommandHandlerImpl::ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement)
448 : {
449 0 : CHIP_ERROR err = CHIP_NO_ERROR;
450 0 : CommandPathIB::Parser commandPath;
451 0 : TLV::TLVReader commandDataReader;
452 : ClusterId clusterId;
453 : CommandId commandId;
454 : GroupId groupId;
455 : FabricIndex fabric;
456 :
457 0 : Credentials::GroupDataProvider::GroupEndpoint mapping;
458 0 : Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider();
459 : Credentials::GroupDataProvider::EndpointIterator * iterator;
460 :
461 0 : err = aCommandElement.GetPath(&commandPath);
462 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
463 :
464 0 : err = commandPath.GetGroupCommandPath(&clusterId, &commandId);
465 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
466 :
467 0 : VerifyOrDie(mpResponder);
468 : // The optionalGroupId must have a value, otherwise we wouldn't have reached this code path.
469 0 : groupId = mpResponder->GetGroupId().Value();
470 0 : fabric = GetAccessingFabricIndex();
471 :
472 0 : ChipLogDetail(DataManagement, "Received group command for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
473 : groupId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
474 :
475 0 : err = aCommandElement.GetFields(&commandDataReader);
476 0 : if (CHIP_END_OF_TLV == err)
477 : {
478 0 : ChipLogDetail(DataManagement,
479 : "Received command without data for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI, groupId,
480 : ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
481 0 : commandDataReader.Init(sNoFields);
482 0 : err = commandDataReader.Next();
483 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
484 : }
485 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::Failure);
486 :
487 : // No check for `CommandIsFabricScoped` unlike in `ProcessCommandDataIB()` since group commands
488 : // always have an accessing fabric, by definition.
489 :
490 : // Find which endpoints can process the command, and dispatch to them.
491 0 : iterator = groupDataProvider->IterateEndpoints(fabric);
492 0 : VerifyOrReturnError(iterator != nullptr, Status::Failure);
493 :
494 0 : while (iterator->Next(mapping))
495 : {
496 0 : if (groupId != mapping.group_id)
497 : {
498 0 : continue;
499 : }
500 :
501 0 : ChipLogDetail(DataManagement,
502 : "Processing group command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
503 : mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
504 :
505 0 : const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId);
506 :
507 : {
508 0 : Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
509 0 : DataModel::InvokeRequest request;
510 :
511 0 : request.path = concretePath;
512 0 : request.subjectDescriptor = &subjectDescriptor;
513 0 : request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke());
514 :
515 0 : Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request);
516 0 : if (preCheckStatus != Status::Success)
517 : {
518 : // Command failed for a specific path, but keep trying the rest of the paths.
519 0 : continue;
520 : }
521 : }
522 :
523 0 : if ((err = DataModelCallbacks::GetInstance()->PreCommandReceived(concretePath, GetSubjectDescriptor())) == CHIP_NO_ERROR)
524 : {
525 0 : TLV::TLVReader dataReader(commandDataReader);
526 0 : mpCallback->DispatchCommand(*this, concretePath, dataReader);
527 0 : DataModelCallbacks::GetInstance()->PostCommandReceived(concretePath, GetSubjectDescriptor());
528 : }
529 : else
530 : {
531 0 : ChipLogError(DataManagement,
532 : "Error when calling PreCommandReceived for Endpoint=%u Cluster=" ChipLogFormatMEI
533 : " Command=" ChipLogFormatMEI " : %" CHIP_ERROR_FORMAT,
534 : mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId), err.Format());
535 0 : continue;
536 : }
537 : }
538 0 : iterator->Release();
539 0 : return Status::Success;
540 : }
541 :
542 40 : CHIP_ERROR CommandHandlerImpl::TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
543 : {
544 : // Return early when response should not be sent out.
545 40 : VerifyOrReturnValue(ResponsesAccepted(), CHIP_NO_ERROR);
546 :
547 39 : ReturnErrorOnFailure(PrepareStatus(aCommandPath));
548 38 : CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
549 38 : StatusIB::Builder & statusIBBuilder = commandStatus.CreateErrorStatus();
550 38 : ReturnErrorOnFailure(commandStatus.GetError());
551 38 : statusIBBuilder.EncodeStatusIB(aStatus);
552 38 : ReturnErrorOnFailure(statusIBBuilder.GetError());
553 38 : return FinishStatus();
554 : }
555 :
556 39 : CHIP_ERROR CommandHandlerImpl::AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
557 : {
558 79 : return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddStatusInternal(aCommandPath, aStatus); });
559 : }
560 :
561 25 : void CommandHandlerImpl::AddStatus(const ConcreteCommandPath & aCommandPath,
562 : const Protocols::InteractionModel::ClusterStatusCode & status, const char * context)
563 : {
564 :
565 25 : CHIP_ERROR error = FallibleAddStatus(aCommandPath, status, context);
566 :
567 25 : if (error != CHIP_NO_ERROR)
568 : {
569 0 : ChipLogError(DataManagement, "Failed to add command status: %" CHIP_ERROR_FORMAT, error.Format());
570 : // TODO(#30453) we could call mpResponder->ResponseDropped() if err == CHIP_ERROR_NO_MEMORY. This should
571 : // be done as a follow up so that change can be evaluated as a standalone PR.
572 :
573 : // Do not crash if the status has not been added due to running out of packet buffers or other resources.
574 : // It is better to drop a single response than to go offline and lose all sessions and subscriptions.
575 0 : VerifyOrDie(error == CHIP_ERROR_NO_MEMORY);
576 : }
577 25 : }
578 :
579 39 : CHIP_ERROR CommandHandlerImpl::FallibleAddStatus(const ConcreteCommandPath & path,
580 : const Protocols::InteractionModel::ClusterStatusCode & status,
581 : const char * context)
582 : {
583 39 : if (!status.IsSuccess())
584 : {
585 17 : if (context == nullptr)
586 : {
587 17 : context = "no additional context";
588 : }
589 :
590 17 : if (status.HasClusterSpecificCode())
591 : {
592 1 : ChipLogError(DataManagement,
593 : "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus
594 : " ClusterSpecificCode=%u (%s)",
595 : path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
596 : ChipLogValueIMStatus(status.GetStatus()), static_cast<unsigned>(status.GetClusterSpecificCode().Value()),
597 : context);
598 : }
599 : else
600 : {
601 16 : ChipLogError(DataManagement,
602 : "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus
603 : " (%s)",
604 : path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
605 : ChipLogValueIMStatus(status.GetStatus()), context);
606 : }
607 : }
608 :
609 39 : return AddStatusInternal(path, StatusIB{ status });
610 : }
611 :
612 23 : CHIP_ERROR CommandHandlerImpl::PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
613 : const CommandHandlerImpl::InvokeResponseParameters & aPrepareParameters)
614 : {
615 23 : auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aPrepareParameters.mRequestCommandPath);
616 23 : VerifyOrReturnValue(commandPathRegistryEntry.has_value(), CHIP_ERROR_INCORRECT_STATE);
617 :
618 22 : return PrepareInvokeResponseCommand(*commandPathRegistryEntry, aResponseCommandPath, aPrepareParameters.mStartOrEndDataStruct);
619 : }
620 :
621 22 : CHIP_ERROR CommandHandlerImpl::PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
622 : const ConcreteCommandPath & aCommandPath, bool aStartDataStruct)
623 : {
624 : // Intentionally omitting the ResponsesAccepted early exit. Direct use of PrepareInvokeResponseCommand
625 : // is discouraged, as it often indicates incorrect usage patterns (see GitHub issue #32486).
626 : // If you're encountering CHIP_ERROR_INCORRECT_STATE, refactoring to use AddResponse is recommended.
627 22 : ReturnErrorOnFailure(AllocateBuffer());
628 :
629 22 : if (!mInternalCallToAddResponseData && mState == State::AddedCommand)
630 : {
631 : // An attempt is being made to add CommandData InvokeResponse using primitive
632 : // CommandHandlerImpl APIs. While not recommended, as this potentially leaves the
633 : // CommandHandlerImpl in an incorrect state upon failure, this approach is permitted
634 : // for legacy reasons. To maximize the likelihood of success, particularly when
635 : // handling large amounts of data, we try to obtain a new, completely empty
636 : // InvokeResponseMessage, as the existing one already has space occupied.
637 0 : ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
638 : }
639 :
640 22 : CreateBackupForResponseRollback();
641 : //
642 : // We must not be in the middle of preparing a command, or having prepared or sent one.
643 : //
644 22 : VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
645 :
646 : // TODO(#30453): See if we can pass this back up the stack so caller can provide this instead of taking up
647 : // space in CommandHanlder.
648 22 : mRefForResponse = apCommandPathRegistryEntry.ref;
649 :
650 22 : MoveToState(State::Preparing);
651 22 : InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
652 22 : InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
653 22 : ReturnErrorOnFailure(invokeResponses.GetError());
654 :
655 20 : CommandDataIB::Builder & commandData = invokeResponse.CreateCommand();
656 20 : ReturnErrorOnFailure(commandData.GetError());
657 20 : CommandPathIB::Builder & path = commandData.CreatePath();
658 20 : ReturnErrorOnFailure(commandData.GetError());
659 20 : ReturnErrorOnFailure(path.Encode(aCommandPath));
660 20 : if (aStartDataStruct)
661 : {
662 2 : ReturnErrorOnFailure(commandData.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
663 : TLV::kTLVType_Structure, mDataElementContainerType));
664 : }
665 20 : MoveToState(State::AddingCommand);
666 20 : return CHIP_NO_ERROR;
667 : }
668 :
669 18 : CHIP_ERROR CommandHandlerImpl::FinishCommand(bool aStartDataStruct)
670 : {
671 : // Intentionally omitting the ResponsesAccepted early exit. Direct use of FinishCommand
672 : // is discouraged, as it often indicates incorrect usage patterns (see GitHub issue #32486).
673 : // If you're encountering CHIP_ERROR_INCORRECT_STATE, refactoring to use AddResponse is recommended.
674 18 : VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
675 17 : CommandDataIB::Builder & commandData = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand();
676 17 : if (aStartDataStruct)
677 : {
678 1 : ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
679 : }
680 :
681 17 : if (mRefForResponse.has_value())
682 : {
683 9 : ReturnErrorOnFailure(commandData.Ref(*mRefForResponse));
684 : }
685 :
686 17 : ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
687 17 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
688 17 : MoveToState(State::AddedCommand);
689 17 : return CHIP_NO_ERROR;
690 : }
691 :
692 39 : CHIP_ERROR CommandHandlerImpl::PrepareStatus(const ConcreteCommandPath & aCommandPath)
693 : {
694 39 : ReturnErrorOnFailure(AllocateBuffer());
695 : //
696 : // We must not be in the middle of preparing a command, or having prepared or sent one.
697 : //
698 39 : VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
699 39 : if (mState == State::AddedCommand)
700 : {
701 8 : CreateBackupForResponseRollback();
702 : }
703 :
704 39 : auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aCommandPath);
705 39 : VerifyOrReturnError(commandPathRegistryEntry.has_value(), CHIP_ERROR_INCORRECT_STATE);
706 39 : mRefForResponse = commandPathRegistryEntry->ref;
707 :
708 39 : MoveToState(State::Preparing);
709 39 : InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
710 39 : InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
711 39 : ReturnErrorOnFailure(invokeResponses.GetError());
712 38 : CommandStatusIB::Builder & commandStatus = invokeResponse.CreateStatus();
713 38 : ReturnErrorOnFailure(commandStatus.GetError());
714 38 : CommandPathIB::Builder & path = commandStatus.CreatePath();
715 38 : ReturnErrorOnFailure(commandStatus.GetError());
716 38 : ReturnErrorOnFailure(path.Encode(aCommandPath));
717 38 : MoveToState(State::AddingCommand);
718 38 : return CHIP_NO_ERROR;
719 : }
720 :
721 38 : CHIP_ERROR CommandHandlerImpl::FinishStatus()
722 : {
723 38 : VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
724 :
725 38 : CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
726 38 : if (mRefForResponse.has_value())
727 : {
728 3 : ReturnErrorOnFailure(commandStatus.Ref(*mRefForResponse));
729 : }
730 :
731 38 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus().EndOfCommandStatusIB());
732 38 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
733 38 : MoveToState(State::AddedCommand);
734 38 : return CHIP_NO_ERROR;
735 : }
736 :
737 30 : void CommandHandlerImpl::CreateBackupForResponseRollback()
738 : {
739 30 : VerifyOrReturn(mState == State::NewResponseMessage || mState == State::AddedCommand);
740 30 : VerifyOrReturn(mInvokeResponseBuilder.GetInvokeResponses().GetError() == CHIP_NO_ERROR);
741 30 : VerifyOrReturn(mInvokeResponseBuilder.GetError() == CHIP_NO_ERROR);
742 30 : mInvokeResponseBuilder.Checkpoint(mBackupWriter);
743 30 : mBackupState = mState;
744 30 : mRollbackBackupValid = true;
745 : }
746 :
747 5 : CHIP_ERROR CommandHandlerImpl::RollbackResponse()
748 : {
749 5 : VerifyOrReturnError(mRollbackBackupValid, CHIP_ERROR_INCORRECT_STATE);
750 5 : VerifyOrReturnError(mState == State::Preparing || mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
751 5 : ChipLogDetail(DataManagement, "Rolling back response");
752 : // TODO(#30453): Rollback of mInvokeResponseBuilder should handle resetting
753 : // InvokeResponses.
754 5 : mInvokeResponseBuilder.GetInvokeResponses().ResetError();
755 5 : mInvokeResponseBuilder.Rollback(mBackupWriter);
756 5 : MoveToState(mBackupState);
757 5 : mRollbackBackupValid = false;
758 5 : return CHIP_NO_ERROR;
759 : }
760 :
761 19 : TLV::TLVWriter * CommandHandlerImpl::GetCommandDataIBTLVWriter()
762 : {
763 19 : if (mState != State::AddingCommand)
764 : {
765 1 : return nullptr;
766 : }
767 :
768 18 : return mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand().GetWriter();
769 : }
770 :
771 0 : FabricIndex CommandHandlerImpl::GetAccessingFabricIndex() const
772 : {
773 0 : VerifyOrDie(!mGoneAsync);
774 0 : VerifyOrDie(mpResponder);
775 0 : return mpResponder->GetAccessingFabricIndex();
776 : }
777 :
778 3 : CHIP_ERROR CommandHandlerImpl::FinalizeInvokeResponseMessageAndPrepareNext()
779 : {
780 3 : ReturnErrorOnFailure(FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ true));
781 : // After successfully finalizing InvokeResponseMessage, no buffer should remain
782 : // allocated.
783 3 : VerifyOrDie(!mBufferAllocated);
784 3 : CHIP_ERROR err = AllocateBuffer();
785 3 : if (err != CHIP_NO_ERROR)
786 : {
787 : // TODO(#30453): Improve ResponseDropped calls to occur only when dropping is
788 : // definitively guaranteed.
789 : // Response dropping is not yet definitive as a subsequent call
790 : // to AllocateBuffer might succeed.
791 0 : VerifyOrDie(mpResponder);
792 0 : mpResponder->ResponseDropped();
793 : }
794 3 : return err;
795 : }
796 :
797 49 : CHIP_ERROR CommandHandlerImpl::FinalizeInvokeResponseMessage(bool aHasMoreChunks)
798 : {
799 49 : System::PacketBufferHandle packet;
800 :
801 49 : VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
802 40 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().EndOfInvokeResponses());
803 40 : if (aHasMoreChunks)
804 : {
805 : // Unreserving space previously reserved for MoreChunkedMessages is done
806 : // in the call to mInvokeResponseBuilder.MoreChunkedMessages.
807 3 : mInvokeResponseBuilder.MoreChunkedMessages(aHasMoreChunks);
808 3 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
809 : }
810 40 : ReturnErrorOnFailure(mInvokeResponseBuilder.EndOfInvokeResponseMessage());
811 40 : ReturnErrorOnFailure(mCommandMessageWriter.Finalize(&packet));
812 40 : VerifyOrDie(mpResponder);
813 40 : mpResponder->AddInvokeResponseToSend(std::move(packet));
814 40 : mBufferAllocated = false;
815 40 : mRollbackBackupValid = false;
816 40 : return CHIP_NO_ERROR;
817 49 : }
818 :
819 54 : void CommandHandlerImpl::SetExchangeInterface(CommandHandlerExchangeInterface * commandResponder)
820 : {
821 54 : VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "CommandResponseSender can only be set in idle state");
822 54 : mpResponder = commandResponder;
823 54 : }
824 :
825 273 : const char * CommandHandlerImpl::GetStateStr() const
826 : {
827 : #if CHIP_DETAIL_LOGGING
828 273 : switch (mState)
829 : {
830 0 : case State::Idle:
831 0 : return "Idle";
832 :
833 50 : case State::NewResponseMessage:
834 50 : return "NewResponseMessage";
835 :
836 61 : case State::Preparing:
837 61 : return "Preparing";
838 :
839 58 : case State::AddingCommand:
840 58 : return "AddingCommand";
841 :
842 58 : case State::AddedCommand:
843 58 : return "AddedCommand";
844 :
845 0 : case State::DispatchResponses:
846 0 : return "DispatchResponses";
847 :
848 46 : case State::AwaitingDestruction:
849 46 : return "AwaitingDestruction";
850 : }
851 : #endif // CHIP_DETAIL_LOGGING
852 0 : return "N/A";
853 : }
854 :
855 273 : void CommandHandlerImpl::MoveToState(const State aTargetState)
856 : {
857 273 : mState = aTargetState;
858 273 : ChipLogDetail(DataManagement, "Command handler moving to [%10.10s]", GetStateStr());
859 273 : }
860 :
861 0 : void CommandHandlerImpl::FlushAcksRightAwayOnSlowCommand()
862 : {
863 0 : if (mpResponder)
864 : {
865 0 : mpResponder->HandlingSlowCommand();
866 : }
867 0 : }
868 :
869 111 : Access::SubjectDescriptor CommandHandlerImpl::GetSubjectDescriptor() const
870 : {
871 111 : VerifyOrDie(!mGoneAsync);
872 111 : VerifyOrDie(mpResponder);
873 111 : return mpResponder->GetSubjectDescriptor();
874 : }
875 :
876 57 : bool CommandHandlerImpl::IsTimedInvoke() const
877 : {
878 57 : return mTimedRequest;
879 : }
880 :
881 8 : void CommandHandlerImpl::AddResponse(const ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
882 : const DataModel::EncodableToTLV & aEncodable)
883 : {
884 8 : CHIP_ERROR err = AddResponseData(aRequestCommandPath, aResponseCommandId, aEncodable);
885 8 : if (err != CHIP_NO_ERROR)
886 : {
887 1 : ChipLogError(DataManagement, "Adding response failed: %" CHIP_ERROR_FORMAT ". Returning failure instead.", err.Format());
888 1 : AddStatus(aRequestCommandPath, Protocols::InteractionModel::Status::Failure);
889 : }
890 8 : }
891 :
892 20 : Messaging::ExchangeContext * CommandHandlerImpl::GetExchangeContext() const
893 : {
894 20 : VerifyOrDie(mpResponder);
895 20 : return mpResponder->GetExchangeContext();
896 : }
897 :
898 : #if CHIP_WITH_NLFAULTINJECTION
899 :
900 : namespace {
901 :
902 0 : CHIP_ERROR TestOnlyExtractCommandPathFromNextInvokeRequest(TLV::TLVReader & invokeRequestsReader,
903 : ConcreteCommandPath & concretePath)
904 : {
905 0 : ReturnErrorOnFailure(invokeRequestsReader.Next(TLV::AnonymousTag()));
906 0 : CommandDataIB::Parser commandData;
907 0 : ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
908 0 : CommandPathIB::Parser commandPath;
909 0 : ReturnErrorOnFailure(commandData.GetPath(&commandPath));
910 0 : return commandPath.GetConcreteCommandPath(concretePath);
911 : }
912 :
913 0 : [[maybe_unused]] const char * GetFaultInjectionTypeStr(CommandHandlerImpl::NlFaultInjectionType faultType)
914 : {
915 0 : switch (faultType)
916 : {
917 0 : case CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessages:
918 : return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be the same as the "
919 0 : "original request.";
920 0 : case CommandHandlerImpl::NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder:
921 : return "Each response will be sent in a separate InvokeResponseMessage. The order of responses will be reversed from the "
922 0 : "original request.";
923 0 : case CommandHandlerImpl::NlFaultInjectionType::SkipSecondResponse:
924 0 : return "Single InvokeResponseMessages. Dropping response to second request";
925 : }
926 0 : ChipLogError(DataManagement, "TH Failure: Unexpected fault type");
927 0 : chipAbort();
928 : }
929 :
930 : } // anonymous namespace
931 :
932 : // This method intentionally duplicates code from other sections. While code consolidation
933 : // is generally preferred, here we prioritize generating a clear crash message to aid in
934 : // troubleshooting test failures.
935 0 : void CommandHandlerImpl::TestOnlyInvokeCommandRequestWithFaultsInjected(CommandHandlerExchangeInterface & commandResponder,
936 : System::PacketBufferHandle && payload, bool isTimedInvoke,
937 : NlFaultInjectionType faultType)
938 : {
939 0 : VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "TH Failure: state should be Idle, issue with TH");
940 0 : SetExchangeInterface(&commandResponder);
941 :
942 0 : ChipLogProgress(DataManagement, "Response to InvokeRequestMessage overridden by fault injection");
943 0 : ChipLogProgress(DataManagement, " Injecting the following response:%s", GetFaultInjectionTypeStr(faultType));
944 :
945 0 : Handle workHandle(this);
946 0 : VerifyOrDieWithMsg(!commandResponder.GetGroupId().HasValue(), DataManagement, "DUT Failure: Unexpected Group Command");
947 :
948 0 : System::PacketBufferTLVReader reader;
949 0 : InvokeRequestMessage::Parser invokeRequestMessage;
950 0 : InvokeRequests::Parser invokeRequests;
951 0 : reader.Init(std::move(payload));
952 0 : VerifyOrDieWithMsg(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, DataManagement,
953 : "TH Failure: Failed 'invokeRequestMessage.Init(reader)'");
954 : #if CHIP_CONFIG_IM_PRETTY_PRINT
955 0 : invokeRequestMessage.PrettyPrint();
956 : #endif
957 :
958 0 : VerifyOrDieWithMsg(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, DataManagement,
959 : "DUT Failure: Mandatory SuppressResponse field missing");
960 0 : VerifyOrDieWithMsg(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, DataManagement,
961 : "DUT Failure: Mandatory TimedRequest field missing");
962 0 : VerifyOrDieWithMsg(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, DataManagement,
963 : "DUT Failure: Mandatory InvokeRequests field missing");
964 0 : VerifyOrDieWithMsg(mTimedRequest == isTimedInvoke, DataManagement,
965 : "DUT Failure: TimedRequest value in message mismatches action");
966 :
967 : {
968 0 : InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
969 0 : VerifyOrDieWithMsg(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
970 : DataManagement, "DUT Failure: InvokeRequestMessage contents were invalid");
971 : }
972 :
973 0 : TLV::TLVReader invokeRequestsReader;
974 0 : invokeRequests.GetReader(&invokeRequestsReader);
975 :
976 0 : size_t commandCount = 0;
977 0 : VerifyOrDieWithMsg(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
978 : DataManagement,
979 : "TH Failure: Failed to get the length of InvokeRequests after InvokeRequestMessage validation");
980 :
981 : // The command count check (specifically for a count of 2) is tied to IDM_1_3. This may need adjustment for
982 : // compatibility with future test plans.
983 0 : VerifyOrDieWithMsg(commandCount == 2, DataManagement, "DUT failure: We were strictly expecting exactly 2 InvokeRequests");
984 0 : mReserveSpaceForMoreChunkMessages = true;
985 :
986 : {
987 : // Response path is the same as request path since we are replying with a failure message.
988 0 : ConcreteCommandPath concreteResponsePath1;
989 0 : ConcreteCommandPath concreteResponsePath2;
990 0 : VerifyOrDieWithMsg(
991 : TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath1) == CHIP_NO_ERROR,
992 : DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the first request");
993 0 : VerifyOrDieWithMsg(
994 : TestOnlyExtractCommandPathFromNextInvokeRequest(invokeRequestsReader, concreteResponsePath2) == CHIP_NO_ERROR,
995 : DataManagement, "DUT Failure: Issues encountered while extracting the ConcreteCommandPath from the second request");
996 :
997 0 : if (faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
998 : {
999 0 : ConcreteCommandPath temp(concreteResponsePath1);
1000 0 : concreteResponsePath1 = concreteResponsePath2;
1001 0 : concreteResponsePath2 = temp;
1002 : }
1003 :
1004 0 : VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath1, Status::Failure) == CHIP_NO_ERROR, DataManagement,
1005 : "TH Failure: Error adding the first InvokeResponse");
1006 0 : if (faultType == NlFaultInjectionType::SeparateResponseMessages ||
1007 : faultType == NlFaultInjectionType::SeparateResponseMessagesAndInvertedResponseOrder)
1008 : {
1009 0 : VerifyOrDieWithMsg(FinalizeInvokeResponseMessageAndPrepareNext() == CHIP_NO_ERROR, DataManagement,
1010 : "TH Failure: Failed to create second InvokeResponseMessage");
1011 : }
1012 0 : if (faultType != NlFaultInjectionType::SkipSecondResponse)
1013 : {
1014 0 : VerifyOrDieWithMsg(FallibleAddStatus(concreteResponsePath2, Status::Failure) == CHIP_NO_ERROR, DataManagement,
1015 : "TH Failure: Error adding the second InvokeResponse");
1016 : }
1017 : }
1018 :
1019 0 : VerifyOrDieWithMsg(invokeRequestsReader.Next() == CHIP_END_OF_TLV, DataManagement,
1020 : "DUT Failure: Unexpected TLV ending of InvokeRequests");
1021 0 : VerifyOrDieWithMsg(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, DataManagement,
1022 : "DUT Failure: InvokeRequestMessage TLV is not properly terminated");
1023 0 : }
1024 : #endif // CHIP_WITH_NLFAULTINJECTION
1025 :
1026 : } // namespace app
1027 : } // namespace chip
|