blob: dcd2f1eff9314e1bc2d960a06568ba804a4da2b2 [file] [log] [blame]
/**
* Copyright (c) 2024 Project CHIP Authors
*
* 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.
*/
#import "MTRDefines_Internal.h"
#import "MTRDeviceController_Internal.h"
#import "MTRLogging_Internal.h"
#import "MTRServerAttribute_Internal.h"
#import "MTRServerCluster_Internal.h"
#import "MTRServerEndpoint_Internal.h"
#import "MTRUnfairLock.h"
#import "NSDataSpanConversion.h"
#import <Matter/MTRClusterConstants.h>
#import <Matter/MTRServerCluster.h>
#include <app/AttributeAccessInterface.h>
#include <app/clusters/descriptor/descriptor.h>
#include <app/data-model/PreEncodedValue.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <protocols/interaction_model/StatusCode.h>
// TODO: These attribute-*.h and AttributeAccessInterfaceRegistry.h bits are a hack that should eventually go away.
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage.h>
using namespace chip;
using namespace chip::app;
class MTRServerAttributeAccessInterface : public AttributeAccessInterface {
public:
MTRServerAttributeAccessInterface(EndpointId aEndpointID, ClusterId aClusterID, NSArray<MTRServerAttribute *> * aAttributes,
NSNumber * aClusterRevision)
: AttributeAccessInterface(MakeOptional(aEndpointID), aClusterID)
, mAttributes(aAttributes)
, mClusterRevision(aClusterRevision)
{
}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
private:
NSArray<MTRServerAttribute *> * mAttributes;
NSNumber * mClusterRevision;
};
MTR_DIRECT_MEMBERS
@implementation MTRServerCluster {
/**
* The access grants our API consumer can manipulate directly. Never
* touched on the Matter queue.
*/
NSMutableSet<MTRAccessGrant *> * _accessGrants;
NSMutableArray<MTRServerAttribute *> * _attributes;
MTRDeviceController * __weak _deviceController;
std::unique_ptr<MTRServerAttributeAccessInterface> _attributeAccessInterface;
// We can't use something like std::unique_ptr<EmberAfAttributeMetadata[]>
// because EmberAfAttributeMetadata does not have a default constructor, so
// we can't alloc and then initialize later.
std::vector<EmberAfAttributeMetadata> _matterAttributeMetadata;
std::unique_ptr<CommandId[]> _matterAcceptedCommandList;
std::unique_ptr<CommandId[]> _matterGeneratedCommandList;
NSSet<MTRAccessGrant *> * _matterAccessGrants;
chip::EndpointId _parentEndpoint;
// _acceptedCommands and _generatedCommands are touched directly by our API
// consumer.
NSArray<NSNumber *> * _acceptedCommands;
NSArray<NSNumber *> * _generatedCommands;
/**
* _lock always protects access to all our mutable ivars (the ones that are
* modified after init).
*/
os_unfair_lock _lock;
}
- (nullable instancetype)initWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision
{
auto clusterIDValue = clusterID.unsignedLongLongValue;
if (!CanCastTo<ClusterId>(clusterIDValue)) {
MTR_LOG_ERROR("MTRServerCluster provided too-large cluster ID: 0x%llx", clusterIDValue);
return nil;
}
auto id = static_cast<ClusterId>(clusterIDValue);
if (!IsValidClusterId(id)) {
MTR_LOG_ERROR("MTRServerCluster provided invalid cluster ID: 0x%" PRIx32, id);
return nil;
}
if (id == MTRClusterIDTypeDescriptorID) {
MTR_LOG_ERROR("Should be using newDescriptorCluster to initialize an MTRServerCluster for Descriptor");
return nil;
}
auto revisionValue = revision.unsignedLongLongValue;
if (revisionValue < 1 || revisionValue > 0xFFFF) {
MTR_LOG_ERROR("MTRServerCluster provided invalid cluster revision: 0x%llx", revisionValue);
return nil;
}
return [self initInternalWithClusterID:clusterID revision:revision accessGrants:[NSSet set] attributes:@[]];
}
+ (MTRServerCluster *)newDescriptorCluster
{
return [[MTRServerCluster alloc] initInternalWithClusterID:@(MTRClusterIDTypeDescriptorID) revision:@(Clusters::Descriptor::kClusterRevision) accessGrants:[NSSet set] attributes:@[]];
}
- (instancetype)initInternalWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision accessGrants:(NSSet *)accessGrants attributes:(NSArray *)attributes
{
if (!(self = [super init])) {
return nil;
}
_lock = OS_UNFAIR_LOCK_INIT;
_clusterID = [clusterID copy];
_clusterRevision = [revision copy];
_accessGrants = [[NSMutableSet alloc] init];
_attributes = [[NSMutableArray alloc] init];
_matterAccessGrants = [NSSet set];
_parentEndpoint = kInvalidEndpointId;
for (MTRAccessGrant * grant in accessGrants) {
// Since our state is MTRServerEndpointStateInitializing, we know this
// will succeed.
[self addAccessGrant:grant];
}
for (MTRServerAttribute * attr in attributes) {
// Since our state is MTRServerEndpointStateInitializing and the initial
// attribute array was valid, we know this will succeed.
[self addAttribute:attr];
}
return self;
}
- (void)updateMatterAccessGrants
{
os_unfair_lock_assert_owner(&_lock);
MTRDeviceController * deviceController = _deviceController;
if (deviceController == nil) {
// _matterAccessGrants will be updated when we get bound to a controller.
return;
}
NSSet * grants = [_accessGrants copy];
[deviceController asyncDispatchToMatterQueue:^{
std::lock_guard lock(self->_lock);
self->_matterAccessGrants = grants;
}
errorHandler:nil];
}
- (void)addAccessGrant:(MTRAccessGrant *)accessGrant
{
std::lock_guard lock(self->_lock);
[_accessGrants addObject:accessGrant];
[self updateMatterAccessGrants];
}
- (void)removeAccessGrant:(MTRAccessGrant *)accessGrant;
{
std::lock_guard lock(self->_lock);
[_accessGrants removeObject:accessGrant];
[self updateMatterAccessGrants];
}
- (NSArray<MTRAccessGrant *> *)matterAccessGrants
{
std::lock_guard lock(self->_lock);
return [_matterAccessGrants allObjects];
}
- (BOOL)addAttribute:(MTRServerAttribute *)attribute
{
std::lock_guard lock(self->_lock);
MTRDeviceController * deviceController = _deviceController;
if (deviceController != nil) {
MTR_LOG_ERROR("Cannot add attribute on cluster %llx which is already in use", _clusterID.unsignedLongLongValue);
return NO;
}
auto attributeID = attribute.attributeID.unsignedLongLongValue;
if (attributeID == MTRAttributeIDTypeGlobalAttributeAttributeListID || attributeID == MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID || attributeID == MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID || attributeID == MTRAttributeIDTypeGlobalAttributeClusterRevisionID) {
MTR_LOG_ERROR("Cannot add global attribute %llx on cluster %llx", attributeID, _clusterID.unsignedLongLongValue);
return NO;
}
if ([_clusterID isEqual:@(MTRClusterIDTypeDescriptorID)]) {
if (attributeID == MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID || attributeID == MTRAttributeIDTypeClusterDescriptorAttributeServerListID || attributeID == MTRAttributeIDTypeClusterDescriptorAttributeClientListID || attributeID == MTRAttributeIDTypeClusterDescriptorAttributePartsListID) {
MTR_LOG_ERROR("Cannot add attribute with id %llx on descriptor cluster", attributeID);
return NO;
}
}
for (MTRServerAttribute * existingAttr in _attributes) {
if (existingAttr.attributeID.unsignedLongLongValue == attributeID) {
MTR_LOG_ERROR("Cannot add second attribute with ID %llx on cluster %llx", attributeID, _clusterID.unsignedLongLongValue);
return NO;
}
}
if (![attribute addToCluster:ConcreteClusterPath(_parentEndpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue))]) {
return NO;
}
[_attributes addObject:attribute];
return YES;
}
static constexpr EmberAfAttributeMetadata sDescriptorAttributesMetadata[] = {
DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID, ARRAY, 0, 0),
DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeServerListID, ARRAY, 0, 0),
DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributeClientListID, ARRAY, 0, 0),
DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeClusterDescriptorAttributePartsListID, ARRAY, 0, 0),
};
- (BOOL)associateWithController:(nullable MTRDeviceController *)controller
{
std::lock_guard lock(self->_lock);
MTRDeviceController * existingController = _deviceController;
if (existingController != nil) {
MTR_LOG_ERROR("Cannot associate MTRServerCluster with controller %@; already associated with controller %@",
controller.uniqueIdentifier, existingController.uniqueIdentifier);
return NO;
}
for (MTRServerAttribute * attr in _attributes) {
if (![attr associateWithController:controller]) {
return NO;
}
}
// Snapshot _matterAccessGrants now; after this point it will only be
// updated on the Matter queue.
_matterAccessGrants = [_accessGrants copy];
// _attributes shouldn't be able to change anymore, so we can now construct
// our EmberAfAttributeMetadata array.
size_t attributeCount = _attributes.count;
// Figure out whether we need to synthesize a FeatureMap attribute.
bool needsFeatureMap = true;
for (MTRServerAttribute * attr in _attributes) {
if ([attr.attributeID isEqual:@(MTRClusterGlobalAttributeFeatureMapID)]) {
needsFeatureMap = false;
break;
}
}
bool needsDescriptorAttributes = [_clusterID isEqual:@(MTRClusterIDTypeDescriptorID)];
if (needsFeatureMap) {
++attributeCount;
}
if (needsDescriptorAttributes) {
attributeCount += ArraySize(sDescriptorAttributesMetadata);
}
// And add one for ClusterRevision
++attributeCount;
if (attributeCount >= UINT16_MAX) {
MTR_LOG_ERROR("Unable to have %llu attributes in a single cluster (clusterID: " ChipLogFormatMEI ")",
static_cast<unsigned long long>(attributeCount), ChipLogValueMEI(_clusterID.unsignedLongLongValue));
return NO;
}
size_t attrIndex = 0;
for (; attrIndex < _attributes.count; ++attrIndex) {
auto * attr = _attributes[attrIndex];
_matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(static_cast<AttributeId>(attr.attributeID.unsignedLongLongValue),
// The type does not actually matter, since we plan to
// handle this entirely via AttributeAccessInterface.
// Claim Array because that one will keep random IM
// code from trying to do things with the attribute
// store.
ARRAY,
// Size in bytes does not matter, since we plan to
// handle this entirely via AttributeAccessInterface.
0,
// ATTRIBUTE_MASK_NULLABLE is not relevant because we
// are handling this all via AttributeAccessInterface.
0)));
}
if (needsFeatureMap) {
_matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeFeatureMapID,
BITMAP32, 4, 0)));
++attrIndex;
}
if (needsDescriptorAttributes) {
for (auto & data : sDescriptorAttributesMetadata) {
_matterAttributeMetadata.emplace_back(data);
++attrIndex;
}
}
// Add our ClusterRevision bit.
_matterAttributeMetadata.emplace_back(EmberAfAttributeMetadata(DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeClusterRevisionID,
INT16U, 2, 0)));
++attrIndex;
_attributeAccessInterface = std::make_unique<MTRServerAttributeAccessInterface>(_parentEndpoint,
static_cast<ClusterId>(_clusterID.unsignedLongLongValue),
_attributes,
_clusterRevision);
// _attributeAccessInterface needs to be registered on the Matter queue; that will happen later.
_matterAcceptedCommandList = [MTRServerCluster makeMatterCommandList:_acceptedCommands];
_matterGeneratedCommandList = [MTRServerCluster makeMatterCommandList:_generatedCommands];
_deviceController = controller;
MTR_LOG_DEFAULT("Associated %@, attribute count %llu, with controller", [self _descriptionWhileLocked],
static_cast<unsigned long long>(attributeCount));
return YES;
}
- (void)invalidate
{
std::lock_guard lock(_lock);
// Undo any work associateWithController did.
for (MTRServerAttribute * attr in _attributes) {
[attr invalidate];
}
// We generally promise to only touch _matterAccessGrants on the Matter
// queue after associateWithController succeeds, but we are no longer being
// looked at from that queue, so it's safe to reset it here.
_matterAccessGrants = [NSSet set];
_matterAttributeMetadata.clear();
_attributeAccessInterface.reset();
_matterAcceptedCommandList.reset();
_matterGeneratedCommandList.reset();
_deviceController = nil;
}
- (void)registerMatterCluster
{
assertChipStackLockedByCurrentThread();
std::lock_guard lock(_lock);
if (!registerAttributeAccessOverride(_attributeAccessInterface.get())) {
// This should only happen if we somehow managed to register an
// AttributeAccessInterface for the same (endpoint, cluster) pair.
MTR_LOG_ERROR("Could not register AttributeAccessInterface for endpoint %u, cluster 0x%llx",
_parentEndpoint, _clusterID.unsignedLongLongValue);
}
}
- (void)unregisterMatterCluster
{
assertChipStackLockedByCurrentThread();
std::lock_guard lock(_lock);
if (_attributeAccessInterface != nullptr) {
unregisterAttributeAccessOverride(_attributeAccessInterface.get());
}
}
- (NSArray<MTRAccessGrant *> *)accessGrants
{
std::lock_guard lock(_lock);
return [_accessGrants allObjects];
}
- (NSArray<MTRServerAttribute *> *)attributes
{
std::lock_guard lock(_lock);
return [_attributes copy];
}
- (BOOL)addToEndpoint:(chip::EndpointId)endpoint
{
std::lock_guard lock(_lock);
if (_parentEndpoint != kInvalidEndpointId) {
MTR_LOG_ERROR("Cannot add cluster " ChipLogFormatMEI " to endpoint %" PRIu32 "; already added to endpoint %" PRIu32,
ChipLogValueMEI(_clusterID.unsignedLongLongValue), endpoint, _parentEndpoint);
return NO;
}
_parentEndpoint = endpoint;
// Update it on all the attributes, in case the attributes were added to us
// before we were added to the endpoint.
for (MTRServerAttribute * attr in _attributes) {
[attr updateParentCluster:ConcreteClusterPath(endpoint, static_cast<ClusterId>(_clusterID.unsignedLongLongValue))];
}
return YES;
}
- (chip::EndpointId)parentEndpoint
{
std::lock_guard lock(_lock);
return _parentEndpoint;
}
- (Span<const EmberAfAttributeMetadata>)matterAttributeMetadata
{
// This is always called after our _matterAttributeMetadata has been set up
// by associateWithController.
std::lock_guard lock(_lock);
return Span<const EmberAfAttributeMetadata>(_matterAttributeMetadata.data(), _matterAttributeMetadata.size());
}
- (void)setAcceptedCommands:(NSArray<NSNumber *> *)acceptedCommands
{
std::lock_guard lock(_lock);
_acceptedCommands = [acceptedCommands copy];
}
- (NSArray<NSNumber *> *)acceptedCommands
{
std::lock_guard lock(_lock);
return [_acceptedCommands copy];
}
- (void)setGeneratedCommands:(NSArray<NSNumber *> *)generatedCommands
{
std::lock_guard lock(_lock);
_generatedCommands = [generatedCommands copy];
}
- (NSArray<NSNumber *> *)generatedCommands
{
std::lock_guard lock(_lock);
return [_generatedCommands copy];
}
- (CommandId *)matterAcceptedCommands
{
std::lock_guard lock(_lock);
return _matterAcceptedCommandList.get();
}
- (CommandId *)matterGeneratedCommands
{
std::lock_guard lock(_lock);
return _matterGeneratedCommandList.get();
}
+ (std::unique_ptr<CommandId[]>)makeMatterCommandList:(NSArray<NSNumber *> * _Nullable)commandList
{
if (commandList.count == 0) {
return nullptr;
}
// Lists of accepted/generated commands are terminated by kInvalidClusterId.
auto matterCommandList = std::make_unique<CommandId[]>(commandList.count + 1);
for (size_t index = 0; index < commandList.count; ++index) {
matterCommandList[index] = static_cast<CommandId>(commandList[index].unsignedLongLongValue);
}
matterCommandList[commandList.count] = kInvalidClusterId;
return matterCommandList;
}
- (NSString *)description
{
std::lock_guard lock(_lock);
return [self _descriptionWhileLocked];
}
- (NSString *)_descriptionWhileLocked
{
os_unfair_lock_assert_owner(&_lock);
return [NSString stringWithFormat:@"<MTRServerCluster endpoint %u, id " ChipLogFormatMEI ">",
_parentEndpoint, ChipLogValueMEI(_clusterID.unsignedLongLongValue)];
}
@end
CHIP_ERROR MTRServerAttributeAccessInterface::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
using DataModel::PreEncodedValue;
// Find the right attribute in our list.
MTRServerAttribute * foundAttr = nil;
for (MTRServerAttribute * attr in mAttributes) {
if ([attr.attributeID isEqual:@(aPath.mAttributeId)]) {
foundAttr = attr;
break;
}
}
if (foundAttr) {
id value = foundAttr.serializedValue;
if (![value isKindOfClass:NSArray.class]) {
// It's a single value, so NSData.
NSData * data = value;
return aEncoder.Encode(PreEncodedValue(AsByteSpan(data)));
}
// It's a list of data values.
NSArray<NSData *> * dataList = value;
return aEncoder.EncodeList([dataList](const auto & itemEncoder) {
for (NSData * item in dataList) {
ReturnErrorOnFailure(itemEncoder.Encode(PreEncodedValue(AsByteSpan(item))));
}
return CHIP_NO_ERROR;
});
}
// This must be the FeatureMap attribute we synthesized.
if (aPath.mAttributeId == MTRAttributeIDTypeGlobalAttributeFeatureMapID) {
// Feature map defaults to 0.
constexpr uint32_t defaultFeatureMap = 0;
return aEncoder.Encode(defaultFeatureMap);
}
if (aPath.mAttributeId == MTRAttributeIDTypeGlobalAttributeClusterRevisionID) {
return aEncoder.Encode(mClusterRevision.unsignedLongLongValue);
}
// Note: This code is not reached for the descriptor cluster, which uses its own AttributeAccessInterface.
return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute);
}