blob: d36a41e37f076cc0c628bfe7e3b1559896665445 [file] [log] [blame]
/*
*
* 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/ConcreteClusterPath.h>
#include <app/ConcreteCommandPath.h>
#include <app/data-model/Decode.h>
#include <app/data-model/List.h> // So we can encode lists
#include <functional>
#include <lib/core/DataModelTypes.h>
#include <lib/support/Iterators.h>
#include <type_traits>
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;
typedef Loop (*CommandIdCallback)(CommandId id, void * context);
/**
* Function that may be implemented to enumerate accepted (client-to-server)
* commands for the given cluster.
*
* If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of accepted
* commands will come from the endpoint metadata for the cluster.
*
* If this function returns any other error, that will be treated as an
* error condition by the caller, and handling will depend on the caller.
*
* Otherwise the list of accepted commands will be the list of values passed
* to the provided callback.
*
* The implementation _must_ pass the provided context to the callback.
*
* If the callback returns Loop::Break, there must be no more calls to it.
* This is used by callbacks that just look for a particular value in the
* list.
*/
virtual CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
/**
* Function that may be implemented to enumerate generated (response)
* commands for the given cluster.
*
* If this function returns CHIP_ERROR_NOT_IMPLEMENTED, the list of
* generated commands will come from the endpoint metadata for the cluster.
*
* If this function returns any other error, that will be treated as an
* error condition by the caller, and handling will depend on the caller.
*
* Otherwise the list of generated commands will be the list of values
* passed to the provided callback.
*
* The implementation _must_ pass the provided context to the callback.
*
* If the callback returns Loop::Break, there must be no more calls to it.
* This is used by callbacks that just look for a particular value in the
* list.
*/
virtual CHIP_ERROR EnumerateGeneratedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context)
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
/**
* 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