-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dynamic Command Dispatch (Server-side) (#11339)
- Loading branch information
1 parent
981bde4
commit 5276258
Showing
14 changed files
with
652 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
/* | ||
* | ||
* Copyright (c) 2021 Project CHIP Authors | ||
* All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <app/CommandHandler.h> | ||
#include <app/ConcreteCommandPath.h> | ||
#include <app/data-model/Decode.h> | ||
#include <app/data-model/List.h> // So we can encode lists | ||
|
||
namespace chip { | ||
namespace app { | ||
|
||
/* | ||
* This interface permits applications to register a server-side command handler | ||
* at run-time for a given cluster. The handler can either be configured to handle all endpoints | ||
* for the given cluster or only handle a specific endpoint. | ||
* | ||
* If a command is not handled through this interface, it will default to invoking the generated DispatchSingleClusterCommand | ||
* instead. | ||
* | ||
*/ | ||
class CommandHandlerInterface | ||
{ | ||
public: | ||
struct HandlerContext | ||
{ | ||
public: | ||
HandlerContext(CommandHandler & commandHandler, const ConcreteCommandPath & requestPath, TLV::TLVReader & aReader) : | ||
mCommandHandler(commandHandler), mRequestPath(requestPath), mPayload(aReader) | ||
{} | ||
|
||
void SetCommandHandled() { mCommandHandled = true; } | ||
void SetCommandNotHandled() { mCommandHandled = false; } | ||
|
||
/* | ||
* Returns a TLVReader positioned at the TLV struct that contains the payload of the command. | ||
* | ||
* If the reader is requested from the context, then we can assume there is an intention | ||
* to access the payload of this command and consequently, to handle this command. | ||
* | ||
* If this is not true, the application should call SetCommandNotHandled(). | ||
* | ||
*/ | ||
TLV::TLVReader & GetReader() | ||
{ | ||
SetCommandHandled(); | ||
return mPayload; | ||
} | ||
|
||
CommandHandler & mCommandHandler; | ||
const ConcreteCommandPath & mRequestPath; | ||
TLV::TLVReader & mPayload; | ||
bool mCommandHandled = false; | ||
}; | ||
|
||
/** | ||
* aEndpointId can be Missing to indicate that this object is meant to be | ||
* used with all endpoints. | ||
*/ | ||
CommandHandlerInterface(Optional<EndpointId> aEndpointId, ClusterId aClusterId) : | ||
mEndpointId(aEndpointId), mClusterId(aClusterId) | ||
{} | ||
|
||
virtual ~CommandHandlerInterface() {} | ||
|
||
/** | ||
* Callback that must be implemented to handle an invoke request. | ||
* | ||
* The callee is required to handle *all* errors that may occur during the handling of this command, | ||
* including errors like those encountered during decode and encode of the payloads as | ||
* well as application-specific errors. As part of handling the error, the callee is required | ||
* to handle generation of an appropriate status response. | ||
* | ||
* The only exception to this rule is if the HandleCommand helper method is used below - it will | ||
* help handle some of these cases (see below). | ||
* | ||
* @param [in] handlerContext Context that encapsulates the current invoke request. | ||
* Handlers are responsible for correctly calling SetCommandHandled() | ||
* on the context if they did handle the command. | ||
* | ||
* This is not necessary if the HandleCommand() method below is invoked. | ||
*/ | ||
virtual void InvokeCommand(HandlerContext & handlerContext) = 0; | ||
|
||
/** | ||
* Mechanism for keeping track of a chain of CommandHandlerInterface. | ||
*/ | ||
void SetNext(CommandHandlerInterface * aNext) { mNext = aNext; } | ||
CommandHandlerInterface * GetNext() const { return mNext; } | ||
|
||
/** | ||
* Check whether a this CommandHandlerInterface is relevant for a | ||
* particular endpoint+cluster. An CommandHandlerInterface will be used | ||
* for an invoke from a particular cluster only when this function returns | ||
* true. | ||
*/ | ||
bool Matches(EndpointId aEndpointId, ClusterId aClusterId) const | ||
{ | ||
return (!mEndpointId.HasValue() || mEndpointId.Value() == aEndpointId) && mClusterId == aClusterId; | ||
} | ||
|
||
/** | ||
* Check whether an CommandHandlerInterface is relevant for a particular | ||
* specific endpoint. This is used to clean up overrides registered for an | ||
* endpoint that becomes disabled. | ||
*/ | ||
bool MatchesEndpoint(EndpointId aEndpointId) const { return mEndpointId.HasValue() && mEndpointId.Value() == aEndpointId; } | ||
|
||
/** | ||
* Check whether another CommandHandlerInterface wants to handle the same set of | ||
* commands as we do. | ||
*/ | ||
bool Matches(const CommandHandlerInterface & aOther) const | ||
{ | ||
return mClusterId == aOther.mClusterId && | ||
(!mEndpointId.HasValue() || !aOther.mEndpointId.HasValue() || mEndpointId.Value() == aOther.mEndpointId.Value()); | ||
} | ||
|
||
protected: | ||
/* | ||
* Helper function to automatically de-serialize the data payload into a cluster object | ||
* of type RequestT if the Cluster ID and Command ID in the context match. Upon successful | ||
* de-serialization, the provided function is invoked and passed in a reference to the cluster object. | ||
* | ||
* Any errors encountered in this function prior to calling func result in the automatic generation of a status response. | ||
* If `func` is called, the responsibility for doing so shifts to the callee to handle any further errors that are encountered. | ||
* | ||
* The provided function is expected to have the following signature: | ||
* void Func(HandlerContext &handlerContext, const RequestT &requestPayload); | ||
*/ | ||
template <typename RequestT, typename FuncT> | ||
void HandleCommand(HandlerContext & handlerContext, FuncT func) | ||
{ | ||
if (!handlerContext.mCommandHandled && (handlerContext.mRequestPath.mClusterId == RequestT::GetClusterId()) && | ||
(handlerContext.mRequestPath.mCommandId == RequestT::GetCommandId())) | ||
{ | ||
RequestT requestPayload; | ||
|
||
// | ||
// If the command matches what the caller is looking for, let's mark this as being handled | ||
// even if errors happen after this. This ensures that we don't execute any fall-back strategies | ||
// to handle this command since at this point, the caller is taking responsibility for handling | ||
// the command in its entirety, warts and all. | ||
// | ||
handlerContext.SetCommandHandled(); | ||
|
||
if (DataModel::Decode(handlerContext.mPayload, requestPayload) != CHIP_NO_ERROR) | ||
{ | ||
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, | ||
Protocols::InteractionModel::Status::InvalidCommand); | ||
return; | ||
} | ||
|
||
func(handlerContext, requestPayload); | ||
} | ||
} | ||
|
||
private: | ||
Optional<EndpointId> mEndpointId; | ||
ClusterId mClusterId; | ||
CommandHandlerInterface * mNext = nullptr; | ||
}; | ||
|
||
} // namespace app | ||
} // namespace chip |
Oops, something went wrong.