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