blob: 0c9825d6792eb4daf90126ab18877abbf0853e88 [file] [log] [blame]
/*
*
* Copyright (c) 2022-2023 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.
*/
#include <app/util/ember-compatibility-functions.h>
#include <access/SubjectDescriptor.h>
#include <app-common/zap-generated/callback.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app-common/zap-generated/ids/Commands.h>
#include <app/CommandHandler.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
#include <app/GlobalAttributes.h>
#include <app/MessageDef/AttributeReportIBs.h>
#include <app/MessageDef/StatusIB.h>
#include <app/WriteHandler.h>
#include <app/data-model/Decode.h>
#include <app/util/att-storage.h>
#include <app/util/attribute-table.h>
#include <app/util/endpoint-config-api.h>
#include <cstddef>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/Optional.h>
#include <lib/core/TLV.h>
#include <protocols/interaction_model/Constants.h>
/**
* This file defines the APIs needed to handle interaction model dispatch.
* These are the APIs normally defined in
* src/app/util/ember-compatibility-functions.cpp and the generated
* IMClusterCommandHandler.cpp but we want a different implementation of these
* to enable more dynamic behavior, since not all framework consumers will be
* implementing the same server clusters.
*/
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
namespace {
// TODO: Maybe consider making this configurable? See also
// AccessControl.cpp.
constexpr EndpointId kSupportedEndpoint = 0;
DataVersion gMockDataVersion = 0;
} // anonymous namespace
namespace chip {
namespace app {
using Access::SubjectDescriptor;
using Protocols::InteractionModel::Status;
namespace {
bool IsSupportedGlobalAttribute(AttributeId aAttribute)
{
// We don't have any non-global attributes.
using namespace Globals::Attributes;
for (auto & attr : GlobalAttributesNotInMetadata)
{
if (attr == aAttribute)
{
return true;
}
}
switch (aAttribute)
{
case FeatureMap::Id:
FALLTHROUGH;
case ClusterRevision::Id:
return true;
}
return false;
}
Status DetermineAttributeStatus(const ConcreteAttributePath & aPath, bool aIsWrite)
{
// TODO: Consider making this configurable for applications that are not
// trying to be an OTA provider, though in practice it just affects which
// error is returned.
if (aPath.mEndpointId != kSupportedEndpoint)
{
return Status::UnsupportedEndpoint;
}
// TODO: Consider making this configurable for applications that are not
// trying to be an OTA provider, though in practice it just affects which
// error is returned.
if (aPath.mClusterId != OtaSoftwareUpdateProvider::Id)
{
return Status::UnsupportedCluster;
}
if (!IsSupportedGlobalAttribute(aPath.mAttributeId))
{
return Status::UnsupportedAttribute;
}
// No permissions for this for read, and none of these are writable for
// write. The writable-or-not check happens before the ACL check.
return aIsWrite ? Status::UnsupportedWrite : Status::UnsupportedAccess;
}
} // anonymous namespace
CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
AttributeEncodeState * aEncoderState)
{
Status status = DetermineAttributeStatus(aPath, /* aIsWrite = */ false);
return aAttributeReports.EncodeAttributeStatus(aPath, StatusIB(status));
}
bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath)
{
return DetermineAttributeStatus(aPath, /* aIsWrite = */ false) == Status::UnsupportedAccess;
}
Status ServerClusterCommandExists(const ConcreteCommandPath & aPath)
{
// TODO: Consider making this configurable for applications that are not
// trying to be an OTA provider.
using namespace OtaSoftwareUpdateProvider::Commands;
if (aPath.mEndpointId != kSupportedEndpoint)
{
return Status::UnsupportedEndpoint;
}
if (aPath.mClusterId != OtaSoftwareUpdateProvider::Id)
{
return Status::UnsupportedCluster;
}
switch (aPath.mCommandId)
{
case QueryImage::Id:
FALLTHROUGH;
case ApplyUpdateRequest::Id:
FALLTHROUGH;
case NotifyUpdateApplied::Id:
return Status::Success;
}
return Status::UnsupportedCommand;
}
bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion)
{
// Will never be called anyway; we have no attributes.
return false;
}
const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath)
{
// Note: This test does not make use of the real attribute metadata.
static EmberAfAttributeMetadata stub = { .defaultValue = EmberAfDefaultOrMinMaxAttributeValue(uint32_t(0)) };
return &stub;
}
bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint)
{
return false;
}
CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath,
TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
{
Status status = DetermineAttributeStatus(aPath, /* aIsWrite = */ true);
return aWriteHandler->AddStatus(aPath, status);
}
void DispatchSingleClusterCommand(const ConcreteCommandPath & aPath, TLV::TLVReader & aReader, CommandHandler * aCommandObj)
{
// This command passed ServerClusterCommandExists so we know it's one of our
// supported commands.
using namespace OtaSoftwareUpdateProvider::Commands;
bool wasHandled = false;
CHIP_ERROR err = CHIP_NO_ERROR;
switch (aPath.mCommandId)
{
case QueryImage::Id: {
QueryImage::DecodableType commandData;
err = DataModel::Decode(aReader, commandData);
if (err == CHIP_NO_ERROR)
{
wasHandled = emberAfOtaSoftwareUpdateProviderClusterQueryImageCallback(aCommandObj, aPath, commandData);
}
break;
}
case ApplyUpdateRequest::Id: {
ApplyUpdateRequest::DecodableType commandData;
err = DataModel::Decode(aReader, commandData);
if (err == CHIP_NO_ERROR)
{
wasHandled = emberAfOtaSoftwareUpdateProviderClusterApplyUpdateRequestCallback(aCommandObj, aPath, commandData);
}
break;
}
case NotifyUpdateApplied::Id: {
NotifyUpdateApplied::DecodableType commandData;
err = DataModel::Decode(aReader, commandData);
if (err == CHIP_NO_ERROR)
{
wasHandled = emberAfOtaSoftwareUpdateProviderClusterNotifyUpdateAppliedCallback(aCommandObj, aPath, commandData);
}
break;
}
default:
break;
}
if (CHIP_NO_ERROR != err || !wasHandled)
{
aCommandObj->AddStatus(aPath, Status::InvalidCommand);
}
}
Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath)
{
return Protocols::InteractionModel::Status::UnsupportedEvent;
}
} // namespace app
} // namespace chip
/**
* Called by the OTA provider cluster server to determine an index
* into its array.
*/
uint16_t emberAfGetClusterServerEndpointIndex(EndpointId endpoint, ClusterId cluster, uint16_t fixedClusterServerEndpointCount)
{
if (endpoint == kSupportedEndpoint && cluster == OtaSoftwareUpdateProvider::Id)
{
return 0;
}
return UINT16_MAX;
}
/**
* Methods used by AttributePathExpandIterator, which need to exist
* because it is part of libCHIP. For AttributePathExpandIterator
* purposes, for now, we just pretend like we have just our one
* endpoint, the OTA Provider cluster, and no attributes (because we
* would be erroring out from them anyway).
*/
uint16_t emberAfGetServerAttributeCount(EndpointId endpoint, ClusterId cluster)
{
return 0;
}
uint16_t emberAfEndpointCount(void)
{
return 1;
}
uint16_t emberAfIndexFromEndpoint(EndpointId endpoint)
{
if (endpoint == kSupportedEndpoint)
{
return 0;
}
return UINT16_MAX;
}
EndpointId emberAfEndpointFromIndex(uint16_t index)
{
// Index must be valid here, so 0.
return kSupportedEndpoint;
}
Optional<ClusterId> emberAfGetNthClusterId(EndpointId endpoint, uint8_t n, bool server)
{
if (endpoint == kSupportedEndpoint && n == 0 && server)
{
return MakeOptional(OtaSoftwareUpdateProvider::Id);
}
return NullOptional;
}
uint16_t emberAfGetServerAttributeIndexByAttributeId(EndpointId endpoint, ClusterId cluster, AttributeId attributeId)
{
return UINT16_MAX;
}
bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId)
{
return false;
}
uint8_t emberAfClusterCount(EndpointId endpoint, bool server)
{
if (endpoint == kSupportedEndpoint && server)
{
return 1;
}
return 0;
}
Optional<AttributeId> emberAfGetServerAttributeIdByIndex(EndpointId endpoint, ClusterId cluster, uint16_t attributeIndex)
{
return NullOptional;
}
uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClusterMask mask)
{
if (endpoint == kSupportedEndpoint && clusterId == OtaSoftwareUpdateProvider::Id && (mask & CLUSTER_MASK_SERVER))
{
return 0;
}
return UINT8_MAX;
}
bool emberAfEndpointIndexIsEnabled(uint16_t index)
{
return index == 0;
}
namespace {
const CommandId acceptedCommands[] = { Clusters::OtaSoftwareUpdateProvider::Commands::QueryImage::Id,
Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateRequest::Id,
Clusters::OtaSoftwareUpdateProvider::Commands::NotifyUpdateApplied::Id, kInvalidCommandId };
const CommandId generatedCommands[] = { Clusters::OtaSoftwareUpdateProvider::Commands::QueryImageResponse::Id,
Clusters::OtaSoftwareUpdateProvider::Commands::ApplyUpdateResponse::Id, kInvalidCommandId };
const EmberAfCluster otaProviderCluster{
.clusterId = Clusters::OtaSoftwareUpdateProvider::Id,
.attributes = nullptr,
.attributeCount = 0,
.clusterSize = 0,
.mask = CLUSTER_MASK_SERVER,
.functions = nullptr,
.acceptedCommandList = acceptedCommands,
.generatedCommandList = generatedCommands,
.eventList = nullptr,
.eventCount = 0,
};
const EmberAfEndpointType otaProviderEndpoint{ .cluster = &otaProviderCluster, .clusterCount = 1, .endpointSize = 0 };
} // namespace
const EmberAfEndpointType * emberAfFindEndpointType(EndpointId endpoint)
{
if (endpoint == kSupportedEndpoint)
{
return &otaProviderEndpoint;
}
return nullptr;
}
const EmberAfCluster * emberAfFindServerCluster(EndpointId endpoint, ClusterId cluster)
{
if (endpoint == kSupportedEndpoint && cluster == Clusters::OtaSoftwareUpdateProvider::Id)
{
return &otaProviderCluster;
}
return nullptr;
}
unsigned emberAfMetadataStructureGeneration()
{
// DynamicDispatcher at this point hardcodes a single OTA provider cluster.
// The structure does not change over time, so the current version stays at 0.
return 0;
}
Protocols::InteractionModel::Status emberAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWriteDataInput & input)
{
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}
Protocols::InteractionModel::Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord,
const EmberAfAttributeMetadata ** metadata, uint8_t * buffer,
uint16_t readLength, bool write)
{
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}
void emberAfAttributeChanged(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId,
AttributesChangedListener * listener)
{
gMockDataVersion++;
listener->MarkDirty(AttributePathParams(endpoint, clusterId, attributeId));
}
DataVersion * emberAfDataVersionStorage(const ConcreteClusterPath & aConcreteClusterPath)
{
return &gMockDataVersion;
}
Protocols::InteractionModel::Status emAfWriteAttributeExternal(const ConcreteAttributePath & path,
const EmberAfWriteDataInput & input)
{
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}
Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err)
{
err = CHIP_ERROR_NOT_IMPLEMENTED;
return Span<const EmberAfDeviceType>();
}
const EmberAfCluster * emberAfFindClusterInType(const EmberAfEndpointType * endpointType, ClusterId clusterId,
EmberAfClusterMask mask, uint8_t * index)
{
if ((endpointType == &otaProviderEndpoint) && (clusterId == Clusters::OtaSoftwareUpdateProvider::Id))
{
return &otaProviderCluster;
}
return nullptr;
}
const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId)
{
// no known attributes even for OTA
return nullptr;
}