| /* |
| * |
| * 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; |
| } |