Skip to content

Commit

Permalink
Add a way for CommandHandlerInterface to provide a list of accepted/g…
Browse files Browse the repository at this point in the history
…enerated command ids. (#16947)

* Add a way for CommandHandlerInterface to provide a list of accepted/generated command ids.

* Address review comments

* Fix shadowing warnings
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Jan 10, 2024
1 parent 204f039 commit 1107736
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 26 deletions.
53 changes: 53 additions & 0 deletions src/app/CommandHandlerInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
#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 <lib/core/DataModelTypes.h>
#include <lib/support/Iterators.h>

namespace chip {
namespace app {
Expand Down Expand Up @@ -98,6 +101,56 @@ class CommandHandlerInterface
*/
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.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/app/util/attribute-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ uint16_t emberAfGetDynamicIndexFromEndpoint(EndpointId id)
return kEmberInvalidEndpointIndex;
}

EmberAfStatus emberAfSetDynamicEndpoint(uint16_t index, EndpointId id, EmberAfEndpointType * ep, uint16_t deviceId,
EmberAfStatus emberAfSetDynamicEndpoint(uint16_t index, EndpointId id, const EmberAfEndpointType * ep, uint16_t deviceId,
uint8_t deviceVersion, const Span<DataVersion> & dataVersionStorage)
{
auto realIndex = index + FIXED_ENDPOINT_COUNT;
Expand Down
2 changes: 1 addition & 1 deletion src/app/util/attribute-storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ uint16_t emberAfGetDeviceIdForEndpoint(chip::EndpointId endpoint);
//
// The memory backing dataVersionStorage needs to stay alive until this dynamic
// endpoint is cleared.
EmberAfStatus emberAfSetDynamicEndpoint(uint16_t index, chip::EndpointId id, EmberAfEndpointType * ep, uint16_t deviceId,
EmberAfStatus emberAfSetDynamicEndpoint(uint16_t index, chip::EndpointId id, const EmberAfEndpointType * ep, uint16_t deviceId,
uint8_t deviceVersion, const chip::Span<chip::DataVersion> & dataVersionStorage);
chip::EndpointId emberAfClearDynamicEndpoint(uint16_t index);
uint16_t emberAfGetDynamicIndexFromEndpoint(chip::EndpointId id);
Expand Down
114 changes: 99 additions & 15 deletions src/app/util/ember-compatibility-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

#include <access/AccessControl.h>
#include <app/CommandHandlerInterface.h>
#include <app/ConcreteAttributePath.h>
#include <app/GlobalAttributes.h>
#include <app/InteractionModelEngine.h>
Expand Down Expand Up @@ -237,7 +238,52 @@ Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCom
return Status::UnsupportedCluster;
}

for (const CommandId * cmd = cluster->acceptedCommandList; cmd != nullptr; cmd++)
auto * commandHandler =
InteractionModelEngine::GetInstance()->FindCommandHandler(aCommandPath.mEndpointId, aCommandPath.mClusterId);
if (commandHandler)
{
struct Context
{
bool commandExists;
CommandId targetCommand;
} context{ false, aCommandPath.mCommandId };

CHIP_ERROR err = commandHandler->EnumerateAcceptedCommands(
aCommandPath,
[](CommandId command, void * closure) -> Loop {
auto * ctx = static_cast<Context *>(closure);
if (ctx->targetCommand == command)
{
ctx->commandExists = true;
return Loop::Break;
}
return Loop::Continue;
},
&context);

// We now have three cases:
// 1) handler returned CHIP_ERROR_NOT_IMPLEMENTED. In that case we
// should fall back to looking at cluster->acceptedCommandList
// 2) handler returned success. In that case, the handler is the source
// of truth about the set of accepted commands, and
// context.commandExists indicates whether a aCommandPath.mCommandId
// was in the set, and we should return either Success or
// UnsupportedCommand accordingly.
// 3) Some other status was returned. In this case we should probably
// err on the side of not allowing the command, since we have no idea
// whether to allow it or not.
if (err != CHIP_ERROR_NOT_IMPLEMENTED)
{
if (err == CHIP_NO_ERROR)
{
return context.commandExists ? Status::Success : Status::UnsupportedCommand;
}

return Status::Failure;
}
}

for (const CommandId * cmd = cluster->acceptedCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++)
{
if (*cmd == aCommandPath.mCommandId)
{
Expand Down Expand Up @@ -315,6 +361,13 @@ class GlobalAttributeReader : public MandatoryGlobalAttributeReader
GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {}

CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;

private:
typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster,
CommandHandlerInterface::CommandIdCallback callback,
void * context);
static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder,
CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList);
};

CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
Expand Down Expand Up @@ -357,26 +410,57 @@ CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath,
return CHIP_NO_ERROR;
});
case AcceptedCommandList::Id:
return aEncoder.EncodeList([this](const auto & encoder) {
for (const CommandId * cmd = mCluster->acceptedCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++)
{
ReturnErrorOnFailure(encoder.Encode(*cmd));
}
return CHIP_NO_ERROR;
});
return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands,
mCluster->acceptedCommandList);
case GeneratedCommandList::Id:
return aEncoder.EncodeList([this](const auto & encoder) {
for (const CommandId * cmd = mCluster->generatedCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++)
{
ReturnErrorOnFailure(encoder.Encode(*cmd));
}
return CHIP_NO_ERROR;
});
return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands,
mCluster->generatedCommandList);
default:
return CHIP_NO_ERROR;
}
}

CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder,
GlobalAttributeReader::CommandListEnumerator aEnumerator,
const CommandId * aClusterCommandList)
{
return aEncoder.EncodeList([&](const auto & encoder) {
auto * commandHandler =
InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId);
if (commandHandler)
{
struct Context
{
decltype(encoder) & commandIdEncoder;
CHIP_ERROR err;
} context{ encoder, CHIP_NO_ERROR };
CHIP_ERROR err = (commandHandler->*aEnumerator)(
aClusterPath,
[](CommandId command, void * closure) -> Loop {
auto * ctx = static_cast<Context *>(closure);
ctx->err = ctx->commandIdEncoder.Encode(command);
if (ctx->err != CHIP_NO_ERROR)
{
return Loop::Break;
}
return Loop::Continue;
},
&context);
if (err != CHIP_ERROR_NOT_IMPLEMENTED)
{
return context.err;
}
// Else fall through to the list in aClusterCommandList.
}

for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++)
{
ReturnErrorOnFailure(encoder.Encode(*cmd));
}
return CHIP_NO_ERROR;
});
}

// Helper function for trying to read an attribute value via an
// AttributeAccessInterface. On failure, the read has failed. On success, the
// aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value.
Expand Down

0 comments on commit 1107736

Please sign in to comment.