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 :
19 : /**
20 : * @file
21 : * This file defines object for a CHIP IM Invoke Command Handler
22 : *
23 : */
24 :
25 : #include "CommandHandler.h"
26 : #include "InteractionModelEngine.h"
27 : #include "RequiredPrivilege.h"
28 : #include "messaging/ExchangeContext.h"
29 :
30 : #include <access/AccessControl.h>
31 : #include <app-common/zap-generated/cluster-objects.h>
32 : #include <app/RequiredPrivilege.h>
33 : #include <app/util/MatterCallbacks.h>
34 : #include <credentials/GroupDataProvider.h>
35 : #include <lib/core/CHIPConfig.h>
36 : #include <lib/core/TLVData.h>
37 : #include <lib/core/TLVUtilities.h>
38 : #include <lib/support/TypeTraits.h>
39 : #include <platform/LockTracker.h>
40 : #include <protocols/secure_channel/Constants.h>
41 :
42 : namespace chip {
43 : namespace app {
44 : using Status = Protocols::InteractionModel::Status;
45 :
46 49 : CommandHandler::CommandHandler(Callback * apCallback) :
47 49 : mpCallback(apCallback), mResponseSenderDone(HandleOnResponseSenderDone, this), mSuppressResponse(false)
48 49 : {}
49 :
50 5 : CommandHandler::CommandHandler(TestOnlyMarker aTestMarker, Callback * apCallback, CommandPathRegistry * apCommandPathRegistry) :
51 5 : CommandHandler(apCallback)
52 : {
53 5 : mMaxPathsPerInvoke = apCommandPathRegistry->MaxSize();
54 5 : mCommandPathRegistry = apCommandPathRegistry;
55 5 : }
56 :
57 65 : CHIP_ERROR CommandHandler::AllocateBuffer()
58 : {
59 65 : if (!mBufferAllocated)
60 : {
61 46 : mCommandMessageWriter.Reset();
62 :
63 46 : System::PacketBufferHandle commandPacket = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes);
64 46 : VerifyOrReturnError(!commandPacket.IsNull(), CHIP_ERROR_NO_MEMORY);
65 :
66 46 : mCommandMessageWriter.Init(std::move(commandPacket));
67 46 : ReturnErrorOnFailure(mInvokeResponseBuilder.InitWithEndBufferReserved(&mCommandMessageWriter));
68 :
69 46 : if (mReserveSpaceForMoreChunkMessages)
70 : {
71 10 : ReturnErrorOnFailure(mInvokeResponseBuilder.ReserveSpaceForMoreChunkedMessages());
72 : }
73 :
74 : // Sending an InvokeResponse to an InvokeResponse is going to be removed from the spec soon.
75 : // It was never implemented in the SDK, and there are no command responses that expect a
76 : // command response. This means we will never receive an InvokeResponse Message in response
77 : // to an InvokeResponse Message that we are sending. This means that the only response
78 : // we are expecting to receive in response to an InvokeResponse Message that we are
79 : // sending-out is a status when we are chunking multiple responses. As a result, to satisfy the
80 : // condition that we don't set SuppressResponse to true while also setting
81 : // MoreChunkedMessages to true, we are hardcoding the value to false here.
82 46 : mInvokeResponseBuilder.SuppressResponse(/* aSuppressResponse = */ false);
83 46 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
84 :
85 46 : mInvokeResponseBuilder.CreateInvokeResponses(/* aReserveEndBuffer = */ true);
86 46 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
87 :
88 46 : mBufferAllocated = true;
89 46 : MoveToState(State::NewResponseMessage);
90 46 : }
91 :
92 65 : return CHIP_NO_ERROR;
93 : }
94 :
95 31 : void CommandHandler::OnInvokeCommandRequest(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
96 : System::PacketBufferHandle && payload, bool isTimedInvoke)
97 : {
98 31 : System::PacketBufferHandle response;
99 31 : Status status = Status::Failure;
100 31 : VerifyOrDieWithMsg(ec != nullptr, DataManagement, "Incoming exchange context should not be null");
101 31 : VerifyOrDieWithMsg(mState == State::Idle, DataManagement, "state should be Idle");
102 :
103 : // NOTE: we already know this is an InvokeCommand Request message because we explicitly registered with the
104 : // Exchange Manager for unsolicited InvokeCommand Requests.
105 31 : mResponseSender.SetExchangeContext(ec);
106 :
107 : // Use the RAII feature, if this is the only Handle when this function returns, DecrementHoldOff will trigger sending response.
108 : // TODO: This is broken! If something under here returns error, we will try
109 : // to StartSendingCommandResponses(), and then our caller will try to send a status
110 : // response too. Figure out at what point it's our responsibility to
111 : // handler errors vs our caller's.
112 31 : Handle workHandle(this);
113 :
114 : // TODO(#30453): It should be possible for SetExchangeContext to internally call WillSendMessage.
115 : // Unfortunately, doing so would require us to either:
116 : // * Make TestCommandInteraction a friend of CommandResponseSender to allow it to set the exchange
117 : // context without calling WillSendMessage, or
118 : // * Understand why unit tests fail when WillSendMessage is called during the execution of
119 : // SetExchangeContext.
120 31 : mResponseSender.WillSendMessage();
121 31 : status = ProcessInvokeRequest(std::move(payload), isTimedInvoke);
122 31 : if (status != Status::Success)
123 : {
124 3 : mResponseSender.SendStatusResponse(status);
125 3 : mSentStatusResponse = true;
126 : }
127 :
128 31 : mGoneAsync = true;
129 31 : }
130 :
131 36 : CHIP_ERROR CommandHandler::ValidateInvokeRequestMessageAndBuildRegistry(InvokeRequestMessage::Parser & invokeRequestMessage)
132 : {
133 36 : CHIP_ERROR err = CHIP_NO_ERROR;
134 36 : size_t commandCount = 0;
135 36 : bool commandRefExpected = false;
136 36 : InvokeRequests::Parser invokeRequests;
137 :
138 36 : ReturnErrorOnFailure(invokeRequestMessage.GetInvokeRequests(&invokeRequests));
139 : TLV::TLVReader invokeRequestsReader;
140 36 : invokeRequests.GetReader(&invokeRequestsReader);
141 :
142 36 : ReturnErrorOnFailure(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */));
143 :
144 : // If this is a GroupRequest the only thing to check is that there is only one
145 : // CommandDataIB.
146 36 : if (IsGroupRequest())
147 : {
148 0 : VerifyOrReturnError(commandCount == 1, CHIP_ERROR_INVALID_ARGUMENT);
149 0 : return CHIP_NO_ERROR;
150 : }
151 : // While technically any commandCount == 1 should already be unique and does not need
152 : // any further validation, we do need to read and populate the registry to help
153 : // in building the InvokeResponse.
154 :
155 36 : VerifyOrReturnError(commandCount <= MaxPathsPerInvoke(), CHIP_ERROR_INVALID_ARGUMENT);
156 :
157 : // If there is more than one CommandDataIB, spec states that CommandRef must be provided.
158 34 : commandRefExpected = commandCount > 1;
159 :
160 69 : while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
161 : {
162 37 : VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), CHIP_ERROR_INVALID_ARGUMENT);
163 36 : CommandDataIB::Parser commandData;
164 36 : ReturnErrorOnFailure(commandData.Init(invokeRequestsReader));
165 :
166 : // First validate that we can get a ConcreteCommandPath.
167 36 : CommandPathIB::Parser commandPath;
168 36 : ConcreteCommandPath concretePath(0, 0, 0);
169 36 : ReturnErrorOnFailure(commandData.GetPath(&commandPath));
170 36 : ReturnErrorOnFailure(commandPath.GetConcreteCommandPath(concretePath));
171 :
172 : // Grab the CommandRef if there is one, and validate that it's there when it
173 : // has to be.
174 35 : Optional<uint16_t> commandRef;
175 : uint16_t ref;
176 35 : err = commandData.GetRef(&ref);
177 35 : VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err);
178 35 : if (err == CHIP_END_OF_TLV && commandRefExpected)
179 : {
180 0 : return CHIP_ERROR_INVALID_ARGUMENT;
181 : }
182 35 : if (err == CHIP_NO_ERROR)
183 : {
184 4 : commandRef.SetValue(ref);
185 : }
186 :
187 : // Adding can fail if concretePath is not unique, or if commandRef is a value
188 : // and is not unique, or if we have already added more paths than we support.
189 35 : ReturnErrorOnFailure(GetCommandPathRegistry().Add(concretePath, commandRef));
190 35 : }
191 :
192 : // It's OK/expected to have reached the end of the container without failure.
193 33 : if (CHIP_END_OF_TLV == err)
194 : {
195 33 : err = CHIP_NO_ERROR;
196 : }
197 33 : ReturnErrorOnFailure(err);
198 33 : return invokeRequestMessage.ExitContainer();
199 : }
200 :
201 39 : Status CommandHandler::ProcessInvokeRequest(System::PacketBufferHandle && payload, bool isTimedInvoke)
202 : {
203 39 : CHIP_ERROR err = CHIP_NO_ERROR;
204 39 : System::PacketBufferTLVReader reader;
205 39 : InvokeRequestMessage::Parser invokeRequestMessage;
206 39 : InvokeRequests::Parser invokeRequests;
207 39 : reader.Init(std::move(payload));
208 39 : VerifyOrReturnError(invokeRequestMessage.Init(reader) == CHIP_NO_ERROR, Status::InvalidAction);
209 : #if CHIP_CONFIG_IM_PRETTY_PRINT
210 38 : invokeRequestMessage.PrettyPrint();
211 : #endif
212 38 : if (mResponseSender.IsForGroup())
213 : {
214 0 : SetGroupRequest(true);
215 : }
216 :
217 38 : VerifyOrReturnError(invokeRequestMessage.GetSuppressResponse(&mSuppressResponse) == CHIP_NO_ERROR, Status::InvalidAction);
218 38 : VerifyOrReturnError(invokeRequestMessage.GetTimedRequest(&mTimedRequest) == CHIP_NO_ERROR, Status::InvalidAction);
219 38 : VerifyOrReturnError(invokeRequestMessage.GetInvokeRequests(&invokeRequests) == CHIP_NO_ERROR, Status::InvalidAction);
220 38 : VerifyOrReturnError(mTimedRequest == isTimedInvoke, Status::TimedRequestMismatch);
221 :
222 : {
223 36 : InvokeRequestMessage::Parser validationInvokeRequestMessage = invokeRequestMessage;
224 36 : VerifyOrReturnError(ValidateInvokeRequestMessageAndBuildRegistry(validationInvokeRequestMessage) == CHIP_NO_ERROR,
225 : Status::InvalidAction);
226 : }
227 :
228 : TLV::TLVReader invokeRequestsReader;
229 32 : invokeRequests.GetReader(&invokeRequestsReader);
230 :
231 32 : size_t commandCount = 0;
232 32 : VerifyOrReturnError(TLV::Utilities::Count(invokeRequestsReader, commandCount, false /* recurse */) == CHIP_NO_ERROR,
233 : Status::InvalidAction);
234 32 : if (commandCount > 1)
235 : {
236 2 : mReserveSpaceForMoreChunkMessages = true;
237 : }
238 :
239 66 : while (CHIP_NO_ERROR == (err = invokeRequestsReader.Next()))
240 : {
241 34 : VerifyOrReturnError(TLV::AnonymousTag() == invokeRequestsReader.GetTag(), Status::InvalidAction);
242 34 : CommandDataIB::Parser commandData;
243 34 : VerifyOrReturnError(commandData.Init(invokeRequestsReader) == CHIP_NO_ERROR, Status::InvalidAction);
244 34 : Status status = Status::Success;
245 34 : if (IsGroupRequest())
246 : {
247 0 : status = ProcessGroupCommandDataIB(commandData);
248 : }
249 : else
250 : {
251 34 : status = ProcessCommandDataIB(commandData);
252 : }
253 34 : if (status != Status::Success)
254 : {
255 0 : return status;
256 : }
257 : }
258 :
259 : // if we have exhausted this container
260 32 : if (CHIP_END_OF_TLV == err)
261 : {
262 32 : err = CHIP_NO_ERROR;
263 : }
264 32 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
265 32 : VerifyOrReturnError(invokeRequestMessage.ExitContainer() == CHIP_NO_ERROR, Status::InvalidAction);
266 32 : return Status::Success;
267 39 : }
268 :
269 32 : void CommandHandler::Close()
270 : {
271 32 : mSuppressResponse = false;
272 32 : MoveToState(State::AwaitingDestruction);
273 :
274 : // We must finish all async work before we can shut down a CommandHandler. The actual CommandHandler MUST finish their work
275 : // in reasonable time or there is a bug. The only case for releasing CommandHandler without CommandHandler::Handle releasing its
276 : // reference is the stack shutting down, in which case Close() is not called. So the below check should always pass.
277 32 : VerifyOrDieWithMsg(mPendingWork == 0, DataManagement, "CommandHandler::Close() called with %u unfinished async work items",
278 : static_cast<unsigned int>(mPendingWork));
279 :
280 32 : if (mpCallback)
281 : {
282 32 : mpCallback->OnDone(*this);
283 : }
284 32 : }
285 :
286 0 : void CommandHandler::HandleOnResponseSenderDone(void * context)
287 : {
288 0 : CommandHandler * const _this = static_cast<CommandHandler *>(context);
289 0 : VerifyOrDie(_this != nullptr);
290 :
291 0 : _this->Close();
292 0 : }
293 :
294 34 : void CommandHandler::IncrementHoldOff()
295 : {
296 34 : mPendingWork++;
297 34 : }
298 :
299 34 : void CommandHandler::DecrementHoldOff()
300 : {
301 34 : mPendingWork--;
302 34 : ChipLogDetail(DataManagement, "Decreasing reference count for CommandHandler, remaining %u",
303 : static_cast<unsigned int>(mPendingWork));
304 34 : if (mPendingWork != 0)
305 : {
306 3 : return;
307 : }
308 :
309 31 : if (!mSentStatusResponse)
310 : {
311 28 : if (!mResponseSender.HasExchangeContext())
312 : {
313 0 : ChipLogProgress(DataManagement, "Skipping command response: exchange context is null");
314 : }
315 28 : else if (!IsGroupRequest())
316 : {
317 28 : CHIP_ERROR err = StartSendingCommandResponses();
318 28 : if (err != CHIP_NO_ERROR)
319 : {
320 2 : ChipLogError(DataManagement, "Failed to send command response: %" CHIP_ERROR_FORMAT, err.Format());
321 : // TODO(#30453): It should be our responsibility to send a Failure StatusResponse to the requestor
322 : // if there is a SessionHandle, but legacy unit tests explicitly check the behavior where
323 : // CommandHandler does not send any message. Changing this behavior should be done in a standalone
324 : // PR where only that specific change is made. Here is a possible solution that should
325 : // be done that fulfills our responsibility to send a Failure StatusResponse, but this causes unit
326 : // tests to start failing.
327 : // ```
328 : // if (mResponseSender.HasSessionHandle())
329 : // {
330 : // mResponseSender.SendStatusResponse(Status::Failure);
331 : // }
332 : // Close();
333 : // return;
334 : // ```
335 : }
336 : }
337 : }
338 :
339 31 : if (mResponseSender.AwaitingStatusResponse())
340 : {
341 : // If we are awaiting a status response, we want to call Close() only once the response sender is done.
342 : // Therefore, register to be notified when CommandResponseSender is done.
343 0 : mResponseSender.RegisterOnResponseSenderDoneCallback(&mResponseSenderDone);
344 0 : return;
345 : }
346 31 : Close();
347 : }
348 :
349 30 : CHIP_ERROR CommandHandler::StartSendingCommandResponses()
350 : {
351 30 : VerifyOrReturnError(mPendingWork == 0, CHIP_ERROR_INCORRECT_STATE);
352 30 : VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
353 28 : VerifyOrReturnError(mResponseSender.HasExchangeContext(), CHIP_ERROR_INCORRECT_STATE);
354 :
355 28 : ReturnErrorOnFailure(FinalizeLastInvokeResponseMessage());
356 28 : ReturnErrorOnFailure(mResponseSender.StartSendingCommandResponses());
357 27 : return CHIP_NO_ERROR;
358 : }
359 :
360 : namespace {
361 : // We use this when the sender did not actually provide a CommandFields struct,
362 : // to avoid downstream consumers having to worry about cases when there is or is
363 : // not a struct available. We use an empty struct with anonymous tag, since we
364 : // can't use a context tag at top level, and consumers should not care about the
365 : // tag here).
366 : constexpr uint8_t sNoFields[] = {
367 : CHIP_TLV_STRUCTURE(CHIP_TLV_TAG_ANONYMOUS),
368 : CHIP_TLV_END_OF_CONTAINER,
369 : };
370 : } // anonymous namespace
371 :
372 34 : Status CommandHandler::ProcessCommandDataIB(CommandDataIB::Parser & aCommandElement)
373 : {
374 34 : CHIP_ERROR err = CHIP_NO_ERROR;
375 34 : CommandPathIB::Parser commandPath;
376 34 : ConcreteCommandPath concretePath(0, 0, 0);
377 : TLV::TLVReader commandDataReader;
378 :
379 : // NOTE: errors may occur before the concrete command path is even fully decoded.
380 :
381 34 : err = aCommandElement.GetPath(&commandPath);
382 34 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
383 :
384 34 : err = commandPath.GetConcreteCommandPath(concretePath);
385 34 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
386 :
387 : {
388 34 : Status commandExists = mpCallback->CommandExists(concretePath);
389 34 : if (commandExists != Status::Success)
390 : {
391 7 : ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
392 : ChipLogValueMEI(concretePath.mCommandId), ChipLogValueMEI(concretePath.mClusterId),
393 : concretePath.mEndpointId);
394 7 : return FallibleAddStatus(concretePath, commandExists) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
395 : }
396 : }
397 :
398 27 : VerifyOrExit(mResponseSender.HasSessionHandle(), err = CHIP_ERROR_INCORRECT_STATE);
399 :
400 : {
401 27 : Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
402 27 : Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId };
403 27 : Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
404 27 : err = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
405 27 : if (err != CHIP_NO_ERROR)
406 : {
407 0 : if (err != CHIP_ERROR_ACCESS_DENIED)
408 : {
409 0 : return FallibleAddStatus(concretePath, Status::Failure) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
410 : }
411 : // TODO: when wildcard invokes are supported, handle them to discard rather than fail with status
412 0 : return FallibleAddStatus(concretePath, Status::UnsupportedAccess) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
413 : }
414 : }
415 :
416 27 : if (CommandNeedsTimedInvoke(concretePath.mClusterId, concretePath.mCommandId) && !IsTimedInvoke())
417 : {
418 : // TODO: when wildcard invokes are supported, discard a
419 : // wildcard-expanded path instead of returning a status.
420 0 : return FallibleAddStatus(concretePath, Status::NeedsTimedInteraction) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
421 : }
422 :
423 27 : if (CommandIsFabricScoped(concretePath.mClusterId, concretePath.mCommandId))
424 : {
425 : // SPEC: Else if the command in the path is fabric-scoped and there is no accessing fabric,
426 : // a CommandStatusIB SHALL be generated with the UNSUPPORTED_ACCESS Status Code.
427 :
428 : // Fabric-scoped commands are not allowed before a specific accessing fabric is available.
429 : // This is mostly just during a PASE session before AddNOC.
430 0 : if (GetAccessingFabricIndex() == kUndefinedFabricIndex)
431 : {
432 : // TODO: when wildcard invokes are supported, discard a
433 : // wildcard-expanded path instead of returning a status.
434 0 : return FallibleAddStatus(concretePath, Status::UnsupportedAccess) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
435 : }
436 : }
437 :
438 27 : err = aCommandElement.GetFields(&commandDataReader);
439 27 : if (CHIP_END_OF_TLV == err)
440 : {
441 2 : ChipLogDetail(DataManagement,
442 : "Received command without data for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
443 : concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
444 2 : commandDataReader.Init(sNoFields);
445 2 : err = commandDataReader.Next();
446 : }
447 27 : if (CHIP_NO_ERROR == err)
448 : {
449 27 : ChipLogDetail(DataManagement, "Received command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
450 : concretePath.mEndpointId, ChipLogValueMEI(concretePath.mClusterId), ChipLogValueMEI(concretePath.mCommandId));
451 27 : SuccessOrExit(err = MatterPreCommandReceivedCallback(concretePath, GetSubjectDescriptor()));
452 27 : mpCallback->DispatchCommand(*this, concretePath, commandDataReader);
453 27 : MatterPostCommandReceivedCallback(concretePath, GetSubjectDescriptor());
454 : }
455 :
456 0 : exit:
457 27 : if (err != CHIP_NO_ERROR)
458 : {
459 0 : return FallibleAddStatus(concretePath, Status::InvalidCommand) != CHIP_NO_ERROR ? Status::Failure : Status::Success;
460 : }
461 :
462 : // We have handled the error status above and put the error status in response, now return success status so we can process
463 : // other commands in the invoke request.
464 27 : return Status::Success;
465 : }
466 :
467 0 : Status CommandHandler::ProcessGroupCommandDataIB(CommandDataIB::Parser & aCommandElement)
468 : {
469 0 : CHIP_ERROR err = CHIP_NO_ERROR;
470 0 : CommandPathIB::Parser commandPath;
471 : TLV::TLVReader commandDataReader;
472 : ClusterId clusterId;
473 : CommandId commandId;
474 : GroupId groupId;
475 : FabricIndex fabric;
476 :
477 0 : Credentials::GroupDataProvider::GroupEndpoint mapping;
478 0 : Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider();
479 : Credentials::GroupDataProvider::EndpointIterator * iterator;
480 :
481 0 : err = aCommandElement.GetPath(&commandPath);
482 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
483 :
484 0 : err = commandPath.GetGroupCommandPath(&clusterId, &commandId);
485 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
486 :
487 0 : groupId = mResponseSender.GetGroupId();
488 0 : fabric = GetAccessingFabricIndex();
489 :
490 0 : ChipLogDetail(DataManagement, "Received group command for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
491 : groupId, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
492 :
493 0 : err = aCommandElement.GetFields(&commandDataReader);
494 0 : if (CHIP_END_OF_TLV == err)
495 : {
496 0 : ChipLogDetail(DataManagement,
497 : "Received command without data for Group=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI, groupId,
498 : ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
499 0 : commandDataReader.Init(sNoFields);
500 0 : err = commandDataReader.Next();
501 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction);
502 : }
503 0 : VerifyOrReturnError(err == CHIP_NO_ERROR, Status::Failure);
504 :
505 : // Per spec, we do the "is this a timed command?" check for every path, but
506 : // since all paths that fail it just get silently discarded we can do it
507 : // once up front and discard all the paths at once. Ordering with respect
508 : // to ACL and command presence checks does not matter, because the behavior
509 : // is the same for all of them: ignore the path.
510 0 : if (CommandNeedsTimedInvoke(clusterId, commandId))
511 : {
512 : // Group commands are never timed.
513 0 : return Status::Success;
514 : }
515 :
516 : // No check for `CommandIsFabricScoped` unlike in `ProcessCommandDataIB()` since group commands
517 : // always have an accessing fabric, by definition.
518 :
519 : // Find which endpoints can process the command, and dispatch to them.
520 0 : iterator = groupDataProvider->IterateEndpoints(fabric);
521 0 : VerifyOrReturnError(iterator != nullptr, Status::Failure);
522 :
523 0 : while (iterator->Next(mapping))
524 : {
525 0 : if (groupId != mapping.group_id)
526 : {
527 0 : continue;
528 : }
529 :
530 0 : ChipLogDetail(DataManagement,
531 : "Processing group command for Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
532 : mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId));
533 :
534 0 : const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId);
535 :
536 0 : if (mpCallback->CommandExists(concretePath) != Status::Success)
537 : {
538 0 : ChipLogDetail(DataManagement, "No command " ChipLogFormatMEI " in Cluster " ChipLogFormatMEI " on Endpoint 0x%x",
539 : ChipLogValueMEI(commandId), ChipLogValueMEI(clusterId), mapping.endpoint_id);
540 :
541 0 : continue;
542 : }
543 :
544 : {
545 0 : Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor();
546 0 : Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId };
547 0 : Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath);
548 0 : err = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege);
549 0 : if (err != CHIP_NO_ERROR)
550 : {
551 : // NOTE: an expected error is CHIP_ERROR_ACCESS_DENIED, but there could be other unexpected errors;
552 : // therefore, keep processing subsequent commands, and if any errors continue, those subsequent
553 : // commands will likewise fail.
554 0 : continue;
555 : }
556 : }
557 0 : if ((err = MatterPreCommandReceivedCallback(concretePath, GetSubjectDescriptor())) == CHIP_NO_ERROR)
558 : {
559 0 : TLV::TLVReader dataReader(commandDataReader);
560 0 : mpCallback->DispatchCommand(*this, concretePath, dataReader);
561 0 : MatterPostCommandReceivedCallback(concretePath, GetSubjectDescriptor());
562 : }
563 : else
564 : {
565 0 : ChipLogError(DataManagement,
566 : "Error when calling MatterPreCommandReceivedCallback for Endpoint=%u Cluster=" ChipLogFormatMEI
567 : " Command=" ChipLogFormatMEI " : %" CHIP_ERROR_FORMAT,
568 : mapping.endpoint_id, ChipLogValueMEI(clusterId), ChipLogValueMEI(commandId), err.Format());
569 0 : continue;
570 : }
571 : }
572 0 : iterator->Release();
573 0 : return Status::Success;
574 : }
575 :
576 38 : CHIP_ERROR CommandHandler::TryAddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
577 : {
578 38 : ReturnErrorOnFailure(PrepareStatus(aCommandPath));
579 37 : CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
580 37 : StatusIB::Builder & statusIBBuilder = commandStatus.CreateErrorStatus();
581 37 : ReturnErrorOnFailure(commandStatus.GetError());
582 37 : statusIBBuilder.EncodeStatusIB(aStatus);
583 37 : ReturnErrorOnFailure(statusIBBuilder.GetError());
584 37 : return FinishStatus();
585 : }
586 :
587 37 : CHIP_ERROR CommandHandler::AddStatusInternal(const ConcreteCommandPath & aCommandPath, const StatusIB & aStatus)
588 : {
589 75 : return TryAddingResponse([&]() -> CHIP_ERROR { return TryAddStatusInternal(aCommandPath, aStatus); });
590 : }
591 :
592 21 : void CommandHandler::AddStatus(const ConcreteCommandPath & aCommandPath, const Protocols::InteractionModel::Status aStatus,
593 : const char * context)
594 : {
595 : // Return early in case of requests targeted to a group, since they should not add a response.
596 21 : VerifyOrReturn(!IsGroupRequest());
597 21 : VerifyOrDie(FallibleAddStatus(aCommandPath, aStatus, context) == CHIP_NO_ERROR);
598 : }
599 :
600 35 : CHIP_ERROR CommandHandler::FallibleAddStatus(const ConcreteCommandPath & path, const Protocols::InteractionModel::Status status,
601 : const char * context)
602 : {
603 :
604 35 : if (status != Status::Success)
605 : {
606 15 : if (context == nullptr)
607 : {
608 15 : context = "no additional context";
609 : }
610 :
611 15 : ChipLogError(DataManagement,
612 : "Endpoint=%u Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI " status " ChipLogFormatIMStatus " (%s)",
613 : path.mEndpointId, ChipLogValueMEI(path.mClusterId), ChipLogValueMEI(path.mCommandId),
614 : ChipLogValueIMStatus(status), context);
615 : }
616 :
617 35 : return AddStatusInternal(path, StatusIB(status));
618 : }
619 :
620 1 : CHIP_ERROR CommandHandler::AddClusterSpecificSuccess(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus)
621 : {
622 1 : return AddStatusInternal(aCommandPath, StatusIB(Status::Success, aClusterStatus));
623 : }
624 :
625 1 : CHIP_ERROR CommandHandler::AddClusterSpecificFailure(const ConcreteCommandPath & aCommandPath, ClusterStatus aClusterStatus)
626 : {
627 1 : return AddStatusInternal(aCommandPath, StatusIB(Status::Failure, aClusterStatus));
628 : }
629 :
630 19 : CHIP_ERROR CommandHandler::PrepareInvokeResponseCommand(const ConcreteCommandPath & aResponseCommandPath,
631 : const CommandHandler::InvokeResponseParameters & aPrepareParameters)
632 : {
633 19 : auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aPrepareParameters.mRequestCommandPath);
634 19 : VerifyOrReturnValue(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
635 :
636 19 : return PrepareInvokeResponseCommand(commandPathRegistryEntry.Value(), aResponseCommandPath,
637 38 : aPrepareParameters.mStartOrEndDataStruct);
638 19 : }
639 :
640 0 : CHIP_ERROR CommandHandler::PrepareCommand(const ConcreteCommandPath & aResponseCommandPath, bool aStartDataStruct)
641 : {
642 : // Legacy code is calling the deprecated version of PrepareCommand. If we are in a case where
643 : // there was a single command in the request, we can just assume this response is triggered by
644 : // the single command.
645 0 : size_t countOfPathRegistryEntries = GetCommandPathRegistry().Count();
646 :
647 : // At this point application supports Batch Invoke Commands since CommandPathRegistry has more than 1 entry,
648 : // but application is calling the deprecated PrepareCommand. We have no way to determine the associated CommandRef
649 : // to put into the InvokeResponse.
650 0 : VerifyOrDieWithMsg(countOfPathRegistryEntries == 1, DataManagement,
651 : "Seemingly device supports batch commands, but is calling the deprecated PrepareCommand API");
652 :
653 0 : auto commandPathRegistryEntry = GetCommandPathRegistry().GetFirstEntry();
654 0 : VerifyOrReturnValue(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
655 :
656 0 : return PrepareInvokeResponseCommand(commandPathRegistryEntry.Value(), aResponseCommandPath, aStartDataStruct);
657 0 : }
658 :
659 19 : CHIP_ERROR CommandHandler::PrepareInvokeResponseCommand(const CommandPathRegistryEntry & apCommandPathRegistryEntry,
660 : const ConcreteCommandPath & aCommandPath, bool aStartDataStruct)
661 : {
662 19 : ReturnErrorOnFailure(AllocateBuffer());
663 :
664 19 : if (!mInternalCallToAddResponseData && mState == State::AddedCommand)
665 : {
666 : // An attempt is being made to add CommandData InvokeResponse using primitive
667 : // CommandHandler APIs. While not recommended, as this potentially leaves the
668 : // CommandHandler in an incorrect state upon failure, this approach is permitted
669 : // for legacy reasons. To maximize the likelihood of success, particularly when
670 : // handling large amounts of data, we try to obtain a new, completely empty
671 : // InvokeResponseMessage, as the existing one already has space occupied.
672 3 : ReturnErrorOnFailure(FinalizeInvokeResponseMessageAndPrepareNext());
673 : }
674 :
675 19 : CreateBackupForResponseRollback();
676 : //
677 : // We must not be in the middle of preparing a command, or having prepared or sent one.
678 : //
679 19 : VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
680 :
681 : // TODO(#30453): See if we can pass this back up the stack so caller can provide this instead of taking up
682 : // space in CommandHanlder.
683 19 : mRefForResponse = apCommandPathRegistryEntry.ref;
684 :
685 19 : MoveToState(State::Preparing);
686 19 : InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
687 19 : InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
688 19 : ReturnErrorOnFailure(invokeResponses.GetError());
689 :
690 18 : CommandDataIB::Builder & commandData = invokeResponse.CreateCommand();
691 18 : ReturnErrorOnFailure(commandData.GetError());
692 18 : CommandPathIB::Builder & path = commandData.CreatePath();
693 18 : ReturnErrorOnFailure(commandData.GetError());
694 18 : ReturnErrorOnFailure(path.Encode(aCommandPath));
695 18 : if (aStartDataStruct)
696 : {
697 7 : ReturnErrorOnFailure(commandData.GetWriter()->StartContainer(TLV::ContextTag(CommandDataIB::Tag::kFields),
698 : TLV::kTLVType_Structure, mDataElementContainerType));
699 : }
700 18 : MoveToState(State::AddingCommand);
701 18 : return CHIP_NO_ERROR;
702 : }
703 :
704 15 : CHIP_ERROR CommandHandler::FinishCommand(bool aStartDataStruct)
705 : {
706 15 : VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
707 15 : CommandDataIB::Builder & commandData = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand();
708 15 : if (aStartDataStruct)
709 : {
710 6 : ReturnErrorOnFailure(commandData.GetWriter()->EndContainer(mDataElementContainerType));
711 : }
712 :
713 15 : if (mRefForResponse.HasValue())
714 : {
715 7 : ReturnErrorOnFailure(commandData.Ref(mRefForResponse.Value()));
716 : }
717 :
718 15 : ReturnErrorOnFailure(commandData.EndOfCommandDataIB());
719 15 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
720 15 : MoveToState(State::AddedCommand);
721 15 : return CHIP_NO_ERROR;
722 : }
723 :
724 38 : CHIP_ERROR CommandHandler::PrepareStatus(const ConcreteCommandPath & aCommandPath)
725 : {
726 38 : ReturnErrorOnFailure(AllocateBuffer());
727 : //
728 : // We must not be in the middle of preparing a command, or having prepared or sent one.
729 : //
730 38 : VerifyOrReturnError(mState == State::NewResponseMessage || mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
731 38 : if (mState == State::AddedCommand)
732 : {
733 8 : CreateBackupForResponseRollback();
734 : }
735 :
736 38 : auto commandPathRegistryEntry = GetCommandPathRegistry().Find(aCommandPath);
737 38 : VerifyOrReturnError(commandPathRegistryEntry.HasValue(), CHIP_ERROR_INCORRECT_STATE);
738 38 : mRefForResponse = commandPathRegistryEntry.Value().ref;
739 :
740 38 : MoveToState(State::Preparing);
741 38 : InvokeResponseIBs::Builder & invokeResponses = mInvokeResponseBuilder.GetInvokeResponses();
742 38 : InvokeResponseIB::Builder & invokeResponse = invokeResponses.CreateInvokeResponse();
743 38 : ReturnErrorOnFailure(invokeResponses.GetError());
744 37 : CommandStatusIB::Builder & commandStatus = invokeResponse.CreateStatus();
745 37 : ReturnErrorOnFailure(commandStatus.GetError());
746 37 : CommandPathIB::Builder & path = commandStatus.CreatePath();
747 37 : ReturnErrorOnFailure(commandStatus.GetError());
748 37 : ReturnErrorOnFailure(path.Encode(aCommandPath));
749 37 : MoveToState(State::AddingCommand);
750 37 : return CHIP_NO_ERROR;
751 38 : }
752 :
753 37 : CHIP_ERROR CommandHandler::FinishStatus()
754 : {
755 37 : VerifyOrReturnError(mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
756 :
757 37 : CommandStatusIB::Builder & commandStatus = mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus();
758 37 : if (mRefForResponse.HasValue())
759 : {
760 3 : ReturnErrorOnFailure(commandStatus.Ref(mRefForResponse.Value()));
761 : }
762 :
763 37 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetStatus().EndOfCommandStatusIB());
764 37 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().EndOfInvokeResponseIB());
765 37 : MoveToState(State::AddedCommand);
766 37 : return CHIP_NO_ERROR;
767 : }
768 :
769 27 : void CommandHandler::CreateBackupForResponseRollback()
770 : {
771 27 : VerifyOrReturn(mState == State::NewResponseMessage || mState == State::AddedCommand);
772 27 : VerifyOrReturn(mInvokeResponseBuilder.GetInvokeResponses().GetError() == CHIP_NO_ERROR);
773 27 : VerifyOrReturn(mInvokeResponseBuilder.GetError() == CHIP_NO_ERROR);
774 27 : mInvokeResponseBuilder.Checkpoint(mBackupWriter);
775 27 : mBackupState = mState;
776 27 : mRollbackBackupValid = true;
777 : }
778 :
779 4 : CHIP_ERROR CommandHandler::RollbackResponse()
780 : {
781 4 : VerifyOrReturnError(mRollbackBackupValid, CHIP_ERROR_INCORRECT_STATE);
782 4 : VerifyOrReturnError(mState == State::Preparing || mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
783 : // TODO(#30453): Rollback of mInvokeResponseBuilder should handle resetting
784 : // InvokeResponses.
785 4 : mInvokeResponseBuilder.GetInvokeResponses().ResetError();
786 4 : mInvokeResponseBuilder.Rollback(mBackupWriter);
787 4 : MoveToState(mBackupState);
788 4 : mRollbackBackupValid = false;
789 4 : return CHIP_NO_ERROR;
790 : }
791 :
792 16 : TLV::TLVWriter * CommandHandler::GetCommandDataIBTLVWriter()
793 : {
794 16 : if (mState != State::AddingCommand)
795 : {
796 0 : return nullptr;
797 : }
798 :
799 16 : return mInvokeResponseBuilder.GetInvokeResponses().GetInvokeResponse().GetCommand().GetWriter();
800 : }
801 :
802 0 : FabricIndex CommandHandler::GetAccessingFabricIndex() const
803 : {
804 0 : VerifyOrDie(!mGoneAsync);
805 0 : return mResponseSender.GetAccessingFabricIndex();
806 : }
807 :
808 4 : CommandHandler * CommandHandler::Handle::Get()
809 : {
810 : // Not safe to work with CommandHandler in parallel with other Matter work.
811 4 : assertChipStackLockedByCurrentThread();
812 :
813 4 : return (mMagic == InteractionModelEngine::GetInstance()->GetMagicNumber()) ? mpHandler : nullptr;
814 : }
815 :
816 60 : void CommandHandler::Handle::Release()
817 : {
818 60 : if (mpHandler != nullptr)
819 : {
820 34 : if (mMagic == InteractionModelEngine::GetInstance()->GetMagicNumber())
821 : {
822 34 : mpHandler->DecrementHoldOff();
823 : }
824 34 : mpHandler = nullptr;
825 34 : mMagic = 0;
826 : }
827 60 : }
828 :
829 34 : CommandHandler::Handle::Handle(CommandHandler * handle)
830 : {
831 34 : if (handle != nullptr)
832 : {
833 34 : handle->IncrementHoldOff();
834 34 : mpHandler = handle;
835 34 : mMagic = InteractionModelEngine::GetInstance()->GetMagicNumber();
836 : }
837 34 : }
838 :
839 5 : CHIP_ERROR CommandHandler::FinalizeInvokeResponseMessageAndPrepareNext()
840 : {
841 5 : ReturnErrorOnFailure(FinalizeInvokeResponseMessage(/* aHasMoreChunks = */ true));
842 : // After successfully finalizing InvokeResponseMessage, no buffer should remain
843 : // allocated.
844 5 : VerifyOrDie(!mBufferAllocated);
845 5 : CHIP_ERROR err = AllocateBuffer();
846 5 : if (err != CHIP_NO_ERROR)
847 : {
848 : // TODO(#30453): Improve ResponseDropped calls to occur only when dropping is
849 : // definitively guaranteed.
850 : // Response dropping is not yet definitive as a subsequent call
851 : // to AllocateBuffer might succeed.
852 0 : mResponseSender.ResponseDropped();
853 : }
854 5 : return err;
855 : }
856 :
857 38 : CHIP_ERROR CommandHandler::FinalizeInvokeResponseMessage(bool aHasMoreChunks)
858 : {
859 38 : System::PacketBufferHandle packet;
860 :
861 38 : VerifyOrReturnError(mState == State::AddedCommand, CHIP_ERROR_INCORRECT_STATE);
862 38 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetInvokeResponses().EndOfInvokeResponses());
863 38 : if (aHasMoreChunks)
864 : {
865 : // Unreserving space previously reserved for MoreChunkedMessages is done
866 : // in the call to mInvokeResponseBuilder.MoreChunkedMessages.
867 5 : mInvokeResponseBuilder.MoreChunkedMessages(aHasMoreChunks);
868 5 : ReturnErrorOnFailure(mInvokeResponseBuilder.GetError());
869 : }
870 38 : ReturnErrorOnFailure(mInvokeResponseBuilder.EndOfInvokeResponseMessage());
871 38 : ReturnErrorOnFailure(mCommandMessageWriter.Finalize(&packet));
872 38 : mResponseSender.AddInvokeResponseToSend(std::move(packet));
873 38 : mBufferAllocated = false;
874 38 : mRollbackBackupValid = false;
875 38 : return CHIP_NO_ERROR;
876 38 : }
877 :
878 246 : const char * CommandHandler::GetStateStr() const
879 : {
880 : #if CHIP_DETAIL_LOGGING
881 246 : switch (mState)
882 : {
883 0 : case State::Idle:
884 0 : return "Idle";
885 :
886 48 : case State::NewResponseMessage:
887 48 : return "NewResponseMessage";
888 :
889 57 : case State::Preparing:
890 57 : return "Preparing";
891 :
892 55 : case State::AddingCommand:
893 55 : return "AddingCommand";
894 :
895 54 : case State::AddedCommand:
896 54 : return "AddedCommand";
897 :
898 0 : case State::DispatchResponses:
899 0 : return "DispatchResponses";
900 :
901 32 : case State::AwaitingDestruction:
902 32 : return "AwaitingDestruction";
903 : }
904 : #endif // CHIP_DETAIL_LOGGING
905 0 : return "N/A";
906 : }
907 :
908 246 : void CommandHandler::MoveToState(const State aTargetState)
909 : {
910 246 : mState = aTargetState;
911 246 : ChipLogDetail(DataManagement, "Command handler moving to [%10.10s]", GetStateStr());
912 246 : }
913 :
914 : } // namespace app
915 : } // namespace chip
916 :
917 27 : CHIP_ERROR __attribute__((weak)) MatterPreCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath,
918 : const chip::Access::SubjectDescriptor & subjectDescriptor)
919 : {
920 27 : return CHIP_NO_ERROR;
921 : }
922 27 : void __attribute__((weak)) MatterPostCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath,
923 : const chip::Access::SubjectDescriptor & subjectDescriptor)
924 27 : {}
|