| /** |
| * 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 "MTRServerCluster_Internal.h" |
| #import "MTRServerEndpoint_Internal.h" |
| #import <Matter/MTRClusterConstants.h> |
| #import <Matter/MTRServerEndpoint.h> |
| |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/support/SafeInt.h> |
| #include <platform/LockTracker.h> |
| |
| // TODO: These af-types.h and att-storage.h and attribute-storage.h and |
| // endpoint-config-api.h and probably CodeUtils.h bits are a hack that should |
| // eventually go away. |
| #include <app/util/af-types.h> |
| #include <app/util/att-storage.h> |
| #include <app/util/attribute-storage.h> |
| #include <app/util/endpoint-config-api.h> |
| #include <lib/support/CodeUtils.h> |
| |
| using namespace chip; |
| |
| MTR_DIRECT_MEMBERS |
| @implementation MTRServerEndpoint { |
| /** |
| * The access grants our API consumer can manipulate directly. Never |
| * touched on the Matter queue. |
| */ |
| NSMutableSet<MTRAccessGrant *> * _accessGrants; |
| NSMutableArray<MTRServerCluster *> * _serverClusters; |
| MTRDeviceController * __weak _deviceController; |
| std::unique_ptr<EmberAfCluster[]> _matterClusterMetadata; |
| EmberAfEndpointType _matterEndpointMetadata; |
| std::unique_ptr<EmberAfDeviceType[]> _matterDeviceTypes; |
| std::unique_ptr<DataVersion[]> _matterDataVersions; |
| |
| // _endpointIndex has a value only when we have the endpoint configured. |
| std::optional<uint16_t> _endpointIndex; |
| } |
| |
| - (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray<MTRDeviceTypeRevision *> *)deviceTypes |
| { |
| auto endpointIDValue = endpointID.unsignedLongLongValue; |
| if (!CanCastTo<EndpointId>(endpointIDValue)) { |
| MTR_LOG_ERROR("MTRServerEndpoint provided too-large endpoint ID: 0x%llx", endpointIDValue); |
| return nil; |
| } |
| |
| auto id = static_cast<EndpointId>(endpointIDValue); |
| if (!IsValidEndpointId(id)) { |
| MTR_LOG_ERROR("MTRServerEndpoint provided invalid endpoint ID: 0x%x", id); |
| return nil; |
| } |
| |
| if (id == kRootEndpointId) { |
| // We don't allow this; we use that endpoint for our own purposes in |
| // Matter.framework. |
| MTR_LOG_ERROR("MTRServerEndpoint provided invalid endpoint ID: 0x%x", id); |
| return nil; |
| } |
| |
| if (deviceTypes.count == 0) { |
| MTR_LOG_ERROR("MTRServerEndpoint needs a non-empty list of device types"); |
| return nil; |
| } |
| |
| return [self initInternalWithEndpointID:endpointID deviceTypes:deviceTypes accessGrants:[NSSet set] clusters:@[]]; |
| } |
| |
| + (MTRServerEndpoint *)rootNodeEndpoint |
| { |
| return [[MTRServerEndpoint alloc] initInternalWithEndpointID:@(kRootEndpointId) deviceTypes:@[] accessGrants:[NSSet set] clusters:@[]]; |
| } |
| |
| - (instancetype)initInternalWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray<MTRDeviceTypeRevision *> *)deviceTypes accessGrants:(NSSet *)accessGrants clusters:(NSArray *)clusters |
| { |
| if (!(self = [super init])) { |
| return nil; |
| } |
| |
| _endpointID = [endpointID copy]; |
| _deviceTypes = [deviceTypes copy]; |
| _accessGrants = [[NSMutableSet alloc] init]; |
| _serverClusters = [[NSMutableArray alloc] init]; |
| _matterAccessGrants = [NSSet set]; |
| |
| for (MTRAccessGrant * grant in accessGrants) { |
| // Since our state is MTRServerEndpointStateInitializing, we know this |
| // will succeed. |
| [self addAccessGrant:[grant copy]]; |
| } |
| |
| for (MTRServerCluster * cluster in clusters) { |
| // Since our state is MTRServerEndpointStateInitializing and the initial |
| // cluster array was valid, we know this will succeed. |
| [self addServerCluster:[cluster copy]]; |
| } |
| |
| return self; |
| } |
| |
| - (void)updateMatterAccessGrants |
| { |
| MTRDeviceController * deviceController = _deviceController; |
| if (deviceController == nil) { |
| // _matterAccessGrants will be updated when we get bound to a controller. |
| return; |
| } |
| |
| NSSet * grants = [_accessGrants copy]; |
| [deviceController asyncDispatchToMatterQueue:^{ |
| self->_matterAccessGrants = grants; |
| } |
| errorHandler:nil]; |
| } |
| |
| - (void)addAccessGrant:(MTRAccessGrant *)accessGrant |
| { |
| [_accessGrants addObject:accessGrant]; |
| |
| [self updateMatterAccessGrants]; |
| } |
| |
| - (void)removeAccessGrant:(MTRAccessGrant *)accessGrant; |
| { |
| [_accessGrants removeObject:accessGrant]; |
| |
| [self updateMatterAccessGrants]; |
| } |
| |
| - (BOOL)addServerCluster:(MTRServerCluster *)serverCluster |
| { |
| MTRDeviceController * deviceController = _deviceController; |
| if (deviceController != nil) { |
| MTR_LOG_ERROR("Cannot add cluster on endpoint %llu which is already in use", _endpointID.unsignedLongLongValue); |
| return NO; |
| } |
| |
| for (MTRServerCluster * existingCluster in _serverClusters) { |
| if ([existingCluster.clusterID isEqual:serverCluster.clusterID]) { |
| MTR_LOG_ERROR("Cannot add second cluster with ID " ChipLogFormatMEI " on endpoint %llu", ChipLogValueMEI(serverCluster.clusterID.unsignedLongLongValue), _endpointID.unsignedLongLongValue); |
| return NO; |
| } |
| } |
| |
| if (![serverCluster addToEndpoint:static_cast<EndpointId>(_endpointID.unsignedLongLongValue)]) { |
| return NO; |
| } |
| [_serverClusters addObject:serverCluster]; |
| |
| return YES; |
| } |
| |
| #define MTR_DECLARE_LIST_ATTRIBUTE(attrID) \ |
| DECLARE_DYNAMIC_ATTRIBUTE(attrID, ARRAY, 0, 0) |
| |
| 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), |
| DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeFeatureMapID, BITMAP32, 4, 0), |
| DECLARE_DYNAMIC_ATTRIBUTE(MTRAttributeIDTypeGlobalAttributeClusterRevisionID, INT16U, 2, 0), |
| }; |
| |
| #undef MTR_DECLARE_LIST_ATTRIBUTE |
| |
| - (BOOL)associateWithController:(nullable MTRDeviceController *)controller |
| { |
| MTRDeviceController * existingController = _deviceController; |
| if (existingController != nil) { |
| MTR_LOG_ERROR("Cannot associate MTRServerEndpoint with controller %@; already associated with controller %@", |
| controller.uniqueIdentifier, existingController.uniqueIdentifier); |
| return NO; |
| } |
| |
| // After this point we have to make sure we clean up on any failures. |
| if (![self finishAssociationWithController:controller]) { |
| [self invalidate]; |
| return NO; |
| } |
| |
| return YES; |
| } |
| |
| - (BOOL)finishAssociationWithController:(nullable MTRDeviceController *)controller |
| { |
| for (MTRServerCluster * cluster in _serverClusters) { |
| if (![cluster associateWithController:controller]) { |
| return NO; |
| } |
| } |
| |
| // Snapshot _matterAccessGrants now; after this point it will only be |
| // updated on the Matter queue. |
| _matterAccessGrants = [_accessGrants copy]; |
| |
| // _serverClusters shouldn't be able to change anymore, so we can now |
| // construct our EmberAfCluster array. |
| size_t clusterCount = _serverClusters.count; |
| |
| // Figure out whether we need to synthesize a Descriptor cluster. |
| bool needsDescriptor = true; |
| for (MTRServerCluster * cluster in _serverClusters) { |
| if ([cluster.clusterID isEqual:@(MTRClusterIDTypeDescriptorID)]) { |
| needsDescriptor = false; |
| break; |
| } |
| } |
| |
| if (needsDescriptor) { |
| ++clusterCount; |
| } |
| |
| if (clusterCount >= 0xFF) { |
| // The ember bits don't allow this many clusters (they use 0xFF to mean |
| // "no such cluster" in various places. |
| MTR_LOG_ERROR("Unable to create endpoint with %llu clusters; it's too many", |
| static_cast<unsigned long long>(clusterCount)); |
| return NO; |
| } |
| |
| _matterClusterMetadata = std::make_unique<EmberAfCluster[]>(clusterCount); |
| // std::make_unique never returns null; it will try to throw an exception |
| // and likely crash on OOM. |
| |
| size_t clusterIndex = 0; |
| for (; clusterIndex < _serverClusters.count; ++clusterIndex) { |
| auto * cluster = _serverClusters[clusterIndex]; |
| auto & metadata = _matterClusterMetadata[clusterIndex]; |
| |
| metadata.clusterId = static_cast<ClusterId>(cluster.clusterID.unsignedLongLongValue); |
| |
| auto attrMetadata = cluster.matterAttributeMetadata; |
| metadata.attributes = attrMetadata.data(); |
| // This cast is safe because clusters check for this constraint on |
| // number of attributes. |
| metadata.attributeCount = static_cast<uint16_t>(attrMetadata.size()); |
| |
| metadata.clusterSize = 0; // All our attributes are external. |
| |
| metadata.mask = CLUSTER_MASK_SERVER; |
| |
| metadata.functions = nullptr; // None of our clusters, including Descriptor, uses these. |
| |
| metadata.acceptedCommandList = cluster.matterAcceptedCommands; |
| metadata.generatedCommandList = cluster.matterGeneratedCommands; |
| |
| metadata.eventList = nullptr; |
| metadata.eventCount = 0; |
| } |
| |
| if (needsDescriptor) { |
| auto & metadata = _matterClusterMetadata[clusterIndex]; |
| |
| metadata.clusterId = MTRClusterIDTypeDescriptorID; |
| |
| metadata.attributes = sDescriptorAttributesMetadata; |
| metadata.attributeCount = ArraySize(sDescriptorAttributesMetadata); |
| |
| metadata.clusterSize = 0; // All our attributes are external. |
| |
| metadata.mask = CLUSTER_MASK_SERVER; |
| |
| metadata.functions = nullptr; // Descriptor does not use these. |
| |
| metadata.acceptedCommandList = nullptr; |
| metadata.generatedCommandList = nullptr; |
| |
| metadata.eventList = nullptr; |
| metadata.eventCount = 0; |
| |
| ++clusterIndex; |
| } |
| |
| _matterEndpointMetadata.cluster = _matterClusterMetadata.get(); |
| // Cast is safe, because we did a range check above. |
| _matterEndpointMetadata.clusterCount = static_cast<decltype(_matterEndpointMetadata.clusterCount)>(clusterCount); |
| _matterEndpointMetadata.endpointSize = 0; // All our attributes are external. |
| |
| _matterDeviceTypes = std::make_unique<EmberAfDeviceType[]>(_deviceTypes.count); |
| for (size_t index = 0; index < _deviceTypes.count; ++index) { |
| auto * deviceType = _deviceTypes[index]; |
| auto & matterType = _matterDeviceTypes[index]; |
| |
| matterType.deviceId = static_cast<DeviceTypeId>(deviceType.deviceTypeID.unsignedLongLongValue); |
| // TODO: The spec allows 16-bit revisions, but the Ember bits only |
| // support 8-bit.... |
| matterType.deviceVersion = static_cast<uint8_t>(deviceType.deviceTypeRevision.unsignedLongLongValue); |
| } |
| |
| _matterDataVersions = std::make_unique<DataVersion[]>(clusterCount); |
| |
| _deviceController = controller; |
| |
| MTR_LOG_DEFAULT("Associated %@, cluster count %llu, with controller", |
| self, static_cast<unsigned long long>(clusterCount)); |
| |
| return YES; |
| } |
| |
| - (void)registerMatterEndpoint |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| static_assert(FIXED_ENDPOINT_COUNT == 0, "Indexing will be off"); |
| |
| // We can't use emberAfEndpointCount here, because that returns just the |
| // count of fixed endpoints up until the first call to |
| // emberAfSetDynamicEndpoint(). |
| uint16_t possibleEndpointCount = MAX_ENDPOINT_COUNT; |
| uint16_t index = 0; |
| for (; index < possibleEndpointCount; ++index) { |
| if (emberAfEndpointFromIndex(index) == kInvalidEndpointId) { |
| break; |
| } |
| } |
| |
| if (index == possibleEndpointCount) { |
| // Something is very broken. We shouldn't have this many endpoints! |
| MTR_LOG_ERROR("We somehow ran out of endpoint slots."); |
| return; |
| } |
| |
| auto status = emberAfSetDynamicEndpoint(index, static_cast<EndpointId>(_endpointID.unsignedLongLongValue), |
| &_matterEndpointMetadata, |
| Span<DataVersion>(_matterDataVersions.get(), _matterEndpointMetadata.clusterCount), |
| Span<EmberAfDeviceType>(_matterDeviceTypes.get(), _deviceTypes.count)); |
| if (status != CHIP_NO_ERROR) { |
| MTR_LOG_ERROR("Unexpected failure to define our Matter endpoint"); |
| } |
| |
| _endpointIndex.emplace(index); |
| |
| for (MTRServerCluster * cluster in _serverClusters) { |
| [cluster registerMatterCluster]; |
| } |
| } |
| |
| - (void)unregisterMatterEndpoint |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| if (_endpointIndex.has_value()) { |
| emberAfClearDynamicEndpoint(_endpointIndex.value()); |
| _endpointIndex.reset(); |
| } |
| |
| for (MTRServerCluster * cluster in _serverClusters) { |
| [cluster unregisterMatterCluster]; |
| } |
| } |
| |
| - (void)invalidate |
| { |
| // Undo any work associateWithController did. |
| for (MTRServerCluster * cluster in _serverClusters) { |
| [cluster 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]; |
| _matterEndpointMetadata.cluster = nullptr; |
| _matterEndpointMetadata.clusterCount = 0; |
| _matterClusterMetadata.reset(); |
| _matterDeviceTypes.reset(); |
| _matterDataVersions.reset(); |
| _deviceController = nil; |
| } |
| |
| - (NSArray<MTRAccessGrant *> *)matterAccessGrantsForCluster:(NSNumber *)clusterID |
| { |
| assertChipStackLockedByCurrentThread(); |
| |
| NSMutableArray<MTRAccessGrant *> * grants = [[_matterAccessGrants allObjects] mutableCopy]; |
| for (MTRServerCluster * cluster in _serverClusters) { |
| if ([cluster.clusterID isEqual:clusterID]) { |
| [grants addObjectsFromArray:cluster.matterAccessGrants]; |
| } |
| } |
| |
| return [grants copy]; |
| } |
| |
| - (NSArray<MTRAccessGrant *> *)accessGrants |
| { |
| return [_accessGrants allObjects]; |
| } |
| |
| - (NSArray<MTRServerCluster *> *)serverClusters |
| { |
| return [_serverClusters copy]; |
| } |
| |
| - (NSString *)description |
| { |
| return [NSString stringWithFormat:@"<MTRServerEndpoint id %u>", static_cast<EndpointId>(_endpointID.unsignedLongLongValue)]; |
| } |
| |
| @end |