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