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